@koa/router 15.7.0 → 15.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -375,6 +375,12 @@ declare class RouterImplementation<StateT = koa.DefaultState, ContextT = koa.Def
375
375
  * @returns This router instance
376
376
  */
377
377
  prefix(prefixPath: string): Router<StateT, ContextT>;
378
+ private _determineMiddlewarePath;
379
+ private _hasPathParameters;
380
+ private _updatePrefix;
381
+ private _getUnprefixedPath;
382
+ private _updateRoutePrefix;
383
+ private _updateRouteCaptureOptions;
378
384
  /**
379
385
  * Returns router middleware which dispatches a route matching the request.
380
386
  *
@@ -943,7 +949,7 @@ type RouterContext<StateT = DefaultState, ContextT = DefaultContext, BodyT = unk
943
949
  /**
944
950
  * Track param middleware execution (internal)
945
951
  */
946
- _matchedParams?: WeakMap<Function, boolean>;
952
+ _matchedParams?: WeakMap<Function, Map<string, string>>;
947
953
  };
948
954
  /**
949
955
  * Router middleware function type
package/dist/index.d.ts CHANGED
@@ -375,6 +375,12 @@ declare class RouterImplementation<StateT = koa.DefaultState, ContextT = koa.Def
375
375
  * @returns This router instance
376
376
  */
377
377
  prefix(prefixPath: string): Router<StateT, ContextT>;
378
+ private _determineMiddlewarePath;
379
+ private _hasPathParameters;
380
+ private _updatePrefix;
381
+ private _getUnprefixedPath;
382
+ private _updateRoutePrefix;
383
+ private _updateRouteCaptureOptions;
378
384
  /**
379
385
  * Returns router middleware which dispatches a route matching the request.
380
386
  *
@@ -943,7 +949,7 @@ type RouterContext<StateT = DefaultState, ContextT = DefaultContext, BodyT = unk
943
949
  /**
944
950
  * Track param middleware execution (internal)
945
951
  */
946
- _matchedParams?: WeakMap<Function, boolean>;
952
+ _matchedParams?: WeakMap<Function, Map<string, string>>;
947
953
  };
948
954
  /**
949
955
  * Router middleware function type
package/dist/index.js CHANGED
@@ -271,7 +271,7 @@ var Layer = class {
271
271
  _configurePathMatching() {
272
272
  if (this.opts.pathAsRegExp === true) {
273
273
  this.regexp = this.path instanceof RegExp ? this.path : new RegExp(this.path);
274
- } else if (this.path) {
274
+ } else if (this.path || this.path === "") {
275
275
  this._configurePathToRegexp();
276
276
  }
277
277
  }
@@ -504,11 +504,17 @@ var Layer = class {
504
504
  if (!context._matchedParams) {
505
505
  context._matchedParams = /* @__PURE__ */ new WeakMap();
506
506
  }
507
- if (context._matchedParams.has(parameterHandler)) {
507
+ const parameterValue = context.params[parameterName];
508
+ const matchedParameters = context._matchedParams.get(parameterHandler) ?? /* @__PURE__ */ new Map();
509
+ const hasMatchedParameter = matchedParameters.has(parameterName);
510
+ const matchedValue = matchedParameters.get(parameterName);
511
+ const hasMatchedValue = hasMatchedParameter && matchedValue === parameterValue;
512
+ if (hasMatchedValue) {
508
513
  return next();
509
514
  }
510
- context._matchedParams.set(parameterHandler, true);
511
- return parameterHandler(context.params[parameterName], context, next);
515
+ matchedParameters.set(parameterName, parameterValue);
516
+ context._matchedParams.set(parameterHandler, matchedParameters);
517
+ return parameterHandler(parameterValue, context, next);
512
518
  });
513
519
  middleware.param = parameterName;
514
520
  middleware._originalFn = parameterHandler;
