hono 0.0.12 → 0.0.16

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,9 +1,9 @@
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
9
  app.get('/', (c) => c.text('Hono!!'))
@@ -11,26 +11,24 @@ app.get('/', (c) => c.text('Hono!!'))
11
11
  app.fire()
12
12
  ```
13
13
 
14
- Hono[炎] - _**means flame🔥 in Japanese**_ - is small, fast and simple web flamework for a Service Workers API based serverless such as **Cloudflare Workers** and **Fastly Compute@Edge**. Hono does not depend on any npm packages. However, Hono has a router, context object, and middleware including the builtins. It's easy to make a web application.
15
-
16
14
  ## Features
17
15
 
18
- - **Fast** - the router is implemented with Trie-Tree structure.
19
- - **Tiny** - zero dependencies, using Web standard API.
20
- - **Flexible** - you can make your own middleware.
21
- - **Easy** - simple API, builtin middleware, and written in TypeScript.
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
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
 
36
34
  ## Hono in 1 minute
@@ -43,14 +41,14 @@ Below is a demonstration to create an application of Cloudflare Workers with Hon
43
41
 
44
42
  You can install from npm registry:
45
43
 
46
- ```
47
- $ yarn add hono
44
+ ```sh
45
+ yarn add hono
48
46
  ```
49
47
 
50
48
  or
51
49
 
52
50
  ```sh
53
- $ npm install hono
51
+ npm install hono
54
52
  ```
55
53
 
56
54
  ## Methods
@@ -62,6 +60,7 @@ Instance of `Hono` has these methods:
62
60
  - app.**route**(path)
63
61
  - app.**use**(path, middleware)
64
62
  - app.**fire**()
63
+ - app.**fetch**(request, env, event)
65
64
 
66
65
  ## Routing
67
66
 
@@ -71,12 +70,12 @@ Instance of `Hono` has these methods:
71
70
 
72
71
  ```js
73
72
  // HTTP Methods
74
- app.get('/', () => new Response('GET /'))
75
- app.post('/', () => new Response('POST /'))
73
+ app.get('/', (c) => c.text('GET /'))
74
+ app.post('/', (c) => c.text('POST /'))
76
75
 
77
76
  // Wildcard
78
- app.get('/wild/*/card', () => {
79
- return new Response('GET /wild/*/card')
77
+ app.get('/wild/*/card', (c) => {
78
+ return c.text('GET /wild/*/card')
80
79
  })
81
80
  ```
82
81
 
