hono 0.3.6 → 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 +68 -67
- package/dist/compose.d.ts +1 -1
- package/dist/compose.js +8 -2
- package/dist/context.d.ts +5 -6
- package/dist/context.js +1 -1
- package/dist/hono.d.ts +28 -19
- package/dist/hono.js +60 -85
- 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/node.d.ts +6 -18
- package/dist/node.js +19 -26
- 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 +15 -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 +97 -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.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 +7 -5
- package/dist/middleware.d.ts +0 -2
- package/dist/middleware.js +0 -6
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Hono
|
|
2
2
|
|
|
3
|
-
Hono[炎] - _**means flame🔥 in Japanese**_ - is small, simple, and ultrafast web
|
|
3
|
+
Hono[炎] - _**means flame🔥 in Japanese**_ - is small, simple, and ultrafast web framework for a Service Workers API based serverless such as Cloudflare Workers and Fastly Compute@Edge.
|
|
4
4
|
|
|
5
5
|
```js
|
|
6
6
|
import { Hono } from 'hono'
|
|
@@ -13,7 +13,7 @@ app.fire()
|
|
|
13
13
|
|
|
14
14
|
## Features
|
|
15
15
|
|
|
16
|
-
- **Ultra fast** - the router is implemented with Trie-Tree structure.
|
|
16
|
+
- **Ultra fast** - the router is implemented with Trie-Tree structure. Not use loops.
|
|
17
17
|
- **Zero dependencies** - using only Web standard API.
|
|
18
18
|
- **Middleware** - builtin middleware, and you can make your own middleware.
|
|
19
19
|
- **Optimized** - for Cloudflare Workers.
|
|
@@ -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
|
|
27
|
-
itty-router x
|
|
28
|
-
sunder x
|
|
29
|
-
worktop x
|
|
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
|
|
31
|
+
✨ Done in 58.29s.
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
## Hono in 1 minute
|
|
@@ -37,9 +37,13 @@ Below is a demonstration to create an application of Cloudflare Workers with Hon
|
|
|
37
37
|
|
|
38
38
|

|
|
39
39
|
|
|
40
|
+
Now, the named path parameter has types.
|
|
41
|
+
|
|
42
|
+

|
|
43
|
+
|
|
40
44
|
## Install
|
|
41
45
|
|
|
42
|
-
You can install from npm registry:
|
|
46
|
+
You can install Hono from npm registry:
|
|
43
47
|
|
|
44
48
|
```sh
|
|
45
49
|
yarn add hono
|
|
@@ -53,7 +57,7 @@ npm install hono
|
|
|
53
57
|
|
|
54
58
|
## Methods
|
|
55
59
|
|
|
56
|
-
|
|
60
|
+
An instance of `Hono` has these methods:
|
|
57
61
|
|
|
58
62
|
- app.**HTTP_METHOD**(path, handler)
|
|
59
63
|
- app.**all**(path, handler)
|
|
@@ -104,27 +108,32 @@ app.get('/post/:date{[0-9]+}/:title{[a-z]+}', (c) => {
|
|
|
104
108
|
...
|
|
105
109
|
```
|
|
106
110
|
|
|
107
|
-
###
|
|
111
|
+
### Nested route
|
|
108
112
|
|
|
109
113
|
```js
|
|
110
|
-
app
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
114
|
+
const book = app.route('/book')
|
|
115
|
+
book.get('/', (c) => c.text('List Books')) // => GET /book
|
|
116
|
+
book.get('/:id', (c) => {
|
|
117
|
+
// => GET /book/:id
|
|
118
|
+
const id = c.req.param('id')
|
|
119
|
+
return c.text('Get Book: ' + id)
|
|
120
|
+
})
|
|
121
|
+
book.post('/', (c) => c.text('Create Book')) // => POST /book
|
|
115
122
|
```
|
|
116
123
|
|
|
117
|
-
###
|
|
124
|
+
### no strict
|
|
118
125
|
|
|
119
|
-
|
|
126
|
+
If `strict` is set `false`, `/hello`and`/hello/` are treated the same:
|
|
120
127
|
|
|
121
128
|
```js
|
|
122
|
-
app
|
|
123
|
-
|
|
124
|
-
|
|
129
|
+
const app = new Hono({ strict: false })
|
|
130
|
+
|
|
131
|
+
app.get('/hello', (c) => c.text('/hello or /hello/'))
|
|
125
132
|
```
|
|
126
133
|
|
|
127
|
-
|
|
134
|
+
Default is `true`.
|
|
135
|
+
|
|
136
|
+
### async/await
|
|
128
137
|
|
|
129
138
|
```js
|
|
130
139
|
app.get('/fetch-url', async (c) => {
|
|
@@ -147,15 +156,7 @@ const app = new Hono()
|
|
|
147
156
|
|
|
148
157
|
app.use('*', poweredBy())
|
|
149
158
|
app.use('*', logger())
|
|
150
|
-
app.use(
|
|
151
|
-
'/auth/*',
|
|
152
|
-
basicAuth({
|
|
153
|
-
username: 'hono',
|
|
154
|
-
password: 'acoolproject',
|
|
155
|
-
})
|
|
156
|
-
)
|
|
157
|
-
|
|
158
|
-
...
|
|
159
|
+
app.use('/auth/*', basicAuth({ username: 'hono', password: 'acoolproject' }))
|
|
159
160
|
```
|
|
160
161
|
|
|
161
162
|
Available builtin middleware are listed on [src/middleware](https://github.com/yusukebe/hono/tree/master/src/middleware).
|
|
@@ -174,49 +175,15 @@ app.use('*', async (c, next) => {
|
|
|
174
175
|
// Add a custom header
|
|
175
176
|
app.use('/message/*', async (c, next) => {
|
|
176
177
|
await next()
|
|
177
|
-
await c.
|
|
178
|
+
await c.header('x-message', 'This is middleware!')
|
|
178
179
|
})
|
|
179
180
|
|
|
180
181
|
app.get('/message/hello', (c) => c.text('Hello Middleware!'))
|
|
181
182
|
```
|
|
182
183
|
|
|
183
|
-
### Handling Error
|
|
184
|
-
|
|
185
|
-
```js
|
|
186
|
-
app.use('*', async (c, next) => {
|
|
187
|
-
try {
|
|
188
|
-
await next()
|
|
189
|
-
} catch (err) {
|
|
190
|
-
console.error(`${err}`)
|
|
191
|
-
c.res = c.text('Custom Error Message', { status: 500 })
|
|
192
|
-
}
|
|
193
|
-
})
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
### Complex Pattern
|
|
197
|
-
|
|
198
|
-
You can also do this:
|
|
199
|
-
|
|
200
|
-
```js
|
|
201
|
-
// Output response time
|
|
202
|
-
app.use('*', async (c, next) => {
|
|
203
|
-
await next()
|
|
204
|
-
const responseTime = await c.res.headers.get('X-Response-Time')
|
|
205
|
-
console.log(`X-Response-Time: ${responseTime}`)
|
|
206
|
-
})
|
|
207
|
-
|
|
208
|
-
// Add X-Response-Time header
|
|
209
|
-
app.use('*', async (c, next) => {
|
|
210
|
-
const start = Date.now()
|
|
211
|
-
await next()
|
|
212
|
-
const ms = Date.now() - start
|
|
213
|
-
await c.res.headers.append('X-Response-Time', `${ms}ms`)
|
|
214
|
-
})
|
|
215
|
-
```
|
|
216
|
-
|
|
217
184
|
## Context
|
|
218
185
|
|
|
219
|
-
To handle Request and Reponse
|
|
186
|
+
To handle Request and Reponse, you can use Context object:
|
|
220
187
|
|
|
221
188
|
### c.req
|
|
222
189
|
|
|
@@ -301,6 +268,16 @@ app.get('/', (c) => {
|
|
|
301
268
|
})
|
|
302
269
|
```
|
|
303
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
|
+
|
|
304
281
|
### c.redirect()
|
|
305
282
|
|
|
306
283
|
Redirect, default status code is `302`:
|
|
@@ -342,6 +319,26 @@ app.get('*', async c => {
|
|
|
342
319
|
})
|
|
343
320
|
```
|
|
344
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
|
+
|
|
345
342
|
## fire
|
|
346
343
|
|
|
347
344
|
`app.fire()` do:
|
|
@@ -430,7 +427,7 @@ Run the development server locally. Then, access like `http://127.0.0.1:8787/` i
|
|
|
430
427
|
wrangler dev
|
|
431
428
|
```
|
|
432
429
|
|
|
433
|
-
### Publish
|
|
430
|
+
### 7. Publish
|
|
434
431
|
|
|
435
432
|
Deploy to Cloudflare. That's all!
|
|
436
433
|
|
|
@@ -461,6 +458,10 @@ Contributions Welcome! You can contribute by the following way:
|
|
|
461
458
|
|
|
462
459
|
If you can, let's make Hono together!
|
|
463
460
|
|
|
461
|
+
## Contributors
|
|
462
|
+
|
|
463
|
+
Thanks to [all contributors](https://github.com/yusukebe/hono/graphs/contributors)!
|
|
464
|
+
|
|
464
465
|
## Author
|
|
465
466
|
|
|
466
467
|
Yusuke Wada <https://github.com/yusukebe>
|
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
|
-
|
|
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
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
/// <reference types="@cloudflare/workers-types" />
|
|
2
|
-
declare type Headers =
|
|
3
|
-
[key: string]: string;
|
|
4
|
-
};
|
|
2
|
+
declare type Headers = Record<string, string>;
|
|
5
3
|
declare type Data = string | ArrayBuffer | ReadableStream;
|
|
6
4
|
export interface Env {
|
|
7
5
|
}
|
|
8
|
-
export declare class Context {
|
|
9
|
-
req: Request
|
|
6
|
+
export declare class Context<RequestParamKeyType = string> {
|
|
7
|
+
req: Request<RequestParamKeyType>;
|
|
10
8
|
res: Response;
|
|
11
9
|
env: Env;
|
|
12
10
|
event: FetchEvent;
|
|
@@ -14,7 +12,8 @@ export declare class Context {
|
|
|
14
12
|
private _status;
|
|
15
13
|
private _statusText;
|
|
16
14
|
render: (template: string, params?: object, options?: object) => Promise<Response>;
|
|
17
|
-
|
|
15
|
+
notFound: () => Response;
|
|
16
|
+
constructor(req: Request<RequestParamKeyType>, opts?: {
|
|
18
17
|
res: Response;
|
|
19
18
|
env: Env;
|
|
20
19
|
event: FetchEvent;
|
package/dist/context.js
CHANGED
package/dist/hono.d.ts
CHANGED
|
@@ -1,45 +1,54 @@
|
|
|
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
|
-
interface Request {
|
|
8
|
-
param: (key:
|
|
8
|
+
interface Request<ParamKeyType = string> {
|
|
9
|
+
param: (key: ParamKeyType) => string;
|
|
9
10
|
query: (key: string) => string;
|
|
10
11
|
header: (name: string) => string;
|
|
11
12
|
parsedBody: any;
|
|
12
13
|
}
|
|
13
14
|
}
|
|
14
|
-
export declare type Handler = (c: Context
|
|
15
|
+
export declare type Handler<RequestParamKeyType = string> = (c: Context<RequestParamKeyType>, next?: Function) => Response | Promise<Response>;
|
|
15
16
|
export declare type MiddlewareHandler = (c: Context, next: Function) => Promise<void>;
|
|
16
|
-
|
|
17
|
+
declare type ParamKeyName<NameWithPattern> = NameWithPattern extends `${infer Name}{${infer _Pattern}` ? Name : NameWithPattern;
|
|
18
|
+
declare type ParamKey<Component> = Component extends `:${infer NameWithPattern}` ? ParamKeyName<NameWithPattern> : never;
|
|
19
|
+
declare type ParamKeys<Path> = Path extends `${infer Component}/${infer Rest}` ? ParamKey<Component> | ParamKeys<Rest> : ParamKey<Path>;
|
|
20
|
+
export declare class TrieRouter<T> extends Router<T> {
|
|
17
21
|
node: Node<T>;
|
|
18
22
|
constructor();
|
|
19
23
|
add(method: string, path: string, handler: T): void;
|
|
20
24
|
match(method: string, path: string): Result<T> | null;
|
|
21
25
|
}
|
|
22
26
|
export declare class Hono {
|
|
23
|
-
|
|
27
|
+
routerClass: {
|
|
28
|
+
new (): Router<any>;
|
|
29
|
+
};
|
|
30
|
+
strict: boolean;
|
|
31
|
+
router: Router<Handler>;
|
|
24
32
|
middlewareRouters: Router<MiddlewareHandler>[];
|
|
25
33
|
tempPath: string;
|
|
26
|
-
constructor();
|
|
27
|
-
get(
|
|
28
|
-
post(
|
|
29
|
-
put(
|
|
30
|
-
head(
|
|
31
|
-
delete(
|
|
32
|
-
options(
|
|
33
|
-
patch(
|
|
34
|
-
all(
|
|
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;
|
|
35
43
|
route(path: string): Hono;
|
|
36
44
|
use(path: string, middleware: MiddlewareHandler): void;
|
|
37
|
-
addRoute(method: string,
|
|
38
|
-
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>>;
|
|
39
47
|
dispatch(request: Request, env?: Env, event?: FetchEvent): Promise<Response>;
|
|
40
48
|
handleEvent(event: FetchEvent): Promise<Response>;
|
|
41
49
|
fetch(request: Request, env?: Env, event?: FetchEvent): Promise<Response>;
|
|
42
50
|
fire(): void;
|
|
43
|
-
onError(err: Error): Response;
|
|
44
|
-
notFound(): Response;
|
|
51
|
+
onError(err: Error, c: Context): Response;
|
|
52
|
+
notFound(c: Context): Response;
|
|
45
53
|
}
|
|
54
|
+
export {};
|
package/dist/hono.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Hono = exports.
|
|
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
|
-
const
|
|
9
|
-
class Router {
|
|
8
|
+
const router_1 = require("./router");
|
|
9
|
+
class TrieRouter extends router_1.Router {
|
|
10
10
|
constructor() {
|
|
11
|
+
super();
|
|
11
12
|
this.node = new node_1.Node();
|
|
12
13
|
}
|
|
13
14
|
add(method, path, handler) {
|
|
@@ -17,84 +18,69 @@ class Router {
|
|
|
17
18
|
return this.node.search(method, path);
|
|
18
19
|
}
|
|
19
20
|
}
|
|
20
|
-
exports.
|
|
21
|
+
exports.TrieRouter = TrieRouter;
|
|
21
22
|
class Hono {
|
|
22
|
-
constructor() {
|
|
23
|
-
this.
|
|
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();
|
|
24
28
|
this.middlewareRouters = [];
|
|
25
|
-
this.tempPath =
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
We may implement these HTTP methods:
|
|
51
|
-
trace
|
|
52
|
-
copy
|
|
53
|
-
lock
|
|
54
|
-
purge
|
|
55
|
-
unlock
|
|
56
|
-
report
|
|
57
|
-
checkout
|
|
58
|
-
merge
|
|
59
|
-
notify
|
|
60
|
-
subscribe
|
|
61
|
-
unsubscribe
|
|
62
|
-
search
|
|
63
|
-
connect
|
|
64
|
-
*/
|
|
65
|
-
all(arg, ...args) {
|
|
66
|
-
return this.addRoute('all', arg, ...args);
|
|
29
|
+
this.tempPath = null;
|
|
30
|
+
}
|
|
31
|
+
get(path, handler) {
|
|
32
|
+
return this.addRoute('get', path, handler);
|
|
33
|
+
}
|
|
34
|
+
post(path, handler) {
|
|
35
|
+
return this.addRoute('post', path, handler);
|
|
36
|
+
}
|
|
37
|
+
put(path, handler) {
|
|
38
|
+
return this.addRoute('put', path, handler);
|
|
39
|
+
}
|
|
40
|
+
head(path, handler) {
|
|
41
|
+
return this.addRoute('head', path, handler);
|
|
42
|
+
}
|
|
43
|
+
delete(path, handler) {
|
|
44
|
+
return this.addRoute('delete', path, handler);
|
|
45
|
+
}
|
|
46
|
+
options(path, handler) {
|
|
47
|
+
return this.addRoute('options', path, handler);
|
|
48
|
+
}
|
|
49
|
+
patch(path, handler) {
|
|
50
|
+
return this.addRoute('patch', path, handler);
|
|
51
|
+
}
|
|
52
|
+
all(path, handler) {
|
|
53
|
+
return this.addRoute('all', path, handler);
|
|
67
54
|
}
|
|
68
55
|
route(path) {
|
|
69
|
-
|
|
70
|
-
|
|
56
|
+
const newHono = new Hono();
|
|
57
|
+
newHono.tempPath = path;
|
|
58
|
+
newHono.router = this.router;
|
|
59
|
+
return newHono;
|
|
71
60
|
}
|
|
72
61
|
use(path, middleware) {
|
|
73
62
|
if (middleware.constructor.name !== 'AsyncFunction') {
|
|
74
63
|
throw new TypeError('middleware must be a async function!');
|
|
75
64
|
}
|
|
76
|
-
const router = new
|
|
77
|
-
router.add(METHOD_NAME_OF_ALL, path, middleware);
|
|
65
|
+
const router = new this.routerClass();
|
|
66
|
+
router.add(router_1.METHOD_NAME_OF_ALL, path, middleware);
|
|
78
67
|
this.middlewareRouters.push(router);
|
|
79
68
|
}
|
|
80
69
|
// addRoute('get', '/', handler)
|
|
81
|
-
addRoute(method,
|
|
70
|
+
addRoute(method, path, handler) {
|
|
82
71
|
method = method.toUpperCase();
|
|
83
|
-
if (
|
|
84
|
-
this.tempPath
|
|
85
|
-
this.router.add(method, arg, args);
|
|
86
|
-
}
|
|
87
|
-
else {
|
|
88
|
-
args.unshift(arg);
|
|
89
|
-
this.router.add(method, this.tempPath, args);
|
|
72
|
+
if (this.tempPath) {
|
|
73
|
+
path = (0, url_1.mergePath)(this.tempPath, path);
|
|
90
74
|
}
|
|
75
|
+
this.router.add(method, path, handler);
|
|
91
76
|
return this;
|
|
92
77
|
}
|
|
93
78
|
async matchRoute(method, path) {
|
|
94
79
|
return this.router.match(method, path);
|
|
95
80
|
}
|
|
96
81
|
async dispatch(request, env, event) {
|
|
97
|
-
const
|
|
82
|
+
const path = (0, url_1.getPathFromURL)(request.url, { strict: this.strict });
|
|
83
|
+
const method = request.method;
|
|
98
84
|
const result = await this.matchRoute(method, path);
|
|
99
85
|
// Methods for Request object
|
|
100
86
|
request.param = (key) => {
|
|
@@ -109,10 +95,10 @@ class Hono {
|
|
|
109
95
|
const url = new URL(c.req.url);
|
|
110
96
|
return url.searchParams.get(key);
|
|
111
97
|
};
|
|
112
|
-
const handler = result ? result.handler
|
|
98
|
+
const handler = result ? result.handler : this.notFound;
|
|
113
99
|
const middleware = [];
|
|
114
100
|
for (const mr of this.middlewareRouters) {
|
|
115
|
-
const mwResult = mr.match(METHOD_NAME_OF_ALL, path);
|
|
101
|
+
const mwResult = mr.match(router_1.METHOD_NAME_OF_ALL, path);
|
|
116
102
|
if (mwResult) {
|
|
117
103
|
middleware.push(mwResult.handler);
|
|
118
104
|
}
|
|
@@ -126,44 +112,33 @@ class Hono {
|
|
|
126
112
|
await next();
|
|
127
113
|
};
|
|
128
114
|
middleware.push(wrappedHandler);
|
|
129
|
-
const composed = (0, compose_1.compose)(middleware);
|
|
115
|
+
const composed = (0, compose_1.compose)(middleware, this.onError);
|
|
130
116
|
const c = new context_1.Context(request, { env: env, event: event, res: null });
|
|
117
|
+
c.notFound = () => this.notFound(c);
|
|
131
118
|
await composed(c);
|
|
132
119
|
return c.res;
|
|
133
120
|
}
|
|
134
121
|
async handleEvent(event) {
|
|
135
|
-
return this.dispatch(event.request, {}, event)
|
|
136
|
-
return this.onError(err);
|
|
137
|
-
});
|
|
122
|
+
return this.dispatch(event.request, {}, event);
|
|
138
123
|
}
|
|
139
124
|
async fetch(request, env, event) {
|
|
140
|
-
return this.dispatch(request, env, event)
|
|
141
|
-
return this.onError(err);
|
|
142
|
-
});
|
|
125
|
+
return this.dispatch(request, env, event);
|
|
143
126
|
}
|
|
144
127
|
fire() {
|
|
145
128
|
addEventListener('fetch', (event) => {
|
|
146
129
|
event.respondWith(this.handleEvent(event));
|
|
147
130
|
});
|
|
148
131
|
}
|
|
149
|
-
|
|
150
|
-
|
|
132
|
+
// Default error Response
|
|
133
|
+
onError(err, c) {
|
|
134
|
+
console.error(`${err.message}`);
|
|
151
135
|
const message = 'Internal Server Error';
|
|
152
|
-
return
|
|
153
|
-
status: 500,
|
|
154
|
-
headers: {
|
|
155
|
-
'Content-Length': message.length.toString(),
|
|
156
|
-
},
|
|
157
|
-
});
|
|
136
|
+
return c.text(message, 500);
|
|
158
137
|
}
|
|
159
|
-
|
|
138
|
+
// Default 404 not found Response
|
|
139
|
+
notFound(c) {
|
|
160
140
|
const message = 'Not Found';
|
|
161
|
-
return
|
|
162
|
-
status: 404,
|
|
163
|
-
headers: {
|
|
164
|
-
'Content-Length': message.length.toString(),
|
|
165
|
-
},
|
|
166
|
-
});
|
|
141
|
+
return c.text(message, 404);
|
|
167
142
|
}
|
|
168
143
|
}
|
|
169
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
|
-
|
|
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);
|
package/dist/node.d.ts
CHANGED
|
@@ -1,24 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
params: {
|
|
4
|
-
[key: string]: string;
|
|
5
|
-
};
|
|
6
|
-
constructor(handler: T, params: {
|
|
7
|
-
[key: string]: string;
|
|
8
|
-
});
|
|
9
|
-
}
|
|
1
|
+
import type { Pattern } from './utils/url';
|
|
2
|
+
import { Result } from './router';
|
|
10
3
|
export declare class Node<T> {
|
|
11
|
-
method:
|
|
12
|
-
[key: string]: T;
|
|
13
|
-
};
|
|
4
|
+
method: Record<string, T>;
|
|
14
5
|
handler: T;
|
|
15
|
-
children:
|
|
16
|
-
[key: string]: Node<T>;
|
|
17
|
-
};
|
|
6
|
+
children: Record<string, Node<T>>;
|
|
18
7
|
middlewares: [];
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
});
|
|
8
|
+
patterns: Pattern[];
|
|
9
|
+
constructor(method?: string, handler?: T, children?: Record<string, Node<T>>);
|
|
22
10
|
insert(method: string, path: string, handler: T): Node<T>;
|
|
23
11
|
search(method: string, path: string): Result<T>;
|
|
24
12
|
}
|
package/dist/node.js
CHANGED
|
@@ -1,15 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Node =
|
|
3
|
+
exports.Node = void 0;
|
|
4
4
|
const url_1 = require("./utils/url");
|
|
5
|
-
const
|
|
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
|
-
|
|
68
|
-
|
|
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 (
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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[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;
|
package/dist/router.d.ts
ADDED
|
@@ -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;
|
package/dist/utils/buffer.js
CHANGED
|
@@ -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
|
|
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 {
|
package/dist/utils/url.d.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
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) =>
|
|
3
|
-
|
|
3
|
+
export declare const getPattern: (label: string) => Pattern | null;
|
|
4
|
+
declare type Params = {
|
|
5
|
+
strict: boolean;
|
|
6
|
+
};
|
|
7
|
+
export declare const getPathFromURL: (url: string, params?: Params) => string;
|
|
4
8
|
export declare const isAbsoluteURL: (url: string) => boolean;
|
|
9
|
+
export declare const mergePath: (...paths: string[]) => string;
|
|
10
|
+
export {};
|
package/dist/utils/url.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isAbsoluteURL = exports.getPathFromURL = exports.getPattern = exports.splitPath = void 0;
|
|
3
|
+
exports.mergePath = exports.isAbsoluteURL = exports.getPathFromURL = exports.getPattern = exports.splitPath = void 0;
|
|
4
4
|
const URL_REGEXP = /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;
|
|
5
5
|
const splitPath = (path) => {
|
|
6
6
|
const paths = path.split(/\//); // faster than path.split('/')
|
|
@@ -10,23 +10,36 @@ 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 (
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
};
|
|
28
36
|
exports.getPattern = getPattern;
|
|
29
|
-
const getPathFromURL = (url) => {
|
|
37
|
+
const getPathFromURL = (url, params = { strict: true }) => {
|
|
38
|
+
// if strict routing is false => `/hello/hey/` and `/hello/hey` are treated the same
|
|
39
|
+
// default is true
|
|
40
|
+
if (!params.strict && url.endsWith('/')) {
|
|
41
|
+
url = url.slice(0, -1);
|
|
42
|
+
}
|
|
30
43
|
const match = url.match(URL_REGEXP);
|
|
31
44
|
if (match) {
|
|
32
45
|
return match[5];
|
|
@@ -42,3 +55,27 @@ const isAbsoluteURL = (url) => {
|
|
|
42
55
|
return false;
|
|
43
56
|
};
|
|
44
57
|
exports.isAbsoluteURL = isAbsoluteURL;
|
|
58
|
+
const mergePath = (...paths) => {
|
|
59
|
+
let p = '';
|
|
60
|
+
let endsWithSlash = false;
|
|
61
|
+
for (let path of paths) {
|
|
62
|
+
/* ['/hey/','/say'] => ['/hey', '/say'] */
|
|
63
|
+
if (p.endsWith('/')) {
|
|
64
|
+
p = p.slice(0, -1);
|
|
65
|
+
endsWithSlash = true;
|
|
66
|
+
}
|
|
67
|
+
/* ['/hey','say'] => ['/hey', '/say'] */
|
|
68
|
+
if (!path.startsWith('/')) {
|
|
69
|
+
path = `/${path}`;
|
|
70
|
+
}
|
|
71
|
+
/* ['/hey/', '/'] => `/hey/` */
|
|
72
|
+
if (path === '/' && endsWithSlash) {
|
|
73
|
+
p = `${p}/`;
|
|
74
|
+
}
|
|
75
|
+
else if (path !== '/') {
|
|
76
|
+
p = `${p}${path}`;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return p;
|
|
80
|
+
};
|
|
81
|
+
exports.mergePath = mergePath;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hono",
|
|
3
|
-
"version": "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",
|
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
"./logger": "./dist/middleware/logger/logger.js",
|
|
17
17
|
"./mustache": "./dist/middleware/mustache/mustache.js",
|
|
18
18
|
"./powered-by": "./dist/middleware/powered-by/powered-by.js",
|
|
19
|
-
"./serve-static": "./dist/middleware/serve-static/serve-static.js"
|
|
19
|
+
"./serve-static": "./dist/middleware/serve-static/serve-static.js",
|
|
20
|
+
"./utils/buffer": "./dist/utils/buffer.js",
|
|
21
|
+
"./package.json": "./package.json"
|
|
20
22
|
},
|
|
21
23
|
"typesVersions": {
|
|
22
24
|
"*": {
|
|
@@ -48,7 +50,7 @@
|
|
|
48
50
|
},
|
|
49
51
|
"scripts": {
|
|
50
52
|
"test": "jest",
|
|
51
|
-
"lint": "eslint --ext js,ts src .eslintrc.js test",
|
|
53
|
+
"lint": "eslint --ext js,ts src .eslintrc.js test && prettier --check src",
|
|
52
54
|
"build": "rimraf dist && tsc",
|
|
53
55
|
"watch": "tsc -w",
|
|
54
56
|
"prepublishOnly": "yarn build"
|
|
@@ -80,18 +82,18 @@
|
|
|
80
82
|
"@typescript-eslint/eslint-plugin": "^5.9.0",
|
|
81
83
|
"@typescript-eslint/parser": "^5.9.0",
|
|
82
84
|
"eslint": "^7.26.0",
|
|
83
|
-
"eslint-config-prettier": "^8.
|
|
85
|
+
"eslint-config-prettier": "^8.3.0",
|
|
84
86
|
"eslint-define-config": "^1.2.1",
|
|
85
87
|
"eslint-import-resolver-typescript": "^2.0.0",
|
|
86
88
|
"eslint-plugin-eslint-comments": "^3.2.0",
|
|
87
89
|
"eslint-plugin-flowtype": "^5.7.2",
|
|
88
90
|
"eslint-plugin-import": "^2.20.2",
|
|
89
91
|
"eslint-plugin-node": "^11.1.0",
|
|
90
|
-
"eslint-plugin-prettier": "^4.0.0",
|
|
91
92
|
"form-data": "^4.0.0",
|
|
92
93
|
"jest": "^27.4.5",
|
|
93
94
|
"jest-environment-miniflare": "^2.0.0",
|
|
94
95
|
"mustache": "^4.2.0",
|
|
96
|
+
"prettier": "^2.5.1",
|
|
95
97
|
"rimraf": "^3.0.2",
|
|
96
98
|
"ts-jest": "^27.1.2",
|
|
97
99
|
"typescript": "^4.5.5"
|
package/dist/middleware.d.ts
DELETED