@h3ravel/router 1.13.6 → 1.15.0-alpha.10

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