@koa/router 12.0.1 → 13.0.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/README.md +2 -1
- package/lib/layer.js +215 -197
- package/lib/router.js +669 -677
- package/package.json +20 -18
package/README.md
CHANGED
|
@@ -69,6 +69,7 @@ See [API Reference](./API.md) for more documentation.
|
|
|
69
69
|
| ---------------- |
|
|
70
70
|
| **Alex Mingoia** |
|
|
71
71
|
| **@koajs** |
|
|
72
|
+
| **Imed Jaberi** |
|
|
72
73
|
|
|
73
74
|
|
|
74
75
|
## License
|
|
@@ -82,4 +83,4 @@ See [API Reference](./API.md) for more documentation.
|
|
|
82
83
|
|
|
83
84
|
[lad]: https://lad.js.org
|
|
84
85
|
|
|
85
|
-
[npm]: https
|
|
86
|
+
[npm]: https://www.npmjs.com
|
package/lib/layer.js
CHANGED
|
@@ -1,229 +1,246 @@
|
|
|
1
|
-
const { parse: parseUrl, format: formatUrl } = require('url');
|
|
2
|
-
|
|
1
|
+
const { parse: parseUrl, format: formatUrl } = require('node:url');
|
|
2
|
+
|
|
3
|
+
const { pathToRegexp, compile, parse, stringify } = require('path-to-regexp');
|
|
4
|
+
|
|
5
|
+
module.exports = class Layer {
|
|
6
|
+
/**
|
|
7
|
+
* Initialize a new routing Layer with given `method`, `path`, and `middleware`.
|
|
8
|
+
*
|
|
9
|
+
* @param {String|RegExp} path Path string or regular expression.
|
|
10
|
+
* @param {Array} methods Array of HTTP verbs.
|
|
11
|
+
* @param {Array} middleware Layer callback/middleware or series of.
|
|
12
|
+
* @param {Object=} opts
|
|
13
|
+
* @param {String=} opts.name route name
|
|
14
|
+
* @param {String=} opts.sensitive case sensitive (default: false)
|
|
15
|
+
* @param {String=} opts.strict require the trailing slash (default: false)
|
|
16
|
+
* @param {Boolean=} opts.pathIsRegexp if true, treat `path` as a regular expression
|
|
17
|
+
* @returns {Layer}
|
|
18
|
+
* @private
|
|
19
|
+
*/
|
|
20
|
+
constructor(path, methods, middleware, opts = {}) {
|
|
21
|
+
this.opts = opts;
|
|
22
|
+
this.name = this.opts.name || null;
|
|
23
|
+
this.methods = [];
|
|
24
|
+
this.paramNames = [];
|
|
25
|
+
this.stack = Array.isArray(middleware) ? middleware : [middleware];
|
|
3
26
|
|
|
4
|
-
|
|
27
|
+
for (const method of methods) {
|
|
28
|
+
const l = this.methods.push(method.toUpperCase());
|
|
29
|
+
if (this.methods[l - 1] === 'GET') this.methods.unshift('HEAD');
|
|
30
|
+
}
|
|
5
31
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
* @private
|
|
18
|
-
*/
|
|
32
|
+
// ensure middleware is a function
|
|
33
|
+
for (let i = 0; i < this.stack.length; i++) {
|
|
34
|
+
const fn = this.stack[i];
|
|
35
|
+
const type = typeof fn;
|
|
36
|
+
if (type !== 'function')
|
|
37
|
+
throw new Error(
|
|
38
|
+
`${methods.toString()} \`${
|
|
39
|
+
this.opts.name || path
|
|
40
|
+
}\`: \`middleware\` must be a function, not \`${type}\``
|
|
41
|
+
);
|
|
42
|
+
}
|
|
19
43
|
|
|
20
|
-
|
|
21
|
-
this.opts = opts;
|
|
22
|
-
this.name = this.opts.name || null;
|
|
23
|
-
this.methods = [];
|
|
24
|
-
this.paramNames = [];
|
|
25
|
-
this.stack = Array.isArray(middleware) ? middleware : [middleware];
|
|
44
|
+
this.path = path;
|
|
26
45
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if (this.
|
|
30
|
-
|
|
46
|
+
if (this.opts.pathIsRegexp === true) {
|
|
47
|
+
this.regexp = new RegExp(path);
|
|
48
|
+
} else if (this.path) {
|
|
49
|
+
if (this.opts.strict === true) {
|
|
50
|
+
// path-to-regexp renamed strict to trailing in v8.1.0
|
|
51
|
+
this.opts.trailing = false;
|
|
52
|
+
}
|
|
31
53
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (type !== 'function')
|
|
37
|
-
throw new Error(
|
|
38
|
-
`${methods.toString()} \`${
|
|
39
|
-
this.opts.name || path
|
|
40
|
-
}\`: \`middleware\` must be a function, not \`${type}\``
|
|
41
|
-
);
|
|
54
|
+
const { regexp: regex, keys } = pathToRegexp(this.path, this.opts);
|
|
55
|
+
this.regexp = regex;
|
|
56
|
+
this.paramNames = keys;
|
|
57
|
+
}
|
|
42
58
|
}
|
|
43
59
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
*/
|
|
55
|
-
|
|
56
|
-
Layer.prototype.match = function (path) {
|
|
57
|
-
return this.regexp.test(path);
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Returns map of URL parameters for given `path` and `paramNames`.
|
|
62
|
-
*
|
|
63
|
-
* @param {String} path
|
|
64
|
-
* @param {Array.<String>} captures
|
|
65
|
-
* @param {Object=} params
|
|
66
|
-
* @returns {Object}
|
|
67
|
-
* @private
|
|
68
|
-
*/
|
|
60
|
+
/**
|
|
61
|
+
* Returns whether request `path` matches route.
|
|
62
|
+
*
|
|
63
|
+
* @param {String} path
|
|
64
|
+
* @returns {Boolean}
|
|
65
|
+
* @private
|
|
66
|
+
*/
|
|
67
|
+
match(path) {
|
|
68
|
+
return this.regexp.test(path);
|
|
69
|
+
}
|
|
69
70
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
71
|
+
/**
|
|
72
|
+
* Returns map of URL parameters for given `path` and `paramNames`.
|
|
73
|
+
*
|
|
74
|
+
* @param {String} path
|
|
75
|
+
* @param {Array.<String>} captures
|
|
76
|
+
* @param {Object=} params
|
|
77
|
+
* @returns {Object}
|
|
78
|
+
* @private
|
|
79
|
+
*/
|
|
80
|
+
params(path, captures, params = {}) {
|
|
81
|
+
for (let len = captures.length, i = 0; i < len; i++) {
|
|
82
|
+
if (this.paramNames[i]) {
|
|
83
|
+
const c = captures[i];
|
|
84
|
+
if (c && c.length > 0)
|
|
85
|
+
params[this.paramNames[i].name] = c ? safeDecodeURIComponent(c) : c;
|
|
86
|
+
}
|
|
76
87
|
}
|
|
77
|
-
}
|
|
78
88
|
|
|
79
|
-
|
|
80
|
-
}
|
|
89
|
+
return params;
|
|
90
|
+
}
|
|
81
91
|
|
|
82
|
-
/**
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
92
|
+
/**
|
|
93
|
+
* Returns array of regexp url path captures.
|
|
94
|
+
*
|
|
95
|
+
* @param {String} path
|
|
96
|
+
* @returns {Array.<String>}
|
|
97
|
+
* @private
|
|
98
|
+
*/
|
|
99
|
+
captures(path) {
|
|
100
|
+
return this.opts.ignoreCaptures ? [] : path.match(this.regexp).slice(1);
|
|
101
|
+
}
|
|
89
102
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Generate URL for route using given `params`.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
*
|
|
108
|
+
* ```javascript
|
|
109
|
+
* const route = new Layer('/users/:id', ['GET'], fn);
|
|
110
|
+
*
|
|
111
|
+
* route.url({ id: 123 }); // => "/users/123"
|
|
112
|
+
* ```
|
|
113
|
+
*
|
|
114
|
+
* @param {Object} params url parameters
|
|
115
|
+
* @returns {String}
|
|
116
|
+
* @private
|
|
117
|
+
*/
|
|
118
|
+
url(params, options) {
|
|
119
|
+
let args = params;
|
|
120
|
+
const url = this.path.replace(/\(\.\*\)/g, '');
|
|
121
|
+
|
|
122
|
+
if (typeof params !== 'object') {
|
|
123
|
+
args = Array.prototype.slice.call(arguments);
|
|
124
|
+
if (typeof args[args.length - 1] === 'object') {
|
|
125
|
+
options = args[args.length - 1];
|
|
126
|
+
args = args.slice(0, -1);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
93
129
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
*
|
|
99
|
-
* ```javascript
|
|
100
|
-
* const route = new Layer('/users/:id', ['GET'], fn);
|
|
101
|
-
*
|
|
102
|
-
* route.url({ id: 123 }); // => "/users/123"
|
|
103
|
-
* ```
|
|
104
|
-
*
|
|
105
|
-
* @param {Object} params url parameters
|
|
106
|
-
* @returns {String}
|
|
107
|
-
* @private
|
|
108
|
-
*/
|
|
130
|
+
const toPath = compile(url, { encode: encodeURIComponent, ...options });
|
|
131
|
+
let replaced;
|
|
132
|
+
const { tokens } = parse(url);
|
|
133
|
+
let replace = {};
|
|
109
134
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
135
|
+
if (Array.isArray(args)) {
|
|
136
|
+
for (let len = tokens.length, i = 0, j = 0; i < len; i++) {
|
|
137
|
+
if (tokens[i].name) {
|
|
138
|
+
replace[tokens[i].name] = args[j++];
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
} else if (tokens.some((token) => token.name)) {
|
|
142
|
+
replace = params;
|
|
143
|
+
} else if (!options) {
|
|
144
|
+
options = params;
|
|
145
|
+
}
|
|
113
146
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
if (typeof args[args.length - 1] === 'object') {
|
|
117
|
-
options = args[args.length - 1];
|
|
118
|
-
args = args.slice(0, -1);
|
|
147
|
+
for (const [key, value] of Object.entries(replace)) {
|
|
148
|
+
replace[key] = String(value);
|
|
119
149
|
}
|
|
120
|
-
}
|
|
121
150
|
|
|
122
|
-
|
|
123
|
-
let replaced;
|
|
151
|
+
replaced = toPath(replace);
|
|
124
152
|
|
|
125
|
-
|
|
126
|
-
|
|
153
|
+
if (options && options.query) {
|
|
154
|
+
replaced = parseUrl(replaced);
|
|
155
|
+
if (typeof options.query === 'string') {
|
|
156
|
+
replaced.search = options.query;
|
|
157
|
+
} else {
|
|
158
|
+
replaced.search = undefined;
|
|
159
|
+
replaced.query = options.query;
|
|
160
|
+
}
|
|
127
161
|
|
|
128
|
-
|
|
129
|
-
for (let len = tokens.length, i = 0, j = 0; i < len; i++) {
|
|
130
|
-
if (tokens[i].name) replace[tokens[i].name] = args[j++];
|
|
162
|
+
return formatUrl(replaced);
|
|
131
163
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
} else if (!options) {
|
|
135
|
-
options = params;
|
|
164
|
+
|
|
165
|
+
return replaced;
|
|
136
166
|
}
|
|
137
167
|
|
|
138
|
-
|
|
168
|
+
/**
|
|
169
|
+
* Run validations on route named parameters.
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
*
|
|
173
|
+
* ```javascript
|
|
174
|
+
* router
|
|
175
|
+
* .param('user', function (id, ctx, next) {
|
|
176
|
+
* ctx.user = users[id];
|
|
177
|
+
* if (!ctx.user) return ctx.status = 404;
|
|
178
|
+
* next();
|
|
179
|
+
* })
|
|
180
|
+
* .get('/users/:user', function (ctx, next) {
|
|
181
|
+
* ctx.body = ctx.user;
|
|
182
|
+
* });
|
|
183
|
+
* ```
|
|
184
|
+
*
|
|
185
|
+
* @param {String} param
|
|
186
|
+
* @param {Function} middleware
|
|
187
|
+
* @returns {Layer}
|
|
188
|
+
* @private
|
|
189
|
+
*/
|
|
190
|
+
param(param, fn) {
|
|
191
|
+
const { stack } = this;
|
|
192
|
+
const params = this.paramNames;
|
|
193
|
+
const middleware = function (ctx, next) {
|
|
194
|
+
return fn.call(this, ctx.params[param], ctx, next);
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
middleware.param = param;
|
|
198
|
+
|
|
199
|
+
const names = params.map(function (p) {
|
|
200
|
+
return p.name;
|
|
201
|
+
});
|
|
139
202
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
203
|
+
const x = names.indexOf(param);
|
|
204
|
+
if (x > -1) {
|
|
205
|
+
// iterate through the stack, to figure out where to place the handler fn
|
|
206
|
+
stack.some((fn, i) => {
|
|
207
|
+
// param handlers are always first, so when we find an fn w/o a param property, stop here
|
|
208
|
+
// if the param handler at this part of the stack comes after the one we are adding, stop here
|
|
209
|
+
if (!fn.param || names.indexOf(fn.param) > x) {
|
|
210
|
+
// inject this param handler right before the current item
|
|
211
|
+
stack.splice(i, 0, middleware);
|
|
212
|
+
return true; // then break the loop
|
|
213
|
+
}
|
|
214
|
+
});
|
|
147
215
|
}
|
|
148
216
|
|
|
149
|
-
return
|
|
217
|
+
return this;
|
|
150
218
|
}
|
|
151
219
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
*
|
|
172
|
-
* @param {String} param
|
|
173
|
-
* @param {Function} middleware
|
|
174
|
-
* @returns {Layer}
|
|
175
|
-
* @private
|
|
176
|
-
*/
|
|
177
|
-
|
|
178
|
-
Layer.prototype.param = function (param, fn) {
|
|
179
|
-
const { stack } = this;
|
|
180
|
-
const params = this.paramNames;
|
|
181
|
-
const middleware = function (ctx, next) {
|
|
182
|
-
return fn.call(this, ctx.params[param], ctx, next);
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
middleware.param = param;
|
|
186
|
-
|
|
187
|
-
const names = params.map(function (p) {
|
|
188
|
-
return p.name;
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
const x = names.indexOf(param);
|
|
192
|
-
if (x > -1) {
|
|
193
|
-
// iterate through the stack, to figure out where to place the handler fn
|
|
194
|
-
stack.some(function (fn, i) {
|
|
195
|
-
// param handlers are always first, so when we find an fn w/o a param property, stop here
|
|
196
|
-
// if the param handler at this part of the stack comes after the one we are adding, stop here
|
|
197
|
-
if (!fn.param || names.indexOf(fn.param) > x) {
|
|
198
|
-
// inject this param handler right before the current item
|
|
199
|
-
stack.splice(i, 0, middleware);
|
|
200
|
-
return true; // then break the loop
|
|
220
|
+
/**
|
|
221
|
+
* Prefix route path.
|
|
222
|
+
*
|
|
223
|
+
* @param {String} prefix
|
|
224
|
+
* @returns {Layer}
|
|
225
|
+
* @private
|
|
226
|
+
*/
|
|
227
|
+
setPrefix(prefix) {
|
|
228
|
+
if (this.path) {
|
|
229
|
+
this.path =
|
|
230
|
+
this.path !== '/' || this.opts.strict === true
|
|
231
|
+
? `${prefix}${this.path}`
|
|
232
|
+
: prefix;
|
|
233
|
+
if (this.opts.pathIsRegexp === true || prefix instanceof RegExp) {
|
|
234
|
+
this.regexp = new RegExp(this.path);
|
|
235
|
+
} else if (this.path) {
|
|
236
|
+
const { regexp: regex, keys } = pathToRegexp(this.path, this.opts);
|
|
237
|
+
this.regexp = regex;
|
|
238
|
+
this.paramNames = keys;
|
|
201
239
|
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
return this;
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Prefix route path.
|
|
210
|
-
*
|
|
211
|
-
* @param {String} prefix
|
|
212
|
-
* @returns {Layer}
|
|
213
|
-
* @private
|
|
214
|
-
*/
|
|
240
|
+
}
|
|
215
241
|
|
|
216
|
-
|
|
217
|
-
if (this.path) {
|
|
218
|
-
this.path =
|
|
219
|
-
this.path !== '/' || this.opts.strict === true
|
|
220
|
-
? `${prefix}${this.path}`
|
|
221
|
-
: prefix;
|
|
222
|
-
this.paramNames = [];
|
|
223
|
-
this.regexp = pathToRegexp(this.path, this.paramNames, this.opts);
|
|
242
|
+
return this;
|
|
224
243
|
}
|
|
225
|
-
|
|
226
|
-
return this;
|
|
227
244
|
};
|
|
228
245
|
|
|
229
246
|
/**
|
|
@@ -237,7 +254,8 @@ Layer.prototype.setPrefix = function (prefix) {
|
|
|
237
254
|
|
|
238
255
|
function safeDecodeURIComponent(text) {
|
|
239
256
|
try {
|
|
240
|
-
|
|
257
|
+
// @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#decoding_query_parameters_from_a_url
|
|
258
|
+
return decodeURIComponent(text.replace(/\+/g, ' '));
|
|
241
259
|
} catch {
|
|
242
260
|
return text;
|
|
243
261
|
}
|