hono 0.0.7 → 0.0.11

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
@@ -11,22 +11,26 @@ app.get('/', () => new Response('Hono!!'))
11
11
  app.fire()
12
12
  ```
13
13
 
14
+ ![carbon](https://user-images.githubusercontent.com/10682/147877725-bce9bd46-953d-4d70-9c2b-3eae47ad4df9.png)
15
+
14
16
  ## Feature
15
17
 
16
18
  - Fast - the router is implemented with Trie-Tree structure.
17
- - Tiny - use only standard API.
18
19
  - Portable - zero dependencies.
19
20
  - Flexible - you can make your own middlewares.
20
- - Optimized - for Cloudflare Workers and Fastly Compute@Edge.
21
+ - Easy - simple API, builtin middleware, and TypeScript support.
22
+ - Optimized - for Cloudflare Workers or Fastly Compute@Edge.
21
23
 
22
24
  ## Benchmark
23
25
 
26
+ Hono is fastest!!
27
+
24
28
  ```
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)
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)
28
32
  Fastest is hono
29
- ✨ Done in 37.03s.
33
+ ✨ Done in 42.84s.
30
34
  ```
31
35
 
32
36
  ## Install
@@ -43,8 +47,8 @@ $ npm install hono
43
47
 
44
48
  ## Methods
45
49
 
46
- - app.**HTTP_METHOD**(path, callback)
47
- - app.**all**(path, callback)
50
+ - app.**HTTP_METHOD**(path, handler)
51
+ - app.**all**(path, handler)
48
52
  - app.**route**(path)
49
53
  - app.**use**(path, middleware)
50
54
 
@@ -100,32 +104,98 @@ app
100
104
  .put(() => {...})
101
105
  ```
102
106
 
107
+ ## Async
108
+
109
+ ```js
110
+ app.get('/fetch-url', async () => {
111
+ const response = await fetch('https://example.com/')
112
+ return new Response(`Status is ${response.status}`)
113
+ })
114
+ ```
115
+
103
116
  ## Middleware
104
117
 
118
+ ### Builtin Middleware
119
+
105
120
  ```js
106
- const logger = (c, next) => {
107
- console.log(`[${c.req.method}] ${c.req.url}`)
108
- next()
109
- }
121
+ const { Hono, Middleware } = require('hono')
110
122
 
111
- const addHeader = (c, next) => {
112
- next()
113
- c.res.headers.add('x-message', 'This is middleware!')
114
- }
123
+ ...
115
124
 
116
- app = app.use('*', logger)
117
- app = app.use('/message/*', addHeader)
125
+ app.use('*', Middleware.poweredBy())
126
+ app.use('*', Middleware.logger())
127
+
128
+ ```
129
+
130
+ ### Custom Middleware
131
+
132
+ ```js
133
+ // Custom logger
134
+ app.use('*', async (c, next) => {
135
+ console.log(`[${c.req.method}] ${c.req.url}`)
136
+ await next()
137
+ })
138
+
139
+ // Add custom header
140
+ app.use('/message/*', async (c, next) => {
141
+ await next()
142
+ await c.res.headers.add('x-message', 'This is middleware!')
143
+ })
118
144
 
119
145
  app.get('/message/hello', () => 'Hello Middleware!')
120
146
  ```
121
147
 
148
+ ### Custom 404 Response
149
+
150
+ ```js
151
+ app.use('*', async (c, next) => {
152
+ await next()
153
+ if (c.res.status === 404) {
154
+ c.res = new Response('Custom 404 Not Found', { status: 404 })
155
+ }
156
+ })
157
+ ```
158
+
159
+ ### Complex Pattern
160
+
161
+ ```js
162
+ // Output response time
163
+ app.use('*', async (c, next) => {
164
+ await next()
165
+ const responseTime = await c.res.headers.get('X-Response-Time')
166
+ console.log(`X-Response-Time: ${responseTime}`)
167
+ })
168
+
169
+ // Add X-Response-Time header
170
+ app.use('*', async (c, next) => {
171
+ const start = Date.now()
172
+ await next()
173
+ const ms = Date.now() - start
174
+ await c.res.headers.append('X-Response-Time', `${ms}ms`)
175
+ })
176
+ ```
177
+
122
178
  ## Context
123
179
 
124
180
  ### req
125
181
 
126
182
  ```js
183
+
184
+ // Get Request object
127
185
  app.get('/hello', (c) => {
128
- const userAgent = c.req.headers('User-Agent')
186
+ const userAgent = c.req.headers.get('User-Agent')
187
+ ...
188
+ })
189
+
190
+ // Query params
191
+ app.get('/search', (c) => {
192
+ const query = c.req.query('q')
193
+ ...
194
+ })
195
+
196
+ // Captured params
197
+ app.get('/entry/:id', (c) => {
198
+ const id = c.req.params('id')
129
199
  ...
130
200
  })
131
201
  ```
