spiceflow 1.0.1 → 1.0.3
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 +148 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/types.d.ts +2 -6
- package/dist/client/types.d.ts.map +1 -1
- package/dist/client.test.js +26 -22
- package/dist/client.test.js.map +1 -1
- package/dist/elysia-fork/context.d.ts +6 -22
- package/dist/elysia-fork/context.d.ts.map +1 -1
- package/dist/elysia-fork/error.d.ts +2 -226
- package/dist/elysia-fork/error.d.ts.map +1 -1
- package/dist/elysia-fork/error.js +13 -166
- package/dist/elysia-fork/error.js.map +1 -1
- package/dist/elysia-fork/types.d.ts +20 -30
- package/dist/elysia-fork/types.d.ts.map +1 -1
- package/dist/elysia-fork/types.js +1 -2
- package/dist/elysia-fork/types.js.map +1 -1
- package/dist/elysia-fork/utils.d.ts +1 -62
- package/dist/elysia-fork/utils.d.ts.map +1 -1
- package/dist/openapi.d.ts +67 -0
- package/dist/openapi.d.ts.map +1 -0
- package/dist/openapi.js +250 -0
- package/dist/openapi.js.map +1 -0
- package/dist/spiceflow.d.ts +35 -23
- package/dist/spiceflow.d.ts.map +1 -1
- package/dist/spiceflow.js +214 -111
- package/dist/spiceflow.js.map +1 -1
- package/dist/spiceflow.test.js +135 -31
- package/dist/spiceflow.test.js.map +1 -1
- package/dist/zod.test.d.ts +2 -0
- package/dist/zod.test.d.ts.map +1 -0
- package/dist/zod.test.js +61 -0
- package/dist/zod.test.js.map +1 -0
- package/package.json +7 -3
- package/src/client/types.ts +14 -19
- package/src/client.test.ts +34 -25
- package/src/elysia-fork/context.ts +19 -49
- package/src/elysia-fork/error.ts +6 -259
- package/src/elysia-fork/types.ts +115 -188
- package/src/openapi.ts +426 -0
- package/src/spiceflow.test.ts +188 -51
- package/src/spiceflow.ts +312 -183
- package/src/zod.test.ts +73 -0
package/src/spiceflow.ts
CHANGED
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
DefinitionBase,
|
|
11
11
|
EphemeralType,
|
|
12
12
|
ErrorHandler,
|
|
13
|
+
Handler,
|
|
14
|
+
HTTPMethod,
|
|
13
15
|
InlineHandler,
|
|
14
16
|
InputSchema,
|
|
15
17
|
JoinPath,
|
|
@@ -24,20 +26,41 @@ import {
|
|
|
24
26
|
RouteBase,
|
|
25
27
|
RouteSchema,
|
|
26
28
|
SingletonBase,
|
|
27
|
-
|
|
29
|
+
TypeSchema,
|
|
30
|
+
UnwrapRoute,
|
|
28
31
|
} from './elysia-fork/types.js'
|
|
32
|
+
import addFormats from 'ajv-formats'
|
|
33
|
+
let globalIndex = 0
|
|
29
34
|
|
|
30
35
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
31
36
|
// @ts-ignore
|
|
32
37
|
import OriginalRouter from '@medley/router'
|
|
33
38
|
import { TSchema } from '@sinclair/typebox'
|
|
34
|
-
import Ajv from 'ajv'
|
|
39
|
+
import Ajv, { ValidateFunction } from 'ajv'
|
|
35
40
|
import { Context } from './elysia-fork/context.js'
|
|
36
41
|
import { isAsyncIterable } from './utils.js'
|
|
37
42
|
import { redirect } from './elysia-fork/utils.js'
|
|
38
43
|
import { ValidationError } from './elysia-fork/error.js'
|
|
44
|
+
import { zodToJsonSchema } from 'zod-to-json-schema'
|
|
45
|
+
import { z, ZodType } from 'zod'
|
|
46
|
+
|
|
47
|
+
const ajv = addFormats(new Ajv({ useDefaults: true }), [
|
|
48
|
+
'date-time',
|
|
49
|
+
'time',
|
|
50
|
+
'date',
|
|
51
|
+
'email',
|
|
52
|
+
'hostname',
|
|
53
|
+
'ipv4',
|
|
54
|
+
'ipv6',
|
|
55
|
+
'uri',
|
|
56
|
+
'uri-reference',
|
|
57
|
+
'uuid',
|
|
58
|
+
'uri-template',
|
|
59
|
+
'json-pointer',
|
|
60
|
+
'relative-json-pointer',
|
|
61
|
+
'regex',
|
|
62
|
+
])
|
|
39
63
|
|
|
40
|
-
const ajv = new Ajv()
|
|
41
64
|
// Should be exported from `hono/router`
|
|
42
65
|
|
|
43
66
|
type P = any
|
|
@@ -47,19 +70,31 @@ type AsyncResponse = Response | Promise<Response>
|
|
|
47
70
|
type OnError = (x: { error: any; request: Request }) => AsyncResponse
|
|
48
71
|
|
|
49
72
|
type RouterTree = {
|
|
73
|
+
id: number
|
|
50
74
|
router: OriginalRouter
|
|
51
75
|
prefix?: string
|
|
52
76
|
onRequestHandlers: Function[]
|
|
53
77
|
onErrorHandlers: OnError[]
|
|
54
78
|
children: RouterTree[]
|
|
79
|
+
routes: InternalRoute[]
|
|
80
|
+
// default store for the router, used as default for context.store
|
|
55
81
|
store: Record<any, any>
|
|
82
|
+
currentRoot?: RouterTree
|
|
56
83
|
}
|
|
57
84
|
|
|
58
85
|
type OnNoMatch = (request: Request, platform: P) => AsyncResponse
|
|
59
86
|
|
|
60
|
-
type
|
|
61
|
-
|
|
62
|
-
|
|
87
|
+
export type InternalRoute = {
|
|
88
|
+
method: HTTPMethod
|
|
89
|
+
path: string
|
|
90
|
+
handler: InlineHandler<any, any, any>
|
|
91
|
+
hooks: LocalHook<any, any, any, any, any, any, any>
|
|
92
|
+
validateBody?: ValidateFunction
|
|
93
|
+
validateQuery?: ValidateFunction
|
|
94
|
+
validateParams?: ValidateFunction
|
|
95
|
+
|
|
96
|
+
prefix: string
|
|
97
|
+
|
|
63
98
|
// store: Record<any, any>
|
|
64
99
|
}
|
|
65
100
|
/**
|
|
@@ -95,40 +130,78 @@ export class Spiceflow<
|
|
|
95
130
|
derive: {}
|
|
96
131
|
resolve: {}
|
|
97
132
|
schema: {}
|
|
98
|
-
}
|
|
133
|
+
},
|
|
99
134
|
> {
|
|
100
135
|
private onNoMatch: OnNoMatch
|
|
101
136
|
// prefix: BasePath | undefined
|
|
102
|
-
routerTree: RouterTree
|
|
137
|
+
private routerTree: RouterTree
|
|
138
|
+
|
|
139
|
+
getAllRoutes() {
|
|
140
|
+
let root = this.routerTree.currentRoot || this.routerTree
|
|
141
|
+
const allApps = bfs(root) || []
|
|
142
|
+
const allRoutes = allApps.flatMap((x) => {
|
|
143
|
+
const prefix = this.getRouteAndParents(x)
|
|
144
|
+
.map((x) => x.prefix)
|
|
145
|
+
.reverse()
|
|
146
|
+
.join('')
|
|
147
|
+
|
|
148
|
+
return x.routes.map((x) => ({ ...x, path: prefix + x.path }))
|
|
149
|
+
})
|
|
150
|
+
return allRoutes
|
|
151
|
+
}
|
|
103
152
|
|
|
104
|
-
add({
|
|
153
|
+
private add({
|
|
105
154
|
method,
|
|
106
155
|
path,
|
|
156
|
+
hooks,
|
|
157
|
+
handler,
|
|
107
158
|
...rest
|
|
108
|
-
}:
|
|
109
|
-
method: string
|
|
110
|
-
path: string
|
|
111
|
-
}) {
|
|
159
|
+
}: Partial<InternalRoute>) {
|
|
112
160
|
const router = this.routerTree
|
|
113
161
|
// if (router.prefix) {
|
|
114
162
|
// path = router.prefix + path
|
|
115
163
|
// }
|
|
116
164
|
|
|
165
|
+
let bodySchema: TypeSchema = hooks?.body
|
|
166
|
+
let validateBody = getValidateFunction(bodySchema)
|
|
167
|
+
let validateQuery = getValidateFunction(hooks?.query)
|
|
168
|
+
let validateParams = getValidateFunction(hooks?.params)
|
|
169
|
+
|
|
117
170
|
const store = router.router.register(path)
|
|
118
|
-
|
|
171
|
+
let route: InternalRoute = {
|
|
172
|
+
...rest,
|
|
173
|
+
|
|
174
|
+
prefix: router.prefix || '',
|
|
175
|
+
method: (method || '') as any,
|
|
176
|
+
path: path || '',
|
|
177
|
+
// prefix,
|
|
178
|
+
handler: handler!,
|
|
179
|
+
hooks,
|
|
180
|
+
validateBody,
|
|
181
|
+
validateParams,
|
|
182
|
+
validateQuery,
|
|
183
|
+
}
|
|
184
|
+
router.routes.push(route)
|
|
185
|
+
store[method] = route
|
|
119
186
|
}
|
|
120
187
|
|
|
121
188
|
private match(method: string, path: string) {
|
|
122
|
-
|
|
123
|
-
|
|
189
|
+
let root = this.routerTree
|
|
190
|
+
const result = bfsFind(this.routerTree, (router) => {
|
|
191
|
+
router.currentRoot = root
|
|
192
|
+
let prefix = this.getRouteAndParents(router)
|
|
193
|
+
.map((x) => x.prefix)
|
|
194
|
+
.reverse()
|
|
195
|
+
.join('')
|
|
196
|
+
if (prefix && !path.startsWith(prefix)) {
|
|
124
197
|
// console.log(
|
|
125
198
|
// `router prefix: ${router.prefix} does not match path: ${path}`
|
|
126
199
|
// )
|
|
127
200
|
return
|
|
128
201
|
}
|
|
129
202
|
let pathWithoutPrefix = path
|
|
130
|
-
if (
|
|
131
|
-
pathWithoutPrefix = path.replace(
|
|
203
|
+
if (prefix) {
|
|
204
|
+
pathWithoutPrefix = path.replace(prefix, '')
|
|
132
205
|
}
|
|
133
206
|
// console.log(`router prefix: ${router.prefix} matches path: ${path}`)
|
|
134
207
|
const route = router.router.find(pathWithoutPrefix)
|
|
@@ -136,19 +209,20 @@ export class Spiceflow<
|
|
|
136
209
|
return
|
|
137
210
|
}
|
|
138
211
|
|
|
139
|
-
let data:
|
|
212
|
+
let data: InternalRoute = route['store'][method]
|
|
140
213
|
if (data) {
|
|
141
214
|
// console.log(`route found: ${method} ${path}`, route)
|
|
142
215
|
|
|
143
216
|
const { onErrorHandlers, onRequestHandlers } = router
|
|
144
217
|
const params = route['params'] || {}
|
|
218
|
+
|
|
145
219
|
return {
|
|
146
220
|
...data,
|
|
147
221
|
router,
|
|
148
222
|
store: router.store,
|
|
149
223
|
onErrorHandlers,
|
|
150
224
|
onRequestHandlers,
|
|
151
|
-
params
|
|
225
|
+
params,
|
|
152
226
|
}
|
|
153
227
|
}
|
|
154
228
|
})
|
|
@@ -158,7 +232,7 @@ export class Spiceflow<
|
|
|
158
232
|
|
|
159
233
|
state<const Name extends string | number | symbol, Value>(
|
|
160
234
|
name: Name,
|
|
161
|
-
value: Value
|
|
235
|
+
value: Value,
|
|
162
236
|
): Spiceflow<
|
|
163
237
|
BasePath,
|
|
164
238
|
Scoped,
|
|
@@ -189,24 +263,25 @@ export class Spiceflow<
|
|
|
189
263
|
*/
|
|
190
264
|
constructor(
|
|
191
265
|
options: {
|
|
192
|
-
|
|
193
|
-
// onError?: OnError
|
|
266
|
+
name?: string
|
|
194
267
|
scoped?: Scoped
|
|
195
268
|
onNoMatch?: (request: Request, platform: P) => AsyncResponse
|
|
196
269
|
basePath?: BasePath
|
|
197
|
-
} = {}
|
|
270
|
+
} = {},
|
|
198
271
|
) {
|
|
199
272
|
this.scoped = options.scoped
|
|
200
273
|
|
|
201
274
|
this.onNoMatch =
|
|
202
275
|
options.onNoMatch ?? (() => new Response(null, { status: 404 }))
|
|
203
276
|
this.routerTree = {
|
|
277
|
+
id: globalIndex++,
|
|
204
278
|
router: new OriginalRouter(),
|
|
205
279
|
prefix: options.basePath,
|
|
206
280
|
onRequestHandlers: [],
|
|
207
281
|
onErrorHandlers: [],
|
|
208
282
|
children: [],
|
|
209
|
-
store: {}
|
|
283
|
+
store: {},
|
|
284
|
+
routes: [],
|
|
210
285
|
}
|
|
211
286
|
|
|
212
287
|
// Bind router methods
|
|
@@ -224,13 +299,12 @@ export class Spiceflow<
|
|
|
224
299
|
Scoped: false as Scoped,
|
|
225
300
|
Singleton: {} as Singleton,
|
|
226
301
|
Definitions: {} as Definitions,
|
|
227
|
-
Metadata: {} as Metadata
|
|
302
|
+
Metadata: {} as Metadata,
|
|
228
303
|
}
|
|
229
304
|
|
|
230
305
|
_ephemeral = {} as Ephemeral
|
|
231
306
|
_volatile = {} as Volatile
|
|
232
307
|
|
|
233
|
-
|
|
234
308
|
post<
|
|
235
309
|
const Path extends string,
|
|
236
310
|
const LocalSchema extends InputSchema<
|
|
@@ -250,7 +324,7 @@ export class Spiceflow<
|
|
|
250
324
|
resolve: Ephemeral['resolve'] & Volatile['resolve']
|
|
251
325
|
},
|
|
252
326
|
JoinPath<BasePath, Path>
|
|
253
|
-
|
|
327
|
+
>,
|
|
254
328
|
>(
|
|
255
329
|
path: Path,
|
|
256
330
|
handler: Handle,
|
|
@@ -264,7 +338,7 @@ export class Spiceflow<
|
|
|
264
338
|
Definitions['error'],
|
|
265
339
|
Metadata['macro'],
|
|
266
340
|
JoinPath<BasePath, Path>
|
|
267
|
-
|
|
341
|
+
>,
|
|
268
342
|
): Spiceflow<
|
|
269
343
|
BasePath,
|
|
270
344
|
Scoped,
|
|
@@ -281,7 +355,7 @@ export class Spiceflow<
|
|
|
281
355
|
? ResolvePath<Path>
|
|
282
356
|
: Schema['params']
|
|
283
357
|
query: Schema['query']
|
|
284
|
-
|
|
358
|
+
|
|
285
359
|
response: ComposeSpiceflowResponse<
|
|
286
360
|
Schema['response'],
|
|
287
361
|
Handle
|
|
@@ -292,7 +366,7 @@ export class Spiceflow<
|
|
|
292
366
|
Ephemeral,
|
|
293
367
|
Volatile
|
|
294
368
|
> {
|
|
295
|
-
this.add({ method: 'POST', path, handler: handler, hook })
|
|
369
|
+
this.add({ method: 'POST', path, handler: handler, hooks: hook })
|
|
296
370
|
|
|
297
371
|
return this as any
|
|
298
372
|
}
|
|
@@ -317,7 +391,7 @@ export class Spiceflow<
|
|
|
317
391
|
resolve: Ephemeral['resolve'] & Volatile['resolve']
|
|
318
392
|
},
|
|
319
393
|
JoinPath<BasePath, Path>
|
|
320
|
-
|
|
394
|
+
>,
|
|
321
395
|
>(
|
|
322
396
|
path: Path,
|
|
323
397
|
handler: Handle,
|
|
@@ -331,7 +405,7 @@ export class Spiceflow<
|
|
|
331
405
|
Definitions['error'],
|
|
332
406
|
Macro,
|
|
333
407
|
JoinPath<BasePath, Path>
|
|
334
|
-
|
|
408
|
+
>,
|
|
335
409
|
): Spiceflow<
|
|
336
410
|
BasePath,
|
|
337
411
|
Scoped,
|
|
@@ -348,7 +422,7 @@ export class Spiceflow<
|
|
|
348
422
|
? ResolvePath<Path>
|
|
349
423
|
: Schema['params']
|
|
350
424
|
query: Schema['query']
|
|
351
|
-
|
|
425
|
+
|
|
352
426
|
response: ComposeSpiceflowResponse<
|
|
353
427
|
Schema['response'],
|
|
354
428
|
Handle
|
|
@@ -359,7 +433,7 @@ export class Spiceflow<
|
|
|
359
433
|
Ephemeral,
|
|
360
434
|
Volatile
|
|
361
435
|
> {
|
|
362
|
-
this.add({ method: 'GET', path, handler: handler, hook })
|
|
436
|
+
this.add({ method: 'GET', path, handler: handler, hooks: hook })
|
|
363
437
|
return this as any
|
|
364
438
|
}
|
|
365
439
|
|
|
@@ -382,7 +456,7 @@ export class Spiceflow<
|
|
|
382
456
|
resolve: Ephemeral['resolve'] & Volatile['resolve']
|
|
383
457
|
},
|
|
384
458
|
JoinPath<BasePath, Path>
|
|
385
|
-
|
|
459
|
+
>,
|
|
386
460
|
>(
|
|
387
461
|
path: Path,
|
|
388
462
|
handler: Handle,
|
|
@@ -396,7 +470,7 @@ export class Spiceflow<
|
|
|
396
470
|
Definitions['error'],
|
|
397
471
|
Metadata['macro'],
|
|
398
472
|
JoinPath<BasePath, Path>
|
|
399
|
-
|
|
473
|
+
>,
|
|
400
474
|
): Spiceflow<
|
|
401
475
|
BasePath,
|
|
402
476
|
Scoped,
|
|
@@ -413,7 +487,7 @@ export class Spiceflow<
|
|
|
413
487
|
? ResolvePath<Path>
|
|
414
488
|
: Schema['params']
|
|
415
489
|
query: Schema['query']
|
|
416
|
-
|
|
490
|
+
|
|
417
491
|
response: ComposeSpiceflowResponse<
|
|
418
492
|
Schema['response'],
|
|
419
493
|
Handle
|
|
@@ -424,7 +498,7 @@ export class Spiceflow<
|
|
|
424
498
|
Ephemeral,
|
|
425
499
|
Volatile
|
|
426
500
|
> {
|
|
427
|
-
this.add({ method: 'PUT', path, handler: handler, hook })
|
|
501
|
+
this.add({ method: 'PUT', path, handler: handler, hooks: hook })
|
|
428
502
|
|
|
429
503
|
return this as any
|
|
430
504
|
}
|
|
@@ -448,7 +522,7 @@ export class Spiceflow<
|
|
|
448
522
|
resolve: Ephemeral['resolve'] & Volatile['resolve']
|
|
449
523
|
},
|
|
450
524
|
JoinPath<BasePath, Path>
|
|
451
|
-
|
|
525
|
+
>,
|
|
452
526
|
>(
|
|
453
527
|
path: Path,
|
|
454
528
|
handler: Handle,
|
|
@@ -462,7 +536,7 @@ export class Spiceflow<
|
|
|
462
536
|
Definitions['error'],
|
|
463
537
|
Metadata['macro'],
|
|
464
538
|
JoinPath<BasePath, Path>
|
|
465
|
-
|
|
539
|
+
>,
|
|
466
540
|
): Spiceflow<
|
|
467
541
|
BasePath,
|
|
468
542
|
Scoped,
|
|
@@ -479,7 +553,7 @@ export class Spiceflow<
|
|
|
479
553
|
? ResolvePath<Path>
|
|
480
554
|
: Schema['params']
|
|
481
555
|
query: Schema['query']
|
|
482
|
-
|
|
556
|
+
|
|
483
557
|
response: ComposeSpiceflowResponse<
|
|
484
558
|
Schema['response'],
|
|
485
559
|
Handle
|
|
@@ -490,7 +564,7 @@ export class Spiceflow<
|
|
|
490
564
|
Ephemeral,
|
|
491
565
|
Volatile
|
|
492
566
|
> {
|
|
493
|
-
this.add({ method: 'PATCH', path, handler: handler, hook })
|
|
567
|
+
this.add({ method: 'PATCH', path, handler: handler, hooks: hook })
|
|
494
568
|
|
|
495
569
|
return this as any
|
|
496
570
|
}
|
|
@@ -514,7 +588,7 @@ export class Spiceflow<
|
|
|
514
588
|
resolve: Ephemeral['resolve'] & Volatile['resolve']
|
|
515
589
|
},
|
|
516
590
|
JoinPath<BasePath, Path>
|
|
517
|
-
|
|
591
|
+
>,
|
|
518
592
|
>(
|
|
519
593
|
path: Path,
|
|
520
594
|
handler: Handle,
|
|
@@ -528,7 +602,7 @@ export class Spiceflow<
|
|
|
528
602
|
Definitions['error'],
|
|
529
603
|
Metadata['macro'],
|
|
530
604
|
JoinPath<BasePath, Path>
|
|
531
|
-
|
|
605
|
+
>,
|
|
532
606
|
): Spiceflow<
|
|
533
607
|
BasePath,
|
|
534
608
|
Scoped,
|
|
@@ -545,7 +619,7 @@ export class Spiceflow<
|
|
|
545
619
|
? ResolvePath<Path>
|
|
546
620
|
: Schema['params']
|
|
547
621
|
query: Schema['query']
|
|
548
|
-
|
|
622
|
+
|
|
549
623
|
response: ComposeSpiceflowResponse<
|
|
550
624
|
Schema['response'],
|
|
551
625
|
Handle
|
|
@@ -556,7 +630,7 @@ export class Spiceflow<
|
|
|
556
630
|
Ephemeral,
|
|
557
631
|
Volatile
|
|
558
632
|
> {
|
|
559
|
-
this.add({ method: 'DELETE', path, handler: handler, hook })
|
|
633
|
+
this.add({ method: 'DELETE', path, handler: handler, hooks: hook })
|
|
560
634
|
|
|
561
635
|
return this as any
|
|
562
636
|
}
|
|
@@ -580,7 +654,7 @@ export class Spiceflow<
|
|
|
580
654
|
resolve: Ephemeral['resolve'] & Volatile['resolve']
|
|
581
655
|
},
|
|
582
656
|
JoinPath<BasePath, Path>
|
|
583
|
-
|
|
657
|
+
>,
|
|
584
658
|
>(
|
|
585
659
|
path: Path,
|
|
586
660
|
handler: Handle,
|
|
@@ -594,7 +668,7 @@ export class Spiceflow<
|
|
|
594
668
|
Definitions['error'],
|
|
595
669
|
Metadata['macro'],
|
|
596
670
|
JoinPath<BasePath, Path>
|
|
597
|
-
|
|
671
|
+
>,
|
|
598
672
|
): Spiceflow<
|
|
599
673
|
BasePath,
|
|
600
674
|
Scoped,
|
|
@@ -611,7 +685,7 @@ export class Spiceflow<
|
|
|
611
685
|
? ResolvePath<Path>
|
|
612
686
|
: Schema['params']
|
|
613
687
|
query: Schema['query']
|
|
614
|
-
|
|
688
|
+
|
|
615
689
|
response: ComposeSpiceflowResponse<
|
|
616
690
|
Schema['response'],
|
|
617
691
|
Handle
|
|
@@ -622,7 +696,7 @@ export class Spiceflow<
|
|
|
622
696
|
Ephemeral,
|
|
623
697
|
Volatile
|
|
624
698
|
> {
|
|
625
|
-
this.add({ method: 'OPTIONS', path, handler: handler, hook })
|
|
699
|
+
this.add({ method: 'OPTIONS', path, handler: handler, hooks: hook })
|
|
626
700
|
|
|
627
701
|
return this as any
|
|
628
702
|
}
|
|
@@ -646,7 +720,7 @@ export class Spiceflow<
|
|
|
646
720
|
resolve: Ephemeral['resolve'] & Volatile['resolve']
|
|
647
721
|
},
|
|
648
722
|
JoinPath<BasePath, Path>
|
|
649
|
-
|
|
723
|
+
>,
|
|
650
724
|
>(
|
|
651
725
|
path: Path,
|
|
652
726
|
handler: Handle,
|
|
@@ -660,7 +734,7 @@ export class Spiceflow<
|
|
|
660
734
|
Definitions['error'],
|
|
661
735
|
Metadata['macro'],
|
|
662
736
|
JoinPath<BasePath, Path>
|
|
663
|
-
|
|
737
|
+
>,
|
|
664
738
|
): Spiceflow<
|
|
665
739
|
BasePath,
|
|
666
740
|
Scoped,
|
|
@@ -677,7 +751,7 @@ export class Spiceflow<
|
|
|
677
751
|
? ResolvePath<Path>
|
|
678
752
|
: Schema['params']
|
|
679
753
|
query: Schema['query']
|
|
680
|
-
|
|
754
|
+
|
|
681
755
|
response: ComposeSpiceflowResponse<
|
|
682
756
|
Schema['response'],
|
|
683
757
|
Handle
|
|
@@ -689,7 +763,7 @@ export class Spiceflow<
|
|
|
689
763
|
Volatile
|
|
690
764
|
> {
|
|
691
765
|
for (const method of METHODS) {
|
|
692
|
-
this.add({ method, path, handler: handler, hook })
|
|
766
|
+
this.add({ method, path, handler: handler, hooks: hook })
|
|
693
767
|
}
|
|
694
768
|
|
|
695
769
|
return this as any
|
|
@@ -714,7 +788,7 @@ export class Spiceflow<
|
|
|
714
788
|
resolve: Ephemeral['resolve'] & Volatile['resolve']
|
|
715
789
|
},
|
|
716
790
|
JoinPath<BasePath, Path>
|
|
717
|
-
|
|
791
|
+
>,
|
|
718
792
|
>(
|
|
719
793
|
path: Path,
|
|
720
794
|
handler: Handle,
|
|
@@ -728,7 +802,7 @@ export class Spiceflow<
|
|
|
728
802
|
Definitions['error'],
|
|
729
803
|
Metadata['macro'],
|
|
730
804
|
JoinPath<BasePath, Path>
|
|
731
|
-
|
|
805
|
+
>,
|
|
732
806
|
): Spiceflow<
|
|
733
807
|
BasePath,
|
|
734
808
|
Scoped,
|
|
@@ -745,7 +819,7 @@ export class Spiceflow<
|
|
|
745
819
|
? ResolvePath<Path>
|
|
746
820
|
: Schema['params']
|
|
747
821
|
query: Schema['query']
|
|
748
|
-
|
|
822
|
+
|
|
749
823
|
response: ComposeSpiceflowResponse<
|
|
750
824
|
Schema['response'],
|
|
751
825
|
Handle
|
|
@@ -756,7 +830,7 @@ export class Spiceflow<
|
|
|
756
830
|
Ephemeral,
|
|
757
831
|
Volatile
|
|
758
832
|
> {
|
|
759
|
-
this.add({ method: 'HEAD', path, handler: handler, hook })
|
|
833
|
+
this.add({ method: 'HEAD', path, handler: handler, hooks: hook })
|
|
760
834
|
|
|
761
835
|
return this as any
|
|
762
836
|
}
|
|
@@ -766,7 +840,7 @@ export class Spiceflow<
|
|
|
766
840
|
*
|
|
767
841
|
* @default false
|
|
768
842
|
*/
|
|
769
|
-
scoped?: Scoped
|
|
843
|
+
private scoped?: Scoped
|
|
770
844
|
get _scoped() {
|
|
771
845
|
return this.scoped as Scoped
|
|
772
846
|
}
|
|
@@ -811,7 +885,7 @@ export class Spiceflow<
|
|
|
811
885
|
// }
|
|
812
886
|
|
|
813
887
|
use<const NewSpiceflow extends AnySpiceflow>(
|
|
814
|
-
instance: NewSpiceflow
|
|
888
|
+
instance: NewSpiceflow,
|
|
815
889
|
): NewSpiceflow['_scoped'] extends false
|
|
816
890
|
? Spiceflow<
|
|
817
891
|
BasePath,
|
|
@@ -840,14 +914,7 @@ export class Spiceflow<
|
|
|
840
914
|
> {
|
|
841
915
|
const thisRouter = this.routerTree
|
|
842
916
|
// TODO use scoped logic to add onRequest and onError on all routers if necessary, add them first
|
|
843
|
-
this.routerTree.children.push(
|
|
844
|
-
mapBfs(instance.routerTree, (r) => {
|
|
845
|
-
return {
|
|
846
|
-
...r,
|
|
847
|
-
prefix: (thisRouter.prefix || '') + r.prefix
|
|
848
|
-
}
|
|
849
|
-
})
|
|
850
|
-
)
|
|
917
|
+
this.routerTree.children.push(instance.routerTree)
|
|
851
918
|
return this as any
|
|
852
919
|
}
|
|
853
920
|
|
|
@@ -866,7 +933,7 @@ export class Spiceflow<
|
|
|
866
933
|
Ephemeral,
|
|
867
934
|
Volatile
|
|
868
935
|
>
|
|
869
|
-
|
|
936
|
+
>,
|
|
870
937
|
): this {
|
|
871
938
|
const router = this.routerTree
|
|
872
939
|
|
|
@@ -893,7 +960,7 @@ export class Spiceflow<
|
|
|
893
960
|
resolve: {}
|
|
894
961
|
}
|
|
895
962
|
>
|
|
896
|
-
|
|
963
|
+
>,
|
|
897
964
|
) {
|
|
898
965
|
const router = this.routerTree
|
|
899
966
|
router.onRequestHandlers ??= []
|
|
@@ -909,12 +976,13 @@ export class Spiceflow<
|
|
|
909
976
|
*/
|
|
910
977
|
async handle(request: Request, platform?: P): Promise<Response> {
|
|
911
978
|
platform ??= {} as P
|
|
979
|
+
|
|
912
980
|
let u = new URL(request.url)
|
|
913
981
|
let path = u.pathname + u.search
|
|
914
982
|
const defaultContext = {
|
|
915
983
|
redirect,
|
|
916
984
|
error: null,
|
|
917
|
-
path
|
|
985
|
+
path,
|
|
918
986
|
}
|
|
919
987
|
let onErrorHandlers: OnError[] = []
|
|
920
988
|
try {
|
|
@@ -925,41 +993,37 @@ export class Spiceflow<
|
|
|
925
993
|
if (!route) {
|
|
926
994
|
return this.onNoMatch(request, platform)
|
|
927
995
|
}
|
|
928
|
-
onErrorHandlers = this.getRouteAndParents(route.router)
|
|
929
|
-
(
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
const
|
|
933
|
-
(
|
|
934
|
-
|
|
996
|
+
onErrorHandlers = this.getRouteAndParents(route.router)
|
|
997
|
+
.reverse()
|
|
998
|
+
.flatMap((x) => x.onErrorHandlers)
|
|
999
|
+
let { params, store: defaultStore } = route
|
|
1000
|
+
const onReqHandlers = this.getRouteAndParents(route.router)
|
|
1001
|
+
.reverse()
|
|
1002
|
+
.flatMap((x) => x.onRequestHandlers)
|
|
1003
|
+
// console.log({ onReqHandlers })
|
|
1004
|
+
let store = { ...defaultStore }
|
|
935
1005
|
// TODO add content type
|
|
936
1006
|
|
|
937
|
-
let content = route?.
|
|
938
|
-
let body = await getRequestBody({ request, content })
|
|
939
|
-
let bodySchema: TSchema = route?.hook?.body
|
|
940
|
-
if (bodySchema) {
|
|
941
|
-
const validate = ajv.compile(bodySchema)
|
|
942
|
-
const valid = validate(body)
|
|
943
|
-
if (!valid) {
|
|
944
|
-
const error = ajv.errorsText(validate.errors, {
|
|
945
|
-
separator: '\n'
|
|
946
|
-
})
|
|
1007
|
+
let content = route?.hooks?.content
|
|
1008
|
+
// let body = await getRequestBody({ request, content })
|
|
947
1009
|
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
})
|
|
954
|
-
}
|
|
1010
|
+
if (route.validateBody) {
|
|
1011
|
+
// TODO don't clone the request
|
|
1012
|
+
let typedRequest = new TypedRequest(request)
|
|
1013
|
+
typedRequest.validateBody = route.validateBody
|
|
1014
|
+
request = typedRequest
|
|
955
1015
|
}
|
|
956
|
-
|
|
957
|
-
|
|
1016
|
+
|
|
1017
|
+
let query = parseQuery.parse((u.search || '').slice(1))
|
|
1018
|
+
|
|
1019
|
+
if (onReqHandlers.length > 0) {
|
|
1020
|
+
for (const handler of onReqHandlers) {
|
|
958
1021
|
const res = await handler({
|
|
959
1022
|
request,
|
|
960
1023
|
response,
|
|
961
1024
|
store,
|
|
962
|
-
path
|
|
1025
|
+
path,
|
|
1026
|
+
query,
|
|
963
1027
|
} satisfies Context<any, any, any>)
|
|
964
1028
|
if (res) {
|
|
965
1029
|
return await turnHandlerResultIntoResponse(res)
|
|
@@ -967,6 +1031,9 @@ export class Spiceflow<
|
|
|
967
1031
|
}
|
|
968
1032
|
}
|
|
969
1033
|
|
|
1034
|
+
query = runValidation(query, route.validateQuery)
|
|
1035
|
+
params = runValidation(params, route.validateParams)
|
|
1036
|
+
|
|
970
1037
|
// console.log(route)
|
|
971
1038
|
|
|
972
1039
|
const res = route.handler({
|
|
@@ -975,8 +1042,9 @@ export class Spiceflow<
|
|
|
975
1042
|
response,
|
|
976
1043
|
params: params as any,
|
|
977
1044
|
store,
|
|
978
|
-
|
|
979
|
-
|
|
1045
|
+
query,
|
|
1046
|
+
// body,
|
|
1047
|
+
path,
|
|
980
1048
|
|
|
981
1049
|
// platform
|
|
982
1050
|
} satisfies Context<any, any, string>)
|
|
@@ -984,20 +1052,22 @@ export class Spiceflow<
|
|
|
984
1052
|
return await this.handleStream({
|
|
985
1053
|
generator: res,
|
|
986
1054
|
request,
|
|
987
|
-
onErrorHandlers
|
|
1055
|
+
onErrorHandlers,
|
|
988
1056
|
})
|
|
989
1057
|
}
|
|
990
1058
|
|
|
991
1059
|
return await turnHandlerResultIntoResponse(res)
|
|
992
1060
|
} catch (err: any) {
|
|
1061
|
+
if (err instanceof Response) return err
|
|
993
1062
|
let res = await this.runErrorHandlers({
|
|
994
1063
|
onErrorHandlers,
|
|
995
1064
|
error: err,
|
|
996
|
-
request
|
|
1065
|
+
request,
|
|
997
1066
|
})
|
|
998
1067
|
if (res) return res
|
|
1068
|
+
let status = err?.status ?? 500
|
|
999
1069
|
return new Response(err?.message || 'Internal Server Error', {
|
|
1000
|
-
status
|
|
1070
|
+
status,
|
|
1001
1071
|
})
|
|
1002
1072
|
}
|
|
1003
1073
|
}
|
|
@@ -1005,7 +1075,7 @@ export class Spiceflow<
|
|
|
1005
1075
|
private async runErrorHandlers({
|
|
1006
1076
|
onErrorHandlers = [] as OnError[],
|
|
1007
1077
|
error: err,
|
|
1008
|
-
request
|
|
1078
|
+
request,
|
|
1009
1079
|
}) {
|
|
1010
1080
|
if (onErrorHandlers.length === 0) {
|
|
1011
1081
|
console.error(`Spiceflow unhandled error:`, err)
|
|
@@ -1019,31 +1089,33 @@ export class Spiceflow<
|
|
|
1019
1089
|
}
|
|
1020
1090
|
}
|
|
1021
1091
|
|
|
1092
|
+
// get the route parents, the order is starting from the current router and going up to the root
|
|
1022
1093
|
private getRouteAndParents(currentRouter?: RouterTree) {
|
|
1023
1094
|
const parents: RouterTree[] = []
|
|
1024
1095
|
let current = currentRouter
|
|
1025
1096
|
|
|
1097
|
+
let root = this.routerTree.currentRoot || this.routerTree
|
|
1026
1098
|
// Perform BFS once to build a parent map
|
|
1027
|
-
const parentMap = new Map<
|
|
1028
|
-
|
|
1099
|
+
const parentMap = new Map<number, RouterTree>()
|
|
1100
|
+
bfsFind(root, (node) => {
|
|
1029
1101
|
for (const child of node.children) {
|
|
1030
|
-
parentMap.set(child, node)
|
|
1102
|
+
parentMap.set(child.id, node)
|
|
1031
1103
|
}
|
|
1032
1104
|
})
|
|
1033
1105
|
|
|
1034
1106
|
// Traverse the parent map to get the parents
|
|
1035
1107
|
while (current) {
|
|
1036
|
-
parents.
|
|
1037
|
-
current = parentMap.get(current)
|
|
1108
|
+
parents.push(current)
|
|
1109
|
+
current = parentMap.get(current.id)
|
|
1038
1110
|
}
|
|
1039
1111
|
|
|
1040
|
-
return parents.
|
|
1112
|
+
return parents.filter((x) => x !== undefined)
|
|
1041
1113
|
}
|
|
1042
1114
|
|
|
1043
|
-
async handleStream({
|
|
1115
|
+
private async handleStream({
|
|
1044
1116
|
onErrorHandlers,
|
|
1045
1117
|
generator,
|
|
1046
|
-
request
|
|
1118
|
+
request,
|
|
1047
1119
|
}: {
|
|
1048
1120
|
generator: Generator | AsyncGenerator
|
|
1049
1121
|
onErrorHandlers: OnError[]
|
|
@@ -1076,9 +1148,9 @@ export class Spiceflow<
|
|
|
1076
1148
|
controller.enqueue(
|
|
1077
1149
|
Buffer.from(
|
|
1078
1150
|
`event: message\ndata: ${JSON.stringify(
|
|
1079
|
-
init.value
|
|
1080
|
-
)}\n\n
|
|
1081
|
-
)
|
|
1151
|
+
init.value,
|
|
1152
|
+
)}\n\n`,
|
|
1153
|
+
),
|
|
1082
1154
|
)
|
|
1083
1155
|
|
|
1084
1156
|
try {
|
|
@@ -1089,23 +1161,23 @@ export class Spiceflow<
|
|
|
1089
1161
|
controller.enqueue(
|
|
1090
1162
|
Buffer.from(
|
|
1091
1163
|
`event: message\ndata: ${JSON.stringify(
|
|
1092
|
-
chunk
|
|
1093
|
-
)}\n\n
|
|
1094
|
-
)
|
|
1164
|
+
chunk,
|
|
1165
|
+
)}\n\n`,
|
|
1166
|
+
),
|
|
1095
1167
|
)
|
|
1096
1168
|
}
|
|
1097
1169
|
} catch (error: any) {
|
|
1098
1170
|
let res = await self.runErrorHandlers({
|
|
1099
1171
|
onErrorHandlers: onErrorHandlers,
|
|
1100
1172
|
error,
|
|
1101
|
-
request
|
|
1173
|
+
request,
|
|
1102
1174
|
})
|
|
1103
1175
|
controller.enqueue(
|
|
1104
1176
|
Buffer.from(
|
|
1105
1177
|
`event: error\ndata: ${JSON.stringify(
|
|
1106
|
-
error.message || error.name || 'Error'
|
|
1107
|
-
)}\n\n
|
|
1108
|
-
)
|
|
1178
|
+
error.message || error.name || 'Error',
|
|
1179
|
+
)}\n\n`,
|
|
1180
|
+
),
|
|
1109
1181
|
)
|
|
1110
1182
|
}
|
|
1111
1183
|
|
|
@@ -1114,74 +1186,74 @@ export class Spiceflow<
|
|
|
1114
1186
|
} catch {
|
|
1115
1187
|
// nothing
|
|
1116
1188
|
}
|
|
1117
|
-
}
|
|
1189
|
+
},
|
|
1118
1190
|
}),
|
|
1119
1191
|
{
|
|
1120
1192
|
// TODO add headers somehow
|
|
1121
1193
|
headers: {
|
|
1122
1194
|
// Manually set transfer-encoding for direct response, eg. app.handle, eden
|
|
1123
1195
|
'transfer-encoding': 'chunked',
|
|
1124
|
-
'content-type': 'text/event-stream; charset=utf-8'
|
|
1196
|
+
'content-type': 'text/event-stream; charset=utf-8',
|
|
1125
1197
|
// ...set?.headers
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1198
|
+
},
|
|
1199
|
+
},
|
|
1128
1200
|
)
|
|
1129
1201
|
}
|
|
1130
1202
|
}
|
|
1131
1203
|
|
|
1132
|
-
async function getRequestBody({
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
}: {
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
}) {
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1204
|
+
// async function getRequestBody({
|
|
1205
|
+
// request,
|
|
1206
|
+
// content,
|
|
1207
|
+
// }: {
|
|
1208
|
+
// content
|
|
1209
|
+
// request: Request
|
|
1210
|
+
// }) {
|
|
1211
|
+
// let body: string | Record<string, any> | undefined
|
|
1212
|
+
// if (request.method === 'GET' || request.method === 'HEAD') {
|
|
1213
|
+
// return
|
|
1214
|
+
// }
|
|
1143
1215
|
|
|
1144
|
-
|
|
1145
|
-
|
|
1216
|
+
// const contentType =
|
|
1217
|
+
// content || request.headers.get('content-type')?.split(';')?.[0]
|
|
1146
1218
|
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1219
|
+
// if (!contentType) {
|
|
1220
|
+
// return
|
|
1221
|
+
// }
|
|
1150
1222
|
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1223
|
+
// switch (contentType) {
|
|
1224
|
+
// case 'application/json':
|
|
1225
|
+
// body = (await request.json()) as any
|
|
1226
|
+
// break
|
|
1155
1227
|
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1228
|
+
// case 'text/plain':
|
|
1229
|
+
// body = await request.text()
|
|
1230
|
+
// break
|
|
1159
1231
|
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1232
|
+
// case 'application/x-www-form-urlencoded':
|
|
1233
|
+
// body = parseQuery.parse(await request.text()) as any
|
|
1234
|
+
// break
|
|
1163
1235
|
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1236
|
+
// case 'application/octet-stream':
|
|
1237
|
+
// body = await request.arrayBuffer()
|
|
1238
|
+
// break
|
|
1167
1239
|
|
|
1168
|
-
|
|
1169
|
-
|
|
1240
|
+
// case 'multipart/form-data':
|
|
1241
|
+
// body = {}
|
|
1170
1242
|
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1243
|
+
// const form = await request.formData()
|
|
1244
|
+
// for (const key of form.keys()) {
|
|
1245
|
+
// if (body[key]) continue
|
|
1174
1246
|
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1247
|
+
// const value = form.getAll(key)
|
|
1248
|
+
// if (value.length === 1) body[key] = value[0]
|
|
1249
|
+
// else body[key] = value
|
|
1250
|
+
// }
|
|
1179
1251
|
|
|
1180
|
-
|
|
1181
|
-
|
|
1252
|
+
// break
|
|
1253
|
+
// }
|
|
1182
1254
|
|
|
1183
|
-
|
|
1184
|
-
}
|
|
1255
|
+
// return body
|
|
1256
|
+
// }
|
|
1185
1257
|
|
|
1186
1258
|
const METHODS = [
|
|
1187
1259
|
'ALL',
|
|
@@ -1193,41 +1265,59 @@ const METHODS = [
|
|
|
1193
1265
|
'PATCH',
|
|
1194
1266
|
'POST',
|
|
1195
1267
|
'PUT',
|
|
1196
|
-
'TRACE'
|
|
1268
|
+
'TRACE',
|
|
1197
1269
|
] as const
|
|
1198
1270
|
|
|
1199
1271
|
/** HTTP method string */
|
|
1200
1272
|
export type Method = (typeof METHODS)[number]
|
|
1201
1273
|
|
|
1202
|
-
function
|
|
1274
|
+
function bfsFind<T>(
|
|
1203
1275
|
tree: RouterTree,
|
|
1204
|
-
onNode: (node: RouterTree) => T | undefined | void
|
|
1276
|
+
onNode: (node: RouterTree) => T | undefined | void,
|
|
1205
1277
|
): T | undefined {
|
|
1206
1278
|
const queue = [tree]
|
|
1279
|
+
|
|
1207
1280
|
while (queue.length > 0) {
|
|
1208
1281
|
const node = queue.shift()!
|
|
1282
|
+
|
|
1209
1283
|
const result = onNode(node)
|
|
1210
1284
|
if (result) {
|
|
1211
1285
|
return result
|
|
1212
1286
|
}
|
|
1213
1287
|
queue.push(...node.children)
|
|
1214
1288
|
}
|
|
1215
|
-
return
|
|
1289
|
+
return
|
|
1216
1290
|
}
|
|
1291
|
+
export class TypedRequest<T = any> extends Request {
|
|
1292
|
+
validateBody?: ValidateFunction
|
|
1217
1293
|
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1294
|
+
async json(): Promise<T> {
|
|
1295
|
+
const body = (await super.json()) as Promise<T>
|
|
1296
|
+
return runValidation(body, this.validateBody)
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
export function bfs(tree: RouterTree) {
|
|
1222
1301
|
const queue = [tree]
|
|
1223
|
-
|
|
1302
|
+
let nodes: RouterTree[] = []
|
|
1224
1303
|
while (queue.length > 0) {
|
|
1225
1304
|
const node = queue.shift()!
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1305
|
+
if (node) {
|
|
1306
|
+
nodes.push(node)
|
|
1307
|
+
}
|
|
1308
|
+
// const result = onNode(node)
|
|
1309
|
+
|
|
1310
|
+
queue.push(...node.children)
|
|
1229
1311
|
}
|
|
1230
|
-
return
|
|
1312
|
+
return nodes
|
|
1313
|
+
}
|
|
1314
|
+
function mapTree<T>(
|
|
1315
|
+
tree: RouterTree,
|
|
1316
|
+
mapper: (node: RouterTree) => T,
|
|
1317
|
+
): T & { children: (T & { children: any[] })[] } {
|
|
1318
|
+
const mappedNode = mapper(tree) as T & { children: any[] }
|
|
1319
|
+
mappedNode.children = tree.children.map((child) => mapTree(child, mapper))
|
|
1320
|
+
return mappedNode
|
|
1231
1321
|
}
|
|
1232
1322
|
|
|
1233
1323
|
export async function turnHandlerResultIntoResponse(result: any) {
|
|
@@ -1245,7 +1335,46 @@ export async function turnHandlerResultIntoResponse(result: any) {
|
|
|
1245
1335
|
// }
|
|
1246
1336
|
// if user returns an object, convert to json
|
|
1247
1337
|
|
|
1248
|
-
return new Response(JSON.stringify(result)
|
|
1338
|
+
return new Response(JSON.stringify(result, null, 2), {
|
|
1339
|
+
headers: {
|
|
1340
|
+
'content-type': 'application/json',
|
|
1341
|
+
},
|
|
1342
|
+
})
|
|
1249
1343
|
}
|
|
1250
1344
|
|
|
1251
1345
|
export type AnySpiceflow = Spiceflow<any, any, any, any, any, any, any, any>
|
|
1346
|
+
|
|
1347
|
+
export function isZodSchema(value: unknown): value is ZodType {
|
|
1348
|
+
return (
|
|
1349
|
+
value instanceof z.ZodType ||
|
|
1350
|
+
(typeof value === 'object' &&
|
|
1351
|
+
value !== null &&
|
|
1352
|
+
'parse' in value &&
|
|
1353
|
+
'safeParse' in value &&
|
|
1354
|
+
'optional' in value &&
|
|
1355
|
+
'nullable' in value)
|
|
1356
|
+
)
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
function getValidateFunction(schema: TypeSchema) {
|
|
1360
|
+
if (isZodSchema(schema)) {
|
|
1361
|
+
let jsonSchema = zodToJsonSchema(schema, {})
|
|
1362
|
+
return ajv.compile(jsonSchema)
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
if (schema) {
|
|
1366
|
+
return ajv.compile(schema)
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
function runValidation(value: any, validate?: ValidateFunction) {
|
|
1371
|
+
if (!validate) return value
|
|
1372
|
+
const valid = validate(value)
|
|
1373
|
+
if (!valid) {
|
|
1374
|
+
const error = ajv.errorsText(validate.errors, {
|
|
1375
|
+
separator: '\n',
|
|
1376
|
+
})
|
|
1377
|
+
throw new ValidationError(error)
|
|
1378
|
+
}
|
|
1379
|
+
return value
|
|
1380
|
+
}
|