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 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 737,602 ops/sec ±3.65% (67 runs sampled)
42
- hono - regexp-router x 1,188,203 ops/sec ±6.42% (60 runs sampled)
43
- itty-router x 163,970 ops/sec ±3.05% (91 runs sampled)
44
- sunder x 344,468 ops/sec ±0.87% (97 runs sampled)
45
- worktop x 222,044 ops/sec ±2.13% (85 runs sampled)
46
- Fastest is hono - regexp-router
47
- ✨ Done in 84.04s.
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**(\[パス,\]handler|middleware...)
141
- - app.**all**(\[パス,\]handler|middleware...)
142
- - app.**route**(パス, \[app\])
143
- - app.**use**(\[パス,\]middleware)
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**(パス, options)
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 operate after/before executing Handler. We can get `Response` before dispatching or manipulate `Response` after dispatching.
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, do `await next()`
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
- ## routerClass
545
+ ## router
522
546
 
523
- The `routerClass` option specify which router is used inside. The default router is `TrieRouter`. If you want to use `RexExpRouter`, write like this:
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({ routerClass: RegExpRouter })
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
@@ -28,7 +28,6 @@ const compose = (middleware, onError, onNotFound) => {
28
28
  if (res && context instanceof context_1.Context) {
29
29
  context.res = res;
30
30
  context.res.finalized = true;
31
- await dispatch(i + 1); // <--- Call next()
32
31
  }
33
32
  return context;
34
33
  })
package/dist/context.d.ts CHANGED
@@ -8,7 +8,6 @@ export declare class Context<RequestParamKeyType extends string = string, E = En
8
8
  res: Response;
9
9
  env: E;
10
10
  event: FetchEvent;
11
- private _headers;
12
11
  private _status;
13
12
  private _pretty;
14
13
  private _prettySpace;
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
- if (this.res) {
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
- init.headers = Object.assign(Object.assign({}, this._headers), init.headers);
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 = this._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 routerClass: {
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 _cacheResponse;
61
+ private _cachedResponse;
65
62
  routes: Route<E>[];
66
- constructor(init?: Partial<Pick<Hono, 'routerClass' | 'strict'>>);
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.routerClass = trie_router_1.TrieRouter;
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._cacheResponse = new Response(null, { status: 404 });
52
- this._cacheResponse.finalized = false;
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._router.add(method, path, handler);
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._router.match(method, path);
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._cacheResponse,
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> extends Router<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 extends router_1.Router {
144
+ class RegExpRouter {
134
145
  constructor() {
135
- super(...arguments);
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
- const { routes, methods } = this.routeData;
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(handler);
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
- handlers: [handler],
157
- hint: initHint(path),
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] || primaryMatchers[router_1.METHOD_NAME_ALL];
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 new router_1.Result([...handlers.values()], params);
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 [handler, paramMap] = matcher[1][index];
230
+ const [handlers, paramMap] = matcher[1][index];
213
231
  if (!paramMap) {
214
- return new router_1.Result(handler, emptyParam);
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 new router_1.Result(handler, params);
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].middleware.push(...routes[i].handlers);
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 getHandlers;
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
- m[method] = handler;
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
- getHandlers(node, method) {
74
- const handlers = [];
84
+ getHandlerSets(node, method, wildcard) {
85
+ const handlerSets = [];
75
86
  node.methods.map((m) => {
76
- let handler = m[method];
77
- if (handler !== undefined) {
78
- handlers.push(handler);
79
- return;
80
- }
81
- handler = m[router_1.METHOD_NAME_ALL];
82
- if (handler !== undefined) {
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 handlers;
97
+ return handlerSets;
88
98
  }
89
99
  next(node, part, method, isLast) {
90
- const handlers = [];
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
- handlers.push(...this.getHandlers(astNode, method));
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
- handlers.push(...this.getHandlers(node.children[key], method));
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['*'] !== undefined) {
126
- handlers.push(...this.getHandlers(nextNode.children['*'], method));
139
+ if (nextNode.children['*']) {
140
+ handlerSets.push(...this.getHandlerSets(nextNode.children['*'], method, true));
127
141
  }
128
- handlers.push(...this.getHandlers(nextNode, method));
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
- handlers: handlers,
148
+ handlerSets: handlerSets,
135
149
  params: params,
136
150
  };
137
151
  return next;
138
152
  }
139
153
  search(method, path) {
140
- const handlers = [];
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
- handlers.push(...res.handlers);
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 (handlers.length <= 0)
175
+ if (handlerSets.length <= 0)
162
176
  return null;
163
- return new router_1.Result(handlers, params);
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> extends Router<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 extends router_1.Router {
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 declare abstract class Router<T> {
4
- abstract add(method: string, path: string, handler: T): void;
5
- abstract match(method: string, path: string): Result<T> | null;
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 declare class Result<T> {
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.Result = exports.Router = exports.METHOD_NAME_ALL_LOWERCASE = exports.METHOD_NAME_ALL = void 0;
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hono",
3
- "version": "1.3.6",
3
+ "version": "1.4.0",
4
4
  "description": "Ultrafast web framework for Cloudflare Workers.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",