@mpen/routekit 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/dist/bin.d.mts +4 -0
  2. package/dist/client/react.d.mts +178 -0
  3. package/dist/client/react.mjs +142 -0
  4. package/dist/client.d.mts +433 -0
  5. package/dist/client.mjs +264 -0
  6. package/dist/content-BuDOmhH_.mjs +102 -0
  7. package/dist/core-CzUCxvGk.d.mts +140 -0
  8. package/dist/core-DbmQauwS.mjs +81 -0
  9. package/dist/handlers.d.mts +72 -0
  10. package/dist/handlers.mjs +153 -0
  11. package/dist/index.d.mts +3 -0
  12. package/dist/index.mjs +1152 -0
  13. package/dist/middleware.d.mts +388 -0
  14. package/dist/middleware.mjs +1222 -0
  15. package/dist/request-Dn0zc-xm.mjs +1025 -0
  16. package/dist/response/content.d.mts +79 -0
  17. package/dist/response/content.mjs +2 -0
  18. package/dist/response/json-rpc.d.mts +1 -0
  19. package/dist/response/json-rpc.mjs +1 -0
  20. package/dist/response/problem/valibot.d.mts +230 -0
  21. package/dist/response/problem/valibot.mjs +258 -0
  22. package/dist/response/problem.d.mts +415 -0
  23. package/dist/response/problem.mjs +183 -0
  24. package/dist/response/status.d.mts +45 -0
  25. package/dist/response/status.mjs +2 -0
  26. package/dist/responses-B379Ep9Y.d.mts +296 -0
  27. package/dist/responses-BpVrgeYi.mjs +101 -0
  28. package/dist/router-Cwb7ak0J.d.mts +1819 -0
  29. package/dist/routes.d.mts +282 -0
  30. package/dist/routes.mjs +311 -0
  31. package/dist/status-C-8mw-FB.mjs +59 -0
  32. package/dist/valibot-D7liFYyB.d.mts +290 -0
  33. package/dist/valibot-Du97X-TS.mjs +326 -0
  34. package/package.json +8 -2
  35. package/src/bin/gen-api-client.test.ts +0 -70
  36. package/src/bin/gen-api-client.ts +0 -986
  37. package/src/client/headers.ts +0 -31
  38. package/src/client/index.ts +0 -8
  39. package/src/client/promise.ts +0 -11
  40. package/src/client/react/index.test.tsx +0 -266
  41. package/src/client/react/index.ts +0 -431
  42. package/src/client/responses.test.ts +0 -151
  43. package/src/client/responses.ts +0 -278
  44. package/src/client/transport.ts +0 -74
  45. package/src/client/transports/body-codec.ts +0 -61
  46. package/src/client/transports/fetch.ts +0 -113
  47. package/src/client/tsconfig.json +0 -9
  48. package/src/client/types.ts +0 -15
  49. package/src/client/url.ts +0 -31
  50. package/src/index.ts +0 -63
  51. package/src/router/fetch-types.ts +0 -13
  52. package/src/router/handlers/index.ts +0 -2
  53. package/src/router/handlers/openapi/index.ts +0 -2
  54. package/src/router/handlers/openapi/openapi.ts +0 -293
  55. package/src/router/integration/zod-openapi.test.ts +0 -74
  56. package/src/router/lib/charset.test.ts +0 -22
  57. package/src/router/lib/charset.ts +0 -133
  58. package/src/router/lib/collections.ts +0 -3
  59. package/src/router/lib/format.test.ts +0 -67
  60. package/src/router/lib/format.ts +0 -35
  61. package/src/router/lib/host.ts +0 -4
  62. package/src/router/lib/json-schema.ts +0 -6
  63. package/src/router/lib/media-type.test.ts +0 -122
  64. package/src/router/lib/media-type.ts +0 -289
  65. package/src/router/lib/pathname.test.ts +0 -18
  66. package/src/router/lib/pathname.ts +0 -19
  67. package/src/router/lib/route-names.ts +0 -70
  68. package/src/router/lib/route-normalize.test.ts +0 -36
  69. package/src/router/lib/route-normalize.ts +0 -67
  70. package/src/router/lib/schema-merge.ts +0 -56
  71. package/src/router/middleware/accept-ctx.test.ts +0 -33
  72. package/src/router/middleware/accept-ctx.ts +0 -12
  73. package/src/router/middleware/body-limit.test.ts +0 -112
  74. package/src/router/middleware/body-limit.ts +0 -121
  75. package/src/router/middleware/content-type-context.ts +0 -0
  76. package/src/router/middleware/cors.test.ts +0 -269
  77. package/src/router/middleware/cors.ts +0 -490
  78. package/src/router/middleware/csrf.test.ts +0 -106
  79. package/src/router/middleware/csrf.ts +0 -192
  80. package/src/router/middleware/define.ts +0 -249
  81. package/src/router/middleware/index.ts +0 -34
  82. package/src/router/middleware/jsxhtml-response.ts +0 -0
  83. package/src/router/middleware/oas-swagger.ts +0 -0
  84. package/src/router/middleware/rate-limit.test.ts +0 -886
  85. package/src/router/middleware/rate-limit.ts +0 -920
  86. package/src/router/middleware/request-id-ctx.test.ts +0 -183
  87. package/src/router/middleware/request-id-ctx.ts +0 -135
  88. package/src/router/middleware/request-logger-format.test.ts +0 -16
  89. package/src/router/middleware/request-logger-format.ts +0 -269
  90. package/src/router/middleware/request-logger.test.ts +0 -267
  91. package/src/router/middleware/request-logger.ts +0 -131
  92. package/src/router/middleware/start-time-ctx.ts +0 -5
  93. package/src/router/request.ts +0 -611
  94. package/src/router/response/core.ts +0 -181
  95. package/src/router/response/directives.ts +0 -233
  96. package/src/router/response/formats/content/bodyless.ts +0 -54
  97. package/src/router/response/formats/content/content.ts +0 -79
  98. package/src/router/response/formats/content/index.ts +0 -2
  99. package/src/router/response/formats/json-rpc/index.ts +0 -2
  100. package/src/router/response/formats/problem/badRequest.ts +0 -90
  101. package/src/router/response/formats/problem/conflict.ts +0 -90
  102. package/src/router/response/formats/problem/created.ts +0 -40
  103. package/src/router/response/formats/problem/index.ts +0 -27
  104. package/src/router/response/formats/problem/notFound.ts +0 -90
  105. package/src/router/response/formats/problem/permissionDenied.ts +0 -90
  106. package/src/router/response/formats/problem/problem.test.ts +0 -888
  107. package/src/router/response/formats/problem/rateLimited.ts +0 -90
  108. package/src/router/response/formats/problem/responses.ts +0 -219
  109. package/src/router/response/formats/problem/root-errors.ts +0 -48
  110. package/src/router/response/formats/problem/sessionExpired.ts +0 -90
  111. package/src/router/response/formats/problem/types.ts +0 -170
  112. package/src/router/response/formats/problem/unauthenticated.ts +0 -90
  113. package/src/router/response/formats/problem/valibot.ts +0 -410
  114. package/src/router/response/formats/status/index.ts +0 -1
  115. package/src/router/response/formats/status/responses.ts +0 -59
  116. package/src/router/response/formats/status/status.test.ts +0 -21
  117. package/src/router/response/framers.ts +0 -85
  118. package/src/router/response/index.ts +0 -28
  119. package/src/router/response/openapi.test.ts +0 -96
  120. package/src/router/response/openapi.ts +0 -1
  121. package/src/router/response/serializers.ts +0 -66
  122. package/src/router/response/stream.ts +0 -35
  123. package/src/router/router.test.ts +0 -1571
  124. package/src/router/router.ts +0 -1965
  125. package/src/router/routes/index.ts +0 -46
  126. package/src/router/routes/valibot/index.ts +0 -18
  127. package/src/router/routes/valibot/valibot.ts +0 -1393
  128. package/src/router/routes/valibot.test.ts +0 -286
  129. package/src/router/routes/zod/index.ts +0 -18
  130. package/src/router/routes/zod/zod.ts +0 -1318
  131. package/src/router/routes/zod.test.ts +0 -280
  132. package/src/router/server-interface.ts +0 -31
  133. package/src/router/types.ts +0 -657
