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