hono 0.0.10 → 0.0.14

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.
Files changed (43) hide show
  1. package/README.md +212 -75
  2. package/dist/compose.d.ts +1 -0
  3. package/dist/compose.js +27 -0
  4. package/dist/context.d.ts +23 -0
  5. package/dist/context.js +65 -0
  6. package/dist/hono.d.ts +44 -0
  7. package/dist/hono.js +147 -0
  8. package/dist/index.d.ts +5 -0
  9. package/dist/index.js +9 -0
  10. package/dist/middleware/basic-auth/basic-auth.d.ts +6 -0
  11. package/dist/middleware/basic-auth/basic-auth.js +50 -0
  12. package/dist/middleware/body-parse/body-parse.d.ts +2 -0
  13. package/dist/middleware/body-parse/body-parse.js +27 -0
  14. package/dist/middleware/default.d.ts +2 -0
  15. package/dist/middleware/default.js +16 -0
  16. package/dist/middleware/logger/logger.d.ts +6 -0
  17. package/dist/middleware/logger/logger.js +58 -0
  18. package/dist/middleware/powered-by/powered-by.d.ts +2 -0
  19. package/dist/middleware/powered-by/powered-by.js +11 -0
  20. package/dist/middleware.d.ts +15 -0
  21. package/dist/middleware.js +16 -0
  22. package/dist/node.d.ts +24 -0
  23. package/dist/node.js +104 -0
  24. package/dist/util.d.ts +5 -0
  25. package/dist/util.js +72 -0
  26. package/package.json +34 -5
  27. package/CODE_OF_CONDUCT.md +0 -128
  28. package/src/compose.js +0 -21
  29. package/src/compose.test.js +0 -42
  30. package/src/hono.d.ts +0 -67
  31. package/src/hono.js +0 -141
  32. package/src/hono.test.js +0 -158
  33. package/src/methods.js +0 -30
  34. package/src/middleware/defaultFilter.js +0 -19
  35. package/src/middleware/poweredBy.js +0 -6
  36. package/src/middleware/poweredBy.test.js +0 -17
  37. package/src/middleware.js +0 -9
  38. package/src/middleware.test.js +0 -17
  39. package/src/node.js +0 -97
  40. package/src/node.test.js +0 -135
  41. package/src/router.test.js +0 -88
  42. package/src/util.js +0 -33
  43. package/src/util.test.js +0 -44
package/README.md CHANGED
@@ -1,54 +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
- - Tiny - use only standard API.
20
- - Portable - zero dependencies.
21
- - Flexible - you can make your own middlewares.
22
- - Optimized - for Cloudflare Workers and 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
- ```
27
- hono x 813,001 ops/sec ±2.96% (75 runs sampled)
28
- itty-router x 160,415 ops/sec ±3.31% (85 runs sampled)
29
- sunder x 307,438 ops/sec ±4.77% (73 runs sampled)
23
+ **Hono is fastest** compared to other routers for Cloudflare Workers.
24
+
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)
30
30
  Fastest is hono
31
- ✨ Done in 37.03s.
31
+ ✨ Done in 52.79s.
32
32
  ```
33
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
+
34
40
  ## Install
35
41
 
36
- ```
37
- $ yarn add hono
42
+ You can install from npm registry:
43
+
44
+ ```sh
45
+ yarn add hono
38
46
  ```
39
47
 
40
48
  or
41
49
 
42
50
  ```sh
43
- $ npm install hono
51
+ npm install hono
44
52
  ```
45
53
 
46
54
  ## Methods
47
55
 
48
- - app.**HTTP_METHOD**(path, callback)
49
- - app.**all**(path, callback)
56
+ Instance of `Hono` has these methods:
57
+
58
+ - app.**HTTP_METHOD**(path, handler)
59
+ - app.**all**(path, handler)
50
60
  - app.**route**(path)
51
61
  - app.**use**(path, middleware)
62
+ - app.**fire**()
63
+ - app.**fetch**(request, env, event)
52
64
 
53
65
  ## Routing
54
66
 
@@ -58,12 +70,12 @@ $ npm install hono
58
70
 
59
71
  ```js
60
72
  // HTTP Methods
