@mpen/routekit 0.1.0 → 0.1.1
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,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
|
-
}
|