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