spiceflow 0.0.6 → 1.0.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 (151) hide show
  1. package/README.md +1 -171
  2. package/dist/client/errors.d.ts +7 -0
  3. package/dist/client/errors.d.ts.map +1 -0
  4. package/dist/client/errors.js +18 -0
  5. package/dist/client/errors.js.map +1 -0
  6. package/dist/client/index.d.ts +14 -0
  7. package/dist/client/index.d.ts.map +1 -0
  8. package/dist/client/index.js +376 -0
  9. package/dist/client/index.js.map +1 -0
  10. package/dist/client/types.d.ts +87 -0
  11. package/dist/client/types.d.ts.map +1 -0
  12. package/dist/client/types.js +2 -0
  13. package/dist/client/types.js.map +1 -0
  14. package/dist/client/utils.d.ts +2 -0
  15. package/dist/client/utils.d.ts.map +1 -0
  16. package/dist/client/utils.js +9 -0
  17. package/dist/client/utils.js.map +1 -0
  18. package/dist/client/ws.d.ts +15 -0
  19. package/dist/client/ws.d.ts.map +1 -0
  20. package/dist/client/ws.js +51 -0
  21. package/dist/client/ws.js.map +1 -0
  22. package/dist/client.test.d.ts +2 -0
  23. package/dist/client.test.d.ts.map +1 -0
  24. package/dist/client.test.js +237 -0
  25. package/dist/client.test.js.map +1 -0
  26. package/dist/elysia-fork/context.d.ts +87 -0
  27. package/dist/elysia-fork/context.d.ts.map +1 -0
  28. package/dist/elysia-fork/context.js +2 -0
  29. package/dist/elysia-fork/context.js.map +1 -0
  30. package/dist/elysia-fork/error.d.ts +246 -0
  31. package/dist/elysia-fork/error.d.ts.map +1 -0
  32. package/dist/elysia-fork/error.js +195 -0
  33. package/dist/elysia-fork/error.js.map +1 -0
  34. package/dist/elysia-fork/types.d.ts +652 -0
  35. package/dist/elysia-fork/types.d.ts.map +1 -0
  36. package/dist/elysia-fork/types.js +3 -0
  37. package/dist/elysia-fork/types.js.map +1 -0
  38. package/dist/elysia-fork/utils.d.ts +134 -0
  39. package/dist/elysia-fork/utils.d.ts.map +1 -0
  40. package/dist/elysia-fork/utils.js +70 -0
  41. package/dist/elysia-fork/utils.js.map +1 -0
  42. package/dist/spiceflow.d.ts +253 -0
  43. package/dist/spiceflow.d.ts.map +1 -0
  44. package/dist/spiceflow.js +500 -0
  45. package/dist/spiceflow.js.map +1 -0
  46. package/dist/spiceflow.test.d.ts +2 -0
  47. package/dist/spiceflow.test.d.ts.map +1 -0
  48. package/dist/spiceflow.test.js +225 -0
  49. package/dist/spiceflow.test.js.map +1 -0
  50. package/dist/stream.test.d.ts +2 -0
  51. package/dist/stream.test.d.ts.map +1 -0
  52. package/dist/stream.test.js +286 -0
  53. package/dist/stream.test.js.map +1 -0
  54. package/dist/types.d.ts +1 -0
  55. package/dist/types.d.ts.map +1 -0
  56. package/dist/types.js +2 -0
  57. package/dist/types.js.map +1 -0
  58. package/dist/utils.d.ts +4 -20
  59. package/dist/utils.d.ts.map +1 -1
  60. package/dist/utils.js +17 -46
  61. package/dist/utils.js.map +1 -1
  62. package/package.json +12 -36
  63. package/src/client/errors.ts +21 -0
  64. package/src/client/index.ts +539 -0
  65. package/src/client/types.ts +233 -0
  66. package/src/client/utils.ts +7 -0
  67. package/src/client/ws.ts +99 -0
  68. package/src/client.test.ts +235 -0
  69. package/src/elysia-fork/context.ts +196 -0
  70. package/src/elysia-fork/error.ts +293 -0
  71. package/src/elysia-fork/types.ts +1454 -0
  72. package/src/elysia-fork/utils.ts +85 -0
  73. package/src/spiceflow.test.ts +290 -0
  74. package/src/spiceflow.ts +1266 -0
  75. package/src/stream.test.ts +342 -0
  76. package/src/types.ts +0 -0
  77. package/src/utils.ts +21 -70
  78. package/context.d.ts +0 -2
  79. package/context.js +0 -1
  80. package/dist/babel.test.d.ts +0 -2
  81. package/dist/babel.test.d.ts.map +0 -1
  82. package/dist/babel.test.js +0 -27
  83. package/dist/babel.test.js.map +0 -1
  84. package/dist/babelDebugOutputs.d.ts +0 -9
  85. package/dist/babelDebugOutputs.d.ts.map +0 -1
  86. package/dist/babelDebugOutputs.js +0 -34
  87. package/dist/babelDebugOutputs.js.map +0 -1
  88. package/dist/babelTransformRpc.d.ts +0 -19
  89. package/dist/babelTransformRpc.d.ts.map +0 -1
  90. package/dist/babelTransformRpc.js +0 -285
  91. package/dist/babelTransformRpc.js.map +0 -1
  92. package/dist/browser.d.ts +0 -8
  93. package/dist/browser.d.ts.map +0 -1
  94. package/dist/browser.js +0 -126
  95. package/dist/browser.js.map +0 -1
  96. package/dist/build.d.ts +0 -13
  97. package/dist/build.d.ts.map +0 -1
  98. package/dist/build.js +0 -230
  99. package/dist/build.js.map +0 -1
  100. package/dist/cli.d.ts +0 -3
  101. package/dist/cli.d.ts.map +0 -1
  102. package/dist/cli.js +0 -111
  103. package/dist/cli.js.map +0 -1
  104. package/dist/context-internal.d.ts +0 -20
  105. package/dist/context-internal.d.ts.map +0 -1
  106. package/dist/context-internal.js +0 -16
  107. package/dist/context-internal.js.map +0 -1
  108. package/dist/context.d.ts +0 -2
  109. package/dist/context.d.ts.map +0 -1
  110. package/dist/context.js +0 -2
  111. package/dist/context.js.map +0 -1
  112. package/dist/expose.d.ts +0 -6
  113. package/dist/expose.d.ts.map +0 -1
  114. package/dist/expose.js +0 -32
  115. package/dist/expose.js.map +0 -1
  116. package/dist/headers.d.ts +0 -2
  117. package/dist/headers.d.ts.map +0 -1
  118. package/dist/headers.js +0 -18
  119. package/dist/headers.js.map +0 -1
  120. package/dist/index.d.ts +0 -8
  121. package/dist/index.d.ts.map +0 -1
  122. package/dist/index.js +0 -23
  123. package/dist/index.js.map +0 -1
  124. package/dist/jsonRpc.d.ts +0 -32
  125. package/dist/jsonRpc.d.ts.map +0 -1
  126. package/dist/jsonRpc.js +0 -3
  127. package/dist/jsonRpc.js.map +0 -1
  128. package/dist/server.d.ts +0 -32
  129. package/dist/server.d.ts.map +0 -1
  130. package/dist/server.js +0 -292
  131. package/dist/server.js.map +0 -1
  132. package/headers.d.ts +0 -2
  133. package/headers.js +0 -1
  134. package/sdk-template/package.json +0 -22
  135. package/sdk-template/src/index.ts +0 -2
  136. package/sdk-template/src/v1/example.ts +0 -5
  137. package/sdk-template/src/v1/generator.ts +0 -12
  138. package/sdk-template/tsconfig.json +0 -16
  139. package/src/babel.test.ts +0 -35
  140. package/src/babelDebugOutputs.ts +0 -56
  141. package/src/babelTransformRpc.ts +0 -394
  142. package/src/browser.ts +0 -141
  143. package/src/build.ts +0 -298
  144. package/src/cli.ts +0 -132
  145. package/src/context-internal.ts +0 -36
  146. package/src/context.ts +0 -5
  147. package/src/expose.ts +0 -34
  148. package/src/headers.ts +0 -19
  149. package/src/index.ts +0 -34
  150. package/src/jsonRpc.ts +0 -43
  151. package/src/server.ts +0 -384
