spiceflow 1.0.8 → 1.1.1
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 +23 -8
- package/dist/benchmark.test.d.ts +2 -0
- package/dist/benchmark.test.d.ts.map +1 -0
- package/dist/benchmark.test.js +8 -0
- package/dist/benchmark.test.js.map +1 -0
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/types.d.ts +1 -1
- package/dist/client/types.d.ts.map +1 -1
- package/dist/client/ws.d.ts +1 -1
- package/dist/client/ws.d.ts.map +1 -1
- package/dist/client.test.js +1 -18
- package/dist/client.test.js.map +1 -1
- package/dist/{elysia-fork/context.d.ts → context.d.ts} +8 -7
- package/dist/context.d.ts.map +1 -0
- package/dist/{elysia-fork/context.js.map → context.js.map} +1 -1
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js.map +1 -0
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/middleware.test.d.ts +2 -0
- package/dist/middleware.test.d.ts.map +1 -0
- package/dist/middleware.test.js +99 -0
- package/dist/middleware.test.js.map +1 -0
- package/dist/openapi.d.ts +4 -15
- package/dist/openapi.d.ts.map +1 -1
- package/dist/spiceflow.d.ts +41 -120
- package/dist/spiceflow.d.ts.map +1 -1
- package/dist/spiceflow.js +223 -169
- package/dist/spiceflow.js.map +1 -1
- package/dist/spiceflow.test.js +54 -16
- package/dist/spiceflow.test.js.map +1 -1
- package/dist/types.d.ts +407 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils.d.ts +72 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +69 -0
- package/dist/utils.js.map +1 -1
- package/package.json +1 -1
- package/src/benchmark.test.ts +8 -0
- package/src/client/index.ts +1 -3
- package/src/client/types.ts +6 -13
- package/src/client/ws.ts +1 -1
- package/src/client.test.ts +1 -19
- package/src/context.ts +128 -0
- package/src/index.ts +1 -2
- package/src/middleware.test.ts +107 -0
- package/src/openapi.ts +1 -1
- package/src/spiceflow.test.ts +74 -16
- package/src/spiceflow.ts +324 -391
- package/src/types.test.ts +1 -1
- package/src/types.ts +929 -0
- package/src/utils.ts +84 -0
- package/dist/elysia-fork/context.d.ts.map +0 -1
- package/dist/elysia-fork/error.d.ts.map +0 -1
- package/dist/elysia-fork/error.js.map +0 -1
- package/dist/elysia-fork/types.d.ts +0 -560
- package/dist/elysia-fork/types.d.ts.map +0 -1
- package/dist/elysia-fork/types.js +0 -2
- package/dist/elysia-fork/types.js.map +0 -1
- package/dist/elysia-fork/utils.d.ts +0 -73
- package/dist/elysia-fork/utils.d.ts.map +0 -1
- package/dist/elysia-fork/utils.js +0 -70
- package/dist/elysia-fork/utils.js.map +0 -1
- package/src/elysia-fork/context.ts +0 -166
- package/src/elysia-fork/types.ts +0 -1281
- package/src/elysia-fork/utils.ts +0 -85
- /package/dist/{elysia-fork/context.js → context.js} +0 -0
- /package/dist/{elysia-fork/error.d.ts → error.d.ts} +0 -0
- /package/dist/{elysia-fork/error.js → error.js} +0 -0
- /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
|
-
|
|
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 './
|
|
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 { isProduction, 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
|
|
@@ -96,11 +74,20 @@ export type InternalRoute = {
|
|
|
96
74
|
validateBody?: ValidateFunction
|
|
97
75
|
validateQuery?: ValidateFunction
|
|
98
76
|
validateParams?: ValidateFunction
|
|
99
|
-
|
|
100
77
|
prefix: string
|
|
101
78
|
|
|
102
79
|
// store: Record<any, any>
|
|
103
80
|
}
|
|
81
|
+
|
|
82
|
+
type MedleyRouter = {
|
|
83
|
+
find: (path: string) =>
|
|
84
|
+
| {
|
|
85
|
+
store: Record<string, InternalRoute> //
|
|
86
|
+
params: Record<string, any>
|
|
87
|
+
}
|
|
88
|
+
| undefined
|
|
89
|
+
register: (path: string | undefined) => Record<string, InternalRoute>
|
|
90
|
+
}
|
|
104
91
|
/**
|
|
105
92
|
* Router class
|
|
106
93
|
*/
|
|
@@ -108,10 +95,7 @@ export class Spiceflow<
|
|
|
108
95
|
const in out BasePath extends string = '',
|
|
109
96
|
const in out Scoped extends boolean = true,
|
|
110
97
|
const in out Singleton extends SingletonBase = {
|
|
111
|
-
decorator: {}
|
|
112
98
|
store: {}
|
|
113
|
-
derive: {}
|
|
114
|
-
resolve: {}
|
|
115
99
|
},
|
|
116
100
|
const in out Definitions extends DefinitionBase = {
|
|
117
101
|
type: {}
|
|
@@ -123,30 +107,28 @@ export class Spiceflow<
|
|
|
123
107
|
macroFn: {}
|
|
124
108
|
},
|
|
125
109
|
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
110
|
> {
|
|
139
|
-
private
|
|
140
|
-
|
|
141
|
-
private
|
|
111
|
+
private id: number = globalIndex++
|
|
112
|
+
private router: MedleyRouter = new OriginalRouter()
|
|
113
|
+
private middlewares: Function[] = []
|
|
114
|
+
private onErrorHandlers: OnError[] = []
|
|
115
|
+
private routes: InternalRoute[] = []
|
|
116
|
+
private defaultStore: Record<any, any> = {}
|
|
117
|
+
private topLevelApp?: AnySpiceflow
|
|
118
|
+
|
|
119
|
+
/** @internal */
|
|
120
|
+
prefix?: string
|
|
142
121
|
|
|
122
|
+
/** @internal */
|
|
123
|
+
childrenApps: AnySpiceflow[] = []
|
|
124
|
+
|
|
125
|
+
/** @internal */
|
|
143
126
|
getAllRoutes() {
|
|
144
|
-
let root = this.
|
|
127
|
+
let root = this.topLevelApp || this
|
|
145
128
|
const allApps = bfs(root) || []
|
|
146
129
|
const allRoutes = allApps.flatMap((x) => {
|
|
147
|
-
const prefix = this.
|
|
130
|
+
const prefix = this.getAppAndParents(x)
|
|
148
131
|
.map((x) => x.prefix)
|
|
149
|
-
.reverse()
|
|
150
132
|
.join('')
|
|
151
133
|
|
|
152
134
|
return x.routes.map((x) => ({ ...x, path: prefix + x.path }))
|
|
@@ -161,73 +143,56 @@ export class Spiceflow<
|
|
|
161
143
|
handler,
|
|
162
144
|
...rest
|
|
163
145
|
}: Partial<InternalRoute>) {
|
|
164
|
-
const router = this.routerTree
|
|
165
|
-
// if (router.prefix) {
|
|
166
|
-
// path = router.prefix + path
|
|
167
|
-
// }
|
|
168
|
-
|
|
169
146
|
let bodySchema: TypeSchema = hooks?.body
|
|
170
147
|
let validateBody = getValidateFunction(bodySchema)
|
|
171
148
|
let validateQuery = getValidateFunction(hooks?.query)
|
|
172
149
|
let validateParams = getValidateFunction(hooks?.params)
|
|
173
150
|
|
|
174
|
-
const store =
|
|
151
|
+
const store = this.router.register(path)
|
|
175
152
|
let route: InternalRoute = {
|
|
176
153
|
...rest,
|
|
177
|
-
|
|
178
|
-
prefix: router.prefix || '',
|
|
154
|
+
prefix: this.prefix || '',
|
|
179
155
|
method: (method || '') as any,
|
|
180
156
|
path: path || '',
|
|
181
|
-
// prefix,
|
|
182
157
|
handler: handler!,
|
|
183
158
|
hooks,
|
|
184
159
|
validateBody,
|
|
185
160
|
validateParams,
|
|
186
161
|
validateQuery,
|
|
187
162
|
}
|
|
188
|
-
|
|
189
|
-
store[method] = route
|
|
163
|
+
this.routes.push(route)
|
|
164
|
+
store[method!] = route
|
|
190
165
|
}
|
|
191
166
|
|
|
192
167
|
private match(method: string, path: string) {
|
|
193
|
-
let root = this
|
|
194
|
-
const result = bfsFind(this
|
|
195
|
-
|
|
196
|
-
let prefix = this.
|
|
168
|
+
let root = this
|
|
169
|
+
const result = bfsFind(this, (app) => {
|
|
170
|
+
app.topLevelApp = root
|
|
171
|
+
let prefix = this.getAppAndParents(app)
|
|
197
172
|
.map((x) => x.prefix)
|
|
198
|
-
.reverse()
|
|
199
173
|
.join('')
|
|
200
174
|
if (prefix && !path.startsWith(prefix)) {
|
|
201
|
-
// console.log(
|
|
202
|
-
// `router prefix: ${router.prefix} does not match path: ${path}`
|
|
203
|
-
// )
|
|
204
175
|
return
|
|
205
176
|
}
|
|
206
177
|
let pathWithoutPrefix = path
|
|
207
178
|
if (prefix) {
|
|
208
179
|
pathWithoutPrefix = path.replace(prefix, '')
|
|
209
180
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if (!route) {
|
|
181
|
+
const medleyRoute = app.router.find(pathWithoutPrefix)
|
|
182
|
+
if (!medleyRoute) {
|
|
213
183
|
return
|
|
214
184
|
}
|
|
215
185
|
|
|
216
|
-
let
|
|
217
|
-
if (
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const { onErrorHandlers, onRequestHandlers } = router
|
|
221
|
-
const params = route['params'] || {}
|
|
186
|
+
let internalRoute: InternalRoute = medleyRoute.store[method]
|
|
187
|
+
if (internalRoute) {
|
|
188
|
+
const params = medleyRoute.params || {}
|
|
222
189
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
store: router.store,
|
|
227
|
-
onErrorHandlers,
|
|
228
|
-
onRequestHandlers,
|
|
190
|
+
const res = {
|
|
191
|
+
app,
|
|
192
|
+
internalRoute: internalRoute,
|
|
229
193
|
params,
|
|
230
194
|
}
|
|
195
|
+
return res
|
|
231
196
|
}
|
|
232
197
|
})
|
|
233
198
|
|
|
@@ -241,23 +206,18 @@ export class Spiceflow<
|
|
|
241
206
|
BasePath,
|
|
242
207
|
Scoped,
|
|
243
208
|
{
|
|
244
|
-
decorator: Singleton['decorator']
|
|
245
209
|
store: Reconcile<
|
|
246
210
|
Singleton['store'],
|
|
247
211
|
{
|
|
248
212
|
[name in Name]: Value
|
|
249
213
|
}
|
|
250
214
|
>
|
|
251
|
-
derive: Singleton['derive']
|
|
252
|
-
resolve: Singleton['resolve']
|
|
253
215
|
},
|
|
254
216
|
Definitions,
|
|
255
217
|
Metadata,
|
|
256
|
-
Routes
|
|
257
|
-
Ephemeral,
|
|
258
|
-
Volatile
|
|
218
|
+
Routes
|
|
259
219
|
> {
|
|
260
|
-
this.
|
|
220
|
+
this.defaultStore[name] = value
|
|
261
221
|
return this as any
|
|
262
222
|
}
|
|
263
223
|
|
|
@@ -269,31 +229,13 @@ export class Spiceflow<
|
|
|
269
229
|
options: {
|
|
270
230
|
name?: string
|
|
271
231
|
scoped?: Scoped
|
|
272
|
-
|
|
232
|
+
|
|
273
233
|
basePath?: BasePath
|
|
274
234
|
} = {},
|
|
275
235
|
) {
|
|
276
236
|
this.scoped = options.scoped
|
|
277
237
|
|
|
278
|
-
this.
|
|
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
|
-
// }
|
|
238
|
+
this.prefix = options.basePath
|
|
297
239
|
}
|
|
298
240
|
|
|
299
241
|
_routes: Routes = {} as any
|
|
@@ -306,27 +248,15 @@ export class Spiceflow<
|
|
|
306
248
|
Metadata: {} as Metadata,
|
|
307
249
|
}
|
|
308
250
|
|
|
309
|
-
_ephemeral = {} as Ephemeral
|
|
310
|
-
_volatile = {} as Volatile
|
|
311
|
-
|
|
312
251
|
post<
|
|
313
252
|
const Path extends string,
|
|
314
253
|
const LocalSchema extends InputSchema<
|
|
315
254
|
keyof Definitions['type'] & string
|
|
316
255
|
>,
|
|
317
|
-
const Schema extends
|
|
318
|
-
UnwrapRoute<LocalSchema, Definitions['type']>,
|
|
319
|
-
MergeSchema<
|
|
320
|
-
Volatile['schema'],
|
|
321
|
-
MergeSchema<Ephemeral['schema'], Metadata['schema']>
|
|
322
|
-
>
|
|
323
|
-
>,
|
|
256
|
+
const Schema extends UnwrapRoute<LocalSchema, Definitions['type']>,
|
|
324
257
|
const Handle extends InlineHandler<
|
|
325
258
|
Schema,
|
|
326
|
-
Singleton
|
|
327
|
-
derive: Ephemeral['derive'] & Volatile['derive']
|
|
328
|
-
resolve: Ephemeral['resolve'] & Volatile['resolve']
|
|
329
|
-
},
|
|
259
|
+
Singleton,
|
|
330
260
|
JoinPath<BasePath, Path>
|
|
331
261
|
>,
|
|
332
262
|
>(
|
|
@@ -335,10 +265,7 @@ export class Spiceflow<
|
|
|
335
265
|
hook?: LocalHook<
|
|
336
266
|
LocalSchema,
|
|
337
267
|
Schema,
|
|
338
|
-
Singleton
|
|
339
|
-
derive: Ephemeral['derive'] & Volatile['derive']
|
|
340
|
-
resolve: Ephemeral['resolve'] & Volatile['resolve']
|
|
341
|
-
},
|
|
268
|
+
Singleton,
|
|
342
269
|
Definitions['error'],
|
|
343
270
|
Metadata['macro'],
|
|
344
271
|
JoinPath<BasePath, Path>
|
|
@@ -359,16 +286,13 @@ export class Spiceflow<
|
|
|
359
286
|
? ResolvePath<Path>
|
|
360
287
|
: Schema['params']
|
|
361
288
|
query: Schema['query']
|
|
362
|
-
|
|
363
289
|
response: ComposeSpiceflowResponse<
|
|
364
290
|
Schema['response'],
|
|
365
291
|
Handle
|
|
366
292
|
>
|
|
367
293
|
}
|
|
368
294
|
}
|
|
369
|
-
|
|
370
|
-
Ephemeral,
|
|
371
|
-
Volatile
|
|
295
|
+
>
|
|
372
296
|
> {
|
|
373
297
|
this.add({ method: 'POST', path, handler: handler, hooks: hook })
|
|
374
298
|
|
|
@@ -380,20 +304,11 @@ export class Spiceflow<
|
|
|
380
304
|
const LocalSchema extends InputSchema<
|
|
381
305
|
keyof Definitions['type'] & string
|
|
382
306
|
>,
|
|
383
|
-
const Schema extends
|
|
384
|
-
UnwrapRoute<LocalSchema, Definitions['type']>,
|
|
385
|
-
MergeSchema<
|
|
386
|
-
Volatile['schema'],
|
|
387
|
-
MergeSchema<Ephemeral['schema'], Metadata['schema']>
|
|
388
|
-
>
|
|
389
|
-
>,
|
|
307
|
+
const Schema extends UnwrapRoute<LocalSchema, Definitions['type']>,
|
|
390
308
|
const Macro extends Metadata['macro'],
|
|
391
309
|
const Handle extends InlineHandler<
|
|
392
310
|
Schema,
|
|
393
|
-
Singleton
|
|
394
|
-
derive: Ephemeral['derive'] & Volatile['derive']
|
|
395
|
-
resolve: Ephemeral['resolve'] & Volatile['resolve']
|
|
396
|
-
},
|
|
311
|
+
Singleton,
|
|
397
312
|
JoinPath<BasePath, Path>
|
|
398
313
|
>,
|
|
399
314
|
>(
|
|
@@ -402,10 +317,7 @@ export class Spiceflow<
|
|
|
402
317
|
hook?: LocalHook<
|
|
403
318
|
LocalSchema,
|
|
404
319
|
Schema,
|
|
405
|
-
Singleton
|
|
406
|
-
derive: Ephemeral['derive'] & Volatile['derive']
|
|
407
|
-
resolve: Ephemeral['resolve'] & Volatile['resolve']
|
|
408
|
-
},
|
|
320
|
+
Singleton,
|
|
409
321
|
Definitions['error'],
|
|
410
322
|
Macro,
|
|
411
323
|
JoinPath<BasePath, Path>
|
|
@@ -433,9 +345,7 @@ export class Spiceflow<
|
|
|
433
345
|
>
|
|
434
346
|
}
|
|
435
347
|
}
|
|
436
|
-
|
|
437
|
-
Ephemeral,
|
|
438
|
-
Volatile
|
|
348
|
+
>
|
|
439
349
|
> {
|
|
440
350
|
this.add({ method: 'GET', path, handler: handler, hooks: hook })
|
|
441
351
|
return this as any
|
|
@@ -446,19 +356,10 @@ export class Spiceflow<
|
|
|
446
356
|
const LocalSchema extends InputSchema<
|
|
447
357
|
keyof Definitions['type'] & string
|
|
448
358
|
>,
|
|
449
|
-
const Schema extends
|
|
450
|
-
UnwrapRoute<LocalSchema, Definitions['type']>,
|
|
451
|
-
MergeSchema<
|
|
452
|
-
Volatile['schema'],
|
|
453
|
-
MergeSchema<Ephemeral['schema'], Metadata['schema']>
|
|
454
|
-
>
|
|
455
|
-
>,
|
|
359
|
+
const Schema extends UnwrapRoute<LocalSchema, Definitions['type']>,
|
|
456
360
|
const Handle extends InlineHandler<
|
|
457
361
|
Schema,
|
|
458
|
-
Singleton
|
|
459
|
-
derive: Ephemeral['derive'] & Volatile['derive']
|
|
460
|
-
resolve: Ephemeral['resolve'] & Volatile['resolve']
|
|
461
|
-
},
|
|
362
|
+
Singleton,
|
|
462
363
|
JoinPath<BasePath, Path>
|
|
463
364
|
>,
|
|
464
365
|
>(
|
|
@@ -467,10 +368,7 @@ export class Spiceflow<
|
|
|
467
368
|
hook?: LocalHook<
|
|
468
369
|
LocalSchema,
|
|
469
370
|
Schema,
|
|
470
|
-
Singleton
|
|
471
|
-
derive: Ephemeral['derive'] & Volatile['derive']
|
|
472
|
-
resolve: Ephemeral['resolve'] & Volatile['resolve']
|
|
473
|
-
},
|
|
371
|
+
Singleton,
|
|
474
372
|
Definitions['error'],
|
|
475
373
|
Metadata['macro'],
|
|
476
374
|
JoinPath<BasePath, Path>
|
|
@@ -498,9 +396,7 @@ export class Spiceflow<
|
|
|
498
396
|
>
|
|
499
397
|
}
|
|
500
398
|
}
|
|
501
|
-
|
|
502
|
-
Ephemeral,
|
|
503
|
-
Volatile
|
|
399
|
+
>
|
|
504
400
|
> {
|
|
505
401
|
this.add({ method: 'PUT', path, handler: handler, hooks: hook })
|
|
506
402
|
|
|
@@ -512,19 +408,10 @@ export class Spiceflow<
|
|
|
512
408
|
const LocalSchema extends InputSchema<
|
|
513
409
|
keyof Definitions['type'] & string
|
|
514
410
|
>,
|
|
515
|
-
const Schema extends
|
|
516
|
-
UnwrapRoute<LocalSchema, Definitions['type']>,
|
|
517
|
-
MergeSchema<
|
|
518
|
-
Volatile['schema'],
|
|
519
|
-
MergeSchema<Ephemeral['schema'], Metadata['schema']>
|
|
520
|
-
>
|
|
521
|
-
>,
|
|
411
|
+
const Schema extends UnwrapRoute<LocalSchema, Definitions['type']>,
|
|
522
412
|
const Handle extends InlineHandler<
|
|
523
413
|
Schema,
|
|
524
|
-
Singleton
|
|
525
|
-
derive: Ephemeral['derive'] & Volatile['derive']
|
|
526
|
-
resolve: Ephemeral['resolve'] & Volatile['resolve']
|
|
527
|
-
},
|
|
414
|
+
Singleton,
|
|
528
415
|
JoinPath<BasePath, Path>
|
|
529
416
|
>,
|
|
530
417
|
>(
|
|
@@ -533,10 +420,7 @@ export class Spiceflow<
|
|
|
533
420
|
hook?: LocalHook<
|
|
534
421
|
LocalSchema,
|
|
535
422
|
Schema,
|
|
536
|
-
Singleton
|
|
537
|
-
derive: Ephemeral['derive'] & Volatile['derive']
|
|
538
|
-
resolve: Ephemeral['resolve'] & Volatile['resolve']
|
|
539
|
-
},
|
|
423
|
+
Singleton,
|
|
540
424
|
Definitions['error'],
|
|
541
425
|
Metadata['macro'],
|
|
542
426
|
JoinPath<BasePath, Path>
|
|
@@ -564,9 +448,7 @@ export class Spiceflow<
|
|
|
564
448
|
>
|
|
565
449
|
}
|
|
566
450
|
}
|
|
567
|
-
|
|
568
|
-
Ephemeral,
|
|
569
|
-
Volatile
|
|
451
|
+
>
|
|
570
452
|
> {
|
|
571
453
|
this.add({ method: 'PATCH', path, handler: handler, hooks: hook })
|
|
572
454
|
|
|
@@ -578,19 +460,10 @@ export class Spiceflow<
|
|
|
578
460
|
const LocalSchema extends InputSchema<
|
|
579
461
|
keyof Definitions['type'] & string
|
|
580
462
|
>,
|
|
581
|
-
const Schema extends
|
|
582
|
-
UnwrapRoute<LocalSchema, Definitions['type']>,
|
|
583
|
-
MergeSchema<
|
|
584
|
-
Volatile['schema'],
|
|
585
|
-
MergeSchema<Ephemeral['schema'], Metadata['schema']>
|
|
586
|
-
>
|
|
587
|
-
>,
|
|
463
|
+
const Schema extends UnwrapRoute<LocalSchema, Definitions['type']>,
|
|
588
464
|
const Handle extends InlineHandler<
|
|
589
465
|
Schema,
|
|
590
|
-
Singleton
|
|
591
|
-
derive: Ephemeral['derive'] & Volatile['derive']
|
|
592
|
-
resolve: Ephemeral['resolve'] & Volatile['resolve']
|
|
593
|
-
},
|
|
466
|
+
Singleton,
|
|
594
467
|
JoinPath<BasePath, Path>
|
|
595
468
|
>,
|
|
596
469
|
>(
|
|
@@ -599,10 +472,7 @@ export class Spiceflow<
|
|
|
599
472
|
hook?: LocalHook<
|
|
600
473
|
LocalSchema,
|
|
601
474
|
Schema,
|
|
602
|
-
Singleton
|
|
603
|
-
derive: Ephemeral['derive'] & Volatile['derive']
|
|
604
|
-
resolve: Ephemeral['resolve'] & Volatile['resolve']
|
|
605
|
-
},
|
|
475
|
+
Singleton,
|
|
606
476
|
Definitions['error'],
|
|
607
477
|
Metadata['macro'],
|
|
608
478
|
JoinPath<BasePath, Path>
|
|
@@ -630,9 +500,7 @@ export class Spiceflow<
|
|
|
630
500
|
>
|
|
631
501
|
}
|
|
632
502
|
}
|
|
633
|
-
|
|
634
|
-
Ephemeral,
|
|
635
|
-
Volatile
|
|
503
|
+
>
|
|
636
504
|
> {
|
|
637
505
|
this.add({ method: 'DELETE', path, handler: handler, hooks: hook })
|
|
638
506
|
|
|
@@ -644,19 +512,10 @@ export class Spiceflow<
|
|
|
644
512
|
const LocalSchema extends InputSchema<
|
|
645
513
|
keyof Definitions['type'] & string
|
|
646
514
|
>,
|
|
647
|
-
const Schema extends
|
|
648
|
-
UnwrapRoute<LocalSchema, Definitions['type']>,
|
|
649
|
-
MergeSchema<
|
|
650
|
-
Volatile['schema'],
|
|
651
|
-
MergeSchema<Ephemeral['schema'], Metadata['schema']>
|
|
652
|
-
>
|
|
653
|
-
>,
|
|
515
|
+
const Schema extends UnwrapRoute<LocalSchema, Definitions['type']>,
|
|
654
516
|
const Handle extends InlineHandler<
|
|
655
517
|
Schema,
|
|
656
|
-
Singleton
|
|
657
|
-
derive: Ephemeral['derive'] & Volatile['derive']
|
|
658
|
-
resolve: Ephemeral['resolve'] & Volatile['resolve']
|
|
659
|
-
},
|
|
518
|
+
Singleton,
|
|
660
519
|
JoinPath<BasePath, Path>
|
|
661
520
|
>,
|
|
662
521
|
>(
|
|
@@ -665,10 +524,7 @@ export class Spiceflow<
|
|
|
665
524
|
hook?: LocalHook<
|
|
666
525
|
LocalSchema,
|
|
667
526
|
Schema,
|
|
668
|
-
Singleton
|
|
669
|
-
derive: Ephemeral['derive'] & Volatile['derive']
|
|
670
|
-
resolve: Ephemeral['resolve'] & Volatile['resolve']
|
|
671
|
-
},
|
|
527
|
+
Singleton,
|
|
672
528
|
Definitions['error'],
|
|
673
529
|
Metadata['macro'],
|
|
674
530
|
JoinPath<BasePath, Path>
|
|
@@ -696,9 +552,7 @@ export class Spiceflow<
|
|
|
696
552
|
>
|
|
697
553
|
}
|
|
698
554
|
}
|
|
699
|
-
|
|
700
|
-
Ephemeral,
|
|
701
|
-
Volatile
|
|
555
|
+
>
|
|
702
556
|
> {
|
|
703
557
|
this.add({ method: 'OPTIONS', path, handler: handler, hooks: hook })
|
|
704
558
|
|
|
@@ -710,19 +564,10 @@ export class Spiceflow<
|
|
|
710
564
|
const LocalSchema extends InputSchema<
|
|
711
565
|
keyof Definitions['type'] & string
|
|
712
566
|
>,
|
|
713
|
-
const Schema extends
|
|
714
|
-
UnwrapRoute<LocalSchema, Definitions['type']>,
|
|
715
|
-
MergeSchema<
|
|
716
|
-
Volatile['schema'],
|
|
717
|
-
MergeSchema<Ephemeral['schema'], Metadata['schema']>
|
|
718
|
-
>
|
|
719
|
-
>,
|
|
567
|
+
const Schema extends UnwrapRoute<LocalSchema, Definitions['type']>,
|
|
720
568
|
const Handle extends InlineHandler<
|
|
721
569
|
Schema,
|
|
722
|
-
Singleton
|
|
723
|
-
derive: Ephemeral['derive'] & Volatile['derive']
|
|
724
|
-
resolve: Ephemeral['resolve'] & Volatile['resolve']
|
|
725
|
-
},
|
|
570
|
+
Singleton,
|
|
726
571
|
JoinPath<BasePath, Path>
|
|
727
572
|
>,
|
|
728
573
|
>(
|
|
@@ -731,10 +576,7 @@ export class Spiceflow<
|
|
|
731
576
|
hook?: LocalHook<
|
|
732
577
|
LocalSchema,
|
|
733
578
|
Schema,
|
|
734
|
-
Singleton
|
|
735
|
-
derive: Ephemeral['derive'] & Volatile['derive']
|
|
736
|
-
resolve: Ephemeral['resolve'] & Volatile['resolve']
|
|
737
|
-
},
|
|
579
|
+
Singleton,
|
|
738
580
|
Definitions['error'],
|
|
739
581
|
Metadata['macro'],
|
|
740
582
|
JoinPath<BasePath, Path>
|
|
@@ -762,9 +604,7 @@ export class Spiceflow<
|
|
|
762
604
|
>
|
|
763
605
|
}
|
|
764
606
|
}
|
|
765
|
-
|
|
766
|
-
Ephemeral,
|
|
767
|
-
Volatile
|
|
607
|
+
>
|
|
768
608
|
> {
|
|
769
609
|
for (const method of METHODS) {
|
|
770
610
|
this.add({ method, path, handler: handler, hooks: hook })
|
|
@@ -778,19 +618,10 @@ export class Spiceflow<
|
|
|
778
618
|
const LocalSchema extends InputSchema<
|
|
779
619
|
keyof Definitions['type'] & string
|
|
780
620
|
>,
|
|
781
|
-
const Schema extends
|
|
782
|
-
UnwrapRoute<LocalSchema, Definitions['type']>,
|
|
783
|
-
MergeSchema<
|
|
784
|
-
Volatile['schema'],
|
|
785
|
-
MergeSchema<Ephemeral['schema'], Metadata['schema']>
|
|
786
|
-
>
|
|
787
|
-
>,
|
|
621
|
+
const Schema extends UnwrapRoute<LocalSchema, Definitions['type']>,
|
|
788
622
|
const Handle extends InlineHandler<
|
|
789
623
|
Schema,
|
|
790
|
-
Singleton
|
|
791
|
-
derive: Ephemeral['derive'] & Volatile['derive']
|
|
792
|
-
resolve: Ephemeral['resolve'] & Volatile['resolve']
|
|
793
|
-
},
|
|
624
|
+
Singleton,
|
|
794
625
|
JoinPath<BasePath, Path>
|
|
795
626
|
>,
|
|
796
627
|
>(
|
|
@@ -799,10 +630,7 @@ export class Spiceflow<
|
|
|
799
630
|
hook?: LocalHook<
|
|
800
631
|
LocalSchema,
|
|
801
632
|
Schema,
|
|
802
|
-
Singleton
|
|
803
|
-
derive: Ephemeral['derive'] & Volatile['derive']
|
|
804
|
-
resolve: Ephemeral['resolve'] & Volatile['resolve']
|
|
805
|
-
},
|
|
633
|
+
Singleton,
|
|
806
634
|
Definitions['error'],
|
|
807
635
|
Metadata['macro'],
|
|
808
636
|
JoinPath<BasePath, Path>
|
|
@@ -830,24 +658,14 @@ export class Spiceflow<
|
|
|
830
658
|
>
|
|
831
659
|
}
|
|
832
660
|
}
|
|
833
|
-
|
|
834
|
-
Ephemeral,
|
|
835
|
-
Volatile
|
|
661
|
+
>
|
|
836
662
|
> {
|
|
837
663
|
this.add({ method: 'HEAD', path, handler: handler, hooks: hook })
|
|
838
664
|
|
|
839
665
|
return this as any
|
|
840
666
|
}
|
|
841
667
|
|
|
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
|
-
}
|
|
668
|
+
private scoped?: Scoped = true as Scoped
|
|
851
669
|
|
|
852
670
|
// group is not needed, you can add another prefixed app instead
|
|
853
671
|
// group<
|
|
@@ -900,75 +718,47 @@ export class Spiceflow<
|
|
|
900
718
|
Metadata,
|
|
901
719
|
BasePath extends ``
|
|
902
720
|
? Routes & NewSpiceflow['_routes']
|
|
903
|
-
: Routes & CreateEden<BasePath, NewSpiceflow['_routes']
|
|
904
|
-
|
|
905
|
-
|
|
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>(
|
|
721
|
+
: Routes & CreateEden<BasePath, NewSpiceflow['_routes']>
|
|
722
|
+
>
|
|
723
|
+
use<const Schema extends RouteSchema>(
|
|
914
724
|
handler: MaybeArray<
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
Volatile['schema'],
|
|
921
|
-
MergeSchema<Ephemeral['schema'], Metadata['schema']>
|
|
922
|
-
>
|
|
923
|
-
>,
|
|
924
|
-
Singleton,
|
|
925
|
-
Ephemeral,
|
|
926
|
-
Volatile
|
|
725
|
+
MiddlewareHandler<
|
|
726
|
+
Schema,
|
|
727
|
+
{
|
|
728
|
+
store: Singleton['store']
|
|
729
|
+
}
|
|
927
730
|
>
|
|
928
731
|
>,
|
|
929
|
-
): this
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
732
|
+
): this
|
|
733
|
+
|
|
734
|
+
use(appOrHandler) {
|
|
735
|
+
if (appOrHandler instanceof Spiceflow) {
|
|
736
|
+
this.childrenApps.push(appOrHandler)
|
|
737
|
+
} else if (typeof appOrHandler === 'function') {
|
|
738
|
+
this.middlewares ??= []
|
|
739
|
+
this.middlewares.push(appOrHandler)
|
|
740
|
+
}
|
|
935
741
|
return this
|
|
936
742
|
}
|
|
937
743
|
|
|
938
|
-
|
|
744
|
+
onError<const Schema extends RouteSchema>(
|
|
939
745
|
handler: MaybeArray<
|
|
940
|
-
|
|
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
|
-
>
|
|
746
|
+
ErrorHandler<Definitions['error'], Schema, Singleton>
|
|
955
747
|
>,
|
|
956
|
-
) {
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
router.onRequestHandlers.push(handler as any)
|
|
748
|
+
): this {
|
|
749
|
+
this.onErrorHandlers ??= []
|
|
750
|
+
this.onErrorHandlers.push(handler as any)
|
|
960
751
|
|
|
961
752
|
return this
|
|
962
753
|
}
|
|
754
|
+
|
|
963
755
|
/**
|
|
964
756
|
* Pass a request through all matching route handles and return a response
|
|
965
757
|
* @param request The `Request`
|
|
966
758
|
* @param platform Platform specific context {@link Platform}
|
|
967
759
|
* @returns The final `Response`
|
|
968
760
|
*/
|
|
969
|
-
async handle(request: Request
|
|
970
|
-
platform ??= {} as P
|
|
971
|
-
|
|
761
|
+
async handle(request: Request): Promise<Response> {
|
|
972
762
|
let u = new URL(request.url, 'http://localhost')
|
|
973
763
|
let path = u.pathname + u.search
|
|
974
764
|
const defaultContext = {
|
|
@@ -976,79 +766,108 @@ export class Spiceflow<
|
|
|
976
766
|
error: null,
|
|
977
767
|
path,
|
|
978
768
|
}
|
|
769
|
+
const root = this.topLevelApp || this
|
|
979
770
|
let onErrorHandlers: OnError[] = []
|
|
980
771
|
try {
|
|
981
|
-
let response: Response | undefined
|
|
982
772
|
// Get all middleware and method specific routes in order
|
|
983
773
|
|
|
984
774
|
const route = this.match(request.method, path)
|
|
775
|
+
|
|
985
776
|
if (!route) {
|
|
986
|
-
|
|
777
|
+
const error = new NotFoundError(`${path} not found`)
|
|
778
|
+
const res = await this.runErrorHandlers({
|
|
779
|
+
onErrorHandlers,
|
|
780
|
+
error,
|
|
781
|
+
request,
|
|
782
|
+
})
|
|
783
|
+
if (res) return res
|
|
784
|
+
return new Response(`Not Found`, {
|
|
785
|
+
status: 404,
|
|
786
|
+
})
|
|
987
787
|
}
|
|
988
|
-
onErrorHandlers = this.
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
let {
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
788
|
+
onErrorHandlers = this.getAppsInScope(route.app).flatMap(
|
|
789
|
+
(x) => x.onErrorHandlers,
|
|
790
|
+
)
|
|
791
|
+
let {
|
|
792
|
+
params,
|
|
793
|
+
app: { defaultStore },
|
|
794
|
+
} = route
|
|
795
|
+
const middlewares = this.getAppsInScope(route.app).flatMap(
|
|
796
|
+
(x) => x.middlewares,
|
|
797
|
+
)
|
|
995
798
|
// console.log({ onReqHandlers })
|
|
996
799
|
let store = { ...defaultStore }
|
|
997
|
-
// TODO add content type
|
|
998
800
|
|
|
999
|
-
let content = route?.hooks?.content
|
|
1000
|
-
// let body = await getRequestBody({ request, content })
|
|
801
|
+
let content = route?.internalRoute?.hooks?.content
|
|
1001
802
|
|
|
1002
|
-
if (route.validateBody) {
|
|
803
|
+
if (route.internalRoute?.validateBody) {
|
|
1003
804
|
// TODO don't clone the request
|
|
1004
|
-
let typedRequest =
|
|
1005
|
-
|
|
805
|
+
let typedRequest =
|
|
806
|
+
request instanceof TypedRequest
|
|
807
|
+
? request
|
|
808
|
+
: new TypedRequest(request)
|
|
809
|
+
typedRequest.validateBody = route.internalRoute?.validateBody
|
|
1006
810
|
request = typedRequest
|
|
1007
811
|
}
|
|
1008
812
|
|
|
1009
813
|
let query = parseQuery.parse((u.search || '').slice(1))
|
|
1010
814
|
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
815
|
+
let index = 0
|
|
816
|
+
|
|
817
|
+
const next = async () => {
|
|
818
|
+
if (index < middlewares.length) {
|
|
819
|
+
const middleware = middlewares[index]
|
|
820
|
+
index++
|
|
821
|
+
let context = {
|
|
1014
822
|
request,
|
|
1015
|
-
response,
|
|
1016
823
|
store,
|
|
1017
824
|
path,
|
|
1018
825
|
query,
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
826
|
+
params,
|
|
827
|
+
redirect,
|
|
828
|
+
} satisfies MiddlewareContext<any>
|
|
829
|
+
const result = await middleware(context, next)
|
|
830
|
+
|
|
831
|
+
if (!result && index < middlewares.length) {
|
|
832
|
+
return await next()
|
|
833
|
+
} else if (result) {
|
|
834
|
+
return await turnHandlerResultIntoResponse(result)
|
|
1022
835
|
}
|
|
1023
836
|
}
|
|
1024
|
-
}
|
|
1025
837
|
|
|
1026
|
-
|
|
1027
|
-
|
|
838
|
+
query = runValidation(query, route.internalRoute?.validateQuery)
|
|
839
|
+
params = runValidation(
|
|
840
|
+
params,
|
|
841
|
+
route.internalRoute?.validateParams,
|
|
842
|
+
)
|
|
1028
843
|
|
|
1029
|
-
|
|
844
|
+
// console.log(route)
|
|
1030
845
|
|
|
1031
|
-
|
|
1032
|
-
|
|
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,
|
|
846
|
+
const res = route.internalRoute?.handler({
|
|
847
|
+
...defaultContext,
|
|
1046
848
|
request,
|
|
1047
|
-
|
|
1048
|
-
|
|
849
|
+
params: params as any,
|
|
850
|
+
redirect,
|
|
851
|
+
store,
|
|
852
|
+
query,
|
|
853
|
+
// body,
|
|
854
|
+
path,
|
|
855
|
+
|
|
856
|
+
// platform
|
|
857
|
+
} satisfies Context<any, any, any>)
|
|
858
|
+
if (isAsyncIterable(res)) {
|
|
859
|
+
return await this.handleStream({
|
|
860
|
+
generator: res,
|
|
861
|
+
request,
|
|
862
|
+
onErrorHandlers,
|
|
863
|
+
})
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
return await turnHandlerResultIntoResponse(res)
|
|
1049
867
|
}
|
|
868
|
+
const response = await next()
|
|
1050
869
|
|
|
1051
|
-
return
|
|
870
|
+
return response
|
|
1052
871
|
} catch (err: any) {
|
|
1053
872
|
if (err instanceof Response) return err
|
|
1054
873
|
let res = await this.runErrorHandlers({
|
|
@@ -1081,29 +900,149 @@ export class Spiceflow<
|
|
|
1081
900
|
}
|
|
1082
901
|
}
|
|
1083
902
|
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
const parents: RouterTree[] = []
|
|
1087
|
-
let current = currentRouter
|
|
903
|
+
private getAppAndParents(currentApp?: AnySpiceflow) {
|
|
904
|
+
let root = this.topLevelApp || this
|
|
1088
905
|
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
906
|
+
if (!root.childrenApps.length) {
|
|
907
|
+
return [root]
|
|
908
|
+
}
|
|
909
|
+
const parents: AnySpiceflow[] = []
|
|
910
|
+
let current = currentApp
|
|
911
|
+
|
|
912
|
+
const parentMap = new Map<number, AnySpiceflow>()
|
|
1092
913
|
bfsFind(root, (node) => {
|
|
1093
|
-
for (const child of node.
|
|
914
|
+
for (const child of node.childrenApps) {
|
|
1094
915
|
parentMap.set(child.id, node)
|
|
1095
916
|
}
|
|
1096
917
|
})
|
|
1097
918
|
|
|
1098
919
|
// Traverse the parent map to get the parents
|
|
1099
920
|
while (current) {
|
|
1100
|
-
parents.
|
|
921
|
+
parents.unshift(current)
|
|
1101
922
|
current = parentMap.get(current.id)
|
|
1102
923
|
}
|
|
1103
924
|
|
|
1104
925
|
return parents.filter((x) => x !== undefined)
|
|
1105
926
|
}
|
|
1106
927
|
|
|
928
|
+
private getAppsInScope(currentApp?: AnySpiceflow) {
|
|
929
|
+
let root = this.topLevelApp || this
|
|
930
|
+
if (!root.childrenApps.length) {
|
|
931
|
+
return [root]
|
|
932
|
+
}
|
|
933
|
+
const withParents = this.getAppAndParents(currentApp)
|
|
934
|
+
|
|
935
|
+
const wantedOrder = bfs(root)
|
|
936
|
+
const scopeFalseApps = wantedOrder.filter((x) => x.scoped === false)
|
|
937
|
+
let appsInScope = [] as AnySpiceflow[]
|
|
938
|
+
for (const app of wantedOrder) {
|
|
939
|
+
if (scopeFalseApps.includes(app)) {
|
|
940
|
+
appsInScope.push(app)
|
|
941
|
+
continue
|
|
942
|
+
}
|
|
943
|
+
if (withParents.includes(app)) {
|
|
944
|
+
appsInScope.push(app)
|
|
945
|
+
continue
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
return appsInScope
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
async listen(port: number, hostname: string = '127.0.0.1') {
|
|
952
|
+
if (typeof Bun !== 'undefined') {
|
|
953
|
+
const server = Bun.serve({
|
|
954
|
+
port,
|
|
955
|
+
development: !isProduction,
|
|
956
|
+
hostname,
|
|
957
|
+
error(error) {
|
|
958
|
+
console.error(error)
|
|
959
|
+
return new Response('Internal Server Error', {
|
|
960
|
+
status: 500,
|
|
961
|
+
})
|
|
962
|
+
},
|
|
963
|
+
|
|
964
|
+
fetch: async (request) => {
|
|
965
|
+
const res = await this.handle(request)
|
|
966
|
+
return res
|
|
967
|
+
},
|
|
968
|
+
})
|
|
969
|
+
console.log(`Listening on http://localhost:${port}`)
|
|
970
|
+
return server
|
|
971
|
+
}
|
|
972
|
+
return this.listenNode(port, hostname)
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
async listenNode(port: number, hostname: string = '0.0.0.0') {
|
|
976
|
+
const { Readable } = await import('stream')
|
|
977
|
+
const { createServer } = await import('http')
|
|
978
|
+
|
|
979
|
+
const server = createServer(async (req, res) => {
|
|
980
|
+
const abortController = new AbortController()
|
|
981
|
+
const { signal } = abortController
|
|
982
|
+
|
|
983
|
+
req.on('close', () => abortController.abort())
|
|
984
|
+
req.on('error', (err) => {
|
|
985
|
+
abortController.abort()
|
|
986
|
+
})
|
|
987
|
+
req.on('aborted', (err) => {
|
|
988
|
+
abortController.abort()
|
|
989
|
+
})
|
|
990
|
+
// this is how you see when a request is aborted in Node.js, laughable
|
|
991
|
+
res.on('close', function () {
|
|
992
|
+
let aborted = !res.writableFinished
|
|
993
|
+
if (aborted) {
|
|
994
|
+
abortController.abort()
|
|
995
|
+
}
|
|
996
|
+
})
|
|
997
|
+
|
|
998
|
+
const url = new URL(
|
|
999
|
+
req.url || '',
|
|
1000
|
+
`http://${req.headers.host || hostname || 'localhost'}`,
|
|
1001
|
+
)
|
|
1002
|
+
const typedRequest = new TypedRequest(url.toString(), {
|
|
1003
|
+
method: req.method,
|
|
1004
|
+
headers: req.headers as HeadersInit,
|
|
1005
|
+
body:
|
|
1006
|
+
req.method !== 'GET' && req.method !== 'HEAD'
|
|
1007
|
+
? (Readable.toWeb(req) as any)
|
|
1008
|
+
: null,
|
|
1009
|
+
signal,
|
|
1010
|
+
})
|
|
1011
|
+
|
|
1012
|
+
try {
|
|
1013
|
+
const response = await this.handle(typedRequest)
|
|
1014
|
+
|
|
1015
|
+
res.statusCode = response.status
|
|
1016
|
+
for (const [key, value] of response.headers) {
|
|
1017
|
+
res.setHeader(key, value)
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
if (response.body) {
|
|
1021
|
+
const reader = response.body.getReader()
|
|
1022
|
+
while (true) {
|
|
1023
|
+
const { done, value } = await reader.read()
|
|
1024
|
+
if (done) break
|
|
1025
|
+
res.write(value)
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
res.end()
|
|
1029
|
+
} catch (error) {
|
|
1030
|
+
console.error('Error handling request:', error)
|
|
1031
|
+
res.statusCode = 500
|
|
1032
|
+
res.end('Internal Server Error')
|
|
1033
|
+
}
|
|
1034
|
+
})
|
|
1035
|
+
|
|
1036
|
+
await new Promise((resolve, reject) => {
|
|
1037
|
+
server.listen(port, hostname, () => {
|
|
1038
|
+
console.log(`Listening on http://localhost:${port}`)
|
|
1039
|
+
resolve(null)
|
|
1040
|
+
})
|
|
1041
|
+
})
|
|
1042
|
+
|
|
1043
|
+
return server
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1107
1046
|
private async handleStream({
|
|
1108
1047
|
onErrorHandlers,
|
|
1109
1048
|
generator,
|
|
@@ -1264,8 +1203,8 @@ const METHODS = [
|
|
|
1264
1203
|
export type Method = (typeof METHODS)[number]
|
|
1265
1204
|
|
|
1266
1205
|
function bfsFind<T>(
|
|
1267
|
-
tree:
|
|
1268
|
-
onNode: (node:
|
|
1206
|
+
tree: AnySpiceflow,
|
|
1207
|
+
onNode: (node: AnySpiceflow) => T | undefined | void,
|
|
1269
1208
|
): T | undefined {
|
|
1270
1209
|
const queue = [tree]
|
|
1271
1210
|
|
|
@@ -1276,7 +1215,7 @@ function bfsFind<T>(
|
|
|
1276
1215
|
if (result) {
|
|
1277
1216
|
return result
|
|
1278
1217
|
}
|
|
1279
|
-
queue.push(...node.
|
|
1218
|
+
queue.push(...node.childrenApps)
|
|
1280
1219
|
}
|
|
1281
1220
|
return
|
|
1282
1221
|
}
|
|
@@ -1289,9 +1228,9 @@ export class TypedRequest<T = any> extends Request {
|
|
|
1289
1228
|
}
|
|
1290
1229
|
}
|
|
1291
1230
|
|
|
1292
|
-
export function bfs(tree:
|
|
1231
|
+
export function bfs(tree: AnySpiceflow) {
|
|
1293
1232
|
const queue = [tree]
|
|
1294
|
-
let nodes:
|
|
1233
|
+
let nodes: AnySpiceflow[] = []
|
|
1295
1234
|
while (queue.length > 0) {
|
|
1296
1235
|
const node = queue.shift()!
|
|
1297
1236
|
if (node) {
|
|
@@ -1299,20 +1238,14 @@ export function bfs(tree: RouterTree) {
|
|
|
1299
1238
|
}
|
|
1300
1239
|
// const result = onNode(node)
|
|
1301
1240
|
|
|
1302
|
-
|
|
1241
|
+
if (node?.childrenApps?.length) {
|
|
1242
|
+
queue.push(...node.childrenApps)
|
|
1243
|
+
}
|
|
1303
1244
|
}
|
|
1304
1245
|
return nodes
|
|
1305
1246
|
}
|
|
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
1247
|
export async function turnHandlerResultIntoResponse(result: any) {
|
|
1248
|
+
// if (result === undefined) return new Response('', { status: 404 })
|
|
1316
1249
|
// if user returns not a response, convert to json
|
|
1317
1250
|
if (result instanceof Response) {
|
|
1318
1251
|
return result
|
|
@@ -1327,14 +1260,14 @@ export async function turnHandlerResultIntoResponse(result: any) {
|
|
|
1327
1260
|
// }
|
|
1328
1261
|
// if user returns an object, convert to json
|
|
1329
1262
|
|
|
1330
|
-
return new Response(JSON.stringify(result, null, 2), {
|
|
1263
|
+
return new Response(JSON.stringify(result ?? null, null, 2), {
|
|
1331
1264
|
headers: {
|
|
1332
1265
|
'content-type': 'application/json',
|
|
1333
1266
|
},
|
|
1334
1267
|
})
|
|
1335
1268
|
}
|
|
1336
1269
|
|
|
1337
|
-
export type AnySpiceflow = Spiceflow<any, any, any, any, any, any
|
|
1270
|
+
export type AnySpiceflow = Spiceflow<any, any, any, any, any, any>
|
|
1338
1271
|
|
|
1339
1272
|
export function isZodSchema(value: unknown): value is ZodType {
|
|
1340
1273
|
return (
|