hono 0.0.9 → 0.0.13

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 (44) hide show
  1. package/README.md +267 -50
  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 +43 -0
  7. package/dist/hono.js +139 -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 +12 -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 +46 -5
  27. package/.github/workflows/ci.yml +0 -24
  28. package/CODE_OF_CONDUCT.md +0 -128
  29. package/src/compose.js +0 -22
  30. package/src/compose.test.js +0 -42
  31. package/src/hono.d.ts +0 -48
  32. package/src/hono.js +0 -139
  33. package/src/hono.test.js +0 -154
  34. package/src/methods.js +0 -30
  35. package/src/middleware/defaultFilter.js +0 -19
  36. package/src/middleware/poweredBy.js +0 -6
  37. package/src/middleware/poweredBy.test.js +0 -17
  38. package/src/middleware.js +0 -9
  39. package/src/middleware.test.js +0 -17
  40. package/src/node.js +0 -97
  41. package/src/node.test.js +0 -135
  42. package/src/router.test.js +0 -88
  43. package/src/util.js +0 -33
  44. package/src/util.test.js +0 -44
package/README.md CHANGED
@@ -1,52 +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
- ## Feature
14
+ ## Features
15
15
 
16
- - Fast - the router is implemented with Trie-Tree structure.
17
- - Tiny - use only standard API.
18
- - Portable - zero dependencies.
19
- - Flexible - you can make your own middlewares.
20
- - 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.
21
20
 
22
21
  ## Benchmark
23
22
 
24
- ```
25
- hono x 813,001 ops/sec ±2.96% (75 runs sampled)
26
- itty-router x 160,415 ops/sec ±3.31% (85 runs sampled)
27
- 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)
28
30
  Fastest is hono
29
- ✨ Done in 37.03s.
31
+ ✨ Done in 52.79s.
30
32
  ```
31
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
+
32
40
  ## Install
33
41
 
34
- ```
35
- $ yarn add hono
42
+ You can install from npm registry:
43
+
44
+ ```sh
45
+ yarn add hono
36
46
  ```
37
47
 
38
48
  or
39
49
 
40
50
  ```sh
41
- $ npm install hono
51
+ npm install hono
42
52
  ```
43
53
 
44
54
  ## Methods
45
55
 
46
- - app.**HTTP_METHOD**(path, callback)
47
- - app.**all**(path, callback)
56
+ Instance of `Hono` has these methods:
57
+
58
+ - app.**HTTP_METHOD**(path, handler)
59
+ - app.**all**(path, handler)
48
60
  - app.**route**(path)
49
61
  - app.**use**(path, middleware)
62
+ - app.**fire**()
63
+ - app.**fetch**(request, env, event)
50
64
 
51
65
  ## Routing
52
66
 
@@ -100,99 +114,302 @@ app
100
114
  .put(() => {...})
101
115
  ```
102
116
 
117
+ ## async/await
118
+
119
+ ```js
120
+ app.get('/fetch-url', async () => {
121
+ const response = await fetch('https://example.com/')
122
+ return new Response(`Status is ${response.status}`)
123
+ })
124
+ ```
125
+
103
126
  ## Middleware
104
127
 
105
128
  ### Builtin Middleware
106
129
 
107
130
  ```js
108
- const { Hono, Middleware } = require('hono')
131
+ import { Hono, Middleware } from 'hono'
109
132
 
110
133
  ...
111
134
 
112
- app.use('*', Middleware.poweredBy)
113
-
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
+ )
114
144
  ```
115
145
 
