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
@@ -4,24 +4,96 @@ import { bfs, Spiceflow } from './spiceflow.js'
4
4
  import { z } from 'zod'
5
5
 
6
6
  test('middleware with next changes the response', async () => {
7
- const res = await new Spiceflow()
8
- .use(async ({ request }, next) => {
9
- expect(request.method).toBe('GET')
10
- const res = await next()
11
- expect(res).toBeInstanceOf(Response)
12
- if (res) {
13
- res.headers.set('x-test', 'ok')
14
- }
15
- return res
16
- })
17
- .get('/ids/:id', () => 'hi')
18
- .post('/ids/:id', ({ params: { id } }) => id, {
19
- params: z.object({ id: z.string() }),
20
- })
21
- .handle(new Request('http://localhost/ids/xxx', { method: 'GET' }))
22
- expect(res.status).toBe(200)
23
- expect(await res.json()).toEqual('hi')
24
- expect(res.headers.get('x-test')).toBe('ok')
7
+ const res = await new Spiceflow()
8
+ .use(async ({ request }, next) => {
9
+ expect(request.method).toBe('GET')
10
+ const res = await next()
11
+ expect(res).toBeInstanceOf(Response)
12
+ if (res) {
13
+ res.headers.set('x-test', 'ok')
14
+ }
15
+ return res
16
+ })
17
+ .get('/ids/:id', () => 'hi')
18
+ .post('/ids/:id', ({ params: { id } }) => id, {
19
+ params: z.object({ id: z.string() }),
20
+ })
21
+ .handle(new Request('http://localhost/ids/xxx', { method: 'GET' }))
22
+ expect(res.status).toBe(200)
23
+ expect(await res.json()).toEqual('hi')
24
+ expect(res.headers.get('x-test')).toBe('ok')
25
+ })
26
+
27
+ test('middleware with no handlers works', async () => {
28
+ const res = await new Spiceflow()
29
+ .use(async ({ request }, next) => {
30
+ expect(request.method).toBe('GET')
31
+ // expect(res).toBeInstanceOf(Response)
32
+ return new Response('ok')
33
+ })
34
+
35
+ .handle(new Request('http://localhost/ids/xxx', { method: 'GET' }))
36
+ expect(res.status).toBe(200)
37
+ expect(await res.text()).toEqual('ok')
38
+ })
39
+ test('middleware calling next() without returning it works', async () => {
40
+ const res = await new Spiceflow()
41
+ .use(async ({ request }, next) => {
42
+ expect(request.method).toBe('GET')
43
+ // expect(res).toBeInstanceOf(Response)
44
+ await next()
45
+ })
46
+ .use(async ({ request }, next) => {
47
+ expect(request.method).toBe('GET')
48
+ // expect(res).toBeInstanceOf(Response)
49
+ return new Response('"hi"')
50
+ })
51
+ .get('/ids/:id', () => 'not hi')
52
+
53
+ .handle(new Request('http://localhost/ids/xxx', { method: 'GET' }))
54
+ expect(res.status).toBe(200)
55
+ expect(await res.json()).toEqual('hi')
56
+ })
57
+ test('middleware calling next() without returning it, calls handler', async () => {
58
+ const res = await new Spiceflow()
59
+ .use(async ({ request }, next) => {
60
+ expect(request.method).toBe('GET')
61
+ // expect(res).toBeInstanceOf(Response)
62
+ await next()
63
+ })
64
+
65
+ .get('/ids/:id', () => 'hi')
66
+
67
+ .handle(new Request('http://localhost/ids/xxx', { method: 'GET' }))
68
+ expect(res.status).toBe(200)
69
+ expect(await res.json()).toEqual('hi')
70
+ })
71
+
72
+ test('middleware state is not shared between requests', async () => {
73
+ const app = await new Spiceflow()
74
+ .state('x', -1)
75
+ .use(async ({ request, state, query }, next) => {
76
+ state.x = Number(query?.x || -1)
77
+ })
78
+ .get('/get', ({ state }) => {
79
+ return state.x
80
+ })
81
+ async function first() {
82
+ const res = await app.handle(new Request('http://localhost/get?x=1'))
83
+ expect(res.status).toBe(200)
84
+ expect(await res.json()).toEqual(1)
85
+ }
86
+ async function second() {
87
+ const res = await app.handle(new Request('http://localhost/get?x=2'))
88
+ expect(res.status).toBe(200)
89
+ expect(await res.json()).toEqual(2)
90
+ }
91
+ await Promise.all(
92
+ new Array(100).fill(0).map((_, i) => {
93
+ if (i % 2 === 0) return first()
94
+ return second()
95
+ }),
96
+ )
25
97
  })
