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