@koa/router 15.0.0 → 15.1.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.js CHANGED
@@ -30,13 +30,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
- Router: () => Router,
34
- default: () => Router
33
+ Router: () => RouterExport,
34
+ default: () => router_default
35
35
  });
36
36
  module.exports = __toCommonJS(index_exports);
37
37
 
38
38
  // src/router.ts
39
- var import_debug = __toESM(require("debug"));
40
39
  var import_koa_compose = __toESM(require("koa-compose"));
41
40
  var import_http_errors = __toESM(require("http-errors"));
42
41
 
@@ -82,7 +81,7 @@ function normalizeLayerOptionsToPathToRegexp(options = {}) {
82
81
  return normalized;
83
82
  }
84
83
 
85
- // src/layer.ts
84
+ // src/utils/safe-decode-uri-components.ts
86
85
  function safeDecodeURIComponent(text) {
87
86
  try {
88
87
  return decodeURIComponent(text);
@@ -90,6 +89,8 @@ function safeDecodeURIComponent(text) {
90
89
  return text;
91
90
  }
92
91
  }
92
+
93
+ // src/layer.ts
93
94
  var Layer = class {
94
95
  opts;
95
96
  name;
@@ -230,9 +231,15 @@ var Layer = class {
230
231
  *
231
232
  * @param args - URL parameters (various formats supported)
232
233
  * @returns Generated URL
234
+ * @throws Error if route path is a RegExp (cannot generate URL from RegExp)
233
235
  * @private
234
236
  */
235
237
  url(...arguments_) {
238
+ if (this.path instanceof RegExp) {
239
+ throw new TypeError(
240
+ "Cannot generate URL for routes defined with RegExp paths. Use string paths with named parameters instead."
241
+ );
242
+ }
236
243
  const { params, options } = this._parseUrlArguments(arguments_);
237
244
  const cleanPath = this.path.replaceAll("(.*)", "");
238
245
  const pathCompiler = compilePath(cleanPath, {
@@ -259,20 +266,28 @@ var Layer = class {
259
266
  * @private
260
267
  */
261
268
  _parseUrlArguments(allArguments) {
262
- let parameters = allArguments[0];
269
+ let parameters = allArguments[0] ?? {};
263
270
  let options = allArguments[1];
264
- if (typeof parameters !== "object") {
271
+ if (typeof parameters !== "object" || parameters === null) {
265
272
  const argumentsList = [...allArguments];
266
273
  const lastArgument = argumentsList.at(-1);
267
- if (typeof lastArgument === "object") {
274
+ if (typeof lastArgument === "object" && lastArgument !== null) {
268
275
  options = lastArgument;
269
276
  parameters = argumentsList.slice(0, -1);
270
277
  } else {
271
278
  parameters = argumentsList;
272
279
  }
273
- } else if (parameters && parameters.query && !options) {
274
- options = parameters;
275
- parameters = {};
280
+ } else if (parameters && !options) {
281
+ const parameterKeys = Object.keys(parameters);
282
+ const isOnlyOptions = parameterKeys.length === 1 && parameterKeys[0] === "query";
283
+ if (isOnlyOptions) {
284
+ options = parameters;
285
+ parameters = {};
286
+ } else if ("query" in parameters && parameters.query) {
287
+ const { query, ...restParameters } = parameters;
288
+ options = { query };
289
+ parameters = restParameters;
290
+ }
276
291
  }
277
292
  return { params: parameters, options };
278
293
  }
@@ -295,7 +310,7 @@ var Layer = class {
295
310
  );
296
311
  }
297
312
  }
298
- } else if (hasNamedParameters && typeof parameters === "object" && !parameters.query) {
313
+ } else if (hasNamedParameters && typeof parameters === "object" && !("query" in parameters)) {
299
314
  for (const [parameterName, parameterValue] of Object.entries(
300
315
  parameters
301
316
  )) {
@@ -309,14 +324,19 @@ var Layer = class {
309
324
  * @private
310
325
  */
311
326
  _addQueryString(baseUrl, query) {
312
- const parsedUrl = (0, import_node_url.parse)(baseUrl);
327
+ const parsed = (0, import_node_url.parse)(baseUrl);
328
+ const urlObject = {
329
+ ...parsed,
330
+ query: parsed.query ?? void 0
331
+ };
313
332
  if (typeof query === "string") {
314
- parsedUrl.search = query;
333
+ urlObject.search = query;
334
+ urlObject.query = void 0;
315
335
  } else {
316
- parsedUrl.search = void 0;
317
- parsedUrl.query = query;
336
+ urlObject.search = void 0;
337
+ urlObject.query = query;
318
338
  }
319
- return (0, import_node_url.format)(parsedUrl);
339
+ return (0, import_node_url.format)(urlObject);
320
340
  }
321
341
  /**
322
342
  * Run validations on route named parameters.
@@ -366,7 +386,7 @@ var Layer = class {
366
386
  * @private
367
387
  */
368
388
  _createParamMiddleware(parameterName, parameterHandler) {
369
- const middleware = function(context, next) {
389
+ const middleware = ((context, next) => {
370
390
  if (!context._matchedParams) {
371
391
  context._matchedParams = /* @__PURE__ */ new WeakMap();
372
392
  }
@@ -374,13 +394,8 @@ var Layer = class {
374
394
  return next();
375
395
  }
376
396
  context._matchedParams.set(parameterHandler, true);
377
- return parameterHandler.call(
378
- this,
379
- context.params[parameterName],
380
- context,
381
- next
382
- );
383
- };
397
+ return parameterHandler(context.params[parameterName], context, next);
398
+ });
384
399
  middleware.param = parameterName;
385
400
  middleware._originalFn = parameterHandler;
386
401
  return middleware;
@@ -390,20 +405,26 @@ var Layer = class {
390
405
  * @private
391
406
  */
392
407
  _insertParamMiddleware(middlewareStack, parameterMiddleware, parameterNamesList, currentParameterPosition) {
393
- middlewareStack.some((existingMiddleware, stackIndex) => {
408
+ let inserted = false;
409
+ for (let stackIndex = 0; stackIndex < middlewareStack.length; stackIndex++) {
410
+ const existingMiddleware = middlewareStack[stackIndex];
394
411
  if (!existingMiddleware.param) {
395
412
  middlewareStack.splice(stackIndex, 0, parameterMiddleware);
396
- return true;
413
+ inserted = true;
414
+ break;
397
415
  }
398
416
  const existingParameterPosition = parameterNamesList.indexOf(
399
417
  existingMiddleware.param
400
418
  );
401
419
  if (existingParameterPosition > currentParameterPosition) {
402
420
  middlewareStack.splice(stackIndex, 0, parameterMiddleware);
403
- return true;
421
+ inserted = true;
422
+ break;
404
423
  }
405
- return false;
406
- });
424
+ }
425
+ if (!inserted) {
426
+ middlewareStack.push(parameterMiddleware);
427
+ }
407
428
  }
408
429
  /**
409
430
  * Prefix route path.
@@ -434,7 +455,7 @@ var Layer = class {
434
455
  const pathIsRawRegex = this.opts.pathAsRegExp === true && typeof this.path === "string";
435
456
  if (prefixHasParameters && pathIsRawRegex) {
436
457
  const currentPath = this.path;
437
- if (currentPath === String.raw`(?:\/|$)` || currentPath === String.raw`(?:\/|$)`) {
458
+ if (currentPath === String.raw`(?:\/|$)` || currentPath === String.raw`(?:\\\/|$)`) {
438
459
  this.path = "{/*rest}";
439
460
  this.opts.pathAsRegExp = false;
440
461
  }
@@ -461,7 +482,9 @@ var Layer = class {
461
482
  this.paramNames = keys;
462
483
  this.opts.pathAsRegExp = false;
463
484
  } else if (treatAsRegExp) {
464
- this.regexp = this.path instanceof RegExp ? this.path : new RegExp(this.path);
485
+ const pathString = this.path;
486
+ const anchoredPattern = pathString.startsWith("^") ? pathString : `^${pathString}`;
487
+ this.regexp = this.path instanceof RegExp ? this.path : new RegExp(anchoredPattern);
465
488
  } else {
466
489
  const options = normalizeLayerOptionsToPathToRegexp(this.opts);
467
490
  const { regexp, keys } = compilePathToRegexp(
@@ -501,7 +524,9 @@ function normalizeParameterMiddleware(parameterMiddleware) {
501
524
  return [parameterMiddleware];
502
525
  }
503
526
  function applyParameterMiddlewareToRoute(route, parameterName, parameterMiddleware) {
504
- const middlewareList = normalizeParameterMiddleware(parameterMiddleware);
527
+ const middlewareList = normalizeParameterMiddleware(
528
+ parameterMiddleware
529
+ );
505
530
  for (const middleware of middlewareList) {
506
531
  route.param(parameterName, middleware);
507
532
  }
@@ -510,7 +535,11 @@ function applyAllParameterMiddleware(route, parametersObject) {
510
535
  const parameterNames = Object.keys(parametersObject);
511
536
  for (const parameterName of parameterNames) {
512
537
  const parameterMiddleware = parametersObject[parameterName];
513
- applyParameterMiddlewareToRoute(route, parameterName, parameterMiddleware);
538
+ applyParameterMiddlewareToRoute(
539
+ route,
540
+ parameterName,
541
+ parameterMiddleware
542
+ );
514
543
  }
515
544
  }
516
545
 
@@ -559,8 +588,11 @@ function determineMiddlewarePath(explicitPath, hasPrefixParameters) {
559
588
  };
560
589
  }
561
590
 
562
- // src/router.ts
591
+ // src/utils/debug.ts
592
+ var import_debug = __toESM(require("debug"));
563
593
  var debug = (0, import_debug.default)("koa-router");
594
+
595
+ // src/router.ts
564
596
  var httpMethods = getAllHttpMethods();
565
597
  var Router = class {
566
598
  opts;
@@ -640,6 +672,11 @@ var Router = class {
640
672
  if (hasExplicitPath) {
641
673
  explicitPath = middleware.shift();
642
674
  }
675
+ if (middleware.length === 0) {
676
+ throw new Error(
677
+ "You must provide at least one middleware function to router.use()"
678
+ );
679
+ }
643
680
  for (const currentMiddleware of middleware) {
644
681
  if (this._isNestedRouter(currentMiddleware)) {
645
682
  this._mountNestedRouter(
@@ -657,11 +694,11 @@ var Router = class {
657
694
  return this;
658
695
  }
659
696
  /**
660
- * Check if first argument is an array of paths
697
+ * Check if first argument is an array of paths (all elements must be strings)
661
698
  * @private
662
699
  */
663
700
  _isPathArray(firstArgument) {
664
- return Array.isArray(firstArgument) && typeof firstArgument[0] === "string";
701
+ return Array.isArray(firstArgument) && firstArgument.length > 0 && firstArgument.every((item) => typeof item === "string");
665
702
  }
666
703
  /**
667
704
  * Check if first argument is an explicit path (string or RegExp)
@@ -676,7 +713,7 @@ var Router = class {
676
713
  * @private
677
714
  */
678
715
  _isNestedRouter(middleware) {
679
- return middleware.router !== void 0;
716
+ return typeof middleware === "function" && "router" in middleware && middleware.router !== void 0;
680
717
  }
681
718
  /**
682
719
  * Apply middleware to multiple paths
@@ -731,14 +768,22 @@ var Router = class {
731
768
  );
732
769
  }
733
770
  /**
734
- * Clone a layer instance
771
+ * Clone a layer instance (deep clone to avoid shared references)
735
772
  * @private
736
773
  */
737
774
  _cloneLayer(sourceLayer) {
738
- return Object.assign(
775
+ const cloned = Object.assign(
739
776
  Object.create(Object.getPrototypeOf(sourceLayer)),
740
- sourceLayer
777
+ sourceLayer,
778
+ {
779
+ // Deep clone arrays and objects to avoid shared references
780
+ stack: [...sourceLayer.stack],
781
+ methods: [...sourceLayer.methods],
782
+ paramNames: [...sourceLayer.paramNames],
783
+ opts: { ...sourceLayer.opts }
784
+ }
741
785
  );
786
+ return cloned;
742
787
  }
743
788
  /**
744
789
  * Apply this router's param middleware to a nested router
@@ -789,6 +834,7 @@ var Router = class {
789
834
  }
790
835
  /**
791
836
  * Set the path prefix for a Router instance that was already initialized.
837
+ * Note: Calling this method multiple times will replace the prefix, not stack them.
792
838
  *
793
839
  * @example
794
840
  *
@@ -801,8 +847,12 @@ var Router = class {
801
847
  */
802
848
  prefix(prefixPath) {
803
849
  const normalizedPrefix = prefixPath.replace(/\/$/, "");
850
+ const previousPrefix = this.opts.prefix || "";
804
851
  this.opts.prefix = normalizedPrefix;
805
852
  for (const route of this.stack) {
853
+ if (previousPrefix && typeof route.path === "string" && route.path.startsWith(previousPrefix)) {
854
+ route.path = route.path.slice(previousPrefix.length) || "/";
855
+ }
806
856
  route.setPrefix(normalizedPrefix);
807
857
  }
808
858
  return this;
@@ -831,7 +881,10 @@ var Router = class {
831
881
  matchedLayers,
832
882
  requestPath
833
883
  );
834
- return (0, import_koa_compose.default)(middlewareChain)(context, next);
884
+ return (0, import_koa_compose.default)(middlewareChain)(
885
+ context,
886
+ next
887
+ );
835
888
  }.bind(this);
836
889
  dispatchMiddleware.router = this;
837
890
  return dispatchMiddleware;
@@ -841,17 +894,19 @@ var Router = class {
841
894
  * @private
842
895
  */
843
896
  _getRequestPath(context) {
844
- return this.opts.routerPath || context.newRouterPath || context.path || context.routerPath || "";
897
+ const context_ = context;
898
+ return this.opts.routerPath || context_.newRouterPath || context_.path || context_.routerPath || "";
845
899
  }
846
900
  /**
847
901
  * Store matched routes on context
848
902
  * @private
849
903
  */
850
904
  _storeMatchedRoutes(context, matchResult) {
851
- if (context.matched) {
852
- context.matched.push(...matchResult.path);
905
+ const context_ = context;
906
+ if (context_.matched) {
907
+ context_.matched.push(...matchResult.path);
853
908
  } else {
854
- context.matched = matchResult.path;
909
+ context_.matched = matchResult.path;
855
910
  }
856
911
  }
857
912
  /**
@@ -859,11 +914,12 @@ var Router = class {
859
914
  * @private
860
915
  */
861
916
  _setMatchedRouteInfo(context, matchedLayers) {
917
+ const context_ = context;
862
918
  const routeLayer = matchedLayers.toReversed().find((layer) => layer.methods.length > 0);
863
919
  if (routeLayer) {
864
- context._matchedRoute = routeLayer.path;
920
+ context_._matchedRoute = routeLayer.path;
865
921
  if (routeLayer.name) {
866
- context._matchedRouteName = routeLayer.name;
922
+ context_._matchedRouteName = routeLayer.name;
867
923
  }
868
924
  }
869
925
  }
@@ -950,11 +1006,11 @@ var Router = class {
950
1006
  if (!this._shouldProcessAllowedMethods(routerContext)) {
951
1007
  return;
952
1008
  }
953
- const allowedMethods = this._collectAllowedMethods(
954
- routerContext.matched
955
- );
1009
+ const matchedRoutes = routerContext.matched || [];
1010
+ const allowedMethods = this._collectAllowedMethods(matchedRoutes);
956
1011
  const allowedMethodsList = Object.keys(allowedMethods);
957
- if (!implementedMethods.includes(context.method)) {
1012
+ const requestMethod = context.method.toUpperCase();
1013
+ if (!implementedMethods.includes(requestMethod)) {
958
1014
  this._handleNotImplemented(
959
1015
  routerContext,
960
1016
  allowedMethodsList,
@@ -962,11 +1018,11 @@ var Router = class {
962
1018
  );
963
1019
  return;
964
1020
  }
965
- if (context.method === "OPTIONS" && allowedMethodsList.length > 0) {
1021
+ if (requestMethod === "OPTIONS" && allowedMethodsList.length > 0) {
966
1022
  this._handleOptionsRequest(routerContext, allowedMethodsList);
967
1023
  return;
968
1024
  }
969
- if (allowedMethodsList.length > 0 && !allowedMethods[context.method]) {
1025
+ if (allowedMethodsList.length > 0 && !allowedMethods[requestMethod]) {
970
1026
  this._handleMethodNotAllowed(
971
1027
  routerContext,
972
1028
  allowedMethodsList,
@@ -1080,12 +1136,12 @@ var Router = class {
1080
1136
  redirect(source, destination, code) {
1081
1137
  let resolvedSource = source;
1082
1138
  let resolvedDestination = destination;
1083
- if (typeof source === "symbol" || source[0] !== "/") {
1139
+ if (typeof source === "symbol" || typeof source === "string" && source[0] !== "/") {
1084
1140
  const sourceUrl = this.url(source);
1085
1141
  if (sourceUrl instanceof Error) throw sourceUrl;
1086
1142
  resolvedSource = sourceUrl;
1087
1143
  }
1088
- if (typeof destination === "symbol" || destination[0] !== "/" && !destination.includes("://")) {
1144
+ if (typeof destination === "symbol" || typeof destination === "string" && destination[0] !== "/" && !destination.includes("://")) {
1089
1145
  const destinationUrl = this.url(destination);
1090
1146
  if (destinationUrl instanceof Error) throw destinationUrl;
1091
1147
  resolvedDestination = destinationUrl;
@@ -1202,7 +1258,7 @@ var Router = class {
1202
1258
  */
1203
1259
  url(name, ...arguments_) {
1204
1260
  const route = this.route(name);
1205
- if (route) return route.url.apply(route, arguments_);
1261
+ if (route) return route.url(...arguments_);
1206
1262
  return new Error(`No route found for name: ${String(name)}`);
1207
1263
  }
1208
1264
  /**
@@ -1219,12 +1275,13 @@ var Router = class {
1219
1275
  pathAndMethod: [],
1220
1276
  route: false
1221
1277
  };
1278
+ const normalizedMethod = method.toUpperCase();
1222
1279
  for (const layer of this.stack) {
1223
1280
  debug("test %s %s", layer.path, layer.regexp);
1224
1281
  if (layer.match(path)) {
1225
1282
  matchResult.path.push(layer);
1226
1283
  const isMiddleware = layer.methods.length === 0;
1227
- const matchesMethod = layer.methods.includes(method);
1284
+ const matchesMethod = layer.methods.includes(normalizedMethod);
1228
1285
  if (isMiddleware || matchesMethod) {
1229
1286
  matchResult.pathAndMethod.push(layer);
1230
1287
  if (layer.methods.length > 0) {
@@ -1350,7 +1407,10 @@ var Router = class {
1350
1407
  return this._registerMethod("delete", ...arguments_);
1351
1408
  }
1352
1409
  del(...arguments_) {
1353
- return this.delete.apply(this, arguments_);
1410
+ return this.delete.apply(
1411
+ this,
1412
+ arguments_
1413
+ );
1354
1414
  }
1355
1415
  head(...arguments_) {
1356
1416
  return this._registerMethod("head", ...arguments_);
@@ -1359,8 +1419,10 @@ var Router = class {
1359
1419
  return this._registerMethod("options", ...arguments_);
1360
1420
  }
1361
1421
  };
1422
+ var RouterExport = Router;
1423
+ var router_default = RouterExport;
1362
1424
  for (const httpMethod of httpMethods) {
1363
- const isAlreadyDefined = COMMON_HTTP_METHODS.includes(httpMethod) || Router.prototype[httpMethod];
1425
+ const isAlreadyDefined = COMMON_HTTP_METHODS.includes(httpMethod) || httpMethod in Router.prototype;
1364
1426
  if (!isAlreadyDefined) {
1365
1427
  Object.defineProperty(Router.prototype, httpMethod, {
1366
1428
  value: function(...arguments_) {
@@ -1376,3 +1438,7 @@ for (const httpMethod of httpMethods) {
1376
1438
  0 && (module.exports = {
1377
1439
  Router
1378
1440
  });
1441
+ if (module.exports.default) {
1442
+ Object.assign(module.exports.default, module.exports);
1443
+ module.exports = module.exports.default;
1444
+ }