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.
- package/README.md +1 -171
- package/dist/client/errors.d.ts +7 -0
- package/dist/client/errors.d.ts.map +1 -0
- package/dist/client/errors.js +18 -0
- package/dist/client/errors.js.map +1 -0
- package/dist/client/index.d.ts +14 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +376 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/types.d.ts +87 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +2 -0
- package/dist/client/types.js.map +1 -0
- package/dist/client/utils.d.ts +2 -0
- package/dist/client/utils.d.ts.map +1 -0
- package/dist/client/utils.js +9 -0
- package/dist/client/utils.js.map +1 -0
- package/dist/client/ws.d.ts +15 -0
- package/dist/client/ws.d.ts.map +1 -0
- package/dist/client/ws.js +51 -0
- package/dist/client/ws.js.map +1 -0
- package/dist/client.test.d.ts +2 -0
- package/dist/client.test.d.ts.map +1 -0
- package/dist/client.test.js +237 -0
- package/dist/client.test.js.map +1 -0
- package/dist/elysia-fork/context.d.ts +87 -0
- package/dist/elysia-fork/context.d.ts.map +1 -0
- package/dist/elysia-fork/context.js +2 -0
- package/dist/elysia-fork/context.js.map +1 -0
- package/dist/elysia-fork/error.d.ts +246 -0
- package/dist/elysia-fork/error.d.ts.map +1 -0
- package/dist/elysia-fork/error.js +195 -0
- package/dist/elysia-fork/error.js.map +1 -0
- package/dist/elysia-fork/types.d.ts +652 -0
- package/dist/elysia-fork/types.d.ts.map +1 -0
- package/dist/elysia-fork/types.js +3 -0
- package/dist/elysia-fork/types.js.map +1 -0
- package/dist/elysia-fork/utils.d.ts +134 -0
- package/dist/elysia-fork/utils.d.ts.map +1 -0
- package/dist/elysia-fork/utils.js +70 -0
- package/dist/elysia-fork/utils.js.map +1 -0
- package/dist/spiceflow.d.ts +253 -0
- package/dist/spiceflow.d.ts.map +1 -0
- package/dist/spiceflow.js +500 -0
- package/dist/spiceflow.js.map +1 -0
- package/dist/spiceflow.test.d.ts +2 -0
- package/dist/spiceflow.test.d.ts.map +1 -0
- package/dist/spiceflow.test.js +225 -0
- package/dist/spiceflow.test.js.map +1 -0
- package/dist/stream.test.d.ts +2 -0
- package/dist/stream.test.d.ts.map +1 -0
- package/dist/stream.test.js +286 -0
- package/dist/stream.test.js.map +1 -0
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +4 -20
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +17 -46
- package/dist/utils.js.map +1 -1
- package/package.json +12 -36
- package/src/client/errors.ts +21 -0
- package/src/client/index.ts +539 -0
- package/src/client/types.ts +233 -0
- package/src/client/utils.ts +7 -0
- package/src/client/ws.ts +99 -0
- package/src/client.test.ts +235 -0
- package/src/elysia-fork/context.ts +196 -0
- package/src/elysia-fork/error.ts +293 -0
- package/src/elysia-fork/types.ts +1454 -0
- package/src/elysia-fork/utils.ts +85 -0
- package/src/spiceflow.test.ts +290 -0
- package/src/spiceflow.ts +1266 -0
- package/src/stream.test.ts +342 -0
- package/src/types.ts +0 -0
- package/src/utils.ts +21 -70
- package/context.d.ts +0 -2
- package/context.js +0 -1
- package/dist/babel.test.d.ts +0 -2
- package/dist/babel.test.d.ts.map +0 -1
- package/dist/babel.test.js +0 -27
- package/dist/babel.test.js.map +0 -1
- package/dist/babelDebugOutputs.d.ts +0 -9
- package/dist/babelDebugOutputs.d.ts.map +0 -1
- package/dist/babelDebugOutputs.js +0 -34
- package/dist/babelDebugOutputs.js.map +0 -1
- package/dist/babelTransformRpc.d.ts +0 -19
- package/dist/babelTransformRpc.d.ts.map +0 -1
- package/dist/babelTransformRpc.js +0 -285
- package/dist/babelTransformRpc.js.map +0 -1
- package/dist/browser.d.ts +0 -8
- package/dist/browser.d.ts.map +0 -1
- package/dist/browser.js +0 -126
- package/dist/browser.js.map +0 -1
- package/dist/build.d.ts +0 -13
- package/dist/build.d.ts.map +0 -1
- package/dist/build.js +0 -230
- package/dist/build.js.map +0 -1
- package/dist/cli.d.ts +0 -3
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -111
- package/dist/cli.js.map +0 -1
- package/dist/context-internal.d.ts +0 -20
- package/dist/context-internal.d.ts.map +0 -1
- package/dist/context-internal.js +0 -16
- package/dist/context-internal.js.map +0 -1
- package/dist/context.d.ts +0 -2
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js +0 -2
- package/dist/context.js.map +0 -1
- package/dist/expose.d.ts +0 -6
- package/dist/expose.d.ts.map +0 -1
- package/dist/expose.js +0 -32
- package/dist/expose.js.map +0 -1
- package/dist/headers.d.ts +0 -2
- package/dist/headers.d.ts.map +0 -1
- package/dist/headers.js +0 -18
- package/dist/headers.js.map +0 -1
- package/dist/index.d.ts +0 -8
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -23
- package/dist/index.js.map +0 -1
- package/dist/jsonRpc.d.ts +0 -32
- package/dist/jsonRpc.d.ts.map +0 -1
- package/dist/jsonRpc.js +0 -3
- package/dist/jsonRpc.js.map +0 -1
- package/dist/server.d.ts +0 -32
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js +0 -292
- package/dist/server.js.map +0 -1
- package/headers.d.ts +0 -2
- package/headers.js +0 -1
- package/sdk-template/package.json +0 -22
- package/sdk-template/src/index.ts +0 -2
- package/sdk-template/src/v1/example.ts +0 -5
- package/sdk-template/src/v1/generator.ts +0 -12
- package/sdk-template/tsconfig.json +0 -16
- package/src/babel.test.ts +0 -35
- package/src/babelDebugOutputs.ts +0 -56
- package/src/babelTransformRpc.ts +0 -394
- package/src/browser.ts +0 -141
- package/src/build.ts +0 -298
- package/src/cli.ts +0 -132
- package/src/context-internal.ts +0 -36
- package/src/context.ts +0 -5
- package/src/expose.ts +0 -34
- package/src/headers.ts +0 -19
- package/src/index.ts +0 -34
- package/src/jsonRpc.ts +0 -43
- package/src/server.ts +0 -384
package/src/spiceflow.ts
ADDED
|
@@ -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>
|