@eggjs/router 2.0.1 → 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,724 +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
- * @class
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 layerChain = matchedLayers.reduce(function(memo, layer) {
179
- memo.push(function(ctx, next) {
180
- ctx.captures = layer.captures(path, ctx.captures);
181
- ctx.params = layer.params(path, ctx.captures, ctx.params);
182
- // ctx._matchedRouteName & ctx._matchedRoute for compatibility
183
- ctx._matchedRouteName = ctx.routerName = layer.name;
184
- if (!layer.name) ctx._matchedRouteName = undefined;
185
- ctx._matchedRoute = ctx.routerPath = layer.path;
186
- return next();
187
- });
188
- return memo.concat(layer.stack);
189
- }, []);
190
-
191
- return compose(layerChain)(ctx, next);
192
- };
193
-
194
- dispatch.router = this;
195
-
196
- return dispatch;
197
- }
198
-
199
- /**
200
- * Returns separate middleware for responding to `OPTIONS` requests with
201
- * an `Allow` header containing the allowed methods, as well as responding
202
- * with `405 Method Not Allowed` and `501 Not Implemented` as appropriate.
203
- *
204
- * @example
205
- *
206
- * ```javascript
207
- * var Koa = require('koa');
208
- * var Router = require('koa-router');
209
- *
210
- * var app = new Koa();
211
- * var router = new Router();
212
- *
213
- * app.use(router.routes());
214
- * app.use(router.allowedMethods());
215
- * ```
216
- *
217
- * **Example with [Boom](https://github.com/hapijs/boom)**
218
- *
219
- * ```javascript
220
- * var Koa = require('koa');
221
- * var Router = require('koa-router');
222
- * var Boom = require('boom');
223
- *
224
- * var app = new Koa();
225
- * var router = new Router();
226
- *
227
- * app.use(router.routes());
228
- * app.use(router.allowedMethods({
229
- * throw: true,
230
- * notImplemented: () => new Boom.notImplemented(),
231
- * methodNotAllowed: () => new Boom.methodNotAllowed()
232
- * }));
233
- * ```
234
- *
235
- * @param {Object=} options optional params
236
- * @param {Boolean=} options.throw throw error instead of setting status and header
237
- * @param {Function=} options.notImplemented throw the returned value in place of the default NotImplemented error
238
- * @param {Function=} options.methodNotAllowed throw the returned value in place of the default MethodNotAllowed error
239
- * @return {Function} middleware function
240
- */
241
- allowedMethods(options) {
242
- options = options || {};
243
- const implemented = this.methods;
244
-
245
- return function allowedMethods(ctx, next) {
246
- return next().then(function() {
247
- const allowed = {};
248
-
249
- if (!ctx.status || ctx.status === 404) {
250
- ctx.matched.forEach(function(route) {
251
- route.methods.forEach(function(method) {
252
- allowed[method] = method;
253
- });
254
- });
255
-
256
- const allowedArr = Object.keys(allowed);
257
-
258
- if (!implemented.includes(ctx.method)) {
259
- if (options.throw) {
260
- let notImplementedThrowable;
261
- if (typeof options.notImplemented === 'function') {
262
- notImplementedThrowable = options.notImplemented(); // set whatever the user returns from their function
263
- } else {
264
- notImplementedThrowable = new HttpError.NotImplemented();
265
- }
266
- throw notImplementedThrowable;
267
- } else {
268
- ctx.status = 501;
269
- ctx.set('Allow', allowedArr.join(', '));
270
- }
271
- } else if (allowedArr.length) {
272
- if (ctx.method === 'OPTIONS') {
273
- ctx.status = 200;
274
- ctx.body = '';
275
- ctx.set('Allow', allowedArr.join(', '));
276
- } else if (!allowed[ctx.method]) {
277
- if (options.throw) {
278
- let notAllowedThrowable;
279
- if (typeof options.methodNotAllowed === 'function') {
280
- notAllowedThrowable = options.methodNotAllowed(); // set whatever the user returns from their function
281
- } else {
282
- notAllowedThrowable = new HttpError.MethodNotAllowed();
283
- }
284
- throw notAllowedThrowable;
285
- } else {
286
- ctx.status = 405;
287
- ctx.set('Allow', allowedArr.join(', '));
288
- }
289
- }
290
- }
291
- }
292
- });
293
- };
294
- }
295
-
296
- /**
297
- * Register route with all methods.
298
- *
299
- * @param {String} name Optional.
300
- * @param {String} path path string
301
- * @param {Function=} middleware You may also pass multiple middleware.
302
- * @param {Function} callback callback function
303
- * @return {Router} router instance
304
- * @private
305
- */
306
- all(name, path/* , middleware */) {
307
- let middleware;
308
-
309
- if (typeof path === 'string') {
310
- middleware = Array.prototype.slice.call(arguments, 2);
311
- } else {
312
- middleware = Array.prototype.slice.call(arguments, 1);
313
- path = name;
314
- name = null;
315
- }
316
-
317
- this.register(path, methods, middleware, {
318
- name,
319
- });
320
-
321
- return this;
322
- }
323
-
324
- /**
325
- * Redirect `source` to `destination` URL with optional 30x status `code`.
326
- *
327
- * Both `source` and `destination` can be route names.
328
- *
329
- * ```javascript
330
- * router.redirect('/login', 'sign-in');
331
- * ```
332
- *
333
- * This is equivalent to:
334
- *
335
- * ```javascript
336
- * router.all('/login', ctx => {
337
- * ctx.redirect('/sign-in');
338
- * ctx.status = 301;
339
- * });
340
- * ```
341
- *
342
- * @param {String} source URL or route name.
343
- * @param {String} destination URL or route name.
344
- * @param {Number=} code HTTP status code (default: 301).
345
- * @return {Router} router instance
346
- */
347
- redirect(source, destination, code) {
348
- // lookup source route by name
349
- if (source[0] !== '/') {
350
- source = this.url(source);
351
- }
352
-
353
- // lookup destination route by name
354
- if (destination[0] !== '/') {
355
- destination = this.url(destination);
356
- }
357
-
358
- return this.all(source, ctx => {
359
- ctx.redirect(destination);
360
- ctx.status = code || 301;
361
- });
362
- }
363
-
364
- /**
365
- * Create and register a route.
366
- *
367
- * @param {String} path Path string.
368
- * @param {Array.<String>} methods Array of HTTP verbs.
369
- * @param {Function} middleware Multiple middleware also accepted.
370
- * @param {Object} [opts] optional params
371
- * @return {Layer} layer instance
372
- * @private
373
- */
374
- register(path, methods, middleware, opts) {
375
- opts = opts || {};
376
-
377
- const router = this;
378
- const stack = this.stack;
379
-
380
- // support array of paths
381
- if (Array.isArray(path)) {
382
- path.forEach(function(p) {
383
- router.register.call(router, p, methods, middleware, opts);
384
- });
385
-
386
- return this;
387
- }
388
-
389
- // create route
390
- const route = new Layer(path, methods, middleware, {
391
- end: opts.end === false ? opts.end : true,
392
- name: opts.name,
393
- sensitive: opts.sensitive || this.opts.sensitive || false,
394
- strict: opts.strict || this.opts.strict || false,
395
- prefix: opts.prefix || this.opts.prefix || '',
396
- ignoreCaptures: opts.ignoreCaptures,
397
- });
398
-
399
- if (this.opts.prefix) {
400
- route.setPrefix(this.opts.prefix);
401
- }
402
-
403
- // add parameter middleware
404
- Object.keys(this.params).forEach(function(param) {
405
- route.param(param, this.params[param]);
406
- }, this);
407
-
408
- stack.push(route);
409
-
410
- return route;
411
- }
412
-
413
- /**
414
- * Lookup route with given `name`.
415
- *
416
- * @param {String} name route name
417
- * @return {Layer|false} layer instance of false
418
- */
419
- route(name) {
420
- const routes = this.stack;
421
-
422
- for (let len = routes.length, i = 0; i < len; i++) {
423
- if (routes[i].name && routes[i].name === name) {
424
- return routes[i];
425
- }
426
- }
427
-
428
- return false;
429
- }
430
-
431
- /**
432
- * Generate URL for route. Takes a route name and map of named `params`.
433
- *
434
- * @example
435
- *
436
- * ```javascript
437
- * router.get('user', '/users/:id', (ctx, next) => {
438
- * // ...
439
- * });
440
- *
441
- * router.url('user', 3);
442
- * // => "/users/3"
443
- *
444
- * router.url('user', { id: 3 });
445
- * // => "/users/3"
446
- *
447
- * router.use((ctx, next) => {
448
- * // redirect to named route
449
- * ctx.redirect(ctx.router.url('sign-in'));
450
- * })
451
- *
452
- * router.url('user', { id: 3 }, { query: { limit: 1 } });
453
- * // => "/users/3?limit=1"
454
- *
455
- * router.url('user', { id: 3 }, { query: "limit=1" });
456
- * // => "/users/3?limit=1"
457
- * ```
458
- *
459
- * @param {String} name route name
460
- * @param {Object} params url parameters
461
- * @param {Object} [options] options parameter
462
- * @param {Object|String} [options.query] query options
463
- * @return {String|Error} string or error instance
464
- */
465
- url(name/* , params */) {
466
- const route = this.route(name);
467
-
468
- if (route) {
469
- const args = Array.prototype.slice.call(arguments, 1);
470
- return route.url.apply(route, args);
471
- }
472
-
473
- return new Error('No route found for name: ' + name);
474
- }
475
-
476
- /**
477
- * Match given `path` and return corresponding routes.
478
- *
479
- * @param {String} path path string
480
- * @param {String} method method name
481
- * @return {Object.<path, pathAndMethod>} returns layers that matched path and
482
- * path and method.
483
- * @private
484
- */
485
- match(path, method) {
486
- const layers = this.stack;
487
- let layer;
488
- const matched = {
489
- // matched path
490
- path: [],
491
- // matched path and method(including none method)
492
- pathAndMethod: [],
493
- // method matched or not
494
- route: false,
495
- };
496
-
497
- for (let len = layers.length, i = 0; i < len; i++) {
498
- layer = layers[i];
499
-
500
- debug('test %s %s', layer.path, layer.regexp);
501
-
502
- if (layer.match(path)) {
503
- matched.path.push(layer);
504
-
505
- if (layer.methods.length === 0 || layer.methods.includes(method)) {
506
- matched.pathAndMethod.push(layer);
507
- if (layer.methods.length) matched.route = true;
508
- }
509
- // if (layer.methods.length === 0) {
510
- // matched.pathAndMethod.push(layer);
511
- // } else if (layer.methods.includes(method)) {
512
- // matched.pathAndMethod.push(layer);
513
- // matched.route = true;
514
- // }
515
- }
516
- }
517
-
518
- return matched;
519
- }
520
-
521
- /**
522
- * Run middleware for named route parameters. Useful for auto-loading or
523
- * validation.
524
- *
525
- * @example
526
- *
527
- * ```javascript
528
- * router
529
- * .param('user', (id, ctx, next) => {
530
- * ctx.user = users[id];
531
- * if (!ctx.user) return ctx.status = 404;
532
- * return next();
533
- * })
534
- * .get('/users/:user', ctx => {
535
- * ctx.body = ctx.user;
536
- * })
537
- * .get('/users/:user/friends', ctx => {
538
- * return ctx.user.getFriends().then(function(friends) {
539
- * ctx.body = friends;
540
- * });
541
- * })
542
- * // /users/3 => {"id": 3, "name": "Alex"}
543
- * // /users/3/friends => [{"id": 4, "name": "TJ"}]
544
- * ```
545
- *
546
- * @param {String} param param
547
- * @param {Function} middleware route middleware
548
- * @return {Router} instance
549
- */
550
- param(param, middleware) {
551
- this.params[param] = middleware;
552
- this.stack.forEach(function(route) {
553
- route.param(param, middleware);
554
- });
555
- return this;
556
- }
557
- }
558
-
559
- /**
560
- * Create `router.verb()` methods, where *verb* is one of the HTTP verbs such
561
- * as `router.get()` or `router.post()`.
562
- *
563
- * Match URL patterns to callback functions or controller actions using `router.verb()`,
564
- * where **verb** is one of the HTTP verbs such as `router.get()` or `router.post()`.
565
- *
566
- * Additionaly, `router.all()` can be used to match against all methods.
567
- *
568
- * ```javascript
569
- * router
570
- * .get('/', (ctx, next) => {
571
- * ctx.body = 'Hello World!';
572
- * })
573
- * .post('/users', (ctx, next) => {
574
- * // ...
575
- * })
576
- * .put('/users/:id', (ctx, next) => {
577
- * // ...
578
- * })
579
- * .del('/users/:id', (ctx, next) => {
580
- * // ...
581
- * })
582
- * .all('/users/:id', (ctx, next) => {
583
- * // ...
584
- * });
585
- * ```
586
- *
587
- * When a route is matched, its path is available at `ctx._matchedRoute` and if named,
588
- * the name is available at `ctx._matchedRouteName`
589
- *
590
- * Route paths will be translated to regular expressions using
591
- * [path-to-regexp](https://github.com/pillarjs/path-to-regexp).
592
- *
593
- * Query strings will not be considered when matching requests.
594
- *
595
- * #### Named routes
596
- *
597
- * Routes can optionally have names. This allows generation of URLs and easy
598
- * renaming of URLs during development.
599
- *
600
- * ```javascript
601
- * router.get('user', '/users/:id', (ctx, next) => {
602
- * // ...
603
- * });
604
- *
605
- * router.url('user', 3);
606
- * // => "/users/3"
607
- * ```
608
- *
609
- * #### Multiple middleware
610
- *
611
- * Multiple middleware may be given:
612
- *
613
- * ```javascript
614
- * router.get(
615
- * '/users/:id',
616
- * (ctx, next) => {
617
- * return User.findOne(ctx.params.id).then(function(user) {
618
- * ctx.user = user;
619
- * next();
620
- * });
621
- * },
622
- * ctx => {
623
- * console.log(ctx.user);
624
- * // => { id: 17, name: "Alex" }
625
- * }
626
- * );
627
- * ```
628
- *
629
- * ### Nested routers
630
- *
631
- * Nesting routers is supported:
632
- *
633
- * ```javascript
634
- * var forums = new Router();
635
- * var posts = new Router();
636
- *
637
- * posts.get('/', (ctx, next) => {...});
638
- * posts.get('/:pid', (ctx, next) => {...});
639
- * forums.use('/forums/:fid/posts', posts.routes(), posts.allowedMethods());
640
- *
641
- * // responds to "/forums/123/posts" and "/forums/123/posts/123"
642
- * app.use(forums.routes());
643
- * ```
644
- *
645
- * #### Router prefixes
646
- *
647
- * Route paths can be prefixed at the router level:
648
- *
649
- * ```javascript
650
- * var router = new Router({
651
- * prefix: '/users'
652
- * });
653
- *
654
- * router.get('/', ...); // responds to "/users"
655
- * router.get('/:id', ...); // responds to "/users/:id"
656
- * ```
657
- *
658
- * #### URL parameters
659
- *
660
- * Named route parameters are captured and added to `ctx.params`.
661
- *
662
- * ```javascript
663
- * router.get('/:category/:title', (ctx, next) => {
664
- * console.log(ctx.params);
665
- * // => { category: 'programming', title: 'how-to-node' }
666
- * });
667
- * ```
668
- *
669
- * The [path-to-regexp](https://github.com/pillarjs/path-to-regexp) module is
670
- * used to convert paths to regular expressions.
671
- *
672
- * @name get|put|post|patch|delete|del
673
- * @memberof module:koa-router.prototype
674
- * @param {String} path
675
- * @param {Function=} middleware route middleware(s)
676
- * @param {Function} callback route callback
677
- * @returns {Router}
678
- */
679
-
680
- methods.forEach(function(method) {
681
- Router.prototype[method] = function(name, path /* , middleware */) {
682
- let middleware;
683
-
684
- if (typeof path === 'string' || path instanceof RegExp) {
685
- middleware = Array.prototype.slice.call(arguments, 2);
686
- } else {
687
- middleware = Array.prototype.slice.call(arguments, 1);
688
- path = name;
689
- name = null;
690
- }
691
-
692
- this.register(path, [ method ], middleware, {
693
- name,
694
- });
695
-
696
- return this;
697
- };
698
- });
699
-
700
- // Alias for `router.delete()` because delete is a reserved word
701
- Router.prototype.del = Router.prototype.delete;
702
-
703
- /**
704
- * Generate URL from url pattern and given `params`.
705
- *
706
- * @example
707
- *
708
- * ```javascript
709
- * var url = Router.url('/users/:id', {id: 1});
710
- * // => "/users/1"
711
- * ```
712
- *
713
- * @param {String} path url pattern
714
- * @param {Object} params url parameters
715
- * @return {String} url string
716
- */
717
- Router.url = function(path/* , params */) {
718
- const args = Array.prototype.slice.call(arguments, 1);
719
- return Layer.prototype.url.apply({ path }, args);
720
- };
721
-
722
- Router.prototype.middleware = Router.prototype.routes;
723
-
724
- module.exports = Router;