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.
Files changed (42) hide show
  1. package/dist/Route.cjs +200 -12
  2. package/dist/Route.d.cts +125 -1
  3. package/dist/Route.d.mts +125 -1
  4. package/dist/Route.mjs +199 -12
  5. package/dist/RouteGroup.cjs +1 -0
  6. package/dist/RouteGroup.mjs +1 -0
  7. package/dist/RouteRegistrar.cjs +68 -0
  8. package/dist/RouteRegistrar.d.cts +62 -0
  9. package/dist/RouteRegistrar.d.mts +62 -0
  10. package/dist/RouteRegistrar.mjs +67 -0
  11. package/dist/core/CoreRouter.cjs +307 -0
  12. package/dist/core/CoreRouter.d.cts +168 -0
  13. package/dist/core/CoreRouter.d.mts +168 -0
  14. package/dist/core/CoreRouter.mjs +307 -0
  15. package/dist/decorators/setup.d.mts +0 -1
  16. package/dist/express/router.cjs +13 -4
  17. package/dist/express/router.d.cts +1 -0
  18. package/dist/express/router.d.mts +1 -0
  19. package/dist/express/router.mjs +13 -4
  20. package/dist/fastify/router.cjs +13 -4
  21. package/dist/fastify/router.d.cts +1 -0
  22. package/dist/fastify/router.d.mts +1 -0
  23. package/dist/fastify/router.mjs +13 -4
  24. package/dist/h3/router.cjs +13 -4
  25. package/dist/h3/router.d.cts +1 -0
  26. package/dist/h3/router.d.mts +1 -0
  27. package/dist/h3/router.mjs +13 -4
  28. package/dist/hono/router.cjs +11 -4
  29. package/dist/hono/router.d.cts +1 -0
  30. package/dist/hono/router.d.mts +1 -0
  31. package/dist/hono/router.mjs +11 -4
  32. package/dist/index.cjs +3 -0
  33. package/dist/index.d.cts +3 -2
  34. package/dist/index.d.mts +3 -3
  35. package/dist/index.mjs +3 -2
  36. package/dist/koa/router.cjs +13 -4
  37. package/dist/koa/router.d.cts +1 -0
  38. package/dist/koa/router.d.mts +1 -0
  39. package/dist/koa/router.mjs +13 -4
  40. package/dist/types/basic.d.cts +2 -0
  41. package/dist/types/basic.d.mts +2 -0
  42. 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
  *
@@ -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 };
@@ -1,3 +1,2 @@
1
1
  import { Bind, BindDecorator, BindFactory, BindToken, BindValue, Container } from "../core/bindings.mjs";
2
- import "reflect-metadata";
3
2
  export { Bind, BindDecorator, BindFactory, BindToken, BindValue, Container };
@@ -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.registrationPaths) router[method](registrationPath, (req, _res, next) => {
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: ctx.req.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.registrationPaths) router.post(registrationPath, (req, _res, next) => {
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: ctx.req.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
  /**