hono 0.4.0 → 0.4.1

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
@@ -23,12 +23,12 @@ app.fire()
23
23
  **Hono is fastest** compared to other routers for Cloudflare Workers.
24
24
 
25
25
  ```plain
26
- hono x 708,671 ops/sec ±2.58% (58 runs sampled)
27
- itty-router x 159,610 ops/sec ±2.86% (87 runs sampled)
28
- sunder x 322,846 ops/sec ±2.24% (86 runs sampled)
29
- worktop x 223,625 ops/sec ±2.01% (95 runs sampled)
26
+ hono x 779,197 ops/sec ±6.55% (78 runs sampled)
27
+ itty-router x 161,813 ops/sec ±3.87% (87 runs sampled)
28
+ sunder x 334,096 ops/sec ±1.33% (93 runs sampled)
29
+ worktop x 212,661 ops/sec ±4.40% (81 runs sampled)
30
30
  Fastest is hono
31
- ✨ Done in 57.83s.
31
+ ✨ Done in 58.29s.
32
32
  ```
33
33
 
34
34
  ## Hono in 1 minute
@@ -121,16 +121,6 @@ book.get('/:id', (c) => {
121
121
  book.post('/', (c) => c.text('Create Book')) // => POST /book
122
122
  ```
123
123
 
124
- ### Custom 404 Response
125
-
126
- You can customize 404 Not Found response:
127
-
128
- ```js
129
- app.get('*', (c) => {
130
- return c.text('Custom 404 Error', 404)
131
- })
132
- ```
133
-
134
124
  ### no strict
135
125
 
136
126
  If `strict` is set `false`, `/hello`and`/hello/` are treated the same:
@@ -143,7 +133,7 @@ app.get('/hello', (c) => c.text('/hello or /hello/'))
143
133
 
144
134
  Default is `true`.
145
135
 
146
- ## async/await
136
+ ### async/await
147
137
 
148
138
  ```js
149
139
  app.get('/fetch-url', async (c) => {
@@ -191,19 +181,6 @@ app.use('/message/*', async (c, next) => {
191
181
  app.get('/message/hello', (c) => c.text('Hello Middleware!'))
192
182
  ```
193
183
 
194
- ### Handling Error
195
-
196
- ```js
197
- app.use('*', async (c, next) => {
198
- try {
199
- await next()
200
- } catch (err) {
201
- console.error(`${err}`)
202
- c.res = c.text('Custom Error Message', { status: 500 })
203
- }
204
- })
205
- ```
206
-
207
184
  ## Context
208
185
 
209
186
  To handle Request and Reponse, you can use Context object:
@@ -291,6 +268,16 @@ app.get('/', (c) => {
291
268
  })
292
269
  ```
293
270
 
271
+ ### c.notFound()
272
+
273
+ Return the default `404 Not Found` Response:
274
+
275
+ ```js
276
+ app.get('/notfound', (c) => {
277
+ return c.notFound()
278
+ })
279
+ ```
280
+
294
281
  ### c.redirect()
295
282
 
296
283
  Redirect, default status code is `302`:
@@ -332,6 +319,26 @@ app.get('*', async c => {
332
319
  })
333
320
  ```
334
321
 
322
+ ## Not Found
323
+
324
+ If you want, you can set the default `404 Not Found` Response:
325
+
326
+ ```js
327
+ app.notFound = (c) => {
328
+ return c.text('This is default 404 Not Found', 404)
329
+ }
330
+ ```
331
+
332
+ ## Error handling
333
+
334
+ You can handle errors in your way:
335
+
336
+ ```js
337
+ app.onError = (err, c) => {
338
+ return c.text(`This is error message: ${err.mssage}`, 500)
339
+ }
340
+ ```
341
+
335
342
  ## fire
336
343
 
337
344
  `app.fire()` do:
package/dist/compose.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const compose: <T>(middleware: Function[]) => (context: T, next?: Function) => Promise<void | object>;
1
+ export declare const compose: <T>(middleware: Function[], onError?: Function) => (context: T, next?: Function) => Promise<void | object>;
package/dist/compose.js CHANGED
@@ -1,8 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.compose = void 0;
4
+ const context_1 = require("./context");
4
5
  // Based on the code in the MIT licensed `koa-compose` package.
5
- const compose = (middleware) => {
6
+ const compose = (middleware, onError) => {
6
7
  const errors = [];
7
8
  return function (context, next) {
8
9
  let index = -1;
@@ -19,7 +20,12 @@ const compose = (middleware) => {
19
20
  try {
20
21
  return Promise.resolve(fn(context, dispatch.bind(null, i + 1))).catch((e) => {
21
22
  errors.push(e);
22
- throw errors[0]; // XXX
23
+ if (onError && context instanceof context_1.Context) {
24
+ context.res = onError(errors[0], context);
25
+ }
26
+ else {
27
+ throw errors[0];
28
+ }
23
29
  });
24
30
  }
25
31
  catch (err) {
package/dist/context.d.ts CHANGED
@@ -12,6 +12,7 @@ export declare class Context<RequestParamKeyType = string> {
12
12
  private _status;
13
13
  private _statusText;
14
14
  render: (template: string, params?: object, options?: object) => Promise<Response>;
15
+ notFound: () => Response;
15
16
  constructor(req: Request<RequestParamKeyType>, opts?: {
16
17
  res: Response;
17
18
  env: Env;
package/dist/hono.d.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  /// <reference types="@cloudflare/workers-types" />
2
- import type { Result } from './node';
3
2
  import { Node } from './node';
4
3
  import { Context } from './context';
5
4
  import type { Env } from './context';
5
+ import type { Result } from './router';
6
+ import { Router } from './router';
6
7
  declare global {
7
8
  interface Request<ParamKeyType = string> {
8
9
  param: (key: ParamKeyType) => string;
@@ -16,38 +17,38 @@ export declare type MiddlewareHandler = (c: Context, next: Function) => Promise<
16
17
  declare type ParamKeyName<NameWithPattern> = NameWithPattern extends `${infer Name}{${infer _Pattern}` ? Name : NameWithPattern;
17
18
  declare type ParamKey<Component> = Component extends `:${infer NameWithPattern}` ? ParamKeyName<NameWithPattern> : never;
18
19
  declare type ParamKeys<Path> = Path extends `${infer Component}/${infer Rest}` ? ParamKey<Component> | ParamKeys<Rest> : ParamKey<Path>;
19
- export declare class Router<T> {
20
+ export declare class TrieRouter<T> extends Router<T> {
20
21
  node: Node<T>;
21
22
  constructor();
22
23
  add(method: string, path: string, handler: T): void;
23
24
  match(method: string, path: string): Result<T> | null;
24
25
  }
25
- declare type Init = {
26
- strict?: boolean;
27
- };
28
26
  export declare class Hono {
29
- router: Router<Handler[]>;
27
+ routerClass: {
28
+ new (): Router<any>;
29
+ };
30
+ strict: boolean;
31
+ router: Router<Handler>;
30
32
  middlewareRouters: Router<MiddlewareHandler>[];
31
33
  tempPath: string;
32
- strict: boolean;
33
- constructor(init?: Init);
34
- get<Path extends string>(path: Path, ...args: Handler<ParamKeys<Path>>[]): Hono;
35
- post<Path extends string>(path: Path, ...args: Handler<ParamKeys<Path>>[]): Hono;
36
- put<Path extends string>(path: Path, ...args: Handler<ParamKeys<Path>>[]): Hono;
37
- head<Path extends string>(path: Path, ...args: Handler<ParamKeys<Path>>[]): Hono;
38
- delete<Path extends string>(path: Path, ...args: Handler<ParamKeys<Path>>[]): Hono;
39
- options<Path extends string>(path: Path, ...args: Handler<ParamKeys<Path>>[]): Hono;
40
- patch<Path extends string>(path: Path, ...args: Handler<ParamKeys<Path>>[]): Hono;
41
- all<Path extends string>(path: Path, ...args: Handler<ParamKeys<Path>>[]): Hono;
34
+ constructor(init?: Partial<Pick<Hono, 'routerClass' | 'strict'>>);
35
+ get<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono;
36
+ post<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono;
37
+ put<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono;
38
+ head<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono;
39
+ delete<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono;
40
+ options<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono;
41
+ patch<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono;
42
+ all<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono;
42
43
  route(path: string): Hono;
43
44
  use(path: string, middleware: MiddlewareHandler): void;
44
- addRoute(method: string, path: string, ...args: Handler[]): Hono;
45
- matchRoute(method: string, path: string): Promise<Result<Handler[]>>;
45
+ addRoute(method: string, path: string, handler: Handler): Hono;
46
+ matchRoute(method: string, path: string): Promise<Result<Handler>>;
46
47
  dispatch(request: Request, env?: Env, event?: FetchEvent): Promise<Response>;
47
48
  handleEvent(event: FetchEvent): Promise<Response>;
48
49
  fetch(request: Request, env?: Env, event?: FetchEvent): Promise<Response>;
49
50
  fire(): void;
50
- onError(err: Error): Response;
51
- notFound(): Response;
51
+ onError(err: Error, c: Context): Response;
52
+ notFound(c: Context): Response;
52
53
  }
53
54
  export {};
package/dist/hono.js CHANGED
@@ -1,12 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Hono = exports.Router = void 0;
3
+ exports.Hono = exports.TrieRouter = void 0;
4
4
  const node_1 = require("./node");
5
5
  const compose_1 = require("./compose");
6
6
  const url_1 = require("./utils/url");
7
7
  const context_1 = require("./context");
8
- class Router {
8
+ const router_1 = require("./router");
9
+ class TrieRouter extends router_1.Router {
9
10
  constructor() {
11
+ super();
10
12
  this.node = new node_1.Node();
11
13
  }
12
14
  add(method, path, handler) {
@@ -16,37 +18,39 @@ class Router {
16
18
  return this.node.search(method, path);
17
19
  }
18
20
  }
19
- exports.Router = Router;
21
+ exports.TrieRouter = TrieRouter;
20
22
  class Hono {
21
- constructor(init = { strict: true }) {
22
- this.router = new Router();
23
+ constructor(init = {}) {
24
+ this.routerClass = TrieRouter;
25
+ this.strict = true; // strict routing - default is true
26
+ Object.assign(this, init);
27
+ this.router = new this.routerClass();
23
28
  this.middlewareRouters = [];
24
29
  this.tempPath = null;
25
- this.strict = init.strict; // strict routing - default is true
26
30
  }
27
- get(path, ...args) {
28
- return this.addRoute('get', path, ...args);
31
+ get(path, handler) {
32
+ return this.addRoute('get', path, handler);
29
33
  }
30
- post(path, ...args) {
31
- return this.addRoute('post', path, ...args);
34
+ post(path, handler) {
35
+ return this.addRoute('post', path, handler);
32
36
  }
33
- put(path, ...args) {
34
- return this.addRoute('put', path, ...args);
37
+ put(path, handler) {
38
+ return this.addRoute('put', path, handler);
35
39
  }
36
- head(path, ...args) {
37
- return this.addRoute('head', path, ...args);
40
+ head(path, handler) {
41
+ return this.addRoute('head', path, handler);
38
42
  }
39
- delete(path, ...args) {
40
- return this.addRoute('delete', path, ...args);
43
+ delete(path, handler) {
44
+ return this.addRoute('delete', path, handler);
41
45
  }
42
- options(path, ...args) {
43
- return this.addRoute('options', path, ...args);
46
+ options(path, handler) {
47
+ return this.addRoute('options', path, handler);
44
48
  }
45
- patch(path, ...args) {
46
- return this.addRoute('patch', path, ...args);
49
+ patch(path, handler) {
50
+ return this.addRoute('patch', path, handler);
47
51
  }
48
- all(path, ...args) {
49
- return this.addRoute('all', path, ...args);
52
+ all(path, handler) {
53
+ return this.addRoute('all', path, handler);
50
54
  }
51
55
  route(path) {
52
56
  const newHono = new Hono();
@@ -58,17 +62,17 @@ class Hono {
58
62
  if (middleware.constructor.name !== 'AsyncFunction') {
59
63
  throw new TypeError('middleware must be a async function!');
60
64
  }
61
- const router = new Router();
62
- router.add(node_1.METHOD_NAME_OF_ALL, path, middleware);
65
+ const router = new this.routerClass();
66
+ router.add(router_1.METHOD_NAME_OF_ALL, path, middleware);
63
67
  this.middlewareRouters.push(router);
64
68
  }
65
69
  // addRoute('get', '/', handler)
66
- addRoute(method, path, ...args) {
70
+ addRoute(method, path, handler) {
67
71
  method = method.toUpperCase();
68
72
  if (this.tempPath) {
69
73
  path = (0, url_1.mergePath)(this.tempPath, path);
70
74
  }
71
- this.router.add(method, path, args);
75
+ this.router.add(method, path, handler);
72
76
  return this;
73
77
  }
74
78
  async matchRoute(method, path) {
@@ -91,10 +95,10 @@ class Hono {
91
95
  const url = new URL(c.req.url);
92
96
  return url.searchParams.get(key);
93
97
  };
94
- const handler = result ? result.handler[0] : this.notFound; // XXX
98
+ const handler = result ? result.handler : this.notFound;
95
99
  const middleware = [];
96
100
  for (const mr of this.middlewareRouters) {
97
- const mwResult = mr.match(node_1.METHOD_NAME_OF_ALL, path);
101
+ const mwResult = mr.match(router_1.METHOD_NAME_OF_ALL, path);
98
102
  if (mwResult) {
99
103
  middleware.push(mwResult.handler);
100
104
  }
@@ -108,44 +112,33 @@ class Hono {
108
112
  await next();
109
113
  };
110
114
  middleware.push(wrappedHandler);
111
- const composed = (0, compose_1.compose)(middleware);
115
+ const composed = (0, compose_1.compose)(middleware, this.onError);
112
116
  const c = new context_1.Context(request, { env: env, event: event, res: null });
117
+ c.notFound = () => this.notFound(c);
113
118
  await composed(c);
114
119
  return c.res;
115
120
  }
116
121
  async handleEvent(event) {
117
- return this.dispatch(event.request, {}, event).catch((err) => {
118
- return this.onError(err);
119
- });
122
+ return this.dispatch(event.request, {}, event);
120
123
  }
121
124
  async fetch(request, env, event) {
122
- return this.dispatch(request, env, event).catch((err) => {
123
- return this.onError(err);
124
- });
125
+ return this.dispatch(request, env, event);
125
126
  }
126
127
  fire() {
127
128
  addEventListener('fetch', (event) => {
128
129
  event.respondWith(this.handleEvent(event));
129
130
  });
130
131
  }
131
- onError(err) {
132
- console.error(`${err}`);
132
+ // Default error Response
133
+ onError(err, c) {
134
+ console.error(`${err.message}`);
133
135
  const message = 'Internal Server Error';
134
- return new Response(message, {
135
- status: 500,
136
- headers: {
137
- 'Content-Length': message.length.toString(),
138
- },
139
- });
136
+ return c.text(message, 500);
140
137
  }
141
- notFound() {
138
+ // Default 404 not found Response
139
+ notFound(c) {
142
140
  const message = 'Not Found';
143
- return new Response(message, {
144
- status: 404,
145
- headers: {
146
- 'Content-Length': message.length.toString(),
147
- },
148
- });
141
+ return c.text(message, 404);
149
142
  }
150
143
  }
151
144
  exports.Hono = Hono;
@@ -12,9 +12,7 @@ const humanize = (n, opts) => {
12
12
  };
13
13
  const time = (start) => {
14
14
  const delta = Date.now() - start;
15
- return humanize([
16
- delta < 10000 ? delta + 'ms' : Math.round(delta / 1000) + 's',
17
- ]);
15
+ return humanize([delta < 10000 ? delta + 'ms' : Math.round(delta / 1000) + 's']);
18
16
  };
19
17
  const LogPrefix = {
20
18
  Outgoing: '-->',
@@ -45,19 +43,9 @@ const logger = (fn = console.log) => {
45
43
  const path = (0, url_1.getPathFromURL)(c.req.url);
46
44
  log(fn, LogPrefix.Incoming, method, path);
47
45
  const start = Date.now();
48
- try {
49
- await next();
50
- }
51
- catch (e) {
52
- log(fn, LogPrefix.Error, method, path, c.res.status || 500, time(start));
53
- throw e;
54
- }
46
+ await next();
55
47
  const len = parseFloat(c.res.headers.get('Content-Length'));
56
- const contentLength = isNaN(len)
57
- ? '0'
58
- : len < 1024
59
- ? `${len}b`
60
- : `${len / 1024}kB`;
48
+ const contentLength = isNaN(len) ? '0' : len < 1024 ? `${len}b` : `${len / 1024}kB`;
61
49
  log(fn, LogPrefix.Outgoing, method, path, c.res.status, time(start), contentLength);
62
50
  };
63
51
  };
@@ -9,7 +9,11 @@ const serveStatic = (opt = { root: '' }) => {
9
9
  return async (c, next) => {
10
10
  await next();
11
11
  const url = new URL(c.req.url);
12
- const path = (0, cloudflare_1.getKVFilePath)({ filename: url.pathname, root: opt.root, defaultDocument: DEFAULT_DOCUMENT });
12
+ const path = (0, cloudflare_1.getKVFilePath)({
13
+ filename: url.pathname,
14
+ root: opt.root,
15
+ defaultDocument: DEFAULT_DOCUMENT,
16
+ });
13
17
  const content = await (0, cloudflare_1.getContentFromKVAsset)(path);
14
18
  if (content) {
15
19
  const mimeType = (0, mime_1.getMimeType)(path);
package/dist/node.d.ts CHANGED
@@ -1,14 +1,11 @@
1
- export declare const METHOD_NAME_OF_ALL = "ALL";
2
- export declare class Result<T> {
3
- handler: T;
4
- params: Record<string, string>;
5
- constructor(handler: T, params: Record<string, string>);
6
- }
1
+ import type { Pattern } from './utils/url';
2
+ import { Result } from './router';
7
3
  export declare class Node<T> {
8
4
  method: Record<string, T>;
9
5
  handler: T;
10
6
  children: Record<string, Node<T>>;
11
7
  middlewares: [];
8
+ patterns: Pattern[];
12
9
  constructor(method?: string, handler?: T, children?: Record<string, Node<T>>);
13
10
  insert(method: string, path: string, handler: T): Node<T>;
14
11
  search(method: string, path: string): Result<T>;
package/dist/node.js CHANGED
@@ -1,15 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Node = exports.Result = exports.METHOD_NAME_OF_ALL = void 0;
3
+ exports.Node = void 0;
4
4
  const url_1 = require("./utils/url");
5
- exports.METHOD_NAME_OF_ALL = 'ALL';
6
- class Result {
7
- constructor(handler, params) {
8
- this.handler = handler;
9
- this.params = params;
10
- }
11
- }
12
- exports.Result = Result;
5
+ const router_1 = require("./router");
13
6
  const noRoute = () => {
14
7
  return null;
15
8
  };
@@ -21,6 +14,7 @@ class Node {
21
14
  this.method[method] = handler;
22
15
  }
23
16
  this.middlewares = [];
17
+ this.patterns = [];
24
18
  }
25
19
  insert(method, path, handler) {
26
20
  // eslint-disable-next-line @typescript-eslint/no-this-alias
@@ -33,6 +27,10 @@ class Node {
33
27
  continue;
34
28
  }
35
29
  curNode.children[p] = new Node();
30
+ const pattern = (0, url_1.getPattern)(p);
31
+ if (pattern) {
32
+ curNode.patterns.push(pattern);
33
+ }
36
34
  curNode = curNode.children[p];
37
35
  }
38
36
  curNode.method[method] = handler;
@@ -64,29 +62,24 @@ class Node {
64
62
  }
65
63
  let isWildcard = false;
66
64
  let isParamMatch = false;
67
- const keys = Object.keys(curNode.children);
68
- for (let j = 0, len = keys.length; j < len; j++) {
69
- const key = keys[j];
65
+ for (let j = 0, len = curNode.patterns.length; j < len; j++) {
66
+ const pattern = curNode.patterns[j];
70
67
  // Wildcard
71
68
  // '/hello/*/foo' => match /hello/bar/foo
72
- if (key === '*') {
69
+ if (pattern === '*') {
73
70
  curNode = curNode.children['*'];
74
71
  isWildcard = true;
75
72
  break;
76
73
  }
77
- const pattern = (0, url_1.getPattern)(key);
78
74
  // Named match
79
- if (pattern) {
80
- const match = p.match(new RegExp(pattern[1]));
81
- if (match) {
82
- const k = pattern[0];
83
- params[k] = match[1];
84
- curNode = curNode.children[key];
85
- isParamMatch = true;
86
- break;
87
- }
88
- return noRoute();
75
+ const [key, name, matcher] = pattern;
76
+ if (p !== '' && (matcher === true || matcher.test(p))) {
77
+ params[name] = p;
78
+ curNode = curNode.children[key];
79
+ isParamMatch = true;
80
+ break;
89
81
  }
82
+ return noRoute();
90
83
  }
91
84
  if (isWildcard && i === len - 1) {
92
85
  break;
@@ -95,11 +88,11 @@ class Node {
95
88
  return noRoute();
96
89
  }
97
90
  }
98
- const handler = curNode.method[exports.METHOD_NAME_OF_ALL] || curNode.method[method];
91
+ const handler = curNode.method[router_1.METHOD_NAME_OF_ALL] || curNode.method[method];
99
92
  if (!handler) {
100
93
  return noRoute();
101
94
  }
102
- return new Result(handler, params);
95
+ return new router_1.Result(handler, params);
103
96
  }
104
97
  }
105
98
  exports.Node = Node;
@@ -0,0 +1 @@
1
+ export { RegExpRouter } from './router';
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RegExpRouter = void 0;
4
+ var router_1 = require("./router");
5
+ Object.defineProperty(exports, "RegExpRouter", { enumerable: true, get: function () { return router_1.RegExpRouter; } });
@@ -0,0 +1,15 @@
1
+ export interface ParamMap {
2
+ [key: string]: number;
3
+ }
4
+ export interface Context {
5
+ varIndex: number;
6
+ }
7
+ export declare class Node {
8
+ index?: number;
9
+ varIndex?: number;
10
+ children: {
11
+ [key: string]: Node;
12
+ };
13
+ insert(tokens: readonly string[], index: number, paramMap: ParamMap, context: Context): void;
14
+ buildRegExpStr(): string;
15
+ }
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Node = void 0;
4
+ const LABEL_REG_EXP_STR = '[^/]+';
5
+ const ONLY_WILDCARD_REG_EXP_STR = '.*';
6
+ const TAIL_WILDCARD_REG_EXP_STR = '(?:|/.*)';
7
+ /**
8
+ * Sort order:
9
+ * 1. literal
10
+ * 2. special pattern (e.g. :label{[0-9]+})
11
+ * 3. common label pattern (e.g. :label)
12
+ * 4. wildcard
13
+ */
14
+ function compareKey(a, b) {
15
+ if (a.length === 1) {
16
+ return b.length === 1 ? (a < b ? -1 : 1) : -1;
17
+ }
18
+ if (b.length === 1) {
19
+ return 1;
20
+ }
21
+ // wildcard
22
+ if (a === ONLY_WILDCARD_REG_EXP_STR || a === TAIL_WILDCARD_REG_EXP_STR) {
23
+ return 1;
24
+ }
25
+ else if (b === ONLY_WILDCARD_REG_EXP_STR || b === TAIL_WILDCARD_REG_EXP_STR) {
26
+ return -1;
27
+ }
28
+ // label
29
+ if (a === LABEL_REG_EXP_STR) {
30
+ return 1;
31
+ }
32
+ else if (b === LABEL_REG_EXP_STR) {
33
+ return -1;
34
+ }
35
+ return a.length === b.length ? (a < b ? -1 : 1) : b.length - a.length;
36
+ }
37
+ class Node {
38
+ constructor() {
39
+ this.children = {};
40
+ }
41
+ insert(tokens, index, paramMap, context) {
42
+ var _a;
43
+ if (tokens.length === 0) {
44
+ this.index = index;
45
+ return;
46
+ }
47
+ const [token, ...restTokens] = tokens;
48
+ const pattern = token === '*'
49
+ ? restTokens.length === 0
50
+ ? ['', '', ONLY_WILDCARD_REG_EXP_STR] // '*' matches to all the trailing paths
51
+ : ['', '', LABEL_REG_EXP_STR]
52
+ : token === '/*'
53
+ ? ['', '', TAIL_WILDCARD_REG_EXP_STR] // '/path/to/*' is /\/path\/to(?:|/.*)$
54
+ : token.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/);
55
+ let node;
56
+ if (pattern) {
57
+ const name = pattern[1];
58
+ const regexpStr = pattern[2] || LABEL_REG_EXP_STR;
59
+ node = this.children[regexpStr];
60
+ if (!node) {
61
+ node = this.children[regexpStr] = new Node();
62
+ if (name !== '') {
63
+ node.varIndex = context.varIndex++;
64
+ }
65
+ }
66
+ if (name !== '') {
67
+ paramMap[name] = node.varIndex;
68
+ }
69
+ }
70
+ else {
71
+ node = (_a = this.children)[token] || (_a[token] = new Node());
72
+ }
73
+ node.insert(restTokens, index, paramMap, context);
74
+ }
75
+ buildRegExpStr() {
76
+ const strList = Object.keys(this.children)
77
+ .sort(compareKey)
78
+ .map((k) => {
79
+ const c = this.children[k];
80
+ return (typeof c.varIndex === 'number' ? `(${k})@${c.varIndex}` : k) + c.buildRegExpStr();
81
+ });
82
+ if (typeof this.index === 'number') {
83
+ strList.push(`#${this.index}`);
84
+ }
85
+ if (strList.length === 0) {
86
+ return '';
87
+ }
88
+ if (strList.length === 1) {
89
+ return strList[0];
90
+ }
91
+ return '(?:' + strList.join('|') + ')';
92
+ }
93
+ }
94
+ exports.Node = Node;
@@ -0,0 +1,18 @@
1
+ import { Router, Result } from '../../router';
2
+ import type { ParamMap, ReplacementMap } from './trie';
3
+ declare type Route<T> = [string, T];
4
+ declare type HandlerData<T> = [T, ParamMap];
5
+ declare type Matcher<T> = [RegExp | true, ReplacementMap, HandlerData<T>[]];
6
+ export declare class RegExpRouter<T> extends Router<T> {
7
+ routes?: {
8
+ [method: string]: Route<T>[];
9
+ };
10
+ matchers?: {
11
+ [method: string]: Matcher<T> | null;
12
+ };
13
+ add(method: string, path: string, handler: T): void;
14
+ match(method: string, path: string): Result<T> | null;
15
+ private buildAllMatchers;
16
+ private buildMatcher;
17
+ }
18
+ export {};
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RegExpRouter = void 0;
4
+ const router_1 = require("../../router");
5
+ const trie_1 = require("./trie");
6
+ class RegExpRouter extends router_1.Router {
7
+ constructor() {
8
+ super(...arguments);
9
+ this.routes = {};
10
+ this.matchers = null;
11
+ }
12
+ add(method, path, handler) {
13
+ var _a;
14
+ if (!this.routes) {
15
+ throw new Error('Can not add a route since the matcher is already built.');
16
+ }
17
+ (_a = this.routes)[method] || (_a[method] = []);
18
+ this.routes[method].push([path, handler]);
19
+ }
20
+ match(method, path) {
21
+ if (!this.matchers) {
22
+ this.buildAllMatchers();
23
+ }
24
+ const matcher = this.matchers[method] || this.matchers[router_1.METHOD_NAME_OF_ALL];
25
+ if (!matcher) {
26
+ return null;
27
+ }
28
+ const [regexp, replacementMap, handlers] = matcher;
29
+ if (regexp === true) {
30
+ // '*'
31
+ return new router_1.Result(handlers[0][0], {});
32
+ }
33
+ const match = path.match(regexp);
34
+ if (!match) {
35
+ return null;
36
+ }
37
+ if (!replacementMap) {
38
+ // there is only one route and no capture
39
+ return new router_1.Result(handlers[0][0], {});
40
+ }
41
+ const index = match.indexOf('', 1);
42
+ const [handler, paramMap] = handlers[replacementMap[index]];
43
+ const params = {};
44
+ const keys = Object.keys(paramMap);
45
+ for (let i = 0; i < keys.length; i++) {
46
+ params[keys[i]] = match[paramMap[keys[i]]];
47
+ }
48
+ return new router_1.Result(handler, params);
49
+ }
50
+ buildAllMatchers() {
51
+ this.matchers || (this.matchers = {});
52
+ Object.keys(this.routes).forEach((method) => {
53
+ this.buildMatcher(method);
54
+ });
55
+ delete this.routes; // to reduce memory usage
56
+ }
57
+ buildMatcher(method) {
58
+ this.matchers || (this.matchers = {});
59
+ const trie = new trie_1.Trie();
60
+ const handlers = [];
61
+ const targetMethods = [method];
62
+ if (method !== router_1.METHOD_NAME_OF_ALL) {
63
+ targetMethods.unshift(router_1.METHOD_NAME_OF_ALL);
64
+ }
65
+ const routes = targetMethods.flatMap((method) => this.routes[method] || []);
66
+ if (routes.length === 0) {
67
+ this.matchers[method] = null;
68
+ return;
69
+ }
70
+ if (routes.length === 1 && routes[0][0] === '*') {
71
+ this.matchers[method] = [true, null, [[routes[0][1], {}]]];
72
+ return;
73
+ }
74
+ if (routes.length === 1 && !routes[0][0].match(/:/)) {
75
+ // there is only one route and no capture
76
+ const tmp = routes[0][0].endsWith('*')
77
+ ? routes[0][0].replace(/\/\*$/, '(?:$|/)') // /path/to/* => /path/to(?:$|/)
78
+ : `${routes[0][0]}$`; // /path/to/action => /path/to/action$
79
+ const regExpStr = `^${tmp.replace(/\*/g, '[^/]+')}`; // /prefix/*/path/to => /prefix/[^/]+/path/to
80
+ this.matchers[method] = [new RegExp(regExpStr), null, [[routes[0][1], {}]]];
81
+ return;
82
+ }
83
+ for (let i = 0; i < routes.length; i++) {
84
+ const paramMap = trie.insert(routes[i][0], i);
85
+ handlers[i] = [routes[i][1], paramMap];
86
+ }
87
+ const [regexp, indexReplacementMap, paramReplacementMap] = trie.buildRegExp();
88
+ for (let i = 0; i < handlers.length; i++) {
89
+ const paramMap = handlers[i][1];
90
+ Object.keys(paramMap).forEach((k) => {
91
+ paramMap[k] = paramReplacementMap[paramMap[k]];
92
+ });
93
+ }
94
+ this.matchers[method] = [new RegExp(regexp), indexReplacementMap, handlers];
95
+ }
96
+ }
97
+ exports.RegExpRouter = RegExpRouter;
@@ -0,0 +1,10 @@
1
+ import type { ParamMap, Context } from './node';
2
+ import { Node } from './node';
3
+ export type { ParamMap } from './node';
4
+ export declare type ReplacementMap = number[];
5
+ export declare class Trie {
6
+ context: Context;
7
+ root: Node;
8
+ insert(path: string, index: number): ParamMap;
9
+ buildRegExp(): [RegExp, ReplacementMap, ReplacementMap];
10
+ }
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Trie = void 0;
4
+ const node_1 = require("./node");
5
+ class Trie {
6
+ constructor() {
7
+ this.context = { varIndex: 0 };
8
+ this.root = new node_1.Node();
9
+ }
10
+ insert(path, index) {
11
+ const paramMap = {};
12
+ /**
13
+ * - pattern (:label, :label{0-9]+}, ...)
14
+ * - /* wildcard
15
+ * - character
16
+ */
17
+ const tokens = path.match(/(?::[^\/]+)|(?:\/\*$)|./g);
18
+ this.root.insert(tokens, index, paramMap, this.context);
19
+ return paramMap;
20
+ }
21
+ buildRegExp() {
22
+ let regexp = this.root.buildRegExpStr();
23
+ let captureIndex = 0;
24
+ const indexReplacementMap = [];
25
+ const paramReplacementMap = [];
26
+ regexp = regexp.replace(/#(\d+)|@(\d+)|\.\*\$/g, (_, handlerIndex, paramIndex) => {
27
+ if (typeof handlerIndex !== 'undefined') {
28
+ indexReplacementMap[++captureIndex] = Number(handlerIndex);
29
+ return '$()';
30
+ }
31
+ if (typeof paramIndex !== 'undefined') {
32
+ paramReplacementMap[Number(paramIndex)] = ++captureIndex;
33
+ return '';
34
+ }
35
+ return '';
36
+ });
37
+ return [new RegExp(`^${regexp}`), indexReplacementMap, paramReplacementMap];
38
+ }
39
+ }
40
+ exports.Trie = Trie;
@@ -0,0 +1,10 @@
1
+ export declare const METHOD_NAME_OF_ALL = "ALL";
2
+ export declare abstract class Router<T> {
3
+ abstract add(method: string, path: string, handler: T): void;
4
+ abstract match(method: string, path: string): Result<T> | null;
5
+ }
6
+ export declare class Result<T> {
7
+ handler: T;
8
+ params: Record<string, string>;
9
+ constructor(handler: T, params: Record<string, string>);
10
+ }
package/dist/router.js ADDED
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Result = exports.Router = exports.METHOD_NAME_OF_ALL = void 0;
4
+ exports.METHOD_NAME_OF_ALL = 'ALL';
5
+ class Router {
6
+ }
7
+ exports.Router = Router;
8
+ class Result {
9
+ constructor(handler, params) {
10
+ this.handler = handler;
11
+ this.params = params;
12
+ }
13
+ }
14
+ exports.Result = Result;
@@ -46,7 +46,9 @@ const sha256 = async (a) => {
46
46
  const buffer = await crypto.subtle.digest({
47
47
  name: 'SHA-256',
48
48
  }, new TextEncoder().encode(String(a)));
49
- const hash = Array.prototype.map.call(new Uint8Array(buffer), (x) => ('00' + x.toString(16)).slice(-2)).join('');
49
+ const hash = Array.prototype.map
50
+ .call(new Uint8Array(buffer), (x) => ('00' + x.toString(16)).slice(-2))
51
+ .join('');
50
52
  return hash;
51
53
  }
52
54
  try {
@@ -1,5 +1,6 @@
1
+ export declare type Pattern = readonly [string, string, RegExp | true] | '*';
1
2
  export declare const splitPath: (path: string) => string[];
2
- export declare const getPattern: (label: string) => string[] | null;
3
+ export declare const getPattern: (label: string) => Pattern | null;
3
4
  declare type Params = {
4
5
  strict: boolean;
5
6
  };
package/dist/utils/url.js CHANGED
@@ -10,18 +10,26 @@ const splitPath = (path) => {
10
10
  return paths;
11
11
  };
12
12
  exports.splitPath = splitPath;
13
+ const patternCache = {};
13
14
  const getPattern = (label) => {
15
+ // * => wildcard
14
16
  // :id{[0-9]+} => ([0-9]+)
15
17
  // :id => (.+)
16
18
  //const name = ''
19
+ if (label === '*') {
20
+ return '*';
21
+ }
17
22
  const match = label.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/);
18
23
  if (match) {
19
- if (match[2]) {
20
- return [match[1], '(' + match[2] + ')'];
21
- }
22
- else {
23
- return [match[1], '(.+)'];
24
+ if (!patternCache[label]) {
25
+ if (match[2]) {
26
+ patternCache[label] = [label, match[1], new RegExp('^' + match[2] + '$')];
27
+ }
28
+ else {
29
+ patternCache[label] = [label, match[1], true];
30
+ }
24
31
  }
32
+ return patternCache[label];
25
33
  }
26
34
  return null;
27
35
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hono",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "[炎] Ultrafast web framework for Cloudflare Workers.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -50,7 +50,7 @@
50
50
  },
51
51
  "scripts": {
52
52
  "test": "jest",
53
- "lint": "eslint --ext js,ts src .eslintrc.js test",
53
+ "lint": "eslint --ext js,ts src .eslintrc.js test && prettier --check src",
54
54
  "build": "rimraf dist && tsc",
55
55
  "watch": "tsc -w",
56
56
  "prepublishOnly": "yarn build"
@@ -82,18 +82,18 @@
82
82
  "@typescript-eslint/eslint-plugin": "^5.9.0",
83
83
  "@typescript-eslint/parser": "^5.9.0",
84
84
  "eslint": "^7.26.0",
85
- "eslint-config-prettier": "^8.1.0",
85
+ "eslint-config-prettier": "^8.3.0",
86
86
  "eslint-define-config": "^1.2.1",
87
87
  "eslint-import-resolver-typescript": "^2.0.0",
88
88
  "eslint-plugin-eslint-comments": "^3.2.0",
89
89
  "eslint-plugin-flowtype": "^5.7.2",
90
90
  "eslint-plugin-import": "^2.20.2",
91
91
  "eslint-plugin-node": "^11.1.0",
92
- "eslint-plugin-prettier": "^4.0.0",
93
92
  "form-data": "^4.0.0",
94
93
  "jest": "^27.4.5",
95
94
  "jest-environment-miniflare": "^2.0.0",
96
95
  "mustache": "^4.2.0",
96
+ "prettier": "^2.5.1",
97
97
  "rimraf": "^3.0.2",
98
98
  "ts-jest": "^27.1.2",
99
99
  "typescript": "^4.5.5"