spiceflow 1.1.7 → 1.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +177 -92
  2. package/dist/benchmark.benchmark.js.map +1 -1
  3. package/dist/client/errors.d.ts.map +1 -1
  4. package/dist/client/errors.js.map +1 -1
  5. package/dist/client/index.d.ts.map +1 -1
  6. package/dist/client/index.js +8 -12
  7. package/dist/client/index.js.map +1 -1
  8. package/dist/client/types.d.ts.map +1 -1
  9. package/dist/client/utils.js.map +1 -1
  10. package/dist/client/ws.d.ts.map +1 -1
  11. package/dist/client/ws.js +1 -3
  12. package/dist/client/ws.js.map +1 -1
  13. package/dist/client.test.js.map +1 -1
  14. package/dist/context.d.ts.map +1 -1
  15. package/dist/cors.d.ts.map +1 -1
  16. package/dist/cors.js.map +1 -1
  17. package/dist/cors.test.js.map +1 -1
  18. package/dist/error.d.ts.map +1 -1
  19. package/dist/error.js.map +1 -1
  20. package/dist/middleware.test.js +65 -0
  21. package/dist/middleware.test.js.map +1 -1
  22. package/dist/openapi.d.ts.map +1 -1
  23. package/dist/openapi.js +1 -1
  24. package/dist/openapi.js.map +1 -1
  25. package/dist/openapi.test.js.map +1 -1
  26. package/dist/spiceflow.d.ts.map +1 -1
  27. package/dist/spiceflow.js +12 -5
  28. package/dist/spiceflow.js.map +1 -1
  29. package/dist/spiceflow.test.js.map +1 -1
  30. package/dist/stream.test.js +6 -6
  31. package/dist/stream.test.js.map +1 -1
  32. package/dist/types.d.ts +1 -1
  33. package/dist/types.d.ts.map +1 -1
  34. package/dist/types.js.map +1 -1
  35. package/dist/types.test.js +56 -6
  36. package/dist/types.test.js.map +1 -1
  37. package/dist/utils.d.ts.map +1 -1
  38. package/dist/utils.js.map +1 -1
  39. package/dist/zod.test.js.map +1 -1
  40. package/package.json +1 -1
  41. package/src/benchmark.benchmark.ts +8 -8
  42. package/src/client/errors.ts +17 -17
  43. package/src/client/index.ts +437 -469
  44. package/src/client/types.ts +168 -191
  45. package/src/client/utils.ts +5 -5
  46. package/src/client/ws.ts +87 -89
  47. package/src/client.test.ts +176 -183
  48. package/src/context.ts +82 -82
  49. package/src/cors.test.ts +38 -38
  50. package/src/cors.ts +87 -92
  51. package/src/error.ts +13 -13
  52. package/src/middleware.test.ts +221 -149
  53. package/src/openapi.test.ts +97 -97
  54. package/src/openapi.ts +365 -365
  55. package/src/spiceflow.test.ts +461 -467
  56. package/src/spiceflow.ts +1117 -1157
  57. package/src/stream.test.ts +310 -310
  58. package/src/types.test.ts +59 -39
  59. package/src/types.ts +698 -701
  60. package/src/utils.ts +79 -79
  61. package/src/zod.test.ts +64 -64
@@ -7,336 +7,336 @@ import { Spiceflow } from './spiceflow.js'
7
7
  import { req, sleep } from './utils.js'
8
8
 
9
9
  function textEventStream(items: string[]) {
10
- return items
11
- .map((item) => `event: message\ndata: ${JSON.stringify(item)}\n\n`)
12
- .join('')
10
+ return items
11
+ .map((item) => `event: message\ndata: ${JSON.stringify(item)}\n\n`)
12
+ .join('')
13
13
  }
14
14
 
15
15
  function parseTextEventStreamItem(item: string) {
16
- const data = item.split('data: ')[1].split('\n')[0]
17
- return JSON.parse(data)
16
+ const data = item.split('data: ')[1].split('\n')[0]
17
+ return JSON.parse(data)
18
18
  }
