hono 0.3.8 → 0.4.2
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 +120 -79
- package/dist/compose.d.ts +1 -1
- package/dist/compose.js +8 -2
- package/dist/context.d.ts +2 -3
- package/dist/hono.d.ts +19 -30
- package/dist/hono.js +50 -71
- package/dist/middleware/basic-auth/basic-auth.d.ts +4 -1
- package/dist/middleware/basic-auth/basic-auth.js +22 -13
- package/dist/middleware/logger/logger.js +3 -15
- package/dist/middleware/mustache/mustache.d.ts +2 -2
- package/dist/middleware/mustache/mustache.js +7 -3
- package/dist/middleware/serve-static/serve-static.js +5 -1
- package/dist/router/reg-exp-router/index.d.ts +1 -0
- package/dist/router/reg-exp-router/index.js +5 -0
- package/dist/router/reg-exp-router/node.d.ts +13 -0
- package/dist/router/reg-exp-router/node.js +94 -0
- package/dist/router/reg-exp-router/router.d.ts +18 -0
- package/dist/router/reg-exp-router/router.js +96 -0
- package/dist/router/reg-exp-router/trie.d.ts +10 -0
- package/dist/router/reg-exp-router/trie.js +40 -0
- package/dist/router/trie-router/index.d.ts +1 -0
- package/dist/router/trie-router/index.js +5 -0
- package/dist/router/trie-router/node.d.ts +12 -0
- package/dist/{node.js → router/trie-router/node.js} +20 -27
- package/dist/router/trie-router/router.d.ts +9 -0
- package/dist/router/trie-router/router.js +18 -0
- package/dist/router.d.ts +10 -0
- package/dist/router.js +14 -0
- package/dist/utils/buffer.js +3 -1
- package/dist/utils/url.d.ts +8 -2
- package/dist/utils/url.js +44 -7
- package/package.json +14 -12
- package/dist/node.d.ts +0 -24
package/dist/hono.js
CHANGED
|
@@ -1,83 +1,73 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Hono =
|
|
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
|
-
const
|
|
9
|
-
|
|
10
|
-
constructor() {
|
|
11
|
-
this.node = new node_1.Node();
|
|
12
|
-
}
|
|
13
|
-
add(method, path, handler) {
|
|
14
|
-
this.node.insert(method, path, handler);
|
|
15
|
-
}
|
|
16
|
-
match(method, path) {
|
|
17
|
-
return this.node.search(method, path);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
exports.Router = Router;
|
|
7
|
+
const router_1 = require("./router");
|
|
8
|
+
const trie_router_1 = require("./router/trie-router"); // Default Router
|
|
21
9
|
class Hono {
|
|
22
|
-
constructor() {
|
|
23
|
-
this.
|
|
10
|
+
constructor(init = {}) {
|
|
11
|
+
this.routerClass = trie_router_1.TrieRouter;
|
|
12
|
+
this.strict = true; // strict routing - default is true
|
|
13
|
+
Object.assign(this, init);
|
|
14
|
+
this.router = new this.routerClass();
|
|
24
15
|
this.middlewareRouters = [];
|
|
25
|
-
this.tempPath =
|
|
16
|
+
this.tempPath = null;
|
|
26
17
|
}
|
|
27
|
-
get(
|
|
28
|
-
return this.addRoute('get',
|
|
18
|
+
get(path, handler) {
|
|
19
|
+
return this.addRoute('get', path, handler);
|
|
29
20
|
}
|
|
30
|
-
post(
|
|
31
|
-
return this.addRoute('post',
|
|
21
|
+
post(path, handler) {
|
|
22
|
+
return this.addRoute('post', path, handler);
|
|
32
23
|
}
|
|
33
|
-
put(
|
|
34
|
-
return this.addRoute('put',
|
|
24
|
+
put(path, handler) {
|
|
25
|
+
return this.addRoute('put', path, handler);
|
|
35
26
|
}
|
|
36
|
-
head(
|
|
37
|
-
return this.addRoute('head',
|
|
27
|
+
head(path, handler) {
|
|
28
|
+
return this.addRoute('head', path, handler);
|
|
38
29
|
}
|
|
39
|
-
delete(
|
|
40
|
-
return this.addRoute('delete',
|
|
30
|
+
delete(path, handler) {
|
|
31
|
+
return this.addRoute('delete', path, handler);
|
|
41
32
|
}
|
|
42
|
-
options(
|
|
43
|
-
return this.addRoute('options',
|
|
33
|
+
options(path, handler) {
|
|
34
|
+
return this.addRoute('options', path, handler);
|
|
44
35
|
}
|
|
45
|
-
patch(
|
|
46
|
-
return this.addRoute('patch',
|
|
36
|
+
patch(path, handler) {
|
|
37
|
+
return this.addRoute('patch', path, handler);
|
|
47
38
|
}
|
|
48
|
-
all(
|
|
49
|
-
return this.addRoute('all',
|
|
39
|
+
all(path, handler) {
|
|
40
|
+
return this.addRoute('all', path, handler);
|
|
50
41
|
}
|
|
51
42
|
route(path) {
|
|
52
|
-
|
|
53
|
-
|
|
43
|
+
const newHono = new Hono();
|
|
44
|
+
newHono.tempPath = path;
|
|
45
|
+
newHono.router = this.router;
|
|
46
|
+
return newHono;
|
|
54
47
|
}
|
|
55
48
|
use(path, middleware) {
|
|
56
49
|
if (middleware.constructor.name !== 'AsyncFunction') {
|
|
57
50
|
throw new TypeError('middleware must be a async function!');
|
|
58
51
|
}
|
|
59
|
-
const router = new
|
|
60
|
-
router.add(METHOD_NAME_OF_ALL, path, middleware);
|
|
52
|
+
const router = new this.routerClass();
|
|
53
|
+
router.add(router_1.METHOD_NAME_OF_ALL, path, middleware);
|
|
61
54
|
this.middlewareRouters.push(router);
|
|
62
55
|
}
|
|
63
56
|
// addRoute('get', '/', handler)
|
|
64
|
-
addRoute(method,
|
|
57
|
+
addRoute(method, path, handler) {
|
|
65
58
|
method = method.toUpperCase();
|
|
66
|
-
if (
|
|
67
|
-
this.tempPath
|
|
68
|
-
this.router.add(method, arg, args);
|
|
69
|
-
}
|
|
70
|
-
else {
|
|
71
|
-
args.unshift(arg);
|
|
72
|
-
this.router.add(method, this.tempPath, args);
|
|
59
|
+
if (this.tempPath) {
|
|
60
|
+
path = (0, url_1.mergePath)(this.tempPath, path);
|
|
73
61
|
}
|
|
62
|
+
this.router.add(method, path, handler);
|
|
74
63
|
return this;
|
|
75
64
|
}
|
|
76
65
|
async matchRoute(method, path) {
|
|
77
66
|
return this.router.match(method, path);
|
|
78
67
|
}
|
|
79
68
|
async dispatch(request, env, event) {
|
|
80
|
-
const
|
|
69
|
+
const path = (0, url_1.getPathFromURL)(request.url, { strict: this.strict });
|
|
70
|
+
const method = request.method;
|
|
81
71
|
const result = await this.matchRoute(method, path);
|
|
82
72
|
// Methods for Request object
|
|
83
73
|
request.param = (key) => {
|
|
@@ -92,10 +82,10 @@ class Hono {
|
|
|
92
82
|
const url = new URL(c.req.url);
|
|
93
83
|
return url.searchParams.get(key);
|
|
94
84
|
};
|
|
95
|
-
const handler = result ? result.handler
|
|
85
|
+
const handler = result ? result.handler : this.notFound;
|
|
96
86
|
const middleware = [];
|
|
97
87
|
for (const mr of this.middlewareRouters) {
|
|
98
|
-
const mwResult = mr.match(METHOD_NAME_OF_ALL, path);
|
|
88
|
+
const mwResult = mr.match(router_1.METHOD_NAME_OF_ALL, path);
|
|
99
89
|
if (mwResult) {
|
|
100
90
|
middleware.push(mwResult.handler);
|
|
101
91
|
}
|
|
@@ -109,44 +99,33 @@ class Hono {
|
|
|
109
99
|
await next();
|
|
110
100
|
};
|
|
111
101
|
middleware.push(wrappedHandler);
|
|
112
|
-
const composed = (0, compose_1.compose)(middleware);
|
|
102
|
+
const composed = (0, compose_1.compose)(middleware, this.onError);
|
|
113
103
|
const c = new context_1.Context(request, { env: env, event: event, res: null });
|
|
104
|
+
c.notFound = () => this.notFound(c);
|
|
114
105
|
await composed(c);
|
|
115
106
|
return c.res;
|
|
116
107
|
}
|
|
117
108
|
async handleEvent(event) {
|
|
118
|
-
return this.dispatch(event.request, {}, event)
|
|
119
|
-
return this.onError(err);
|
|
120
|
-
});
|
|
109
|
+
return this.dispatch(event.request, {}, event);
|
|
121
110
|
}
|
|
122
111
|
async fetch(request, env, event) {
|
|
123
|
-
return this.dispatch(request, env, event)
|
|
124
|
-
return this.onError(err);
|
|
125
|
-
});
|
|
112
|
+
return this.dispatch(request, env, event);
|
|
126
113
|
}
|
|
127
114
|
fire() {
|
|
128
115
|
addEventListener('fetch', (event) => {
|
|
129
116
|
event.respondWith(this.handleEvent(event));
|
|
130
117
|
});
|
|
131
118
|
}
|
|
132
|
-
|
|
133
|
-
|
|
119
|
+
// Default error Response
|
|
120
|
+
onError(err, c) {
|
|
121
|
+
console.error(`${err.message}`);
|
|
134
122
|
const message = 'Internal Server Error';
|
|
135
|
-
return
|
|
136
|
-
status: 500,
|
|
137
|
-
headers: {
|
|
138
|
-
'Content-Length': message.length.toString(),
|
|
139
|
-
},
|
|
140
|
-
});
|
|
123
|
+
return c.text(message, 500);
|
|
141
124
|
}
|
|
142
|
-
|
|
125
|
+
// Default 404 not found Response
|
|
126
|
+
notFound(c) {
|
|
143
127
|
const message = 'Not Found';
|
|
144
|
-
return
|
|
145
|
-
status: 404,
|
|
146
|
-
headers: {
|
|
147
|
-
'Content-Length': message.length.toString(),
|
|
148
|
-
},
|
|
149
|
-
});
|
|
128
|
+
return c.text(message, 404);
|
|
150
129
|
}
|
|
151
130
|
}
|
|
152
131
|
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
|
-
}
|
|
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
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Context } from '../../context';
|
|
2
|
-
declare type
|
|
2
|
+
declare type Init = {
|
|
3
3
|
root: string;
|
|
4
4
|
};
|
|
5
|
-
export declare const mustache: (
|
|
5
|
+
export declare const mustache: (init?: Init) => (c: Context, next: Function) => Promise<void>;
|
|
6
6
|
export {};
|
|
@@ -4,8 +4,8 @@ exports.mustache = void 0;
|
|
|
4
4
|
const cloudflare_1 = require("../../utils/cloudflare");
|
|
5
5
|
const EXTENSION = '.mustache';
|
|
6
6
|
const DEFAULT_DOCUMENT = 'index.mustache';
|
|
7
|
-
const mustache = (
|
|
8
|
-
const { root } =
|
|
7
|
+
const mustache = (init = { root: '' }) => {
|
|
8
|
+
const { root } = init;
|
|
9
9
|
return async (c, next) => {
|
|
10
10
|
let Mustache;
|
|
11
11
|
try {
|
|
@@ -15,7 +15,11 @@ const mustache = (opt = { root: '' }) => {
|
|
|
15
15
|
throw new Error('If you want to use Mustache Middleware, install "mustache" package first.');
|
|
16
16
|
}
|
|
17
17
|
c.render = async (filename, params = {}, options) => {
|
|
18
|
-
const path = (0, cloudflare_1.getKVFilePath)({
|
|
18
|
+
const path = (0, cloudflare_1.getKVFilePath)({
|
|
19
|
+
filename: `${filename}${EXTENSION}`,
|
|
20
|
+
root: root,
|
|
21
|
+
defaultDocument: DEFAULT_DOCUMENT,
|
|
22
|
+
});
|
|
19
23
|
const buffer = await (0, cloudflare_1.getContentFromKVAsset)(path);
|
|
20
24
|
if (!buffer) {
|
|
21
25
|
throw new Error(`Template "${path}" is not found or blank.`);
|
|
@@ -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)({
|
|
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,12 @@
|
|
|
1
|
+
import type { Pattern } from '../../utils/url';
|
|
2
|
+
import { Result } from '../../router';
|
|
3
|
+
export declare class Node<T> {
|
|
4
|
+
method: Record<string, T>;
|
|
5
|
+
handler: T;
|
|
6
|
+
children: Record<string, Node<T>>;
|
|
7
|
+
middlewares: [];
|
|
8
|
+
patterns: Pattern[];
|
|
9
|
+
constructor(method?: string, handler?: T, children?: Record<string, Node<T>>);
|
|
10
|
+
insert(method: string, path: string, handler: T): Node<T>;
|
|
11
|
+
search(method: string, path: string): Result<T>;
|
|
12
|
+
}
|