@koa/router 13.1.1 → 15.0.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/router.js ADDED
@@ -0,0 +1,1371 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/router.ts
31
+ var router_exports = {};
32
+ __export(router_exports, {
33
+ default: () => Router
34
+ });
35
+ module.exports = __toCommonJS(router_exports);
36
+ var import_debug = __toESM(require("debug"));
37
+ var import_koa_compose = __toESM(require("koa-compose"));
38
+ var import_http_errors = __toESM(require("http-errors"));
39
+
40
+ // src/layer.ts
41
+ var import_node_url = require("url");
42
+
43
+ // src/utils/path-to-regexp-wrapper.ts
44
+ var import_path_to_regexp = require("path-to-regexp");
45
+ function compilePathToRegexp(path, options = {}) {
46
+ const normalizedOptions = { ...options };
47
+ if ("strict" in normalizedOptions && !("trailing" in normalizedOptions)) {
48
+ normalizedOptions.trailing = normalizedOptions.strict !== true;
49
+ delete normalizedOptions.strict;
50
+ }
51
+ delete normalizedOptions.pathAsRegExp;
52
+ delete normalizedOptions.ignoreCaptures;
53
+ delete normalizedOptions.prefix;
54
+ const { regexp, keys } = (0, import_path_to_regexp.pathToRegexp)(path, normalizedOptions);
55
+ return { regexp, keys };
56
+ }
57
+ function compilePath(path, options = {}) {
58
+ return (0, import_path_to_regexp.compile)(path, options);
59
+ }
60
+ function parsePath(path, options) {
61
+ return (0, import_path_to_regexp.parse)(path, options);
62
+ }
63
+ function normalizeLayerOptionsToPathToRegexp(options = {}) {
64
+ const normalized = {
65
+ sensitive: options.sensitive,
66
+ end: options.end,
67
+ strict: options.strict,
68
+ trailing: options.trailing
69
+ };
70
+ if ("strict" in normalized && !("trailing" in normalized)) {
71
+ normalized.trailing = normalized.strict !== true;
72
+ delete normalized.strict;
73
+ }
74
+ for (const key of Object.keys(normalized)) {
75
+ if (normalized[key] === void 0) {
76
+ delete normalized[key];
77
+ }
78
+ }
79
+ return normalized;
80
+ }
81
+
82
+ // src/layer.ts
83
+ function safeDecodeURIComponent(text) {
84
+ try {
85
+ return decodeURIComponent(text);
86
+ } catch {
87
+ return text;
88
+ }
89
+ }
90
+ var Layer = class {
91
+ opts;
92
+ name;
93
+ methods;
94
+ paramNames;
95
+ stack;
96
+ path;
97
+ regexp;
98
+ /**
99
+ * Initialize a new routing Layer with given `method`, `path`, and `middleware`.
100
+ *
101
+ * @param path - Path string or regular expression
102
+ * @param methods - Array of HTTP verbs
103
+ * @param middleware - Layer callback/middleware or series of
104
+ * @param opts - Layer options
105
+ * @private
106
+ */
107
+ constructor(path, methods, middleware, options = {}) {
108
+ this.opts = options;
109
+ this.name = this.opts.name || void 0;
110
+ this.methods = this._normalizeHttpMethods(methods);
111
+ this.stack = this._normalizeAndValidateMiddleware(
112
+ middleware,
113
+ methods,
114
+ path
115
+ );
116
+ this.path = path;
117
+ this.paramNames = [];
118
+ this._configurePathMatching();
119
+ }
120
+ /**
121
+ * Normalize HTTP methods and add automatic HEAD support for GET
122
+ * @private
123
+ */
124
+ _normalizeHttpMethods(methods) {
125
+ const normalizedMethods = [];
126
+ for (const method of methods) {
127
+ const upperMethod = method.toUpperCase();
128
+ normalizedMethods.push(upperMethod);
129
+ if (upperMethod === "GET") {
130
+ normalizedMethods.unshift("HEAD");
131
+ }
132
+ }
133
+ return normalizedMethods;
134
+ }
135
+ /**
136
+ * Normalize middleware to array and validate all are functions
137
+ * @private
138
+ */
139
+ _normalizeAndValidateMiddleware(middleware, methods, path) {
140
+ const middlewareArray = Array.isArray(middleware) ? middleware : [middleware];
141
+ for (const middlewareFunction of middlewareArray) {
142
+ const middlewareType = typeof middlewareFunction;
143
+ if (middlewareType !== "function") {
144
+ const routeIdentifier = this.opts.name || path;
145
+ throw new Error(
146
+ `${methods.toString()} \`${routeIdentifier}\`: \`middleware\` must be a function, not \`${middlewareType}\``
147
+ );
148
+ }
149
+ }
150
+ return middlewareArray;
151
+ }
152
+ /**
153
+ * Configure path matching regexp and parameters
154
+ * @private
155
+ */
156
+ _configurePathMatching() {
157
+ if (this.opts.pathAsRegExp === true) {
158
+ this.regexp = this.path instanceof RegExp ? this.path : new RegExp(this.path);
159
+ } else if (this.path) {
160
+ this._configurePathToRegexp();
161
+ }
162
+ }
163
+ /**
164
+ * Configure path-to-regexp for string paths
165
+ * @private
166
+ */
167
+ _configurePathToRegexp() {
168
+ const options = normalizeLayerOptionsToPathToRegexp(this.opts);
169
+ const { regexp, keys } = compilePathToRegexp(this.path, options);
170
+ this.regexp = regexp;
171
+ this.paramNames = keys;
172
+ }
173
+ /**
174
+ * Returns whether request `path` matches route.
175
+ *
176
+ * @param path - Request path
177
+ * @returns Whether path matches
178
+ * @private
179
+ */
180
+ match(path) {
181
+ return this.regexp.test(path);
182
+ }
183
+ /**
184
+ * Returns map of URL parameters for given `path` and `paramNames`.
185
+ *
186
+ * @param _path - Request path (not used, kept for API compatibility)
187
+ * @param captures - Captured values from regexp
188
+ * @param existingParams - Existing params to merge with
189
+ * @returns Parameter map
190
+ * @private
191
+ */
192
+ params(_path, captures, existingParameters = {}) {
193
+ const parameterValues = { ...existingParameters };
194
+ for (const [captureIndex, capturedValue] of captures.entries()) {
195
+ const parameterDefinition = this.paramNames[captureIndex];
196
+ if (parameterDefinition && capturedValue && capturedValue.length > 0) {
197
+ const parameterName = parameterDefinition.name;
198
+ parameterValues[parameterName] = safeDecodeURIComponent(capturedValue);
199
+ }
200
+ }
201
+ return parameterValues;
202
+ }
203
+ /**
204
+ * Returns array of regexp url path captures.
205
+ *
206
+ * @param path - Request path
207
+ * @returns Array of captured values
208
+ * @private
209
+ */
210
+ captures(path) {
211
+ if (this.opts.ignoreCaptures) {
212
+ return [];
213
+ }
214
+ const match = path.match(this.regexp);
215
+ return match ? match.slice(1) : [];
216
+ }
217
+ /**
218
+ * Generate URL for route using given `params`.
219
+ *
220
+ * @example
221
+ *
222
+ * ```javascript
223
+ * const route = new Layer('/users/:id', ['GET'], fn);
224
+ *
225
+ * route.url({ id: 123 }); // => "/users/123"
226
+ * ```
227
+ *
228
+ * @param args - URL parameters (various formats supported)
229
+ * @returns Generated URL
230
+ * @private
231
+ */
232
+ url(...arguments_) {
233
+ const { params, options } = this._parseUrlArguments(arguments_);
234
+ const cleanPath = this.path.replaceAll("(.*)", "");
235
+ const pathCompiler = compilePath(cleanPath, {
236
+ encode: encodeURIComponent,
237
+ ...options
238
+ });
239
+ const parameterReplacements = this._buildParamReplacements(
240
+ params,
241
+ cleanPath
242
+ );
243
+ const generatedUrl = pathCompiler(parameterReplacements);
244
+ if (options && options.query) {
245
+ return this._addQueryString(generatedUrl, options.query);
246
+ }
247
+ return generatedUrl;
248
+ }
249
+ /**
250
+ * Parse url() arguments into params and options
251
+ * Supports multiple call signatures:
252
+ * - url({ id: 1 })
253
+ * - url(1, 2, 3)
254
+ * - url({ query: {...} })
255
+ * - url({ id: 1 }, { query: {...} })
256
+ * @private
257
+ */
258
+ _parseUrlArguments(allArguments) {
259
+ let parameters = allArguments[0];
260
+ let options = allArguments[1];
261
+ if (typeof parameters !== "object") {
262
+ const argumentsList = [...allArguments];
263
+ const lastArgument = argumentsList.at(-1);
264
+ if (typeof lastArgument === "object") {
265
+ options = lastArgument;
266
+ parameters = argumentsList.slice(0, -1);
267
+ } else {
268
+ parameters = argumentsList;
269
+ }
270
+ } else if (parameters && parameters.query && !options) {
271
+ options = parameters;
272
+ parameters = {};
273
+ }
274
+ return { params: parameters, options };
275
+ }
276
+ /**
277
+ * Build parameter replacements for URL generation
278
+ * @private
279
+ */
280
+ _buildParamReplacements(parameters, cleanPath) {
281
+ const { tokens } = parsePath(cleanPath);
282
+ const hasNamedParameters = tokens.some(
283
+ (token) => "name" in token && token.name
284
+ );
285
+ const parameterReplacements = {};
286
+ if (Array.isArray(parameters)) {
287
+ let parameterIndex = 0;
288
+ for (const token of tokens) {
289
+ if ("name" in token && token.name) {
290
+ parameterReplacements[token.name] = String(
291
+ parameters[parameterIndex++]
292
+ );
293
+ }
294
+ }
295
+ } else if (hasNamedParameters && typeof parameters === "object" && !parameters.query) {
296
+ for (const [parameterName, parameterValue] of Object.entries(
297
+ parameters
298
+ )) {
299
+ parameterReplacements[parameterName] = String(parameterValue);
300
+ }
301
+ }
302
+ return parameterReplacements;
303
+ }
304
+ /**
305
+ * Add query string to URL
306
+ * @private
307
+ */
308
+ _addQueryString(baseUrl, query) {
309
+ const parsedUrl = (0, import_node_url.parse)(baseUrl);
310
+ if (typeof query === "string") {
311
+ parsedUrl.search = query;
312
+ } else {
313
+ parsedUrl.search = void 0;
314
+ parsedUrl.query = query;
315
+ }
316
+ return (0, import_node_url.format)(parsedUrl);
317
+ }
318
+ /**
319
+ * Run validations on route named parameters.
320
+ *
321
+ * @example
322
+ *
323
+ * ```javascript
324
+ * router
325
+ * .param('user', function (id, ctx, next) {
326
+ * ctx.user = users[id];
327
+ * if (!ctx.user) return ctx.status = 404;
328
+ * next();
329
+ * })
330
+ * .get('/users/:user', function (ctx, next) {
331
+ * ctx.body = ctx.user;
332
+ * });
333
+ * ```
334
+ *
335
+ * @param paramName - Parameter name
336
+ * @param paramHandler - Middleware function
337
+ * @returns This layer instance
338
+ * @private
339
+ */
340
+ param(parameterName, parameterHandler) {
341
+ const middlewareStack = this.stack;
342
+ const routeParameterNames = this.paramNames;
343
+ const parameterMiddleware = this._createParamMiddleware(
344
+ parameterName,
345
+ parameterHandler
346
+ );
347
+ const parameterNamesList = routeParameterNames.map(
348
+ (parameterDefinition) => parameterDefinition.name
349
+ );
350
+ const parameterPosition = parameterNamesList.indexOf(parameterName);
351
+ if (parameterPosition !== -1) {
352
+ this._insertParamMiddleware(
353
+ middlewareStack,
354
+ parameterMiddleware,
355
+ parameterNamesList,
356
+ parameterPosition
357
+ );
358
+ }
359
+ return this;
360
+ }
361
+ /**
362
+ * Create param middleware with deduplication tracking
363
+ * @private
364
+ */
365
+ _createParamMiddleware(parameterName, parameterHandler) {
366
+ const middleware = function(context, next) {
367
+ if (!context._matchedParams) {
368
+ context._matchedParams = /* @__PURE__ */ new WeakMap();
369
+ }
370
+ if (context._matchedParams.has(parameterHandler)) {
371
+ return next();
372
+ }
373
+ context._matchedParams.set(parameterHandler, true);
374
+ return parameterHandler.call(
375
+ this,
376
+ context.params[parameterName],
377
+ context,
378
+ next
379
+ );
380
+ };
381
+ middleware.param = parameterName;
382
+ middleware._originalFn = parameterHandler;
383
+ return middleware;
384
+ }
385
+ /**
386
+ * Insert param middleware at the correct position in the stack
387
+ * @private
388
+ */
389
+ _insertParamMiddleware(middlewareStack, parameterMiddleware, parameterNamesList, currentParameterPosition) {
390
+ middlewareStack.some((existingMiddleware, stackIndex) => {
391
+ if (!existingMiddleware.param) {
392
+ middlewareStack.splice(stackIndex, 0, parameterMiddleware);
393
+ return true;
394
+ }
395
+ const existingParameterPosition = parameterNamesList.indexOf(
396
+ existingMiddleware.param
397
+ );
398
+ if (existingParameterPosition > currentParameterPosition) {
399
+ middlewareStack.splice(stackIndex, 0, parameterMiddleware);
400
+ return true;
401
+ }
402
+ return false;
403
+ });
404
+ }
405
+ /**
406
+ * Prefix route path.
407
+ *
408
+ * @param prefixPath - Prefix to prepend
409
+ * @returns This layer instance
410
+ * @private
411
+ */
412
+ setPrefix(prefixPath) {
413
+ if (!this.path) {
414
+ return this;
415
+ }
416
+ if (this.path instanceof RegExp) {
417
+ return this;
418
+ }
419
+ this.path = this._applyPrefix(prefixPath);
420
+ this._reconfigurePathMatching(prefixPath);
421
+ return this;
422
+ }
423
+ /**
424
+ * Apply prefix to the current path
425
+ * @private
426
+ */
427
+ _applyPrefix(prefixPath) {
428
+ const isRootPath = this.path === "/";
429
+ const isStrictMode = this.opts.strict === true;
430
+ const prefixHasParameters = prefixPath.includes(":");
431
+ const pathIsRawRegex = this.opts.pathAsRegExp === true && typeof this.path === "string";
432
+ if (prefixHasParameters && pathIsRawRegex) {
433
+ const currentPath = this.path;
434
+ if (currentPath === String.raw`(?:\/|$)` || currentPath === String.raw`(?:\/|$)`) {
435
+ this.path = "{/*rest}";
436
+ this.opts.pathAsRegExp = false;
437
+ }
438
+ }
439
+ if (isRootPath && !isStrictMode) {
440
+ return prefixPath;
441
+ }
442
+ return `${prefixPath}${this.path}`;
443
+ }
444
+ /**
445
+ * Reconfigure path matching after prefix is applied
446
+ * @private
447
+ */
448
+ _reconfigurePathMatching(prefixPath) {
449
+ const treatAsRegExp = this.opts.pathAsRegExp === true;
450
+ const prefixHasParameters = prefixPath && prefixPath.includes(":");
451
+ if (prefixHasParameters && treatAsRegExp) {
452
+ const options = normalizeLayerOptionsToPathToRegexp(this.opts);
453
+ const { regexp, keys } = compilePathToRegexp(
454
+ this.path,
455
+ options
456
+ );
457
+ this.regexp = regexp;
458
+ this.paramNames = keys;
459
+ this.opts.pathAsRegExp = false;
460
+ } else if (treatAsRegExp) {
461
+ this.regexp = this.path instanceof RegExp ? this.path : new RegExp(this.path);
462
+ } else {
463
+ const options = normalizeLayerOptionsToPathToRegexp(this.opts);
464
+ const { regexp, keys } = compilePathToRegexp(
465
+ this.path,
466
+ options
467
+ );
468
+ this.regexp = regexp;
469
+ this.paramNames = keys;
470
+ }
471
+ }
472
+ };
473
+
474
+ // src/utils/http-methods.ts
475
+ var import_node_http = __toESM(require("http"));
476
+ function getAllHttpMethods() {
477
+ return import_node_http.default.METHODS.map((method) => method.toLowerCase());
478
+ }
479
+ var COMMON_HTTP_METHODS = [
480
+ "get",
481
+ "post",
482
+ "put",
483
+ "patch",
484
+ "delete",
485
+ "del",
486
+ "head",
487
+ "options"
488
+ ];
489
+
490
+ // src/utils/parameter-helpers.ts
491
+ function normalizeParameterMiddleware(parameterMiddleware) {
492
+ if (!parameterMiddleware) {
493
+ return [];
494
+ }
495
+ if (Array.isArray(parameterMiddleware)) {
496
+ return parameterMiddleware;
497
+ }
498
+ return [parameterMiddleware];
499
+ }
500
+ function applyParameterMiddlewareToRoute(route, parameterName, parameterMiddleware) {
501
+ const middlewareList = normalizeParameterMiddleware(parameterMiddleware);
502
+ for (const middleware of middlewareList) {
503
+ route.param(parameterName, middleware);
504
+ }
505
+ }
506
+ function applyAllParameterMiddleware(route, parametersObject) {
507
+ const parameterNames = Object.keys(parametersObject);
508
+ for (const parameterName of parameterNames) {
509
+ const parameterMiddleware = parametersObject[parameterName];
510
+ applyParameterMiddlewareToRoute(route, parameterName, parameterMiddleware);
511
+ }
512
+ }
513
+
514
+ // src/utils/path-helpers.ts
515
+ function hasPathParameters(path, options = {}) {
516
+ if (!path) {
517
+ return false;
518
+ }
519
+ const { keys } = compilePathToRegexp(path, options);
520
+ return keys.length > 0;
521
+ }
522
+ function determineMiddlewarePath(explicitPath, hasPrefixParameters) {
523
+ if (explicitPath !== void 0) {
524
+ if (typeof explicitPath === "string") {
525
+ if (explicitPath === "") {
526
+ return {
527
+ path: "{/*rest}",
528
+ pathAsRegExp: false
529
+ };
530
+ }
531
+ if (explicitPath === "/") {
532
+ return {
533
+ path: "/",
534
+ pathAsRegExp: false
535
+ };
536
+ }
537
+ return {
538
+ path: explicitPath,
539
+ pathAsRegExp: false
540
+ };
541
+ }
542
+ return {
543
+ path: explicitPath,
544
+ pathAsRegExp: true
545
+ };
546
+ }
547
+ if (hasPrefixParameters) {
548
+ return {
549
+ path: "{/*rest}",
550
+ pathAsRegExp: false
551
+ };
552
+ }
553
+ return {
554
+ path: String.raw`(?:\/|$)`,
555
+ pathAsRegExp: true
556
+ };
557
+ }
558
+
559
+ // src/router.ts
560
+ var debug = (0, import_debug.default)("koa-router");
561
+ var httpMethods = getAllHttpMethods();
562
+ var Router = class {
563
+ opts;
564
+ methods;
565
+ exclusive;
566
+ params;
567
+ stack;
568
+ host;
569
+ /**
570
+ * Create a new router.
571
+ *
572
+ * @example
573
+ *
574
+ * Basic usage:
575
+ *
576
+ * ```javascript
577
+ * const Koa = require('koa');
578
+ * const Router = require('@koa/router');
579
+ *
580
+ * const app = new Koa();
581
+ * const router = new Router();
582
+ *
583
+ * router.get('/', (ctx, next) => {
584
+ * // ctx.router available
585
+ * });
586
+ *
587
+ * app
588
+ * .use(router.routes())
589
+ * .use(router.allowedMethods());
590
+ * ```
591
+ *
592
+ * @alias module:koa-router
593
+ * @param opts - Router options
594
+ * @constructor
595
+ */
596
+ constructor(options = {}) {
597
+ this.opts = options;
598
+ this.methods = this.opts.methods || [
599
+ "HEAD",
600
+ "OPTIONS",
601
+ "GET",
602
+ "PUT",
603
+ "PATCH",
604
+ "POST",
605
+ "DELETE"
606
+ ];
607
+ this.exclusive = Boolean(this.opts.exclusive);
608
+ this.params = {};
609
+ this.stack = [];
610
+ this.host = this.opts.host;
611
+ }
612
+ /**
613
+ * Generate URL from url pattern and given `params`.
614
+ *
615
+ * @example
616
+ *
617
+ * ```javascript
618
+ * const url = Router.url('/users/:id', {id: 1});
619
+ * // => "/users/1"
620
+ * ```
621
+ *
622
+ * @param path - URL pattern
623
+ * @param args - URL parameters
624
+ * @returns Generated URL
625
+ */
626
+ static url(path, ...arguments_) {
627
+ const temporaryLayer = new Layer(path, [], () => {
628
+ });
629
+ return temporaryLayer.url(...arguments_);
630
+ }
631
+ use(...middleware) {
632
+ let explicitPath;
633
+ if (this._isPathArray(middleware[0])) {
634
+ return this._useWithPathArray(middleware);
635
+ }
636
+ const hasExplicitPath = this._hasExplicitPath(middleware[0]);
637
+ if (hasExplicitPath) {
638
+ explicitPath = middleware.shift();
639
+ }
640
+ for (const currentMiddleware of middleware) {
641
+ if (this._isNestedRouter(currentMiddleware)) {
642
+ this._mountNestedRouter(
643
+ currentMiddleware,
644
+ explicitPath
645
+ );
646
+ } else {
647
+ this._registerMiddleware(
648
+ currentMiddleware,
649
+ explicitPath,
650
+ hasExplicitPath
651
+ );
652
+ }
653
+ }
654
+ return this;
655
+ }
656
+ /**
657
+ * Check if first argument is an array of paths
658
+ * @private
659
+ */
660
+ _isPathArray(firstArgument) {
661
+ return Array.isArray(firstArgument) && typeof firstArgument[0] === "string";
662
+ }
663
+ /**
664
+ * Check if first argument is an explicit path (string or RegExp)
665
+ * Empty string counts as explicit path to enable param capture
666
+ * @private
667
+ */
668
+ _hasExplicitPath(firstArgument) {
669
+ return typeof firstArgument === "string" || firstArgument instanceof RegExp;
670
+ }
671
+ /**
672
+ * Check if middleware contains a nested router
673
+ * @private
674
+ */
675
+ _isNestedRouter(middleware) {
676
+ return middleware.router !== void 0;
677
+ }
678
+ /**
679
+ * Apply middleware to multiple paths
680
+ * @private
681
+ */
682
+ _useWithPathArray(middleware) {
683
+ const pathArray = middleware[0];
684
+ const remainingMiddleware = middleware.slice(1);
685
+ for (const singlePath of pathArray) {
686
+ Reflect.apply(this.use, this, [singlePath, ...remainingMiddleware]);
687
+ }
688
+ return this;
689
+ }
690
+ /**
691
+ * Mount a nested router
692
+ * @private
693
+ */
694
+ _mountNestedRouter(middlewareWithRouter, mountPath) {
695
+ const nestedRouter = middlewareWithRouter.router;
696
+ const clonedRouter = this._cloneRouter(nestedRouter);
697
+ const mountPathHasParameters = mountPath && typeof mountPath === "string" && hasPathParameters(mountPath, this.opts);
698
+ for (let routeIndex = 0; routeIndex < clonedRouter.stack.length; routeIndex++) {
699
+ const nestedLayer = clonedRouter.stack[routeIndex];
700
+ const clonedLayer = this._cloneLayer(nestedLayer);
701
+ if (mountPath && typeof mountPath === "string") {
702
+ clonedLayer.setPrefix(mountPath);
703
+ }
704
+ if (this.opts.prefix) {
705
+ clonedLayer.setPrefix(this.opts.prefix);
706
+ }
707
+ if (clonedLayer.methods.length === 0 && mountPathHasParameters) {
708
+ clonedLayer.opts.ignoreCaptures = false;
709
+ }
710
+ this.stack.push(clonedLayer);
711
+ clonedRouter.stack[routeIndex] = clonedLayer;
712
+ }
713
+ if (this.params) {
714
+ this._applyParamMiddlewareToRouter(clonedRouter);
715
+ }
716
+ }
717
+ /**
718
+ * Clone a router instance
719
+ * @private
720
+ */
721
+ _cloneRouter(sourceRouter) {
722
+ return Object.assign(
723
+ Object.create(Object.getPrototypeOf(sourceRouter)),
724
+ sourceRouter,
725
+ {
726
+ stack: [...sourceRouter.stack]
727
+ }
728
+ );
729
+ }
730
+ /**
731
+ * Clone a layer instance
732
+ * @private
733
+ */
734
+ _cloneLayer(sourceLayer) {
735
+ return Object.assign(
736
+ Object.create(Object.getPrototypeOf(sourceLayer)),
737
+ sourceLayer
738
+ );
739
+ }
740
+ /**
741
+ * Apply this router's param middleware to a nested router
742
+ * @private
743
+ */
744
+ _applyParamMiddlewareToRouter(targetRouter) {
745
+ const parameterNames = Object.keys(this.params);
746
+ for (const parameterName of parameterNames) {
747
+ const parameterMiddleware = this.params[parameterName];
748
+ applyParameterMiddlewareToRoute(
749
+ targetRouter,
750
+ parameterName,
751
+ parameterMiddleware
752
+ );
753
+ }
754
+ }
755
+ /**
756
+ * Register regular middleware (not nested router)
757
+ * @private
758
+ */
759
+ _registerMiddleware(middleware, explicitPath, hasExplicitPath) {
760
+ const prefixHasParameters = hasPathParameters(
761
+ this.opts.prefix || "",
762
+ this.opts
763
+ );
764
+ const effectiveExplicitPath = (() => {
765
+ if (explicitPath !== void 0) return explicitPath;
766
+ if (prefixHasParameters) return "";
767
+ return;
768
+ })();
769
+ const effectiveHasExplicitPath = hasExplicitPath || explicitPath === void 0 && prefixHasParameters;
770
+ const { path: middlewarePath, pathAsRegExp } = determineMiddlewarePath(
771
+ effectiveExplicitPath,
772
+ prefixHasParameters
773
+ );
774
+ let finalPath = middlewarePath;
775
+ let usePathToRegexp = pathAsRegExp;
776
+ const isRootPath = effectiveHasExplicitPath && middlewarePath === "/";
777
+ if (effectiveHasExplicitPath && typeof middlewarePath === "string") {
778
+ finalPath = middlewarePath;
779
+ usePathToRegexp = false;
780
+ }
781
+ this.register(finalPath, [], middleware, {
782
+ end: isRootPath,
783
+ ignoreCaptures: !effectiveHasExplicitPath && !prefixHasParameters,
784
+ pathAsRegExp: usePathToRegexp
785
+ });
786
+ }
787
+ /**
788
+ * Set the path prefix for a Router instance that was already initialized.
789
+ *
790
+ * @example
791
+ *
792
+ * ```javascript
793
+ * router.prefix('/things/:thing_id')
794
+ * ```
795
+ *
796
+ * @param prefixPath - Prefix string
797
+ * @returns This router instance
798
+ */
799
+ prefix(prefixPath) {
800
+ const normalizedPrefix = prefixPath.replace(/\/$/, "");
801
+ this.opts.prefix = normalizedPrefix;
802
+ for (const route of this.stack) {
803
+ route.setPrefix(normalizedPrefix);
804
+ }
805
+ return this;
806
+ }
807
+ /**
808
+ * Returns router middleware which dispatches a route matching the request.
809
+ *
810
+ * @returns Router middleware
811
+ */
812
+ middleware() {
813
+ const dispatchMiddleware = function(context, next) {
814
+ debug("%s %s", context.method, context.path);
815
+ if (!this.matchHost(context.host)) {
816
+ return next();
817
+ }
818
+ const requestPath = this._getRequestPath(context);
819
+ const matchResult = this.match(requestPath, context.method);
820
+ this._storeMatchedRoutes(context, matchResult);
821
+ context.router = this;
822
+ if (!matchResult.route) {
823
+ return next();
824
+ }
825
+ const matchedLayers = matchResult.pathAndMethod;
826
+ this._setMatchedRouteInfo(context, matchedLayers);
827
+ const middlewareChain = this._buildMiddlewareChain(
828
+ matchedLayers,
829
+ requestPath
830
+ );
831
+ return (0, import_koa_compose.default)(middlewareChain)(context, next);
832
+ }.bind(this);
833
+ dispatchMiddleware.router = this;
834
+ return dispatchMiddleware;
835
+ }
836
+ /**
837
+ * Get the request path to use for routing
838
+ * @private
839
+ */
840
+ _getRequestPath(context) {
841
+ return this.opts.routerPath || context.newRouterPath || context.path || context.routerPath || "";
842
+ }
843
+ /**
844
+ * Store matched routes on context
845
+ * @private
846
+ */
847
+ _storeMatchedRoutes(context, matchResult) {
848
+ if (context.matched) {
849
+ context.matched.push(...matchResult.path);
850
+ } else {
851
+ context.matched = matchResult.path;
852
+ }
853
+ }
854
+ /**
855
+ * Set matched route information on context
856
+ * @private
857
+ */
858
+ _setMatchedRouteInfo(context, matchedLayers) {
859
+ const routeLayer = matchedLayers.toReversed().find((layer) => layer.methods.length > 0);
860
+ if (routeLayer) {
861
+ context._matchedRoute = routeLayer.path;
862
+ if (routeLayer.name) {
863
+ context._matchedRouteName = routeLayer.name;
864
+ }
865
+ }
866
+ }
867
+ /**
868
+ * Build middleware chain from matched layers
869
+ * @private
870
+ */
871
+ _buildMiddlewareChain(matchedLayers, requestPath) {
872
+ const layersToExecute = this.opts.exclusive ? [matchedLayers.at(-1)].filter(
873
+ (layer) => layer !== void 0
874
+ ) : matchedLayers;
875
+ const middlewareChain = [];
876
+ for (const layer of layersToExecute) {
877
+ middlewareChain.push(
878
+ (context, next) => {
879
+ const routerContext = context;
880
+ routerContext.captures = layer.captures(requestPath);
881
+ routerContext.request.params = layer.params(
882
+ requestPath,
883
+ routerContext.captures || [],
884
+ routerContext.params
885
+ );
886
+ routerContext.params = routerContext.request.params;
887
+ routerContext.routerPath = layer.path;
888
+ routerContext.routerName = layer.name || void 0;
889
+ routerContext._matchedRoute = layer.path;
890
+ if (layer.name) {
891
+ routerContext._matchedRouteName = layer.name;
892
+ }
893
+ return next();
894
+ },
895
+ ...layer.stack
896
+ );
897
+ }
898
+ return middlewareChain;
899
+ }
900
+ routes() {
901
+ return this.middleware();
902
+ }
903
+ /**
904
+ * Returns separate middleware for responding to `OPTIONS` requests with
905
+ * an `Allow` header containing the allowed methods, as well as responding
906
+ * with `405 Method Not Allowed` and `501 Not Implemented` as appropriate.
907
+ *
908
+ * @example
909
+ *
910
+ * ```javascript
911
+ * const Koa = require('koa');
912
+ * const Router = require('@koa/router');
913
+ *
914
+ * const app = new Koa();
915
+ * const router = new Router();
916
+ *
917
+ * app.use(router.routes());
918
+ * app.use(router.allowedMethods());
919
+ * ```
920
+ *
921
+ * **Example with [Boom](https://github.com/hapijs/boom)**
922
+ *
923
+ * ```javascript
924
+ * const Koa = require('koa');
925
+ * const Router = require('@koa/router');
926
+ * const Boom = require('boom');
927
+ *
928
+ * const app = new Koa();
929
+ * const router = new Router();
930
+ *
931
+ * app.use(router.routes());
932
+ * app.use(router.allowedMethods({
933
+ * throw: true,
934
+ * notImplemented: () => new Boom.notImplemented(),
935
+ * methodNotAllowed: () => new Boom.methodNotAllowed()
936
+ * }));
937
+ * ```
938
+ *
939
+ * @param options - Options object
940
+ * @returns Middleware function
941
+ */
942
+ allowedMethods(options = {}) {
943
+ const implementedMethods = this.methods;
944
+ return (context, next) => {
945
+ const routerContext = context;
946
+ return next().then(() => {
947
+ if (!this._shouldProcessAllowedMethods(routerContext)) {
948
+ return;
949
+ }
950
+ const allowedMethods = this._collectAllowedMethods(
951
+ routerContext.matched
952
+ );
953
+ const allowedMethodsList = Object.keys(allowedMethods);
954
+ if (!implementedMethods.includes(context.method)) {
955
+ this._handleNotImplemented(
956
+ routerContext,
957
+ allowedMethodsList,
958
+ options
959
+ );
960
+ return;
961
+ }
962
+ if (context.method === "OPTIONS" && allowedMethodsList.length > 0) {
963
+ this._handleOptionsRequest(routerContext, allowedMethodsList);
964
+ return;
965
+ }
966
+ if (allowedMethodsList.length > 0 && !allowedMethods[context.method]) {
967
+ this._handleMethodNotAllowed(
968
+ routerContext,
969
+ allowedMethodsList,
970
+ options
971
+ );
972
+ }
973
+ });
974
+ };
975
+ }
976
+ /**
977
+ * Check if we should process allowed methods
978
+ * @private
979
+ */
980
+ _shouldProcessAllowedMethods(context) {
981
+ return !!(context.matched && (!context.status || context.status === 404));
982
+ }
983
+ /**
984
+ * Collect all allowed methods from matched routes
985
+ * @private
986
+ */
987
+ _collectAllowedMethods(matchedRoutes) {
988
+ const allowedMethods = {};
989
+ for (const route of matchedRoutes) {
990
+ for (const method of route.methods) {
991
+ allowedMethods[method] = method;
992
+ }
993
+ }
994
+ return allowedMethods;
995
+ }
996
+ /**
997
+ * Handle 501 Not Implemented response
998
+ * @private
999
+ */
1000
+ _handleNotImplemented(context, allowedMethodsList, options) {
1001
+ if (options.throw) {
1002
+ const error = typeof options.notImplemented === "function" ? options.notImplemented() : new import_http_errors.default.NotImplemented();
1003
+ throw error;
1004
+ }
1005
+ context.status = 501;
1006
+ context.set("Allow", allowedMethodsList.join(", "));
1007
+ }
1008
+ /**
1009
+ * Handle OPTIONS request
1010
+ * @private
1011
+ */
1012
+ _handleOptionsRequest(context, allowedMethodsList) {
1013
+ context.status = 200;
1014
+ context.body = "";
1015
+ context.set("Allow", allowedMethodsList.join(", "));
1016
+ }
1017
+ /**
1018
+ * Handle 405 Method Not Allowed response
1019
+ * @private
1020
+ */
1021
+ _handleMethodNotAllowed(context, allowedMethodsList, options) {
1022
+ if (options.throw) {
1023
+ const error = typeof options.methodNotAllowed === "function" ? options.methodNotAllowed() : new import_http_errors.default.MethodNotAllowed();
1024
+ throw error;
1025
+ }
1026
+ context.status = 405;
1027
+ context.set("Allow", allowedMethodsList.join(", "));
1028
+ }
1029
+ all(...arguments_) {
1030
+ let name;
1031
+ let path;
1032
+ let middleware;
1033
+ if (arguments_.length >= 2 && (typeof arguments_[1] === "string" || arguments_[1] instanceof RegExp)) {
1034
+ name = arguments_[0];
1035
+ path = arguments_[1];
1036
+ middleware = arguments_.slice(2);
1037
+ } else {
1038
+ name = void 0;
1039
+ path = arguments_[0];
1040
+ middleware = arguments_.slice(1);
1041
+ }
1042
+ if (typeof path !== "string" && !(path instanceof RegExp) && (!Array.isArray(path) || path.length === 0))
1043
+ throw new Error("You have to provide a path when adding an all handler");
1044
+ const routeOptions = {
1045
+ name,
1046
+ pathAsRegExp: path instanceof RegExp
1047
+ };
1048
+ this.register(path, httpMethods, middleware, {
1049
+ ...this.opts,
1050
+ ...routeOptions
1051
+ });
1052
+ return this;
1053
+ }
1054
+ /**
1055
+ * Redirect `source` to `destination` URL with optional 30x status `code`.
1056
+ *
1057
+ * Both `source` and `destination` can be route names.
1058
+ *
1059
+ * ```javascript
1060
+ * router.redirect('/login', 'sign-in');
1061
+ * ```
1062
+ *
1063
+ * This is equivalent to:
1064
+ *
1065
+ * ```javascript
1066
+ * router.all('/login', ctx => {
1067
+ * ctx.redirect('/sign-in');
1068
+ * ctx.status = 301;
1069
+ * });
1070
+ * ```
1071
+ *
1072
+ * @param source - URL or route name
1073
+ * @param destination - URL or route name
1074
+ * @param code - HTTP status code (default: 301)
1075
+ * @returns This router instance
1076
+ */
1077
+ redirect(source, destination, code) {
1078
+ let resolvedSource = source;
1079
+ let resolvedDestination = destination;
1080
+ if (typeof source === "symbol" || source[0] !== "/") {
1081
+ const sourceUrl = this.url(source);
1082
+ if (sourceUrl instanceof Error) throw sourceUrl;
1083
+ resolvedSource = sourceUrl;
1084
+ }
1085
+ if (typeof destination === "symbol" || destination[0] !== "/" && !destination.includes("://")) {
1086
+ const destinationUrl = this.url(destination);
1087
+ if (destinationUrl instanceof Error) throw destinationUrl;
1088
+ resolvedDestination = destinationUrl;
1089
+ }
1090
+ const result = this.all(
1091
+ resolvedSource,
1092
+ (context) => {
1093
+ context.redirect(resolvedDestination);
1094
+ context.status = code || 301;
1095
+ }
1096
+ );
1097
+ return result;
1098
+ }
1099
+ /**
1100
+ * Create and register a route.
1101
+ *
1102
+ * @param path - Path string
1103
+ * @param methods - Array of HTTP verbs
1104
+ * @param middleware - Middleware functions
1105
+ * @param additionalOptions - Additional options
1106
+ * @returns Created layer
1107
+ * @private
1108
+ */
1109
+ register(path, methods, middleware, additionalOptions = {}) {
1110
+ const mergedOptions = { ...this.opts, ...additionalOptions };
1111
+ if (Array.isArray(path)) {
1112
+ return this._registerMultiplePaths(
1113
+ path,
1114
+ methods,
1115
+ middleware,
1116
+ mergedOptions
1117
+ );
1118
+ }
1119
+ const routeLayer = this._createRouteLayer(
1120
+ path,
1121
+ methods,
1122
+ middleware,
1123
+ mergedOptions
1124
+ );
1125
+ if (this.opts.prefix) {
1126
+ routeLayer.setPrefix(this.opts.prefix);
1127
+ }
1128
+ applyAllParameterMiddleware(routeLayer, this.params);
1129
+ this.stack.push(routeLayer);
1130
+ debug("defined route %s %s", routeLayer.methods, routeLayer.path);
1131
+ return routeLayer;
1132
+ }
1133
+ /**
1134
+ * Register multiple paths with the same configuration
1135
+ * @private
1136
+ */
1137
+ _registerMultiplePaths(pathArray, methods, middleware, options) {
1138
+ for (const singlePath of pathArray) {
1139
+ this.register.call(this, singlePath, methods, middleware, options);
1140
+ }
1141
+ return this;
1142
+ }
1143
+ /**
1144
+ * Create a route layer with given configuration
1145
+ * @private
1146
+ */
1147
+ _createRouteLayer(path, methods, middleware, options) {
1148
+ return new Layer(path, methods, middleware, {
1149
+ end: options.end === false ? options.end : true,
1150
+ name: options.name,
1151
+ sensitive: options.sensitive || false,
1152
+ strict: options.strict || false,
1153
+ prefix: options.prefix || "",
1154
+ ignoreCaptures: options.ignoreCaptures,
1155
+ pathAsRegExp: options.pathAsRegExp
1156
+ });
1157
+ }
1158
+ /**
1159
+ * Lookup route with given `name`.
1160
+ *
1161
+ * @param name - Route name
1162
+ * @returns Matched layer or false
1163
+ */
1164
+ route(name) {
1165
+ const matchingRoute = this.stack.find((route) => route.name === name);
1166
+ return matchingRoute || false;
1167
+ }
1168
+ /**
1169
+ * Generate URL for route. Takes a route name and map of named `params`.
1170
+ *
1171
+ * @example
1172
+ *
1173
+ * ```javascript
1174
+ * router.get('user', '/users/:id', (ctx, next) => {
1175
+ * // ...
1176
+ * });
1177
+ *
1178
+ * router.url('user', 3);
1179
+ * // => "/users/3"
1180
+ *
1181
+ * router.url('user', { id: 3 });
1182
+ * // => "/users/3"
1183
+ *
1184
+ * router.use((ctx, next) => {
1185
+ * // redirect to named route
1186
+ * ctx.redirect(ctx.router.url('sign-in'));
1187
+ * })
1188
+ *
1189
+ * router.url('user', { id: 3 }, { query: { limit: 1 } });
1190
+ * // => "/users/3?limit=1"
1191
+ *
1192
+ * router.url('user', { id: 3 }, { query: "limit=1" });
1193
+ * // => "/users/3?limit=1"
1194
+ * ```
1195
+ *
1196
+ * @param name - Route name
1197
+ * @param args - URL parameters
1198
+ * @returns Generated URL or Error
1199
+ */
1200
+ url(name, ...arguments_) {
1201
+ const route = this.route(name);
1202
+ if (route) return route.url.apply(route, arguments_);
1203
+ return new Error(`No route found for name: ${String(name)}`);
1204
+ }
1205
+ /**
1206
+ * Match given `path` and return corresponding routes.
1207
+ *
1208
+ * @param path - Request path
1209
+ * @param method - HTTP method
1210
+ * @returns Match result with matched layers
1211
+ * @private
1212
+ */
1213
+ match(path, method) {
1214
+ const matchResult = {
1215
+ path: [],
1216
+ pathAndMethod: [],
1217
+ route: false
1218
+ };
1219
+ for (const layer of this.stack) {
1220
+ debug("test %s %s", layer.path, layer.regexp);
1221
+ if (layer.match(path)) {
1222
+ matchResult.path.push(layer);
1223
+ const isMiddleware = layer.methods.length === 0;
1224
+ const matchesMethod = layer.methods.includes(method);
1225
+ if (isMiddleware || matchesMethod) {
1226
+ matchResult.pathAndMethod.push(layer);
1227
+ if (layer.methods.length > 0) {
1228
+ matchResult.route = true;
1229
+ }
1230
+ }
1231
+ }
1232
+ }
1233
+ return matchResult;
1234
+ }
1235
+ /**
1236
+ * Match given `input` to allowed host
1237
+ * @param input - Host to check
1238
+ * @returns Whether host matches
1239
+ */
1240
+ matchHost(input) {
1241
+ const { host } = this;
1242
+ if (!host) {
1243
+ return true;
1244
+ }
1245
+ if (!input) {
1246
+ return false;
1247
+ }
1248
+ if (typeof host === "string") {
1249
+ return input === host;
1250
+ }
1251
+ if (Array.isArray(host)) {
1252
+ return host.includes(input);
1253
+ }
1254
+ if (host instanceof RegExp) {
1255
+ return host.test(input);
1256
+ }
1257
+ return false;
1258
+ }
1259
+ /**
1260
+ * Run middleware for named route parameters. Useful for auto-loading or
1261
+ * validation.
1262
+ *
1263
+ * @example
1264
+ *
1265
+ * ```javascript
1266
+ * router
1267
+ * .param('user', (id, ctx, next) => {
1268
+ * ctx.user = users[id];
1269
+ * if (!ctx.user) return ctx.status = 404;
1270
+ * return next();
1271
+ * })
1272
+ * .get('/users/:user', ctx => {
1273
+ * ctx.body = ctx.user;
1274
+ * })
1275
+ * .get('/users/:user/friends', ctx => {
1276
+ * return ctx.user.getFriends().then(function(friends) {
1277
+ * ctx.body = friends;
1278
+ * });
1279
+ * })
1280
+ * // /users/3 => {"id": 3, "name": "Alex"}
1281
+ * // /users/3/friends => [{"id": 4, "name": "TJ"}]
1282
+ * ```
1283
+ *
1284
+ * @param param - Parameter name
1285
+ * @param middleware - Parameter middleware
1286
+ * @returns This router instance
1287
+ */
1288
+ param(parameter, middleware) {
1289
+ if (!this.params[parameter]) {
1290
+ this.params[parameter] = [];
1291
+ }
1292
+ if (!Array.isArray(this.params[parameter])) {
1293
+ this.params[parameter] = [
1294
+ this.params[parameter]
1295
+ ];
1296
+ }
1297
+ this.params[parameter].push(middleware);
1298
+ for (const route of this.stack) {
1299
+ route.param(parameter, middleware);
1300
+ }
1301
+ return this;
1302
+ }
1303
+ /**
1304
+ * Helper method for registering HTTP verb routes
1305
+ * @internal - Used by dynamically added HTTP methods
1306
+ */
1307
+ _registerMethod(method, ...arguments_) {
1308
+ let name;
1309
+ let path;
1310
+ let middleware;
1311
+ if (arguments_.length >= 2 && (typeof arguments_[1] === "string" || arguments_[1] instanceof RegExp)) {
1312
+ name = arguments_[0];
1313
+ path = arguments_[1];
1314
+ middleware = arguments_.slice(2);
1315
+ } else {
1316
+ name = void 0;
1317
+ path = arguments_[0];
1318
+ middleware = arguments_.slice(1);
1319
+ }
1320
+ if (typeof path !== "string" && !(path instanceof RegExp) && (!Array.isArray(path) || path.length === 0))
1321
+ throw new Error(
1322
+ `You have to provide a path when adding a ${method} handler`
1323
+ );
1324
+ const options = {
1325
+ name,
1326
+ pathAsRegExp: path instanceof RegExp
1327
+ };
1328
+ this.register(path, [method], middleware, {
1329
+ ...this.opts,
1330
+ ...options
1331
+ });
1332
+ return this;
1333
+ }
1334
+ get(...arguments_) {
1335
+ return this._registerMethod("get", ...arguments_);
1336
+ }
1337
+ post(...arguments_) {
1338
+ return this._registerMethod("post", ...arguments_);
1339
+ }
1340
+ put(...arguments_) {
1341
+ return this._registerMethod("put", ...arguments_);
1342
+ }
1343
+ patch(...arguments_) {
1344
+ return this._registerMethod("patch", ...arguments_);
1345
+ }
1346
+ delete(...arguments_) {
1347
+ return this._registerMethod("delete", ...arguments_);
1348
+ }
1349
+ del(...arguments_) {
1350
+ return this.delete.apply(this, arguments_);
1351
+ }
1352
+ head(...arguments_) {
1353
+ return this._registerMethod("head", ...arguments_);
1354
+ }
1355
+ options(...arguments_) {
1356
+ return this._registerMethod("options", ...arguments_);
1357
+ }
1358
+ };
1359
+ for (const httpMethod of httpMethods) {
1360
+ const isAlreadyDefined = COMMON_HTTP_METHODS.includes(httpMethod) || Router.prototype[httpMethod];
1361
+ if (!isAlreadyDefined) {
1362
+ Object.defineProperty(Router.prototype, httpMethod, {
1363
+ value: function(...arguments_) {
1364
+ return this._registerMethod(httpMethod, ...arguments_);
1365
+ },
1366
+ writable: true,
1367
+ configurable: true,
1368
+ enumerable: false
1369
+ });
1370
+ }
1371
+ }