@koa/router 14.0.0 → 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/LICENSE +1 -1
- package/README.md +1136 -43
- package/dist/index.d.mts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1378 -0
- package/dist/index.mjs +1341 -0
- package/dist/layer.d.mts +761 -0
- package/dist/layer.d.ts +761 -0
- package/dist/layer.js +457 -0
- package/dist/layer.mjs +436 -0
- package/dist/router.d.mts +3 -0
- package/dist/router.d.ts +3 -0
- package/dist/router.js +1371 -0
- package/dist/router.mjs +1340 -0
- package/dist/types.d.mts +3 -0
- package/dist/types.d.ts +3 -0
- package/dist/types.js +18 -0
- package/dist/types.mjs +0 -0
- package/package.json +55 -28
- package/HISTORY.md +0 -187
- package/lib/API_tpl.hbs +0 -7
- package/lib/layer.js +0 -262
- package/lib/router.js +0 -835
package/lib/router.js
DELETED
|
@@ -1,835 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RESTful resource routing middleware for koa.
|
|
3
|
-
*
|
|
4
|
-
* @author Alex Mingoia <talk@alexmingoia.com>
|
|
5
|
-
* @link https://github.com/alexmingoia/koa-router
|
|
6
|
-
*/
|
|
7
|
-
const http = require('node:http');
|
|
8
|
-
|
|
9
|
-
const debug = require('debug')('koa-router');
|
|
10
|
-
|
|
11
|
-
const compose = require('koa-compose');
|
|
12
|
-
const HttpError = require('http-errors');
|
|
13
|
-
const { pathToRegexp } = require('path-to-regexp');
|
|
14
|
-
|
|
15
|
-
const Layer = require('./layer');
|
|
16
|
-
|
|
17
|
-
const methods = http.METHODS.map((method) => method.toLowerCase());
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* @module koa-router
|
|
21
|
-
*/
|
|
22
|
-
class Router {
|
|
23
|
-
/**
|
|
24
|
-
* Create a new router.
|
|
25
|
-
*
|
|
26
|
-
* @example
|
|
27
|
-
*
|
|
28
|
-
* Basic usage:
|
|
29
|
-
*
|
|
30
|
-
* ```javascript
|
|
31
|
-
* const Koa = require('koa');
|
|
32
|
-
* const Router = require('@koa/router');
|
|
33
|
-
*
|
|
34
|
-
* const app = new Koa();
|
|
35
|
-
* const router = new Router();
|
|
36
|
-
*
|
|
37
|
-
* router.get('/', (ctx, next) => {
|
|
38
|
-
* // ctx.router available
|
|
39
|
-
* });
|
|
40
|
-
*
|
|
41
|
-
* app
|
|
42
|
-
* .use(router.routes())
|
|
43
|
-
* .use(router.allowedMethods());
|
|
44
|
-
* ```
|
|
45
|
-
*
|
|
46
|
-
* @alias module:koa-router
|
|
47
|
-
* @param {Object=} opts
|
|
48
|
-
* @param {Boolean=false} opts.exclusive only run last matched route's controller when there are multiple matches
|
|
49
|
-
* @param {String=} opts.prefix prefix router paths
|
|
50
|
-
* @param {String|RegExp=} opts.host host for router match
|
|
51
|
-
* @constructor
|
|
52
|
-
*/
|
|
53
|
-
constructor(opts = {}) {
|
|
54
|
-
if (!(this instanceof Router)) return new Router(opts);
|
|
55
|
-
|
|
56
|
-
this.opts = opts;
|
|
57
|
-
this.methods = this.opts.methods || [
|
|
58
|
-
'HEAD',
|
|
59
|
-
'OPTIONS',
|
|
60
|
-
'GET',
|
|
61
|
-
'PUT',
|
|
62
|
-
'PATCH',
|
|
63
|
-
'POST',
|
|
64
|
-
'DELETE'
|
|
65
|
-
];
|
|
66
|
-
this.exclusive = Boolean(this.opts.exclusive);
|
|
67
|
-
|
|
68
|
-
this.params = {};
|
|
69
|
-
this.stack = [];
|
|
70
|
-
this.host = this.opts.host;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Generate URL from url pattern and given `params`.
|
|
75
|
-
*
|
|
76
|
-
* @example
|
|
77
|
-
*
|
|
78
|
-
* ```javascript
|
|
79
|
-
* const url = Router.url('/users/:id', {id: 1});
|
|
80
|
-
* // => "/users/1"
|
|
81
|
-
* ```
|
|
82
|
-
*
|
|
83
|
-
* @param {String} path url pattern
|
|
84
|
-
* @param {Object} params url parameters
|
|
85
|
-
* @returns {String}
|
|
86
|
-
*/
|
|
87
|
-
static url(path, ...args) {
|
|
88
|
-
return Layer.prototype.url.apply({ path }, args);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Use given middleware.
|
|
93
|
-
*
|
|
94
|
-
* Middleware run in the order they are defined by `.use()`. They are invoked
|
|
95
|
-
* sequentially, requests start at the first middleware and work their way
|
|
96
|
-
* "down" the middleware stack.
|
|
97
|
-
*
|
|
98
|
-
* @example
|
|
99
|
-
*
|
|
100
|
-
* ```javascript
|
|
101
|
-
* // session middleware will run before authorize
|
|
102
|
-
* router
|
|
103
|
-
* .use(session())
|
|
104
|
-
* .use(authorize());
|
|
105
|
-
*
|
|
106
|
-
* // use middleware only with given path
|
|
107
|
-
* router.use('/users', userAuth());
|
|
108
|
-
*
|
|
109
|
-
* // or with an array of paths
|
|
110
|
-
* router.use(['/users', '/admin'], userAuth());
|
|
111
|
-
*
|
|
112
|
-
* app.use(router.routes());
|
|
113
|
-
* ```
|
|
114
|
-
*
|
|
115
|
-
* @param {String=} path
|
|
116
|
-
* @param {Function} middleware
|
|
117
|
-
* @param {Function=} ...
|
|
118
|
-
* @returns {Router}
|
|
119
|
-
*/
|
|
120
|
-
use(...middleware) {
|
|
121
|
-
const router = this;
|
|
122
|
-
let path;
|
|
123
|
-
|
|
124
|
-
// support array of paths
|
|
125
|
-
if (Array.isArray(middleware[0]) && typeof middleware[0][0] === 'string') {
|
|
126
|
-
const arrPaths = middleware[0];
|
|
127
|
-
for (const p of arrPaths) {
|
|
128
|
-
router.use.apply(router, [p, ...middleware.slice(1)]);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return this;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const hasPath = typeof middleware[0] === 'string';
|
|
135
|
-
if (hasPath) path = middleware.shift();
|
|
136
|
-
|
|
137
|
-
for (const m of middleware) {
|
|
138
|
-
if (m.router) {
|
|
139
|
-
const cloneRouter = Object.assign(
|
|
140
|
-
Object.create(Router.prototype),
|
|
141
|
-
m.router,
|
|
142
|
-
{
|
|
143
|
-
stack: [...m.router.stack]
|
|
144
|
-
}
|
|
145
|
-
);
|
|
146
|
-
|
|
147
|
-
for (let j = 0; j < cloneRouter.stack.length; j++) {
|
|
148
|
-
const nestedLayer = cloneRouter.stack[j];
|
|
149
|
-
const cloneLayer = Object.assign(
|
|
150
|
-
Object.create(Layer.prototype),
|
|
151
|
-
nestedLayer
|
|
152
|
-
);
|
|
153
|
-
|
|
154
|
-
if (path) cloneLayer.setPrefix(path);
|
|
155
|
-
if (router.opts.prefix) cloneLayer.setPrefix(router.opts.prefix);
|
|
156
|
-
router.stack.push(cloneLayer);
|
|
157
|
-
cloneRouter.stack[j] = cloneLayer;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (router.params) {
|
|
161
|
-
const routerParams = Object.keys(router.params);
|
|
162
|
-
for (const key of routerParams) {
|
|
163
|
-
cloneRouter.param(key, router.params[key]);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
} else {
|
|
167
|
-
const { keys } = pathToRegexp(router.opts.prefix || '', router.opts);
|
|
168
|
-
const routerPrefixHasParam = Boolean(
|
|
169
|
-
router.opts.prefix && keys.length > 0
|
|
170
|
-
);
|
|
171
|
-
router.register(path || '([^/]*)', [], m, {
|
|
172
|
-
end: false,
|
|
173
|
-
ignoreCaptures: !hasPath && !routerPrefixHasParam,
|
|
174
|
-
pathAsRegExp: true
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
return this;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Set the path prefix for a Router instance that was already initialized.
|
|
184
|
-
*
|
|
185
|
-
* @example
|
|
186
|
-
*
|
|
187
|
-
* ```javascript
|
|
188
|
-
* router.prefix('/things/:thing_id')
|
|
189
|
-
* ```
|
|
190
|
-
*
|
|
191
|
-
* @param {String} prefix
|
|
192
|
-
* @returns {Router}
|
|
193
|
-
*/
|
|
194
|
-
prefix(prefix) {
|
|
195
|
-
prefix = prefix.replace(/\/$/, '');
|
|
196
|
-
|
|
197
|
-
this.opts.prefix = prefix;
|
|
198
|
-
|
|
199
|
-
for (let i = 0; i < this.stack.length; i++) {
|
|
200
|
-
const route = this.stack[i];
|
|
201
|
-
route.setPrefix(prefix);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
return this;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Returns router middleware which dispatches a route matching the request.
|
|
209
|
-
*
|
|
210
|
-
* @returns {Function}
|
|
211
|
-
*/
|
|
212
|
-
middleware() {
|
|
213
|
-
const router = this;
|
|
214
|
-
const dispatch = (ctx, next) => {
|
|
215
|
-
debug('%s %s', ctx.method, ctx.path);
|
|
216
|
-
|
|
217
|
-
const hostMatched = router.matchHost(ctx.host);
|
|
218
|
-
|
|
219
|
-
if (!hostMatched) {
|
|
220
|
-
return next();
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const path =
|
|
224
|
-
router.opts.routerPath ||
|
|
225
|
-
ctx.newRouterPath ||
|
|
226
|
-
ctx.path ||
|
|
227
|
-
ctx.routerPath;
|
|
228
|
-
const matched = router.match(path, ctx.method);
|
|
229
|
-
if (ctx.matched) {
|
|
230
|
-
ctx.matched.push.apply(ctx.matched, matched.path);
|
|
231
|
-
} else {
|
|
232
|
-
ctx.matched = matched.path;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
ctx.router = router;
|
|
236
|
-
|
|
237
|
-
if (!matched.route) return next();
|
|
238
|
-
|
|
239
|
-
const matchedLayers = matched.pathAndMethod;
|
|
240
|
-
const mostSpecificLayer = matchedLayers[matchedLayers.length - 1];
|
|
241
|
-
ctx._matchedRoute = mostSpecificLayer.path;
|
|
242
|
-
if (mostSpecificLayer.name) {
|
|
243
|
-
ctx._matchedRouteName = mostSpecificLayer.name;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
const layerChain = (
|
|
247
|
-
router.exclusive ? [mostSpecificLayer] : matchedLayers
|
|
248
|
-
).reduce((memo, layer) => {
|
|
249
|
-
memo.push((ctx, next) => {
|
|
250
|
-
ctx.captures = layer.captures(path, ctx.captures);
|
|
251
|
-
ctx.request.params = layer.params(path, ctx.captures, ctx.params);
|
|
252
|
-
ctx.params = ctx.request.params;
|
|
253
|
-
ctx.routerPath = layer.path;
|
|
254
|
-
ctx.routerName = layer.name;
|
|
255
|
-
ctx._matchedRoute = layer.path;
|
|
256
|
-
if (layer.name) {
|
|
257
|
-
ctx._matchedRouteName = layer.name;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
return next();
|
|
261
|
-
});
|
|
262
|
-
return [...memo, ...layer.stack];
|
|
263
|
-
}, []);
|
|
264
|
-
|
|
265
|
-
return compose(layerChain)(ctx, next);
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
dispatch.router = this;
|
|
269
|
-
|
|
270
|
-
return dispatch;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
routes() {
|
|
274
|
-
return this.middleware();
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* Returns separate middleware for responding to `OPTIONS` requests with
|
|
279
|
-
* an `Allow` header containing the allowed methods, as well as responding
|
|
280
|
-
* with `405 Method Not Allowed` and `501 Not Implemented` as appropriate.
|
|
281
|
-
*
|
|
282
|
-
* @example
|
|
283
|
-
*
|
|
284
|
-
* ```javascript
|
|
285
|
-
* const Koa = require('koa');
|
|
286
|
-
* const Router = require('@koa/router');
|
|
287
|
-
*
|
|
288
|
-
* const app = new Koa();
|
|
289
|
-
* const router = new Router();
|
|
290
|
-
*
|
|
291
|
-
* app.use(router.routes());
|
|
292
|
-
* app.use(router.allowedMethods());
|
|
293
|
-
* ```
|
|
294
|
-
*
|
|
295
|
-
* **Example with [Boom](https://github.com/hapijs/boom)**
|
|
296
|
-
*
|
|
297
|
-
* ```javascript
|
|
298
|
-
* const Koa = require('koa');
|
|
299
|
-
* const Router = require('@koa/router');
|
|
300
|
-
* const Boom = require('boom');
|
|
301
|
-
*
|
|
302
|
-
* const app = new Koa();
|
|
303
|
-
* const router = new Router();
|
|
304
|
-
*
|
|
305
|
-
* app.use(router.routes());
|
|
306
|
-
* app.use(router.allowedMethods({
|
|
307
|
-
* throw: true,
|
|
308
|
-
* notImplemented: () => new Boom.notImplemented(),
|
|
309
|
-
* methodNotAllowed: () => new Boom.methodNotAllowed()
|
|
310
|
-
* }));
|
|
311
|
-
* ```
|
|
312
|
-
*
|
|
313
|
-
* @param {Object=} options
|
|
314
|
-
* @param {Boolean=} options.throw throw error instead of setting status and header
|
|
315
|
-
* @param {Function=} options.notImplemented throw the returned value in place of the default NotImplemented error
|
|
316
|
-
* @param {Function=} options.methodNotAllowed throw the returned value in place of the default MethodNotAllowed error
|
|
317
|
-
* @returns {Function}
|
|
318
|
-
*/
|
|
319
|
-
allowedMethods(options = {}) {
|
|
320
|
-
const implemented = this.methods;
|
|
321
|
-
|
|
322
|
-
return (ctx, next) => {
|
|
323
|
-
return next().then(() => {
|
|
324
|
-
const allowed = {};
|
|
325
|
-
|
|
326
|
-
if (ctx.matched && (!ctx.status || ctx.status === 404)) {
|
|
327
|
-
for (let i = 0; i < ctx.matched.length; i++) {
|
|
328
|
-
const route = ctx.matched[i];
|
|
329
|
-
for (let j = 0; j < route.methods.length; j++) {
|
|
330
|
-
const method = route.methods[j];
|
|
331
|
-
allowed[method] = method;
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
const allowedArr = Object.keys(allowed);
|
|
336
|
-
if (!implemented.includes(ctx.method)) {
|
|
337
|
-
if (options.throw) {
|
|
338
|
-
const notImplementedThrowable =
|
|
339
|
-
typeof options.notImplemented === 'function'
|
|
340
|
-
? options.notImplemented() // set whatever the user returns from their function
|
|
341
|
-
: new HttpError.NotImplemented();
|
|
342
|
-
|
|
343
|
-
throw notImplementedThrowable;
|
|
344
|
-
} else {
|
|
345
|
-
ctx.status = 501;
|
|
346
|
-
ctx.set('Allow', allowedArr.join(', '));
|
|
347
|
-
}
|
|
348
|
-
} else if (allowedArr.length > 0) {
|
|
349
|
-
if (ctx.method === 'OPTIONS') {
|
|
350
|
-
ctx.status = 200;
|
|
351
|
-
ctx.body = '';
|
|
352
|
-
ctx.set('Allow', allowedArr.join(', '));
|
|
353
|
-
} else if (!allowed[ctx.method]) {
|
|
354
|
-
if (options.throw) {
|
|
355
|
-
const notAllowedThrowable =
|
|
356
|
-
typeof options.methodNotAllowed === 'function'
|
|
357
|
-
? options.methodNotAllowed() // set whatever the user returns from their function
|
|
358
|
-
: new HttpError.MethodNotAllowed();
|
|
359
|
-
|
|
360
|
-
throw notAllowedThrowable;
|
|
361
|
-
} else {
|
|
362
|
-
ctx.status = 405;
|
|
363
|
-
ctx.set('Allow', allowedArr.join(', '));
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
});
|
|
369
|
-
};
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
/**
|
|
373
|
-
* Register route with all methods.
|
|
374
|
-
*
|
|
375
|
-
* @param {String} name Optional.
|
|
376
|
-
* @param {String} path
|
|
377
|
-
* @param {Function=} middleware You may also pass multiple middleware.
|
|
378
|
-
* @param {Function} callback
|
|
379
|
-
* @returns {Router}
|
|
380
|
-
*/
|
|
381
|
-
all(name, path, middleware) {
|
|
382
|
-
if (typeof path === 'string' || path instanceof RegExp) {
|
|
383
|
-
middleware = Array.prototype.slice.call(arguments, 2);
|
|
384
|
-
} else {
|
|
385
|
-
middleware = Array.prototype.slice.call(arguments, 1);
|
|
386
|
-
path = name;
|
|
387
|
-
name = null;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// Sanity check to ensure we have a viable path candidate (eg: string|regex|non-empty array)
|
|
391
|
-
if (
|
|
392
|
-
typeof path !== 'string' &&
|
|
393
|
-
!(path instanceof RegExp) &&
|
|
394
|
-
(!Array.isArray(path) || path.length === 0)
|
|
395
|
-
)
|
|
396
|
-
throw new Error('You have to provide a path when adding an all handler');
|
|
397
|
-
|
|
398
|
-
const opts = {
|
|
399
|
-
name,
|
|
400
|
-
pathAsRegExp: path instanceof RegExp
|
|
401
|
-
};
|
|
402
|
-
|
|
403
|
-
this.register(path, methods, middleware, { ...this.opts, ...opts });
|
|
404
|
-
|
|
405
|
-
return this;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
/**
|
|
409
|
-
* Redirect `source` to `destination` URL with optional 30x status `code`.
|
|
410
|
-
*
|
|
411
|
-
* Both `source` and `destination` can be route names.
|
|
412
|
-
*
|
|
413
|
-
* ```javascript
|
|
414
|
-
* router.redirect('/login', 'sign-in');
|
|
415
|
-
* ```
|
|
416
|
-
*
|
|
417
|
-
* This is equivalent to:
|
|
418
|
-
*
|
|
419
|
-
* ```javascript
|
|
420
|
-
* router.all('/login', ctx => {
|
|
421
|
-
* ctx.redirect('/sign-in');
|
|
422
|
-
* ctx.status = 301;
|
|
423
|
-
* });
|
|
424
|
-
* ```
|
|
425
|
-
*
|
|
426
|
-
* @param {String} source URL or route name.
|
|
427
|
-
* @param {String} destination URL or route name.
|
|
428
|
-
* @param {Number=} code HTTP status code (default: 301).
|
|
429
|
-
* @returns {Router}
|
|
430
|
-
*/
|
|
431
|
-
redirect(source, destination, code) {
|
|
432
|
-
// lookup source route by name
|
|
433
|
-
if (typeof source === 'symbol' || source[0] !== '/') {
|
|
434
|
-
source = this.url(source);
|
|
435
|
-
if (source instanceof Error) throw source;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
// lookup destination route by name
|
|
439
|
-
if (
|
|
440
|
-
typeof destination === 'symbol' ||
|
|
441
|
-
(destination[0] !== '/' && !destination.includes('://'))
|
|
442
|
-
) {
|
|
443
|
-
destination = this.url(destination);
|
|
444
|
-
if (destination instanceof Error) throw destination;
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
return this.all(source, (ctx) => {
|
|
448
|
-
ctx.redirect(destination);
|
|
449
|
-
ctx.status = code || 301;
|
|
450
|
-
});
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
/**
|
|
454
|
-
* Create and register a route.
|
|
455
|
-
*
|
|
456
|
-
* @param {String} path Path string.
|
|
457
|
-
* @param {Array.<String>} methods Array of HTTP verbs.
|
|
458
|
-
* @param {Function} middleware Multiple middleware also accepted.
|
|
459
|
-
* @returns {Layer}
|
|
460
|
-
* @private
|
|
461
|
-
*/
|
|
462
|
-
register(path, methods, middleware, newOpts = {}) {
|
|
463
|
-
const router = this;
|
|
464
|
-
const { stack } = this;
|
|
465
|
-
const opts = { ...this.opts, ...newOpts };
|
|
466
|
-
// support array of paths
|
|
467
|
-
if (Array.isArray(path)) {
|
|
468
|
-
for (const curPath of path) {
|
|
469
|
-
router.register.call(router, curPath, methods, middleware, opts);
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
return this;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
// create route
|
|
476
|
-
const route = new Layer(path, methods, middleware, {
|
|
477
|
-
end: opts.end === false ? opts.end : true,
|
|
478
|
-
name: opts.name,
|
|
479
|
-
sensitive: opts.sensitive || false,
|
|
480
|
-
strict: opts.strict || false,
|
|
481
|
-
prefix: opts.prefix || '',
|
|
482
|
-
ignoreCaptures: opts.ignoreCaptures,
|
|
483
|
-
pathAsRegExp: opts.pathAsRegExp
|
|
484
|
-
});
|
|
485
|
-
|
|
486
|
-
// if parent prefix exists, add prefix to new route
|
|
487
|
-
if (this.opts.prefix) {
|
|
488
|
-
route.setPrefix(this.opts.prefix);
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
// add parameter middleware
|
|
492
|
-
for (let i = 0; i < Object.keys(this.params).length; i++) {
|
|
493
|
-
const param = Object.keys(this.params)[i];
|
|
494
|
-
route.param(param, this.params[param]);
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
stack.push(route);
|
|
498
|
-
|
|
499
|
-
debug('defined route %s %s', route.methods, route.path);
|
|
500
|
-
|
|
501
|
-
return route;
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
/**
|
|
505
|
-
* Lookup route with given `name`.
|
|
506
|
-
*
|
|
507
|
-
* @param {String} name
|
|
508
|
-
* @returns {Layer|false}
|
|
509
|
-
*/
|
|
510
|
-
route(name) {
|
|
511
|
-
const routes = this.stack;
|
|
512
|
-
|
|
513
|
-
for (let len = routes.length, i = 0; i < len; i++) {
|
|
514
|
-
if (routes[i].name && routes[i].name === name) return routes[i];
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
return false;
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
/**
|
|
521
|
-
* Generate URL for route. Takes a route name and map of named `params`.
|
|
522
|
-
*
|
|
523
|
-
* @example
|
|
524
|
-
*
|
|
525
|
-
* ```javascript
|
|
526
|
-
* router.get('user', '/users/:id', (ctx, next) => {
|
|
527
|
-
* // ...
|
|
528
|
-
* });
|
|
529
|
-
*
|
|
530
|
-
* router.url('user', 3);
|
|
531
|
-
* // => "/users/3"
|
|
532
|
-
*
|
|
533
|
-
* router.url('user', { id: 3 });
|
|
534
|
-
* // => "/users/3"
|
|
535
|
-
*
|
|
536
|
-
* router.use((ctx, next) => {
|
|
537
|
-
* // redirect to named route
|
|
538
|
-
* ctx.redirect(ctx.router.url('sign-in'));
|
|
539
|
-
* })
|
|
540
|
-
*
|
|
541
|
-
* router.url('user', { id: 3 }, { query: { limit: 1 } });
|
|
542
|
-
* // => "/users/3?limit=1"
|
|
543
|
-
*
|
|
544
|
-
* router.url('user', { id: 3 }, { query: "limit=1" });
|
|
545
|
-
* // => "/users/3?limit=1"
|
|
546
|
-
* ```
|
|
547
|
-
*
|
|
548
|
-
* @param {String} name route name
|
|
549
|
-
* @param {Object} params url parameters
|
|
550
|
-
* @param {Object} [options] options parameter
|
|
551
|
-
* @param {Object|String} [options.query] query options
|
|
552
|
-
* @returns {String|Error}
|
|
553
|
-
*/
|
|
554
|
-
url(name, ...args) {
|
|
555
|
-
const route = this.route(name);
|
|
556
|
-
if (route) return route.url.apply(route, args);
|
|
557
|
-
|
|
558
|
-
return new Error(`No route found for name: ${String(name)}`);
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
/**
|
|
562
|
-
* Match given `path` and return corresponding routes.
|
|
563
|
-
*
|
|
564
|
-
* @param {String} path
|
|
565
|
-
* @param {String} method
|
|
566
|
-
* @returns {Object.<path, pathAndMethod>} returns layers that matched path and
|
|
567
|
-
* path and method.
|
|
568
|
-
* @private
|
|
569
|
-
*/
|
|
570
|
-
match(path, method) {
|
|
571
|
-
const layers = this.stack;
|
|
572
|
-
let layer;
|
|
573
|
-
const matched = {
|
|
574
|
-
path: [],
|
|
575
|
-
pathAndMethod: [],
|
|
576
|
-
route: false
|
|
577
|
-
};
|
|
578
|
-
|
|
579
|
-
for (let len = layers.length, i = 0; i < len; i++) {
|
|
580
|
-
layer = layers[i];
|
|
581
|
-
|
|
582
|
-
debug('test %s %s', layer.path, layer.regexp);
|
|
583
|
-
|
|
584
|
-
if (layer.match(path)) {
|
|
585
|
-
matched.path.push(layer);
|
|
586
|
-
|
|
587
|
-
if (layer.methods.length === 0 || layer.methods.includes(method)) {
|
|
588
|
-
matched.pathAndMethod.push(layer);
|
|
589
|
-
if (layer.methods.length > 0) matched.route = true;
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
return matched;
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
/**
|
|
598
|
-
* Match given `input` to allowed host
|
|
599
|
-
* @param {String} input
|
|
600
|
-
* @returns {boolean}
|
|
601
|
-
*/
|
|
602
|
-
matchHost(input) {
|
|
603
|
-
const { host } = this;
|
|
604
|
-
|
|
605
|
-
if (!host) {
|
|
606
|
-
return true;
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
if (!input) {
|
|
610
|
-
return false;
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
if (typeof host === 'string') {
|
|
614
|
-
return input === host;
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
if (typeof host === 'object' && host instanceof RegExp) {
|
|
618
|
-
return host.test(input);
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
/**
|
|
623
|
-
* Run middleware for named route parameters. Useful for auto-loading or
|
|
624
|
-
* validation.
|
|
625
|
-
*
|
|
626
|
-
* @example
|
|
627
|
-
*
|
|
628
|
-
* ```javascript
|
|
629
|
-
* router
|
|
630
|
-
* .param('user', (id, ctx, next) => {
|
|
631
|
-
* ctx.user = users[id];
|
|
632
|
-
* if (!ctx.user) return ctx.status = 404;
|
|
633
|
-
* return next();
|
|
634
|
-
* })
|
|
635
|
-
* .get('/users/:user', ctx => {
|
|
636
|
-
* ctx.body = ctx.user;
|
|
637
|
-
* })
|
|
638
|
-
* .get('/users/:user/friends', ctx => {
|
|
639
|
-
* return ctx.user.getFriends().then(function(friends) {
|
|
640
|
-
* ctx.body = friends;
|
|
641
|
-
* });
|
|
642
|
-
* })
|
|
643
|
-
* // /users/3 => {"id": 3, "name": "Alex"}
|
|
644
|
-
* // /users/3/friends => [{"id": 4, "name": "TJ"}]
|
|
645
|
-
* ```
|
|
646
|
-
*
|
|
647
|
-
* @param {String} param
|
|
648
|
-
* @param {Function} middleware
|
|
649
|
-
* @returns {Router}
|
|
650
|
-
*/
|
|
651
|
-
param(param, middleware) {
|
|
652
|
-
this.params[param] = middleware;
|
|
653
|
-
for (let i = 0; i < this.stack.length; i++) {
|
|
654
|
-
const route = this.stack[i];
|
|
655
|
-
route.param(param, middleware);
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
return this;
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
/**
|
|
663
|
-
* Create `router.verb()` methods, where *verb* is one of the HTTP verbs such
|
|
664
|
-
* as `router.get()` or `router.post()`.
|
|
665
|
-
*
|
|
666
|
-
* Match URL patterns to callback functions or controller actions using `router.verb()`,
|
|
667
|
-
* where **verb** is one of the HTTP verbs such as `router.get()` or `router.post()`.
|
|
668
|
-
*
|
|
669
|
-
* Additionally, `router.all()` can be used to match against all methods.
|
|
670
|
-
*
|
|
671
|
-
* ```javascript
|
|
672
|
-
* router
|
|
673
|
-
* .get('/', (ctx, next) => {
|
|
674
|
-
* ctx.body = 'Hello World!';
|
|
675
|
-
* })
|
|
676
|
-
* .post('/users', (ctx, next) => {
|
|
677
|
-
* // ...
|
|
678
|
-
* })
|
|
679
|
-
* .put('/users/:id', (ctx, next) => {
|
|
680
|
-
* // ...
|
|
681
|
-
* })
|
|
682
|
-
* .del('/users/:id', (ctx, next) => {
|
|
683
|
-
* // ...
|
|
684
|
-
* })
|
|
685
|
-
* .all('/users/:id', (ctx, next) => {
|
|
686
|
-
* // ...
|
|
687
|
-
* });
|
|
688
|
-
* ```
|
|
689
|
-
*
|
|
690
|
-
* When a route is matched, its path is available at `ctx._matchedRoute` and if named,
|
|
691
|
-
* the name is available at `ctx._matchedRouteName`
|
|
692
|
-
*
|
|
693
|
-
* Route paths will be translated to regular expressions using
|
|
694
|
-
* [path-to-regexp](https://github.com/pillarjs/path-to-regexp).
|
|
695
|
-
*
|
|
696
|
-
* Query strings will not be considered when matching requests.
|
|
697
|
-
*
|
|
698
|
-
* #### Named routes
|
|
699
|
-
*
|
|
700
|
-
* Routes can optionally have names. This allows generation of URLs and easy
|
|
701
|
-
* renaming of URLs during development.
|
|
702
|
-
*
|
|
703
|
-
* ```javascript
|
|
704
|
-
* router.get('user', '/users/:id', (ctx, next) => {
|
|
705
|
-
* // ...
|
|
706
|
-
* });
|
|
707
|
-
*
|
|
708
|
-
* router.url('user', 3);
|
|
709
|
-
* // => "/users/3"
|
|
710
|
-
* ```
|
|
711
|
-
*
|
|
712
|
-
* #### Multiple middleware
|
|
713
|
-
*
|
|
714
|
-
* Multiple middleware may be given:
|
|
715
|
-
*
|
|
716
|
-
* ```javascript
|
|
717
|
-
* router.get(
|
|
718
|
-
* '/users/:id',
|
|
719
|
-
* (ctx, next) => {
|
|
720
|
-
* return User.findOne(ctx.params.id).then(function(user) {
|
|
721
|
-
* ctx.user = user;
|
|
722
|
-
* next();
|
|
723
|
-
* });
|
|
724
|
-
* },
|
|
725
|
-
* ctx => {
|
|
726
|
-
* console.log(ctx.user);
|
|
727
|
-
* // => { id: 17, name: "Alex" }
|
|
728
|
-
* }
|
|
729
|
-
* );
|
|
730
|
-
* ```
|
|
731
|
-
*
|
|
732
|
-
* ### Nested routers
|
|
733
|
-
*
|
|
734
|
-
* Nesting routers is supported:
|
|
735
|
-
*
|
|
736
|
-
* ```javascript
|
|
737
|
-
* const forums = new Router();
|
|
738
|
-
* const posts = new Router();
|
|
739
|
-
*
|
|
740
|
-
* posts.get('/', (ctx, next) => {...});
|
|
741
|
-
* posts.get('/:pid', (ctx, next) => {...});
|
|
742
|
-
* forums.use('/forums/:fid/posts', posts.routes(), posts.allowedMethods());
|
|
743
|
-
*
|
|
744
|
-
* // responds to "/forums/123/posts" and "/forums/123/posts/123"
|
|
745
|
-
* app.use(forums.routes());
|
|
746
|
-
* ```
|
|
747
|
-
*
|
|
748
|
-
* #### Router prefixes
|
|
749
|
-
*
|
|
750
|
-
* Route paths can be prefixed at the router level:
|
|
751
|
-
*
|
|
752
|
-
* ```javascript
|
|
753
|
-
* const router = new Router({
|
|
754
|
-
* prefix: '/users'
|
|
755
|
-
* });
|
|
756
|
-
*
|
|
757
|
-
* router.get('/', ...); // responds to "/users"
|
|
758
|
-
* router.get('/:id', ...); // responds to "/users/:id"
|
|
759
|
-
* ```
|
|
760
|
-
*
|
|
761
|
-
* #### URL parameters
|
|
762
|
-
*
|
|
763
|
-
* Named route parameters are captured and added to `ctx.params`.
|
|
764
|
-
*
|
|
765
|
-
* ```javascript
|
|
766
|
-
* router.get('/:category/:title', (ctx, next) => {
|
|
767
|
-
* console.log(ctx.params);
|
|
768
|
-
* // => { category: 'programming', title: 'how-to-node' }
|
|
769
|
-
* });
|
|
770
|
-
* ```
|
|
771
|
-
*
|
|
772
|
-
* The [path-to-regexp](https://github.com/pillarjs/path-to-regexp) module is
|
|
773
|
-
* used to convert paths to regular expressions.
|
|
774
|
-
*
|
|
775
|
-
*
|
|
776
|
-
* ### Match host for each router instance
|
|
777
|
-
*
|
|
778
|
-
* ```javascript
|
|
779
|
-
* const router = new Router({
|
|
780
|
-
* host: 'example.domain' // only match if request host exactly equal `example.domain`
|
|
781
|
-
* });
|
|
782
|
-
*
|
|
783
|
-
* ```
|
|
784
|
-
*
|
|
785
|
-
* OR host cloud be a regexp
|
|
786
|
-
*
|
|
787
|
-
* ```javascript
|
|
788
|
-
* const router = new Router({
|
|
789
|
-
* host: /.*\.?example\.domain$/ // all host end with .example.domain would be matched
|
|
790
|
-
* });
|
|
791
|
-
* ```
|
|
792
|
-
*
|
|
793
|
-
* @name get|put|post|patch|delete|del
|
|
794
|
-
* @memberof module:koa-router.prototype
|
|
795
|
-
* @param {String} path
|
|
796
|
-
* @param {Function=} middleware route middleware(s)
|
|
797
|
-
* @param {Function} callback route callback
|
|
798
|
-
* @returns {Router}
|
|
799
|
-
*/
|
|
800
|
-
for (const method of methods) {
|
|
801
|
-
Router.prototype[method] = function (name, path, middleware) {
|
|
802
|
-
if (typeof path === 'string' || path instanceof RegExp) {
|
|
803
|
-
middleware = Array.prototype.slice.call(arguments, 2);
|
|
804
|
-
} else {
|
|
805
|
-
middleware = Array.prototype.slice.call(arguments, 1);
|
|
806
|
-
path = name;
|
|
807
|
-
name = null;
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
// Sanity check to ensure we have a viable path candidate (eg: string|regex|non-empty array)
|
|
811
|
-
if (
|
|
812
|
-
typeof path !== 'string' &&
|
|
813
|
-
!(path instanceof RegExp) &&
|
|
814
|
-
(!Array.isArray(path) || path.length === 0)
|
|
815
|
-
)
|
|
816
|
-
throw new Error(
|
|
817
|
-
`You have to provide a path when adding a ${method} handler`
|
|
818
|
-
);
|
|
819
|
-
|
|
820
|
-
const opts = {
|
|
821
|
-
name,
|
|
822
|
-
pathAsRegExp: path instanceof RegExp
|
|
823
|
-
};
|
|
824
|
-
|
|
825
|
-
// pass opts to register call on verb methods
|
|
826
|
-
this.register(path, [method], middleware, { ...this.opts, ...opts });
|
|
827
|
-
return this;
|
|
828
|
-
};
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
// Alias for `router.delete()` because delete is a reserved word
|
|
832
|
-
|
|
833
|
-
Router.prototype.del = Router.prototype['delete'];
|
|
834
|
-
|
|
835
|
-
module.exports = Router;
|