@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.
Files changed (4) hide show
  1. package/README.md +2 -1
  2. package/lib/layer.js +192 -197
  3. package/lib/router.js +658 -678
  4. 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//www.npmjs.com
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
- * 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
- */
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
- 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];
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
- for (const method of methods) {
28
- const l = this.methods.push(method.toUpperCase());
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
- // 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
- );
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
- 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
- */
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
- * Returns array of regexp url path captures.
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
- Layer.prototype.url = function (params, options) {
111
- let args = params;
112
- const url = this.path.replace(/\(\.\*\)/g, '');
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
- 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);
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
- const toPath = compile(url, { encode: encodeURIComponent, ...options });
123
- let replaced;
117
+ const toPath = compile(url, { encode: encodeURIComponent, ...options });
118
+ let replaced;
124
119
 
125
- const tokens = parse(url);
126
- let replace = {};
120
+ const tokens = parse(url);
121
+ let replace = {};
127
122
 
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++];
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
- replaced = toPath(replace);
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
- 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;
144
+ return formatUrl(replaced);
147
145
  }
148
146
 
149
- return formatUrl(replaced);
147
+ return replaced;
150
148
  }
151
149
 
152
- return replaced;
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
- * 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
- */
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
- 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
201
- }
202
- });
199
+ return this;
203
200
  }
204
201
 
205
- return this;
206
- };
207
-
208
- /**
209
- * Prefix route path.
210
- *
211
- * @param {String} prefix
212
- * @returns {Layer}
213
- * @private
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
- 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);
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
- return decodeURIComponent(text);
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
  }