spiceflow 1.0.7 → 1.1.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 (76) hide show
  1. package/README.md +1 -1
  2. package/dist/client/index.d.ts +1 -1
  3. package/dist/client/index.d.ts.map +1 -1
  4. package/dist/client/index.js.map +1 -1
  5. package/dist/client/types.d.ts +1 -2
  6. package/dist/client/types.d.ts.map +1 -1
  7. package/dist/client/ws.d.ts +1 -1
  8. package/dist/client/ws.d.ts.map +1 -1
  9. package/dist/client.test.js +1 -18
  10. package/dist/client.test.js.map +1 -1
  11. package/dist/{elysia-fork/context.d.ts → context.d.ts} +8 -7
  12. package/dist/context.d.ts.map +1 -0
  13. package/dist/{elysia-fork/context.js.map → context.js.map} +1 -1
  14. package/dist/error.d.ts.map +1 -0
  15. package/dist/error.js.map +1 -0
  16. package/dist/index.d.ts +1 -2
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +1 -1
  19. package/dist/index.js.map +1 -1
  20. package/dist/middleware.test.d.ts +2 -0
  21. package/dist/middleware.test.d.ts.map +1 -0
  22. package/dist/middleware.test.js +99 -0
  23. package/dist/middleware.test.js.map +1 -0
  24. package/dist/openapi.d.ts +23 -3
  25. package/dist/openapi.d.ts.map +1 -1
  26. package/dist/openapi.js +2 -2
  27. package/dist/openapi.js.map +1 -1
  28. package/dist/openapi.test.js +66 -1
  29. package/dist/openapi.test.js.map +1 -1
  30. package/dist/spiceflow.d.ts +44 -125
  31. package/dist/spiceflow.d.ts.map +1 -1
  32. package/dist/spiceflow.js +129 -166
  33. package/dist/spiceflow.js.map +1 -1
  34. package/dist/spiceflow.test.js +54 -16
  35. package/dist/spiceflow.test.js.map +1 -1
  36. package/dist/types.d.ts +406 -0
  37. package/dist/types.d.ts.map +1 -1
  38. package/dist/types.js +1 -1
  39. package/dist/types.js.map +1 -1
  40. package/dist/utils.d.ts +72 -0
  41. package/dist/utils.d.ts.map +1 -1
  42. package/dist/utils.js +69 -0
  43. package/dist/utils.js.map +1 -1
  44. package/package.json +1 -1
  45. package/src/client/index.ts +1 -3
  46. package/src/client/types.ts +6 -13
  47. package/src/client/ws.ts +1 -1
  48. package/src/client.test.ts +1 -19
  49. package/src/context.ts +128 -0
  50. package/src/index.ts +1 -2
  51. package/src/middleware.test.ts +107 -0
  52. package/src/openapi.test.ts +71 -1
  53. package/src/openapi.ts +1 -1
  54. package/src/spiceflow.test.ts +74 -16
  55. package/src/spiceflow.ts +213 -386
  56. package/src/types.test.ts +1 -1
  57. package/src/types.ts +926 -0
  58. package/src/utils.ts +84 -0
  59. package/dist/elysia-fork/context.d.ts.map +0 -1
  60. package/dist/elysia-fork/error.d.ts.map +0 -1
  61. package/dist/elysia-fork/error.js.map +0 -1
  62. package/dist/elysia-fork/types.d.ts +0 -562
  63. package/dist/elysia-fork/types.d.ts.map +0 -1
  64. package/dist/elysia-fork/types.js +0 -2
  65. package/dist/elysia-fork/types.js.map +0 -1
  66. package/dist/elysia-fork/utils.d.ts +0 -134
  67. package/dist/elysia-fork/utils.d.ts.map +0 -1
  68. package/dist/elysia-fork/utils.js +0 -70
  69. package/dist/elysia-fork/utils.js.map +0 -1
  70. package/src/elysia-fork/context.ts +0 -166
  71. package/src/elysia-fork/types.ts +0 -1281
  72. package/src/elysia-fork/utils.ts +0 -85
  73. /package/dist/{elysia-fork/context.js → context.js} +0 -0
  74. /package/dist/{elysia-fork/error.d.ts → error.d.ts} +0 -0
  75. /package/dist/{elysia-fork/error.js → error.js} +0 -0
  76. /package/src/{elysia-fork/error.ts → error.ts} +0 -0
package/src/spiceflow.ts CHANGED
@@ -4,13 +4,12 @@ import { Type } from '@sinclair/typebox'
4
4
 
5
5
  export { Type as t }
6
6
 
