hono 0.4.1 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,14 @@
1
1
  # Hono
2
2
 
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.
3
+ [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/yusukebe/hono/ci)](https://github.com/yusukebe/hono/actions)
4
+ [![GitHub](https://img.shields.io/github/license/yusukebe/hono)](https://github.com/yusukebe/hono/blob/master/LICENSE)
5
+ [![npm](https://img.shields.io/npm/v/hono)](https://www.npmjs.com/package/hono)
6
+ [![npm](https://img.shields.io/npm/dm/hono)](https://www.npmjs.com/package/hono)
7
+ [![npm type definitions](https://img.shields.io/npm/types/hono)](https://www.npmjs.com/package/hono)
8
+ [![GitHub commit activity](https://img.shields.io/github/commit-activity/m/yusukebe/hono)](https://github.com/yusukebe/hono/pulse)
9
+ [![GitHub last commit](https://img.shields.io/github/last-commit/yusukebe/hono)](https://github.com/yusukebe/hono/commits/master)
10
+
11
+ Hono[炎] - _**means flame🔥 in Japanese**_ - is small, simple, and ultrafast web framework for Service Worker based serverless applications like Cloudflare Workers and Fastly Compute@Edge.
4
12
 
5
13
  ```js
6
14
  import { Hono } from 'hono'
@@ -13,9 +21,9 @@ app.fire()
13
21
 
14
22
  ## Features
15
23
 
16
- - **Ultra fast** - the router is implemented with Trie-Tree structure. Not use loops.
17
- - **Zero dependencies** - using only Web standard API.
18
- - **Middleware** - builtin middleware, and you can make your own middleware.
24
+ - **Ultrafast** - the router does not use linear loops.
25
+ - **Zero-dependencies** - using only Web standard API.
26
+ - **Middleware** - builtin middleware and your own middleware.
19
27
  - **Optimized** - for Cloudflare Workers.
20
28
 
21
29
  ## Benchmark
@@ -23,17 +31,17 @@ app.fire()
23
31
  **Hono is fastest** compared to other routers for Cloudflare Workers.
24
32
 
25
33
  ```plain
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)
34
+ hono x 809,503 ops/sec ±6.94% (73 runs sampled)
35
+ itty-router x 157,310 ops/sec ±4.31% (87 runs sampled)
36
+ sunder x 328,350 ops/sec ±2.30% (95 runs sampled)
37
+ worktop x 209,758 ops/sec ±4.28% (83 runs sampled)
30
38
  Fastest is hono
31
- ✨ Done in 58.29s.
39
+ ✨ Done in 60.66s.
32
40
  ```
33
41
 
34
42
  ## Hono in 1 minute
35
43
 
36
- Below is a demonstration to create an application of Cloudflare Workers with Hono.
44
+ A demonstration to create an application of Cloudflare Workers with Hono.
37
45
 
38
46
  ![Demo](https://user-images.githubusercontent.com/10682/151973526-342644f9-71c5-4fee-81f4-64a7558bb192.gif)
39
47
 
@@ -43,26 +51,28 @@ Now, the named path parameter has types.
43
51
 
44
52
  ## Install
45
53
 
46
- You can install Hono from npm registry:
54
+ You can install Hono from the npm registry.
47
55
 
48
56
  ```sh
49
- yarn add hono
57
+ $ yarn add hono
50
58
  ```
51
59
 
52
60
  or
53
61
 
54
62
  ```sh
55
- npm install hono
63
+ $ npm install hono
56
64
  ```
57
65
 
58
66
  ## Methods
59
67
 
60
- An instance of `Hono` has these methods:
68
+ An instance of `Hono` has these methods.
61
69
 
62
70
  - app.**HTTP_METHOD**(path, handler)
63
71
  - app.**all**(path, handler)
64
72
  - app.**route**(path)
65
73
  - app.**use**(path, middleware)
74
+ - app.**notFound**(handler)
75
+ - app.**onError**(err, handler)
66
76
  - app.**fire**()
67
77
  - app.**fetch**(request, env, event)
68
78
 
@@ -70,8 +80,6 @@ An instance of `Hono` has these methods:
70
80
 
71
81
  ### Basic
72
82
 
73
- `app.HTTP_METHOD`
74
-
75
83
  ```js
76
84
  // HTTP Methods
77
85
  app.get('/', (c) => c.text('GET /'))
@@ -81,11 +89,7 @@ app.post('/', (c) => c.text('POST /'))
81
89
  app.get('/wild/*/card', (c) => {
82
90
  return c.text('GET /wild/*/card')
83
91
  })
84
- ```
85
92
 
86
- `app.all`
87
-
88
- ```js
89
93
  // Any HTTP methods
90
94
  app.all('/hello', (c) => c.text('Any Method /hello'))
91
95
  ```
@@ -106,33 +110,32 @@ app.get('/post/:date{[0-9]+}/:title{[a-z]+}', (c) => {
106
110
  const date = c.req.param('date')
107
111
  const title = c.req.param('title')
108
112
  ...
113
+ })
109
114
  ```
110
115
 
111
116
  ### Nested route
112
117
 
113
118
  ```js
114
119
  const book = app.route('/book')
115
- book.get('/', (c) => c.text('List Books')) // => GET /book
120
+ book.get('/', (c) => c.text('List Books')) // GET /book
116
121
  book.get('/:id', (c) => {
117
- // => GET /book/:id
122
+ // GET /book/:id
118
123
  const id = c.req.param('id')
119
124
  return c.text('Get Book: ' + id)
120
125
  })
121
- book.post('/', (c) => c.text('Create Book')) // => POST /book
126
+ book.post('/', (c) => c.text('Create Book')) // POST /book
122
127
  ```
123
128
 
124
129
  ### no strict
125
130
 
126
- If `strict` is set `false`, `/hello`and`/hello/` are treated the same:
131
+ If `strict` is set false, `/hello`and`/hello/` are treated the same.
127
132
 
128
133
  ```js
129
- const app = new Hono({ strict: false })
134
+ const app = new Hono({ strict: false }) // Default is true
130
135
 
131
136
  app.get('/hello', (c) => c.text('/hello or /hello/'))
132
137
  ```
133
138
 
134
- Default is `true`.
135
-
136
139
  ### async/await
137
140
 
138
141
  ```js
@@ -156,14 +159,20 @@ const app = new Hono()
156
159
 
157
160
  app.use('*', poweredBy())
158
161
  app.use('*', logger())
159
- app.use('/auth/*', basicAuth({ username: 'hono', password: 'acoolproject' }))
162
+ app.use(
163
+ '/auth/*',
164
+ basicAuth({
165
+ username: 'hono',
166
+ password: 'acoolproject',
167
+ })
168
+ )
160
169
  ```
161
170
 
162
171
  Available builtin middleware are listed on [src/middleware](https://github.com/yusukebe/hono/tree/master/src/middleware).
163
172
 
164
173
  ### Custom Middleware
165
174
 
166
- You can write your own middleware:
175
+ You can write your own middleware.
167
176
 
168
177
  ```js
169
178
  // Custom logger
@@ -175,15 +184,36 @@ app.use('*', async (c, next) => {
175
184
  // Add a custom header
176
185
  app.use('/message/*', async (c, next) => {
177
186
  await next()
178
- await c.header('x-message', 'This is middleware!')
187
+ c.header('x-message', 'This is middleware!')
179
188
  })
180
189
 
181
190
  app.get('/message/hello', (c) => c.text('Hello Middleware!'))
182
191
  ```
183
192
 
193
+ ## Not Found
194
+
195
+ `app.notFound` for customizing Not Found Response.
196
+
197
+ ```js
198
+ app.notFound((c) => {
199
+ return c.text('Custom 404 Message', 404)
200
+ })
201
+ ```
202
+
203
+ ## Error Handling
204
+
205
+ `app.onError` handle the error and return the customized Response.
206
+
207
+ ```js
208
+ app.onError((err, c) => {
209
+ console.error(`${err}`)
210
+ return c.text('Custom Error Message', 500)
211
+ })
212
+ ```
213
+
184
214
  ## Context
185
215
 
186
- To handle Request and Reponse, you can use Context object:
216
+ To handle Request and Reponse, you can use Context object.
187
217
 
188
218
  ### c.req
189
219
 
@@ -217,30 +247,33 @@ app.get('/entry/:id', (c) => {
217
247
 
218
248
  ```js
219
249
  app.get('/welcome', (c) => {
250
+ // Set headers
220
251
  c.header('X-Message', 'Hello!')
221
252
  c.header('Content-Type', 'text/plain')
253
+ // Set HTTP status code
222
254
  c.status(201)
223
-
255
+ // Return the response body
224
256
  return c.body('Thank you for comming')
257
+ })
258
+ ```
225
259
 
226
- /*
227
- Same as:
228
- return new Response('Thank you for comming', {
229
- status: 201,
230
- statusText: 'Created',
231
- headers: {
232
- 'X-Message': 'Hello',
233
- 'Content-Type': 'text/plain',
234
- 'Content-Length: '22'
235
- }
236
- })
237
- */
260
+ The Response is the same as below.
261
+
262
+ ```js
263
+ new Response('Thank you for comming', {
264
+ status: 201,
265
+ statusText: 'Created',
266
+ headers: {
267
+ 'X-Message': 'Hello',
268
+ 'Content-Type': 'text/plain',
269
+ 'Content-Length': '22',
270
+ },
238
271
  })
239
272
  ```
240
273
 
241
274
  ### c.text()
242
275
 
243
- Render text as `Content-Type:text/plain`:
276
+ Render texts as `Content-Type:text/plain`.
244
277
 
245
278
  ```js
246
279
  app.get('/say', (c) => {
@@ -250,7 +283,7 @@ app.get('/say', (c) => {
250
283
 
251
284
  ### c.json()
252
285
 
253
- Render JSON as `Content-Type:application/json`:
286
+ Render JSON as `Content-Type:application/json`.
254
287
 
255
288
  ```js
256
289
  app.get('/api', (c) => {
@@ -260,7 +293,7 @@ app.get('/api', (c) => {
260
293
 
261
294
  ### c.html()
262
295
 
263
- Render HTML as `Content-Type:text/html`:
296
+ Render HTML as `Content-Type:text/html`.
264
297
 
265
298
  ```js
266
299
  app.get('/', (c) => {
@@ -270,7 +303,7 @@ app.get('/', (c) => {
270
303
 
271
304
  ### c.notFound()
272
305
 
273
- Return the default `404 Not Found` Response:
306
+ Return the `404 Not Found` Response.
274
307
 
275
308
  ```js
276
309
  app.get('/notfound', (c) => {
@@ -280,7 +313,7 @@ app.get('/notfound', (c) => {
280
313
 
281
314
  ### c.redirect()
282
315
 
283
- Redirect, default status code is `302`:
316
+ Redirect, default status code is `302`.
284
317
 
285
318
  ```js
286
319
  app.get('/redirect', (c) => c.redirect('/'))
@@ -319,29 +352,9 @@ app.get('*', async c => {
319
352
  })
320
353
  ```
321
354
 
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
-
342
355
  ## fire
343
356
 
344
- `app.fire()` do:
357
+ `app.fire()` do this.
345
358
 
346
359
  ```js
347
360
  addEventListener('fetch', (event) => {
@@ -351,7 +364,7 @@ addEventListener('fetch', (event) => {
351
364
 
352
365
  ## fetch
353
366
 
354
- `app.fetch()` is for Cloudflare Module Worker syntax.
367
+ `app.fetch` for Cloudflare Module Worker syntax.
355
368
 
356
369
  ```js
357
370
  export default {
@@ -361,7 +374,7 @@ export default {
361
374
  }
362
375
 
363
376
  /*
364
- or just do this:
377
+ or just do:
365
378
  export default app
366
379
  */
367
380
  ```
@@ -374,15 +387,15 @@ Let's write your first code for Cloudflare Workers with Hono.
374
387
 
375
388
  ### 1. Install Wrangler
376
389
 
377
- Install Cloudflare Command Line "[Wrangler](https://github.com/cloudflare/wrangler)"
390
+ Install Cloudflare Command Line "[Wrangler](https://github.com/cloudflare/wrangler)".
378
391
 
379
392
  ```sh
380
- npm i @cloudflare/wrangler -g
393
+ $ npm i @cloudflare/wrangler -g
381
394
  ```
382
395
 
383
396
  ### 2. `npm init`
384
397
 
385
- Make npm skeleton directory.
398
+ Make a npm skeleton directory.
386
399
 
387
400
  ```sh
388
401
  mkdir hono-example
@@ -395,15 +408,15 @@ npm init -y
395
408
  Init as a wrangler project.
396
409
 
397
410
  ```sh
398
- wrangler init
411
+ $ wrangler init
399
412
  ```
400
413
 
401
414
  ### 4. `npm install hono`
402
415
 
403
- Install `hono` from npm registry.
416
+ Install `hono` from the npm registry.
404
417
 
405
418
  ```sh
406
- npm i hono
419
+ $ npm i hono
407
420
  ```
408
421
 
409
422
  ### 5. Write your app
@@ -421,10 +434,10 @@ app.fire()
421
434
 
422
435
  ### 6. Run
423
436
 
424
- Run the development server locally. Then, access like `http://127.0.0.1:8787/` in your Web browser.
437
+ Run the development server locally. Then, access `http://127.0.0.1:8787/` in your Web browser.
425
438
 
426
439
  ```sh
427
- wrangler dev
440
+ $ wrangler dev
428
441
  ```
429
442
 
430
443
  ### 7. Publish
@@ -432,12 +445,22 @@ wrangler dev
432
445
  Deploy to Cloudflare. That's all!
433
446
 
434
447
  ```sh
435
- wrangler publish
448
+ $ wrangler publish
449
+ ```
450
+
451
+ ## Starter template
452
+
453
+ You can start making your application of Cloudflare Workers with [the starter template](https://github.com/yusukebe/hono-minimal). It is a realy minimal using TypeScript, esbuild, and Miniflare.
454
+
455
+ To generate a project skelton, run this command.
456
+
457
+ ```
458
+ $ wrangler generate my-app https://github.com/yusukebe/hono-minimal
436
459
  ```
437
460
 
438
461
  ## Related projects
439
462
 
440
- Implementation of the router is inspired by [goblin](https://github.com/bmf-san/goblin). API design is inspired by [express](https://github.com/expressjs/express) and [koa](https://github.com/koajs/koa). [itty-router](https://github.com/kwhitley/itty-router), [Sunder](https://github.com/SunderJS/sunder), and [worktop](https://github.com/lukeed/worktop) are the other routers or frameworks for Cloudflare Workers.
463
+ Implementation of the original router `TrieRouter` is inspired by [goblin](https://github.com/bmf-san/goblin). `RegExpRouter` is inspired by [Router::Boom](https://github.com/tokuhirom/Router-Boom). API design is inspired by [express](https://github.com/expressjs/express) and [koa](https://github.com/koajs/koa). [itty-router](https://github.com/kwhitley/itty-router), [Sunder](https://github.com/SunderJS/sunder), and [worktop](https://github.com/lukeed/worktop) are the other routers or frameworks for Cloudflare Workers.
441
464
 
442
465
  - express <https://github.com/expressjs/express>
443
466
  - koa <https://github.com/koajs/koa>
@@ -445,10 +468,11 @@ Implementation of the router is inspired by [goblin](https://github.com/bmf-san/
445
468
  - Sunder <https://github.com/SunderJS/sunder>
446
469
  - goblin <https://github.com/bmf-san/goblin>
447
470
  - worktop <https://github.com/lukeed/worktop>
471
+ - Router::Boom <https://github.com/tokuhirom/Router-Boom>
448
472
 
449
473
  ## Contributing
450
474
 
451
- Contributions Welcome! You can contribute by the following way:
475
+ Contributions Welcome! You can contribute by the following way.
452
476
 
453
477
  - Write or fix documents
454
478
  - Write code of middleware
@@ -456,7 +480,7 @@ Contributions Welcome! You can contribute by the following way:
456
480
  - Refactor the code
457
481
  - etc.
458
482
 
459
- If you can, let's make Hono together!
483
+ Let's make Hono together!
460
484
 
461
485
  ## Contributors
462
486
 
package/dist/compose.d.ts CHANGED
@@ -1 +1,2 @@
1
- export declare const compose: <T>(middleware: Function[], onError?: Function) => (context: T, next?: Function) => Promise<void | object>;
1
+ import type { ErrorHandler } from './hono';
2
+ export declare const compose: <C>(middleware: Function[], onError?: ErrorHandler) => (context: C) => Promise<C>;
package/dist/compose.js CHANGED
@@ -4,33 +4,31 @@ exports.compose = void 0;
4
4
  const context_1 = require("./context");
5
5
  // Based on the code in the MIT licensed `koa-compose` package.
6
6
  const compose = (middleware, onError) => {
7
- const errors = [];
8
- return function (context, next) {
7
+ return function (context) {
9
8
  let index = -1;
10
9
  return dispatch(0);
11
10
  async function dispatch(i) {
12
- if (i <= index)
13
- return Promise.reject(new Error('next() called multiple times'));
14
- index = i;
15
- let fn = middleware[i];
16
- if (i === middleware.length)
17
- fn = next;
18
- if (!fn)
19
- return Promise.resolve();
20
- try {
21
- return Promise.resolve(fn(context, dispatch.bind(null, i + 1))).catch((e) => {
22
- errors.push(e);
23
- if (onError && context instanceof context_1.Context) {
24
- context.res = onError(errors[0], context);
25
- }
26
- else {
27
- throw errors[0];
28
- }
29
- });
11
+ if (i === middleware.length) {
12
+ return context;
30
13
  }
31
- catch (err) {
32
- return Promise.reject(err);
14
+ if (i <= index) {
15
+ return Promise.reject(new Error('next() called multiple times'));
33
16
  }
17
+ const handler = middleware[i];
18
+ index = i;
19
+ return Promise.resolve(handler(context, dispatch.bind(null, i + 1)))
20
+ .then(() => {
21
+ return context;
22
+ })
23
+ .catch((err) => {
24
+ if (onError && context instanceof context_1.Context) {
25
+ context.res = onError(err, context);
26
+ return context;
27
+ }
28
+ else {
29
+ throw err;
30
+ }
31
+ });
34
32
  }
35
33
  };
36
34
  };
package/dist/hono.d.ts CHANGED
@@ -1,9 +1,7 @@
1
1
  /// <reference types="@cloudflare/workers-types" />
2
- import { Node } from './node';
3
2
  import { Context } from './context';
4
3
  import type { Env } from './context';
5
- import type { Result } from './router';
6
- import { Router } from './router';
4
+ import type { Result, Router } from './router';
7
5
  declare global {
8
6
  interface Request<ParamKeyType = string> {
9
7
  param: (key: ParamKeyType) => string;
@@ -14,15 +12,11 @@ declare global {
14
12
  }
15
13
  export declare type Handler<RequestParamKeyType = string> = (c: Context<RequestParamKeyType>, next?: Function) => Response | Promise<Response>;
16
14
  export declare type MiddlewareHandler = (c: Context, next: Function) => Promise<void>;
15
+ export declare type NotFoundHandler = (c: Context) => Response;
16
+ export declare type ErrorHandler = (err: Error, c: Context) => Response;
17
17
  declare type ParamKeyName<NameWithPattern> = NameWithPattern extends `${infer Name}{${infer _Pattern}` ? Name : NameWithPattern;
18
18
  declare type ParamKey<Component> = Component extends `:${infer NameWithPattern}` ? ParamKeyName<NameWithPattern> : never;
19
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> {
21
- node: Node<T>;
22
- constructor();
23
- add(method: string, path: string, handler: T): void;
24
- match(method: string, path: string): Result<T> | null;
25
- }
26
20
  export declare class Hono {
27
21
  routerClass: {
28
22
  new (): Router<any>;
@@ -32,6 +26,8 @@ export declare class Hono {
32
26
  middlewareRouters: Router<MiddlewareHandler>[];
33
27
  tempPath: string;
34
28
  constructor(init?: Partial<Pick<Hono, 'routerClass' | 'strict'>>);
29
+ notFoundHandler: NotFoundHandler;
30
+ errorHandler: ErrorHandler;
35
31
  get<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono;
36
32
  post<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono;
37
33
  put<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono;
@@ -42,13 +38,13 @@ export declare class Hono {
42
38
  all<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono;
43
39
  route(path: string): Hono;
44
40
  use(path: string, middleware: MiddlewareHandler): void;
41
+ onError(handler: ErrorHandler): Hono;
42
+ notFound(handler: NotFoundHandler): Hono;
45
43
  addRoute(method: string, path: string, handler: Handler): Hono;
46
44
  matchRoute(method: string, path: string): Promise<Result<Handler>>;
47
45
  dispatch(request: Request, env?: Env, event?: FetchEvent): Promise<Response>;
48
46
  handleEvent(event: FetchEvent): Promise<Response>;
49
47
  fetch(request: Request, env?: Env, event?: FetchEvent): Promise<Response>;
50
48
  fire(): void;
51
- onError(err: Error, c: Context): Response;
52
- notFound(c: Context): Response;
53
49
  }
54
50
  export {};
package/dist/hono.js CHANGED
@@ -1,28 +1,24 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Hono = exports.TrieRouter = 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
7
  const router_1 = require("./router");
9
- class TrieRouter extends router_1.Router {
10
- constructor() {
11
- super();
12
- this.node = new node_1.Node();
13
- }
14
- add(method, path, handler) {
15
- this.node.insert(method, path, handler);
16
- }
17
- match(method, path) {
18
- return this.node.search(method, path);
19
- }
20
- }
21
- exports.TrieRouter = TrieRouter;
8
+ const trie_router_1 = require("./router/trie-router"); // Default Router
22
9
  class Hono {
23
10
  constructor(init = {}) {
24
- this.routerClass = TrieRouter;
11
+ this.routerClass = trie_router_1.TrieRouter;
25
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
+ };
26
22
  Object.assign(this, init);
27
23
  this.router = new this.routerClass();
28
24
  this.middlewareRouters = [];
@@ -66,6 +62,14 @@ class Hono {
66
62
  router.add(router_1.METHOD_NAME_OF_ALL, path, middleware);
67
63
  this.middlewareRouters.push(router);
68
64
  }
65
+ onError(handler) {
66
+ this.errorHandler = handler;
67
+ return this;
68
+ }
69
+ notFound(handler) {
70
+ this.notFoundHandler = handler;
71
+ return this;
72
+ }
69
73
  // addRoute('get', '/', handler)
70
74
  addRoute(method, path, handler) {
71
75
  method = method.toUpperCase();
@@ -95,7 +99,7 @@ class Hono {
95
99
  const url = new URL(c.req.url);
96
100
  return url.searchParams.get(key);
97
101
  };
98
- const handler = result ? result.handler : this.notFound;
102
+ const handler = result ? result.handler : this.notFoundHandler;
99
103
  const middleware = [];
100
104
  for (const mr of this.middlewareRouters) {
101
105
  const mwResult = mr.match(router_1.METHOD_NAME_OF_ALL, path);
@@ -112,11 +116,11 @@ class Hono {
112
116
  await next();
113
117
  };
114
118
  middleware.push(wrappedHandler);
115
- const composed = (0, compose_1.compose)(middleware, this.onError);
119
+ const composed = (0, compose_1.compose)(middleware, this.errorHandler);
116
120
  const c = new context_1.Context(request, { env: env, event: event, res: null });
117
- c.notFound = () => this.notFound(c);
118
- await composed(c);
119
- return c.res;
121
+ c.notFound = () => this.notFoundHandler(c);
122
+ const context = await composed(c);
123
+ return context.res;
120
124
  }
121
125
  async handleEvent(event) {
122
126
  return this.dispatch(event.request, {}, event);
@@ -129,16 +133,5 @@ class Hono {
129
133
  event.respondWith(this.handleEvent(event));
130
134
  });
131
135
  }
132
- // Default error Response
133
- onError(err, c) {
134
- console.error(`${err.message}`);
135
- const message = 'Internal Server Error';
136
- return c.text(message, 500);
137
- }
138
- // Default 404 not found Response
139
- notFound(c) {
140
- const message = 'Not Found';
141
- return c.text(message, 404);
142
- }
143
136
  }
144
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>;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.basicAuth = void 0;
4
4
  const buffer_1 = require("../../utils/buffer");
5
+ const crypto_1 = require("../../utils/crypto");
5
6
  const CREDENTIALS_REGEXP = /^ *(?:[Bb][Aa][Ss][Ii][Cc]) +([A-Za-z0-9._~+/-]+=*) *$/;
6
7
  const USER_PASS_REGEXP = /^([^:]*):(.*)$/;
7
8
  const auth = (req) => {
@@ -18,30 +19,39 @@ const auth = (req) => {
18
19
  if (!match) {
19
20
  return undefined;
20
21
  }
21
- const userPass = USER_PASS_REGEXP.exec((0, buffer_1.decodeBase64)(match[1]));
22
+ const userPass = USER_PASS_REGEXP.exec((0, crypto_1.decodeBase64)(match[1]));
22
23
  if (!userPass) {
23
24
  return undefined;
24
25
  }
25
26
  return { username: userPass[1], password: userPass[2] };
26
27
  };
27
- const basicAuth = (options) => {
28
+ const basicAuth = (options, ...users) => {
29
+ if (!options) {
30
+ throw new Error('basic auth middleware requires options for "username and password"');
31
+ }
28
32
  if (!options.realm) {
29
33
  options.realm = 'Secure Area';
30
34
  }
35
+ users.unshift({ username: options.username, password: options.password });
31
36
  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;
37
+ const requestUser = auth(ctx.req);
38
+ if (requestUser) {
39
+ for (const user of users) {
40
+ const usernameEqual = await (0, buffer_1.timingSafeEqual)(user.username, requestUser.username);
41
+ const passwordEqual = await (0, buffer_1.timingSafeEqual)(user.password, requestUser.password);
42
+ if (usernameEqual && passwordEqual) {
43
+ // Authorized OK
44
+ return next();
45
+ }
46
+ }
43
47
  }
44
- return next();
48
+ ctx.res = new Response('Unauthorized', {
49
+ status: 401,
50
+ headers: {
51
+ 'WWW-Authenticate': 'Basic realm="' + options.realm.replace(/"/g, '\\"') + '"',
52
+ },
53
+ });
54
+ return;
45
55
  };
46
56
  };
47
57
  exports.basicAuth = basicAuth;
@@ -1,26 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.bodyParse = void 0;
4
+ const body_1 = require("../../utils/body");
4
5
  const bodyParse = () => {
5
6
  return async (ctx, next) => {
6
- const contentType = ctx.req.headers.get('Content-Type') || '';
7
- if (contentType.includes('application/json')) {
8
- ctx.req.parsedBody = await ctx.req.json();
9
- }
10
- else if (contentType.includes('application/text')) {
11
- ctx.req.parsedBody = await ctx.req.text();
12
- }
13
- else if (contentType.includes('text/html')) {
14
- ctx.req.parsedBody = await ctx.req.text();
15
- }
16
- else if (contentType.includes('form')) {
17
- const form = {};
18
- const data = [...(await ctx.req.formData())].reduce((acc, cur) => {
19
- acc[cur[0]] = cur[1];
20
- return acc;
21
- }, form);
22
- ctx.req.parsedBody = data;
23
- }
7
+ ctx.req.parsedBody = await (0, body_1.parseBody)(ctx.req);
24
8
  await next();
25
9
  };
26
10
  };
@@ -0,0 +1,6 @@
1
+ import type { Context } from '../../context';
2
+ declare type ETagOptions = {
3
+ weak: boolean;
4
+ };
5
+ export declare const etag: (options?: ETagOptions) => (c: Context, next: Function) => Promise<void>;
6
+ export {};
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.etag = void 0;
4
+ const crypto_1 = require("../../utils/crypto");
5
+ const body_1 = require("../../utils/body");
6
+ const etag = (options = { weak: false }) => {
7
+ return async (c, next) => {
8
+ const ifNoneMatch = c.req.header('If-None-Match') || c.req.header('if-none-match');
9
+ await next();
10
+ const clone = c.res.clone();
11
+ const body = await (0, body_1.parseBody)(c.res);
12
+ const hash = await (0, crypto_1.sha1)(body);
13
+ const etag = options.weak ? `W/"${hash}"` : `"${hash}"`;
14
+ if (ifNoneMatch && ifNoneMatch === etag) {
15
+ await clone.blob(); // Force using body
16
+ c.res = new Response(null, {
17
+ status: 304,
18
+ statusText: 'Not Modified',
19
+ });
20
+ c.res.headers.delete('Content-Length');
21
+ }
22
+ else {
23
+ c.res = new Response(clone.body, clone);
24
+ c.res.headers.append('ETag', etag);
25
+ }
26
+ };
27
+ };
28
+ exports.etag = etag;
@@ -1,6 +1,4 @@
1
- export interface ParamMap {
2
- [key: string]: number;
3
- }
1
+ export declare type ParamMap = Array<[string, number]>;
4
2
  export interface Context {
5
3
  varIndex: number;
6
4
  }
@@ -64,7 +64,7 @@ class Node {
64
64
  }
65
65
  }
66
66
  if (name !== '') {
67
- paramMap[name] = node.varIndex;
67
+ paramMap.push([name, node.varIndex]);
68
68
  }
69
69
  }
70
70
  else {
@@ -41,9 +41,8 @@ class RegExpRouter extends router_1.Router {
41
41
  const index = match.indexOf('', 1);
42
42
  const [handler, paramMap] = handlers[replacementMap[index]];
43
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]]];
44
+ for (let i = 0; i < paramMap.length; i++) {
45
+ params[paramMap[i][0]] = match[paramMap[i][1]];
47
46
  }
48
47
  return new router_1.Result(handler, params);
49
48
  }
@@ -68,7 +67,7 @@ class RegExpRouter extends router_1.Router {
68
67
  return;
69
68
  }
70
69
  if (routes.length === 1 && routes[0][0] === '*') {
71
- this.matchers[method] = [true, null, [[routes[0][1], {}]]];
70
+ this.matchers[method] = [true, null, [[routes[0][1], []]]];
72
71
  return;
73
72
  }
74
73
  if (routes.length === 1 && !routes[0][0].match(/:/)) {
@@ -77,7 +76,7 @@ class RegExpRouter extends router_1.Router {
77
76
  ? routes[0][0].replace(/\/\*$/, '(?:$|/)') // /path/to/* => /path/to(?:$|/)
78
77
  : `${routes[0][0]}$`; // /path/to/action => /path/to/action$
79
78
  const regExpStr = `^${tmp.replace(/\*/g, '[^/]+')}`; // /prefix/*/path/to => /prefix/[^/]+/path/to
80
- this.matchers[method] = [new RegExp(regExpStr), null, [[routes[0][1], {}]]];
79
+ this.matchers[method] = [new RegExp(regExpStr), null, [[routes[0][1], []]]];
81
80
  return;
82
81
  }
83
82
  for (let i = 0; i < routes.length; i++) {
@@ -87,9 +86,9 @@ class RegExpRouter extends router_1.Router {
87
86
  const [regexp, indexReplacementMap, paramReplacementMap] = trie.buildRegExp();
88
87
  for (let i = 0; i < handlers.length; i++) {
89
88
  const paramMap = handlers[i][1];
90
- Object.keys(paramMap).forEach((k) => {
91
- paramMap[k] = paramReplacementMap[paramMap[k]];
92
- });
89
+ for (let i = 0; i < paramMap.length; i++) {
90
+ paramMap[i][1] = paramReplacementMap[paramMap[i][1]];
91
+ }
93
92
  }
94
93
  this.matchers[method] = [new RegExp(regexp), indexReplacementMap, handlers];
95
94
  }
@@ -8,7 +8,7 @@ class Trie {
8
8
  this.root = new node_1.Node();
9
9
  }
10
10
  insert(path, index) {
11
- const paramMap = {};
11
+ const paramMap = [];
12
12
  /**
13
13
  * - pattern (:label, :label{0-9]+}, ...)
14
14
  * - /* wildcard
@@ -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,5 +1,5 @@
1
- import type { Pattern } from './utils/url';
2
- import { Result } from './router';
1
+ import type { Pattern } from '../../utils/url';
2
+ import { Result } from '../../router';
3
3
  export declare class Node<T> {
4
4
  method: Record<string, T>;
5
5
  handler: T;
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Node = void 0;
4
- const url_1 = require("./utils/url");
5
- const router_1 = require("./router");
4
+ const url_1 = require("../../utils/url");
5
+ const router_1 = require("../../router");
6
6
  const noRoute = () => {
7
7
  return null;
8
8
  };
@@ -0,0 +1,9 @@
1
+ import { Router } from '../../router';
2
+ import type { Result } from '../../router';
3
+ import { Node } from './node';
4
+ export declare class TrieRouter<T> extends Router<T> {
5
+ node: Node<T>;
6
+ constructor();
7
+ add(method: string, path: string, handler: T): void;
8
+ match(method: string, path: string): Result<T> | null;
9
+ }
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TrieRouter = void 0;
4
+ const router_1 = require("../../router");
5
+ const node_1 = require("./node");
6
+ class TrieRouter extends router_1.Router {
7
+ constructor() {
8
+ super();
9
+ this.node = new node_1.Node();
10
+ }
11
+ add(method, path, handler) {
12
+ this.node.insert(method, path, handler);
13
+ }
14
+ match(method, path) {
15
+ return this.node.search(method, path);
16
+ }
17
+ }
18
+ exports.TrieRouter = TrieRouter;
@@ -0,0 +1 @@
1
+ export declare const parseBody: (r: Request | Response) => Promise<string | object | Record<string, string | File>>;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseBody = void 0;
4
+ const parseBody = async (r) => {
5
+ const contentType = r.headers.get('Content-Type') || '';
6
+ if (contentType.includes('application/json')) {
7
+ return await r.json();
8
+ }
9
+ else if (contentType.includes('application/text')) {
10
+ return r.text();
11
+ }
12
+ else if (contentType.startsWith('text')) {
13
+ return r.text();
14
+ }
15
+ else if (contentType.includes('form')) {
16
+ const form = {};
17
+ const data = [...(await r.formData())].reduce((acc, cur) => {
18
+ acc[cur[0]] = cur[1];
19
+ return acc;
20
+ }, form);
21
+ return data;
22
+ }
23
+ };
24
+ exports.parseBody = parseBody;
@@ -1,4 +1,2 @@
1
1
  export declare const equal: (a: ArrayBuffer, b: ArrayBuffer) => boolean;
2
- export declare const decodeBase64: (str: string) => any;
3
- export declare const sha256: (a: string | object | boolean) => Promise<string>;
4
2
  export declare const timingSafeEqual: (a: string | object | boolean, b: string | object | boolean) => Promise<boolean>;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.timingSafeEqual = exports.sha256 = exports.decodeBase64 = exports.equal = void 0;
3
+ exports.timingSafeEqual = exports.equal = void 0;
4
+ const crypto_1 = require("./crypto");
4
5
  const equal = (a, b) => {
5
6
  if (a === b) {
6
7
  return true;
@@ -19,52 +20,9 @@ const equal = (a, b) => {
19
20
  return true;
20
21
  };
21
22
  exports.equal = equal;
22
- const decodeBase64 = (str) => {
23
- try {
24
- const text = atob(str);
25
- const length = text.length;
26
- const bytes = new Uint8Array(length);
27
- for (let i = 0; i < length; i++) {
28
- bytes[i] = text.charCodeAt(i);
29
- }
30
- const decoder = new TextDecoder();
31
- return decoder.decode(bytes);
32
- }
33
- catch (_a) { }
34
- try {
35
- const { Buffer } = require('buffer');
36
- return Buffer.from(str, 'base64').toString();
37
- }
38
- catch (e) {
39
- console.error('If you want to do "decodeBase64", polyfill "buffer" module.');
40
- throw e;
41
- }
42
- };
43
- exports.decodeBase64 = decodeBase64;
44
- const sha256 = async (a) => {
45
- if (crypto && crypto.subtle) {
46
- const buffer = await crypto.subtle.digest({
47
- name: 'SHA-256',
48
- }, new TextEncoder().encode(String(a)));
49
- const hash = Array.prototype.map
50
- .call(new Uint8Array(buffer), (x) => ('00' + x.toString(16)).slice(-2))
51
- .join('');
52
- return hash;
53
- }
54
- try {
55
- const crypto = require('crypto');
56
- const hash = crypto.createHash('sha256').update(a).digest('hex');
57
- return hash;
58
- }
59
- catch (e) {
60
- console.error('If you want to do "sha256", polyfill "crypto" module.');
61
- throw e;
62
- }
63
- };
64
- exports.sha256 = sha256;
65
23
  const timingSafeEqual = async (a, b) => {
66
- const sa = await (0, exports.sha256)(a);
67
- const sb = await (0, exports.sha256)(b);
24
+ const sa = await (0, crypto_1.sha256)(a);
25
+ const sb = await (0, crypto_1.sha256)(b);
68
26
  return sa === sb && a === b;
69
27
  };
70
28
  exports.timingSafeEqual = timingSafeEqual;
@@ -0,0 +1,10 @@
1
+ declare type Algorithm = {
2
+ name: string;
3
+ alias: string;
4
+ };
5
+ declare type Data = string | object | boolean;
6
+ export declare const sha256: (data: Data) => Promise<string>;
7
+ export declare const sha1: (data: Data) => Promise<string>;
8
+ export declare const createHash: (data: Data, algorithm: Algorithm) => Promise<string>;
9
+ export declare const decodeBase64: (str: string) => any;
10
+ export {};
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.decodeBase64 = exports.createHash = exports.sha1 = exports.sha256 = void 0;
4
+ const sha256 = async (data) => {
5
+ const algorithm = { name: 'SHA-256', alias: 'sha256' };
6
+ const hash = await (0, exports.createHash)(data, algorithm);
7
+ return hash;
8
+ };
9
+ exports.sha256 = sha256;
10
+ const sha1 = async (data) => {
11
+ const algorithm = { name: 'SHA-1', alias: 'sha1' };
12
+ const hash = await (0, exports.createHash)(data, algorithm);
13
+ return hash;
14
+ };
15
+ exports.sha1 = sha1;
16
+ const createHash = async (data, algorithm) => {
17
+ if (crypto && crypto.subtle) {
18
+ const buffer = await crypto.subtle.digest({
19
+ name: algorithm.name,
20
+ }, new TextEncoder().encode(String(data)));
21
+ const hash = Array.prototype.map
22
+ .call(new Uint8Array(buffer), (x) => ('00' + x.toString(16)).slice(-2))
23
+ .join('');
24
+ return hash;
25
+ }
26
+ try {
27
+ const crypto = require('crypto');
28
+ const hash = crypto.createHash(algorithm.alias).update(data).digest('hex');
29
+ return hash;
30
+ }
31
+ catch (e) {
32
+ console.error(`If you want to create hash ${algorithm.name}, polyfill "crypto" module.`);
33
+ throw e;
34
+ }
35
+ };
36
+ exports.createHash = createHash;
37
+ const decodeBase64 = (str) => {
38
+ try {
39
+ const text = atob(str);
40
+ const length = text.length;
41
+ const bytes = new Uint8Array(length);
42
+ for (let i = 0; i < length; i++) {
43
+ bytes[i] = text.charCodeAt(i);
44
+ }
45
+ const decoder = new TextDecoder();
46
+ return decoder.decode(bytes);
47
+ }
48
+ catch (_a) { }
49
+ try {
50
+ const { Buffer } = require('buffer');
51
+ return Buffer.from(str, 'base64').toString();
52
+ }
53
+ catch (e) {
54
+ console.error('If you want to do "decodeBase64", polyfill "buffer" module.');
55
+ throw e;
56
+ }
57
+ };
58
+ exports.decodeBase64 = decodeBase64;
@@ -4,11 +4,10 @@ exports.getMimeType = void 0;
4
4
  const getMimeType = (filename) => {
5
5
  const regexp = /\.([a-zA-Z0-9]+?)$/;
6
6
  const match = filename.match(regexp);
7
- if (!match) {
7
+ if (!match)
8
8
  return;
9
- }
10
9
  let mimeType = mimes[match[1]];
11
- if (mimeType.startsWith('text') || mimeType === 'application/json') {
10
+ if ((mimeType && mimeType.startsWith('text')) || mimeType === 'application/json') {
12
11
  mimeType += '; charset=utf-8';
13
12
  }
14
13
  return mimeType;
@@ -43,6 +42,7 @@ const mimes = {
43
42
  js: 'text/javascript',
44
43
  json: 'application/json',
45
44
  jsonld: 'application/ld+json',
45
+ map: 'application/json',
46
46
  mid: 'audio/x-midi',
47
47
  midi: 'audio/x-midi',
48
48
  mjs: 'text/javascript',
package/package.json CHANGED
@@ -1,12 +1,19 @@
1
1
  {
2
2
  "name": "hono",
3
- "version": "0.4.1",
3
+ "version": "0.5.1",
4
4
  "description": "[炎] Ultrafast web framework for Cloudflare Workers.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "files": [
8
8
  "dist"
9
9
  ],
10
+ "scripts": {
11
+ "test": "jest",
12
+ "lint": "eslint --ext js,ts src .eslintrc.js && prettier --check src",
13
+ "build": "rimraf dist && tsc",
14
+ "watch": "tsc -w",
15
+ "prepublishOnly": "yarn build"
16
+ },
10
17
  "exports": {
11
18
  ".": "./dist/index.js",
12
19
  "./basic-auth": "./dist/middleware/basic-auth/basic-auth.js",
@@ -17,8 +24,7 @@
17
24
  "./mustache": "./dist/middleware/mustache/mustache.js",
18
25
  "./powered-by": "./dist/middleware/powered-by/powered-by.js",
19
26
  "./serve-static": "./dist/middleware/serve-static/serve-static.js",
20
- "./utils/buffer": "./dist/utils/buffer.js",
21
- "./package.json": "./package.json"
27
+ "./router/reg-exp-router": "./dist/router/reg-exp-router/index.js"
22
28
  },
23
29
  "typesVersions": {
24
30
  "*": {
@@ -45,16 +51,12 @@
45
51
  ],
46
52
  "serve-static": [
47
53
  "./dist/middleware/serve-static/serve-static.d.ts"
54
+ ],
55
+ "router/reg-exp-router": [
56
+ "./dist/router/reg-exp-router/router.d.ts"
48
57
  ]
49
58
  }
50
59
  },
51
- "scripts": {
52
- "test": "jest",
53
- "lint": "eslint --ext js,ts src .eslintrc.js test && prettier --check src",
54
- "build": "rimraf dist && tsc",
55
- "watch": "tsc -w",
56
- "prepublishOnly": "yarn build"
57
- },
58
60
  "author": "Yusuke Wada <yusuke@kamawada.com> (https://github.com/yusukebe)",
59
61
  "license": "MIT",
60
62
  "repository": {