hono 1.3.6 → 1.4.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/README.md +77 -21
- package/dist/compose.js +0 -1
- package/dist/context.d.ts +0 -1
- package/dist/context.js +8 -7
- package/dist/hono.d.ts +3 -6
- package/dist/hono.js +6 -7
- package/dist/middleware/jwt/index.js +3 -1
- package/dist/router/reg-exp-router/router.d.ts +10 -4
- package/dist/router/reg-exp-router/router.js +42 -14
- package/dist/router/trie-router/node.d.ts +11 -4
- package/dist/router/trie-router/node.js +45 -24
- package/dist/router/trie-router/router.d.ts +2 -3
- package/dist/router/trie-router/router.js +1 -3
- package/dist/router.d.ts +4 -5
- package/dist/router.js +1 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,13 +38,13 @@ app.fire()
|
|
|
38
38
|
**Hono is fastest**, compared to other routers for Cloudflare Workers.
|
|
39
39
|
|
|
40
40
|
```plain
|
|
41
|
-
hono - trie-router(default) x
|
|
42
|
-
hono - regexp-router x
|
|
43
|
-
itty-router x
|
|
44
|
-
sunder x
|
|
45
|
-
worktop x
|
|
46
|
-
Fastest is hono - regexp-router
|
|
47
|
-
✨ Done in
|
|
41
|
+
hono - trie-router(default) x 725,892 ops/sec ±4.02% (74 runs sampled)
|
|
42
|
+
hono - regexp-router x 733,494 ops/sec ±3.00% (67 runs sampled)
|
|
43
|
+
itty-router x 167,167 ops/sec ±1.25% (91 runs sampled)
|
|
44
|
+
sunder x 327,697 ops/sec ±2.45% (92 runs sampled)
|
|
45
|
+
worktop x 216,468 ops/sec ±3.01% (85 runs sampled)
|
|
46
|
+
Fastest is hono - regexp-router,hono - trie-router(default)
|
|
47
|
+
✨ Done in 69.57s.
|
|
48
48
|
```
|
|
49
49
|
|
|
50
50
|
## Why so fast?
|
|
@@ -137,15 +137,15 @@ npm install hono
|
|
|
137
137
|
|
|
138
138
|
An instance of `Hono` has these methods.
|
|
139
139
|
|
|
140
|
-
- app.**HTTP_METHOD**(\[
|
|
141
|
-
- app.**all**(\[
|
|
142
|
-
- app.**route**(
|
|
143
|
-
- app.**use**(\[
|
|
140
|
+
- app.**HTTP_METHOD**(\[path,\]handler|middleware...)
|
|
141
|
+
- app.**all**(\[path,\]handler|middleware...)
|
|
142
|
+
- app.**route**(path, \[app\])
|
|
143
|
+
- app.**use**(\[path,\]middleware)
|
|
144
144
|
- app.**notFound**(handler)
|
|
145
145
|
- app.**onError**(err, handler)
|
|
146
146
|
- app.**fire**()
|
|
147
147
|
- app.**fetch**(request, env, event)
|
|
148
|
-
- app.**request**(
|
|
148
|
+
- app.**request**(path, options)
|
|
149
149
|
|
|
150
150
|
## Routing
|
|
151
151
|
|
|
@@ -249,12 +249,37 @@ app.route('/book', book)
|
|
|
249
249
|
|
|
250
250
|
## Middleware
|
|
251
251
|
|
|
252
|
-
Middleware
|
|
252
|
+
Middleware works after/before Handler. We can get `Request` before dispatching or manipulate `Response` after dispatching.
|
|
253
253
|
|
|
254
254
|
### Definition of Middleware
|
|
255
255
|
|
|
256
|
-
- Handler - should return `Response` object.
|
|
257
|
-
- Middleware - should return nothing,
|
|
256
|
+
- Handler - should return `Response` object. Only one handler will be called.
|
|
257
|
+
- Middleware - should return nothing, will be proceeded to next middleware with `await next()`
|
|
258
|
+
|
|
259
|
+
The user can register middleware using `c.use` or using `c.HTTP_METHOD` as well as the handlers. For this feature, it's easy to specify the path and the method.
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
// match any method, all routes
|
|
263
|
+
app.use('*', logger())
|
|
264
|
+
|
|
265
|
+
// specify path
|
|
266
|
+
app.use('/posts/*', cors())
|
|
267
|
+
|
|
268
|
+
// specify method and path
|
|
269
|
+
app.post('/posts/*', basicAuth(), bodyParse())
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
If the handler returns `Response`, it will be used for the end-user, and stopping the processing.
|
|
273
|
+
|
|
274
|
+
```ts
|
|
275
|
+
app.post('/posts', (c) => c.text('Created!', 201))
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
In this case, four middleware are processed before dispatching like this:
|
|
279
|
+
|
|
280
|
+
```ts
|
|
281
|
+
logger() -> cors() -> basicAuth() -> bodyParse() -> *handler*
|
|
282
|
+
```
|
|
258
283
|
|
|
259
284
|
### Built-in Middleware
|
|
260
285
|
|
|
@@ -390,7 +415,6 @@ The Response is the same as below.
|
|
|
390
415
|
```ts
|
|
391
416
|
new Response('Thank you for comming', {
|
|
392
417
|
status: 201,
|
|
393
|
-
statusText: 'Created',
|
|
394
418
|
headers: {
|
|
395
419
|
'X-Message': 'Hello',
|
|
396
420
|
'Content-Type': 'text/plain',
|
|
@@ -518,14 +542,47 @@ test('GET /hello is ok', async () => {
|
|
|
518
542
|
})
|
|
519
543
|
```
|
|
520
544
|
|
|
521
|
-
##
|
|
545
|
+
## router
|
|
522
546
|
|
|
523
|
-
The `
|
|
547
|
+
The `router` option specify which router is used inside. The default router is `TrieRouter`. If you want to use `RexExpRouter`, write like this:
|
|
524
548
|
|
|
525
549
|
```ts
|
|
526
550
|
import { RegExpRouter } from 'hono/router/reg-exp-router'
|
|
527
551
|
|
|
528
|
-
const app = new Hono({
|
|
552
|
+
const app = new Hono({ router: new RegExpRouter() })
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
## Routing Ordering
|
|
556
|
+
|
|
557
|
+
The routing priority is decided by the order of registration. Only one handler will be dispatched.
|
|
558
|
+
|
|
559
|
+
```ts
|
|
560
|
+
app.get('/book/a', (c) => c.text('a')) // a
|
|
561
|
+
app.get('/book/:slug', (c) => c.text('common')) // common
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
```http
|
|
565
|
+
GET /book/a ---> `a` // common will not be dispatched
|
|
566
|
+
GET /book/b ---> `common` // a will not be dispatched
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
All scoring rules:
|
|
570
|
+
|
|
571
|
+
```ts
|
|
572
|
+
app.get('/api/*', 'c') // score 1.1 <--- `/*` is special wildcard
|
|
573
|
+
app.get('/api/:type/:id', 'd') // score 3.2
|
|
574
|
+
app.get('/api/posts/:id', 'e') // score 3.3
|
|
575
|
+
app.get('/api/posts/123', 'f') // score 3.4
|
|
576
|
+
app.get('/*/*/:id', 'g') // score 3.5
|
|
577
|
+
app.get('/api/posts/*/comment', 'h') // score 4.6 - not match
|
|
578
|
+
app.get('*', 'a') // score 0.7
|
|
579
|
+
app.get('*', 'b') // score 0.8
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
```plain
|
|
583
|
+
GET /api/posts/123
|
|
584
|
+
---> will match => c, d, e, f, b, a, b
|
|
585
|
+
---> sort by score => a, b, c, d, e, f, g
|
|
529
586
|
```
|
|
530
587
|
|
|
531
588
|
## Cloudflare Workers with Hono
|
|
@@ -615,6 +672,7 @@ export interface Bindings {
|
|
|
615
672
|
}
|
|
616
673
|
|
|
617
674
|
const api = new Hono<Bindings>()
|
|
675
|
+
api.use('/posts/*', cors())
|
|
618
676
|
|
|
619
677
|
api.get('/posts', (c) => {
|
|
620
678
|
const { limit, offset } = c.req.query()
|
|
@@ -641,8 +699,6 @@ api.post(
|
|
|
641
699
|
}
|
|
642
700
|
)
|
|
643
701
|
|
|
644
|
-
app.use('/posts/*', cors())
|
|
645
|
-
|
|
646
702
|
app.route('/api', api)
|
|
647
703
|
|
|
648
704
|
export default app
|
package/dist/compose.js
CHANGED
package/dist/context.d.ts
CHANGED
package/dist/context.js
CHANGED
|
@@ -4,7 +4,6 @@ exports.Context = void 0;
|
|
|
4
4
|
const url_1 = require("./utils/url");
|
|
5
5
|
class Context {
|
|
6
6
|
constructor(req, opts) {
|
|
7
|
-
this._headers = {};
|
|
8
7
|
this._status = 200;
|
|
9
8
|
this._pretty = false;
|
|
10
9
|
this._prettySpace = 2;
|
|
@@ -59,10 +58,7 @@ class Context {
|
|
|
59
58
|
return req;
|
|
60
59
|
}
|
|
61
60
|
header(name, value) {
|
|
62
|
-
|
|
63
|
-
this.res.headers.set(name, value);
|
|
64
|
-
}
|
|
65
|
-
this._headers[name] = value;
|
|
61
|
+
this.res.headers.set(name, value);
|
|
66
62
|
}
|
|
67
63
|
status(status) {
|
|
68
64
|
this._status = status;
|
|
@@ -79,10 +75,15 @@ class Context {
|
|
|
79
75
|
}
|
|
80
76
|
newResponse(data, init = {}) {
|
|
81
77
|
init.status = init.status || this._status || 200;
|
|
82
|
-
|
|
78
|
+
let headers = {};
|
|
79
|
+
this.res.headers.forEach((v, k) => {
|
|
80
|
+
headers[k] = v;
|
|
81
|
+
});
|
|
82
|
+
init.headers = Object.assign(headers, init.headers);
|
|
83
|
+
headers = {};
|
|
83
84
|
return new Response(data, init);
|
|
84
85
|
}
|
|
85
|
-
body(data, status = this._status, headers =
|
|
86
|
+
body(data, status = this._status, headers = {}) {
|
|
86
87
|
return this.newResponse(data, {
|
|
87
88
|
status: status,
|
|
88
89
|
headers: headers,
|
package/dist/hono.d.ts
CHANGED
|
@@ -54,16 +54,13 @@ declare const Hono_base: new <E_1 extends Env, T extends string, U>() => {
|
|
|
54
54
|
patch: HandlerInterface<T, E_1, U>;
|
|
55
55
|
};
|
|
56
56
|
export declare class Hono<E = Env, P extends string = '/'> extends Hono_base<E, P, Hono<E, P>> {
|
|
57
|
-
readonly
|
|
58
|
-
new (): Router<any>;
|
|
59
|
-
};
|
|
57
|
+
readonly router: Router<Handler<string, E>>;
|
|
60
58
|
readonly strict: boolean;
|
|
61
|
-
private _router;
|
|
62
59
|
private _tempPath;
|
|
63
60
|
private path;
|
|
64
|
-
private
|
|
61
|
+
private _cachedResponse;
|
|
65
62
|
routes: Route<E>[];
|
|
66
|
-
constructor(init?: Partial<Pick<Hono, '
|
|
63
|
+
constructor(init?: Partial<Pick<Hono, 'router' | 'strict'>>);
|
|
67
64
|
private notFoundHandler;
|
|
68
65
|
private errorHandler;
|
|
69
66
|
route(path: string, app?: Hono<any>): Hono<E, P>;
|
package/dist/hono.js
CHANGED
|
@@ -15,7 +15,7 @@ function defineDynamicClass() {
|
|
|
15
15
|
class Hono extends defineDynamicClass() {
|
|
16
16
|
constructor(init = {}) {
|
|
17
17
|
super();
|
|
18
|
-
this.
|
|
18
|
+
this.router = new trie_router_1.TrieRouter();
|
|
19
19
|
this.strict = true; // strict routing - default is true
|
|
20
20
|
this.path = '/';
|
|
21
21
|
this.routes = [];
|
|
@@ -46,10 +46,9 @@ class Hono extends defineDynamicClass() {
|
|
|
46
46
|
};
|
|
47
47
|
});
|
|
48
48
|
Object.assign(this, init);
|
|
49
|
-
this._router = new this.routerClass();
|
|
50
49
|
this._tempPath = null;
|
|
51
|
-
this.
|
|
52
|
-
this.
|
|
50
|
+
this._cachedResponse = new Response(null, { status: 404 });
|
|
51
|
+
this._cachedResponse.finalized = false;
|
|
53
52
|
}
|
|
54
53
|
route(path, app) {
|
|
55
54
|
this._tempPath = path;
|
|
@@ -86,12 +85,12 @@ class Hono extends defineDynamicClass() {
|
|
|
86
85
|
if (this._tempPath) {
|
|
87
86
|
path = (0, url_1.mergePath)(this._tempPath, path);
|
|
88
87
|
}
|
|
89
|
-
this.
|
|
88
|
+
this.router.add(method, path, handler);
|
|
90
89
|
const r = { path: path, method: method, handler: handler };
|
|
91
90
|
this.routes.push(r);
|
|
92
91
|
}
|
|
93
92
|
async matchRoute(method, path) {
|
|
94
|
-
return this.
|
|
93
|
+
return this.router.match(method, path);
|
|
95
94
|
}
|
|
96
95
|
async dispatch(request, event, env) {
|
|
97
96
|
const path = (0, url_1.getPathFromURL)(request.url, { strict: this.strict });
|
|
@@ -111,7 +110,7 @@ class Hono extends defineDynamicClass() {
|
|
|
111
110
|
const c = new context_1.Context(request, {
|
|
112
111
|
env: env,
|
|
113
112
|
event: event,
|
|
114
|
-
res: this.
|
|
113
|
+
res: this._cachedResponse,
|
|
115
114
|
});
|
|
116
115
|
c.notFound = () => this.notFoundHandler(c);
|
|
117
116
|
const composed = (0, compose_1.compose)(handlers, this.errorHandler, this.notFoundHandler);
|
|
@@ -8,7 +8,6 @@ const jwt = (options) => {
|
|
|
8
8
|
}
|
|
9
9
|
return async (ctx, next) => {
|
|
10
10
|
const credentials = ctx.req.headers.get('Authorization');
|
|
11
|
-
await next();
|
|
12
11
|
if (!credentials) {
|
|
13
12
|
ctx.res = new Response('Unauthorized', {
|
|
14
13
|
status: 401,
|
|
@@ -26,6 +25,7 @@ const jwt = (options) => {
|
|
|
26
25
|
'WWW-Authenticate': 'Basic ${options.secret}',
|
|
27
26
|
},
|
|
28
27
|
});
|
|
28
|
+
return;
|
|
29
29
|
}
|
|
30
30
|
let authorized = false;
|
|
31
31
|
let msg = '';
|
|
@@ -43,7 +43,9 @@ const jwt = (options) => {
|
|
|
43
43
|
'WWW-Authenticate': 'Bearer ${options.secret}',
|
|
44
44
|
},
|
|
45
45
|
});
|
|
46
|
+
return;
|
|
46
47
|
}
|
|
48
|
+
await next();
|
|
47
49
|
};
|
|
48
50
|
};
|
|
49
51
|
exports.jwt = jwt;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Router, Result } from '../../router';
|
|
1
|
+
import type { Router, Result } from '../../router';
|
|
2
2
|
interface Hint {
|
|
3
3
|
components: string[];
|
|
4
4
|
regExpComponents: Array<true | string>;
|
|
@@ -8,16 +8,22 @@ interface Hint {
|
|
|
8
8
|
maybeHandler: boolean;
|
|
9
9
|
namedParams: [number, string, string][];
|
|
10
10
|
}
|
|
11
|
+
interface HandlerWithSortIndex<T> {
|
|
12
|
+
handler: T;
|
|
13
|
+
index: number;
|
|
14
|
+
componentsLength: number;
|
|
15
|
+
}
|
|
11
16
|
interface Route<T> {
|
|
12
17
|
method: string;
|
|
13
18
|
path: string;
|
|
14
19
|
hint: Hint;
|
|
15
|
-
handlers: T[];
|
|
16
|
-
middleware: T[];
|
|
20
|
+
handlers: HandlerWithSortIndex<T>[];
|
|
21
|
+
middleware: HandlerWithSortIndex<T>[];
|
|
17
22
|
paramAliasMap: Record<string, string[]>;
|
|
18
23
|
}
|
|
19
|
-
export declare class RegExpRouter<T>
|
|
24
|
+
export declare class RegExpRouter<T> implements Router<T> {
|
|
20
25
|
routeData?: {
|
|
26
|
+
index: number;
|
|
21
27
|
routes: Route<T>[];
|
|
22
28
|
methods: Set<string>;
|
|
23
29
|
};
|
|
@@ -64,6 +64,14 @@ function compareRoute(a, b) {
|
|
|
64
64
|
}
|
|
65
65
|
return i === b.hint.regExpComponents.length || a.hint.endWithWildcard ? 1 : 0;
|
|
66
66
|
}
|
|
67
|
+
function compareHandler(a, b) {
|
|
68
|
+
return a.componentsLength !== b.componentsLength
|
|
69
|
+
? a.componentsLength - b.componentsLength
|
|
70
|
+
: a.index - b.index;
|
|
71
|
+
}
|
|
72
|
+
function getSortedHandlers(handlers) {
|
|
73
|
+
return [...handlers].sort(compareHandler).map((h) => h.handler);
|
|
74
|
+
}
|
|
67
75
|
function buildMatcherFromPreprocessedRoutes(routes, hasAmbiguous = false) {
|
|
68
76
|
const trie = new trie_1.Trie({ reverse: hasAmbiguous });
|
|
69
77
|
const handlers = [];
|
|
@@ -76,6 +84,9 @@ function buildMatcherFromPreprocessedRoutes(routes, hasAmbiguous = false) {
|
|
|
76
84
|
[...routes[i].middleware, ...routes[i].handlers],
|
|
77
85
|
Object.keys(paramMap).length !== 0 ? paramMap : null,
|
|
78
86
|
];
|
|
87
|
+
if (!hasAmbiguous) {
|
|
88
|
+
handlers[i][0] = getSortedHandlers(handlers[i][0]);
|
|
89
|
+
}
|
|
79
90
|
}
|
|
80
91
|
const [regexp, indexReplacementMap, paramReplacementMap] = trie.buildRegExp();
|
|
81
92
|
for (let i = 0, len = handlers.length; i < len; i++) {
|
|
@@ -130,22 +141,28 @@ function verifyDuplicateParam(routes) {
|
|
|
130
141
|
}
|
|
131
142
|
return true;
|
|
132
143
|
}
|
|
133
|
-
class RegExpRouter
|
|
144
|
+
class RegExpRouter {
|
|
134
145
|
constructor() {
|
|
135
|
-
|
|
136
|
-
this.routeData = { routes: [], methods: new Set() };
|
|
146
|
+
this.routeData = { index: 0, routes: [], methods: new Set() };
|
|
137
147
|
}
|
|
138
148
|
add(method, path, handler) {
|
|
139
149
|
if (!this.routeData) {
|
|
140
150
|
throw new Error('Can not add a route since the matcher is already built.');
|
|
141
151
|
}
|
|
142
|
-
|
|
152
|
+
this.routeData.index++;
|
|
153
|
+
const { index, routes, methods } = this.routeData;
|
|
143
154
|
if (path === '/*') {
|
|
144
155
|
path = '*';
|
|
145
156
|
}
|
|
157
|
+
const hint = initHint(path);
|
|
158
|
+
const handlerWithSortIndex = {
|
|
159
|
+
index,
|
|
160
|
+
handler,
|
|
161
|
+
componentsLength: hint.components.length,
|
|
162
|
+
};
|
|
146
163
|
for (let i = 0, len = routes.length; i < len; i++) {
|
|
147
164
|
if (routes[i].method === method && routes[i].path === path) {
|
|
148
|
-
routes[i].handlers.push(
|
|
165
|
+
routes[i].handlers.push(handlerWithSortIndex);
|
|
149
166
|
return;
|
|
150
167
|
}
|
|
151
168
|
}
|
|
@@ -153,8 +170,8 @@ class RegExpRouter extends router_1.Router {
|
|
|
153
170
|
routes.push({
|
|
154
171
|
method,
|
|
155
172
|
path,
|
|
156
|
-
|
|
157
|
-
|
|
173
|
+
hint,
|
|
174
|
+
handlers: [handlerWithSortIndex],
|
|
158
175
|
middleware: [],
|
|
159
176
|
paramAliasMap: {},
|
|
160
177
|
});
|
|
@@ -163,7 +180,8 @@ class RegExpRouter extends router_1.Router {
|
|
|
163
180
|
const [primaryMatchers, secondaryMatchers, hasAmbiguous] = this.buildAllMatchers();
|
|
164
181
|
this.match = hasAmbiguous
|
|
165
182
|
? (method, path) => {
|
|
166
|
-
const matcher = primaryMatchers[method] ||
|
|
183
|
+
const matcher = (primaryMatchers[method] ||
|
|
184
|
+
primaryMatchers[router_1.METHOD_NAME_ALL]);
|
|
167
185
|
let match = path.match(matcher[0]);
|
|
168
186
|
if (!match) {
|
|
169
187
|
// do not support secondary matchers here.
|
|
@@ -193,10 +211,10 @@ class RegExpRouter extends router_1.Router {
|
|
|
193
211
|
regExpSrc = regExpSrc.replace(new RegExp(`((?:(?:\\(\\?:|.)*?\\([^)]*\\)){${index - 1}}.*?)\\(\\)`), '$1(^)');
|
|
194
212
|
match = path.match(new RegExp(regExpSrc));
|
|
195
213
|
}
|
|
196
|
-
return
|
|
214
|
+
return { handlers: getSortedHandlers(handlers.values()), params };
|
|
197
215
|
}
|
|
198
216
|
: (method, path) => {
|
|
199
|
-
let matcher = primaryMatchers[method] || primaryMatchers[router_1.METHOD_NAME_ALL];
|
|
217
|
+
let matcher = (primaryMatchers[method] || primaryMatchers[router_1.METHOD_NAME_ALL]);
|
|
200
218
|
let match = path.match(matcher[0]);
|
|
201
219
|
if (!match) {
|
|
202
220
|
const matchers = secondaryMatchers[method] || secondaryMatchers[router_1.METHOD_NAME_ALL];
|
|
@@ -209,15 +227,15 @@ class RegExpRouter extends router_1.Router {
|
|
|
209
227
|
}
|
|
210
228
|
}
|
|
211
229
|
const index = match.indexOf('', 1);
|
|
212
|
-
const [
|
|
230
|
+
const [handlers, paramMap] = matcher[1][index];
|
|
213
231
|
if (!paramMap) {
|
|
214
|
-
return
|
|
232
|
+
return { handlers, params: emptyParam };
|
|
215
233
|
}
|
|
216
234
|
const params = {};
|
|
217
235
|
for (let i = 0, len = paramMap.length; i < len; i++) {
|
|
218
236
|
params[paramMap[i][0]] = match[paramMap[i][1]];
|
|
219
237
|
}
|
|
220
|
-
return
|
|
238
|
+
return { handlers, params };
|
|
221
239
|
};
|
|
222
240
|
return this.match(method, path);
|
|
223
241
|
}
|
|
@@ -296,7 +314,17 @@ class RegExpRouter extends router_1.Router {
|
|
|
296
314
|
routes[j].path = components.join('');
|
|
297
315
|
}
|
|
298
316
|
}
|
|
299
|
-
routes[j].
|
|
317
|
+
if (routes[j].hint.components.length < routes[i].hint.components.length) {
|
|
318
|
+
const componentsLength = routes[j].hint.components.length;
|
|
319
|
+
routes[j].middleware.push(...routes[i].handlers.map((h) => ({
|
|
320
|
+
componentsLength,
|
|
321
|
+
index: h.index,
|
|
322
|
+
handler: h.handler,
|
|
323
|
+
})));
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
routes[j].middleware.push(...routes[i].handlers);
|
|
327
|
+
}
|
|
300
328
|
routes[i].hint.maybeHandler = false;
|
|
301
329
|
}
|
|
302
330
|
else if (compareResult === 2) {
|
|
@@ -1,13 +1,20 @@
|
|
|
1
|
-
import { Result } from '../../router';
|
|
1
|
+
import type { Result } from '../../router';
|
|
2
2
|
import type { Pattern } from '../../utils/url';
|
|
3
|
+
declare type HandlerSet<T> = {
|
|
4
|
+
handler: T;
|
|
5
|
+
score: number;
|
|
6
|
+
name: string;
|
|
7
|
+
};
|
|
3
8
|
export declare class Node<T> {
|
|
4
|
-
methods: Record<string, T
|
|
5
|
-
handlers: T[];
|
|
9
|
+
methods: Record<string, HandlerSet<T>>[];
|
|
6
10
|
children: Record<string, Node<T>>;
|
|
7
11
|
patterns: Pattern[];
|
|
12
|
+
order: number;
|
|
13
|
+
name: string;
|
|
8
14
|
constructor(method?: string, handler?: T, children?: Record<string, Node<T>>);
|
|
9
15
|
insert(method: string, path: string, handler: T): Node<T>;
|
|
10
|
-
private
|
|
16
|
+
private getHandlerSets;
|
|
11
17
|
private next;
|
|
12
18
|
search(method: string, path: string): Result<T>;
|
|
13
19
|
}
|
|
20
|
+
export {};
|
|
@@ -19,16 +19,19 @@ function findParam(node, name) {
|
|
|
19
19
|
}
|
|
20
20
|
class Node {
|
|
21
21
|
constructor(method, handler, children) {
|
|
22
|
+
this.order = 0;
|
|
22
23
|
this.children = children || {};
|
|
23
24
|
this.methods = [];
|
|
24
25
|
if (method && handler) {
|
|
25
26
|
const m = {};
|
|
26
|
-
m[method] = handler;
|
|
27
|
+
m[method] = { handler: handler, score: 0, name: this.name };
|
|
27
28
|
this.methods = [m];
|
|
28
29
|
}
|
|
29
30
|
this.patterns = [];
|
|
30
31
|
}
|
|
31
32
|
insert(method, path, handler) {
|
|
33
|
+
this.name = `${method} ${path}`;
|
|
34
|
+
this.order = ++this.order;
|
|
32
35
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
33
36
|
let curNode = this;
|
|
34
37
|
const parts = (0, url_1.splitPath)(path);
|
|
@@ -62,32 +65,39 @@ class Node {
|
|
|
62
65
|
parentPatterns.push(...curNode.patterns);
|
|
63
66
|
curNode = curNode.children[p];
|
|
64
67
|
}
|
|
68
|
+
let score = 1;
|
|
69
|
+
if (path === '*') {
|
|
70
|
+
score = score + this.order * 0.01;
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
score = parts.length + 1 + this.order * 0.01;
|
|
74
|
+
}
|
|
65
75
|
if (!curNode.methods.length) {
|
|
66
76
|
curNode.methods = [];
|
|
67
77
|
}
|
|
68
78
|
const m = {};
|
|
69
|
-
|
|
79
|
+
const handlerSet = { handler: handler, name: this.name, score: score };
|
|
80
|
+
m[method] = handlerSet;
|
|
70
81
|
curNode.methods.push(m);
|
|
71
82
|
return curNode;
|
|
72
83
|
}
|
|
73
|
-
|
|
74
|
-
const
|
|
84
|
+
getHandlerSets(node, method, wildcard) {
|
|
85
|
+
const handlerSets = [];
|
|
75
86
|
node.methods.map((m) => {
|
|
76
|
-
|
|
77
|
-
if (
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
handlers.push(handler);
|
|
87
|
+
const handlerSet = m[method] || m[router_1.METHOD_NAME_ALL];
|
|
88
|
+
if (handlerSet !== undefined) {
|
|
89
|
+
const hs = Object.assign({}, handlerSet);
|
|
90
|
+
if (wildcard) {
|
|
91
|
+
hs.score = handlerSet.score - 1;
|
|
92
|
+
}
|
|
93
|
+
handlerSets.push(hs);
|
|
84
94
|
return;
|
|
85
95
|
}
|
|
86
96
|
});
|
|
87
|
-
return
|
|
97
|
+
return handlerSets;
|
|
88
98
|
}
|
|
89
99
|
next(node, part, method, isLast) {
|
|
90
|
-
const
|
|
100
|
+
const handlerSets = [];
|
|
91
101
|
const nextNodes = [];
|
|
92
102
|
const params = {};
|
|
93
103
|
for (let j = 0, len = node.patterns.length; j < len; j++) {
|
|
@@ -97,7 +107,11 @@ class Node {
|
|
|
97
107
|
if (pattern === '*') {
|
|
98
108
|
const astNode = node.children['*'];
|
|
99
109
|
if (astNode) {
|
|
100
|
-
|
|
110
|
+
let wildcard = false;
|
|
111
|
+
if (!Object.keys(astNode.children).length) {
|
|
112
|
+
wildcard = true;
|
|
113
|
+
}
|
|
114
|
+
handlerSets.push(...this.getHandlerSets(astNode, method, wildcard));
|
|
101
115
|
nextNodes.push(astNode);
|
|
102
116
|
}
|
|
103
117
|
}
|
|
@@ -109,7 +123,7 @@ class Node {
|
|
|
109
123
|
if (matcher === true || (matcher instanceof RegExp && matcher.test(part))) {
|
|
110
124
|
if (typeof key === 'string') {
|
|
111
125
|
if (isLast === true) {
|
|
112
|
-
|
|
126
|
+
handlerSets.push(...this.getHandlerSets(node.children[key], method));
|
|
113
127
|
}
|
|
114
128
|
nextNodes.push(node.children[key]);
|
|
115
129
|
}
|
|
@@ -122,22 +136,22 @@ class Node {
|
|
|
122
136
|
if (nextNode) {
|
|
123
137
|
if (isLast === true) {
|
|
124
138
|
// '/hello/*' => match '/hello'
|
|
125
|
-
if (nextNode.children['*']
|
|
126
|
-
|
|
139
|
+
if (nextNode.children['*']) {
|
|
140
|
+
handlerSets.push(...this.getHandlerSets(nextNode.children['*'], method, true));
|
|
127
141
|
}
|
|
128
|
-
|
|
142
|
+
handlerSets.push(...this.getHandlerSets(nextNode, method));
|
|
129
143
|
}
|
|
130
144
|
nextNodes.push(nextNode);
|
|
131
145
|
}
|
|
132
146
|
const next = {
|
|
133
147
|
nodes: nextNodes,
|
|
134
|
-
|
|
148
|
+
handlerSets: handlerSets,
|
|
135
149
|
params: params,
|
|
136
150
|
};
|
|
137
151
|
return next;
|
|
138
152
|
}
|
|
139
153
|
search(method, path) {
|
|
140
|
-
const
|
|
154
|
+
const handlerSets = [];
|
|
141
155
|
let params = {};
|
|
142
156
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
143
157
|
const curNode = this;
|
|
@@ -152,15 +166,22 @@ class Node {
|
|
|
152
166
|
if (res.nodes.length === 0) {
|
|
153
167
|
continue;
|
|
154
168
|
}
|
|
155
|
-
|
|
169
|
+
handlerSets.push(...res.handlerSets);
|
|
156
170
|
params = Object.assign(params, res.params);
|
|
157
171
|
tempNodes.push(...res.nodes);
|
|
158
172
|
}
|
|
159
173
|
curNodes = tempNodes;
|
|
160
174
|
}
|
|
161
|
-
if (
|
|
175
|
+
if (handlerSets.length <= 0)
|
|
162
176
|
return null;
|
|
163
|
-
|
|
177
|
+
const handlers = handlerSets
|
|
178
|
+
.sort((a, b) => {
|
|
179
|
+
return a.score - b.score;
|
|
180
|
+
})
|
|
181
|
+
.map((s) => {
|
|
182
|
+
return s.handler;
|
|
183
|
+
});
|
|
184
|
+
return { handlers, params };
|
|
164
185
|
}
|
|
165
186
|
}
|
|
166
187
|
exports.Node = Node;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { Router } from '../../router';
|
|
2
|
-
import type { Result } from '../../router';
|
|
1
|
+
import type { Result, Router } from '../../router';
|
|
3
2
|
import { Node } from './node';
|
|
4
|
-
export declare class TrieRouter<T>
|
|
3
|
+
export declare class TrieRouter<T> implements Router<T> {
|
|
5
4
|
node: Node<T>;
|
|
6
5
|
constructor();
|
|
7
6
|
add(method: string, path: string, handler: T): void;
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.TrieRouter = void 0;
|
|
4
|
-
const router_1 = require("../../router");
|
|
5
4
|
const node_1 = require("./node");
|
|
6
|
-
class TrieRouter
|
|
5
|
+
class TrieRouter {
|
|
7
6
|
constructor() {
|
|
8
|
-
super();
|
|
9
7
|
this.node = new node_1.Node();
|
|
10
8
|
}
|
|
11
9
|
add(method, path, handler) {
|
package/dist/router.d.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
export declare const METHOD_NAME_ALL: "ALL";
|
|
2
2
|
export declare const METHOD_NAME_ALL_LOWERCASE: "all";
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
export interface Router<T> {
|
|
4
|
+
add(method: string, path: string, handler: T): void;
|
|
5
|
+
match(method: string, path: string): Result<T> | null;
|
|
6
6
|
}
|
|
7
|
-
export
|
|
7
|
+
export interface Result<T> {
|
|
8
8
|
handlers: T[];
|
|
9
9
|
params: Record<string, string>;
|
|
10
|
-
constructor(handlers: T[], params: Record<string, string>);
|
|
11
10
|
}
|
package/dist/router.js
CHANGED
|
@@ -1,15 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.METHOD_NAME_ALL_LOWERCASE = exports.METHOD_NAME_ALL = void 0;
|
|
4
4
|
exports.METHOD_NAME_ALL = 'ALL';
|
|
5
5
|
exports.METHOD_NAME_ALL_LOWERCASE = 'all';
|
|
6
|
-
class Router {
|
|
7
|
-
}
|
|
8
|
-
exports.Router = Router;
|
|
9
|
-
class Result {
|
|
10
|
-
constructor(handlers, params) {
|
|
11
|
-
this.handlers = handlers;
|
|
12
|
-
this.params = params;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
exports.Result = Result;
|