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.
Files changed (58) hide show
  1. package/README.md +1 -0
  2. package/dist/client/errors.js +4 -1
  3. package/dist/client/errors.js.map +1 -1
  4. package/dist/client/index.d.ts.map +1 -1
  5. package/dist/client/index.js +73 -51
  6. package/dist/client/index.js.map +1 -1
  7. package/dist/client.test.js +32 -73
  8. package/dist/client.test.js.map +1 -1
  9. package/dist/context.d.ts +0 -1
  10. package/dist/context.d.ts.map +1 -1
  11. package/dist/cors.d.ts.map +1 -1
  12. package/dist/cors.js +9 -6
  13. package/dist/cors.js.map +1 -1
  14. package/dist/error.js +8 -18
  15. package/dist/error.js.map +1 -1
  16. package/dist/mcp-transport.js +12 -8
  17. package/dist/mcp-transport.js.map +1 -1
  18. package/dist/mcp.d.ts.map +1 -1
  19. package/dist/mcp.js +10 -12
  20. package/dist/mcp.js.map +1 -1
  21. package/dist/mcp.test.js +5 -12
  22. package/dist/mcp.test.js.map +1 -1
  23. package/dist/middleware.test.js +2 -2
  24. package/dist/middleware.test.js.map +1 -1
  25. package/dist/openapi.js +117 -77
  26. package/dist/openapi.js.map +1 -1
  27. package/dist/openapi.test.js +98 -27
  28. package/dist/openapi.test.js.map +1 -1
  29. package/dist/spiceflow.d.ts +1 -2
  30. package/dist/spiceflow.d.ts.map +1 -1
  31. package/dist/spiceflow.js +99 -104
  32. package/dist/spiceflow.js.map +1 -1
  33. package/dist/spiceflow.test.js +127 -62
  34. package/dist/spiceflow.test.js.map +1 -1
  35. package/dist/static-node.js +6 -3
  36. package/dist/static-node.js.map +1 -1
  37. package/dist/static.js +3 -5
  38. package/dist/static.js.map +1 -1
  39. package/dist/stream.test.js +45 -66
  40. package/dist/stream.test.js.map +1 -1
  41. package/dist/types.test.js +10 -45
  42. package/dist/types.test.js.map +1 -1
  43. package/dist/utils.d.ts.map +1 -1
  44. package/dist/zod.test.js +2 -2
  45. package/dist/zod.test.js.map +1 -1
  46. package/package.json +10 -2
  47. package/src/client/index.ts +29 -7
  48. package/src/client.test.ts +20 -14
  49. package/src/context.ts +1 -1
  50. package/src/cors.ts +1 -0
  51. package/src/mcp.test.ts +5 -12
  52. package/src/mcp.ts +1 -0
  53. package/src/middleware.test.ts +1 -1
  54. package/src/openapi.test.ts +88 -0
  55. package/src/spiceflow.test.ts +120 -2
  56. package/src/spiceflow.ts +33 -11
  57. package/src/stream.test.ts +10 -2
  58. package/src/zod.test.ts +2 -3
@@ -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(`"{"message":"error message"}"`)
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
- JSON.stringify({ message: err?.message || 'Internal Server Error' }),
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
- JSON.stringify({ message: 'Internal Server Error' }),
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(JSON.stringify({ message: 'Internal Server Error' }))
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: ' + JSON.stringify(init.value) + '\n\n',
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: ' + JSON.stringify(chunk) + '\n\n',
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
- JSON.stringify(error.message || error.name || 'Error') +
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
- return new Response(JSON.stringify(result ?? null, null, 2), {
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 {
@@ -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).toBe(
73
- 'event: message\ndata: "a"\n\nevent: error\ndata: "an error"\n\n',
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 { test, describe, expect } from 'vitest'
2
- import { Type } from '@sinclair/typebox'
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 = ''