Skip to content

Commit 92faaf9

Browse files
authored
feat(client, server)!: update event-source errors and decode behavior (#182)
* fix(client, server)!: throw an error if event source ends incompletely * fix tests * feat(server): not serialize ErrorEvent * ignore UnknownEvent * improve event-source compatibility
1 parent ca29a36 commit 92faaf9

File tree

15 files changed

+246
-358
lines changed

15 files changed

+246
-358
lines changed

packages/client/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@ export * from './event-iterator'
77
export * from './event-iterator-state'
88
export * from './types'
99
export * from './utils'
10+
11+
export { ErrorEvent } from '@orpc/standard-server'

packages/client/src/openapi/serializer.test.ts

Lines changed: 0 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -160,54 +160,6 @@ describe('openAPISerializer', () => {
160160
expect(serialize).toHaveBeenCalledWith(expect.objectContaining({ code: 'BAD_GATEWAY', data: { order: 3 } }))
161161
serialize.mockClear()
162162
})
163-
164-
it('on error with ErrorEvent', async () => {
165-
const date = new Date()
166-
const error = withEventMeta(new ErrorEvent({ data: { order: 3 } }), { id: '123456' })
167-
168-
const serialized = openapiSerializer.serialize((async function* () {
169-
yield 1
170-
yield withEventMeta({ order: 2, date }, { retry: 1000 })
171-
throw error
172-
})()) as any
173-
174-
await expect(serialized.next()).resolves.toSatisfy(({ value, done }) => {
175-
expect(done).toBe(false)
176-
expect(value).toBe(serialize.mock.results[0]!.value[0])
177-
expect(getEventMeta(value)).toEqual(undefined)
178-
179-
return true
180-
})
181-
182-
expect(serialize).toHaveBeenCalledOnce()
183-
expect(serialize).toHaveBeenCalledWith(1)
184-
serialize.mockClear()
185-
186-
await expect(serialized.next()).resolves.toSatisfy(({ value, done }) => {
187-
expect(done).toBe(false)
188-
expect(value).toEqual(serialize.mock.results[0]!.value[0])
189-
expect(getEventMeta(value)).toEqual({ retry: 1000 })
190-
191-
return true
192-
})
193-
194-
expect(serialize).toHaveBeenCalledOnce()
195-
expect(serialize).toHaveBeenCalledWith({ order: 2, date })
196-
serialize.mockClear()
197-
198-
await expect(serialized.next()).rejects.toSatisfy((e: any) => {
199-
expect(e).toBeInstanceOf(ErrorEvent)
200-
expect(e.data).toEqual(serialize.mock.results[0]!.value[0])
201-
expect(e.cause).toBe(error)
202-
expect(getEventMeta(e)).toEqual({ id: '123456' })
203-
204-
return true
205-
})
206-
207-
expect(serialize).toHaveBeenCalledOnce()
208-
expect(serialize).toHaveBeenCalledWith({ order: 3 })
209-
serialize.mockClear()
210-
})
211163
})
212164
})
213165

