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