@ditojs/router 2.27.0 → 2.28.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/package.json +3 -3
- package/src/Node.js +4 -2
- package/src/Router.test.js +108 -74
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ditojs/router",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.28.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Dito.js Router – Dito.js is a declarative and modern web framework, based on Objection.js, Koa.js and Vue.js",
|
|
6
6
|
"repository": "https://github.com/ditojs/dito/tree/master/packages/router",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"node >= 18"
|
|
22
22
|
],
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@ditojs/utils": "^2.
|
|
24
|
+
"@ditojs/utils": "^2.28.0"
|
|
25
25
|
},
|
|
26
26
|
"keywords": [
|
|
27
27
|
"route",
|
|
@@ -30,6 +30,6 @@
|
|
|
30
30
|
"koa",
|
|
31
31
|
"dito"
|
|
32
32
|
],
|
|
33
|
-
"gitHead": "
|
|
33
|
+
"gitHead": "2bb631b6048da2d1dcb850d89796c87a4787b258",
|
|
34
34
|
"readme": "# Dito.js Router\n\nDito.js is a declarative and modern web framework with a focus on API driven\ndevelopment, based on Koa.js, Objection.js and Vue.js\n\nReleased in 2018 under the MIT license, with support by https://lineto.com/\n\nDito.js Router is a high performance, tree-based, framework agnostic HTTP\nrouter, based on [trek-router](https://github.com/trekjs/router), which in turn\nis inspired by [Echo](https://github.com/labstack/echo)'s Router.\n\n## How does it work?\n\nThe router relies on a tree structure which makes heavy use of common\nprefixes, essentially a [prefix tree](https://en.wikipedia.org/wiki/Trie).\n\n## Usage\n\n```js\nimport Koa from 'koa'\nimport Router from '@ditojs/router'\n\nconst app = new Koa()\nconst router = new Router()\n\n// static route\nrouter.get('/folders/files/bolt.gif', ctx => {\n ctx.body = `this ain't no GIF!`\n})\n\n// param route\nrouter.get('/users/:id', ctx => {\n ctx.body = `requesting user ${ctx.params.id}`\n})\n\n// match-any route\nrouter.get('/books/*', ctx => {\n ctx.body = `sub-route: ${ctx.params['*']}`\n})\n\n// Handler found\nlet { handler, params } = router.find('get', '/users/233')\nconsole.log(handler) // ctx => { ... }\n\n// Entry not Found\nlet { handler, params } = router.find('get', '/photos/233')\nconsole.log(handler) // null\n\n// Install router middleware\napp.use(async (ctx, next) => {\n const { method, path } = ctx\n const result = router.find(method, path)\n const { handler, params } = result\n if (handler) {\n ctx.params = params || {}\n return handler(ctx, next)\n } else {\n try {\n await next()\n } finally {\n if (ctx.body === undefined && ctx.status === 404) {\n ctx.status = result.status || 404\n if (ctx.status !== 404 && result.allowed) {\n ctx.set('Allow', result.allowed.join(', '))\n }\n }\n }\n }\n})\n\napp.listen(4040, () => console.log('Koa app listening on 4040'))\n```\n"
|
|
35
35
|
}
|
package/src/Node.js
CHANGED
|
@@ -26,11 +26,13 @@ export default class Node {
|
|
|
26
26
|
this.type = type
|
|
27
27
|
this.label = prefix[0]
|
|
28
28
|
this.prefix = prefix
|
|
29
|
-
this.children =
|
|
29
|
+
this.children = []
|
|
30
30
|
this.parameters = parameters
|
|
31
31
|
this.handler = handler
|
|
32
32
|
this.paramKey = null
|
|
33
|
-
|
|
33
|
+
for (const child of children) {
|
|
34
|
+
this.addChild(child)
|
|
35
|
+
}
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
setup(parameters, handler) {
|
package/src/Router.test.js
CHANGED
|
@@ -179,22 +179,27 @@ describe('Router', () => {
|
|
|
179
179
|
expect(result.params).toEqual({
|
|
180
180
|
$: 'hello'
|
|
181
181
|
})
|
|
182
|
+
|
|
183
|
+
result = router.find('GET', '/prefix/hello/there/suffix')
|
|
184
|
+
expect(result.handler).toBeUndefined()
|
|
182
185
|
})
|
|
183
186
|
|
|
184
187
|
it('handles match-any nodes with suffixes', () => {
|
|
185
|
-
router
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
188
|
+
createRoutes(router, [
|
|
189
|
+
['GET', '/static/**/suffix', 'one'],
|
|
190
|
+
['GET', '/static/**/suffix/**/more', 'two'],
|
|
191
|
+
['GET', '/static/**/suffix/*/1/:param/2', 'three'],
|
|
192
|
+
['GET', '/static/**/suffix/*/1/:param/2/**/end', 'four']
|
|
193
|
+
])
|
|
189
194
|
|
|
190
195
|
expect(router.toString()).toBe(
|
|
191
196
|
deindent`
|
|
192
197
|
/ children=1
|
|
193
198
|
└── static/ children=4
|
|
194
|
-
├── **/suffix
|
|
195
|
-
├── **/suffix/**/more
|
|
196
|
-
├── **/suffix/*/1/:param/2
|
|
197
|
-
└── **/suffix/*/1/:param/2/**/end
|
|
199
|
+
├── **/suffix one() children=0
|
|
200
|
+
├── **/suffix/**/more two() children=0
|
|
201
|
+
├── **/suffix/*/1/:param/2 three() children=0
|
|
202
|
+
└── **/suffix/*/1/:param/2/**/end four() children=0
|
|
198
203
|
`.trim()
|
|
199
204
|
)
|
|
200
205
|
|
|
@@ -211,13 +216,13 @@ describe('Router', () => {
|
|
|
211
216
|
expect(result.handler).toBeUndefined()
|
|
212
217
|
|
|
213
218
|
result = router.find('GET', '/static/one/suffix')
|
|
214
|
-
expect(result.handler).toBe(
|
|
219
|
+
expect(result.handler?.name).toBe('one')
|
|
215
220
|
expect(result.params).toEqual({
|
|
216
221
|
$$: 'one'
|
|
217
222
|
})
|
|
218
223
|
|
|
219
224
|
result = router.find('GET', '/static/one/two/suffix')
|
|
220
|
-
expect(result.handler).toBe(
|
|
225
|
+
expect(result.handler?.name).toBe('one')
|
|
221
226
|
expect(result.params).toEqual({
|
|
222
227
|
$$: 'one/two'
|
|
223
228
|
})
|
|
@@ -226,14 +231,14 @@ describe('Router', () => {
|
|
|
226
231
|
expect(result.handler).toBeUndefined()
|
|
227
232
|
|
|
228
233
|
result = router.find('GET', '/static/one/two/suffix/three/four/more')
|
|
229
|
-
expect(result.handler).toBe(
|
|
234
|
+
expect(result.handler?.name).toBe('two')
|
|
230
235
|
expect(result.params).toEqual({
|
|
231
236
|
$$0: 'one/two',
|
|
232
237
|
$$1: 'three/four'
|
|
233
238
|
})
|
|
234
239
|
|
|
235
240
|
result = router.find('GET', '/static/one/two/suffix/three/1/bla/2')
|
|
236
|
-
expect(result.handler).toBe(
|
|
241
|
+
expect(result.handler?.name).toBe('three')
|
|
237
242
|
expect(result.params).toEqual({
|
|
238
243
|
$$: 'one/two',
|
|
239
244
|
$: 'three',
|
|
@@ -247,7 +252,7 @@ describe('Router', () => {
|
|
|
247
252
|
'GET',
|
|
248
253
|
`/static/one/two/suffix/three/1/bla/2/what/ever/end`
|
|
249
254
|
)
|
|
250
|
-
expect(result.handler).toBe(
|
|
255
|
+
expect(result.handler?.name).toBe('four')
|
|
251
256
|
expect(result.params).toEqual({
|
|
252
257
|
$: 'three',
|
|
253
258
|
param: 'bla',
|
|
@@ -256,22 +261,49 @@ describe('Router', () => {
|
|
|
256
261
|
})
|
|
257
262
|
})
|
|
258
263
|
|
|
264
|
+
it.only('Allow match-any child-routes to coexist with other child-routes', () => {
|
|
265
|
+
createRoutes(router, [
|
|
266
|
+
['POST', '/api/admin/pages'],
|
|
267
|
+
['POST', '/api/admin/pages/upload/items/**/file'],
|
|
268
|
+
['POST', '/api/admin/pages/upload/items/**/files'],
|
|
269
|
+
['POST', '/api/admin/pages/:id/child-pages']
|
|
270
|
+
])
|
|
271
|
+
expect(router.toString('POST')).toBe(
|
|
272
|
+
deindent`
|
|
273
|
+
/ children=1
|
|
274
|
+
└── api/admin/pages ƒ() children=1
|
|
275
|
+
└── / children=2
|
|
276
|
+
├── upload/items/ children=2
|
|
277
|
+
│ ├── **/file ƒ() children=0
|
|
278
|
+
│ └── **/files ƒ() children=0
|
|
279
|
+
└── :id children=1
|
|
280
|
+
└── /child-pages ƒ() children=0
|
|
281
|
+
`.trim()
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
result = router.find('POST', '/api/admin/pages/upload/items/1/file')
|
|
285
|
+
expect(result.handler).not.toBeUndefined()
|
|
286
|
+
|
|
287
|
+
result = router.find('POST', '/api/admin/pages/upload/items/1/2/file')
|
|
288
|
+
expect(result.handler).not.toBeUndefined()
|
|
289
|
+
})
|
|
290
|
+
|
|
259
291
|
it('handles resources', () => {
|
|
260
292
|
createRoutes(router, [
|
|
261
|
-
['/', 'root'],
|
|
262
|
-
['/geocoder', 'geocoder'],
|
|
263
|
-
['/geocoder/new', 'newGeocoder'],
|
|
264
|
-
['/geocoder/notify', 'notifyGeocoder'],
|
|
265
|
-
// ['/geocoder/nnn', 'nnnGeocoder'],
|
|
266
|
-
['/geocoder/edit', 'editGeocoder'],
|
|
267
|
-
['/geocoder/edit/email', 'editEmailGeocoder'],
|
|
268
|
-
['/geocoder/edit/:item', 'editItemGeocoder'],
|
|
269
|
-
['/geocoder/exchange', 'exchangeGeocoder'],
|
|
270
|
-
['/geocoder/exchange/email', 'exchangeEmailGeocoder'],
|
|
271
|
-
['/geocoder/exchange/:item', 'exchangeItemGeocoder'],
|
|
272
|
-
['/geocoder/:id/echo', 'echoGeocoder'],
|
|
273
|
-
['/geocoder/:action', 'actionGeocoder'],
|
|
274
|
-
['/geocoder/**', 'anyGeocoder']
|
|
293
|
+
['GET', '/', 'root'],
|
|
294
|
+
['GET', '/geocoder', 'geocoder'],
|
|
295
|
+
['GET', '/geocoder/new', 'newGeocoder'],
|
|
296
|
+
['GET', '/geocoder/notify', 'notifyGeocoder'],
|
|
297
|
+
// ['GET', '/geocoder/nnn', 'nnnGeocoder'],
|
|
298
|
+
['GET', '/geocoder/edit', 'editGeocoder'],
|
|
299
|
+
['GET', '/geocoder/edit/email', 'editEmailGeocoder'],
|
|
300
|
+
['GET', '/geocoder/edit/:item', 'editItemGeocoder'],
|
|
301
|
+
['GET', '/geocoder/exchange', 'exchangeGeocoder'],
|
|
302
|
+
['GET', '/geocoder/exchange/email', 'exchangeEmailGeocoder'],
|
|
303
|
+
['GET', '/geocoder/exchange/:item', 'exchangeItemGeocoder'],
|
|
304
|
+
['GET', '/geocoder/:id/echo', 'echoGeocoder'],
|
|
305
|
+
['GET', '/geocoder/:action', 'actionGeocoder'],
|
|
306
|
+
['GET', '/geocoder/**', 'anyGeocoder']
|
|
275
307
|
])
|
|
276
308
|
|
|
277
309
|
expect(router.toString()).toBe(
|
|
@@ -377,17 +409,17 @@ describe('Router', () => {
|
|
|
377
409
|
|
|
378
410
|
it('handles nested resources', () => {
|
|
379
411
|
createRoutes(router, [
|
|
380
|
-
['/users', 'users'],
|
|
381
|
-
['/users/new', 'newUser'],
|
|
382
|
-
['/users/noi', 'newUser'],
|
|
383
|
-
['/users/nei', 'newUser'],
|
|
384
|
-
['/users/:id', 'user'],
|
|
385
|
-
['/users/:id/edit', 'editUser'],
|
|
386
|
-
['/users/:id/:hello/:good/:bad/ddd', 'editUser'],
|
|
387
|
-
['/users/:id/:action', 'actionUser'],
|
|
388
|
-
['/users/:userId/photos/:id', 'photo'],
|
|
389
|
-
['/users/:userId/books/:id', 'book'],
|
|
390
|
-
['/users/**', 'anyUser']
|
|
412
|
+
['GET', '/users', 'users'],
|
|
413
|
+
['GET', '/users/new', 'newUser'],
|
|
414
|
+
['GET', '/users/noi', 'newUser'],
|
|
415
|
+
['GET', '/users/nei', 'newUser'],
|
|
416
|
+
['GET', '/users/:id', 'user'],
|
|
417
|
+
['GET', '/users/:id/edit', 'editUser'],
|
|
418
|
+
['GET', '/users/:id/:hello/:good/:bad/ddd', 'editUser'],
|
|
419
|
+
['GET', '/users/:id/:action', 'actionUser'],
|
|
420
|
+
['GET', '/users/:userId/photos/:id', 'photo'],
|
|
421
|
+
['GET', '/users/:userId/books/:id', 'book'],
|
|
422
|
+
['GET', '/users/**', 'anyUser']
|
|
391
423
|
])
|
|
392
424
|
|
|
393
425
|
expect(router.toString()).toBe(
|
|
@@ -479,27 +511,27 @@ describe('Router', () => {
|
|
|
479
511
|
|
|
480
512
|
describe('handles multiple resources', () => {
|
|
481
513
|
const routes = [
|
|
482
|
-
['/users', 'users'],
|
|
483
|
-
['/users/new', 'newUser'],
|
|
484
|
-
['/users/:id', 'user'],
|
|
485
|
-
['/users/:id/:action', 'actionUser'],
|
|
486
|
-
['/users/:id/edit', 'editUser'],
|
|
487
|
-
['/users/:id/change', 'changeUser'],
|
|
488
|
-
['/users/:id/event', 'eventUser'],
|
|
489
|
-
['/photos', 'photos'],
|
|
490
|
-
['/photos/new', 'newPhoto'],
|
|
491
|
-
['/photos/:id', 'photo'],
|
|
492
|
-
['/photos/:id/:action', 'actionPhoto'],
|
|
493
|
-
['/photos/:id/edit', 'editPhoto'],
|
|
494
|
-
['/photos/:id/change', 'changePhoto'],
|
|
495
|
-
['/photos/:id/event', 'eventPhoto'],
|
|
496
|
-
['/books', 'books'],
|
|
497
|
-
['/books/new', 'newBook'],
|
|
498
|
-
['/books/:id', 'book'],
|
|
499
|
-
['/books/:id/:action', 'actionBook'],
|
|
500
|
-
['/books/:id/edit', 'editBook'],
|
|
501
|
-
['/books/:id/change', 'changeBook'],
|
|
502
|
-
['/books/:id/event', 'eventBook']
|
|
514
|
+
['GET', '/users', 'users'],
|
|
515
|
+
['GET', '/users/new', 'newUser'],
|
|
516
|
+
['GET', '/users/:id', 'user'],
|
|
517
|
+
['GET', '/users/:id/:action', 'actionUser'],
|
|
518
|
+
['GET', '/users/:id/edit', 'editUser'],
|
|
519
|
+
['GET', '/users/:id/change', 'changeUser'],
|
|
520
|
+
['GET', '/users/:id/event', 'eventUser'],
|
|
521
|
+
['GET', '/photos', 'photos'],
|
|
522
|
+
['GET', '/photos/new', 'newPhoto'],
|
|
523
|
+
['GET', '/photos/:id', 'photo'],
|
|
524
|
+
['GET', '/photos/:id/:action', 'actionPhoto'],
|
|
525
|
+
['GET', '/photos/:id/edit', 'editPhoto'],
|
|
526
|
+
['GET', '/photos/:id/change', 'changePhoto'],
|
|
527
|
+
['GET', '/photos/:id/event', 'eventPhoto'],
|
|
528
|
+
['GET', '/books', 'books'],
|
|
529
|
+
['GET', '/books/new', 'newBook'],
|
|
530
|
+
['GET', '/books/:id', 'book'],
|
|
531
|
+
['GET', '/books/:id/:action', 'actionBook'],
|
|
532
|
+
['GET', '/books/:id/edit', 'editBook'],
|
|
533
|
+
['GET', '/books/:id/change', 'changeBook'],
|
|
534
|
+
['GET', '/books/:id/event', 'eventBook']
|
|
503
535
|
]
|
|
504
536
|
|
|
505
537
|
it('parses routes into the correct tree', () => {
|
|
@@ -632,10 +664,10 @@ describe('Router', () => {
|
|
|
632
664
|
|
|
633
665
|
describe('handles namespaces', () => {
|
|
634
666
|
const routes = [
|
|
635
|
-
['/admin/articles', 'articles'],
|
|
636
|
-
['/admin/articles/new', 'newArticle'],
|
|
637
|
-
['/admin/articles/:id', 'article'],
|
|
638
|
-
['/admin/articles/:id/edit', 'editArticle']
|
|
667
|
+
['GET', '/admin/articles', 'articles'],
|
|
668
|
+
['GET', '/admin/articles/new', 'newArticle'],
|
|
669
|
+
['GET', '/admin/articles/:id', 'article'],
|
|
670
|
+
['GET', '/admin/articles/:id/edit', 'editArticle']
|
|
639
671
|
]
|
|
640
672
|
|
|
641
673
|
it('parses routes into the correct tree', () => {
|
|
@@ -677,14 +709,14 @@ describe('Router', () => {
|
|
|
677
709
|
|
|
678
710
|
describe('handles multiple nested resources', () => {
|
|
679
711
|
const routes = [
|
|
680
|
-
['/magazines/:mid/articles', 'articles'],
|
|
681
|
-
['/magazines/:mid/articles/new', 'newArticle'],
|
|
682
|
-
['/magazines/:mid/articles/:id', 'article'],
|
|
683
|
-
['/magazines/:mid/articles/:id/edit', 'editArticle'],
|
|
684
|
-
['/magazines/:m_id/photos', 'photos'],
|
|
685
|
-
['/magazines/:m_id/photos/new', 'newPhoto'],
|
|
686
|
-
['/magazines/:m_id/photos/:id', 'photo'],
|
|
687
|
-
['/magazines/:m_id/photos/:id/edit', 'editPhoto']
|
|
712
|
+
['GET', '/magazines/:mid/articles', 'articles'],
|
|
713
|
+
['GET', '/magazines/:mid/articles/new', 'newArticle'],
|
|
714
|
+
['GET', '/magazines/:mid/articles/:id', 'article'],
|
|
715
|
+
['GET', '/magazines/:mid/articles/:id/edit', 'editArticle'],
|
|
716
|
+
['GET', '/magazines/:m_id/photos', 'photos'],
|
|
717
|
+
['GET', '/magazines/:m_id/photos/new', 'newPhoto'],
|
|
718
|
+
['GET', '/magazines/:m_id/photos/:id', 'photo'],
|
|
719
|
+
['GET', '/magazines/:m_id/photos/:id/edit', 'editPhoto']
|
|
688
720
|
]
|
|
689
721
|
|
|
690
722
|
it('parses routes into the correct tree', () => {
|
|
@@ -781,11 +813,13 @@ describe('Router', () => {
|
|
|
781
813
|
|
|
782
814
|
function createFunc(name) {
|
|
783
815
|
// eslint-disable-next-line no-new-func
|
|
784
|
-
return new Function(
|
|
816
|
+
return new Function(
|
|
817
|
+
`return ${name ? `function ${name}(){}` : '() => {}'}`
|
|
818
|
+
)()
|
|
785
819
|
}
|
|
786
820
|
|
|
787
821
|
function createRoutes(router, routes) {
|
|
788
|
-
routes.forEach(([path, name]) => {
|
|
789
|
-
router.add(
|
|
822
|
+
routes.forEach(([method, path, name]) => {
|
|
823
|
+
router.add(method, path, createFunc(name))
|
|
790
824
|
})
|
|
791
825
|
}
|