hono 0.0.11 → 0.0.15

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,56 +1,66 @@
1
1
  # Hono
2
2
 
3
- Hono [炎] - Tiny web framework for Cloudflare Workers and others.
3
+ Hono[炎] - _**means flame🔥 in Japanese**_ - is small, simple, and ultrafast web flamework for a Service Workers API based serverless such as Cloudflare Workers and Fastly Compute@Edge.
4
4
 
5
5
  ```js
6
- const { Hono } = require('hono')
6
+ import { Hono } from 'hono'
7
7
  const app = new Hono()
8
8
 
9
- app.get('/', () => new Response('Hono!!'))
9
+ app.get('/', (c) => c.text('Hono!!'))
10
10
 
11
11
  app.fire()
12
12
  ```
13
13
 
14
- ![carbon](https://user-images.githubusercontent.com/10682/147877725-bce9bd46-953d-4d70-9c2b-3eae47ad4df9.png)
14
+ ## Features
15
15
 
16
- ## Feature
17
-
18
- - Fast - the router is implemented with Trie-Tree structure.
19
- - Portable - zero dependencies.
20
- - Flexible - you can make your own middlewares.
21
- - Easy - simple API, builtin middleware, and TypeScript support.
22
- - Optimized - for Cloudflare Workers or Fastly Compute@Edge.
16
+ - **Ultra fast** - the router is implemented with Trie-Tree structure.
17
+ - **Zero dependencies** - using only Web standard API.
18
+ - **Middleware** - builtin middleware, and you can make your own middleware.
19
+ - **Optimized** - for Cloudflare Workers.
23
20
 
24
21
  ## Benchmark
25
22
 
26
- Hono is fastest!!
23
+ **Hono is fastest** compared to other routers for Cloudflare Workers.
27
24
 
28
- ```
29
- hono x 758,264 ops/sec ±5.41% (75 runs sampled)
30
- itty-router x 158,359 ops/sec ±3.21% (89 runs sampled)
31
- sunder x 297,581 ops/sec ±4.74% (83 runs sampled)
25
+ ```plain
26
+ hono x 748,188 ops/sec ±5.40% (77 runs sampled)
27
+ itty-router x 158,817 ops/sec ±3.62% (87 runs sampled)
28
+ sunder x 332,339 ops/sec ±1.11% (95 runs sampled)
29
+ worktop x 205,906 ops/sec ±4.43% (83 runs sampled)
32
30
  Fastest is hono
33
- ✨ Done in 42.84s.
31
+ ✨ Done in 52.79s.
34
32
  ```
35
33
 
34
+ ## Hono in 1 minute
35
+
36
+ Below is a demonstration to create an application of Cloudflare Workers with Hono.
37
+
38
+ ![Demo](https://user-images.githubusercontent.com/10682/148223268-2484a891-57c1-472f-9df3-936a5586f002.gif)
39
+
36
40
  ## Install
37
41
 
38
- ```
39
- $ yarn add hono
42
+ You can install from npm registry:
43
+
44
+ ```sh
45
+ yarn add hono
40
46
  ```
41
47
 
42
48
  or
43
49
 
44
50
  ```sh
45
- $ npm install hono
51
+ npm install hono
46
52
  ```
47
53
 
48
54
  ## Methods
49
55
 
56
+ Instance of `Hono` has these methods:
57
+
50
58
  - app.**HTTP_METHOD**(path, handler)
51
59
  - app.**all**(path, handler)
52
60
  - app.**route**(path)
53
61
  - app.**use**(path, middleware)
62
+ - app.**fire**()
63
+ - app.**fetch**(request, env, event)
54
64
 
55
65
  ## Routing
56
66
 
@@ -60,12 +70,12 @@ $ npm install hono
60
70
 
61
71
  ```js
62
72
  // HTTP Methods
63
- app.get('/', () => new Response('GET /'))
64
- app.post('/', () => new Response('POST /'))
73
+ app.get('/', (c) => c.text('GET /'))
74
+ app.post('/', (c) => c.text('POST /'))
65
75
 
66
76
  // Wildcard
67
- app.get('/wild/*/card', () => {
68
- return new Response('GET /wild/*/card')
77
+ app.get('/wild/*/card', (c) => {
78
+ return c.text('GET /wild/*/card')
69
79
  })
70
80
  ```
71
81
 
@@ -73,7 +83,7 @@ app.get('/wild/*/card', () => {
73
83
 
74
84
  ```js
75
85
  // Any HTTP methods
76
- app.all('/hello', () => new Response('ALL Method /hello'))
86
+ app.all('/hello', (c) => c.text('Any Method /hello'))
77
87
  ```
78
88
 
79
89
  ### Named Parameter
@@ -104,12 +114,12 @@ app
104
114
  .put(() => {...})
105
115
  ```
106
116
 
107
- ## Async
117
+ ## async/await
108
118
 
109
119
  ```js
110
- app.get('/fetch-url', async () => {
120
+ app.get('/fetch-url', async (c) => {
111
121
  const response = await fetch('https://example.com/')
112
- return new Response(`Status is ${response.status}`)
122
+ return c.text(`Status is ${response.status}`)
113
123
  })
114
124
  ```
115
125
 
@@ -118,17 +128,27 @@ app.get('/fetch-url', async () => {
118
128
  ### Builtin Middleware
119
129
 
120
130
  ```js
121
- const { Hono, Middleware } = require('hono')
131
+ import { Hono, Middleware } from 'hono'
122
132
 
123
133
  ...
124
134
 
125
135
  app.use('*', Middleware.poweredBy())
126
136
  app.use('*', Middleware.logger())
127
-
137
+ app.use(
138
+ '/auth/*',
139
+ Middleware.basicAuth({
140
+ username: 'hono',
141
+ password: 'acoolproject',
142
+ })
143
+ )
128
144
  ```
129
145
 
146
+ Available builtin middleware are listed on [src/middleware](https://github.com/yusukebe/hono/tree/master/src/middleware).
147
+
130
148
  ### Custom Middleware
131
149
 
150
+ You can write your own middleware:
151
+
132
152
  ```js
133
153
  // Custom logger
134
154
  app.use('*', async (c, next) => {
@@ -136,17 +156,19 @@ app.use('*', async (c, next) => {
136
156
  await next()
137
157
  })
138
158
 
139
- // Add custom header
159
+ // Add a custom header
140
160
  app.use('/message/*', async (c, next) => {
141
161
  await next()
142
162
  await c.res.headers.add('x-message', 'This is middleware!')
143
163
  })
144
164
 
145
- app.get('/message/hello', () => 'Hello Middleware!')
165
+ app.get('/message/hello', (c) => c.text('Hello Middleware!'))
146
166
  ```
147
167
 
148
168
  ### Custom 404 Response
149
169
 
170
+ You can customize 404 Not Found response:
171
+
150
172
  ```js
151
173
  app.use('*', async (c, next) => {
152
174
  await next()
@@ -156,8 +178,23 @@ app.use('*', async (c, next) => {
156
178
  })
157
179
  ```
158
180
 
181
+ ### Handling Error
182
+
183
+ ```js
184
+ app.use('*', async (c, next) => {
185
+ try {
186
+ await next()
187
+ } catch (err) {
188
+ console.error(`${err}`)
189
+ c.res = new Response('Custom Error Message', { status: 500 })
190
+ }
191
+ })
192
+ ```
193
+
159
194
  ### Complex Pattern
160
195
 
196
+ You can also do this:
197
+
161
198
  ```js
162
199
  // Output response time
163
200
  app.use('*', async (c, next) => {
@@ -177,7 +214,9 @@ app.use('*', async (c, next) => {
177
214
 
178
215
  ## Context
179
216
 
180
- ### req
217
+ To handle Request and Reponse easily, you can use Context object:
218
+
219
+ ### c.req
181
220
 
182
221
  ```js
183
222
 
@@ -200,7 +239,7 @@ app.get('/entry/:id', (c) => {
200
239
  })
201
240
  ```
202
241
 
203
- ### res
242
+ ### c.res
204
243
 
205
244
  ```js
206
245
  // Response object
@@ -210,7 +249,31 @@ app.use('/', (c, next) => {
210
249
  })
211
250
  ```
212
251
 
213
- ### text
252
+ ### c.event
253
+
254
+ ```js
255
+ // FetchEvent object
256
+ app.use('*', async (c, next) => {
257
+ c.event.waitUntil(
258
+ ...
259
+ )
260
+ await next()
261
+ })
262
+ ```
263
+
264
+ ### c.env
265
+
266
+ ```js
267
+ // Environment object for Cloudflare Workers
268
+ app.get('*', async c => {
269
+ const counter = c.env.COUNTER
270
+ ...
271
+ })
272
+ ```
273
+
274
+ ### c.text()
275
+
276
+ Render text as `Content-Type:text/plain`:
214
277
 
215
278
  ```js
216
279
  app.get('/say', (c) => {
@@ -218,20 +281,69 @@ app.get('/say', (c) => {
218
281
  })
219
282
  ```
220
283
 
221
- ## Hono in 1 minute
284
+ ### c.json()
222
285
 
223
- Create your first Cloudflare Workers with Hono from scratch.
286
+ Render JSON as `Content-Type:application/json`:
224
287
 
225
- ### How to setup
288
+ ```js
289
+ app.get('/api', (c) => {
290
+ return c.json({ message: 'Hello!' })
291
+ })
292
+ ```
293
+
294
+ ### c.html()
295
+
296
+ Render HTML as `Content-Type:text/html`:
297
+
298
+ ```js
299
+ app.get('/', (c) => {
300
+ return c.html('<h1>Hello! Hono!</h1>')
301
+ })
302
+ ```
303
+
304
+ ### c.redirect()
305
+
306
+ Redirect, default status code is `302`:
307
+
308
+ ```js
309
+ app.get('/redirect', (c) => c.redirect('/'))
310
+ app.get('/redirect-permanently', (c) => c.redirect('/', 301))
311
+ ```
312
+
313
+ ## fire
314
+
315
+ `app.fire()` do:
316
+
317
+ ```js
318
+ addEventListener('fetch', (event) => {
319
+ event.respondWith(this.handleEvent(event))
320
+ })
321
+ ```
226
322
 
227
- ![Demo](https://user-images.githubusercontent.com/10682/147877447-ff5907cd-49be-4976-b3b4-5df2ac6dfda4.gif)
323
+ ## fetch
324
+
325
+ `app.fetch()` is for Cloudflare Module Worker syntax.
326
+
327
+ ```js
328
+ export default {
329
+ fetch(request: Request, env: Env, event: FetchEvent) {
330
+ return app.fetch(request, env, event)
331
+ },
332
+ }
333
+ ```
334
+
335
+ ## Cloudflare Workers with Hono
336
+
337
+ Using `wrangler` or `miniflare`, you can develop the application locally and publish it with few commands.
338
+
339
+ Let's write your first code for Cloudflare Workers with Hono.
228
340
 
229
341
  ### 1. Install Wrangler
230
342
 
231
343
  Install Cloudflare Command Line "[Wrangler](https://github.com/cloudflare/wrangler)"
232
344
 
233
345
  ```sh
234
- $ npm i @cloudflare/wrangler -g
346
+ npm i @cloudflare/wrangler -g
235
347
  ```
236
348
 
237
349
  ### 2. `npm init`
@@ -239,9 +351,9 @@ $ npm i @cloudflare/wrangler -g
239
351
  Make npm skeleton directory.
240
352
 
241
353
  ```sh
242
- $ mkdir hono-example
243
- $ ch hono-example
244
- $ npm init -y
354
+ mkdir hono-example
355
+ cd hono-example
356
+ npm init -y
245
357
  ```
246
358
 
247
359
  ### 3. `wrangler init`
@@ -249,46 +361,68 @@ $ npm init -y
249
361
  Init as a wrangler project.
250
362
 
251
363
  ```sh
252
- $ wrangler init
364
+ wrangler init
253
365
  ```
254
366
 
255
367
  ### 4. `npm install hono`
256
368
 
257
- Install `hono` from npm repository.
369
+ Install `hono` from npm registry.
258
370
 
259
- ```
260
- $ npm i hono
371
+ ```sh
372
+ npm i hono
261
373
  ```
262
374
 
263
375
  ### 5. Write your app
264
376
 
265
- Only 4 line!!
377
+ Only 4 lines!!
266
378
 
267
379
  ```js
268
- const { Hono } = require('hono')
380
+ import { Hono } from 'hono'
269
381
  const app = new Hono()
270
382
 
271
- app.get('/', () => new Response('Hello! Hono!'))
383
+ app.get('/', (c) => c.text('Hello! Hono!'))
272
384
 
273
385
  app.fire()
274
386
  ```
275
387
 
276
- ### 6. Run!
388
+ ### 6. Run
277
389
 
278
- Run the development server locally.
390
+ Run the development server locally. Then, access like `http://127.0.0.1:8787/` in your Web browser.
279
391
 
280
392
  ```sh
281
- $ wrangler dev
393
+ wrangler dev
394
+ ```
395
+
396
+ ### Publish
397
+
398
+ Deploy to Cloudflare. That's all!
399
+
400
+ ```sh
401
+ wrangler publish
282
402
  ```
283
403
 
284
404
  ## Related projects
285
405
 
286
- - koa <https://github.com/koajs/koa>
406
+ 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.
407
+
287
408
  - express <https://github.com/expressjs/express>
288
- - oak <https://github.com/oakserver/oak>
409
+ - koa <https://github.com/koajs/koa>
289
410
  - itty-router <https://github.com/kwhitley/itty-router>
290
411
  - Sunder <https://github.com/SunderJS/sunder>
291
412
  - goblin <https://github.com/bmf-san/goblin>
413
+ - worktop <https://github.com/lukeed/worktop>
414
+
415
+ ## Contributing
416
+
417
+ Contributions Welcome! You can contribute by the following way:
418
+
419
+ - Write or fix documents
420
+ - Write code of middleware
421
+ - Fix bugs
422
+ - Refactor the code
423
+ - etc.
424
+
425
+ If you can, let's make Hono together!
292
426
 
293
427
  ## Author
294
428
 
@@ -296,4 +430,4 @@ Yusuke Wada <https://github.com/yusukebe>
296
430
 
297
431
  ## License
298
432
 
299
- MIT
433
+ Distributed under the MIT License. See [LICENSE](LICENSE) for more information.
@@ -0,0 +1,23 @@
1
+ /// <reference types="@cloudflare/workers-types" />
2
+ declare type Headers = {
3
+ [key: string]: string;
4
+ };
5
+ export interface Env {
6
+ }
7
+ export declare class Context {
8
+ req: Request;
9
+ res: Response;
10
+ env: Env;
11
+ event: FetchEvent;
12
+ constructor(req: Request, opts?: {
13
+ res: Response;
14
+ env: Env;
15
+ event: FetchEvent;
16
+ });
17
+ newResponse(body?: BodyInit | null | undefined, init?: ResponseInit | undefined): Response;
18
+ text(text: string, status?: number, headers?: Headers): Response;
19
+ json(object: object, status?: number, headers?: Headers): Response;
20
+ html(html: string, status?: number, headers?: Headers): Response;
21
+ redirect(location: string, status?: number): Response;
22
+ }
23
+ export {};
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Context = void 0;
4
+ const util_1 = require("./util");
5
+ class Context {
6
+ constructor(req, opts) {
7
+ this.req = req;
8
+ if (opts) {
9
+ this.res = opts.res;
10
+ this.env = opts.env;
11
+ this.event = opts.event;
12
+ }
13
+ }
14
+ newResponse(body, init) {
15
+ return new Response(body, init);
16
+ }
17
+ text(text, status = 200, headers = {}) {
18
+ if (typeof text !== 'string') {
19
+ throw new TypeError('text method arg must be a string!');
20
+ }
21
+ headers['Content-Type'] = 'text/plain';
22
+ return this.newResponse(text, {
23
+ status: status,
24
+ headers: headers,
25
+ });
26
+ }
27
+ json(object, status = 200, headers = {}) {
28
+ if (typeof object !== 'object') {
29
+ throw new TypeError('json method arg must be a object!');
30
+ }
31
+ const body = JSON.stringify(object);
32
+ headers['Content-Type'] = 'application/json; charset=UTF-8';
33
+ return this.newResponse(body, {
34
+ status: status,
35
+ headers: headers,
36
+ });
37
+ }
38
+ html(html, status = 200, headers = {}) {
39
+ if (typeof html !== 'string') {
40
+ throw new TypeError('html method arg must be a string!');
41
+ }
42
+ headers['Content-Type'] = 'text/html; charset=UTF-8';
43
+ return this.newResponse(html, {
44
+ status: status,
45
+ headers: headers,
46
+ });
47
+ }
48
+ redirect(location, status = 302) {
49
+ if (typeof location !== 'string') {
50
+ throw new TypeError('location must be a string!');
51
+ }
52
+ if (!(0, util_1.isAbsoluteURL)(location)) {
53
+ const url = new URL(this.req.url);
54
+ url.pathname = location;
55
+ location = url.toString();
56
+ }
57
+ return this.newResponse(null, {
58
+ status: status,
59
+ headers: {
60
+ Location: location,
61
+ },
62
+ });
63
+ }
64
+ }
65
+ exports.Context = Context;
package/dist/hono.d.ts CHANGED
@@ -1,22 +1,17 @@
1
1
  /// <reference types="@cloudflare/workers-types" />
2
- import { Node, Result } from './node';
3
- import { Middleware } from './middleware';
4
- export { Middleware };
2
+ import type { Result } from './node';
3
+ import { Node } from './node';
4
+ import { Context } from './context';
5
+ import type { Env } from './context';
5
6
  declare global {
6
7
  interface Request {
7
- params: (key: string) => any;
8
+ params: (key: string) => string;
8
9
  query: (key: string) => string | null;
10
+ parsedBody: any;
9
11
  }
10
12
  }
11
- export declare class Context {
12
- req: Request;
13
- res: Response;
14
- constructor(req: Request, res: Response);
15
- newResponse(body?: BodyInit | null | undefined, init?: ResponseInit | undefined): Response;
16
- text(body: string): Response;
17
- }
18
- declare type Handler = (c: Context, next?: Function) => Response | Promise<Response>;
19
- declare type MiddlwareHandler = (c: Context, next: Function) => Promise<void>;
13
+ export declare type Handler = (c: Context, next?: Function) => Response | Promise<Response>;
14
+ export declare type MiddlewareHandler = (c: Context, next: Function) => Promise<void>;
20
15
  export declare class Router<T> {
21
16
  node: Node<T>;
22
17
  constructor();
@@ -25,7 +20,7 @@ export declare class Router<T> {
25
20
  }
26
21
  export declare class Hono {
27
22
  router: Router<Handler[]>;
28
- middlewareRouters: Router<MiddlwareHandler>[];
23
+ middlewareRouters: Router<MiddlewareHandler>[];
29
24
  tempPath: string;
30
25
  constructor();
31
26
  get(arg: string | Handler, ...args: Handler[]): Hono;
@@ -37,11 +32,13 @@ export declare class Hono {
37
32
  patch(arg: string | Handler, ...args: Handler[]): Hono;
38
33
  all(arg: string | Handler, ...args: Handler[]): Hono;
39
34
  route(path: string): Hono;
40
- use(path: string, middleware: MiddlwareHandler): void;
35
+ use(path: string, middleware: MiddlewareHandler): void;
41
36
  addRoute(method: string, arg: string | Handler, ...args: Handler[]): Hono;
42
37
  matchRoute(method: string, path: string): Promise<Result<Handler[]>>;
43
- dispatch(request: Request, response?: Response): Promise<Response>;
38
+ dispatch(request: Request, env?: Env, event?: FetchEvent): Promise<Response>;
44
39
  handleEvent(event: FetchEvent): Promise<Response>;
40
+ fetch(request: Request, env?: Env, event?: FetchEvent): Promise<Response>;
45
41
  fire(): void;
42
+ onError(err: any): Response;
46
43
  notFound(): Response;
47
44
  }
package/dist/hono.js CHANGED
@@ -1,30 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Hono = exports.Router = exports.Context = exports.Middleware = void 0;
3
+ exports.Hono = exports.Router = void 0;
4
4
  const node_1 = require("./node");
5
5
  const compose_1 = require("./compose");
6
6
  const util_1 = require("./util");
7
7
  const middleware_1 = require("./middleware");
8
- Object.defineProperty(exports, "Middleware", { enumerable: true, get: function () { return middleware_1.Middleware; } });
8
+ const context_1 = require("./context");
9
9
  const METHOD_NAME_OF_ALL = 'ALL';
10
- class Context {
11
- constructor(req, res) {
12
- this.req = req;
13
- this.res = res;
14
- }
15
- newResponse(body, init) {
16
- return new Response(body, init);
17
- }
18
- text(body) {
19
- return this.newResponse(body, {
20
- status: 200,
21
- headers: {
22
- 'Content-Type': 'text/plain',
23
- },
24
- });
25
- }
26
- }
27
- exports.Context = Context;
28
10
  class Router {
29
11
  constructor() {
30
12
  this.node = new node_1.Node();
@@ -99,6 +81,7 @@ class Hono {
99
81
  addRoute(method, arg, ...args) {
100
82
  method = method.toUpperCase();
101
83
  if (typeof arg === 'string') {
84
+ this.tempPath = arg;
102
85
  this.router.add(method, arg, args);
103
86
  }
104
87
  else {
@@ -110,7 +93,7 @@ class Hono {
110
93
  async matchRoute(method, path) {
111
94
  return this.router.match(method, path);
112
95
  }
113
- async dispatch(request, response) {
96
+ async dispatch(request, env, event) {
114
97
  const [method, path] = [request.method, (0, util_1.getPathFromURL)(request.url)];
115
98
  const result = await this.matchRoute(method, path);
116
99
  request.params = (key) => {
@@ -119,7 +102,7 @@ class Hono {
119
102
  }
120
103
  return '';
121
104
  };
122
- let handler = result ? result.handler[0] : this.notFound; // XXX
105
+ const handler = result ? result.handler[0] : this.notFound; // XXX
123
106
  const middleware = [];
124
107
  for (const mr of this.middlewareRouters) {
125
108
  const mwResult = mr.match(METHOD_NAME_OF_ALL, path);
@@ -127,25 +110,36 @@ class Hono {
127
110
  middleware.push(mwResult.handler);
128
111
  }
129
112
  }
130
- let wrappedHandler = async (context, next) => {
113
+ const wrappedHandler = async (context, next) => {
131
114
  context.res = await handler(context);
132
115
  await next();
133
116
  };
134
- middleware.push(middleware_1.Middleware.defaultFilter);
117
+ middleware.push(middleware_1.Middleware.default);
135
118
  middleware.push(wrappedHandler);
136
119
  const composed = (0, compose_1.compose)(middleware);
137
- const c = new Context(request, response);
120
+ const c = new context_1.Context(request, { env: env, event: event, res: null });
138
121
  await composed(c);
139
122
  return c.res;
140
123
  }
141
124
  async handleEvent(event) {
142
- return this.dispatch(event.request);
125
+ return this.dispatch(event.request, {}, event).catch((err) => {
126
+ return this.onError(err);
127
+ });
128
+ }
129
+ async fetch(request, env, event) {
130
+ return this.dispatch(request, env, event).catch((err) => {
131
+ return this.onError(err);
132
+ });
143
133
  }
144
134
  fire() {
145
135
  addEventListener('fetch', (event) => {
146
136
  event.respondWith(this.handleEvent(event));
147
137
  });
148
138
  }
139
+ onError(err) {
140
+ console.error(err);
141
+ return new Response('Internal Server Error', { status: 500 });
142
+ }
149
143
  notFound() {
150
144
  return new Response('Not Found', { status: 404 });
151
145
  }
package/dist/index.d.ts CHANGED
@@ -1 +1,5 @@
1
- export { Hono, Middleware, Context } from './hono';
1
+ export { Hono } from './hono';
2
+ export type { Handler, MiddlewareHandler } from './hono';
3
+ export { Middleware } from './middleware';
4
+ export { Context } from './context';
5
+ export type { Env } from './context';
package/dist/index.js CHANGED
@@ -3,5 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Context = exports.Middleware = exports.Hono = void 0;
4
4
  var hono_1 = require("./hono");
5
5
  Object.defineProperty(exports, "Hono", { enumerable: true, get: function () { return hono_1.Hono; } });
6
- Object.defineProperty(exports, "Middleware", { enumerable: true, get: function () { return hono_1.Middleware; } });
7
- Object.defineProperty(exports, "Context", { enumerable: true, get: function () { return hono_1.Context; } });
6
+ var middleware_1 = require("./middleware");
7
+ Object.defineProperty(exports, "Middleware", { enumerable: true, get: function () { return middleware_1.Middleware; } });
8
+ var context_1 = require("./context");
9
+ Object.defineProperty(exports, "Context", { enumerable: true, get: function () { return context_1.Context; } });
@@ -0,0 +1,6 @@
1
+ import type { Context } from '../../context';
2
+ export declare const basicAuth: (options: {
3
+ username: string;
4
+ password: string;
5
+ realm?: string;
6
+ }) => (ctx: Context, next: Function) => Promise<any>;
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.basicAuth = void 0;
4
+ const util_1 = require("../../util");
5
+ const CREDENTIALS_REGEXP = /^ *(?:[Bb][Aa][Ss][Ii][Cc]) +([A-Za-z0-9._~+/-]+=*) *$/;
6
+ const USER_PASS_REGEXP = /^([^:]*):(.*)$/;
7
+ const auth = (req) => {
8
+ if (!req) {
9
+ throw new TypeError('argument req is required');
10
+ }
11
+ if (typeof req !== 'object') {
12
+ throw new TypeError('argument req is required to be an object');
13
+ }
14
+ if (!req.headers || typeof req.headers !== 'object') {
15
+ throw new TypeError('argument req is required to have headers property');
16
+ }
17
+ const match = CREDENTIALS_REGEXP.exec(req.headers.get('Authorization'));
18
+ if (!match) {
19
+ return undefined;
20
+ }
21
+ const userPass = USER_PASS_REGEXP.exec(decodeBase64(match[1]));
22
+ if (!userPass) {
23
+ return undefined;
24
+ }
25
+ return { username: userPass[1], password: userPass[2] };
26
+ };
27
+ function decodeBase64(str) {
28
+ return Buffer.from(str, 'base64').toString();
29
+ }
30
+ const basicAuth = (options) => {
31
+ if (!options.realm) {
32
+ options.realm = 'Secure Area';
33
+ }
34
+ return async (ctx, next) => {
35
+ const user = auth(ctx.req);
36
+ const usernameEqual = user && await (0, util_1.timingSafeEqual)(options.username, user.username);
37
+ const passwordEqual = user && await (0, util_1.timingSafeEqual)(options.password, user.password);
38
+ if (!user || !usernameEqual || !passwordEqual) {
39
+ ctx.res = new Response('Unauthorized', {
40
+ status: 401,
41
+ headers: {
42
+ 'WWW-Authenticate': 'Basic realm="' + options.realm.replace(/"/g, '\\"') + '"',
43
+ },
44
+ });
45
+ return;
46
+ }
47
+ return next();
48
+ };
49
+ };
50
+ exports.basicAuth = basicAuth;
@@ -0,0 +1,2 @@
1
+ import type { Context } from '../../context';
2
+ export declare const bodyParse: () => (ctx: Context, next: Function) => Promise<void>;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.bodyParse = void 0;
4
+ const bodyParse = () => {
5
+ 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
+ }
24
+ await next();
25
+ };
26
+ };
27
+ exports.bodyParse = bodyParse;
@@ -0,0 +1,2 @@
1
+ import type { Context } from '../context';
2
+ export declare const defaultMiddleware: (c: Context, next: Function) => Promise<void>;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.defaultMiddleware = void 0;
4
+ const defaultMiddleware = async (c, next) => {
5
+ c.req.query = (key) => {
6
+ // eslint-disable-next-line
7
+ const url = new URL(c.req.url);
8
+ return url.searchParams.get(key);
9
+ };
10
+ await next();
11
+ if (c.res.body) {
12
+ const buff = await c.res.clone().arrayBuffer();
13
+ c.res.headers.append('Content-Length', buff.byteLength.toString());
14
+ }
15
+ };
16
+ exports.defaultMiddleware = defaultMiddleware;
@@ -1,5 +1,6 @@
1
- import { Context } from '../../hono';
1
+ import type { Context } from '../../context';
2
2
  export declare const logger: (fn?: {
3
3
  (...data: any[]): void;
4
4
  (...data: any[]): void;
5
+ (message?: any, ...optionalParams: any[]): void;
5
6
  }) => (c: Context, next: Function) => Promise<void>;
@@ -12,7 +12,9 @@ const humanize = (n, opts) => {
12
12
  };
13
13
  const time = (start) => {
14
14
  const delta = Date.now() - start;
15
- return humanize([delta < 10000 ? delta + 'ms' : Math.round(delta / 1000) + 's']);
15
+ return humanize([
16
+ delta < 10000 ? delta + 'ms' : Math.round(delta / 1000) + 's',
17
+ ]);
16
18
  };
17
19
  const LogPrefix = {
18
20
  Outgoing: '-->',
@@ -1,2 +1,2 @@
1
- import { Context } from '../../hono';
1
+ import type { Context } from '../../context';
2
2
  export declare const poweredBy: () => (c: Context, next: Function) => Promise<void>;
@@ -1,8 +1,15 @@
1
1
  export declare class Middleware {
2
- static defaultFilter: (c: import("./hono").Context, next: Function) => Promise<void>;
3
- static poweredBy: () => (c: import("./hono").Context, next: Function) => Promise<void>;
2
+ static default: (c: import("./context").Context, next: Function) => Promise<void>;
3
+ static poweredBy: () => (c: import("./context").Context, next: Function) => Promise<void>;
4
4
  static logger: (fn?: {
5
5
  (...data: any[]): void;
6
6
  (...data: any[]): void;
7
- }) => (c: import("./hono").Context, next: Function) => Promise<void>;
7
+ (message?: any, ...optionalParams: any[]): void;
8
+ }) => (c: import("./context").Context, next: Function) => Promise<void>;
9
+ static basicAuth: (options: {
10
+ username: string;
11
+ password: string;
12
+ realm?: string;
13
+ }) => (ctx: import("./context").Context, next: Function) => Promise<any>;
14
+ static bodyParse: () => (ctx: import("./context").Context, next: Function) => Promise<void>;
8
15
  }
@@ -1,12 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Middleware = void 0;
4
- const defaultFilter_1 = require("./middleware/defaultFilter");
5
- const poweredBy_1 = require("./middleware/poweredBy/poweredBy");
4
+ const default_1 = require("./middleware/default");
5
+ const powered_by_1 = require("./middleware/powered-by/powered-by");
6
6
  const logger_1 = require("./middleware/logger/logger");
7
+ const basic_auth_1 = require("./middleware/basic-auth/basic-auth");
8
+ const body_parse_1 = require("./middleware/body-parse/body-parse");
7
9
  class Middleware {
8
10
  }
9
11
  exports.Middleware = Middleware;
10
- Middleware.defaultFilter = defaultFilter_1.defaultFilter;
11
- Middleware.poweredBy = poweredBy_1.poweredBy;
12
+ Middleware.default = default_1.defaultMiddleware;
13
+ Middleware.poweredBy = powered_by_1.poweredBy;
12
14
  Middleware.logger = logger_1.logger;
15
+ Middleware.basicAuth = basic_auth_1.basicAuth;
16
+ Middleware.bodyParse = body_parse_1.bodyParse;
package/dist/node.js CHANGED
@@ -23,6 +23,7 @@ class Node {
23
23
  this.middlewares = [];
24
24
  }
25
25
  insert(method, path, handler) {
26
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
26
27
  let curNode = this;
27
28
  const parts = (0, util_1.splitPath)(path);
28
29
  for (let i = 0, len = parts.length; i < len; i++) {
@@ -38,13 +39,15 @@ class Node {
38
39
  return curNode;
39
40
  }
40
41
  search(method, path) {
42
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
41
43
  let curNode = this;
42
44
  const params = {};
43
- let parts = (0, util_1.splitPath)(path);
45
+ const parts = (0, util_1.splitPath)(path);
44
46
  for (let i = 0, len = parts.length; i < len; i++) {
45
47
  const p = parts[i];
46
48
  // '*' => match any path
47
- if (curNode.children['*']) {
49
+ // /api/* => default wildcard match
50
+ if (curNode.children['*'] && !curNode.children[p]) {
48
51
  const astNode = curNode.children['*'];
49
52
  if (Object.keys(astNode.children).length === 0) {
50
53
  curNode = astNode;
package/dist/util.d.ts CHANGED
@@ -1,3 +1,5 @@
1
1
  export declare const splitPath: (path: string) => string[];
2
2
  export declare const getPattern: (label: string) => string[] | null;
3
3
  export declare const getPathFromURL: (url: string) => string;
4
+ export declare const isAbsoluteURL: (url: string) => boolean;
5
+ export declare const timingSafeEqual: (a: any, b: any) => Promise<boolean>;
package/dist/util.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getPathFromURL = exports.getPattern = exports.splitPath = void 0;
3
+ exports.timingSafeEqual = exports.isAbsoluteURL = exports.getPathFromURL = exports.getPattern = exports.splitPath = void 0;
4
+ const URL_REGEXP = /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;
4
5
  const splitPath = (path) => {
5
6
  const paths = path.split(/\//); // faster than path.split('/')
6
7
  if (paths[0] === '') {
@@ -27,10 +28,45 @@ const getPattern = (label) => {
27
28
  exports.getPattern = getPattern;
28
29
  const getPathFromURL = (url) => {
29
30
  // XXX
30
- const match = url.match(/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/);
31
+ const match = url.match(URL_REGEXP);
31
32
  if (match) {
32
33
  return match[5];
33
34
  }
34
35
  return '';
35
36
  };
36
37
  exports.getPathFromURL = getPathFromURL;
38
+ const isAbsoluteURL = (url) => {
39
+ const match = url.match(URL_REGEXP);
40
+ if (match && match[1]) {
41
+ return true;
42
+ }
43
+ return false;
44
+ };
45
+ exports.isAbsoluteURL = isAbsoluteURL;
46
+ const bufferEqual = (a, b) => {
47
+ if (a === b) {
48
+ return true;
49
+ }
50
+ if (a.byteLength !== b.byteLength) {
51
+ return false;
52
+ }
53
+ const va = new DataView(a);
54
+ const vb = new DataView(b);
55
+ let i = va.byteLength;
56
+ while (i--) {
57
+ if (va.getUint8(i) !== vb.getUint8(i)) {
58
+ return false;
59
+ }
60
+ }
61
+ return true;
62
+ };
63
+ const timingSafeEqual = async (a, b) => {
64
+ const sa = await crypto.subtle.digest({
65
+ name: 'SHA-256',
66
+ }, new TextEncoder().encode(String(a)));
67
+ const sb = await crypto.subtle.digest({
68
+ name: 'SHA-256',
69
+ }, new TextEncoder().encode(String(b)));
70
+ return bufferEqual(sa, sb) && a === b;
71
+ };
72
+ exports.timingSafeEqual = timingSafeEqual;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "hono",
3
- "version": "0.0.11",
4
- "description": "Tiny web framework for Cloudflare Workers and others.",
3
+ "version": "0.0.15",
4
+ "description": "[炎] Ultrafast web framework for Cloudflare Workers.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "files": [
@@ -9,6 +9,7 @@
9
9
  ],
10
10
  "scripts": {
11
11
  "test": "jest",
12
+ "lint": "eslint --ext js,ts src .eslintrc.js test",
12
13
  "build": "rimraf dist && tsc",
13
14
  "watch": "tsc -w",
14
15
  "prepublishOnly": "yarn build"
@@ -35,11 +36,26 @@
35
36
  "devDependencies": {
36
37
  "@cloudflare/workers-types": "^3.3.0",
37
38
  "@types/jest": "^27.4.0",
38
- "@types/service-worker-mock": "^2.0.1",
39
+ "@types/node": "^17.0.8",
40
+ "@typescript-eslint/eslint-plugin": "^5.9.0",
41
+ "@typescript-eslint/parser": "^5.9.0",
42
+ "eslint": "^7.26.0",
43
+ "eslint-config-prettier": "^8.1.0",
44
+ "eslint-define-config": "^1.2.1",
45
+ "eslint-import-resolver-typescript": "^2.0.0",
46
+ "eslint-plugin-eslint-comments": "^3.2.0",
47
+ "eslint-plugin-flowtype": "^5.7.2",
48
+ "eslint-plugin-import": "^2.20.2",
49
+ "eslint-plugin-node": "^11.1.0",
50
+ "eslint-plugin-prettier": "^4.0.0",
51
+ "form-data": "^4.0.0",
39
52
  "jest": "^27.4.5",
53
+ "jest-environment-miniflare": "^2.0.0",
40
54
  "rimraf": "^3.0.2",
41
- "service-worker-mock": "^2.0.5",
42
55
  "ts-jest": "^27.1.2",
43
56
  "typescript": "^4.5.4"
57
+ },
58
+ "engines": {
59
+ "node": ">=11.0.0"
44
60
  }
45
61
  }
package/dist/methods.d.ts DELETED
@@ -1 +0,0 @@
1
- export declare const methods: string[];
package/dist/methods.js DELETED
@@ -1,31 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.methods = void 0;
4
- exports.methods = [
5
- 'get',
6
- 'post',
7
- 'put',
8
- 'head',
9
- 'delete',
10
- 'options',
11
- 'trace',
12
- 'copy',
13
- 'lock',
14
- 'mkcol',
15
- 'move',
16
- 'patch',
17
- 'purge',
18
- 'propfind',
19
- 'proppatch',
20
- 'unlock',
21
- 'report',
22
- 'mkactivity',
23
- 'checkout',
24
- 'merge',
25
- 'm-search',
26
- 'notify',
27
- 'subscribe',
28
- 'unsubscribe',
29
- 'search',
30
- 'connect',
31
- ];
@@ -1,2 +0,0 @@
1
- import { Context } from '../hono';
2
- export declare const defaultFilter: (c: Context, next: Function) => Promise<void>;
@@ -1,11 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.defaultFilter = void 0;
4
- const defaultFilter = async (c, next) => {
5
- c.req.query = (key) => {
6
- const url = new URL(c.req.url);
7
- return url.searchParams.get(key);
8
- };
9
- await next();
10
- };
11
- exports.defaultFilter = defaultFilter;