26
98
 
27
99
  // test('child app that adds state also adds state in the parent app', async () => {
@@ -55,152 +127,152 @@ test('middleware with next changes the response', async () => {
55
127
  // })
56
128
 
57
129
  test('middleware next returns a response even for 404, if there are no routes', async () => {
58
- const res = await new Spiceflow()
59
- .use(async ({ request }, next) => {
60
- expect(request.method).toBe('GET')
61
- const res = await next()
62
- expect(res).toBeInstanceOf(Response)
63
- if (res) {
64
- res.headers.set('x-test', 'ok')
65
- }
66
- return res
67
- })
68
- .handle(new Request('http://localhost/non-existent', { method: 'GET' }))
69
- expect(res.status).toBe(404)
70
- expect(res.headers.get('x-test')).toBe('ok')
71
- expect(await res.text()).toContain('Not Found')
130
+ const res = await new Spiceflow()
131
+ .use(async ({ request }, next) => {
132
+ expect(request.method).toBe('GET')
133
+ const res = await next()
134
+ expect(res).toBeInstanceOf(Response)
135
+ if (res) {
136
+ res.headers.set('x-test', 'ok')
137
+ }
138
+ return res
139
+ })
140
+ .handle(new Request('http://localhost/non-existent', { method: 'GET' }))
141
+ expect(res.status).toBe(404)
142
+ expect(res.headers.get('x-test')).toBe('ok')
143
+ expect(await res.text()).toContain('Not Found')
72
144
  })
73
145
 
74
146
  test('middleware without next runs the next middleware and handler', async () => {
75
- let middlewaresCalled = [] as string[]
76
- const res = await new Spiceflow()
77
- .use(async ({ request }) => {
78
- middlewaresCalled.push('first')
79
- })
80
- .use(async ({ request }, next) => {
81
- middlewaresCalled.push('second')
82
- const res = await next()
83
- if (res instanceof Response) {
84
- res.headers.set('x-test', 'ok')
85
- }
86
- return res
87
- })
88
- .get('/ids/:id', () => 'hi')
89
-
90
- .handle(new Request('http://localhost/ids/xxx', { method: 'GET' }))
91
- expect(res.status).toBe(200)
92
- expect(middlewaresCalled).toEqual(['first', 'second'])
93
- expect(await res.json()).toEqual('hi')
94
- expect(res.headers.get('x-test')).toBe('ok')
147
+ let middlewaresCalled = [] as string[]
148
+ const res = await new Spiceflow()
149
+ .use(async ({ request }) => {
150
+ middlewaresCalled.push('first')
151
+ })
152
+ .use(async ({ request }, next) => {
153
+ middlewaresCalled.push('second')
154
+ const res = await next()
155
+ if (res instanceof Response) {
156
+ res.headers.set('x-test', 'ok')
157
+ }
158
+ return res
159
+ })
160
+ .get('/ids/:id', () => 'hi')
161
+
162
+ .handle(new Request('http://localhost/ids/xxx', { method: 'GET' }))
163
+ expect(res.status).toBe(200)
164
+ expect(middlewaresCalled).toEqual(['first', 'second'])
165
+ expect(await res.json()).toEqual('hi')
166
+ expect(res.headers.get('x-test')).toBe('ok')
95
167
  })
96
168
  test('middleware throws response', async () => {
97
- let middlewaresCalled = [] as string[]
98
- const res = await new Spiceflow()
99
- .use(async ({ request }) => {
100
- middlewaresCalled.push('first')
101
- })
102
- .use(async ({ request }, next) => {
103
- middlewaresCalled.push('second')
104
- throw new Response('ok')
105
- })
106
- .get('/ids/:id', () => 'hi')
107
-
108
- .handle(new Request('http://localhost/ids/xxx', { method: 'GET' }))
109
- expect(res.status).toBe(200)
110
- expect(middlewaresCalled).toEqual(['first', 'second'])
111
- expect(await res.text()).toEqual('ok')
169
+ let middlewaresCalled = [] as string[]
170
+ const res = await new Spiceflow()
171
+ .use(async ({ request }) => {
172
+ middlewaresCalled.push('first')
173
+ })
174
+ .use(async ({ request }, next) => {
175
+ middlewaresCalled.push('second')
176
+ throw new Response('ok')
177
+ })
178
+ .get('/ids/:id', () => 'hi')
179
+
180
+ .handle(new Request('http://localhost/ids/xxx', { method: 'GET' }))
181
+ expect(res.status).toBe(200)
182
+ expect(middlewaresCalled).toEqual(['first', 'second'])
183
+ expect(await res.text()).toEqual('ok')
112
184
  })
113
185
 
114
186
  test('middleware stops other middlewares', async () => {
115
- let middlewaresCalled = [] as string[]
116
- const res = await new Spiceflow()
117
- .use(async ({ request }) => {
118
- middlewaresCalled.push('first')
119
- return new Response('ok')
120
- })
121
- .use(async ({ request }) => {
122
- middlewaresCalled.push('second')
123
- })
124
- .get('/ids/:id', () => 'hi')
125
-
126
- .handle(new Request('http://localhost/ids/xxx', { method: 'GET' }))
127
- expect(res.status).toBe(200)
128
- expect(middlewaresCalled).toEqual(['first'])
129
- expect(await res.text()).toEqual('ok')
187
+ let middlewaresCalled = [] as string[]
188
+ const res = await new Spiceflow()
189
+ .use(async ({ request }) => {
190
+ middlewaresCalled.push('first')
191
+ return new Response('ok')
192
+ })
193
+ .use(async ({ request }) => {
194
+ middlewaresCalled.push('second')
195
+ })
196
+ .get('/ids/:id', () => 'hi')
197
+
198
+ .handle(new Request('http://localhost/ids/xxx', { method: 'GET' }))
199
+ expect(res.status).toBe(200)
200
+ expect(middlewaresCalled).toEqual(['first'])
201
+ expect(await res.text()).toEqual('ok')
130
202
  })
131
203
 
132
204
  test('calling next and then returning a new response works', async () => {
133
- let middlewaresCalled = [] as string[]
134
- const res = await new Spiceflow()
135
- .use(async (ctx, next) => {
136
- middlewaresCalled.push('first')
137
- await next()
138
- return new Response('middleware response')
139
- })
140
- .use(async (ctx, next) => {
141
- middlewaresCalled.push('second')
142
- return next()
143
- })
144
- .get('/ids/:id', () => {
145
- middlewaresCalled.push('handler')
146
- return 'handler response'
147
- })
148
-
149
- .handle(new Request('http://localhost/ids/xxx', { method: 'GET' }))
150
- expect(res.status).toBe(200)
151
- expect(middlewaresCalled).toEqual(['first', 'second', 'handler'])
152
- expect(await res.text()).toEqual('middleware response')
205
+ let middlewaresCalled = [] as string[]
206
+ const res = await new Spiceflow()
207
+ .use(async (ctx, next) => {
208
+ middlewaresCalled.push('first')
209
+ await next()
210
+ return new Response('middleware response')
211
+ })
212
+ .use(async (ctx, next) => {
213
+ middlewaresCalled.push('second')
214
+ return next()
215
+ })
216
+ .get('/ids/:id', () => {
217
+ middlewaresCalled.push('handler')
218
+ return 'handler response'
219
+ })
220
+
221
+ .handle(new Request('http://localhost/ids/xxx', { method: 'GET' }))
222
+ expect(res.status).toBe(200)
223
+ expect(middlewaresCalled).toEqual(['first', 'second', 'handler'])
224
+ expect(await res.text()).toEqual('middleware response')
153
225
  })
154
226
 
155
227
  test('middleware changes handler response body', async () => {
156
- let middlewaresCalled = [] as string[]
157
- const res = await new Spiceflow()
158
- .use(async (ctx, next) => {
159
- middlewaresCalled.push('first')
160
- const response = await next()
161
- if (response) {
162
- const body = await response.text()
163
- return new Response(body.toUpperCase(), response)
164
- }
165
- return response
166
- })
167
- .use(async (ctx, next) => {
168
- middlewaresCalled.push('second')
169
- return next()
170
- })
171
- .get('/test', () => 'hello world')
172
- .handle(new Request('http://localhost/test'))
173
-
174
- expect(res.status).toBe(200)
175
- expect(middlewaresCalled).toEqual(['first', 'second'])
176
- expect(await res.json()).toEqual('HELLO WORLD')
228
+ let middlewaresCalled = [] as string[]
229
+ const res = await new Spiceflow()
230
+ .use(async (ctx, next) => {
231
+ middlewaresCalled.push('first')
232
+ const response = await next()
233
+ if (response) {
234
+ const body = await response.text()
235
+ return new Response(body.toUpperCase(), response)
236
+ }
237
+ return response
238
+ })
239
+ .use(async (ctx, next) => {
240
+ middlewaresCalled.push('second')
241
+ return next()
242
+ })
243
+ .get('/test', () => 'hello world')
244
+ .handle(new Request('http://localhost/test'))
245
+
246
+ expect(res.status).toBe(200)
247
+ expect(middlewaresCalled).toEqual(['first', 'second'])
248
+ expect(await res.json()).toEqual('HELLO WORLD')
177
249
  })
178
250
 
179
251
  test('mutating response returned by next without returning it works', async () => {
180
- let handlerCalledTimes = 0
181
- const res = await new Spiceflow()
182
- .use(async (ctx, next) => {
183
- const response = await next()
184
- if (response) {
185
- response.headers.set('X-Custom-Header', 'Modified')
186
- }
187
- // Not returning the response, letting it pass through
188
- })
189
- .use(async (ctx, next) => {
190
- const response = await next()
191
- if (response) {
192
- response.headers.set('X-Another-Header', 'Added')
193
- }
194
- })
195
- .get('/test', () => {
196
- handlerCalledTimes++
197
- return 'hello world'
198
- })
199
- .handle(new Request('http://localhost/test'))
200
-
201
- expect(res.status).toBe(200)
202
- expect(handlerCalledTimes).toBe(1)
203
- expect(await res.json()).toBe('hello world')
204
- expect(res.headers.get('X-Custom-Header')).toBe('Modified')
205
- expect(res.headers.get('X-Another-Header')).toBe('Added')
252
+ let handlerCalledTimes = 0
253
+ const res = await new Spiceflow()
254
+ .use(async (ctx, next) => {
255
+ const response = await next()
256
+ if (response) {
257
+ response.headers.set('X-Custom-Header', 'Modified')
258
+ }
259
+ // Not returning the response, letting it pass through
260
+ })
261
+ .use(async (ctx, next) => {
262
+ const response = await next()
263
+ if (response) {
264
+ response.headers.set('X-Another-Header', 'Added')
265
+ }
266
+ })
267
+ .get('/test', () => {
268
+ handlerCalledTimes++
269
+ return 'hello world'
270
+ })
271
+ .handle(new Request('http://localhost/test'))
272
+
273
+ expect(res.status).toBe(200)
274
+ expect(handlerCalledTimes).toBe(1)
275
+ expect(await res.json()).toBe('hello world')
276
+ expect(res.headers.get('X-Custom-Header')).toBe('Modified')
277
+ expect(res.headers.get('X-Another-Header')).toBe('Added')
206
278
  })
@@ -4,104 +4,104 @@ import { openapi } from './openapi.js'
4
4
  import { z } from 'zod'
5
5
 
6
6
  test('openapi response', async () => {
7
- const app = await new Spiceflow()
8
- .use(
9
- openapi({
10
- documentation: {
11
- info: {
12
- title: 'Spiceflow Docs',
13
- version: '0.0.0',
14
- },
15
- },
16
- }),
17
- )
18
- .use(
19
- new Spiceflow({ basePath: '/one' }).get(
20
- '/ids/:id',
21
- ({ params }) => {
22
- if (Math.random() < 0.5) {
23
- // TODO add a way to set status
7
+ const app = await new Spiceflow()
8
+ .use(
9
+ openapi({
10
+ documentation: {
11
+ info: {
12
+ title: 'Spiceflow Docs',
13
+ version: '0.0.0',
14
+ },
15
+ },
16
+ }),
17
+ )
18
+ .use(
19
+ new Spiceflow({ basePath: '/one' }).get(
20
+ '/ids/:id',
21
+ ({ params }) => {
22
+ if (Math.random() < 0.5) {
23
+ // TODO add a way to set status
24
24
 
25
- return {
26
- message: 'sdf',
27
- }
28
- }
29
- return params.id
30
- },
31
- {
32
- response: {
33
- 200: z.string(),
34
- 404: z.object({
35
- message: z.string(),
36
- }),
37
- },
38
- },
39
- ),
40
- )
41
- .patch(
42
- '/addBody',
43
- async (c) => {
44
- let body = await c.request.json()
45
- return body
46
- },
47
- {
48
- body: z.object({
49
- name: z.string(),
50
- }),
51
- response: z.object({
52
- name: z.string().optional(),
53
- }),
54
- },
55
- )
56
- .get(
57
- '/queryParams',
58
- async (c) => {
59
- const query = c.query
60
- return query
61
- },
62
- {
63
- query: z.object({
64
- name: z.string(),
65
- }),
66
- response: z.object({
67
- name: z.string().optional(),
68
- }),
69
- },
70
- )
71
- .post(
72
- '/queryParams',
73
- async (c) => {
74
- const query = c.query
75
- return query
76
- },
77
- {
78
- detail: {
79
- description: 'This is a post',
80
- operationId: 'postQueryParamsXXX',
81
- },
82
- body: z.object({
83
- name: z.string(),
84
- }),
85
- response: z.object({
86
- name: z.string().optional(),
87
- }),
88
- },
89
- )
90
- .use(
91
- new Spiceflow({ basePath: '/two' }).get(
92
- '/ids/:id',
93
- ({ params }) => params.id,
94
- {
95
- params: z.object({
96
- id: z.string(),
97
- }),
98
- },
99
- ),
100
- )
101
- const openapiSchema = await app
102
- .handle(new Request('http://localhost/openapi'))
103
- .then((x) => x.json())
104
- expect(openapiSchema).toMatchInlineSnapshot(`
25
+ return {
26
+ message: 'sdf',
27
+ }
28
+ }
29
+ return params.id
30
+ },
31
+ {
32
+ response: {
33
+ 200: z.string(),
34
+ 404: z.object({
35
+ message: z.string(),
36
+ }),
37
+ },
38
+ },
39
+ ),
40
+ )
41
+ .patch(
42
+ '/addBody',
43
+ async (c) => {
44
+ let body = await c.request.json()
45
+ return body
46
+ },
47
+ {
48
+ body: z.object({
49
+ name: z.string(),
50
+ }),
51
+ response: z.object({
52
+ name: z.string().optional(),
53
+ }),
54
+ },
55
+ )
56
+ .get(
57
+ '/queryParams',
58
+ async (c) => {
59
+ const query = c.query
60
+ return query
61
+ },
62
+ {
63
+ query: z.object({
64
+ name: z.string(),
65
+ }),
66
+ response: z.object({
67
+ name: z.string().optional(),
68
+ }),
69
+ },
70
+ )
71
+ .post(
72
+ '/queryParams',
73
+ async (c) => {
74
+ const query = c.query
75
+ return query
76
+ },
77
+ {
78
+ detail: {
79
+ description: 'This is a post',
80
+ operationId: 'postQueryParamsXXX',
81
+ },
82
+ body: z.object({
83
+ name: z.string(),
84
+ }),
85
+ response: z.object({
86
+ name: z.string().optional(),
87
+ }),
88
+ },
89
+ )
90
+ .use(
91
+ new Spiceflow({ basePath: '/two' }).get(
92
+ '/ids/:id',
93
+ ({ params }) => params.id,
94
+ {
95
+ params: z.object({
96
+ id: z.string(),
97
+ }),
98
+ },
99
+ ),
100
+ )
101
+ const openapiSchema = await app
102
+ .handle(new Request('http://localhost/openapi'))
103
+ .then((x) => x.json())
104
+ expect(openapiSchema).toMatchInlineSnapshot(`
105
105
  {
106
106
  "components": {
107
107
  "schemas": {},