spiceflow 1.9.0 → 1.10.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 (96) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1 -2
  3. package/dist/_node-server-unsupported.d.ts +5 -0
  4. package/dist/_node-server-unsupported.d.ts.map +1 -0
  5. package/dist/_node-server-unsupported.js +6 -0
  6. package/dist/_node-server.d.ts +7 -0
  7. package/dist/_node-server.d.ts.map +1 -0
  8. package/dist/_node-server.js +77 -0
  9. package/dist/client/index.d.ts +2 -2
  10. package/dist/client/index.d.ts.map +1 -1
  11. package/dist/client/index.js +2 -2
  12. package/dist/client/types.d.ts +2 -2
  13. package/dist/client/types.d.ts.map +1 -1
  14. package/dist/client.test.js +2 -2
  15. package/dist/context.d.ts +3 -3
  16. package/dist/cors.d.ts +2 -2
  17. package/dist/cors.d.ts.map +1 -1
  18. package/dist/cors.js +5 -2
  19. package/dist/cors.test.js +2 -2
  20. package/dist/error.d.ts +0 -1
  21. package/dist/error.d.ts.map +1 -1
  22. package/dist/error.js +0 -10
  23. package/dist/index.d.ts +3 -3
  24. package/dist/index.js +2 -2
  25. package/dist/mcp-transport.d.ts +2 -2
  26. package/dist/mcp-transport.d.ts.map +1 -1
  27. package/dist/mcp-transport.js +1 -6
  28. package/dist/mcp.d.ts +3 -3
  29. package/dist/mcp.d.ts.map +1 -1
  30. package/dist/mcp.js +3 -3
  31. package/dist/middleware.test.js +8 -3
  32. package/dist/openapi.d.ts +3 -3
  33. package/dist/openapi.d.ts.map +1 -1
  34. package/dist/openapi.js +15 -2
  35. package/dist/openapi.test.js +8 -8
  36. package/dist/serialize.d.ts +2 -0
  37. package/dist/serialize.d.ts.map +1 -0
  38. package/dist/serialize.js +9 -0
  39. package/dist/simple.benchmark.js +1 -1
  40. package/dist/spiceflow.d.ts +15 -8
  41. package/dist/spiceflow.d.ts.map +1 -1
  42. package/dist/spiceflow.js +30 -86
  43. package/dist/spiceflow.test.js +6 -4
  44. package/dist/static-node.d.ts +2 -2
  45. package/dist/static-node.d.ts.map +1 -1
  46. package/dist/static-node.js +1 -1
  47. package/dist/static.benchmark.js +2 -2
  48. package/dist/static.d.ts +1 -1
  49. package/dist/static.d.ts.map +1 -1
  50. package/dist/static.js +1 -1
  51. package/dist/stream.test.js +2 -2
  52. package/dist/types.d.ts +6 -6
  53. package/dist/types.d.ts.map +1 -1
  54. package/dist/types.js +1 -1
  55. package/dist/types.test.js +2 -2
  56. package/dist/utils.d.ts.map +1 -1
  57. package/dist/zod.test.js +2 -2
  58. package/package.json +13 -12
  59. package/src/_node-server-unsupported.ts +20 -0
  60. package/src/_node-server.ts +115 -0
  61. package/src/client/index.ts +4 -4
  62. package/src/client/types.ts +47 -49
  63. package/src/client.test.ts +2 -3
  64. package/src/context.ts +3 -3
  65. package/src/cors.test.ts +11 -9
  66. package/src/cors.ts +7 -5
  67. package/src/error.ts +0 -12
  68. package/src/index.ts +3 -3
  69. package/src/mcp-transport.ts +2 -11
  70. package/src/mcp.ts +3 -4
  71. package/src/middleware.test.ts +19 -12
  72. package/src/openapi.test.ts +8 -8
  73. package/src/openapi.ts +20 -5
  74. package/src/serialize.ts +10 -0
  75. package/src/simple.benchmark.ts +1 -1
  76. package/src/spiceflow.test.ts +12 -10
  77. package/src/spiceflow.ts +70 -137
  78. package/src/static-node.ts +2 -2
  79. package/src/static.benchmark.ts +2 -2
  80. package/src/static.ts +2 -2
  81. package/src/stream.test.ts +2 -3
  82. package/src/types.test.ts +3 -3
  83. package/src/types.ts +18 -18
  84. package/src/zod.test.ts +2 -2
  85. package/dist/_node_utils.d.ts +0 -3
  86. package/dist/_node_utils.d.ts.map +0 -1
  87. package/dist/_node_utils.js +0 -2
  88. package/dist/_node_utils_browser.d.ts +0 -2
  89. package/dist/_node_utils_browser.d.ts.map +0 -1
  90. package/dist/_node_utils_browser.js +0 -3
  91. package/dist/mcp.test.d.ts +0 -2
  92. package/dist/mcp.test.d.ts.map +0 -1
  93. package/dist/mcp.test.js +0 -217
  94. package/src/_node_utils.ts +0 -2
  95. package/src/_node_utils_browser.ts +0 -3
  96. package/src/mcp.test.ts +0 -267
