@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/router.ts
DELETED
|
@@ -1,1965 +0,0 @@
|
|
|
1
|
-
import { CommonHeaders, HttpMethod, HttpStatus, StatusText } from '@mpen/http'
|
|
2
|
-
import { ConsoleLogger, type Logger } from '@mpen/logger'
|
|
3
|
-
import type { SimpleServerInterface } from './server-interface'
|
|
4
|
-
import { joinPrefixPathname, stripPrefixPathname } from './lib/pathname'
|
|
5
|
-
import { normalizeRoute } from './lib/route-normalize'
|
|
6
|
-
import { mergeRouteSchemas } from './lib/schema-merge'
|
|
7
|
-
import { isDeclaredMiddleware, type DeclaredMiddleware } from './middleware/define'
|
|
8
|
-
import {
|
|
9
|
-
mediaRangeAccepts,
|
|
10
|
-
mediaRangeQuality,
|
|
11
|
-
mediaRangeToContentType,
|
|
12
|
-
mediaTypeMatches,
|
|
13
|
-
parseAcceptHeader,
|
|
14
|
-
parseContentType,
|
|
15
|
-
} from './lib/media-type'
|
|
16
|
-
import {
|
|
17
|
-
createRoutekitRequest,
|
|
18
|
-
defaultRequestBodyParsers,
|
|
19
|
-
RequestBodyError,
|
|
20
|
-
responseFromRequestBodyError,
|
|
21
|
-
type RequestBodyParser,
|
|
22
|
-
type RoutekitRequest,
|
|
23
|
-
} from './request'
|
|
24
|
-
import type {
|
|
25
|
-
AddedContextFromMiddlewareInput,
|
|
26
|
-
AnyContext,
|
|
27
|
-
ContextMiddleware,
|
|
28
|
-
Handler,
|
|
29
|
-
HandlerBody,
|
|
30
|
-
HandlerFinalResult,
|
|
31
|
-
HandlerResult,
|
|
32
|
-
HandlerContext,
|
|
33
|
-
HandlerYield,
|
|
34
|
-
MiddlewareEntry,
|
|
35
|
-
MiddlewareInput,
|
|
36
|
-
NormalizedRoute,
|
|
37
|
-
RequestContext,
|
|
38
|
-
RequestBodyParserInput,
|
|
39
|
-
RequestMiddleware,
|
|
40
|
-
RequestMiddlewareEntry,
|
|
41
|
-
RequestMiddlewareInput,
|
|
42
|
-
Route,
|
|
43
|
-
RouteOptions,
|
|
44
|
-
RouterExtension,
|
|
45
|
-
RouterMountOptions,
|
|
46
|
-
ResponseBodySerializerInput,
|
|
47
|
-
} from './types'
|
|
48
|
-
import {
|
|
49
|
-
defaultResponseBodySerializers,
|
|
50
|
-
isChunkDirective,
|
|
51
|
-
isHeadersDirective,
|
|
52
|
-
isHeadDirective,
|
|
53
|
-
isResponseBodyInit,
|
|
54
|
-
isRoutekitBody,
|
|
55
|
-
isRoutekitResponse,
|
|
56
|
-
isStatusDirective,
|
|
57
|
-
isStreamDirective,
|
|
58
|
-
response as routekitResponse,
|
|
59
|
-
type ResponseBodySerializer,
|
|
60
|
-
type RoutekitResponse,
|
|
61
|
-
type StreamFramer,
|
|
62
|
-
} from './response'
|
|
63
|
-
import { text } from './response/formats/content'
|
|
64
|
-
import type { RouterBodyInit } from './fetch-types'
|
|
65
|
-
|
|
66
|
-
type ExecutableMiddleware = ContextMiddleware<any, any> | DeclaredMiddleware<any, any>
|
|
67
|
-
type ExecutableRequestMiddleware = RequestMiddleware<any, any>
|
|
68
|
-
|
|
69
|
-
type RouteEntry =
|
|
70
|
-
| { kind: 'route'; route: NormalizedRoute<any>; middleware: ExecutableMiddleware[] }
|
|
71
|
-
| { kind: 'router'; prefix?: string; router: Router<any> }
|
|
72
|
-
|
|
73
|
-
type MatchResult = {
|
|
74
|
-
kind: 'match'
|
|
75
|
-
route: NormalizedRoute<any>
|
|
76
|
-
routePath: string
|
|
77
|
-
match: URLPatternResult | null
|
|
78
|
-
middleware: ExecutableMiddleware[]
|
|
79
|
-
router: Router<any>
|
|
80
|
-
config: RuntimeConfig
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
type HandlerRegistration = {
|
|
84
|
-
handler: Handler<any, any>
|
|
85
|
-
router: Router<any>
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
type RuntimeConfig = {
|
|
89
|
-
requestBodyParsers: RequestBodyParser[]
|
|
90
|
-
responseBodySerializers: ResponseBodySerializer[]
|
|
91
|
-
logger: Logger
|
|
92
|
-
requestMiddleware: ExecutableRequestMiddleware[]
|
|
93
|
-
onNotFoundHandler?: HandlerRegistration
|
|
94
|
-
onMethodNotAllowedHandler?: HandlerRegistration
|
|
95
|
-
onUnsupportedMediaTypeHandler?: HandlerRegistration
|
|
96
|
-
onInternalErrorHandler?: HandlerRegistration
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
type ResponseBodySerializerCandidate = {
|
|
100
|
-
serializer: ResponseBodySerializer
|
|
101
|
-
mediaType: string
|
|
102
|
-
acceptQuality: number
|
|
103
|
-
mediaTypeQuality: number
|
|
104
|
-
acceptIndex: number
|
|
105
|
-
serializerIndex: number
|
|
106
|
-
mediaTypeIndex: number
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
type MatchAttempt = {
|
|
110
|
-
match: MatchResult | null
|
|
111
|
-
unsupportedMediaType: MatchResult | null
|
|
112
|
-
methodNotAllowedConfig: RuntimeConfig | null
|
|
113
|
-
notFoundConfig: RuntimeConfig
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
type MethodRouteInput<Ctx extends object> = Handler<any, Ctx> | RouteOptions<any, any, any>
|
|
117
|
-
|
|
118
|
-
function normalizeMiddlewareList<Ctx extends object>(
|
|
119
|
-
middleware: MiddlewareInput<Ctx>,
|
|
120
|
-
): ExecutableMiddleware[] {
|
|
121
|
-
if (!middleware) return []
|
|
122
|
-
if (Array.isArray(middleware)) {
|
|
123
|
-
return middleware.filter(Boolean) as ExecutableMiddleware[]
|
|
124
|
-
}
|
|
125
|
-
return [middleware as ExecutableMiddleware]
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function normalizeRequestMiddlewareList<Ctx extends object>(
|
|
129
|
-
middleware: RequestMiddlewareInput<Ctx>,
|
|
130
|
-
): ExecutableRequestMiddleware[] {
|
|
131
|
-
if (!middleware) return []
|
|
132
|
-
if (Array.isArray(middleware)) {
|
|
133
|
-
return middleware.filter(Boolean) as ExecutableRequestMiddleware[]
|
|
134
|
-
}
|
|
135
|
-
return [middleware as ExecutableRequestMiddleware]
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function normalizeRequestBodyParsers(input: RequestBodyParserInput): RequestBodyParser[] {
|
|
139
|
-
if (!input) return []
|
|
140
|
-
if (Array.isArray(input)) return input.filter(Boolean) as RequestBodyParser[]
|
|
141
|
-
return [input as RequestBodyParser]
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function normalizeResponseBodySerializers(
|
|
145
|
-
input: ResponseBodySerializerInput,
|
|
146
|
-
): ResponseBodySerializer[] {
|
|
147
|
-
if (!input) return []
|
|
148
|
-
if (Array.isArray(input)) return input.filter(Boolean) as ResponseBodySerializer[]
|
|
149
|
-
return [input as ResponseBodySerializer]
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function isHandler<Ctx extends object>(input: MethodRouteInput<Ctx>): input is Handler<any, Ctx> {
|
|
153
|
-
return typeof input === 'function'
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Router that matches requests against registered routes and executes middleware.
|
|
158
|
-
*
|
|
159
|
-
* @example
|
|
160
|
-
* ```ts
|
|
161
|
-
* const router = new Router()
|
|
162
|
-
* router.add({method: HttpMethod.GET, path: '/', handler: async ({request}) => new Response(request.url)})
|
|
163
|
-
* ```
|
|
164
|
-
*/
|
|
165
|
-
export class Router<Ctx extends object = AnyContext> implements SimpleServerInterface {
|
|
166
|
-
private _entries: RouteEntry[] = []
|
|
167
|
-
private _middleware: ExecutableMiddleware[] = []
|
|
168
|
-
private _requestMiddleware: ExecutableRequestMiddleware[] = []
|
|
169
|
-
private _responseBodySerializers: ResponseBodySerializer[] = []
|
|
170
|
-
private _responseBodySerializersMode: 'inherit' | 'replace' = 'inherit'
|
|
171
|
-
private _requestBodyParsers: RequestBodyParser[] = []
|
|
172
|
-
private _requestBodyParsersMode: 'inherit' | 'replace' = 'inherit'
|
|
173
|
-
private _logger?: Logger
|
|
174
|
-
private readonly _defaultLogger = new ConsoleLogger()
|
|
175
|
-
private _onNotFoundHandler?: Handler<any, Ctx>
|
|
176
|
-
private _onMethodNotAllowedHandler?: Handler<any, Ctx>
|
|
177
|
-
private _onUnsupportedMediaTypeHandler?: Handler<any, Ctx>
|
|
178
|
-
private _onInternalErrorHandler?: Handler<any, Ctx>
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Create a new router instance.
|
|
182
|
-
*/
|
|
183
|
-
constructor() {}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Install an extension that configures this router instance.
|
|
187
|
-
*
|
|
188
|
-
* @example
|
|
189
|
-
* ```ts
|
|
190
|
-
* router.install(problemRootErrors())
|
|
191
|
-
* ```
|
|
192
|
-
*
|
|
193
|
-
* @param extension - Extension function that mutates or configures this router.
|
|
194
|
-
* @returns The router instance for chaining.
|
|
195
|
-
*/
|
|
196
|
-
install(extension: RouterExtension<Ctx>): this {
|
|
197
|
-
extension(this)
|
|
198
|
-
return this
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Register a handler for requests that do not match any route.
|
|
203
|
-
*
|
|
204
|
-
* @example
|
|
205
|
-
* ```ts
|
|
206
|
-
* router.onNotFound(() => new Response('missing', {status: 404}))
|
|
207
|
-
* ```
|
|
208
|
-
*
|
|
209
|
-
* @param handler - Handler invoked when no route matches.
|
|
210
|
-
* @returns The router instance for chaining.
|
|
211
|
-
*/
|
|
212
|
-
onNotFound(handler: Handler<any, Ctx>): this {
|
|
213
|
-
this._onNotFoundHandler = handler
|
|
214
|
-
return this
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Register a handler for requests that match a path but use an unsupported method.
|
|
219
|
-
*
|
|
220
|
-
* @example
|
|
221
|
-
* ```ts
|
|
222
|
-
* router.onMethodNotAllowed(() => new Response('nope', {status: 405}))
|
|
223
|
-
* ```
|
|
224
|
-
*
|
|
225
|
-
* @param handler - Handler invoked when a route exists but the method does not match.
|
|
226
|
-
* @returns The router instance for chaining.
|
|
227
|
-
*/
|
|
228
|
-
onMethodNotAllowed(handler: Handler<any, Ctx>): this {
|
|
229
|
-
this._onMethodNotAllowedHandler = handler
|
|
230
|
-
return this
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Register a handler for requests with unsupported request body media types.
|
|
235
|
-
*
|
|
236
|
-
* @example
|
|
237
|
-
* ```ts
|
|
238
|
-
* router.onUnsupportedMediaType(() => new Response('unsupported', {status: 415}))
|
|
239
|
-
* ```
|
|
240
|
-
*
|
|
241
|
-
* @param handler - Handler invoked when the incoming `Content-Type` is not accepted.
|
|
242
|
-
* @returns The router instance for chaining.
|
|
243
|
-
*/
|
|
244
|
-
onUnsupportedMediaType(handler: Handler<any, Ctx>): this {
|
|
245
|
-
this._onUnsupportedMediaTypeHandler = handler
|
|
246
|
-
return this
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Register a handler for unhandled errors thrown by route handlers.
|
|
251
|
-
*
|
|
252
|
-
* @example
|
|
253
|
-
* ```ts
|
|
254
|
-
* router.onInternalError(() => new Response('oops', {status: 500}))
|
|
255
|
-
* ```
|
|
256
|
-
*
|
|
257
|
-
* @param handler - Handler invoked when a route handler throws.
|
|
258
|
-
* @returns The router instance for chaining.
|
|
259
|
-
*/
|
|
260
|
-
onInternalError(handler: Handler<any, Ctx>): this {
|
|
261
|
-
this._onInternalErrorHandler = handler
|
|
262
|
-
return this
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Add a request body parser to the inherited parser list.
|
|
267
|
-
*
|
|
268
|
-
* @example
|
|
269
|
-
* ```ts
|
|
270
|
-
* router.addRequestBodyParser(jsonRequestBodyParser())
|
|
271
|
-
* ```
|
|
272
|
-
*
|
|
273
|
-
* @param parser - Request body parser to append.
|
|
274
|
-
* @returns The router instance for chaining.
|
|
275
|
-
*/
|
|
276
|
-
addRequestBodyParser(parser: RequestBodyParser): this {
|
|
277
|
-
return this.addRequestBodyParsers([parser])
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Add request body parsers to the inherited parser list.
|
|
282
|
-
*
|
|
283
|
-
* @example
|
|
284
|
-
* ```ts
|
|
285
|
-
* router.addRequestBodyParsers([jsonRequestBodyParser(), textRequestBodyParser()])
|
|
286
|
-
* ```
|
|
287
|
-
*
|
|
288
|
-
* @param parsers - Request body parsers to append.
|
|
289
|
-
* @returns The router instance for chaining.
|
|
290
|
-
*/
|
|
291
|
-
addRequestBodyParsers(parsers: RequestBodyParserInput): this {
|
|
292
|
-
this._requestBodyParsers.push(...normalizeRequestBodyParsers(parsers))
|
|
293
|
-
return this
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Replace the inherited request body parser list for this router subtree.
|
|
298
|
-
*
|
|
299
|
-
* @example
|
|
300
|
-
* ```ts
|
|
301
|
-
* router.setRequestBodyParsers([jsonRequestBodyParser()])
|
|
302
|
-
* ```
|
|
303
|
-
*
|
|
304
|
-
* @param parsers - Exact request body parsers to use for this router subtree.
|
|
305
|
-
* @returns The router instance for chaining.
|
|
306
|
-
*/
|
|
307
|
-
setRequestBodyParsers(parsers: RequestBodyParserInput): this {
|
|
308
|
-
this._requestBodyParsers = normalizeRequestBodyParsers(parsers)
|
|
309
|
-
this._requestBodyParsersMode = 'replace'
|
|
310
|
-
return this
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* Add a response body serializer to the inherited serializer list.
|
|
315
|
-
*
|
|
316
|
-
* @example
|
|
317
|
-
* ```ts
|
|
318
|
-
* router.addResponseBodySerializer(jsonResponseBodySerializer())
|
|
319
|
-
* ```
|
|
320
|
-
*
|
|
321
|
-
* @param serializer - Response body serializer to append.
|
|
322
|
-
* @returns The router instance for chaining.
|
|
323
|
-
*/
|
|
324
|
-
addResponseBodySerializer(serializer: ResponseBodySerializer): this {
|
|
325
|
-
return this.addResponseBodySerializers([serializer])
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
/**
|
|
329
|
-
* Add response body serializers to the inherited serializer list.
|
|
330
|
-
*
|
|
331
|
-
* @example
|
|
332
|
-
* ```ts
|
|
333
|
-
* router.addResponseBodySerializers([jsonResponseBodySerializer(), yamlSerializer])
|
|
334
|
-
* ```
|
|
335
|
-
*
|
|
336
|
-
* @param serializers - Response body serializers to append.
|
|
337
|
-
* @returns The router instance for chaining.
|
|
338
|
-
*/
|
|
339
|
-
addResponseBodySerializers(serializers: ResponseBodySerializerInput): this {
|
|
340
|
-
this._responseBodySerializers.push(...normalizeResponseBodySerializers(serializers))
|
|
341
|
-
return this
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* Replace the inherited response body serializer list for this router subtree.
|
|
346
|
-
*
|
|
347
|
-
* @example
|
|
348
|
-
* ```ts
|
|
349
|
-
* router.setResponseBodySerializers([jsonResponseBodySerializer()])
|
|
350
|
-
* ```
|
|
351
|
-
*
|
|
352
|
-
* @param serializers - Exact response body serializers to use for this router subtree.
|
|
353
|
-
* @returns The router instance for chaining.
|
|
354
|
-
*/
|
|
355
|
-
setResponseBodySerializers(serializers: ResponseBodySerializerInput): this {
|
|
356
|
-
this._responseBodySerializers = normalizeResponseBodySerializers(serializers)
|
|
357
|
-
this._responseBodySerializersMode = 'replace'
|
|
358
|
-
return this
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
/**
|
|
362
|
-
* Set the logger for this router subtree.
|
|
363
|
-
*
|
|
364
|
-
* @example
|
|
365
|
-
* ```ts
|
|
366
|
-
* router.setLogger(logger)
|
|
367
|
-
* ```
|
|
368
|
-
*
|
|
369
|
-
* @param logger - Logger exposed through request contexts and used for internal server errors.
|
|
370
|
-
* @returns The router instance for chaining.
|
|
371
|
-
*/
|
|
372
|
-
setLogger(logger: Logger): this {
|
|
373
|
-
this._logger = logger
|
|
374
|
-
return this
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
/**
|
|
378
|
-
* Register middleware on the current router.
|
|
379
|
-
*
|
|
380
|
-
* @example
|
|
381
|
-
* ```ts
|
|
382
|
-
* router.use(ctx => {
|
|
383
|
-
* ctx.traceId = crypto.randomUUID()
|
|
384
|
-
* })
|
|
385
|
-
* ```
|
|
386
|
-
*
|
|
387
|
-
* @param middleware - Middleware or a list of middleware to register.
|
|
388
|
-
* @returns The router instance for chaining.
|
|
389
|
-
*/
|
|
390
|
-
use<AddedCtx extends object>(
|
|
391
|
-
middleware: ContextMiddleware<AddedCtx, Ctx> | null | undefined | false,
|
|
392
|
-
): Router<Ctx & AddedCtx>
|
|
393
|
-
use<AddedCtx extends object>(
|
|
394
|
-
middleware: DeclaredMiddleware<AddedCtx, Ctx>,
|
|
395
|
-
): Router<Ctx & AddedCtx>
|
|
396
|
-
/**
|
|
397
|
-
* Register middleware on the current router.
|
|
398
|
-
*
|
|
399
|
-
* @example
|
|
400
|
-
* ```ts
|
|
401
|
-
* router.use([auth(), logging()])
|
|
402
|
-
* ```
|
|
403
|
-
*
|
|
404
|
-
* @param middleware - Middleware list to register.
|
|
405
|
-
* @returns The router instance for chaining.
|
|
406
|
-
*/
|
|
407
|
-
use<List extends readonly MiddlewareEntry<Ctx>[]>(
|
|
408
|
-
middleware: List,
|
|
409
|
-
): Router<Ctx & AddedContextFromMiddlewareInput<List>>
|
|
410
|
-
use(middleware: MiddlewareInput<Ctx>): Router<any> {
|
|
411
|
-
const list = normalizeMiddlewareList(middleware)
|
|
412
|
-
this._middleware.push(...list)
|
|
413
|
-
return this
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
/**
|
|
417
|
-
* Register middleware that surrounds final response production for every request.
|
|
418
|
-
*
|
|
419
|
-
* @example
|
|
420
|
-
* ```ts
|
|
421
|
-
* router.useRequest(requestIdCtx())
|
|
422
|
-
* router.useRequest(requestLogger())
|
|
423
|
-
* ```
|
|
424
|
-
*
|
|
425
|
-
* @param middleware - Request-boundary middleware or a middleware list.
|
|
426
|
-
* @returns The router instance with added request context fields.
|
|
427
|
-
*/
|
|
428
|
-
useRequest<AddedCtx extends object>(
|
|
429
|
-
middleware: RequestMiddleware<AddedCtx, Ctx> | null | undefined | false,
|
|
430
|
-
): Router<Ctx & AddedCtx>
|
|
431
|
-
useRequest<List extends readonly RequestMiddlewareEntry<Ctx>[]>(
|
|
432
|
-
middleware: List,
|
|
433
|
-
): Router<Ctx & AddedContextFromMiddlewareInput<List>>
|
|
434
|
-
useRequest(middleware: RequestMiddlewareInput<Ctx>): Router<any> {
|
|
435
|
-
this._requestMiddleware.push(...normalizeRequestMiddlewareList(middleware))
|
|
436
|
-
return this
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
/**
|
|
440
|
-
* Mount a router at the current path.
|
|
441
|
-
*
|
|
442
|
-
* @example
|
|
443
|
-
* ```ts
|
|
444
|
-
* router.mount(apiRouter)
|
|
445
|
-
* ```
|
|
446
|
-
*
|
|
447
|
-
* @param router - Router to mount.
|
|
448
|
-
* @returns The router instance for chaining.
|
|
449
|
-
*/
|
|
450
|
-
mount(router: Router<any>): this
|
|
451
|
-
/**
|
|
452
|
-
* Mount an inline router at the current path.
|
|
453
|
-
*
|
|
454
|
-
* @example
|
|
455
|
-
* ```ts
|
|
456
|
-
* router.mount(admin => {
|
|
457
|
-
* admin.get('/admin/users', handler)
|
|
458
|
-
* })
|
|
459
|
-
* ```
|
|
460
|
-
*
|
|
461
|
-
* @param configure - Callback used to register routes on the mounted router.
|
|
462
|
-
* @returns The router instance for chaining.
|
|
463
|
-
*/
|
|
464
|
-
mount(configure: (router: Router<Ctx>) => void): this
|
|
465
|
-
/**
|
|
466
|
-
* Mount a router under a pathname prefix.
|
|
467
|
-
*
|
|
468
|
-
* @example
|
|
469
|
-
* ```ts
|
|
470
|
-
* router.mount('/api', apiRouter)
|
|
471
|
-
* ```
|
|
472
|
-
*
|
|
473
|
-
* @param prefix - Pathname prefix to strip before routing.
|
|
474
|
-
* @param router - Router to mount.
|
|
475
|
-
* @returns The router instance for chaining.
|
|
476
|
-
*/
|
|
477
|
-
mount(prefix: string, router: Router<any>): this
|
|
478
|
-
/**
|
|
479
|
-
* Mount an inline router under a pathname prefix.
|
|
480
|
-
*
|
|
481
|
-
* @example
|
|
482
|
-
* ```ts
|
|
483
|
-
* router.mount('/api', api => {
|
|
484
|
-
* api.get('/items', handler)
|
|
485
|
-
* })
|
|
486
|
-
* ```
|
|
487
|
-
*
|
|
488
|
-
* @param prefix - Pathname prefix to strip before routing.
|
|
489
|
-
* @param configure - Callback used to register routes on the mounted router.
|
|
490
|
-
* @returns The router instance for chaining.
|
|
491
|
-
*/
|
|
492
|
-
mount(prefix: string, configure: (router: Router<Ctx>) => void): this
|
|
493
|
-
/**
|
|
494
|
-
* Mount an inline router with additional middleware.
|
|
495
|
-
*
|
|
496
|
-
* @example
|
|
497
|
-
* ```ts
|
|
498
|
-
* router.mount({prefix: '/admin', middleware: auth()}, admin => {
|
|
499
|
-
* admin.get('/users', ({userId}) => ok({userId}))
|
|
500
|
-
* })
|
|
501
|
-
* ```
|
|
502
|
-
*
|
|
503
|
-
* @param options - Mount options for the inline router.
|
|
504
|
-
* @param configure - Callback used to register routes on the mounted router.
|
|
505
|
-
* @returns The router instance for chaining.
|
|
506
|
-
*/
|
|
507
|
-
mount<AddedCtx extends object>(
|
|
508
|
-
options: RouterMountOptions<Ctx> & {
|
|
509
|
-
middleware:
|
|
510
|
-
| ContextMiddleware<AddedCtx, Ctx>
|
|
511
|
-
| DeclaredMiddleware<AddedCtx, Ctx>
|
|
512
|
-
| null
|
|
513
|
-
| undefined
|
|
514
|
-
| false
|
|
515
|
-
},
|
|
516
|
-
configure: (router: Router<Ctx & AddedCtx>) => void,
|
|
517
|
-
): this
|
|
518
|
-
/**
|
|
519
|
-
* Mount an inline router with a middleware list.
|
|
520
|
-
*
|
|
521
|
-
* @example
|
|
522
|
-
* ```ts
|
|
523
|
-
* router.mount({middleware: [auth(), logging()] as const}, scoped => {
|
|
524
|
-
* scoped.get('/me', handler)
|
|
525
|
-
* })
|
|
526
|
-
* ```
|
|
527
|
-
*
|
|
528
|
-
* @param options - Mount options for the inline router.
|
|
529
|
-
* @param configure - Callback used to register routes on the mounted router.
|
|
530
|
-
* @returns The router instance for chaining.
|
|
531
|
-
*/
|
|
532
|
-
mount<List extends readonly MiddlewareEntry<Ctx>[]>(
|
|
533
|
-
options: RouterMountOptions<Ctx> & { middleware: List },
|
|
534
|
-
configure: (router: Router<Ctx & AddedContextFromMiddlewareInput<List>>) => void,
|
|
535
|
-
): this
|
|
536
|
-
/**
|
|
537
|
-
* Mount an inline router with options and no additional typed middleware.
|
|
538
|
-
*
|
|
539
|
-
* @example
|
|
540
|
-
* ```ts
|
|
541
|
-
* router.mount({prefix: '/reports'}, reports => {
|
|
542
|
-
* reports.get('/export', handler)
|
|
543
|
-
* })
|
|
544
|
-
* ```
|
|
545
|
-
*
|
|
546
|
-
* @param options - Mount options for the inline router.
|
|
547
|
-
* @param configure - Callback used to register routes on the mounted router.
|
|
548
|
-
* @returns The router instance for chaining.
|
|
549
|
-
*/
|
|
550
|
-
mount(options: RouterMountOptions<Ctx>, configure: (router: Router<Ctx>) => void): this
|
|
551
|
-
/**
|
|
552
|
-
* Mount an existing router with options.
|
|
553
|
-
*
|
|
554
|
-
* @example
|
|
555
|
-
* ```ts
|
|
556
|
-
* router.mount({prefix: '/api', middleware: logging()}, apiRouter)
|
|
557
|
-
* ```
|
|
558
|
-
*
|
|
559
|
-
* @param options - Mount options for the existing router.
|
|
560
|
-
* @param router - Router to mount.
|
|
561
|
-
* @returns The router instance for chaining.
|
|
562
|
-
*/
|
|
563
|
-
mount(options: RouterMountOptions<Ctx>, router: Router<any>): this
|
|
564
|
-
mount(
|
|
565
|
-
prefixOrOptionsOrRouter:
|
|
566
|
-
| string
|
|
567
|
-
| RouterMountOptions<Ctx>
|
|
568
|
-
| Router<any>
|
|
569
|
-
| ((router: Router<any>) => void),
|
|
570
|
-
routerOrConfigure?: Router<any> | ((router: Router<any>) => void),
|
|
571
|
-
): this {
|
|
572
|
-
const mountOptions: RouterMountOptions<Ctx> =
|
|
573
|
-
typeof prefixOrOptionsOrRouter === 'string'
|
|
574
|
-
? { prefix: prefixOrOptionsOrRouter }
|
|
575
|
-
: prefixOrOptionsOrRouter instanceof Router ||
|
|
576
|
-
typeof prefixOrOptionsOrRouter === 'function'
|
|
577
|
-
? {}
|
|
578
|
-
: prefixOrOptionsOrRouter
|
|
579
|
-
const target =
|
|
580
|
-
prefixOrOptionsOrRouter instanceof Router ||
|
|
581
|
-
typeof prefixOrOptionsOrRouter === 'function'
|
|
582
|
-
? prefixOrOptionsOrRouter
|
|
583
|
-
: routerOrConfigure
|
|
584
|
-
|
|
585
|
-
if (target instanceof Router && !mountOptions.middleware) {
|
|
586
|
-
this._entries.push({ kind: 'router', prefix: mountOptions.prefix, router: target })
|
|
587
|
-
return this
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
const scope = new Router<any>()
|
|
591
|
-
if (mountOptions.middleware) {
|
|
592
|
-
scope.use(normalizeMiddlewareList(mountOptions.middleware as MiddlewareInput<any>))
|
|
593
|
-
}
|
|
594
|
-
if (target instanceof Router) {
|
|
595
|
-
scope.mount(target)
|
|
596
|
-
} else {
|
|
597
|
-
target?.(scope)
|
|
598
|
-
}
|
|
599
|
-
this._entries.push({ kind: 'router', prefix: mountOptions.prefix, router: scope })
|
|
600
|
-
return this
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
/**
|
|
604
|
-
* Add a route definition to this router.
|
|
605
|
-
*
|
|
606
|
-
* @example
|
|
607
|
-
* ```ts
|
|
608
|
-
* router.add({method: HttpMethod.POST, path: '/items', handler: ({request}) => new Response(request.url)})
|
|
609
|
-
* ```
|
|
610
|
-
*
|
|
611
|
-
* @param route - Route definition to normalize and register.
|
|
612
|
-
* @returns The router instance for chaining.
|
|
613
|
-
*/
|
|
614
|
-
add<
|
|
615
|
-
RouteMiddleware extends MiddlewareInput<Ctx> = undefined,
|
|
616
|
-
HandlerCtx extends object = Ctx & AddedContextFromMiddlewareInput<RouteMiddleware>,
|
|
617
|
-
>(route: Route<Ctx, RouteMiddleware, HandlerCtx>): this {
|
|
618
|
-
this._entries.push({
|
|
619
|
-
kind: 'route',
|
|
620
|
-
route: normalizeRoute(route),
|
|
621
|
-
middleware: normalizeMiddlewareList(route.middleware as MiddlewareInput<any>),
|
|
622
|
-
})
|
|
623
|
-
return this
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
private _addMethod<
|
|
627
|
-
RouteMiddleware extends MiddlewareInput<Ctx> = undefined,
|
|
628
|
-
HandlerCtx extends object = Ctx & AddedContextFromMiddlewareInput<RouteMiddleware>,
|
|
629
|
-
>(
|
|
630
|
-
method: HttpMethod,
|
|
631
|
-
path: NonNullable<Route<Ctx>['path']>,
|
|
632
|
-
input: Handler<any, Ctx> | RouteOptions<Ctx, RouteMiddleware, HandlerCtx>,
|
|
633
|
-
): this {
|
|
634
|
-
return this.add({
|
|
635
|
-
...(isHandler(input) ? { handler: input } : input),
|
|
636
|
-
path,
|
|
637
|
-
method,
|
|
638
|
-
} as Route<Ctx, RouteMiddleware, HandlerCtx>)
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
/**
|
|
642
|
-
* Add a GET route handler to this router.
|
|
643
|
-
*
|
|
644
|
-
* @param path - URL path pattern to match.
|
|
645
|
-
* @param handler - Handler invoked when the route matches.
|
|
646
|
-
* @returns The router instance for chaining.
|
|
647
|
-
*/
|
|
648
|
-
get(path: NonNullable<Route<Ctx>['path']>, handler: Handler<any, Ctx>): this
|
|
649
|
-
/**
|
|
650
|
-
* Add a GET route definition to this router.
|
|
651
|
-
*
|
|
652
|
-
* @param path - URL path pattern to match.
|
|
653
|
-
* @param options - Route options registered with the GET method.
|
|
654
|
-
* @returns The router instance for chaining.
|
|
655
|
-
*/
|
|
656
|
-
get<
|
|
657
|
-
RouteMiddleware extends MiddlewareInput<Ctx> = undefined,
|
|
658
|
-
HandlerCtx extends object = Ctx & AddedContextFromMiddlewareInput<RouteMiddleware>,
|
|
659
|
-
>(
|
|
660
|
-
path: NonNullable<Route<Ctx>['path']>,
|
|
661
|
-
options: RouteOptions<Ctx, RouteMiddleware, HandlerCtx>,
|
|
662
|
-
): this
|
|
663
|
-
get(path: NonNullable<Route<Ctx>['path']>, input: MethodRouteInput<Ctx>): this {
|
|
664
|
-
return this._addMethod(HttpMethod.GET, path, input)
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
/**
|
|
668
|
-
* Add a HEAD route handler to this router.
|
|
669
|
-
*
|
|
670
|
-
* @param path - URL path pattern to match.
|
|
671
|
-
* @param handler - Handler invoked when the route matches.
|
|
672
|
-
* @returns The router instance for chaining.
|
|
673
|
-
*/
|
|
674
|
-
head(path: NonNullable<Route<Ctx>['path']>, handler: Handler<any, Ctx>): this
|
|
675
|
-
/**
|
|
676
|
-
* Add a HEAD route definition to this router.
|
|
677
|
-
*
|
|
678
|
-
* @param path - URL path pattern to match.
|
|
679
|
-
* @param options - Route options registered with the HEAD method.
|
|
680
|
-
* @returns The router instance for chaining.
|
|
681
|
-
*/
|
|
682
|
-
head<
|
|
683
|
-
RouteMiddleware extends MiddlewareInput<Ctx> = undefined,
|
|
684
|
-
HandlerCtx extends object = Ctx & AddedContextFromMiddlewareInput<RouteMiddleware>,
|
|
685
|
-
>(
|
|
686
|
-
path: NonNullable<Route<Ctx>['path']>,
|
|
687
|
-
options: RouteOptions<Ctx, RouteMiddleware, HandlerCtx>,
|
|
688
|
-
): this
|
|
689
|
-
head(path: NonNullable<Route<Ctx>['path']>, input: MethodRouteInput<Ctx>): this {
|
|
690
|
-
return this._addMethod(HttpMethod.HEAD, path, input)
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
/**
|
|
694
|
-
* Add a POST route handler to this router.
|
|
695
|
-
*
|
|
696
|
-
* @param path - URL path pattern to match.
|
|
697
|
-
* @param handler - Handler invoked when the route matches.
|
|
698
|
-
* @returns The router instance for chaining.
|
|
699
|
-
*/
|
|
700
|
-
post(path: NonNullable<Route<Ctx>['path']>, handler: Handler<any, Ctx>): this
|
|
701
|
-
/**
|
|
702
|
-
* Add a POST route definition to this router.
|
|
703
|
-
*
|
|
704
|
-
* @param path - URL path pattern to match.
|
|
705
|
-
* @param options - Route options registered with the POST method.
|
|
706
|
-
* @returns The router instance for chaining.
|
|
707
|
-
*/
|
|
708
|
-
post<
|
|
709
|
-
RouteMiddleware extends MiddlewareInput<Ctx> = undefined,
|
|
710
|
-
HandlerCtx extends object = Ctx & AddedContextFromMiddlewareInput<RouteMiddleware>,
|
|
711
|
-
>(
|
|
712
|
-
path: NonNullable<Route<Ctx>['path']>,
|
|
713
|
-
options: RouteOptions<Ctx, RouteMiddleware, HandlerCtx>,
|
|
714
|
-
): this
|
|
715
|
-
post(path: NonNullable<Route<Ctx>['path']>, input: MethodRouteInput<Ctx>): this {
|
|
716
|
-
return this._addMethod(HttpMethod.POST, path, input)
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
/**
|
|
720
|
-
* Add a PUT route handler to this router.
|
|
721
|
-
*
|
|
722
|
-
* @param path - URL path pattern to match.
|
|
723
|
-
* @param handler - Handler invoked when the route matches.
|
|
724
|
-
* @returns The router instance for chaining.
|
|
725
|
-
*/
|
|
726
|
-
put(path: NonNullable<Route<Ctx>['path']>, handler: Handler<any, Ctx>): this
|
|
727
|
-
/**
|
|
728
|
-
* Add a PUT route definition to this router.
|
|
729
|
-
*
|
|
730
|
-
* @param path - URL path pattern to match.
|
|
731
|
-
* @param options - Route options registered with the PUT method.
|
|
732
|
-
* @returns The router instance for chaining.
|
|
733
|
-
*/
|
|
734
|
-
put<
|
|
735
|
-
RouteMiddleware extends MiddlewareInput<Ctx> = undefined,
|
|
736
|
-
HandlerCtx extends object = Ctx & AddedContextFromMiddlewareInput<RouteMiddleware>,
|
|
737
|
-
>(
|
|
738
|
-
path: NonNullable<Route<Ctx>['path']>,
|
|
739
|
-
options: RouteOptions<Ctx, RouteMiddleware, HandlerCtx>,
|
|
740
|
-
): this
|
|
741
|
-
put(path: NonNullable<Route<Ctx>['path']>, input: MethodRouteInput<Ctx>): this {
|
|
742
|
-
return this._addMethod(HttpMethod.PUT, path, input)
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
/**
|
|
746
|
-
* Add a DELETE route handler to this router.
|
|
747
|
-
*
|
|
748
|
-
* @param path - URL path pattern to match.
|
|
749
|
-
* @param handler - Handler invoked when the route matches.
|
|
750
|
-
* @returns The router instance for chaining.
|
|
751
|
-
*/
|
|
752
|
-
delete(path: NonNullable<Route<Ctx>['path']>, handler: Handler<any, Ctx>): this
|
|
753
|
-
/**
|
|
754
|
-
* Add a DELETE route definition to this router.
|
|
755
|
-
*
|
|
756
|
-
* @param path - URL path pattern to match.
|
|
757
|
-
* @param options - Route options registered with the DELETE method.
|
|
758
|
-
* @returns The router instance for chaining.
|
|
759
|
-
*/
|
|
760
|
-
delete<
|
|
761
|
-
RouteMiddleware extends MiddlewareInput<Ctx> = undefined,
|
|
762
|
-
HandlerCtx extends object = Ctx & AddedContextFromMiddlewareInput<RouteMiddleware>,
|
|
763
|
-
>(
|
|
764
|
-
path: NonNullable<Route<Ctx>['path']>,
|
|
765
|
-
options: RouteOptions<Ctx, RouteMiddleware, HandlerCtx>,
|
|
766
|
-
): this
|
|
767
|
-
delete(path: NonNullable<Route<Ctx>['path']>, input: MethodRouteInput<Ctx>): this {
|
|
768
|
-
return this._addMethod(HttpMethod.DELETE, path, input)
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
/**
|
|
772
|
-
* Add a PATCH route handler to this router.
|
|
773
|
-
*
|
|
774
|
-
* @param path - URL path pattern to match.
|
|
775
|
-
* @param handler - Handler invoked when the route matches.
|
|
776
|
-
* @returns The router instance for chaining.
|
|
777
|
-
*/
|
|
778
|
-
patch(path: NonNullable<Route<Ctx>['path']>, handler: Handler<any, Ctx>): this
|
|
779
|
-
/**
|
|
780
|
-
* Add a PATCH route definition to this router.
|
|
781
|
-
*
|
|
782
|
-
* @param path - URL path pattern to match.
|
|
783
|
-
* @param options - Route options registered with the PATCH method.
|
|
784
|
-
* @returns The router instance for chaining.
|
|
785
|
-
*/
|
|
786
|
-
patch<
|
|
787
|
-
RouteMiddleware extends MiddlewareInput<Ctx> = undefined,
|
|
788
|
-
HandlerCtx extends object = Ctx & AddedContextFromMiddlewareInput<RouteMiddleware>,
|
|
789
|
-
>(
|
|
790
|
-
path: NonNullable<Route<Ctx>['path']>,
|
|
791
|
-
options: RouteOptions<Ctx, RouteMiddleware, HandlerCtx>,
|
|
792
|
-
): this
|
|
793
|
-
patch(path: NonNullable<Route<Ctx>['path']>, input: MethodRouteInput<Ctx>): this {
|
|
794
|
-
return this._addMethod(HttpMethod.PATCH, path, input)
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
/**
|
|
798
|
-
* Return a flattened list of registered routes.
|
|
799
|
-
*
|
|
800
|
-
* @example
|
|
801
|
-
* ```ts
|
|
802
|
-
* const routes = router.getRoutes()
|
|
803
|
-
* ```
|
|
804
|
-
*
|
|
805
|
-
* @returns Array of normalized routes.
|
|
806
|
-
*/
|
|
807
|
-
getRoutes(): NormalizedRoute<any>[] {
|
|
808
|
-
return this._getRoutes([])
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
private _getRoutes(parentMiddleware: ExecutableMiddleware[]): NormalizedRoute<any>[] {
|
|
812
|
-
const routes: NormalizedRoute<any>[] = []
|
|
813
|
-
const middleware = [...parentMiddleware, ...this._middleware]
|
|
814
|
-
for (const entry of this._entries) {
|
|
815
|
-
if (entry.kind === 'route') {
|
|
816
|
-
const routeMiddleware = [...middleware, ...entry.middleware]
|
|
817
|
-
const schema = mergeRouteSchemas(
|
|
818
|
-
...routeMiddleware.flatMap((value) =>
|
|
819
|
-
isDeclaredMiddleware(value) &&
|
|
820
|
-
(!value.schemaAppliesTo || value.schemaAppliesTo(entry.route))
|
|
821
|
-
? [value.schema]
|
|
822
|
-
: [],
|
|
823
|
-
),
|
|
824
|
-
entry.route.schema,
|
|
825
|
-
)
|
|
826
|
-
routes.push({
|
|
827
|
-
...entry.route,
|
|
828
|
-
...(schema === undefined ? {} : { schema }),
|
|
829
|
-
})
|
|
830
|
-
continue
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
for (const subRoute of entry.router._getRoutes(middleware)) {
|
|
834
|
-
if (!entry.prefix) {
|
|
835
|
-
routes.push(subRoute)
|
|
836
|
-
continue
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
const path = new URLPattern({
|
|
840
|
-
pathname: joinPrefixPathname(entry.prefix, subRoute.path.pathname),
|
|
841
|
-
})
|
|
842
|
-
routes.push({
|
|
843
|
-
...subRoute,
|
|
844
|
-
path,
|
|
845
|
-
})
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
return routes
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
private _defaultRuntimeConfig(): RuntimeConfig {
|
|
852
|
-
return {
|
|
853
|
-
requestBodyParsers: defaultRequestBodyParsers(),
|
|
854
|
-
responseBodySerializers: defaultResponseBodySerializers(),
|
|
855
|
-
logger: this._defaultLogger,
|
|
856
|
-
requestMiddleware: [],
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
private _handlerRegistration(
|
|
861
|
-
handler: Handler<any, any> | undefined,
|
|
862
|
-
): HandlerRegistration | undefined {
|
|
863
|
-
return handler ? { handler, router: this } : undefined
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
private _resolveRuntimeConfig(parent: RuntimeConfig): RuntimeConfig {
|
|
867
|
-
const requestBodyParsers =
|
|
868
|
-
this._requestBodyParsersMode === 'replace'
|
|
869
|
-
? [...this._requestBodyParsers]
|
|
870
|
-
: [...parent.requestBodyParsers, ...this._requestBodyParsers]
|
|
871
|
-
const responseBodySerializers =
|
|
872
|
-
this._responseBodySerializersMode === 'replace'
|
|
873
|
-
? [...this._responseBodySerializers]
|
|
874
|
-
: [...parent.responseBodySerializers, ...this._responseBodySerializers]
|
|
875
|
-
|
|
876
|
-
return {
|
|
877
|
-
requestBodyParsers,
|
|
878
|
-
responseBodySerializers,
|
|
879
|
-
logger: this._logger ?? parent.logger,
|
|
880
|
-
requestMiddleware: [...parent.requestMiddleware, ...this._requestMiddleware],
|
|
881
|
-
onNotFoundHandler:
|
|
882
|
-
this._handlerRegistration(this._onNotFoundHandler) ?? parent.onNotFoundHandler,
|
|
883
|
-
onMethodNotAllowedHandler:
|
|
884
|
-
this._handlerRegistration(this._onMethodNotAllowedHandler) ??
|
|
885
|
-
parent.onMethodNotAllowedHandler,
|
|
886
|
-
onUnsupportedMediaTypeHandler:
|
|
887
|
-
this._handlerRegistration(this._onUnsupportedMediaTypeHandler) ??
|
|
888
|
-
parent.onUnsupportedMediaTypeHandler,
|
|
889
|
-
onInternalErrorHandler:
|
|
890
|
-
this._handlerRegistration(this._onInternalErrorHandler) ??
|
|
891
|
-
parent.onInternalErrorHandler,
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
private _match(
|
|
896
|
-
request: RoutekitRequest,
|
|
897
|
-
method: HttpMethod,
|
|
898
|
-
url: URL,
|
|
899
|
-
parentConfig: RuntimeConfig,
|
|
900
|
-
):
|
|
901
|
-
| MatchResult
|
|
902
|
-
| { kind: 'method_not_allowed'; config: RuntimeConfig }
|
|
903
|
-
| { kind: 'unsupported_media_type'; found: MatchResult }
|
|
904
|
-
| { kind: 'not_found'; config: RuntimeConfig } {
|
|
905
|
-
const isHead = method === HttpMethod.HEAD
|
|
906
|
-
if (isHead) {
|
|
907
|
-
const headOnly = this._matchWithMethodCheck(
|
|
908
|
-
request,
|
|
909
|
-
url,
|
|
910
|
-
(routeMethod) => this._methodMatches(routeMethod, HttpMethod.HEAD),
|
|
911
|
-
parentConfig,
|
|
912
|
-
[],
|
|
913
|
-
)
|
|
914
|
-
if (headOnly.match) return headOnly.match
|
|
915
|
-
const getOnly = this._matchWithMethodCheck(
|
|
916
|
-
request,
|
|
917
|
-
url,
|
|
918
|
-
(routeMethod) => this._methodMatches(routeMethod, HttpMethod.GET),
|
|
919
|
-
parentConfig,
|
|
920
|
-
[],
|
|
921
|
-
)
|
|
922
|
-
if (getOnly.match) return getOnly.match
|
|
923
|
-
if (headOnly.unsupportedMediaType)
|
|
924
|
-
return { kind: 'unsupported_media_type', found: headOnly.unsupportedMediaType }
|
|
925
|
-
if (getOnly.unsupportedMediaType)
|
|
926
|
-
return { kind: 'unsupported_media_type', found: getOnly.unsupportedMediaType }
|
|
927
|
-
const methodNotAllowedConfig =
|
|
928
|
-
headOnly.methodNotAllowedConfig ?? getOnly.methodNotAllowedConfig
|
|
929
|
-
if (methodNotAllowedConfig) {
|
|
930
|
-
return { kind: 'method_not_allowed', config: methodNotAllowedConfig }
|
|
931
|
-
}
|
|
932
|
-
return { kind: 'not_found', config: headOnly.notFoundConfig }
|
|
933
|
-
}
|
|
934
|
-
const match = this._matchWithMethodCheck(
|
|
935
|
-
request,
|
|
936
|
-
url,
|
|
937
|
-
(routeMethod) => this._methodMatches(routeMethod, method),
|
|
938
|
-
parentConfig,
|
|
939
|
-
[],
|
|
940
|
-
)
|
|
941
|
-
if (match.match) return match.match
|
|
942
|
-
if (match.unsupportedMediaType)
|
|
943
|
-
return { kind: 'unsupported_media_type', found: match.unsupportedMediaType }
|
|
944
|
-
if (match.methodNotAllowedConfig) {
|
|
945
|
-
return { kind: 'method_not_allowed', config: match.methodNotAllowedConfig }
|
|
946
|
-
}
|
|
947
|
-
return { kind: 'not_found', config: match.notFoundConfig }
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
private _collectAllowedMethods(url: URL): Set<HttpMethod> {
|
|
951
|
-
const methods = new Set<HttpMethod>()
|
|
952
|
-
function addMethods(routeMethod?: HttpMethod | HttpMethod[]) {
|
|
953
|
-
if (!routeMethod) return
|
|
954
|
-
const normalized = Array.isArray(routeMethod) ? routeMethod : [routeMethod]
|
|
955
|
-
for (const method of normalized) {
|
|
956
|
-
methods.add(method)
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
function visit(entries: RouteEntry[], currentUrl: URL) {
|
|
960
|
-
for (const entry of entries) {
|
|
961
|
-
if (entry.kind === 'route') {
|
|
962
|
-
if (entry.route.match) continue
|
|
963
|
-
if (!entry.route.path.exec(currentUrl)) continue
|
|
964
|
-
addMethods(entry.route.method)
|
|
965
|
-
continue
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
if (entry.prefix) {
|
|
969
|
-
const subPathname = stripPrefixPathname(entry.prefix, currentUrl.pathname)
|
|
970
|
-
if (subPathname == null) continue
|
|
971
|
-
const subUrl = new URL(currentUrl.toString())
|
|
972
|
-
subUrl.pathname = subPathname
|
|
973
|
-
visit(entry.router._entries, subUrl)
|
|
974
|
-
} else {
|
|
975
|
-
visit(entry.router._entries, currentUrl)
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
visit(this._entries, url)
|
|
980
|
-
return methods
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
private _acceptMatches(route: NormalizedRoute<any>, request: RoutekitRequest): boolean {
|
|
984
|
-
if (!route.accept || route.accept.length === 0) return true
|
|
985
|
-
const contentTypeHeader = request.headers.get(CommonHeaders.CONTENT_TYPE)
|
|
986
|
-
if (!contentTypeHeader) return false
|
|
987
|
-
const contentType = parseContentType(contentTypeHeader)
|
|
988
|
-
if (!contentType) return false
|
|
989
|
-
return route.accept.some((accept) => mediaTypeMatches(accept, contentType))
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
private _formatAllowMethods(methods: Set<HttpMethod>): string {
|
|
993
|
-
const order = [
|
|
994
|
-
HttpMethod.GET,
|
|
995
|
-
HttpMethod.HEAD,
|
|
996
|
-
HttpMethod.POST,
|
|
997
|
-
HttpMethod.PUT,
|
|
998
|
-
HttpMethod.PATCH,
|
|
999
|
-
HttpMethod.DELETE,
|
|
1000
|
-
HttpMethod.OPTIONS,
|
|
1001
|
-
]
|
|
1002
|
-
const ordered: HttpMethod[] = []
|
|
1003
|
-
for (const method of order) {
|
|
1004
|
-
if (methods.has(method)) ordered.push(method)
|
|
1005
|
-
}
|
|
1006
|
-
const remaining = [...methods].filter((method) => !order.includes(method)).sort()
|
|
1007
|
-
return [...ordered, ...remaining].join(', ')
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
|
-
private _buildHandlerContext(
|
|
1011
|
-
request: RoutekitRequest,
|
|
1012
|
-
url: URL,
|
|
1013
|
-
path: Record<string, string>,
|
|
1014
|
-
config: RuntimeConfig,
|
|
1015
|
-
routePath?: string,
|
|
1016
|
-
): HandlerContext<Ctx> {
|
|
1017
|
-
return {
|
|
1018
|
-
request,
|
|
1019
|
-
url,
|
|
1020
|
-
path,
|
|
1021
|
-
logger: config.logger.withContext({
|
|
1022
|
-
'http.request.method': request.method,
|
|
1023
|
-
'url.path': url.pathname,
|
|
1024
|
-
...(routePath == null ? {} : { 'http.route': routePath }),
|
|
1025
|
-
}),
|
|
1026
|
-
} as HandlerContext<Ctx>
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
private _runRequestMiddleware(
|
|
1030
|
-
middleware: ExecutableRequestMiddleware[],
|
|
1031
|
-
ctx: HandlerContext<any>,
|
|
1032
|
-
respond: () => Promise<Response>,
|
|
1033
|
-
): Promise<Response> {
|
|
1034
|
-
let index = -1
|
|
1035
|
-
const dispatch = async (nextIndex: number): Promise<Response> => {
|
|
1036
|
-
if (nextIndex <= index) throw new Error('next() called multiple times')
|
|
1037
|
-
index = nextIndex
|
|
1038
|
-
|
|
1039
|
-
const current = middleware[nextIndex]
|
|
1040
|
-
if (current == null) {
|
|
1041
|
-
return nextIndex >= middleware.length
|
|
1042
|
-
? await respond()
|
|
1043
|
-
: await dispatch(nextIndex + 1)
|
|
1044
|
-
}
|
|
1045
|
-
return await current(ctx, () => dispatch(nextIndex + 1))
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
return dispatch(0)
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
private async _executeHandler(
|
|
1052
|
-
handler: Handler<any, any>,
|
|
1053
|
-
middleware: ExecutableMiddleware[],
|
|
1054
|
-
ctx: HandlerContext<any>,
|
|
1055
|
-
router: Router<any>,
|
|
1056
|
-
request: RoutekitRequest,
|
|
1057
|
-
config: RuntimeConfig,
|
|
1058
|
-
): Promise<Response> {
|
|
1059
|
-
const result = await this._run(handler, middleware, ctx, router)
|
|
1060
|
-
if (result instanceof Response) {
|
|
1061
|
-
return result
|
|
1062
|
-
}
|
|
1063
|
-
if (this._isAsyncGenerator(result)) {
|
|
1064
|
-
const response = await this._responseFromGenerator(result, request, config)
|
|
1065
|
-
if (response) return response
|
|
1066
|
-
return new Response(null, { status: HttpStatus.CLIENT_CLOSED_REQUEST })
|
|
1067
|
-
}
|
|
1068
|
-
return await this._finalizeResult(result, request, config)
|
|
1069
|
-
}
|
|
1070
|
-
|
|
1071
|
-
private _statusResponse(status: HttpStatus, request?: RoutekitRequest): Response {
|
|
1072
|
-
const result = text(StatusText[status] ?? `HTTP Status ${status}`, { status })
|
|
1073
|
-
return new Response(
|
|
1074
|
-
request?.method.toUpperCase() === HttpMethod.HEAD ? null : (result.body as string),
|
|
1075
|
-
{
|
|
1076
|
-
status,
|
|
1077
|
-
headers: result.headers,
|
|
1078
|
-
},
|
|
1079
|
-
)
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
private _logInternalServerError(error: unknown, logger: Logger): void {
|
|
1083
|
-
try {
|
|
1084
|
-
logger.error('Routekit internal server error', error)
|
|
1085
|
-
} catch {
|
|
1086
|
-
// Logging must not change the response produced for an internal server error.
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
private async _finalizeResult(
|
|
1091
|
-
result: HandlerFinalResult,
|
|
1092
|
-
request: RoutekitRequest,
|
|
1093
|
-
config: RuntimeConfig,
|
|
1094
|
-
): Promise<Response> {
|
|
1095
|
-
if (result instanceof Response) {
|
|
1096
|
-
return result
|
|
1097
|
-
}
|
|
1098
|
-
if (this._isAsyncGenerator(result)) {
|
|
1099
|
-
const response = await this._responseFromGenerator(result, request, config)
|
|
1100
|
-
if (response) return response
|
|
1101
|
-
return new Response(null, { status: HttpStatus.CLIENT_CLOSED_REQUEST })
|
|
1102
|
-
}
|
|
1103
|
-
if (isRoutekitResponse(result)) {
|
|
1104
|
-
return await this._responseFromRoutekitResponse(result, request, config)
|
|
1105
|
-
}
|
|
1106
|
-
if (isRoutekitBody(result)) {
|
|
1107
|
-
return await this._responseFromRoutekitResponse(
|
|
1108
|
-
routekitResponse(result.value),
|
|
1109
|
-
request,
|
|
1110
|
-
config,
|
|
1111
|
-
)
|
|
1112
|
-
}
|
|
1113
|
-
if (isResponseBodyInit(result)) {
|
|
1114
|
-
const body = this._toResponseBody(result)
|
|
1115
|
-
const headers = new Headers()
|
|
1116
|
-
this._setContentLength(headers, body)
|
|
1117
|
-
return new Response(body, {
|
|
1118
|
-
status: HttpStatus.OK,
|
|
1119
|
-
headers,
|
|
1120
|
-
})
|
|
1121
|
-
}
|
|
1122
|
-
return await this._responseFromRoutekitResponse(routekitResponse(result), request, config)
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
|
-
private async _responseFromRoutekitResponse(
|
|
1126
|
-
result: RoutekitResponse,
|
|
1127
|
-
request: RoutekitRequest,
|
|
1128
|
-
config: RuntimeConfig,
|
|
1129
|
-
): Promise<Response> {
|
|
1130
|
-
const isHead = request.method.toUpperCase() === HttpMethod.HEAD
|
|
1131
|
-
const headers = new Headers(result.headers)
|
|
1132
|
-
const status = result.status
|
|
1133
|
-
|
|
1134
|
-
if (headers.has(CommonHeaders.CONTENT_TYPE)) {
|
|
1135
|
-
if (!isResponseBodyInit(result.body)) {
|
|
1136
|
-
throw new TypeError(
|
|
1137
|
-
'Routekit response has Content-Type set, so body must be a native Response body.',
|
|
1138
|
-
)
|
|
1139
|
-
}
|
|
1140
|
-
const body = this._toResponseBody(result.body)
|
|
1141
|
-
this._setContentLength(headers, body)
|
|
1142
|
-
return new Response(isHead ? null : body, {
|
|
1143
|
-
status,
|
|
1144
|
-
headers,
|
|
1145
|
-
})
|
|
1146
|
-
}
|
|
1147
|
-
|
|
1148
|
-
if (result.body === undefined) {
|
|
1149
|
-
return new Response(null, { status, headers })
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1152
|
-
const serializer = this._selectSerializer(result.body, request, config)
|
|
1153
|
-
if (!serializer) {
|
|
1154
|
-
return this._statusResponse(HttpStatus.NOT_ACCEPTABLE, request)
|
|
1155
|
-
}
|
|
1156
|
-
headers.set(CommonHeaders.CONTENT_TYPE, serializer.mediaType)
|
|
1157
|
-
this._appendVary(headers, CommonHeaders.ACCEPT)
|
|
1158
|
-
const body = await serializer.serializer.serialize(result.body)
|
|
1159
|
-
this._setContentLength(headers, body)
|
|
1160
|
-
|
|
1161
|
-
return new Response(isHead ? null : body, {
|
|
1162
|
-
status,
|
|
1163
|
-
headers,
|
|
1164
|
-
})
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
|
-
private _appendVary(headers: Headers, value: string): void {
|
|
1168
|
-
const current = headers.get(CommonHeaders.VARY)
|
|
1169
|
-
if (!current) {
|
|
1170
|
-
headers.set(CommonHeaders.VARY, value)
|
|
1171
|
-
return
|
|
1172
|
-
}
|
|
1173
|
-
const entries = current.split(',').map((entry) => entry.trim().toLowerCase())
|
|
1174
|
-
if (entries.includes(value.toLowerCase())) return
|
|
1175
|
-
headers.set(CommonHeaders.VARY, `${current}, ${value}`)
|
|
1176
|
-
}
|
|
1177
|
-
|
|
1178
|
-
private _selectSerializer(
|
|
1179
|
-
body: unknown,
|
|
1180
|
-
request: RoutekitRequest,
|
|
1181
|
-
config: RuntimeConfig,
|
|
1182
|
-
): { serializer: ResponseBodySerializer; mediaType: string } | null {
|
|
1183
|
-
const accepted = parseAcceptHeader(request.headers.get(CommonHeaders.ACCEPT) ?? '*/*')
|
|
1184
|
-
const ranges = accepted.length ? accepted : parseAcceptHeader('*/*')
|
|
1185
|
-
let best: ResponseBodySerializerCandidate | null = null
|
|
1186
|
-
for (const [acceptIndex, accept] of ranges.entries()) {
|
|
1187
|
-
if (accept.q === 0) continue
|
|
1188
|
-
for (const [serializerIndex, serializer] of config.responseBodySerializers.entries()) {
|
|
1189
|
-
if (serializer.canSerialize && !serializer.canSerialize(body)) continue
|
|
1190
|
-
for (const [mediaTypeIndex, mediaTypeRange] of serializer.mediaTypes.entries()) {
|
|
1191
|
-
const mediaTypeQuality = mediaRangeQuality(mediaTypeRange)
|
|
1192
|
-
if (mediaTypeQuality === 0) continue
|
|
1193
|
-
if (mediaRangeAccepts(accept, mediaTypeRange)) {
|
|
1194
|
-
const mediaType = mediaRangeToContentType(mediaTypeRange)
|
|
1195
|
-
if (!mediaType) continue
|
|
1196
|
-
const candidate = {
|
|
1197
|
-
serializer,
|
|
1198
|
-
mediaType,
|
|
1199
|
-
acceptQuality: accept.q,
|
|
1200
|
-
mediaTypeQuality,
|
|
1201
|
-
acceptIndex,
|
|
1202
|
-
serializerIndex,
|
|
1203
|
-
mediaTypeIndex,
|
|
1204
|
-
}
|
|
1205
|
-
if (this._isBetterSerializerCandidate(candidate, best)) {
|
|
1206
|
-
best = candidate
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
|
-
return best ? { serializer: best.serializer, mediaType: best.mediaType } : null
|
|
1213
|
-
}
|
|
1214
|
-
|
|
1215
|
-
private _isBetterSerializerCandidate(
|
|
1216
|
-
candidate: ResponseBodySerializerCandidate,
|
|
1217
|
-
best: ResponseBodySerializerCandidate | null,
|
|
1218
|
-
): boolean {
|
|
1219
|
-
if (!best) return true
|
|
1220
|
-
if (candidate.acceptQuality !== best.acceptQuality) {
|
|
1221
|
-
return candidate.acceptQuality > best.acceptQuality
|
|
1222
|
-
}
|
|
1223
|
-
if (candidate.mediaTypeQuality !== best.mediaTypeQuality) {
|
|
1224
|
-
return candidate.mediaTypeQuality > best.mediaTypeQuality
|
|
1225
|
-
}
|
|
1226
|
-
if (candidate.acceptIndex !== best.acceptIndex) {
|
|
1227
|
-
return candidate.acceptIndex < best.acceptIndex
|
|
1228
|
-
}
|
|
1229
|
-
if (candidate.serializerIndex !== best.serializerIndex) {
|
|
1230
|
-
return candidate.serializerIndex < best.serializerIndex
|
|
1231
|
-
}
|
|
1232
|
-
return candidate.mediaTypeIndex < best.mediaTypeIndex
|
|
1233
|
-
}
|
|
1234
|
-
|
|
1235
|
-
private async _tryCustomHandler(
|
|
1236
|
-
registration: HandlerRegistration | undefined,
|
|
1237
|
-
ctx: HandlerContext<any>,
|
|
1238
|
-
config: RuntimeConfig,
|
|
1239
|
-
request: RoutekitRequest,
|
|
1240
|
-
): Promise<Response | null> {
|
|
1241
|
-
if (!registration) return null
|
|
1242
|
-
try {
|
|
1243
|
-
return await this._executeHandler(
|
|
1244
|
-
registration.handler,
|
|
1245
|
-
[],
|
|
1246
|
-
ctx,
|
|
1247
|
-
registration.router,
|
|
1248
|
-
request,
|
|
1249
|
-
config,
|
|
1250
|
-
)
|
|
1251
|
-
} catch (error) {
|
|
1252
|
-
if (error instanceof RequestBodyError) return responseFromRequestBodyError(error)
|
|
1253
|
-
this._logInternalServerError(error, ctx.logger)
|
|
1254
|
-
return this._statusResponse(HttpStatus.INTERNAL_SERVER_ERROR, request)
|
|
1255
|
-
}
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
|
-
private _requestWithBodyParsers(
|
|
1259
|
-
request: RoutekitRequest,
|
|
1260
|
-
config: RuntimeConfig,
|
|
1261
|
-
): RoutekitRequest {
|
|
1262
|
-
return createRoutekitRequest(request.raw, request.url, config.requestBodyParsers)
|
|
1263
|
-
}
|
|
1264
|
-
|
|
1265
|
-
private async _handleNotFound(
|
|
1266
|
-
request: RoutekitRequest,
|
|
1267
|
-
url: URL,
|
|
1268
|
-
config: RuntimeConfig,
|
|
1269
|
-
): Promise<Response> {
|
|
1270
|
-
request = this._requestWithBodyParsers(request, config)
|
|
1271
|
-
const ctx = this._buildHandlerContext(request, url, {}, config)
|
|
1272
|
-
return await this._runRequestMiddleware(config.requestMiddleware, ctx, async () => {
|
|
1273
|
-
const custom = await this._tryCustomHandler(
|
|
1274
|
-
config.onNotFoundHandler,
|
|
1275
|
-
ctx,
|
|
1276
|
-
config,
|
|
1277
|
-
request,
|
|
1278
|
-
)
|
|
1279
|
-
if (custom) return custom
|
|
1280
|
-
return this._statusResponse(HttpStatus.NOT_FOUND, request)
|
|
1281
|
-
})
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
private async _handleMethodNotAllowed(
|
|
1285
|
-
request: RoutekitRequest,
|
|
1286
|
-
url: URL,
|
|
1287
|
-
config: RuntimeConfig,
|
|
1288
|
-
): Promise<Response> {
|
|
1289
|
-
request = this._requestWithBodyParsers(request, config)
|
|
1290
|
-
const ctx = this._buildHandlerContext(request, url, {}, config)
|
|
1291
|
-
return await this._runRequestMiddleware(config.requestMiddleware, ctx, async () => {
|
|
1292
|
-
const custom = await this._tryCustomHandler(
|
|
1293
|
-
config.onMethodNotAllowedHandler,
|
|
1294
|
-
ctx,
|
|
1295
|
-
config,
|
|
1296
|
-
request,
|
|
1297
|
-
)
|
|
1298
|
-
if (custom) return custom
|
|
1299
|
-
return this._statusResponse(HttpStatus.METHOD_NOT_ALLOWED, request)
|
|
1300
|
-
})
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
private async _handleInternalError(
|
|
1304
|
-
config: RuntimeConfig,
|
|
1305
|
-
ctx: HandlerContext<any>,
|
|
1306
|
-
request: RoutekitRequest,
|
|
1307
|
-
error: unknown,
|
|
1308
|
-
): Promise<Response> {
|
|
1309
|
-
if (error instanceof RequestBodyError) return responseFromRequestBodyError(error)
|
|
1310
|
-
this._logInternalServerError(error, ctx.logger)
|
|
1311
|
-
const custom = await this._tryCustomHandler(
|
|
1312
|
-
config.onInternalErrorHandler,
|
|
1313
|
-
ctx,
|
|
1314
|
-
config,
|
|
1315
|
-
request,
|
|
1316
|
-
)
|
|
1317
|
-
if (custom) return custom
|
|
1318
|
-
return this._statusResponse(HttpStatus.INTERNAL_SERVER_ERROR, request)
|
|
1319
|
-
}
|
|
1320
|
-
|
|
1321
|
-
private async _handleUnsupportedMediaType(
|
|
1322
|
-
found: MatchResult,
|
|
1323
|
-
request: RoutekitRequest,
|
|
1324
|
-
url: URL,
|
|
1325
|
-
): Promise<Response> {
|
|
1326
|
-
request = this._requestWithBodyParsers(request, found.config)
|
|
1327
|
-
const rawPathParams = found.match?.pathname.groups ?? {}
|
|
1328
|
-
const path = Object.fromEntries(
|
|
1329
|
-
Object.entries(rawPathParams).filter(([, value]) => value !== undefined),
|
|
1330
|
-
) as Record<string, string>
|
|
1331
|
-
const handlerCtx = this._buildHandlerContext(
|
|
1332
|
-
request,
|
|
1333
|
-
url,
|
|
1334
|
-
path,
|
|
1335
|
-
found.config,
|
|
1336
|
-
found.routePath,
|
|
1337
|
-
)
|
|
1338
|
-
return await this._runRequestMiddleware(
|
|
1339
|
-
found.config.requestMiddleware,
|
|
1340
|
-
handlerCtx,
|
|
1341
|
-
async () => {
|
|
1342
|
-
const custom = await this._tryCustomHandler(
|
|
1343
|
-
found.config.onUnsupportedMediaTypeHandler,
|
|
1344
|
-
handlerCtx,
|
|
1345
|
-
found.config,
|
|
1346
|
-
request,
|
|
1347
|
-
)
|
|
1348
|
-
if (custom) return custom
|
|
1349
|
-
return this._statusResponse(HttpStatus.UNSUPPORTED_MEDIA_TYPE, request)
|
|
1350
|
-
},
|
|
1351
|
-
)
|
|
1352
|
-
}
|
|
1353
|
-
|
|
1354
|
-
private async _handleMatch(
|
|
1355
|
-
found: MatchResult,
|
|
1356
|
-
request: RoutekitRequest,
|
|
1357
|
-
url: URL,
|
|
1358
|
-
): Promise<Response> {
|
|
1359
|
-
request = this._requestWithBodyParsers(request, found.config)
|
|
1360
|
-
const rawPathParams = found.match?.pathname.groups ?? {}
|
|
1361
|
-
const path = Object.fromEntries(
|
|
1362
|
-
Object.entries(rawPathParams).filter(([, value]) => value !== undefined),
|
|
1363
|
-
) as Record<string, string>
|
|
1364
|
-
const handlerCtx = this._buildHandlerContext(
|
|
1365
|
-
request,
|
|
1366
|
-
url,
|
|
1367
|
-
path,
|
|
1368
|
-
found.config,
|
|
1369
|
-
found.routePath,
|
|
1370
|
-
)
|
|
1371
|
-
|
|
1372
|
-
return await this._runRequestMiddleware(
|
|
1373
|
-
found.config.requestMiddleware,
|
|
1374
|
-
handlerCtx,
|
|
1375
|
-
async () => {
|
|
1376
|
-
try {
|
|
1377
|
-
mergeRouteSchemas(
|
|
1378
|
-
...found.middleware.flatMap((value) =>
|
|
1379
|
-
isDeclaredMiddleware(value) &&
|
|
1380
|
-
(!value.schemaAppliesTo || value.schemaAppliesTo(found.route))
|
|
1381
|
-
? [value.schema]
|
|
1382
|
-
: [],
|
|
1383
|
-
),
|
|
1384
|
-
found.route.schema,
|
|
1385
|
-
)
|
|
1386
|
-
return await this._executeHandler(
|
|
1387
|
-
found.route.handler,
|
|
1388
|
-
found.middleware,
|
|
1389
|
-
handlerCtx,
|
|
1390
|
-
found.router,
|
|
1391
|
-
request,
|
|
1392
|
-
found.config,
|
|
1393
|
-
)
|
|
1394
|
-
} catch (error) {
|
|
1395
|
-
return await this._handleInternalError(found.config, handlerCtx, request, error)
|
|
1396
|
-
}
|
|
1397
|
-
},
|
|
1398
|
-
)
|
|
1399
|
-
}
|
|
1400
|
-
|
|
1401
|
-
private _methodMatches(
|
|
1402
|
-
routeMethod: HttpMethod | HttpMethod[] | undefined,
|
|
1403
|
-
method: HttpMethod,
|
|
1404
|
-
): boolean {
|
|
1405
|
-
if (!routeMethod) return true
|
|
1406
|
-
const normalized = Array.isArray(routeMethod) ? routeMethod : [routeMethod]
|
|
1407
|
-
return normalized.some((routeValue) => routeValue === method)
|
|
1408
|
-
}
|
|
1409
|
-
|
|
1410
|
-
private _withRoutePrefix(match: MatchResult, prefix?: string): MatchResult {
|
|
1411
|
-
return prefix == null
|
|
1412
|
-
? match
|
|
1413
|
-
: {
|
|
1414
|
-
...match,
|
|
1415
|
-
routePath: joinPrefixPathname(prefix, match.routePath),
|
|
1416
|
-
}
|
|
1417
|
-
}
|
|
1418
|
-
|
|
1419
|
-
private _matchWithMethodCheck(
|
|
1420
|
-
request: RoutekitRequest,
|
|
1421
|
-
url: URL,
|
|
1422
|
-
methodCheck: (routeMethod?: HttpMethod | HttpMethod[]) => boolean,
|
|
1423
|
-
parentConfig: RuntimeConfig,
|
|
1424
|
-
parentMiddleware: ExecutableMiddleware[],
|
|
1425
|
-
): MatchAttempt {
|
|
1426
|
-
const config = this._resolveRuntimeConfig(parentConfig)
|
|
1427
|
-
const middleware = [...parentMiddleware, ...this._middleware]
|
|
1428
|
-
let methodNotAllowedConfig: RuntimeConfig | null = null
|
|
1429
|
-
let unsupportedMediaType: MatchResult | null = null
|
|
1430
|
-
let notFoundConfig = config
|
|
1431
|
-
for (const entry of this._entries) {
|
|
1432
|
-
if (entry.kind === 'route') {
|
|
1433
|
-
const route = entry.route
|
|
1434
|
-
const routeMiddleware = [...middleware, ...entry.middleware]
|
|
1435
|
-
const pathMatch = route.path.exec(url)
|
|
1436
|
-
if (route.match) {
|
|
1437
|
-
if (!route.match(request)) continue
|
|
1438
|
-
return {
|
|
1439
|
-
match: {
|
|
1440
|
-
kind: 'match',
|
|
1441
|
-
route,
|
|
1442
|
-
routePath: route.path.pathname,
|
|
1443
|
-
match: pathMatch,
|
|
1444
|
-
middleware: routeMiddleware,
|
|
1445
|
-
router: this,
|
|
1446
|
-
config,
|
|
1447
|
-
},
|
|
1448
|
-
unsupportedMediaType,
|
|
1449
|
-
methodNotAllowedConfig,
|
|
1450
|
-
notFoundConfig,
|
|
1451
|
-
}
|
|
1452
|
-
}
|
|
1453
|
-
if (!pathMatch) continue
|
|
1454
|
-
if (!methodCheck(route.method)) {
|
|
1455
|
-
methodNotAllowedConfig ??= config
|
|
1456
|
-
continue
|
|
1457
|
-
}
|
|
1458
|
-
if (!this._acceptMatches(route, request)) {
|
|
1459
|
-
unsupportedMediaType ??= {
|
|
1460
|
-
kind: 'match',
|
|
1461
|
-
route,
|
|
1462
|
-
routePath: route.path.pathname,
|
|
1463
|
-
match: pathMatch,
|
|
1464
|
-
middleware: routeMiddleware,
|
|
1465
|
-
router: this,
|
|
1466
|
-
config,
|
|
1467
|
-
}
|
|
1468
|
-
continue
|
|
1469
|
-
}
|
|
1470
|
-
return {
|
|
1471
|
-
match: {
|
|
1472
|
-
kind: 'match',
|
|
1473
|
-
route,
|
|
1474
|
-
routePath: route.path.pathname,
|
|
1475
|
-
match: pathMatch,
|
|
1476
|
-
middleware: routeMiddleware,
|
|
1477
|
-
router: this,
|
|
1478
|
-
config,
|
|
1479
|
-
},
|
|
1480
|
-
unsupportedMediaType,
|
|
1481
|
-
methodNotAllowedConfig,
|
|
1482
|
-
notFoundConfig,
|
|
1483
|
-
}
|
|
1484
|
-
}
|
|
1485
|
-
|
|
1486
|
-
if (entry.prefix) {
|
|
1487
|
-
const subPathname = stripPrefixPathname(entry.prefix, url.pathname)
|
|
1488
|
-
if (subPathname == null) continue
|
|
1489
|
-
const subUrl = new URL(url.toString())
|
|
1490
|
-
subUrl.pathname = subPathname
|
|
1491
|
-
const subMatch = entry.router._matchWithMethodCheck(
|
|
1492
|
-
request,
|
|
1493
|
-
subUrl,
|
|
1494
|
-
methodCheck,
|
|
1495
|
-
config,
|
|
1496
|
-
middleware,
|
|
1497
|
-
)
|
|
1498
|
-
if (subMatch.match) {
|
|
1499
|
-
return {
|
|
1500
|
-
match: this._withRoutePrefix(subMatch.match, entry.prefix),
|
|
1501
|
-
unsupportedMediaType,
|
|
1502
|
-
methodNotAllowedConfig,
|
|
1503
|
-
notFoundConfig,
|
|
1504
|
-
}
|
|
1505
|
-
}
|
|
1506
|
-
if (subMatch.unsupportedMediaType && !unsupportedMediaType) {
|
|
1507
|
-
unsupportedMediaType = this._withRoutePrefix(
|
|
1508
|
-
subMatch.unsupportedMediaType,
|
|
1509
|
-
entry.prefix,
|
|
1510
|
-
)
|
|
1511
|
-
}
|
|
1512
|
-
methodNotAllowedConfig ??= subMatch.methodNotAllowedConfig
|
|
1513
|
-
notFoundConfig = subMatch.notFoundConfig
|
|
1514
|
-
} else {
|
|
1515
|
-
const subMatch = entry.router._matchWithMethodCheck(
|
|
1516
|
-
request,
|
|
1517
|
-
url,
|
|
1518
|
-
methodCheck,
|
|
1519
|
-
config,
|
|
1520
|
-
middleware,
|
|
1521
|
-
)
|
|
1522
|
-
if (subMatch.match) {
|
|
1523
|
-
return {
|
|
1524
|
-
match: subMatch.match,
|
|
1525
|
-
unsupportedMediaType,
|
|
1526
|
-
methodNotAllowedConfig,
|
|
1527
|
-
notFoundConfig,
|
|
1528
|
-
}
|
|
1529
|
-
}
|
|
1530
|
-
if (subMatch.unsupportedMediaType && !unsupportedMediaType) {
|
|
1531
|
-
unsupportedMediaType = subMatch.unsupportedMediaType
|
|
1532
|
-
}
|
|
1533
|
-
methodNotAllowedConfig ??= subMatch.methodNotAllowedConfig
|
|
1534
|
-
notFoundConfig = subMatch.notFoundConfig
|
|
1535
|
-
}
|
|
1536
|
-
}
|
|
1537
|
-
return { match: null, unsupportedMediaType, methodNotAllowedConfig, notFoundConfig }
|
|
1538
|
-
}
|
|
1539
|
-
|
|
1540
|
-
private _run(
|
|
1541
|
-
handler: Handler<any, any>,
|
|
1542
|
-
middleware: ExecutableMiddleware[],
|
|
1543
|
-
ctx: HandlerContext<Ctx>,
|
|
1544
|
-
router: Router<any>,
|
|
1545
|
-
): Promise<HandlerResult> {
|
|
1546
|
-
let idx = -1
|
|
1547
|
-
const dispatch = async (i: number): Promise<HandlerResult> => {
|
|
1548
|
-
if (i <= idx) throw new Error('next() called multiple times')
|
|
1549
|
-
idx = i
|
|
1550
|
-
|
|
1551
|
-
if (i === middleware.length) {
|
|
1552
|
-
return await Promise.resolve().then(() => handler.call(router, ctx as any))
|
|
1553
|
-
}
|
|
1554
|
-
|
|
1555
|
-
const mw = middleware[i]
|
|
1556
|
-
if (!mw) {
|
|
1557
|
-
return await dispatch(i + 1)
|
|
1558
|
-
}
|
|
1559
|
-
if (isDeclaredMiddleware(mw)) {
|
|
1560
|
-
return await mw(ctx as HandlerContext<any>, () => dispatch(i + 1))
|
|
1561
|
-
}
|
|
1562
|
-
await mw(ctx as RequestContext<any>)
|
|
1563
|
-
return await dispatch(i + 1)
|
|
1564
|
-
}
|
|
1565
|
-
|
|
1566
|
-
return dispatch(0)
|
|
1567
|
-
}
|
|
1568
|
-
|
|
1569
|
-
private _isAsyncGenerator(
|
|
1570
|
-
value: unknown,
|
|
1571
|
-
): value is AsyncGenerator<HandlerYield, HandlerFinalResult> {
|
|
1572
|
-
return !!value && typeof (value as AsyncGenerator)[Symbol.asyncIterator] === 'function'
|
|
1573
|
-
}
|
|
1574
|
-
|
|
1575
|
-
private async _closeGenerator(
|
|
1576
|
-
generator: AsyncGenerator<HandlerYield, HandlerFinalResult>,
|
|
1577
|
-
): Promise<void> {
|
|
1578
|
-
try {
|
|
1579
|
-
await generator.return?.(undefined as unknown as HandlerFinalResult)
|
|
1580
|
-
} catch {
|
|
1581
|
-
// Ignore generator errors during teardown.
|
|
1582
|
-
}
|
|
1583
|
-
}
|
|
1584
|
-
|
|
1585
|
-
private _toResponseBody(body: HandlerBody | null): RouterBodyInit | null {
|
|
1586
|
-
if (body == null) return null
|
|
1587
|
-
return body
|
|
1588
|
-
}
|
|
1589
|
-
|
|
1590
|
-
private _setContentLength(headers: Headers, body: RouterBodyInit | null): void {
|
|
1591
|
-
if (headers.has(CommonHeaders.CONTENT_LENGTH) || body == null) {
|
|
1592
|
-
return
|
|
1593
|
-
}
|
|
1594
|
-
|
|
1595
|
-
const size = this._getBodyByteLength(body)
|
|
1596
|
-
if (size != null) {
|
|
1597
|
-
headers.set(CommonHeaders.CONTENT_LENGTH, String(size))
|
|
1598
|
-
}
|
|
1599
|
-
}
|
|
1600
|
-
|
|
1601
|
-
private _getBodyByteLength(body: RouterBodyInit): number | undefined {
|
|
1602
|
-
if (typeof body === 'string') {
|
|
1603
|
-
return new TextEncoder().encode(body).byteLength
|
|
1604
|
-
}
|
|
1605
|
-
|
|
1606
|
-
if (body instanceof Blob) {
|
|
1607
|
-
return body.size
|
|
1608
|
-
}
|
|
1609
|
-
|
|
1610
|
-
if (body instanceof URLSearchParams) {
|
|
1611
|
-
return new TextEncoder().encode(body.toString()).byteLength
|
|
1612
|
-
}
|
|
1613
|
-
|
|
1614
|
-
if (body instanceof ArrayBuffer) {
|
|
1615
|
-
return body.byteLength
|
|
1616
|
-
}
|
|
1617
|
-
|
|
1618
|
-
if (ArrayBuffer.isView(body)) {
|
|
1619
|
-
return body.byteLength
|
|
1620
|
-
}
|
|
1621
|
-
|
|
1622
|
-
return undefined
|
|
1623
|
-
}
|
|
1624
|
-
|
|
1625
|
-
private _isBodyChunk(value: unknown): value is Uint8Array | Buffer | string {
|
|
1626
|
-
return typeof value === 'string' || value instanceof Uint8Array
|
|
1627
|
-
}
|
|
1628
|
-
|
|
1629
|
-
private _toBodyChunk(value: Uint8Array | Buffer | string): Uint8Array {
|
|
1630
|
-
if (typeof value === 'string') {
|
|
1631
|
-
return new TextEncoder().encode(value)
|
|
1632
|
-
}
|
|
1633
|
-
return value
|
|
1634
|
-
}
|
|
1635
|
-
|
|
1636
|
-
private _mergeHeaders(target: Headers, source: Headers): void {
|
|
1637
|
-
source.forEach((value, key) => target.set(key, value))
|
|
1638
|
-
}
|
|
1639
|
-
|
|
1640
|
-
private async _frameChunk(
|
|
1641
|
-
value: unknown,
|
|
1642
|
-
framer: StreamFramer | undefined,
|
|
1643
|
-
): Promise<Uint8Array> {
|
|
1644
|
-
if (!framer) {
|
|
1645
|
-
if (!this._isBodyChunk(value)) {
|
|
1646
|
-
throw new TypeError(
|
|
1647
|
-
'Generator yielded a structured chunk without stream(). Use stream(sseFramer()) or yield raw string/bytes chunks.',
|
|
1648
|
-
)
|
|
1649
|
-
}
|
|
1650
|
-
return this._toBodyChunk(value)
|
|
1651
|
-
}
|
|
1652
|
-
if (framer.canFrame && !framer.canFrame(value)) {
|
|
1653
|
-
throw new TypeError('Stream framer cannot encode the yielded chunk.')
|
|
1654
|
-
}
|
|
1655
|
-
return this._toBodyChunk(await framer.frame(value as never))
|
|
1656
|
-
}
|
|
1657
|
-
|
|
1658
|
-
private async _responseFromGenerator(
|
|
1659
|
-
generator: AsyncGenerator<HandlerYield, HandlerFinalResult>,
|
|
1660
|
-
request: RoutekitRequest,
|
|
1661
|
-
config: RuntimeConfig,
|
|
1662
|
-
): Promise<Response | null> {
|
|
1663
|
-
const isHead = request.method.toUpperCase() === HttpMethod.HEAD
|
|
1664
|
-
let status: number | HttpStatus | undefined
|
|
1665
|
-
let statusSet = false
|
|
1666
|
-
const headers = new Headers()
|
|
1667
|
-
let framer: StreamFramer | undefined
|
|
1668
|
-
let bodyStream: ReadableStream<Uint8Array> | undefined
|
|
1669
|
-
let bodyController: ReadableStreamDefaultController<Uint8Array> | undefined
|
|
1670
|
-
let responseResolved = false
|
|
1671
|
-
let resolveResponse: ((value: Response | null) => void) | undefined
|
|
1672
|
-
let rejectResponse: ((reason?: unknown) => void) | undefined
|
|
1673
|
-
const responsePromise = new Promise<Response | null>((resolve, reject) => {
|
|
1674
|
-
resolveResponse = resolve
|
|
1675
|
-
rejectResponse = reject
|
|
1676
|
-
})
|
|
1677
|
-
let abortListener: (() => void) | undefined
|
|
1678
|
-
const abortSignal = request.signal
|
|
1679
|
-
const abortPromise = abortSignal
|
|
1680
|
-
? new Promise<'aborted'>((resolve) => {
|
|
1681
|
-
if (abortSignal.aborted) {
|
|
1682
|
-
resolve('aborted')
|
|
1683
|
-
return
|
|
1684
|
-
}
|
|
1685
|
-
abortListener = () => resolve('aborted')
|
|
1686
|
-
abortSignal.addEventListener('abort', abortListener, { once: true })
|
|
1687
|
-
})
|
|
1688
|
-
: null
|
|
1689
|
-
|
|
1690
|
-
if (abortSignal?.aborted) {
|
|
1691
|
-
await this._closeGenerator(generator)
|
|
1692
|
-
return null
|
|
1693
|
-
}
|
|
1694
|
-
|
|
1695
|
-
const resolveResponseOnce = (response: Response | null) => {
|
|
1696
|
-
if (responseResolved) return
|
|
1697
|
-
responseResolved = true
|
|
1698
|
-
resolveResponse?.(response)
|
|
1699
|
-
}
|
|
1700
|
-
|
|
1701
|
-
const rejectResponseOnce = (error: unknown) => {
|
|
1702
|
-
if (responseResolved) {
|
|
1703
|
-
bodyController?.error?.(error)
|
|
1704
|
-
return
|
|
1705
|
-
}
|
|
1706
|
-
responseResolved = true
|
|
1707
|
-
rejectResponse?.(error)
|
|
1708
|
-
}
|
|
1709
|
-
|
|
1710
|
-
const buildResponseInit = (): ResponseInit => ({
|
|
1711
|
-
status: status ?? HttpStatus.OK,
|
|
1712
|
-
headers,
|
|
1713
|
-
})
|
|
1714
|
-
|
|
1715
|
-
const ensureStreamResponse = () => {
|
|
1716
|
-
if (responseResolved) return
|
|
1717
|
-
if (!bodyStream) {
|
|
1718
|
-
bodyStream = new ReadableStream<Uint8Array>({
|
|
1719
|
-
start(controller) {
|
|
1720
|
-
bodyController = controller
|
|
1721
|
-
},
|
|
1722
|
-
})
|
|
1723
|
-
}
|
|
1724
|
-
resolveResponseOnce(new Response(bodyStream, buildResponseInit()))
|
|
1725
|
-
}
|
|
1726
|
-
|
|
1727
|
-
const setStatus = (value: number | HttpStatus) => {
|
|
1728
|
-
if (bodyStream) {
|
|
1729
|
-
throw new TypeError('Generator cannot set status after streaming has started.')
|
|
1730
|
-
}
|
|
1731
|
-
if (statusSet) {
|
|
1732
|
-
throw new TypeError('Generator response status was already set.')
|
|
1733
|
-
}
|
|
1734
|
-
status = value
|
|
1735
|
-
statusSet = true
|
|
1736
|
-
}
|
|
1737
|
-
|
|
1738
|
-
const mergeHeaders = (value: Headers) => {
|
|
1739
|
-
if (bodyStream) {
|
|
1740
|
-
throw new TypeError('Generator cannot set headers after streaming has started.')
|
|
1741
|
-
}
|
|
1742
|
-
this._mergeHeaders(headers, value)
|
|
1743
|
-
}
|
|
1744
|
-
|
|
1745
|
-
const finalizeReturnedValue = async (value: HandlerFinalResult) => {
|
|
1746
|
-
if (isRoutekitResponse(value)) {
|
|
1747
|
-
if (statusSet) {
|
|
1748
|
-
throw new TypeError(
|
|
1749
|
-
'Generator returned a Routekit response after status was already set.',
|
|
1750
|
-
)
|
|
1751
|
-
}
|
|
1752
|
-
const mergedHeaders = new Headers(headers)
|
|
1753
|
-
this._mergeHeaders(mergedHeaders, value.headers)
|
|
1754
|
-
return await this._responseFromRoutekitResponse(
|
|
1755
|
-
routekitResponse(value.body, {
|
|
1756
|
-
status: value.status,
|
|
1757
|
-
headers: mergedHeaders,
|
|
1758
|
-
}),
|
|
1759
|
-
request,
|
|
1760
|
-
config,
|
|
1761
|
-
)
|
|
1762
|
-
}
|
|
1763
|
-
if (value instanceof Response) {
|
|
1764
|
-
if (statusSet || [...headers.keys()].length > 0) {
|
|
1765
|
-
throw new TypeError(
|
|
1766
|
-
'Generator returned a native Response after response metadata was already set.',
|
|
1767
|
-
)
|
|
1768
|
-
}
|
|
1769
|
-
return value
|
|
1770
|
-
}
|
|
1771
|
-
const returnedBody = isRoutekitBody(value) ? value.value : value
|
|
1772
|
-
return await this._responseFromRoutekitResponse(
|
|
1773
|
-
routekitResponse(returnedBody, {
|
|
1774
|
-
status: status ?? HttpStatus.OK,
|
|
1775
|
-
headers,
|
|
1776
|
-
}),
|
|
1777
|
-
request,
|
|
1778
|
-
config,
|
|
1779
|
-
)
|
|
1780
|
-
}
|
|
1781
|
-
|
|
1782
|
-
try {
|
|
1783
|
-
const pump = async () => {
|
|
1784
|
-
try {
|
|
1785
|
-
while (true) {
|
|
1786
|
-
const next = abortPromise
|
|
1787
|
-
? await Promise.race([generator.next(), abortPromise])
|
|
1788
|
-
: await generator.next()
|
|
1789
|
-
if (next === 'aborted') {
|
|
1790
|
-
await this._closeGenerator(generator)
|
|
1791
|
-
bodyController?.error?.(new Error('Request aborted'))
|
|
1792
|
-
resolveResponseOnce(null)
|
|
1793
|
-
return
|
|
1794
|
-
}
|
|
1795
|
-
|
|
1796
|
-
if (next.done) {
|
|
1797
|
-
if (bodyStream) {
|
|
1798
|
-
if (next.value !== undefined) {
|
|
1799
|
-
throw new TypeError(
|
|
1800
|
-
'Generator cannot return a final body after yielding chunks.',
|
|
1801
|
-
)
|
|
1802
|
-
}
|
|
1803
|
-
if (framer?.close && !isHead) {
|
|
1804
|
-
const finalChunk = await framer.close()
|
|
1805
|
-
if (finalChunk !== undefined) {
|
|
1806
|
-
bodyController?.enqueue(this._toBodyChunk(finalChunk))
|
|
1807
|
-
}
|
|
1808
|
-
}
|
|
1809
|
-
bodyController?.close()
|
|
1810
|
-
if (!responseResolved) {
|
|
1811
|
-
resolveResponseOnce(
|
|
1812
|
-
new Response(bodyStream, buildResponseInit()),
|
|
1813
|
-
)
|
|
1814
|
-
}
|
|
1815
|
-
return
|
|
1816
|
-
}
|
|
1817
|
-
|
|
1818
|
-
resolveResponseOnce(await finalizeReturnedValue(next.value))
|
|
1819
|
-
return
|
|
1820
|
-
}
|
|
1821
|
-
|
|
1822
|
-
const yielded = next.value
|
|
1823
|
-
if (yielded === undefined) {
|
|
1824
|
-
continue
|
|
1825
|
-
}
|
|
1826
|
-
|
|
1827
|
-
if (isStatusDirective(yielded)) {
|
|
1828
|
-
setStatus(yielded.status)
|
|
1829
|
-
continue
|
|
1830
|
-
}
|
|
1831
|
-
|
|
1832
|
-
if (isHeadersDirective(yielded)) {
|
|
1833
|
-
mergeHeaders(yielded.headers)
|
|
1834
|
-
if (isHead) {
|
|
1835
|
-
await this._closeGenerator(generator)
|
|
1836
|
-
resolveResponseOnce(new Response(null, buildResponseInit()))
|
|
1837
|
-
return
|
|
1838
|
-
}
|
|
1839
|
-
continue
|
|
1840
|
-
}
|
|
1841
|
-
|
|
1842
|
-
if (isHeadDirective(yielded)) {
|
|
1843
|
-
setStatus(yielded.status)
|
|
1844
|
-
mergeHeaders(yielded.headers)
|
|
1845
|
-
if (isHead) {
|
|
1846
|
-
await this._closeGenerator(generator)
|
|
1847
|
-
resolveResponseOnce(new Response(null, buildResponseInit()))
|
|
1848
|
-
return
|
|
1849
|
-
}
|
|
1850
|
-
continue
|
|
1851
|
-
}
|
|
1852
|
-
|
|
1853
|
-
if (isStreamDirective(yielded)) {
|
|
1854
|
-
if (bodyStream) {
|
|
1855
|
-
throw new TypeError(
|
|
1856
|
-
'Generator cannot select a stream framer after streaming has started.',
|
|
1857
|
-
)
|
|
1858
|
-
}
|
|
1859
|
-
framer = yielded.framer
|
|
1860
|
-
mergeHeaders(yielded.headers)
|
|
1861
|
-
headers.set(CommonHeaders.CONTENT_TYPE, framer.contentType)
|
|
1862
|
-
if (isHead) {
|
|
1863
|
-
await this._closeGenerator(generator)
|
|
1864
|
-
resolveResponseOnce(new Response(null, buildResponseInit()))
|
|
1865
|
-
return
|
|
1866
|
-
}
|
|
1867
|
-
continue
|
|
1868
|
-
}
|
|
1869
|
-
|
|
1870
|
-
if (isChunkDirective(yielded)) {
|
|
1871
|
-
if (isHead) {
|
|
1872
|
-
await this._closeGenerator(generator)
|
|
1873
|
-
resolveResponseOnce(new Response(null, buildResponseInit()))
|
|
1874
|
-
return
|
|
1875
|
-
}
|
|
1876
|
-
ensureStreamResponse()
|
|
1877
|
-
bodyController?.enqueue(await this._frameChunk(yielded.value, framer))
|
|
1878
|
-
continue
|
|
1879
|
-
}
|
|
1880
|
-
|
|
1881
|
-
throw new TypeError(
|
|
1882
|
-
'Generator yielded an unsupported value. Use status(), headers(), head(), stream(), or chunk().',
|
|
1883
|
-
)
|
|
1884
|
-
}
|
|
1885
|
-
} catch (err) {
|
|
1886
|
-
rejectResponseOnce(err)
|
|
1887
|
-
}
|
|
1888
|
-
}
|
|
1889
|
-
|
|
1890
|
-
void pump()
|
|
1891
|
-
return await responsePromise
|
|
1892
|
-
} finally {
|
|
1893
|
-
if (abortSignal && abortListener) {
|
|
1894
|
-
abortSignal.removeEventListener('abort', abortListener)
|
|
1895
|
-
}
|
|
1896
|
-
}
|
|
1897
|
-
}
|
|
1898
|
-
|
|
1899
|
-
private async _handleRequest(request: Request): Promise<Response> {
|
|
1900
|
-
const url = new URL(request.url)
|
|
1901
|
-
const method = request.method.toUpperCase() as HttpMethod
|
|
1902
|
-
const rootConfig = this._resolveRuntimeConfig(this._defaultRuntimeConfig())
|
|
1903
|
-
const routekitRequest = createRoutekitRequest(request, url, rootConfig.requestBodyParsers)
|
|
1904
|
-
|
|
1905
|
-
if (method === HttpMethod.OPTIONS) {
|
|
1906
|
-
const explicit = this._match(routekitRequest, method, url, this._defaultRuntimeConfig())
|
|
1907
|
-
if (explicit.kind === 'match') {
|
|
1908
|
-
return await this._handleMatch(explicit, routekitRequest, url)
|
|
1909
|
-
}
|
|
1910
|
-
if (explicit.kind === 'unsupported_media_type') {
|
|
1911
|
-
return await this._handleUnsupportedMediaType(explicit.found, routekitRequest, url)
|
|
1912
|
-
}
|
|
1913
|
-
const allowedMethods = this._collectAllowedMethods(url)
|
|
1914
|
-
if (allowedMethods.size === 0) {
|
|
1915
|
-
return await this._handleNotFound(routekitRequest, url, explicit.config)
|
|
1916
|
-
}
|
|
1917
|
-
if (allowedMethods.has(HttpMethod.GET)) {
|
|
1918
|
-
allowedMethods.add(HttpMethod.HEAD)
|
|
1919
|
-
}
|
|
1920
|
-
allowedMethods.add(HttpMethod.OPTIONS)
|
|
1921
|
-
const optionsRequest = this._requestWithBodyParsers(routekitRequest, explicit.config)
|
|
1922
|
-
const ctx = this._buildHandlerContext(optionsRequest, url, {}, explicit.config)
|
|
1923
|
-
return await this._runRequestMiddleware(
|
|
1924
|
-
explicit.config.requestMiddleware,
|
|
1925
|
-
ctx,
|
|
1926
|
-
async () =>
|
|
1927
|
-
new Response(null, {
|
|
1928
|
-
status: HttpStatus.NO_CONTENT,
|
|
1929
|
-
headers: {
|
|
1930
|
-
'access-control-allow-methods':
|
|
1931
|
-
this._formatAllowMethods(allowedMethods),
|
|
1932
|
-
},
|
|
1933
|
-
}),
|
|
1934
|
-
)
|
|
1935
|
-
}
|
|
1936
|
-
|
|
1937
|
-
const found = this._match(routekitRequest, method, url, this._defaultRuntimeConfig())
|
|
1938
|
-
if (found.kind === 'not_found') {
|
|
1939
|
-
return await this._handleNotFound(routekitRequest, url, found.config)
|
|
1940
|
-
}
|
|
1941
|
-
if (found.kind === 'method_not_allowed') {
|
|
1942
|
-
return await this._handleMethodNotAllowed(routekitRequest, url, found.config)
|
|
1943
|
-
}
|
|
1944
|
-
if (found.kind === 'unsupported_media_type') {
|
|
1945
|
-
return await this._handleUnsupportedMediaType(found.found, routekitRequest, url)
|
|
1946
|
-
}
|
|
1947
|
-
return await this._handleMatch(found, routekitRequest, url)
|
|
1948
|
-
}
|
|
1949
|
-
|
|
1950
|
-
/**
|
|
1951
|
-
* Handle an incoming request by matching routes and executing middleware.
|
|
1952
|
-
*
|
|
1953
|
-
* @example
|
|
1954
|
-
* ```ts
|
|
1955
|
-
* const response = await router.fetch(new Request('https://example.com/'))
|
|
1956
|
-
* ```
|
|
1957
|
-
*
|
|
1958
|
-
* @param request - Incoming request to route.
|
|
1959
|
-
* @returns The response produced by the matched handler.
|
|
1960
|
-
*/
|
|
1961
|
-
fetch = (request: Request): Promise<Response> => {
|
|
1962
|
-
// Use = syntax to force `this` binding so that Bun can execute the router directly
|
|
1963
|
-
return this._handleRequest(request)
|
|
1964
|
-
}
|
|
1965
|
-
}
|