@@ -0,0 +1,1266 @@
1
+ import parseQuery from 'fast-querystring'
2
+
3
+ import { Type } from '@sinclair/typebox'
4
+
5
+ export { Type as t }
6
+
7
+ import {
8
+ ComposeElysiaResponse,
9
+ CreateEden,
10
+ DefinitionBase,
11
+ EphemeralType,
12
+ ErrorHandler,
13
+ InlineHandler,
14
+ InputSchema,
15
+ JoinPath,
16
+ LocalHook,
17
+ MaybeArray,
18
+ MergeSchema,
19
+ MetadataBase,
20
+ PreHandler,
21
+ Prettify2,
22
+ Reconcile,
23
+ ResolvePath,
24
+ RouteBase,
25
+ RouteSchema,
26
+ SingletonBase,
27
+ UnwrapRoute
28
+ } from './elysia-fork/types.js'
29
+
30
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
31
+ // @ts-ignore
32
+ import OriginalRouter from '@medley/router'
33
+ import { TSchema } from '@sinclair/typebox'
34
+ import Ajv from 'ajv'
35
+ import { Context } from './elysia-fork/context.js'
36
+ import { isAsyncIterable } from './utils.js'
37
+ import { redirect } from './elysia-fork/utils.js'
38
+ import { ValidationError } from './elysia-fork/error.js'
39
+
40
+ const ajv = new Ajv()
41
+ // Should be exported from `hono/router`
42
+
43
+ type P = any
44
+
45
+ type AsyncResponse = Response | Promise<Response>
46
+
47
+ type OnError = (x: { error: any; request: Request }) => AsyncResponse
48
+
49
+ type RouterTree = {
50
+ router: OriginalRouter
51
+ prefix?: string
52
+ onRequestHandlers: Function[]
53
+ onErrorHandlers: OnError[]
54
+ children: RouterTree[]
55
+ store: Record<any, any>
56
+ }
57
+
58
+ type OnNoMatch = (request: Request, platform: P) => AsyncResponse
59
+
60
+ type InternalRouterState = {
61
+ hook: any
62
+ handler: any
63
+ // store: Record<any, any>
64
+ }
65
+ /**
66
+ * Router class
67
+ */
68
+ export class Elysia<
69
+ const in out BasePath extends string = '',
70
+ const in out Scoped extends boolean = true,
71
+ const in out Singleton extends SingletonBase = {
72
+ decorator: {}
73
+ store: {}
74
+ derive: {}
75
+ resolve: {}
76
+ },
77
+ const in out Definitions extends DefinitionBase = {
78
+ type: {}
79
+ error: {}
80
+ },
81
+ const in out Metadata extends MetadataBase = {
82
+ schema: {}
83
+ macro: {}
84
+ macroFn: {}
85
+ },
86
+ const out Routes extends RouteBase = {},
87
+ // ? scoped
88
+ const in out Ephemeral extends EphemeralType = {
89
+ derive: {}
90
+ resolve: {}
91
+ schema: {}
92
+ },
93
+ // ? local
94
+ const in out Volatile extends EphemeralType = {
95
+ derive: {}
96
+ resolve: {}
97
+ schema: {}
98
+ }
99
+ > {
100
+ private onNoMatch: OnNoMatch
101
+ // prefix: BasePath | undefined
102
+ routerTree: RouterTree
103
+
104
+ add({
105
+ method,
106
+ path,
107
+ ...rest
108
+ }: InternalRouterState & {
109
+ method: string
110
+ path: string
111
+ }) {
112
+ const router = this.routerTree
113
+ // if (router.prefix) {
114
+ // path = router.prefix + path
115
+ // }
116
+
117
+ const store = router.router.register(path)
118
+ store[method] = { ...rest }
119
+ }
120
+
121
+ private match(method: string, path: string) {
122
+ const result = bfs(this.routerTree, (router) => {
123
+ if (router.prefix && !path.startsWith(router.prefix)) {
124
+ // console.log(
125
+ // `router prefix: ${router.prefix} does not match path: ${path}`
126
+ // )
127
+ return
128
+ }
129
+ let pathWithoutPrefix = path
130
+ if (router.prefix) {
131
+ pathWithoutPrefix = path.replace(router.prefix, '')
132
+ }
133
+ // console.log(`router prefix: ${router.prefix} matches path: ${path}`)
134
+ const route = router.router.find(pathWithoutPrefix)
135
+ if (!route) {
136
+ return
137
+ }
138
+
139
+ let data: InternalRouterState = route['store'][method]
140
+ if (data) {
141
+ // console.log(`route found: ${method} ${path}`, route)
142
+
143
+ const { onErrorHandlers, onRequestHandlers } = router
144
+ const params = route['params'] || {}
145
+ return {
146
+ ...data,
147
+ router,
148
+ store: router.store,
149
+ onErrorHandlers,
150
+ onRequestHandlers,
151
+ params
152
+ }
153
+ }
154
+ })
155
+
156
+ return result
157
+ }
158
+
159
+ state<const Name extends string | number | symbol, Value>(
160
+ name: Name,
161
+ value: Value
162
+ ): Elysia<
163
+ BasePath,
164
+ Scoped,
165
+ {
166
+ decorator: Singleton['decorator']
167
+ store: Reconcile<
168
+ Singleton['store'],
169
+ {
170
+ [name in Name]: Value
171
+ }
172
+ >
173
+ derive: Singleton['derive']
174
+ resolve: Singleton['resolve']
175
+ },
176
+ Definitions,
177
+ Metadata,
178
+ Routes,
179
+ Ephemeral,
180
+ Volatile
181
+ > {
182
+ this.routerTree.store[name] = value
183
+ return this as any
184
+ }
185
+
186
+ /**
187
+ * Create a new Router
188
+ * @param options {@link RouterOptions} {@link Platform}
189
+ */
190
+ constructor(
191
+ options: {
192
+ /** Fallback handle if an error is thrown (500 response is default) */
193
+ // onError?: OnError
194
+ scoped?: Scoped
195
+ onNoMatch?: (request: Request, platform: P) => AsyncResponse
196
+ basePath?: BasePath
197
+ } = {}
198
+ ) {
199
+ this.scoped = options.scoped
200
+
201
+ this.onNoMatch =
202
+ options.onNoMatch ?? (() => new Response(null, { status: 404 }))
203
+ this.routerTree = {
204
+ router: new OriginalRouter(),
205
+ prefix: options.basePath,
206
+ onRequestHandlers: [],
207
+ onErrorHandlers: [],
208
+ children: [],
209
+ store: {}
210
+ }
211
+
212
+ // Bind router methods
213
+ // for (const method of METHODS) {
214
+ // this.#routes.set(method as Method, [])
215
+ // const key = method.toLowerCase() as Lowercase<Method>
216
+ // this[key as any] = this.#add.bind(this, method)
217
+ // }
218
+ }
219
+
220
+ _routes: Routes = {} as any
221
+
222
+ _types = {
223
+ Prefix: '' as BasePath,
224
+ Scoped: false as Scoped,
225
+ Singleton: {} as Singleton,
226
+ Definitions: {} as Definitions,
227
+ Metadata: {} as Metadata
228
+ }
229
+
230
+ _ephemeral = {} as Ephemeral
231
+ _volatile = {} as Volatile
232
+
233
+ /**
234
+ * ### post
235
+ * Register handler for path with method [POST]
236
+ *
237
+ * ---
238
+ * @example
239
+ * ```typescript
240
+ * import { Elysia, t } from 'elysia'
241
+ *
242
+ * new Elysia()
243
+ * .post('/', () => 'hi')
244
+ * .post('/with-hook', () => 'hi', {
245
+ * response: t.String()
246
+ * })
247
+ * ```
248
+ */
249
+ post<
250
+ const Path extends string,
251
+ const LocalSchema extends InputSchema<
252
+ keyof Definitions['type'] & string
253
+ >,
254
+ const Schema extends MergeSchema<
255
+ UnwrapRoute<LocalSchema, Definitions['type']>,
256
+ MergeSchema<
257
+ Volatile['schema'],
258
+ MergeSchema<Ephemeral['schema'], Metadata['schema']>
259
+ >
260
+ >,
261
+ const Handle extends InlineHandler<
262
+ Schema,
263
+ Singleton & {
264
+ derive: Ephemeral['derive'] & Volatile['derive']
265
+ resolve: Ephemeral['resolve'] & Volatile['resolve']
266
+ },
267
+ JoinPath<BasePath, Path>
268
+ >
269
+ >(
270
+ path: Path,
271
+ handler: Handle,
272
+ hook?: LocalHook<
273
+ LocalSchema,
274
+ Schema,
275
+ Singleton & {
276
+ derive: Ephemeral['derive'] & Volatile['derive']
277
+ resolve: Ephemeral['resolve'] & Volatile['resolve']
278
+ },
279
+ Definitions['error'],
280
+ Metadata['macro'],
281
+ JoinPath<BasePath, Path>
282
+ >
283
+ ): Elysia<
284
+ BasePath,
285
+ Scoped,
286
+ Singleton,
287
+ Definitions,
288
+ Metadata,
289
+ Routes &
290
+ CreateEden<
291
+ JoinPath<BasePath, Path>,
292
+ {
293
+ post: {
294
+ body: Schema['body']
295
+ params: undefined extends Schema['params']
296
+ ? ResolvePath<Path>
297
+ : Schema['params']
298
+ query: Schema['query']
299
+ headers: Schema['headers']
300
+ response: ComposeElysiaResponse<
301
+ Schema['response'],
302
+ Handle
303
+ >
304
+ }
305
+ }
306
+ >,
307
+ Ephemeral,
308
+ Volatile
309
+ > {
310
+ this.add({ method: 'POST', path, handler: handler, hook })
311
+
312
+ return this as any
313
+ }
314
+
315
+ get<
316
+ const Path extends string,
317
+ const LocalSchema extends InputSchema<
318
+ keyof Definitions['type'] & string
319
+ >,
320
+ const Schema extends MergeSchema<
321
+ UnwrapRoute<LocalSchema, Definitions['type']>,
322
+ MergeSchema<
323
+ Volatile['schema'],
324
+ MergeSchema<Ephemeral['schema'], Metadata['schema']>
325
+ >
326
+ >,
327
+ const Macro extends Metadata['macro'],
328
+ const Handle extends InlineHandler<
329
+ Schema,
330
+ Singleton & {
331
+ derive: Ephemeral['derive'] & Volatile['derive']
332
+ resolve: Ephemeral['resolve'] & Volatile['resolve']
333
+ },
334
+ JoinPath<BasePath, Path>
335
+ >
336
+ >(
337
+ path: Path,
338
+ handler: Handle,
339
+ hook?: LocalHook<
340
+ LocalSchema,
341
+ Schema,
342
+ Singleton & {
343
+ derive: Ephemeral['derive'] & Volatile['derive']
344
+ resolve: Ephemeral['resolve'] & Volatile['resolve']
345
+ },
346
+ Definitions['error'],
347
+ Macro,
348
+ JoinPath<BasePath, Path>
349
+ >
350
+ ): Elysia<
351
+ BasePath,
352
+ Scoped,
353
+ Singleton,
354
+ Definitions,
355
+ Metadata,
356
+ Routes &
357
+ CreateEden<
358
+ JoinPath<BasePath, Path>,
359
+ {
360
+ get: {
361
+ body: Schema['body']
362
+ params: undefined extends Schema['params']
363
+ ? ResolvePath<Path>
364
+ : Schema['params']
365
+ query: Schema['query']
366
+ headers: Schema['headers']
367
+ response: ComposeElysiaResponse<
368
+ Schema['response'],
369
+ Handle
370
+ >
371
+ }
372
+ }
373
+ >,
374
+ Ephemeral,
375
+ Volatile
376
+ > {
377
+ this.add({ method: 'GET', path, handler: handler, hook })
378
+ return this as any
379
+ }
380
+
381
+ put<
382
+ const Path extends string,
383
+ const LocalSchema extends InputSchema<
384
+ keyof Definitions['type'] & string
385
+ >,
386
+ const Schema extends MergeSchema<
387
+ UnwrapRoute<LocalSchema, Definitions['type']>,
388
+ MergeSchema<
389
+ Volatile['schema'],
390
+ MergeSchema<Ephemeral['schema'], Metadata['schema']>
391
+ >
392
+ >,
393
+ const Handle extends InlineHandler<
394
+ Schema,
395
+ Singleton & {
396
+ derive: Ephemeral['derive'] & Volatile['derive']
397
+ resolve: Ephemeral['resolve'] & Volatile['resolve']
398
+ },
399
+ JoinPath<BasePath, Path>
400
+ >
401
+ >(
402
+ path: Path,
403
+ handler: Handle,
404
+ hook?: LocalHook<
405
+ LocalSchema,
406
+ Schema,
407
+ Singleton & {
408
+ derive: Ephemeral['derive'] & Volatile['derive']
409
+ resolve: Ephemeral['resolve'] & Volatile['resolve']
410
+ },
411
+ Definitions['error'],
412
+ Metadata['macro'],
413
+ JoinPath<BasePath, Path>
414
+ >
415
+ ): Elysia<
416
+ BasePath,
417
+ Scoped,
418
+ Singleton,
419
+ Definitions,
420
+ Metadata,
421
+ Routes &
422
+ CreateEden<
423
+ JoinPath<BasePath, Path>,
424
+ {
425
+ put: {
426
+ body: Schema['body']
427
+ params: undefined extends Schema['params']
428
+ ? ResolvePath<Path>
429
+ : Schema['params']
430
+ query: Schema['query']
431
+ headers: Schema['headers']
432
+ response: ComposeElysiaResponse<
433
+ Schema['response'],
434
+ Handle
435
+ >
436
+ }
437
+ }
438
+ >,
439
+ Ephemeral,
440
+ Volatile
441
+ > {
442
+ this.add({ method: 'PUT', path, handler: handler, hook })
443
+
444
+ return this as any
445
+ }
446
+
447
+ patch<
448
+ const Path extends string,
449
+ const LocalSchema extends InputSchema<
450
+ keyof Definitions['type'] & string
451
+ >,
452
+ const Schema extends MergeSchema<
453
+ UnwrapRoute<LocalSchema, Definitions['type']>,
454
+ MergeSchema<
455
+ Volatile['schema'],
456
+ MergeSchema<Ephemeral['schema'], Metadata['schema']>
457
+ >
458
+ >,
459
+ const Handle extends InlineHandler<
460
+ Schema,
461
+ Singleton & {
462
+ derive: Ephemeral['derive'] & Volatile['derive']
463
+ resolve: Ephemeral['resolve'] & Volatile['resolve']
464
+ },
465
+ JoinPath<BasePath, Path>
466
+ >
467
+ >(
468
+ path: Path,
469
+ handler: Handle,
470
+ hook?: LocalHook<
471
+ LocalSchema,
472
+ Schema,
473
+ Singleton & {
474
+ derive: Ephemeral['derive'] & Volatile['derive']
475
+ resolve: Ephemeral['resolve'] & Volatile['resolve']
476
+ },
477
+ Definitions['error'],
478
+ Metadata['macro'],
479
+ JoinPath<BasePath, Path>
480
+ >
481
+ ): Elysia<
482
+ BasePath,
483
+ Scoped,
484
+ Singleton,
485
+ Definitions,
486
+ Metadata,
487
+ Routes &
488
+ CreateEden<
489
+ JoinPath<BasePath, Path>,
490
+ {
491
+ patch: {
492
+ body: Schema['body']
493
+ params: undefined extends Schema['params']
494
+ ? ResolvePath<Path>
495
+ : Schema['params']
496
+ query: Schema['query']
497
+ headers: Schema['headers']
498
+ response: ComposeElysiaResponse<
499
+ Schema['response'],
500
+ Handle
501
+ >
502
+ }
503
+ }
504
+ >,
505
+ Ephemeral,
506
+ Volatile
507
+ > {
508
+ this.add({ method: 'PATCH', path, handler: handler, hook })
509
+
510
+ return this as any
511
+ }
512
+
513
+ delete<
514
+ const Path extends string,
515
+ const LocalSchema extends InputSchema<
516
+ keyof Definitions['type'] & string
517
+ >,
518
+ const Schema extends MergeSchema<
519
+ UnwrapRoute<LocalSchema, Definitions['type']>,
520
+ MergeSchema<
521
+ Volatile['schema'],
522
+ MergeSchema<Ephemeral['schema'], Metadata['schema']>
523
+ >
524
+ >,
525
+ const Handle extends InlineHandler<
526
+ Schema,
527
+ Singleton & {
528
+ derive: Ephemeral['derive'] & Volatile['derive']
529
+ resolve: Ephemeral['resolve'] & Volatile['resolve']
530
+ },
531
+ JoinPath<BasePath, Path>
532
+ >
533
+ >(
534
+ path: Path,
535
+ handler: Handle,
536
+ hook?: LocalHook<
537
+ LocalSchema,
538
+ Schema,
539
+ Singleton & {
540
+ derive: Ephemeral['derive'] & Volatile['derive']
541
+ resolve: Ephemeral['resolve'] & Volatile['resolve']
542
+ },
543
+ Definitions['error'],
544
+ Metadata['macro'],
545
+ JoinPath<BasePath, Path>
546
+ >
547
+ ): Elysia<
548
+ BasePath,
549
+ Scoped,
550
+ Singleton,
551
+ Definitions,
552
+ Metadata,
553
+ Routes &
554
+ CreateEden<
555
+ JoinPath<BasePath, Path>,
556
+ {
557
+ delete: {
558
+ body: Schema['body']
559
+ params: undefined extends Schema['params']
560
+ ? ResolvePath<Path>
561
+ : Schema['params']
562
+ query: Schema['query']
563
+ headers: Schema['headers']
564
+ response: ComposeElysiaResponse<
565
+ Schema['response'],
566
+ Handle
567
+ >
568
+ }
569
+ }
570
+ >,
571
+ Ephemeral,
572
+ Volatile
573
+ > {
574
+ this.add({ method: 'DELETE', path, handler: handler, hook })
575
+
576
+ return this as any
577
+ }
578
+
579
+ options<
580
+ const Path extends string,
581
+ const LocalSchema extends InputSchema<
582
+ keyof Definitions['type'] & string
583
+ >,
584
+ const Schema extends MergeSchema<
585
+ UnwrapRoute<LocalSchema, Definitions['type']>,
586
+ MergeSchema<
587
+ Volatile['schema'],
588
+ MergeSchema<Ephemeral['schema'], Metadata['schema']>
589
+ >
590
+ >,
591
+ const Handle extends InlineHandler<
592
+ Schema,
593
+ Singleton & {
594
+ derive: Ephemeral['derive'] & Volatile['derive']
595
+ resolve: Ephemeral['resolve'] & Volatile['resolve']
596
+ },
597
+ JoinPath<BasePath, Path>
598
+ >
599
+ >(
600
+ path: Path,
601
+ handler: Handle,
602
+ hook?: LocalHook<
603
+ LocalSchema,
604
+ Schema,
605
+ Singleton & {
606
+ derive: Ephemeral['derive'] & Volatile['derive']
607
+ resolve: Ephemeral['resolve'] & Volatile['resolve']
608
+ },
609
+ Definitions['error'],
610
+ Metadata['macro'],
611
+ JoinPath<BasePath, Path>
612
+ >
613
+ ): Elysia<
614
+ BasePath,
615
+ Scoped,
616
+ Singleton,
617
+ Definitions,
618
+ Metadata,
619
+ Routes &
620
+ CreateEden<
621
+ JoinPath<BasePath, Path>,
622
+ {
623
+ options: {
624
+ body: Schema['body']
625
+ params: undefined extends Schema['params']
626
+ ? ResolvePath<Path>
627
+ : Schema['params']
628
+ query: Schema['query']
629
+ headers: Schema['headers']
630
+ response: ComposeElysiaResponse<
631
+ Schema['response'],
632
+ Handle
633
+ >
634
+ }
635
+ }
636
+ >,
637
+ Ephemeral,
638
+ Volatile
639
+ > {
640
+ this.add({ method: 'OPTIONS', path, handler: handler, hook })
641
+
642
+ return this as any
643
+ }
644
+
645
+ all<
646
+ const Path extends string,
647
+ const LocalSchema extends InputSchema<
648
+ keyof Definitions['type'] & string
649
+ >,
650
+ const Schema extends MergeSchema<
651
+ UnwrapRoute<LocalSchema, Definitions['type']>,
652
+ MergeSchema<
653
+ Volatile['schema'],
654
+ MergeSchema<Ephemeral['schema'], Metadata['schema']>
655
+ >
656
+ >,
657
+ const Handle extends InlineHandler<
658
+ Schema,
659
+ Singleton & {
660
+ derive: Ephemeral['derive'] & Volatile['derive']
661
+ resolve: Ephemeral['resolve'] & Volatile['resolve']
662
+ },
663
+ JoinPath<BasePath, Path>
664
+ >
665
+ >(
666
+ path: Path,
667
+ handler: Handle,
668
+ hook?: LocalHook<
669
+ LocalSchema,
670
+ Schema,
671
+ Singleton & {
672
+ derive: Ephemeral['derive'] & Volatile['derive']
673
+ resolve: Ephemeral['resolve'] & Volatile['resolve']
674
+ },
675
+ Definitions['error'],
676
+ Metadata['macro'],
677
+ JoinPath<BasePath, Path>
678
+ >
679
+ ): Elysia<
680
+ BasePath,
681
+ Scoped,
682
+ Singleton,
683
+ Definitions,
684
+ Metadata,
685
+ Routes &
686
+ CreateEden<
687
+ JoinPath<BasePath, Path>,
688
+ {
689
+ [method in string]: {
690
+ body: Schema['body']
691
+ params: undefined extends Schema['params']
692
+ ? ResolvePath<Path>
693
+ : Schema['params']
694
+ query: Schema['query']
695
+ headers: Schema['headers']
696
+ response: ComposeElysiaResponse<
697
+ Schema['response'],
698
+ Handle
699
+ >
700
+ }
701
+ }
702
+ >,
703
+ Ephemeral,
704
+ Volatile
705
+ > {
706
+ for (const method of METHODS) {
707
+ this.add({ method, path, handler: handler, hook })
708
+ }
709
+
710
+ return this as any
711
+ }
712
+
713
+ head<
714
+ const Path extends string,
715
+ const LocalSchema extends InputSchema<
716
+ keyof Definitions['type'] & string
717
+ >,
718
+ const Schema extends MergeSchema<
719
+ UnwrapRoute<LocalSchema, Definitions['type']>,
720
+ MergeSchema<
721
+ Volatile['schema'],
722
+ MergeSchema<Ephemeral['schema'], Metadata['schema']>
723
+ >
724
+ >,
725
+ const Handle extends InlineHandler<
726
+ Schema,
727
+ Singleton & {
728
+ derive: Ephemeral['derive'] & Volatile['derive']
729
+ resolve: Ephemeral['resolve'] & Volatile['resolve']
730
+ },
731
+ JoinPath<BasePath, Path>
732
+ >
733
+ >(
734
+ path: Path,
735
+ handler: Handle,
736
+ hook?: LocalHook<
737
+ LocalSchema,
738
+ Schema,
739
+ Singleton & {
740
+ derive: Ephemeral['derive'] & Volatile['derive']
741
+ resolve: Ephemeral['resolve'] & Volatile['resolve']
742
+ },
743
+ Definitions['error'],
744
+ Metadata['macro'],
745
+ JoinPath<BasePath, Path>
746
+ >
747
+ ): Elysia<
748
+ BasePath,
749
+ Scoped,
750
+ Singleton,
751
+ Definitions,
752
+ Metadata,
753
+ Routes &
754
+ CreateEden<
755
+ JoinPath<BasePath, Path>,
756
+ {
757
+ head: {
758
+ body: Schema['body']
759
+ params: undefined extends Schema['params']
760
+ ? ResolvePath<Path>
761
+ : Schema['params']
762
+ query: Schema['query']
763
+ headers: Schema['headers']
764
+ response: ComposeElysiaResponse<
765
+ Schema['response'],
766
+ Handle
767
+ >
768
+ }
769
+ }
770
+ >,
771
+ Ephemeral,
772
+ Volatile
773
+ > {
774
+ this.add({ method: 'HEAD', path, handler: handler, hook })
775
+
776
+ return this as any
777
+ }
778
+
779
+ /**
780
+ * If set to true, other Elysia handler will not inherits global life-cycle, store, decorators from the current instance
781
+ *
782
+ * @default false
783
+ */
784
+ scoped?: Scoped
785
+ get _scoped() {
786
+ return this.scoped as Scoped
787
+ }
788
+
789
+ // group is not needed, you can add another prefixed app instead
790
+ // group<
791
+ // const Prefix extends string,
792
+ // const NewElysia extends Elysia<any, any, any, any, any, any, any, any>
793
+ // >(
794
+ // prefix: Prefix,
795
+ // run: (
796
+ // group: Elysia<
797
+ // `${BasePath}${Prefix}`,
798
+ // Scoped,
799
+ // Singleton,
800
+ // Definitions,
801
+ // Metadata,
802
+ // {},
803
+ // Ephemeral,
804
+ // Volatile
805
+ // >
806
+ // ) => NewElysia
807
+ // ): Elysia<
808
+ // BasePath,
809
+ // Scoped,
810
+ // Singleton,
811
+ // Definitions,
812
+ // Metadata,
813
+ // Prettify<Routes & NewElysia['_routes']>,
814
+ // Ephemeral,
815
+ // Volatile
816
+ // > {
817
+ // let thisRouter = this.routers[0]
818
+ // this.routers.push(
819
+ // ...instance.routers.map((r) => ({
820
+ // ...r,
821
+ // prefix: (thisRouter.prefix || '') + r.prefix
822
+ // }))
823
+ // )
824
+
825
+ // return this
826
+ // }
827
+
828
+ use<const NewElysia extends AnyElysia>(
829
+ instance: NewElysia
830
+ ): NewElysia['_scoped'] extends false
831
+ ? Elysia<
832
+ BasePath,
833
+ Scoped,
834
+ // @ts-expect-error - This is truly ideal
835
+ Prettify2<Singleton & NewElysia['_types']['Singleton']>,
836
+ Prettify2<Definitions & NewElysia['_types']['Definitions']>,
837
+ Prettify2<Metadata & NewElysia['_types']['Metadata']>,
838
+ BasePath extends ``
839
+ ? Routes & NewElysia['_routes']
840
+ : Routes & CreateEden<BasePath, NewElysia['_routes']>,
841
+ Ephemeral,
842
+ Prettify2<Volatile & NewElysia['_ephemeral']>
843
+ >
844
+ : Elysia<
845
+ BasePath,
846
+ Scoped,
847
+ Singleton,
848
+ Definitions,
849
+ Metadata,
850
+ BasePath extends ``
851
+ ? Routes & NewElysia['_routes']
852
+ : Routes & CreateEden<BasePath, NewElysia['_routes']>,
853
+ Ephemeral,
854
+ Volatile
855
+ > {
856
+ const thisRouter = this.routerTree
857
+ // TODO use scoped logic to add onRequest and onError on all routers if necessary, add them first
858
+ this.routerTree.children.push(
859
+ mapBfs(instance.routerTree, (r) => {
860
+ return {
861
+ ...r,
862
+ prefix: (thisRouter.prefix || '') + r.prefix
863
+ }
864
+ })
865
+ )
866
+ return this as any
867
+ }
868
+
869
+ onError<const Schema extends RouteSchema>(
870
+ handler: MaybeArray<
871
+ ErrorHandler<
872
+ Definitions['error'],
873
+ MergeSchema<
874
+ Schema,
875
+ MergeSchema<
876
+ Volatile['schema'],
877
+ MergeSchema<Ephemeral['schema'], Metadata['schema']>
878
+ >
879
+ >,
880
+ Singleton,
881
+ Ephemeral,
882
+ Volatile
883
+ >
884
+ >
885
+ ): this {
886
+ const router = this.routerTree
887
+
888
+ router.onErrorHandlers ??= []
889
+ router.onErrorHandlers.push(handler as any)
890
+
891
+ return this
892
+ }
893
+
894
+ onRequest<const Schema extends RouteSchema>(
895
+ handler: MaybeArray<
896
+ PreHandler<
897
+ MergeSchema<
898
+ Schema,
899
+ MergeSchema<
900
+ Volatile['schema'],
901
+ MergeSchema<Ephemeral['schema'], Metadata['schema']>
902
+ >
903
+ >,
904
+ {
905
+ decorator: Singleton['decorator']
906
+ store: Singleton['store']
907
+ derive: {}
908
+ resolve: {}
909
+ }
910
+ >
911
+ >
912
+ ) {
913
+ const router = this.routerTree
914
+ router.onRequestHandlers ??= []
915
+ router.onRequestHandlers.push(handler as any)
916
+
917
+ return this
918
+ }
919
+ /**
920
+ * Pass a request through all matching route handles and return a response
921
+ * @param request The `Request`
922
+ * @param platform Platform specific context {@link Platform}
923
+ * @returns The final `Response`
924
+ */
925
+ async handle(request: Request, platform?: P): Promise<Response> {
926
+ platform ??= {} as P
927
+ let u = new URL(request.url)
928
+ let path = u.pathname + u.search
929
+ const defaultContext = {
930
+ redirect,
931
+ error: null,
932
+ path
933
+ }
934
+ let onErrorHandlers: OnError[] = []
935
+ try {
936
+ let response: Response | undefined
937
+ // Get all middleware and method specific routes in order
938
+
939
+ const route = this.match(request.method, path)
940
+ if (!route) {
941
+ return this.onNoMatch(request, platform)
942
+ }
943
+ onErrorHandlers = this.getRouteAndParents(route.router).flatMap(
944
+ (x) => x.onErrorHandlers
945
+ )
946
+ const { params, store } = route
947
+ const onReq = this.getRouteAndParents(route.router).flatMap(
948
+ (x) => x.onRequestHandlers
949
+ )
950
+ // TODO add content type
951
+
952
+ let content = route?.hook?.content
953
+ let body = await getRequestBody({ request, content })
954
+ let bodySchema: TSchema = route?.hook?.body
955
+ if (bodySchema) {
956
+ const validate = ajv.compile(bodySchema)
957
+ const valid = validate(body)
958
+ if (!valid) {
959
+ const error = ajv.errorsText(validate.errors, {
960
+ separator: '\n'
961
+ })
962
+
963
+ return new Response(error, {
964
+ status: 400,
965
+ headers: {
966
+ 'content-type': 'text/plain'
967
+ }
968
+ })
969
+ }
970
+ }
971
+ if (onReq.length > 0) {
972
+ for (const handler of onReq) {
973
+ const res = await handler({
974
+ request,
975
+ response,
976
+ store,
977
+ path
978
+ } satisfies Context<any, any, any>)
979
+ if (res) {
980
+ return await turnHandlerResultIntoResponse(res)
981
+ }
982
+ }
983
+ }
984
+
985
+ // console.log(route)
986
+
987
+ const res = route.handler({
988
+ ...defaultContext,
989
+ request,
990
+ response,
991
+ params: params as any,
992
+ store,
993
+ body,
994
+ path
995
+
996
+ // platform
997
+ } satisfies Context<any, any, string>)
998
+ if (isAsyncIterable(res)) {
999
+ return await this.handleStream({
1000
+ generator: res,
1001
+ request,
1002
+ onErrorHandlers
1003
+ })
1004
+ }
1005
+
1006
+ return await turnHandlerResultIntoResponse(res)
1007
+ } catch (err: any) {
1008
+ let res = await this.runErrorHandlers({
1009
+ onErrorHandlers,
1010
+ error: err,
1011
+ request
1012
+ })
1013
+ if (res) return res
1014
+ return new Response(err?.message || 'Internal Server Error', {
1015
+ status: 500
1016
+ })
1017
+ }
1018
+ }
1019
+
1020
+ private async runErrorHandlers({
1021
+ onErrorHandlers = [] as OnError[],
1022
+ error: err,
1023
+ request
1024
+ }) {
1025
+ if (onErrorHandlers.length === 0) {
1026
+ console.error(`Spiceflow unhandled error:`, err)
1027
+ } else {
1028
+ for (const errHandler of onErrorHandlers) {
1029
+ const res = errHandler({ error: err, request })
1030
+ if (res instanceof Response) {
1031
+ return res
1032
+ }
1033
+ }
1034
+ }
1035
+ }
1036
+
1037
+ private getRouteAndParents(currentRouter?: RouterTree) {
1038
+ const parents: RouterTree[] = []
1039
+ let current = currentRouter
1040
+
1041
+ // Perform BFS once to build a parent map
1042
+ const parentMap = new Map<RouterTree, RouterTree>()
1043
+ bfs(this.routerTree, (node) => {
1044
+ for (const child of node.children) {
1045
+ parentMap.set(child, node)
1046
+ }
1047
+ })
1048
+
1049
+ // Traverse the parent map to get the parents
1050
+ while (current) {
1051
+ parents.unshift(current)
1052
+ current = parentMap.get(current)
1053
+ }
1054
+
1055
+ return parents.reverse().filter((x) => x !== undefined)
1056
+ }
1057
+
1058
+ async handleStream({
1059
+ onErrorHandlers,
1060
+ generator,
1061
+ request
1062
+ }: {
1063
+ generator: Generator | AsyncGenerator
1064
+ onErrorHandlers: OnError[]
1065
+ request: Request
1066
+ }) {
1067
+ let init = generator.next()
1068
+ if (init instanceof Promise) init = await init
1069
+
1070
+ if (init?.done) {
1071
+ return await turnHandlerResultIntoResponse(init.value)
1072
+ }
1073
+ // let errorHandlers = this.routerTree.onErrorHandlers
1074
+ let self = this
1075
+ return new Response(
1076
+ new ReadableStream({
1077
+ async start(controller) {
1078
+ let end = false
1079
+
1080
+ request?.signal.addEventListener('abort', () => {
1081
+ end = true
1082
+
1083
+ try {
1084
+ controller.close()
1085
+ } catch {
1086
+ // nothing
1087
+ }
1088
+ })
1089
+
1090
+ if (init?.value !== undefined && init?.value !== null)
1091
+ controller.enqueue(
1092
+ Buffer.from(
1093
+ `event: message\ndata: ${JSON.stringify(
1094
+ init.value
1095
+ )}\n\n`
1096
+ )
1097
+ )
1098
+
1099
+ try {
1100
+ for await (const chunk of generator) {
1101
+ if (end) break
1102
+ if (chunk === undefined || chunk === null) continue
1103
+
1104
+ controller.enqueue(
1105
+ Buffer.from(
1106
+ `event: message\ndata: ${JSON.stringify(
1107
+ chunk
1108
+ )}\n\n`
1109
+ )
1110
+ )
1111
+ }
1112
+ } catch (error: any) {
1113
+ let res = await self.runErrorHandlers({
1114
+ onErrorHandlers: onErrorHandlers,
1115
+ error,
1116
+ request
1117
+ })
1118
+ controller.enqueue(
1119
+ Buffer.from(
1120
+ `event: error\ndata: ${JSON.stringify(
1121
+ error.message || error.name || 'Error'
1122
+ )}\n\n`
1123
+ )
1124
+ )
1125
+ }
1126
+
1127
+ try {
1128
+ controller.close()
1129
+ } catch {
1130
+ // nothing
1131
+ }
1132
+ }
1133
+ }),
1134
+ {
1135
+ // TODO add headers somehow
1136
+ headers: {
1137
+ // Manually set transfer-encoding for direct response, eg. app.handle, eden
1138
+ 'transfer-encoding': 'chunked',
1139
+ 'content-type': 'text/event-stream; charset=utf-8'
1140
+ // ...set?.headers
1141
+ }
1142
+ }
1143
+ )
1144
+ }
1145
+ }
1146
+
1147
+ async function getRequestBody({
1148
+ request,
1149
+ content
1150
+ }: {
1151
+ content
1152
+ request: Request
1153
+ }) {
1154
+ let body: string | Record<string, any> | undefined
1155
+ if (request.method === 'GET' || request.method === 'HEAD') {
1156
+ return
1157
+ }
1158
+
1159
+ const contentType =
1160
+ content || request.headers.get('content-type')?.split(';')?.[0]
1161
+
1162
+ if (!contentType) {
1163
+ return
1164
+ }
1165
+
1166
+ switch (contentType) {
1167
+ case 'application/json':
1168
+ body = (await request.json()) as any
1169
+ break
1170
+
1171
+ case 'text/plain':
1172
+ body = await request.text()
1173
+ break
1174
+
1175
+ case 'application/x-www-form-urlencoded':
1176
+ body = parseQuery.parse(await request.text()) as any
1177
+ break
1178
+
1179
+ case 'application/octet-stream':
1180
+ body = await request.arrayBuffer()
1181
+ break
1182
+
1183
+ case 'multipart/form-data':
1184
+ body = {}
1185
+
1186
+ const form = await request.formData()
1187
+ for (const key of form.keys()) {
1188
+ if (body[key]) continue
1189
+
1190
+ const value = form.getAll(key)
1191
+ if (value.length === 1) body[key] = value[0]
1192
+ else body[key] = value
1193
+ }
1194
+
1195
+ break
1196
+ }
1197
+
1198
+ return body
1199
+ }
1200
+
1201
+ const METHODS = [
1202
+ 'ALL',
1203
+ 'CONNECT',
1204
+ 'DELETE',
1205
+ 'GET',
1206
+ 'HEAD',
1207
+ 'OPTIONS',
1208
+ 'PATCH',
1209
+ 'POST',
1210
+ 'PUT',
1211
+ 'TRACE'
1212
+ ] as const
1213
+
1214
+ /** HTTP method string */
1215
+ export type Method = (typeof METHODS)[number]
1216
+
1217
+ function bfs<T>(
1218
+ tree: RouterTree,
1219
+ onNode: (node: RouterTree) => T | undefined | void
1220
+ ): T | undefined {
1221
+ const queue = [tree]
1222
+ while (queue.length > 0) {
1223
+ const node = queue.shift()!
1224
+ const result = onNode(node)
1225
+ if (result) {
1226
+ return result
1227
+ }
1228
+ queue.push(...node.children)
1229
+ }
1230
+ return undefined
1231
+ }
1232
+
1233
+ function mapBfs(
1234
+ tree: RouterTree,
1235
+ mapper: (node: RouterTree) => RouterTree
1236
+ ): RouterTree {
1237
+ const queue = [tree]
1238
+ const result: RouterTree = { ...mapper(tree), children: [] }
1239
+ while (queue.length > 0) {
1240
+ const node = queue.shift()!
1241
+ const mappedNode = mapper(node)
1242
+ result.children.push(mappedNode)
1243
+ queue.push(...mappedNode.children)
1244
+ }
1245
+ return result
1246
+ }
1247
+
1248
+ export async function turnHandlerResultIntoResponse(result: any) {
1249
+ // if user returns not a response, convert to json
1250
+ if (result instanceof Response) {
1251
+ return result
1252
+ }
1253
+ // if user returns a promise, await it
1254
+ if (result instanceof Promise) {
1255
+ result = await result
1256
+ }
1257
+ // // if user returns a string, convert to json
1258
+ // if (typeof result === 'string') {
1259
+ // result = new Response(result)
1260
+ // }
1261
+ // if user returns an object, convert to json
1262
+
1263
+ return new Response(JSON.stringify(result))
1264
+ }
1265
+
1266
+ export type AnyElysia = Elysia<any, any, any, any, any, any, any, any>