@@ -133,30 +203,82 @@ app.get('/hello', (c) => {
133
203
  ### res
134
204
 
135
205
  ```js
206
+ // Response object
136
207
  app.use('/', (c, next) => {
137
208
  next()
138
209
  c.res.headers.append('X-Debug', 'Debug message')
139
210
  })
140
211
  ```
141
212
 
142
- ## Request
143
-
144
- ### query
213
+ ### text
145
214
 
146
215
  ```js
147
- app.get('/search', (c) => {
148
- const query = c.req.query('q')
149
- ...
216
+ app.get('/say', (c) => {
217
+ return c.text('Hello!')
150
218
  })
151
219
  ```
152
220
 
153
- ### params
221
+ ## Hono in 1 minute
222
+
223
+ Create your first Cloudflare Workers with Hono from scratch.
224
+
225
+ ### How to setup
226
+
227
+ ![Demo](https://user-images.githubusercontent.com/10682/147877447-ff5907cd-49be-4976-b3b4-5df2ac6dfda4.gif)
228
+
229
+ ### 1. Install Wrangler
230
+
231
+ Install Cloudflare Command Line "[Wrangler](https://github.com/cloudflare/wrangler)"
232
+
233
+ ```sh
234
+ $ npm i @cloudflare/wrangler -g
235
+ ```
236
+
237
+ ### 2. `npm init`
238
+
239
+ Make npm skeleton directory.
240
+
241
+ ```sh
242
+ $ mkdir hono-example
243
+ $ ch hono-example
244
+ $ npm init -y
245
+ ```
246
+
247
+ ### 3. `wrangler init`
248
+
249
+ Init as a wrangler project.
250
+
251
+ ```sh
252
+ $ wrangler init
253
+ ```
254
+
255
+ ### 4. `npm install hono`
256
+
257
+ Install `hono` from npm repository.
258
+
259
+ ```
260
+ $ npm i hono
261
+ ```
262
+
263
+ ### 5. Write your app
264
+
265
+ Only 4 line!!
154
266
 
155
267
  ```js
156
- app.get('/entry/:id', (c) => {
157
- const id = c.req.params('id')
158
- ...
159
- })
268
+ const { Hono } = require('hono')
269
+ const app = new Hono()
270
+
271
+ app.get('/', () => new Response('Hello! Hono!'))
272
+
273
+ app.fire()
274
+ ```
275
+
276
+ ### 6. Run!
277
+
278
+ Run the development server locally.
279
+
280
+ ```sh
281
+ $ wrangler dev
160
282
  ```
161
283
 
162
284
  ## Related projects
@@ -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;
package/dist/hono.d.ts ADDED
@@ -0,0 +1,47 @@
1
+ /// <reference types="@cloudflare/workers-types" />
2
+ import { Node, Result } from './node';
3
+ import { Middleware } from './middleware';
4
+ export { Middleware };
5
+ declare global {
6
+ interface Request {
7
+ params: (key: string) => any;
8
+ query: (key: string) => string | null;
9
+ }
10
+ }
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>;
20
+ export declare class 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
+ export declare class Hono {
27
+ router: Router<Handler[]>;
28
+ middlewareRouters: Router<MiddlwareHandler>[];
29
+ tempPath: string;
30
+ constructor();
31
+ get(arg: string | Handler, ...args: Handler[]): Hono;
32
+ post(arg: string | Handler, ...args: Handler[]): Hono;
33
+ put(arg: string | Handler, ...args: Handler[]): Hono;
34
+ head(arg: string | Handler, ...args: Handler[]): Hono;
35
+ delete(arg: string | Handler, ...args: Handler[]): Hono;
36
+ options(arg: string | Handler, ...args: Handler[]): Hono;
37
+ patch(arg: string | Handler, ...args: Handler[]): Hono;
38
+ all(arg: string | Handler, ...args: Handler[]): Hono;
39
+ route(path: string): Hono;
40
+ use(path: string, middleware: MiddlwareHandler): void;
41
+ addRoute(method: string, arg: string | Handler, ...args: Handler[]): Hono;
42
+ matchRoute(method: string, path: string): Promise<Result<Handler[]>>;
43
+ dispatch(request: Request, response?: Response): Promise<Response>;
44
+ handleEvent(event: FetchEvent): Promise<Response>;
45
+ fire(): void;
46
+ notFound(): Response;
47
+ }
package/dist/hono.js ADDED
@@ -0,0 +1,153 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Hono = exports.Router = exports.Context = exports.Middleware = void 0;
4
+ const node_1 = require("./node");
5
+ const compose_1 = require("./compose");
6
+ const util_1 = require("./util");
7
+ const middleware_1 = require("./middleware");
8
+ Object.defineProperty(exports, "Middleware", { enumerable: true, get: function () { return middleware_1.Middleware; } });
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
+ class Router {
29
+ constructor() {
30
+ this.node = new node_1.Node();
31
+ }
32
+ add(method, path, handler) {
33
+ this.node.insert(method, path, handler);
34
+ }
35
+ match(method, path) {
36
+ return this.node.search(method, path);
37
+ }
38
+ }
39
+ exports.Router = Router;
40
+ class Hono {
41
+ constructor() {
42
+ this.router = new Router();
43
+ this.middlewareRouters = [];
44
+ this.tempPath = '/';
45
+ }
46
+ /* HTTP METHODS */
47
+ get(arg, ...args) {
48
+ return this.addRoute('get', arg, ...args);
49
+ }
50
+ post(arg, ...args) {
51
+ return this.addRoute('post', arg, ...args);
52
+ }
53
+ put(arg, ...args) {
54
+ return this.addRoute('put', arg, ...args);
55
+ }
56
+ head(arg, ...args) {
57
+ return this.addRoute('head', arg, ...args);
58
+ }
59
+ delete(arg, ...args) {
60
+ return this.addRoute('delete', arg, ...args);
61
+ }
62
+ options(arg, ...args) {
63
+ return this.addRoute('options', arg, ...args);
64
+ }
65
+ patch(arg, ...args) {
66
+ return this.addRoute('patch', arg, ...args);
67
+ }
68
+ /*
69
+ trace
70
+ copy
71
+ lock
72
+ purge
73
+ unlock
74
+ report
75
+ checkout
76
+ merge
77
+ notify
78
+ subscribe
79
+ unsubscribe
80
+ search
81
+ connect
82
+ */
83
+ all(arg, ...args) {
84
+ return this.addRoute('all', arg, ...args);
85
+ }
86
+ route(path) {
87
+ this.tempPath = path;
88
+ return this;
89
+ }
90
+ use(path, middleware) {
91
+ if (middleware.constructor.name !== 'AsyncFunction') {
92
+ throw new TypeError('middleware must be a async function!');
93
+ }
94
+ const router = new Router();
95
+ router.add(METHOD_NAME_OF_ALL, path, middleware);
96
+ this.middlewareRouters.push(router);
97
+ }
98
+ // addRoute('get', '/', handler)
99
+ addRoute(method, arg, ...args) {
100
+ method = method.toUpperCase();
101
+ if (typeof arg === 'string') {
102
+ this.router.add(method, arg, args);
103
+ }
104
+ else {
105
+ args.unshift(arg);
106
+ this.router.add(method, this.tempPath, args);
107
+ }
108
+ return this;
109
+ }
110
+ async matchRoute(method, path) {
111
+ return this.router.match(method, path);
112
+ }
113
+ async dispatch(request, response) {
114
+ const [method, path] = [request.method, (0, util_1.getPathFromURL)(request.url)];
115
+ const result = await this.matchRoute(method, path);
116
+ request.params = (key) => {
117
+ if (result) {
118
+ return result.params[key];
119
+ }
120
+ return '';
121
+ };
122
+ let handler = result ? result.handler[0] : this.notFound; // XXX
123
+ const middleware = [];
124
+ for (const mr of this.middlewareRouters) {
125
+ const mwResult = mr.match(METHOD_NAME_OF_ALL, path);
126
+ if (mwResult) {
127
+ middleware.push(mwResult.handler);
128
+ }
129
+ }
130
+ let wrappedHandler = async (context, next) => {
131
+ context.res = await handler(context);
132
+ await next();
133
+ };
134
+ middleware.push(middleware_1.Middleware.defaultFilter);
135
+ middleware.push(wrappedHandler);
136
+ const composed = (0, compose_1.compose)(middleware);
137
+ const c = new Context(request, response);
138
+ await composed(c);
139
+ return c.res;
140
+ }
141
+ async handleEvent(event) {
142
+ return this.dispatch(event.request);
143
+ }
144
+ fire() {
145
+ addEventListener('fetch', (event) => {
146
+ event.respondWith(this.handleEvent(event));
147
+ });
148
+ }
149
+ notFound() {
150
+ return new Response('Not Found', { status: 404 });
151
+ }
152
+ }
153
+ exports.Hono = Hono;
@@ -0,0 +1 @@
1
+ export { Hono, Middleware, Context } from './hono';
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Context = exports.Middleware = exports.Hono = void 0;
4
+ var hono_1 = require("./hono");
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; } });
@@ -0,0 +1 @@
1
+ export declare const methods: string[];
@@ -0,0 +1,31 @@
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
+ ];
@@ -0,0 +1,2 @@
1
+ import { Context } from '../hono';
2
+ export declare const defaultFilter: (c: Context, next: Function) => Promise<void>;
@@ -0,0 +1,11 @@
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;
@@ -0,0 +1,5 @@
1
+ import { Context } from '../../hono';
2
+ export declare const logger: (fn?: {
3
+ (...data: any[]): void;
4
+ (...data: any[]): void;
5
+ }) => (c: Context, next: Function) => Promise<void>;
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.logger = void 0;
4
+ const util_1 = require("../../util");
5
+ const humanize = (n, opts) => {
6
+ const options = opts || {};
7
+ const d = options.delimiter || ',';
8
+ const s = options.separator || '.';
9
+ n = n.toString().split('.');
10
+ n[0] = n[0].replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1' + d);
11
+ return n.join(s);
12
+ };
13
+ const time = (start) => {
14
+ const delta = Date.now() - start;
15
+ return humanize([delta < 10000 ? delta + 'ms' : Math.round(delta / 1000) + 's']);
16
+ };
17
+ const LogPrefix = {
18
+ Outgoing: '-->',
19
+ Incoming: '<--',
20
+ Error: 'xxx',
21
+ };
22
+ const colorStatus = (status = 0) => {
23
+ const out = {
24
+ 7: `\x1b[35m${status}\x1b[0m`,
25
+ 5: `\x1b[31m${status}\x1b[0m`,
26
+ 4: `\x1b[33m${status}\x1b[0m`,
27
+ 3: `\x1b[36m${status}\x1b[0m`,
28
+ 2: `\x1b[32m${status}\x1b[0m`,
29
+ 1: `\x1b[32m${status}\x1b[0m`,
30
+ 0: `\x1b[33m${status}\x1b[0m`,
31
+ };
32
+ return out[(status / 100) | 0];
33
+ };
34
+ function log(fn, prefix, method, path, status, elasped) {
35
+ const out = prefix === LogPrefix.Incoming
36
+ ? ` ${prefix} ${method} ${path}`
37
+ : ` ${prefix} ${method} ${path} ${colorStatus(status)} ${elasped}`;
38
+ fn(out);
39
+ }
40
+ const logger = (fn = console.log) => {
41
+ return async (c, next) => {
42
+ const { method } = c.req;
43
+ const path = (0, util_1.getPathFromURL)(c.req.url);
44
+ log(fn, LogPrefix.Incoming, method, path);
45
+ const start = Date.now();
46
+ try {
47
+ await next();
48
+ }
49
+ catch (e) {
50
+ log(fn, LogPrefix.Error, method, path, c.res.status || 500, time(start));
51
+ throw e;
52
+ }
53
+ log(fn, LogPrefix.Outgoing, method, path, c.res.status, time(start));
54
+ };
55
+ };
56
+ exports.logger = logger;
@@ -0,0 +1,2 @@
1
+ import { Context } from '../../hono';
2
+ export declare const poweredBy: () => (c: Context, next: Function) => Promise<void>;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.poweredBy = void 0;
4
+ const poweredBy = () => {
5
+ return async (c, next) => {
6
+ await next();
7
+ // await c.res.headers.append('X-Powered-By', 'Hono')
8
+ c.res.headers.append('X-Powered-By', 'Hono');
9
+ };
10
+ };
11
+ exports.poweredBy = poweredBy;
@@ -0,0 +1,8 @@
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>;
4
+ static logger: (fn?: {
5
+ (...data: any[]): void;
6
+ (...data: any[]): void;
7
+ }) => (c: import("./hono").Context, next: Function) => Promise<void>;
8
+ }
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Middleware = void 0;
4
+ const defaultFilter_1 = require("./middleware/defaultFilter");
5
+ const poweredBy_1 = require("./middleware/poweredBy/poweredBy");
6
+ const logger_1 = require("./middleware/logger/logger");
7
+ class Middleware {
8
+ }
9
+ exports.Middleware = Middleware;
10
+ Middleware.defaultFilter = defaultFilter_1.defaultFilter;
11
+ Middleware.poweredBy = poweredBy_1.poweredBy;
12
+ Middleware.logger = logger_1.logger;
package/dist/node.d.ts ADDED
@@ -0,0 +1,24 @@
1
+ export declare class Result<T> {
2
+ handler: T;
3
+ params: {
4
+ [key: string]: string;
5
+ };
6
+ constructor(handler: T, params: {
7
+ [key: string]: string;
8
+ });
9
+ }
10
+ export declare class Node<T> {
11
+ method: {
12
+ [key: string]: T;
13
+ };
14
+ handler: T;
15
+ children: {
16
+ [key: string]: Node<T>;
17
+ };
18
+ middlewares: [];
19
+ constructor(method?: string, handler?: any, children?: {
20
+ [key: string]: Node<T>;
21
+ });
22
+ insert(method: string, path: string, handler: T): Node<T>;
23
+ search(method: string, path: string): Result<T>;
24
+ }