@@ -1,9 +1,8 @@
1
1
  import { test, describe, expect } from 'vitest'
2
2
 
3
- import { bfs, cloneDeep, Spiceflow } from './spiceflow.js'
3
+ import { bfs, cloneDeep, Spiceflow } from './spiceflow.ts'
4
4
  import { z } from 'zod'
5
- import { createSpiceflowClient } from './client/index.js'
6
- import { Type } from 'ajv/dist/compile/util.js'
5
+ import { createSpiceflowClient } from './client/index.ts'
7
6
 
8
7
  test('works', async () => {
9
8
  const res = await new Spiceflow()
@@ -30,7 +29,6 @@ describe('cloneDeep', () => {
30
29
  })
31
30
  })
32
31
 
33
-
34
32
  test('can encode superjson types', async () => {
35
33
  const app = new Spiceflow().post('/superjson', () => {
36
34
  const item = {
@@ -238,7 +236,9 @@ test('onError fires on validation errors', async () => {
238
236
  )
239
237
 
240
238
  expect(res.status).toBe(400)
241
- expect(errorMessage).toMatchInlineSnapshot(`"name: Expected string, received number"`)
239
+ expect(errorMessage).toMatchInlineSnapshot(
240
+ `"name: Expected string, received number"`,
241
+ )
242
242
  expect(await res.text()).toMatchInlineSnapshot(`"Error"`)
243
243
  })
244
244
 
@@ -816,11 +816,13 @@ test('async generators handle non-ASCII characters correctly', async () => {
816
816
 
817
817
  test('can pass additional props to body schema', async () => {
818
818
  const app = new Spiceflow().post('/user', ({ request }) => request.json(), {
819
- body: z.object({
820
- name: z.string(),
821
- age: z.number(),
822
- email: z.string().email(),
823
- }).passthrough(),
819
+ body: z
820
+ .object({
821
+ name: z.string(),
822
+ age: z.number(),
823
+ email: z.string().email(),
824
+ })
825
+ .passthrough(),
824
826
  })
825
827
 
826
828
  const res = await app.handle(
package/src/spiceflow.ts CHANGED
@@ -1,38 +1,38 @@
1
- import { createServer } from 'spiceflow/_node_utils'
2
1
  import lodashCloneDeep from 'lodash.clonedeep'
3
- import superjson from 'superjson'
4
2
  import {
5
- ComposeSpiceflowResponse,
6
- ContentType,
7
- CreateClient,
8
- DefinitionBase,
9
- ErrorHandler,
10
- HTTPMethod,
11
- InlineHandler,
12
- InputSchema,
13
- IsAny,
14
- JoinPath,
15
- LocalHook,
16
- MaybeArray,
17
- MetadataBase,
18
- MiddlewareHandler,
19
- Reconcile,
20
- ResolvePath,
21
- RouteBase,
22
- RouteSchema,
23
- SingletonBase,
24
- TypeSchema,
25
- UnwrapRoute,
26
- } from './types.js'
3
+ ComposeSpiceflowResponse,
4
+ ContentType,
5
+ CreateClient,
6
+ DefinitionBase,
7
+ ErrorHandler,
8
+ HTTPMethod,
9
+ InlineHandler,
10
+ InputSchema,
11
+ IsAny,
12
+ JoinPath,
13
+ LocalHook,
14
+ MetadataBase,
15
+ MiddlewareHandler,
16
+ Reconcile,
17
+ ResolvePath,
18
+ RouteBase,
19
+ RouteSchema,
20
+ SingletonBase,
21
+ TypeSchema,
22
+ UnwrapRoute
23
+ } from './types.ts'
27
24
 
28
25
  import OriginalRouter from '@medley/router'
29
- import { type IncomingMessage, type ServerResponse } from 'http'
30
- import { z, ZodType } from 'zod'
26
+ import { ZodType } from 'zod'
31
27
 
32
- import { MiddlewareContext } from './context.js'
33
- import { isProduction, ValidationError } from './error.js'
34
- import { isAsyncIterable, isResponse, redirect } from './utils.js'
35
28
  import { StandardSchemaV1 } from '@standard-schema/spec'
29
+ import { IncomingMessage, ServerResponse } from 'node:http'
30
+ import { handleForNode, listenForNode } from 'spiceflow/_node-server'
31
+ import { MiddlewareContext } from './context.ts'
32
+ import { ValidationError } from './error.ts'
33
+ import { superjsonSerialize } from './serialize.ts'
34
+ import { isAsyncIterable, isResponse, redirect } from './utils.ts'
35
+
36
36
  let globalIndex = 0
37
37
 
38
38
  type AsyncResponse = Response | Promise<Response>
@@ -52,7 +52,6 @@ export type InternalRoute = {
52
52
  validateBody?: ValidationFunction
53
53
  validateQuery?: ValidationFunction
54
54
  validateParams?: ValidationFunction
55
- // prefix: string
56
55
  }
57
56
 
58
57
  type MedleyRouter = {
@@ -673,12 +672,7 @@ export class Spiceflow<
673
672
  : Routes & CreateClient<BasePath, NewSpiceflow['_routes']>
674
673
  >
675
674
  use<const Schema extends RouteSchema>(
676
- handler: MiddlewareHandler<
677
- Schema,
678
- {
679
- state: Singleton['state']
680
- }
681
- >,
675
+ handler: MiddlewareHandler<Schema, Singleton>,
682
676
  ): this
683
677
 
684
678
  use(appOrHandler) {
@@ -692,7 +686,7 @@ export class Spiceflow<
692
686
  }
693
687
 
694
688
  onError<const Schema extends RouteSchema>(
695
- handler: MaybeArray<ErrorHandler<Definitions['error'], Schema, Singleton>>,
689
+ handler: ErrorHandler<Definitions['error'], Schema, Singleton>,
696
690
  ): this {
697
691
  this.onErrorHandlers ??= []
698
692
  this.onErrorHandlers.push(handler as any)
@@ -898,12 +892,11 @@ export class Spiceflow<
898
892
  }
899
893
 
900
894
  async listen(port: number, hostname: string = '0.0.0.0') {
901
- // @ts-ignore
895
+ const app = this
902
896
  if (typeof Bun !== 'undefined') {
903
- // @ts-ignore
904
897
  const server = Bun.serve({
905
898
  port,
906
- development: !isProduction,
899
+ development: (Bun.env.NODE_ENV ?? Bun.env.ENV) !== 'production',
907
900
  hostname,
908
901
  reusePort: true,
909
902
  error(error) {
@@ -915,115 +908,54 @@ export class Spiceflow<
915
908
  },
916
909
  )
917
910
  },
918
-
919
- fetch: async (request) => {
920
- const res = await this.handle(request)
911
+ async fetch(request) {
912
+ const res = await app.handle(request)
921
913
  return res
922
914
  },
923
915
  })
916
+
924
917
  process.on('beforeExit', () => {
925
918
  server.stop()
926
919
  })
927
- console.log(`Listening on http://localhost:${port}`)
920
+
921
+ const displayedHost =
922
+ server.hostname === '0.0.0.0' ? 'localhost' : server.hostname
923
+ console.log(`Listening on http://${displayedHost}:${server.port}`)
924
+
928
925
  return server
929
926
  }
930
- return this.listenNode(port, hostname)
931
- }
932
- async listenNode(port: number, hostname: string = '0.0.0.0') {
933
- const server = createServer((req, res) => {
934
- return this.handleNode(req, res)
935
- })
936
-
937
- await new Promise((resolve, reject) => {
938
- server.listen(port, hostname, () => {
939
- console.log(`Listening on http://localhost:${port}`)
940
- resolve(null)
941
- })
942
- })
943
927
 
944
- return server
928
+ return this.listenForNode(port, hostname)
945
929
  }
946
930
 
931
+ /**
932
+ * @deprecated Use `handleForNode` instead.
933
+ */
947
934
  async handleNode(
948
935
  req: IncomingMessage,
949
936
  res: ServerResponse,
950
937
  context: { state?: Singleton['state'] } = {},
951
938
  ) {
952
- if (req?.['body']) {
953
- throw new Error(
954
- 'req.body is defined, you should disable your framework body parser to be able to use the request in Spiceflow',
955
- )
956
- }
957
-
958
- const abortController = new AbortController()
959
- const { signal } = abortController
960
-
961
- req.on('error', (err) => {
962
- abortController.abort()
963
- })
964
- req.on('aborted', (err) => {
965
- abortController.abort()
966
- })
967
- res.on('close', function () {
968
- let aborted = !res.writableFinished
969
- if (aborted) {
970
- abortController.abort()
971
- }
972
- })
939
+ return this.handleForNode(req, res, context)
940
+ }
973
941
 
974
- const url = new URL(
975
- req.url || '',
976
- `http://${req.headers.host || 'localhost'}`,
977
- )
978
- const typedRequest = new SpiceflowRequest(url.toString(), {
979
- method: req.method,
980
- headers: req.headers as HeadersInit,
981
- body:
982
- req.method !== 'GET' && req.method !== 'HEAD'
983
- ? new ReadableStream({
984
- start(controller) {
985
- req.on('data', (chunk) => {
986
- controller.enqueue(
987
- new Uint8Array(
988
- chunk.buffer,
989
- chunk.byteOffset,
990
- chunk.byteLength,
991
- ),
992
- )
993
- })
994
- req.on('end', () => {
995
- controller.close()
996
- })
997
- },
998
- })
999
- : null,
1000
- signal,
1001
- // @ts-ignore
1002
- duplex: 'half',
1003
- })
942
+ async handleForNode(
943
+ req: IncomingMessage,
944
+ res: ServerResponse,
945
+ context: { state?: Singleton['state'] } = {},
946
+ ) {
947
+ return handleForNode(this, req, res, context)
948
+ }
1004
949
 
1005
- try {
1006
- const response = await this.handle(typedRequest, context)
1007
- res.writeHead(
1008
- response.status,
1009
- Object.fromEntries(response.headers.entries()),
950
+ async listenForNode(port: number, hostname: string = '0.0.0.0') {
951
+ if (typeof Bun !== 'undefined') {
952
+ console.warn(
953
+ "Server is being started with node:http but the current runtime is Bun, not Node. Consider using the method 'handle' with 'Bun.serve' instead.",
1010
954
  )
1011
-
1012
- if (response.body) {
1013
- const reader = response.body.getReader()
1014
- while (true) {
1015
- const { done, value } = await reader.read()
1016
- if (done) break
1017
- res.write(value)
1018
- }
1019
- }
1020
- res.end()
1021
- } catch (error) {
1022
- console.error('Error handling request:', error)
1023
- res.statusCode = 500
1024
- res.end(superjsonSerialize({ message: 'Internal Server Error' }))
1025
955
  }
956
+ return listenForNode(this, port, hostname)
1026
957
  }
958
+
1027
959
  private async handleStream({
1028
960
  onErrorHandlers,
1029
961
  generator,
@@ -1277,15 +1209,6 @@ export async function turnHandlerResultIntoResponse(
1277
1209
  })
1278
1210
  }
1279
1211
 
1280
- function superjsonSerialize(value: any, indent = false) {
1281
- // return JSON.stringify(value)
1282
- const { json, meta } = superjson.serialize(value)
1283
- if (json && meta) {
1284
- json['__superjsonMeta'] = meta
1285
- }
1286
- return JSON.stringify(json ?? null, null, indent ? 2 : undefined)
1287
- }
1288
-
1289
1212
  export type AnySpiceflow = Spiceflow<any, any, any, any, any, any>
1290
1213
 
1291
1214
  export function isZodSchema(value: unknown): value is ZodType {
@@ -1300,6 +1223,13 @@ export function isZodSchema(value: unknown): value is ZodType {
1300
1223
  )
1301
1224
  }
1302
1225
 
1226
+ import type * as z4 from 'zod/v4/core'
1227
+
1228
+ /** `true` ⇒ the value was created by Zod 4, `false` ⇒ Zod 3 */
1229
+ export function isZod4(schema: any): schema is z4.$ZodObject {
1230
+ return '_zod' in schema // ⇦ only v4 adds this marker
1231
+ }
1232
+
1303
1233
  function getValidateFunction(
1304
1234
  schema: TypeSchema,
1305
1235
  ): ValidationFunction | undefined {
@@ -1334,6 +1264,9 @@ async function runValidation(value: any, validate?: ValidationFunction) {
1334
1264
  .join('\\n')
1335
1265
  throw new ValidationError(errorMessages || 'Validation failed')
1336
1266
  }
1267
+ if ('value' in result) {
1268
+ return result.value
1269
+ }
1337
1270
  return value
1338
1271
  }
1339
1272
 
@@ -1,7 +1,7 @@
1
1
  import { stat } from 'fs/promises'
2
2
  import fs from 'fs'
3
- import { ServeStaticOptions, serveStatic as baseServeStatic } from './static.js'
4
- import { MiddlewareHandler } from './types.js'
3
+ import { ServeStaticOptions, serveStatic as baseServeStatic } from './static.ts'
4
+ import { MiddlewareHandler } from './types.ts'
5
5
 
6
6
  export const serveStatic = (options: ServeStaticOptions): MiddlewareHandler => {
7
7
  const getContent = (path: string) => {
@@ -1,7 +1,7 @@
1
1
  import { bench } from 'vitest'
2
2
 
3
- import { Spiceflow } from './spiceflow.js'
4
- import { serveStatic } from './static-node.js'
3
+ import { Spiceflow } from './spiceflow.ts'
4
+ import { serveStatic } from './static-node.ts'
5
5
 
6
6
  bench('Spiceflow static', async () => {
7
7
  const app = new Spiceflow()
package/src/static.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { MiddlewareHandler } from './types.js'
2
- import { isResponse } from './utils.js'
1
+ import { MiddlewareHandler } from './types.ts'
2
+ import { isResponse } from './utils.ts'
3
3
 
4
4
  type Env = {}
5
5
  type Context<E extends Env = Env> = {}
@@ -2,9 +2,9 @@ import { describe, it, expect } from 'vitest'
2
2
 
3
3
  import { createParser } from 'eventsource-parser'
4
4
 
5
- import { Spiceflow } from './spiceflow.js'
5
+ import { Spiceflow } from './spiceflow.ts'
6
6
 
7
- import { req, sleep } from './utils.js'
7
+ import { req, sleep } from './utils.ts'
8
8
 
9
9
  function textEventStream(items: string[]) {
10
10
  return items
@@ -321,7 +321,6 @@ describe('Stream', () => {
321
321
  "
322
322
  `)
323
323
  expect(parseTextEventStreamItem(text)).toMatchInlineSnapshot(`"hello"`)
324
-
325
324
  })
326
325
 
327
326
  it('handle object and array', async () => {
package/src/types.test.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { expect, test } from 'vitest'
2
- import { createSpiceflowClient } from './client/index.js'
3
- import { Spiceflow } from './spiceflow.js'
4
- import { Prettify } from './types.js'
2
+ import { createSpiceflowClient } from './client/index.ts'
3
+ import { Spiceflow } from './spiceflow.ts'
4
+ import { Prettify } from './types.ts'
5
5
 
6
6
  test('`use` on non Spiceflow return', async () => {
7
7
  function nonSpiceflowReturn() {
package/src/types.ts CHANGED
@@ -1,19 +1,19 @@
1
1
  // https://github.com/remorses/elysia/blob/main/src/types.ts#L6
2
2
 
3
- import z from 'zod'
4
3
  import { StandardSchemaV1 } from '@standard-schema/spec'
4
+ import z from 'zod'
5
5
 
6
6
  import type { OpenAPIV3 } from 'openapi-types'
7
7
 
8
- import { ZodObject, ZodTypeAny } from 'zod'
9
- import type { Context, ErrorContext, MiddlewareContext } from './context.js'
8
+ import { ZodTypeAny } from 'zod'
9
+ import type { Context, ErrorContext, MiddlewareContext } from './context.ts'
10
10
  import {
11
- SPICEFLOW_RESPONSE,
12
- InternalServerError,
13
- ParseError,
14
- ValidationError,
15
- } from './error.js'
16
- import { Spiceflow } from './spiceflow.js'
11
+ InternalServerError,
12
+ ParseError,
13
+ SPICEFLOW_RESPONSE,
14
+ ValidationError,
15
+ } from './error.ts'
16
+ import { Spiceflow } from './spiceflow.ts'
17
17
 
18
18
  export type MaybeArray<T> = T | T[]
19
19
  export type MaybePromise<T> = T | Promise<T>
@@ -161,7 +161,7 @@ export type RouteSchema = {
161
161
 
162
162
  export type TypeSchema = StandardSchemaV1
163
163
 
164
- export type TypeObject = ZodObject<any, any, any>
164
+ export type TypeObject = StandardSchemaV1
165
165
 
166
166
  export type UnwrapSchema<
167
167
  Schema extends TypeSchema | string | undefined,
@@ -169,14 +169,14 @@ export type UnwrapSchema<
169
169
  > = Schema extends undefined
170
170
  ? unknown
171
171
  : Schema extends StandardSchemaV1
172
- ? StandardSchemaV1.InferOutput<Schema>
173
- : Schema extends ZodTypeAny
174
- ? z.infer<Schema>
175
- : Schema extends string
176
- ? Definitions extends Record<Schema, infer NamedSchema>
177
- ? NamedSchema
178
- : Definitions
179
- : unknown
172
+ ? StandardSchemaV1.InferOutput<Schema>
173
+ : Schema extends ZodTypeAny
174
+ ? z.infer<Schema>
175
+ : Schema extends string
176
+ ? Definitions extends Record<Schema, infer NamedSchema>
177
+ ? NamedSchema
178
+ : Definitions
179
+ : unknown
180
180
 
181
181
  export interface UnwrapRoute<
182
182
  in out Schema extends InputSchema<any>,
package/src/zod.test.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { expect, test } from 'vitest'
2
2
  import { z } from 'zod'
3
- import { Spiceflow } from './spiceflow.js'
4
- import { req } from './utils.js'
3
+ import { Spiceflow } from './spiceflow.ts'
4
+ import { req } from './utils.ts'
5
5
 
6
6
  test('body is parsed as json', async () => {
7
7
  let name = ''
@@ -1,3 +0,0 @@
1
- import { createServer } from 'http';
2
- export { createServer };
3
- //# sourceMappingURL=_node_utils.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"_node_utils.d.ts","sourceRoot":"","sources":["../src/_node_utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,EAAE,YAAY,EAAE,CAAA"}
@@ -1,2 +0,0 @@
1
- import { createServer } from 'http';
2
- export { createServer };
@@ -1,2 +0,0 @@
1
- export declare function createServer(): void;
2
- //# sourceMappingURL=_node_utils_browser.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"_node_utils_browser.d.ts","sourceRoot":"","sources":["../src/_node_utils_browser.ts"],"names":[],"mappings":"AAAA,wBAAgB,YAAY,SAE3B"}
@@ -1,3 +0,0 @@
1
- export function createServer() {
2
- throw new Error('createServer is not supported in non Node.js environments');
3
- }
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=mcp.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mcp.test.d.ts","sourceRoot":"","sources":["../src/mcp.test.ts"],"names":[],"mappings":""}