spiceflow 1.4.1 → 1.5.0
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.
- package/README.md +1 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +17 -5
- package/dist/client/index.js.map +1 -1
- package/dist/client.test.js +10 -10
- package/dist/client.test.js.map +1 -1
- package/dist/context.d.ts +0 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/cors.d.ts.map +1 -1
- package/dist/cors.js +1 -0
- package/dist/cors.js.map +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js.map +1 -1
- package/dist/mcp.test.js +5 -12
- package/dist/mcp.test.js.map +1 -1
- package/dist/openapi.test.js +88 -0
- package/dist/openapi.test.js.map +1 -1
- package/dist/spiceflow.d.ts +1 -2
- package/dist/spiceflow.d.ts.map +1 -1
- package/dist/spiceflow.js +26 -9
- package/dist/spiceflow.js.map +1 -1
- package/dist/spiceflow.test.js +115 -1
- package/dist/spiceflow.test.js.map +1 -1
- package/dist/stream.test.js +9 -1
- package/dist/stream.test.js.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/zod.test.js +2 -2
- package/dist/zod.test.js.map +1 -1
- package/package.json +10 -2
- package/src/client/index.ts +18 -5
- package/src/client.test.ts +11 -11
- package/src/context.ts +1 -1
- package/src/cors.ts +1 -0
- package/src/mcp.test.ts +5 -12
- package/src/mcp.ts +1 -0
- package/src/openapi.test.ts +88 -0
- package/src/spiceflow.test.ts +120 -2
- package/src/spiceflow.ts +33 -11
- package/src/stream.test.ts +10 -2
- package/src/zod.test.ts +2 -3
package/src/client/index.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
/* eslint-disable no-case-declarations */
|
|
3
3
|
/* eslint-disable prefer-const */
|
|
4
4
|
import type { Spiceflow } from '../spiceflow.js'
|
|
5
|
+
import superjson from 'superjson'
|
|
5
6
|
import { EventSourceParserStream } from 'eventsource-parser/stream'
|
|
6
7
|
|
|
7
8
|
import type { SpiceflowClient } from './types.js'
|
|
@@ -161,17 +162,17 @@ export async function* streamSSEResponse(
|
|
|
161
162
|
const { done, value: event } = await reader.read()
|
|
162
163
|
if (done) break
|
|
163
164
|
if (event?.event === 'error') {
|
|
164
|
-
throw new SpiceflowFetchError(500, event.data)
|
|
165
|
+
throw new SpiceflowFetchError(500, superjsonDeserialize(event.data))
|
|
165
166
|
}
|
|
166
167
|
if (event) {
|
|
167
|
-
yield
|
|
168
|
+
yield tryParsingSSEJson(event.data)
|
|
168
169
|
}
|
|
169
170
|
}
|
|
170
171
|
}
|
|
171
172
|
|
|
172
|
-
function
|
|
173
|
+
function tryParsingSSEJson(data: string): any {
|
|
173
174
|
try {
|
|
174
|
-
return JSON.parse(data)
|
|
175
|
+
return superjsonDeserialize(JSON.parse(data))
|
|
175
176
|
} catch (error) {
|
|
176
177
|
return null
|
|
177
178
|
}
|
|
@@ -407,6 +408,7 @@ const createProxy = (
|
|
|
407
408
|
|
|
408
409
|
case 'application/json':
|
|
409
410
|
data = await response.json()
|
|
411
|
+
data = superjsonDeserialize(data)
|
|
410
412
|
break
|
|
411
413
|
case 'application/octet-stream':
|
|
412
414
|
data = await response.arrayBuffer()
|
|
@@ -432,7 +434,7 @@ const createProxy = (
|
|
|
432
434
|
response.status,
|
|
433
435
|
data || 'Unknown error',
|
|
434
436
|
)
|
|
435
|
-
console.trace({ error, data })
|
|
437
|
+
// console.trace({ error, data })
|
|
436
438
|
data = null
|
|
437
439
|
}
|
|
438
440
|
|
|
@@ -477,3 +479,14 @@ export const createSpiceflowClient = <
|
|
|
477
479
|
|
|
478
480
|
return createProxy('http://e.ly', config, [], domain)
|
|
479
481
|
}
|
|
482
|
+
|
|
483
|
+
function superjsonDeserialize(data: any) {
|
|
484
|
+
if (data?.__superjsonMeta) {
|
|
485
|
+
const { __superjsonMeta, ...rest } = data
|
|
486
|
+
return superjson.deserialize({
|
|
487
|
+
json: rest,
|
|
488
|
+
meta: __superjsonMeta,
|
|
489
|
+
})
|
|
490
|
+
}
|
|
491
|
+
return data
|
|
492
|
+
}
|
package/src/client.test.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
2
|
import { createSpiceflowClient } from './client/index.js'
|
|
3
|
-
import { Spiceflow
|
|
3
|
+
import { Spiceflow } from './spiceflow.js'
|
|
4
|
+
import { Type as t } from '@sinclair/typebox'
|
|
4
5
|
|
|
5
6
|
import { describe, expect, it } from 'vitest'
|
|
6
|
-
|
|
7
7
|
const app = new Spiceflow()
|
|
8
8
|
.get('/', () => 'a')
|
|
9
9
|
.post('/', () => 'a')
|
|
@@ -11,11 +11,11 @@ const app = new Spiceflow()
|
|
|
11
11
|
.get('/true', () => true)
|
|
12
12
|
.get('/false', () => false)
|
|
13
13
|
.post('/array', async ({ request }) => await request.json(), {
|
|
14
|
-
body:
|
|
14
|
+
body: z.array(z.string()),
|
|
15
15
|
})
|
|
16
16
|
.post('/mirror', async ({ request }) => await request.json())
|
|
17
17
|
.post('/body', async ({ request }) => await request.text(), {
|
|
18
|
-
body:
|
|
18
|
+
body: z.string(),
|
|
19
19
|
})
|
|
20
20
|
.post('/zodAny', async ({ request }) => await request.json(), {
|
|
21
21
|
body: z.object({ body: z.array(z.any()) }),
|
|
@@ -25,9 +25,9 @@ const app = new Spiceflow()
|
|
|
25
25
|
return { body: body || null }
|
|
26
26
|
})
|
|
27
27
|
.post('/deep/nested/mirror', async ({ request }) => await request.json(), {
|
|
28
|
-
body:
|
|
29
|
-
username:
|
|
30
|
-
password:
|
|
28
|
+
body: z.object({
|
|
29
|
+
username: z.string(),
|
|
30
|
+
password: z.string(),
|
|
31
31
|
}),
|
|
32
32
|
})
|
|
33
33
|
.get('/throws', () => {
|
|
@@ -62,8 +62,8 @@ const app = new Spiceflow()
|
|
|
62
62
|
},
|
|
63
63
|
{
|
|
64
64
|
response: {
|
|
65
|
-
200:
|
|
66
|
-
x:
|
|
65
|
+
200: z.object({
|
|
66
|
+
x: z.string(),
|
|
67
67
|
}),
|
|
68
68
|
},
|
|
69
69
|
},
|
|
@@ -78,8 +78,8 @@ const app = new Spiceflow()
|
|
|
78
78
|
.get('/dateObject', () => ({ date: new Date() }))
|
|
79
79
|
.get('/redirect', ({ redirect }) => redirect('http://localhost:8083/true'))
|
|
80
80
|
.post('/redirect', ({ redirect }) => redirect('http://localhost:8083/true'), {
|
|
81
|
-
body:
|
|
82
|
-
username:
|
|
81
|
+
body: z.object({
|
|
82
|
+
username: z.string(),
|
|
83
83
|
}),
|
|
84
84
|
})
|
|
85
85
|
// .get('/formdata', () => ({
|
package/src/context.ts
CHANGED
package/src/cors.ts
CHANGED
package/src/mcp.test.ts
CHANGED
|
@@ -195,12 +195,12 @@ describe('MCP Plugin', () => {
|
|
|
195
195
|
{
|
|
196
196
|
"mimeType": "application/json",
|
|
197
197
|
"name": "GET /goSomething",
|
|
198
|
-
"uri": "http://localhost:
|
|
198
|
+
"uri": "http://localhost:4000/goSomething",
|
|
199
199
|
},
|
|
200
200
|
{
|
|
201
201
|
"mimeType": "application/json",
|
|
202
202
|
"name": "GET /users",
|
|
203
|
-
"uri": "http://localhost:
|
|
203
|
+
"uri": "http://localhost:4000/users",
|
|
204
204
|
},
|
|
205
205
|
]
|
|
206
206
|
`)
|
|
@@ -220,22 +220,15 @@ describe('MCP Plugin', () => {
|
|
|
220
220
|
[
|
|
221
221
|
{
|
|
222
222
|
"mimeType": "application/json",
|
|
223
|
-
"text": "{
|
|
224
|
-
|
|
225
|
-
{
|
|
226
|
-
"id": 1,
|
|
227
|
-
"name": "John"
|
|
228
|
-
}
|
|
229
|
-
]
|
|
230
|
-
}",
|
|
231
|
-
"uri": "http://localhost:3000/users",
|
|
223
|
+
"text": "{"users":[{"id":1,"name":"John"}]}",
|
|
224
|
+
"uri": "http://localhost:4000/users",
|
|
232
225
|
},
|
|
233
226
|
]
|
|
234
227
|
`)
|
|
235
228
|
})
|
|
236
229
|
})
|
|
237
230
|
|
|
238
|
-
async function getAvailablePort(startPort =
|
|
231
|
+
async function getAvailablePort(startPort = 4000, maxRetries = 10) {
|
|
239
232
|
const net = await import('net')
|
|
240
233
|
|
|
241
234
|
return await new Promise<number>((resolve, reject) => {
|
package/src/mcp.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
import { zodToJsonSchema } from 'zod-to-json-schema'
|
|
10
10
|
import { SSEServerTransportSpiceflow } from './mcp-transport.js'
|
|
11
11
|
import { isZodSchema, Spiceflow } from './spiceflow.js'
|
|
12
|
+
import { OpenAPIV3 } from 'openapi-types'
|
|
12
13
|
|
|
13
14
|
function getJsonSchema(schema: any) {
|
|
14
15
|
if (!schema) return undefined
|
package/src/openapi.test.ts
CHANGED
|
@@ -159,6 +159,73 @@ test('openapi response', async () => {
|
|
|
159
159
|
.then((x) => x.json())
|
|
160
160
|
expect(openapiSchema).toMatchInlineSnapshot(`
|
|
161
161
|
{
|
|
162
|
+
"__superjsonMeta": {
|
|
163
|
+
"values": {
|
|
164
|
+
"paths./addBody.patch.responses.200.content.application/json.schema.items": [
|
|
165
|
+
"undefined",
|
|
166
|
+
],
|
|
167
|
+
"paths./addBody.patch.responses.200.content.application/json.schema.patternProperties": [
|
|
168
|
+
"undefined",
|
|
169
|
+
],
|
|
170
|
+
"paths./addBody.patch.responses.200.content.application/json.schema.required": [
|
|
171
|
+
"undefined",
|
|
172
|
+
],
|
|
173
|
+
"paths./formWithSchemaForm.post.responses.200.content.multipart/form-data.schema.items": [
|
|
174
|
+
"undefined",
|
|
175
|
+
],
|
|
176
|
+
"paths./formWithSchemaForm.post.responses.200.content.multipart/form-data.schema.patternProperties": [
|
|
177
|
+
"undefined",
|
|
178
|
+
],
|
|
179
|
+
"paths./one/ids/{id}.get.parameters.0.description": [
|
|
180
|
+
"undefined",
|
|
181
|
+
],
|
|
182
|
+
"paths./one/ids/{id}.get.parameters.0.examples": [
|
|
183
|
+
"undefined",
|
|
184
|
+
],
|
|
185
|
+
"paths./one/ids/{id}.get.responses.404.content.application/json.schema.items": [
|
|
186
|
+
"undefined",
|
|
187
|
+
],
|
|
188
|
+
"paths./one/ids/{id}.get.responses.404.content.application/json.schema.patternProperties": [
|
|
189
|
+
"undefined",
|
|
190
|
+
],
|
|
191
|
+
"paths./queryParams.get.parameters.0.description": [
|
|
192
|
+
"undefined",
|
|
193
|
+
],
|
|
194
|
+
"paths./queryParams.get.parameters.0.examples": [
|
|
195
|
+
"undefined",
|
|
196
|
+
],
|
|
197
|
+
"paths./queryParams.get.responses.200.content.application/json.schema.items": [
|
|
198
|
+
"undefined",
|
|
199
|
+
],
|
|
200
|
+
"paths./queryParams.get.responses.200.content.application/json.schema.patternProperties": [
|
|
201
|
+
"undefined",
|
|
202
|
+
],
|
|
203
|
+
"paths./queryParams.get.responses.200.content.application/json.schema.required": [
|
|
204
|
+
"undefined",
|
|
205
|
+
],
|
|
206
|
+
"paths./queryParams.post.responses.200.content.application/json.schema.items": [
|
|
207
|
+
"undefined",
|
|
208
|
+
],
|
|
209
|
+
"paths./queryParams.post.responses.200.content.application/json.schema.patternProperties": [
|
|
210
|
+
"undefined",
|
|
211
|
+
],
|
|
212
|
+
"paths./queryParams.post.responses.200.content.application/json.schema.required": [
|
|
213
|
+
"undefined",
|
|
214
|
+
],
|
|
215
|
+
"paths./streamWithSchema.get.responses.200.content.application/json.schema.items": [
|
|
216
|
+
"undefined",
|
|
217
|
+
],
|
|
218
|
+
"paths./streamWithSchema.get.responses.200.content.application/json.schema.patternProperties": [
|
|
219
|
+
"undefined",
|
|
220
|
+
],
|
|
221
|
+
"paths./two/ids/{id}.get.parameters.0.description": [
|
|
222
|
+
"undefined",
|
|
223
|
+
],
|
|
224
|
+
"paths./two/ids/{id}.get.parameters.0.examples": [
|
|
225
|
+
"undefined",
|
|
226
|
+
],
|
|
227
|
+
},
|
|
228
|
+
},
|
|
162
229
|
"components": {
|
|
163
230
|
"schemas": {},
|
|
164
231
|
},
|
|
@@ -196,11 +263,14 @@ test('openapi response', async () => {
|
|
|
196
263
|
"content": {
|
|
197
264
|
"application/json": {
|
|
198
265
|
"schema": {
|
|
266
|
+
"items": null,
|
|
267
|
+
"patternProperties": null,
|
|
199
268
|
"properties": {
|
|
200
269
|
"name": {
|
|
201
270
|
"type": "string",
|
|
202
271
|
},
|
|
203
272
|
},
|
|
273
|
+
"required": null,
|
|
204
274
|
"type": "object",
|
|
205
275
|
},
|
|
206
276
|
},
|
|
@@ -226,6 +296,8 @@ test('openapi response', async () => {
|
|
|
226
296
|
"content": {
|
|
227
297
|
"multipart/form-data": {
|
|
228
298
|
"schema": {
|
|
299
|
+
"items": null,
|
|
300
|
+
"patternProperties": null,
|
|
229
301
|
"properties": {
|
|
230
302
|
"age": {
|
|
231
303
|
"type": "string",
|
|
@@ -259,6 +331,8 @@ test('openapi response', async () => {
|
|
|
259
331
|
"get": {
|
|
260
332
|
"parameters": [
|
|
261
333
|
{
|
|
334
|
+
"description": null,
|
|
335
|
+
"examples": null,
|
|
262
336
|
"in": "path",
|
|
263
337
|
"name": "id",
|
|
264
338
|
"required": true,
|
|
@@ -282,6 +356,8 @@ test('openapi response', async () => {
|
|
|
282
356
|
"content": {
|
|
283
357
|
"application/json": {
|
|
284
358
|
"schema": {
|
|
359
|
+
"items": null,
|
|
360
|
+
"patternProperties": null,
|
|
285
361
|
"properties": {
|
|
286
362
|
"message": {
|
|
287
363
|
"type": "string",
|
|
@@ -333,6 +409,8 @@ test('openapi response', async () => {
|
|
|
333
409
|
"get": {
|
|
334
410
|
"parameters": [
|
|
335
411
|
{
|
|
412
|
+
"description": null,
|
|
413
|
+
"examples": null,
|
|
336
414
|
"in": "query",
|
|
337
415
|
"name": "name",
|
|
338
416
|
"required": true,
|
|
@@ -346,11 +424,14 @@ test('openapi response', async () => {
|
|
|
346
424
|
"content": {
|
|
347
425
|
"application/json": {
|
|
348
426
|
"schema": {
|
|
427
|
+
"items": null,
|
|
428
|
+
"patternProperties": null,
|
|
349
429
|
"properties": {
|
|
350
430
|
"name": {
|
|
351
431
|
"type": "string",
|
|
352
432
|
},
|
|
353
433
|
},
|
|
434
|
+
"required": null,
|
|
354
435
|
"type": "object",
|
|
355
436
|
},
|
|
356
437
|
},
|
|
@@ -395,11 +476,14 @@ test('openapi response', async () => {
|
|
|
395
476
|
"content": {
|
|
396
477
|
"application/json": {
|
|
397
478
|
"schema": {
|
|
479
|
+
"items": null,
|
|
480
|
+
"patternProperties": null,
|
|
398
481
|
"properties": {
|
|
399
482
|
"name": {
|
|
400
483
|
"type": "string",
|
|
401
484
|
},
|
|
402
485
|
},
|
|
486
|
+
"required": null,
|
|
403
487
|
"type": "object",
|
|
404
488
|
},
|
|
405
489
|
},
|
|
@@ -451,6 +535,8 @@ test('openapi response', async () => {
|
|
|
451
535
|
"content": {
|
|
452
536
|
"application/json": {
|
|
453
537
|
"schema": {
|
|
538
|
+
"items": null,
|
|
539
|
+
"patternProperties": null,
|
|
454
540
|
"properties": {
|
|
455
541
|
"count": {
|
|
456
542
|
"type": "number",
|
|
@@ -483,6 +569,8 @@ test('openapi response', async () => {
|
|
|
483
569
|
"get": {
|
|
484
570
|
"parameters": [
|
|
485
571
|
{
|
|
572
|
+
"description": null,
|
|
573
|
+
"examples": null,
|
|
486
574
|
"in": "path",
|
|
487
575
|
"name": "id",
|
|
488
576
|
"required": true,
|
package/src/spiceflow.test.ts
CHANGED
|
@@ -11,6 +11,122 @@ test('works', async () => {
|
|
|
11
11
|
expect(res.status).toBe(200)
|
|
12
12
|
expect(await res.json()).toEqual('hi')
|
|
13
13
|
})
|
|
14
|
+
test('can encode superjson types', async () => {
|
|
15
|
+
const app = new Spiceflow().post('/superjson', () => {
|
|
16
|
+
const item = {
|
|
17
|
+
date: new Date('2025-01-20T18:01:57.852Z'),
|
|
18
|
+
map: new Map([['a', 1]]),
|
|
19
|
+
set: new Set([1, 2, 3]),
|
|
20
|
+
bigint: BigInt(123),
|
|
21
|
+
}
|
|
22
|
+
return { items: Array(2).fill(item) }
|
|
23
|
+
})
|
|
24
|
+
const res = await app.handle(
|
|
25
|
+
new Request('http://localhost/superjson', { method: 'POST' }),
|
|
26
|
+
)
|
|
27
|
+
expect(res.status).toBe(200)
|
|
28
|
+
const client = createSpiceflowClient(app)
|
|
29
|
+
expect(await client.superjson.post().then((x) => x.data))
|
|
30
|
+
.toMatchInlineSnapshot(`
|
|
31
|
+
{
|
|
32
|
+
"items": [
|
|
33
|
+
{
|
|
34
|
+
"bigint": 123n,
|
|
35
|
+
"date": 2025-01-20T18:01:57.852Z,
|
|
36
|
+
"map": Map {
|
|
37
|
+
"a" => 1,
|
|
38
|
+
},
|
|
39
|
+
"set": Set {
|
|
40
|
+
1,
|
|
41
|
+
2,
|
|
42
|
+
3,
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"bigint": 123n,
|
|
47
|
+
"date": 2025-01-20T18:01:57.852Z,
|
|
48
|
+
"map": Map {
|
|
49
|
+
"a" => 1,
|
|
50
|
+
},
|
|
51
|
+
"set": Set {
|
|
52
|
+
1,
|
|
53
|
+
2,
|
|
54
|
+
3,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
}
|
|
59
|
+
`)
|
|
60
|
+
expect(await res.json()).toMatchInlineSnapshot(`
|
|
61
|
+
{
|
|
62
|
+
"__superjsonMeta": {
|
|
63
|
+
"referentialEqualities": {
|
|
64
|
+
"items.0": [
|
|
65
|
+
"items.1",
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
"values": {
|
|
69
|
+
"items.0.bigint": [
|
|
70
|
+
"bigint",
|
|
71
|
+
],
|
|
72
|
+
"items.0.date": [
|
|
73
|
+
"Date",
|
|
74
|
+
],
|
|
75
|
+
"items.0.map": [
|
|
76
|
+
"map",
|
|
77
|
+
],
|
|
78
|
+
"items.0.set": [
|
|
79
|
+
"set",
|
|
80
|
+
],
|
|
81
|
+
"items.1.bigint": [
|
|
82
|
+
"bigint",
|
|
83
|
+
],
|
|
84
|
+
"items.1.date": [
|
|
85
|
+
"Date",
|
|
86
|
+
],
|
|
87
|
+
"items.1.map": [
|
|
88
|
+
"map",
|
|
89
|
+
],
|
|
90
|
+
"items.1.set": [
|
|
91
|
+
"set",
|
|
92
|
+
],
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
"items": [
|
|
96
|
+
{
|
|
97
|
+
"bigint": "123",
|
|
98
|
+
"date": "2025-01-20T18:01:57.852Z",
|
|
99
|
+
"map": [
|
|
100
|
+
[
|
|
101
|
+
"a",
|
|
102
|
+
1,
|
|
103
|
+
],
|
|
104
|
+
],
|
|
105
|
+
"set": [
|
|
106
|
+
1,
|
|
107
|
+
2,
|
|
108
|
+
3,
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"bigint": "123",
|
|
113
|
+
"date": "2025-01-20T18:01:57.852Z",
|
|
114
|
+
"map": [
|
|
115
|
+
[
|
|
116
|
+
"a",
|
|
117
|
+
1,
|
|
118
|
+
],
|
|
119
|
+
],
|
|
120
|
+
"set": [
|
|
121
|
+
1,
|
|
122
|
+
2,
|
|
123
|
+
3,
|
|
124
|
+
],
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
}
|
|
128
|
+
`)
|
|
129
|
+
})
|
|
14
130
|
test('dynamic route', async () => {
|
|
15
131
|
const res = await new Spiceflow()
|
|
16
132
|
.post('/ids/:id', () => 'hi')
|
|
@@ -311,7 +427,7 @@ test('validate body works, request fails', async () => {
|
|
|
311
427
|
)
|
|
312
428
|
expect(res.status).toBe(422)
|
|
313
429
|
expect(await res.text()).toMatchInlineSnapshot(
|
|
314
|
-
`"{"message":"data must have required property 'requiredField'"}"`,
|
|
430
|
+
`"{"code":"VALIDATION","status":422,"message":"data must have required property 'requiredField'"}"`,
|
|
315
431
|
)
|
|
316
432
|
})
|
|
317
433
|
|
|
@@ -578,7 +694,9 @@ test('errors inside basPath works', async () => {
|
|
|
578
694
|
expect(onErrorTriggered).toEqual(['root', 'two', 'nested'])
|
|
579
695
|
expect(onReqTriggered).toEqual(['root', 'two', 'nested'])
|
|
580
696
|
expect(res.status).toBe(500)
|
|
581
|
-
expect(await res.text()).toMatchInlineSnapshot(
|
|
697
|
+
expect(await res.text()).toMatchInlineSnapshot(
|
|
698
|
+
`"{"message":"error message"}"`,
|
|
699
|
+
)
|
|
582
700
|
// expect(await res.json()).toEqual('nested'))
|
|
583
701
|
}
|
|
584
702
|
})
|
package/src/spiceflow.ts
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import { Type } from '@sinclair/typebox'
|
|
2
|
-
|
|
3
|
-
export { Type as t }
|
|
4
|
-
|
|
5
1
|
import addFormats from 'ajv-formats'
|
|
2
|
+
import superjson from 'superjson'
|
|
6
3
|
import {
|
|
7
4
|
ComposeSpiceflowResponse,
|
|
8
5
|
ContentType,
|
|
@@ -35,6 +32,7 @@ import { zodToJsonSchema } from 'zod-to-json-schema'
|
|
|
35
32
|
import { Context, MiddlewareContext } from './context.js'
|
|
36
33
|
import { isProduction, ValidationError } from './error.js'
|
|
37
34
|
import { isAsyncIterable, isResponse, redirect } from './utils.js'
|
|
35
|
+
import { json } from 'stream/consumers'
|
|
38
36
|
|
|
39
37
|
const ajv = (addFormats.default || addFormats)(
|
|
40
38
|
new (Ajv.default || Ajv)({ useDefaults: true }),
|
|
@@ -777,7 +775,10 @@ export class Spiceflow<
|
|
|
777
775
|
|
|
778
776
|
let status = err?.status ?? 500
|
|
779
777
|
res ||= new Response(
|
|
780
|
-
|
|
778
|
+
superjsonSerialize({
|
|
779
|
+
...err,
|
|
780
|
+
message: err?.message || 'Internal Server Error',
|
|
781
|
+
}),
|
|
781
782
|
{
|
|
782
783
|
status,
|
|
783
784
|
headers: {
|
|
@@ -922,7 +923,7 @@ export class Spiceflow<
|
|
|
922
923
|
error(error) {
|
|
923
924
|
console.error(error)
|
|
924
925
|
return new Response(
|
|
925
|
-
|
|
926
|
+
superjsonSerialize({ message: 'Internal Server Error' }),
|
|
926
927
|
{
|
|
927
928
|
status: 500,
|
|
928
929
|
},
|
|
@@ -1002,7 +1003,7 @@ export class Spiceflow<
|
|
|
1002
1003
|
} catch (error) {
|
|
1003
1004
|
console.error('Error handling request:', error)
|
|
1004
1005
|
res.statusCode = 500
|
|
1005
|
-
res.end(
|
|
1006
|
+
res.end(superjsonSerialize({ message: 'Internal Server Error' }))
|
|
1006
1007
|
}
|
|
1007
1008
|
})
|
|
1008
1009
|
|
|
@@ -1054,6 +1055,7 @@ export class Spiceflow<
|
|
|
1054
1055
|
// 1. return() allows for cleanup in finally blocks
|
|
1055
1056
|
// 2. throw() would trigger error handling which isn't needed for normal aborts
|
|
1056
1057
|
// 3. return() is the more graceful way to stop iteration
|
|
1058
|
+
|
|
1057
1059
|
if ('return' in generator) {
|
|
1058
1060
|
try {
|
|
1059
1061
|
await generator.return(undefined)
|
|
@@ -1072,7 +1074,9 @@ export class Spiceflow<
|
|
|
1072
1074
|
if (init?.value !== undefined && init?.value !== null)
|
|
1073
1075
|
controller.enqueue(
|
|
1074
1076
|
Buffer.from(
|
|
1075
|
-
'event: message\ndata: ' +
|
|
1077
|
+
'event: message\ndata: ' +
|
|
1078
|
+
superjsonSerialize(init.value, false) +
|
|
1079
|
+
'\n\n',
|
|
1076
1080
|
),
|
|
1077
1081
|
)
|
|
1078
1082
|
|
|
@@ -1083,7 +1087,9 @@ export class Spiceflow<
|
|
|
1083
1087
|
|
|
1084
1088
|
controller.enqueue(
|
|
1085
1089
|
Buffer.from(
|
|
1086
|
-
'event: message\ndata: ' +
|
|
1090
|
+
'event: message\ndata: ' +
|
|
1091
|
+
superjsonSerialize(chunk, false) +
|
|
1092
|
+
'\n\n',
|
|
1087
1093
|
),
|
|
1088
1094
|
)
|
|
1089
1095
|
}
|
|
@@ -1096,7 +1102,13 @@ export class Spiceflow<
|
|
|
1096
1102
|
controller.enqueue(
|
|
1097
1103
|
Buffer.from(
|
|
1098
1104
|
'event: error\ndata: ' +
|
|
1099
|
-
|
|
1105
|
+
superjsonSerialize(
|
|
1106
|
+
{
|
|
1107
|
+
...error,
|
|
1108
|
+
message: error.message || error.name || 'Error',
|
|
1109
|
+
},
|
|
1110
|
+
false,
|
|
1111
|
+
) +
|
|
1100
1112
|
'\n\n',
|
|
1101
1113
|
),
|
|
1102
1114
|
)
|
|
@@ -1292,13 +1304,23 @@ export async function turnHandlerResultIntoResponse(
|
|
|
1292
1304
|
})
|
|
1293
1305
|
}
|
|
1294
1306
|
}
|
|
1295
|
-
|
|
1307
|
+
|
|
1308
|
+
return new Response(superjsonSerialize(result), {
|
|
1296
1309
|
headers: {
|
|
1297
1310
|
'content-type': 'application/json',
|
|
1298
1311
|
},
|
|
1299
1312
|
})
|
|
1300
1313
|
}
|
|
1301
1314
|
|
|
1315
|
+
function superjsonSerialize(value: any, indent = false) {
|
|
1316
|
+
// return JSON.stringify(value)
|
|
1317
|
+
const { json, meta } = superjson.serialize(value)
|
|
1318
|
+
if (json && meta) {
|
|
1319
|
+
json['__superjsonMeta'] = meta
|
|
1320
|
+
}
|
|
1321
|
+
return JSON.stringify(json ?? null, null, indent ? 2 : undefined)
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1302
1324
|
export type AnySpiceflow = Spiceflow<any, any, any, any, any, any>
|
|
1303
1325
|
|
|
1304
1326
|
export function isZodSchema(value: unknown): value is ZodType {
|
package/src/stream.test.ts
CHANGED
|
@@ -69,8 +69,16 @@ describe('Stream', () => {
|
|
|
69
69
|
|
|
70
70
|
const response = await app.handle(req('/')).then((x) => x.text())
|
|
71
71
|
|
|
72
|
-
expect(response).
|
|
73
|
-
|
|
72
|
+
expect(response).toMatchInlineSnapshot(
|
|
73
|
+
`
|
|
74
|
+
"event: message
|
|
75
|
+
data: "a"
|
|
76
|
+
|
|
77
|
+
event: error
|
|
78
|
+
data: {"message":"an error"}
|
|
79
|
+
|
|
80
|
+
"
|
|
81
|
+
`,
|
|
74
82
|
)
|
|
75
83
|
})
|
|
76
84
|
|
package/src/zod.test.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { expect, test } from 'vitest'
|
|
2
|
+
import { z } from 'zod'
|
|
3
3
|
import { Spiceflow } from './spiceflow.js'
|
|
4
4
|
import { req } from './utils.js'
|
|
5
|
-
import { z } from 'zod'
|
|
6
5
|
|
|
7
6
|
test('body is parsed as json', async () => {
|
|
8
7
|
let name = ''
|