19
19
 
20
20
  describe('Stream', () => {
21
- it('handle stream', async () => {
22
- const expected = ['a', 'b', 'c']
21
+ it('handle stream', async () => {
22
+ const expected = ['a', 'b', 'c']
23
23
 
24
- const app = new Spiceflow().get('/', async function* () {
25
- yield 'a'
26
- await sleep(10)
24
+ const app = new Spiceflow().get('/', async function* () {
25
+ yield 'a'
26
+ await sleep(10)
27
27
 
28
- yield 'b'
29
- await sleep(10)
28
+ yield 'b'
29
+ await sleep(10)
30
30
 
31
- yield 'c'
32
- })
31
+ yield 'c'
32
+ })
33
33
 
34
- const response = await app
35
- .handle(req('/'))
36
- .then((x) => x.body)
37
- .then((x) => {
38
- if (!x) return
34
+ const response = await app
35
+ .handle(req('/'))
36
+ .then((x) => x.body)
37
+ .then((x) => {
38
+ if (!x) return
39
39
 
40
- const reader = x?.getReader()
40
+ const reader = x?.getReader()
41
41
 
42
- let acc = ''
43
- const { promise, resolve } = Promise.withResolvers()
44
-
45
- reader.read().then(function pump({ done, value }): unknown {
46
- if (done) return resolve(acc)
47
-
48
- expect(parseTextEventStreamItem(value.toString())).toBe(
49
- expected.shift()!
50
- )
42
+ let acc = ''
43
+ const { promise, resolve } = Promise.withResolvers()
44
+
45
+ reader.read().then(function pump({ done, value }): unknown {
46
+ if (done) return resolve(acc)
47
+
48
+ expect(parseTextEventStreamItem(value.toString())).toBe(
49
+ expected.shift()!,
50
+ )
51
51
 
52
- acc += value.toString()
53
- return reader.read().then(pump)
54
- })
52
+ acc += value.toString()
53
+ return reader.read().then(pump)
54
+ })
55
55
 
56
- return promise
57
- })
56
+ return promise
57
+ })
58
58
 
59
- expect(expected).toHaveLength(0)
60
- expect(response).toBe(textEventStream(['a', 'b', 'c']))
61
- })
62
- it('handle errors after yield', async () => {
63
- const app = new Spiceflow().get('/', async function* () {
64
- yield 'a'
65
- await sleep(10)
59
+ expect(expected).toHaveLength(0)
60
+ expect(response).toBe(textEventStream(['a', 'b', 'c']))
61
+ })
62
+ it('handle errors after yield', async () => {
63
+ const app = new Spiceflow().get('/', async function* () {
64
+ yield 'a'
65
+ await sleep(10)
66
66
 
67
- throw new Error('an error')
68
- })
67
+ throw new Error('an error')
68
+ })
69
69
 
70
- const response = await app.handle(req('/')).then((x) => x.text())
70
+ const response = await app.handle(req('/')).then((x) => x.text())
71
71
 
72
- expect(response).toBe(
73
- 'event: message\ndata: "a"\n\nevent: error\ndata: "an error"\n\n'
74
- )
75
- })
72
+ expect(response).toBe(
73
+ 'event: message\ndata: "a"\n\nevent: error\ndata: "an error"\n\n',
74
+ )
75
+ })
76
76
 
77
- it('handle errors before yield when aot is false', async () => {
78
- const app = new Spiceflow()
79
- .onError(({ error }) => {
80
- return new Response(error.message)
81
- })
82
- .get('/', async function* () {
83
- throw new Error('an error xxxx')
84
- })
77
+ it('handle errors before yield when aot is false', async () => {
78
+ const app = new Spiceflow()
79
+ .onError(({ error }) => {
80
+ return new Response(error.message)
81
+ })
82
+ .get('/', async function* () {
83
+ throw new Error('an error xxxx')
84
+ })
85
85
 
86
- const response = await app.handle(req('/')).then((x) => x.text())
86
+ const response = await app.handle(req('/')).then((x) => x.text())
87
87
 
88
- expect(response).toContain('an error')
89
- })
88
+ expect(response).toContain('an error')
89
+ })
90
90
 
91
- it.todo('handle errors before yield when aot is true', async () => {
92
- const app = new Spiceflow()
93
- .onError(({ error }) => {
94
- return new Response(error.message)
95
- })
96
- .get('/', async function* () {
97
- throw new Error('an error')
98
- })
99
-
100
- const response = await app.handle(req('/')).then((x) => x.text())
101
-
102
- expect(response).toContain('an error')
103
- })
104
-
105
- it.todo('handle errors before yield with onError', async () => {
106
- const expected = 'error expected'
107
- const app = new Spiceflow()
108
- .onError(({}) => {
109
- return new Response(expected)
110
- })
111
- .get('/', async function* () {
112
- throw new Error('an error')
113
- })
114
-
115
- const response = await app.handle(req('/')).then((x) => x.text())
116
-
117
- expect(response).toBe(expected)
118
- })
119
-
120
- it('stop stream on canceled request', async () => {
121
- const expected = ['a', 'b']
122
-
123
- const app = new Spiceflow().get('/', async function* () {
124
- yield 'a'
125
- await sleep(10)
126
-
127
- yield 'b'
128
- await sleep(10)
129
-
130
- yield 'c'
131
- })
132
-
133
- const controller = new AbortController()
134
-
135
- setTimeout(() => {
136
- controller.abort()
137
- }, 15)
138
-
139
- const response = await app
140
- .handle(
141
- new Request('http://e.ly', {
142
- signal: controller.signal
143
- })
144
- )
145
- .then((x) => x.body)
146
- .then((x) => {
147
- if (!x) return
148
-
149
- const reader = x?.getReader()
150
-
151
- let acc = ''
152
- const { promise, resolve } = Promise.withResolvers()
153
-
154
- reader.read().then(function pump({ done, value }): unknown {
155
- if (done) {
156
- return resolve(acc)
157
- }
158
-
159
- expect(parseTextEventStreamItem(value.toString())).toBe(
160
- expected.shift()!
161
- )
162
-
163
- acc += value.toString()
164
- return reader.read().then(pump)
165
- })
166
-
167
- return promise
168
- })
169
-
170
- expect(expected).toHaveLength(0)
171
- expect(response).toBe(textEventStream(['a', 'b']))
172
- })
173
-
174
- // it('mutate set before yield is called', async () => {
175
- // const expected = ['a', 'b', 'c']
176
-
177
- // const app = new Spiceflow().get('/', function* () {
178
- // set.headers['access-control-allow-origin'] = 'http://saltyaom.com'
179
-
180
- // yield 'a'
181
- // yield 'b'
182
- // yield 'c'
183
- // })
184
-
185
- // const response = await app.handle(req('/')).then((x) => x.headers)
186
-
187
- // expect(response.get('access-control-allow-origin')).toBe(
188
- // 'http://saltyaom.com'
189
- // )
190
- // })
191
- it('handle stream with objects', async () => {
192
- const objects = [
193
- { message: 'hello' },
194
- { response: 'world' },
195
- { data: [1, 2, 3] },
196
- { result: [4, 5, 6] }
197
- ]
198
- const app = new Spiceflow().get('/', async function* ({}) {
199
- for (const obj of objects) {
200
- yield obj
201
- }
202
- })
203
-
204
- const body = await app.handle(req('/')).then((x) => x.body)
205
-
206
- let events = [] as any[]
207
- const parser = createParser((event) => {
208
- events.push(event)
209
- })
210
- const { promise, resolve } = Promise.withResolvers<void>()
211
- const reader = body?.getReader()!
212
-
213
- reader.read().then(function pump({ done, value }): unknown {
214
- if (done) {
215
- return resolve()
216
- }
217
- const text = value.toString()
218
- parser.feed(text)
219
- return reader.read().then(pump)
220
- })
221
- await promise
222
-
223
- expect(events.map((x) => x.data)).toEqual(
224
- objects.map((x) => JSON.stringify(x))
225
- )
226
- })
227
-
228
- // it('mutate set before yield is called', async () => {
229
- // const expected = ['a', 'b', 'c']
230
-
231
- // const app = new Spiceflow().get('/', function* () {
232
- // set.headers['access-control-allow-origin'] = 'http://saltyaom.com'
233
-
234
- // yield 'a'
235
- // yield 'b'
236
- // yield 'c'
237
- // })
238
-
239
- // const response = await app.handle(req('/')).then((x) => x.headers)
240
-
241
- // expect(response.get('access-control-allow-origin')).toBe(
242
- // 'http://saltyaom.com'
243
- // )
244
- // })
245
-
246
- // it('async mutate set before yield is called', async () => {
247
- // const expected = ['a', 'b', 'c']
248
-
249
- // const app = new Spiceflow().get('/', async function* () {
250
- // set.headers['access-control-allow-origin'] = 'http://saltyaom.com'
251
-
252
- // yield 'a'
253
- // yield 'b'
254
- // yield 'c'
255
- // })
256
-
257
- // const response = await app.handle(req('/')).then((x) => x.headers)
258
-
259
- // expect(response.get('access-control-allow-origin')).toBe(
260
- // 'http://saltyaom.com'
261
- // )
262
- // })
263
-
264
- it('return value if not yield', async () => {
265
- const app = new Spiceflow()
266
- .get('/', function* () {
267
- return 'hello'
268
- })
269
- .get('/json', function* () {
270
- return { hello: 'world' }
271
- })
272
-
273
- const response = await Promise.all([
274
- app.handle(req('/')),
275
- app.handle(req('/json'))
276
- ])
277
-
278
- expect(await response[0].text()).toBe('"hello"')
279
- expect(await response[1].json()).toEqual({
280
- hello: 'world'
281
- })
282
- })
283
-
284
- it('return async value if not yield', async () => {
285
- const app = new Spiceflow()
286
- .get('/', function* () {
287
- return 'hello'
288
- })
289
- .get('/json', function* () {
290
- return { hello: 'world' }
291
- })
292
-
293
- const response = await Promise.all([
294
- app.handle(req('/')),
295
- app.handle(req('/json'))
296
- ])
297
-
298
- expect(await response[0].text()).toBe('"hello"')
299
- expect(await response[1].json()).toEqual({
300
- hello: 'world'
301
- })
302
- })
303
-
304
- it('handle object and array', async () => {
305
- const expected = [{ a: 'b' }, ['a'], ['a', 1, { a: 'b' }]]
306
- const expectedResponse = JSON.stringify([...expected])
307
- let i = 0
308
-
309
- const app = new Spiceflow().get('/', async function* () {
310
- yield expected[0]
311
- await sleep(10)
312
-
313
- yield expected[1]
314
- await sleep(10)
315
-
316
- yield expected[2]
317
- })
318
-
319
- const response = await app
320
- .handle(req('/'))
321
- .then((x) => x.body)
322
- .then((x) => {
323
- if (!x) return
324
-
325
- const reader = x?.getReader()
326
-
327
- const { promise, resolve } = Promise.withResolvers<void>()
328
-
329
- reader.read().then(function pump({ done, value }): unknown {
330
- if (done) return resolve()
331
-
332
- expect(parseTextEventStreamItem(value.toString())).toEqual(
333
- expected[i++]
334
- )
335
-
336
- return reader.read().then(pump)
337
- })
338
-
339
- return promise
340
- })
341
- })
91
+ it.todo('handle errors before yield when aot is true', async () => {
92
+ const app = new Spiceflow()
93
+ .onError(({ error }) => {
94
+ return new Response(error.message)
95
+ })
96
+ .get('/', async function* () {
97
+ throw new Error('an error')
98
+ })
99
+
100
+ const response = await app.handle(req('/')).then((x) => x.text())
101
+
102
+ expect(response).toContain('an error')
103
+ })
104
+
105
+ it.todo('handle errors before yield with onError', async () => {
106
+ const expected = 'error expected'
107
+ const app = new Spiceflow()
108
+ .onError(({}) => {
109
+ return new Response(expected)
110
+ })
111
+ .get('/', async function* () {
112
+ throw new Error('an error')
113
+ })
114
+
115
+ const response = await app.handle(req('/')).then((x) => x.text())
116
+
117
+ expect(response).toBe(expected)
118
+ })
119
+
120
+ it('stop stream on canceled request', async () => {
121
+ const expected = ['a', 'b']
122
+
123
+ const app = new Spiceflow().get('/', async function* () {
124
+ yield 'a'
125
+ await sleep(10)
126
+
127
+ yield 'b'
128
+ await sleep(10)
129
+
130
+ yield 'c'
131
+ })
132
+
133
+ const controller = new AbortController()
134
+
135
+ setTimeout(() => {
136
+ controller.abort()
137
+ }, 15)
138
+
139
+ const response = await app
140
+ .handle(
141
+ new Request('http://e.ly', {
142
+ signal: controller.signal,
143
+ }),
144
+ )
145
+ .then((x) => x.body)
146
+ .then((x) => {
147
+ if (!x) return
148
+
149
+ const reader = x?.getReader()
150
+
151
+ let acc = ''
152
+ const { promise, resolve } = Promise.withResolvers()
153
+
154
+ reader.read().then(function pump({ done, value }): unknown {
155
+ if (done) {
156
+ return resolve(acc)
157
+ }
158
+
159
+ expect(parseTextEventStreamItem(value.toString())).toBe(
160
+ expected.shift()!,
161
+ )
162
+
163
+ acc += value.toString()
164
+ return reader.read().then(pump)
165
+ })
166
+
167
+ return promise
168
+ })
169
+
170
+ expect(expected).toHaveLength(0)
171
+ expect(response).toBe(textEventStream(['a', 'b']))
172
+ })
173
+
174
+ // it('mutate set before yield is called', async () => {
175
+ // const expected = ['a', 'b', 'c']
176
+
177
+ // const app = new Spiceflow().get('/', function* () {
178
+ // set.headers['access-control-allow-origin'] = 'http://saltyaom.com'
179
+
180
+ // yield 'a'
181
+ // yield 'b'
182
+ // yield 'c'
183
+ // })
184
+
185
+ // const response = await app.handle(req('/')).then((x) => x.headers)
186
+
187
+ // expect(response.get('access-control-allow-origin')).toBe(
188
+ // 'http://saltyaom.com'
189
+ // )
190
+ // })
191
+ it('handle stream with objects', async () => {
192
+ const objects = [
193
+ { message: 'hello' },
194
+ { response: 'world' },
195
+ { data: [1, 2, 3] },
196
+ { result: [4, 5, 6] },
197
+ ]
198
+ const app = new Spiceflow().get('/', async function* ({}) {
199
+ for (const obj of objects) {
200
+ yield obj
201
+ }
202
+ })
203
+
204
+ const body = await app.handle(req('/')).then((x) => x.body)
205
+
206
+ let events = [] as any[]
207
+ const parser = createParser((event) => {
208
+ events.push(event)
209
+ })
210
+ const { promise, resolve } = Promise.withResolvers<void>()
211
+ const reader = body?.getReader()!
212
+
213
+ reader.read().then(function pump({ done, value }): unknown {
214
+ if (done) {
215
+ return resolve()
216
+ }
217
+ const text = value.toString()
218
+ parser.feed(text)
219
+ return reader.read().then(pump)
220
+ })
221
+ await promise
222
+
223
+ expect(events.map((x) => x.data)).toEqual(
224
+ objects.map((x) => JSON.stringify(x)),
225
+ )
226
+ })
227
+
228
+ // it('mutate set before yield is called', async () => {
229
+ // const expected = ['a', 'b', 'c']
230
+
231
+ // const app = new Spiceflow().get('/', function* () {
232
+ // set.headers['access-control-allow-origin'] = 'http://saltyaom.com'
233
+
234
+ // yield 'a'
235
+ // yield 'b'
236
+ // yield 'c'
237
+ // })
238
+
239
+ // const response = await app.handle(req('/')).then((x) => x.headers)
240
+
241
+ // expect(response.get('access-control-allow-origin')).toBe(
242
+ // 'http://saltyaom.com'
243
+ // )
244
+ // })
245
+
246
+ // it('async mutate set before yield is called', async () => {
247
+ // const expected = ['a', 'b', 'c']
248
+
249
+ // const app = new Spiceflow().get('/', async function* () {
250
+ // set.headers['access-control-allow-origin'] = 'http://saltyaom.com'
251
+
252
+ // yield 'a'
253
+ // yield 'b'
254
+ // yield 'c'
255
+ // })
256
+
257
+ // const response = await app.handle(req('/')).then((x) => x.headers)
258
+
259
+ // expect(response.get('access-control-allow-origin')).toBe(
260
+ // 'http://saltyaom.com'
261
+ // )
262
+ // })
263
+
264
+ it('return value if not yield', async () => {
265
+ const app = new Spiceflow()
266
+ .get('/', function* () {
267
+ return 'hello'
268
+ })
269
+ .get('/json', function* () {
270
+ return { hello: 'world' }
271
+ })
272
+
273
+ const response = await Promise.all([
274
+ app.handle(req('/')),
275
+ app.handle(req('/json')),
276
+ ])
277
+
278
+ expect(await response[0].text()).toBe('"hello"')
279
+ expect(await response[1].json()).toEqual({
280
+ hello: 'world',
281
+ })
282
+ })
283
+
284
+ it('return async value if not yield', async () => {
285
+ const app = new Spiceflow()
286
+ .get('/', function* () {
287
+ return 'hello'
288
+ })
289
+ .get('/json', function* () {
290
+ return { hello: 'world' }
291
+ })
292
+
293
+ const response = await Promise.all([
294
+ app.handle(req('/')),
295
+ app.handle(req('/json')),
296
+ ])
297
+
298
+ expect(await response[0].text()).toBe('"hello"')
299
+ expect(await response[1].json()).toEqual({
300
+ hello: 'world',
301
+ })
302
+ })
303
+
304
+ it('handle object and array', async () => {
305
+ const expected = [{ a: 'b' }, ['a'], ['a', 1, { a: 'b' }]]
306
+ const expectedResponse = JSON.stringify([...expected])
307
+ let i = 0
308
+
309
+ const app = new Spiceflow().get('/', async function* () {
310
+ yield expected[0]
311
+ await sleep(10)
312
+
313
+ yield expected[1]
314
+ await sleep(10)
315
+
316
+ yield expected[2]
317
+ })
318
+
319
+ const response = await app
320
+ .handle(req('/'))
321
+ .then((x) => x.body)
322
+ .then((x) => {
323
+ if (!x) return
324
+
325
+ const reader = x?.getReader()
326
+
327
+ const { promise, resolve } = Promise.withResolvers<void>()
328
+
329
+ reader.read().then(function pump({ done, value }): unknown {
330
+ if (done) return resolve()
331
+
332
+ expect(parseTextEventStreamItem(value.toString())).toEqual(
333
+ expected[i++],
334
+ )
335
+
336
+ return reader.read().then(pump)
337
+ })
338
+
339
+ return promise
340
+ })
341
+ })
342
342
  })