@mpen/routekit 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.d.mts +4 -0
- package/dist/client/react.d.mts +178 -0
- package/dist/client/react.mjs +142 -0
- package/dist/client.d.mts +433 -0
- package/dist/client.mjs +264 -0
- package/dist/content-BuDOmhH_.mjs +102 -0
- package/dist/core-CzUCxvGk.d.mts +140 -0
- package/dist/core-DbmQauwS.mjs +81 -0
- package/dist/handlers.d.mts +72 -0
- package/dist/handlers.mjs +153 -0
- package/dist/index.d.mts +3 -0
- package/dist/index.mjs +1152 -0
- package/dist/middleware.d.mts +388 -0
- package/dist/middleware.mjs +1222 -0
- package/dist/request-Dn0zc-xm.mjs +1025 -0
- package/dist/response/content.d.mts +79 -0
- package/dist/response/content.mjs +2 -0
- package/dist/response/json-rpc.d.mts +1 -0
- package/dist/response/json-rpc.mjs +1 -0
- package/dist/response/problem/valibot.d.mts +230 -0
- package/dist/response/problem/valibot.mjs +258 -0
- package/dist/response/problem.d.mts +415 -0
- package/dist/response/problem.mjs +183 -0
- package/dist/response/status.d.mts +45 -0
- package/dist/response/status.mjs +2 -0
- package/dist/responses-B379Ep9Y.d.mts +296 -0
- package/dist/responses-BpVrgeYi.mjs +101 -0
- package/dist/router-Cwb7ak0J.d.mts +1819 -0
- package/dist/routes.d.mts +282 -0
- package/dist/routes.mjs +311 -0
- package/dist/status-C-8mw-FB.mjs +59 -0
- package/dist/valibot-D7liFYyB.d.mts +290 -0
- package/dist/valibot-Du97X-TS.mjs +326 -0
- package/package.json +8 -2
- package/src/bin/gen-api-client.test.ts +0 -70
- package/src/bin/gen-api-client.ts +0 -986
- package/src/client/headers.ts +0 -31
- package/src/client/index.ts +0 -8
- package/src/client/promise.ts +0 -11
- package/src/client/react/index.test.tsx +0 -266
- package/src/client/react/index.ts +0 -431
- package/src/client/responses.test.ts +0 -151
- package/src/client/responses.ts +0 -278
- package/src/client/transport.ts +0 -74
- package/src/client/transports/body-codec.ts +0 -61
- package/src/client/transports/fetch.ts +0 -113
- package/src/client/tsconfig.json +0 -9
- package/src/client/types.ts +0 -15
- package/src/client/url.ts +0 -31
- package/src/index.ts +0 -63
- package/src/router/fetch-types.ts +0 -13
- package/src/router/handlers/index.ts +0 -2
- package/src/router/handlers/openapi/index.ts +0 -2
- package/src/router/handlers/openapi/openapi.ts +0 -293
- package/src/router/integration/zod-openapi.test.ts +0 -74
- package/src/router/lib/charset.test.ts +0 -22
- package/src/router/lib/charset.ts +0 -133
- package/src/router/lib/collections.ts +0 -3
- package/src/router/lib/format.test.ts +0 -67
- package/src/router/lib/format.ts +0 -35
- package/src/router/lib/host.ts +0 -4
- package/src/router/lib/json-schema.ts +0 -6
- package/src/router/lib/media-type.test.ts +0 -122
- package/src/router/lib/media-type.ts +0 -289
- package/src/router/lib/pathname.test.ts +0 -18
- package/src/router/lib/pathname.ts +0 -19
- package/src/router/lib/route-names.ts +0 -70
- package/src/router/lib/route-normalize.test.ts +0 -36
- package/src/router/lib/route-normalize.ts +0 -67
- package/src/router/lib/schema-merge.ts +0 -56
- package/src/router/middleware/accept-ctx.test.ts +0 -33
- package/src/router/middleware/accept-ctx.ts +0 -12
- package/src/router/middleware/body-limit.test.ts +0 -112
- package/src/router/middleware/body-limit.ts +0 -121
- package/src/router/middleware/content-type-context.ts +0 -0
- package/src/router/middleware/cors.test.ts +0 -269
- package/src/router/middleware/cors.ts +0 -490
- package/src/router/middleware/csrf.test.ts +0 -106
- package/src/router/middleware/csrf.ts +0 -192
- package/src/router/middleware/define.ts +0 -249
- package/src/router/middleware/index.ts +0 -34
- package/src/router/middleware/jsxhtml-response.ts +0 -0
- package/src/router/middleware/oas-swagger.ts +0 -0
- package/src/router/middleware/rate-limit.test.ts +0 -886
- package/src/router/middleware/rate-limit.ts +0 -920
- package/src/router/middleware/request-id-ctx.test.ts +0 -183
- package/src/router/middleware/request-id-ctx.ts +0 -135
- package/src/router/middleware/request-logger-format.test.ts +0 -16
- package/src/router/middleware/request-logger-format.ts +0 -269
- package/src/router/middleware/request-logger.test.ts +0 -267
- package/src/router/middleware/request-logger.ts +0 -131
- package/src/router/middleware/start-time-ctx.ts +0 -5
- package/src/router/request.ts +0 -611
- package/src/router/response/core.ts +0 -181
- package/src/router/response/directives.ts +0 -233
- package/src/router/response/formats/content/bodyless.ts +0 -54
- package/src/router/response/formats/content/content.ts +0 -79
- package/src/router/response/formats/content/index.ts +0 -2
- package/src/router/response/formats/json-rpc/index.ts +0 -2
- package/src/router/response/formats/problem/badRequest.ts +0 -90
- package/src/router/response/formats/problem/conflict.ts +0 -90
- package/src/router/response/formats/problem/created.ts +0 -40
- package/src/router/response/formats/problem/index.ts +0 -27
- package/src/router/response/formats/problem/notFound.ts +0 -90
- package/src/router/response/formats/problem/permissionDenied.ts +0 -90
- package/src/router/response/formats/problem/problem.test.ts +0 -888
- package/src/router/response/formats/problem/rateLimited.ts +0 -90
- package/src/router/response/formats/problem/responses.ts +0 -219
- package/src/router/response/formats/problem/root-errors.ts +0 -48
- package/src/router/response/formats/problem/sessionExpired.ts +0 -90
- package/src/router/response/formats/problem/types.ts +0 -170
- package/src/router/response/formats/problem/unauthenticated.ts +0 -90
- package/src/router/response/formats/problem/valibot.ts +0 -410
- package/src/router/response/formats/status/index.ts +0 -1
- package/src/router/response/formats/status/responses.ts +0 -59
- package/src/router/response/formats/status/status.test.ts +0 -21
- package/src/router/response/framers.ts +0 -85
- package/src/router/response/index.ts +0 -28
- package/src/router/response/openapi.test.ts +0 -96
- package/src/router/response/openapi.ts +0 -1
- package/src/router/response/serializers.ts +0 -66
- package/src/router/response/stream.ts +0 -35
- package/src/router/router.test.ts +0 -1571
- package/src/router/router.ts +0 -1965
- package/src/router/routes/index.ts +0 -46
- package/src/router/routes/valibot/index.ts +0 -18
- package/src/router/routes/valibot/valibot.ts +0 -1393
- package/src/router/routes/valibot.test.ts +0 -286
- package/src/router/routes/zod/index.ts +0 -18
- package/src/router/routes/zod/zod.ts +0 -1318
- package/src/router/routes/zod.test.ts +0 -280
- package/src/router/server-interface.ts +0 -31
- package/src/router/types.ts +0 -657
package/src/router/request.ts
DELETED
|
@@ -1,611 +0,0 @@
|
|
|
1
|
-
import { CommonHeaders, HttpStatus, StatusText } from '@mpen/http'
|
|
2
|
-
import type { ContentType, MaybePromise } from './types'
|
|
3
|
-
import { mediaRangeAccepts, mediaRangeQuality, parseContentType } from './lib/media-type'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Request body stream exposed by Routekit request bodies.
|
|
7
|
-
*
|
|
8
|
-
* @example
|
|
9
|
-
* ```ts
|
|
10
|
-
* const stream = request.body.stream()
|
|
11
|
-
* ```
|
|
12
|
-
*/
|
|
13
|
-
export type RequestBodyStream = ReadableStream<Uint8Array<ArrayBufferLike>> | null
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* FormData type returned by the active Fetch runtime.
|
|
17
|
-
*
|
|
18
|
-
* @example
|
|
19
|
-
* ```ts
|
|
20
|
-
* const form: RequestBodyFormData = await request.body.formData()
|
|
21
|
-
* ```
|
|
22
|
-
*/
|
|
23
|
-
export type RequestBodyFormData = Awaited<ReturnType<Response['formData']>>
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Error thrown when Routekit cannot read or parse a request body.
|
|
27
|
-
*
|
|
28
|
-
* @example
|
|
29
|
-
* ```ts
|
|
30
|
-
* throw new RequestBodyError('Unsupported body', HttpStatus.UNSUPPORTED_MEDIA_TYPE)
|
|
31
|
-
* ```
|
|
32
|
-
*/
|
|
33
|
-
export class RequestBodyError extends Error {
|
|
34
|
-
/**
|
|
35
|
-
* HTTP status code that should be returned for this request body failure.
|
|
36
|
-
*/
|
|
37
|
-
readonly status: HttpStatus
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Create a request body error.
|
|
41
|
-
*
|
|
42
|
-
* @param message - Error message.
|
|
43
|
-
* @param status - HTTP status code for the failure.
|
|
44
|
-
*/
|
|
45
|
-
constructor(message: string, status: HttpStatus) {
|
|
46
|
-
super(message)
|
|
47
|
-
this.name = 'RequestBodyError'
|
|
48
|
-
this.status = status
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Error thrown when no registered parser accepts the request `Content-Type`.
|
|
54
|
-
*
|
|
55
|
-
* @example
|
|
56
|
-
* ```ts
|
|
57
|
-
* throw new UnsupportedRequestBodyMediaTypeError('image/png')
|
|
58
|
-
* ```
|
|
59
|
-
*/
|
|
60
|
-
export class UnsupportedRequestBodyMediaTypeError extends RequestBodyError {
|
|
61
|
-
/**
|
|
62
|
-
* Incoming `Content-Type` value that was rejected.
|
|
63
|
-
*/
|
|
64
|
-
readonly contentType: string | null
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Create an unsupported media type error.
|
|
68
|
-
*
|
|
69
|
-
* @param contentType - Incoming content type, when present.
|
|
70
|
-
*/
|
|
71
|
-
constructor(contentType: string | null) {
|
|
72
|
-
super(
|
|
73
|
-
contentType
|
|
74
|
-
? `Unsupported request body media type: ${contentType}`
|
|
75
|
-
: 'Request body is missing Content-Type.',
|
|
76
|
-
HttpStatus.UNSUPPORTED_MEDIA_TYPE,
|
|
77
|
-
)
|
|
78
|
-
this.name = 'UnsupportedRequestBodyMediaTypeError'
|
|
79
|
-
this.contentType = contentType
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Error thrown when a request body exceeds a configured byte limit.
|
|
85
|
-
*
|
|
86
|
-
* @example
|
|
87
|
-
* ```ts
|
|
88
|
-
* throw new RequestBodyTooLargeError(1024, 2048)
|
|
89
|
-
* ```
|
|
90
|
-
*/
|
|
91
|
-
export class RequestBodyTooLargeError extends RequestBodyError {
|
|
92
|
-
/**
|
|
93
|
-
* Maximum allowed body size in bytes.
|
|
94
|
-
*/
|
|
95
|
-
readonly maxSize: number
|
|
96
|
-
/**
|
|
97
|
-
* Number of bytes received before the limit failed.
|
|
98
|
-
*/
|
|
99
|
-
readonly received: number
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Create a body-too-large error.
|
|
103
|
-
*
|
|
104
|
-
* @param maxSize - Maximum allowed body size in bytes.
|
|
105
|
-
* @param received - Number of bytes received.
|
|
106
|
-
*/
|
|
107
|
-
constructor(maxSize: number, received: number) {
|
|
108
|
-
super(`Payload exceeded ${maxSize} bytes`, HttpStatus.PAYLOAD_TOO_LARGE)
|
|
109
|
-
this.name = 'RequestBodyTooLargeError'
|
|
110
|
-
this.maxSize = maxSize
|
|
111
|
-
this.received = received
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Error thrown when `Content-Length` does not match the streamed body size.
|
|
117
|
-
*
|
|
118
|
-
* @example
|
|
119
|
-
* ```ts
|
|
120
|
-
* throw new RequestBodyLengthMismatchError(4, 3)
|
|
121
|
-
* ```
|
|
122
|
-
*/
|
|
123
|
-
export class RequestBodyLengthMismatchError extends RequestBodyError {
|
|
124
|
-
/**
|
|
125
|
-
* Expected byte count from `Content-Length`.
|
|
126
|
-
*/
|
|
127
|
-
readonly expected: number
|
|
128
|
-
/**
|
|
129
|
-
* Actual byte count read from the stream.
|
|
130
|
-
*/
|
|
131
|
-
readonly received: number
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Create a content-length mismatch error.
|
|
135
|
-
*
|
|
136
|
-
* @param expected - Expected byte count.
|
|
137
|
-
* @param received - Actual byte count.
|
|
138
|
-
*/
|
|
139
|
-
constructor(expected: number, received: number) {
|
|
140
|
-
super(
|
|
141
|
-
`Content-Length ${expected} bytes did not match ${received} bytes`,
|
|
142
|
-
HttpStatus.BAD_REQUEST,
|
|
143
|
-
)
|
|
144
|
-
this.name = 'RequestBodyLengthMismatchError'
|
|
145
|
-
this.expected = expected
|
|
146
|
-
this.received = received
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Body reader context passed to request body parsers.
|
|
152
|
-
*
|
|
153
|
-
* @example
|
|
154
|
-
* ```ts
|
|
155
|
-
* const parser = {
|
|
156
|
-
* mediaTypes: ['text/plain'],
|
|
157
|
-
* parse: (ctx: RequestBodyParseContext) => ctx.text(),
|
|
158
|
-
* }
|
|
159
|
-
* ```
|
|
160
|
-
*/
|
|
161
|
-
export interface RequestBodyParseContext {
|
|
162
|
-
/**
|
|
163
|
-
* Parsed request `Content-Type`, when present and valid.
|
|
164
|
-
*/
|
|
165
|
-
readonly contentType: ContentType | null
|
|
166
|
-
/**
|
|
167
|
-
* Request headers.
|
|
168
|
-
*/
|
|
169
|
-
readonly headers: Headers
|
|
170
|
-
/**
|
|
171
|
-
* Return the current body stream.
|
|
172
|
-
*
|
|
173
|
-
* @returns Body stream, or `null` when the request has no body.
|
|
174
|
-
*/
|
|
175
|
-
stream(): RequestBodyStream
|
|
176
|
-
/**
|
|
177
|
-
* Read the body as UTF-8 text.
|
|
178
|
-
*
|
|
179
|
-
* @returns Body text.
|
|
180
|
-
*/
|
|
181
|
-
text(): Promise<string>
|
|
182
|
-
/**
|
|
183
|
-
* Read the body as raw bytes.
|
|
184
|
-
*
|
|
185
|
-
* @returns Body bytes.
|
|
186
|
-
*/
|
|
187
|
-
arrayBuffer(): Promise<ArrayBuffer>
|
|
188
|
-
/**
|
|
189
|
-
* Read the body as form data.
|
|
190
|
-
*
|
|
191
|
-
* @returns Parsed form data.
|
|
192
|
-
*/
|
|
193
|
-
formData(): Promise<RequestBodyFormData>
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Parser used by [`RoutekitRequestBody.parse`]{@link RoutekitRequestBody#parse}.
|
|
198
|
-
*
|
|
199
|
-
* @example
|
|
200
|
-
* ```ts
|
|
201
|
-
* const jsonParser: RequestBodyParser = {
|
|
202
|
-
* mediaTypes: ['application/json'],
|
|
203
|
-
* parse: async ctx => JSON.parse(await ctx.text()),
|
|
204
|
-
* }
|
|
205
|
-
* ```
|
|
206
|
-
*/
|
|
207
|
-
export interface RequestBodyParser<T = unknown> {
|
|
208
|
-
/**
|
|
209
|
-
* Media ranges this parser can consume. Entries may include an Accept-style
|
|
210
|
-
* `q` parameter to prefer more specific parsers over broader media ranges.
|
|
211
|
-
*/
|
|
212
|
-
readonly mediaTypes: readonly string[]
|
|
213
|
-
/**
|
|
214
|
-
* Return `true` when this parser should handle the content type.
|
|
215
|
-
*
|
|
216
|
-
* @param contentType - Parsed request content type.
|
|
217
|
-
* @returns Whether this parser can parse the request body.
|
|
218
|
-
*/
|
|
219
|
-
canParse?(contentType: ContentType | null): boolean
|
|
220
|
-
/**
|
|
221
|
-
* Parse the request body.
|
|
222
|
-
*
|
|
223
|
-
* @param ctx - Lazy body reader context.
|
|
224
|
-
* @returns Parsed request body.
|
|
225
|
-
*/
|
|
226
|
-
parse(ctx: RequestBodyParseContext): MaybePromise<T>
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Lazy request body reader exposed to handlers and middleware.
|
|
231
|
-
*
|
|
232
|
-
* @example
|
|
233
|
-
* ```ts
|
|
234
|
-
* const body = await request.body.parse()
|
|
235
|
-
* ```
|
|
236
|
-
*/
|
|
237
|
-
export interface RoutekitRequestBody {
|
|
238
|
-
/**
|
|
239
|
-
* Parsed request `Content-Type`, when present and valid.
|
|
240
|
-
*/
|
|
241
|
-
readonly contentType: ContentType | null
|
|
242
|
-
/**
|
|
243
|
-
* Return the current body stream.
|
|
244
|
-
*
|
|
245
|
-
* @returns Body stream, or `null` when the request has no body.
|
|
246
|
-
*/
|
|
247
|
-
stream(): RequestBodyStream
|
|
248
|
-
/**
|
|
249
|
-
* Return a body wrapper that reads from another stream.
|
|
250
|
-
*
|
|
251
|
-
* @param stream - Replacement body stream.
|
|
252
|
-
* @returns Request body wrapper using the replacement stream.
|
|
253
|
-
*/
|
|
254
|
-
withStream(stream: RequestBodyStream): RoutekitRequestBody
|
|
255
|
-
/**
|
|
256
|
-
* Parse the body with the first matching registered body parser.
|
|
257
|
-
*
|
|
258
|
-
* @returns Parsed request body.
|
|
259
|
-
* @typeParam T - Expected parsed body type.
|
|
260
|
-
*/
|
|
261
|
-
parse<T = unknown>(): Promise<T>
|
|
262
|
-
/**
|
|
263
|
-
* Read the body as JSON.
|
|
264
|
-
*
|
|
265
|
-
* @returns Parsed JSON body.
|
|
266
|
-
* @typeParam T - Expected JSON body type.
|
|
267
|
-
*/
|
|
268
|
-
json<T = unknown>(): Promise<T>
|
|
269
|
-
/**
|
|
270
|
-
* Read the body as UTF-8 text.
|
|
271
|
-
*
|
|
272
|
-
* @returns Body text.
|
|
273
|
-
*/
|
|
274
|
-
text(): Promise<string>
|
|
275
|
-
/**
|
|
276
|
-
* Read the body as raw bytes.
|
|
277
|
-
*
|
|
278
|
-
* @returns Body bytes.
|
|
279
|
-
*/
|
|
280
|
-
arrayBuffer(): Promise<ArrayBuffer>
|
|
281
|
-
/**
|
|
282
|
-
* Read the body as form data.
|
|
283
|
-
*
|
|
284
|
-
* @returns Parsed form data.
|
|
285
|
-
*/
|
|
286
|
-
formData(): Promise<RequestBodyFormData>
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Routekit request wrapper exposed to handlers and middleware.
|
|
291
|
-
*
|
|
292
|
-
* @example
|
|
293
|
-
* ```ts
|
|
294
|
-
* const method = request.method
|
|
295
|
-
* const body = await request.body.parse()
|
|
296
|
-
* ```
|
|
297
|
-
*/
|
|
298
|
-
export interface RoutekitRequest {
|
|
299
|
-
/**
|
|
300
|
-
* Native Fetch request for low-level escape hatches.
|
|
301
|
-
*/
|
|
302
|
-
readonly raw: Request
|
|
303
|
-
/**
|
|
304
|
-
* Parsed request URL.
|
|
305
|
-
*/
|
|
306
|
-
readonly url: URL
|
|
307
|
-
/**
|
|
308
|
-
* Request method.
|
|
309
|
-
*/
|
|
310
|
-
readonly method: string
|
|
311
|
-
/**
|
|
312
|
-
* Request headers.
|
|
313
|
-
*/
|
|
314
|
-
readonly headers: Headers
|
|
315
|
-
/**
|
|
316
|
-
* Request abort signal.
|
|
317
|
-
*/
|
|
318
|
-
readonly signal: AbortSignal
|
|
319
|
-
/**
|
|
320
|
-
* Lazy request body reader.
|
|
321
|
-
*/
|
|
322
|
-
readonly body: RoutekitRequestBody
|
|
323
|
-
/**
|
|
324
|
-
* Return a request wrapper with another body reader.
|
|
325
|
-
*
|
|
326
|
-
* @param body - Replacement body reader.
|
|
327
|
-
* @returns Request wrapper using the replacement body reader.
|
|
328
|
-
*/
|
|
329
|
-
withBody(body: RoutekitRequestBody): RoutekitRequest
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
type RequestBodyOptions = {
|
|
333
|
-
raw: Request
|
|
334
|
-
headers: Headers
|
|
335
|
-
stream: RequestBodyStream
|
|
336
|
-
parsers: readonly RequestBodyParser[]
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
class DefaultRoutekitRequestBody implements RoutekitRequestBody {
|
|
340
|
-
readonly contentType: ContentType | null
|
|
341
|
-
|
|
342
|
-
readonly #raw: Request
|
|
343
|
-
readonly #headers: Headers
|
|
344
|
-
readonly #stream: RequestBodyStream
|
|
345
|
-
readonly #parsers: readonly RequestBodyParser[]
|
|
346
|
-
#arrayBufferPromise: Promise<ArrayBuffer> | undefined
|
|
347
|
-
#textPromise: Promise<string> | undefined
|
|
348
|
-
#formDataPromise: Promise<RequestBodyFormData> | undefined
|
|
349
|
-
#parsePromise: Promise<unknown> | undefined
|
|
350
|
-
|
|
351
|
-
constructor(options: RequestBodyOptions) {
|
|
352
|
-
this.#raw = options.raw
|
|
353
|
-
this.#headers = options.headers
|
|
354
|
-
this.#stream = options.stream
|
|
355
|
-
this.#parsers = options.parsers
|
|
356
|
-
this.contentType = parseContentType(options.headers.get(CommonHeaders.CONTENT_TYPE))
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
get headers(): Headers {
|
|
360
|
-
return this.#headers
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
stream(): RequestBodyStream {
|
|
364
|
-
return this.#stream
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
withStream(stream: RequestBodyStream): RoutekitRequestBody {
|
|
368
|
-
return new DefaultRoutekitRequestBody({
|
|
369
|
-
raw: this.#raw,
|
|
370
|
-
headers: this.#headers,
|
|
371
|
-
stream,
|
|
372
|
-
parsers: this.#parsers,
|
|
373
|
-
})
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
async parse<T = unknown>(): Promise<T> {
|
|
377
|
-
if (!this.#stream) return undefined as T
|
|
378
|
-
this.#parsePromise ??= this.#parse()
|
|
379
|
-
return (await this.#parsePromise) as T
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
async json<T = unknown>(): Promise<T> {
|
|
383
|
-
return JSON.parse(await this.text()) as T
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
text(): Promise<string> {
|
|
387
|
-
this.#textPromise ??= this.arrayBuffer().then((buffer) => new TextDecoder().decode(buffer))
|
|
388
|
-
return this.#textPromise
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
arrayBuffer(): Promise<ArrayBuffer> {
|
|
392
|
-
this.#arrayBufferPromise ??= new Response(this.#stream).arrayBuffer()
|
|
393
|
-
return this.#arrayBufferPromise
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
formData(): Promise<RequestBodyFormData> {
|
|
397
|
-
return (this.#formDataPromise ??= new Response(this.#stream, {
|
|
398
|
-
headers: this.#headers,
|
|
399
|
-
}).formData())
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
async #parse(): Promise<unknown> {
|
|
403
|
-
const parser = this.#selectParser()
|
|
404
|
-
if (!parser) {
|
|
405
|
-
throw new UnsupportedRequestBodyMediaTypeError(
|
|
406
|
-
this.#headers.get(CommonHeaders.CONTENT_TYPE),
|
|
407
|
-
)
|
|
408
|
-
}
|
|
409
|
-
try {
|
|
410
|
-
return await parser.parse(this)
|
|
411
|
-
} catch (error) {
|
|
412
|
-
if (error instanceof RequestBodyError) throw error
|
|
413
|
-
const message = error instanceof Error ? error.message : String(error)
|
|
414
|
-
throw new RequestBodyError(message, HttpStatus.BAD_REQUEST)
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
#parserQuality(parser: RequestBodyParser): number | null {
|
|
419
|
-
let bestQuality = parser.canParse?.(this.contentType) ? 1 : 0
|
|
420
|
-
if (this.contentType) {
|
|
421
|
-
for (const mediaType of parser.mediaTypes) {
|
|
422
|
-
if (mediaRangeAccepts(mediaType, this.contentType)) {
|
|
423
|
-
bestQuality = Math.max(bestQuality, mediaRangeQuality(mediaType))
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
return bestQuality > 0 ? bestQuality : null
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
#selectParser(): RequestBodyParser | null {
|
|
431
|
-
let best: { parser: RequestBodyParser; quality: number } | null = null
|
|
432
|
-
for (const parser of this.#parsers) {
|
|
433
|
-
const quality = this.#parserQuality(parser)
|
|
434
|
-
if (quality == null) continue
|
|
435
|
-
if (!best || quality > best.quality) {
|
|
436
|
-
best = { parser, quality }
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
return best?.parser ?? null
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
class DefaultRoutekitRequest implements RoutekitRequest {
|
|
444
|
-
readonly raw: Request
|
|
445
|
-
readonly url: URL
|
|
446
|
-
readonly method: string
|
|
447
|
-
readonly headers: Headers
|
|
448
|
-
readonly signal: AbortSignal
|
|
449
|
-
readonly body: RoutekitRequestBody
|
|
450
|
-
|
|
451
|
-
constructor(raw: Request, url: URL, body: RoutekitRequestBody) {
|
|
452
|
-
this.raw = raw
|
|
453
|
-
this.url = url
|
|
454
|
-
this.method = raw.method
|
|
455
|
-
this.headers = raw.headers
|
|
456
|
-
this.signal = raw.signal
|
|
457
|
-
this.body = body
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
withBody(body: RoutekitRequestBody): RoutekitRequest {
|
|
461
|
-
return new DefaultRoutekitRequest(this.raw, this.url, body)
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
function readParams(searchParams: URLSearchParams): Record<string, string | string[]> {
|
|
466
|
-
const query: Record<string, string | string[]> = {}
|
|
467
|
-
for (const [key, value] of searchParams.entries()) {
|
|
468
|
-
const existing = query[key]
|
|
469
|
-
if (existing === undefined) {
|
|
470
|
-
query[key] = value
|
|
471
|
-
continue
|
|
472
|
-
}
|
|
473
|
-
if (Array.isArray(existing)) {
|
|
474
|
-
existing.push(value)
|
|
475
|
-
} else {
|
|
476
|
-
query[key] = [existing, value]
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
return query
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
/**
|
|
483
|
-
* Create the default JSON request body parser.
|
|
484
|
-
*
|
|
485
|
-
* @example
|
|
486
|
-
* ```ts
|
|
487
|
-
* const router = new Router().setRequestBodyParsers([jsonRequestBodyParser()])
|
|
488
|
-
* ```
|
|
489
|
-
*
|
|
490
|
-
* @returns Parser for `application/json` and `application/*+json` request bodies.
|
|
491
|
-
*/
|
|
492
|
-
export function jsonRequestBodyParser(): RequestBodyParser<unknown> {
|
|
493
|
-
return {
|
|
494
|
-
mediaTypes: ['application/json', 'application/*+json;q=0.5'],
|
|
495
|
-
parse: async (ctx) => JSON.parse(await ctx.text()),
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
/**
|
|
500
|
-
* Create the default text request body parser.
|
|
501
|
-
*
|
|
502
|
-
* @example
|
|
503
|
-
* ```ts
|
|
504
|
-
* const router = new Router().setRequestBodyParsers([textRequestBodyParser()])
|
|
505
|
-
* ```
|
|
506
|
-
*
|
|
507
|
-
* @returns Parser for `text/*` request bodies.
|
|
508
|
-
*/
|
|
509
|
-
export function textRequestBodyParser(): RequestBodyParser<string> {
|
|
510
|
-
return {
|
|
511
|
-
mediaTypes: ['text/*'],
|
|
512
|
-
parse: (ctx) => ctx.text(),
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
/**
|
|
517
|
-
* Create the default URL-encoded form request body parser.
|
|
518
|
-
*
|
|
519
|
-
* @example
|
|
520
|
-
* ```ts
|
|
521
|
-
* const router = new Router().setRequestBodyParsers([urlEncodedRequestBodyParser()])
|
|
522
|
-
* ```
|
|
523
|
-
*
|
|
524
|
-
* @returns Parser for `application/x-www-form-urlencoded` request bodies.
|
|
525
|
-
*/
|
|
526
|
-
export function urlEncodedRequestBodyParser(): RequestBodyParser<
|
|
527
|
-
Record<string, string | string[]>
|
|
528
|
-
> {
|
|
529
|
-
return {
|
|
530
|
-
mediaTypes: ['application/x-www-form-urlencoded'],
|
|
531
|
-
parse: async (ctx) => readParams(new URLSearchParams(await ctx.text())),
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
/**
|
|
536
|
-
* Create the default multipart form request body parser.
|
|
537
|
-
*
|
|
538
|
-
* @example
|
|
539
|
-
* ```ts
|
|
540
|
-
* const router = new Router().setRequestBodyParsers([formDataRequestBodyParser()])
|
|
541
|
-
* ```
|
|
542
|
-
*
|
|
543
|
-
* @returns Parser for `multipart/form-data` request bodies.
|
|
544
|
-
*/
|
|
545
|
-
export function formDataRequestBodyParser(): RequestBodyParser<RequestBodyFormData> {
|
|
546
|
-
return {
|
|
547
|
-
mediaTypes: ['multipart/form-data'],
|
|
548
|
-
parse: (ctx) => ctx.formData(),
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
/**
|
|
553
|
-
* Create the default Routekit request body parser list.
|
|
554
|
-
*
|
|
555
|
-
* @example
|
|
556
|
-
* ```ts
|
|
557
|
-
* const parsers = defaultRequestBodyParsers()
|
|
558
|
-
* ```
|
|
559
|
-
*
|
|
560
|
-
* @returns Default parser list.
|
|
561
|
-
*/
|
|
562
|
-
export function defaultRequestBodyParsers(): RequestBodyParser[] {
|
|
563
|
-
return [
|
|
564
|
-
jsonRequestBodyParser(),
|
|
565
|
-
textRequestBodyParser(),
|
|
566
|
-
urlEncodedRequestBodyParser(),
|
|
567
|
-
formDataRequestBodyParser(),
|
|
568
|
-
]
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
/**
|
|
572
|
-
* Create a Routekit request wrapper for a native Fetch request.
|
|
573
|
-
*
|
|
574
|
-
* @example
|
|
575
|
-
* ```ts
|
|
576
|
-
* const request = createRoutekitRequest(new Request('https://example.com'), new URL('https://example.com'), defaultRequestBodyParsers())
|
|
577
|
-
* ```
|
|
578
|
-
*
|
|
579
|
-
* @param raw - Native Fetch request.
|
|
580
|
-
* @param url - Parsed request URL.
|
|
581
|
-
* @param parsers - Request body parsers.
|
|
582
|
-
* @returns Routekit request wrapper.
|
|
583
|
-
*/
|
|
584
|
-
export function createRoutekitRequest(
|
|
585
|
-
raw: Request,
|
|
586
|
-
url: URL,
|
|
587
|
-
parsers: readonly RequestBodyParser[],
|
|
588
|
-
): RoutekitRequest {
|
|
589
|
-
const body = new DefaultRoutekitRequestBody({
|
|
590
|
-
raw,
|
|
591
|
-
headers: raw.headers,
|
|
592
|
-
stream: raw.body,
|
|
593
|
-
parsers,
|
|
594
|
-
})
|
|
595
|
-
return new DefaultRoutekitRequest(raw, url, body)
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
/**
|
|
599
|
-
* Create a plain response for a request body error.
|
|
600
|
-
*
|
|
601
|
-
* @example
|
|
602
|
-
* ```ts
|
|
603
|
-
* const response = responseFromRequestBodyError(error)
|
|
604
|
-
* ```
|
|
605
|
-
*
|
|
606
|
-
* @param error - Request body error to convert.
|
|
607
|
-
* @returns HTTP response for the body error.
|
|
608
|
-
*/
|
|
609
|
-
export function responseFromRequestBodyError(error: RequestBodyError): Response {
|
|
610
|
-
return new Response(StatusText[error.status] ?? error.message, { status: error.status })
|
|
611
|
-
}
|