package/dist/index.mjs ADDED
@@ -0,0 +1,1152 @@
1
+ import { A as stream, B as parseMediaType, C as isChunkDirective, D as isStatusDirective, E as isRoutekitDirective, F as mediaRangeToContentType, I as mediaTypeMatches, L as normalizeMediaType, N as mediaRangeAccepts, O as isStreamDirective, P as mediaRangeQuality, R as parseAcceptHeader, S as headers, T as isHeadersDirective, _ as jsonResponseBodySerializer, a as createRoutekitRequest, b as chunk, c as jsonRequestBodyParser, d as urlEncodedRequestBodyParser, f as defineMiddleware, g as defaultResponseBodySerializers, h as createStartStream, i as UnsupportedRequestBodyMediaTypeError, j as mergeRouteSchemas, k as status, l as responseFromRequestBodyError, m as createAsyncStream, n as RequestBodyLengthMismatchError, o as defaultRequestBodyParsers, p as isDeclaredMiddleware, r as RequestBodyTooLargeError, s as formDataRequestBodyParser, t as RequestBodyError, u as textRequestBodyParser, v as jsonLinesFramer, w as isHeadDirective, x as head, y as sseFramer, z as parseContentType } from "./request-Dn0zc-xm.mjs";
2
+ import { a as response, i as isRoutekitResponse, n as isResponseBodyInit, r as isRoutekitBody, t as body } from "./core-DbmQauwS.mjs";
3
+ import { a as text } from "./content-BuDOmhH_.mjs";
4
+ import { CommonHeaders, HttpMethod, HttpStatus, StatusText } from "@mpen/http";
5
+ import { ConsoleLogger } from "@mpen/logger";
6
+ //#region src/router/lib/pathname.ts
7
+ function joinPrefixPathname(prefix, pathname) {
8
+ if (!prefix) return pathname;
9
+ if (!prefix.startsWith("/")) prefix = "/" + prefix;
10
+ if (prefix.endsWith("/")) prefix = prefix.slice(0, -1);
11
+ if (pathname === "/") return prefix || "/";
12
+ if (!pathname.startsWith("/")) pathname = "/" + pathname;
13
+ return prefix + pathname || "/";
14
+ }
15
+ function stripPrefixPathname(prefix, pathname) {
16
+ if (!prefix) return pathname;
17
+ if (!prefix.startsWith("/")) prefix = "/" + prefix;
18
+ if (prefix.endsWith("/")) prefix = prefix.slice(0, -1);
19
+ if (prefix === "/") return pathname;
20
+ if (pathname === prefix) return "/";
21
+ if (pathname.startsWith(prefix + "/")) return pathname.slice(prefix.length);
22
+ return null;
23
+ }
24
+ //#endregion
25
+ //#region src/router/lib/route-names.ts
26
+ function sanitizeNamePart(part) {
27
+ const cleaned = part.replace(/:([a-zA-Z_$][a-zA-Z0-9_$]*)/g, "$$$1").replace(/[^a-zA-Z0-9_$]/g, "");
28
+ if (cleaned.length === 0) return "index";
29
+ if (!/^[a-zA-Z_$]/.test(cleaned)) return "_" + cleaned;
30
+ return cleaned;
31
+ }
32
+ function sanitizeNameParts(parts) {
33
+ return parts.map(sanitizeNamePart).filter(Boolean);
34
+ }
35
+ function splitNameString(name) {
36
+ const parts = [];
37
+ let current = "";
38
+ let escaping = false;
39
+ for (const char of name) {
40
+ if (escaping) {
41
+ current += char;
42
+ escaping = false;
43
+ continue;
44
+ }
45
+ if (char === "\\") {
46
+ escaping = true;
47
+ continue;
48
+ }
49
+ if (char === ".") {
50
+ parts.push(current);
51
+ current = "";
52
+ continue;
53
+ }
54
+ current += char;
55
+ }
56
+ parts.push(current);
57
+ return parts.filter((p) => p.length > 0);
58
+ }
59
+ function upperFirst(str) {
60
+ return str.slice(0, 1).toUpperCase() + str.slice(1);
61
+ }
62
+ function lowerFirst(str) {
63
+ return str.slice(0, 1).toLowerCase() + str.slice(1);
64
+ }
65
+ function segmentToDefaultName(segment) {
66
+ const paramMatch = segment.match(/^:([a-zA-Z0-9_]+)(?:\\(.+\\))?$/);
67
+ if (paramMatch) {
68
+ const key = paramMatch[1];
69
+ if (key) return "By" + upperFirst(key);
70
+ }
71
+ const cleaned = segment.split(/[^a-zA-Z0-9]+/).filter(Boolean);
72
+ if (cleaned.length === 0) return "Index";
73
+ return cleaned.map(upperFirst).join("");
74
+ }
75
+ function pattToName(_method, patt) {
76
+ const parts = patt.pathname.split("/").filter((p) => p.length > 0);
77
+ if (parts.length === 0) return [];
78
+ return sanitizeNameParts([lowerFirst(parts.map(segmentToDefaultName).join(""))]);
79
+ }
80
+ //#endregion
81
+ //#region src/router/lib/route-normalize.ts
82
+ function normalizeRouteName(name, method, path) {
83
+ const methodName = Array.isArray(method) ? method[0] : method;
84
+ if (!name) return pattToName(methodName ?? "ANY", path);
85
+ if (typeof name === "string") return sanitizeNameParts(splitNameString(name));
86
+ return sanitizeNameParts(name);
87
+ }
88
+ function normalizeRoute(route) {
89
+ if (!route.path) throw new Error("Route is missing a path");
90
+ const path = typeof route.path === "string" ? new URLPattern({ pathname: route.path }) : route.path;
91
+ const method = route.method;
92
+ const accept = route.accept;
93
+ let normalizedAccept;
94
+ if (accept) {
95
+ normalizedAccept = (Array.isArray(accept) ? accept : [accept]).map((entry) => {
96
+ if (typeof entry === "string") {
97
+ const parsed = parseMediaType(entry);
98
+ if (!parsed) throw new Error(`Invalid accept media type: ${entry}`);
99
+ return parsed;
100
+ }
101
+ return normalizeMediaType(entry);
102
+ });
103
+ if (normalizedAccept.length === 0) normalizedAccept = void 0;
104
+ }
105
+ return {
106
+ name: normalizeRouteName(route.name, route.method, path),
107
+ path,
108
+ handler: route.handler,
109
+ ...route.match === void 0 ? {} : { match: route.match },
110
+ ...route.meta === void 0 ? {} : { meta: route.meta },
111
+ ...route.schema === void 0 ? {} : { schema: route.schema },
112
+ ...method === void 0 ? {} : { method },
113
+ ...normalizedAccept === void 0 ? {} : { accept: normalizedAccept }
114
+ };
115
+ }
116
+ //#endregion
117
+ //#region src/router/router.ts
118
+ function normalizeMiddlewareList(middleware) {
119
+ if (!middleware) return [];
120
+ if (Array.isArray(middleware)) return middleware.filter(Boolean);
121
+ return [middleware];
122
+ }
123
+ function normalizeRequestMiddlewareList(middleware) {
124
+ if (!middleware) return [];
125
+ if (Array.isArray(middleware)) return middleware.filter(Boolean);
126
+ return [middleware];
127
+ }
128
+ function normalizeRequestBodyParsers(input) {
129
+ if (!input) return [];
130
+ if (Array.isArray(input)) return input.filter(Boolean);
131
+ return [input];
132
+ }
133
+ function normalizeResponseBodySerializers(input) {
134
+ if (!input) return [];
135
+ if (Array.isArray(input)) return input.filter(Boolean);
136
+ return [input];
137
+ }
138
+ function isHandler(input) {
139
+ return typeof input === "function";
140
+ }
141
+ /**
142
+ * Router that matches requests against registered routes and executes middleware.
143
+ *
144
+ * @example
145
+ * ```ts
146
+ * const router = new Router()
147
+ * router.add({method: HttpMethod.GET, path: '/', handler: async ({request}) => new Response(request.url)})
148
+ * ```
149
+ */
150
+ var Router = class Router {
151
+ _entries = [];
152
+ _middleware = [];
153
+ _requestMiddleware = [];
154
+ _responseBodySerializers = [];
155
+ _responseBodySerializersMode = "inherit";
156
+ _requestBodyParsers = [];
157
+ _requestBodyParsersMode = "inherit";
158
+ _logger;
159
+ _defaultLogger = new ConsoleLogger();
160
+ _onNotFoundHandler;
161
+ _onMethodNotAllowedHandler;
162
+ _onUnsupportedMediaTypeHandler;
163
+ _onInternalErrorHandler;
164
+ /**
165
+ * Create a new router instance.
166
+ */
167
+ constructor() {}
168
+ /**
169
+ * Install an extension that configures this router instance.
170
+ *
171
+ * @example
172
+ * ```ts
173
+ * router.install(problemRootErrors())
174
+ * ```
175
+ *
176
+ * @param extension - Extension function that mutates or configures this router.
177
+ * @returns The router instance for chaining.
178
+ */
179
+ install(extension) {
180
+ extension(this);
181
+ return this;
182
+ }
183
+ /**
184
+ * Register a handler for requests that do not match any route.
185
+ *
186
+ * @example
187
+ * ```ts
188
+ * router.onNotFound(() => new Response('missing', {status: 404}))
189
+ * ```
190
+ *
191
+ * @param handler - Handler invoked when no route matches.
192
+ * @returns The router instance for chaining.
193
+ */
194
+ onNotFound(handler) {
195
+ this._onNotFoundHandler = handler;
196
+ return this;
197
+ }
198
+ /**
199
+ * Register a handler for requests that match a path but use an unsupported method.
200
+ *
201
+ * @example
202
+ * ```ts
203
+ * router.onMethodNotAllowed(() => new Response('nope', {status: 405}))
204
+ * ```
205
+ *
206
+ * @param handler - Handler invoked when a route exists but the method does not match.
207
+ * @returns The router instance for chaining.
208
+ */
209
+ onMethodNotAllowed(handler) {
210
+ this._onMethodNotAllowedHandler = handler;
211
+ return this;
212
+ }
213
+ /**
214
+ * Register a handler for requests with unsupported request body media types.
215
+ *
216
+ * @example
217
+ * ```ts
218
+ * router.onUnsupportedMediaType(() => new Response('unsupported', {status: 415}))
219
+ * ```
220
+ *
221
+ * @param handler - Handler invoked when the incoming `Content-Type` is not accepted.
222
+ * @returns The router instance for chaining.
223
+ */
224
+ onUnsupportedMediaType(handler) {
225
+ this._onUnsupportedMediaTypeHandler = handler;
226
+ return this;
227
+ }
228
+ /**
229
+ * Register a handler for unhandled errors thrown by route handlers.
230
+ *
231
+ * @example
232
+ * ```ts
233
+ * router.onInternalError(() => new Response('oops', {status: 500}))
234
+ * ```
235
+ *
236
+ * @param handler - Handler invoked when a route handler throws.
237
+ * @returns The router instance for chaining.
238
+ */
239
+ onInternalError(handler) {
240
+ this._onInternalErrorHandler = handler;
241
+ return this;
242
+ }
243
+ /**
244
+ * Add a request body parser to the inherited parser list.
245
+ *
246
+ * @example
247
+ * ```ts
248
+ * router.addRequestBodyParser(jsonRequestBodyParser())
249
+ * ```
250
+ *
251
+ * @param parser - Request body parser to append.
252
+ * @returns The router instance for chaining.
253
+ */
254
+ addRequestBodyParser(parser) {
255
+ return this.addRequestBodyParsers([parser]);
256
+ }
257
+ /**
258
+ * Add request body parsers to the inherited parser list.
259
+ *
260
+ * @example
261
+ * ```ts
262
+ * router.addRequestBodyParsers([jsonRequestBodyParser(), textRequestBodyParser()])
263
+ * ```
264
+ *
265
+ * @param parsers - Request body parsers to append.
266
+ * @returns The router instance for chaining.
267
+ */
268
+ addRequestBodyParsers(parsers) {
269
+ this._requestBodyParsers.push(...normalizeRequestBodyParsers(parsers));
270
+ return this;
271
+ }
272
+ /**
273
+ * Replace the inherited request body parser list for this router subtree.
274
+ *
275
+ * @example
276
+ * ```ts
277
+ * router.setRequestBodyParsers([jsonRequestBodyParser()])
278
+ * ```
279
+ *
280
+ * @param parsers - Exact request body parsers to use for this router subtree.
281
+ * @returns The router instance for chaining.
282
+ */
283
+ setRequestBodyParsers(parsers) {
284
+ this._requestBodyParsers = normalizeRequestBodyParsers(parsers);
285
+ this._requestBodyParsersMode = "replace";
286
+ return this;
287
+ }
288
+ /**
289
+ * Add a response body serializer to the inherited serializer list.
290
+ *
291
+ * @example
292
+ * ```ts
293
+ * router.addResponseBodySerializer(jsonResponseBodySerializer())
294
+ * ```
295
+ *
296
+ * @param serializer - Response body serializer to append.
297
+ * @returns The router instance for chaining.
298
+ */
299
+ addResponseBodySerializer(serializer) {
300
+ return this.addResponseBodySerializers([serializer]);
301
+ }
302
+ /**
303
+ * Add response body serializers to the inherited serializer list.
304
+ *
305
+ * @example
306
+ * ```ts
307
+ * router.addResponseBodySerializers([jsonResponseBodySerializer(), yamlSerializer])
308
+ * ```
309
+ *
310
+ * @param serializers - Response body serializers to append.
311
+ * @returns The router instance for chaining.
312
+ */
313
+ addResponseBodySerializers(serializers) {
314
+ this._responseBodySerializers.push(...normalizeResponseBodySerializers(serializers));
315
+ return this;
316
+ }
317
+ /**
318
+ * Replace the inherited response body serializer list for this router subtree.
319
+ *
320
+ * @example
321
+ * ```ts
322
+ * router.setResponseBodySerializers([jsonResponseBodySerializer()])
323
+ * ```
324
+ *
325
+ * @param serializers - Exact response body serializers to use for this router subtree.
326
+ * @returns The router instance for chaining.
327
+ */
328
+ setResponseBodySerializers(serializers) {
329
+ this._responseBodySerializers = normalizeResponseBodySerializers(serializers);
330
+ this._responseBodySerializersMode = "replace";
331
+ return this;
332
+ }
333
+ /**
334
+ * Set the logger for this router subtree.
335
+ *
336
+ * @example
337
+ * ```ts
338
+ * router.setLogger(logger)
339
+ * ```
340
+ *
341
+ * @param logger - Logger exposed through request contexts and used for internal server errors.
342
+ * @returns The router instance for chaining.
343
+ */
344
+ setLogger(logger) {
345
+ this._logger = logger;
346
+ return this;
347
+ }
348
+ use(middleware) {
349
+ const list = normalizeMiddlewareList(middleware);
350
+ this._middleware.push(...list);
351
+ return this;
352
+ }
353
+ useRequest(middleware) {
354
+ this._requestMiddleware.push(...normalizeRequestMiddlewareList(middleware));
355
+ return this;
356
+ }
357
+ mount(prefixOrOptionsOrRouter, routerOrConfigure) {
358
+ const mountOptions = typeof prefixOrOptionsOrRouter === "string" ? { prefix: prefixOrOptionsOrRouter } : prefixOrOptionsOrRouter instanceof Router || typeof prefixOrOptionsOrRouter === "function" ? {} : prefixOrOptionsOrRouter;
359
+ const target = prefixOrOptionsOrRouter instanceof Router || typeof prefixOrOptionsOrRouter === "function" ? prefixOrOptionsOrRouter : routerOrConfigure;
360
+ if (target instanceof Router && !mountOptions.middleware) {
361
+ this._entries.push({
362
+ kind: "router",
363
+ prefix: mountOptions.prefix,
364
+ router: target
365
+ });
366
+ return this;
367
+ }
368
+ const scope = new Router();
369
+ if (mountOptions.middleware) scope.use(normalizeMiddlewareList(mountOptions.middleware));
370
+ if (target instanceof Router) scope.mount(target);
371
+ else target?.(scope);
372
+ this._entries.push({
373
+ kind: "router",
374
+ prefix: mountOptions.prefix,
375
+ router: scope
376
+ });
377
+ return this;
378
+ }
379
+ /**
380
+ * Add a route definition to this router.
381
+ *
382
+ * @example
383
+ * ```ts
384
+ * router.add({method: HttpMethod.POST, path: '/items', handler: ({request}) => new Response(request.url)})
385
+ * ```
386
+ *
387
+ * @param route - Route definition to normalize and register.
388
+ * @returns The router instance for chaining.
389
+ */
390
+ add(route) {
391
+ this._entries.push({
392
+ kind: "route",
393
+ route: normalizeRoute(route),
394
+ middleware: normalizeMiddlewareList(route.middleware)
395
+ });
396
+ return this;
397
+ }
398
+ _addMethod(method, path, input) {
399
+ return this.add({
400
+ ...isHandler(input) ? { handler: input } : input,
401
+ path,
402
+ method
403
+ });
404
+ }
405
+ get(path, input) {
406
+ return this._addMethod(HttpMethod.GET, path, input);
407
+ }
408
+ head(path, input) {
409
+ return this._addMethod(HttpMethod.HEAD, path, input);
410
+ }
411
+ post(path, input) {
412
+ return this._addMethod(HttpMethod.POST, path, input);
413
+ }
414
+ put(path, input) {
415
+ return this._addMethod(HttpMethod.PUT, path, input);
416
+ }
417
+ delete(path, input) {
418
+ return this._addMethod(HttpMethod.DELETE, path, input);
419
+ }
420
+ patch(path, input) {
421
+ return this._addMethod(HttpMethod.PATCH, path, input);
422
+ }
423
+ /**
424
+ * Return a flattened list of registered routes.
425
+ *
426
+ * @example
427
+ * ```ts
428
+ * const routes = router.getRoutes()
429
+ * ```
430
+ *
431
+ * @returns Array of normalized routes.
432
+ */
433
+ getRoutes() {
434
+ return this._getRoutes([]);
435
+ }
436
+ _getRoutes(parentMiddleware) {
437
+ const routes = [];
438
+ const middleware = [...parentMiddleware, ...this._middleware];
439
+ for (const entry of this._entries) {
440
+ if (entry.kind === "route") {
441
+ const schema = mergeRouteSchemas(...[...middleware, ...entry.middleware].flatMap((value) => isDeclaredMiddleware(value) && (!value.schemaAppliesTo || value.schemaAppliesTo(entry.route)) ? [value.schema] : []), entry.route.schema);
442
+ routes.push({
443
+ ...entry.route,
444
+ ...schema === void 0 ? {} : { schema }
445
+ });
446
+ continue;
447
+ }
448
+ for (const subRoute of entry.router._getRoutes(middleware)) {
449
+ if (!entry.prefix) {
450
+ routes.push(subRoute);
451
+ continue;
452
+ }
453
+ const path = new URLPattern({ pathname: joinPrefixPathname(entry.prefix, subRoute.path.pathname) });
454
+ routes.push({
455
+ ...subRoute,
456
+ path
457
+ });
458
+ }
459
+ }
460
+ return routes;
461
+ }
462
+ _defaultRuntimeConfig() {
463
+ return {
464
+ requestBodyParsers: defaultRequestBodyParsers(),
465
+ responseBodySerializers: defaultResponseBodySerializers(),
466
+ logger: this._defaultLogger,
467
+ requestMiddleware: []
468
+ };
469
+ }
470
+ _handlerRegistration(handler) {
471
+ return handler ? {
472
+ handler,
473
+ router: this
474
+ } : void 0;
475
+ }
476
+ _resolveRuntimeConfig(parent) {
477
+ return {
478
+ requestBodyParsers: this._requestBodyParsersMode === "replace" ? [...this._requestBodyParsers] : [...parent.requestBodyParsers, ...this._requestBodyParsers],
479
+ responseBodySerializers: this._responseBodySerializersMode === "replace" ? [...this._responseBodySerializers] : [...parent.responseBodySerializers, ...this._responseBodySerializers],
480
+ logger: this._logger ?? parent.logger,
481
+ requestMiddleware: [...parent.requestMiddleware, ...this._requestMiddleware],
482
+ onNotFoundHandler: this._handlerRegistration(this._onNotFoundHandler) ?? parent.onNotFoundHandler,
483
+ onMethodNotAllowedHandler: this._handlerRegistration(this._onMethodNotAllowedHandler) ?? parent.onMethodNotAllowedHandler,
484
+ onUnsupportedMediaTypeHandler: this._handlerRegistration(this._onUnsupportedMediaTypeHandler) ?? parent.onUnsupportedMediaTypeHandler,
485
+ onInternalErrorHandler: this._handlerRegistration(this._onInternalErrorHandler) ?? parent.onInternalErrorHandler
486
+ };
487
+ }
488
+ _match(request, method, url, parentConfig) {
489
+ if (method === HttpMethod.HEAD) {
490
+ const headOnly = this._matchWithMethodCheck(request, url, (routeMethod) => this._methodMatches(routeMethod, HttpMethod.HEAD), parentConfig, []);
491
+ if (headOnly.match) return headOnly.match;
492
+ const getOnly = this._matchWithMethodCheck(request, url, (routeMethod) => this._methodMatches(routeMethod, HttpMethod.GET), parentConfig, []);
493
+ if (getOnly.match) return getOnly.match;
494
+ if (headOnly.unsupportedMediaType) return {
495
+ kind: "unsupported_media_type",
496
+ found: headOnly.unsupportedMediaType
497
+ };
498
+ if (getOnly.unsupportedMediaType) return {
499
+ kind: "unsupported_media_type",
500
+ found: getOnly.unsupportedMediaType
501
+ };
502
+ const methodNotAllowedConfig = headOnly.methodNotAllowedConfig ?? getOnly.methodNotAllowedConfig;
503
+ if (methodNotAllowedConfig) return {
504
+ kind: "method_not_allowed",
505
+ config: methodNotAllowedConfig
506
+ };
507
+ return {
508
+ kind: "not_found",
509
+ config: headOnly.notFoundConfig
510
+ };
511
+ }
512
+ const match = this._matchWithMethodCheck(request, url, (routeMethod) => this._methodMatches(routeMethod, method), parentConfig, []);
513
+ if (match.match) return match.match;
514
+ if (match.unsupportedMediaType) return {
515
+ kind: "unsupported_media_type",
516
+ found: match.unsupportedMediaType
517
+ };
518
+ if (match.methodNotAllowedConfig) return {
519
+ kind: "method_not_allowed",
520
+ config: match.methodNotAllowedConfig
521
+ };
522
+ return {
523
+ kind: "not_found",
524
+ config: match.notFoundConfig
525
+ };
526
+ }
527
+ _collectAllowedMethods(url) {
528
+ const methods = /* @__PURE__ */ new Set();
529
+ function addMethods(routeMethod) {
530
+ if (!routeMethod) return;
531
+ const normalized = Array.isArray(routeMethod) ? routeMethod : [routeMethod];
532
+ for (const method of normalized) methods.add(method);
533
+ }
534
+ function visit(entries, currentUrl) {
535
+ for (const entry of entries) {
536
+ if (entry.kind === "route") {
537
+ if (entry.route.match) continue;
538
+ if (!entry.route.path.exec(currentUrl)) continue;
539
+ addMethods(entry.route.method);
540
+ continue;
541
+ }
542
+ if (entry.prefix) {
543
+ const subPathname = stripPrefixPathname(entry.prefix, currentUrl.pathname);
544
+ if (subPathname == null) continue;
545
+ const subUrl = new URL(currentUrl.toString());
546
+ subUrl.pathname = subPathname;
547
+ visit(entry.router._entries, subUrl);
548
+ } else visit(entry.router._entries, currentUrl);
549
+ }
550
+ }
551
+ visit(this._entries, url);
552
+ return methods;
553
+ }
554
+ _acceptMatches(route, request) {
555
+ if (!route.accept || route.accept.length === 0) return true;
556
+ const contentTypeHeader = request.headers.get(CommonHeaders.CONTENT_TYPE);
557
+ if (!contentTypeHeader) return false;
558
+ const contentType = parseContentType(contentTypeHeader);
559
+ if (!contentType) return false;
560
+ return route.accept.some((accept) => mediaTypeMatches(accept, contentType));
561
+ }
562
+ _formatAllowMethods(methods) {
563
+ const order = [
564
+ HttpMethod.GET,
565
+ HttpMethod.HEAD,
566
+ HttpMethod.POST,
567
+ HttpMethod.PUT,
568
+ HttpMethod.PATCH,
569
+ HttpMethod.DELETE,
570
+ HttpMethod.OPTIONS
571
+ ];
572
+ const ordered = [];
573
+ for (const method of order) if (methods.has(method)) ordered.push(method);
574
+ const remaining = [...methods].filter((method) => !order.includes(method)).sort();
575
+ return [...ordered, ...remaining].join(", ");
576
+ }
577
+ _buildHandlerContext(request, url, path, config, routePath) {
578
+ return {
579
+ request,
580
+ url,
581
+ path,
582
+ logger: config.logger.withContext({
583
+ "http.request.method": request.method,
584
+ "url.path": url.pathname,
585
+ ...routePath == null ? {} : { "http.route": routePath }
586
+ })
587
+ };
588
+ }
589
+ _runRequestMiddleware(middleware, ctx, respond) {
590
+ let index = -1;
591
+ const dispatch = async (nextIndex) => {
592
+ if (nextIndex <= index) throw new Error("next() called multiple times");
593
+ index = nextIndex;
594
+ const current = middleware[nextIndex];
595
+ if (current == null) return nextIndex >= middleware.length ? await respond() : await dispatch(nextIndex + 1);
596
+ return await current(ctx, () => dispatch(nextIndex + 1));
597
+ };
598
+ return dispatch(0);
599
+ }
600
+ async _executeHandler(handler, middleware, ctx, router, request, config) {
601
+ const result = await this._run(handler, middleware, ctx, router);
602
+ if (result instanceof Response) return result;
603
+ if (this._isAsyncGenerator(result)) {
604
+ const response = await this._responseFromGenerator(result, request, config);
605
+ if (response) return response;
606
+ return new Response(null, { status: HttpStatus.CLIENT_CLOSED_REQUEST });
607
+ }
608
+ return await this._finalizeResult(result, request, config);
609
+ }
610
+ _statusResponse(status, request) {
611
+ const result = text(StatusText[status] ?? `HTTP Status ${status}`, { status });
612
+ return new Response(request?.method.toUpperCase() === HttpMethod.HEAD ? null : result.body, {
613
+ status,
614
+ headers: result.headers
615
+ });
616
+ }
617
+ _logInternalServerError(error, logger) {
618
+ try {
619
+ logger.error("Routekit internal server error", error);
620
+ } catch {}
621
+ }
622
+ async _finalizeResult(result, request, config) {
623
+ if (result instanceof Response) return result;
624
+ if (this._isAsyncGenerator(result)) {
625
+ const response = await this._responseFromGenerator(result, request, config);
626
+ if (response) return response;
627
+ return new Response(null, { status: HttpStatus.CLIENT_CLOSED_REQUEST });
628
+ }
629
+ if (isRoutekitResponse(result)) return await this._responseFromRoutekitResponse(result, request, config);
630
+ if (isRoutekitBody(result)) return await this._responseFromRoutekitResponse(response(result.value), request, config);
631
+ if (isResponseBodyInit(result)) {
632
+ const body = this._toResponseBody(result);
633
+ const headers = new Headers();
634
+ this._setContentLength(headers, body);
635
+ return new Response(body, {
636
+ status: HttpStatus.OK,
637
+ headers
638
+ });
639
+ }
640
+ return await this._responseFromRoutekitResponse(response(result), request, config);
641
+ }
642
+ async _responseFromRoutekitResponse(result, request, config) {
643
+ const isHead = request.method.toUpperCase() === HttpMethod.HEAD;
644
+ const headers = new Headers(result.headers);
645
+ const status = result.status;
646
+ if (headers.has(CommonHeaders.CONTENT_TYPE)) {
647
+ if (!isResponseBodyInit(result.body)) throw new TypeError("Routekit response has Content-Type set, so body must be a native Response body.");
648
+ const body = this._toResponseBody(result.body);
649
+ this._setContentLength(headers, body);
650
+ return new Response(isHead ? null : body, {
651
+ status,
652
+ headers
653
+ });
654
+ }
655
+ if (result.body === void 0) return new Response(null, {
656
+ status,
657
+ headers
658
+ });
659
+ const serializer = this._selectSerializer(result.body, request, config);
660
+ if (!serializer) return this._statusResponse(HttpStatus.NOT_ACCEPTABLE, request);
661
+ headers.set(CommonHeaders.CONTENT_TYPE, serializer.mediaType);
662
+ this._appendVary(headers, CommonHeaders.ACCEPT);
663
+ const body = await serializer.serializer.serialize(result.body);
664
+ this._setContentLength(headers, body);
665
+ return new Response(isHead ? null : body, {
666
+ status,
667
+ headers
668
+ });
669
+ }
670
+ _appendVary(headers, value) {
671
+ const current = headers.get(CommonHeaders.VARY);
672
+ if (!current) {
673
+ headers.set(CommonHeaders.VARY, value);
674
+ return;
675
+ }
676
+ if (current.split(",").map((entry) => entry.trim().toLowerCase()).includes(value.toLowerCase())) return;
677
+ headers.set(CommonHeaders.VARY, `${current}, ${value}`);
678
+ }
679
+ _selectSerializer(body, request, config) {
680
+ const accepted = parseAcceptHeader(request.headers.get(CommonHeaders.ACCEPT) ?? "*/*");
681
+ const ranges = accepted.length ? accepted : parseAcceptHeader("*/*");
682
+ let best = null;
683
+ for (const [acceptIndex, accept] of ranges.entries()) {
684
+ if (accept.q === 0) continue;
685
+ for (const [serializerIndex, serializer] of config.responseBodySerializers.entries()) {
686
+ if (serializer.canSerialize && !serializer.canSerialize(body)) continue;
687
+ for (const [mediaTypeIndex, mediaTypeRange] of serializer.mediaTypes.entries()) {
688
+ const mediaTypeQuality = mediaRangeQuality(mediaTypeRange);
689
+ if (mediaTypeQuality === 0) continue;
690
+ if (mediaRangeAccepts(accept, mediaTypeRange)) {
691
+ const mediaType = mediaRangeToContentType(mediaTypeRange);
692
+ if (!mediaType) continue;
693
+ const candidate = {
694
+ serializer,
695
+ mediaType,
696
+ acceptQuality: accept.q,
697
+ mediaTypeQuality,
698
+ acceptIndex,
699
+ serializerIndex,
700
+ mediaTypeIndex
701
+ };
702
+ if (this._isBetterSerializerCandidate(candidate, best)) best = candidate;
703
+ }
704
+ }
705
+ }
706
+ }
707
+ return best ? {
708
+ serializer: best.serializer,
709
+ mediaType: best.mediaType
710
+ } : null;
711
+ }
712
+ _isBetterSerializerCandidate(candidate, best) {
713
+ if (!best) return true;
714
+ if (candidate.acceptQuality !== best.acceptQuality) return candidate.acceptQuality > best.acceptQuality;
715
+ if (candidate.mediaTypeQuality !== best.mediaTypeQuality) return candidate.mediaTypeQuality > best.mediaTypeQuality;
716
+ if (candidate.acceptIndex !== best.acceptIndex) return candidate.acceptIndex < best.acceptIndex;
717
+ if (candidate.serializerIndex !== best.serializerIndex) return candidate.serializerIndex < best.serializerIndex;
718
+ return candidate.mediaTypeIndex < best.mediaTypeIndex;
719
+ }
720
+ async _tryCustomHandler(registration, ctx, config, request) {
721
+ if (!registration) return null;
722
+ try {
723
+ return await this._executeHandler(registration.handler, [], ctx, registration.router, request, config);
724
+ } catch (error) {
725
+ if (error instanceof RequestBodyError) return responseFromRequestBodyError(error);
726
+ this._logInternalServerError(error, ctx.logger);
727
+ return this._statusResponse(HttpStatus.INTERNAL_SERVER_ERROR, request);
728
+ }
729
+ }
730
+ _requestWithBodyParsers(request, config) {
731
+ return createRoutekitRequest(request.raw, request.url, config.requestBodyParsers);
732
+ }
733
+ async _handleNotFound(request, url, config) {
734
+ request = this._requestWithBodyParsers(request, config);
735
+ const ctx = this._buildHandlerContext(request, url, {}, config);
736
+ return await this._runRequestMiddleware(config.requestMiddleware, ctx, async () => {
737
+ const custom = await this._tryCustomHandler(config.onNotFoundHandler, ctx, config, request);
738
+ if (custom) return custom;
739
+ return this._statusResponse(HttpStatus.NOT_FOUND, request);
740
+ });
741
+ }
742
+ async _handleMethodNotAllowed(request, url, config) {
743
+ request = this._requestWithBodyParsers(request, config);
744
+ const ctx = this._buildHandlerContext(request, url, {}, config);
745
+ return await this._runRequestMiddleware(config.requestMiddleware, ctx, async () => {
746
+ const custom = await this._tryCustomHandler(config.onMethodNotAllowedHandler, ctx, config, request);
747
+ if (custom) return custom;
748
+ return this._statusResponse(HttpStatus.METHOD_NOT_ALLOWED, request);
749
+ });
750
+ }
751
+ async _handleInternalError(config, ctx, request, error) {
752
+ if (error instanceof RequestBodyError) return responseFromRequestBodyError(error);
753
+ this._logInternalServerError(error, ctx.logger);
754
+ const custom = await this._tryCustomHandler(config.onInternalErrorHandler, ctx, config, request);
755
+ if (custom) return custom;
756
+ return this._statusResponse(HttpStatus.INTERNAL_SERVER_ERROR, request);
757
+ }
758
+ async _handleUnsupportedMediaType(found, request, url) {
759
+ request = this._requestWithBodyParsers(request, found.config);
760
+ const rawPathParams = found.match?.pathname.groups ?? {};
761
+ const path = Object.fromEntries(Object.entries(rawPathParams).filter(([, value]) => value !== void 0));
762
+ const handlerCtx = this._buildHandlerContext(request, url, path, found.config, found.routePath);
763
+ return await this._runRequestMiddleware(found.config.requestMiddleware, handlerCtx, async () => {
764
+ const custom = await this._tryCustomHandler(found.config.onUnsupportedMediaTypeHandler, handlerCtx, found.config, request);
765
+ if (custom) return custom;
766
+ return this._statusResponse(HttpStatus.UNSUPPORTED_MEDIA_TYPE, request);
767
+ });
768
+ }
769
+ async _handleMatch(found, request, url) {
770
+ request = this._requestWithBodyParsers(request, found.config);
771
+ const rawPathParams = found.match?.pathname.groups ?? {};
772
+ const path = Object.fromEntries(Object.entries(rawPathParams).filter(([, value]) => value !== void 0));
773
+ const handlerCtx = this._buildHandlerContext(request, url, path, found.config, found.routePath);
774
+ return await this._runRequestMiddleware(found.config.requestMiddleware, handlerCtx, async () => {
775
+ try {
776
+ mergeRouteSchemas(...found.middleware.flatMap((value) => isDeclaredMiddleware(value) && (!value.schemaAppliesTo || value.schemaAppliesTo(found.route)) ? [value.schema] : []), found.route.schema);
777
+ return await this._executeHandler(found.route.handler, found.middleware, handlerCtx, found.router, request, found.config);
778
+ } catch (error) {
779
+ return await this._handleInternalError(found.config, handlerCtx, request, error);
780
+ }
781
+ });
782
+ }
783
+ _methodMatches(routeMethod, method) {
784
+ if (!routeMethod) return true;
785
+ return (Array.isArray(routeMethod) ? routeMethod : [routeMethod]).some((routeValue) => routeValue === method);
786
+ }
787
+ _withRoutePrefix(match, prefix) {
788
+ return prefix == null ? match : {
789
+ ...match,
790
+ routePath: joinPrefixPathname(prefix, match.routePath)
791
+ };
792
+ }
793
+ _matchWithMethodCheck(request, url, methodCheck, parentConfig, parentMiddleware) {
794
+ const config = this._resolveRuntimeConfig(parentConfig);
795
+ const middleware = [...parentMiddleware, ...this._middleware];
796
+ let methodNotAllowedConfig = null;
797
+ let unsupportedMediaType = null;
798
+ let notFoundConfig = config;
799
+ for (const entry of this._entries) {
800
+ if (entry.kind === "route") {
801
+ const route = entry.route;
802
+ const routeMiddleware = [...middleware, ...entry.middleware];
803
+ const pathMatch = route.path.exec(url);
804
+ if (route.match) {
805
+ if (!route.match(request)) continue;
806
+ return {
807
+ match: {
808
+ kind: "match",
809
+ route,
810
+ routePath: route.path.pathname,
811
+ match: pathMatch,
812
+ middleware: routeMiddleware,
813
+ router: this,
814
+ config
815
+ },
816
+ unsupportedMediaType,
817
+ methodNotAllowedConfig,
818
+ notFoundConfig
819
+ };
820
+ }
821
+ if (!pathMatch) continue;
822
+ if (!methodCheck(route.method)) {
823
+ methodNotAllowedConfig ??= config;
824
+ continue;
825
+ }
826
+ if (!this._acceptMatches(route, request)) {
827
+ unsupportedMediaType ??= {
828
+ kind: "match",
829
+ route,
830
+ routePath: route.path.pathname,
831
+ match: pathMatch,
832
+ middleware: routeMiddleware,
833
+ router: this,
834
+ config
835
+ };
836
+ continue;
837
+ }
838
+ return {
839
+ match: {
840
+ kind: "match",
841
+ route,
842
+ routePath: route.path.pathname,
843
+ match: pathMatch,
844
+ middleware: routeMiddleware,
845
+ router: this,
846
+ config
847
+ },
848
+ unsupportedMediaType,
849
+ methodNotAllowedConfig,
850
+ notFoundConfig
851
+ };
852
+ }
853
+ if (entry.prefix) {
854
+ const subPathname = stripPrefixPathname(entry.prefix, url.pathname);
855
+ if (subPathname == null) continue;
856
+ const subUrl = new URL(url.toString());
857
+ subUrl.pathname = subPathname;
858
+ const subMatch = entry.router._matchWithMethodCheck(request, subUrl, methodCheck, config, middleware);
859
+ if (subMatch.match) return {
860
+ match: this._withRoutePrefix(subMatch.match, entry.prefix),
861
+ unsupportedMediaType,
862
+ methodNotAllowedConfig,
863
+ notFoundConfig
864
+ };
865
+ if (subMatch.unsupportedMediaType && !unsupportedMediaType) unsupportedMediaType = this._withRoutePrefix(subMatch.unsupportedMediaType, entry.prefix);
866
+ methodNotAllowedConfig ??= subMatch.methodNotAllowedConfig;
867
+ notFoundConfig = subMatch.notFoundConfig;
868
+ } else {
869
+ const subMatch = entry.router._matchWithMethodCheck(request, url, methodCheck, config, middleware);
870
+ if (subMatch.match) return {
871
+ match: subMatch.match,
872
+ unsupportedMediaType,
873
+ methodNotAllowedConfig,
874
+ notFoundConfig
875
+ };
876
+ if (subMatch.unsupportedMediaType && !unsupportedMediaType) unsupportedMediaType = subMatch.unsupportedMediaType;
877
+ methodNotAllowedConfig ??= subMatch.methodNotAllowedConfig;
878
+ notFoundConfig = subMatch.notFoundConfig;
879
+ }
880
+ }
881
+ return {
882
+ match: null,
883
+ unsupportedMediaType,
884
+ methodNotAllowedConfig,
885
+ notFoundConfig
886
+ };
887
+ }
888
+ _run(handler, middleware, ctx, router) {
889
+ let idx = -1;
890
+ const dispatch = async (i) => {
891
+ if (i <= idx) throw new Error("next() called multiple times");
892
+ idx = i;
893
+ if (i === middleware.length) return await Promise.resolve().then(() => handler.call(router, ctx));
894
+ const mw = middleware[i];
895
+ if (!mw) return await dispatch(i + 1);
896
+ if (isDeclaredMiddleware(mw)) return await mw(ctx, () => dispatch(i + 1));
897
+ await mw(ctx);
898
+ return await dispatch(i + 1);
899
+ };
900
+ return dispatch(0);
901
+ }
902
+ _isAsyncGenerator(value) {
903
+ return !!value && typeof value[Symbol.asyncIterator] === "function";
904
+ }
905
+ async _closeGenerator(generator) {
906
+ try {
907
+ await generator.return?.(void 0);
908
+ } catch {}
909
+ }
910
+ _toResponseBody(body) {
911
+ if (body == null) return null;
912
+ return body;
913
+ }
914
+ _setContentLength(headers, body) {
915
+ if (headers.has(CommonHeaders.CONTENT_LENGTH) || body == null) return;
916
+ const size = this._getBodyByteLength(body);
917
+ if (size != null) headers.set(CommonHeaders.CONTENT_LENGTH, String(size));
918
+ }
919
+ _getBodyByteLength(body) {
920
+ if (typeof body === "string") return new TextEncoder().encode(body).byteLength;
921
+ if (body instanceof Blob) return body.size;
922
+ if (body instanceof URLSearchParams) return new TextEncoder().encode(body.toString()).byteLength;
923
+ if (body instanceof ArrayBuffer) return body.byteLength;
924
+ if (ArrayBuffer.isView(body)) return body.byteLength;
925
+ }
926
+ _isBodyChunk(value) {
927
+ return typeof value === "string" || value instanceof Uint8Array;
928
+ }
929
+ _toBodyChunk(value) {
930
+ if (typeof value === "string") return new TextEncoder().encode(value);
931
+ return value;
932
+ }
933
+ _mergeHeaders(target, source) {
934
+ source.forEach((value, key) => target.set(key, value));
935
+ }
936
+ async _frameChunk(value, framer) {
937
+ if (!framer) {
938
+ if (!this._isBodyChunk(value)) throw new TypeError("Generator yielded a structured chunk without stream(). Use stream(sseFramer()) or yield raw string/bytes chunks.");
939
+ return this._toBodyChunk(value);
940
+ }
941
+ if (framer.canFrame && !framer.canFrame(value)) throw new TypeError("Stream framer cannot encode the yielded chunk.");
942
+ return this._toBodyChunk(await framer.frame(value));
943
+ }
944
+ async _responseFromGenerator(generator, request, config) {
945
+ const isHead = request.method.toUpperCase() === HttpMethod.HEAD;
946
+ let status;
947
+ let statusSet = false;
948
+ const headers = new Headers();
949
+ let framer;
950
+ let bodyStream;
951
+ let bodyController;
952
+ let responseResolved = false;
953
+ let resolveResponse;
954
+ let rejectResponse;
955
+ const responsePromise = new Promise((resolve, reject) => {
956
+ resolveResponse = resolve;
957
+ rejectResponse = reject;
958
+ });
959
+ let abortListener;
960
+ const abortSignal = request.signal;
961
+ const abortPromise = abortSignal ? new Promise((resolve) => {
962
+ if (abortSignal.aborted) {
963
+ resolve("aborted");
964
+ return;
965
+ }
966
+ abortListener = () => resolve("aborted");
967
+ abortSignal.addEventListener("abort", abortListener, { once: true });
968
+ }) : null;
969
+ if (abortSignal?.aborted) {
970
+ await this._closeGenerator(generator);
971
+ return null;
972
+ }
973
+ const resolveResponseOnce = (response) => {
974
+ if (responseResolved) return;
975
+ responseResolved = true;
976
+ resolveResponse?.(response);
977
+ };
978
+ const rejectResponseOnce = (error) => {
979
+ if (responseResolved) {
980
+ bodyController?.error?.(error);
981
+ return;
982
+ }
983
+ responseResolved = true;
984
+ rejectResponse?.(error);
985
+ };
986
+ const buildResponseInit = () => ({
987
+ status: status ?? HttpStatus.OK,
988
+ headers
989
+ });
990
+ const ensureStreamResponse = () => {
991
+ if (responseResolved) return;
992
+ if (!bodyStream) bodyStream = new ReadableStream({ start(controller) {
993
+ bodyController = controller;
994
+ } });
995
+ resolveResponseOnce(new Response(bodyStream, buildResponseInit()));
996
+ };
997
+ const setStatus = (value) => {
998
+ if (bodyStream) throw new TypeError("Generator cannot set status after streaming has started.");
999
+ if (statusSet) throw new TypeError("Generator response status was already set.");
1000
+ status = value;
1001
+ statusSet = true;
1002
+ };
1003
+ const mergeHeaders = (value) => {
1004
+ if (bodyStream) throw new TypeError("Generator cannot set headers after streaming has started.");
1005
+ this._mergeHeaders(headers, value);
1006
+ };
1007
+ const finalizeReturnedValue = async (value) => {
1008
+ if (isRoutekitResponse(value)) {
1009
+ if (statusSet) throw new TypeError("Generator returned a Routekit response after status was already set.");
1010
+ const mergedHeaders = new Headers(headers);
1011
+ this._mergeHeaders(mergedHeaders, value.headers);
1012
+ return await this._responseFromRoutekitResponse(response(value.body, {
1013
+ status: value.status,
1014
+ headers: mergedHeaders
1015
+ }), request, config);
1016
+ }
1017
+ if (value instanceof Response) {
1018
+ if (statusSet || [...headers.keys()].length > 0) throw new TypeError("Generator returned a native Response after response metadata was already set.");
1019
+ return value;
1020
+ }
1021
+ const returnedBody = isRoutekitBody(value) ? value.value : value;
1022
+ return await this._responseFromRoutekitResponse(response(returnedBody, {
1023
+ status: status ?? HttpStatus.OK,
1024
+ headers
1025
+ }), request, config);
1026
+ };
1027
+ try {
1028
+ const pump = async () => {
1029
+ try {
1030
+ while (true) {
1031
+ const next = abortPromise ? await Promise.race([generator.next(), abortPromise]) : await generator.next();
1032
+ if (next === "aborted") {
1033
+ await this._closeGenerator(generator);
1034
+ bodyController?.error?.(/* @__PURE__ */ new Error("Request aborted"));
1035
+ resolveResponseOnce(null);
1036
+ return;
1037
+ }
1038
+ if (next.done) {
1039
+ if (bodyStream) {
1040
+ if (next.value !== void 0) throw new TypeError("Generator cannot return a final body after yielding chunks.");
1041
+ if (framer?.close && !isHead) {
1042
+ const finalChunk = await framer.close();
1043
+ if (finalChunk !== void 0) bodyController?.enqueue(this._toBodyChunk(finalChunk));
1044
+ }
1045
+ bodyController?.close();
1046
+ if (!responseResolved) resolveResponseOnce(new Response(bodyStream, buildResponseInit()));
1047
+ return;
1048
+ }
1049
+ resolveResponseOnce(await finalizeReturnedValue(next.value));
1050
+ return;
1051
+ }
1052
+ const yielded = next.value;
1053
+ if (yielded === void 0) continue;
1054
+ if (isStatusDirective(yielded)) {
1055
+ setStatus(yielded.status);
1056
+ continue;
1057
+ }
1058
+ if (isHeadersDirective(yielded)) {
1059
+ mergeHeaders(yielded.headers);
1060
+ if (isHead) {
1061
+ await this._closeGenerator(generator);
1062
+ resolveResponseOnce(new Response(null, buildResponseInit()));
1063
+ return;
1064
+ }
1065
+ continue;
1066
+ }
1067
+ if (isHeadDirective(yielded)) {
1068
+ setStatus(yielded.status);
1069
+ mergeHeaders(yielded.headers);
1070
+ if (isHead) {
1071
+ await this._closeGenerator(generator);
1072
+ resolveResponseOnce(new Response(null, buildResponseInit()));
1073
+ return;
1074
+ }
1075
+ continue;
1076
+ }
1077
+ if (isStreamDirective(yielded)) {
1078
+ if (bodyStream) throw new TypeError("Generator cannot select a stream framer after streaming has started.");
1079
+ framer = yielded.framer;
1080
+ mergeHeaders(yielded.headers);
1081
+ headers.set(CommonHeaders.CONTENT_TYPE, framer.contentType);
1082
+ if (isHead) {
1083
+ await this._closeGenerator(generator);
1084
+ resolveResponseOnce(new Response(null, buildResponseInit()));
1085
+ return;
1086
+ }
1087
+ continue;
1088
+ }
1089
+ if (isChunkDirective(yielded)) {
1090
+ if (isHead) {
1091
+ await this._closeGenerator(generator);
1092
+ resolveResponseOnce(new Response(null, buildResponseInit()));
1093
+ return;
1094
+ }
1095
+ ensureStreamResponse();
1096
+ bodyController?.enqueue(await this._frameChunk(yielded.value, framer));
1097
+ continue;
1098
+ }
1099
+ throw new TypeError("Generator yielded an unsupported value. Use status(), headers(), head(), stream(), or chunk().");
1100
+ }
1101
+ } catch (err) {
1102
+ rejectResponseOnce(err);
1103
+ }
1104
+ };
1105
+ pump();
1106
+ return await responsePromise;
1107
+ } finally {
1108
+ if (abortSignal && abortListener) abortSignal.removeEventListener("abort", abortListener);
1109
+ }
1110
+ }
1111
+ async _handleRequest(request) {
1112
+ const url = new URL(request.url);
1113
+ const method = request.method.toUpperCase();
1114
+ const routekitRequest = createRoutekitRequest(request, url, this._resolveRuntimeConfig(this._defaultRuntimeConfig()).requestBodyParsers);
1115
+ if (method === HttpMethod.OPTIONS) {
1116
+ const explicit = this._match(routekitRequest, method, url, this._defaultRuntimeConfig());
1117
+ if (explicit.kind === "match") return await this._handleMatch(explicit, routekitRequest, url);
1118
+ if (explicit.kind === "unsupported_media_type") return await this._handleUnsupportedMediaType(explicit.found, routekitRequest, url);
1119
+ const allowedMethods = this._collectAllowedMethods(url);
1120
+ if (allowedMethods.size === 0) return await this._handleNotFound(routekitRequest, url, explicit.config);
1121
+ if (allowedMethods.has(HttpMethod.GET)) allowedMethods.add(HttpMethod.HEAD);
1122
+ allowedMethods.add(HttpMethod.OPTIONS);
1123
+ const optionsRequest = this._requestWithBodyParsers(routekitRequest, explicit.config);
1124
+ const ctx = this._buildHandlerContext(optionsRequest, url, {}, explicit.config);
1125
+ return await this._runRequestMiddleware(explicit.config.requestMiddleware, ctx, async () => new Response(null, {
1126
+ status: HttpStatus.NO_CONTENT,
1127
+ headers: { "access-control-allow-methods": this._formatAllowMethods(allowedMethods) }
1128
+ }));
1129
+ }
1130
+ const found = this._match(routekitRequest, method, url, this._defaultRuntimeConfig());
1131
+ if (found.kind === "not_found") return await this._handleNotFound(routekitRequest, url, found.config);
1132
+ if (found.kind === "method_not_allowed") return await this._handleMethodNotAllowed(routekitRequest, url, found.config);
1133
+ if (found.kind === "unsupported_media_type") return await this._handleUnsupportedMediaType(found.found, routekitRequest, url);
1134
+ return await this._handleMatch(found, routekitRequest, url);
1135
+ }
1136
+ /**
1137
+ * Handle an incoming request by matching routes and executing middleware.
1138
+ *
1139
+ * @example
1140
+ * ```ts
1141
+ * const response = await router.fetch(new Request('https://example.com/'))
1142
+ * ```
1143
+ *
1144
+ * @param request - Incoming request to route.
1145
+ * @returns The response produced by the matched handler.
1146
+ */
1147
+ fetch = (request) => {
1148
+ return this._handleRequest(request);
1149
+ };
1150
+ };
1151
+ //#endregion
1152
+ export { RequestBodyError, RequestBodyLengthMismatchError, RequestBodyTooLargeError, Router, UnsupportedRequestBodyMediaTypeError, body, chunk, createAsyncStream, createRoutekitRequest, createStartStream, defaultRequestBodyParsers, defaultResponseBodySerializers, defineMiddleware, formDataRequestBodyParser, head, headers, isChunkDirective, isHeadDirective, isHeadersDirective, isResponseBodyInit, isRoutekitBody, isRoutekitDirective, isRoutekitResponse, isStatusDirective, isStreamDirective, jsonLinesFramer, jsonRequestBodyParser, jsonResponseBodySerializer, response, responseFromRequestBodyError, sseFramer, status, stream, textRequestBodyParser, urlEncodedRequestBodyParser };