@koa/router 15.0.0 → 15.1.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/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,9 +847,19 @@ 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) {
806
- route.setPrefix(normalizedPrefix);
853
+ if (previousPrefix && typeof route.path === "string") {
854
+ if (route.path.startsWith(previousPrefix)) {
855
+ route.path = route.path.slice(previousPrefix.length) || "/";
856
+ route.setPrefix(normalizedPrefix);
857
+ } else {
858
+ route.setPrefix(normalizedPrefix);
859
+ }
860
+ } else {
861
+ route.setPrefix(normalizedPrefix);
862
+ }
807
863
  }
808
864
  return this;
809
865
  }
@@ -831,7 +887,10 @@ var Router = class {
831
887
  matchedLayers,
832
888
  requestPath
833
889
  );
834
- return (0, import_koa_compose.default)(middlewareChain)(context, next);
890
+ return (0, import_koa_compose.default)(middlewareChain)(
891
+ context,
892
+ next
893
+ );
835
894
  }.bind(this);
836
895
  dispatchMiddleware.router = this;
837
896
  return dispatchMiddleware;
@@ -841,17 +900,19 @@ var Router = class {
841
900
  * @private
842
901
  */
843
902
  _getRequestPath(context) {
844
- return this.opts.routerPath || context.newRouterPath || context.path || context.routerPath || "";
903
+ const context_ = context;
904
+ return this.opts.routerPath || context_.newRouterPath || context_.path || context_.routerPath || "";
845
905
  }
846
906
  /**
847
907
  * Store matched routes on context
848
908
  * @private
849
909
  */
850
910
  _storeMatchedRoutes(context, matchResult) {
851
- if (context.matched) {
852
- context.matched.push(...matchResult.path);
911
+ const context_ = context;
912
+ if (context_.matched) {
913
+ context_.matched.push(...matchResult.path);
853
914
  } else {
854
- context.matched = matchResult.path;
915
+ context_.matched = matchResult.path;
855
916
  }
856
917
  }
857
918
  /**
@@ -859,11 +920,12 @@ var Router = class {
859
920
  * @private
860
921
  */
861
922
  _setMatchedRouteInfo(context, matchedLayers) {
923
+ const context_ = context;
862
924
  const routeLayer = matchedLayers.toReversed().find((layer) => layer.methods.length > 0);
863
925
  if (routeLayer) {
864
- context._matchedRoute = routeLayer.path;
926
+ context_._matchedRoute = routeLayer.path;
865
927
  if (routeLayer.name) {
866
- context._matchedRouteName = routeLayer.name;
928
+ context_._matchedRouteName = routeLayer.name;
867
929
  }
868
930
  }
869
931
  }
@@ -950,11 +1012,11 @@ var Router = class {
950
1012
  if (!this._shouldProcessAllowedMethods(routerContext)) {
951
1013
  return;
952
1014
  }
953
- const allowedMethods = this._collectAllowedMethods(
954
- routerContext.matched
955
- );
1015
+ const matchedRoutes = routerContext.matched || [];
1016
+ const allowedMethods = this._collectAllowedMethods(matchedRoutes);
956
1017
  const allowedMethodsList = Object.keys(allowedMethods);
957
- if (!implementedMethods.includes(context.method)) {
1018
+ const requestMethod = context.method.toUpperCase();
1019
+ if (!implementedMethods.includes(requestMethod)) {
958
1020
  this._handleNotImplemented(
959
1021
  routerContext,
960
1022
  allowedMethodsList,
@@ -962,11 +1024,11 @@ var Router = class {
962
1024
  );
963
1025
  return;
964
1026
  }
965
- if (context.method === "OPTIONS" && allowedMethodsList.length > 0) {
1027
+ if (requestMethod === "OPTIONS" && allowedMethodsList.length > 0) {
966
1028
  this._handleOptionsRequest(routerContext, allowedMethodsList);
967
1029
  return;
968
1030
  }
969
- if (allowedMethodsList.length > 0 && !allowedMethods[context.method]) {
1031
+ if (allowedMethodsList.length > 0 && !allowedMethods[requestMethod]) {
970
1032
  this._handleMethodNotAllowed(
971
1033
  routerContext,
972
1034
  allowedMethodsList,
@@ -1080,12 +1142,12 @@ var Router = class {
1080
1142
  redirect(source, destination, code) {
1081
1143
  let resolvedSource = source;
1082
1144
  let resolvedDestination = destination;
1083
- if (typeof source === "symbol" || source[0] !== "/") {
1145
+ if (typeof source === "symbol" || typeof source === "string" && source[0] !== "/") {
1084
1146
  const sourceUrl = this.url(source);
1085
1147
  if (sourceUrl instanceof Error) throw sourceUrl;
1086
1148
  resolvedSource = sourceUrl;
1087
1149
  }
1088
- if (typeof destination === "symbol" || destination[0] !== "/" && !destination.includes("://")) {
1150
+ if (typeof destination === "symbol" || typeof destination === "string" && destination[0] !== "/" && !destination.includes("://")) {
1089
1151
  const destinationUrl = this.url(destination);
1090
1152
  if (destinationUrl instanceof Error) throw destinationUrl;
1091
1153
  resolvedDestination = destinationUrl;
@@ -1202,7 +1264,7 @@ var Router = class {
1202
1264
  */
1203
1265
  url(name, ...arguments_) {
1204
1266
  const route = this.route(name);
1205
- if (route) return route.url.apply(route, arguments_);
1267
+ if (route) return route.url(...arguments_);
1206
1268
  return new Error(`No route found for name: ${String(name)}`);
1207
1269
  }
1208
1270
  /**
@@ -1219,12 +1281,13 @@ var Router = class {
1219
1281
  pathAndMethod: [],
1220
1282
  route: false
1221
1283
  };
1284
+ const normalizedMethod = method.toUpperCase();
1222
1285
  for (const layer of this.stack) {
1223
1286
  debug("test %s %s", layer.path, layer.regexp);
1224
1287
  if (layer.match(path)) {
1225
1288
  matchResult.path.push(layer);
1226
1289
  const isMiddleware = layer.methods.length === 0;
1227
- const matchesMethod = layer.methods.includes(method);
1290
+ const matchesMethod = layer.methods.includes(normalizedMethod);
1228
1291
  if (isMiddleware || matchesMethod) {
1229
1292
  matchResult.pathAndMethod.push(layer);
1230
1293
  if (layer.methods.length > 0) {
@@ -1350,7 +1413,10 @@ var Router = class {
1350
1413
  return this._registerMethod("delete", ...arguments_);
1351
1414
  }
1352
1415
  del(...arguments_) {
1353
- return this.delete.apply(this, arguments_);
1416
+ return this.delete.apply(
1417
+ this,
1418
+ arguments_
1419
+ );
1354
1420
  }
1355
1421
  head(...arguments_) {
1356
1422
  return this._registerMethod("head", ...arguments_);
@@ -1359,8 +1425,10 @@ var Router = class {
1359
1425
  return this._registerMethod("options", ...arguments_);
1360
1426
  }
1361
1427
  };
1428
+ var RouterExport = Router;
1429
+ var router_default = RouterExport;
1362
1430
  for (const httpMethod of httpMethods) {
1363
- const isAlreadyDefined = COMMON_HTTP_METHODS.includes(httpMethod) || Router.prototype[httpMethod];
1431
+ const isAlreadyDefined = COMMON_HTTP_METHODS.includes(httpMethod) || httpMethod in Router.prototype;
1364
1432
  if (!isAlreadyDefined) {
1365
1433
  Object.defineProperty(Router.prototype, httpMethod, {
1366
1434
  value: function(...arguments_) {
@@ -1376,3 +1444,7 @@ for (const httpMethod of httpMethods) {
1376
1444
  0 && (module.exports = {
1377
1445
  Router
1378
1446
  });
1447
+ if (module.exports.default) {
1448
+ Object.assign(module.exports.default, module.exports);
1449
+ module.exports = module.exports.default;
1450
+ }