spiceflow 1.4.0 → 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/errors.js +4 -1
- package/dist/client/errors.js.map +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +73 -51
- package/dist/client/index.js.map +1 -1
- package/dist/client.test.js +32 -73
- 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 +9 -6
- package/dist/cors.js.map +1 -1
- package/dist/error.js +8 -18
- package/dist/error.js.map +1 -1
- package/dist/mcp-transport.js +12 -8
- package/dist/mcp-transport.js.map +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +10 -12
- package/dist/mcp.js.map +1 -1
- package/dist/mcp.test.js +5 -12
- package/dist/mcp.test.js.map +1 -1
- package/dist/middleware.test.js +2 -2
- package/dist/middleware.test.js.map +1 -1
- package/dist/openapi.js +117 -77
- package/dist/openapi.js.map +1 -1
- package/dist/openapi.test.js +98 -27
- 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 +99 -104
- package/dist/spiceflow.js.map +1 -1
- package/dist/spiceflow.test.js +127 -62
- package/dist/spiceflow.test.js.map +1 -1
- package/dist/static-node.js +6 -3
- package/dist/static-node.js.map +1 -1
- package/dist/static.js +3 -5
- package/dist/static.js.map +1 -1
- package/dist/stream.test.js +45 -66
- package/dist/stream.test.js.map +1 -1
- package/dist/types.test.js +10 -45
- package/dist/types.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 +29 -7
- package/src/client.test.ts +20 -14
- 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/middleware.test.ts +1 -1
- 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/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 = ''
|