146
+ Available builtin middleware are listed on [src/middleware](https://github.com/yusukebe/hono/tree/master/src/middleware).
147
+
116
148
  ### Custom Middleware
117
149
 
150
+ You can write your own middleware:
151
+
118
152
  ```js
119
- const logger = (c, next) => {
153
+ // Custom logger
154
+ app.use('*', async (c, next) => {
120
155
  console.log(`[${c.req.method}] ${c.req.url}`)
121
- next()
122
- }
123
-
124
- const addHeader = (c, next) => {
125
- next()
126
- c.res.headers.add('x-message', 'This is middleware!')
127
- }
156
+ await next()
157
+ })
128
158
 
129
- app.use('*', logger)
130
- app.use('/message/*', addHeader)
159
+ // Add a custom header
160
+ app.use('/message/*', async (c, next) => {
161
+ await next()
162
+ await c.res.headers.add('x-message', 'This is middleware!')
163
+ })
131
164
 
132
165
  app.get('/message/hello', () => 'Hello Middleware!')
133
166
  ```
134
167
 
135
168
  ### Custom 404 Response
136
169
 
170
+ You can customize 404 Not Found response:
171
+
137
172
  ```js
138
- const customNotFound = (c, next) => {
139
- next()
173
+ app.use('*', async (c, next) => {
174
+ await next()
140
175
  if (c.res.status === 404) {
141
176
  c.res = new Response('Custom 404 Not Found', { status: 404 })
142
177
  }
143
- }
178
+ })
179
+ ```
180
+
181
+ ### Complex Pattern
182
+
183
+ You can also do this:
184
+
185
+ ```js
186
+ // Output response time
187
+ app.use('*', async (c, next) => {
188
+ await next()
189
+ const responseTime = await c.res.headers.get('X-Response-Time')
190
+ console.log(`X-Response-Time: ${responseTime}`)
191
+ })
144
192
 
145
- app.use('*', customNotFound)
193
+ // Add X-Response-Time header
194
+ app.use('*', async (c, next) => {
195
+ const start = Date.now()
196
+ await next()
197
+ const ms = Date.now() - start
198
+ await c.res.headers.append('X-Response-Time', `${ms}ms`)
199
+ })
146
200
  ```
147
201
 
148
202
  ## Context
149
203
 
150
- ### req
204
+ To handle Request and Reponse easily, you can use Context object:
205
+
206
+ ### c.req
151
207
 
152
208
  ```js
209
+
210
+ // Get Request object
153
211
  app.get('/hello', (c) => {
154
- const userAgent = c.req.headers('User-Agent')
212
+ const userAgent = c.req.headers.get('User-Agent')
213
+ ...
214
+ })
215
+
216
+ // Query params
217
+ app.get('/search', (c) => {
218
+ const query = c.req.query('q')
219
+ ...
220
+ })
221
+
222
+ // Captured params
223
+ app.get('/entry/:id', (c) => {
224
+ const id = c.req.params('id')
155
225
  ...
156
226
  })
157
227
  ```
158
228
 
159
- ### res
229
+ ### c.res
160
230
 
161
231
  ```js
232
+ // Response object
162
233
  app.use('/', (c, next) => {
163
234
  next()
164
235
  c.res.headers.append('X-Debug', 'Debug message')
165
236
  })
166
237
  ```
167
238
 
168
- ## Request
239
+ ### c.event
240
+
241
+ ```js
242
+ // FetchEvent objest
243
+ app.use('*', async (c, next) => {
244
+ c.event.waitUntil(
245
+ ...
246
+ )
247
+ await next()
248
+ })
249
+ ```
169
250
 
170
- ### query
251
+ ### c.env
171
252
 
172
253
  ```js
173
- app.get('/search', (c) => {
174
- const query = c.req.query('q')
254
+ // Environment object for Cloudflare Workers
255
+ app.get('*', async c => {
256
+ const counter = c.env.COUNTER
175
257
  ...
176
258
  })
177
259
  ```
178
260
 
179
- ### params
261
+ ### c.text()
262
+
263
+ Render text as `Content-Type:text/plain`:
180
264
 
181
265
  ```js
182
- app.get('/entry/:id', (c) => {
183
- const id = c.req.params('id')
184
- ...
266
+ app.get('/say', (c) => {
267
+ return c.text('Hello!')
185
268
  })
186
269
  ```
187
270
 
271
+ ### c.json()
272
+
273
+ Render JSON as `Content-Type:application/json`:
274
+
275
+ ```js
276
+ app.get('/api', (c) => {
277
+ return c.json({ message: 'Hello!' })
278
+ })
279
+ ```
280
+
281
+ ### c.html()
282
+
283
+ Render HTML as `Content-Type:text/html`:
284
+
285
+ ```js
286
+ app.get('/', (c) => {
287
+ return c.html('<h1>Hello! Hono!</h1>')
288
+ })
289
+ ```
290
+
291
+ ### c.redirect()
292
+
293
+ Redirect, default status code is `302`:
294
+
295
+ ```js
296
+ app.get('/redirect', (c) => c.redirect('/'))
297
+ app.get('/redirect-permanently', (c) => c.redirect('/', 301))
298
+ ```
299
+
300
+ ## fire
301
+
302
+ `app.fire()` do:
303
+
304
+ ```js
305
+ addEventListener('fetch', (event) => {
306
+ event.respondWith(this.handleEvent(event))
307
+ })
308
+ ```
309
+
310
+ ## fetch
311
+
312
+ `app.fetch()` is for Cloudflare Module Worker syntax.
313
+
314
+ ```js
315
+ export default {
316
+ fetch(request: Request, env: Env, event: FetchEvent) {
317
+ return app.fetch(request, env, event)
318
+ },
319
+ }
320
+ ```
321
+
322
+ ## Cloudflare Workers with Hono
323
+
324
+ Using `wrangler` or `miniflare`, you can develop the application locally and publish it with few commands.
325
+
326
+ Let's write your first code for Cloudflare Workers with Hono.
327
+
328
+ ### 1. Install Wrangler
329
+
330
+ Install Cloudflare Command Line "[Wrangler](https://github.com/cloudflare/wrangler)"
331
+
332
+ ```sh
333
+ npm i @cloudflare/wrangler -g
334
+ ```
335
+
336
+ ### 2. `npm init`
337
+
338
+ Make npm skeleton directory.
339
+
340
+ ```sh
341
+ mkdir hono-example
342
+ ch hono-example
343
+ npm init -y
344
+ ```
345
+
346
+ ### 3. `wrangler init`
347
+
348
+ Init as a wrangler project.
349
+
350
+ ```sh
351
+ wrangler init
352
+ ```
353
+
354
+ ### 4. `npm install hono`
355
+
356
+ Install `hono` from npm registry.
357
+
358
+ ```sh
359
+ npm i hono
360
+ ```
361
+
362
+ ### 5. Write your app
363
+
364
+ Only 4 lines!!
365
+
366
+ ```js
367
+ import { Hono } from 'hono'
368
+ const app = new Hono()
369
+
370
+ app.get('/', (c) => c.text('Hello! Hono!'))
371
+
372
+ app.fire()
373
+ ```
374
+
375
+ ### 6. Run
376
+
377
+ Run the development server locally. Then, access like `http://127.0.0.1:8787/` in your Web browser.
378
+
379
+ ```sh
380
+ wrangler dev
381
+ ```
382
+
383
+ ### Publish
384
+
385
+ Deploy to Cloudflare. That's all!
386
+
387
+ ```sh
388
+ wrangler publish
389
+ ```
390
+
188
391
  ## Related projects
189
392
 
190
- - koa <https://github.com/koajs/koa>
393
+ 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.
394
+
191
395
  - express <https://github.com/expressjs/express>
192
- - oak <https://github.com/oakserver/oak>
396
+ - koa <https://github.com/koajs/koa>
193
397
  - itty-router <https://github.com/kwhitley/itty-router>
194
398
  - Sunder <https://github.com/SunderJS/sunder>
195
399
  - goblin <https://github.com/bmf-san/goblin>
400
+ - worktop <https://github.com/lukeed/worktop>
401
+
402
+ ## Contributing
403
+
404
+ Contributions Welcome! You can contribute by the following way:
405
+
406
+ - Write or fix documents
407
+ - Write code of middleware
408
+ - Fix bugs
409
+ - Refactor the code
410
+ - etc.
411
+
412
+ If you can, let's make Hono together!
196
413
 
197
414
  ## Author
198
415
 
@@ -200,4 +417,4 @@ Yusuke Wada <https://github.com/yusukebe>
200
417
 
201
418
  ## License
202
419
 
203
- MIT
420
+ 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,43 @@
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
+ notFound(): Response;
43
+ }