packages/client/src/openapi/serializer.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,6 @@ export class OpenAPISerializer {
1717
return mapEventIterator(data, {
1818
value: async value => this.#serialize(value, false),
1919
error: async (e) => {
20-
if (e instanceof ErrorEvent) {
21-
return new ErrorEvent({
22-
data: this.#serialize(e.data, false),
23-
cause: e,
24-
})
25-
}
26-
2720
return new ErrorEvent({
2821
data: this.#serialize(toORPCError(e).toJSON(), false),
2922
cause: e,

packages/client/src/rpc/serializer.test.ts

Lines changed: 15 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -142,43 +142,6 @@ describe('rpcSerializer: event-source iterator', async () => {
142142
})
143143
})
144144

145-
it('on error with ErrorEvent', async () => {
146-
const error = withEventMeta(new ErrorEvent({ data: { order: 3 } }), { id: '123456' })
147-
148-
const iterator = (async function* () {
149-
yield 1
150-
yield withEventMeta({ order: 2, date }, { retry: 1000 })
151-
throw error
152-
})()
153-
154-
const deserialized = serializeAndDeserialize(iterator) as any
155-
156-
expect(deserialized).toSatisfy(isAsyncIteratorObject)
157-
await expect(deserialized.next()).resolves.toSatisfy(({ value, done }) => {
158-
expect(done).toBe(false)
159-
expect(value).toEqual(1)
160-
expect(getEventMeta(value)).toEqual(undefined)
161-
162-
return true
163-
})
164-
165-
await expect(deserialized.next()).resolves.toSatisfy(({ value, done }) => {
166-
expect(done).toBe(false)
167-
expect(value).toEqual({ order: 2, date })
168-
expect(getEventMeta(value)).toEqual({ retry: 1000 })
169-
170-
return true
171-
})
172-
173-
await expect(deserialized.next()).rejects.toSatisfy((e: any) => {
174-
expect(e).toEqual(error)
175-
expect(e).toBeInstanceOf(ErrorEvent)
176-
expect(e.cause).toBeInstanceOf(ErrorEvent)
177-
178-
return true
179-
})
180-
})
181-
182145
it('on error with unknown error when deserialize', async () => {
183146
const error = withEventMeta(new Error('UNKNOWN'), { id: '123456' })
184147

@@ -213,4 +176,19 @@ describe('rpcSerializer: event-source iterator', async () => {
213176
return true
214177
})
215178
})
179+
180+
it('deserialize an invalid-ORPCError', async () => {
181+
const iterator = serializer.deserialize((async function* () {
182+
throw new ErrorEvent({
183+
data: { json: { value: 1234 } },
184+
})
185+
})()) as any
186+
187+
await expect(iterator.next()).rejects.toSatisfy((e: any) => {
188+
expect(e).toBeInstanceOf(ErrorEvent)
189+
expect(e.data).toEqual({ value: 1234 })
190+
191+
return true
192+
})
193+
})
216194
})

packages/client/src/rpc/serializer.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,6 @@ export class RPCSerializer {
1414
return mapEventIterator(data, {
1515
value: async (value: unknown) => this.#serialize(value, false),
1616
error: async (e) => {
17-
if (e instanceof ErrorEvent) {
18-
return new ErrorEvent({
19-
data: this.#serialize(e.data, false),
20-
cause: e,
21-
})
22-
}
23-
2417
return new ErrorEvent({
2518
data: this.#serialize(toORPCError(e).toJSON(), false),
2619
cause: e,

packages/standard-server-fetch/src/event-source.test.ts

Lines changed: 7 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { isAsyncIteratorObject } from '@orpc/shared'
2-
import { ErrorEvent, getEventMeta, UnknownEvent, withEventMeta } from '@orpc/standard-server'
2+
import { ErrorEvent, getEventMeta, withEventMeta } from '@orpc/standard-server'
33
import { toEventIterator, toEventStream } from './event-source'
44

55
describe('toEventIterator', () => {
@@ -8,7 +8,7 @@ describe('toEventIterator', () => {
88
async pull(controller) {
99
controller.enqueue('event: message\ndata: {"order": 1}\nid: id-1\nretry: 10000\n\n')
1010
controller.enqueue('event: message\ndata: {"order": 2}\nid: id-2\n\n')
11-
controller.enqueue('event: done\ndata: {"order": 3}\nid: id-3\nretry: 30000')
11+
controller.enqueue('event: done\ndata: {"order": 3}\nid: id-3\nretry: 30000\n\n')
1212
controller.close()
1313
},
1414
}).pipeThrough(new TextEncoderStream())
@@ -39,8 +39,6 @@ describe('toEventIterator', () => {
3939

4040
return true
4141
})
42-
43-
await expect(stream.getReader().closed).resolves.toBe(undefined)
4442
})
4543

4644
it('without dont event', async () => {
@@ -87,7 +85,7 @@ describe('toEventIterator', () => {
8785
async pull(controller) {
8886
controller.enqueue('event: message\ndata: {"order": 1}\nid: id-1\nretry: 10000\n\n')
8987
controller.enqueue('event: message\ndata: {"order": 2}\nid: id-2\n\n')
90-
controller.enqueue('event: error\ndata: {"order": 3}\nid: id-3\nretry: 30000')
88+
controller.enqueue('event: error\ndata: {"order": 3}\nid: id-3\nretry: 30000\n\n')
9189
controller.close()
9290
},
9391
}).pipeThrough(new TextEncoderStream())
@@ -122,46 +120,6 @@ describe('toEventIterator', () => {
122120
await expect(stream.getReader().closed).resolves.toBe(undefined)
123121
})
124122

125-
it('with unknown event', async () => {
126-
const stream = new ReadableStream<string>({
127-
async pull(controller) {
128-
controller.enqueue('event: message\ndata: {"order": 1}\nid: id-1\nretry: 10000\n\n')
129-
controller.enqueue('event: message\ndata: {"order": 2}\nid: id-2\n\n')
130-
controller.enqueue('event: unknown\ndata: {"order": 3}\nid: id-3\nretry: 30000')
131-
controller.close()
132-
},
133-
}).pipeThrough(new TextEncoderStream())
134-
135-
const generator = toEventIterator(stream)
136-
expect(generator).toSatisfy(isAsyncIteratorObject)
137-
138-
expect(await generator.next()).toSatisfy(({ done, value }) => {
139-
expect(done).toEqual(false)
140-
expect(value).toEqual({ order: 1 })
141-
expect(getEventMeta(value)).toEqual(expect.objectContaining({ id: 'id-1', retry: 10000 }))
142-
143-
return true
144-
})
145-
146-
expect(await generator.next()).toSatisfy(({ done, value }) => {
147-
expect(done).toEqual(false)
148-
expect(value).toEqual({ order: 2 })
149-
expect(getEventMeta(value)).toEqual(expect.objectContaining({ id: 'id-2', retry: undefined }))
150-
151-
return true
152-
})
153-
154-
await expect(generator.next()).rejects.toSatisfy((error: any) => {
155-
expect(error).toBeInstanceOf(UnknownEvent)
156-
expect(error.data).toEqual({ order: 3 })
157-
expect(getEventMeta(error)).toEqual(expect.objectContaining({ id: 'id-3', retry: 30000 }))
158-
159-
return true
160-
})
161-
162-
await expect(stream.getReader().closed).resolves.toBe(undefined)
163-
})
164-
165123
it('when .return() before finish reading', async () => {
166124
const stream = new ReadableStream<string>({
167125
async pull(controller) {
@@ -205,7 +163,7 @@ describe('toEventStream', () => {
205163

206164
expect((await reader.read()).value).toEqual('event: message\nid: id-1\ndata: {"order":1}\n\n')
207165
expect((await reader.read()).value).toEqual('event: message\nretry: 20000\ndata: {"order":2}\n\n')
208-
expect((await reader.read()).value).toEqual('event: message\ndata: \n\n')
166+
expect((await reader.read()).value).toEqual('event: message\n\n')
209167
expect((await reader.read()).value).toEqual('event: done\nretry: 40000\nid: id-4\ndata: {"order":4}\n\n')
210168
expect((await reader.read()).done).toEqual(true)
211169
})
@@ -224,8 +182,8 @@ describe('toEventStream', () => {
224182

225183
expect((await reader.read()).value).toEqual('event: message\nid: id-1\ndata: {"order":1}\n\n')
226184
expect((await reader.read()).value).toEqual('event: message\nretry: 20000\ndata: {"order":2}\n\n')
227-
expect((await reader.read()).value).toEqual('event: message\ndata: \n\n')
228-
expect((await reader.read()).value).toEqual('event: error\nretry: 40000\nid: id-4\ndata: \n\n')
185+
expect((await reader.read()).value).toEqual('event: message\n\n')
186+
expect((await reader.read()).value).toEqual('event: error\nretry: 40000\nid: id-4\n\n')
229187
expect((await reader.read()).done).toEqual(true)
230188
})
231189

@@ -243,7 +201,7 @@ describe('toEventStream', () => {
243201

244202
expect((await reader.read()).value).toEqual('event: message\nid: id-1\ndata: {"order":1}\n\n')
245203
expect((await reader.read()).value).toEqual('event: message\nretry: 20000\ndata: {"order":2}\n\n')
246-
expect((await reader.read()).value).toEqual('event: message\ndata: \n\n')
204+
expect((await reader.read()).value).toEqual('event: message\n\n')
247205
expect((await reader.read()).value).toEqual('event: error\nretry: 40000\nid: id-4\ndata: {"order":4}\n\n')
248206
expect((await reader.read()).done).toEqual(true)
249207
})

packages/standard-server-fetch/src/event-source.ts

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import {
44
ErrorEvent,
55
EventDecoderStream,
66
getEventMeta,
7-
UnknownEvent,
87
withEventMeta,
98
} from '@orpc/standard-server'
109

@@ -57,17 +56,6 @@ export function toEventIterator(
5756

5857
return done
5958
}
60-
61-
default: {
62-
let error = new UnknownEvent({
63-
message: `Unknown event: ${value.event}`,
64-
data: parseEmptyableJSON(value.data),
65-
})
66-
67-
error = withEventMeta(error, value)
68-
69-
throw error
70-
}
7159
}
7260
}
7361
}

packages/standard-server-node/src/event-source.test.ts

Lines changed: 7 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Readable } from 'node:stream'
22
import { isAsyncIteratorObject } from '@orpc/shared'
3-
import { ErrorEvent, getEventMeta, UnknownEvent, withEventMeta } from '@orpc/standard-server'
3+
import { ErrorEvent, getEventMeta, withEventMeta } from '@orpc/standard-server'
44
import { toEventIterator, toEventStream } from './event-source'
55

66
describe('toEventIterator', () => {
@@ -9,7 +9,7 @@ describe('toEventIterator', () => {
99
async pull(controller) {
1010
controller.enqueue('event: message\ndata: {"order": 1}\nid: id-1\nretry: 10000\n\n')
1111
controller.enqueue('event: message\ndata: {"order": 2}\nid: id-2\n\n')
12-
controller.enqueue('event: done\ndata: {"order": 3}\nid: id-3\nretry: 30000')
12+
controller.enqueue('event: done\ndata: {"order": 3}\nid: id-3\nretry: 30000\n\n')
1313
controller.close()
1414
},
1515
}).pipeThrough(new TextEncoderStream()))
@@ -40,8 +40,6 @@ describe('toEventIterator', () => {
4040

4141
return true
4242
})
43-
44-
await expect(Readable.toWeb(stream).getReader().closed).resolves.toBe(undefined)
4543
})
4644

4745
it('without dont event', async () => {
@@ -88,7 +86,7 @@ describe('toEventIterator', () => {
8886
async pull(controller) {
8987
controller.enqueue('event: message\ndata: {"order": 1}\nid: id-1\nretry: 10000\n\n')
9088
controller.enqueue('event: message\ndata: {"order": 2}\nid: id-2\n\n')
91-
controller.enqueue('event: error\ndata: {"order": 3}\nid: id-3\nretry: 30000')
89+
controller.enqueue('event: error\ndata: {"order": 3}\nid: id-3\nretry: 30000\n\n')
9290
controller.close()
9391
},
9492
}).pipeThrough(new TextEncoderStream()))
@@ -123,46 +121,6 @@ describe('toEventIterator', () => {
123121
await expect(Readable.toWeb(stream).getReader().closed).resolves.toBe(undefined)
124122
})
125123

126-
it('with unknown event', async () => {
127-
const stream = Readable.fromWeb(new ReadableStream<string>({
128-
async pull(controller) {
129-
controller.enqueue('event: message\ndata: {"order": 1}\nid: id-1\nretry: 10000\n\n')
130-
controller.enqueue('event: message\ndata: {"order": 2}\nid: id-2\n\n')
131-
controller.enqueue('event: unknown\ndata: {"order": 3}\nid: id-3\nretry: 30000')
132-
controller.close()
133-
},
134-
}).pipeThrough(new TextEncoderStream()))
135-
136-
const generator = toEventIterator(stream)
137-
expect(generator).toSatisfy(isAsyncIteratorObject)
138-
139-
expect(await generator.next()).toSatisfy(({ done, value }) => {
140-
expect(done).toEqual(false)
141-
expect(value).toEqual({ order: 1 })
142-
expect(getEventMeta(value)).toEqual(expect.objectContaining({ id: 'id-1', retry: 10000 }))
143-
144-
return true
145-
})
146-
147-
expect(await generator.next()).toSatisfy(({ done, value }) => {
148-
expect(done).toEqual(false)
149-
expect(value).toEqual({ order: 2 })
150-
expect(getEventMeta(value)).toEqual(expect.objectContaining({ id: 'id-2', retry: undefined }))
151-
152-
return true
153-
})
154-
155-
await expect(generator.next()).rejects.toSatisfy((error: any) => {
156-
expect(error).toBeInstanceOf(UnknownEvent)
157-
expect(error.data).toEqual({ order: 3 })
158-
expect(getEventMeta(error)).toEqual(expect.objectContaining({ id: 'id-3', retry: 30000 }))
159-
160-
return true
161-
})
162-
163-
await expect(Readable.toWeb(stream).getReader().closed).resolves.toBe(undefined)
164-
})
165-
166124
it('when .return() before finish reading', async () => {
167125
const stream = Readable.fromWeb(new ReadableStream<string>({
168126
async pull(controller) {
@@ -205,7 +163,7 @@ describe('toEventStream', () => {
205163

206164
expect((await reader.read()).value).toEqual('event: message\nid: id-1\ndata: {"order":1}\n\n')
207165
expect((await reader.read()).value).toEqual('event: message\nretry: 20000\ndata: {"order":2}\n\n')
208-
expect((await reader.read()).value).toEqual('event: message\ndata: \n\n')
166+
expect((await reader.read()).value).toEqual('event: message\n\n')
209167
expect((await reader.read()).value).toEqual('event: done\nretry: 40000\nid: id-4\ndata: {"order":4}\n\n')
210168
expect((await reader.read()).done).toEqual(true)
211169
})
@@ -224,8 +182,8 @@ describe('toEventStream', () => {
224182

225183
expect((await reader.read()).value).toEqual('event: message\nid: id-1\ndata: {"order":1}\n\n')
226184
expect((await reader.read()).value).toEqual('event: message\nretry: 20000\ndata: {"order":2}\n\n')
227-
expect((await reader.read()).value).toEqual('event: message\ndata: \n\n')
228-
expect((await reader.read()).value).toEqual('event: error\nretry: 40000\nid: id-4\ndata: \n\n')
185+
expect((await reader.read()).value).toEqual('event: message\n\n')
186+
expect((await reader.read()).value).toEqual('event: error\nretry: 40000\nid: id-4\n\n')
229187
expect((await reader.read()).done).toEqual(true)
230188
})
231189

@@ -243,7 +201,7 @@ describe('toEventStream', () => {
243201

244202
expect((await reader.read()).value).toEqual('event: message\nid: id-1\ndata: {"order":1}\n\n')
245203
expect((await reader.read()).value).toEqual('event: message\nretry: 20000\ndata: {"order":2}\n\n')
246-
expect((await reader.read()).value).toEqual('event: message\ndata: \n\n')
204+
expect((await reader.read()).value).toEqual('event: message\n\n')
247205
expect((await reader.read()).value).toEqual('event: error\nretry: 40000\nid: id-4\ndata: {"order":4}\n\n')
248206
expect((await reader.read()).done).toEqual(true)
249207
})

0 commit comments

Comments
 (0)