7
+ import addFormats from 'ajv-formats'
7
8
  import {
8
9
  ComposeSpiceflowResponse,
9
10
  CreateEden,
10
11
  DefinitionBase,
11
- EphemeralType,
12
12
  ErrorHandler,
13
- Handler,
14
13
  HTTPMethod,
15
14
  InlineHandler,
16
15
  InputSchema,
@@ -20,8 +19,7 @@ import {
20
19
  MaybeArray,
21
20
  MergeSchema,
22
21
  MetadataBase,
23
- PreHandler,
24
- Prettify2,
22
+ MiddlewareHandler,
25
23
  Reconcile,
26
24
  ResolvePath,
27
25
  RouteBase,
@@ -29,21 +27,18 @@ import {
29
27
  SingletonBase,
30
28
  TypeSchema,
31
29
  UnwrapRoute,
32
- } from './elysia-fork/types.js'
33
- import addFormats from 'ajv-formats'
30
+ } from './types.js'
34
31
  let globalIndex = 0
35
32
 
36
33
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
37
34
  // @ts-ignore
38
35
  import OriginalRouter from '@medley/router'
39
- import { TSchema } from '@sinclair/typebox'
40
36
  import Ajv, { ValidateFunction } from 'ajv'
41
- import { Context } from './elysia-fork/context.js'
42
- import { isAsyncIterable } from './utils.js'
43
- import { redirect } from './elysia-fork/utils.js'
44
- import { ValidationError } from './elysia-fork/error.js'
45
- import { zodToJsonSchema } from 'zod-to-json-schema'
46
37
  import { z, ZodType } from 'zod'
38
+ import { zodToJsonSchema } from 'zod-to-json-schema'
39
+ import { Context, MiddlewareContext } from './context.js'
40
+ import { NotFoundError, ValidationError } from './error.js'
41
+ import { isAsyncIterable, redirect } from './utils.js'
47
42
 
48
43
  const ajv = (addFormats.default || addFormats)(
49
44
  new (Ajv.default || Ajv)({ useDefaults: true }),
@@ -67,27 +62,10 @@ const ajv = (addFormats.default || addFormats)(
67
62
 
68
63
  // Should be exported from `hono/router`
69
64
 
70
- type P = any
71
-
72
65
  type AsyncResponse = Response | Promise<Response>
73
66
 
74
67
  type OnError = (x: { error: any; request: Request }) => AsyncResponse
75
68
 
76
- type RouterTree = {
77
- id: number
78
- router: OriginalRouter
79
- prefix?: string
80
- onRequestHandlers: Function[]
81
- onErrorHandlers: OnError[]
82
- children: RouterTree[]
83
- routes: InternalRoute[]
84
- // default store for the router, used as default for context.store
85
- store: Record<any, any>
86
- currentRoot?: RouterTree
87
- }
88
-
89
- type OnNoMatch = (request: Request, platform: P) => AsyncResponse
90
-
91
69
  export type InternalRoute = {
92
70
  method: HTTPMethod
93
71
  path: string
@@ -101,6 +79,11 @@ export type InternalRoute = {
101
79
 
102
80
  // store: Record<any, any>
103
81
  }
82
+
83
+ type MedleyRouter = {
84
+ find: (path: string) => InternalRoute | undefined
85
+ register: (path: string | undefined) => Record<string, InternalRoute>
86
+ }
104
87
  /**
105
88
  * Router class
106
89
  */
@@ -108,10 +91,7 @@ export class Spiceflow<
108
91
  const in out BasePath extends string = '',
109
92
  const in out Scoped extends boolean = true,
110
93
  const in out Singleton extends SingletonBase = {
111
- decorator: {}
112
94
  store: {}
113
- derive: {}
114
- resolve: {}
115
95
  },
116
96
  const in out Definitions extends DefinitionBase = {
117
97
  type: {}
@@ -123,30 +103,29 @@ export class Spiceflow<
123
103
  macroFn: {}
124
104
  },
125
105
  const out Routes extends RouteBase = {},
126
- // ? scoped
127
- const in out Ephemeral extends EphemeralType = {
128
- derive: {}
129
- resolve: {}
130
- schema: {}
131
- },
132
- // ? local
133
- const in out Volatile extends EphemeralType = {
134
- derive: {}
135
- resolve: {}
136
- schema: {}
137
- },
138
106
  > {
139
- private onNoMatch: OnNoMatch
140
- // prefix: BasePath | undefined
141
- private routerTree: RouterTree
107
+ private id: number = globalIndex++
108
+ private router: MedleyRouter = new OriginalRouter()
109
+ private middlewares: Function[] = []
110
+ private onErrorHandlers: OnError[] = []
111
+ private routes: InternalRoute[] = []
112
+ private defaultStore: Record<any, any> = {}
113
+ private topLevelApp?: AnySpiceflow
114
+
115
+ /** @internal */
116
+ prefix?: string
117
+
118
+ /** @internal */
119
+ childrenApps: AnySpiceflow[] = []
142
120
 
121
+ /** @internal */
143
122
  getAllRoutes() {
144
- let root = this.routerTree.currentRoot || this.routerTree
123
+ let root = this.topLevelApp || this
145
124
  const allApps = bfs(root) || []
146
125
  const allRoutes = allApps.flatMap((x) => {
147
- const prefix = this.getRouteAndParents(x)
126
+ const prefix = this.getAppAndParents(x)
148
127
  .map((x) => x.prefix)
149
- .reverse()
128
+
150
129
  .join('')
151
130
 
152
131
  return x.routes.map((x) => ({ ...x, path: prefix + x.path }))
@@ -161,73 +140,58 @@ export class Spiceflow<
161
140
  handler,
162
141
  ...rest
163
142
  }: Partial<InternalRoute>) {
164
- const router = this.routerTree
165
- // if (router.prefix) {
166
- // path = router.prefix + path
167
- // }
168
-
169
143
  let bodySchema: TypeSchema = hooks?.body
170
144
  let validateBody = getValidateFunction(bodySchema)
171
145
  let validateQuery = getValidateFunction(hooks?.query)
172
146
  let validateParams = getValidateFunction(hooks?.params)
173
147
 
174
- const store = router.router.register(path)
148
+ const store = this.router.register(path)
175
149
  let route: InternalRoute = {
176
150
  ...rest,
177
151
 
178
- prefix: router.prefix || '',
152
+ prefix: this.prefix || '',
179
153
  method: (method || '') as any,
180
154
  path: path || '',
181
- // prefix,
182
155
  handler: handler!,
183
156
  hooks,
184
157
  validateBody,
185
158
  validateParams,
186
159
  validateQuery,
187
160
  }
188
- router.routes.push(route)
189
- store[method] = route
161
+ this.routes.push(route)
162
+ store[method!] = route
190
163
  }
191
164
 
192
165
  private match(method: string, path: string) {
193
- let root = this.routerTree
194
- const result = bfsFind(this.routerTree, (router) => {
195
- router.currentRoot = root
196
- let prefix = this.getRouteAndParents(router)
166
+ let root = this
167
+ const result = bfsFind(this, (app) => {
168
+ app.topLevelApp = root
169
+ let prefix = this.getAppAndParents(app)
197
170
  .map((x) => x.prefix)
198
- .reverse()
171
+
199
172
  .join('')
200
173
  if (prefix && !path.startsWith(prefix)) {
201
- // console.log(
202
- // `router prefix: ${router.prefix} does not match path: ${path}`
203
- // )
204
174
  return
205
175
  }
206
176
  let pathWithoutPrefix = path
207
177
  if (prefix) {
208
178
  pathWithoutPrefix = path.replace(prefix, '')
209
179
  }
210
- // console.log(`router prefix: ${router.prefix} matches path: ${path}`)
211
- const route = router.router.find(pathWithoutPrefix)
180
+ const route = app.router.find(pathWithoutPrefix)
212
181
  if (!route) {
213
182
  return
214
183
  }
215
184
 
216
- let data: InternalRoute = route['store'][method]
217
- if (data) {
218
- // console.log(`route found: ${method} ${path}`, route)
219
-
220
- const { onErrorHandlers, onRequestHandlers } = router
185
+ let internalRoute: InternalRoute = route['store'][method]
186
+ if (internalRoute) {
221
187
  const params = route['params'] || {}
222
188
 
223
- return {
224
- ...data,
225
- router,
226
- store: router.store,
227
- onErrorHandlers,
228
- onRequestHandlers,
189
+ const res = {
190
+ app,
191
+ internalRoute: internalRoute,
229
192
  params,
230
193
  }
194
+ return res
231
195
  }
232
196
  })
233
197
 
@@ -241,23 +205,18 @@ export class Spiceflow<
241
205
  BasePath,
242
206
  Scoped,
243
207
  {
244
- decorator: Singleton['decorator']
245
208
  store: Reconcile<
246
209
  Singleton['store'],
247
210
  {
248
211
  [name in Name]: Value
249
212
  }
250
213
  >
251
- derive: Singleton['derive']
252
- resolve: Singleton['resolve']
253
214
  },
254
215
  Definitions,
255
216
  Metadata,
256
- Routes,
257
- Ephemeral,
258
- Volatile
217
+ Routes
259
218
  > {
260
- this.routerTree.store[name] = value
219
+ this.defaultStore[name] = value
261
220
  return this as any
262
221
  }
263
222
 
@@ -269,31 +228,13 @@ export class Spiceflow<
269
228
  options: {
270
229
  name?: string
271
230
  scoped?: Scoped
272
- onNoMatch?: (request: Request, platform: P) => AsyncResponse
231
+
273
232
  basePath?: BasePath
274
233
  } = {},
275
234
  ) {
276
235
  this.scoped = options.scoped
277
236
 
278
- this.onNoMatch =
279
- options.onNoMatch ?? (() => new Response(null, { status: 404 }))
280
- this.routerTree = {
281
- id: globalIndex++,
282
- router: new OriginalRouter(),
283
- prefix: options.basePath,
284
- onRequestHandlers: [],
285
- onErrorHandlers: [],
286
- children: [],
287
- store: {},
288
- routes: [],
289
- }
290
-
291
- // Bind router methods
292
- // for (const method of METHODS) {
293
- // this.#routes.set(method as Method, [])
294
- // const key = method.toLowerCase() as Lowercase<Method>
295
- // this[key as any] = this.#add.bind(this, method)
296
- // }
237
+ this.prefix = options.basePath
297
238
  }
298
239
 
299
240
  _routes: Routes = {} as any
@@ -306,27 +247,15 @@ export class Spiceflow<
306
247
  Metadata: {} as Metadata,
307
248
  }
308
249
 
309
- _ephemeral = {} as Ephemeral
310
- _volatile = {} as Volatile
311
-
312
250
  post<
313
251
  const Path extends string,
314
252
  const LocalSchema extends InputSchema<
315
253
  keyof Definitions['type'] & string
316
254
  >,
317
- const Schema extends MergeSchema<
318
- UnwrapRoute<LocalSchema, Definitions['type']>,
319
- MergeSchema<
320
- Volatile['schema'],
321
- MergeSchema<Ephemeral['schema'], Metadata['schema']>
322
- >
323
- >,
255
+ const Schema extends UnwrapRoute<LocalSchema, Definitions['type']>,
324
256
  const Handle extends InlineHandler<
325
257
  Schema,
326
- Singleton & {
327
- derive: Ephemeral['derive'] & Volatile['derive']
328
- resolve: Ephemeral['resolve'] & Volatile['resolve']
329
- },
258
+ Singleton,
330
259
  JoinPath<BasePath, Path>
331
260
  >,
332
261
  >(
@@ -335,10 +264,7 @@ export class Spiceflow<
335
264
  hook?: LocalHook<
336
265
  LocalSchema,
337
266
  Schema,
338
- Singleton & {
339
- derive: Ephemeral['derive'] & Volatile['derive']
340
- resolve: Ephemeral['resolve'] & Volatile['resolve']
341
- },
267
+ Singleton,
342
268
  Definitions['error'],
343
269
  Metadata['macro'],
344
270
  JoinPath<BasePath, Path>
@@ -359,16 +285,13 @@ export class Spiceflow<
359
285
  ? ResolvePath<Path>
360
286
  : Schema['params']
361
287
  query: Schema['query']
362
-
363
288
  response: ComposeSpiceflowResponse<
364
289
  Schema['response'],
365
290
  Handle
366
291
  >
367
292
  }
368
293
  }
369
- >,
370
- Ephemeral,
371
- Volatile
294
+ >
372
295
  > {
373
296
  this.add({ method: 'POST', path, handler: handler, hooks: hook })
374
297
 
@@ -380,20 +303,11 @@ export class Spiceflow<
380
303
  const LocalSchema extends InputSchema<
381
304
  keyof Definitions['type'] & string
382
305
  >,
383
- const Schema extends MergeSchema<
384
- UnwrapRoute<LocalSchema, Definitions['type']>,
385
- MergeSchema<
386
- Volatile['schema'],
387
- MergeSchema<Ephemeral['schema'], Metadata['schema']>
388
- >
389
- >,
306
+ const Schema extends UnwrapRoute<LocalSchema, Definitions['type']>,
390
307
  const Macro extends Metadata['macro'],
391
308
  const Handle extends InlineHandler<
392
309
  Schema,
393
- Singleton & {
394
- derive: Ephemeral['derive'] & Volatile['derive']
395
- resolve: Ephemeral['resolve'] & Volatile['resolve']
396
- },
310
+ Singleton,
397
311
  JoinPath<BasePath, Path>
398
312
  >,
399
313
  >(
@@ -402,10 +316,7 @@ export class Spiceflow<
402
316
  hook?: LocalHook<
403
317
  LocalSchema,
404
318
  Schema,
405
- Singleton & {
406
- derive: Ephemeral['derive'] & Volatile['derive']
407
- resolve: Ephemeral['resolve'] & Volatile['resolve']
408
- },
319
+ Singleton,
409
320
  Definitions['error'],
410
321
  Macro,
411
322
  JoinPath<BasePath, Path>
@@ -433,9 +344,7 @@ export class Spiceflow<
433
344
  >
434
345
  }
435
346
  }
436
- >,
437
- Ephemeral,
438
- Volatile
347
+ >
439
348
  > {
440
349
  this.add({ method: 'GET', path, handler: handler, hooks: hook })
441
350
  return this as any
@@ -446,19 +355,10 @@ export class Spiceflow<
446
355
  const LocalSchema extends InputSchema<
447
356
  keyof Definitions['type'] & string
448
357
  >,
449
- const Schema extends MergeSchema<
450
- UnwrapRoute<LocalSchema, Definitions['type']>,
451
- MergeSchema<
452
- Volatile['schema'],
453
- MergeSchema<Ephemeral['schema'], Metadata['schema']>
454
- >
455
- >,
358
+ const Schema extends UnwrapRoute<LocalSchema, Definitions['type']>,
456
359
  const Handle extends InlineHandler<
457
360
  Schema,
458
- Singleton & {
459
- derive: Ephemeral['derive'] & Volatile['derive']
460
- resolve: Ephemeral['resolve'] & Volatile['resolve']
461
- },
361
+ Singleton,
462
362
  JoinPath<BasePath, Path>
463
363
  >,
464
364
  >(
@@ -467,10 +367,7 @@ export class Spiceflow<
467
367
  hook?: LocalHook<
468
368
  LocalSchema,
469
369
  Schema,
470
- Singleton & {
471
- derive: Ephemeral['derive'] & Volatile['derive']
472
- resolve: Ephemeral['resolve'] & Volatile['resolve']
473
- },
370
+ Singleton,
474
371
  Definitions['error'],
475
372
  Metadata['macro'],
476
373
  JoinPath<BasePath, Path>
@@ -498,9 +395,7 @@ export class Spiceflow<
498
395
  >
499
396
  }
500
397
  }
501
- >,
502
- Ephemeral,
503
- Volatile
398
+ >
504
399
  > {
505
400
  this.add({ method: 'PUT', path, handler: handler, hooks: hook })
506
401
 
@@ -512,19 +407,10 @@ export class Spiceflow<
512
407
  const LocalSchema extends InputSchema<
513
408
  keyof Definitions['type'] & string
514
409
  >,
515
- const Schema extends MergeSchema<
516
- UnwrapRoute<LocalSchema, Definitions['type']>,
517
- MergeSchema<
518
- Volatile['schema'],
519
- MergeSchema<Ephemeral['schema'], Metadata['schema']>
520
- >
521
- >,
410
+ const Schema extends UnwrapRoute<LocalSchema, Definitions['type']>,
522
411
  const Handle extends InlineHandler<
523
412
  Schema,
524
- Singleton & {
525
- derive: Ephemeral['derive'] & Volatile['derive']
526
- resolve: Ephemeral['resolve'] & Volatile['resolve']
527
- },
413
+ Singleton,
528
414
  JoinPath<BasePath, Path>
529
415
  >,
530
416
  >(
@@ -533,10 +419,7 @@ export class Spiceflow<
533
419
  hook?: LocalHook<
534
420
  LocalSchema,
535
421
  Schema,
536
- Singleton & {
537
- derive: Ephemeral['derive'] & Volatile['derive']
538
- resolve: Ephemeral['resolve'] & Volatile['resolve']
539
- },
422
+ Singleton,
540
423
  Definitions['error'],
541
424
  Metadata['macro'],
542
425
  JoinPath<BasePath, Path>
@@ -564,9 +447,7 @@ export class Spiceflow<
564
447
  >
565
448
  }
566
449
  }
567
- >,
568
- Ephemeral,
569
- Volatile
450
+ >
570
451
  > {
571
452
  this.add({ method: 'PATCH', path, handler: handler, hooks: hook })
572
453
 
@@ -578,19 +459,10 @@ export class Spiceflow<
578
459
  const LocalSchema extends InputSchema<
579
460
  keyof Definitions['type'] & string
580
461
  >,
581
- const Schema extends MergeSchema<
582
- UnwrapRoute<LocalSchema, Definitions['type']>,
583
- MergeSchema<
584
- Volatile['schema'],
585
- MergeSchema<Ephemeral['schema'], Metadata['schema']>
586
- >
587
- >,
462
+ const Schema extends UnwrapRoute<LocalSchema, Definitions['type']>,
588
463
  const Handle extends InlineHandler<
589
464
  Schema,
590
- Singleton & {
591
- derive: Ephemeral['derive'] & Volatile['derive']
592
- resolve: Ephemeral['resolve'] & Volatile['resolve']
593
- },
465
+ Singleton,
594
466
  JoinPath<BasePath, Path>
595
467
  >,
596
468
  >(
@@ -599,10 +471,7 @@ export class Spiceflow<
599
471
  hook?: LocalHook<
600
472
  LocalSchema,
601
473
  Schema,
602
- Singleton & {
603
- derive: Ephemeral['derive'] & Volatile['derive']
604
- resolve: Ephemeral['resolve'] & Volatile['resolve']
605
- },
474
+ Singleton,
606
475
  Definitions['error'],
607
476
  Metadata['macro'],
608
477
  JoinPath<BasePath, Path>
@@ -630,9 +499,7 @@ export class Spiceflow<
630
499
  >
631
500
  }
632
501
  }
633
- >,
634
- Ephemeral,
635
- Volatile
502
+ >
636
503
  > {
637
504
  this.add({ method: 'DELETE', path, handler: handler, hooks: hook })
638
505
 
@@ -644,19 +511,10 @@ export class Spiceflow<
644
511
  const LocalSchema extends InputSchema<
645
512
  keyof Definitions['type'] & string
646
513
  >,
647
- const Schema extends MergeSchema<
648
- UnwrapRoute<LocalSchema, Definitions['type']>,
649
- MergeSchema<
650
- Volatile['schema'],
651
- MergeSchema<Ephemeral['schema'], Metadata['schema']>
652
- >
653
- >,
514
+ const Schema extends UnwrapRoute<LocalSchema, Definitions['type']>,
654
515
  const Handle extends InlineHandler<
655
516
  Schema,
656
- Singleton & {
657
- derive: Ephemeral['derive'] & Volatile['derive']
658
- resolve: Ephemeral['resolve'] & Volatile['resolve']
659
- },
517
+ Singleton,
660
518
  JoinPath<BasePath, Path>
661
519
  >,
662
520
  >(
@@ -665,10 +523,7 @@ export class Spiceflow<
665
523
  hook?: LocalHook<
666
524
  LocalSchema,
667
525
  Schema,
668
- Singleton & {
669
- derive: Ephemeral['derive'] & Volatile['derive']
670
- resolve: Ephemeral['resolve'] & Volatile['resolve']
671
- },
526
+ Singleton,
672
527
  Definitions['error'],
673
528
  Metadata['macro'],
674
529
  JoinPath<BasePath, Path>
@@ -696,9 +551,7 @@ export class Spiceflow<
696
551
  >
697
552
  }
698
553
  }
699
- >,
700
- Ephemeral,
701
- Volatile
554
+ >
702
555
  > {
703
556
  this.add({ method: 'OPTIONS', path, handler: handler, hooks: hook })
704
557
 
@@ -710,19 +563,10 @@ export class Spiceflow<
710
563
  const LocalSchema extends InputSchema<
711
564
  keyof Definitions['type'] & string
712
565
  >,
713
- const Schema extends MergeSchema<
714
- UnwrapRoute<LocalSchema, Definitions['type']>,
715
- MergeSchema<
716
- Volatile['schema'],
717
- MergeSchema<Ephemeral['schema'], Metadata['schema']>
718
- >
719
- >,
566
+ const Schema extends UnwrapRoute<LocalSchema, Definitions['type']>,
720
567
  const Handle extends InlineHandler<
721
568
  Schema,
722
- Singleton & {
723
- derive: Ephemeral['derive'] & Volatile['derive']
724
- resolve: Ephemeral['resolve'] & Volatile['resolve']
725
- },
569
+ Singleton,
726
570
  JoinPath<BasePath, Path>
727
571
  >,
728
572
  >(
@@ -731,10 +575,7 @@ export class Spiceflow<
731
575
  hook?: LocalHook<
732
576
  LocalSchema,
733
577
  Schema,
734
- Singleton & {
735
- derive: Ephemeral['derive'] & Volatile['derive']
736
- resolve: Ephemeral['resolve'] & Volatile['resolve']
737
- },
578
+ Singleton,
738
579
  Definitions['error'],
739
580
  Metadata['macro'],
740
581
  JoinPath<BasePath, Path>
@@ -762,9 +603,7 @@ export class Spiceflow<
762
603
  >
763
604
  }
764
605
  }
765
- >,
766
- Ephemeral,
767
- Volatile
606
+ >
768
607
  > {
769
608
  for (const method of METHODS) {
770
609
  this.add({ method, path, handler: handler, hooks: hook })
@@ -778,19 +617,10 @@ export class Spiceflow<
778
617
  const LocalSchema extends InputSchema<
779
618
  keyof Definitions['type'] & string
780
619
  >,
781
- const Schema extends MergeSchema<
782
- UnwrapRoute<LocalSchema, Definitions['type']>,
783
- MergeSchema<
784
- Volatile['schema'],
785
- MergeSchema<Ephemeral['schema'], Metadata['schema']>
786
- >
787
- >,
620
+ const Schema extends UnwrapRoute<LocalSchema, Definitions['type']>,
788
621
  const Handle extends InlineHandler<
789
622
  Schema,
790
- Singleton & {
791
- derive: Ephemeral['derive'] & Volatile['derive']
792
- resolve: Ephemeral['resolve'] & Volatile['resolve']
793
- },
623
+ Singleton,
794
624
  JoinPath<BasePath, Path>
795
625
  >,
796
626
  >(
@@ -799,10 +629,7 @@ export class Spiceflow<
799
629
  hook?: LocalHook<
800
630
  LocalSchema,
801
631
  Schema,
802
- Singleton & {
803
- derive: Ephemeral['derive'] & Volatile['derive']
804
- resolve: Ephemeral['resolve'] & Volatile['resolve']
805
- },
632
+ Singleton,
806
633
  Definitions['error'],
807
634
  Metadata['macro'],
808
635
  JoinPath<BasePath, Path>
@@ -830,24 +657,14 @@ export class Spiceflow<
830
657
  >
831
658
  }
832
659
  }
833
- >,
834
- Ephemeral,
835
- Volatile
660
+ >
836
661
  > {
837
662
  this.add({ method: 'HEAD', path, handler: handler, hooks: hook })
838
663
 
839
664
  return this as any
840
665
  }
841
666
 
842
- /**
843
- * If set to true, other Spiceflow handler will not inherits global life-cycle, store, decorators from the current instance
844
- *
845
- * @default false
846
- */
847
- private scoped?: Scoped
848
- get _scoped() {
849
- return this.scoped as Scoped
850
- }
667
+ private scoped?: Scoped = true as Scoped
851
668
 
852
669
  // group is not needed, you can add another prefixed app instead
853
670
  // group<
@@ -900,155 +717,153 @@ export class Spiceflow<
900
717
  Metadata,
901
718
  BasePath extends ``
902
719
  ? Routes & NewSpiceflow['_routes']
903
- : Routes & CreateEden<BasePath, NewSpiceflow['_routes']>,
904
- Ephemeral,
905
- Volatile
906
- > {
907
- const thisRouter = this.routerTree
908
- // TODO use scoped logic to add onRequest and onError on all routers if necessary, add them first
909
- this.routerTree.children.push(instance.routerTree)
910
- return this as any
911
- }
912
-
913
- onError<const Schema extends RouteSchema>(
720
+ : Routes & CreateEden<BasePath, NewSpiceflow['_routes']>
721
+ >
722
+ use<const Schema extends RouteSchema>(
914
723
  handler: MaybeArray<
915
- ErrorHandler<
916
- Definitions['error'],
917
- MergeSchema<
918
- Schema,
919
- MergeSchema<
920
- Volatile['schema'],
921
- MergeSchema<Ephemeral['schema'], Metadata['schema']>
922
- >
923
- >,
924
- Singleton,
925
- Ephemeral,
926
- Volatile
724
+ MiddlewareHandler<
725
+ Schema,
726
+ {
727
+ store: Singleton['store']
728
+ }
927
729
  >
928
730
  >,
929
- ): this {
930
- const router = this.routerTree
931
-
932
- router.onErrorHandlers ??= []
933
- router.onErrorHandlers.push(handler as any)
934
-
731
+ ): this
732
+
733
+ use(appOrHandler) {
734
+ if (appOrHandler instanceof Spiceflow) {
735
+ this.childrenApps.push(appOrHandler)
736
+ } else if (typeof appOrHandler === 'function') {
737
+ this.middlewares ??= []
738
+ this.middlewares.push(appOrHandler)
739
+ }
935
740
  return this
936
741
  }
937
742
 
938
- onRequest<const Schema extends RouteSchema>(
743
+ onError<const Schema extends RouteSchema>(
939
744
  handler: MaybeArray<
940
- PreHandler<
941
- MergeSchema<
942
- Schema,
943
- MergeSchema<
944
- Volatile['schema'],
945
- MergeSchema<Ephemeral['schema'], Metadata['schema']>
946
- >
947
- >,
948
- {
949
- decorator: Singleton['decorator']
950
- store: Singleton['store']
951
- derive: {}
952
- resolve: {}
953
- }
954
- >
745
+ ErrorHandler<Definitions['error'], Schema, Singleton>
955
746
  >,
956
- ) {
957
- const router = this.routerTree
958
- router.onRequestHandlers ??= []
959
- router.onRequestHandlers.push(handler as any)
747
+ ): this {
748
+ this.onErrorHandlers ??= []
749
+ this.onErrorHandlers.push(handler as any)
960
750
 
961
751
  return this
962
752
  }
753
+
963
754
  /**
964
755
  * Pass a request through all matching route handles and return a response
965
756
  * @param request The `Request`
966
757
  * @param platform Platform specific context {@link Platform}
967
758
  * @returns The final `Response`
968
759
  */
969
- async handle(request: Request, platform?: P): Promise<Response> {
970
- platform ??= {} as P
971
-
972
- let u = new URL(request.url)
760
+ async handle(request: Request): Promise<Response> {
761
+ let u = new URL(request.url, 'http://localhost')
973
762
  let path = u.pathname + u.search
974
763
  const defaultContext = {
975
764
  redirect,
976
765
  error: null,
977
766
  path,
978
767
  }
768
+ const root = this.topLevelApp || this
979
769
  let onErrorHandlers: OnError[] = []
980
770
  try {
981
- let response: Response | undefined
982
771
  // Get all middleware and method specific routes in order
983
772
 
984
773
  const route = this.match(request.method, path)
774
+
985
775
  if (!route) {
986
- return this.onNoMatch(request, platform)
776
+ const error = new NotFoundError()
777
+ const res = await this.runErrorHandlers({
778
+ onErrorHandlers,
779
+ error,
780
+ request,
781
+ })
782
+ if (res) return res
783
+ return new Response(`Not Found`, {
784
+ status: 404,
785
+ })
987
786
  }
988
- onErrorHandlers = this.getRouteAndParents(route.router)
989
- .reverse()
990
- .flatMap((x) => x.onErrorHandlers)
991
- let { params, store: defaultStore } = route
992
- const onReqHandlers = this.getRouteAndParents(route.router)
993
- .reverse()
994
- .flatMap((x) => x.onRequestHandlers)
787
+ onErrorHandlers = this.getAppsInScope(route.app).flatMap(
788
+ (x) => x.onErrorHandlers,
789
+ )
790
+ let {
791
+ params,
792
+ app: { defaultStore },
793
+ } = route
794
+ const middlewares = this.getAppsInScope(route.app).flatMap(
795
+ (x) => x.middlewares,
796
+ )
995
797
  // console.log({ onReqHandlers })
996
798
  let store = { ...defaultStore }
997
- // TODO add content type
998
799
 
999
- let content = route?.hooks?.content
1000
- // let body = await getRequestBody({ request, content })
800
+ let content = route?.internalRoute?.hooks?.content
1001
801
 
1002
- if (route.validateBody) {
802
+ if (route.internalRoute?.validateBody) {
1003
803
  // TODO don't clone the request
1004
804
  let typedRequest = new TypedRequest(request)
1005
- typedRequest.validateBody = route.validateBody
805
+ typedRequest.validateBody = route.internalRoute?.validateBody
1006
806
  request = typedRequest
1007
807
  }
1008
808
 
1009
809
  let query = parseQuery.parse((u.search || '').slice(1))
1010
810
 
1011
- if (onReqHandlers.length > 0) {
1012
- for (const handler of onReqHandlers) {
1013
- const res = await handler({
811
+ let index = 0
812
+
813
+ const next = async () => {
814
+ if (index < middlewares.length) {
815
+ const middleware = middlewares[index]
816
+ index++
817
+ let context = {
1014
818
  request,
1015
- response,
1016
819
  store,
1017
820
  path,
1018
821
  query,
1019
- } satisfies Context<any, any, any>)
1020
- if (res) {
1021
- return await turnHandlerResultIntoResponse(res)
822
+ params,
823
+ redirect,
824
+ } satisfies MiddlewareContext<any>
825
+ const result = await middleware(context, next)
826
+
827
+ if (!result && index < middlewares.length) {
828
+ return await next()
829
+ } else if (result) {
830
+ return await turnHandlerResultIntoResponse(result)
1022
831
  }
1023
832
  }
1024
- }
1025
833
 
1026
- query = runValidation(query, route.validateQuery)
1027
- params = runValidation(params, route.validateParams)
834
+ query = runValidation(query, route.internalRoute?.validateQuery)
835
+ params = runValidation(
836
+ params,
837
+ route.internalRoute?.validateParams,
838
+ )
1028
839
 
1029
- // console.log(route)
840
+ // console.log(route)
1030
841
 
1031
- const res = route.handler({
1032
- ...defaultContext,
1033
- request,
1034
- response,
1035
- params: params as any,
1036
- store,
1037
- query,
1038
- // body,
1039
- path,
1040
-
1041
- // platform
1042
- } satisfies Context<any, any, string>)
1043
- if (isAsyncIterable(res)) {
1044
- return await this.handleStream({
1045
- generator: res,
842
+ const res = route.internalRoute?.handler({
843
+ ...defaultContext,
1046
844
  request,
1047
- onErrorHandlers,
1048
- })
845
+ params: params as any,
846
+ redirect,
847
+ store,
848
+ query,
849
+ // body,
850
+ path,
851
+
852
+ // platform
853
+ } satisfies Context<any, any, any>)
854
+ if (isAsyncIterable(res)) {
855
+ return await this.handleStream({
856
+ generator: res,
857
+ request,
858
+ onErrorHandlers,
859
+ })
860
+ }
861
+
862
+ return await turnHandlerResultIntoResponse(res)
1049
863
  }
864
+ const response = await next()
1050
865
 
1051
- return await turnHandlerResultIntoResponse(res)
866
+ return response
1052
867
  } catch (err: any) {
1053
868
  if (err instanceof Response) return err
1054
869
  let res = await this.runErrorHandlers({
@@ -1081,29 +896,48 @@ export class Spiceflow<
1081
896
  }
1082
897
  }
1083
898
 
1084
- // get the route parents, the order is starting from the current router and going up to the root
1085
- private getRouteAndParents(currentRouter?: RouterTree) {
1086
- const parents: RouterTree[] = []
1087
- let current = currentRouter
899
+ private getAppAndParents(currentApp?: AnySpiceflow) {
900
+ const parents: AnySpiceflow[] = []
901
+ let current = currentApp
902
+
903
+ let root = this.topLevelApp || this
1088
904
 
1089
- let root = this.routerTree.currentRoot || this.routerTree
1090
- // Perform BFS once to build a parent map
1091
- const parentMap = new Map<number, RouterTree>()
905
+ const parentMap = new Map<number, AnySpiceflow>()
1092
906
  bfsFind(root, (node) => {
1093
- for (const child of node.children) {
907
+ for (const child of node.childrenApps) {
1094
908
  parentMap.set(child.id, node)
1095
909
  }
1096
910
  })
1097
911
 
1098
912
  // Traverse the parent map to get the parents
1099
913
  while (current) {
1100
- parents.push(current)
914
+ parents.unshift(current)
1101
915
  current = parentMap.get(current.id)
1102
916
  }
1103
917
 
1104
918
  return parents.filter((x) => x !== undefined)
1105
919
  }
1106
920
 
921
+ private getAppsInScope(currentApp?: AnySpiceflow) {
922
+ const withParents = this.getAppAndParents(currentApp)
923
+
924
+ let root = this.topLevelApp || this
925
+ const wantedOrder = bfs(root)
926
+ const scopeFalseApps = wantedOrder.filter((x) => x.scoped === false)
927
+ let appsInScope = [] as AnySpiceflow[]
928
+ for (const app of wantedOrder) {
929
+ if (scopeFalseApps.includes(app)) {
930
+ appsInScope.push(app)
931
+ continue
932
+ }
933
+ if (withParents.includes(app)) {
934
+ appsInScope.push(app)
935
+ continue
936
+ }
937
+ }
938
+ return appsInScope
939
+ }
940
+
1107
941
  private async handleStream({
1108
942
  onErrorHandlers,
1109
943
  generator,
@@ -1264,8 +1098,8 @@ const METHODS = [
1264
1098
  export type Method = (typeof METHODS)[number]
1265
1099
 
1266
1100
  function bfsFind<T>(
1267
- tree: RouterTree,
1268
- onNode: (node: RouterTree) => T | undefined | void,
1101
+ tree: AnySpiceflow,
1102
+ onNode: (node: AnySpiceflow) => T | undefined | void,
1269
1103
  ): T | undefined {
1270
1104
  const queue = [tree]
1271
1105
 
@@ -1276,7 +1110,7 @@ function bfsFind<T>(
1276
1110
  if (result) {
1277
1111
  return result
1278
1112
  }
1279
- queue.push(...node.children)
1113
+ queue.push(...node.childrenApps)
1280
1114
  }
1281
1115
  return
1282
1116
  }
@@ -1289,9 +1123,9 @@ export class TypedRequest<T = any> extends Request {
1289
1123
  }
1290
1124
  }
1291
1125
 
1292
- export function bfs(tree: RouterTree) {
1126
+ export function bfs(tree: AnySpiceflow) {
1293
1127
  const queue = [tree]
1294
- let nodes: RouterTree[] = []
1128
+ let nodes: AnySpiceflow[] = []
1295
1129
  while (queue.length > 0) {
1296
1130
  const node = queue.shift()!
1297
1131
  if (node) {
@@ -1299,19 +1133,12 @@ export function bfs(tree: RouterTree) {
1299
1133
  }
1300
1134
  // const result = onNode(node)
1301
1135
 
1302
- queue.push(...node.children)
1136
+ if (node?.childrenApps?.length) {
1137
+ queue.push(...node.childrenApps)
1138
+ }
1303
1139
  }
1304
1140
  return nodes
1305
1141
  }
1306
- function mapTree<T>(
1307
- tree: RouterTree,
1308
- mapper: (node: RouterTree) => T,
1309
- ): T & { children: (T & { children: any[] })[] } {
1310
- const mappedNode = mapper(tree) as T & { children: any[] }
1311
- mappedNode.children = tree.children.map((child) => mapTree(child, mapper))
1312
- return mappedNode
1313
- }
1314
-
1315
1142
  export async function turnHandlerResultIntoResponse(result: any) {
1316
1143
  // if user returns not a response, convert to json
1317
1144
  if (result instanceof Response) {
@@ -1334,7 +1161,7 @@ export async function turnHandlerResultIntoResponse(result: any) {
1334
1161
  })
1335
1162
  }
1336
1163
 
1337
- export type AnySpiceflow = Spiceflow<any, any, any, any, any, any, any, any>
1164
+ export type AnySpiceflow = Spiceflow<any, any, any, any, any, any>
1338
1165
 
1339
1166
  export function isZodSchema(value: unknown): value is ZodType {
1340
1167
  return (