61
- app.get('/', () => new Response('GET /'))
62
- app.post('/', () => new Response('POST /'))
73
+ app.get('/', (c) => c.text('GET /'))
74
+ app.post('/', (c) => c.text('POST /'))
63
75
 
64
76
  // Wildcard
65
- app.get('/wild/*/card', () => {
66
- return new Response('GET /wild/*/card')
77
+ app.get('/wild/*/card', (c) => {
78
+ return c.text('GET /wild/*/card')
67
79
  })
68
80
  ```
69
81
 
@@ -71,7 +83,7 @@ app.get('/wild/*/card', () => {
71
83
 
72
84
  ```js
73
85
  // Any HTTP methods
74
- app.all('/hello', () => new Response('ALL Method /hello'))
86
+ app.all('/hello', (c) => c.text('Any Method /hello'))
75
87
  ```
76
88
 
77
89
  ### Named Parameter
@@ -102,12 +114,12 @@ app
102
114
  .put(() => {...})
103
115
  ```
104
116
 
105
- ## Async
117
+ ## async/await
106
118
 
107
119
  ```js
108
- app.get('/fetch-url', async () => {
120
+ app.get('/fetch-url', async (c) => {
109
121
  const response = await fetch('https://example.com/')
110
- return new Response(`Status is ${response.status}`)
122
+ return c.text(`Status is ${response.status}`)
111
123
  })
112
124
  ```
113
125
 
@@ -116,50 +128,75 @@ app.get('/fetch-url', async () => {
116
128
  ### Builtin Middleware
117
129
 
118
130
  ```js
119
- const { Hono, Middleware } = require('hono')
131
+ import { Hono, Middleware } from 'hono'
120
132
 
121
133
  ...
122
134
 
123
- app.use('*', Middleware.poweredBy)
124
-
135
+ app.use('*', Middleware.poweredBy())
136
+ app.use('*', Middleware.logger())
137
+ app.use(
138
+ '/auth/*',
139
+ Middleware.basicAuth({
140
+ username: 'hono',
141
+ password: 'acoolproject',
142
+ })
143
+ )
125
144
  ```
126
145
 
146
+ Available builtin middleware are listed on [src/middleware](https://github.com/yusukebe/hono/tree/master/src/middleware).
147
+
127
148
  ### Custom Middleware
128
149
 
150
+ You can write your own middleware:
151
+
129
152
  ```js
130
- const logger = async (c, next) => {
153
+ // Custom logger
154
+ app.use('*', async (c, next) => {
131
155
  console.log(`[${c.req.method}] ${c.req.url}`)
132
156
  await next()
133
- }
157
+ })
134
158
 
135
- const addHeader = async (c, next) => {
159
+ // Add a custom header
160
+ app.use('/message/*', async (c, next) => {
136
161
  await next()
137
162
  await c.res.headers.add('x-message', 'This is middleware!')
138
- }
139
-
140
- app.use('*', logger)
141
- app.use('/message/*', addHeader)
163
+ })
142
164
 
143
- app.get('/message/hello', () => 'Hello Middleware!')
165
+ app.get('/message/hello', (c) => c.text('Hello Middleware!'))
144
166
  ```
145
167
 
146
168
  ### Custom 404 Response
147
169
 
170
+ You can customize 404 Not Found response:
171
+
148
172
  ```js
149
- const customNotFound = async (c, next) => {
173
+ app.use('*', async (c, next) => {
150
174
  await next()
151
175
  if (c.res.status === 404) {
152
176
  c.res = new Response('Custom 404 Not Found', { status: 404 })
153
177
  }
154
- }
178
+ })
179
+ ```
155
180
 
156
- app.use('*', customNotFound)
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
+ })
157
192
  ```
158
193
 
159
194
  ### Complex Pattern
160
195
 
196
+ You can also do this:
197
+
161
198
  ```js
162
- // Log response time
199
+ // Output response time
163
200
  app.use('*', async (c, next) => {
164
201
  await next()
165
202
  const responseTime = await c.res.headers.get('X-Response-Time')
@@ -177,58 +214,136 @@ 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
222
+
223
+ // Get Request object
183
224
  app.get('/hello', (c) => {
184
225
  const userAgent = c.req.headers.get('User-Agent')
185
226
  ...
186
227
  })
228
+
229
+ // Query params
230
+ app.get('/search', (c) => {
231
+ const query = c.req.query('q')
232
+ ...
233
+ })
234
+
235
+ // Captured params
236
+ app.get('/entry/:id', (c) => {
237
+ const id = c.req.params('id')
238
+ ...
239
+ })
187
240
  ```
188
241
 
189
- ### res
242
+ ### c.res
190
243
 
191
244
  ```js
245
+ // Response object
192
246
  app.use('/', (c, next) => {
193
247
  next()
194
248
  c.res.headers.append('X-Debug', 'Debug message')
195
249
  })
196
250
  ```
197
251
 
198
- ## Request
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
+ ```
199
263
 
200
- ### query
264
+ ### c.env
201
265
 
202
266
  ```js
203
- app.get('/search', (c) => {
204
- const query = c.req.query('q')
267
+ // Environment object for Cloudflare Workers
268
+ app.get('*', async c => {
269
+ const counter = c.env.COUNTER
205
270
  ...
206
271
  })
207
272
  ```
208
273
 
209
- ### params
274
+ ### c.text()
275
+
276
+ Render text as `Content-Type:text/plain`:
210
277
 
211
278
  ```js
212
- app.get('/entry/:id', (c) => {
213
- const id = c.req.params('id')
214
- ...
279
+ app.get('/say', (c) => {
280
+ return c.text('Hello!')
215
281
  })
216
282
  ```
217
283
 
218
- ## Hono in 1 minute
284
+ ### c.json()
219
285
 
220
- Create your first Cloudflare Workers with Hono from scratch.
286
+ Render JSON as `Content-Type:application/json`:
221
287
 
222
- ### Demo
288
+ ```js
289
+ app.get('/api', (c) => {
290
+ return c.json({ message: 'Hello!' })
291
+ })
292
+ ```
223
293
 
224
- ![Demo](https://user-images.githubusercontent.com/10682/147877447-ff5907cd-49be-4976-b3b4-5df2ac6dfda4.gif)
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
+ ```
322
+
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.
225
340
 
226
341
  ### 1. Install Wrangler
227
342
 
228
343
  Install Cloudflare Command Line "[Wrangler](https://github.com/cloudflare/wrangler)"
229
344
 
230
345
  ```sh
231
- $ npm i @cloudflare/wrangler -g
346
+ npm i @cloudflare/wrangler -g
232
347
  ```
233
348
 
234
349
  ### 2. `npm init`
@@ -236,9 +351,9 @@ $ npm i @cloudflare/wrangler -g
236
351
  Make npm skeleton directory.
237
352
 
238
353
  ```sh
239
- $ mkdir hono-example
240
- $ ch hono-example
241
- $ npm init -y
354
+ mkdir hono-example
355
+ cd hono-example
356
+ npm init -y
242
357
  ```
243
358
 
244
359
  ### 3. `wrangler init`
@@ -246,46 +361,68 @@ $ npm init -y
246
361
  Init as a wrangler project.
247
362
 
248
363
  ```sh
249
- $ wrangler init
364
+ wrangler init
250
365
  ```
251
366
 
252
367
  ### 4. `npm install hono`
253
368
 
254
- Install `hono` from npm repository.
369
+ Install `hono` from npm registry.
255
370
 
256
- ```
257
- $ npm i hono
371
+ ```sh
372
+ npm i hono
258
373
  ```
259
374
 
260
375
  ### 5. Write your app
261
376
 
262
- Only 4 line!!
377
+ Only 4 lines!!
263
378
 
264
379
  ```js
265
- const { Hono } = require('hono')
380
+ import { Hono } from 'hono'
266
381
  const app = new Hono()
267
382
 
268
- app.get('/', () => new Response('Hello! Hono!'))
383
+ app.get('/', (c) => c.text('Hello! Hono!'))
269
384
 
270
385
  app.fire()
271
386
  ```
272
387
 
273
- ### 6. Run!
388
+ ### 6. Run
274
389
 
275
- Run the development server locally.
390
+ Run the development server locally. Then, access like `http://127.0.0.1:8787/` in your Web browser.
276
391
 
277
392
  ```sh
278
- $ wrangler dev
393
+ wrangler dev
394
+ ```
395
+
396
+ ### Publish
397
+
398
+ Deploy to Cloudflare. That's all!
399
+
400
+ ```sh
401
+ wrangler publish
279
402
  ```
280
403
 
281
404
  ## Related projects
282
405
 
283
- - 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
+
284
408
  - express <https://github.com/expressjs/express>
285
- - oak <https://github.com/oakserver/oak>
409
+ - koa <https://github.com/koajs/koa>
286
410
  - itty-router <https://github.com/kwhitley/itty-router>
287
411
  - Sunder <https://github.com/SunderJS/sunder>
288
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!
289
426
 
290
427
  ## Author
291
428
 
@@ -293,4 +430,4 @@ Yusuke Wada <https://github.com/yusukebe>
293
430
 
294
431
  ## License
295
432
 
296
- MIT
433
+ Distributed under the MIT License. See [LICENSE](LICENSE) for more information.
@@ -0,0 +1 @@
1
+ export declare const compose: (middleware: any) => (context: any, next?: Function) => any;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.compose = void 0;
4
+ // Based on the code in the MIT licensed `koa-compose` package.
5
+ const compose = (middleware) => {
6
+ return function (context, next) {
7
+ let index = -1;
8
+ return dispatch(0);
9
+ function dispatch(i) {
10
+ if (i <= index)
11
+ return Promise.reject(new Error('next() called multiple times'));
12
+ index = i;
13
+ let fn = middleware[i];
14
+ if (i === middleware.length)
15
+ fn = next;
16
+ if (!fn)
17
+ return Promise.resolve();
18
+ try {
19
+ return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
20
+ }
21
+ catch (err) {
22
+ return Promise.reject(err);
23
+ }
24
+ }
25
+ };
26
+ };
27
+ exports.compose = compose;
@@ -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 ADDED
@@ -0,0 +1,44 @@
1
+ /// <reference types="@cloudflare/workers-types" />
2
+ import type { Result } from './node';
3
+ import { Node } from './node';
4
+ import { Context } from './context';
5
+ import type { Env } from './context';
6
+ declare global {
7
+ interface Request {
8
+ params: (key: string) => string;
9
+ query: (key: string) => string | null;
10
+ parsedBody: any;
11
+ }
12
+ }
13
+ export declare type Handler = (c: Context, next?: Function) => Response | Promise<Response>;
14
+ export declare type MiddlewareHandler = (c: Context, next: Function) => Promise<void>;
15
+ export declare class Router<T> {
16
+ node: Node<T>;
17
+ constructor();
18
+ add(method: string, path: string, handler: T): void;
19
+ match(method: string, path: string): Result<T> | null;
20
+ }
21
+ export declare class Hono {
22
+ router: Router<Handler[]>;
23
+ middlewareRouters: Router<MiddlewareHandler>[];
24
+ tempPath: string;
25
+ constructor();
26
+ get(arg: string | Handler, ...args: Handler[]): Hono;
27
+ post(arg: string | Handler, ...args: Handler[]): Hono;
28
+ put(arg: string | Handler, ...args: Handler[]): Hono;
29
+ head(arg: string | Handler, ...args: Handler[]): Hono;
30
+ delete(arg: string | Handler, ...args: Handler[]): Hono;
31
+ options(arg: string | Handler, ...args: Handler[]): Hono;
32
+ patch(arg: string | Handler, ...args: Handler[]): Hono;
33
+ all(arg: string | Handler, ...args: Handler[]): Hono;
34
+ route(path: string): Hono;
35
+ use(path: string, middleware: MiddlewareHandler): void;
36
+ addRoute(method: string, arg: string | Handler, ...args: Handler[]): Hono;
37
+ matchRoute(method: string, path: string): Promise<Result<Handler[]>>;
38
+ dispatch(request: Request, env?: Env, event?: FetchEvent): Promise<Response>;
39
+ handleEvent(event: FetchEvent): Promise<Response>;
40
+ fetch(request: Request, env?: Env, event?: FetchEvent): Promise<Response>;
41
+ fire(): void;
42
+ onError(err: any): Response;
43
+ notFound(): Response;
44
+ }