spiceflow 1.0.2 → 1.0.4
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 +3 -3
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +18 -21
- package/dist/client/index.js.map +1 -1
- package/dist/client/types.d.ts +3 -8
- package/dist/client/types.d.ts.map +1 -1
- package/dist/client.test.js +20 -17
- 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 -166
- 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 +8 -11
- package/dist/elysia-fork/types.d.ts.map +1 -1
- package/dist/elysia-fork/types.js.map +1 -1
- package/dist/openapi.d.ts +0 -1
- package/dist/openapi.d.ts.map +1 -1
- package/dist/spiceflow.d.ts +10 -10
- package/dist/spiceflow.d.ts.map +1 -1
- package/dist/spiceflow.js +94 -68
- package/dist/spiceflow.js.map +1 -1
- package/dist/spiceflow.test.js +75 -8
- package/dist/spiceflow.test.js.map +1 -1
- package/dist/zod.test.js +10 -8
- package/dist/zod.test.js.map +1 -1
- package/package.json +1 -1
- package/src/client/index.ts +38 -47
- package/src/client/types.ts +17 -22
- package/src/client.test.ts +28 -20
- package/src/elysia-fork/context.ts +19 -49
- package/src/elysia-fork/error.ts +6 -259
- package/src/elysia-fork/types.ts +28 -26
- package/src/spiceflow.test.ts +92 -8
- package/src/spiceflow.ts +112 -80
- package/src/zod.test.ts +10 -8
package/src/spiceflow.ts
CHANGED
|
@@ -89,7 +89,10 @@ export type InternalRoute = {
|
|
|
89
89
|
path: string
|
|
90
90
|
handler: InlineHandler<any, any, any>
|
|
91
91
|
hooks: LocalHook<any, any, any, any, any, any, any>
|
|
92
|
-
|
|
92
|
+
validateBody?: ValidateFunction
|
|
93
|
+
validateQuery?: ValidateFunction
|
|
94
|
+
validateParams?: ValidateFunction
|
|
95
|
+
|
|
93
96
|
prefix: string
|
|
94
97
|
|
|
95
98
|
// store: Record<any, any>
|
|
@@ -160,15 +163,9 @@ export class Spiceflow<
|
|
|
160
163
|
// }
|
|
161
164
|
|
|
162
165
|
let bodySchema: TypeSchema = hooks?.body
|
|
163
|
-
let
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
let jsonSchema = zodToJsonSchema(bodySchema, {})
|
|
167
|
-
validate = ajv.compile(jsonSchema)
|
|
168
|
-
} else if (bodySchema) {
|
|
169
|
-
// console.log(bodySchema)
|
|
170
|
-
validate = ajv.compile(bodySchema)
|
|
171
|
-
}
|
|
166
|
+
let validateBody = getValidateFunction(bodySchema)
|
|
167
|
+
let validateQuery = getValidateFunction(hooks?.query)
|
|
168
|
+
let validateParams = getValidateFunction(hooks?.params)
|
|
172
169
|
|
|
173
170
|
const store = router.router.register(path)
|
|
174
171
|
let route: InternalRoute = {
|
|
@@ -180,7 +177,9 @@ export class Spiceflow<
|
|
|
180
177
|
// prefix,
|
|
181
178
|
handler: handler!,
|
|
182
179
|
hooks,
|
|
183
|
-
|
|
180
|
+
validateBody,
|
|
181
|
+
validateParams,
|
|
182
|
+
validateQuery,
|
|
184
183
|
}
|
|
185
184
|
router.routes.push(route)
|
|
186
185
|
store[method] = route
|
|
@@ -216,6 +215,7 @@ export class Spiceflow<
|
|
|
216
215
|
|
|
217
216
|
const { onErrorHandlers, onRequestHandlers } = router
|
|
218
217
|
const params = route['params'] || {}
|
|
218
|
+
|
|
219
219
|
return {
|
|
220
220
|
...data,
|
|
221
221
|
router,
|
|
@@ -355,7 +355,7 @@ export class Spiceflow<
|
|
|
355
355
|
? ResolvePath<Path>
|
|
356
356
|
: Schema['params']
|
|
357
357
|
query: Schema['query']
|
|
358
|
-
|
|
358
|
+
|
|
359
359
|
response: ComposeSpiceflowResponse<
|
|
360
360
|
Schema['response'],
|
|
361
361
|
Handle
|
|
@@ -422,7 +422,7 @@ export class Spiceflow<
|
|
|
422
422
|
? ResolvePath<Path>
|
|
423
423
|
: Schema['params']
|
|
424
424
|
query: Schema['query']
|
|
425
|
-
|
|
425
|
+
|
|
426
426
|
response: ComposeSpiceflowResponse<
|
|
427
427
|
Schema['response'],
|
|
428
428
|
Handle
|
|
@@ -487,7 +487,7 @@ export class Spiceflow<
|
|
|
487
487
|
? ResolvePath<Path>
|
|
488
488
|
: Schema['params']
|
|
489
489
|
query: Schema['query']
|
|
490
|
-
|
|
490
|
+
|
|
491
491
|
response: ComposeSpiceflowResponse<
|
|
492
492
|
Schema['response'],
|
|
493
493
|
Handle
|
|
@@ -553,7 +553,7 @@ export class Spiceflow<
|
|
|
553
553
|
? ResolvePath<Path>
|
|
554
554
|
: Schema['params']
|
|
555
555
|
query: Schema['query']
|
|
556
|
-
|
|
556
|
+
|
|
557
557
|
response: ComposeSpiceflowResponse<
|
|
558
558
|
Schema['response'],
|
|
559
559
|
Handle
|
|
@@ -619,7 +619,7 @@ export class Spiceflow<
|
|
|
619
619
|
? ResolvePath<Path>
|
|
620
620
|
: Schema['params']
|
|
621
621
|
query: Schema['query']
|
|
622
|
-
|
|
622
|
+
|
|
623
623
|
response: ComposeSpiceflowResponse<
|
|
624
624
|
Schema['response'],
|
|
625
625
|
Handle
|
|
@@ -685,7 +685,7 @@ export class Spiceflow<
|
|
|
685
685
|
? ResolvePath<Path>
|
|
686
686
|
: Schema['params']
|
|
687
687
|
query: Schema['query']
|
|
688
|
-
|
|
688
|
+
|
|
689
689
|
response: ComposeSpiceflowResponse<
|
|
690
690
|
Schema['response'],
|
|
691
691
|
Handle
|
|
@@ -751,7 +751,7 @@ export class Spiceflow<
|
|
|
751
751
|
? ResolvePath<Path>
|
|
752
752
|
: Schema['params']
|
|
753
753
|
query: Schema['query']
|
|
754
|
-
|
|
754
|
+
|
|
755
755
|
response: ComposeSpiceflowResponse<
|
|
756
756
|
Schema['response'],
|
|
757
757
|
Handle
|
|
@@ -819,7 +819,7 @@ export class Spiceflow<
|
|
|
819
819
|
? ResolvePath<Path>
|
|
820
820
|
: Schema['params']
|
|
821
821
|
query: Schema['query']
|
|
822
|
-
|
|
822
|
+
|
|
823
823
|
response: ComposeSpiceflowResponse<
|
|
824
824
|
Schema['response'],
|
|
825
825
|
Handle
|
|
@@ -976,6 +976,7 @@ export class Spiceflow<
|
|
|
976
976
|
*/
|
|
977
977
|
async handle(request: Request, platform?: P): Promise<Response> {
|
|
978
978
|
platform ??= {} as P
|
|
979
|
+
|
|
979
980
|
let u = new URL(request.url)
|
|
980
981
|
let path = u.pathname + u.search
|
|
981
982
|
const defaultContext = {
|
|
@@ -995,7 +996,7 @@ export class Spiceflow<
|
|
|
995
996
|
onErrorHandlers = this.getRouteAndParents(route.router)
|
|
996
997
|
.reverse()
|
|
997
998
|
.flatMap((x) => x.onErrorHandlers)
|
|
998
|
-
|
|
999
|
+
let { params, store: defaultStore } = route
|
|
999
1000
|
const onReqHandlers = this.getRouteAndParents(route.router)
|
|
1000
1001
|
.reverse()
|
|
1001
1002
|
.flatMap((x) => x.onRequestHandlers)
|
|
@@ -1004,25 +1005,17 @@ export class Spiceflow<
|
|
|
1004
1005
|
// TODO add content type
|
|
1005
1006
|
|
|
1006
1007
|
let content = route?.hooks?.content
|
|
1007
|
-
let body = await getRequestBody({ request, content })
|
|
1008
|
+
// let body = await getRequestBody({ request, content })
|
|
1008
1009
|
|
|
1009
|
-
if (route.
|
|
1010
|
-
// TODO
|
|
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
|
|
1015
|
+
}
|
|
1011
1016
|
|
|
1012
|
-
|
|
1013
|
-
if (!valid) {
|
|
1014
|
-
const error = ajv.errorsText(route.validate.errors, {
|
|
1015
|
-
separator: '\n',
|
|
1016
|
-
})
|
|
1017
|
+
let query = parseQuery.parse((u.search || '').slice(1))
|
|
1017
1018
|
|
|
1018
|
-
return new Response(error, {
|
|
1019
|
-
status: 400,
|
|
1020
|
-
headers: {
|
|
1021
|
-
'content-type': 'text/plain',
|
|
1022
|
-
},
|
|
1023
|
-
})
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
1019
|
if (onReqHandlers.length > 0) {
|
|
1027
1020
|
for (const handler of onReqHandlers) {
|
|
1028
1021
|
const res = await handler({
|
|
@@ -1030,6 +1023,7 @@ export class Spiceflow<
|
|
|
1030
1023
|
response,
|
|
1031
1024
|
store,
|
|
1032
1025
|
path,
|
|
1026
|
+
query,
|
|
1033
1027
|
} satisfies Context<any, any, any>)
|
|
1034
1028
|
if (res) {
|
|
1035
1029
|
return await turnHandlerResultIntoResponse(res)
|
|
@@ -1037,6 +1031,9 @@ export class Spiceflow<
|
|
|
1037
1031
|
}
|
|
1038
1032
|
}
|
|
1039
1033
|
|
|
1034
|
+
query = runValidation(query, route.validateQuery)
|
|
1035
|
+
params = runValidation(params, route.validateParams)
|
|
1036
|
+
|
|
1040
1037
|
// console.log(route)
|
|
1041
1038
|
|
|
1042
1039
|
const res = route.handler({
|
|
@@ -1045,7 +1042,8 @@ export class Spiceflow<
|
|
|
1045
1042
|
response,
|
|
1046
1043
|
params: params as any,
|
|
1047
1044
|
store,
|
|
1048
|
-
|
|
1045
|
+
query,
|
|
1046
|
+
// body,
|
|
1049
1047
|
path,
|
|
1050
1048
|
|
|
1051
1049
|
// platform
|
|
@@ -1060,14 +1058,16 @@ export class Spiceflow<
|
|
|
1060
1058
|
|
|
1061
1059
|
return await turnHandlerResultIntoResponse(res)
|
|
1062
1060
|
} catch (err: any) {
|
|
1061
|
+
if (err instanceof Response) return err
|
|
1063
1062
|
let res = await this.runErrorHandlers({
|
|
1064
1063
|
onErrorHandlers,
|
|
1065
1064
|
error: err,
|
|
1066
1065
|
request,
|
|
1067
1066
|
})
|
|
1068
1067
|
if (res) return res
|
|
1068
|
+
let status = err?.status ?? 500
|
|
1069
1069
|
return new Response(err?.message || 'Internal Server Error', {
|
|
1070
|
-
status
|
|
1070
|
+
status,
|
|
1071
1071
|
})
|
|
1072
1072
|
}
|
|
1073
1073
|
}
|
|
@@ -1201,59 +1201,59 @@ export class Spiceflow<
|
|
|
1201
1201
|
}
|
|
1202
1202
|
}
|
|
1203
1203
|
|
|
1204
|
-
async function getRequestBody({
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
}: {
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
}) {
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
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
|
+
// }
|
|
1215
1215
|
|
|
1216
|
-
|
|
1217
|
-
|
|
1216
|
+
// const contentType =
|
|
1217
|
+
// content || request.headers.get('content-type')?.split(';')?.[0]
|
|
1218
1218
|
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1219
|
+
// if (!contentType) {
|
|
1220
|
+
// return
|
|
1221
|
+
// }
|
|
1222
1222
|
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1223
|
+
// switch (contentType) {
|
|
1224
|
+
// case 'application/json':
|
|
1225
|
+
// body = (await request.json()) as any
|
|
1226
|
+
// break
|
|
1227
1227
|
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1228
|
+
// case 'text/plain':
|
|
1229
|
+
// body = await request.text()
|
|
1230
|
+
// break
|
|
1231
1231
|
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1232
|
+
// case 'application/x-www-form-urlencoded':
|
|
1233
|
+
// body = parseQuery.parse(await request.text()) as any
|
|
1234
|
+
// break
|
|
1235
1235
|
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1236
|
+
// case 'application/octet-stream':
|
|
1237
|
+
// body = await request.arrayBuffer()
|
|
1238
|
+
// break
|
|
1239
1239
|
|
|
1240
|
-
|
|
1241
|
-
|
|
1240
|
+
// case 'multipart/form-data':
|
|
1241
|
+
// body = {}
|
|
1242
1242
|
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1243
|
+
// const form = await request.formData()
|
|
1244
|
+
// for (const key of form.keys()) {
|
|
1245
|
+
// if (body[key]) continue
|
|
1246
1246
|
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1247
|
+
// const value = form.getAll(key)
|
|
1248
|
+
// if (value.length === 1) body[key] = value[0]
|
|
1249
|
+
// else body[key] = value
|
|
1250
|
+
// }
|
|
1251
1251
|
|
|
1252
|
-
|
|
1253
|
-
|
|
1252
|
+
// break
|
|
1253
|
+
// }
|
|
1254
1254
|
|
|
1255
|
-
|
|
1256
|
-
}
|
|
1255
|
+
// return body
|
|
1256
|
+
// }
|
|
1257
1257
|
|
|
1258
1258
|
const METHODS = [
|
|
1259
1259
|
'ALL',
|
|
@@ -1288,6 +1288,15 @@ function bfsFind<T>(
|
|
|
1288
1288
|
}
|
|
1289
1289
|
return
|
|
1290
1290
|
}
|
|
1291
|
+
export class TypedRequest<T = any> extends Request {
|
|
1292
|
+
validateBody?: ValidateFunction
|
|
1293
|
+
|
|
1294
|
+
async json(): Promise<T> {
|
|
1295
|
+
const body = (await super.json()) as Promise<T>
|
|
1296
|
+
return runValidation(body, this.validateBody)
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1291
1300
|
export function bfs(tree: RouterTree) {
|
|
1292
1301
|
const queue = [tree]
|
|
1293
1302
|
let nodes: RouterTree[] = []
|
|
@@ -1346,3 +1355,26 @@ export function isZodSchema(value: unknown): value is ZodType {
|
|
|
1346
1355
|
'nullable' in value)
|
|
1347
1356
|
)
|
|
1348
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
|
+
}
|
package/src/zod.test.ts
CHANGED
|
@@ -11,13 +11,14 @@ test('body is parsed as json', async () => {
|
|
|
11
11
|
|
|
12
12
|
.post(
|
|
13
13
|
'/post',
|
|
14
|
-
(c) => {
|
|
15
|
-
|
|
14
|
+
async (c) => {
|
|
15
|
+
const body = await c.request.json()
|
|
16
|
+
name = body.name
|
|
16
17
|
// @ts-expect-error
|
|
17
|
-
|
|
18
|
+
body.nonExistingField
|
|
18
19
|
return {
|
|
19
20
|
name,
|
|
20
|
-
nameEcho:
|
|
21
|
+
nameEcho: body.name,
|
|
21
22
|
// add: 3,
|
|
22
23
|
}
|
|
23
24
|
},
|
|
@@ -33,13 +34,14 @@ test('body is parsed as json', async () => {
|
|
|
33
34
|
)
|
|
34
35
|
.post(
|
|
35
36
|
'/post2',
|
|
36
|
-
(c) => {
|
|
37
|
-
|
|
37
|
+
async (c) => {
|
|
38
|
+
const body = await c.request.json()
|
|
39
|
+
name = body.name
|
|
38
40
|
// @ts-expect-error
|
|
39
|
-
|
|
41
|
+
body.nonExistingField
|
|
40
42
|
return {
|
|
41
43
|
name,
|
|
42
|
-
nameEcho:
|
|
44
|
+
nameEcho: body.name,
|
|
43
45
|
}
|
|
44
46
|
},
|
|
45
47
|
{
|