@@ -84,7 +83,7 @@ app.get('/wild/*/card', () => {
84
83
 
85
84
  ```js
86
85
  // Any HTTP methods
87
- app.all('/hello', () => new Response('ALL Method /hello'))
86
+ app.all('/hello', (c) => c.text('Any Method /hello'))
88
87
  ```
89
88
 
90
89
  ### Named Parameter
@@ -118,9 +117,9 @@ app
118
117
  ## async/await
119
118
 
120
119
  ```js
121
- app.get('/fetch-url', async () => {
120
+ app.get('/fetch-url', async (c) => {
122
121
  const response = await fetch('https://example.com/')
123
- return new Response(`Status is ${response.status}`)
122
+ return c.text(`Status is ${response.status}`)
124
123
  })
125
124
  ```
126
125
 
@@ -129,10 +128,12 @@ app.get('/fetch-url', async () => {
129
128
  ### Builtin Middleware
130
129
 
131
130
  ```js
132
- const { Hono, Middleware } = require('hono')
131
+ import { Hono, Middleware } from 'hono'
133
132
 
134
133
  ...
135
134
 
135
+ app.use('*', Middleware.poweredBy())
136
+ app.use('*', Middleware.logger())
136
137
  app.use(
137
138
  '/auth/*',
138
139
  Middleware.basicAuth({
@@ -161,7 +162,7 @@ app.use('/message/*', async (c, next) => {
161
162
  await c.res.headers.add('x-message', 'This is middleware!')
162
163
  })
163
164
 
164
- app.get('/message/hello', () => 'Hello Middleware!')
165
+ app.get('/message/hello', (c) => c.text('Hello Middleware!'))
165
166
  ```
166
167
 
167
168
  ### Custom 404 Response
@@ -177,6 +178,19 @@ app.use('*', async (c, next) => {
177
178
  })
178
179
  ```
179
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
+
180
194
  ### Complex Pattern
181
195
 
182
196
  You can also do this:
@@ -235,6 +249,28 @@ app.use('/', (c, next) => {
235
249
  })
236
250
  ```
237
251
 
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
+
238
274
  ### c.text()
239
275
 
240
276
  Render text as `Content-Type:text/plain`:
@@ -260,11 +296,20 @@ app.get('/api', (c) => {
260
296
  Render HTML as `Content-Type:text/html`:
261
297
 
262
298
  ```js
263
- app.get('/api', (c) => {
299
+ app.get('/', (c) => {
264
300
  return c.html('<h1>Hello! Hono!</h1>')
265
301
  })
266
302
  ```
267
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
+
268
313
  ## fire
269
314
 
270
315
  `app.fire()` do:
@@ -275,6 +320,18 @@ addEventListener('fetch', (event) => {
275
320
  })
276
321
  ```
277
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
+
278
335
  ## Cloudflare Workers with Hono
279
336
 
280
337
  Using `wrangler` or `miniflare`, you can develop the application locally and publish it with few commands.
@@ -286,7 +343,7 @@ Let's write your first code for Cloudflare Workers with Hono.
286
343
  Install Cloudflare Command Line "[Wrangler](https://github.com/cloudflare/wrangler)"
287
344
 
288
345
  ```sh
289
- $ npm i @cloudflare/wrangler -g
346
+ npm i @cloudflare/wrangler -g
290
347
  ```
291
348
 
292
349
  ### 2. `npm init`
@@ -294,9 +351,9 @@ $ npm i @cloudflare/wrangler -g
294
351
  Make npm skeleton directory.
295
352
 
296
353
  ```sh
297
- $ mkdir hono-example
298
- $ ch hono-example
299
- $ npm init -y
354
+ mkdir hono-example
355
+ cd hono-example
356
+ npm init -y
300
357
  ```
301
358
 
302
359
  ### 3. `wrangler init`
@@ -304,15 +361,15 @@ $ npm init -y
304
361
  Init as a wrangler project.
305
362
 
306
363
  ```sh
307
- $ wrangler init
364
+ wrangler init
308
365
  ```
309
366
 
310
367
  ### 4. `npm install hono`
311
368
 
312
369
  Install `hono` from npm registry.
313
370
 
314
- ```
315
- $ npm i hono
371
+ ```sh
372
+ npm i hono
316
373
  ```
317
374
 
318
375
  ### 5. Write your app
@@ -320,7 +377,7 @@ $ npm i hono
320
377
  Only 4 lines!!
321
378
 
322
379
  ```js
323
- const { Hono } = require('hono')
380
+ import { Hono } from 'hono'
324
381
  const app = new Hono()
325
382
 
326
383
  app.get('/', (c) => c.text('Hello! Hono!'))
@@ -333,7 +390,7 @@ app.fire()
333
390
  Run the development server locally. Then, access like `http://127.0.0.1:8787/` in your Web browser.
334
391
 
335
392
  ```sh
336
- $ wrangler dev
393
+ wrangler dev
337
394
  ```
338
395
 
339
396
  ### Publish
@@ -341,18 +398,19 @@ $ wrangler dev
341
398
  Deploy to Cloudflare. That's all!
342
399
 
343
400
  ```sh
344
- $ wrangler publish
401
+ wrangler publish
345
402
  ```
346
403
 
347
404
  ## Related projects
348
405
 
349
- 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) and [Sunder](https://github.com/SunderJS/sunder) are the other routers or frameworks for Cloudflare Workers.
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.
350
407
 
351
408
  - express <https://github.com/expressjs/express>
352
409
  - koa <https://github.com/koajs/koa>
353
410
  - itty-router <https://github.com/kwhitley/itty-router>
354
411
  - Sunder <https://github.com/SunderJS/sunder>
355
412
  - goblin <https://github.com/bmf-san/goblin>
413
+ - worktop <https://github.com/lukeed/worktop>
356
414
 
357
415
  ## Contributing
358
416
 
package/dist/context.d.ts CHANGED
@@ -1,9 +1,23 @@
1
+ /// <reference types="@cloudflare/workers-types" />
2
+ declare type Headers = {
3
+ [key: string]: string;
4
+ };
5
+ export interface Env {
6
+ }
1
7
  export declare class Context {
2
8
  req: Request;
3
9
  res: Response;
4
- constructor(req: Request, res: Response);
10
+ env: Env;
11
+ event: FetchEvent;
12
+ constructor(req: Request, opts?: {
13
+ res: Response;
14
+ env: Env;
15
+ event: FetchEvent;
16
+ });
5
17
  newResponse(body?: BodyInit | null | undefined, init?: ResponseInit | undefined): Response;
6
- text(body: string): Response;
7
- json(object: object, replacer?: (string | number)[], space?: string | number): Response;
8
- html(body: string): 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;
9
22
  }
23
+ export {};
package/dist/context.js CHANGED
@@ -1,45 +1,63 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Context = void 0;
4
+ const util_1 = require("./util");
4
5
  class Context {
5
- constructor(req, res) {
6
+ constructor(req, opts) {
6
7
  this.req = req;
7
- this.res = res;
8
+ if (opts) {
9
+ this.res = opts.res;
10
+ this.env = opts.env;
11
+ this.event = opts.event;
12
+ }
8
13
  }
9
14
  newResponse(body, init) {
10
15
  return new Response(body, init);
11
16
  }
12
- text(body) {
13
- if (typeof body !== 'string') {
17
+ text(text, status = 200, headers = {}) {
18
+ if (typeof text !== 'string') {
14
19
  throw new TypeError('text method arg must be a string!');
15
20
  }
16
- return this.newResponse(body, {
17
- status: 200,
18
- headers: {
19
- 'Content-Type': 'text/plain',
20
- },
21
+ headers['Content-Type'] = 'text/plain';
22
+ return this.newResponse(text, {
23
+ status: status,
24
+ headers: headers,
21
25
  });
22
26
  }
23
- json(object, replacer, space) {
27
+ json(object, status = 200, headers = {}) {
24
28
  if (typeof object !== 'object') {
25
29
  throw new TypeError('json method arg must be a object!');
26
30
  }
27
- const body = JSON.stringify(object, replacer, space);
31
+ const body = JSON.stringify(object);
32
+ headers['Content-Type'] = 'application/json; charset=UTF-8';
28
33
  return this.newResponse(body, {
29
- status: 200,
30
- headers: {
31
- 'Content-Type': 'application/json; charset=UTF-8',
32
- },
34
+ status: status,
35
+ headers: headers,
33
36
  });
34
37
  }
35
- html(body) {
36
- if (typeof body !== 'string') {
38
+ html(html, status = 200, headers = {}) {
39
+ if (typeof html !== 'string') {
37
40
  throw new TypeError('html method arg must be a string!');
38
41
  }
39
- return this.newResponse(body, {
40
- status: 200,
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,
41
59
  headers: {
42
- 'Content-Type': 'text/html; charset=UTF-8',
60
+ Location: location,
43
61
  },
44
62
  });
45
63
  }
package/dist/hono.d.ts CHANGED
@@ -1,17 +1,17 @@
1
1
  /// <reference types="@cloudflare/workers-types" />
2
2
  import type { Result } from './node';
3
3
  import { Node } from './node';
4
- import { Middleware } from './middleware';
5
4
  import { Context } from './context';
6
- export { Middleware };
5
+ import type { Env } from './context';
7
6
  declare global {
8
7
  interface Request {
9
- params: (key: string) => any;
8
+ params: (key: string) => string;
10
9
  query: (key: string) => string | null;
10
+ parsedBody: any;
11
11
  }
12
12
  }
13
- declare type Handler = (c: Context, next?: Function) => Response | Promise<Response>;
14
- 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>;
15
15
  export declare class Router<T> {
16
16
  node: Node<T>;
17
17
  constructor();
@@ -20,7 +20,7 @@ export declare class Router<T> {
20
20
  }
21
21
  export declare class Hono {
22
22
  router: Router<Handler[]>;
23
- middlewareRouters: Router<MiddlwareHandler>[];
23
+ middlewareRouters: Router<MiddlewareHandler>[];
24
24
  tempPath: string;
25
25
  constructor();
26
26
  get(arg: string | Handler, ...args: Handler[]): Hono;
@@ -32,11 +32,13 @@ export declare class Hono {
32
32
  patch(arg: string | Handler, ...args: Handler[]): Hono;
33
33
  all(arg: string | Handler, ...args: Handler[]): Hono;
34
34
  route(path: string): Hono;
35
- use(path: string, middleware: MiddlwareHandler): void;
35
+ use(path: string, middleware: MiddlewareHandler): void;
36
36
  addRoute(method: string, arg: string | Handler, ...args: Handler[]): Hono;
37
37
  matchRoute(method: string, path: string): Promise<Result<Handler[]>>;
38
- dispatch(request: Request, response?: Response): Promise<Response>;
38
+ dispatch(request: Request, env?: Env, event?: FetchEvent): Promise<Response>;
39
39
  handleEvent(event: FetchEvent): Promise<Response>;
40
+ fetch(request: Request, env?: Env, event?: FetchEvent): Promise<Response>;
40
41
  fire(): void;
42
+ onError(err: any): Response;
41
43
  notFound(): Response;
42
44
  }
package/dist/hono.js CHANGED
@@ -1,11 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Hono = exports.Router = 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; } });
9
8
  const context_1 = require("./context");
10
9
  const METHOD_NAME_OF_ALL = 'ALL';
11
10
  class Router {
@@ -94,7 +93,7 @@ class Hono {
94
93
  async matchRoute(method, path) {
95
94
  return this.router.match(method, path);
96
95
  }
97
- async dispatch(request, response) {
96
+ async dispatch(request, env, event) {
98
97
  const [method, path] = [request.method, (0, util_1.getPathFromURL)(request.url)];
99
98
  const result = await this.matchRoute(method, path);
100
99
  request.params = (key) => {
@@ -115,21 +114,32 @@ class Hono {
115
114
  context.res = await handler(context);
116
115
  await next();
117
116
  };
118
- middleware.push(middleware_1.Middleware.defaultFilter);
117
+ middleware.push(middleware_1.Middleware.default);
119
118
  middleware.push(wrappedHandler);
120
119
  const composed = (0, compose_1.compose)(middleware);
121
- const c = new context_1.Context(request, response);
120
+ const c = new context_1.Context(request, { env: env, event: event, res: null });
122
121
  await composed(c);
123
122
  return c.res;
124
123
  }
125
124
  async handleEvent(event) {
126
- 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
+ });
127
133
  }
128
134
  fire() {
129
135
  addEventListener('fetch', (event) => {
130
136
  event.respondWith(this.handleEvent(event));
131
137
  });
132
138
  }
139
+ onError(err) {
140
+ console.error(err);
141
+ return new Response('Internal Server Error', { status: 500 });
142
+ }
133
143
  notFound() {
134
144
  return new Response('Not Found', { status: 404 });
135
145
  }
package/dist/index.d.ts CHANGED
@@ -1,2 +1,5 @@
1
- export { Hono, Middleware } from './hono';
1
+ export { Hono } from './hono';
2
+ export type { Handler, MiddlewareHandler } from './hono';
3
+ export { Middleware } from './middleware';
2
4
  export { Context } from './context';
5
+ export type { Env } from './context';
package/dist/index.js CHANGED
@@ -3,6 +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; } });
6
+ var middleware_1 = require("./middleware");
7
+ Object.defineProperty(exports, "Middleware", { enumerable: true, get: function () { return middleware_1.Middleware; } });
7
8
  var context_1 = require("./context");
8
9
  Object.defineProperty(exports, "Context", { enumerable: true, get: function () { return context_1.Context; } });
@@ -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,11 @@
1
+ import type { Context } from '../../context';
2
+ declare type CORSOptions = {
3
+ origin: string;
4
+ allowMethods?: string[];
5
+ allowHeaders?: string[];
6
+ maxAge?: number;
7
+ credentials?: boolean;
8
+ exposeHeaders?: string[];
9
+ };
10
+ export declare const cors: (options?: CORSOptions) => (c: Context, next: Function) => Promise<void>;
11
+ export {};
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.cors = void 0;
4
+ const cors = (options) => {
5
+ const defaults = {
6
+ origin: '*',
7
+ allowMethods: ['GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'PATCH'],
8
+ allowHeaders: [],
9
+ exposeHeaders: [],
10
+ };
11
+ const opts = Object.assign(Object.assign({}, defaults), options);
12
+ return async (c, next) => {
13
+ await next();
14
+ function set(key, value) {
15
+ c.res.headers.append(key, value);
16
+ }
17
+ set('Access-Control-Allow-Origin', opts.origin);
18
+ // Suppose the server sends a response with an Access-Control-Allow-Origin value with an explicit origin (rather than the "*" wildcard).
19
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
20
+ if (opts.origin !== '*') {
21
+ set('Vary', 'Origin');
22
+ }
23
+ if (opts.credentials) {
24
+ set('Access-Control-Allow-Credentials', 'true');
25
+ }
26
+ if (opts.exposeHeaders.length) {
27
+ set('Access-Control-Expose-Headers', opts.exposeHeaders.join(','));
28
+ }
29
+ if (c.req.method === 'OPTIONS') {
30
+ // Preflight
31
+ if (opts.maxAge != null) {
32
+ set('Access-Control-Max-Age', opts.maxAge.toString());
33
+ }
34
+ if (opts.allowMethods.length) {
35
+ set('Access-Control-Allow-Methods', opts.allowMethods.join(','));
36
+ }
37
+ let headers = opts.allowHeaders;
38
+ if (!headers.length) {
39
+ const requestHeaders = c.req.headers.get('Access-Control-Request-Headers');
40
+ if (requestHeaders) {
41
+ headers = requestHeaders.split(/\s*,\s*/);
42
+ }
43
+ }
44
+ if (headers.length) {
45
+ set('Access-Control-Allow-Headers', headers.join(','));
46
+ set('Vary', 'Access-Control-Request-Headers');
47
+ }
48
+ c.res = new Response(null, {
49
+ headers: c.res.headers,
50
+ status: 204,
51
+ statusText: c.res.statusText,
52
+ });
53
+ }
54
+ };
55
+ };
56
+ exports.cors = cors;
@@ -0,0 +1,2 @@
1
+ import type { Context } from '../context';
2
+ export declare const defaultMiddleware: (c: Context, next: Function) => Promise<void>;
@@ -0,0 +1,28 @@
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
+ /*
12
+ TODO:
13
+ Adding Content-Length header make it more slower.
14
+ This should not be default middleware...
15
+ if (c.res.body) {
16
+ // Do not clone Response, ex: c.res.clone().arrayBuffer()
17
+ const response = new Response(c.res.body, {
18
+ status: c.res.status,
19
+ statusText: c.res.statusText,
20
+ headers: c.res.headers,
21
+ })
22
+ c.res = response
23
+ const buff = await c.res.clone().arrayBuffer()
24
+ c.res.headers.append('Content-Length', buff.byteLength.toString())
25
+ }
26
+ */
27
+ };
28
+ exports.defaultMiddleware = defaultMiddleware;
@@ -1,5 +1,5 @@
1
1
  export declare class Middleware {
2
- static defaultFilter: (c: import("./context").Context, next: Function) => Promise<void>;
2
+ static default: (c: import("./context").Context, next: Function) => Promise<void>;
3
3
  static poweredBy: () => (c: import("./context").Context, next: Function) => Promise<void>;
4
4
  static logger: (fn?: {
5
5
  (...data: any[]): void;
@@ -11,4 +11,13 @@ export declare class Middleware {
11
11
  password: string;
12
12
  realm?: string;
13
13
  }) => (ctx: import("./context").Context, next: Function) => Promise<any>;
14
+ static bodyParse: () => (ctx: import("./context").Context, next: Function) => Promise<void>;
15
+ static cors: (options?: {
16
+ origin: string;
17
+ allowMethods?: string[];
18
+ allowHeaders?: string[];
19
+ maxAge?: number;
20
+ credentials?: boolean;
21
+ exposeHeaders?: string[];
22
+ }) => (c: import("./context").Context, next: Function) => Promise<void>;
14
23
  }
@@ -1,14 +1,18 @@
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
7
  const basic_auth_1 = require("./middleware/basic-auth/basic-auth");
8
+ const body_parse_1 = require("./middleware/body-parse/body-parse");
9
+ const cors_1 = require("./middleware/cors/cors");
8
10
  class Middleware {
9
11
  }
10
12
  exports.Middleware = Middleware;
11
- Middleware.defaultFilter = defaultFilter_1.defaultFilter;
12
- Middleware.poweredBy = poweredBy_1.poweredBy;
13
+ Middleware.default = default_1.defaultMiddleware;
14
+ Middleware.poweredBy = powered_by_1.poweredBy;
13
15
  Middleware.logger = logger_1.logger;
14
16
  Middleware.basicAuth = basic_auth_1.basicAuth;
17
+ Middleware.bodyParse = body_parse_1.bodyParse;
18
+ Middleware.cors = cors_1.cors;
package/dist/node.js CHANGED
@@ -46,7 +46,8 @@ class Node {
46
46
  for (let i = 0, len = parts.length; i < len; i++) {
47
47
  const p = parts[i];
48
48
  // '*' => match any path
49
- if (curNode.children['*']) {
49
+ // /api/* => default wildcard match
50
+ if (curNode.children['*'] && !curNode.children[p]) {
50
51
  const astNode = curNode.children['*'];
51
52
  if (Object.keys(astNode.children).length === 0) {
52
53
  curNode = astNode;
package/dist/util.d.ts CHANGED
@@ -1,4 +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;
4
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.timingSafeEqual = 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,13 +28,21 @@ 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;
37
46
  const bufferEqual = (a, b) => {
38
47
  if (a === b) {
39
48
  return true;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "hono",
3
- "version": "0.0.12",
4
- "description": "Tiny web framework for Cloudflare Workers and others.",
3
+ "version": "0.0.16",
4
+ "description": "[炎] Ultrafast web framework for Cloudflare Workers.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "files": [
@@ -37,8 +37,8 @@
37
37
  "@cloudflare/workers-types": "^3.3.0",
38
38
  "@types/jest": "^27.4.0",
39
39
  "@types/node": "^17.0.8",
40
- "@types/service-worker-mock": "^2.0.1",
41
40
  "@typescript-eslint/eslint-plugin": "^5.9.0",
41
+ "@typescript-eslint/parser": "^5.9.0",
42
42
  "eslint": "^7.26.0",
43
43
  "eslint-config-prettier": "^8.1.0",
44
44
  "eslint-define-config": "^1.2.1",
@@ -48,11 +48,11 @@
48
48
  "eslint-plugin-import": "^2.20.2",
49
49
  "eslint-plugin-node": "^11.1.0",
50
50
  "eslint-plugin-prettier": "^4.0.0",
51
+ "form-data": "^4.0.0",
51
52
  "jest": "^27.4.5",
53
+ "jest-environment-miniflare": "^2.0.0",
52
54
  "rimraf": "^3.0.2",
53
- "service-worker-mock": "^2.0.5",
54
55
  "ts-jest": "^27.1.2",
55
- "@typescript-eslint/parser": "^5.9.0",
56
56
  "typescript": "^4.5.4"
57
57
  },
58
58
  "engines": {
@@ -1,2 +0,0 @@
1
- import type { Context } from '../context';
2
- export declare const defaultFilter: (c: Context, next: Function) => Promise<void>;
@@ -1,12 +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
- // eslint-disable-next-line
7
- const url = new URL(c.req.url);
8
- return url.searchParams.get(key);
9
- };
10
- await next();
11
- };
12
- exports.defaultFilter = defaultFilter;