@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.
Files changed (4) hide show
  1. package/README.md +2 -1
  2. package/lib/layer.js +215 -197
  3. package/lib/router.js +669 -677
  4. 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//www.npmjs.com
86
+ [npm]: https://www.npmjs.com
package/lib/layer.js CHANGED
@@ -1,229 +1,246 @@
1
- const { parse: parseUrl, format: formatUrl } = require('url');
2
- const { pathToRegexp, compile, parse } = require('path-to-regexp');
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
- module.exports = Layer;
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
- * 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
- */
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
- function Layer(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];
44
+ this.path = path;
26
45
 
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
- }
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
- // 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
- );
54
+ const { regexp: regex, keys } = pathToRegexp(this.path, this.opts);
55
+ this.regexp = regex;
56
+ this.paramNames = keys;
57
+ }
42
58
  }
43
59
 
44
- this.path = path;
45
- this.regexp = pathToRegexp(path, this.paramNames, this.opts);
46
- }
47
-
48
- /**
49
- * Returns whether request `path` matches route.
50
- *
51
- * @param {String} path
52
- * @returns {Boolean}
53
- * @private
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
- 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;
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
- return params;
80
- };
89
+ return params;
90
+ }
81
91
 
82
- /**
83
- * Returns array of regexp url path captures.
84
- *
85
- * @param {String} path
86
- * @returns {Array.<String>}
87
- * @private
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
- Layer.prototype.captures = function (path) {
91
- return this.opts.ignoreCaptures ? [] : path.match(this.regexp).slice(1);
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
- * 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
- */
130
+ const toPath = compile(url, { encode: encodeURIComponent, ...options });
131
+ let replaced;
132
+ const { tokens } = parse(url);
133
+ let replace = {};
109
134
 
110
- Layer.prototype.url = function (params, options) {
111
- let args = params;
112
- const url = this.path.replace(/\(\.\*\)/g, '');
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
- if (typeof params !== 'object') {
115
- args = Array.prototype.slice.call(arguments);
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
- const toPath = compile(url, { encode: encodeURIComponent, ...options });
123
- let replaced;
151
+ replaced = toPath(replace);
124
152
 
125
- const tokens = parse(url);
126
- let replace = {};
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
- if (Array.isArray(args)) {
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
- } else if (tokens.some((token) => token.name)) {
133
- replace = params;
134
- } else if (!options) {
135
- options = params;
164
+
165
+ return replaced;
136
166
  }
137
167
 
138
- replaced = toPath(replace);
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
- if (options && options.query) {
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;
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 formatUrl(replaced);
217
+ return this;
150
218
  }
151
219
 
152
- return replaced;
153
- };
154
-
155
- /**
156
- * Run validations on route named parameters.
157
- *
158
- * @example
159
- *
160
- * ```javascript
161
- * router
162
- * .param('user', function (id, ctx, next) {
163
- * ctx.user = users[id];
164
- * if (!ctx.user) return ctx.status = 404;
165
- * next();
166
- * })
167
- * .get('/users/:user', function (ctx, next) {
168
- * ctx.body = ctx.user;
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
- Layer.prototype.setPrefix = function (prefix) {
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
- return decodeURIComponent(text);
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
  }