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/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
- validate?: ValidateFunction
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 validate: ValidateFunction | undefined
164
-
165
- if (isZodSchema(bodySchema)) {
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
- validate,
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
- headers: Schema['headers']
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
- headers: Schema['headers']
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
- headers: Schema['headers']
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
- headers: Schema['headers']
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
- headers: Schema['headers']
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
- headers: Schema['headers']
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
- headers: Schema['headers']
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
- headers: Schema['headers']
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
- const { params, store: defaultStore } = route
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.validate) {
1010
- // TODO move compile to the router
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
- const valid = route.validate(body)
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
- body,
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: 500,
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
- 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
- }
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
- const contentType =
1217
- content || request.headers.get('content-type')?.split(';')?.[0]
1216
+ // const contentType =
1217
+ // content || request.headers.get('content-type')?.split(';')?.[0]
1218
1218
 
1219
- if (!contentType) {
1220
- return
1221
- }
1219
+ // if (!contentType) {
1220
+ // return
1221
+ // }
1222
1222
 
1223
- switch (contentType) {
1224
- case 'application/json':
1225
- body = (await request.json()) as any
1226
- break
1223
+ // switch (contentType) {
1224
+ // case 'application/json':
1225
+ // body = (await request.json()) as any
1226
+ // break
1227
1227
 
1228
- case 'text/plain':
1229
- body = await request.text()
1230
- break
1228
+ // case 'text/plain':
1229
+ // body = await request.text()
1230
+ // break
1231
1231
 
1232
- case 'application/x-www-form-urlencoded':
1233
- body = parseQuery.parse(await request.text()) as any
1234
- break
1232
+ // case 'application/x-www-form-urlencoded':
1233
+ // body = parseQuery.parse(await request.text()) as any
1234
+ // break
1235
1235
 
1236
- case 'application/octet-stream':
1237
- body = await request.arrayBuffer()
1238
- break
1236
+ // case 'application/octet-stream':
1237
+ // body = await request.arrayBuffer()
1238
+ // break
1239
1239
 
1240
- case 'multipart/form-data':
1241
- body = {}
1240
+ // case 'multipart/form-data':
1241
+ // body = {}
1242
1242
 
1243
- const form = await request.formData()
1244
- for (const key of form.keys()) {
1245
- if (body[key]) continue
1243
+ // const form = await request.formData()
1244
+ // for (const key of form.keys()) {
1245
+ // if (body[key]) continue
1246
1246
 
1247
- const value = form.getAll(key)
1248
- if (value.length === 1) body[key] = value[0]
1249
- else body[key] = value
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
- break
1253
- }
1252
+ // break
1253
+ // }
1254
1254
 
1255
- return body
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
- name = c.body.name
14
+ async (c) => {
15
+ const body = await c.request.json()
16
+ name = body.name
16
17
  // @ts-expect-error
17
- c.body.nonExistingField
18
+ body.nonExistingField
18
19
  return {
19
20
  name,
20
- nameEcho: c.body.name,
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
- name = c.body.name
37
+ async (c) => {
38
+ const body = await c.request.json()
39
+ name = body.name
38
40
  // @ts-expect-error
39
- c.body.nonExistingField
41
+ body.nonExistingField
40
42
  return {
41
43
  name,
42
- nameEcho: c.body.name,
44
+ nameEcho: body.name,
43
45
  }
44
46
  },
45
47
  {