hono 0.4.0 → 0.5.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/dist/hono.js CHANGED
@@ -1,52 +1,52 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Hono = exports.Router = void 0;
4
- const node_1 = require("./node");
3
+ exports.Hono = void 0;
5
4
  const compose_1 = require("./compose");
6
5
  const url_1 = require("./utils/url");
7
6
  const context_1 = require("./context");
8
- class Router {
9
- constructor() {
10
- this.node = new node_1.Node();
11
- }
12
- add(method, path, handler) {
13
- this.node.insert(method, path, handler);
14
- }
15
- match(method, path) {
16
- return this.node.search(method, path);
17
- }
18
- }
19
- exports.Router = Router;
7
+ const router_1 = require("./router");
8
+ const trie_router_1 = require("./router/trie-router"); // Default Router
20
9
  class Hono {
21
- constructor(init = { strict: true }) {
22
- this.router = new Router();
10
+ constructor(init = {}) {
11
+ this.routerClass = trie_router_1.TrieRouter;
12
+ this.strict = true; // strict routing - default is true
13
+ this.notFoundHandler = (c) => {
14
+ const message = '404 Not Found';
15
+ return c.text(message, 404);
16
+ };
17
+ this.errorHandler = (err, c) => {
18
+ console.error(`${err.message}`);
19
+ const message = 'Internal Server Error';
20
+ return c.text(message, 500);
21
+ };
22
+ Object.assign(this, init);
23
+ this.router = new this.routerClass();
23
24
  this.middlewareRouters = [];
24
25
  this.tempPath = null;
25
- this.strict = init.strict; // strict routing - default is true
26
26
  }
27
- get(path, ...args) {
28
- return this.addRoute('get', path, ...args);
27
+ get(path, handler) {
28
+ return this.addRoute('get', path, handler);
29
29
  }
30
- post(path, ...args) {
31
- return this.addRoute('post', path, ...args);
30
+ post(path, handler) {
31
+ return this.addRoute('post', path, handler);
32
32
  }
33
- put(path, ...args) {
34
- return this.addRoute('put', path, ...args);
33
+ put(path, handler) {
34
+ return this.addRoute('put', path, handler);
35
35
  }
36
- head(path, ...args) {
37
- return this.addRoute('head', path, ...args);
36
+ head(path, handler) {
37
+ return this.addRoute('head', path, handler);
38
38
  }
39
- delete(path, ...args) {
40
- return this.addRoute('delete', path, ...args);
39
+ delete(path, handler) {
40
+ return this.addRoute('delete', path, handler);
41
41
  }
42
- options(path, ...args) {
43
- return this.addRoute('options', path, ...args);
42
+ options(path, handler) {
43
+ return this.addRoute('options', path, handler);
44
44
  }
45
- patch(path, ...args) {
46
- return this.addRoute('patch', path, ...args);
45
+ patch(path, handler) {
46
+ return this.addRoute('patch', path, handler);
47
47
  }
48
- all(path, ...args) {
49
- return this.addRoute('all', path, ...args);
48
+ all(path, handler) {
49
+ return this.addRoute('all', path, handler);
50
50
  }
51
51
  route(path) {
52
52
  const newHono = new Hono();
@@ -58,17 +58,25 @@ class Hono {
58
58
  if (middleware.constructor.name !== 'AsyncFunction') {
59
59
  throw new TypeError('middleware must be a async function!');
60
60
  }
61
- const router = new Router();
62
- router.add(node_1.METHOD_NAME_OF_ALL, path, middleware);
61
+ const router = new this.routerClass();
62
+ router.add(router_1.METHOD_NAME_OF_ALL, path, middleware);
63
63
  this.middlewareRouters.push(router);
64
64
  }
65
+ onError(handler) {
66
+ this.errorHandler = handler;
67
+ return this;
68
+ }
69
+ notFound(handler) {
70
+ this.notFoundHandler = handler;
71
+ return this;
72
+ }
65
73
  // addRoute('get', '/', handler)
66
- addRoute(method, path, ...args) {
74
+ addRoute(method, path, handler) {
67
75
  method = method.toUpperCase();
68
76
  if (this.tempPath) {
69
77
  path = (0, url_1.mergePath)(this.tempPath, path);
70
78
  }
71
- this.router.add(method, path, args);
79
+ this.router.add(method, path, handler);
72
80
  return this;
73
81
  }
74
82
  async matchRoute(method, path) {
@@ -91,10 +99,10 @@ class Hono {
91
99
  const url = new URL(c.req.url);
92
100
  return url.searchParams.get(key);
93
101
  };
94
- const handler = result ? result.handler[0] : this.notFound; // XXX
102
+ const handler = result ? result.handler : this.notFoundHandler;
95
103
  const middleware = [];
96
104
  for (const mr of this.middlewareRouters) {
97
- const mwResult = mr.match(node_1.METHOD_NAME_OF_ALL, path);
105
+ const mwResult = mr.match(router_1.METHOD_NAME_OF_ALL, path);
98
106
  if (mwResult) {
99
107
  middleware.push(mwResult.handler);
100
108
  }
@@ -108,44 +116,22 @@ class Hono {
108
116
  await next();
109
117
  };
110
118
  middleware.push(wrappedHandler);
111
- const composed = (0, compose_1.compose)(middleware);
119
+ const composed = (0, compose_1.compose)(middleware, this.errorHandler);
112
120
  const c = new context_1.Context(request, { env: env, event: event, res: null });
113
- await composed(c);
114
- return c.res;
121
+ c.notFound = () => this.notFoundHandler(c);
122
+ const context = await composed(c);
123
+ return context.res;
115
124
  }
116
125
  async handleEvent(event) {
117
- return this.dispatch(event.request, {}, event).catch((err) => {
118
- return this.onError(err);
119
- });
126
+ return this.dispatch(event.request, {}, event);
120
127
  }
121
128
  async fetch(request, env, event) {
122
- return this.dispatch(request, env, event).catch((err) => {
123
- return this.onError(err);
124
- });
129
+ return this.dispatch(request, env, event);
125
130
  }
126
131
  fire() {
127
132
  addEventListener('fetch', (event) => {
128
133
  event.respondWith(this.handleEvent(event));
129
134
  });
130
135
  }
131
- onError(err) {
132
- console.error(`${err}`);
133
- const message = 'Internal Server Error';
134
- return new Response(message, {
135
- status: 500,
136
- headers: {
137
- 'Content-Length': message.length.toString(),
138
- },
139
- });
140
- }
141
- notFound() {
142
- const message = 'Not Found';
143
- return new Response(message, {
144
- status: 404,
145
- headers: {
146
- 'Content-Length': message.length.toString(),
147
- },
148
- });
149
- }
150
136
  }
151
137
  exports.Hono = Hono;
@@ -3,4 +3,7 @@ export declare const basicAuth: (options: {
3
3
  username: string;
4
4
  password: string;
5
5
  realm?: string;
6
- }) => (ctx: Context, next: Function) => Promise<any>;
6
+ }, ...users: {
7
+ username: string;
8
+ password: string;
9
+ }[]) => (ctx: Context, next: Function) => Promise<any>;
@@ -24,24 +24,33 @@ const auth = (req) => {
24
24
  }
25
25
  return { username: userPass[1], password: userPass[2] };
26
26
  };
27
- const basicAuth = (options) => {
27
+ const basicAuth = (options, ...users) => {
28
+ if (!options) {
29
+ throw new Error('basic auth middleware requires options for "username and password"');
30
+ }
28
31
  if (!options.realm) {
29
32
  options.realm = 'Secure Area';
30
33
  }
34
+ users.unshift({ username: options.username, password: options.password });
31
35
  return async (ctx, next) => {
32
- const user = auth(ctx.req);
33
- const usernameEqual = user && (await (0, buffer_1.timingSafeEqual)(options.username, user.username));
34
- const passwordEqual = user && (await (0, buffer_1.timingSafeEqual)(options.password, user.password));
35
- if (!user || !usernameEqual || !passwordEqual) {
36
- ctx.res = new Response('Unauthorized', {
37
- status: 401,
38
- headers: {
39
- 'WWW-Authenticate': 'Basic realm="' + options.realm.replace(/"/g, '\\"') + '"',
40
- },
41
- });
42
- return;
36
+ const requestUser = auth(ctx.req);
37
+ if (requestUser) {
38
+ for (const user of users) {
39
+ const usernameEqual = await (0, buffer_1.timingSafeEqual)(user.username, requestUser.username);
40
+ const passwordEqual = await (0, buffer_1.timingSafeEqual)(user.password, requestUser.password);
41
+ if (usernameEqual && passwordEqual) {
42
+ // Authorized OK
43
+ return next();
44
+ }
45
+ }
43
46
  }
44
- return next();
47
+ ctx.res = new Response('Unauthorized', {
48
+ status: 401,
49
+ headers: {
50
+ 'WWW-Authenticate': 'Basic realm="' + options.realm.replace(/"/g, '\\"') + '"',
51
+ },
52
+ });
53
+ return;
45
54
  };
46
55
  };
47
56
  exports.basicAuth = basicAuth;
@@ -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);
@@ -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,13 @@
1
+ export declare type ParamMap = Array<[string, number]>;
2
+ export interface Context {
3
+ varIndex: number;
4
+ }
5
+ export declare class Node {
6
+ index?: number;
7
+ varIndex?: number;
8
+ children: {
9
+ [key: string]: Node;
10
+ };
11
+ insert(tokens: readonly string[], index: number, paramMap: ParamMap, context: Context): void;
12
+ buildRegExpStr(): string;
13
+ }
@@ -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.push([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,96 @@
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
+ for (let i = 0; i < paramMap.length; i++) {
45
+ params[paramMap[i][0]] = match[paramMap[i][1]];
46
+ }
47
+ return new router_1.Result(handler, params);
48
+ }
49
+ buildAllMatchers() {
50
+ this.matchers || (this.matchers = {});
51
+ Object.keys(this.routes).forEach((method) => {
52
+ this.buildMatcher(method);
53
+ });
54
+ delete this.routes; // to reduce memory usage
55
+ }
56
+ buildMatcher(method) {
57
+ this.matchers || (this.matchers = {});
58
+ const trie = new trie_1.Trie();
59
+ const handlers = [];
60
+ const targetMethods = [method];
61
+ if (method !== router_1.METHOD_NAME_OF_ALL) {
62
+ targetMethods.unshift(router_1.METHOD_NAME_OF_ALL);
63
+ }
64
+ const routes = targetMethods.flatMap((method) => this.routes[method] || []);
65
+ if (routes.length === 0) {
66
+ this.matchers[method] = null;
67
+ return;
68
+ }
69
+ if (routes.length === 1 && routes[0][0] === '*') {
70
+ this.matchers[method] = [true, null, [[routes[0][1], []]]];
71
+ return;
72
+ }
73
+ if (routes.length === 1 && !routes[0][0].match(/:/)) {
74
+ // there is only one route and no capture
75
+ const tmp = routes[0][0].endsWith('*')
76
+ ? routes[0][0].replace(/\/\*$/, '(?:$|/)') // /path/to/* => /path/to(?:$|/)
77
+ : `${routes[0][0]}$`; // /path/to/action => /path/to/action$
78
+ const regExpStr = `^${tmp.replace(/\*/g, '[^/]+')}`; // /prefix/*/path/to => /prefix/[^/]+/path/to
79
+ this.matchers[method] = [new RegExp(regExpStr), null, [[routes[0][1], []]]];
80
+ return;
81
+ }
82
+ for (let i = 0; i < routes.length; i++) {
83
+ const paramMap = trie.insert(routes[i][0], i);
84
+ handlers[i] = [routes[i][1], paramMap];
85
+ }
86
+ const [regexp, indexReplacementMap, paramReplacementMap] = trie.buildRegExp();
87
+ for (let i = 0; i < handlers.length; i++) {
88
+ const paramMap = handlers[i][1];
89
+ for (let i = 0; i < paramMap.length; i++) {
90
+ paramMap[i][1] = paramReplacementMap[paramMap[i][1]];
91
+ }
92
+ }
93
+ this.matchers[method] = [new RegExp(regexp), indexReplacementMap, handlers];
94
+ }
95
+ }
96
+ 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 @@
1
+ export { TrieRouter } from './router';
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TrieRouter = void 0;
4
+ var router_1 = require("./router");
5
+ Object.defineProperty(exports, "TrieRouter", { enumerable: true, get: function () { return router_1.TrieRouter; } });
@@ -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>;