@@ -548,10 +554,7 @@ var Layer = class {
548
554
  * @private
549
555
  */
550
556
  setPrefix(prefixPath) {
551
- if (!this.path) {
552
- return this;
553
- }
554
- if (this.path instanceof RegExp) {
557
+ if (typeof this.path !== "string") {
555
558
  return this;
556
559
  }
557
560
  this.path = this._applyPrefix(prefixPath);
@@ -569,9 +572,14 @@ var Layer = class {
569
572
  const isPathIsRawRegex = this.opts.pathAsRegExp === true && typeof this.path === "string";
570
573
  if (isPrefixHasParameters && isPathIsRawRegex) {
571
574
  const currentPath = this.path;
572
- if (currentPath === String.raw`(?:\/|$)` || currentPath === String.raw`(?:\\\/|$)`) {
573
- this.path = "{/*rest}";
575
+ const middlewareBoundary = [
576
+ String.raw`(?:\/|$)`,
577
+ String.raw`(?:\\\/|$)`
578
+ ].find((boundary) => currentPath.endsWith(boundary));
579
+ if (middlewareBoundary) {
580
+ this.path = `${currentPath.slice(0, -middlewareBoundary.length)}{/*rest}`;
574
581
  this.opts.pathAsRegExp = false;
582
+ this.opts.ignoreWildcardParameter = "rest";
575
583
  }
576
584
  }
577
585
  if (isRootPath && !isStrictMode) {
@@ -598,7 +606,7 @@ var Layer = class {
598
606
  } else if (isTreatAsRegExp) {
599
607
  const pathString = this.path;
600
608
  const anchoredPattern = pathString.startsWith("^") ? pathString : `^${pathString}`;
601
- this.regexp = this.path instanceof RegExp ? this.path : new RegExp(anchoredPattern);
609
+ this.regexp = this.path instanceof RegExp ? this.path : new RegExp(anchoredPattern, this.opts.sensitive ? "" : "i");
602
610
  } else {
603
611
  const options = normalizeLayerOptionsToPathToRegexp(this.opts);
604
612
  const { regexp, keys } = compilePathToRegexp(
@@ -657,57 +665,14 @@ function applyAllParameterMiddleware(route, parametersObject) {
657
665
  }
658
666
  }
659
667
 
660
- // src/utils/path-helpers.ts
661
- function hasPathParameters(path, options = {}) {
662
- if (!path) {
663
- return false;
664
- }
665
- const { keys } = compilePathToRegexp(path, options);
666
- return keys.length > 0;
667
- }
668
- function determineMiddlewarePath(explicitPath, hasPrefixParameters) {
669
- if (explicitPath !== void 0) {
670
- if (typeof explicitPath === "string") {
671
- if (explicitPath === "") {
672
- return {
673
- path: "{/*rest}",
674
- pathAsRegExp: false
675
- };
676
- }
677
- if (explicitPath === "/") {
678
- return {
679
- path: "/",
680
- pathAsRegExp: false
681
- };
682
- }
683
- return {
684
- path: explicitPath,
685
- pathAsRegExp: false
686
- };
687
- }
688
- return {
689
- path: explicitPath,
690
- pathAsRegExp: true
691
- };
692
- }
693
- if (hasPrefixParameters) {
694
- return {
695
- path: "{/*rest}",
696
- pathAsRegExp: false
697
- };
698
- }
699
- return {
700
- path: String.raw`(?:\/|$)`,
701
- pathAsRegExp: true
702
- };
703
- }
704
-
705
668
  // src/utils/debug.ts
706
669
  var import_debug = __toESM(require("debug"));
707
670
  var debug = (0, import_debug.default)("koa-router");
708
671
 
709
672
  // src/router.ts
710
673
  var httpMethods = getAllHttpMethods();
674
+ var MIDDLEWARE_BOUNDARY_PATH = String.raw`(?:\/|$)`;
675
+ var MIDDLEWARE_WILDCARD_PATH = "{/*rest}";
711
676
  var RouterImplementation = class {
712
677
  opts;
713
678
  methods;
@@ -853,7 +818,10 @@ var RouterImplementation = class {
853
818
  _mountNestedRouter(middlewareWithRouter, mountPath) {
854
819
  const nestedRouter = middlewareWithRouter.router;
855
820
  const clonedRouter = this._cloneRouter(nestedRouter);
856
- const mountPathHasParameters = mountPath && typeof mountPath === "string" && hasPathParameters(mountPath, this.opts);
821
+ const isMountPathHasParameters = this._hasPathParameters(mountPath);
822
+ const isRouterPrefixHasParameters = this._hasPathParameters(
823
+ this.opts.prefix
824
+ );
857
825
  for (let routeIndex = 0; routeIndex < clonedRouter.stack.length; routeIndex++) {
858
826
  const nestedLayer = clonedRouter.stack[routeIndex];
859
827
  const clonedLayer = this._cloneLayer(nestedLayer);
@@ -863,7 +831,7 @@ var RouterImplementation = class {
863
831
  if (this.opts.prefix) {
864
832
  clonedLayer.setPrefix(this.opts.prefix);
865
833
  }
866
- if (clonedLayer.methods.length === 0 && mountPathHasParameters) {
834
+ if (clonedLayer.methods.length === 0 && (isMountPathHasParameters || isRouterPrefixHasParameters)) {
867
835
  clonedLayer.opts.ignoreCaptures = false;
868
836
  }
869
837
  this.stack.push(clonedLayer);
@@ -924,20 +892,14 @@ var RouterImplementation = class {
924
892
  * @private
925
893
  */
926
894
  _registerMiddleware(middleware, explicitPath, hasExplicitPath) {
927
- const isPrefixHasParameters = hasPathParameters(
928
- this.opts.prefix || "",
929
- this.opts
930
- );
895
+ const isPrefixHasParameters = this._hasPathParameters(this.opts.prefix);
931
896
  const effectiveExplicitPath = (() => {
932
897
  if (explicitPath !== void 0) return explicitPath;
933
898
  if (isPrefixHasParameters) return "";
934
899
  return;
935
900
  })();
936
901
  const isEffectiveHasExplicitPath = hasExplicitPath || explicitPath === void 0 && isPrefixHasParameters;
937
- const { path: middlewarePath, pathAsRegExp } = determineMiddlewarePath(
938
- effectiveExplicitPath,
939
- isPrefixHasParameters
940
- );
902
+ const { path: middlewarePath, pathAsRegExp } = this._determineMiddlewarePath(effectiveExplicitPath);
941
903
  let finalPath = middlewarePath;
942
904
  let isUsePathToRegexp = pathAsRegExp;
943
905
  const isRootPath = isEffectiveHasExplicitPath && middlewarePath === "/";
@@ -945,7 +907,7 @@ var RouterImplementation = class {
945
907
  finalPath = middlewarePath;
946
908
  isUsePathToRegexp = false;
947
909
  }
948
- const ignoreWildcardParameter = effectiveExplicitPath === "" && middlewarePath === "{/*rest}" ? "rest" : void 0;
910
+ const ignoreWildcardParameter = effectiveExplicitPath === "" && middlewarePath === MIDDLEWARE_WILDCARD_PATH ? "rest" : void 0;
949
911
  this.register(finalPath, [], middleware, {
950
912
  end: isRootPath,
951
913
  ignoreCaptures: !isEffectiveHasExplicitPath && !isPrefixHasParameters,
@@ -967,19 +929,56 @@ var RouterImplementation = class {
967
929
  * @returns This router instance
968
930
  */
969
931
  prefix(prefixPath) {
970
- const normalizedPrefix = prefixPath.replace(/\/$/, "");
932
+ this._updatePrefix(prefixPath);
933
+ return this;
934
+ }
935
+ _determineMiddlewarePath(explicitPath) {
936
+ if (explicitPath === "") {
937
+ return { path: MIDDLEWARE_WILDCARD_PATH, pathAsRegExp: false };
938
+ }
939
+ if (explicitPath === void 0) {
940
+ return { path: MIDDLEWARE_BOUNDARY_PATH, pathAsRegExp: true };
941
+ }
942
+ const isPathAsRegExp = typeof explicitPath !== "string";
943
+ return { path: explicitPath, pathAsRegExp: isPathAsRegExp };
944
+ }
945
+ _hasPathParameters(path = "") {
946
+ if (typeof path !== "string" || !path) return false;
947
+ const { keys } = compilePathToRegexp(path, this.opts);
948
+ return keys.length > 0;
949
+ }
950
+ _updatePrefix(currentPrefix) {
971
951
  const previousPrefix = this.opts.prefix || "";
952
+ const normalizedPrefix = currentPrefix.replace(/\/$/, "");
953
+ const isPrefixHasParameters = this._hasPathParameters(normalizedPrefix);
972
954
  this.opts.prefix = normalizedPrefix;
973
955
  for (const route of this.stack) {
974
- if (typeof route.path === "string" && previousPrefix) {
975
- const stripBoundary = previousPrefix + "/";
976
- if (route.path === previousPrefix || route.path.startsWith(stripBoundary)) {
977
- route.path = route.path.slice(previousPrefix.length) || "/";
978
- }
979
- }
980
- route.setPrefix(normalizedPrefix);
956
+ this._updateRoutePrefix(route, previousPrefix);
957
+ this._updateRouteCaptureOptions(route, isPrefixHasParameters);
958
+ }
959
+ }
960
+ _getUnprefixedPath(route, previousPrefix) {
961
+ if (typeof route.path !== "string" || !previousPrefix) return route.path;
962
+ const prefixedMiddlewarePaths = /* @__PURE__ */ new Set([
963
+ previousPrefix + MIDDLEWARE_BOUNDARY_PATH,
964
+ previousPrefix + MIDDLEWARE_WILDCARD_PATH
965
+ ]);
966
+ const stripBoundary = previousPrefix + "/";
967
+ const isExactPrefix = route.path === previousPrefix;
968
+ const hasPrefixBoundary = route.path.startsWith(stripBoundary);
969
+ const isPathlessMiddleware = route.methods.length === 0 && prefixedMiddlewarePaths.has(route.path);
970
+ const hasReplaceablePrefix = isExactPrefix || hasPrefixBoundary || isPathlessMiddleware;
971
+ if (!hasReplaceablePrefix) return route.path;
972
+ return route.path.slice(previousPrefix.length) || "/";
973
+ }
974
+ _updateRoutePrefix(route, previousPrefix) {
975
+ route.path = this._getUnprefixedPath(route, previousPrefix);
976
+ route.setPrefix(this.opts.prefix || "");
977
+ }
978
+ _updateRouteCaptureOptions(route, isPrefixHasParameters) {
979
+ if (route.methods.length === 0 && isPrefixHasParameters) {
980
+ route.opts.ignoreCaptures = false;
981
981
  }
982
- return this;
983
982
  }
984
983
  /**
985
984
  * Returns router middleware which dispatches a route matching the request.
@@ -1058,23 +1057,29 @@ var RouterImplementation = class {
1058
1057
  */
1059
1058
  _buildMiddlewareChain(matchedLayers, requestPath) {
1060
1059
  let layersToExecute;
1060
+ let chosenLayer;
1061
1061
  if (this.exclusive) {
1062
- let chosenLayer;
1062
+ const routeLayers = matchedLayers.filter(
1063
+ (layer) => layer.methods.length > 0
1064
+ );
1063
1065
  if (this.opts.exclusive === "specificity") {
1064
- for (const layer of matchedLayers) {
1066
+ for (const layer of routeLayers) {
1065
1067
  if (!chosenLayer || layer.paramNames.length < chosenLayer.paramNames.length) {
1066
1068
  chosenLayer = layer;
1067
1069
  }
1068
1070
  }
1069
1071
  } else {
1070
- chosenLayer = matchedLayers.at(-1);
1072
+ chosenLayer = routeLayers.at(-1);
1071
1073
  }
1072
- layersToExecute = chosenLayer ? [chosenLayer] : [];
1074
+ layersToExecute = matchedLayers.filter(
1075
+ (layer) => layer.methods.length === 0 || layer === chosenLayer
1076
+ );
1073
1077
  } else {
1074
1078
  layersToExecute = matchedLayers;
1075
1079
  }
1076
1080
  const middlewareChain = [];
1077
1081
  for (const layer of layersToExecute) {
1082
+ const metadataLayer = chosenLayer ?? layer;
1078
1083
  middlewareChain.push(
1079
1084
  (context, next) => {
1080
1085
  const routerContext = context;
@@ -1085,11 +1090,11 @@ var RouterImplementation = class {
1085
1090
  routerContext.params
1086
1091
  );
1087
1092
  routerContext.params = routerContext.request.params;
1088
- routerContext.routerPath = layer.path;
1089
- routerContext.routerName = layer.name || void 0;
1090
- routerContext._matchedRoute = layer.path;
1091
- if (layer.name) {
1092
- routerContext._matchedRouteName = layer.name;
1093
+ routerContext.routerPath = metadataLayer.path;
1094
+ routerContext.routerName = metadataLayer.name || void 0;
1095
+ routerContext._matchedRoute = metadataLayer.path;
1096
+ if (this.exclusive || metadataLayer.name) {
1097
+ routerContext._matchedRouteName = metadataLayer.name || void 0;
1093
1098
  }
1094
1099
  return next();
1095
1100
  },
package/dist/index.mjs CHANGED
@@ -232,7 +232,7 @@ var Layer = class {
232
232
  _configurePathMatching() {
233
233
  if (this.opts.pathAsRegExp === true) {
234
234
  this.regexp = this.path instanceof RegExp ? this.path : new RegExp(this.path);
235
- } else if (this.path) {
235
+ } else if (this.path || this.path === "") {
236
236
  this._configurePathToRegexp();
237
237
  }
238
238
  }
@@ -465,11 +465,17 @@ var Layer = class {
465
465
  if (!context._matchedParams) {
466
466
  context._matchedParams = /* @__PURE__ */ new WeakMap();
467
467
  }
468
- if (context._matchedParams.has(parameterHandler)) {
468
+ const parameterValue = context.params[parameterName];
469
+ const matchedParameters = context._matchedParams.get(parameterHandler) ?? /* @__PURE__ */ new Map();
470
+ const hasMatchedParameter = matchedParameters.has(parameterName);
471
+ const matchedValue = matchedParameters.get(parameterName);
472
+ const hasMatchedValue = hasMatchedParameter && matchedValue === parameterValue;
473
+ if (hasMatchedValue) {
469
474
  return next();
470
475
  }
471
- context._matchedParams.set(parameterHandler, true);
472
- return parameterHandler(context.params[parameterName], context, next);
476
+ matchedParameters.set(parameterName, parameterValue);
477
+ context._matchedParams.set(parameterHandler, matchedParameters);
478
+ return parameterHandler(parameterValue, context, next);
473
479
  });
474
480
  middleware.param = parameterName;
475
481
  middleware._originalFn = parameterHandler;
@@ -509,10 +515,7 @@ var Layer = class {
509
515
  * @private
510
516
  */
511
517
  setPrefix(prefixPath) {
512
- if (!this.path) {
513
- return this;
514
- }
515
- if (this.path instanceof RegExp) {
518
+ if (typeof this.path !== "string") {
516
519
  return this;
517
520
  }
518
521
  this.path = this._applyPrefix(prefixPath);
@@ -530,9 +533,14 @@ var Layer = class {
530
533
  const isPathIsRawRegex = this.opts.pathAsRegExp === true && typeof this.path === "string";
531
534
  if (isPrefixHasParameters && isPathIsRawRegex) {
532
535
  const currentPath = this.path;
533
- if (currentPath === String.raw`(?:\/|$)` || currentPath === String.raw`(?:\\\/|$)`) {
534
- this.path = "{/*rest}";
536
+ const middlewareBoundary = [
537
+ String.raw`(?:\/|$)`,
538
+ String.raw`(?:\\\/|$)`
539
+ ].find((boundary) => currentPath.endsWith(boundary));
540
+ if (middlewareBoundary) {
541
+ this.path = `${currentPath.slice(0, -middlewareBoundary.length)}{/*rest}`;
535
542
  this.opts.pathAsRegExp = false;
543
+ this.opts.ignoreWildcardParameter = "rest";
536
544
  }
537
545
  }
538
546
  if (isRootPath && !isStrictMode) {
@@ -559,7 +567,7 @@ var Layer = class {
559
567
  } else if (isTreatAsRegExp) {
560
568
  const pathString = this.path;
561
569
  const anchoredPattern = pathString.startsWith("^") ? pathString : `^${pathString}`;
562
- this.regexp = this.path instanceof RegExp ? this.path : new RegExp(anchoredPattern);
570
+ this.regexp = this.path instanceof RegExp ? this.path : new RegExp(anchoredPattern, this.opts.sensitive ? "" : "i");
563
571
  } else {
564
572
  const options = normalizeLayerOptionsToPathToRegexp(this.opts);
565
573
  const { regexp, keys } = compilePathToRegexp(
@@ -618,57 +626,14 @@ function applyAllParameterMiddleware(route, parametersObject) {
618
626
  }
619
627
  }
620
628
 
621
- // src/utils/path-helpers.ts
622
- function hasPathParameters(path, options = {}) {
623
- if (!path) {
624
- return false;
625
- }
626
- const { keys } = compilePathToRegexp(path, options);
627
- return keys.length > 0;
628
- }
629
- function determineMiddlewarePath(explicitPath, hasPrefixParameters) {
630
- if (explicitPath !== void 0) {
631
- if (typeof explicitPath === "string") {
632
- if (explicitPath === "") {
633
- return {
634
- path: "{/*rest}",
635
- pathAsRegExp: false
636
- };
637
- }
638
- if (explicitPath === "/") {
639
- return {
640
- path: "/",
641
- pathAsRegExp: false
642
- };
643
- }
644
- return {
645
- path: explicitPath,
646
- pathAsRegExp: false
647
- };
648
- }
649
- return {
650
- path: explicitPath,
651
- pathAsRegExp: true
652
- };
653
- }
654
- if (hasPrefixParameters) {
655
- return {
656
- path: "{/*rest}",
657
- pathAsRegExp: false
658
- };
659
- }
660
- return {
661
- path: String.raw`(?:\/|$)`,
662
- pathAsRegExp: true
663
- };
664
- }
665
-
666
629
  // src/utils/debug.ts
667
630
  import debugModule from "debug";
668
631
  var debug = debugModule("koa-router");
669
632
 
670
633
  // src/router.ts
671
634
  var httpMethods = getAllHttpMethods();
635
+ var MIDDLEWARE_BOUNDARY_PATH = String.raw`(?:\/|$)`;
636
+ var MIDDLEWARE_WILDCARD_PATH = "{/*rest}";
672
637
  var RouterImplementation = class {
673
638
  opts;
674
639
  methods;
@@ -814,7 +779,10 @@ var RouterImplementation = class {
814
779
  _mountNestedRouter(middlewareWithRouter, mountPath) {
815
780
  const nestedRouter = middlewareWithRouter.router;
816
781
  const clonedRouter = this._cloneRouter(nestedRouter);
817
- const mountPathHasParameters = mountPath && typeof mountPath === "string" && hasPathParameters(mountPath, this.opts);
782
+ const isMountPathHasParameters = this._hasPathParameters(mountPath);
783
+ const isRouterPrefixHasParameters = this._hasPathParameters(
784
+ this.opts.prefix
785
+ );
818
786
  for (let routeIndex = 0; routeIndex < clonedRouter.stack.length; routeIndex++) {
819
787
  const nestedLayer = clonedRouter.stack[routeIndex];
820
788
  const clonedLayer = this._cloneLayer(nestedLayer);
@@ -824,7 +792,7 @@ var RouterImplementation = class {
824
792
  if (this.opts.prefix) {
825
793
  clonedLayer.setPrefix(this.opts.prefix);
826
794
  }
827
- if (clonedLayer.methods.length === 0 && mountPathHasParameters) {
795
+ if (clonedLayer.methods.length === 0 && (isMountPathHasParameters || isRouterPrefixHasParameters)) {
828
796
  clonedLayer.opts.ignoreCaptures = false;
829
797
  }
830
798
  this.stack.push(clonedLayer);
@@ -885,20 +853,14 @@ var RouterImplementation = class {
885
853
  * @private
886
854
  */
887
855
  _registerMiddleware(middleware, explicitPath, hasExplicitPath) {
888
- const isPrefixHasParameters = hasPathParameters(
889
- this.opts.prefix || "",
890
- this.opts
891
- );
856
+ const isPrefixHasParameters = this._hasPathParameters(this.opts.prefix);
892
857
  const effectiveExplicitPath = (() => {
893
858
  if (explicitPath !== void 0) return explicitPath;
894
859
  if (isPrefixHasParameters) return "";
895
860
  return;
896
861
  })();
897
862
  const isEffectiveHasExplicitPath = hasExplicitPath || explicitPath === void 0 && isPrefixHasParameters;
898
- const { path: middlewarePath, pathAsRegExp } = determineMiddlewarePath(
899
- effectiveExplicitPath,
900
- isPrefixHasParameters
901
- );
863
+ const { path: middlewarePath, pathAsRegExp } = this._determineMiddlewarePath(effectiveExplicitPath);
902
864
  let finalPath = middlewarePath;
903
865
  let isUsePathToRegexp = pathAsRegExp;
904
866
  const isRootPath = isEffectiveHasExplicitPath && middlewarePath === "/";
@@ -906,7 +868,7 @@ var RouterImplementation = class {
906
868
  finalPath = middlewarePath;
907
869
  isUsePathToRegexp = false;
908
870
  }
909
- const ignoreWildcardParameter = effectiveExplicitPath === "" && middlewarePath === "{/*rest}" ? "rest" : void 0;
871
+ const ignoreWildcardParameter = effectiveExplicitPath === "" && middlewarePath === MIDDLEWARE_WILDCARD_PATH ? "rest" : void 0;
910
872
  this.register(finalPath, [], middleware, {
911
873
  end: isRootPath,
912
874
  ignoreCaptures: !isEffectiveHasExplicitPath && !isPrefixHasParameters,
@@ -928,19 +890,56 @@ var RouterImplementation = class {
928
890
  * @returns This router instance
929
891
  */
930
892
  prefix(prefixPath) {
931
- const normalizedPrefix = prefixPath.replace(/\/$/, "");
893
+ this._updatePrefix(prefixPath);
894
+ return this;
895
+ }
896
+ _determineMiddlewarePath(explicitPath) {
897
+ if (explicitPath === "") {
898
+ return { path: MIDDLEWARE_WILDCARD_PATH, pathAsRegExp: false };
899
+ }
900
+ if (explicitPath === void 0) {
901
+ return { path: MIDDLEWARE_BOUNDARY_PATH, pathAsRegExp: true };
902
+ }
903
+ const isPathAsRegExp = typeof explicitPath !== "string";
904
+ return { path: explicitPath, pathAsRegExp: isPathAsRegExp };
905
+ }
906
+ _hasPathParameters(path = "") {
907
+ if (typeof path !== "string" || !path) return false;
908
+ const { keys } = compilePathToRegexp(path, this.opts);
909
+ return keys.length > 0;
910
+ }
911
+ _updatePrefix(currentPrefix) {
932
912
  const previousPrefix = this.opts.prefix || "";
913
+ const normalizedPrefix = currentPrefix.replace(/\/$/, "");
914
+ const isPrefixHasParameters = this._hasPathParameters(normalizedPrefix);
933
915
  this.opts.prefix = normalizedPrefix;
934
916
  for (const route of this.stack) {
935
- if (typeof route.path === "string" && previousPrefix) {
936
- const stripBoundary = previousPrefix + "/";
937
- if (route.path === previousPrefix || route.path.startsWith(stripBoundary)) {
938
- route.path = route.path.slice(previousPrefix.length) || "/";
939
- }
940
- }
941
- route.setPrefix(normalizedPrefix);
917
+ this._updateRoutePrefix(route, previousPrefix);
918
+ this._updateRouteCaptureOptions(route, isPrefixHasParameters);
919
+ }
920
+ }
921
+ _getUnprefixedPath(route, previousPrefix) {
922
+ if (typeof route.path !== "string" || !previousPrefix) return route.path;
923
+ const prefixedMiddlewarePaths = /* @__PURE__ */ new Set([
924
+ previousPrefix + MIDDLEWARE_BOUNDARY_PATH,
925
+ previousPrefix + MIDDLEWARE_WILDCARD_PATH
926
+ ]);
927
+ const stripBoundary = previousPrefix + "/";
928
+ const isExactPrefix = route.path === previousPrefix;
929
+ const hasPrefixBoundary = route.path.startsWith(stripBoundary);
930
+ const isPathlessMiddleware = route.methods.length === 0 && prefixedMiddlewarePaths.has(route.path);
931
+ const hasReplaceablePrefix = isExactPrefix || hasPrefixBoundary || isPathlessMiddleware;
932
+ if (!hasReplaceablePrefix) return route.path;
933
+ return route.path.slice(previousPrefix.length) || "/";
934
+ }
935
+ _updateRoutePrefix(route, previousPrefix) {
936
+ route.path = this._getUnprefixedPath(route, previousPrefix);
937
+ route.setPrefix(this.opts.prefix || "");
938
+ }
939
+ _updateRouteCaptureOptions(route, isPrefixHasParameters) {
940
+ if (route.methods.length === 0 && isPrefixHasParameters) {
941
+ route.opts.ignoreCaptures = false;
942
942
  }
943
- return this;
944
943
  }
945
944
  /**
946
945
  * Returns router middleware which dispatches a route matching the request.
@@ -1019,23 +1018,29 @@ var RouterImplementation = class {
1019
1018
  */
1020
1019
  _buildMiddlewareChain(matchedLayers, requestPath) {
1021
1020
  let layersToExecute;
1021
+ let chosenLayer;
1022
1022
  if (this.exclusive) {
1023
- let chosenLayer;
1023
+ const routeLayers = matchedLayers.filter(
1024
+ (layer) => layer.methods.length > 0
1025
+ );
1024
1026
  if (this.opts.exclusive === "specificity") {
1025
- for (const layer of matchedLayers) {
1027
+ for (const layer of routeLayers) {
1026
1028
  if (!chosenLayer || layer.paramNames.length < chosenLayer.paramNames.length) {
1027
1029
  chosenLayer = layer;
1028
1030
  }
1029
1031
  }
1030
1032
  } else {
1031
- chosenLayer = matchedLayers.at(-1);
1033
+ chosenLayer = routeLayers.at(-1);
1032
1034
  }
1033
- layersToExecute = chosenLayer ? [chosenLayer] : [];
1035
+ layersToExecute = matchedLayers.filter(
1036
+ (layer) => layer.methods.length === 0 || layer === chosenLayer
1037
+ );
1034
1038
  } else {
1035
1039
  layersToExecute = matchedLayers;
1036
1040
  }
1037
1041
  const middlewareChain = [];
1038
1042
  for (const layer of layersToExecute) {
1043
+ const metadataLayer = chosenLayer ?? layer;
1039
1044
  middlewareChain.push(
1040
1045
  (context, next) => {
1041
1046
  const routerContext = context;
@@ -1046,11 +1051,11 @@ var RouterImplementation = class {
1046
1051
  routerContext.params
1047
1052
  );
1048
1053
  routerContext.params = routerContext.request.params;
1049
- routerContext.routerPath = layer.path;
1050
- routerContext.routerName = layer.name || void 0;
1051
- routerContext._matchedRoute = layer.path;
1052
- if (layer.name) {
1053
- routerContext._matchedRouteName = layer.name;
1054
+ routerContext.routerPath = metadataLayer.path;
1055
+ routerContext.routerName = metadataLayer.name || void 0;
1056
+ routerContext._matchedRoute = metadataLayer.path;
1057
+ if (this.exclusive || metadataLayer.name) {
1058
+ routerContext._matchedRouteName = metadataLayer.name || void 0;
1054
1059
  }
1055
1060
  return next();
1056
1061
  },
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@koa/router",
3
3
  "description": "Router middleware for Koa.",
4
- "version": "15.7.0",
4
+ "version": "15.7.1",
5
5
  "author": "Koa.js",
6
6
  "bugs": {
7
7
  "url": "https://github.com/koajs/router/issues",
@@ -31,30 +31,30 @@
31
31
  "path-to-regexp": "^8.4.2"
32
32
  },
33
33
  "devDependencies": {
34
- "@commitlint/cli": "^21.2.0",
35
- "@commitlint/config-conventional": "^21.2.0",
34
+ "@commitlint/cli": "^21.2.2",
35
+ "@commitlint/config-conventional": "^21.2.2",
36
36
  "@eslint/js": "^10.0.1",
37
37
  "@koa/bodyparser": "^6.1.0",
38
38
  "@types/debug": "^4.1.13",
39
39
  "@types/jsonwebtoken": "^9.0.10",
40
40
  "@types/koa": "^3.0.3",
41
- "@types/node": "^26.1.0",
42
- "@types/supertest": "^7.2.0",
43
- "@typescript-eslint/eslint-plugin": "^8.62.1",
44
- "@typescript-eslint/parser": "^8.62.1",
45
- "c8": "^11.0.0",
46
- "chalk": "^5.6.2",
47
- "eslint": "^10.6.0",
48
- "eslint-plugin-unicorn": "^70.0.0",
41
+ "@types/node": "^26.6.2",
42
+ "@types/supertest": "^7.2.1",
43
+ "@typescript-eslint/eslint-plugin": "^8.70.0",
44
+ "@typescript-eslint/parser": "^8.70.0",
45
+ "c8": "^12.0.0",
46
+ "chalk": "^6.0.0",
47
+ "eslint": "^10.11.0",
48
+ "eslint-plugin-unicorn": "^73.0.0",
49
49
  "husky": "^9.1.7",
50
- "joi": "^18.2.3",
50
+ "joi": "^18.2.9",
51
51
  "jsonwebtoken": "^9.0.3",
52
52
  "koa": "^3.2.1",
53
- "lint-staged": "^17.0.8",
54
- "np": "^11.2.1",
55
- "prettier": "^3.9.4",
53
+ "lint-staged": "^17.5.1",
54
+ "np": "^12.1.1",
55
+ "prettier": "^3.9.8",
56
56
  "rimraf": "^6.1.3",
57
- "supertest": "^7.2.2",
57
+ "supertest": "^7.3.1",
58
58
  "ts-node": "^10.9.2",
59
59
  "tsup": "^8.5.1",
60
60
  "typescript": "^5.9.3"
@@ -104,6 +104,7 @@
104
104
  "format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,json,md,yml,yaml}\"",
105
105
  "lint:ts": "eslint src test --fix",
106
106
  "lint": "npm run lint:ts",
107
+ "lint:ci": "eslint src test",
107
108
  "test:core": "TS_NODE_PROJECT=tsconfig.ts-node.json node --require ts-node/register --test test/*.test.ts test/**/*.test.ts",
108
109
  "test:recipes": "TS_NODE_PROJECT=tsconfig.recipes.json node --require ts-node/register --test recipes/**/*.test.ts",
109
110
  "pretest:all": "npm run lint && npm run build",
@@ -115,8 +116,5 @@
115
116
  "build": "tsup",
116
117
  "prepublishOnly": "npm run build",
117
118
  "release": "np --test-script test:all"
118
- },
119
- "resolutions": {
120
- "c8/yargs": "^18.0.0"
121
119
  }
122
120
  }