@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.
- package/dist/bin.d.mts +4 -0
- package/dist/client/react.d.mts +178 -0
- package/dist/client/react.mjs +142 -0
- package/dist/client.d.mts +433 -0
- package/dist/client.mjs +264 -0
- package/dist/content-BuDOmhH_.mjs +102 -0
- package/dist/core-CzUCxvGk.d.mts +140 -0
- package/dist/core-DbmQauwS.mjs +81 -0
- package/dist/handlers.d.mts +72 -0
- package/dist/handlers.mjs +153 -0
- package/dist/index.d.mts +3 -0
- package/dist/index.mjs +1152 -0
- package/dist/middleware.d.mts +388 -0
- package/dist/middleware.mjs +1222 -0
- package/dist/request-Dn0zc-xm.mjs +1025 -0
- package/dist/response/content.d.mts +79 -0
- package/dist/response/content.mjs +2 -0
- package/dist/response/json-rpc.d.mts +1 -0
- package/dist/response/json-rpc.mjs +1 -0
- package/dist/response/problem/valibot.d.mts +230 -0
- package/dist/response/problem/valibot.mjs +258 -0
- package/dist/response/problem.d.mts +415 -0
- package/dist/response/problem.mjs +183 -0
- package/dist/response/status.d.mts +45 -0
- package/dist/response/status.mjs +2 -0
- package/dist/responses-B379Ep9Y.d.mts +296 -0
- package/dist/responses-BpVrgeYi.mjs +101 -0
- package/dist/router-Cwb7ak0J.d.mts +1819 -0
- package/dist/routes.d.mts +282 -0
- package/dist/routes.mjs +311 -0
- package/dist/status-C-8mw-FB.mjs +59 -0
- package/dist/valibot-D7liFYyB.d.mts +290 -0
- package/dist/valibot-Du97X-TS.mjs +326 -0
- package/package.json +8 -2
- package/src/bin/gen-api-client.test.ts +0 -70
- package/src/bin/gen-api-client.ts +0 -986
- package/src/client/headers.ts +0 -31
- package/src/client/index.ts +0 -8
- package/src/client/promise.ts +0 -11
- package/src/client/react/index.test.tsx +0 -266
- package/src/client/react/index.ts +0 -431
- package/src/client/responses.test.ts +0 -151
- package/src/client/responses.ts +0 -278
- package/src/client/transport.ts +0 -74
- package/src/client/transports/body-codec.ts +0 -61
- package/src/client/transports/fetch.ts +0 -113
- package/src/client/tsconfig.json +0 -9
- package/src/client/types.ts +0 -15
- package/src/client/url.ts +0 -31
- package/src/index.ts +0 -63
- package/src/router/fetch-types.ts +0 -13
- package/src/router/handlers/index.ts +0 -2
- package/src/router/handlers/openapi/index.ts +0 -2
- package/src/router/handlers/openapi/openapi.ts +0 -293
- package/src/router/integration/zod-openapi.test.ts +0 -74
- package/src/router/lib/charset.test.ts +0 -22
- package/src/router/lib/charset.ts +0 -133
- package/src/router/lib/collections.ts +0 -3
- package/src/router/lib/format.test.ts +0 -67
- package/src/router/lib/format.ts +0 -35
- package/src/router/lib/host.ts +0 -4
- package/src/router/lib/json-schema.ts +0 -6
- package/src/router/lib/media-type.test.ts +0 -122
- package/src/router/lib/media-type.ts +0 -289
- package/src/router/lib/pathname.test.ts +0 -18
- package/src/router/lib/pathname.ts +0 -19
- package/src/router/lib/route-names.ts +0 -70
- package/src/router/lib/route-normalize.test.ts +0 -36
- package/src/router/lib/route-normalize.ts +0 -67
- package/src/router/lib/schema-merge.ts +0 -56
- package/src/router/middleware/accept-ctx.test.ts +0 -33
- package/src/router/middleware/accept-ctx.ts +0 -12
- package/src/router/middleware/body-limit.test.ts +0 -112
- package/src/router/middleware/body-limit.ts +0 -121
- package/src/router/middleware/content-type-context.ts +0 -0
- package/src/router/middleware/cors.test.ts +0 -269
- package/src/router/middleware/cors.ts +0 -490
- package/src/router/middleware/csrf.test.ts +0 -106
- package/src/router/middleware/csrf.ts +0 -192
- package/src/router/middleware/define.ts +0 -249
- package/src/router/middleware/index.ts +0 -34
- package/src/router/middleware/jsxhtml-response.ts +0 -0
- package/src/router/middleware/oas-swagger.ts +0 -0
- package/src/router/middleware/rate-limit.test.ts +0 -886
- package/src/router/middleware/rate-limit.ts +0 -920
- package/src/router/middleware/request-id-ctx.test.ts +0 -183
- package/src/router/middleware/request-id-ctx.ts +0 -135
- package/src/router/middleware/request-logger-format.test.ts +0 -16
- package/src/router/middleware/request-logger-format.ts +0 -269
- package/src/router/middleware/request-logger.test.ts +0 -267
- package/src/router/middleware/request-logger.ts +0 -131
- package/src/router/middleware/start-time-ctx.ts +0 -5
- package/src/router/request.ts +0 -611
- package/src/router/response/core.ts +0 -181
- package/src/router/response/directives.ts +0 -233
- package/src/router/response/formats/content/bodyless.ts +0 -54
- package/src/router/response/formats/content/content.ts +0 -79
- package/src/router/response/formats/content/index.ts +0 -2
- package/src/router/response/formats/json-rpc/index.ts +0 -2
- package/src/router/response/formats/problem/badRequest.ts +0 -90
- package/src/router/response/formats/problem/conflict.ts +0 -90
- package/src/router/response/formats/problem/created.ts +0 -40
- package/src/router/response/formats/problem/index.ts +0 -27
- package/src/router/response/formats/problem/notFound.ts +0 -90
- package/src/router/response/formats/problem/permissionDenied.ts +0 -90
- package/src/router/response/formats/problem/problem.test.ts +0 -888
- package/src/router/response/formats/problem/rateLimited.ts +0 -90
- package/src/router/response/formats/problem/responses.ts +0 -219
- package/src/router/response/formats/problem/root-errors.ts +0 -48
- package/src/router/response/formats/problem/sessionExpired.ts +0 -90
- package/src/router/response/formats/problem/types.ts +0 -170
- package/src/router/response/formats/problem/unauthenticated.ts +0 -90
- package/src/router/response/formats/problem/valibot.ts +0 -410
- package/src/router/response/formats/status/index.ts +0 -1
- package/src/router/response/formats/status/responses.ts +0 -59
- package/src/router/response/formats/status/status.test.ts +0 -21
- package/src/router/response/framers.ts +0 -85
- package/src/router/response/index.ts +0 -28
- package/src/router/response/openapi.test.ts +0 -96
- package/src/router/response/openapi.ts +0 -1
- package/src/router/response/serializers.ts +0 -66
- package/src/router/response/stream.ts +0 -35
- package/src/router/router.test.ts +0 -1571
- package/src/router/router.ts +0 -1965
- package/src/router/routes/index.ts +0 -46
- package/src/router/routes/valibot/index.ts +0 -18
- package/src/router/routes/valibot/valibot.ts +0 -1393
- package/src/router/routes/valibot.test.ts +0 -286
- package/src/router/routes/zod/index.ts +0 -18
- package/src/router/routes/zod/zod.ts +0 -1318
- package/src/router/routes/zod.test.ts +0 -280
- package/src/router/server-interface.ts +0 -31
- 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
|
-
}
|