clear-router 2.8.9 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Route.cjs +200 -12
- package/dist/Route.d.cts +125 -1
- package/dist/Route.d.mts +125 -1
- package/dist/Route.mjs +199 -12
- package/dist/RouteGroup.cjs +1 -0
- package/dist/RouteGroup.mjs +1 -0
- package/dist/RouteRegistrar.cjs +68 -0
- package/dist/RouteRegistrar.d.cts +62 -0
- package/dist/RouteRegistrar.d.mts +62 -0
- package/dist/RouteRegistrar.mjs +67 -0
- package/dist/core/CoreRouter.cjs +307 -0
- package/dist/core/CoreRouter.d.cts +168 -0
- package/dist/core/CoreRouter.d.mts +168 -0
- package/dist/core/CoreRouter.mjs +307 -0
- package/dist/decorators/setup.d.mts +0 -1
- package/dist/express/router.cjs +13 -4
- package/dist/express/router.d.cts +1 -0
- package/dist/express/router.d.mts +1 -0
- package/dist/express/router.mjs +13 -4
- package/dist/fastify/router.cjs +13 -4
- package/dist/fastify/router.d.cts +1 -0
- package/dist/fastify/router.d.mts +1 -0
- package/dist/fastify/router.mjs +13 -4
- package/dist/h3/router.cjs +13 -4
- package/dist/h3/router.d.cts +1 -0
- package/dist/h3/router.d.mts +1 -0
- package/dist/h3/router.mjs +13 -4
- package/dist/hono/router.cjs +11 -4
- package/dist/hono/router.d.cts +1 -0
- package/dist/hono/router.d.mts +1 -0
- package/dist/hono/router.mjs +11 -4
- package/dist/index.cjs +3 -0
- package/dist/index.d.cts +3 -2
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +3 -2
- package/dist/koa/router.cjs +13 -4
- package/dist/koa/router.d.cts +1 -0
- package/dist/koa/router.d.mts +1 -0
- package/dist/koa/router.mjs +13 -4
- package/dist/types/basic.d.cts +2 -0
- package/dist/types/basic.d.mts +2 -0
- package/package.json +1 -1
|
@@ -6,6 +6,7 @@ import { ClearRouterPluginArgumentsContext, ClearRouterPluginInput, ClearRouterP
|
|
|
6
6
|
import { Controller } from "../Controller.mjs";
|
|
7
7
|
import { ResourceRoutes } from "../ResourceRoutes.mjs";
|
|
8
8
|
import { RouteGroup } from "../RouteGroup.mjs";
|
|
9
|
+
import { RouteRegistrar } from "../RouteRegistrar.mjs";
|
|
9
10
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
10
11
|
|
|
11
12
|
//#region src/core/CoreRouter.d.ts
|
|
@@ -26,6 +27,9 @@ declare abstract class CoreRouter {
|
|
|
26
27
|
private static readonly pluginArgumentResolversKey;
|
|
27
28
|
private static requestProvider?;
|
|
28
29
|
private static responseProvider?;
|
|
30
|
+
private static readonly domainMatcherCache;
|
|
31
|
+
private static readonly constraintRegexCache;
|
|
32
|
+
static routePatterns: Map<string, string | RegExp>;
|
|
29
33
|
static config: RouterConfig;
|
|
30
34
|
protected static groupContext: AsyncLocalStorage<RouteGroupContext>;
|
|
31
35
|
protected static pluginRequestContext: AsyncLocalStorage<ClearRouterPluginRequestContext<any>>;
|
|
@@ -111,6 +115,149 @@ declare abstract class CoreRouter {
|
|
|
111
115
|
}>;
|
|
112
116
|
protected static expandRoutePath(path: string): string[];
|
|
113
117
|
protected static routeRegistrationPaths(path: string): string[];
|
|
118
|
+
/**
|
|
119
|
+
* Compile a host pattern such as `{account}.example.com` into a matcher and
|
|
120
|
+
* the ordered list of placeholder names it captures. Results are memoized.
|
|
121
|
+
*
|
|
122
|
+
* @param pattern
|
|
123
|
+
* @returns
|
|
124
|
+
*/
|
|
125
|
+
protected static compileDomain(pattern: string): {
|
|
126
|
+
regex: RegExp;
|
|
127
|
+
params: string[];
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* Match a host against a domain pattern, returning the captured parameters or
|
|
131
|
+
* `null` when the host does not match.
|
|
132
|
+
*
|
|
133
|
+
* @param pattern
|
|
134
|
+
* @param host
|
|
135
|
+
* @returns
|
|
136
|
+
*/
|
|
137
|
+
static matchDomain(pattern: string, host?: string | null): Record<string, string> | null;
|
|
138
|
+
/**
|
|
139
|
+
* Best-effort extraction of the request host across every supported adapter
|
|
140
|
+
* context shape (Express/Fastify plain headers, H3 `Headers`, Hono accessor,
|
|
141
|
+
* Koa context).
|
|
142
|
+
*
|
|
143
|
+
* @param ctx
|
|
144
|
+
* @returns
|
|
145
|
+
*/
|
|
146
|
+
protected static extractHost(ctx: any): string;
|
|
147
|
+
/**
|
|
148
|
+
* Resolve the domain parameters for a route given the active request context.
|
|
149
|
+
* Returns `null` when the route is not domain-constrained, `false` when it is
|
|
150
|
+
* but the host does not match, or the captured parameters on a match.
|
|
151
|
+
*
|
|
152
|
+
* @param route
|
|
153
|
+
* @param ctx
|
|
154
|
+
* @returns
|
|
155
|
+
*/
|
|
156
|
+
protected static matchRouteDomain(route: Route<any, any, any>, ctx: any): Record<string, string> | null | false;
|
|
157
|
+
/**
|
|
158
|
+
* Register a global pattern applied to every route parameter sharing the
|
|
159
|
+
* given name (equivalent to Laravel's `Route::pattern`).
|
|
160
|
+
*
|
|
161
|
+
* @param name
|
|
162
|
+
* @param pattern
|
|
163
|
+
*/
|
|
164
|
+
static pattern(name: string, pattern: string | RegExp): void;
|
|
165
|
+
/**
|
|
166
|
+
* Register multiple global parameter patterns at once.
|
|
167
|
+
*
|
|
168
|
+
* @param patterns
|
|
169
|
+
*/
|
|
170
|
+
static patterns(patterns: Record<string, string | RegExp>): void;
|
|
171
|
+
/**
|
|
172
|
+
* Merge the global parameter patterns with the route's own constraints. Route
|
|
173
|
+
* level constraints take precedence over global patterns.
|
|
174
|
+
*
|
|
175
|
+
* @param route
|
|
176
|
+
* @returns
|
|
177
|
+
*/
|
|
178
|
+
protected static resolveConstraints(route: Route<any, any, any>): Record<string, string | RegExp>;
|
|
179
|
+
/**
|
|
180
|
+
* Compile a constraint pattern into a fully-anchored regular expression.
|
|
181
|
+
*
|
|
182
|
+
* @param pattern
|
|
183
|
+
* @returns
|
|
184
|
+
*/
|
|
185
|
+
protected static toConstraintRegex(pattern: string | RegExp): RegExp;
|
|
186
|
+
/**
|
|
187
|
+
* Determine whether the resolved parameters satisfy the route's constraints.
|
|
188
|
+
* Absent parameters (e.g. optional ones) are ignored.
|
|
189
|
+
*
|
|
190
|
+
* @param route
|
|
191
|
+
* @param params
|
|
192
|
+
* @returns
|
|
193
|
+
*/
|
|
194
|
+
protected static satisfiesConstraints(route: Route<any, any, any>, params: Record<string, any>): boolean;
|
|
195
|
+
/**
|
|
196
|
+
* Determine which of a route's parameters are allowed to span multiple path
|
|
197
|
+
* segments (i.e. their constraint matches an encoded forward slash). These are
|
|
198
|
+
* registered with the adapter's catch-all syntax.
|
|
199
|
+
*
|
|
200
|
+
* @param route
|
|
201
|
+
* @returns
|
|
202
|
+
*/
|
|
203
|
+
protected static wildcardParameters(route: Route<any, any, any>): Set<string>;
|
|
204
|
+
/**
|
|
205
|
+
* Render a single wildcard (slash-spanning) parameter for the underlying
|
|
206
|
+
* router's registration path. Overridden per adapter; the base form keeps the
|
|
207
|
+
* plain `:name` placeholder.
|
|
208
|
+
*
|
|
209
|
+
* @param name
|
|
210
|
+
* @returns
|
|
211
|
+
*/
|
|
212
|
+
protected static formatWildcardParam(name: string): string;
|
|
213
|
+
/**
|
|
214
|
+
* Rewrite a route's registration paths so any wildcard parameters use the
|
|
215
|
+
* adapter's catch-all syntax. Non-wildcard routes are returned unchanged.
|
|
216
|
+
*
|
|
217
|
+
* @param route
|
|
218
|
+
* @returns
|
|
219
|
+
*/
|
|
220
|
+
protected static resolveRegistrationPaths(route: Route<any, any, any>): string[];
|
|
221
|
+
/**
|
|
222
|
+
* Resolve the final parameters for a dispatched route, applying domain
|
|
223
|
+
* matching and constraint validation. Returns the merged parameters, or
|
|
224
|
+
* `false` when the route should not handle the request (host mismatch or a
|
|
225
|
+
* constraint failure) so the adapter can fall through.
|
|
226
|
+
*
|
|
227
|
+
* @param route
|
|
228
|
+
* @param ctx
|
|
229
|
+
* @param baseParams
|
|
230
|
+
* @returns
|
|
231
|
+
*/
|
|
232
|
+
protected static matchRoute(route: Route<any, any, any>, ctx: any, baseParams?: Record<string, any>): Record<string, any> | false;
|
|
233
|
+
/**
|
|
234
|
+
* Normalize wildcard (slash-spanning) parameters into a single string keyed by
|
|
235
|
+
* the declared parameter name, smoothing over the differing shapes adapters
|
|
236
|
+
* return (Express yields an array of segments, Fastify keys it under `*`).
|
|
237
|
+
*
|
|
238
|
+
* @param route
|
|
239
|
+
* @param params
|
|
240
|
+
*/
|
|
241
|
+
protected static normalizeWildcardParams(route: Route<any, any, any>, params: Record<string, any>): void;
|
|
242
|
+
/**
|
|
243
|
+
* Get the route currently being dispatched, if any.
|
|
244
|
+
*
|
|
245
|
+
* @returns
|
|
246
|
+
*/
|
|
247
|
+
static current(): Route<any, any, any> | undefined;
|
|
248
|
+
/**
|
|
249
|
+
* Get the name of the route currently being dispatched.
|
|
250
|
+
*
|
|
251
|
+
* @returns
|
|
252
|
+
*/
|
|
253
|
+
static currentRouteName(): string;
|
|
254
|
+
/**
|
|
255
|
+
* Get the action (`Controller@method` or `Closure`) of the route currently
|
|
256
|
+
* being dispatched.
|
|
257
|
+
*
|
|
258
|
+
* @returns
|
|
259
|
+
*/
|
|
260
|
+
static currentRouteAction(): string;
|
|
114
261
|
/**
|
|
115
262
|
* Configures the router with the given options, such as method override settings.
|
|
116
263
|
*
|
|
@@ -215,6 +362,27 @@ declare abstract class CoreRouter {
|
|
|
215
362
|
* @param middlewares
|
|
216
363
|
*/
|
|
217
364
|
static group<S extends RouteGroupSource>(prefix: string, source: S, middlewares?: any[]): RouteGroup<any, any, any, S>;
|
|
365
|
+
/**
|
|
366
|
+
* Build a route group, optionally constrained to a host pattern. Shared by
|
|
367
|
+
* `group` and the `domain` registrar.
|
|
368
|
+
*
|
|
369
|
+
* @param prefix
|
|
370
|
+
* @param source
|
|
371
|
+
* @param middlewares
|
|
372
|
+
* @param extra
|
|
373
|
+
*/
|
|
374
|
+
protected static makeGroup<S extends RouteGroupSource>(prefix: string, source: S, middlewares?: any[], extra?: {
|
|
375
|
+
domain?: string;
|
|
376
|
+
}): RouteGroup<any, any, any, S>;
|
|
377
|
+
/**
|
|
378
|
+
* Begin a route registration constrained to a host pattern such as
|
|
379
|
+
* `{account}.example.com`. Returns a registrar whose `.group()` registers the
|
|
380
|
+
* routes under that domain (matched parameters become route parameters).
|
|
381
|
+
*
|
|
382
|
+
* @param pattern
|
|
383
|
+
* @returns
|
|
384
|
+
*/
|
|
385
|
+
static domain(pattern: string): RouteRegistrar;
|
|
218
386
|
/**
|
|
219
387
|
* Adds global middlewares to the router, which will be applied to all routes.
|
|
220
388
|
*
|
package/dist/core/CoreRouter.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Route } from "../Route.mjs";
|
|
2
2
|
import { RouteGroup } from "../RouteGroup.mjs";
|
|
3
|
+
import { RouteRegistrar } from "../RouteRegistrar.mjs";
|
|
3
4
|
import { Request } from "./Request.mjs";
|
|
4
5
|
import { Response } from "./Response.mjs";
|
|
5
6
|
import { Container, getBindingMetadataFromTargets, getDesignParamTypes, getStandardMetadata, isClass } from "./bindings.mjs";
|
|
@@ -25,6 +26,9 @@ var CoreRouter = class {
|
|
|
25
26
|
static pluginArgumentResolversKey = Symbol.for("clear-router:plugin-argument-resolvers");
|
|
26
27
|
static requestProvider;
|
|
27
28
|
static responseProvider;
|
|
29
|
+
static domainMatcherCache = /* @__PURE__ */ new Map();
|
|
30
|
+
static constraintRegexCache = /* @__PURE__ */ new Map();
|
|
31
|
+
static routePatterns = /* @__PURE__ */ new Map();
|
|
28
32
|
static config = {
|
|
29
33
|
inferParamName: false,
|
|
30
34
|
methodOverride: {
|
|
@@ -111,6 +115,7 @@ var CoreRouter = class {
|
|
|
111
115
|
this.routesByPathMethod.clear();
|
|
112
116
|
this.routesByMethod.clear();
|
|
113
117
|
this.routesByName.clear();
|
|
118
|
+
this.routePatterns.clear();
|
|
114
119
|
return this;
|
|
115
120
|
}
|
|
116
121
|
static createBaseConfig() {
|
|
@@ -436,6 +441,271 @@ var CoreRouter = class {
|
|
|
436
441
|
return this.expandRoutePath(path);
|
|
437
442
|
}
|
|
438
443
|
/**
|
|
444
|
+
* Compile a host pattern such as `{account}.example.com` into a matcher and
|
|
445
|
+
* the ordered list of placeholder names it captures. Results are memoized.
|
|
446
|
+
*
|
|
447
|
+
* @param pattern
|
|
448
|
+
* @returns
|
|
449
|
+
*/
|
|
450
|
+
static compileDomain(pattern) {
|
|
451
|
+
const cached = this.domainMatcherCache.get(pattern);
|
|
452
|
+
if (cached) return cached;
|
|
453
|
+
const cleanPattern = pattern.split(":", 1)[0].trim();
|
|
454
|
+
const escape = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, (match) => `\\${match}`);
|
|
455
|
+
const placeholder = /\{([^{}]+)\}/g;
|
|
456
|
+
const params = [];
|
|
457
|
+
let source = "^";
|
|
458
|
+
let lastIndex = 0;
|
|
459
|
+
let match;
|
|
460
|
+
while ((match = placeholder.exec(cleanPattern)) !== null) {
|
|
461
|
+
source += escape(cleanPattern.slice(lastIndex, match.index));
|
|
462
|
+
const raw = match[1].trim();
|
|
463
|
+
const optional = raw.endsWith("?");
|
|
464
|
+
const name = (optional ? raw.slice(0, -1) : raw).split(":", 1)[0].trim();
|
|
465
|
+
params.push(name);
|
|
466
|
+
source += optional ? "([^.]*)" : "([^.]+)";
|
|
467
|
+
lastIndex = match.index + match[0].length;
|
|
468
|
+
}
|
|
469
|
+
source += escape(cleanPattern.slice(lastIndex));
|
|
470
|
+
source += "$";
|
|
471
|
+
const compiled = {
|
|
472
|
+
regex: new RegExp(source, "i"),
|
|
473
|
+
params
|
|
474
|
+
};
|
|
475
|
+
this.domainMatcherCache.set(pattern, compiled);
|
|
476
|
+
return compiled;
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Match a host against a domain pattern, returning the captured parameters or
|
|
480
|
+
* `null` when the host does not match.
|
|
481
|
+
*
|
|
482
|
+
* @param pattern
|
|
483
|
+
* @param host
|
|
484
|
+
* @returns
|
|
485
|
+
*/
|
|
486
|
+
static matchDomain(pattern, host) {
|
|
487
|
+
if (!pattern) return null;
|
|
488
|
+
const cleanHost = String(host ?? "").split(":", 1)[0].trim().toLowerCase();
|
|
489
|
+
const { regex, params } = this.compileDomain(pattern);
|
|
490
|
+
const match = regex.exec(cleanHost);
|
|
491
|
+
if (!match) return null;
|
|
492
|
+
const result = {};
|
|
493
|
+
params.forEach((name, index) => {
|
|
494
|
+
const value = match[index + 1];
|
|
495
|
+
if (typeof value !== "undefined") result[name] = decodeURIComponent(value);
|
|
496
|
+
});
|
|
497
|
+
return result;
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Best-effort extraction of the request host across every supported adapter
|
|
501
|
+
* context shape (Express/Fastify plain headers, H3 `Headers`, Hono accessor,
|
|
502
|
+
* Koa context).
|
|
503
|
+
*
|
|
504
|
+
* @param ctx
|
|
505
|
+
* @returns
|
|
506
|
+
*/
|
|
507
|
+
static extractHost(ctx) {
|
|
508
|
+
const headers = ctx?.req?.headers ?? ctx?.headers;
|
|
509
|
+
let host;
|
|
510
|
+
if (headers) host = typeof headers.get === "function" ? headers.get("host") ?? headers.get(":authority") : headers.host ?? headers[":authority"];
|
|
511
|
+
if (!host && typeof ctx?.req?.header === "function") host = ctx.req.header("host");
|
|
512
|
+
if (!host && typeof ctx?.host === "string") host = ctx.host;
|
|
513
|
+
if (Array.isArray(host)) host = host[0];
|
|
514
|
+
return String(host ?? "").split(",", 1)[0].trim();
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Resolve the domain parameters for a route given the active request context.
|
|
518
|
+
* Returns `null` when the route is not domain-constrained, `false` when it is
|
|
519
|
+
* but the host does not match, or the captured parameters on a match.
|
|
520
|
+
*
|
|
521
|
+
* @param route
|
|
522
|
+
* @param ctx
|
|
523
|
+
* @returns
|
|
524
|
+
*/
|
|
525
|
+
static matchRouteDomain(route, ctx) {
|
|
526
|
+
if (!route.domainPattern) return null;
|
|
527
|
+
return this.matchDomain(route.domainPattern, this.extractHost(ctx)) ?? false;
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Register a global pattern applied to every route parameter sharing the
|
|
531
|
+
* given name (equivalent to Laravel's `Route::pattern`).
|
|
532
|
+
*
|
|
533
|
+
* @param name
|
|
534
|
+
* @param pattern
|
|
535
|
+
*/
|
|
536
|
+
static pattern(name, pattern) {
|
|
537
|
+
this.ensureState();
|
|
538
|
+
this.routePatterns.set(name, pattern);
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Register multiple global parameter patterns at once.
|
|
542
|
+
*
|
|
543
|
+
* @param patterns
|
|
544
|
+
*/
|
|
545
|
+
static patterns(patterns) {
|
|
546
|
+
for (const [name, pattern] of Object.entries(patterns)) this.pattern(name, pattern);
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Merge the global parameter patterns with the route's own constraints. Route
|
|
550
|
+
* level constraints take precedence over global patterns.
|
|
551
|
+
*
|
|
552
|
+
* @param route
|
|
553
|
+
* @returns
|
|
554
|
+
*/
|
|
555
|
+
static resolveConstraints(route) {
|
|
556
|
+
if (!this.routePatterns.size && !Object.keys(route.constraints).length) return route.constraints;
|
|
557
|
+
return {
|
|
558
|
+
...Object.fromEntries(this.routePatterns),
|
|
559
|
+
...route.constraints
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Compile a constraint pattern into a fully-anchored regular expression.
|
|
564
|
+
*
|
|
565
|
+
* @param pattern
|
|
566
|
+
* @returns
|
|
567
|
+
*/
|
|
568
|
+
static toConstraintRegex(pattern) {
|
|
569
|
+
if (pattern instanceof RegExp) return new RegExp(`^(?:${pattern.source})$`, pattern.flags.replace("g", ""));
|
|
570
|
+
const cached = this.constraintRegexCache.get(pattern);
|
|
571
|
+
if (cached) return cached;
|
|
572
|
+
const regex = new RegExp(`^(?:${pattern})$`);
|
|
573
|
+
this.constraintRegexCache.set(pattern, regex);
|
|
574
|
+
return regex;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Determine whether the resolved parameters satisfy the route's constraints.
|
|
578
|
+
* Absent parameters (e.g. optional ones) are ignored.
|
|
579
|
+
*
|
|
580
|
+
* @param route
|
|
581
|
+
* @param params
|
|
582
|
+
* @returns
|
|
583
|
+
*/
|
|
584
|
+
static satisfiesConstraints(route, params) {
|
|
585
|
+
const constraints = this.resolveConstraints(route);
|
|
586
|
+
for (const name of Object.keys(constraints)) {
|
|
587
|
+
const value = params[name];
|
|
588
|
+
if (typeof value === "undefined" || value === null) continue;
|
|
589
|
+
const values = Array.isArray(value) ? value : [value];
|
|
590
|
+
const regex = this.toConstraintRegex(constraints[name]);
|
|
591
|
+
if (!values.every((entry) => regex.test(String(entry)))) return false;
|
|
592
|
+
}
|
|
593
|
+
return true;
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Determine which of a route's parameters are allowed to span multiple path
|
|
597
|
+
* segments (i.e. their constraint matches an encoded forward slash). These are
|
|
598
|
+
* registered with the adapter's catch-all syntax.
|
|
599
|
+
*
|
|
600
|
+
* @param route
|
|
601
|
+
* @returns
|
|
602
|
+
*/
|
|
603
|
+
static wildcardParameters(route) {
|
|
604
|
+
const wildcards = /* @__PURE__ */ new Set();
|
|
605
|
+
const constraints = this.resolveConstraints(route);
|
|
606
|
+
const declared = new Set(route.parameters.map((parameter) => parameter.name));
|
|
607
|
+
for (const name of Object.keys(constraints)) {
|
|
608
|
+
if (!declared.has(name)) continue;
|
|
609
|
+
if (this.toConstraintRegex(constraints[name]).test("a/b")) wildcards.add(name);
|
|
610
|
+
}
|
|
611
|
+
return wildcards;
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Render a single wildcard (slash-spanning) parameter for the underlying
|
|
615
|
+
* router's registration path. Overridden per adapter; the base form keeps the
|
|
616
|
+
* plain `:name` placeholder.
|
|
617
|
+
*
|
|
618
|
+
* @param name
|
|
619
|
+
* @returns
|
|
620
|
+
*/
|
|
621
|
+
static formatWildcardParam(name) {
|
|
622
|
+
return `:${name}`;
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Rewrite a route's registration paths so any wildcard parameters use the
|
|
626
|
+
* adapter's catch-all syntax. Non-wildcard routes are returned unchanged.
|
|
627
|
+
*
|
|
628
|
+
* @param route
|
|
629
|
+
* @returns
|
|
630
|
+
*/
|
|
631
|
+
static resolveRegistrationPaths(route) {
|
|
632
|
+
const wildcards = this.wildcardParameters(route);
|
|
633
|
+
if (!wildcards.size) return route.registrationPaths;
|
|
634
|
+
return route.registrationPaths.map((path) => path.split("/").map((segment) => {
|
|
635
|
+
const name = segment.startsWith(":") ? segment.slice(1) : "";
|
|
636
|
+
return name && wildcards.has(name) ? this.formatWildcardParam(name) : segment;
|
|
637
|
+
}).join("/"));
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Resolve the final parameters for a dispatched route, applying domain
|
|
641
|
+
* matching and constraint validation. Returns the merged parameters, or
|
|
642
|
+
* `false` when the route should not handle the request (host mismatch or a
|
|
643
|
+
* constraint failure) so the adapter can fall through.
|
|
644
|
+
*
|
|
645
|
+
* @param route
|
|
646
|
+
* @param ctx
|
|
647
|
+
* @param baseParams
|
|
648
|
+
* @returns
|
|
649
|
+
*/
|
|
650
|
+
static matchRoute(route, ctx, baseParams = {}) {
|
|
651
|
+
const params = { ...baseParams ?? {} };
|
|
652
|
+
if (route.domainPattern) {
|
|
653
|
+
const domainParams = this.matchDomain(route.domainPattern, this.extractHost(ctx));
|
|
654
|
+
if (!domainParams) return false;
|
|
655
|
+
Object.assign(params, domainParams);
|
|
656
|
+
}
|
|
657
|
+
this.normalizeWildcardParams(route, params);
|
|
658
|
+
if (!this.satisfiesConstraints(route, params)) return false;
|
|
659
|
+
return params;
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Normalize wildcard (slash-spanning) parameters into a single string keyed by
|
|
663
|
+
* the declared parameter name, smoothing over the differing shapes adapters
|
|
664
|
+
* return (Express yields an array of segments, Fastify keys it under `*`).
|
|
665
|
+
*
|
|
666
|
+
* @param route
|
|
667
|
+
* @param params
|
|
668
|
+
*/
|
|
669
|
+
static normalizeWildcardParams(route, params) {
|
|
670
|
+
const wildcards = this.wildcardParameters(route);
|
|
671
|
+
if (!wildcards.size) return;
|
|
672
|
+
for (const name of wildcards) {
|
|
673
|
+
let value = params[name];
|
|
674
|
+
if (typeof value === "undefined" && typeof params["*"] !== "undefined") {
|
|
675
|
+
value = params["*"];
|
|
676
|
+
delete params["*"];
|
|
677
|
+
}
|
|
678
|
+
if (Array.isArray(value)) value = value.join("/");
|
|
679
|
+
if (typeof value !== "undefined") params[name] = value;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Get the route currently being dispatched, if any.
|
|
684
|
+
*
|
|
685
|
+
* @returns
|
|
686
|
+
*/
|
|
687
|
+
static current() {
|
|
688
|
+
const store = this.pluginRequestContext.getStore();
|
|
689
|
+
return store?.request?.route ?? store?.ctx?.clearRequest?.route;
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Get the name of the route currently being dispatched.
|
|
693
|
+
*
|
|
694
|
+
* @returns
|
|
695
|
+
*/
|
|
696
|
+
static currentRouteName() {
|
|
697
|
+
return this.current()?.routeName ?? "";
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* Get the action (`Controller@method` or `Closure`) of the route currently
|
|
701
|
+
* being dispatched.
|
|
702
|
+
*
|
|
703
|
+
* @returns
|
|
704
|
+
*/
|
|
705
|
+
static currentRouteAction() {
|
|
706
|
+
return this.current()?.action ?? "";
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
439
709
|
* Configures the router with the given options, such as method override settings.
|
|
440
710
|
*
|
|
441
711
|
* @param this
|
|
@@ -507,6 +777,7 @@ var CoreRouter = class {
|
|
|
507
777
|
const context = this.groupContext.getStore();
|
|
508
778
|
const activePrefix = context?.prefix ?? this.prefix;
|
|
509
779
|
const activeGroupMiddlewares = context?.groupMiddlewares ?? this.groupMiddlewares;
|
|
780
|
+
const activeDomain = context?.domain;
|
|
510
781
|
methods = Array.isArray(methods) ? methods : [methods];
|
|
511
782
|
middlewares = middlewares ? Array.isArray(middlewares) ? middlewares : [middlewares] : void 0;
|
|
512
783
|
const fullPath = this.normalizePath(`${activePrefix}/${path}`);
|
|
@@ -523,6 +794,7 @@ var CoreRouter = class {
|
|
|
523
794
|
]), {
|
|
524
795
|
registrationPaths,
|
|
525
796
|
parameters,
|
|
797
|
+
domain: activeDomain,
|
|
526
798
|
onName: (name, route, previousName) => {
|
|
527
799
|
if (previousName && this.routesByName.get(previousName) === route) this.routesByName.delete(previousName);
|
|
528
800
|
this.routesByName.set(name, route);
|
|
@@ -643,11 +915,24 @@ var CoreRouter = class {
|
|
|
643
915
|
* @param middlewares
|
|
644
916
|
*/
|
|
645
917
|
static group(prefix, source, middlewares) {
|
|
918
|
+
return this.makeGroup(prefix, source, middlewares);
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* Build a route group, optionally constrained to a host pattern. Shared by
|
|
922
|
+
* `group` and the `domain` registrar.
|
|
923
|
+
*
|
|
924
|
+
* @param prefix
|
|
925
|
+
* @param source
|
|
926
|
+
* @param middlewares
|
|
927
|
+
* @param extra
|
|
928
|
+
*/
|
|
929
|
+
static makeGroup(prefix, source, middlewares, extra) {
|
|
646
930
|
this.ensureState();
|
|
647
931
|
return new RouteGroup({
|
|
648
932
|
prefix,
|
|
649
933
|
source,
|
|
650
934
|
middlewares,
|
|
935
|
+
domain: extra?.domain,
|
|
651
936
|
context: this.groupContext,
|
|
652
937
|
defaultPrefix: this.prefix,
|
|
653
938
|
defaultMiddlewares: this.groupMiddlewares,
|
|
@@ -656,6 +941,18 @@ var CoreRouter = class {
|
|
|
656
941
|
});
|
|
657
942
|
}
|
|
658
943
|
/**
|
|
944
|
+
* Begin a route registration constrained to a host pattern such as
|
|
945
|
+
* `{account}.example.com`. Returns a registrar whose `.group()` registers the
|
|
946
|
+
* routes under that domain (matched parameters become route parameters).
|
|
947
|
+
*
|
|
948
|
+
* @param pattern
|
|
949
|
+
* @returns
|
|
950
|
+
*/
|
|
951
|
+
static domain(pattern) {
|
|
952
|
+
this.ensureState();
|
|
953
|
+
return new RouteRegistrar((prefix, source, middlewares, extra) => this.makeGroup(prefix, source, middlewares, extra), { domain: pattern });
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
659
956
|
* Adds global middlewares to the router, which will be applied to all routes.
|
|
660
957
|
*
|
|
661
958
|
* @param this
|
|
@@ -831,6 +1128,16 @@ var CoreRouter = class {
|
|
|
831
1128
|
instance.clearRequest = clearRequest;
|
|
832
1129
|
}
|
|
833
1130
|
};
|
|
1131
|
+
/**
|
|
1132
|
+
* Expose the active request's route through the `Route` facade (`Route.current()`,
|
|
1133
|
+
* `Route.currentRouteName()`, `Route.currentRouteAction()`) by delegating to the
|
|
1134
|
+
* shared router request context.
|
|
1135
|
+
*/
|
|
1136
|
+
Route.bindCurrentResolvers({
|
|
1137
|
+
current: () => CoreRouter.current(),
|
|
1138
|
+
currentRouteName: () => CoreRouter.currentRouteName(),
|
|
1139
|
+
currentRouteAction: () => CoreRouter.currentRouteAction()
|
|
1140
|
+
});
|
|
834
1141
|
|
|
835
1142
|
//#endregion
|
|
836
1143
|
export { CoreRouter };
|
package/dist/express/router.cjs
CHANGED
|
@@ -11,6 +11,9 @@ const require_responses = require('../core/responses.cjs');
|
|
|
11
11
|
*/
|
|
12
12
|
var Router = class Router extends require_CoreRouter.CoreRouter {
|
|
13
13
|
static routerStateNamespace = "clear-router:express";
|
|
14
|
+
static formatWildcardParam(name) {
|
|
15
|
+
return `*${name}`;
|
|
16
|
+
}
|
|
14
17
|
static ensureRequestBodyAccessor(req) {
|
|
15
18
|
if (typeof req.getBody !== "function") req.getBody = () => req.body ?? {};
|
|
16
19
|
}
|
|
@@ -196,7 +199,7 @@ var Router = class Router extends require_CoreRouter.CoreRouter {
|
|
|
196
199
|
console.error("[ROUTES]", error.message);
|
|
197
200
|
throw error;
|
|
198
201
|
}
|
|
199
|
-
for (const registrationPath of route
|
|
202
|
+
for (const registrationPath of this.resolveRegistrationPaths(route)) router[method](registrationPath, (req, _res, next) => {
|
|
200
203
|
Router.ensureRequestBodyAccessor(req);
|
|
201
204
|
const override = Router.resolveMethodOverride(req.method, req.headers, req.body);
|
|
202
205
|
if (method === "post" && override && override !== "post") return next("route");
|
|
@@ -210,10 +213,13 @@ var Router = class Router extends require_CoreRouter.CoreRouter {
|
|
|
210
213
|
next
|
|
211
214
|
};
|
|
212
215
|
const inst = instance ?? route;
|
|
216
|
+
const params = Router.matchRoute(route, ctx, ctx.req.params);
|
|
217
|
+
if (params === false) return next("route");
|
|
218
|
+
Object.assign(ctx.req.params, params);
|
|
213
219
|
Router.bindRequestToInstance(ctx, inst, route, {
|
|
214
220
|
body: ctx.req.getBody(),
|
|
215
221
|
query: ctx.req.query,
|
|
216
|
-
params
|
|
222
|
+
params,
|
|
217
223
|
method
|
|
218
224
|
});
|
|
219
225
|
const result = await Router.callHandler(handlerFunction, ctx, bindingTarget, bindingMethod, bindingHandler, bindingMetadata);
|
|
@@ -228,7 +234,7 @@ var Router = class Router extends require_CoreRouter.CoreRouter {
|
|
|
228
234
|
"put",
|
|
229
235
|
"patch",
|
|
230
236
|
"delete"
|
|
231
|
-
].includes(method)) for (const registrationPath of route
|
|
237
|
+
].includes(method)) for (const registrationPath of this.resolveRegistrationPaths(route)) router.post(registrationPath, (req, _res, next) => {
|
|
232
238
|
Router.ensureRequestBodyAccessor(req);
|
|
233
239
|
if (Router.resolveMethodOverride(req.method, req.headers, req.body) !== method) return next("route");
|
|
234
240
|
req.method = method.toUpperCase();
|
|
@@ -242,10 +248,13 @@ var Router = class Router extends require_CoreRouter.CoreRouter {
|
|
|
242
248
|
next
|
|
243
249
|
};
|
|
244
250
|
const inst = instance ?? route;
|
|
251
|
+
const params = Router.matchRoute(route, ctx, ctx.req.params);
|
|
252
|
+
if (params === false) return next("route");
|
|
253
|
+
Object.assign(ctx.req.params, params);
|
|
245
254
|
Router.bindRequestToInstance(ctx, inst, route, {
|
|
246
255
|
body: ctx.req.getBody(),
|
|
247
256
|
query: ctx.req.query,
|
|
248
|
-
params
|
|
257
|
+
params,
|
|
249
258
|
method
|
|
250
259
|
});
|
|
251
260
|
const result = await Router.callHandler(handlerFunction, ctx, bindingTarget, bindingMethod, bindingHandler, bindingMetadata);
|
|
@@ -16,6 +16,7 @@ import { Router } from "express";
|
|
|
16
16
|
*/
|
|
17
17
|
declare class Router$1 extends CoreRouter {
|
|
18
18
|
protected static routerStateNamespace: string;
|
|
19
|
+
protected static formatWildcardParam(name: string): string;
|
|
19
20
|
private static ensureRequestBodyAccessor;
|
|
20
21
|
private static sendReturnValue;
|
|
21
22
|
/**
|
|
@@ -16,6 +16,7 @@ import { Router } from "express";
|
|
|
16
16
|
*/
|
|
17
17
|
declare class Router$1 extends CoreRouter {
|
|
18
18
|
protected static routerStateNamespace: string;
|
|
19
|
+
protected static formatWildcardParam(name: string): string;
|
|
19
20
|
private static ensureRequestBodyAccessor;
|
|
20
21
|
private static sendReturnValue;
|
|
21
22
|
/**
|