@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,410 +0,0 @@
|
|
|
1
|
-
import * as v from 'valibot'
|
|
2
|
-
import { validationProblem, type ValidationProblemInit } from './responses'
|
|
3
|
-
import type { ProblemIssue, ProblemIssuePathSegment, ProblemResponse } from './types'
|
|
4
|
-
import type { RoutekitResponse } from '../../core'
|
|
5
|
-
import {
|
|
6
|
-
createValibotRouteBuilder as baseCreateValibotRouteBuilder,
|
|
7
|
-
ValibotValidationError,
|
|
8
|
-
type ValibotRouteBuilder,
|
|
9
|
-
type ValibotRouteHelperDefaults,
|
|
10
|
-
} from '../../../routes/valibot/valibot'
|
|
11
|
-
import type { ContextFromMiddlewareInput, MiddlewareInput } from '../../../types'
|
|
12
|
-
import { HttpStatus } from '@mpen/http'
|
|
13
|
-
|
|
14
|
-
type AnySchema = v.BaseSchema<any, any, any>
|
|
15
|
-
type StringSchema = v.BaseSchema<any, string, any>
|
|
16
|
-
type ProblemSchemaResult<CodeSchema extends StringSchema, Schema extends AnySchema> = v.BaseSchema<
|
|
17
|
-
v.InferInput<Schema>,
|
|
18
|
-
ProblemResponse<v.InferOutput<CodeSchema> & string>,
|
|
19
|
-
v.InferIssue<Schema>
|
|
20
|
-
>
|
|
21
|
-
|
|
22
|
-
function pathSegmentFromValibotKey(key: unknown): ProblemIssuePathSegment | undefined {
|
|
23
|
-
if (typeof key === 'string' || typeof key === 'number') return key
|
|
24
|
-
return undefined
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function pathFromValibotIssue(issue: v.BaseIssue<unknown>): ProblemIssuePathSegment[] | undefined {
|
|
28
|
-
const path = issue.path
|
|
29
|
-
?.map((item) => pathSegmentFromValibotKey(item.key))
|
|
30
|
-
.filter((item): item is ProblemIssuePathSegment => item !== undefined)
|
|
31
|
-
return path?.length ? path : undefined
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function collectValibotIssues(issue: v.BaseIssue<unknown>, target: ProblemIssue[]): ProblemIssue[] {
|
|
35
|
-
if (issue.issues?.length) {
|
|
36
|
-
for (const child of issue.issues) {
|
|
37
|
-
collectValibotIssues(child, target)
|
|
38
|
-
}
|
|
39
|
-
return target
|
|
40
|
-
}
|
|
41
|
-
target.push(issueFromValibotIssue(issue))
|
|
42
|
-
return target
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function createSuccessSchema<const DataSchema extends AnySchema>(dataSchema: DataSchema) {
|
|
46
|
-
return v.object({
|
|
47
|
-
success: v.literal(true),
|
|
48
|
-
data: dataSchema,
|
|
49
|
-
})
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function createSuccessSchemaWithMeta<
|
|
53
|
-
const DataSchema extends AnySchema,
|
|
54
|
-
const MetaSchema extends AnySchema,
|
|
55
|
-
>(dataSchema: DataSchema, metaSchema: MetaSchema) {
|
|
56
|
-
return v.object({
|
|
57
|
-
success: v.literal(true),
|
|
58
|
-
data: dataSchema,
|
|
59
|
-
meta: v.optional(metaSchema),
|
|
60
|
-
})
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
type SuccessSchemaResult<
|
|
64
|
-
DataSchema extends AnySchema,
|
|
65
|
-
MetaSchema extends AnySchema | undefined,
|
|
66
|
-
> = MetaSchema extends AnySchema
|
|
67
|
-
? ReturnType<typeof createSuccessSchemaWithMeta<DataSchema, MetaSchema>>
|
|
68
|
-
: ReturnType<typeof createSuccessSchema<DataSchema>>
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Create a Valibot schema for standard response issues.
|
|
72
|
-
*
|
|
73
|
-
* @example
|
|
74
|
-
* ```ts
|
|
75
|
-
* const schema = problemIssueSchema(v.picklist(['required', 'invalid_type']))
|
|
76
|
-
* ```
|
|
77
|
-
*
|
|
78
|
-
* @param codeSchema - Optional issue code schema. Defaults to `v.string()`.
|
|
79
|
-
* @returns Valibot object schema for [`ProblemIssue`]{@link ProblemIssue}.
|
|
80
|
-
* @typeParam CodeSchema - Valibot schema used for issue codes.
|
|
81
|
-
*/
|
|
82
|
-
export function problemIssueSchema<const CodeSchema extends StringSchema = StringSchema>(
|
|
83
|
-
codeSchema: CodeSchema = v.string() as unknown as CodeSchema,
|
|
84
|
-
) {
|
|
85
|
-
return v.object({
|
|
86
|
-
code: codeSchema,
|
|
87
|
-
message: v.string(),
|
|
88
|
-
path: v.optional(v.pipe(v.array(v.union([v.string(), v.number()])), v.readonly())),
|
|
89
|
-
expected: v.optional(v.string()),
|
|
90
|
-
received: v.optional(v.string()),
|
|
91
|
-
})
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Create a Valibot schema for standard primary error metadata.
|
|
96
|
-
*
|
|
97
|
-
* @example
|
|
98
|
-
* ```ts
|
|
99
|
-
* const schema = problemErrorSchema(v.picklist(['not_found', 'validation_failed']))
|
|
100
|
-
* ```
|
|
101
|
-
*
|
|
102
|
-
* @param codeSchema - Optional primary error code schema. Defaults to `v.string()`.
|
|
103
|
-
* @returns Valibot object schema for problem error metadata.
|
|
104
|
-
* @typeParam CodeSchema - Valibot schema used for primary error codes.
|
|
105
|
-
*/
|
|
106
|
-
export function problemErrorSchema<const CodeSchema extends StringSchema = StringSchema>(
|
|
107
|
-
codeSchema: CodeSchema = v.string() as unknown as CodeSchema,
|
|
108
|
-
) {
|
|
109
|
-
return v.object({
|
|
110
|
-
code: codeSchema,
|
|
111
|
-
message: v.string(),
|
|
112
|
-
title: v.optional(v.string()),
|
|
113
|
-
})
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Create a Valibot schema for successful standard response envelopes.
|
|
118
|
-
*
|
|
119
|
-
* @example
|
|
120
|
-
* ```ts
|
|
121
|
-
* const schema = okSchema(v.object({ id: v.string() }))
|
|
122
|
-
* ```
|
|
123
|
-
*
|
|
124
|
-
* @param dataSchema - Valibot schema for the response payload.
|
|
125
|
-
* @param metaSchema - Optional Valibot schema for response metadata.
|
|
126
|
-
* @returns Valibot object schema for a successful standard response.
|
|
127
|
-
* @typeParam DataSchema - Valibot schema used for the response payload.
|
|
128
|
-
* @typeParam MetaSchema - Optional Valibot schema used for response metadata.
|
|
129
|
-
*/
|
|
130
|
-
export function okSchema<
|
|
131
|
-
const DataSchema extends AnySchema,
|
|
132
|
-
const MetaSchema extends AnySchema | undefined = undefined,
|
|
133
|
-
>(dataSchema: DataSchema, metaSchema?: MetaSchema): SuccessSchemaResult<DataSchema, MetaSchema> {
|
|
134
|
-
if (metaSchema) {
|
|
135
|
-
return createSuccessSchemaWithMeta(dataSchema, metaSchema) as SuccessSchemaResult<
|
|
136
|
-
DataSchema,
|
|
137
|
-
MetaSchema
|
|
138
|
-
>
|
|
139
|
-
}
|
|
140
|
-
return createSuccessSchema(dataSchema) as SuccessSchemaResult<DataSchema, MetaSchema>
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Create a Valibot schema for standard problem response envelopes.
|
|
145
|
-
*
|
|
146
|
-
* @example
|
|
147
|
-
* ```ts
|
|
148
|
-
* const schema = problemSchema({
|
|
149
|
-
* code: v.picklist(['not_found', 'validation_failed']),
|
|
150
|
-
* })
|
|
151
|
-
* ```
|
|
152
|
-
*
|
|
153
|
-
* @param options - Optional primary error and issue schemas.
|
|
154
|
-
* @returns Valibot object schema for a standard problem response.
|
|
155
|
-
* @typeParam CodeSchema - Valibot schema used for primary error codes.
|
|
156
|
-
* @typeParam IssueSchema - Valibot schema used for response issues.
|
|
157
|
-
*/
|
|
158
|
-
export function problemSchema<
|
|
159
|
-
const CodeSchema extends StringSchema = StringSchema,
|
|
160
|
-
const IssueSchema extends AnySchema = ReturnType<typeof problemIssueSchema>,
|
|
161
|
-
>(
|
|
162
|
-
options: {
|
|
163
|
-
code?: CodeSchema
|
|
164
|
-
issue?: IssueSchema
|
|
165
|
-
} = {},
|
|
166
|
-
) {
|
|
167
|
-
const codeSchema = options.code ?? (v.string() as unknown as CodeSchema)
|
|
168
|
-
const issueSchema = options.issue ?? (problemIssueSchema() as unknown as IssueSchema)
|
|
169
|
-
|
|
170
|
-
const schema = v.object({
|
|
171
|
-
success: v.literal(false),
|
|
172
|
-
error: problemErrorSchema(codeSchema),
|
|
173
|
-
issues: v.optional(v.pipe(v.array(issueSchema), v.readonly())),
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
return schema as unknown as ProblemSchemaResult<CodeSchema, typeof schema>
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Create a Valibot schema for the full standard response union.
|
|
181
|
-
*
|
|
182
|
-
* @example
|
|
183
|
-
* ```ts
|
|
184
|
-
* const schema = standardResponseSchema(v.object({ id: v.string() }), {
|
|
185
|
-
* code: v.picklist(['not_found']),
|
|
186
|
-
* })
|
|
187
|
-
* ```
|
|
188
|
-
*
|
|
189
|
-
* @param dataSchema - Valibot schema for the successful response payload.
|
|
190
|
-
* @param options - Optional metadata, primary error, and issue schemas.
|
|
191
|
-
* @returns Valibot union schema for successful and problem responses.
|
|
192
|
-
* @typeParam DataSchema - Valibot schema used for the response payload.
|
|
193
|
-
* @typeParam MetaSchema - Optional Valibot schema used for response metadata.
|
|
194
|
-
* @typeParam CodeSchema - Valibot schema used for primary error codes.
|
|
195
|
-
* @typeParam IssueSchema - Valibot schema used for response issues.
|
|
196
|
-
*/
|
|
197
|
-
export function standardResponseSchema<
|
|
198
|
-
const DataSchema extends AnySchema,
|
|
199
|
-
const MetaSchema extends AnySchema | undefined = undefined,
|
|
200
|
-
const CodeSchema extends StringSchema = StringSchema,
|
|
201
|
-
const IssueSchema extends AnySchema = ReturnType<typeof problemIssueSchema>,
|
|
202
|
-
>(
|
|
203
|
-
dataSchema: DataSchema,
|
|
204
|
-
options: {
|
|
205
|
-
meta?: MetaSchema
|
|
206
|
-
code?: CodeSchema
|
|
207
|
-
issue?: IssueSchema
|
|
208
|
-
} = {},
|
|
209
|
-
) {
|
|
210
|
-
const successEnvelopeSchema =
|
|
211
|
-
options.meta === undefined ? okSchema(dataSchema) : okSchema(dataSchema, options.meta)
|
|
212
|
-
return v.union([
|
|
213
|
-
successEnvelopeSchema,
|
|
214
|
-
problemSchema({ code: options.code, issue: options.issue }),
|
|
215
|
-
])
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Convert a Valibot issue into a standard response issue.
|
|
220
|
-
*
|
|
221
|
-
* @example
|
|
222
|
-
* ```ts
|
|
223
|
-
* const parsed = v.safeParse(v.object({ id: v.string() }), { id: 123 })
|
|
224
|
-
* if (!parsed.success) {
|
|
225
|
-
* const issue = issueFromValibotIssue(parsed.issues[0])
|
|
226
|
-
* }
|
|
227
|
-
* ```
|
|
228
|
-
*
|
|
229
|
-
* @param issue - Valibot issue to convert.
|
|
230
|
-
* @returns Standard response issue.
|
|
231
|
-
*/
|
|
232
|
-
export function issueFromValibotIssue(issue: v.BaseIssue<unknown>): ProblemIssue {
|
|
233
|
-
const path = pathFromValibotIssue(issue)
|
|
234
|
-
return {
|
|
235
|
-
code: issue.type,
|
|
236
|
-
message: issue.message,
|
|
237
|
-
...(path ? { path } : {}),
|
|
238
|
-
...(issue.expected == null ? {} : { expected: issue.expected }),
|
|
239
|
-
received: issue.received,
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* Convert Valibot issues into standard response issues.
|
|
245
|
-
*
|
|
246
|
-
* @example
|
|
247
|
-
* ```ts
|
|
248
|
-
* const parsed = v.safeParse(v.object({ id: v.string() }), { id: 123 })
|
|
249
|
-
* if (!parsed.success) {
|
|
250
|
-
* const issues = issuesFromValibotIssues(parsed.issues)
|
|
251
|
-
* }
|
|
252
|
-
* ```
|
|
253
|
-
*
|
|
254
|
-
* @param issues - Valibot issues to convert.
|
|
255
|
-
* @returns Standard response issues.
|
|
256
|
-
*/
|
|
257
|
-
export function issuesFromValibotIssues(issues: readonly v.BaseIssue<unknown>[]): ProblemIssue[] {
|
|
258
|
-
return issues.flatMap((issue) => collectValibotIssues(issue, []))
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Create a validation problem response from Valibot issues.
|
|
263
|
-
*
|
|
264
|
-
* @example
|
|
265
|
-
* ```ts
|
|
266
|
-
* const route = createValibotRouteBuilder()
|
|
267
|
-
* route({
|
|
268
|
-
* onRequestValidationError: (_component, issues) => validationProblemFromValibotIssues(issues),
|
|
269
|
-
* handler: () => ok({ saved: true }),
|
|
270
|
-
* })
|
|
271
|
-
* ```
|
|
272
|
-
*
|
|
273
|
-
* @param issues - Valibot issues to convert and include in the response.
|
|
274
|
-
* @param init - Optional problem response overrides and headers.
|
|
275
|
-
* @returns Routekit logical response with a validation [`ProblemResponse`]{@link ProblemResponse}.
|
|
276
|
-
* @typeParam Code - Machine-readable primary error code.
|
|
277
|
-
*/
|
|
278
|
-
export function validationProblemFromValibotIssues<const Code extends string = 'validation_failed'>(
|
|
279
|
-
issues: readonly v.BaseIssue<unknown>[],
|
|
280
|
-
init: ValidationProblemInit<Code> = {},
|
|
281
|
-
): RoutekitResponse<ProblemResponse<Code>> {
|
|
282
|
-
return validationProblem(issuesFromValibotIssues(issues), init)
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* A Valibot validation error handler that maps request validation errors to problem-compliant responses.
|
|
287
|
-
*
|
|
288
|
-
* This handler returns `422 Unprocessable Content` with error code `validation_failed:body` for request body
|
|
289
|
-
* validation failures, and `400 Bad Request` with `validation_failed:query` or `validation_failed:path` for
|
|
290
|
-
* query and path parameter validation failures respectively.
|
|
291
|
-
*
|
|
292
|
-
* @example
|
|
293
|
-
* ```ts
|
|
294
|
-
* const route = createValibotRouteBuilder({
|
|
295
|
-
* onRequestValidationError: problemValidationErrorHandler,
|
|
296
|
-
* })
|
|
297
|
-
* ```
|
|
298
|
-
*
|
|
299
|
-
* @param component - The request component that failed validation.
|
|
300
|
-
* @param issues - A non-empty tuple of Valibot validation issues.
|
|
301
|
-
* @returns A Routekit handler result containing the formatted problem response.
|
|
302
|
-
*/
|
|
303
|
-
export function problemValidationErrorHandler(
|
|
304
|
-
component: ValibotValidationError,
|
|
305
|
-
issues: [v.BaseIssue<unknown>, ...v.BaseIssue<unknown>[]],
|
|
306
|
-
): RoutekitResponse<
|
|
307
|
-
ProblemResponse<'validation_failed:body' | 'validation_failed:query' | 'validation_failed:path'>
|
|
308
|
-
> {
|
|
309
|
-
let code: 'validation_failed:body' | 'validation_failed:query' | 'validation_failed:path'
|
|
310
|
-
let status: number
|
|
311
|
-
|
|
312
|
-
switch (component) {
|
|
313
|
-
case ValibotValidationError.REQUEST_BODY:
|
|
314
|
-
code = 'validation_failed:body'
|
|
315
|
-
status = HttpStatus.UNPROCESSABLE_ENTITY
|
|
316
|
-
break
|
|
317
|
-
case ValibotValidationError.QUERY_PARAMETERS:
|
|
318
|
-
code = 'validation_failed:query'
|
|
319
|
-
status = HttpStatus.BAD_REQUEST
|
|
320
|
-
break
|
|
321
|
-
case ValibotValidationError.URL_PATH:
|
|
322
|
-
code = 'validation_failed:path'
|
|
323
|
-
status = HttpStatus.BAD_REQUEST
|
|
324
|
-
break
|
|
325
|
-
default:
|
|
326
|
-
code = 'validation_failed:body'
|
|
327
|
-
status = HttpStatus.UNPROCESSABLE_ENTITY
|
|
328
|
-
break
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
return validationProblemFromValibotIssues(issues, {
|
|
332
|
-
code,
|
|
333
|
-
status,
|
|
334
|
-
message: `Validation failed: ${
|
|
335
|
-
component === ValibotValidationError.REQUEST_BODY
|
|
336
|
-
? 'body'
|
|
337
|
-
: component === ValibotValidationError.QUERY_PARAMETERS
|
|
338
|
-
? 'query'
|
|
339
|
-
: 'path'
|
|
340
|
-
}`,
|
|
341
|
-
})
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* Create a Valibot route builder pre-configured with problem-spec-compliant request validation errors.
|
|
346
|
-
*
|
|
347
|
-
* This route builder automatically configures standard error schemas for `400 Bad Request` and
|
|
348
|
-
* `422 Unprocessable Content` statuses. It also uses [`problemValidationErrorHandler`]{@link problemValidationErrorHandler}
|
|
349
|
-
* as its default validation error handler to output structured JSON responses when validation fails.
|
|
350
|
-
*
|
|
351
|
-
* @example
|
|
352
|
-
* ```ts
|
|
353
|
-
* const route = createValibotRouteBuilder()
|
|
354
|
-
*
|
|
355
|
-
* router.post('/todos', route({
|
|
356
|
-
* schema: {
|
|
357
|
-
* request: {
|
|
358
|
-
* body: v.object({ title: v.string() })
|
|
359
|
-
* }
|
|
360
|
-
* },
|
|
361
|
-
* handler: ({ body }) => ok({ title: body.title })
|
|
362
|
-
* }))
|
|
363
|
-
* ```
|
|
364
|
-
*
|
|
365
|
-
* @param defaults - Additional defaults to apply to built routes.
|
|
366
|
-
* @returns A problem-spec-compliant Valibot route builder.
|
|
367
|
-
* @typeParam BuilderCtx - Custom context registered by middlewares (e.g. Services, Auth).
|
|
368
|
-
*/
|
|
369
|
-
export function createValibotRouteBuilder<
|
|
370
|
-
BuilderMiddleware extends MiddlewareInput<any>,
|
|
371
|
-
BuilderCtx extends object = ContextFromMiddlewareInput<BuilderMiddleware>,
|
|
372
|
-
>(
|
|
373
|
-
defaults: ValibotRouteHelperDefaults<BuilderCtx, BuilderMiddleware> & {
|
|
374
|
-
middleware: BuilderMiddleware
|
|
375
|
-
},
|
|
376
|
-
): ValibotRouteBuilder<BuilderCtx, BuilderMiddleware>
|
|
377
|
-
export function createValibotRouteBuilder<
|
|
378
|
-
BuilderCtx extends object = object,
|
|
379
|
-
BuilderMiddleware extends MiddlewareInput<BuilderCtx> = undefined,
|
|
380
|
-
>(
|
|
381
|
-
defaults?: ValibotRouteHelperDefaults<BuilderCtx, BuilderMiddleware>,
|
|
382
|
-
): ValibotRouteBuilder<BuilderCtx, BuilderMiddleware>
|
|
383
|
-
export function createValibotRouteBuilder<
|
|
384
|
-
BuilderCtx extends object = object,
|
|
385
|
-
BuilderMiddleware extends MiddlewareInput<BuilderCtx> = undefined,
|
|
386
|
-
>(
|
|
387
|
-
defaults: ValibotRouteHelperDefaults<BuilderCtx, BuilderMiddleware> = {},
|
|
388
|
-
): ValibotRouteBuilder<BuilderCtx, BuilderMiddleware> {
|
|
389
|
-
const defaultSchema = {
|
|
390
|
-
response: {
|
|
391
|
-
body: {
|
|
392
|
-
400: v.union([
|
|
393
|
-
problemSchema({ code: v.literal('validation_failed:query') }),
|
|
394
|
-
problemSchema({ code: v.literal('validation_failed:path') }),
|
|
395
|
-
]),
|
|
396
|
-
422: problemSchema({
|
|
397
|
-
code: v.literal('validation_failed:body'),
|
|
398
|
-
}),
|
|
399
|
-
},
|
|
400
|
-
},
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
const mergedDefaults: ValibotRouteHelperDefaults<BuilderCtx, BuilderMiddleware> = {
|
|
404
|
-
onRequestValidationError: problemValidationErrorHandler,
|
|
405
|
-
validationResponses: defaultSchema.response.body,
|
|
406
|
-
...defaults,
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
return baseCreateValibotRouteBuilder(mergedDefaults)
|
|
410
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { badRequest, ok, unprocessableContent } from './responses'
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { HttpStatus } from '@mpen/http'
|
|
2
|
-
import { response, type RoutekitResponse, type RoutekitResponseInit } from '../../core'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Create a `200 OK` logical response.
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```ts
|
|
9
|
-
* return ok({payload: 'ready'})
|
|
10
|
-
* ```
|
|
11
|
-
*
|
|
12
|
-
* @param responseBody - Logical response body.
|
|
13
|
-
* @param init - Response headers.
|
|
14
|
-
* @returns Routekit logical response.
|
|
15
|
-
*/
|
|
16
|
-
export function ok<const T>(
|
|
17
|
-
responseBody: T,
|
|
18
|
-
init: Omit<RoutekitResponseInit, 'status'> = {},
|
|
19
|
-
): RoutekitResponse<T, HttpStatus.OK> {
|
|
20
|
-
return response(responseBody, { ...init, status: HttpStatus.OK })
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Create a `400 Bad Request` logical response.
|
|
25
|
-
*
|
|
26
|
-
* @example
|
|
27
|
-
* ```ts
|
|
28
|
-
* return badRequest({message: 'Invalid request'})
|
|
29
|
-
* ```
|
|
30
|
-
*
|
|
31
|
-
* @param responseBody - Logical response body.
|
|
32
|
-
* @param init - Response headers.
|
|
33
|
-
* @returns Routekit logical response.
|
|
34
|
-
*/
|
|
35
|
-
export function badRequest<const T>(
|
|
36
|
-
responseBody: T,
|
|
37
|
-
init: Omit<RoutekitResponseInit, 'status'> = {},
|
|
38
|
-
): RoutekitResponse<T, HttpStatus.BAD_REQUEST> {
|
|
39
|
-
return response(responseBody, { ...init, status: HttpStatus.BAD_REQUEST })
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Create a `422 Unprocessable Content` logical response.
|
|
44
|
-
*
|
|
45
|
-
* @example
|
|
46
|
-
* ```ts
|
|
47
|
-
* return unprocessableContent({message: 'Email is already taken'})
|
|
48
|
-
* ```
|
|
49
|
-
*
|
|
50
|
-
* @param responseBody - Logical response body.
|
|
51
|
-
* @param init - Response headers.
|
|
52
|
-
* @returns Routekit logical response.
|
|
53
|
-
*/
|
|
54
|
-
export function unprocessableContent<const T>(
|
|
55
|
-
responseBody: T,
|
|
56
|
-
init: Omit<RoutekitResponseInit, 'status'> = {},
|
|
57
|
-
): RoutekitResponse<T, HttpStatus.UNPROCESSABLE_ENTITY> {
|
|
58
|
-
return response(responseBody, { ...init, status: HttpStatus.UNPROCESSABLE_ENTITY })
|
|
59
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env -S bun test
|
|
2
|
-
import { describe, expect, it } from 'bun:test'
|
|
3
|
-
import { HttpStatus } from '@mpen/http'
|
|
4
|
-
import { badRequest, unprocessableContent } from './responses'
|
|
5
|
-
|
|
6
|
-
describe('status response helpers', () => {
|
|
7
|
-
it('creates bad request responses', () => {
|
|
8
|
-
const result = badRequest({ message: 'Invalid request' }, { headers: { 'x-error': '1' } })
|
|
9
|
-
|
|
10
|
-
expect(result.status).toBe(HttpStatus.BAD_REQUEST)
|
|
11
|
-
expect(result.body).toEqual({ message: 'Invalid request' })
|
|
12
|
-
expect(result.headers.get('x-error')).toBe('1')
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
it('creates unprocessable content responses', () => {
|
|
16
|
-
const result = unprocessableContent({ message: 'Email is already taken' })
|
|
17
|
-
|
|
18
|
-
expect(result.status).toBe(HttpStatus.UNPROCESSABLE_ENTITY)
|
|
19
|
-
expect(result.body).toEqual({ message: 'Email is already taken' })
|
|
20
|
-
})
|
|
21
|
-
})
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { CommonHeaders } from '@mpen/http'
|
|
2
|
-
import type { RouterHeadersInit } from '../fetch-types'
|
|
3
|
-
|
|
4
|
-
type MaybePromise<T> = T | Promise<T>
|
|
5
|
-
type BodyChunk = string | Uint8Array | Buffer
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Framer used by streaming generators to encode structured chunks.
|
|
9
|
-
*
|
|
10
|
-
* @example
|
|
11
|
-
* ```ts
|
|
12
|
-
* yield stream(sseFramer())
|
|
13
|
-
* yield chunk({event: 'ready'})
|
|
14
|
-
* ```
|
|
15
|
-
*/
|
|
16
|
-
export interface StreamFramer<T = unknown> {
|
|
17
|
-
/**
|
|
18
|
-
* Content type for the stream.
|
|
19
|
-
*/
|
|
20
|
-
contentType: string
|
|
21
|
-
/**
|
|
22
|
-
* Headers to add when this stream starts.
|
|
23
|
-
*/
|
|
24
|
-
headers?: RouterHeadersInit
|
|
25
|
-
/**
|
|
26
|
-
* Return `true` when this framer can encode the provided chunk.
|
|
27
|
-
*
|
|
28
|
-
* @param value - Stream chunk to inspect.
|
|
29
|
-
* @returns Whether `value` can be framed.
|
|
30
|
-
*/
|
|
31
|
-
canFrame?: (value: unknown) => value is T
|
|
32
|
-
/**
|
|
33
|
-
* Encode one logical stream chunk.
|
|
34
|
-
*
|
|
35
|
-
* @param value - Stream chunk to encode.
|
|
36
|
-
* @returns Encoded stream bytes or text.
|
|
37
|
-
*/
|
|
38
|
-
frame(value: T): MaybePromise<BodyChunk>
|
|
39
|
-
/**
|
|
40
|
-
* Optional final stream bytes emitted when the generator completes.
|
|
41
|
-
*
|
|
42
|
-
* @returns Encoded closing bytes or text.
|
|
43
|
-
*/
|
|
44
|
-
close?(): MaybePromise<BodyChunk | undefined>
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Create an SSE stream framer.
|
|
49
|
-
*
|
|
50
|
-
* @example
|
|
51
|
-
* ```ts
|
|
52
|
-
* yield stream(sseFramer())
|
|
53
|
-
* yield chunk({event: 'ready'})
|
|
54
|
-
* ```
|
|
55
|
-
*
|
|
56
|
-
* @returns Stream framer for `text/event-stream`.
|
|
57
|
-
*/
|
|
58
|
-
export function sseFramer(): StreamFramer<unknown> {
|
|
59
|
-
return {
|
|
60
|
-
contentType: 'text/event-stream; charset=utf-8',
|
|
61
|
-
headers: {
|
|
62
|
-
[CommonHeaders.CACHE_CONTROL]: 'no-cache',
|
|
63
|
-
[CommonHeaders.CONNECTION]: 'keep-alive',
|
|
64
|
-
},
|
|
65
|
-
frame: (value) => `data: ${JSON.stringify(value) ?? 'null'}\n\n`,
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Create a JSON Lines stream framer.
|
|
71
|
-
*
|
|
72
|
-
* @example
|
|
73
|
-
* ```ts
|
|
74
|
-
* yield stream(jsonLinesFramer())
|
|
75
|
-
* yield chunk({event: 'ready'})
|
|
76
|
-
* ```
|
|
77
|
-
*
|
|
78
|
-
* @returns Stream framer for `application/jsonl`.
|
|
79
|
-
*/
|
|
80
|
-
export function jsonLinesFramer(): StreamFramer<unknown> {
|
|
81
|
-
return {
|
|
82
|
-
contentType: 'application/jsonl; charset=utf-8',
|
|
83
|
-
frame: (value) => `${JSON.stringify(value) ?? 'null'}\n`,
|
|
84
|
-
}
|
|
85
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
export { body, isResponseBodyInit, isRoutekitBody, isRoutekitResponse, response } from './core'
|
|
2
|
-
export type { RoutekitBody, RoutekitResponse, RoutekitResponseInit } from './core'
|
|
3
|
-
export {
|
|
4
|
-
chunk,
|
|
5
|
-
head,
|
|
6
|
-
headers,
|
|
7
|
-
isChunkDirective,
|
|
8
|
-
isHeadersDirective,
|
|
9
|
-
isHeadDirective,
|
|
10
|
-
isRoutekitDirective,
|
|
11
|
-
isStatusDirective,
|
|
12
|
-
isStreamDirective,
|
|
13
|
-
status,
|
|
14
|
-
stream,
|
|
15
|
-
} from './directives'
|
|
16
|
-
export type {
|
|
17
|
-
ChunkDirective,
|
|
18
|
-
HeadDirective,
|
|
19
|
-
HeadersDirective,
|
|
20
|
-
RoutekitYield,
|
|
21
|
-
StatusDirective,
|
|
22
|
-
StreamDirective,
|
|
23
|
-
} from './directives'
|
|
24
|
-
export { jsonLinesFramer, sseFramer } from './framers'
|
|
25
|
-
export type { StreamFramer } from './framers'
|
|
26
|
-
export { defaultResponseBodySerializers, jsonResponseBodySerializer } from './serializers'
|
|
27
|
-
export type { ResponseBodySerializer } from './serializers'
|
|
28
|
-
export { createAsyncStream, createStartStream } from './stream'
|