@h3ravel/router 1.13.6 → 1.15.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/dist/index.cjs +4473 -363
  2. package/dist/index.d.ts +2757 -159
  3. package/dist/index.js +4425 -362
  4. package/package.json +12 -10
package/dist/index.js CHANGED
@@ -1,15 +1,172 @@
1
- import { Logger, Resolver } from "@h3ravel/shared";
2
- import { Command } from "@h3ravel/musket";
3
- import { readFile, readdir, stat } from "node:fs/promises";
4
- import { Container, Kernel, ServiceProvider } from "@h3ravel/core";
5
- import { Str } from "@h3ravel/support";
6
- import path, { join } from "node:path";
7
- import { serveStatic } from "h3";
8
- import { statSync } from "node:fs";
1
+ import { Arr, Collection, Macroable, MacroableClass, Obj, RuntimeException, ServiceProvider, Str, Stringable, isCallable, isClass, optional, tap } from "@h3ravel/support";
2
+ import { Injectable, LogicException, ModelNotFoundException, NotFoundHttpException, RouteNotFoundException, UrlGenerationException } from "@h3ravel/foundation";
3
+ import { IApplication, ICallableDispatcher, IControllerDispatcher, IResponsable, IResponse, IRoute, IRouteUrlGenerator, IRouter, IUrlGenerator, UrlRoutable } from "@h3ravel/contracts";
9
4
  import "reflect-metadata";
10
- import { HttpContext as HttpContext$1, Request, Response } from "@h3ravel/http";
11
- import { Model } from "@h3ravel/database";
5
+ import { Finalizable, Logger, Magic, UseMagic, internal, mix, trait, use } from "@h3ravel/shared";
6
+ import { Command } from "@h3ravel/musket";
7
+ import { JsonResponse, Middleware, Request, Response, UnexpectedValueException } from "@h3ravel/http";
8
+ import { createRequire } from "module";
9
+ import { existsSync } from "node:fs";
10
+ import crypto from "crypto";
11
+
12
+ //#region src/AbstractRouteCollection.ts
13
+ var AbstractRouteCollection = class AbstractRouteCollection {
14
+ static verbs = [
15
+ "GET",
16
+ "HEAD",
17
+ "POST",
18
+ "PUT",
19
+ "PATCH",
20
+ "DELETE",
21
+ "OPTIONS"
22
+ ];
23
+ /**
24
+ * Match a request against a set of routes belonging to one HTTP verb.
25
+ *
26
+ * @param routes
27
+ * @param req
28
+ * @param includingMethod
29
+ * @returns
30
+ */
31
+ matchAgainstRoutes(routes, req, includingMethod = true) {
32
+ const [fallbacks, routeList] = new Collection(routes).partition(function(route) {
33
+ return route.isFallback;
34
+ });
35
+ return new Collection({
36
+ ...routeList.all(),
37
+ ...fallbacks.all()
38
+ }).first((route) => route.matches(req, includingMethod));
39
+ }
40
+ /**
41
+ * Final handler for a matched route. Responsible for:
42
+ * - Throwing for not found
43
+ * - Throwing for method not allowed
44
+ * - Attaching params extracted from the match
45
+ *
46
+ * @param req
47
+ * @param route
48
+ * @returns
49
+ */
50
+ handleMatchedRoute(req, route) {
51
+ if (route) return route.bind(req);
52
+ throw new NotFoundHttpException(`The route "${req.path()}" was not found.`, void 0, 404);
53
+ }
54
+ /**
55
+ * Determine if any routes match on another HTTP verb.
56
+ *
57
+ * @param request
58
+ */
59
+ checkForAlternateVerbs(request) {
60
+ return AbstractRouteCollection.verbs.filter((m) => m !== request.getMethod()).filter((method) => {
61
+ const routesForMethod = this.get(method);
62
+ return this.matchAgainstRoutes(routesForMethod, request) != null;
63
+ });
64
+ }
65
+ matchDomain(domain, host) {
66
+ if (!domain) return true;
67
+ if (domain === host) return true;
68
+ if (domain.includes("*")) {
69
+ const pattern = domain.replace("*", "(.*)");
70
+ return (/* @__PURE__ */ new RegExp(`^${pattern}$`)).test(host);
71
+ }
72
+ return false;
73
+ }
74
+ matchUri(route, path) {
75
+ return path === route.uri();
76
+ }
77
+ /**
78
+ * Count the number of items in the collection.
79
+ */
80
+ count() {
81
+ return this.getRoutes().length;
82
+ }
83
+ };
84
+
85
+ //#endregion
86
+ //#region src/Traits/RouteDependencyResolver.ts
87
+ var RouteDependencyResolver = class {
88
+ constructor(container) {
89
+ this.container = container;
90
+ }
91
+ /**
92
+ * Resolve the object method's type-hinted dependencies.
93
+ *
94
+ * @param parameters
95
+ * @param instance
96
+ * @param method
97
+ */
98
+ async resolveClassMethodDependencies(parameters, instance, method) {
99
+ /**
100
+ * Ensure the method exists on the controller
101
+ */
102
+ if (typeof instance[method] !== "function") throw new RuntimeException(`[${method}] not found on controller [${instance.constructor.name}]`);
103
+ /**
104
+ * Get param types for the controller method
105
+ */
106
+ const paramTypes = Reflect.getMetadata("design:paramtypes", instance, method) || [];
107
+ /**
108
+ * Resolve the bound dependencies
109
+ */
110
+ let args = await Promise.all(paramTypes.map(async (paramType) => {
111
+ const instance$1 = Object.values(parameters).find((e) => e instanceof paramType);
112
+ if (instance$1 && typeof instance$1 === "object") return instance$1;
113
+ return await this.container.make(paramType);
114
+ }));
115
+ /**
116
+ * Ensure that the HttpContext and Application instances are always available
117
+ */
118
+ if (args.length < 1) args = [this.container.getHttpContext(), this.container];
119
+ /**
120
+ * Call the controller method, passing all resolved dependencies
121
+ */
122
+ return this.resolveMethodDependencies([...args, ...Object.values(parameters)]);
123
+ }
124
+ /**
125
+ * Resolve the given method's type-hinted dependencies.
126
+ *
127
+ * @param parameters
128
+ */
129
+ resolveMethodDependencies(parameters) {
130
+ /**
131
+ * Call the route callback handler
132
+ */
133
+ return parameters;
134
+ }
135
+ };
136
+
137
+ //#endregion
138
+ //#region src/CallableDispatcher.ts
139
+ var CallableDispatcher = class extends mix(ICallableDispatcher, RouteDependencyResolver) {
140
+ /**
141
+ *
142
+ * @param container The container instance.
143
+ */
144
+ constructor(container) {
145
+ super(container);
146
+ this.container = container;
147
+ }
148
+ /**
149
+ * Dispatch a request to a given callback.
150
+ *
151
+ * @param route
152
+ * @param handler
153
+ * @param method
154
+ */
155
+ async dispatch(route, handler) {
156
+ return handler(this.container.make("http.context"), ...Object.values(this.resolveParameters(route)));
157
+ }
158
+ /**
159
+ * Resolve the parameters for the callable.
160
+ *
161
+ * @param route
162
+ * @param handler
163
+ */
164
+ resolveParameters(route) {
165
+ return this.resolveMethodDependencies(route.parametersWithoutNulls());
166
+ }
167
+ };
12
168
 
169
+ //#endregion
13
170
  //#region src/Commands/RouteListCommand.ts
