@mpen/routekit 0.1.0 → 0.1.2

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.
Files changed (133) hide show
  1. package/dist/bin.d.mts +4 -0
  2. package/dist/client/react.d.mts +178 -0
  3. package/dist/client/react.mjs +142 -0
  4. package/dist/client.d.mts +433 -0
  5. package/dist/client.mjs +264 -0
  6. package/dist/content-BuDOmhH_.mjs +102 -0
  7. package/dist/core-CzUCxvGk.d.mts +140 -0
  8. package/dist/core-DbmQauwS.mjs +81 -0
  9. package/dist/handlers.d.mts +72 -0
  10. package/dist/handlers.mjs +153 -0
  11. package/dist/index.d.mts +3 -0
  12. package/dist/index.mjs +1152 -0
  13. package/dist/middleware.d.mts +388 -0
  14. package/dist/middleware.mjs +1222 -0
  15. package/dist/request-Dn0zc-xm.mjs +1025 -0
  16. package/dist/response/content.d.mts +79 -0
  17. package/dist/response/content.mjs +2 -0
  18. package/dist/response/json-rpc.d.mts +1 -0
  19. package/dist/response/json-rpc.mjs +1 -0
  20. package/dist/response/problem/valibot.d.mts +230 -0
  21. package/dist/response/problem/valibot.mjs +258 -0
  22. package/dist/response/problem.d.mts +415 -0
  23. package/dist/response/problem.mjs +183 -0
  24. package/dist/response/status.d.mts +45 -0
  25. package/dist/response/status.mjs +2 -0
  26. package/dist/responses-B379Ep9Y.d.mts +296 -0
  27. package/dist/responses-BpVrgeYi.mjs +101 -0
  28. package/dist/router-Cwb7ak0J.d.mts +1819 -0
  29. package/dist/routes.d.mts +282 -0
  30. package/dist/routes.mjs +311 -0
  31. package/dist/status-C-8mw-FB.mjs +59 -0
  32. package/dist/valibot-D7liFYyB.d.mts +290 -0
  33. package/dist/valibot-Du97X-TS.mjs +326 -0
  34. package/package.json +8 -2
  35. package/src/bin/gen-api-client.test.ts +0 -70
  36. package/src/bin/gen-api-client.ts +0 -986
  37. package/src/client/headers.ts +0 -31
  38. package/src/client/index.ts +0 -8
  39. package/src/client/promise.ts +0 -11
  40. package/src/client/react/index.test.tsx +0 -266
  41. package/src/client/react/index.ts +0 -431
  42. package/src/client/responses.test.ts +0 -151
  43. package/src/client/responses.ts +0 -278
  44. package/src/client/transport.ts +0 -74
  45. package/src/client/transports/body-codec.ts +0 -61
  46. package/src/client/transports/fetch.ts +0 -113
  47. package/src/client/tsconfig.json +0 -9
  48. package/src/client/types.ts +0 -15
  49. package/src/client/url.ts +0 -31
  50. package/src/index.ts +0 -63
  51. package/src/router/fetch-types.ts +0 -13
  52. package/src/router/handlers/index.ts +0 -2
  53. package/src/router/handlers/openapi/index.ts +0 -2
  54. package/src/router/handlers/openapi/openapi.ts +0 -293
  55. package/src/router/integration/zod-openapi.test.ts +0 -74
  56. package/src/router/lib/charset.test.ts +0 -22
  57. package/src/router/lib/charset.ts +0 -133
  58. package/src/router/lib/collections.ts +0 -3
  59. package/src/router/lib/format.test.ts +0 -67
  60. package/src/router/lib/format.ts +0 -35
  61. package/src/router/lib/host.ts +0 -4
  62. package/src/router/lib/json-schema.ts +0 -6
  63. package/src/router/lib/media-type.test.ts +0 -122
  64. package/src/router/lib/media-type.ts +0 -289
  65. package/src/router/lib/pathname.test.ts +0 -18
  66. package/src/router/lib/pathname.ts +0 -19
  67. package/src/router/lib/route-names.ts +0 -70
  68. package/src/router/lib/route-normalize.test.ts +0 -36
  69. package/src/router/lib/route-normalize.ts +0 -67
  70. package/src/router/lib/schema-merge.ts +0 -56
  71. package/src/router/middleware/accept-ctx.test.ts +0 -33
  72. package/src/router/middleware/accept-ctx.ts +0 -12
  73. package/src/router/middleware/body-limit.test.ts +0 -112
  74. package/src/router/middleware/body-limit.ts +0 -121
  75. package/src/router/middleware/content-type-context.ts +0 -0
  76. package/src/router/middleware/cors.test.ts +0 -269
  77. package/src/router/middleware/cors.ts +0 -490
  78. package/src/router/middleware/csrf.test.ts +0 -106
  79. package/src/router/middleware/csrf.ts +0 -192
  80. package/src/router/middleware/define.ts +0 -249
  81. package/src/router/middleware/index.ts +0 -34
  82. package/src/router/middleware/jsxhtml-response.ts +0 -0
  83. package/src/router/middleware/oas-swagger.ts +0 -0
  84. package/src/router/middleware/rate-limit.test.ts +0 -886
  85. package/src/router/middleware/rate-limit.ts +0 -920
  86. package/src/router/middleware/request-id-ctx.test.ts +0 -183
  87. package/src/router/middleware/request-id-ctx.ts +0 -135
  88. package/src/router/middleware/request-logger-format.test.ts +0 -16
  89. package/src/router/middleware/request-logger-format.ts +0 -269
  90. package/src/router/middleware/request-logger.test.ts +0 -267
  91. package/src/router/middleware/request-logger.ts +0 -131
  92. package/src/router/middleware/start-time-ctx.ts +0 -5
  93. package/src/router/request.ts +0 -611
  94. package/src/router/response/core.ts +0 -181
  95. package/src/router/response/directives.ts +0 -233
  96. package/src/router/response/formats/content/bodyless.ts +0 -54
  97. package/src/router/response/formats/content/content.ts +0 -79
  98. package/src/router/response/formats/content/index.ts +0 -2
  99. package/src/router/response/formats/json-rpc/index.ts +0 -2
  100. package/src/router/response/formats/problem/badRequest.ts +0 -90
  101. package/src/router/response/formats/problem/conflict.ts +0 -90
  102. package/src/router/response/formats/problem/created.ts +0 -40
  103. package/src/router/response/formats/problem/index.ts +0 -27
  104. package/src/router/response/formats/problem/notFound.ts +0 -90
  105. package/src/router/response/formats/problem/permissionDenied.ts +0 -90
  106. package/src/router/response/formats/problem/problem.test.ts +0 -888
  107. package/src/router/response/formats/problem/rateLimited.ts +0 -90
  108. package/src/router/response/formats/problem/responses.ts +0 -219
  109. package/src/router/response/formats/problem/root-errors.ts +0 -48
  110. package/src/router/response/formats/problem/sessionExpired.ts +0 -90
  111. package/src/router/response/formats/problem/types.ts +0 -170
  112. package/src/router/response/formats/problem/unauthenticated.ts +0 -90
  113. package/src/router/response/formats/problem/valibot.ts +0 -410
  114. package/src/router/response/formats/status/index.ts +0 -1
  115. package/src/router/response/formats/status/responses.ts +0 -59
  116. package/src/router/response/formats/status/status.test.ts +0 -21
  117. package/src/router/response/framers.ts +0 -85
  118. package/src/router/response/index.ts +0 -28
  119. package/src/router/response/openapi.test.ts +0 -96
  120. package/src/router/response/openapi.ts +0 -1
  121. package/src/router/response/serializers.ts +0 -66
  122. package/src/router/response/stream.ts +0 -35
  123. package/src/router/router.test.ts +0 -1571
  124. package/src/router/router.ts +0 -1965
  125. package/src/router/routes/index.ts +0 -46
  126. package/src/router/routes/valibot/index.ts +0 -18
  127. package/src/router/routes/valibot/valibot.ts +0 -1393
  128. package/src/router/routes/valibot.test.ts +0 -286
  129. package/src/router/routes/zod/index.ts +0 -18
  130. package/src/router/routes/zod/zod.ts +0 -1318
  131. package/src/router/routes/zod.test.ts +0 -280
  132. package/src/router/server-interface.ts +0 -31
  133. package/src/router/types.ts +0 -657
