@adonisjs/http-server 8.1.2 → 8.2.0

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.
@@ -0,0 +1,1511 @@
1
+ import { t as createURL } from "./helpers-Dqw8abku.js";
2
+ import { createRequire } from "node:module";
3
+ import { InvalidArgumentsException, RuntimeException } from "@poppinss/utils/exception";
4
+ import { debuglog } from "node:util";
5
+ import { serialize } from "cookie-es";
6
+ import matchit from "@poppinss/matchit";
7
+ import string from "@poppinss/utils/string";
8
+ import { moduleCaller, moduleImporter, parseBindingReference } from "@adonisjs/fold";
9
+ import Cache from "tmp-cache";
10
+ import Macroable from "@poppinss/macroable";
11
+ import is from "@sindresorhus/is";
12
+ import Middleware from "@poppinss/middleware";
13
+ import diagnostics_channel from "node:diagnostics_channel";
14
+ import StringBuilder from "@poppinss/utils/string_builder";
15
+ import encodeUrl from "encodeurl";
16
+ import mime from "mime-types";
17
+ //#region \0rolldown/runtime.js
18
+ var __create = Object.create;
19
+ var __defProp = Object.defineProperty;
20
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
21
+ var __getOwnPropNames = Object.getOwnPropertyNames;
22
+ var __getProtoOf = Object.getPrototypeOf;
23
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
24
+ var __commonJSMin = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
25
+ var __exportAll = (all, no_symbols) => {
26
+ let target = {};
27
+ for (var name in all) __defProp(target, name, {
28
+ get: all[name],
29
+ enumerable: true
30
+ });
31
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
32
+ return target;
33
+ };
34
+ var __copyProps = (to, from, except, desc) => {
35
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
36
+ key = keys[i];
37
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
38
+ get: ((k) => from[k]).bind(null, key),
39
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
40
+ });
41
+ }
42
+ return to;
43
+ };
44
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
45
+ value: mod,
46
+ enumerable: true
47
+ }) : target, mod));
48
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
49
+ //#endregion
50
+ //#region src/debug.ts
51
+ /**
52
+ * Debug logger instance for the AdonisJS HTTP server package.
53
+ *
54
+ * This debug logger can be enabled by setting the NODE_DEBUG environment variable
55
+ * to include 'adonisjs:http'. When enabled, it will output detailed debugging
56
+ * information about HTTP server operations including route matching, middleware
57
+ * execution, and response generation.
58
+ *
59
+ * @example
60
+ * ```bash
61
+ * NODE_DEBUG=adonisjs:http node ace serve
62
+ * ```
63
+ */
64
+ var debug_default = debuglog("adonisjs:http");
65
+ //#endregion
66
+ //#region src/router/factories/use_return_value.ts
67
+ /**
68
+ * Check if the value can be used to write the response body. Returns
69
+ * false when the response body has already been set
70
+ * @param value - The value to check
71
+ * @param ctx - The HTTP context instance
72
+ * @returns True if value can be used for response body
73
+ */
74
+ function canWriteResponseBody(value, ctx) {
75
+ return value !== void 0 && !ctx.response.hasLazyBody && value !== ctx.response;
76
+ }
77
+ /**
78
+ * A factory function that uses the return value of the request
79
+ * pipeline as the response
80
+ * @param ctx - The HTTP context instance
81
+ * @returns Function that handles return values
82
+ */
83
+ function useReturnValue(ctx) {
84
+ return function(value) {
85
+ if (canWriteResponseBody(value, ctx)) ctx.response.send(value);
86
+ };
87
+ }
88
+ //#endregion
89
+ //#region src/tracing_channels.ts
90
+ var tracing_channels_exports = /* @__PURE__ */ __exportAll({
91
+ httpExceptionHandler: () => httpExceptionHandler,
92
+ httpMiddleware: () => httpMiddleware,
93
+ httpRequest: () => httpRequest,
94
+ httpResponseSerializer: () => httpResponseSerializer,
95
+ httpRouteHandler: () => httpRouteHandler
96
+ });
97
+ /**
98
+ * Traces every HTTP request handled by the {@link Server} class.
99
+ */
100
+ const httpRequest = diagnostics_channel.tracingChannel("adonisjs.http.request");
101
+ /**
102
+ * Traces middleware executed during the HTTP request
103
+ */
104
+ const httpMiddleware = diagnostics_channel.tracingChannel("adonisjs.http.middleware");
105
+ /**
106
+ * Traces the exception handler that converts errors into HTTP responses
107
+ */
108
+ const httpExceptionHandler = diagnostics_channel.tracingChannel("adonisjs.http.exception.handler");
109
+ /**
110
+ * Traces route handler executed during the HTTP request
111
+ */
112
+ const httpRouteHandler = diagnostics_channel.tracingChannel("adonisjs.http.route.handler");
113
+ /**
114
+ * Traces non-stream and non-file download responses written by the AdonisJS
115
+ * response class
116
+ */
117
+ const httpResponseSerializer = diagnostics_channel.tracingChannel("adonisjs.http.response.serializer");
118
+ //#endregion
119
+ //#region src/router/executor.ts
120
+ /**
121
+ * Executor to execute the route middleware pipeline the route
122
+ * handler
123
+ * @param route - The route JSON object containing route information
124
+ * @param resolver - Container resolver for dependency injection
125
+ * @param ctx - The HTTP context instance
126
+ * @param errorResponder - Error handler function for handling errors
127
+ */
128
+ function execute(route, resolver, ctx, errorResponder) {
129
+ return route.middleware.runner().errorHandler((error) => errorResponder(error, ctx)).finalHandler(() => {
130
+ if (typeof route.handler === "function") return httpRouteHandler.tracePromise(($ctx) => Promise.resolve(route.handler($ctx)), httpRouteHandler.hasSubscribers ? { route } : void 0, void 0, ctx).then(useReturnValue(ctx));
131
+ return httpRouteHandler.tracePromise(route.handler.handle, httpRouteHandler.hasSubscribers ? { route } : void 0, void 0, resolver, ctx).then(useReturnValue(ctx));
132
+ }).run(async (middleware, next) => {
133
+ if (typeof middleware === "function") return httpMiddleware.tracePromise(middleware, httpMiddleware.hasSubscribers ? { middleware } : void 0, void 0, ctx, next);
134
+ return httpMiddleware.tracePromise(middleware.handle, httpMiddleware.hasSubscribers ? { middleware } : void 0, void 0, resolver, ctx, next, middleware.args);
135
+ });
136
+ }
137
+ //#endregion
138
+ //#region src/router/route.ts
139
+ /**
140
+ * The Route class provides a fluent API for constructing and configuring HTTP routes.
141
+ *
142
+ * Routes define how HTTP requests are handled by mapping URL patterns and HTTP methods
143
+ * to controller actions or inline handlers. This class supports middleware application,
144
+ * parameter validation, naming, and various other route-specific configurations.
145
+ *
146
+ * @example
147
+ * ```ts
148
+ * const route = new Route(app, middleware, {
149
+ * pattern: '/users/:id',
150
+ * methods: ['GET'],
151
+ * handler: 'UsersController.show'
152
+ * })
153
+ *
154
+ * route
155
+ * .where('id', /^[0-9]+$/)
156
+ * .middleware(['auth'])
157
+ * .as('users.show')
158
+ * ```
159
+ */
160
+ var Route = class extends Macroable {
161
+ /**
162
+ * The URL pattern for this route. May contain dynamic parameters
163
+ * prefixed with a colon (e.g., '/users/:id').
164
+ */
165
+ #pattern;
166
+ /**
167
+ * Array of HTTP methods this route responds to (e.g., ['GET', 'POST']).
168
+ */
169
+ #methods;
170
+ /**
171
+ * A unique identifier for the route, used for URL generation and
172
+ * route referencing.
173
+ */
174
+ #name;
175
+ /**
176
+ * Flag indicating whether the route should be excluded from registration
177
+ * in the route store. This must be set before calling Router.commit().
178
+ */
179
+ #isDeleted = false;
180
+ /**
181
+ * The handler function or controller method that processes requests
182
+ * matching this route.
183
+ */
184
+ #handler;
185
+ /**
186
+ * Route parameter matchers inherited from the global router configuration.
187
+ * These are applied to all routes unless overridden locally.
188
+ */
189
+ #globalMatchers;
190
+ /**
191
+ * Reference to the AdonisJS application instance, used for module
192
+ * resolution and dependency injection.
193
+ */
194
+ #app;
195
+ /**
196
+ * Global middleware registered on the router that applies to this route.
197
+ */
198
+ #routerMiddleware;
199
+ /**
200
+ * The domain this route belongs to. Defaults to 'root' when no specific
201
+ * domain is configured.
202
+ */
203
+ #routeDomain = "root";
204
+ /**
205
+ * Route-specific parameter matchers that validate dynamic segments.
206
+ * Populated via the where() method and takes precedence over global matchers.
207
+ */
208
+ #matchers = {};
209
+ /**
210
+ * Stack of URL prefixes applied to this route, typically inherited from
211
+ * route groups. Prefixes are applied in reverse order during pattern computation.
212
+ */
213
+ #prefixes = [];
214
+ /**
215
+ * Multi-dimensional array of middleware, where each nested array represents
216
+ * a layer in the middleware stack. This structure maintains the order of
217
+ * middleware application from groups and direct assignments.
218
+ */
219
+ #middleware = [];
220
+ /**
221
+ * Creates a new Route instance
222
+ * @param app - The AdonisJS application instance
223
+ * @param routerMiddleware - Array of global middleware registered on the router
224
+ * @param options - Configuration options for the route
225
+ */
226
+ constructor(app, routerMiddleware, options) {
227
+ super();
228
+ this.#app = app;
229
+ this.#routerMiddleware = routerMiddleware;
230
+ this.#pattern = options.pattern;
231
+ this.#methods = options.methods;
232
+ this.#globalMatchers = options.globalMatchers;
233
+ const { handler, routeName } = this.#resolveRouteHandle(options.handler);
234
+ this.#handler = handler;
235
+ this.#name = routeName;
236
+ }
237
+ /**
238
+ * Resolves the route handler from various input formats into a normalized
239
+ * StoreRouteHandler object. Supports string references, inline functions,
240
+ * class constructors, and lazy imports.
241
+ *
242
+ * @param handler - The handler in one of the supported formats:
243
+ * - String: 'Controller.method' or 'Controller' (defaults to 'handle')
244
+ * - Function: Inline route handler function
245
+ * - Tuple: [Controller class or lazy import, optional method name]
246
+ */
247
+ #resolveRouteHandle(handler) {
248
+ /**
249
+ * Convert magic string to handle method call
250
+ */
251
+ if (typeof handler === "string") {
252
+ const parts = handler.split(".");
253
+ const method = parts.length === 1 ? "handle" : parts.pop();
254
+ const moduleRefId = parts.join(".");
255
+ const controllerName = new StringBuilder(moduleRefId.split("/").pop()).removeSuffix("controller").snakeCase();
256
+ return {
257
+ handler: {
258
+ method,
259
+ reference: handler,
260
+ importExpression: moduleRefId,
261
+ ...moduleImporter(() => this.#app.import(moduleRefId), method).toHandleMethod(),
262
+ name: handler
263
+ },
264
+ routeName: method === "handle" ? controllerName.toString() : `${controllerName}.${string.snakeCase(method)}`
265
+ };
266
+ }
267
+ /**
268
+ * Using a lazily imported controller
269
+ */
270
+ if (Array.isArray(handler)) {
271
+ const controller = handler[0];
272
+ const method = handler[1] ?? "handle";
273
+ /**
274
+ * The first item of the tuple is a class constructor
275
+ */
276
+ if (is.class(controller)) {
277
+ const controllerName = new StringBuilder(controller.name).removeSuffix("controller").snakeCase();
278
+ return {
279
+ handler: {
280
+ method,
281
+ reference: handler,
282
+ importExpression: null,
283
+ ...moduleCaller(controller, method).toHandleMethod()
284
+ },
285
+ routeName: method === "handle" ? controllerName.toString() : `${controllerName}.${string.snakeCase(method)}`
286
+ };
287
+ }
288
+ const controllerName = controller.name ? new StringBuilder(controller.name).removeSuffix("controller").snakeCase() : void 0;
289
+ /**
290
+ * The first item of the tuple is a function that lazily
291
+ * loads the controller
292
+ */
293
+ return {
294
+ handler: {
295
+ method,
296
+ reference: handler,
297
+ importExpression: String(controller),
298
+ ...moduleImporter(controller, method).toHandleMethod()
299
+ },
300
+ routeName: controllerName ? method === "handle" ? controllerName.toString() : `${controllerName}.${string.snakeCase(method)}` : void 0
301
+ };
302
+ }
303
+ return { handler };
304
+ }
305
+ /**
306
+ * Merges global and route-specific parameter matchers into a single object.
307
+ * Local matchers take precedence over global matchers when conflicts occur.
308
+ */
309
+ #getMatchers() {
310
+ return {
311
+ ...this.#globalMatchers,
312
+ ...this.#matchers
313
+ };
314
+ }
315
+ /**
316
+ * Computes the final route pattern by applying all prefixes from route groups.
317
+ * Prefixes are applied in reverse order (innermost group first) and normalized
318
+ * to remove leading/trailing slashes.
319
+ */
320
+ #computePattern() {
321
+ const pattern = dropSlash(this.#pattern);
322
+ const prefix = this.#prefixes.slice().reverse().map((one) => dropSlash(one)).join("");
323
+ return prefix ? `${prefix}${pattern === "/" ? "" : pattern}` : pattern;
324
+ }
325
+ /**
326
+ * Returns the route's handler configuration object.
327
+ */
328
+ getHandler() {
329
+ return this.#handler;
330
+ }
331
+ /**
332
+ * Defines a validation matcher for a route parameter. Route-level matchers
333
+ * take precedence over group-level matchers to ensure routes can override
334
+ * group constraints.
335
+ *
336
+ * @param param - The name of the route parameter to validate
337
+ * @param matcher - The validation pattern as a string, RegExp, or RouteMatcher object
338
+ *
339
+ * @example
340
+ * ```ts
341
+ * // Validate that 'id' is numeric
342
+ * route.where('id', /^[0-9]+$/)
343
+ *
344
+ * // Using a string pattern
345
+ * route.where('slug', '[a-z0-9-]+')
346
+ *
347
+ * // Route matcher takes precedence over group matcher
348
+ * Route.group(() => {
349
+ * Route.get('/:id', 'handler').where('id', /^[0-9]$/)
350
+ * }).where('id', /[^a-z$]/)
351
+ * // The route's /^[0-9]$/ wins over the group's matcher
352
+ * ```
353
+ */
354
+ where(param, matcher) {
355
+ if (this.#matchers[param]) return this;
356
+ if (typeof matcher === "string") this.#matchers[param] = { match: new RegExp(matcher) };
357
+ else if (is.regExp(matcher)) this.#matchers[param] = { match: matcher };
358
+ else this.#matchers[param] = matcher;
359
+ return this;
360
+ }
361
+ /**
362
+ * Adds a URL prefix to the route pattern. Multiple calls stack prefixes
363
+ * which are applied in reverse order during pattern computation.
364
+ *
365
+ * @param prefix - The URL prefix to prepend to the route pattern
366
+ *
367
+ * @example
368
+ * ```ts
369
+ * route.prefix('/api').prefix('/v1')
370
+ * // Results in pattern: /v1/api/users (for original pattern /users)
371
+ * ```
372
+ */
373
+ prefix(prefix) {
374
+ this.#prefixes.push(prefix);
375
+ return this;
376
+ }
377
+ /**
378
+ * Assigns a custom domain to the route. By default, routes belong to the
379
+ * 'root' domain. Once set, the domain is not overwritten unless the
380
+ * overwrite flag is true.
381
+ *
382
+ * @param domain - The domain identifier for this route
383
+ * @param overwrite - Whether to overwrite an existing non-root domain
384
+ *
385
+ * @example
386
+ * ```ts
387
+ * route.domain('api.example.com')
388
+ *
389
+ * // Overwrite existing domain
390
+ * route.domain('new.example.com', true)
391
+ * ```
392
+ */
393
+ domain(domain, overwrite = false) {
394
+ if (this.#routeDomain === "root" || overwrite) this.#routeDomain = domain;
395
+ return this;
396
+ }
397
+ /**
398
+ * Registers one or more middleware to execute before the route handler.
399
+ * Middleware can be inline functions or named middleware references registered
400
+ * with the router's middleware store.
401
+ *
402
+ * @param middleware - Single middleware or array of middleware to apply
403
+ *
404
+ * @example
405
+ * ```ts
406
+ * // Single middleware
407
+ * route.use(async (ctx, next) => {
408
+ * console.log('Before handler')
409
+ * await next()
410
+ * })
411
+ *
412
+ * // Multiple middleware
413
+ * route.use(['auth', 'admin'])
414
+ * ```
415
+ */
416
+ use(middleware) {
417
+ this.#middleware.push(Array.isArray(middleware) ? middleware : [middleware]);
418
+ return this;
419
+ }
420
+ /**
421
+ * Alias for the {@link Route.use} method.
422
+ *
423
+ * @param middleware - Single middleware or array of middleware to apply
424
+ */
425
+ middleware(middleware) {
426
+ return this.use(middleware);
427
+ }
428
+ /**
429
+ * Assigns a unique name to the route for use in URL generation and route
430
+ * referencing. Assigning a new name replaces any existing name unless
431
+ * prepend is true.
432
+ *
433
+ * @param name - The route name to assign
434
+ * @param prepend - If true, prepends the name to the existing name with a dot separator
435
+ *
436
+ * @example
437
+ * ```ts
438
+ * // Set route name
439
+ * route.as('users.show')
440
+ *
441
+ * // Prepend to existing name (typically used by route groups)
442
+ * route.as('admin', true) // Results in 'admin.users.show'
443
+ * ```
444
+ */
445
+ as(name, prepend = false) {
446
+ if (prepend) {
447
+ if (!this.#name) throw new RuntimeException(`Routes inside a group must have names before calling "router.group.as"`);
448
+ this.#name = `${name}.${this.#name}`;
449
+ return this;
450
+ }
451
+ this.#name = name;
452
+ return this;
453
+ }
454
+ /**
455
+ * Checks whether the route has been marked for deletion. Deleted routes
456
+ * are excluded from the route store during registration.
457
+ */
458
+ isDeleted() {
459
+ return this.#isDeleted;
460
+ }
461
+ /**
462
+ * Marks the route for deletion. Deleted routes will not be registered
463
+ * with the route store when Router.commit() is called.
464
+ *
465
+ * @example
466
+ * ```ts
467
+ * const route = Route.get('/admin', 'handler')
468
+ * route.markAsDeleted()
469
+ * // This route will not be registered
470
+ * ```
471
+ */
472
+ markAsDeleted() {
473
+ this.#isDeleted = true;
474
+ }
475
+ /**
476
+ * Returns the unique name assigned to the route, if any.
477
+ */
478
+ getName() {
479
+ return this.#name;
480
+ }
481
+ /**
482
+ * Returns the route's URL pattern with dynamic parameters.
483
+ */
484
+ getPattern() {
485
+ return this.#pattern;
486
+ }
487
+ /**
488
+ * Updates the route's URL pattern.
489
+ *
490
+ * @param pattern - The new URL pattern to assign to the route
491
+ *
492
+ * @example
493
+ * ```ts
494
+ * route.setPattern('/users/:id/posts/:postId')
495
+ * ```
496
+ */
497
+ setPattern(pattern) {
498
+ this.#pattern = pattern;
499
+ return this;
500
+ }
501
+ /**
502
+ * Returns the multi-dimensional middleware stack registered on this route.
503
+ * The returned value is shared by reference, not a copy.
504
+ */
505
+ getMiddleware() {
506
+ return this.#middleware;
507
+ }
508
+ /**
509
+ * Constructs a frozen Middleware instance for storage. This combines global
510
+ * router middleware with route-specific middleware in the correct execution order.
511
+ * The middleware is frozen to prevent modifications after route registration.
512
+ */
513
+ #getMiddlewareForStore() {
514
+ const middleware = new Middleware();
515
+ this.#routerMiddleware.forEach((one) => {
516
+ debug_default("adding global middleware to route %s, %O", this.#pattern, one);
517
+ middleware.add(one);
518
+ });
519
+ this.#middleware.flat().forEach((one) => {
520
+ debug_default("adding named middleware to route %s, %O", this.#pattern, one);
521
+ middleware.add(one);
522
+ });
523
+ middleware.freeze();
524
+ return middleware;
525
+ }
526
+ /**
527
+ * Serializes the route into a JSON representation suitable for storage and
528
+ * execution. This includes the computed pattern with prefixes, merged matchers,
529
+ * parsed route tokens, and frozen middleware stack.
530
+ *
531
+ * @example
532
+ * ```ts
533
+ * const json = route.toJSON()
534
+ * console.log(json.pattern) // '/api/users/:id'
535
+ * console.log(json.methods) // ['GET']
536
+ * console.log(json.name) // 'users.show'
537
+ * ```
538
+ */
539
+ toJSON() {
540
+ const pattern = this.#computePattern();
541
+ const matchers = this.#getMatchers();
542
+ return {
543
+ domain: this.#routeDomain,
544
+ pattern,
545
+ matchers,
546
+ tokens: parseRoute(pattern, matchers),
547
+ meta: {},
548
+ name: this.#name,
549
+ handler: this.#handler,
550
+ methods: this.#methods,
551
+ middleware: this.#getMiddlewareForStore(),
552
+ execute
553
+ };
554
+ }
555
+ };
556
+ //#endregion
557
+ //#region src/router/brisk.ts
558
+ /**
559
+ * Brisk routes exposes the API to configure the route handler by chaining
560
+ * one of the pre-defined methods.
561
+ *
562
+ * For example: Instead of defining the redirect logic as a callback, one can
563
+ * chain the `.redirect` method.
564
+ *
565
+ * Brisk routes are always registered under the `GET` HTTP method.
566
+ */
567
+ var BriskRoute = class extends Macroable {
568
+ /**
569
+ * Route pattern
570
+ */
571
+ #pattern;
572
+ /**
573
+ * Matchers inherited from the router
574
+ */
575
+ #globalMatchers;
576
+ /**
577
+ * Reference to the AdonisJS application
578
+ */
579
+ #app;
580
+ /**
581
+ * Middleware registered on the router
582
+ */
583
+ #routerMiddleware;
584
+ /**
585
+ * Reference to route instance. Set after `setHandler` is called
586
+ */
587
+ route = null;
588
+ /**
589
+ * Creates a new BriskRoute instance
590
+ * @param app - The AdonisJS application instance
591
+ * @param routerMiddleware - Array of global middleware registered on the router
592
+ * @param options - Configuration options for the brisk route
593
+ */
594
+ constructor(app, routerMiddleware, options) {
595
+ super();
596
+ this.#app = app;
597
+ this.#routerMiddleware = routerMiddleware;
598
+ this.#pattern = options.pattern;
599
+ this.#globalMatchers = options.globalMatchers;
600
+ }
601
+ /**
602
+ * Set handler for the brisk route
603
+ * @param handler - The route handler function
604
+ * @returns The created route instance
605
+ */
606
+ setHandler(handler) {
607
+ this.route = new Route(this.#app, this.#routerMiddleware, {
608
+ pattern: this.#pattern,
609
+ globalMatchers: this.#globalMatchers,
610
+ methods: ["GET", "HEAD"],
611
+ handler
612
+ });
613
+ return this.route;
614
+ }
615
+ /**
616
+ * Redirects to a given route. Params from the original request will
617
+ * be used when no custom params are defined.
618
+ * @param args - Route identifier, parameters, and options for building the redirect URL
619
+ * @returns The created route instance
620
+ */
621
+ redirect(...args) {
622
+ const [identifier, params, options] = args;
623
+ function redirectsToRoute(ctx) {
624
+ const redirector = ctx.response.redirect();
625
+ if (options?.status) redirector.status(options.status);
626
+ return redirector.toRoute(identifier, params || ctx.params, options);
627
+ }
628
+ Object.defineProperty(redirectsToRoute, "listArgs", {
629
+ value: identifier,
630
+ writable: false
631
+ });
632
+ return this.setHandler(redirectsToRoute);
633
+ }
634
+ /**
635
+ * Redirect request to a fixed URL
636
+ * @param url - The URL to redirect to
637
+ * @param options - Optional redirect options including HTTP status code
638
+ * @returns The created route instance
639
+ */
640
+ redirectToPath(url, options) {
641
+ function redirectsToPath(ctx) {
642
+ const redirector = ctx.response.redirect();
643
+ if (options?.status) redirector.status(options.status);
644
+ return redirector.toPath(url);
645
+ }
646
+ Object.defineProperty(redirectsToPath, "listArgs", {
647
+ value: url,
648
+ writable: false
649
+ });
650
+ return this.setHandler(redirectsToPath);
651
+ }
652
+ };
653
+ //#endregion
654
+ //#region src/router/resource.ts
655
+ /**
656
+ * Route resource exposes the API to register multiple routes for a resource.
657
+ */
658
+ var RouteResource = class extends Macroable {
659
+ /**
660
+ * Resource identifier. Nested resources are separated
661
+ * with a dot notation
662
+ */
663
+ #resource;
664
+ /**
665
+ * The controller to handle resource routing requests
666
+ */
667
+ #controller;
668
+ /**
669
+ * Is it a shallow resource? Shallow resources URLs do not have parent
670
+ * resource name and id once they can be identified with the id.
671
+ */
672
+ #shallow = false;
673
+ /**
674
+ * Matchers inherited from the router
675
+ */
676
+ #globalMatchers;
677
+ /**
678
+ * Reference to the AdonisJS application
679
+ */
680
+ #app;
681
+ /**
682
+ * Middleware registered on the router
683
+ */
684
+ #routerMiddleware;
685
+ /**
686
+ * Parameter names for the resources. Defaults to `id` for
687
+ * a singular resource and `resource_id` for nested
688
+ * resources.
689
+ */
690
+ #params = {};
691
+ /**
692
+ * Base name for the routes. We suffix action names
693
+ * on top of the base name
694
+ */
695
+ #routesBaseName;
696
+ /**
697
+ * A collection of routes instances that belongs to this resource
698
+ */
699
+ routes = [];
700
+ /**
701
+ * Creates a new RouteResource instance
702
+ * @param app - The AdonisJS application instance
703
+ * @param routerMiddleware - Array of global middleware registered on the router
704
+ * @param options - Configuration options for the route resource
705
+ */
706
+ constructor(app, routerMiddleware, options) {
707
+ super();
708
+ this.#validateResourceName(options.resource);
709
+ this.#app = app;
710
+ this.#shallow = options.shallow;
711
+ this.#routerMiddleware = routerMiddleware;
712
+ this.#controller = options.controller;
713
+ this.#globalMatchers = options.globalMatchers;
714
+ this.#resource = this.#normalizeResourceName(options.resource);
715
+ this.#routesBaseName = this.#getRoutesBaseName();
716
+ this.#buildRoutes();
717
+ }
718
+ /**
719
+ * Normalizes the resource name to dropping leading and trailing
720
+ * slashes.
721
+ */
722
+ #normalizeResourceName(resource) {
723
+ return resource.replace(/^\//, "").replace(/\/$/, "");
724
+ }
725
+ /**
726
+ * Ensure resource name is not an empty string
727
+ */
728
+ #validateResourceName(resource) {
729
+ if (!resource || resource === "/") throw new RuntimeException(`Invalid resource name "${resource}"`);
730
+ }
731
+ /**
732
+ * Converting segments of a resource to snake case to
733
+ * make the route name.
734
+ */
735
+ #getRoutesBaseName() {
736
+ return this.#resource.split(".").map((token) => string.snakeCase(token)).join(".");
737
+ }
738
+ /**
739
+ * Create a new route for the given pattern, methods and controller action
740
+ */
741
+ #createRoute(pattern, methods, action) {
742
+ const route = new Route(this.#app, this.#routerMiddleware, {
743
+ pattern,
744
+ methods,
745
+ handler: typeof this.#controller === "string" ? `${this.#controller}.${action}` : [this.#controller, action],
746
+ globalMatchers: this.#globalMatchers
747
+ });
748
+ route.as(`${this.#routesBaseName}.${action}`);
749
+ this.routes.push(route);
750
+ }
751
+ /**
752
+ * Returns the `resource_id` name for a given resource. The
753
+ * resource name is converted to singular form and
754
+ * transformed to snake case.
755
+ *
756
+ * photos becomes photo_id
757
+ * users becomes user_id
758
+ */
759
+ #getResourceId(resource) {
760
+ return `${string.snakeCase(string.singular(resource))}_id`;
761
+ }
762
+ /**
763
+ * Build routes for the given resource
764
+ */
765
+ #buildRoutes() {
766
+ const resources = this.#resource.split(".");
767
+ const mainResource = resources.pop();
768
+ this.#params[mainResource] = ":id";
769
+ const baseURI = `${resources.map((resource) => {
770
+ const paramName = `:${this.#getResourceId(resource)}`;
771
+ this.#params[resource] = paramName;
772
+ return `${resource}/${paramName}`;
773
+ }).join("/")}/${mainResource}`;
774
+ this.#createRoute(baseURI, ["GET", "HEAD"], "index");
775
+ this.#createRoute(`${baseURI}/create`, ["GET", "HEAD"], "create");
776
+ this.#createRoute(baseURI, ["POST"], "store");
777
+ this.#createRoute(`${this.#shallow ? mainResource : baseURI}/:id`, ["GET", "HEAD"], "show");
778
+ this.#createRoute(`${this.#shallow ? mainResource : baseURI}/:id/edit`, ["GET", "HEAD"], "edit");
779
+ this.#createRoute(`${this.#shallow ? mainResource : baseURI}/:id`, ["PUT", "PATCH"], "update");
780
+ this.#createRoute(`${this.#shallow ? mainResource : baseURI}/:id`, ["DELETE"], "destroy");
781
+ }
782
+ /**
783
+ * Filter the routes based on their partial names
784
+ */
785
+ #filter(names, inverse) {
786
+ const actions = Array.isArray(names) ? names : [names];
787
+ return this.routes.filter((route) => {
788
+ const match = actions.find((name) => route.getName().endsWith(name));
789
+ return inverse ? !match : match;
790
+ });
791
+ }
792
+ /**
793
+ * Register only given routes and remove others
794
+ * @param names - Array of action names to keep
795
+ * @returns Current RouteResource instance with filtered actions
796
+ */
797
+ only(names) {
798
+ this.#filter(names, true).forEach((route) => route.markAsDeleted());
799
+ return this;
800
+ }
801
+ /**
802
+ * Register all routes, except the one's defined
803
+ * @param names - Array of action names to exclude
804
+ * @returns Current RouteResource instance with filtered actions
805
+ */
806
+ except(names) {
807
+ this.#filter(names, false).forEach((route) => route.markAsDeleted());
808
+ return this;
809
+ }
810
+ /**
811
+ * Register api only routes. The `create` and `edit` routes, which
812
+ * are meant to show forms will not be registered
813
+ * @returns Current RouteResource instance without create and edit actions
814
+ */
815
+ apiOnly() {
816
+ return this.except(["create", "edit"]);
817
+ }
818
+ /**
819
+ * Define matcher for params inside the resource
820
+ * @param key - The parameter name to match
821
+ * @param matcher - The matcher pattern (RegExp, string, or RouteMatcher)
822
+ * @returns Current RouteResource instance for method chaining
823
+ */
824
+ where(key, matcher) {
825
+ this.routes.forEach((route) => {
826
+ route.where(key, matcher);
827
+ });
828
+ return this;
829
+ }
830
+ tap(actions, callback) {
831
+ if (typeof actions === "function") {
832
+ this.routes.forEach((route) => {
833
+ if (!route.isDeleted()) actions(route);
834
+ });
835
+ return this;
836
+ }
837
+ this.#filter(actions, false).forEach((route) => {
838
+ if (!route.isDeleted()) callback(route);
839
+ });
840
+ return this;
841
+ }
842
+ /**
843
+ * Set the param name for a given resource
844
+ * @param resources - Object mapping resource names to parameter names
845
+ * @returns Current RouteResource instance for method chaining
846
+ */
847
+ params(resources) {
848
+ Object.keys(resources).forEach((resource) => {
849
+ const param = resources[resource];
850
+ const existingParam = this.#params[resource];
851
+ this.#params[resource] = `:${param}`;
852
+ this.routes.forEach((route) => {
853
+ route.setPattern(route.getPattern().replace(`${resource}/${existingParam}`, `${resource}/:${param}`));
854
+ });
855
+ });
856
+ return this;
857
+ }
858
+ /**
859
+ * Define one or more middleware on the routes created by
860
+ * the resource.
861
+ *
862
+ * Calling this method multiple times will append middleware
863
+ * to existing list.
864
+ * @param actions - Action name(s) or '*' for all actions
865
+ * @param middleware - Middleware function(s) to apply
866
+ * @returns Current RouteResource instance for method chaining
867
+ */
868
+ use(actions, middleware) {
869
+ if (actions === "*") this.tap((route) => route.use(middleware));
870
+ else this.tap(actions, (route) => route.use(middleware));
871
+ return this;
872
+ }
873
+ /**
874
+ * Alias for {@link RouteResource.use}
875
+ * @param actions - Action name(s) or '*' for all actions
876
+ * @param middleware - Middleware function(s) to apply
877
+ * @returns Current RouteResource instance for method chaining
878
+ */
879
+ middleware(actions, middleware) {
880
+ return this.use(actions, middleware);
881
+ }
882
+ /**
883
+ * Prepend name to all the routes
884
+ * @param name - The name to prepend to all route names
885
+ * @param normalizeName - Whether to normalize the name to snake_case
886
+ * @returns Current RouteResource instance for method chaining
887
+ */
888
+ as(name, normalizeName = true) {
889
+ name = normalizeName ? string.snakeCase(name) : name;
890
+ this.routes.forEach((route) => {
891
+ route.as(route.getName().replace(this.#routesBaseName, name), false);
892
+ });
893
+ this.#routesBaseName = name;
894
+ return this;
895
+ }
896
+ };
897
+ //#endregion
898
+ //#region src/router/group.ts
899
+ /**
900
+ * Group class exposes the API to take action on a group of routes.
901
+ * The group routes must be pre-defined using the constructor.
902
+ */
903
+ var RouteGroup = class RouteGroup extends Macroable {
904
+ /**
905
+ * Array of middleware registered on the group.
906
+ */
907
+ #middleware = [];
908
+ /**
909
+ * Creates a new RouteGroup instance
910
+ * @param routes - Array of routes that belong to this group
911
+ */
912
+ constructor(routes) {
913
+ super();
914
+ this.routes = routes;
915
+ }
916
+ /**
917
+ * Shares midldeware stack with the routes. The method is invoked recursively
918
+ * to only register middleware with the route class and not with the
919
+ * resource or the child group
920
+ */
921
+ #shareMiddlewareStackWithRoutes(route) {
922
+ if (route instanceof RouteGroup) {
923
+ route.routes.forEach((child) => this.#shareMiddlewareStackWithRoutes(child));
924
+ return;
925
+ }
926
+ if (route instanceof RouteResource) {
927
+ route.routes.forEach((child) => child.getMiddleware().unshift(this.#middleware));
928
+ return;
929
+ }
930
+ if (route instanceof BriskRoute) {
931
+ route.route.getMiddleware().unshift(this.#middleware);
932
+ return;
933
+ }
934
+ route.getMiddleware().unshift(this.#middleware);
935
+ }
936
+ /**
937
+ * Updates the route name. The method is invoked recursively to only update
938
+ * the name with the route class and not with the resource or the child
939
+ * group.
940
+ */
941
+ #updateRouteName(route, name) {
942
+ if (route instanceof RouteGroup) {
943
+ route.routes.forEach((child) => this.#updateRouteName(child, name));
944
+ return;
945
+ }
946
+ if (route instanceof RouteResource) {
947
+ route.routes.forEach((child) => child.as(name, true));
948
+ return;
949
+ }
950
+ if (route instanceof BriskRoute) {
951
+ route.route.as(name, true);
952
+ return;
953
+ }
954
+ route.as(name, true);
955
+ }
956
+ /**
957
+ * Sets prefix on the route. The method is invoked recursively to only set
958
+ * the prefix with the route class and not with the resource or the
959
+ * child group.
960
+ */
961
+ #setRoutePrefix(route, prefix) {
962
+ if (route instanceof RouteGroup) {
963
+ route.routes.forEach((child) => this.#setRoutePrefix(child, prefix));
964
+ return;
965
+ }
966
+ if (route instanceof RouteResource) {
967
+ route.routes.forEach((child) => child.prefix(prefix));
968
+ return;
969
+ }
970
+ if (route instanceof BriskRoute) {
971
+ route.route.prefix(prefix);
972
+ return;
973
+ }
974
+ route.prefix(prefix);
975
+ }
976
+ /**
977
+ * Updates domain on the route. The method is invoked recursively to only update
978
+ * the domain with the route class and not with the resource or the child
979
+ * group.
980
+ */
981
+ #updateRouteDomain(route, domain) {
982
+ if (route instanceof RouteGroup) {
983
+ route.routes.forEach((child) => this.#updateRouteDomain(child, domain));
984
+ return;
985
+ }
986
+ if (route instanceof RouteResource) {
987
+ route.routes.forEach((child) => child.domain(domain));
988
+ return;
989
+ }
990
+ if (route instanceof BriskRoute) {
991
+ route.route.domain(domain, false);
992
+ return;
993
+ }
994
+ route.domain(domain, false);
995
+ }
996
+ /**
997
+ * Updates matchers on the route. The method is invoked recursively to only update
998
+ * the matchers with the route class and not with the resource or the child
999
+ * group.
1000
+ */
1001
+ #updateRouteMatchers(route, param, matcher) {
1002
+ if (route instanceof RouteGroup) {
1003
+ route.routes.forEach((child) => this.#updateRouteMatchers(child, param, matcher));
1004
+ return;
1005
+ }
1006
+ if (route instanceof RouteResource) {
1007
+ route.routes.forEach((child) => child.where(param, matcher));
1008
+ return;
1009
+ }
1010
+ if (route instanceof BriskRoute) {
1011
+ route.route.where(param, matcher);
1012
+ return;
1013
+ }
1014
+ route.where(param, matcher);
1015
+ }
1016
+ /**
1017
+ * Define route param matcher
1018
+ *
1019
+ * ```ts
1020
+ * Route.group(() => {
1021
+ * }).where('id', /^[0-9]+/)
1022
+ * ```
1023
+ * @param param - The parameter name to match
1024
+ * @param matcher - The matcher pattern (RegExp, string, or RouteMatcher)
1025
+ * @returns Current RouteGroup instance for method chaining
1026
+ */
1027
+ where(param, matcher) {
1028
+ this.routes.forEach((route) => this.#updateRouteMatchers(route, param, matcher));
1029
+ return this;
1030
+ }
1031
+ /**
1032
+ * Define prefix all the routes in the group.
1033
+ *
1034
+ * ```ts
1035
+ * Route.group(() => {
1036
+ * }).prefix('v1')
1037
+ * ```
1038
+ * @param prefix - The prefix to add to all routes in the group
1039
+ * @returns Current RouteGroup instance for method chaining
1040
+ */
1041
+ prefix(prefix) {
1042
+ this.routes.forEach((route) => this.#setRoutePrefix(route, prefix));
1043
+ return this;
1044
+ }
1045
+ /**
1046
+ * Define domain for all the routes.
1047
+ *
1048
+ * ```ts
1049
+ * Route.group(() => {
1050
+ * }).domain(':name.adonisjs.com')
1051
+ * ```
1052
+ * @param domain - The domain pattern for all routes in the group
1053
+ * @returns Current RouteGroup instance for method chaining
1054
+ */
1055
+ domain(domain) {
1056
+ this.routes.forEach((route) => this.#updateRouteDomain(route, domain));
1057
+ return this;
1058
+ }
1059
+ /**
1060
+ * Prepend name to the routes name.
1061
+ *
1062
+ * ```ts
1063
+ * Route.group(() => {
1064
+ * }).as('version1')
1065
+ * ```
1066
+ * @param name - The name to prepend to all route names in the group
1067
+ * @returns Current RouteGroup instance for method chaining
1068
+ */
1069
+ as(name) {
1070
+ this.routes.forEach((route) => this.#updateRouteName(route, name));
1071
+ return this;
1072
+ }
1073
+ /**
1074
+ * Prepend an array of middleware to all routes middleware.
1075
+ *
1076
+ * ```ts
1077
+ * Route.group(() => {
1078
+ * }).use(middleware.auth())
1079
+ * ```
1080
+ * @param middleware - Middleware function(s) to apply to all routes in the group
1081
+ * @returns Current RouteGroup instance for method chaining
1082
+ */
1083
+ use(middleware) {
1084
+ /**
1085
+ * Register middleware with children. We share the group middleware
1086
+ * array by reference, therefore have to register it only for the
1087
+ * first time.
1088
+ */
1089
+ if (!this.#middleware.length) this.routes.forEach((route) => this.#shareMiddlewareStackWithRoutes(route));
1090
+ if (Array.isArray(middleware)) for (let one of middleware) this.#middleware.push(one);
1091
+ else this.#middleware.push(middleware);
1092
+ return this;
1093
+ }
1094
+ /**
1095
+ * Alias for {@link RouteGroup.use}
1096
+ * @param middleware - Middleware function(s) to apply to all routes in the group
1097
+ * @returns Current RouteGroup instance for method chaining
1098
+ */
1099
+ middleware(middleware) {
1100
+ return this.use(middleware);
1101
+ }
1102
+ };
1103
+ //#endregion
1104
+ //#region src/utils.ts
1105
+ const proxyCache = new Cache({ max: 200 });
1106
+ /**
1107
+ * Makes input string consistent by having only the starting slash.
1108
+ *
1109
+ * Removes trailing slashes and ensures the path starts with a forward slash,
1110
+ * except for the root path '/' which remains unchanged.
1111
+ *
1112
+ * @param input - The input path string to normalize
1113
+ *
1114
+ * @example
1115
+ * ```ts
1116
+ * dropSlash('/users/') // '/users'
1117
+ * dropSlash('users') // '/users'
1118
+ * dropSlash('/') // '/'
1119
+ * ```
1120
+ */
1121
+ function dropSlash(input) {
1122
+ if (input === "/") return "/";
1123
+ return `/${input.replace(/^\//, "").replace(/\/$/, "")}`;
1124
+ }
1125
+ /**
1126
+ * Returns a flat list of routes from route groups, resources, and brisk routes.
1127
+ *
1128
+ * This function recursively processes route collections, extracting individual routes
1129
+ * from groups and resources while filtering out any deleted routes.
1130
+ *
1131
+ * @param routes - Array containing route groups, individual routes, resources, and brisk routes
1132
+ *
1133
+ * @example
1134
+ * ```ts
1135
+ * const flatRoutes = toRoutesJSON([
1136
+ * routeGroup,
1137
+ * singleRoute,
1138
+ * resourceRoutes
1139
+ * ])
1140
+ * ```
1141
+ */
1142
+ function toRoutesJSON(routes) {
1143
+ return routes.reduce((list, route) => {
1144
+ if (route instanceof RouteGroup) {
1145
+ list = list.concat(toRoutesJSON(route.routes));
1146
+ return list;
1147
+ }
1148
+ if (route instanceof RouteResource) {
1149
+ list = list.concat(toRoutesJSON(route.routes));
1150
+ return list;
1151
+ }
1152
+ if (route instanceof BriskRoute) {
1153
+ if (route.route && !route.route.isDeleted()) list.push(route.route.toJSON());
1154
+ return list;
1155
+ }
1156
+ if (!route.isDeleted()) list.push(route.toJSON());
1157
+ return list;
1158
+ }, []);
1159
+ }
1160
+ /**
1161
+ * Helper to determine if a remote address should be trusted.
1162
+ *
1163
+ * Uses caching to avoid repeated expensive proxy function calls for the same
1164
+ * remote address. The cache improves performance when the same addresses are
1165
+ * checked multiple times.
1166
+ *
1167
+ * @param remoteAddress - The remote IP address to check
1168
+ * @param proxyFn - Function that determines if an address should be trusted
1169
+ *
1170
+ * @example
1171
+ * ```ts
1172
+ * const isTrusted = trustProxy('192.168.1.1', proxyAddr.compile('loopback'))
1173
+ * ```
1174
+ */
1175
+ function trustProxy(remoteAddress, proxyFn) {
1176
+ if (proxyCache.has(remoteAddress)) return proxyCache.get(remoteAddress);
1177
+ const result = proxyFn(remoteAddress, 0);
1178
+ proxyCache.set(remoteAddress, result);
1179
+ return result;
1180
+ }
1181
+ /**
1182
+ * Parses a range expression (e.g., '200..299') into an object with numeric keys.
1183
+ *
1184
+ * Supports both single values and ranges. For ranges, all numbers between
1185
+ * the start and end (inclusive) are mapped to the provided value.
1186
+ *
1187
+ * @param range - Range expression as a string (e.g., '200', '200..299')
1188
+ * @param value - Value to assign to each number in the range
1189
+ *
1190
+ * @example
1191
+ * ```ts
1192
+ * parseRange('200', 'success') // { 200: 'success' }
1193
+ * parseRange('200..202', 'success') // { 200: 'success', 201: 'success', 202: 'success' }
1194
+ * ```
1195
+ */
1196
+ function parseRange(range, value) {
1197
+ const parts = range.split("..");
1198
+ const min = Number(parts[0]);
1199
+ const max = Number(parts[1]);
1200
+ /**
1201
+ * The ending status code does not exists
1202
+ */
1203
+ if (parts.length === 1 && !Number.isNaN(min)) return { [min]: value };
1204
+ /**
1205
+ * The starting status code is not a number
1206
+ */
1207
+ if (Number.isNaN(min) || Number.isNaN(max)) return {};
1208
+ /**
1209
+ * Min and max are same
1210
+ */
1211
+ if (min === max) return { [min]: value };
1212
+ /**
1213
+ * Max cannot be smaller than min
1214
+ */
1215
+ if (max < min) throw new InvalidArgumentsException(`Invalid range "${range}"`);
1216
+ /**
1217
+ * Loop over the range and create a collection
1218
+ * of status codes
1219
+ */
1220
+ return [...Array(max - min + 1).keys()].reduce((result, step) => {
1221
+ result[min + step] = value;
1222
+ return result;
1223
+ }, {});
1224
+ }
1225
+ /**
1226
+ * Decodes specific percent-encoded characters in URI components.
1227
+ *
1228
+ * This function handles decoding of specific character codes that are commonly
1229
+ * percent-encoded in URIs, such as %, #, $, &, +, etc.
1230
+ *
1231
+ * @param highCharCode - The high character code of the hex pair
1232
+ * @param lowCharCode - The low character code of the hex pair
1233
+ */
1234
+ function decodeComponentChar(highCharCode, lowCharCode) {
1235
+ if (highCharCode === 50) {
1236
+ if (lowCharCode === 53) return "%";
1237
+ if (lowCharCode === 51) return "#";
1238
+ if (lowCharCode === 52) return "$";
1239
+ if (lowCharCode === 54) return "&";
1240
+ if (lowCharCode === 66) return "+";
1241
+ if (lowCharCode === 98) return "+";
1242
+ if (lowCharCode === 67) return ",";
1243
+ if (lowCharCode === 99) return ",";
1244
+ if (lowCharCode === 70) return "/";
1245
+ if (lowCharCode === 102) return "/";
1246
+ return null;
1247
+ }
1248
+ if (highCharCode === 51) {
1249
+ if (lowCharCode === 65) return ":";
1250
+ if (lowCharCode === 97) return ":";
1251
+ if (lowCharCode === 66) return ";";
1252
+ if (lowCharCode === 98) return ";";
1253
+ if (lowCharCode === 68) return "=";
1254
+ if (lowCharCode === 100) return "=";
1255
+ if (lowCharCode === 70) return "?";
1256
+ if (lowCharCode === 102) return "?";
1257
+ return null;
1258
+ }
1259
+ if (highCharCode === 52 && lowCharCode === 48) return "@";
1260
+ return null;
1261
+ }
1262
+ /**
1263
+ * Safely decodes a URI path while handling special characters and query strings.
1264
+ *
1265
+ * This function carefully parses and decodes URI components, handling edge cases
1266
+ * like double-encoded characters and non-standard query string delimiters.
1267
+ * It separates the pathname from query parameters and determines whether
1268
+ * route parameters should be decoded.
1269
+ *
1270
+ * @param path - The URI path to decode
1271
+ * @param useSemicolonDelimiter - Whether to treat semicolons as query string delimiters
1272
+ *
1273
+ * @example
1274
+ * ```ts
1275
+ * const result = safeDecodeURI('/users/123?name=john', false)
1276
+ * // Returns: { pathname: '/users/123', query: 'name=john', shouldDecodeParam: false }
1277
+ * ```
1278
+ */
1279
+ function safeDecodeURI(path, useSemicolonDelimiter) {
1280
+ let shouldDecode = false;
1281
+ let shouldDecodeParam = false;
1282
+ let querystring = "";
1283
+ for (let i = 1; i < path.length; i++) {
1284
+ const charCode = path.charCodeAt(i);
1285
+ if (charCode === 37) {
1286
+ const highCharCode = path.charCodeAt(i + 1);
1287
+ const lowCharCode = path.charCodeAt(i + 2);
1288
+ if (decodeComponentChar(highCharCode, lowCharCode) === null) shouldDecode = true;
1289
+ else {
1290
+ shouldDecodeParam = true;
1291
+ if (highCharCode === 50 && lowCharCode === 53) {
1292
+ shouldDecode = true;
1293
+ path = path.slice(0, i + 1) + "25" + path.slice(i + 1);
1294
+ i += 2;
1295
+ }
1296
+ i += 2;
1297
+ }
1298
+ } else if (charCode === 63 || charCode === 35 || charCode === 59 && useSemicolonDelimiter) {
1299
+ querystring = path.slice(i + 1);
1300
+ path = path.slice(0, i);
1301
+ break;
1302
+ }
1303
+ }
1304
+ return {
1305
+ pathname: shouldDecode ? decodeURI(path) : path,
1306
+ query: querystring,
1307
+ shouldDecodeParam
1308
+ };
1309
+ }
1310
+ //#endregion
1311
+ //#region src/helpers.ts
1312
+ /**
1313
+ * Validates that a URL is safe to use as a redirect destination.
1314
+ *
1315
+ * - Relative URLs must start with `/` and not be protocol-relative (`//`)
1316
+ * - Absolute URLs must parse successfully and their host must match
1317
+ * `currentHost` or be listed in `allowedHosts`
1318
+ *
1319
+ * When `currentHost` and `allowedHosts` are omitted, absolute URLs
1320
+ * are accepted as long as they parse successfully.
1321
+ *
1322
+ * @param url - The URL to validate
1323
+ * @param currentHost - The current request's Host header value
1324
+ * @param allowedHosts - Array of additionally allowed hosts
1325
+ */
1326
+ function isValidRedirectUrl(url, currentHost, allowedHosts) {
1327
+ if (typeof url !== "string" || url.trim() === "") return false;
1328
+ if (url.startsWith("//")) return false;
1329
+ if (url.startsWith("/")) try {
1330
+ return new URL(url, "http://localhost").host === "localhost";
1331
+ } catch {
1332
+ return false;
1333
+ }
1334
+ try {
1335
+ const parsed = new URL(url);
1336
+ /**
1337
+ * When no host constraints are provided, accept any
1338
+ * parseable absolute URL
1339
+ */
1340
+ if (!currentHost && (!allowedHosts || allowedHosts.length === 0)) return true;
1341
+ if (currentHost && parsed.host === currentHost) return true;
1342
+ if (allowedHosts && allowedHosts.length > 0 && allowedHosts.includes(parsed.host)) return true;
1343
+ return false;
1344
+ } catch {
1345
+ return false;
1346
+ }
1347
+ }
1348
+ /**
1349
+ * Returns the previous URL from the request's `Referer` header,
1350
+ * validated against the request's `Host` header and an optional
1351
+ * list of allowed hosts using `isValidRedirectUrl`.
1352
+ *
1353
+ * @param headers - The incoming request headers
1354
+ * @param allowedHosts - Array of allowed referrer hosts
1355
+ * @param fallback - URL to return when referrer is missing or invalid
1356
+ */
1357
+ function getPreviousUrl(headers, allowedHosts, fallback) {
1358
+ let referrer = headers["referer"] || headers["referrer"];
1359
+ if (!referrer) return fallback;
1360
+ if (Array.isArray(referrer)) referrer = referrer[0];
1361
+ if (isValidRedirectUrl(referrer, headers["host"], allowedHosts)) return referrer;
1362
+ return fallback;
1363
+ }
1364
+ /**
1365
+ * Parse a route pattern into an array of tokens. These tokes can be used
1366
+ * to match routes, or print them with semantic information.
1367
+ *
1368
+ * Token types
1369
+ *
1370
+ * - 0: (static) segment
1371
+ * - 1: (parameter) segment
1372
+ * - 2: (wildcard) segment
1373
+ * - 3: (optional parameter) segment
1374
+ *
1375
+ * Value (val) refers to the segment value
1376
+ *
1377
+ * end refers to be the suffix or the segment (if any)
1378
+ *
1379
+ * @param pattern - The route pattern to parse
1380
+ * @param matchers - Optional route matchers
1381
+ * @returns {MatchItRouteToken[]} Array of parsed route tokens
1382
+ */
1383
+ function parseRoute(pattern, matchers) {
1384
+ return matchit.parse(pattern, matchers);
1385
+ }
1386
+ /**
1387
+ * Makes signed URL for a given route pattern using its parsed tokens. The
1388
+ * tokens could be generated using the "parseRoute" method.
1389
+ *
1390
+ * @param identifier - Route identifier
1391
+ * @param tokens - Array of parsed route tokens
1392
+ * @param searchParamsStringifier - Function to stringify query parameters
1393
+ * @param encryption - Encryption instance for signing
1394
+ * @param params - Route parameters as array or object
1395
+ * @param options - Signed URL options
1396
+ * @returns {string} The generated signed URL
1397
+ */
1398
+ function createSignedURL(identifier, tokens, searchParamsStringifier, encryption, params, options) {
1399
+ const signature = encryption.getMessageVerifier().sign(createURL(identifier, tokens, searchParamsStringifier, params, {
1400
+ ...options,
1401
+ prefixUrl: void 0
1402
+ }), options?.expiresIn, options?.purpose);
1403
+ return createURL(identifier, tokens, searchParamsStringifier, params, {
1404
+ ...options,
1405
+ qs: {
1406
+ ...options?.qs,
1407
+ signature
1408
+ }
1409
+ });
1410
+ }
1411
+ /**
1412
+ * Match a given URI with an array of patterns and extract the params
1413
+ * from the URL. Null value is returned in case of no match
1414
+ *
1415
+ * @param url - The URL to match
1416
+ * @param patterns - Array of route patterns to match against
1417
+ * @returns {null | Record<string, string>} Extracted parameters or null if no match
1418
+ */
1419
+ function matchRoute(url, patterns) {
1420
+ const tokensBucket = patterns.map((pattern) => parseRoute(pattern));
1421
+ const match = matchit.match(url, tokensBucket);
1422
+ if (!match.length) return null;
1423
+ return matchit.exec(url, match);
1424
+ }
1425
+ /**
1426
+ * Serialize the value of a cookie to a string you can send via
1427
+ * set-cookie response header.
1428
+ *
1429
+ * @param key - Cookie name
1430
+ * @param value - Cookie value
1431
+ * @param options - Cookie options
1432
+ * @returns {string} Serialized cookie string
1433
+ */
1434
+ function serializeCookie(key, value, options) {
1435
+ let expires;
1436
+ let maxAge;
1437
+ if (options) {
1438
+ expires = typeof options.expires === "function" ? options.expires() : options.expires;
1439
+ maxAge = options.maxAge ? string.seconds.parse(options.maxAge) : void 0;
1440
+ }
1441
+ return serialize(key, value, {
1442
+ ...options,
1443
+ maxAge,
1444
+ expires
1445
+ });
1446
+ }
1447
+ /**
1448
+ * Returns the info about a middleware handler. In case of lazy imports, the method
1449
+ * will return the import path
1450
+ *
1451
+ * @param middleware - The middleware function or parsed middleware
1452
+ * @returns {Promise<MiddlewareHandlerInfo>} Promise resolving to middleware handler information
1453
+ */
1454
+ async function middlewareInfo(middleware) {
1455
+ if (typeof middleware === "function") return {
1456
+ type: "closure",
1457
+ name: middleware.name || "closure"
1458
+ };
1459
+ if ("args" in middleware) return {
1460
+ type: "named",
1461
+ name: middleware.name,
1462
+ args: middleware.args,
1463
+ ...await parseBindingReference([middleware.reference])
1464
+ };
1465
+ return {
1466
+ type: "global",
1467
+ name: middleware.name,
1468
+ ...await parseBindingReference([middleware.reference])
1469
+ };
1470
+ }
1471
+ /**
1472
+ * Returns the info about a route handler. In case of lazy imports, the method
1473
+ * will return the import path.
1474
+ *
1475
+ * @param route - The route JSON object
1476
+ * @returns {Promise<RouteHandlerInfo>} Promise resolving to route handler information
1477
+ */
1478
+ async function routeInfo(route) {
1479
+ return "reference" in route.handler ? {
1480
+ type: "controller",
1481
+ ...await parseBindingReference(route.handler.reference)
1482
+ } : {
1483
+ type: "closure",
1484
+ name: route.handler.name || "closure",
1485
+ args: "listArgs" in route.handler ? String(route.handler.listArgs) : void 0
1486
+ };
1487
+ }
1488
+ /**
1489
+ * Appends query string parameters to a URI. Existing query parameters
1490
+ * in the URI are merged with the new ones.
1491
+ *
1492
+ * @param uri - The base URI to append query string to
1493
+ * @param queryString - Object containing query parameters to append
1494
+ * @param qsParser - Query string parser instance for stringify/parse operations
1495
+ *
1496
+ * @example
1497
+ * ```ts
1498
+ * const result = appendQueryString('/users', { page: 1, limit: 10 }, qsParser)
1499
+ * // Returns: '/users?page=1&limit=10'
1500
+ *
1501
+ * const result2 = appendQueryString('/users?sort=name', { page: 1 }, qsParser)
1502
+ * // Returns: '/users?sort=name&page=1'
1503
+ * ```
1504
+ */
1505
+ function appendQueryString(uri, queryString, qsParser) {
1506
+ const { query, pathname } = safeDecodeURI(uri, false);
1507
+ const mergedQueryString = qsParser.stringify(Object.assign(qsParser.parse(query), queryString));
1508
+ return mergedQueryString ? `${pathname}?${mergedQueryString}` : pathname;
1509
+ }
1510
+ //#endregion
1511
+ export { __toESM as A, httpResponseSerializer as C, __commonJSMin as D, debug_default as E, __exportAll as O, httpRequest as S, canWriteResponseBody as T, RouteResource as _, isValidRedirectUrl as a, httpExceptionHandler as b, mime as c, serializeCookie as d, parseRange as f, RouteGroup as g, trustProxy as h, getPreviousUrl as i, __require as k, parseRoute as l, toRoutesJSON as m, createSignedURL as n, matchRoute as o, safeDecodeURI as p, encodeUrl as r, middlewareInfo as s, appendQueryString as t, routeInfo as u, BriskRoute as v, tracing_channels_exports as w, httpMiddleware as x, Route as y };