14
171
  var RouteListCommand = class extends Command {
15
172
  /**
@@ -21,6 +178,11 @@ var RouteListCommand = class extends Command {
21
178
  {list : List all registered routes.
22
179
  | {--json : Output the route list as JSON}
23
180
  | {--r|reverse : Reverse the ordering of the routes}
181
+ | {--s|sort=uri : Sort the routes by a given column (uri, name, method)}
182
+ | {--m|method= : Filter the routes by a specific HTTP method}
183
+ | {--n|name= : Filter the routes by a specific name}
184
+ | {--p|path= : Filter the routes by a specific path}
185
+ | {--e|except-path= : Exclude routes with a specific path}
24
186
  }
25
187
  `;
26
188
  /**
@@ -33,32 +195,101 @@ var RouteListCommand = class extends Command {
33
195
  * Execute the console command.
34
196
  */
35
197
  async handle() {
36
- console.log("");
37
- const command = this.dictionary.baseCommand ?? this.dictionary.name;
38
- await this[command]();
198
+ this.newLine();
199
+ if (!this.app.make("router").getRoutes().count()) {
200
+ this.error("ERROR: Your application doesn't have any routes.").newLine();
201
+ return;
202
+ }
203
+ const routes = this.getRoutes();
204
+ if (routes.length === 0) {
205
+ this.error("ERROR: Your application doesn't have any routes matching the given criteria.").newLine();
206
+ return;
207
+ }
208
+ await this.showRoutes(routes);
39
209
  }
40
210
  /**
41
- * List all registered routes.
211
+ * Compile the routes into a displayable format.
42
212
  */
43
- async list() {
213
+ getRoutes() {
44
214
  /**
45
- * Log the route list
215
+ * Sort the routes alphabetically
46
216
  */
47
- [...this.app.make("app.routes")].sort((a, b) => {
217
+ const list = this.app.make("router").getRoutes().getRoutes().sort((a, b) => {
48
218
  if (a.path === "/" && b.path !== "/") return -1;
49
219
  if (b.path === "/" && a.path !== "/") return 1;
50
220
  return a.path.localeCompare(b.path);
51
- }).filter((e) => !["head", "patch"].includes(e.method)).forEach((route) => {
52
- const path$1 = route.path === "/" ? route.path : Logger.log(route.path.slice(1).split("/").map((e) => [(e.includes(":") ? Logger.log("/", "white", false) : "") + e, e.startsWith(":") ? "yellow" : "white"]), "", false);
53
- const method = (route.method.startsWith("/") ? route.method.slice(1) : route.method).toUpperCase();
54
- const name = route.signature[1] ? [
55
- route.name ?? "",
56
- route.name ? "›" : "",
57
- route.signature.join("@")
58
- ].join(" ") : "";
59
- const desc = Logger.describe(Logger.log(Logger.log(method + this.pair(method), this.color(method), false), "green", false), path$1, 15, false);
60
- return Logger.twoColumnDetail(desc.join(""), name);
61
221
  });
222
+ if (this.option("reverse")) list.reverse();
223
+ if (this.option("sort")) {
224
+ const sort = this.option("sort").toLowerCase();
225
+ list.sort((a, b) => {
226
+ switch (sort) {
227
+ case "uri": return a.path.localeCompare(b.path);
228
+ case "name": return (a.getName() ?? "").localeCompare(b.getName() ?? "");
229
+ case "method": return a.methods.join("|").localeCompare(b.methods.join("|"));
230
+ default: return 0;
231
+ }
232
+ });
233
+ }
234
+ if (this.option("method")) {
235
+ const method = this.option("method").toUpperCase();
236
+ list.splice(0, list.length, ...list.filter((route) => route.getMethods().includes(method)));
237
+ }
238
+ if (this.option("name")) {
239
+ const name = this.option("name");
240
+ list.splice(0, list.length, ...list.filter((route) => route.getName() === name));
241
+ }
242
+ if (this.option("path")) {
243
+ const path = this.option("path");
244
+ list.splice(0, list.length, ...list.filter((route) => route.path === path));
245
+ }
246
+ if (this.option("except-path")) {
247
+ const path = this.option("except-path");
248
+ list.splice(0, list.length, ...list.filter((route) => route.path !== path));
249
+ }
250
+ return list;
251
+ }
252
+ /**
253
+ * List all registered routes.
254
+ */
255
+ async showRoutes(list) {
256
+ if (this.option("json")) return this.asJson(list);
257
+ return this.forCli(list);
258
+ }
259
+ forCli(list) {
260
+ /**
261
+ * Log the route list
262
+ */
263
+ list.forEach((route) => {
264
+ const uri = route.uri();
265
+ const name = route.getName() ?? "";
266
+ const formatedPath = uri === "/" ? uri : uri.split("/").map((e) => [e, /\{.*\}/.test(e) ? "yellow" : "white"]).reduce((acc, [segment, color], i) => {
267
+ return acc + (i > 0 ? Logger.log("/", "white", false) : "") + Logger.log(segment, color, false);
268
+ }, "");
269
+ const formatedMethod = route.getMethods().map((method) => Logger.log(method, this.color(method), false)).join(Logger.log("|", "gray", false));
270
+ const formatedName = route.action.controller ? [
271
+ name,
272
+ name !== "" ? "›" : "",
273
+ route.action.controller
274
+ ].join(" ") : name;
275
+ const desc = Logger.describe(Logger.log(formatedMethod, "green", false), formatedPath, 15, false);
276
+ return Logger.twoColumnDetail(desc.join(""), formatedName);
277
+ });
278
+ this.newLine(2);
279
+ Logger.split("", Logger.log(`Showing [${list.length}] routes`, ["blue", "bold"], false), "info", false, false, " ");
280
+ }
281
+ asJson(list) {
282
+ const routes = list.map((route) => ({
283
+ methods: route.getMethods(),
284
+ uri: route.uri(),
285
+ name: route.getName(),
286
+ action: route.action
287
+ }));
288
+ if (this.app.runningInConsole()) {
289
+ this.newLine();
290
+ console.log(JSON.stringify(routes, null, 2));
291
+ this.newLine();
292
+ } else return routes;
62
293
  }
63
294
  /**
64
295
  * Get the color
@@ -82,13 +313,247 @@ var RouteListCommand = class extends Command {
82
313
  */
83
314
  pair(method) {
84
315
  switch (method.toLowerCase()) {
85
- case "get": return Logger.log("|", "gray", false) + Logger.log("HEAD", this.color("head"), false);
86
- case "put": return Logger.log("|", "gray", false) + Logger.log("PATCH", this.color("patch"), false);
316
+ case "get": return Logger.log("|", "gray", false) + Logger.log("HEAD", this.color("HEAD"), false);
317
+ case "put": return Logger.log("|", "gray", false) + Logger.log("PATCH", this.color("PATCH"), false);
87
318
  default: return "";
88
319
  }
89
320
  }
90
321
  };
91
322
 
323
+ //#endregion
324
+ //#region src/CompiledRoute.ts
325
+ var CompiledRoute = class {
326
+ path;
327
+ tokens;
328
+ variables;
329
+ paramNames;
330
+ optionalParams;
331
+ regex;
332
+ hostPattern;
333
+ hostRegex;
334
+ constructor(path, optionalParams, hostPattern) {
335
+ this.path = path;
336
+ this.variables = this.buildParams();
337
+ this.paramNames = this.variables;
338
+ this.optionalParams = optionalParams;
339
+ this.hostPattern = hostPattern;
340
+ this.tokens = this.tokenizePath();
341
+ this.regex = this.buildRegex(this.path, this.paramNames, this.optionalParams);
342
+ if (this.hostPattern) this.hostRegex = this.buildRegex(this.hostPattern, [], {});
343
+ }
344
+ /**
345
+ * Get the compiled path regex
346
+ */
347
+ getRegex() {
348
+ return this.regex;
349
+ }
350
+ /**
351
+ * Get the compiled host regex (if any)
352
+ */
353
+ getHostRegex() {
354
+ return this.hostRegex;
355
+ }
356
+ /**
357
+ * Returns list of all param names (including optional)
358
+ */
359
+ getParamNames() {
360
+ return this.paramNames;
361
+ }
362
+ /**
363
+ * Returns list of all path variables
364
+ */
365
+ getVariables() {
366
+ return this.variables;
367
+ }
368
+ /**
369
+ * Returns list of all compiled tokens
370
+ */
371
+ getTokens() {
372
+ return this.tokens;
373
+ }
374
+ /**
375
+ * Returns optional params record
376
+ */
377
+ getOptionalParams() {
378
+ return { ...this.optionalParams };
379
+ }
380
+ /**
381
+ * Build the route params
382
+ *
383
+ * @returns
384
+ */
385
+ buildParams() {
386
+ const paramNames = [];
387
+ this.path.replace(/\{([\w]+)(?:[:][\w]+)?\??\}/g, (_, paramName) => {
388
+ paramNames.push(paramName);
389
+ return "";
390
+ });
391
+ return paramNames;
392
+ }
393
+ /**
394
+ * Build a regex from a path pattern
395
+ *
396
+ * @param path
397
+ * @param paramNames
398
+ * @param optionalParams
399
+ * @returns
400
+ */
401
+ buildRegex(path, paramNames, optionalParams) {
402
+ const regexStr = path.replace(/\/?\{([a-zA-Z0-9_]+)(\?)?(?::[a-zA-Z0-9_]+)?\}/g, (_, paramName, optionalMark) => {
403
+ if (optionalMark === "?" || optionalParams[paramName] === null) return "(?:/([^/]+))?";
404
+ else return "/([^/]+)";
405
+ });
406
+ return /* @__PURE__ */ new RegExp(`^${regexStr}$`);
407
+ }
408
+ /**
409
+ * Tokenize the the path
410
+ *
411
+ * @param optionalParams
412
+ * @returns
413
+ */
414
+ tokenizePath() {
415
+ const tokens = [];
416
+ const regex = /(\{([a-zA-Z0-9_]+)(\?)?(?::[a-zA-Z0-9_]+)?\})|([^{}]+)/g;
417
+ let match;
418
+ while ((match = regex.exec(this.path)) !== null) if (match[1]) {
419
+ const paramName = match[2];
420
+ const isOptional = match[3] === "?" || this.optionalParams[paramName] === null;
421
+ const prefix = match.index === 0 ? "" : "/";
422
+ tokens.push([
423
+ "variable",
424
+ prefix,
425
+ "[^/]++",
426
+ paramName,
427
+ !isOptional
428
+ ]);
429
+ } else if (match[4]) tokens.push(["text", match[4]]);
430
+ return tokens;
431
+ }
432
+ };
433
+
434
+ //#endregion
435
+ //#region src/Contracts/IRouteValidator.ts
436
+ var IRouteValidator = class {};
437
+
438
+ //#endregion
439
+ //#region src/Traits/FiltersControllerMiddleware.ts
440
+ var FiltersControllerMiddleware = class {
441
+ /**
442
+ * Determine if the given options exclude a particular method.
443
+ *
444
+ * @param method
445
+ * @param options
446
+ */
447
+ static methodExcludedByOptions(method, options) {
448
+ return typeof options.only !== "undefined" && !options.only.includes(method) || !!options.except && options.except.length > 0 && options.except.includes(method);
449
+ }
450
+ };
451
+
452
+ //#endregion
453
+ //#region src/ControllerDispatcher.ts
454
+ var ControllerDispatcher = class ControllerDispatcher extends mix(IControllerDispatcher, RouteDependencyResolver, FiltersControllerMiddleware) {
455
+ /**
456
+ *
457
+ * @param container The container instance.
458
+ */
459
+ constructor(container) {
460
+ super(container);
461
+ this.container = container;
462
+ }
463
+ /**
464
+ * Dispatch a request to a given controller and method.
465
+ *
466
+ * @param route
467
+ * @param controller
468
+ * @param method
469
+ */
470
+ async dispatch(route, controller, method) {
471
+ const parameters = await this.resolveParameters(route, controller, method);
472
+ if (Object.prototype.hasOwnProperty.call(controller, "callAction")) return controller.callAction(method, Object.values(parameters));
473
+ return await controller[method].apply(controller, [...Object.values(parameters)]);
474
+ }
475
+ /**
476
+ * Resolve the parameters for the controller.
477
+ *
478
+ * @param route
479
+ * @param controller
480
+ * @param method
481
+ */
482
+ async resolveParameters(route, controller, method) {
483
+ return this.resolveClassMethodDependencies(route.parametersWithoutNulls(), controller, method);
484
+ }
485
+ /**
486
+ * Get the middleware for the controller instance.
487
+ *
488
+ * @param controller
489
+ * @param method
490
+ */
491
+ getMiddleware(controller, method) {
492
+ if (!Object.prototype.hasOwnProperty.call(controller, "getMiddleware")) return [];
493
+ return new Collection(controller.getMiddleware?.() ?? {}).reject((data) => ControllerDispatcher.methodExcludedByOptions(method, data.options)).pluck("middleware").all();
494
+ }
495
+ };
496
+
497
+ //#endregion
498
+ //#region src/Events/PreparingResponse.ts
499
+ var PreparingResponse = class {
500
+ /**
501
+ * Create a new event instance.
502
+ *
503
+ *
504
+ * @param $request The request instance.
505
+ * @param $response The response instance.
506
+ */
507
+ constructor(request, response$1) {
508
+ this.request = request;
509
+ this.response = response$1;
510
+ }
511
+ };
512
+
513
+ //#endregion
514
+ //#region src/Events/ResponsePrepared.ts
515
+ var ResponsePrepared = class {
516
+ /**
517
+ * Create a new event instance.
518
+ *
519
+ *
520
+ * @param $request The request instance.
521
+ * @param $response The response instance.
522
+ */
523
+ constructor(request, response$1) {
524
+ this.request = request;
525
+ this.response = response$1;
526
+ }
527
+ };
528
+
529
+ //#endregion
530
+ //#region src/Events/RouteMatched.ts
531
+ var RouteMatched = class {
532
+ /**
533
+ * Create a new event instance.
534
+ *
535
+ * @param route The route instance.
536
+ * @param request The request instance.
537
+ */
538
+ constructor(route, request) {
539
+ this.route = route;
540
+ this.request = request;
541
+ }
542
+ };
543
+
544
+ //#endregion
545
+ //#region src/Events/Routing.ts
546
+ var Routing = class {
547
+ /**
548
+ * Create a new event instance.
549
+ *
550
+ * @param request The request instance.
551
+ */
552
+ constructor(request) {
553
+ this.request = request;
554
+ }
555
+ };
556
+
92
557
  //#endregion
93
558
  //#region src/Helpers.ts
94
559
  var Helpers = class Helpers {
@@ -102,11 +567,11 @@ var Helpers = class Helpers {
102
567
  * @param path - The route path string (e.g. "/groups/:group/users/:user")
103
568
  * @returns An array of parameter names (e.g. ["group", "user"])
104
569
  */
105
- static extractParams(path$1) {
570
+ static extractParams(path) {
106
571
  const regex = /:([^/]+)/g;
107
572
  const params = [];
108
573
  let match;
109
- while ((match = regex.exec(path$1)) !== null) params.push(match[1]);
574
+ while ((match = regex.exec(path)) !== null) params.push(match[1]);
110
575
  return params;
111
576
  }
112
577
  /**
@@ -123,7 +588,7 @@ var Helpers = class Helpers {
123
588
  * @param model - The model instance to resolve bindings against
124
589
  * @returns A resolved model instance or an object containing param values
125
590
  */
126
- static async resolveRouteModelBinding(path$1, ctx, model) {
591
+ static async resolveRouteModelBinding(path, ctx, model) {
127
592
  const name = model.constructor.name.toLowerCase();
128
593
  /**
129
594
  * Extract field (defaults to 'id' if not specified after '|')
@@ -132,7 +597,7 @@ var Helpers = class Helpers {
132
597
  /**
133
598
  * Iterate through extracted parameters from the path
134
599
  */
135
- for await (const e of Helpers.extractParams(path$1)) {
600
+ for await (const e of Helpers.extractParams(path)) {
136
601
  const value = ctx.request.params[e] ?? null;
137
602
  if (e === name) return await model.resolveRouteBinding(value, field);
138
603
  else return { [e]: ctx.request.params[e] ?? {} };
@@ -142,405 +607,4003 @@ var Helpers = class Helpers {
142
607
  };
143
608
 
144
609
  //#endregion
145
- //#region src/Providers/AssetsServiceProvider.ts
146
- /**
147
- * Handles public assets loading
148
- *
149
- * Auto-Registered
150
- */
151
- var AssetsServiceProvider = class extends ServiceProvider {
152
- static priority = 996;
153
- register() {
154
- const app = this.app.make("router");
155
- const publicPath = this.app.getPath("public");
156
- /**
157
- * Use a middleware to check if this request for for a file
158
- */
159
- app.middleware((event) => {
160
- const { pathname } = new URL(event.req.url);
161
- /**
162
- * Only serve if it looks like a static asset (has an extension)
163
- * but skip dotfiles or sensitive files
164
- */
165
- if (!/\.[a-zA-Z0-9]+$/.test(pathname)) return;
166
- if (pathname.startsWith("/.") || pathname.includes("..")) return;
167
- /**
168
- * Serve the asset
169
- */
170
- return serveStatic(event, {
171
- indexNames: ["/index.html"],
172
- getContents: (id) => {
173
- return readFile(join(Str.before(publicPath, id), id));
174
- },
175
- getMeta: async (id) => {
176
- const stats = await stat(join(Str.before(publicPath, id), id)).catch(() => {});
177
- if (stats?.isFile()) return {
178
- size: stats.size,
179
- mtime: stats.mtimeMs
180
- };
181
- }
182
- });
183
- });
184
- this.app.singleton("asset", () => {
185
- return (key, def) => {
186
- if (def) try {
187
- statSync(join(Str.before(publicPath, key), key));
188
- } catch {
189
- key = def;
190
- }
191
- return key;
192
- };
193
- });
194
- }
195
- };
196
-
197
- //#endregion
198
- //#region src/Route.ts
199
- var Router = class {
200
- routes = [];
201
- nameMap = [];
202
- groupPrefix = "";
203
- middlewareMap = [];
204
- groupMiddleware = [];
205
- constructor(h3App, app) {
206
- this.h3App = h3App;
207
- this.app = app;
208
- }
610
+ //#region src/ImplicitRouteBinding.ts
611
+ var ImplicitRouteBinding = class {
209
612
  /**
210
- * Route Resolver
613
+ * Resolve the implicit route bindings for the given route.
211
614
  *
212
- * @param handler
213
- * @param middleware
214
- * @returns
615
+ * @param container
616
+ * @param route
215
617
  */
216
- resolveHandler(handler, middleware = []) {
217
- return async (event) => {
218
- this.app.context ??= async (event$1) => {
219
- if (event$1._h3ravelContext) return event$1._h3ravelContext;
220
- Request.enableHttpMethodParameterOverride();
221
- const ctx = HttpContext$1.init({
222
- app: this.app,
223
- request: await Request.create(event$1, this.app),
224
- response: new Response(event$1, this.app)
225
- });
226
- event$1._h3ravelContext = ctx;
227
- return ctx;
228
- };
229
- return new Kernel(this.app.context, middleware).handle(event, (ctx) => new Promise((resolve) => {
230
- if (Resolver.isAsyncFunction(handler)) handler(ctx).then((response) => {
231
- if (response instanceof Response) resolve(response.prepare(ctx.request).send());
232
- else resolve(response);
233
- });
234
- else resolve(handler(ctx));
235
- }));
236
- };
618
+ static async resolveForRoute(container, route) {
619
+ const parameters = route.getParameters();
620
+ for (const parameter of route.signatureParameters({ subClass: UrlRoutable })) {
621
+ const parameterName = this.getParameterName(parameter.getName(), parameters);
622
+ if (!parameterName) continue;
623
+ const parameterValue = parameters[parameterName];
624
+ if (parameterValue instanceof UrlRoutable) continue;
625
+ const instanceClass = parameter.getType();
626
+ const instance = container.make(instanceClass);
627
+ const parent = route.parentOfParameter(parameterName);
628
+ const isSoftDeletable = typeof instanceClass.isSoftDeletable === "function" && instanceClass.isSoftDeletable();
629
+ const useSoftDelete = route.allowsTrashedBindings() && isSoftDeletable;
630
+ let model;
631
+ if (parent instanceof UrlRoutable && !route.preventsScopedBindings() && (route.enforcesScopedBindings() || parameterName in route.getBindingFields())) {
632
+ const childMethod = useSoftDelete ? "resolveSoftDeletableChildRouteBinding" : "resolveChildRouteBinding";
633
+ model = await Reflect.apply(parent[childMethod], parent, [
634
+ parameterName,
635
+ parameterValue,
636
+ route.bindingFieldFor(parameterName)
637
+ ]);
638
+ } else {
639
+ const method = useSoftDelete ? "resolveSoftDeletableRouteBinding" : "resolveRouteBinding";
640
+ model = await Reflect.apply(instance[method], instance, [parameterValue, route.bindingFieldFor(parameterName)]);
641
+ }
642
+ if (!model) throw new ModelNotFoundException().setModel(instanceClass, [parameterValue]);
643
+ route.setParameter(parameterName, model);
644
+ }
237
645
  }
238
646
  /**
239
- * Add a route to the stack
647
+ * Return the parameter name if it exists in the given parameters.
240
648
  *
241
- * @param method
242
- * @param path
243
- * @param handler
244
- * @param name
245
- * @param middleware
649
+ * @param name
650
+ * @param parameters
651
+ * @returns
246
652
  */
247
- addRoute(method, path$1, handler, name, middleware = [], signature = ["", ""]) {
248
- /**
249
- * Join all defined route names to make a single route name
250
- */
251
- if (this.nameMap.length > 0) name = this.nameMap.join(".");
252
- /**
253
- * Join all defined middlewares
254
- */
255
- if (this.middlewareMap.length > 0) middleware = this.middlewareMap;
256
- const fullPath = `${this.groupPrefix}${path$1}`.replace(/\/+/g, "/");
257
- this.routes.push({
258
- method,
259
- path: fullPath,
260
- name,
261
- handler,
262
- signature
263
- });
264
- this.h3App[method](fullPath, this.resolveHandler(handler, middleware));
265
- this.app.singleton("app.routes", () => this.routes);
653
+ static getParameterName(name, parameters) {
654
+ if (name in parameters) return name;
655
+ const snakedName = Str.snake(name);
656
+ if (snakedName in parameters) return snakedName;
266
657
  }
658
+ };
659
+
660
+ //#endregion
661
+ //#region src/Matchers/HostValidator.ts
662
+ var HostValidator = class extends IRouteValidator {
267
663
  /**
268
- * Resolves a route handler definition into an executable EventHandler.
269
- *
270
- * A handler can be:
271
- * - A function matching the EventHandler signature
272
- * - A controller class (optionally decorated for IoC resolution)
664
+ * Validate a given rule against a route and request.
273
665
  *
274
- * If it’s a controller class, this method will:
275
- * - Instantiate it (via IoC or manually)
276
- * - Call the specified method (defaults to `index`)
277
- *
278
- * @param handler Event handler function OR controller class
279
- * @param methodName Method to invoke on the controller (defaults to 'index')
666
+ * @param route
667
+ * @param request
280
668
  */
281
- resolveControllerOrHandler(handler, methodName, path$1) {
282
- /**
283
- * Checks if the handler is a function (either a plain function or a class constructor)
284
- */
285
- if (typeof handler === "function" && typeof handler.prototype !== "undefined") return async (ctx) => {
286
- let controller;
287
- if (Container.hasAnyDecorator(handler))
288
- /**
289
- * If the controller is decorated use the IoC container
290
- */
291
- controller = this.app.make(handler);
292
- else
293
- /**
294
- * Otherwise instantiate manually so that we can at least
295
- * pass the app instance
296
- */
297
- controller = new handler(this.app);
298
- /**
299
- * The method to execute (defaults to 'index')
300
- */
301
- const action = methodName || "index";
302
- /**
303
- * Ensure the method exists on the controller
304
- */
305
- if (typeof controller[action] !== "function") throw new Error(`Method "${String(action)}" not found on controller ${handler.name}`);
306
- /**
307
- * Get param types for the controller method
308
- */
309
- const paramTypes = Reflect.getMetadata("design:paramtypes", controller, action) || [];
310
- /**
311
- * Resolve the bound dependencies
312
- */
313
- let args = await Promise.all(paramTypes.map(async (paramType) => {
314
- switch (paramType?.name) {
315
- case "Application": return this.app;
316
- case "Request": return ctx.request;
317
- case "Response": return ctx.response;
318
- case "HttpContext": return ctx;
319
- default: {
320
- const inst = this.app.make(paramType);
321
- if (inst instanceof Model) return await Helpers.resolveRouteModelBinding(path$1 ?? "", ctx, inst);
322
- return inst;
323
- }
324
- }
325
- }));
326
- /**
327
- * Ensure that the HttpContext is always available
328
- */
329
- if (args.length < 1) args = [ctx];
330
- /**
331
- * Call the controller method, passing all resolved dependencies
332
- */
333
- return await controller[action](...args);
334
- };
335
- return handler;
669
+ matches(route, request) {
670
+ const hostRegex = route.getCompiled()?.getHostRegex();
671
+ if (!hostRegex) return true;
672
+ return hostRegex.test(request.getHost());
336
673
  }
674
+ };
675
+
676
+ //#endregion
677
+ //#region src/Matchers/MethodValidator.ts
678
+ var MethodValidator = class extends IRouteValidator {
337
679
  /**
338
- * Registers a route that responds to HTTP GET requests.
680
+ * Validate a given rule against a route and request.
339
681
  *
340
- * @param path The URL pattern to match (can include parameters, e.g., '/users/:id').
341
- * @param definition Either:
342
- * - An EventHandler function
343
- * - A tuple: [ControllerClass, methodName]
344
- * @param name Optional route name (for URL generation or referencing).
345
- * @param middleware Optional array of middleware functions to execute before the handler.
682
+ * @param route
683
+ * @param request
346
684
  */
347
- get(path$1, definition, name, middleware = []) {
348
- const handler = Array.isArray(definition) ? definition[0] : definition;
349
- const methodName = Array.isArray(definition) ? definition[1] : void 0;
350
- this.addRoute("get", path$1, this.resolveControllerOrHandler(handler, methodName, path$1), name, middleware, [handler.name, methodName]);
351
- return this;
685
+ matches(route, request) {
686
+ return route.methods.includes(request.getMethod());
687
+ }
688
+ };
689
+
690
+ //#endregion
691
+ //#region src/Matchers/SchemeValidator.ts
692
+ var SchemeValidator = class extends IRouteValidator {
693
+ /**
694
+ * Validate a given rule against a route and request.
695
+ *
696
+ * @param route
697
+ * @param request
698
+ */
699
+ matches(route, request) {
700
+ if (route.httpOnly()) return !request.secure();
701
+ else if (route.secure()) return request.secure();
702
+ return true;
703
+ }
704
+ };
705
+
706
+ //#endregion
707
+ //#region src/Matchers/UriValidator.ts
708
+ var UriValidator = class extends IRouteValidator {
709
+ /**
710
+ * Validate a given rule against a route and request.
711
+ *
712
+ * @param route
713
+ * @param request
714
+ */
715
+ matches(route, request) {
716
+ const path = Str.of(request.getPathInfo()).ltrim("/").rtrim("/").toString() || "/";
717
+ return route.getCompiled().getRegex().test(decodeURIComponent(path));
718
+ }
719
+ };
720
+
721
+ //#endregion
722
+ //#region \0@oxc-project+runtime@0.99.0/helpers/decorateMetadata.js
723
+ function __decorateMetadata(k, v) {
724
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
725
+ }
726
+
727
+ //#endregion
728
+ //#region \0@oxc-project+runtime@0.99.0/helpers/decorate.js
729
+ function __decorate(decorators, target, key, desc) {
730
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
731
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
732
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
733
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
734
+ }
735
+
736
+ //#endregion
737
+ //#region src/Middleware/SubstituteBindings.ts
738
+ var _ref$1, _ref2, _ref3;
739
+ let SubstituteBindings = class SubstituteBindings$1 extends Middleware {
740
+ /**
741
+ *
742
+ * @param router The router instance.
743
+ */
744
+ constructor(app, router) {
745
+ super(app);
746
+ this.app = app;
747
+ this.router = router;
748
+ }
749
+ /**
750
+ * Handle an incoming request.
751
+ *
752
+ * @param request
753
+ * @param next
754
+ */
755
+ async handle(request, next) {
756
+ const route = request.route();
757
+ try {
758
+ await this.router.substituteBindings(route);
759
+ await this.router.substituteImplicitBindings(route);
760
+ } catch (e) {
761
+ if (e instanceof ModelNotFoundException) {
762
+ const getMissing = route.getMissing();
763
+ if (typeof getMissing !== "undefined") return getMissing(request, e);
764
+ }
765
+ throw e;
766
+ }
767
+ return next(request);
768
+ }
769
+ };
770
+ __decorate([
771
+ Injectable(),
772
+ __decorateMetadata("design:type", Function),
773
+ __decorateMetadata("design:paramtypes", [typeof (_ref3 = typeof Request !== "undefined" && Request) === "function" ? _ref3 : Object, Function]),
774
+ __decorateMetadata("design:returntype", Promise)
775
+ ], SubstituteBindings.prototype, "handle", null);
776
+ SubstituteBindings = __decorate([Injectable(), __decorateMetadata("design:paramtypes", [typeof (_ref$1 = typeof IApplication !== "undefined" && IApplication) === "function" ? _ref$1 : Object, typeof (_ref2 = typeof IRouter !== "undefined" && IRouter) === "function" ? _ref2 : Object])], SubstituteBindings);
777
+
778
+ //#endregion
779
+ //#region src/MiddlewareResolver.ts
780
+ var MiddlewareResolver = class {
781
+ static app;
782
+ static setApp(app) {
783
+ this.app = app;
784
+ return this;
785
+ }
786
+ /**
787
+ * Resolve the middleware name to a class name(s) preserving passed parameters.
788
+ */
789
+ static resolve(name, map, middlewareGroups) {
790
+ /**
791
+ * Inline middleware (closure)
792
+ */
793
+ if (typeof name !== "string") return name;
794
+ /**
795
+ * Mapped closure
796
+ */
797
+ if (map[name] && typeof map[name] === "function") return map[name];
798
+ /**
799
+ * Middleware group
800
+ */
801
+ if (middlewareGroups[name]) return this.parseMiddlewareGroup(name, map, middlewareGroups);
802
+ /**
803
+ * Parse name + parameters
804
+ */
805
+ const [base, parameters] = name.split(":", 2);
806
+ const resolved = map[base] ?? base;
807
+ return parameters ? `${resolved}:${parameters}` : resolved;
808
+ }
809
+ /**
810
+ * Parse the middleware group and format it for usage.
811
+ */
812
+ static parseMiddlewareGroup(name, map, middlewareGroups) {
813
+ const results = [];
814
+ for (const middleware of middlewareGroups[name]) {
815
+ /**
816
+ * Nested group
817
+ */
818
+ if (typeof middleware === "string" && middlewareGroups[middleware]) {
819
+ results.push(...this.parseMiddlewareGroup(middleware, map, middlewareGroups));
820
+ continue;
821
+ }
822
+ let resolved = "";
823
+ let parameters = "";
824
+ if (typeof middleware === "string") {
825
+ const base = middleware.split(":", 2)[0];
826
+ parameters = middleware.split(":", 2)[1];
827
+ resolved = map[base] ?? base;
828
+ results.push(parameters ? `${String(resolved)}:${parameters}` : String(resolved));
829
+ const bound = this.app.boundMiddlewares(resolved);
830
+ if (bound) results.push(bound);
831
+ } else {
832
+ const bound = this.app.boundMiddlewares(middleware);
833
+ if (bound) results.push(bound);
834
+ }
835
+ }
836
+ return results.filter((e) => typeof e !== "string");
837
+ }
838
+ };
839
+
840
+ //#endregion
841
+ //#region src/Traits/CreatesRegularExpressionRouteConstraints.ts
842
+ const CreatesRegularExpressionRouteConstraints = trait((Base) => {
843
+ return class CreatesRegularExpressionRouteConstraints$1 extends Base {
844
+ where(_wheres) {
845
+ return this;
846
+ }
847
+ /**
848
+ * Specify that the given route parameters must be alphabetic.
849
+ *
850
+ * @param parameters
851
+ */
852
+ whereAlpha(parameters) {
853
+ return this.assignExpressionToParameters(parameters, "[a-zA-Z]+");
854
+ }
855
+ /**
856
+ * Specify that the given route parameters must be alphanumeric.
857
+ *
858
+ * @param parameters
859
+ */
860
+ whereAlphaNumeric(parameters) {
861
+ return this.assignExpressionToParameters(parameters, "[a-zA-Z0-9]+");
862
+ }
863
+ /**
864
+ * Specify that the given route parameters must be numeric.
865
+ *
866
+ * @param parameters
867
+ */
868
+ whereNumber(parameters) {
869
+ return this.assignExpressionToParameters(parameters, "[0-9]+");
870
+ }
871
+ /**
872
+ * Specify that the given route parameters must be ULIDs.
873
+ *
874
+ * @param parameters
875
+ */
876
+ whereUlid(parameters) {
877
+ return this.assignExpressionToParameters(parameters, "[0-7][0-9a-hjkmnp-tv-zA-HJKMNP-TV-Z]{25}");
878
+ }
879
+ /**
880
+ * Specify that the given route parameters must be UUIDs.
881
+ *
882
+ * @param parameters
883
+ */
884
+ whereUuid(parameters) {
885
+ return this.assignExpressionToParameters(parameters, "[da-fA-F]{8}-[da-fA-F]{4}-[da-fA-F]{4}-[da-fA-F]{4}-[da-fA-F]{12}");
886
+ }
887
+ /**
888
+ * Specify that the given route parameters must be one of the given values.
889
+ *
890
+ * @param parameters
891
+ * @param values
892
+ */
893
+ whereIn(parameters, values) {
894
+ return this.assignExpressionToParameters(parameters, new Collection(values).map((value) => value).implode("|"));
895
+ }
896
+ /**
897
+ * Apply the given regular expression to the given parameters.
898
+ *
899
+ * @param parameters
900
+ * @param expression
901
+ */
902
+ assignExpressionToParameters(parameters, expression) {
903
+ return this.where(Collection.wrap(parameters).mapWithKeys((parameter) => ({ [parameter]: expression })).all());
904
+ }
905
+ };
906
+ });
907
+
908
+ //#endregion
909
+ //#region src/RouteAction.ts
910
+ var RouteAction = class {
911
+ /**
912
+ * The route action array.
913
+ */
914
+ static action = {};
915
+ static parse(uri, action) {
916
+ /**
917
+ * If no action was provided return the missing action error handler
918
+ */
919
+ if (!action) return this.missingAction(uri);
920
+ /**
921
+ * Handle closure
922
+ */
923
+ if (isCallable(action)) return { uses: action };
924
+ /**
925
+ * Handle Controller class
926
+ */
927
+ if (this.isClass(action)) return {
928
+ uses: action,
929
+ controller: action.name + "@index"
930
+ };
931
+ /**
932
+ * Handle [Controller, method] map
933
+ */
934
+ if (Array.isArray(action)) {
935
+ const [uses, method] = action;
936
+ if (!this.isClass(uses)) throw new LogicException(`Invalid controller reference for route: ${uri}`);
937
+ return {
938
+ uses,
939
+ controller: uses.name + "@" + method
940
+ };
941
+ }
942
+ /**
943
+ * Handle an object with "uses" property
944
+ */
945
+ if (typeof action === "object" && action.uses) {
946
+ this.action = action;
947
+ return this.normalizeUses(action.uses, uri);
948
+ }
949
+ throw new LogicException(`Unrecognized route action for URI: ${uri}`);
950
+ }
951
+ /**
952
+ * Normalize the "uses" field
953
+ */
954
+ static normalizeUses(uses, uri) {
955
+ /**
956
+ * uses: function
957
+ */
958
+ if (isCallable(uses)) return {
959
+ ...this.action,
960
+ uses
961
+ };
962
+ /**
963
+ * uses: Controller
964
+ */
965
+ if (this.isClass(uses)) return {
966
+ uses: this.action,
967
+ controller: this.action.name + "@index",
968
+ ...this.action
969
+ };
970
+ /**
971
+ * uses: [Controller, 'method']
972
+ */
973
+ if (Array.isArray(uses)) {
974
+ const [controller, method] = uses;
975
+ if (!this.isClass(controller)) throw new LogicException(`Invalid controller reference in 'uses' for route: ${uri}`);
976
+ return {
977
+ ...this.action,
978
+ uses: controller,
979
+ controller: controller.name + "@" + method
980
+ };
981
+ }
982
+ throw new LogicException(`Invalid 'uses' value for route: ${uri}`);
983
+ }
984
+ /**
985
+ * Missing action fallback
986
+ */
987
+ static missingAction(uri) {
988
+ return { handler: () => {
989
+ throw new LogicException(`Route for [${uri}] has no action.`);
990
+ } };
991
+ }
992
+ /**
993
+ * Make an action for an invokable controller.
994
+ *
995
+ * @param action
996
+ *
997
+ * @throws {UnexpectedValueException}
998
+ */
999
+ static makeInvokable(action) {
1000
+ if (!action["__invoke"]) throw new UnexpectedValueException(`Invalid route action: [${action}].`);
1001
+ return action["__invoke"];
1002
+ }
1003
+ /**
1004
+ * Detect if a value is a class constructor
1005
+ */
1006
+ static isClass(value) {
1007
+ return typeof value === "function" && value.prototype && value.prototype.constructor === value;
1008
+ }
1009
+ };
1010
+
1011
+ //#endregion
1012
+ //#region src/RouteParameterBinder.ts
1013
+ var RouteParameterBinder = class {
1014
+ /**
1015
+ * Create a new Route parameter binder instance.
1016
+ *
1017
+ * @param route The route instance.
1018
+ */
1019
+ constructor(route) {
1020
+ this.route = route;
1021
+ }
1022
+ /**
1023
+ * Get the parameters for the route.
1024
+ *
1025
+ * @param request
1026
+ */
1027
+ parameters(request) {
1028
+ let parameters = this.bindPathParameters(request);
1029
+ if (this.route.compiled?.getHostRegex()) parameters = this.bindHostParameters(request, parameters);
1030
+ return this.replaceDefaults(parameters);
1031
+ }
1032
+ /**
1033
+ * Get the parameter matches for the path portion of the URI.
1034
+ *
1035
+ * @param request
1036
+ */
1037
+ bindPathParameters(request) {
1038
+ const path = request.decodedPath().replace(/^\/+/, "");
1039
+ const pathRegex = this.route.compiled?.getRegex() ?? "";
1040
+ const matches = path.match(pathRegex) ?? [];
1041
+ return this.matchToKeys(matches.slice(1));
1042
+ }
1043
+ /**
1044
+ * Extract the parameter list from the host part of the request.
1045
+ *
1046
+ * @param request
1047
+ * @param parameters
1048
+ */
1049
+ bindHostParameters(request, parameters) {
1050
+ const host = request.getHost();
1051
+ const hostRegex = this.route.compiled?.getHostRegex() ?? "";
1052
+ const matches = host.match(hostRegex) ?? [];
1053
+ return {
1054
+ ...this.matchToKeys(matches.slice(1)),
1055
+ ...parameters
1056
+ };
1057
+ }
1058
+ /**
1059
+ * Combine a set of parameter matches with the route's keys.
1060
+ *
1061
+ * @param matches
1062
+ */
1063
+ matchToKeys(matches) {
1064
+ const parameterNames = this.route.parameterNames();
1065
+ if (!parameterNames || parameterNames.length === 0) return {};
1066
+ const parameters = {};
1067
+ for (let i = 0; i < parameterNames.length; i++) {
1068
+ const name = parameterNames[i];
1069
+ const value = matches[i];
1070
+ if (typeof value === "string" && value.length > 0) parameters[name] = value;
1071
+ }
1072
+ return parameters;
1073
+ }
1074
+ /**
1075
+ * Replace null parameters with their defaults.
1076
+ *
1077
+ * @param parameters
1078
+ * @return array
1079
+ */
1080
+ replaceDefaults(parameters) {
1081
+ for (const [key, value] of Object.entries(parameters)) parameters[key] = value ?? Obj.get(this.route._defaults, key);
1082
+ for (const [key, value] of Object.entries(this.route._defaults)) if (!parameters[key]) parameters[key] = value;
1083
+ return parameters;
1084
+ }
1085
+ };
1086
+
1087
+ //#endregion
1088
+ //#region src/RouteParameter.ts
1089
+ var RouteParameter = class {
1090
+ constructor(name, type) {
1091
+ this.name = name;
1092
+ this.type = type;
1093
+ }
1094
+ /**
1095
+ * Ge the route parameter name
1096
+ */
1097
+ getName() {
1098
+ return this.name;
1099
+ }
1100
+ /**
1101
+ * Ge the route parameter type
1102
+ */
1103
+ getType() {
1104
+ return this.type;
1105
+ }
1106
+ };
1107
+
1108
+ //#endregion
1109
+ //#region src/RouteSignatureParameters.ts
1110
+ var RouteSignatureParameters = class {
1111
+ static app;
1112
+ static route;
1113
+ /**
1114
+ * set the current Application and Route instances
1115
+ *
1116
+ * @param app
1117
+ */
1118
+ static setRequirements(app, route) {
1119
+ this.app = app;
1120
+ this.route = route;
1121
+ return this;
1122
+ }
1123
+ /**
1124
+ * Extract the route action's signature parameters.
1125
+ *
1126
+ * @param action
1127
+ * @param conditions
1128
+ * @returns
1129
+ */
1130
+ static fromAction(action, conditions = {}) {
1131
+ const uses = action.uses;
1132
+ let target, methodName;
1133
+ if (isClass(uses)) {
1134
+ target = this.app.make(uses);
1135
+ methodName = this.getControllerMethod(action);
1136
+ } else if (Array.isArray(uses)) {
1137
+ const [_target, _methodName] = uses;
1138
+ target = target.prototype;
1139
+ methodName = _methodName;
1140
+ } else return [new RouteParameter("context", this.app.getHttpContext()), new RouteParameter("app", this.app)];
1141
+ const types = Reflect.getMetadata("design:paramtypes", target, methodName) || [];
1142
+ const routeParamNames = Object.keys(this.route.getParameters());
1143
+ let routeParamIndex = 0;
1144
+ const parameters = types.map((type) => {
1145
+ let name = "unknown";
1146
+ if (conditions.subClass && (type === conditions.subClass || type.prototype instanceof conditions.subClass)) name = routeParamNames[routeParamIndex++] || "unnamed_binding";
1147
+ else name = type.name?.toLowerCase() || "injected";
1148
+ return new RouteParameter(name, type);
1149
+ });
1150
+ if (conditions.subClass) {
1151
+ const subClass = conditions.subClass;
1152
+ return parameters.filter((p) => {
1153
+ const type = p.getType();
1154
+ return type === subClass || type.prototype instanceof subClass;
1155
+ });
1156
+ }
1157
+ return parameters;
1158
+ }
1159
+ /**
1160
+ * Get the controller method used for the route.
1161
+ *
1162
+ * @param action
1163
+ * @returns
1164
+ */
1165
+ static getControllerMethod(action) {
1166
+ const holder = isClass(action.uses) && typeof action.controller === "string" ? action.controller : "index";
1167
+ return Str.parseCallback(holder).at(1);
1168
+ }
1169
+ };
1170
+
1171
+ //#endregion
1172
+ //#region src/RouteUri.ts
1173
+ var RouteUri = class RouteUri {
1174
+ /**
1175
+ * The route URI.
1176
+ */
1177
+ uri;
1178
+ /**
1179
+ * The fields that should be used when resolving bindings.
1180
+ */
1181
+ bindingFields = {};
1182
+ /**
1183
+ * Create a new route URI instance.
1184
+ *
1185
+ * @param uri The route URI.
1186
+ * @param bindingFields The fields that should be used when resolving bindings.
1187
+ */
1188
+ constructor(uri, bindingFields = {}) {
1189
+ this.uri = uri;
1190
+ this.bindingFields = bindingFields;
1191
+ }
1192
+ /**
1193
+ * Parse the given URI.
1194
+ *
1195
+ * @param uri The route URI.
1196
+ */
1197
+ static parse(uri) {
1198
+ const matches = [...uri.matchAll(/\{([\w:]+?)\??\}/g)];
1199
+ const bindingFields = {};
1200
+ for (const match of matches) {
1201
+ const fullMatch = match[0];
1202
+ const inner = match[1];
1203
+ if (!inner.includes(":")) continue;
1204
+ const segments = inner.split(":");
1205
+ bindingFields[segments[0]] = segments[1];
1206
+ const replacement = fullMatch.includes("?") ? `{${segments[0]}?}` : `{${segments[0]}}`;
1207
+ uri = uri.replace(fullMatch, replacement);
1208
+ }
1209
+ return new RouteUri(uri, bindingFields);
1210
+ }
1211
+ };
1212
+
1213
+ //#endregion
1214
+ //#region src/Route.ts
1215
+ var Route = class Route extends IRoute {
1216
+ /**
1217
+ * The URI pattern the route responds to.
1218
+ */
1219
+ #uri;
1220
+ /**
1221
+ * The the matched parameters' original values object.
1222
+ */
1223
+ #originalParameters;
1224
+ /**
1225
+ * The parameter names for the route.
1226
+ */
1227
+ #parameterNames;
1228
+ /**
1229
+ * The default values for the route.
1230
+ */
1231
+ _defaults = {};
1232
+ /**
1233
+ * The router instance used by the route.
1234
+ */
1235
+ router;
1236
+ /**
1237
+ * The compiled version of the route.
1238
+ */
1239
+ compiled = void 0;
1240
+ /**
1241
+ * The matched parameters object.
1242
+ */
1243
+ parameters;
1244
+ /**
1245
+ * The container instance used by the route.
1246
+ */
1247
+ container;
1248
+ /**
1249
+ * The fields that implicit binding should use for a given parameter.
1250
+ */
1251
+ bindingFields;
1252
+ /**
1253
+ * Indicates "trashed" models can be retrieved when resolving implicit model bindings for this route.
1254
+ */
1255
+ withTrashedBindings = false;
1256
+ /**
1257
+ * Indicates whether the route is a fallback route.
1258
+ */
1259
+ isFallback = false;
1260
+ /**
1261
+ * The route action array.
1262
+ */
1263
+ action;
1264
+ /**
1265
+ * The HTTP methods the route responds to.
1266
+ */
1267
+ methods;
1268
+ /**
1269
+ * The route path that can be handled by H3.
1270
+ */
1271
+ path = "";
1272
+ /**
1273
+ * The computed gathered middleware.
1274
+ */
1275
+ computedMiddleware;
1276
+ /**
1277
+ * The controller instance.
1278
+ */
1279
+ controller;
1280
+ /**
1281
+ * The validators used by the routes.
1282
+ */
1283
+ static validators;
1284
+ /**
1285
+ *
1286
+ * @param methods The HTTP methods the route responds to.
1287
+ * @param uri The URI pattern the route responds to.
1288
+ */
1289
+ constructor(methods, uri, action) {
1290
+ super();
1291
+ this.#uri = uri;
1292
+ this.methods = Arr.wrap(methods);
1293
+ this.action = Arr.except(this.parseAction(action), ["prefix"]);
1294
+ if (this.methods.includes("GET") && !this.methods.includes("HEAD")) this.methods.push("HEAD");
1295
+ this.prefix(Obj.isPlainObject(action) ? Obj.get(action, "prefix") : "");
1296
+ }
1297
+ /**
1298
+ * Set the router instance on the route.
1299
+ *
1300
+ * @param router
1301
+ */
1302
+ setRouter(router) {
1303
+ this.router = router;
1304
+ return this;
1305
+ }
1306
+ /**
1307
+ * Set the container instance on the route.
1308
+ *
1309
+ * @param container
1310
+ */
1311
+ setContainer(container) {
1312
+ this.container = container;
1313
+ return this;
1314
+ }
1315
+ /**
1316
+ * Set the URI that the route responds to.
1317
+ *
1318
+ * @param uri
1319
+ */
1320
+ setUri(uri) {
1321
+ this.#uri = this.parseUri(uri);
1322
+ this.path = this.#uri.replace(/\{([^}]+)\}/g, ":$1").replace(/:([^/]+)\?\s*$/, "*").replace(/:([^/]+)\?(?=\/|$)/g, ":$1");
1323
+ return this;
1324
+ }
1325
+ /**
1326
+ * Parse the route URI and normalize / store any implicit binding fields.
1327
+ *
1328
+ * @param uri
1329
+ */
1330
+ parseUri(uri) {
1331
+ this.bindingFields = {};
1332
+ const parsed = RouteUri.parse(uri);
1333
+ this.bindingFields = parsed.bindingFields;
1334
+ return parsed.uri;
1335
+ }
1336
+ /**
1337
+ * Get the URI associated with the route.
1338
+ */
1339
+ uri() {
1340
+ return this.#uri;
1341
+ }
1342
+ /**
1343
+ * Add a prefix to the route URI.
1344
+ *
1345
+ * @param prefix
1346
+ */
1347
+ prefix(prefix) {
1348
+ prefix ??= "";
1349
+ this.updatePrefixOnAction(prefix);
1350
+ const uri = Str.rtrim(prefix, "/") + "/" + Str.ltrim(this.#uri, "/");
1351
+ return this.setUri(uri !== "/" ? Str.trim(uri, "/") : uri);
1352
+ }
1353
+ /**
1354
+ * Update the "prefix" attribute on the action array.
1355
+ *
1356
+ * @param prefix
1357
+ */
1358
+ updatePrefixOnAction(prefix) {
1359
+ const newPrefix = Str.trim(Str.rtrim(prefix, "/") + "/" + Str.ltrim(this.action.prefix ?? "", "/"), "/");
1360
+ if (newPrefix) this.action.prefix = newPrefix;
1361
+ }
1362
+ /**
1363
+ * Get the name of the route instance.
1364
+ */
1365
+ getName() {
1366
+ return this.action.as ?? void 0;
1367
+ }
1368
+ /**
1369
+ * Add or change the route name.
1370
+ *
1371
+ * @param name
1372
+ *
1373
+ * @throws {InvalidArgumentException}
1374
+ */
1375
+ name(name) {
1376
+ this.action.as = this.action.as ? this.action.as + name : name;
1377
+ return this;
1378
+ }
1379
+ /**
1380
+ * Determine whether the route's name matches the given patterns.
1381
+ *
1382
+ * @param patterns
1383
+ */
1384
+ named(...patterns) {
1385
+ const routeName = this.getName();
1386
+ if (!routeName) return false;
1387
+ for (const pattern of patterns) if (Str.is(pattern, routeName)) return true;
1388
+ return false;
1389
+ }
1390
+ /**
1391
+ * Get the action name for the route.
1392
+ */
1393
+ getActionName() {
1394
+ return this.action.handler ?? "Closure";
1395
+ }
1396
+ /**
1397
+ * Get the method name of the route action.
1398
+ *
1399
+ * @return string
1400
+ */
1401
+ getActionMethod() {
1402
+ const name = this.getActionName();
1403
+ return typeof name === "string" ? Arr.last(name.split("@")) : name.name;
1404
+ }
1405
+ /**
1406
+ * Get the action array or one of its properties for the route.
1407
+ * @param key
1408
+ */
1409
+ getAction(key) {
1410
+ if (!key) return this.action;
1411
+ return Obj.get(this.action, key);
1412
+ }
1413
+ /**
1414
+ * Mark this route as a fallback route.
1415
+ */
1416
+ fallback() {
1417
+ this.isFallback = true;
1418
+ return this;
1419
+ }
1420
+ /**
1421
+ * Set the fallback value.
1422
+ *
1423
+ * @param sFallback
1424
+ */
1425
+ setFallback(isFallback) {
1426
+ this.isFallback = isFallback;
1427
+ return this;
1428
+ }
1429
+ /**
1430
+ * Get the HTTP verbs the route responds to.
1431
+ */
1432
+ getMethods() {
1433
+ return this.methods;
1434
+ }
1435
+ /**
1436
+ * Determine if the route only responds to HTTP requests.
1437
+ */
1438
+ httpOnly() {
1439
+ return Obj.has(this.action, "http");
1440
+ }
1441
+ /**
1442
+ * Determine if the route only responds to HTTPS requests.
1443
+ */
1444
+ httpsOnly() {
1445
+ return this.secure();
1446
+ }
1447
+ middleware(middleware) {
1448
+ if (!middleware) return Arr.wrap(this.action.middleware ?? []);
1449
+ if (!Array.isArray(middleware)) middleware = Arr.wrap(middleware);
1450
+ this.action.middleware = [...Arr.wrap(this.action.middleware ?? []), ...middleware];
1451
+ return this;
1452
+ }
1453
+ /**
1454
+ * Specify that the "Authorize" / "can" middleware should be applied to the route with the given options.
1455
+ *
1456
+ * @param ability
1457
+ * @param models
1458
+ */
1459
+ can(ability, models = []) {
1460
+ return !models ? this.middleware(["can:" + ability]) : this.middleware(["can:" + ability + "," + Arr.wrap(models).join(",")]);
1461
+ }
1462
+ /**
1463
+ * Set the action array for the route.
1464
+ *
1465
+ * @param action
1466
+ */
1467
+ setAction(action) {
1468
+ this.action = action;
1469
+ if (this.action.domain) this.domain(this.action.domain);
1470
+ if (this.action.can) for (const can of this.action.can) this.can(can[0], can[1] ?? []);
1471
+ return this;
1472
+ }
1473
+ /**
1474
+ * Determine if the route only responds to HTTPS requests.
1475
+ */
1476
+ secure() {
1477
+ return this.action.https === true;
1478
+ }
1479
+ /**
1480
+ * Sync the current route with H3
1481
+ *
1482
+ * @param h3App
1483
+ */
1484
+ sync(h3App) {
1485
+ for (const method of this.methods) h3App[method.toLowerCase()](this.getPath(), () => response);
1486
+ }
1487
+ /**
1488
+ * Bind the route to a given request for execution.
1489
+ *
1490
+ * @param request
1491
+ */
1492
+ bind(request) {
1493
+ this.compileRoute();
1494
+ this.parameters = new RouteParameterBinder(this).parameters(request);
1495
+ this.#originalParameters = this.parameters;
1496
+ return this;
1497
+ }
1498
+ /**
1499
+ * Get or set the domain for the route.
1500
+ *
1501
+ * @param domain
1502
+ *
1503
+ * @throws {InvalidArgumentException}
1504
+ */
1505
+ domain(domain) {
1506
+ if (!domain) return this.getDomain();
1507
+ const parsed = RouteUri.parse(domain);
1508
+ this.action.domain = parsed.uri ?? "";
1509
+ this.bindingFields = Object.assign({}, this.bindingFields, parsed.bindingFields);
1510
+ return this;
1511
+ }
1512
+ /**
1513
+ * Parse the route action into a standard array.
1514
+ *
1515
+ * @param action
1516
+ *
1517
+ * @throws {UnexpectedValueException}
1518
+ */
1519
+ parseAction(action) {
1520
+ return RouteAction.parse(this.#uri, action);
1521
+ }
1522
+ /**
1523
+ * Run the route action and return the response.
1524
+ */
1525
+ async run() {
1526
+ if (!this.container) {
1527
+ const { Container } = await import("@h3ravel/core");
1528
+ this.container = new Container();
1529
+ }
1530
+ try {
1531
+ if (this.isControllerAction()) return await this.runController();
1532
+ return this.runCallable();
1533
+ } catch (e) {
1534
+ if (typeof e.getResponse !== "undefined") return e.getResponse();
1535
+ throw e;
1536
+ }
1537
+ }
1538
+ /**
1539
+ * Get the key / value list of parameters without empty values.
1540
+ */
1541
+ parametersWithoutNulls() {
1542
+ return Object.fromEntries(Object.entries(this.getParameters()).filter((e) => !!e));
1543
+ }
1544
+ /**
1545
+ * Get the key / value list of original parameters for the route.
1546
+ *
1547
+ * @throws {LogicException}
1548
+ */
1549
+ originalParameters() {
1550
+ if (this.#originalParameters) return this.#originalParameters;
1551
+ throw new LogicException("Route is not bound.");
1552
+ }
1553
+ /**
1554
+ * Get the matched parameters object.
1555
+ */
1556
+ getParameters() {
1557
+ if (typeof this.parameters !== "undefined") return this.parameters;
1558
+ throw new LogicException("Route is not bound.");
1559
+ }
1560
+ /**
1561
+ * Get a given parameter from the route.
1562
+ *
1563
+ * @param name
1564
+ * @param defaultParam
1565
+ */
1566
+ parameter(name, defaultParam) {
1567
+ return Obj.get(this.getParameters(), name, defaultParam);
1568
+ }
1569
+ /**
1570
+ * Get the domain defined for the route.
1571
+ */
1572
+ getDomain() {
1573
+ if (this.action && this.action.domain) return this.action.domain.replace(/https?:\/\//, "");
1574
+ return "";
1575
+ }
1576
+ /**
1577
+ * Get the compiled version of the route.
1578
+ */
1579
+ getCompiled() {
1580
+ return this.compiled;
1581
+ }
1582
+ /**
1583
+ * Get the binding field for the given parameter.
1584
+ *
1585
+ * @param parameter
1586
+ */
1587
+ bindingFieldFor(parameter) {
1588
+ if (typeof parameter === "number") return Object.values(this.bindingFields)[parameter];
1589
+ return this.bindingFields[parameter];
1590
+ }
1591
+ /**
1592
+ * Get the binding fields for the route.
1593
+ */
1594
+ getBindingFields() {
1595
+ return this.bindingFields ?? {};
1596
+ }
1597
+ /**
1598
+ * Set the binding fields for the route.
1599
+ *
1600
+ * @param bindingFields
1601
+ */
1602
+ setBindingFields(bindingFields) {
1603
+ this.bindingFields = bindingFields;
1604
+ return this;
1605
+ }
1606
+ /**
1607
+ * Get the parent parameter of the given parameter.
1608
+ *
1609
+ * @param parameter
1610
+ */
1611
+ parentOfParameter(parameter) {
1612
+ const key = Object.keys(this.getParameters()).findIndex((e) => e == parameter);
1613
+ if (!key || key === 0) return;
1614
+ return Object.values(this.getParameters())[key - 1];
1615
+ }
1616
+ /**
1617
+ * Determines if the route allows "trashed" models to be retrieved when resolving implicit model bindings.
1618
+ */
1619
+ allowsTrashedBindings() {
1620
+ return this.withTrashedBindings;
1621
+ }
1622
+ /**
1623
+ * Set a default value for the route.
1624
+ *
1625
+ * @param key
1626
+ * @param value
1627
+ */
1628
+ defaults(key, value) {
1629
+ this._defaults[key] = value;
1630
+ return this;
1631
+ }
1632
+ /**
1633
+ * Set the default values for the route.
1634
+ *
1635
+ * @param defaults
1636
+ */
1637
+ setDefaults(defaults) {
1638
+ this._defaults = defaults;
1639
+ return this;
1640
+ }
1641
+ /**
1642
+ * Get the optional parameter names for the route.
1643
+ */
1644
+ getOptionalParameterNames() {
1645
+ const matches = [...this.uri().matchAll(/\{([\w]+)(?:[:][\w]+)?(\?)?\}/g)];
1646
+ const result = {};
1647
+ for (const match of matches) {
1648
+ const paramName = match[1];
1649
+ if (!!match[2]) result[paramName] = null;
1650
+ }
1651
+ return result;
1652
+ }
1653
+ /**
1654
+ * Get all of the parameter names for the route.
1655
+ */
1656
+ parameterNames() {
1657
+ if (this.#parameterNames) return this.#parameterNames;
1658
+ return this.#parameterNames = this.compileParameterNames();
1659
+ }
1660
+ /**
1661
+ * Checks whether the route's action is a controller.
1662
+ */
1663
+ isControllerAction() {
1664
+ return !!this.action.uses && isClass(this.action.uses);
1665
+ }
1666
+ compileParameterNames() {
1667
+ return [...((this.getDomain() ?? "") + this.uri()).matchAll(/\{([\w]+)(?:[:][\w]+)?\??\}/g)].map((m) => m[1]);
1668
+ }
1669
+ /**
1670
+ * Get the parameters that are listed in the route / controller signature.
1671
+ *
1672
+ * @param conditions
1673
+ */
1674
+ signatureParameters(conditions) {
1675
+ if (isClass(conditions)) conditions = { subClass: conditions };
1676
+ return RouteSignatureParameters.setRequirements(this.container, this).fromAction(this.action, conditions);
1677
+ }
1678
+ /**
1679
+ * Compile the route once, cache the result, return compiled data
1680
+ */
1681
+ compileRoute() {
1682
+ if (!this.compiled) {
1683
+ const optionalParams = this.getOptionalParameterNames();
1684
+ this.compiled = new CompiledRoute(this.uri(), optionalParams);
1685
+ }
1686
+ return this.compiled;
1687
+ }
1688
+ /**
1689
+ * Set a parameter to the given value.
1690
+ *
1691
+ * @param name
1692
+ * @param value
1693
+ */
1694
+ setParameter(name, value) {
1695
+ this.getParameters();
1696
+ this.parameters[name] = value;
1697
+ }
1698
+ /**
1699
+ * Unset a parameter on the route if it is set.
1700
+ *
1701
+ * @param name
1702
+ */
1703
+ forgetParameter(name) {
1704
+ this.getParameters();
1705
+ delete this.parameters[name];
1706
+ }
1707
+ /**
1708
+ * Get the value of the action that should be taken on a missing model exception.
1709
+ */
1710
+ getMissing() {
1711
+ return this.action["missing"] ?? void 0;
1712
+ }
1713
+ /**
1714
+ * The route path that can be handled by H3.
1715
+ */
1716
+ getPath() {
1717
+ return this.path;
1718
+ }
1719
+ /**
1720
+ * Define the callable that should be invoked on a missing model exception.
1721
+ *
1722
+ * @param missing
1723
+ */
1724
+ missing(missing) {
1725
+ this.action["missing"] = missing;
1726
+ return this;
1727
+ }
1728
+ /**
1729
+ * Specify middleware that should be removed from the given route.
1730
+ *
1731
+ * @param middleware
1732
+ */
1733
+ withoutMiddleware(middleware) {
1734
+ this.action.excluded_middleware = Object.assign({}, this.action.excluded_middleware ?? {}, Arr.wrap(middleware));
1735
+ return this;
1736
+ }
1737
+ /**
1738
+ * Get the middleware that should be removed from the route.
1739
+ */
1740
+ excludedMiddleware() {
1741
+ return this.action.excluded_middleware ?? {};
1742
+ }
1743
+ /**
1744
+ * Get all middleware, including the ones from the controller.
1745
+ */
1746
+ gatherMiddleware() {
1747
+ if (this.computedMiddleware) return this.computedMiddleware;
1748
+ this.computedMiddleware = Router.uniqueMiddleware([...this.middleware(), ...this.controllerMiddleware()]);
1749
+ return this.computedMiddleware;
1750
+ }
1751
+ /**
1752
+ * Indicate that the route should enforce scoping of multiple implicit Eloquent bindings.
1753
+ */
1754
+ scopeBindings() {
1755
+ this.action["scope_bindings"] = true;
1756
+ return this;
1757
+ }
1758
+ /**
1759
+ * Indicate that the route should not enforce scoping of multiple implicit Eloquent bindings.
1760
+ */
1761
+ withoutScopedBindings() {
1762
+ this.action["scope_bindings"] = false;
1763
+ return this;
1764
+ }
1765
+ /**
1766
+ * Determine if the route should enforce scoping of multiple implicit Eloquent bindings.
1767
+ */
1768
+ enforcesScopedBindings() {
1769
+ return this.action["scope_bindings"] ?? false;
1770
+ }
1771
+ /**
1772
+ * Determine if the route should prevent scoping of multiple implicit Eloquent bindings.
1773
+ */
1774
+ preventsScopedBindings() {
1775
+ return typeof this.action["scope_bindings"] !== "undefined" && this.action["scope_bindings"] === false;
1776
+ }
1777
+ /**
1778
+ * Get the dispatcher for the route's controller.
1779
+ *
1780
+ * @throws {BindingResolutionException}
1781
+ */
1782
+ controllerDispatcher() {
1783
+ if (this.container.bound(IControllerDispatcher)) return this.container.make(IControllerDispatcher);
1784
+ return new ControllerDispatcher(this.container);
1785
+ }
1786
+ /**
1787
+ * Get the route validators for the instance.
1788
+ *
1789
+ * @return array
1790
+ */
1791
+ static getValidators() {
1792
+ if (typeof this.validators !== "undefined") return this.validators;
1793
+ return this.validators = [
1794
+ new UriValidator(),
1795
+ new MethodValidator(),
1796
+ new SchemeValidator(),
1797
+ new HostValidator()
1798
+ ];
1799
+ }
1800
+ /**
1801
+ * Run the route action and return the response.
1802
+ *
1803
+ * @return mixed
1804
+ * @throws {NotFoundHttpException}
1805
+ */
1806
+ async runController() {
1807
+ return await this.controllerDispatcher().dispatch(this, this.getController(), this.getControllerMethod());
1808
+ }
1809
+ async runCallable() {
1810
+ const callable = this.action.uses;
1811
+ return this.container.make(ICallableDispatcher).dispatch(this, callable);
1812
+ }
1813
+ /**
1814
+ * Get the controller instance for the route.
1815
+ *
1816
+ * @return mixed
1817
+ *
1818
+ * @throws {BindingResolutionException}
1819
+ */
1820
+ getController() {
1821
+ if (!this.isControllerAction()) return;
1822
+ if (!this.controller) {
1823
+ const instance = this.getControllerClass();
1824
+ this.controller = this.container.make(instance);
1825
+ }
1826
+ return this.controller;
1827
+ }
1828
+ /**
1829
+ * Flush the cached container instance on the route.
1830
+ */
1831
+ flushController() {
1832
+ this.computedMiddleware = void 0;
1833
+ this.controller = void 0;
1834
+ }
1835
+ /**
1836
+ * Determine if the route matches a given request.
1837
+ *
1838
+ * @param request
1839
+ * @param includingMethod
1840
+ */
1841
+ matches(request, includingMethod = true) {
1842
+ this.compileRoute();
1843
+ for (const validator of Route.getValidators()) {
1844
+ if (!includingMethod && validator instanceof MethodValidator) continue;
1845
+ if (!validator.matches(this, request)) return false;
1846
+ }
1847
+ return true;
1848
+ }
1849
+ /**
1850
+ * Get the controller class used for the route.
1851
+ */
1852
+ getControllerClass() {
1853
+ return this.isControllerAction() ? this.action.uses : void 0;
1854
+ }
1855
+ /**
1856
+ * Get the controller method used for the route.
1857
+ */
1858
+ getControllerMethod() {
1859
+ const holder = isClass(this.action.uses) && typeof this.action.controller === "string" ? this.action.controller : "index";
1860
+ return Str.parseCallback(holder).at(1);
1861
+ }
1862
+ /**
1863
+ * Get the middleware for the route's controller.
1864
+ *
1865
+ * @return array
1866
+ */
1867
+ controllerMiddleware() {
1868
+ let controllerClass, ResourceMethod$1;
1869
+ if (!this.isControllerAction()) return [];
1870
+ if (typeof this.action.uses === "string") [controllerClass, ResourceMethod$1] = [this.getControllerClass(), this.getControllerMethod()];
1871
+ return [];
1872
+ }
1873
+ };
1874
+
1875
+ //#endregion
1876
+ //#region src/RouteCollection.ts
1877
+ var RouteCollection = class extends AbstractRouteCollection {
1878
+ /**
1879
+ * An array of the routes keyed by method.
1880
+ */
1881
+ routes = {};
1882
+ /**
1883
+ * A flattened array of all of the routes.
1884
+ */
1885
+ allRoutes = {};
1886
+ /**
1887
+ * A look-up table of routes by their names.
1888
+ */
1889
+ nameList = {};
1890
+ /**
1891
+ * A look-up table of routes by controller action.
1892
+ */
1893
+ actionList = {};
1894
+ /**
1895
+ * Add a Route instance to the collection.
1896
+ */
1897
+ add(route) {
1898
+ this.addToCollections(route);
1899
+ this.addLookups(route);
1900
+ return route;
1901
+ }
1902
+ /**
1903
+ * Add the given route to the arrays of routes.
1904
+ */
1905
+ addToCollections(route) {
1906
+ const domainAndUri = `${route.getDomain()}${route.uri()}`;
1907
+ for (const method of route.methods) {
1908
+ if (!this.routes[method]) this.routes[method] = {};
1909
+ this.routes[method][domainAndUri] = route;
1910
+ }
1911
+ this.allRoutes[route.methods.join("|") + domainAndUri] = route;
1912
+ }
1913
+ /**
1914
+ * Add the route to any look-up tables if necessary.
1915
+ */
1916
+ addLookups(route) {
1917
+ const name = route.getName();
1918
+ if (name && !this.inNameLookup(name)) this.nameList[name] = route;
1919
+ const action = route.getAction();
1920
+ const controller = action.controller ?? void 0;
1921
+ if (controller && !this.inActionLookup(controller)) this.addToActionList(action, route);
1922
+ }
1923
+ /**
1924
+ * Add a route to the controller action dictionary.
1925
+ */
1926
+ addToActionList(action, route) {
1927
+ const key = (typeof action.controller === "string" ? action.controller : action.controller?.constructor.name) ?? "";
1928
+ if (key) this.actionList[key] = route;
1929
+ }
1930
+ /**
1931
+ * Determine if the given controller is in the action lookup table.
1932
+ */
1933
+ inActionLookup(controller) {
1934
+ return Object.prototype.hasOwnProperty.call(this.actionList, controller);
1935
+ }
1936
+ /**
1937
+ * Determine if the given name is in the name lookup table.
1938
+ */
1939
+ inNameLookup(name) {
1940
+ return Object.prototype.hasOwnProperty.call(this.nameList, name);
1941
+ }
1942
+ /**
1943
+ * Refresh the name look-up table.
1944
+ *
1945
+ * This is done in case any names are fluently defined or if routes are overwritten.
1946
+ */
1947
+ refreshNameLookups() {
1948
+ this.nameList = {};
1949
+ for (const key of Object.keys(this.allRoutes)) {
1950
+ const route = this.allRoutes[key];
1951
+ const name = route.getName();
1952
+ if (name && !this.inNameLookup(name)) this.nameList[name] = route;
1953
+ }
1954
+ }
1955
+ /**
1956
+ * Refresh the action look-up table.
1957
+ *
1958
+ * This is done in case any actions are overwritten with new controllers.
1959
+ */
1960
+ refreshActionLookups() {
1961
+ this.actionList = {};
1962
+ for (const key of Object.keys(this.allRoutes)) {
1963
+ const route = this.allRoutes[key];
1964
+ const controller = route.getAction().controller ?? void 0;
1965
+ if (controller && !this.inActionLookup(controller)) this.addToActionList(route.getAction(), route);
1966
+ }
1967
+ }
1968
+ /**
1969
+ * Find the first route matching a given request.
1970
+ *
1971
+ * May throw framework-specific exceptions (MethodNotAllowed / NotFound).
1972
+ */
1973
+ match(request) {
1974
+ const routes = this.get(request.getMethod());
1975
+ const route = this.matchAgainstRoutes(routes, request);
1976
+ return this.handleMatchedRoute(request, route);
1977
+ }
1978
+ get(method) {
1979
+ if (typeof method === "undefined" || method === void 0) return this.getRoutes();
1980
+ return this.routes[method] ?? {};
1981
+ }
1982
+ /**
1983
+ * Determine if the route collection contains a given named route.
1984
+ */
1985
+ hasNamedRoute(name) {
1986
+ return this.getByName(name) !== void 0;
1987
+ }
1988
+ /**
1989
+ * Get a route instance by its name.
1990
+ */
1991
+ getByName(name) {
1992
+ return this.nameList[name] ?? void 0;
1993
+ }
1994
+ /**
1995
+ * Get a route instance by its controller action.
1996
+ */
1997
+ getByAction(action) {
1998
+ return this.actionList[action] ?? void 0;
1999
+ }
2000
+ /**
2001
+ * Get all of the routes in the collection.
2002
+ */
2003
+ getRoutes() {
2004
+ return Object.values(this.allRoutes);
2005
+ }
2006
+ /**
2007
+ * Get all of the routes keyed by their HTTP verb / method.
2008
+ */
2009
+ getRoutesByMethod() {
2010
+ return this.routes;
2011
+ }
2012
+ /**
2013
+ * Get all of the routes keyed by their name.
2014
+ */
2015
+ getRoutesByName() {
2016
+ return this.nameList;
2017
+ }
2018
+ };
2019
+
2020
+ //#endregion
2021
+ //#region src/RouteGroup.ts
2022
+ var RouteGroup = class RouteGroup {
2023
+ /**
2024
+ * Merge route groups into a new array.
2025
+ *
2026
+ * @param newAct
2027
+ * @param old
2028
+ * @param prependExistingPrefix
2029
+ */
2030
+ static merge(newAct, old, prependExistingPrefix = true) {
2031
+ if (newAct.domain) delete old.domain;
2032
+ if (newAct.controller) delete old.controller;
2033
+ newAct = Object.assign(RouteGroup.formatAs(newAct, old), {
2034
+ namespace: RouteGroup.formatNamespace(newAct, old),
2035
+ prefix: RouteGroup.formatPrefix(newAct, old, prependExistingPrefix),
2036
+ where: RouteGroup.formatWhere(newAct, old)
2037
+ });
2038
+ return Obj.deepMerge(Arr.except(old, [
2039
+ "namespace",
2040
+ "prefix",
2041
+ "where",
2042
+ "as"
2043
+ ]), newAct);
2044
+ }
2045
+ /**
2046
+ * Format the namespace for the new group attributes.
2047
+ *
2048
+ * @param newAct
2049
+ * @param old
2050
+ */
2051
+ static formatNamespace(newAct, old) {
2052
+ if (newAct.namespace) return !!old.namespace && !!newAct.namespace ? Str.trim(old.namespace, "/") + "/" + Str.trim(newAct.namespace, "/") : Str.trim(newAct.namespace, "/");
2053
+ return old.namespace ?? void 0;
2054
+ }
2055
+ /**
2056
+ * Format the prefix for the new group attributes.
2057
+ *
2058
+ * @param newAct
2059
+ * @param old
2060
+ * @param prependExistingPrefix
2061
+ */
2062
+ static formatPrefix(newAct, old, prependExistingPrefix = true) {
2063
+ const prefix = old.prefix ?? "";
2064
+ if (prependExistingPrefix) return newAct.prefix ? Str.trim(prefix, "/") + "/" + Str.trim(newAct.prefix, "/") : prefix;
2065
+ return newAct.prefix ? Str.trim(newAct["prefix"], "/") + "/" + Str.trim(prefix, "/") : prefix;
2066
+ }
2067
+ /**
2068
+ * Format the "wheres" for the new group attributes.
2069
+ *
2070
+ * @param newAct
2071
+ * @param old
2072
+ */
2073
+ static formatWhere(newAct, old) {
2074
+ return Object.assign({}, old.where ?? {}, newAct.where ?? {});
2075
+ }
2076
+ /**
2077
+ * Format the "as" clause of the new group attributes.
2078
+ *
2079
+ * @param newAct
2080
+ * @param old
2081
+ * @param prependExistingPrefix
2082
+ */
2083
+ static formatAs(newAct, old) {
2084
+ if (old.as) newAct.as = old.as + (newAct.as ?? "");
2085
+ return newAct;
2086
+ }
2087
+ };
2088
+
2089
+ //#endregion
2090
+ //#region src/Pipeline.ts
2091
+ var Pipeline = class {
2092
+ /**
2093
+ * The final callback to be executed after the pipeline ends regardless of the outcome.
2094
+ */
2095
+ finally;
2096
+ /**
2097
+ * Indicates whether to wrap the pipeline in a database transaction.
2098
+ */
2099
+ withinTransaction = false;
2100
+ /**
2101
+ * The container implementation.
2102
+ */
2103
+ container;
2104
+ /**
2105
+ * The object being passed through the pipeline.
2106
+ */
2107
+ passable;
2108
+ /**
2109
+ * The array of class pipes.
2110
+ */
2111
+ pipes = [];
2112
+ /**
2113
+ * The method to call on each pipe.
2114
+ */
2115
+ method = "handle";
2116
+ constructor(app) {
2117
+ this.container = app;
2118
+ }
2119
+ /**
2120
+ * Set the method to call on the pipes.
2121
+ *
2122
+ * @param method
2123
+ */
2124
+ via(method) {
2125
+ this.method = method;
2126
+ return this;
2127
+ }
2128
+ send(passable) {
2129
+ this.passable = passable;
2130
+ return this;
2131
+ }
2132
+ through(pipes) {
2133
+ this.pipes = pipes;
2134
+ return this;
2135
+ }
2136
+ /**
2137
+ * Run the pipeline with a final destination callback.
2138
+ *
2139
+ * @param destination
2140
+ */
2141
+ async then(destination) {
2142
+ const pipeline = [...this.pipes].reverse().reduce(this.carry(), this.prepareDestination(destination));
2143
+ try {
2144
+ if (this.withinTransaction !== false) return await this.getContainer().make("db").connection(this.withinTransaction).transaction(async () => {
2145
+ return pipeline(this.passable);
2146
+ });
2147
+ return await pipeline(this.passable);
2148
+ } finally {
2149
+ if (this.finally) this.finally(this.passable);
2150
+ }
2151
+ }
2152
+ /**
2153
+ * Run the pipeline and return the result.
2154
+ */
2155
+ async thenReturn() {
2156
+ return await this.then(async function(passable) {
2157
+ return passable;
2158
+ });
2159
+ }
2160
+ carry() {
2161
+ return (stack, pipe) => {
2162
+ return async (passable) => {
2163
+ try {
2164
+ if (typeof pipe === "function" && isCallable(pipe)) return await pipe(passable, stack);
2165
+ let instance = pipe;
2166
+ let parameters = [passable, stack];
2167
+ if (typeof pipe === "string") {
2168
+ const [name, extras] = this.parsePipeString(pipe);
2169
+ const bound = this.getContainer().boundMiddlewares(name);
2170
+ if (bound) {
2171
+ instance = this.getContainer().make(bound);
2172
+ parameters = [
2173
+ passable,
2174
+ stack,
2175
+ ...extras
2176
+ ];
2177
+ } else instance = async function(request, next) {
2178
+ Logger.error(`Error: Middleware [${name}] requested by [${request.getRequestUri()}] not bound: Skipping...`, false);
2179
+ return next;
2180
+ };
2181
+ } else if (typeof pipe === "function") instance = this.getContainer().make(pipe);
2182
+ const handler = instance[this.method] ?? instance;
2183
+ const result = Reflect.apply(handler, instance, parameters);
2184
+ return await this.handleCarry(result);
2185
+ } catch (e) {
2186
+ return this.handleException(passable, e);
2187
+ }
2188
+ };
2189
+ };
2190
+ }
2191
+ async handleCarry(carry) {
2192
+ if (typeof carry?.then === "function") return await carry;
2193
+ return carry;
2194
+ }
2195
+ /**
2196
+ * Get the final piece of the Closure onion.
2197
+ *
2198
+ * @param destination
2199
+ */
2200
+ prepareDestination(destination) {
2201
+ return async (passable) => {
2202
+ try {
2203
+ return await destination(passable ?? this.passable);
2204
+ } catch (e) {
2205
+ return this.handleException(passable ?? this.passable, e);
2206
+ }
2207
+ };
2208
+ }
2209
+ /**
2210
+ * Handle the given exception.
2211
+ *
2212
+ * @param _passable
2213
+ * @param e
2214
+ * @throws {Error}
2215
+ */
2216
+ handleException(_passable, e) {
2217
+ throw e;
2218
+ }
2219
+ /**
2220
+ * Parse full pipe string to get name and parameters.
2221
+ *
2222
+ * @param pipe
2223
+ */
2224
+ parsePipeString(pipe) {
2225
+ const [name, paramString] = pipe.split(":");
2226
+ return [name, paramString ? paramString.split(",") : []];
2227
+ }
2228
+ /**
2229
+ * Set the container instance.
2230
+ *
2231
+ * @param container
2232
+ */
2233
+ setContainer(container) {
2234
+ this.container = container;
2235
+ return this;
2236
+ }
2237
+ /**
2238
+ * Execute each pipeline step within a database transaction.
2239
+ *
2240
+ * @param withinTransaction
2241
+ */
2242
+ setWithinTransaction(withinTransaction) {
2243
+ this.withinTransaction = withinTransaction;
2244
+ return this;
2245
+ }
2246
+ /**
2247
+ * Set a final callback to be executed after the pipeline ends regardless of the outcome.
2248
+ *
2249
+ * @param callback
2250
+ */
2251
+ setFinally(callback) {
2252
+ this.finally = callback;
2253
+ return this;
2254
+ }
2255
+ /**
2256
+ * Get the container instance.
2257
+ */
2258
+ getContainer() {
2259
+ if (!this.container) throw new RuntimeException("A container instance has not been passed to the Pipeline.");
2260
+ return this.container;
2261
+ }
2262
+ };
2263
+
2264
+ //#endregion
2265
+ //#region ../support/src/Helpers.ts
2266
+ /**
2267
+ * Variadic helper function
2268
+ *
2269
+ * @param args
2270
+ */
2271
+ function variadic(args) {
2272
+ if (Array.isArray(args[0])) return args[0];
2273
+ return args;
2274
+ }
2275
+
2276
+ //#endregion
2277
+ //#region src/PendingSingletonResourceRegistration.ts
2278
+ var PendingSingletonResourceRegistration = class extends use(Finalizable, CreatesRegularExpressionRouteConstraints, Macroable) {
2279
+ /**
2280
+ * The resource registrar.
2281
+ */
2282
+ registrar;
2283
+ /**
2284
+ * The resource name.
2285
+ */
2286
+ name;
2287
+ /**
2288
+ * The resource controller.
2289
+ */
2290
+ controller;
2291
+ /**
2292
+ * The resource options.
2293
+ */
2294
+ options = {};
2295
+ /**
2296
+ * The resource's registration status.
2297
+ */
2298
+ registered = false;
2299
+ /**
2300
+ * Create a new pending singleton resource registration instance.
2301
+ *
2302
+ * @param registrar
2303
+ * @param name
2304
+ * @param controller
2305
+ * @param options
2306
+ */
2307
+ constructor(registrar, name, controller, options) {
2308
+ super();
2309
+ this.name = name;
2310
+ this.options = options;
2311
+ this.registrar = registrar;
2312
+ this.controller = controller;
2313
+ }
2314
+ /**
2315
+ * Set the methods the controller should apply to.
2316
+ *
2317
+ * @param methods
2318
+ */
2319
+ only(...methods) {
2320
+ this.options.only = variadic(methods);
2321
+ return this;
2322
+ }
2323
+ /**
2324
+ * Set the methods the controller should exclude.
2325
+ *
2326
+ * @param methods
2327
+ */
2328
+ except(...methods) {
2329
+ this.options.except = variadic(methods);
2330
+ return this;
2331
+ }
2332
+ /**
2333
+ * Indicate that the resource should have creation and storage routes.
2334
+ *
2335
+ * @return this
2336
+ */
2337
+ creatable() {
2338
+ this.options.creatable = true;
2339
+ return this;
2340
+ }
2341
+ /**
2342
+ * Indicate that the resource should have a deletion route.
2343
+ *
2344
+ * @return this
2345
+ */
2346
+ destroyable() {
2347
+ this.options.destroyable = true;
2348
+ return this;
2349
+ }
2350
+ /**
2351
+ * Set the route names for controller actions.
2352
+ *
2353
+ * @param names
2354
+ */
2355
+ names(names) {
2356
+ this.options.names = names;
2357
+ return this;
2358
+ }
2359
+ /**
2360
+ * Set the route name for a controller action.
2361
+ *
2362
+ * @param method
2363
+ * @param name
2364
+ */
2365
+ setName(method, name) {
2366
+ if (this.options.names) this.options.names[method] = name;
2367
+ else this.options.names = { [method]: name };
2368
+ return this;
2369
+ }
2370
+ /**
2371
+ * Override the route parameter names.
2372
+ *
2373
+ * @param parameters
2374
+ */
2375
+ parameters(parameters) {
2376
+ this.options.parameters = parameters;
2377
+ return this;
2378
+ }
2379
+ /**
2380
+ * Override a route parameter's name.
2381
+ *
2382
+ * @param previous
2383
+ * @param newValue
2384
+ */
2385
+ parameter(previous, newValue) {
2386
+ this.options.parameters[previous] = newValue;
2387
+ return this;
2388
+ }
2389
+ /**
2390
+ * Add middleware to the resource routes.
2391
+ *
2392
+ * @param middleware
2393
+ */
2394
+ middleware(middleware) {
2395
+ const middlewares = Arr.wrap(middleware);
2396
+ for (let key = 0; key < middlewares.length; key++) middlewares[key] = middlewares[key];
2397
+ this.options.middleware = middlewares;
2398
+ if (typeof this.options.middleware_for !== "undefined") for (const [method, value] of Object.entries(this.options.middleware_for ?? {})) this.options.middleware_for[method] = Router.uniqueMiddleware([...Arr.wrap(value), ...middlewares]);
2399
+ return this;
2400
+ }
2401
+ /**
2402
+ * Specify middleware that should be added to the specified resource routes.
2403
+ *
2404
+ * @param methods
2405
+ * @param middleware
2406
+ */
2407
+ middlewareFor(methods, middleware) {
2408
+ methods = Arr.wrap(methods);
2409
+ let middlewares = Arr.wrap(middleware);
2410
+ if (typeof this.options.middleware !== "undefined") middlewares = Router.uniqueMiddleware([...this.options.middleware ?? [], ...middlewares]);
2411
+ for (const method of methods) if (this.options.middleware_for) this.options.middleware_for[method] = middlewares;
2412
+ else this.options.middleware_for = { [method]: middlewares };
2413
+ return this;
2414
+ }
2415
+ /**
2416
+ * Specify middleware that should be removed from the resource routes.
2417
+ *
2418
+ * @param middleware
2419
+ */
2420
+ withoutMiddleware(middleware) {
2421
+ this.options.excluded_middleware = [...this.options.excluded_middleware ?? [], ...Arr.wrap(middleware)];
2422
+ return this;
2423
+ }
2424
+ /**
2425
+ * Specify middleware that should be removed from the specified resource routes.
2426
+ *
2427
+ * @param methods
2428
+ * @param middleware
2429
+ */
2430
+ withoutMiddlewareFor(methods, middleware) {
2431
+ methods = Arr.wrap(methods);
2432
+ const middlewares = Arr.wrap(middleware);
2433
+ for (const method of methods) if (this.options.excluded_middleware_for) this.options.excluded_middleware_for[method] = middlewares;
2434
+ else this.options.excluded_middleware_for = { [method]: middlewares };
2435
+ return this;
2436
+ }
2437
+ /**
2438
+ * Add "where" constraints to the resource routes.
2439
+ *
2440
+ * @param wheres
2441
+ */
2442
+ where(wheres) {
2443
+ this.options.wheres = wheres;
2444
+ return this;
2445
+ }
2446
+ /**
2447
+ * Register the singleton resource route.
2448
+ */
2449
+ register() {
2450
+ this.registered = true;
2451
+ return this.registrar.singleton(this.name, this.controller, this.options);
2452
+ }
2453
+ $finalize(e) {
2454
+ if (!this.registered) (e ?? this).register();
2455
+ return this ?? e;
2456
+ }
2457
+ };
2458
+
2459
+ //#endregion
2460
+ //#region src/ResourceRegistrar.ts
2461
+ var ResourceRegistrar = class ResourceRegistrar {
2462
+ /**
2463
+ * The router instance.
2464
+ */
2465
+ router;
2466
+ /**
2467
+ * The default actions for a resourceful controller.
2468
+ */
2469
+ resourceDefaults = [
2470
+ "index",
2471
+ "create",
2472
+ "store",
2473
+ "show",
2474
+ "edit",
2475
+ "update",
2476
+ "destroy"
2477
+ ];
2478
+ /**
2479
+ * The default actions for a singleton resource controller.
2480
+ */
2481
+ singletonResourceDefaults = [
2482
+ "show",
2483
+ "edit",
2484
+ "update"
2485
+ ];
2486
+ /**
2487
+ * The parameters set for this resource instance.
2488
+ */
2489
+ parameters;
2490
+ /**
2491
+ * The global parameter mapping.
2492
+ */
2493
+ static parameterMap = {};
2494
+ /**
2495
+ * Singular global parameters.
2496
+ */
2497
+ static _singularParameters = true;
2498
+ /**
2499
+ * The verbs used in the resource URIs.
2500
+ */
2501
+ static _verbs = {
2502
+ create: "create",
2503
+ edit: "edit"
2504
+ };
2505
+ /**
2506
+ * Create a new resource registrar instance.
2507
+ *
2508
+ * @param router
2509
+ */
2510
+ constructor(router) {
2511
+ this.router = router;
2512
+ }
2513
+ /**
2514
+ * Route a resource to a controller.
2515
+ *
2516
+ * @param name
2517
+ * @param controller
2518
+ * @param options
2519
+ */
2520
+ register(name, controller, options = {}) {
2521
+ if (typeof options.parameters !== "undefined" && this.parameters == null) this.parameters = options.parameters;
2522
+ if (name.includes("/")) {
2523
+ this.prefixedResource(name, controller, options);
2524
+ return;
2525
+ }
2526
+ const base = this.getResourceWildcard(name.split("+").at(-1));
2527
+ const defaults = this.resourceDefaults;
2528
+ const collection = new RouteCollection();
2529
+ const resourceMethods = this.getResourceMethods(defaults, options);
2530
+ for (const m of resourceMethods) {
2531
+ const optionsForMethod = options;
2532
+ if (typeof optionsForMethod.middleware_for?.[m] !== "undefined") optionsForMethod.middleware = optionsForMethod.middleware_for?.[m];
2533
+ if (typeof optionsForMethod.excluded_middleware_for?.[m] !== "undefined") optionsForMethod.excluded_middleware = Router.uniqueMiddleware([...optionsForMethod.excluded_middleware ?? [], ...optionsForMethod.excluded_middleware_for[m]]);
2534
+ const route = this["addResource" + Str.ucfirst(m)](name, base, controller, optionsForMethod);
2535
+ if (typeof options.bindingFields !== "undefined") this.setResourceBindingFields(route, options.bindingFields);
2536
+ const allowed = options.trashed != null ? options.trashed : resourceMethods.filter((m$1) => [
2537
+ "show",
2538
+ "edit",
2539
+ "update"
2540
+ ].includes(m$1));
2541
+ if (typeof options.trashed !== "undefined" && allowed.includes(m)) route.withTrashed();
2542
+ collection.add(route);
2543
+ }
2544
+ return collection;
2545
+ }
2546
+ /**
2547
+ * Route a singleton resource to a controller.
2548
+ *
2549
+ * @param name
2550
+ * @param controller
2551
+ * @param options
2552
+ */
2553
+ singleton(name, controller, options = {}) {
2554
+ if (typeof options.parameters !== "undefined" && this.parameters == null) this.parameters = options.parameters;
2555
+ if (name.includes("/")) {
2556
+ this.prefixedSingleton(name, controller, options);
2557
+ return;
2558
+ }
2559
+ let defaults = this.singletonResourceDefaults;
2560
+ if (typeof options.creatable !== "undefined") defaults = defaults.concat([
2561
+ "create",
2562
+ "store",
2563
+ "destroy"
2564
+ ]);
2565
+ else if (typeof options.destroyable !== "undefined") defaults = defaults.concat(["destroy"]);
2566
+ const collection = new RouteCollection();
2567
+ const resourceMethods = this.getResourceMethods(defaults, options);
2568
+ for (const m of resourceMethods) {
2569
+ const optionsForMethod = options;
2570
+ if (typeof optionsForMethod.middleware_for?.[m] !== "undefined") optionsForMethod.middleware = optionsForMethod.middleware_for[m];
2571
+ if (typeof optionsForMethod.excluded_middleware_for?.[m] !== "undefined") optionsForMethod.excluded_middleware = Router.uniqueMiddleware([...optionsForMethod.excluded_middleware ?? [], ...optionsForMethod.excluded_middleware_for[m]]);
2572
+ const route = this["addSingleton" + Str.ucfirst(m)](name, controller, optionsForMethod);
2573
+ if (typeof options.bindingFields !== "undefined") this.setResourceBindingFields(route, options.bindingFields);
2574
+ collection.add(route);
2575
+ }
2576
+ return collection;
2577
+ }
2578
+ /**
2579
+ * Build a set of prefixed resource routes.
2580
+ *
2581
+ * @param name
2582
+ * @param controller
2583
+ * @param options
2584
+ */
2585
+ prefixedResource(name, controller, options) {
2586
+ let prefix;
2587
+ [name, prefix] = this.getResourcePrefix(name);
2588
+ const callback = (me) => {
2589
+ me.resource(name, controller, options);
2590
+ };
2591
+ return this.router.group({ prefix }, callback);
2592
+ }
2593
+ /**
2594
+ * Build a set of prefixed singleton routes.
2595
+ *
2596
+ * @param name
2597
+ * @param controller
2598
+ * @param options
2599
+ */
2600
+ prefixedSingleton(name, controller, options) {
2601
+ let prefix;
2602
+ [name, prefix] = this.getResourcePrefix(name);
2603
+ const callback = function(me) {
2604
+ me.singleton(name, controller, options);
2605
+ };
2606
+ return this.router.group({ prefix }, callback);
2607
+ }
2608
+ /**
2609
+ * Extract the resource and prefix from a resource name.
2610
+ *
2611
+ * @param name
2612
+ *
2613
+ */
2614
+ getResourcePrefix(name) {
2615
+ const segments = name.split("/");
2616
+ const prefix = segments.slice(0, -1).join("/");
2617
+ return [segments.at(-1), prefix];
2618
+ }
2619
+ /**
2620
+ * Get the applicable resource methods.
2621
+ *
2622
+ * @param defaults
2623
+ * @param options
2624
+ *
2625
+ */
2626
+ getResourceMethods(defaults, options) {
2627
+ let methods = defaults;
2628
+ if (typeof options.only !== "undefined") methods = methods.filter((m) => new Set(options.only).has(m));
2629
+ if (typeof options.except !== "undefined") methods = methods.filter((m) => !new Set(options.except).has(m));
2630
+ return methods;
2631
+ }
2632
+ /**
2633
+ * Add the index method for a resourceful route.
2634
+ *
2635
+ * @param name
2636
+ * @param base
2637
+ * @param controller
2638
+ * @param options
2639
+ */
2640
+ addResourceIndex(name, _base, controller, options) {
2641
+ const uri = this.getResourceUri(name);
2642
+ delete options.missing;
2643
+ const action = this.getResourceAction(name, controller, "index", options);
2644
+ return this.router.get(uri, action);
2645
+ }
2646
+ /**
2647
+ * Add the create method for a resourceful route.
2648
+ *
2649
+ * @param name
2650
+ * @param base
2651
+ * @param controller
2652
+ * @param options
2653
+ */
2654
+ addResourceCreate(name, base, controller, options) {
2655
+ const uri = this.getResourceUri(name) + "/" + ResourceRegistrar._verbs["create"];
2656
+ delete options.missing;
2657
+ const action = this.getResourceAction(name, controller, "create", options);
2658
+ return this.router.get(uri, action);
2659
+ }
2660
+ /**
2661
+ * Add the store method for a resourceful route.
2662
+ *
2663
+ * @param name
2664
+ * @param base
2665
+ * @param controller
2666
+ * @param options
2667
+ */
2668
+ addResourceStore(name, base, controller, options) {
2669
+ const uri = this.getResourceUri(name);
2670
+ delete options.missing;
2671
+ const action = this.getResourceAction(name, controller, "store", options);
2672
+ return this.router.post(uri, action);
2673
+ }
2674
+ /**
2675
+ * Add the show method for a resourceful route.
2676
+ *
2677
+ * @param name
2678
+ * @param base
2679
+ * @param controller
2680
+ * @param options
2681
+ */
2682
+ addResourceShow(name, base, controller, options) {
2683
+ name = this.getShallowName(name, options);
2684
+ const uri = this.getResourceUri(name) + "/{" + base + "}";
2685
+ const action = this.getResourceAction(name, controller, "show", options);
2686
+ return this.router.get(uri, action);
2687
+ }
2688
+ /**
2689
+ * Add the edit method for a resourceful route.
2690
+ *
2691
+ * @param name
2692
+ * @param base
2693
+ * @param controller
2694
+ * @param options
2695
+ */
2696
+ addResourceEdit(name, base, controller, options) {
2697
+ name = this.getShallowName(name, options);
2698
+ const uri = this.getResourceUri(name) + "/{" + base + "}/" + ResourceRegistrar._verbs["edit"];
2699
+ const action = this.getResourceAction(name, controller, "edit", options);
2700
+ return this.router.get(uri, action);
2701
+ }
2702
+ /**
2703
+ * Add the update method for a resourceful route.
2704
+ *
2705
+ * @param name
2706
+ * @param base
2707
+ * @param controller
2708
+ * @param options
2709
+ */
2710
+ addResourceUpdate(name, base, controller, options) {
2711
+ name = this.getShallowName(name, options);
2712
+ const uri = this.getResourceUri(name) + "/{" + base + "}";
2713
+ const action = this.getResourceAction(name, controller, "update", options);
2714
+ return this.router.match(["PUT", "PATCH"], uri, action);
2715
+ }
2716
+ /**
2717
+ * Add the destroy method for a resourceful route.
2718
+ *
2719
+ * @param name
2720
+ * @param base
2721
+ * @param controller
2722
+ * @param options
2723
+ */
2724
+ addResourceDestroy(name, base, controller, options) {
2725
+ name = this.getShallowName(name, options);
2726
+ const uri = this.getResourceUri(name) + "/{" + base + "}";
2727
+ const action = this.getResourceAction(name, controller, "destroy", options);
2728
+ return this.router.delete(uri, action);
2729
+ }
2730
+ /**
2731
+ * Add the create method for a singleton route.
2732
+ *
2733
+ * @param name
2734
+ * @param controller
2735
+ * @param options
2736
+ */
2737
+ addSingletonCreate(name, controller, options) {
2738
+ const uri = this.getResourceUri(name) + "/" + ResourceRegistrar._verbs["create"];
2739
+ delete options.missing;
2740
+ const action = this.getResourceAction(name, controller, "create", options);
2741
+ return this.router.get(uri, action);
2742
+ }
2743
+ /**
2744
+ * Add the store method for a singleton route.
2745
+ *
2746
+ * @param name
2747
+ * @param controller
2748
+ * @param options
2749
+ */
2750
+ addSingletonStore(name, controller, options) {
2751
+ const uri = this.getResourceUri(name);
2752
+ delete options.missing;
2753
+ const action = this.getResourceAction(name, controller, "store", options);
2754
+ return this.router.post(uri, action);
2755
+ }
2756
+ /**
2757
+ * Add the show method for a singleton route.
2758
+ *
2759
+ * @param name
2760
+ * @param controller
2761
+ * @param options
2762
+ */
2763
+ addSingletonShow(name, controller, options) {
2764
+ const uri = this.getResourceUri(name);
2765
+ delete options.missing;
2766
+ const action = this.getResourceAction(name, controller, "show", options);
2767
+ return this.router.get(uri, action);
2768
+ }
2769
+ /**
2770
+ * Add the edit method for a singleton route.
2771
+ *
2772
+ * @param name
2773
+ * @param controller
2774
+ * @param options
2775
+ */
2776
+ addSingletonEdit(name, controller, options) {
2777
+ name = this.getShallowName(name, options);
2778
+ const uri = this.getResourceUri(name) + "/" + ResourceRegistrar._verbs["edit"];
2779
+ const action = this.getResourceAction(name, controller, "edit", options);
2780
+ return this.router.get(uri, action);
2781
+ }
2782
+ /**
2783
+ * Add the update method for a singleton route.
2784
+ *
2785
+ * @param name
2786
+ * @param controller
2787
+ * @param options
2788
+ */
2789
+ addSingletonUpdate(name, controller, options) {
2790
+ name = this.getShallowName(name, options);
2791
+ const uri = this.getResourceUri(name);
2792
+ const action = this.getResourceAction(name, controller, "update", options);
2793
+ return this.router.match(["PUT", "PATCH"], uri, action);
2794
+ }
2795
+ /**
2796
+ * Add the destroy method for a singleton route.
2797
+ *
2798
+ * @param name
2799
+ * @param controller
2800
+ * @param options
2801
+ */
2802
+ addSingletonDestroy(name, controller, options) {
2803
+ name = this.getShallowName(name, options);
2804
+ const uri = this.getResourceUri(name);
2805
+ const action = this.getResourceAction(name, controller, "destroy", options);
2806
+ return this.router.delete(uri, action);
2807
+ }
2808
+ /**
2809
+ * Get the name for a given resource with shallowness applied when applicable.
2810
+ *
2811
+ * @param name
2812
+ * @param options
2813
+ *
2814
+ */
2815
+ getShallowName(name, options) {
2816
+ return typeof options.shallow !== "undefined" && options.shallow ? name.split("+").at(-1) : name;
2817
+ }
2818
+ /**
2819
+ * Set the route's binding fields if the resource is scoped.
2820
+ *
2821
+ * @param \Illuminate\Routing\Route route
2822
+ * @param bindingFields
2823
+ *
2824
+ */
2825
+ setResourceBindingFields(route, bindingFields) {
2826
+ const matches = [...route.uri().matchAll(/(?<={).*?(?=})/g)];
2827
+ const fields = Object.fromEntries(matches.map((m) => [m[0], null]));
2828
+ const intersected = Object.fromEntries(Object.keys(fields).filter((k) => k in bindingFields).map((k) => [k, bindingFields[k]]));
2829
+ route.setBindingFields({
2830
+ ...fields,
2831
+ ...intersected
2832
+ });
2833
+ }
2834
+ /**
2835
+ * Get the base resource URI for a given resource.
2836
+ *
2837
+ * @param resource
2838
+ *
2839
+ */
2840
+ getResourceUri(resource) {
2841
+ if (!resource.includes("+")) return resource;
2842
+ const segments = resource.split("+");
2843
+ return this.getNestedResourceUri(segments).replaceAll("/{" + this.getResourceWildcard(segments.at(-1)) + "}", "");
2844
+ }
2845
+ /**
2846
+ * Get the URI for a nested resource segment array.
2847
+ *
2848
+ * @param segments
2849
+ */
2850
+ getNestedResourceUri(segments) {
2851
+ return segments.map((s) => {
2852
+ return s + "/{" + this.getResourceWildcard(s) + "}";
2853
+ }).join("/");
2854
+ }
2855
+ /**
2856
+ * Format a resource parameter for usage.
2857
+ *
2858
+ * @param value
2859
+ */
2860
+ getResourceWildcard(value) {
2861
+ if (typeof this.parameters === "object" && typeof this.parameters?.[value] !== "undefined") value = this.parameters[value];
2862
+ else if (typeof ResourceRegistrar.parameterMap[value] !== "undefined") value = ResourceRegistrar.parameterMap[value];
2863
+ else if (this.parameters === "singular" || ResourceRegistrar._singularParameters) value = Str.singular(value);
2864
+ return value.replaceAll("-", "_");
2865
+ }
2866
+ /**
2867
+ * Get the action array for a resource route.
2868
+ *
2869
+ * @param resource
2870
+ * @param controller
2871
+ * @param method
2872
+ * @param options
2873
+ *
2874
+ */
2875
+ getResourceAction(resource, controller, method, options) {
2876
+ const name = this.getResourceRouteName(resource, method, options);
2877
+ const action = {
2878
+ "as": name,
2879
+ "uses": controller,
2880
+ "controller": controller.constructor.name + "@" + method
2881
+ };
2882
+ if (typeof options.middleware !== "undefined") action.middleware = options.middleware;
2883
+ if (typeof options.excluded_middleware !== "undefined") action.excluded_middleware = options.excluded_middleware;
2884
+ if (typeof options.wheres !== "undefined") action.where = options.wheres;
2885
+ if (typeof options.missing !== "undefined") action.missing = options.missing;
2886
+ return action;
2887
+ }
2888
+ /**
2889
+ * Get the name for a given resource.
2890
+ *
2891
+ * @param resource
2892
+ * @param method
2893
+ * @param options
2894
+ *
2895
+ */
2896
+ getResourceRouteName(resource, method, options) {
2897
+ let name = resource;
2898
+ if (typeof options.names !== "undefined") {
2899
+ if (typeof options.names === "string") name = options.names;
2900
+ else if (typeof options.names[method] !== "undefined") return options.names[method];
2901
+ }
2902
+ return `${typeof options.as !== "undefined" ? options.as + "+" : ""}${name}.${method}`.replace(/^\++|\++$/g, "");
2903
+ }
2904
+ /**
2905
+ * Set or unset the unmapped global parameters to singular.
2906
+ *
2907
+ * @param singular
2908
+ */
2909
+ static singularParameters(singular = true) {
2910
+ this._singularParameters = singular;
2911
+ }
2912
+ /**
2913
+ * Get the global parameter map.
2914
+ */
2915
+ static getParameters() {
2916
+ return this.parameterMap;
2917
+ }
2918
+ /**
2919
+ * Set the global parameter mapping.
2920
+ *
2921
+ * @param $parameters
2922
+ *
2923
+ */
2924
+ static setParameters(parameters = []) {
2925
+ this.parameterMap = parameters;
2926
+ }
2927
+ /**
2928
+ * Get or set the action verbs used in the resource URIs.
2929
+ *
2930
+ * @param verbs
2931
+ *
2932
+ */
2933
+ static verbs(verbs = {}) {
2934
+ if (Object.entries(verbs).length < 1) return ResourceRegistrar._verbs;
2935
+ ResourceRegistrar._verbs = {
2936
+ ...ResourceRegistrar._verbs,
2937
+ ...verbs
2938
+ };
2939
+ }
2940
+ };
2941
+
2942
+ //#endregion
2943
+ //#region src/RouteRegisterer.ts
2944
+ var _ref;
2945
+ const Inference = trait((e) => class extends e {});
2946
+ let RouteRegistrar = class RouteRegistrar$1 extends use(Inference, CreatesRegularExpressionRouteConstraints, Macroable, UseMagic) {
2947
+ router;
2948
+ attributes = {};
2949
+ passthru = [
2950
+ "get",
2951
+ "post",
2952
+ "put",
2953
+ "patch",
2954
+ "delete",
2955
+ "options",
2956
+ "any"
2957
+ ];
2958
+ allowedAttributes = [
2959
+ "as",
2960
+ "can",
2961
+ "controller",
2962
+ "domain",
2963
+ "middleware",
2964
+ "missing",
2965
+ "name",
2966
+ "namespace",
2967
+ "prefix",
2968
+ "scopeBindings",
2969
+ "where",
2970
+ "withoutMiddleware",
2971
+ "withoutScopedBindings"
2972
+ ];
2973
+ aliases = {
2974
+ name: "as",
2975
+ scopeBindings: "scope_bindings",
2976
+ withoutScopedBindings: "scope_bindings",
2977
+ withoutMiddleware: "excluded_middleware"
2978
+ };
2979
+ constructor(router) {
2980
+ super();
2981
+ this.router = router;
2982
+ this.group;
2983
+ }
2984
+ attribute(key, value) {
2985
+ if (!this.allowedAttributes.includes(key)) throw new Error(`Attribute [${key}] does not exist.`);
2986
+ if (key === "middleware") value = Arr.wrap(value).filter(Boolean).map(String);
2987
+ const attributeKey = this.aliases[key] ?? key;
2988
+ if (key === "withoutMiddleware") value = [...this.attributes[attributeKey] ?? [], ...Arr.wrap(value)];
2989
+ if (key === "withoutScopedBindings") value = false;
2990
+ this.attributes[attributeKey] = value;
2991
+ return this;
2992
+ }
2993
+ resource(name, controller, options = {}) {
2994
+ return this.router.resource(name, controller, {
2995
+ ...this.attributes,
2996
+ ...options
2997
+ });
2998
+ }
2999
+ apiResource(name, controller, options = {}) {
3000
+ return this.router.apiResource(name, controller, {
3001
+ ...this.attributes,
3002
+ ...options
3003
+ });
3004
+ }
3005
+ singleton(name, controller, options = {}) {
3006
+ return this.router.singleton(name, controller, {
3007
+ ...this.attributes,
3008
+ ...options
3009
+ });
3010
+ }
3011
+ apiSingleton(name, controller, options = {}) {
3012
+ return this.router.apiSingleton(name, controller, {
3013
+ ...this.attributes,
3014
+ ...options
3015
+ });
3016
+ }
3017
+ group(callback) {
3018
+ this.router.group(this.attributes, callback);
3019
+ return this;
3020
+ }
3021
+ match(methods, uri, action) {
3022
+ return this.router.match(methods, uri, this.compileAction(action));
3023
+ }
3024
+ registerRoute(method, uri, action) {
3025
+ if (!Array.isArray(action)) action = {
3026
+ ...this.attributes,
3027
+ ...action ? { uses: action } : {}
3028
+ };
3029
+ return this.router[method](uri, this.compileAction(action));
3030
+ }
3031
+ compileAction(action) {
3032
+ if (action == null) return this.attributes;
3033
+ if (typeof action === "string" || typeof action === "function") action = { uses: action };
3034
+ if (Array.isArray(action) && action.length === 2 && typeof action[0] === "string") {
3035
+ const controller = action[0].startsWith("\\") ? action[0] : `\\${action[0]}`;
3036
+ action = {
3037
+ uses: `${controller}@${action[1]}`,
3038
+ controller: `${controller}@${action[1]}`
3039
+ };
3040
+ }
3041
+ return {
3042
+ ...this.attributes,
3043
+ ...action
3044
+ };
3045
+ }
3046
+ /**
3047
+ * PHP __call equivalent
3048
+ * Handled via Proxy in Magic
3049
+ */
3050
+ __call(method, parameters) {
3051
+ if (this.constructor.hasMacro?.(method)) return this.macroCall(method, parameters);
3052
+ if (this.passthru.includes(method)) return Reflect.apply(this.registerRoute, this, [method, ...parameters]);
3053
+ if (this.allowedAttributes.includes(method)) {
3054
+ if (method === "middleware") return this.attribute(method, Array.isArray(parameters[0]) ? parameters[0] : parameters);
3055
+ if (method === "can") return this.attribute(method, [parameters]);
3056
+ return this.attribute(method, parameters.length ? parameters[0] : true);
3057
+ }
3058
+ throw new Error(`Method ${this.constructor.name}::${method} does not exist.`);
3059
+ }
3060
+ };
3061
+ RouteRegistrar = __decorate([Injectable(), __decorateMetadata("design:paramtypes", [typeof (_ref = typeof Router !== "undefined" && Router) === "function" ? _ref : Object])], RouteRegistrar);
3062
+
3063
+ //#endregion
3064
+ //#region src/Router.ts
3065
+ var Router = class Router extends mix(IRouter, MacroableClass, Magic) {
3066
+ DIST_DIR;
3067
+ routes;
3068
+ routeNames = [];
3069
+ current;
3070
+ currentRequest;
3071
+ middlewareMap = [];
3072
+ groupMiddleware = [];
3073
+ /**
3074
+ * The registered route value binders.
3075
+ */
3076
+ binders = {};
3077
+ /**
3078
+ * All of the short-hand keys for middlewares.
3079
+ */
3080
+ middlewares = {};
3081
+ /**
3082
+ * All of the middleware groups.
3083
+ */
3084
+ middlewareGroups = {};
3085
+ /**
3086
+ * The route group attribute stack.
3087
+ */
3088
+ groupStack = [];
3089
+ /**
3090
+ * The event dispatcher instance.
3091
+ */
3092
+ events;
3093
+ /**
3094
+ * The priority-sorted list of middleware.
3095
+ *
3096
+ * Forces the listed middleware to always be in the given order.
3097
+ */
3098
+ middlewarePriority = [];
3099
+ /**
3100
+ * The registered custom implicit binding callback.
3101
+ */
3102
+ implicitBindingCallback;
3103
+ /**
3104
+ * All of the verbs supported by the router.
3105
+ */
3106
+ static verbs = [
3107
+ "GET",
3108
+ "HEAD",
3109
+ "POST",
3110
+ "PUT",
3111
+ "PATCH",
3112
+ "DELETE",
3113
+ "OPTIONS"
3114
+ ];
3115
+ constructor(h3App, app) {
3116
+ super();
3117
+ this.h3App = h3App;
3118
+ this.app = app;
3119
+ this.events = app.has("app.events") ? app.make("app.events") : void 0;
3120
+ this.routes = new RouteCollection();
3121
+ this.DIST_DIR = env("DIST_DIR", "/.h3ravel/serve/");
3122
+ }
3123
+ /**
3124
+ * Add a route to the underlying route collection.
3125
+ *
3126
+ * @param method
3127
+ * @param uri
3128
+ * @param action
3129
+ */
3130
+ addRoute(methods, uri, action) {
3131
+ return this.routes.add(this.createRoute(methods, uri, action));
3132
+ }
3133
+ /**
3134
+ * Get the currently dispatched route instance.
3135
+ */
3136
+ getCurrentRoute() {
3137
+ return this.current;
3138
+ }
3139
+ /**
3140
+ * Check if a route with the given name exists.
3141
+ *
3142
+ * @param name
3143
+ */
3144
+ has(...name) {
3145
+ for (const value of name) if (!this.routes.hasNamedRoute(value)) return false;
3146
+ return true;
3147
+ }
3148
+ /**
3149
+ * Get the current route name.
3150
+ */
3151
+ currentRouteName() {
3152
+ return this.current?.getName();
3153
+ }
3154
+ /**
3155
+ * Alias for the "currentRouteNamed" method.
3156
+ *
3157
+ * @param patterns
3158
+ */
3159
+ is(...patterns) {
3160
+ return this.currentRouteNamed(...patterns);
3161
+ }
3162
+ /**
3163
+ * Determine if the current route matches a pattern.
3164
+ *
3165
+ * @param patterns
3166
+ */
3167
+ currentRouteNamed(...patterns) {
3168
+ return !!this.current?.named(...patterns);
3169
+ }
3170
+ /**
3171
+ * Get the underlying route collection.
3172
+ */
3173
+ getRoutes() {
3174
+ return this.routes;
3175
+ }
3176
+ /**
3177
+ * Determine if the action is routing to a controller.
3178
+ *
3179
+ * @param action
3180
+ */
3181
+ actionReferencesController(action) {
3182
+ if (typeof action !== "function") return typeof action === "string" || action && !Array.isArray(action) && action.uses && typeof action === action.uses;
3183
+ return false;
3184
+ }
3185
+ /**
3186
+ * Create a new route instance.
3187
+ *
3188
+ * @param methods
3189
+ * @param uri
3190
+ * @param action
3191
+ */
3192
+ createRoute(methods, uri, action) {
3193
+ const route = this.newRoute(methods, this.prefix(uri), action);
3194
+ if (this.hasGroupStack()) this.mergeGroupAttributesIntoRoute(route);
3195
+ return route;
3196
+ }
3197
+ /**
3198
+ * Create a new Route object.
3199
+ *
3200
+ * @param methods
3201
+ * @param uri
3202
+ * @param action
3203
+ */
3204
+ newRoute(methods, uri, action) {
3205
+ return new Route(methods, uri, action).setRouter(this).setContainer(this.app).setUri(uri);
3206
+ }
3207
+ /**
3208
+ * Dispatch the request to the application.
3209
+ *
3210
+ * @param request
3211
+ */
3212
+ async dispatch(request) {
3213
+ this.currentRequest = request;
3214
+ return await this.dispatchToRoute(request);
3215
+ }
3216
+ /**
3217
+ * Dispatch the request to a route and return the response.
3218
+ *
3219
+ * @param request
3220
+ */
3221
+ async dispatchToRoute(request) {
3222
+ return await this.runRoute(request, this.findRoute(request));
3223
+ }
3224
+ /**
3225
+ * Find the route matching a given request.
3226
+ *
3227
+ * @param request
3228
+ */
3229
+ findRoute(request) {
3230
+ this.events?.dispatch(new Routing(request));
3231
+ const route = this.routes.match(request);
3232
+ this.current = route;
3233
+ route.setContainer(this.app);
3234
+ this.app.instance(Route, route);
3235
+ return route;
3236
+ }
3237
+ /**
3238
+ * Return the response for the given route.
3239
+ *
3240
+ * @param request
3241
+ * @param route
3242
+ */
3243
+ async runRoute(request, route) {
3244
+ request.setRouteResolver(() => route);
3245
+ this.events?.dispatch(new RouteMatched(route, request));
3246
+ return await this.prepareResponse(request, await this.runRouteWithinStack(route, request));
3247
+ }
3248
+ /**
3249
+ * Run the given route within a Stack (onion) instance.
3250
+ *
3251
+ * @param route
3252
+ * @param request
3253
+ */
3254
+ async runRouteWithinStack(route, request) {
3255
+ const middleware = this.app.bound("middleware.disable") && this.app.make("middleware.disable") === true ? [] : this.gatherRouteMiddleware(route);
3256
+ return await new Pipeline(this.app).send(request).through(middleware).then(async (request$1) => {
3257
+ return this.prepareResponse(request$1, await route.run());
3258
+ });
3259
+ }
3260
+ /**
3261
+ * Get all of the defined middleware short-hand names.
3262
+ */
3263
+ getMiddleware() {
3264
+ return this.middlewares;
3265
+ }
3266
+ /**
3267
+ * Register a short-hand name for a middleware.
3268
+ *
3269
+ * @param name
3270
+ * @param class
3271
+ */
3272
+ aliasMiddleware(name, cls) {
3273
+ this.middlewares[name] = cls;
3274
+ return this;
3275
+ }
3276
+ /**
3277
+ * Gather the middleware for the given route with resolved class names.
3278
+ *
3279
+ * @param route
3280
+ */
3281
+ gatherRouteMiddleware(route) {
3282
+ return this.resolveMiddleware(route.gatherMiddleware(), route.excludedMiddleware());
3283
+ }
3284
+ /**
3285
+ * Resolve a flat array of middleware classes from the provided array.
3286
+ *
3287
+ * @param middleware
3288
+ * @param excluded
3289
+ */
3290
+ resolveMiddleware(middleware, excluded = []) {
3291
+ excluded = excluded.length === 0 ? excluded : new Collection(excluded).map((name) => MiddlewareResolver.setApp(this.app).resolve(name, this.middlewares, this.middlewareGroups)).flatten().values().all();
3292
+ const middlewares = new Collection(middleware).map((name) => MiddlewareResolver.setApp(this.app).resolve(name, this.middlewares, this.middlewareGroups)).flatten();
3293
+ middlewares.when(excluded.length > 0, (collection) => {
3294
+ collection.reject((name) => {
3295
+ if (typeof name === "function") return false;
3296
+ if (excluded.includes(name)) return true;
3297
+ if (!isClass(name)) return false;
3298
+ const instance = this.app.make(name);
3299
+ return new Collection(excluded).contains((exclude) => isClass(exclude) && instance instanceof exclude);
3300
+ });
3301
+ return collection;
3302
+ }).values();
3303
+ return this.sortMiddleware(middlewares);
3304
+ }
3305
+ /**
3306
+ * Sort the given middleware by priority.
3307
+ *
3308
+ * @param middlewares
3309
+ */
3310
+ sortMiddleware(middlewares) {
3311
+ return middlewares.all();
3312
+ }
3313
+ /**
3314
+ * Register a group of middleware.
3315
+ *
3316
+ * @param name
3317
+ * @param middleware
3318
+ */
3319
+ middlewareGroup(name, middleware) {
3320
+ this.middlewareGroups[name] = middleware;
3321
+ return this;
3322
+ }
3323
+ /**
3324
+ * Create a response instance from the given value.
3325
+ *
3326
+ * @param request
3327
+ * @param response
3328
+ */
3329
+ async prepareResponse(request, response$1) {
3330
+ this.events?.dispatch(new PreparingResponse(request, response$1));
3331
+ return tap(Router.toResponse(request, response$1), (response$2) => {
3332
+ this.events?.dispatch(new ResponsePrepared(request, response$2));
3333
+ });
3334
+ }
3335
+ /**
3336
+ * Static version of prepareResponse.
3337
+ *
3338
+ * @param request
3339
+ * @param response
3340
+ */
3341
+ static toResponse(request, response$1) {
3342
+ if (response$1 instanceof IResponsable) response$1 = response$1.toResponse(request);
3343
+ if (response$1 instanceof Stringable || typeof response$1 === "string") response$1 = new Response(request.app, response$1.toString(), 200, { "Content-Type": "text/html" });
3344
+ else if (!(response$1 instanceof IResponse) && !(response$1 instanceof Response)) response$1 = new JsonResponse(request.app, response$1);
3345
+ if (response$1.getStatusCode() === Response.codes.HTTP_NOT_MODIFIED) response$1.setNotModified();
3346
+ return response$1.prepare(request);
3347
+ }
3348
+ /**
3349
+ * Substitute the route bindings onto the route.
3350
+ *
3351
+ * @param route
3352
+ *
3353
+ * @throws {ModelNotFoundException<IModel>}
3354
+ */
3355
+ async substituteBindings(route) {
3356
+ for (const [key, value] of Object.entries(route.parameters ?? {})) if (typeof this.binders[key] !== "undefined") route.setParameter(key, await this.performBinding(key, value, route));
3357
+ return route;
3358
+ }
3359
+ /**
3360
+ * Substitute the implicit route bindings for the given route.
3361
+ *
3362
+ * @param route
3363
+ *
3364
+ * @throws {ModelNotFoundException<IModel>}
3365
+ */
3366
+ async substituteImplicitBindings(route) {
3367
+ const defaultFn = () => ImplicitRouteBinding.resolveForRoute(this.app, route);
3368
+ return await Reflect.apply(this.implicitBindingCallback ?? defaultFn, void 0, [
3369
+ this.app,
3370
+ route,
3371
+ defaultFn
3372
+ ]);
3373
+ }
3374
+ /**
3375
+ * Register a callback to run after implicit bindings are substituted.
3376
+ *
3377
+ * @param callback
3378
+ */
3379
+ substituteImplicitBindingsUsing(callback) {
3380
+ this.implicitBindingCallback = callback;
3381
+ return this;
3382
+ }
3383
+ /**
3384
+ * Call the binding callback for the given key.
3385
+ *
3386
+ * @param key
3387
+ * @param value
3388
+ * @param route
3389
+ *
3390
+ * @throws {ModelNotFoundException<IModel>}
3391
+ */
3392
+ performBinding(key, value, route) {
3393
+ return Reflect.apply(this.binders[key], void 0, [value, route]);
3394
+ }
3395
+ /**
3396
+ * Remove any duplicate middleware from the given array.
3397
+ *
3398
+ * @param middleware
3399
+ */
3400
+ static uniqueMiddleware(middleware) {
3401
+ return Array.from(new Set(middleware));
3402
+ }
3403
+ /**
3404
+ * Registers a route that responds to HTTP GET requests.
3405
+ *
3406
+ * @param uri - The route uri.
3407
+ * @param action - The handler function or [controller class, method] array.
3408
+ * @returns
3409
+ */
3410
+ get(uri, action) {
3411
+ return this.addRoute(["GET"], uri, action);
3412
+ }
3413
+ /**
3414
+ * Registers a route that responds to HTTP HEAD requests.
3415
+ *
3416
+ * @param uri - The route uri.
3417
+ * @param action - The handler function or [controller class, method] array.
3418
+ * @returns
3419
+ */
3420
+ head(uri, action) {
3421
+ return this.addRoute(["HEAD"], uri, action);
3422
+ }
3423
+ /**
3424
+ * Registers a route that responds to HTTP POST requests.
3425
+ *
3426
+ * @param uri - The route uri.
3427
+ * @param action - The handler function or [controller class, method] array.
3428
+ * @returns
3429
+ */
3430
+ post(uri, action) {
3431
+ return this.addRoute(["POST"], uri, action);
3432
+ }
3433
+ /**
3434
+ * Registers a route that responds to HTTP PUT requests.
3435
+ *
3436
+ * @param uri - The route uri.
3437
+ * @param action - The handler function or [controller class, method] array.
3438
+ * @returns
3439
+ */
3440
+ put(uri, action) {
3441
+ return this.addRoute(["PUT"], uri, action);
3442
+ }
3443
+ /**
3444
+ * Registers a route that responds to HTTP PATCH requests.
3445
+ *
3446
+ * @param uri - The route uri.
3447
+ * @param action - The handler function or [controller class, method] array.
3448
+ * @returns
3449
+ */
3450
+ patch(uri, action) {
3451
+ return this.addRoute(["PATCH"], uri, action);
3452
+ }
3453
+ /**
3454
+ * Registers a route that responds to HTTP OPTIONS requests.
3455
+ *
3456
+ * @param uri - The route uri.
3457
+ * @param action - The handler function or [controller class, method] array.
3458
+ * @returns
3459
+ */
3460
+ options(uri, action) {
3461
+ return this.addRoute(["OPTIONS"], uri, action);
3462
+ }
3463
+ /**
3464
+ * Registers a route that responds to HTTP DELETE requests.
3465
+ *
3466
+ * @param uri - The route uri.
3467
+ * @param action - The handler function or [controller class, method] array.
3468
+ * @returns
3469
+ */
3470
+ delete(uri, action) {
3471
+ return this.addRoute(["DELETE"], uri, action);
3472
+ }
3473
+ /**
3474
+ * Registers a route the matches the provided methods.
3475
+ *
3476
+ * @param methods - The route methods to match.
3477
+ * @param uri - The route uri.
3478
+ * @param action - The handler function or [controller class, method] array.
3479
+ */
3480
+ match(methods, uri, action) {
3481
+ return this.addRoute(Arr.wrap(methods), uri, action);
3482
+ }
3483
+ /**
3484
+ * Route a resource to a controller.
3485
+ *
3486
+ * @param name
3487
+ * @param controller
3488
+ * @param options
3489
+ */
3490
+ resource(name, controller, options = {}) {
3491
+ let registrar;
3492
+ if (this.app && this.app.bound(ResourceRegistrar)) registrar = this.app.make(ResourceRegistrar);
3493
+ else registrar = new ResourceRegistrar(this);
3494
+ return new PendingResourceRegistration(registrar, name, controller, options).$finalize();
3495
+ }
3496
+ /**
3497
+ * Register an array of API resource controllers.
3498
+ *
3499
+ * @param resources
3500
+ * @param options
3501
+ */
3502
+ apiResources(resources, options = {}) {
3503
+ for (const [name, controller] of Object.entries(resources)) this.apiResource(name, controller, options);
3504
+ }
3505
+ /**
3506
+ * Route an API resource to a controller.
3507
+ *
3508
+ * @param name
3509
+ * @param controller
3510
+ * @param options
3511
+ */
3512
+ apiResource(name, controller, options = {}) {
3513
+ let only = [
3514
+ "index",
3515
+ "show",
3516
+ "store",
3517
+ "update",
3518
+ "destroy"
3519
+ ];
3520
+ if (typeof options.except !== "undefined") only = only.filter((value) => !options.except?.includes(value));
3521
+ return this.resource(name, controller, Object.assign({}, { only }, options));
3522
+ }
3523
+ /**
3524
+ * Register an array of singleton resource controllers.
3525
+ *
3526
+ * @param singletons
3527
+ * @param options
3528
+ */
3529
+ singletons(singletons, options = {}) {
3530
+ for (const [name, controller] of Object.entries(singletons)) this.singleton(name, controller, options);
3531
+ }
3532
+ /**
3533
+ * Route a singleton resource to a controller.
3534
+ *
3535
+ * @param name
3536
+ * @param controller
3537
+ * @param options
3538
+ */
3539
+ singleton(name, controller, options = {}) {
3540
+ let registrar;
3541
+ if (this.app && this.app.bound(ResourceRegistrar)) registrar = this.app.make(ResourceRegistrar);
3542
+ else registrar = new ResourceRegistrar(this);
3543
+ return new PendingSingletonResourceRegistration(registrar, name, controller, options).$finalize();
3544
+ }
3545
+ /**
3546
+ * Register an array of API singleton resource controllers.
3547
+ *
3548
+ * @param singletons
3549
+ * @param options
3550
+ */
3551
+ apiSingletons(singletons, options = {}) {
3552
+ for (const [name, controller] of Object.entries(singletons)) this.apiSingleton(name, controller, options);
3553
+ }
3554
+ /**
3555
+ * Route an API singleton resource to a controller.
3556
+ *
3557
+ * @param name
3558
+ * @param controller
3559
+ * @param options
3560
+ */
3561
+ apiSingleton(name, controller, options = {}) {
3562
+ let only = [
3563
+ "store",
3564
+ "show",
3565
+ "update",
3566
+ "destroy"
3567
+ ];
3568
+ if (typeof options.except !== "undefined") only = only.filter((v) => !options.except?.includes(v));
3569
+ return this.singleton(name, controller, Object.assign({ only }, options));
3570
+ }
3571
+ /**
3572
+ * Create a route group with shared attributes.
3573
+ *
3574
+ * @param attributes
3575
+ * @param routes
3576
+ */
3577
+ group(attributes, routes) {
3578
+ for (const groupRoutes of Arr.wrap(routes)) {
3579
+ this.updateGroupStack(attributes);
3580
+ this.loadRoutes(groupRoutes);
3581
+ this.groupStack.pop();
3582
+ }
3583
+ return this;
3584
+ }
3585
+ /**
3586
+ * Update the group stack with the given attributes.
3587
+ *
3588
+ * @param attributes
3589
+ */
3590
+ updateGroupStack(attributes) {
3591
+ if (this.hasGroupStack()) attributes = this.mergeWithLastGroup(attributes);
3592
+ this.groupStack.push(attributes);
3593
+ }
3594
+ /**
3595
+ * Merge the given array with the last group stack.
3596
+ *
3597
+ * @param newItems
3598
+ * @param prependExistingPrefix
3599
+ */
3600
+ mergeWithLastGroup(newItems, prependExistingPrefix = true) {
3601
+ return RouteGroup.merge(newItems, Arr.last(this.groupStack, true)[0], prependExistingPrefix);
3602
+ }
3603
+ /**
3604
+ * Load the provided routes.
3605
+ *
3606
+ * @param routes
3607
+ */
3608
+ async loadRoutes(routes) {
3609
+ const require = createRequire(import.meta.url);
3610
+ if (typeof routes === "function") routes(this);
3611
+ else if (existsSync(this.app.paths.distPath(routes))) require(this.app.paths.distPath(routes));
3612
+ }
3613
+ /**
3614
+ * Get the prefix from the last group on the stack.
3615
+ */
3616
+ getLastGroupPrefix() {
3617
+ if (this.hasGroupStack()) return Arr.last(this.groupStack, true)[0].prefix ?? "";
3618
+ return "";
3619
+ }
3620
+ /**
3621
+ * Merge the group stack with the controller action.
3622
+ *
3623
+ * @param route
3624
+ */
3625
+ mergeGroupAttributesIntoRoute(route) {
3626
+ route.setAction(this.mergeWithLastGroup(route.getAction(), false));
3627
+ }
3628
+ /**
3629
+ * Determine if the router currently has a group stack.
3630
+ */
3631
+ hasGroupStack() {
3632
+ return this.groupStack.length > 0;
3633
+ }
3634
+ /**
3635
+ * Set the name of the current route
3636
+ *
3637
+ * @param name
3638
+ */
3639
+ name(name) {
3640
+ this.routeNames.push(name);
3641
+ return this;
3642
+ }
3643
+ /**
3644
+ * Prefix the given URI with the last prefix.
3645
+ *
3646
+ * @param uri
3647
+ */
3648
+ prefix(uri) {
3649
+ return Str.trim(Str.trim(this.getLastGroupPrefix(), "/") + "/" + Str.trim(uri, "/"), "/") || "/";
3650
+ }
3651
+ /**
3652
+ * Registers H3 middleware for a specific path.
3653
+ *
3654
+ * @param path - The middleware or path to apply the middleware.
3655
+ * @param handler - The middleware handler.
3656
+ * @param opts - Optional middleware options.
3657
+ */
3658
+ h3middleware(path, handler, opts) {
3659
+ opts = typeof handler === "object" ? handler : typeof opts === "function" ? opts : {};
3660
+ handler = typeof path === "function" ? path : typeof handler === "function" ? handler : () => {};
3661
+ if (Array.isArray(path)) this.middlewareMap.concat(path);
3662
+ else if (typeof path === "function") this.h3App.use("/", () => {}).use(path);
3663
+ else this.h3App.use(path, handler, opts);
3664
+ return this;
3665
+ }
3666
+ /**
3667
+ * Dynamically handle calls into the router instance.
3668
+ *
3669
+ * @param method
3670
+ * @param parameters
3671
+ */
3672
+ __call(method, parameters) {
3673
+ if (Router.hasMacro(method)) return this.macroCall(method, parameters);
3674
+ if (method === "middleware") return new RouteRegistrar(this).attribute(method, Array.isArray(parameters[0]) ? parameters[0] : parameters);
3675
+ if (method === "can") return new RouteRegistrar(this).attribute(method, [parameters]);
3676
+ if (method !== "where" && Str.startsWith(method, "where")) {
3677
+ const registerer = new RouteRegistrar(this);
3678
+ return Reflect.apply(registerer[method], registerer, parameters);
3679
+ }
3680
+ return new RouteRegistrar(this).attribute(method, parameters?.[0] ?? true);
3681
+ }
3682
+ };
3683
+ __decorate([
3684
+ internal,
3685
+ __decorateMetadata("design:type", Function),
3686
+ __decorateMetadata("design:paramtypes", [String]),
3687
+ __decorateMetadata("design:returntype", void 0)
3688
+ ], Router.prototype, "prefix", null);
3689
+
3690
+ //#endregion
3691
+ //#region src/PendingResourceRegistration.ts
3692
+ var PendingResourceRegistration = class extends use(CreatesRegularExpressionRouteConstraints, Macroable) {
3693
+ /**
3694
+ * The resource registrar.
3695
+ */
3696
+ registrar;
3697
+ /**
3698
+ * The resource name.
3699
+ */
3700
+ name;
3701
+ /**
3702
+ * The resource controller.
3703
+ */
3704
+ controller;
3705
+ /**
3706
+ * The resource options.
3707
+ */
3708
+ options = {};
3709
+ /**
3710
+ * The resource's registration status.
3711
+ */
3712
+ registered = false;
3713
+ /**
3714
+ * Create a new pending resource registration instance.
3715
+ *
3716
+ * @param registrar
3717
+ * @param name
3718
+ * @param controller
3719
+ * @param options
3720
+ */
3721
+ constructor(registrar, name, controller, options) {
3722
+ super();
3723
+ this.name = name;
3724
+ this.options = options;
3725
+ this.registrar = registrar;
3726
+ this.controller = controller;
3727
+ }
3728
+ /**
3729
+ * Set the methods the controller should apply to.
3730
+ *
3731
+ * @param methods
3732
+ */
3733
+ only(...methods) {
3734
+ this.options.only = variadic(methods);
3735
+ return this;
3736
+ }
3737
+ /**
3738
+ * Set the methods the controller should exclude.
3739
+ *
3740
+ * @param methods
3741
+ */
3742
+ except(...methods) {
3743
+ this.options.except = variadic(methods);
3744
+ return this;
3745
+ }
3746
+ /**
3747
+ * Set the route names for controller actions.
3748
+ *
3749
+ * @param names
3750
+ */
3751
+ names(names) {
3752
+ this.options.names = names;
3753
+ return this;
3754
+ }
3755
+ /**
3756
+ * Set the route name for a controller action.
3757
+ *
3758
+ * @param method
3759
+ * @param name
3760
+ */
3761
+ setName(method, name) {
3762
+ if (this.options.names) this.options.names[method] = name;
3763
+ else this.options.names = { [method]: name };
3764
+ return this;
3765
+ }
3766
+ /**
3767
+ * Override the route parameter names.
3768
+ *
3769
+ * @param parameters
3770
+ */
3771
+ parameters(parameters) {
3772
+ this.options.parameters = parameters;
3773
+ return this;
3774
+ }
3775
+ /**
3776
+ * Override a route parameter's name.
3777
+ *
3778
+ * @param previous
3779
+ * @param newValue
3780
+ */
3781
+ parameter(previous, newValue) {
3782
+ this.options.parameters[previous] = newValue;
3783
+ return this;
3784
+ }
3785
+ /**
3786
+ * Add middleware to the resource routes.
3787
+ *
3788
+ * @param middleware
3789
+ */
3790
+ middleware(middleware) {
3791
+ const middlewares = Arr.wrap(middleware);
3792
+ for (let key = 0; key < middlewares.length; key++) middlewares[key] = middlewares[key];
3793
+ this.options.middleware = middlewares;
3794
+ if (typeof this.options.middleware_for !== "undefined") for (const [method, value] of Object.entries(this.options.middleware_for ?? {})) this.options.middleware_for[method] = Router.uniqueMiddleware([...Arr.wrap(value), ...middlewares]);
3795
+ return this;
3796
+ }
3797
+ /**
3798
+ * Specify middleware that should be added to the specified resource routes.
3799
+ *
3800
+ * @param methods
3801
+ * @param middleware
3802
+ */
3803
+ middlewareFor(methods, middleware) {
3804
+ methods = Arr.wrap(methods);
3805
+ let middlewares = Arr.wrap(middleware);
3806
+ if (typeof this.options.middleware !== "undefined") middlewares = Router.uniqueMiddleware([...this.options.middleware ?? [], ...middlewares]);
3807
+ for (const method of methods) if (this.options.middleware_for) this.options.middleware_for[method] = middlewares;
3808
+ else this.options.middleware_for = { [method]: middlewares };
3809
+ return this;
3810
+ }
3811
+ /**
3812
+ * Specify middleware that should be removed from the resource routes.
3813
+ *
3814
+ * @param middleware
3815
+ */
3816
+ withoutMiddleware(middleware) {
3817
+ this.options.excluded_middleware = [...this.options.excluded_middleware ?? [], ...Arr.wrap(middleware)];
3818
+ return this;
3819
+ }
3820
+ /**
3821
+ * Specify middleware that should be removed from the specified resource routes.
3822
+ *
3823
+ * @param methods
3824
+ * @param middleware
3825
+ */
3826
+ withoutMiddlewareFor(methods, middleware) {
3827
+ methods = Arr.wrap(methods);
3828
+ const middlewares = Arr.wrap(middleware);
3829
+ for (const method of methods) if (this.options.excluded_middleware_for) this.options.excluded_middleware_for[method] = middlewares;
3830
+ else this.options.excluded_middleware_for = { [method]: middlewares };
3831
+ return this;
3832
+ }
3833
+ /**
3834
+ * Add "where" constraints to the resource routes.
3835
+ *
3836
+ * @param wheres
3837
+ */
3838
+ where(wheres) {
3839
+ this.options.wheres = wheres;
3840
+ return this;
352
3841
  }
353
3842
  /**
354
- * Registers a route that responds to HTTP POST requests.
3843
+ * Indicate that the resource routes should have "shallow" nesting.
355
3844
  *
356
- * @param path The URL pattern to match (can include parameters, e.g., '/users').
357
- * @param definition Either:
358
- * - An EventHandler function
359
- * - A tuple: [ControllerClass, methodName]
360
- * @param name Optional route name (for URL generation or referencing).
361
- * @param middleware Optional array of middleware functions to execute before the handler.
3845
+ * @param shallow
362
3846
  */
363
- post(path$1, definition, name, middleware = []) {
364
- const handler = Array.isArray(definition) ? definition[0] : definition;
365
- const methodName = Array.isArray(definition) ? definition[1] : void 0;
366
- this.addRoute("post", path$1, this.resolveControllerOrHandler(handler, methodName, path$1), name, middleware, [handler.name, methodName]);
3847
+ shallow(shallow = true) {
3848
+ this.options.shallow = shallow;
367
3849
  return this;
368
3850
  }
369
3851
  /**
370
- * Registers a route that responds to HTTP PUT requests.
3852
+ * Define the callable that should be invoked on a missing model exception.
371
3853
  *
372
- * @param path The URL pattern to match (can include parameters, e.g., '/users/:id').
373
- * @param definition Either:
374
- * - An EventHandler function
375
- * - A tuple: [ControllerClass, methodName]
376
- * @param name Optional route name (for URL generation or referencing).
377
- * @param middleware Optional array of middleware functions to execute before the handler.
3854
+ * @param callback
378
3855
  */
379
- put(path$1, definition, name, middleware = []) {
380
- const handler = Array.isArray(definition) ? definition[0] : definition;
381
- const methodName = Array.isArray(definition) ? definition[1] : void 0;
382
- this.addRoute("put", path$1, this.resolveControllerOrHandler(handler, methodName, path$1), name, middleware, [handler.name, methodName]);
3856
+ missing(callback) {
3857
+ this.options.missing = callback;
383
3858
  return this;
384
3859
  }
385
3860
  /**
386
- * Registers a route that responds to HTTP PATCH requests.
3861
+ * Indicate that the resource routes should be scoped using the given binding fields.
387
3862
  *
388
- * @param path The URL pattern to match (can include parameters, e.g., '/users/:id').
389
- * @param definition Either:
390
- * - An EventHandler function
391
- * - A tuple: [ControllerClass, methodName]
392
- * @param name Optional route name (for URL generation or referencing).
393
- * @param middleware Optional array of middleware functions to execute before the handler.
3863
+ * @param fields
394
3864
  */
395
- patch(path$1, definition, name, middleware = []) {
396
- const handler = Array.isArray(definition) ? definition[0] : definition;
397
- const methodName = Array.isArray(definition) ? definition[1] : void 0;
398
- this.addRoute("patch", path$1, this.resolveControllerOrHandler(handler, methodName, path$1), name, middleware, [handler.name, methodName]);
3865
+ scoped(fields = []) {
3866
+ this.options.bindingFields = fields;
399
3867
  return this;
400
3868
  }
401
3869
  /**
402
- * Registers a route that responds to HTTP DELETE requests.
3870
+ * Define which routes should allow "trashed" models to be retrieved when resolving implicit model bindings.
403
3871
  *
404
- * @param path The URL pattern to match (can include parameters, e.g., '/users/:id').
405
- * @param definition Either:
406
- * - An EventHandler function
407
- * - A tuple: [ControllerClass, methodName]
408
- * @param name Optional route name (for URL generation or referencing).
409
- * @param middleware Optional array of middleware functions to execute before the handler.
3872
+ * @param array methods
410
3873
  */
411
- delete(path$1, definition, name, middleware = []) {
412
- const handler = Array.isArray(definition) ? definition[0] : definition;
413
- const methodName = Array.isArray(definition) ? definition[1] : void 0;
414
- this.addRoute("delete", path$1, this.resolveControllerOrHandler(handler, methodName, path$1), name, middleware, [handler.name, methodName]);
3874
+ withTrashed(methods = []) {
3875
+ this.options["trashed"] = methods;
415
3876
  return this;
416
3877
  }
417
3878
  /**
418
- * API Resource support
3879
+ * Register the singleton resource route.
3880
+ */
3881
+ register() {
3882
+ this.registered = true;
3883
+ return this.registrar.register(this.name, this.controller, this.options);
3884
+ }
3885
+ $finalize(e) {
3886
+ if (!this.registered) (e ?? this).register();
3887
+ return this ?? e;
3888
+ }
3889
+ };
3890
+
3891
+ //#endregion
3892
+ //#region src/RouteUrlGenerator.ts
3893
+ var RouteUrlGenerator = class extends IRouteUrlGenerator {
3894
+ /**
3895
+ * The URL generator instance.
3896
+ */
3897
+ url;
3898
+ /**
3899
+ * The request instance.
3900
+ */
3901
+ request;
3902
+ /**
3903
+ * The named parameter defaults.
3904
+ */
3905
+ defaultParameters = {};
3906
+ /**
3907
+ * Characters that should not be URL encoded.
3908
+ */
3909
+ dontEncode = {
3910
+ "%2F": "/",
3911
+ "%40": "@",
3912
+ "%3A": ":",
3913
+ "%3B": ";",
3914
+ "%2C": ",",
3915
+ "%3D": "=",
3916
+ "%2B": "+",
3917
+ "%21": "!",
3918
+ "%2A": "*",
3919
+ "%7C": "|",
3920
+ "%3F": "?",
3921
+ "%26": "&",
3922
+ "%23": "#",
3923
+ "%25": "%"
3924
+ };
3925
+ /**
3926
+ * Create a new Route URL generator.
3927
+ *
3928
+ * @param url
3929
+ * @param request
3930
+ */
3931
+ constructor(url, request) {
3932
+ super();
3933
+ this.url = url;
3934
+ this.request = request;
3935
+ }
3936
+ /**
3937
+ * Generate a URL for the given route.
3938
+ *
3939
+ * @param route
3940
+ * @param parameters
3941
+ * @param absolute
3942
+ */
3943
+ to(route, parameters = {}, absolute = false) {
3944
+ parameters = this.formatParameters(route, parameters);
3945
+ const domain = this.getRouteDomain(route, parameters);
3946
+ const root = this.replaceRootParameters(route, domain, parameters);
3947
+ const path = this.replaceRouteParameters(route.uri(), parameters);
3948
+ let uri = this.addQueryString(this.url.format(root, path, route), parameters);
3949
+ const missingMatches = [...uri.matchAll(/\{([\w]+)(?:[:][\w]+)?\??\}/g)];
3950
+ if (missingMatches.length) throw UrlGenerationException.forMissingParameters(route, missingMatches.map((m) => m[1]));
3951
+ uri = encodeURI(uri);
3952
+ if (!absolute) {
3953
+ uri = uri.replace(/^(\/\/|[^/?])+/i, "");
3954
+ const base = this.request.getBaseUrl();
3955
+ if (base) uri = uri.replace(new RegExp(`^${base}`, "i"), "");
3956
+ return "/" + uri.replace(/^\/+/, "");
3957
+ }
3958
+ return uri;
3959
+ }
3960
+ /**
3961
+ * Get the formatted domain for a given route.
3962
+ *
3963
+ * @param route
3964
+ * @param parameters
3965
+ */
3966
+ getRouteDomain(route, parameters) {
3967
+ return route.getDomain() ? this.formatDomain(route, parameters) : void 0;
3968
+ }
3969
+ /**
3970
+ * Format the domain and port for the route and request.
3971
+ *
3972
+ * @param route
3973
+ * @param parameters
3974
+ */
3975
+ formatDomain(route, parameters) {
3976
+ return this.addPortToDomain(this.getRouteScheme(route) + route.getDomain());
3977
+ }
3978
+ /**
3979
+ * Get the scheme for the given route.
3980
+ *
3981
+ * @param route
3982
+ */
3983
+ getRouteScheme(route) {
3984
+ if (route.httpOnly()) return "http://";
3985
+ else if (route.httpsOnly()) return "https://";
3986
+ return this.url.formatScheme();
3987
+ }
3988
+ /**
3989
+ * Add the port to the domain if necessary.
3990
+ *
3991
+ * @param domain
3992
+ */
3993
+ addPortToDomain(domain) {
3994
+ const secure = this.request.isSecure();
3995
+ const port = Number(this.request.getPort());
3996
+ return secure && port === 443 || !secure && port === 80 ? domain : domain + ":" + port;
3997
+ }
3998
+ /**
3999
+ * Format the array of route parameters.
4000
+ *
4001
+ * @param route
4002
+ * @param parameters
4003
+ */
4004
+ formatParameters(route, parameters) {
4005
+ parameters = Obj.wrap(parameters);
4006
+ this.defaultParameters = Obj.wrap(this.defaultParameters);
4007
+ const namedParameters = {};
4008
+ const namedQueryParameters = {};
4009
+ const requiredRouteParametersWithoutDefaultsOrNamedParameters = [];
4010
+ const routeParameters = route.parameterNames();
4011
+ const optionalParameters = route.getOptionalParameterNames();
4012
+ for (const name of routeParameters) {
4013
+ if (parameters[name] !== void 0) {
4014
+ namedParameters[name] = parameters[name];
4015
+ delete parameters[name];
4016
+ continue;
4017
+ } else {
4018
+ const bindingField = route.bindingFieldFor(name);
4019
+ const defaultParameterKey = bindingField ? `name:${bindingField}` : name;
4020
+ if (this.defaultParameters[defaultParameterKey] === void 0 && optionalParameters[name] === void 0) requiredRouteParametersWithoutDefaultsOrNamedParameters.push(name);
4021
+ }
4022
+ namedParameters[name] = "";
4023
+ }
4024
+ for (const [key, value] of Object.entries(parameters)) if (!Str.isInteger(key)) {
4025
+ namedQueryParameters[key] = value;
4026
+ delete parameters[key];
4027
+ }
4028
+ if (Object.keys(parameters).length === requiredRouteParametersWithoutDefaultsOrNamedParameters.length) for (const name of [...requiredRouteParametersWithoutDefaultsOrNamedParameters].reverse()) {
4029
+ if (Obj.isEmpty(parameters)) break;
4030
+ namedParameters[name] = Obj.pop(parameters);
4031
+ }
4032
+ let offset = 0;
4033
+ const emptyParameters = Object.fromEntries(Object.entries(namedParameters).filter(([_, val]) => val === ""));
4034
+ if (requiredRouteParametersWithoutDefaultsOrNamedParameters.length && Object.keys(parameters).length !== Object.keys(emptyParameters).length) {
4035
+ offset = Object.keys(namedParameters).indexOf(requiredRouteParametersWithoutDefaultsOrNamedParameters[0]);
4036
+ const remaining = Object.keys(emptyParameters).length - offset - Object.keys(parameters).length;
4037
+ if (remaining < 0) offset += remaining;
4038
+ if (offset < 0) offset = 0;
4039
+ } else if (!requiredRouteParametersWithoutDefaultsOrNamedParameters.length && !Obj.isEmpty(parameters)) {
4040
+ let remainingCount = Object.keys(parameters).length;
4041
+ const namedKeys$1 = Object.keys(namedParameters);
4042
+ for (let i = namedKeys$1.length - 1; i >= 0; i--) if (namedParameters[namedKeys$1[i]] === "") {
4043
+ offset = i;
4044
+ remainingCount--;
4045
+ if (remainingCount === 0) break;
4046
+ }
4047
+ }
4048
+ const namedKeys = Object.keys(namedParameters);
4049
+ for (let i = offset; i < namedKeys.length; i++) {
4050
+ const key = namedKeys[i];
4051
+ if (namedParameters[key] !== "") continue;
4052
+ else if (!Obj.isEmpty(parameters)) namedParameters[key] = Obj.shift(parameters);
4053
+ }
4054
+ for (const [key, value] of Object.entries(namedParameters)) {
4055
+ const bindingField = route.bindingFieldFor(key);
4056
+ const defaultParameterKey = bindingField ? `key:${bindingField}` : key;
4057
+ if (value === "" && Obj.isAssoc(this.defaultParameters) && this.defaultParameters[defaultParameterKey] !== void 0) namedParameters[key] = this.defaultParameters[defaultParameterKey];
4058
+ }
4059
+ parameters = {
4060
+ ...namedParameters,
4061
+ ...namedQueryParameters,
4062
+ ...parameters
4063
+ };
4064
+ parameters = new Collection(parameters).map((value, key) => value instanceof IRoute && route.bindingFieldFor(key) ? value[route.bindingFieldFor(key)] : value).all();
4065
+ return this.url.formatParameters(parameters);
4066
+ }
4067
+ /**
4068
+ * Replace the parameters on the root path.
4069
+ *
4070
+ * @param oute
4071
+ * @param domain
4072
+ * @param parameters
4073
+ */
4074
+ replaceRootParameters(route, domain, parameters) {
4075
+ const scheme = this.getRouteScheme(route);
4076
+ return this.replaceRouteParameters(this.url.formatRoot(scheme, domain), parameters);
4077
+ }
4078
+ /**
4079
+ * Replace all of the wildcard parameters for a route path.
4080
+ *
4081
+ * @param path
4082
+ * @param parameters
4083
+ */
4084
+ replaceRouteParameters(path, parameters) {
4085
+ path = this.replaceNamedParameters(path, parameters);
4086
+ path = path.replace(/\{.*?\}/g, (match) => {
4087
+ parameters = { ...parameters };
4088
+ if (!(0 in parameters) && !match.endsWith("?}")) return match;
4089
+ const val = parameters[0];
4090
+ delete parameters[0];
4091
+ return val ?? "";
4092
+ });
4093
+ return path.replace(/\{.*?\?\}/g, "").replace(/^\/+|\/+$/g, "");
4094
+ }
4095
+ /**
4096
+ * Replace all of the named parameters in the path.
4097
+ *
4098
+ * @param path
4099
+ * @param parameters
4100
+ */
4101
+ replaceNamedParameters(path, parameters) {
4102
+ parameters = Obj.wrap(parameters);
4103
+ this.defaultParameters = Obj.wrap(this.defaultParameters);
4104
+ return path.replace(/\{([^}?]+)(\?)?\}/g, (_, key, optional$1) => {
4105
+ if (parameters[key] !== void 0 && parameters[key] !== "") {
4106
+ const val = parameters[key];
4107
+ delete parameters[key];
4108
+ return val;
4109
+ }
4110
+ if (this.defaultParameters[key] !== void 0) return this.defaultParameters[key];
4111
+ if (parameters[key] !== void 0) delete parameters[key];
4112
+ if (optional$1) return `{${key}?}`;
4113
+ return `{${key}}`;
4114
+ });
4115
+ }
4116
+ /**
4117
+ * Add a query string to the URI.
4118
+ *
4119
+ * @param uri
4120
+ * @param parameters
4121
+ */
4122
+ addQueryString(uri, parameters) {
4123
+ const hashIndex = uri.indexOf("#");
4124
+ let fragment = null;
4125
+ if (hashIndex !== -1) {
4126
+ fragment = uri.slice(hashIndex + 1);
4127
+ uri = uri.slice(0, hashIndex);
4128
+ }
4129
+ uri += this.getRouteQueryString(parameters);
4130
+ return fragment == null ? uri : `${uri}#${fragment}`;
4131
+ }
4132
+ /**
4133
+ * Get the query string for a given route.
4134
+ *
4135
+ * @param parameters
4136
+ * @return string
4137
+ */
4138
+ getRouteQueryString(parameters) {
4139
+ parameters = Obj.wrap(parameters);
4140
+ if (Obj.isEmpty(parameters)) return "";
4141
+ const keyed = this.getStringParameters(parameters);
4142
+ let query = Obj.query(keyed);
4143
+ if (keyed.length < Object.keys(parameters).length) query += "&" + this.getNumericParameters(parameters).join("&");
4144
+ query = Str.trim(query, "&");
4145
+ return query === "" ? "" : "?{query}";
4146
+ }
4147
+ /**
4148
+ * Get the string parameters from a given list.
4149
+ *
4150
+ * @param parameters
4151
+ */
4152
+ getStringParameters(parameters) {
4153
+ return Object.fromEntries(Object.entries(parameters).filter(([key]) => typeof key === "string"));
4154
+ }
4155
+ /**
4156
+ * Get the numeric parameters from a given list.
4157
+ *
4158
+ * @param parameters
4159
+ */
4160
+ getNumericParameters(parameters) {
4161
+ return Object.fromEntries(Object.entries(parameters).filter(([key]) => !Number.isNaN(Number(key))));
4162
+ }
4163
+ /**
4164
+ * Set the default named parameters used by the URL generator.
4165
+ *
4166
+ * @param $defaults
4167
+ */
4168
+ defaults(defaults) {
4169
+ defaults = Obj.wrap(defaults);
4170
+ this.defaultParameters = Obj.wrap(this.defaultParameters);
4171
+ this.defaultParameters = {
4172
+ ...this.defaultParameters,
4173
+ ...defaults
4174
+ };
4175
+ }
4176
+ };
4177
+
4178
+ //#endregion
4179
+ //#region src/UrlGenerator.ts
4180
+ var UrlGenerator = class extends IUrlGenerator {
4181
+ routes;
4182
+ request;
4183
+ assetRoot;
4184
+ forcedRoot;
4185
+ forceScheme;
4186
+ cachedRoot;
4187
+ cachedScheme;
4188
+ keyResolver;
4189
+ missingNamedRouteResolver;
4190
+ /**
4191
+ * The session resolver callable.
4192
+ */
4193
+ sessionResolver;
4194
+ /**
4195
+ * The route URL generator instance.
4196
+ */
4197
+ routeGenerator;
4198
+ /**
4199
+ * The named parameter defaults.
4200
+ */
4201
+ defaultParameters = {};
4202
+ /**
4203
+ * The callback to use to format hosts.
4204
+ */
4205
+ #formatHostUsing;
4206
+ /**
4207
+ * The callback to use to format paths.
4208
+ */
4209
+ #formatPathUsing;
4210
+ constructor(routes, request, assetRoot) {
4211
+ super();
4212
+ this.routes = routes;
4213
+ this.request = request;
4214
+ this.assetRoot = assetRoot;
4215
+ }
4216
+ /**
4217
+ * Get the full URL for the current request,
4218
+ * including the query string.
4219
+ *
4220
+ * Example:
4221
+ * https://example.com/users?page=2
4222
+ */
4223
+ full() {
4224
+ return this.request.fullUrl();
4225
+ }
4226
+ /**
4227
+ * Get the URL for the current request path
4228
+ * without modifying the query string.
4229
+ */
4230
+ current() {
4231
+ return this.to(this.request.getPathInfo());
4232
+ }
4233
+ /**
4234
+ * Get the URL for the previous request.
4235
+ *
4236
+ * Resolution order:
4237
+ * 1. HTTP Referer header
4238
+ * 2. Session-stored previous URL
4239
+ * 3. Fallback (if provided)
4240
+ * 4. Root "/"
4241
+ *
4242
+ * @param fallback Optional fallback path or URL
4243
+ */
4244
+ previous(fallback = false) {
4245
+ const referrer = this.request.headers.get("referer");
4246
+ const url = referrer ? this.to(referrer) : this.getPreviousUrlFromSession();
4247
+ if (url) return url;
4248
+ else if (fallback) return this.to(fallback);
4249
+ return this.to("/");
4250
+ }
4251
+ /**
4252
+ * Generate an absolute URL to the given path.
4253
+ *
4254
+ * - Accepts relative paths or full URLs
4255
+ * - Automatically prefixes scheme + host
4256
+ * - Encodes extra path parameters safely
4257
+ *
4258
+ * @param path Relative or absolute path
4259
+ * @param extra Additional path segments
4260
+ * @param secure Force HTTPS or HTTP
4261
+ */
4262
+ to(path, extra = [], secure = null) {
4263
+ if (this.isValidUrl(path)) return path;
4264
+ const tail = extra.map((v) => encodeURIComponent(String(v))).join("/");
4265
+ const root = this.formatRoot(this.formatScheme(secure));
4266
+ const [cleanPath, query] = this.extractQueryString(path);
4267
+ return this.format(root, "/" + [cleanPath, tail].filter(Boolean).join("/")) + query;
4268
+ }
4269
+ /**
4270
+ * Generate a secure (HTTPS) absolute URL.
419
4271
  *
420
4272
  * @param path
421
- * @param controller
4273
+ * @param parameters
4274
+ * @returns
422
4275
  */
423
- apiResource(path$1, Controller, middleware = []) {
424
- path$1 = path$1.replace(/\//g, "/");
425
- const basePath = `/${path$1}`.replace(/\/+$/, "").replace(/(\/)+/g, "$1");
426
- const name = basePath.substring(basePath.lastIndexOf("/") + 1).replaceAll(/\/|:/g, "") || "";
427
- const param = Str.singular(name);
428
- this.get(basePath, [Controller, "index"], `${name}.index`, middleware);
429
- this.post(basePath, [Controller, "store"], `${name}.store`, middleware);
430
- this.get(`${basePath}/:${param}`, [Controller, "show"], `${name}.show`, middleware);
431
- this.put(`${basePath}/:${param}`, [Controller, "update"], `${name}.update`, middleware);
432
- this.patch(`${basePath}/:${param}`, [Controller, "update"], `${name}.update`, middleware);
433
- this.delete(`${basePath}/:${param}`, [Controller, "destroy"], `${name}.destroy`, middleware);
434
- return this;
4276
+ secure(path, parameters = []) {
4277
+ return this.to(path, parameters, true);
4278
+ }
4279
+ /**
4280
+ * Generate a URL to a public asset.
4281
+ *
4282
+ * - Skips URL generation if path is already absolute
4283
+ * - Removes index.php from root if present
4284
+ *
4285
+ * @param path Asset path
4286
+ * @param secure Force HTTPS
4287
+ */
4288
+ asset(path, secure = null) {
4289
+ if (this.isValidUrl(path)) return path;
4290
+ const root = this.assetRoot ?? this.formatRoot(this.formatScheme(secure));
4291
+ return this.removeIndex(root).replace(/\/$/, "") + "/" + path.replace(/^\/+/, "");
4292
+ }
4293
+ /**
4294
+ * Generate a secure (HTTPS) asset URL.
4295
+ *
4296
+ * @param path
4297
+ * @returns
4298
+ */
4299
+ secureAsset(path) {
4300
+ return this.asset(path, true);
4301
+ }
4302
+ /**
4303
+ * Resolve the URL scheme to use.
4304
+ *
4305
+ * Priority:
4306
+ * 1. Explicit `secure` flag
4307
+ * 2. Forced scheme
4308
+ * 3. Request scheme (cached)
4309
+ *
4310
+ * @param secure
4311
+ */
4312
+ formatScheme(secure = null) {
4313
+ if (secure !== null) return secure ? "https://" : "http://";
4314
+ if (!this.cachedScheme) this.cachedScheme = this.forceScheme ?? `${this.request.getScheme()}://`;
4315
+ return this.cachedScheme;
4316
+ }
4317
+ /**
4318
+ * Format the base root URL.
4319
+ *
4320
+ * - Applies forced root if present
4321
+ * - Replaces scheme while preserving host
4322
+ * - Result is cached per request
4323
+ *
4324
+ * @param scheme URL scheme
4325
+ * @param root Optional custom root
4326
+ */
4327
+ formatRoot(scheme, root) {
4328
+ return (root ?? this.forcedRoot ?? `${this.request.getScheme()}://${this.request.getHost()}`).replace(/^https?:\/\//, scheme);
435
4329
  }
436
4330
  /**
437
- * Named route URL generator
4331
+ * Create a signed route URL for a named route.
438
4332
  *
439
4333
  * @param name
440
- * @param params
4334
+ * @param parameters
4335
+ * @param expiration
4336
+ * @param absolute
441
4337
  * @returns
442
4338
  */
443
- route(name, params = {}) {
444
- const found = this.routes.find((r) => r.name === name);
445
- if (!found) return void 0;
446
- let url = found.path;
447
- for (const [key, value] of Object.entries(params)) url = url.replace(`:${key}`, value);
448
- return url;
4339
+ signedRoute(name, parameters = {}, expiration, absolute = true) {
4340
+ if (!this.keyResolver) throw new Error("No key resolver configured.");
4341
+ if (expiration) parameters.expires = expiration;
4342
+ const url = this.route(name, parameters, absolute);
4343
+ const resolvedKeys = this.keyResolver();
4344
+ const keys = Array.isArray(resolvedKeys) ? resolvedKeys : [resolvedKeys];
4345
+ const signature = crypto.createHmac("sha256", keys[0]).update(url).digest("hex");
4346
+ return this.route(name, {
4347
+ ...parameters,
4348
+ signature
4349
+ }, absolute);
449
4350
  }
450
4351
  /**
451
- * Grouping
4352
+ * Check if the given request has a valid signature for a relative URL.
452
4353
  *
453
- * @param options
454
- * @param callback
4354
+ * @param request
4355
+ * @returns
455
4356
  */
456
- group(options, callback) {
457
- const prevPrefix = this.groupPrefix;
458
- const prevMiddleware = [...this.groupMiddleware];
459
- this.groupPrefix += options.prefix || "";
460
- this.groupMiddleware.push(...options.middleware || []);
461
- callback(this);
462
- /**
463
- * Restore state after group
464
- */
465
- this.groupPrefix = prevPrefix;
466
- this.groupMiddleware = prevMiddleware;
467
- return this;
4357
+ hasValidSignature(request) {
4358
+ const signature = request.query("signature");
4359
+ if (!signature || !this.keyResolver) return false;
4360
+ const original = request.url();
4361
+ const resolvedKeys = this.keyResolver();
4362
+ return (Array.isArray(resolvedKeys) ? resolvedKeys : [resolvedKeys]).some((key) => crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(crypto.createHmac("sha256", key).update(original).digest("hex"))));
468
4363
  }
469
4364
  /**
470
- * Set the name of the current route
4365
+ * Get the URL to a named route.
471
4366
  *
472
4367
  * @param name
4368
+ * @param parameters
4369
+ * @param absolute
4370
+ * @returns
473
4371
  */
474
- name(name) {
475
- this.nameMap.push(name);
4372
+ route(name, parameters = {}, absolute = true) {
4373
+ const route = this.routes.getByName(name);
4374
+ if (route != null) return this.toRoute(route, parameters, absolute);
4375
+ if (this.missingNamedRouteResolver) {
4376
+ const url = this.missingNamedRouteResolver(name, parameters, absolute);
4377
+ if (url != null) return url;
4378
+ }
4379
+ throw new RouteNotFoundException(`Route [${name}] not defined.`);
4380
+ }
4381
+ /**
4382
+ * Get the URL for a given route instance.
4383
+ *
4384
+ * @param route
4385
+ * @param parameters
4386
+ * @param absolute
4387
+ */
4388
+ toRoute(route, parameters = {}, absolute = true) {
4389
+ return this.routeUrl().to(route, parameters, absolute);
4390
+ }
4391
+ /**
4392
+ * Combine root and path into a final URL.
4393
+ *
4394
+ * Allows optional host and path formatters
4395
+ * to modify the output dynamically.
4396
+ *
4397
+ * @param root
4398
+ * @param path
4399
+ * @param route
4400
+ * @returns
4401
+ */
4402
+ format(root, path, route) {
4403
+ let finalPath = "/" + path.replace(/^\/+/, "");
4404
+ if (this.#formatHostUsing) root = this.#formatHostUsing(root, route);
4405
+ if (this.#formatPathUsing) finalPath = this.#formatPathUsing(finalPath, route);
4406
+ return (root + finalPath).replace(/\/+$/, "");
4407
+ }
4408
+ /**
4409
+ * Format the array of URL parameters.
4410
+ *
4411
+ * @param parameters
4412
+ */
4413
+ formatParameters(parameters) {
4414
+ parameters = Obj.wrap(parameters);
4415
+ for (const [key, parameter] of Object.entries(parameters)) if (Obj.isAssoc(parameter) && typeof parameter.getRouteKey === "function") parameters[key] = parameter.getRouteKey();
4416
+ return parameters;
4417
+ }
4418
+ extractQueryString(path) {
4419
+ const i = path.indexOf("?");
4420
+ return i === -1 ? [path, ""] : [path.slice(0, i), path.slice(i)];
4421
+ }
4422
+ /**
4423
+ * @param root
4424
+ */
4425
+ removeIndex(root) {
4426
+ return root;
4427
+ }
4428
+ /**
4429
+ * Determine whether a string is a valid URL.
4430
+ *
4431
+ * Supports:
4432
+ * - Absolute URLs
4433
+ * - Protocol-relative URLs
4434
+ * - Anchors and special schemes
4435
+ *
4436
+ * @param path
4437
+ * @returns
4438
+ */
4439
+ isValidUrl(path) {
4440
+ if (/^(#|\/\/|https?:\/\/|(mailto|tel|sms):)/.test(path)) return true;
4441
+ try {
4442
+ new URL(path);
4443
+ return true;
4444
+ } catch {
4445
+ return false;
4446
+ }
4447
+ }
4448
+ /**
4449
+ * Get the Route URL generator instance.
4450
+ */
4451
+ routeUrl() {
4452
+ if (!this.routeGenerator) this.routeGenerator = new RouteUrlGenerator(this, this.request);
4453
+ return this.routeGenerator;
4454
+ }
4455
+ /**
4456
+ * Force HTTPS for all generated URLs.
4457
+ *
4458
+ * @param force
4459
+ */
4460
+ forceHttps(force = true) {
4461
+ if (force) this.forceScheme = "https://";
4462
+ }
4463
+ /**
4464
+ * Set the origin (scheme + host) for generated URLs.
4465
+ *
4466
+ * @param root
4467
+ */
4468
+ useOrigin(root) {
4469
+ this.forcedRoot = root?.replace(/\/$/, "");
4470
+ this.cachedRoot = void 0;
4471
+ }
4472
+ useAssetOrigin(root) {
4473
+ this.assetRoot = root?.replace(/\/$/, "");
4474
+ }
4475
+ setKeyResolver(resolver) {
4476
+ this.keyResolver = resolver;
4477
+ }
4478
+ resolveMissingNamedRoutesUsing(resolver) {
4479
+ this.missingNamedRouteResolver = resolver;
4480
+ }
4481
+ formatHostUsing(callback) {
4482
+ this.#formatHostUsing = callback;
4483
+ return this;
4484
+ }
4485
+ formatPathUsing(callback) {
4486
+ this.#formatPathUsing = callback;
476
4487
  return this;
477
4488
  }
478
4489
  /**
479
- * Registers middleware for a specific path.
480
- * @param path - The path to apply the middleware.
481
- * @param handler - The middleware handler.
482
- * @param opts - Optional middleware options.
4490
+ * Get the request instance.
483
4491
  */
484
- middleware(path$1, handler, opts) {
485
- opts = typeof handler === "object" ? handler : typeof opts === "function" ? opts : {};
486
- handler = typeof path$1 === "function" ? path$1 : typeof handler === "function" ? handler : () => {};
487
- if (Array.isArray(path$1)) this.middlewareMap.concat(path$1);
488
- else if (typeof path$1 === "function") this.h3App.use("/", () => {}).use(path$1);
489
- else this.h3App.use(path$1, handler, opts);
4492
+ getRequest() {
4493
+ return this.request;
4494
+ }
4495
+ /**
4496
+ * Set the current request instance.
4497
+ *
4498
+ * @param request
4499
+ */
4500
+ setRequest(request) {
4501
+ this.request = request;
4502
+ this.cachedRoot = void 0;
4503
+ this.cachedScheme = void 0;
4504
+ tap(optional(this.routeGenerator).defaultParameters || [], (defaults) => {
4505
+ this.routeGenerator = void 0;
4506
+ if (defaults) this.defaults(defaults);
4507
+ });
4508
+ }
4509
+ /**
4510
+ * Set the route collection.
4511
+ *
4512
+ * @param routes
4513
+ */
4514
+ setRoutes(routes) {
4515
+ this.routes = routes;
4516
+ return this;
4517
+ }
4518
+ /**
4519
+ * Get the route collection.
4520
+ */
4521
+ getRoutes() {
4522
+ return this.routes;
4523
+ }
4524
+ /**
4525
+ * Get the session implementation from the resolver.
4526
+ */
4527
+ getSession() {
4528
+ if (this.sessionResolver) return this.sessionResolver();
4529
+ }
4530
+ /**
4531
+ * Set the session resolver for the generator.
4532
+ *
4533
+ * @param sessionResolver
4534
+ */
4535
+ setSessionResolver(sessionResolver) {
4536
+ this.sessionResolver = sessionResolver;
490
4537
  return this;
491
4538
  }
4539
+ /**
4540
+ * Clone a new instance of the URL generator with a different encryption key resolver.
4541
+ *
4542
+ * @param keyResolver
4543
+ */
4544
+ withKeyResolver(keyResolver) {
4545
+ return structuredClone(this).setKeyResolver(keyResolver);
4546
+ }
4547
+ /**
4548
+ * Set the default named parameters used by the URL generator.
4549
+ *
4550
+ * @param array $defaults
4551
+ * @return void
4552
+ */
4553
+ defaults(defaults) {
4554
+ this.defaultParameters = Object.assign({}, this.defaultParameters, defaults);
4555
+ }
4556
+ /**
4557
+ * Get the previous URL from the session if possible.
4558
+ */
4559
+ getPreviousUrlFromSession() {
4560
+ return "";
4561
+ }
492
4562
  };
493
4563
 
494
4564
  //#endregion
495
- //#region src/Providers/RouteServiceProvider.ts
496
- /**
497
- * Handles routing registration
498
- *
499
- * Load route files (web.ts, api.ts).
500
- * Map controllers to routes.
501
- * Register route-related middleware.
502
- *
503
- * Auto-Registered
504
- */
505
- var RouteServiceProvider = class extends ServiceProvider {
506
- static priority = 997;
507
- register() {
508
- this.app.singleton("router", () => {
509
- try {
510
- return new Router(this.app.make("http.app"), this.app);
511
- } catch (error) {
512
- if (String(error.message).includes("http.app")) Logger.log([
513
- ["The", "white"],
514
- ["@h3ravel/http", ["italic", "gray"]],
515
- ["package is required to use the routing system.", "white"]
516
- ], " ");
517
- else Logger.log(error, "white");
518
- }
519
- return {};
4565
+ //#region src/Providers/RoutingServiceProvider.ts
4566
+ var RoutingServiceProvider = class extends ServiceProvider {
4567
+ static order = "before:ConfigServiceProvider";
4568
+ async register() {
4569
+ this.bindUrlGenerator();
4570
+ this.app.singleton(ICallableDispatcher, (app) => {
4571
+ return new CallableDispatcher(app);
4572
+ });
4573
+ this.app.singleton(IControllerDispatcher, (app) => {
4574
+ return new ControllerDispatcher(app);
520
4575
  });
521
- this.registerCommands([RouteListCommand]);
522
4576
  }
523
4577
  /**
524
- * Load routes from src/routes
4578
+ * Bind the URL generator service.
4579
+ *
4580
+ * @return void
525
4581
  */
526
- async boot() {
527
- try {
528
- const routePath = this.app.getPath("routes");
529
- const files = (await readdir(routePath)).filter((e) => {
530
- return !e.includes(".d.ts") && !e.includes(".d.cts") && !e.includes(".map");
4582
+ bindUrlGenerator() {
4583
+ this.app.alias(IUrlGenerator, "url");
4584
+ this.app.singleton("url", (app) => {
4585
+ const routes = app.make("router").getRoutes();
4586
+ app.instance("routes", routes);
4587
+ return new UrlGenerator(routes, app.rebinding("http.request", (_app, request) => {
4588
+ this.app.make("url").setRequest(request);
4589
+ return request;
4590
+ }), app.make("config").get("app.asset_url"));
4591
+ });
4592
+ this.app.extend("url", (url, app) => {
4593
+ url.setSessionResolver(() => {
4594
+ return this.app.make("session") ?? null;
531
4595
  });
532
- for (let i = 0; i < files.length; i++) {
533
- const routesModule = await import(path.join(routePath, files[i]));
534
- if (typeof routesModule.default === "function") {
535
- const router = this.app.make("router");
536
- routesModule.default(router);
537
- }
538
- }
539
- } catch (e) {
540
- Logger.log([["No auto discorvered routes.", "white"], [e.message, ["grey", "italic"]]], "\n");
541
- }
4596
+ url.setKeyResolver(() => {
4597
+ const config = this.app.make("config");
4598
+ return [config.get("app.key"), ...config.get("app.previous_keys") ?? []];
4599
+ });
4600
+ app.rebinding("routes", (_app, routes) => {
4601
+ this.app.make("url").setRoutes(routes);
4602
+ });
4603
+ return url;
4604
+ });
542
4605
  }
543
4606
  };
544
4607
 
545
4608
  //#endregion
546
- export { AssetsServiceProvider, Helpers, RouteListCommand, RouteServiceProvider, Router };
4609
+ export { AbstractRouteCollection, CallableDispatcher, CompiledRoute, ControllerDispatcher, CreatesRegularExpressionRouteConstraints, FiltersControllerMiddleware, Helpers, HostValidator, IRouteValidator, ImplicitRouteBinding, MethodValidator, MiddlewareResolver, PendingResourceRegistration, PendingSingletonResourceRegistration, Pipeline, PreparingResponse, ResourceRegistrar, ResponsePrepared, Route, RouteAction, RouteCollection, RouteDependencyResolver, RouteGroup, RouteListCommand, RouteMatched, RouteParameter, RouteParameterBinder, RouteRegistrar, RouteSignatureParameters, RouteUri, RouteUrlGenerator, Router, Routing, RoutingServiceProvider, SchemeValidator, SubstituteBindings, UriValidator, UrlGenerator };