@@ -1,1318 +0,0 @@
1
- import { HttpStatus } from '@mpen/http'
2
- import type { Router } from '../../router'
3
- import {
4
- defineMiddleware,
5
- type DeclaredMiddleware,
6
- type DefineMiddlewareOptions,
7
- type MiddlewareResponseDeclaration,
8
- } from '../../middleware/define'
9
- import type {
10
- AddedContextFromMiddlewareInput,
11
- AnyContext,
12
- ContextFromMiddlewareInput,
13
- ContextMiddleware,
14
- Handler,
15
- HandlerContext,
16
- HandlerResult,
17
- JsonObjectSchema,
18
- JsonSchema,
19
- MiddlewareInput,
20
- Route,
21
- RouteOptions,
22
- RouteSchema,
23
- } from '../../types'
24
- import { isJsonContentType } from '../../lib/media-type'
25
- import { RequestBodyError } from '../../request'
26
- import { isRoutekitBody, isRoutekitResponse, response, type RoutekitResponse } from '../../response'
27
- import { z } from 'zod'
28
-
29
- /**
30
- * Validation error component identifiers for Zod-backed routes.
31
- */
32
- export const enum ValidationError {
33
- REQUEST_BODY,
34
- URL_PATH,
35
- QUERY_PARAMETERS,
36
- }
37
-
38
- type ErrorTree = ReturnType<typeof z.treeifyError>
39
- type ZodSchema = z.ZodTypeAny | undefined
40
- type AnyZodResponseBodySchemas = Partial<Record<number | 'default', z.ZodTypeAny>>
41
- type ZodResponseBodySchemas = AnyZodResponseBodySchemas | undefined
42
- type ResponseValidationMode = false | 'strict' | 'parse'
43
- type ResponseValidationOption = boolean | ResponseValidationMode
44
-
45
- type ZodRouteContext<Ctx extends object, BuilderMiddleware, RouteMiddleware> = Ctx &
46
- AddedContextFromMiddlewareInput<BuilderMiddleware> &
47
- AddedContextFromMiddlewareInput<RouteMiddleware>
48
-
49
- type ZodBuilderMiddlewareInput<
50
- Ctx extends object,
51
- BuilderMiddleware extends MiddlewareInput<Ctx>,
52
- RouteMiddleware extends MiddlewareInput<Ctx>,
53
- > = [BuilderMiddleware] extends [undefined]
54
- ? RouteMiddleware
55
- : [RouteMiddleware] extends [undefined]
56
- ? BuilderMiddleware
57
- : MiddlewareInput<Ctx>
58
-
59
- /**
60
- * Default validation error payload returned by Zod-backed routes.
61
- */
62
- export type ZodValidationErrorBody = {
63
- component: 'request_body' | 'url_path' | 'query_parameters'
64
- errorTree: ErrorTree
65
- message: string
66
- }
67
-
68
- /**
69
- * Zod schema input that mirrors the core route `schema` shape.
70
- */
71
- export type ZodRouteSchemaInput<
72
- BodySchema extends ZodSchema = undefined,
73
- PathSchema extends ZodSchema = undefined,
74
- QuerySchema extends ZodSchema = undefined,
75
- ResponseBodySchemas extends ZodResponseBodySchemas = undefined,
76
- > = {
77
- request?: {
78
- query?: QuerySchema
79
- path?: PathSchema
80
- body?: BodySchema
81
- }
82
- response?: {
83
- body?: ResponseBodySchemas
84
- }
85
- }
86
-
87
- type InferSchema<Schema extends ZodSchema> = Schema extends z.ZodTypeAny ? z.infer<Schema> : unknown
88
-
89
- type InferResponseBodySchemaUnion<ResponseBodySchemas extends AnyZodResponseBodySchemas> = [
90
- keyof ResponseBodySchemas,
91
- ] extends [never]
92
- ? unknown
93
- : {
94
- [Status in keyof ResponseBodySchemas]: NonNullable<
95
- ResponseBodySchemas[Status]
96
- > extends z.ZodTypeAny
97
- ? z.infer<NonNullable<ResponseBodySchemas[Status]>>
98
- : never
99
- }[keyof ResponseBodySchemas]
100
-
101
- type InferResponseBody<ResponseBodySchemas extends ZodResponseBodySchemas> =
102
- ResponseBodySchemas extends AnyZodResponseBodySchemas
103
- ? 200 extends keyof ResponseBodySchemas
104
- ? InferResponseBodySchemaUnion<ResponseBodySchemas>
105
- : 'default' extends keyof ResponseBodySchemas
106
- ? InferResponseBodySchemaUnion<ResponseBodySchemas>
107
- : unknown
108
- : unknown
109
-
110
- type NormalizeSchema<Schema> =
111
- Schema extends ZodRouteSchemaInput<any, any, any, any>
112
- ? Schema
113
- : ZodRouteSchemaInput<undefined, undefined, undefined, undefined>
114
-
115
- type ExtractBodySchema<Schema extends ZodRouteSchemaInput<any, any, any, any> | undefined> =
116
- NormalizeSchema<Schema> extends ZodRouteSchemaInput<infer BodySchema, any, any, any>
117
- ? BodySchema
118
- : undefined
119
-
120
- type ExtractPathSchema<Schema extends ZodRouteSchemaInput<any, any, any, any> | undefined> =
121
- NormalizeSchema<Schema> extends ZodRouteSchemaInput<any, infer PathSchema, any, any>
122
- ? PathSchema
123
- : undefined
124
-
125
- type ExtractQuerySchema<Schema extends ZodRouteSchemaInput<any, any, any, any> | undefined> =
126
- NormalizeSchema<Schema> extends ZodRouteSchemaInput<any, any, infer QuerySchema, any>
127
- ? QuerySchema
128
- : undefined
129
-
130
- type ExtractResponseBodySchemas<
131
- Schema extends ZodRouteSchemaInput<any, any, any, any> | undefined,
132
- > =
133
- NormalizeSchema<Schema> extends ZodRouteSchemaInput<any, any, any, infer ResponseBodySchemas>
134
- ? ResponseBodySchemas
135
- : undefined
136
-
137
- type ZodMiddlewareDeclarations<Schemas extends AnyZodResponseBodySchemas> = {
138
- [Status in keyof Schemas]: NonNullable<Schemas[Status]> extends z.ZodTypeAny
139
- ? MiddlewareResponseDeclaration<z.infer<NonNullable<Schemas[Status]>>>
140
- : never
141
- }
142
-
143
- type ZodDeclaredResponse<Schemas extends AnyZodResponseBodySchemas> = {
144
- [Status in keyof Schemas]-?: NonNullable<Schemas[Status]> extends z.ZodTypeAny
145
- ? RoutekitResponse<
146
- z.infer<NonNullable<Schemas[Status]>>,
147
- Status extends number ? Status : number
148
- >
149
- : never
150
- }[keyof Schemas]
151
-
152
- type ZodSchemaMiddlewareContext<
153
- Schema extends ZodRouteSchemaInput<any, any, any, any> | undefined,
154
- > = {
155
- params: ZodHandlerParams<
156
- ExtractBodySchema<Schema>,
157
- ExtractPathSchema<Schema>,
158
- ExtractQuerySchema<Schema>
159
- >
160
- }
161
-
162
- /**
163
- * Validated request inputs exposed to a Zod-backed handler.
164
- */
165
- export type ZodHandlerParams<
166
- BodySchema extends ZodSchema,
167
- PathSchema extends ZodSchema,
168
- QuerySchema extends ZodSchema,
169
- > = {
170
- path: InferSchema<PathSchema>
171
- query: InferSchema<QuerySchema>
172
- body: InferSchema<BodySchema>
173
- }
174
-
175
- /**
176
- * Context object exposed to a Zod-backed handler.
177
- */
178
- export type ZodHandlerContext<
179
- BodySchema extends ZodSchema,
180
- PathSchema extends ZodSchema,
181
- QuerySchema extends ZodSchema,
182
- Ctx extends object,
183
- > = Omit<HandlerContext<Ctx>, 'path'> & {
184
- params: ZodHandlerParams<BodySchema, PathSchema, QuerySchema>
185
- }
186
-
187
- /**
188
- * Validation error handler used when request parsing fails.
189
- *
190
- * @param component - Request component that failed validation.
191
- * @param error - The Zod validation error.
192
- * @returns A handler result that should be returned to the client.
193
- */
194
- export type ValidationErrorHandler<
195
- Responses extends AnyZodResponseBodySchemas = AnyZodResponseBodySchemas,
196
- > = (component: ValidationError, error: z.ZodError) => ZodDeclaredResponse<Responses>
197
-
198
- /**
199
- * Shared defaults that can be applied to Zod handler helpers.
200
- */
201
- type ZodHandlerDefaults = {
202
- /**
203
- * Route schema fragments that should be merged into every factory-built route.
204
- * Route-level schemas override matching request fields and response status codes.
205
- */
206
- schema?: ZodRouteSchemaInput<any, any, any, any>
207
- /**
208
- * Whether and how to apply `schema.response.body` to handler responses.
209
- * `false` disables response validation, `true` and `'strict'` validate without changing the
210
- * response body, and `'parse'` returns the parsed response body. Defaults to `'parse'`.
211
- */
212
- validateResponse?: ResponseValidationOption
213
- } & (
214
- | {
215
- onRequestValidationError?: undefined
216
- validationResponses?: undefined
217
- }
218
- | {
219
- /**
220
- * Override the default request validation error response.
221
- */
222
- onRequestValidationError: ValidationErrorHandler
223
- /**
224
- * Response schemas returned by `onRequestValidationError`.
225
- */
226
- validationResponses: AnyZodResponseBodySchemas
227
- }
228
- )
229
-
230
- /**
231
- * Shared defaults that can be applied to Zod route helpers.
232
- */
233
- export type ZodRouteHelperDefaults<
234
- BuilderCtx extends object = AnyContext,
235
- BuilderMiddleware extends MiddlewareInput<BuilderCtx> = undefined,
236
- > = ZodHandlerDefaults & {
237
- /**
238
- * Middleware applied to every route built by the helper.
239
- */
240
- middleware?: BuilderMiddleware
241
- }
242
-
243
- /**
244
- * Handler signature used by Zod schema boundaries and route builders.
245
- */
246
- export type ZodRouteHandler<
247
- BodySchema extends ZodSchema,
248
- PathSchema extends ZodSchema,
249
- QuerySchema extends ZodSchema,
250
- ResponseBodySchemas extends ZodResponseBodySchemas,
251
- Ctx extends object = AnyContext,
252
- > = (
253
- this: Router<any>,
254
- ctx: ZodHandlerContext<BodySchema, PathSchema, QuerySchema, Ctx>,
255
- ) => HandlerResult<InferResponseBody<ResponseBodySchemas>>
256
-
257
- /**
258
- * Shared options used by Zod route builders.
259
- */
260
- export type ZodHandlerOptions<
261
- Schema extends ZodRouteSchemaInput<any, any, any, any> | undefined,
262
- Ctx extends object = AnyContext,
263
- > = ZodHandlerDefaults & {
264
- schema?: Schema
265
- handler: ZodRouteHandler<
266
- ExtractBodySchema<Schema>,
267
- ExtractPathSchema<Schema>,
268
- ExtractQuerySchema<Schema>,
269
- ExtractResponseBodySchemas<Schema>,
270
- Ctx
271
- >
272
- }
273
-
274
- /**
275
- * Full route options used by Zod route builders.
276
- */
277
- export type ZodRouteOptions<
278
- Schema extends ZodRouteSchemaInput<any, any, any, any> | undefined,
279
- Ctx extends object = AnyContext,
280
- BuilderMiddleware extends MiddlewareInput<Ctx> = undefined,
281
- RouteMiddleware extends MiddlewareInput<Ctx> = undefined,
282
- > = Omit<
283
- Route<Ctx, RouteMiddleware, ZodRouteContext<Ctx, BuilderMiddleware, RouteMiddleware>>,
284
- 'handler' | 'schema' | 'middleware'
285
- > &
286
- ZodHandlerOptions<Schema, ZodRouteContext<Ctx, BuilderMiddleware, RouteMiddleware>> & {
287
- middleware?: RouteMiddleware
288
- }
289
-
290
- /**
291
- * Method-specific route options used by Zod route builders.
292
- */
293
- export type WithZodOptions<
294
- Schema extends ZodRouteSchemaInput<any, any, any, any> | undefined,
295
- Ctx extends object = AnyContext,
296
- BuilderMiddleware extends MiddlewareInput<Ctx> = undefined,
297
- RouteMiddleware extends MiddlewareInput<Ctx> = undefined,
298
- > = Omit<
299
- RouteOptions<Ctx, RouteMiddleware, ZodRouteContext<Ctx, BuilderMiddleware, RouteMiddleware>>,
300
- 'handler' | 'schema' | 'middleware'
301
- > &
302
- ZodHandlerOptions<Schema, ZodRouteContext<Ctx, BuilderMiddleware, RouteMiddleware>> & {
303
- middleware?: RouteMiddleware
304
- }
305
-
306
- /**
307
- * Zod route builder created by [`createZodRouteBuilder`]{@link createZodRouteBuilder}.
308
- *
309
- * @example
310
- * ```ts
311
- * const route = createZodRouteBuilder()
312
- *
313
- * router.get('/users/:id', route({
314
- * schema: {
315
- * request: {
316
- * path: z.object({id: z.string()}),
317
- * },
318
- * },
319
- * handler: ({params}) => ({id: params.path.id}),
320
- * }))
321
- *
322
- * router.add(route({
323
- * method: HttpMethod.GET,
324
- * path: '/health',
325
- * handler: () => ({ok: true}),
326
- * }))
327
- * ```
328
- */
329
- export type ZodRouteBuilder<
330
- BuilderCtx extends object = AnyContext,
331
- BuilderMiddleware extends MiddlewareInput<BuilderCtx> = undefined,
332
- > = {
333
- /**
334
- * Build a full route definition that includes its own path.
335
- *
336
- * @param options - Full route options including `path`.
337
- * @returns A full route definition compatible with [`Router.add`]{@link Router#add}.
338
- */
339
- <
340
- Schema extends ZodRouteSchemaInput<any, any, any, any> | undefined,
341
- RouteMiddleware extends MiddlewareInput<BuilderCtx> = undefined,
342
- >(
343
- options: ZodRouteOptions<Schema, BuilderCtx, BuilderMiddleware, RouteMiddleware>,
344
- ): Route<
345
- BuilderCtx,
346
- ZodBuilderMiddlewareInput<BuilderCtx, BuilderMiddleware, RouteMiddleware>,
347
- ZodRouteContext<BuilderCtx, BuilderMiddleware, RouteMiddleware>
348
- >
349
- /**
350
- * Build method-specific route options that leave the path to the registering router.
351
- *
352
- * @param options - Method route options without a route path.
353
- * @returns Route options compatible with helpers like [`Router.get`]{@link Router#get}.
354
- */
355
- <
356
- Schema extends ZodRouteSchemaInput<any, any, any, any> | undefined,
357
- RouteMiddleware extends MiddlewareInput<BuilderCtx> = undefined,
358
- >(
359
- options: WithZodOptions<Schema, BuilderCtx, BuilderMiddleware, RouteMiddleware>,
360
- ): RouteOptions<
361
- BuilderCtx,
362
- ZodBuilderMiddlewareInput<BuilderCtx, BuilderMiddleware, RouteMiddleware>,
363
- ZodRouteContext<BuilderCtx, BuilderMiddleware, RouteMiddleware>
364
- >
365
- }
366
-
367
- type ResolvedZodHandlerOptions<
368
- Schema extends ZodRouteSchemaInput<any, any, any, any> | undefined,
369
- Ctx extends object,
370
- > = {
371
- schema: Schema | undefined
372
- handler: ZodRouteHandler<
373
- ExtractBodySchema<Schema>,
374
- ExtractPathSchema<Schema>,
375
- ExtractQuerySchema<Schema>,
376
- ExtractResponseBodySchemas<Schema>,
377
- Ctx
378
- >
379
- validateResponse: ResponseValidationMode
380
- onRequestValidationError: ValidationErrorHandler
381
- }
382
-
383
- type ResponseBodyForValidation = {
384
- value: unknown
385
- writableJson: boolean
386
- }
387
-
388
- const validationErrorComponentName = new Map<ValidationError, ZodValidationErrorBody['component']>([
389
- [ValidationError.REQUEST_BODY, 'request_body'],
390
- [ValidationError.URL_PATH, 'url_path'],
391
- [ValidationError.QUERY_PARAMETERS, 'query_parameters'],
392
- ])
393
-
394
- class ZodResponseValidationError extends Error {
395
- readonly status: number
396
- readonly error: z.ZodError
397
-
398
- constructor(status: number, error: z.ZodError) {
399
- super(`Response validation failed for status ${status}: ${z.prettifyError(error)}`)
400
- this.name = 'ZodResponseValidationError'
401
- this.status = status
402
- this.error = error
403
- }
404
- }
405
-
406
- function createValidationResponse(
407
- component: ValidationError,
408
- error: z.ZodError,
409
- ): RoutekitResponse<ZodValidationErrorBody, HttpStatus.BAD_REQUEST> {
410
- const payload: ZodValidationErrorBody = {
411
- component: validationErrorComponentName.get(component) ?? 'request_body',
412
- errorTree: z.treeifyError(error),
413
- message: z.prettifyError(error),
414
- }
415
- return response(payload, { status: HttpStatus.BAD_REQUEST })
416
- }
417
-
418
- function normalizeResponseValidationMode(
419
- option: ResponseValidationOption | undefined,
420
- ): ResponseValidationMode | undefined {
421
- if (option === true) return 'strict'
422
- return option
423
- }
424
-
425
- function resolveDefaults<
426
- Schema extends ZodRouteSchemaInput<any, any, any, any> | undefined,
427
- Ctx extends object,
428
- >(
429
- options: ZodHandlerOptions<Schema, Ctx>,
430
- defaults?: ZodHandlerDefaults,
431
- ): ResolvedZodHandlerOptions<Schema, Ctx> {
432
- return {
433
- schema: options.schema,
434
- handler: options.handler,
435
- validateResponse:
436
- normalizeResponseValidationMode(options.validateResponse) ??
437
- normalizeResponseValidationMode(defaults?.validateResponse) ??
438
- 'parse',
439
- onRequestValidationError:
440
- options.onRequestValidationError ??
441
- defaults?.onRequestValidationError ??
442
- createValidationResponse,
443
- }
444
- }
445
-
446
- function readQueryParams(searchParams: URLSearchParams): Record<string, string | string[]> {
447
- const query: Record<string, string | string[]> = {}
448
- for (const [key, value] of searchParams.entries()) {
449
- const existing = query[key]
450
- if (existing === undefined) {
451
- query[key] = value
452
- continue
453
- }
454
- if (Array.isArray(existing)) {
455
- existing.push(value)
456
- } else {
457
- query[key] = [existing, value]
458
- }
459
- }
460
- return query
461
- }
462
-
463
- function zodErrorFromThrowable(error: unknown): z.ZodError {
464
- return new z.ZodError([
465
- {
466
- code: z.ZodIssueCode.custom,
467
- path: [],
468
- message: error instanceof Error ? error.message : String(error),
469
- },
470
- ])
471
- }
472
-
473
- function sanitizeJsonSchema(schema: JsonSchema): JsonSchema {
474
- if (Array.isArray(schema)) {
475
- return schema.map((entry) =>
476
- sanitizeJsonSchema(entry as JsonSchema),
477
- ) as unknown as JsonSchema
478
- }
479
- if (!schema || typeof schema !== 'object') {
480
- return schema
481
- }
482
-
483
- const sanitizedEntries = Object.entries(schema)
484
- .filter(([key]) => key !== '$schema' && key !== '~standard')
485
- .map(([key, value]) => [key, sanitizeJsonSchema(value as JsonSchema)])
486
- return Object.fromEntries(sanitizedEntries)
487
- }
488
-
489
- function toJsonSchema(schema: z.ZodTypeAny): JsonSchema {
490
- return sanitizeJsonSchema(z.toJSONSchema(schema) as JsonSchema)
491
- }
492
-
493
- function buildRouteSchema(
494
- schema?: ZodRouteSchemaInput<any, any, any, any>,
495
- ): RouteSchema | undefined {
496
- if (!schema) return undefined
497
-
498
- const request = schema.request
499
- ? {
500
- ...(schema.request.query
501
- ? { query: toJsonSchema(schema.request.query) as JsonObjectSchema }
502
- : {}),
503
- ...(schema.request.path
504
- ? { path: toJsonSchema(schema.request.path) as JsonObjectSchema }
505
- : {}),
506
- ...(schema.request.body ? { body: toJsonSchema(schema.request.body) } : {}),
507
- }
508
- : undefined
509
-
510
- const responseBody = schema.response?.body
511
- ? Object.fromEntries(
512
- Object.entries(schema.response.body).map(([status, responseSchema]) => {
513
- const normalizedStatus = status === 'default' ? status : Number(status)
514
- return [normalizedStatus, toJsonSchema(responseSchema as z.ZodTypeAny)]
515
- }),
516
- )
517
- : undefined
518
-
519
- const response =
520
- responseBody && Object.keys(responseBody).length > 0 ? { body: responseBody } : undefined
521
-
522
- if ((!request || Object.keys(request).length === 0) && !response) {
523
- return undefined
524
- }
525
-
526
- return {
527
- ...(request && Object.keys(request).length > 0 ? { request } : {}),
528
- ...(response ? { response } : {}),
529
- }
530
- }
531
-
532
- function buildZodMiddlewareDeclarations<Schemas extends AnyZodResponseBodySchemas>(
533
- schemas: Schemas,
534
- ): ZodMiddlewareDeclarations<Schemas> {
535
- return Object.fromEntries(
536
- Object.entries(schemas).flatMap(([status, schema]) =>
537
- schema
538
- ? [
539
- [
540
- status,
541
- {
542
- schema: toJsonSchema(schema as z.ZodTypeAny),
543
- parse: (value: unknown) => (schema as z.ZodTypeAny).parse(value),
544
- },
545
- ],
546
- ]
547
- : [],
548
- ),
549
- ) as ZodMiddlewareDeclarations<Schemas>
550
- }
551
-
552
- /**
553
- * Options for [`defineZodMiddleware`]{@link defineZodMiddleware}.
554
- *
555
- * @typeParam AddedCtx - Context made available to downstream handlers.
556
- * @typeParam Ctx - Context required by the middleware.
557
- * @typeParam Responses - Status-keyed Zod schemas for locally originated responses.
558
- */
559
- export type DefineZodMiddlewareOptions<
560
- AddedCtx extends object,
561
- Ctx extends object,
562
- Responses extends AnyZodResponseBodySchemas,
563
- > = {
564
- responses: Responses
565
- run: DefineMiddlewareOptions<AddedCtx, Ctx, ZodMiddlewareDeclarations<Responses>>['run']
566
- }
567
-
568
- /**
569
- * Create response-declaring middleware backed by Zod response schemas.
570
- *
571
- * @example
572
- * ```ts
573
- * const requireAuth = defineZodMiddleware({
574
- * responses: { 401: z.object({ message: z.string() }) },
575
- * run: (_ctx, { respond }) => respond(response({ message: 'Sign in' }, { status: 401 })),
576
- * })
577
- * ```
578
- *
579
- * @param options - Locally originated response schemas and middleware implementation.
580
- * @returns Declared middleware with inferred, runtime-validated terminal responses.
581
- * @typeParam AddedCtx - Context made available to downstream handlers.
582
- * @typeParam Ctx - Context required by the middleware.
583
- * @typeParam Responses - Status-keyed Zod schemas for locally originated responses.
584
- */
585
- export function defineZodMiddleware<
586
- AddedCtx extends object = {},
587
- Ctx extends object = AnyContext,
588
- const Responses extends AnyZodResponseBodySchemas = {},
589
- >(options: DefineZodMiddlewareOptions<AddedCtx, Ctx, Responses>): DeclaredMiddleware<AddedCtx, Ctx>
590
- export function defineZodMiddleware<
591
- AddedCtx extends object = {},
592
- Ctx extends object = AnyContext,
593
- const Responses extends AnyZodResponseBodySchemas = {},
594
- >(
595
- options: DefineZodMiddlewareOptions<AddedCtx, Ctx, Responses>,
596
- ): DeclaredMiddleware<AddedCtx, Ctx> {
597
- return defineMiddleware<AddedCtx, Ctx, ZodMiddlewareDeclarations<Responses>>({
598
- responses: buildZodMiddlewareDeclarations(options.responses),
599
- run: options.run,
600
- })
601
- }
602
-
603
- function mergeRouteSchema(
604
- defaults: ZodHandlerDefaults,
605
- schema: ZodRouteSchemaInput<any, any, any, any> | undefined,
606
- ): ZodRouteSchemaInput<any, any, any, any> | undefined {
607
- const defaultSchema = defaults.schema
608
- if (!defaultSchema) return schema
609
- if (!schema) return defaultSchema
610
-
611
- const request =
612
- !defaultSchema.request && !schema.request
613
- ? undefined
614
- : {
615
- ...defaultSchema.request,
616
- ...schema.request,
617
- }
618
-
619
- const defaultBody = defaultSchema.response?.body ?? {}
620
- const routeBody = schema.response?.body ?? {}
621
- const responseBody: AnyZodResponseBodySchemas = {}
622
-
623
- const statuses = new Set([...Object.keys(defaultBody), ...Object.keys(routeBody)])
624
- for (const status of statuses) {
625
- const s = status === 'default' ? status : Number(status)
626
- const defaultStatusSchema = defaultBody[s as keyof typeof defaultBody]
627
- const routeStatusSchema = routeBody[s as keyof typeof routeBody]
628
- if (defaultStatusSchema && routeStatusSchema) {
629
- responseBody[s as keyof typeof responseBody] = z.union([
630
- defaultStatusSchema,
631
- routeStatusSchema,
632
- ])
633
- } else if (defaultStatusSchema) {
634
- responseBody[s as keyof typeof responseBody] = defaultStatusSchema
635
- } else if (routeStatusSchema) {
636
- responseBody[s as keyof typeof responseBody] = routeStatusSchema
637
- }
638
- }
639
-
640
- const response = Object.keys(responseBody).length > 0 ? { body: responseBody } : undefined
641
-
642
- return {
643
- ...(request ? { request } : {}),
644
- ...(response ? { response } : {}),
645
- }
646
- }
647
-
648
- function normalizeMiddlewareInput<Ctx extends object>(
649
- middleware: MiddlewareInput<Ctx>,
650
- ): ContextMiddleware<any, Ctx>[] {
651
- if (!middleware) return []
652
- if (Array.isArray(middleware)) {
653
- return middleware.filter(Boolean) as ContextMiddleware<any, Ctx>[]
654
- }
655
- return [middleware as ContextMiddleware<any, Ctx>]
656
- }
657
-
658
- function mergeMiddleware<Ctx extends object>(
659
- defaultMiddleware: MiddlewareInput<Ctx>,
660
- routeMiddleware: MiddlewareInput<Ctx>,
661
- ): MiddlewareInput<Ctx> {
662
- const list = [
663
- ...normalizeMiddlewareInput(defaultMiddleware),
664
- ...normalizeMiddlewareInput(routeMiddleware),
665
- ]
666
- return list.length ? list : undefined
667
- }
668
-
669
- function mergeZodOptions<
670
- Ctx extends object,
671
- BuilderMiddleware extends MiddlewareInput<Ctx>,
672
- RouteMiddleware extends MiddlewareInput<Ctx>,
673
- Options extends {
674
- schema?: ZodRouteSchemaInput<any, any, any, any> | undefined
675
- middleware?: RouteMiddleware
676
- },
677
- >(
678
- defaults: ZodRouteHelperDefaults<Ctx, BuilderMiddleware>,
679
- options: Options,
680
- ): Omit<Options, 'middleware'> & { middleware?: MiddlewareInput<Ctx> } {
681
- const { middleware: defaultMiddleware, ...defaultOptions } = defaults
682
- const schema = mergeRouteSchema(defaults, options.schema)
683
- const middleware = mergeMiddleware(defaultMiddleware, options.middleware)
684
- return {
685
- ...defaultOptions,
686
- ...options,
687
- ...(schema === undefined ? {} : { schema }),
688
- ...(middleware === undefined ? {} : { middleware }),
689
- } as Omit<Options, 'middleware'> & { middleware?: MiddlewareInput<Ctx> }
690
- }
691
-
692
- function hasRoutePath(options: unknown): options is { path: unknown } {
693
- return (options as { path?: unknown }).path !== undefined
694
- }
695
-
696
- function isSkippableResponseValidationValue(value: unknown): boolean {
697
- return (
698
- value instanceof ReadableStream ||
699
- value instanceof Uint8Array ||
700
- (typeof Buffer !== 'undefined' && value instanceof Buffer) ||
701
- (!!value && typeof value === 'object' && Symbol.asyncIterator in value)
702
- )
703
- }
704
-
705
- function jsonValuesEqual(left: unknown, right: unknown): boolean {
706
- if (Object.is(left, right)) return true
707
- if (typeof left !== typeof right) return false
708
- if (!left || !right || typeof left !== 'object') return false
709
- if (Array.isArray(left) || Array.isArray(right)) {
710
- return (
711
- Array.isArray(left) &&
712
- Array.isArray(right) &&
713
- left.length === right.length &&
714
- left.every((value, index) => jsonValuesEqual(value, right[index]))
715
- )
716
- }
717
-
718
- const leftEntries = Object.entries(left)
719
- const rightRecord = right as Record<string, unknown>
720
- const rightKeys = new Set(Object.keys(rightRecord))
721
- return (
722
- leftEntries.length === rightKeys.size &&
723
- leftEntries.every(
724
- ([key, value]) => rightKeys.has(key) && jsonValuesEqual(value, rightRecord[key]),
725
- )
726
- )
727
- }
728
-
729
- function getResponseSchemaForStatus(
730
- schema: ZodRouteSchemaInput<any, any, any, any> | undefined,
731
- status: number,
732
- ): z.ZodTypeAny | undefined {
733
- return schema?.response?.body?.[status] ?? schema?.response?.body?.default
734
- }
735
-
736
- async function readResponseBodyForValidation(
737
- response: Response,
738
- ): Promise<ResponseBodyForValidation | undefined> {
739
- if (!response.body) return undefined
740
- const contentType = response.headers.get('content-type') ?? ''
741
- const clone = response.clone()
742
- if (isJsonContentType(contentType)) {
743
- return {
744
- value: await clone.json(),
745
- writableJson: true,
746
- }
747
- }
748
- return {
749
- value: await clone.text(),
750
- writableJson: false,
751
- }
752
- }
753
-
754
- function responseWithJsonBody(response: Response, value: unknown): Response {
755
- const headers = new Headers(response.headers)
756
- headers.delete('content-length')
757
- if (!headers.has('content-type')) {
758
- headers.set('content-type', 'application/json')
759
- }
760
- return new Response(value === undefined ? undefined : JSON.stringify(value), {
761
- status: response.status,
762
- statusText: response.statusText,
763
- headers,
764
- })
765
- }
766
-
767
- function parseResponseSchema(
768
- schema: ZodRouteSchemaInput<any, any, any, any> | undefined,
769
- status: number,
770
- value: unknown,
771
- mode: ResponseValidationMode,
772
- ): unknown {
773
- const responseSchema = getResponseSchemaForStatus(schema, status)
774
- if (!responseSchema) return value
775
- const result = responseSchema.safeParse(value)
776
- if (!result.success) {
777
- throw new ZodResponseValidationError(status, result.error)
778
- }
779
- if (mode === 'strict' && !jsonValuesEqual(value, result.data)) {
780
- throw new ZodResponseValidationError(
781
- status,
782
- new z.ZodError([
783
- {
784
- code: z.ZodIssueCode.custom,
785
- path: [],
786
- message: 'Response body does not match the parsed schema output.',
787
- },
788
- ]),
789
- )
790
- }
791
- return mode === 'parse' ? result.data : value
792
- }
793
-
794
- async function validateHandlerResult(
795
- schema: ZodRouteSchemaInput<any, any, any, any> | undefined,
796
- result: unknown,
797
- mode: ResponseValidationMode,
798
- ): Promise<unknown> {
799
- if (result instanceof Response) {
800
- const body = await readResponseBodyForValidation(result)
801
- if (!body) return result
802
- const parsed = parseResponseSchema(schema, result.status, body.value, mode)
803
- return mode === 'parse' && body.writableJson ? responseWithJsonBody(result, parsed) : result
804
- }
805
- if (isRoutekitResponse(result)) {
806
- const parsed = parseResponseSchema(schema, result.status, result.body, mode)
807
- return mode === 'parse'
808
- ? response(parsed, { status: result.status, headers: result.headers })
809
- : result
810
- }
811
- if (isRoutekitBody(result)) {
812
- const parsed = parseResponseSchema(schema, HttpStatus.OK, result.value, mode)
813
- return mode === 'parse' ? parsed : result
814
- }
815
- if (isSkippableResponseValidationValue(result)) {
816
- return result
817
- }
818
- return parseResponseSchema(schema, HttpStatus.OK, result, mode)
819
- }
820
-
821
- const defaultValidationErrorSchema = z.object({
822
- component: z.enum(['request_body', 'url_path', 'query_parameters']),
823
- errorTree: z.unknown(),
824
- message: z.string(),
825
- })
826
-
827
- /**
828
- * Options for [`zodSchemaMiddleware`]{@link zodSchemaMiddleware}.
829
- *
830
- * @typeParam Schema - Zod request and downstream response schema declaration.
831
- */
832
- type ZodSchemaMiddlewareBaseOptions<
833
- Schema extends ZodRouteSchemaInput<any, any, any, any> | undefined,
834
- > = {
835
- schema?: Schema
836
- validateResponse?: ResponseValidationOption
837
- }
838
-
839
- /**
840
- * Options for a Zod request/response schema boundary.
841
- *
842
- * A custom request-validation callback must declare the response schemas it can originate.
843
- *
844
- * @typeParam Schema - Zod request and downstream response schema declaration.
845
- * @typeParam ValidationResponses - Schemas owned by a custom validation-error callback.
846
- */
847
- export type ZodSchemaMiddlewareOptions<
848
- Schema extends ZodRouteSchemaInput<any, any, any, any> | undefined,
849
- ValidationResponses extends AnyZodResponseBodySchemas | undefined = undefined,
850
- > = ZodSchemaMiddlewareBaseOptions<Schema> &
851
- ([ValidationResponses] extends [undefined]
852
- ? {
853
- onRequestValidationError?: undefined
854
- validationResponses?: undefined
855
- }
856
- : {
857
- onRequestValidationError: ValidationErrorHandler<
858
- Extract<ValidationResponses, AnyZodResponseBodySchemas>
859
- >
860
- validationResponses: ValidationResponses
861
- })
862
-
863
- /**
864
- * Create Zod middleware that parses request inputs and validates downstream responses.
865
- *
866
- * @example
867
- * ```ts
868
- * const parseUserId = zodSchemaMiddleware({
869
- * schema: { request: { path: z.object({ id: z.string() }) } },
870
- * })
871
- * ```
872
- *
873
- * @param options - Request, response, and validation-error schema behavior.
874
- * @returns Declared middleware exposing parsed request values to downstream handlers.
875
- * @typeParam Schema - Zod request and downstream response schema declaration.
876
- * @typeParam Ctx - Context required before this middleware executes.
877
- */
878
- export function zodSchemaMiddleware<
879
- Schema extends ZodRouteSchemaInput<any, any, any, any> | undefined,
880
- const ValidationResponses extends AnyZodResponseBodySchemas | undefined = undefined,
881
- Ctx extends object = AnyContext,
882
- >(
883
- options: ZodSchemaMiddlewareOptions<Schema, ValidationResponses>,
884
- ): DeclaredMiddleware<ZodSchemaMiddlewareContext<Schema>, Ctx> {
885
- const validateResponse = normalizeResponseValidationMode(options.validateResponse) ?? 'parse'
886
- const parsesRequest = options.schema?.request !== undefined
887
- const validationResponses =
888
- options.validationResponses ??
889
- (parsesRequest
890
- ? ({
891
- [HttpStatus.BAD_REQUEST]: defaultValidationErrorSchema,
892
- } satisfies AnyZodResponseBodySchemas)
893
- : {})
894
- const declarations = buildZodMiddlewareDeclarations(validationResponses)
895
- const onRequestValidationError = options.onRequestValidationError ?? createValidationResponse
896
-
897
- return defineMiddleware<ZodSchemaMiddlewareContext<Schema>, Ctx, typeof declarations>({
898
- schema: buildRouteSchema(options.schema),
899
- responses: declarations,
900
- async run(ctx, { next, forward, respond }) {
901
- const bodySchema = options.schema?.request?.body
902
- const pathSchema = options.schema?.request?.path
903
- const querySchema = options.schema?.request?.query
904
- const queryParams = readQueryParams(ctx.url.searchParams)
905
- const handlerContext = ctx as HandlerContext<Ctx> & ZodSchemaMiddlewareContext<Schema>
906
- const params = {
907
- path: ctx.path as InferSchema<ExtractPathSchema<Schema>>,
908
- query: undefined as InferSchema<ExtractQuerySchema<Schema>>,
909
- body: undefined as InferSchema<ExtractBodySchema<Schema>>,
910
- }
911
- handlerContext.params = params
912
-
913
- if (querySchema) {
914
- const parsed = querySchema.safeParse(queryParams)
915
- if (!parsed.success) {
916
- return respond(
917
- onRequestValidationError(
918
- ValidationError.QUERY_PARAMETERS,
919
- parsed.error,
920
- ) as never,
921
- )
922
- }
923
- params.query = parsed.data as InferSchema<ExtractQuerySchema<Schema>>
924
- }
925
-
926
- if (pathSchema) {
927
- const parsed = pathSchema.safeParse(ctx.path)
928
- if (!parsed.success) {
929
- return respond(
930
- onRequestValidationError(ValidationError.URL_PATH, parsed.error) as never,
931
- )
932
- }
933
- params.path = parsed.data as InferSchema<ExtractPathSchema<Schema>>
934
- }
935
-
936
- if (bodySchema) {
937
- let rawBody: unknown
938
- try {
939
- rawBody = await ctx.request.body.parse()
940
- } catch (err) {
941
- if (err instanceof RequestBodyError) throw err
942
- return respond(
943
- onRequestValidationError(
944
- ValidationError.REQUEST_BODY,
945
- zodErrorFromThrowable(err),
946
- ) as never,
947
- )
948
- }
949
- const parsed = bodySchema.safeParse(rawBody)
950
- if (!parsed.success) {
951
- return respond(
952
- onRequestValidationError(
953
- ValidationError.REQUEST_BODY,
954
- parsed.error,
955
- ) as never,
956
- )
957
- }
958
- params.body = parsed.data as InferSchema<ExtractBodySchema<Schema>>
959
- }
960
-
961
- const result = await next()
962
- return forward(
963
- validateResponse === false
964
- ? result
965
- : ((await validateHandlerResult(
966
- options.schema,
967
- result,
968
- validateResponse,
969
- )) as HandlerResult),
970
- )
971
- },
972
- })
973
- }
974
-
975
- /**
976
- * Build a route handler that parses request inputs with Zod and optionally validates responses.
977
- *
978
- * @example
979
- * ```ts
980
- * const handler = zodHandler({
981
- * schema: {
982
- * request: {
983
- * path: z.object({id: z.string()}),
984
- * },
985
- * response: {
986
- * body: {
987
- * 200: z.object({id: z.string()}),
988
- * },
989
- * },
990
- * },
991
- * handler: ({params}) => ({id: params.path.id}),
992
- * })
993
- * ```
994
- *
995
- * @param options - Handler definition extended with request and response Zod schemas.
996
- * @returns A handler that validates request inputs before invoking the provided handler.
997
- */
998
- export function zodHandler<
999
- Schema extends ZodRouteSchemaInput<any, any, any, any> | undefined,
1000
- Ctx extends object = AnyContext,
1001
- >(
1002
- options: ZodHandlerOptions<Schema, Ctx>,
1003
- ): Handler<InferResponseBody<ExtractResponseBodySchemas<Schema>>, Ctx> {
1004
- const resolved = resolveDefaults(options)
1005
-
1006
- return async function (this: Router<any>, ctx: HandlerContext<Ctx>) {
1007
- const run = async (): Promise<
1008
- HandlerResult<InferResponseBody<ExtractResponseBodySchemas<Schema>>>
1009
- > => {
1010
- const validateAndReturn = async (
1011
- result: HandlerResult<InferResponseBody<ExtractResponseBodySchemas<Schema>>>,
1012
- ): Promise<HandlerResult<InferResponseBody<ExtractResponseBodySchemas<Schema>>>> => {
1013
- if (resolved.validateResponse !== false) {
1014
- return (await validateHandlerResult(
1015
- resolved.schema,
1016
- result,
1017
- resolved.validateResponse,
1018
- )) as HandlerResult<InferResponseBody<ExtractResponseBodySchemas<Schema>>>
1019
- }
1020
- return result
1021
- }
1022
- const bodySchema = resolved.schema?.request?.body
1023
- const pathSchema = resolved.schema?.request?.path
1024
- const querySchema = resolved.schema?.request?.query
1025
- const queryParams = readQueryParams(ctx.url.searchParams)
1026
-
1027
- const handlerContext = {
1028
- ...ctx,
1029
- params: {
1030
- path: ctx.path as InferSchema<ExtractPathSchema<Schema>>,
1031
- query: undefined as InferSchema<ExtractQuerySchema<Schema>>,
1032
- body: undefined as InferSchema<ExtractBodySchema<Schema>>,
1033
- },
1034
- } as ZodHandlerContext<
1035
- ExtractBodySchema<Schema>,
1036
- ExtractPathSchema<Schema>,
1037
- ExtractQuerySchema<Schema>,
1038
- Ctx
1039
- >
1040
-
1041
- if (querySchema) {
1042
- const queryResult = querySchema.safeParse(queryParams)
1043
- if (!queryResult.success) {
1044
- return await validateAndReturn(
1045
- resolved.onRequestValidationError(
1046
- ValidationError.QUERY_PARAMETERS,
1047
- queryResult.error,
1048
- ) as HandlerResult<InferResponseBody<ExtractResponseBodySchemas<Schema>>>,
1049
- )
1050
- }
1051
- handlerContext.params.query = queryResult.data as InferSchema<
1052
- ExtractQuerySchema<Schema>
1053
- >
1054
- }
1055
-
1056
- if (pathSchema) {
1057
- const pathResult = pathSchema.safeParse(ctx.path)
1058
- if (!pathResult.success) {
1059
- return await validateAndReturn(
1060
- resolved.onRequestValidationError(
1061
- ValidationError.URL_PATH,
1062
- pathResult.error,
1063
- ) as HandlerResult<InferResponseBody<ExtractResponseBodySchemas<Schema>>>,
1064
- )
1065
- }
1066
- handlerContext.params.path = pathResult.data as InferSchema<
1067
- ExtractPathSchema<Schema>
1068
- >
1069
- }
1070
-
1071
- if (bodySchema) {
1072
- let rawBody: unknown
1073
- try {
1074
- rawBody = await ctx.request.body.parse()
1075
- } catch (err) {
1076
- if (err instanceof RequestBodyError) throw err
1077
- return await validateAndReturn(
1078
- resolved.onRequestValidationError(
1079
- ValidationError.REQUEST_BODY,
1080
- zodErrorFromThrowable(err),
1081
- ) as HandlerResult<InferResponseBody<ExtractResponseBodySchemas<Schema>>>,
1082
- )
1083
- }
1084
- const bodyResult = bodySchema.safeParse(rawBody)
1085
- if (!bodyResult.success) {
1086
- return await validateAndReturn(
1087
- resolved.onRequestValidationError(
1088
- ValidationError.REQUEST_BODY,
1089
- bodyResult.error,
1090
- ) as HandlerResult<InferResponseBody<ExtractResponseBodySchemas<Schema>>>,
1091
- )
1092
- }
1093
- handlerContext.params.body = bodyResult.data as InferSchema<
1094
- ExtractBodySchema<Schema>
1095
- >
1096
- }
1097
-
1098
- const result = await resolved.handler.call(this, handlerContext)
1099
- return await validateAndReturn(result)
1100
- }
1101
-
1102
- return await run()
1103
- }
1104
- }
1105
-
1106
- /**
1107
- * Build a validated handler plus the matching JSON Schema route metadata.
1108
- *
1109
- * @param options - Zod-backed handler definition with optional request and response schemas.
1110
- * @returns The validated handler and generated route `schema`.
1111
- */
1112
- export function zodPartial<
1113
- Schema extends ZodRouteSchemaInput<any, any, any, any> | undefined,
1114
- Ctx extends object = AnyContext,
1115
- >(
1116
- options: ZodHandlerOptions<Schema, Ctx>,
1117
- ): {
1118
- handler: Handler<InferResponseBody<ExtractResponseBodySchemas<Schema>>, Ctx>
1119
- schema?: RouteSchema
1120
- } {
1121
- const schema = buildRouteSchema(options.schema)
1122
- return {
1123
- handler: zodHandler(options),
1124
- ...(schema ? { schema } : {}),
1125
- }
1126
- }
1127
-
1128
- /**
1129
- * Build method-specific route options that validate inputs with Zod and expose JSON Schema metadata.
1130
- *
1131
- * @example
1132
- * ```ts
1133
- * router.post('/users/:id', withZod({
1134
- * name: 'user.update',
1135
- * schema: {
1136
- * request: {
1137
- * path: z.object({id: z.string()}),
1138
- * body: z.object({name: z.string()}),
1139
- * },
1140
- * },
1141
- * handler: ({params}) => ({id: params.path.id, name: params.body.name}),
1142
- * }))
1143
- * ```
1144
- *
1145
- * @param options - Method route options extended with Zod request and response schemas.
1146
- * @returns Route options compatible with method-specific router helpers.
1147
- */
1148
- export function withZod<
1149
- Schema extends ZodRouteSchemaInput<any, any, any, any> | undefined,
1150
- Ctx extends object = AnyContext,
1151
- RouteMiddleware extends MiddlewareInput<Ctx> = undefined,
1152
- >(
1153
- options: WithZodOptions<Schema, Ctx, undefined, RouteMiddleware>,
1154
- ): RouteOptions<Ctx, RouteMiddleware, ZodRouteContext<Ctx, undefined, RouteMiddleware>> {
1155
- const {
1156
- schema,
1157
- handler,
1158
- onRequestValidationError,
1159
- validateResponse,
1160
- validationResponses,
1161
- ...routeOptions
1162
- } = options
1163
- const partial = zodPartial<Schema, ZodRouteContext<Ctx, undefined, RouteMiddleware>>({
1164
- ...(schema ? { schema } : {}),
1165
- handler,
1166
- ...(onRequestValidationError ? { onRequestValidationError } : {}),
1167
- ...(validationResponses ? { validationResponses } : {}),
1168
- ...(validateResponse === undefined ? {} : { validateResponse }),
1169
- } as ZodHandlerOptions<Schema, ZodRouteContext<Ctx, undefined, RouteMiddleware>>)
1170
- return {
1171
- ...routeOptions,
1172
- handler: partial.handler,
1173
- ...(partial.schema ? { schema: partial.schema } : {}),
1174
- }
1175
- }
1176
-
1177
- /**
1178
- * Create a Zod route builder with shared defaults.
1179
- *
1180
- * @example
1181
- * ```ts
1182
- * const route = createZodRouteBuilder({
1183
- * validateResponse: false,
1184
- * schema: {
1185
- * response: {
1186
- * body: {
1187
- * 400: z.object({message: z.string()}),
1188
- * },
1189
- * },
1190
- * },
1191
- * })
1192
- *
1193
- * router.post('/users/:id', route({
1194
- * name: 'user.update',
1195
- * schema: {
1196
- * request: {
1197
- * path: z.object({id: z.string()}),
1198
- * },
1199
- * },
1200
- * handler: ({params}) => ({id: params.path.id}),
1201
- * }))
1202
- * ```
1203
- *
1204
- * @param defaults - Default schema fragments, response validation, and request validation error handling.
1205
- * @returns A route builder compatible with method-specific router helpers and full route definitions.
1206
- */
1207
- export function createZodRouteBuilder<
1208
- BuilderMiddleware extends MiddlewareInput<any>,
1209
- BuilderCtx extends object = ContextFromMiddlewareInput<BuilderMiddleware>,
1210
- >(
1211
- defaults: ZodRouteHelperDefaults<BuilderCtx, BuilderMiddleware> & {
1212
- middleware: BuilderMiddleware
1213
- },
1214
- ): ZodRouteBuilder<BuilderCtx, BuilderMiddleware>
1215
- export function createZodRouteBuilder<
1216
- BuilderCtx extends object = AnyContext,
1217
- BuilderMiddleware extends MiddlewareInput<BuilderCtx> = undefined,
1218
- >(
1219
- defaults?: ZodRouteHelperDefaults<BuilderCtx, BuilderMiddleware>,
1220
- ): ZodRouteBuilder<BuilderCtx, BuilderMiddleware>
1221
- export function createZodRouteBuilder<
1222
- BuilderCtx extends object = AnyContext,
1223
- BuilderMiddleware extends MiddlewareInput<BuilderCtx> = undefined,
1224
- >(
1225
- defaults: ZodRouteHelperDefaults<BuilderCtx, BuilderMiddleware> = {},
1226
- ): ZodRouteBuilder<BuilderCtx, BuilderMiddleware> {
1227
- return (<
1228
- Schema extends ZodRouteSchemaInput<any, any, any, any> | undefined,
1229
- RouteMiddleware extends MiddlewareInput<BuilderCtx> = undefined,
1230
- >(
1231
- options:
1232
- | ZodRouteOptions<Schema, BuilderCtx, BuilderMiddleware, RouteMiddleware>
1233
- | WithZodOptions<Schema, BuilderCtx, BuilderMiddleware, RouteMiddleware>,
1234
- ):
1235
- | Route<
1236
- BuilderCtx,
1237
- ZodBuilderMiddlewareInput<BuilderCtx, BuilderMiddleware, RouteMiddleware>,
1238
- ZodRouteContext<BuilderCtx, BuilderMiddleware, RouteMiddleware>
1239
- >
1240
- | RouteOptions<
1241
- BuilderCtx,
1242
- ZodBuilderMiddlewareInput<BuilderCtx, BuilderMiddleware, RouteMiddleware>,
1243
- ZodRouteContext<BuilderCtx, BuilderMiddleware, RouteMiddleware>
1244
- > => {
1245
- const merged = mergeZodOptions(defaults, options)
1246
- const {
1247
- schema,
1248
- handler,
1249
- onRequestValidationError,
1250
- validateResponse,
1251
- validationResponses,
1252
- middleware,
1253
- ...routeOptions
1254
- } = merged
1255
- const boundary = zodSchemaMiddleware({
1256
- ...(schema ? { schema } : {}),
1257
- ...(onRequestValidationError ? { onRequestValidationError } : {}),
1258
- ...(validateResponse === undefined ? {} : { validateResponse }),
1259
- ...(validationResponses === undefined ? {} : { validationResponses }),
1260
- } as any)
1261
- const routeMiddleware = mergeMiddleware(
1262
- middleware as MiddlewareInput<BuilderCtx>,
1263
- boundary as unknown as MiddlewareInput<BuilderCtx>,
1264
- )
1265
- const built = {
1266
- ...routeOptions,
1267
- handler,
1268
- middleware: routeMiddleware,
1269
- }
1270
- if (hasRoutePath(built)) {
1271
- return built as unknown as Route<
1272
- BuilderCtx,
1273
- ZodBuilderMiddlewareInput<BuilderCtx, BuilderMiddleware, RouteMiddleware>,
1274
- ZodRouteContext<BuilderCtx, BuilderMiddleware, RouteMiddleware>
1275
- >
1276
- }
1277
- return built as unknown as RouteOptions<
1278
- BuilderCtx,
1279
- ZodBuilderMiddlewareInput<BuilderCtx, BuilderMiddleware, RouteMiddleware>,
1280
- ZodRouteContext<BuilderCtx, BuilderMiddleware, RouteMiddleware>
1281
- >
1282
- }) as ZodRouteBuilder<BuilderCtx, BuilderMiddleware>
1283
- }
1284
-
1285
- /**
1286
- * Build a full route definition that validates inputs with Zod and exposes JSON Schema metadata.
1287
- *
1288
- * @param options - Route definition extended with Zod request and response schemas.
1289
- * @returns A route compatible with the core router.
1290
- */
1291
- export function zodRoute<
1292
- Schema extends ZodRouteSchemaInput<any, any, any, any> | undefined,
1293
- Ctx extends object = AnyContext,
1294
- RouteMiddleware extends MiddlewareInput<Ctx> = undefined,
1295
- >(
1296
- options: ZodRouteOptions<Schema, Ctx, undefined, RouteMiddleware>,
1297
- ): Route<Ctx, RouteMiddleware, ZodRouteContext<Ctx, undefined, RouteMiddleware>> {
1298
- const {
1299
- schema,
1300
- handler,
1301
- onRequestValidationError,
1302
- validateResponse,
1303
- validationResponses,
1304
- ...route
1305
- } = options
1306
- const partial = zodPartial<Schema, ZodRouteContext<Ctx, undefined, RouteMiddleware>>({
1307
- ...(schema ? { schema } : {}),
1308
- handler,
1309
- ...(onRequestValidationError ? { onRequestValidationError } : {}),
1310
- ...(validationResponses ? { validationResponses } : {}),
1311
- ...(validateResponse === undefined ? {} : { validateResponse }),
1312
- } as ZodHandlerOptions<Schema, ZodRouteContext<Ctx, undefined, RouteMiddleware>>)
1313
- return {
1314
- ...route,
1315
- handler: partial.handler,
1316
- ...(partial.schema ? { schema: partial.schema } : {}),
1317
- }
1318
- }