hono 0.0.13 → 0.1.0

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
@@ -13,7 +13,7 @@ app.fire()
13
13
 
14
14
  ## Features
15
15
 
16
- - **Ultra Fast** - the router is implemented with Trie-Tree structure.
16
+ - **Ultra fast** - the router is implemented with Trie-Tree structure.
17
17
  - **Zero dependencies** - using only Web standard API.
18
18
  - **Middleware** - builtin middleware, and you can make your own middleware.
19
19
  - **Optimized** - for Cloudflare Workers.
@@ -70,12 +70,12 @@ Instance of `Hono` has these methods:
70
70
 
71
71
  ```js
72
72
  // HTTP Methods
73
- app.get('/', () => new Response('GET /'))
74
- app.post('/', () => new Response('POST /'))
73
+ app.get('/', (c) => c.text('GET /'))
74
+ app.post('/', (c) => c.text('POST /'))
75
75
 
76
76
  // Wildcard
77
- app.get('/wild/*/card', () => {
78
- return new Response('GET /wild/*/card')
77
+ app.get('/wild/*/card', (c) => {
78
+ return c.text('GET /wild/*/card')
79
79
  })
80
80
  ```
81
81
 
@@ -83,7 +83,7 @@ app.get('/wild/*/card', () => {
83
83
 
84
84
  ```js
85
85
  // Any HTTP methods
86
- app.all('/hello', () => new Response('ALL Method /hello'))
86
+ app.all('/hello', (c) => c.text('Any Method /hello'))
87
87
  ```
88
88
 
89
89
  ### Named Parameter
@@ -117,9 +117,9 @@ app
117
117
  ## async/await
118
118
 
119
119
  ```js
120
- app.get('/fetch-url', async () => {
120
+ app.get('/fetch-url', async (c) => {
121
121
  const response = await fetch('https://example.com/')
122
- return new Response(`Status is ${response.status}`)
122
+ return c.text(`Status is ${response.status}`)
123
123
  })
124
124
  ```
125
125
 
@@ -162,7 +162,7 @@ app.use('/message/*', async (c, next) => {
162
162
  await c.res.headers.add('x-message', 'This is middleware!')
163
163
  })
164
164
 
165
- app.get('/message/hello', () => 'Hello Middleware!')
165
+ app.get('/message/hello', (c) => c.text('Hello Middleware!'))
166
166
  ```
167
167
 
168
168
  ### Custom 404 Response
@@ -178,6 +178,19 @@ app.use('*', async (c, next) => {
178
178
  })
179
179
  ```
180
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
+
181
194
  ### Complex Pattern
182
195
 
183
196
  You can also do this:
@@ -239,7 +252,7 @@ app.use('/', (c, next) => {
239
252
  ### c.event
240
253
 
241
254
  ```js
242
- // FetchEvent objest
255
+ // FetchEvent object
243
256
  app.use('*', async (c, next) => {
244
257
  c.event.waitUntil(
245
258
  ...
@@ -339,7 +352,7 @@ Make npm skeleton directory.
339
352
 
340
353
  ```sh
341
354
  mkdir hono-example
342
- ch hono-example
355
+ cd hono-example
343
356
  npm init -y
344
357
  ```
345
358
 
package/dist/context.js CHANGED
@@ -1,7 +1,7 @@
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
+ const url_1 = require("./utils/url");
5
5
  class Context {
6
6
  constructor(req, opts) {
7
7
  this.req = req;
@@ -49,7 +49,7 @@ class Context {
49
49
  if (typeof location !== 'string') {
50
50
  throw new TypeError('location must be a string!');
51
51
  }
52
- if (!(0, util_1.isAbsoluteURL)(location)) {
52
+ if (!(0, url_1.isAbsoluteURL)(location)) {
53
53
  const url = new URL(this.req.url);
54
54
  url.pathname = location;
55
55
  location = url.toString();
package/dist/hono.d.ts CHANGED
@@ -39,5 +39,6 @@ export declare class Hono {
39
39
  handleEvent(event: FetchEvent): Promise<Response>;
40
40
  fetch(request: Request, env?: Env, event?: FetchEvent): Promise<Response>;
41
41
  fire(): void;
42
+ onError(err: any): Response;
42
43
  notFound(): Response;
43
44
  }
package/dist/hono.js CHANGED
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Hono = exports.Router = void 0;
4
4
  const node_1 = require("./node");
5
5
  const compose_1 = require("./compose");
6
- const util_1 = require("./util");
6
+ const url_1 = require("./utils/url");
7
7
  const middleware_1 = require("./middleware");
8
8
  const context_1 = require("./context");
9
9
  const METHOD_NAME_OF_ALL = 'ALL';
@@ -94,7 +94,7 @@ class Hono {
94
94
  return this.router.match(method, path);
95
95
  }
96
96
  async dispatch(request, env, event) {
97
- const [method, path] = [request.method, (0, util_1.getPathFromURL)(request.url)];
97
+ const [method, path] = [request.method, (0, url_1.getPathFromURL)(request.url)];
98
98
  const result = await this.matchRoute(method, path);
99
99
  request.params = (key) => {
100
100
  if (result) {
@@ -122,16 +122,24 @@ class Hono {
122
122
  return c.res;
123
123
  }
124
124
  async handleEvent(event) {
125
- return this.dispatch(event.request, {}, event);
125
+ return this.dispatch(event.request, {}, event).catch((err) => {
126
+ return this.onError(err);
127
+ });
126
128
  }
127
129
  async fetch(request, env, event) {
128
- return this.dispatch(request, env, event);
130
+ return this.dispatch(request, env, event).catch((err) => {
131
+ return this.onError(err);
132
+ });
129
133
  }
130
134
  fire() {
131
135
  addEventListener('fetch', (event) => {
132
136
  event.respondWith(this.handleEvent(event));
133
137
  });
134
138
  }
139
+ onError(err) {
140
+ console.error(err);
141
+ return new Response('Internal Server Error', { status: 500 });
142
+ }
135
143
  notFound() {
136
144
  return new Response('Not Found', { status: 404 });
137
145
  }
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.basicAuth = void 0;
4
- const util_1 = require("../../util");
4
+ const buffer_1 = require("../../utils/buffer");
5
5
  const CREDENTIALS_REGEXP = /^ *(?:[Bb][Aa][Ss][Ii][Cc]) +([A-Za-z0-9._~+/-]+=*) *$/;
6
6
  const USER_PASS_REGEXP = /^([^:]*):(.*)$/;
7
7
  const auth = (req) => {
@@ -33,8 +33,8 @@ const basicAuth = (options) => {
33
33
  }
34
34
  return async (ctx, next) => {
35
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);
36
+ const usernameEqual = user && await (0, buffer_1.timingSafeEqual)(options.username, user.username);
37
+ const passwordEqual = user && await (0, buffer_1.timingSafeEqual)(options.password, user.password);
38
38
  if (!user || !usernameEqual || !passwordEqual) {
39
39
  ctx.res = new Response('Unauthorized', {
40
40
  status: 401,
@@ -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;
@@ -8,5 +8,16 @@ const defaultMiddleware = async (c, next) => {
8
8
  return url.searchParams.get(key);
9
9
  };
10
10
  await next();
11
+ if (c.res.body) {
12
+ // Do not clone Response, ex: c.res.clone().arrayBuffer()
13
+ const buffer = await c.res.arrayBuffer();
14
+ const res = new Response(buffer, {
15
+ status: c.res.status,
16
+ statusText: c.res.statusText,
17
+ headers: c.res.headers,
18
+ });
19
+ res.headers.append('Content-Length', buffer.byteLength.toString());
20
+ c.res = res;
21
+ }
11
22
  };
12
23
  exports.defaultMiddleware = defaultMiddleware;
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.logger = void 0;
4
- const util_1 = require("../../util");
4
+ const url_1 = require("../../utils/url");
5
5
  const humanize = (n, opts) => {
6
6
  const options = opts || {};
7
7
  const d = options.delimiter || ',';
@@ -33,16 +33,16 @@ const colorStatus = (status = 0) => {
33
33
  };
34
34
  return out[(status / 100) | 0];
35
35
  };
36
- function log(fn, prefix, method, path, status, elasped) {
36
+ function log(fn, prefix, method, path, status, elasped, contentLength) {
37
37
  const out = prefix === LogPrefix.Incoming
38
38
  ? ` ${prefix} ${method} ${path}`
39
- : ` ${prefix} ${method} ${path} ${colorStatus(status)} ${elasped}`;
39
+ : ` ${prefix} ${method} ${path} ${colorStatus(status)} ${elasped} ${contentLength}`;
40
40
  fn(out);
41
41
  }
42
42
  const logger = (fn = console.log) => {
43
43
  return async (c, next) => {
44
44
  const { method } = c.req;
45
- const path = (0, util_1.getPathFromURL)(c.req.url);
45
+ const path = (0, url_1.getPathFromURL)(c.req.url);
46
46
  log(fn, LogPrefix.Incoming, method, path);
47
47
  const start = Date.now();
48
48
  try {
@@ -52,7 +52,13 @@ const logger = (fn = console.log) => {
52
52
  log(fn, LogPrefix.Error, method, path, c.res.status || 500, time(start));
53
53
  throw e;
54
54
  }
55
- log(fn, LogPrefix.Outgoing, method, path, c.res.status, time(start));
55
+ const len = parseFloat(c.res.headers.get('Content-Length'));
56
+ const contentLength = isNaN(len)
57
+ ? '0'
58
+ : len < 1024
59
+ ? `${len}b`
60
+ : `${len / 1024}kB`;
61
+ log(fn, LogPrefix.Outgoing, method, path, c.res.status, time(start), contentLength);
56
62
  };
57
63
  };
58
64
  exports.logger = logger;
@@ -12,4 +12,12 @@ export declare class Middleware {
12
12
  realm?: string;
13
13
  }) => (ctx: import("./context").Context, next: Function) => Promise<any>;
14
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>;
15
23
  }
@@ -6,6 +6,7 @@ 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
8
  const body_parse_1 = require("./middleware/body-parse/body-parse");
9
+ const cors_1 = require("./middleware/cors/cors");
9
10
  class Middleware {
10
11
  }
11
12
  exports.Middleware = Middleware;
@@ -14,3 +15,4 @@ Middleware.poweredBy = powered_by_1.poweredBy;
14
15
  Middleware.logger = logger_1.logger;
15
16
  Middleware.basicAuth = basic_auth_1.basicAuth;
16
17
  Middleware.bodyParse = body_parse_1.bodyParse;
18
+ Middleware.cors = cors_1.cors;
package/dist/node.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Node = exports.Result = void 0;
4
- const util_1 = require("./util");
4
+ const url_1 = require("./utils/url");
5
5
  const METHOD_NAME_OF_ALL = 'ALL';
6
6
  class Result {
7
7
  constructor(handler, params) {
@@ -25,7 +25,7 @@ class Node {
25
25
  insert(method, path, handler) {
26
26
  // eslint-disable-next-line @typescript-eslint/no-this-alias
27
27
  let curNode = this;
28
- const parts = (0, util_1.splitPath)(path);
28
+ const parts = (0, url_1.splitPath)(path);
29
29
  for (let i = 0, len = parts.length; i < len; i++) {
30
30
  const p = parts[i];
31
31
  if (Object.keys(curNode.children).includes(p)) {
@@ -42,11 +42,12 @@ class Node {
42
42
  // eslint-disable-next-line @typescript-eslint/no-this-alias
43
43
  let curNode = this;
44
44
  const params = {};
45
- const parts = (0, util_1.splitPath)(path);
45
+ const parts = (0, url_1.splitPath)(path);
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;
@@ -73,7 +74,7 @@ class Node {
73
74
  isWildcard = true;
74
75
  break;
75
76
  }
76
- const pattern = (0, util_1.getPattern)(key);
77
+ const pattern = (0, url_1.getPattern)(key);
77
78
  // Named match
78
79
  if (pattern) {
79
80
  const match = p.match(new RegExp(pattern[1]));
@@ -0,0 +1,2 @@
1
+ export declare const equal: (a: ArrayBuffer, b: ArrayBuffer) => boolean;
2
+ export declare const timingSafeEqual: (a: any, b: any) => Promise<boolean>;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.timingSafeEqual = exports.equal = void 0;
4
+ const equal = (a, b) => {
5
+ if (a === b) {
6
+ return true;
7
+ }
8
+ if (a.byteLength !== b.byteLength) {
9
+ return false;
10
+ }
11
+ const va = new DataView(a);
12
+ const vb = new DataView(b);
13
+ let i = va.byteLength;
14
+ while (i--) {
15
+ if (va.getUint8(i) !== vb.getUint8(i)) {
16
+ return false;
17
+ }
18
+ }
19
+ return true;
20
+ };
21
+ exports.equal = equal;
22
+ const timingSafeEqual = async (a, b) => {
23
+ const sa = await crypto.subtle.digest({
24
+ name: 'SHA-256',
25
+ }, new TextEncoder().encode(String(a)));
26
+ const sb = await crypto.subtle.digest({
27
+ name: 'SHA-256',
28
+ }, new TextEncoder().encode(String(b)));
29
+ return (0, exports.equal)(sa, sb) && a === b;
30
+ };
31
+ exports.timingSafeEqual = timingSafeEqual;
@@ -2,4 +2,3 @@ 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
4
  export declare const isAbsoluteURL: (url: string) => boolean;
5
- export declare const timingSafeEqual: (a: any, b: any) => Promise<boolean>;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.timingSafeEqual = exports.isAbsoluteURL = exports.getPathFromURL = exports.getPattern = exports.splitPath = void 0;
3
+ exports.isAbsoluteURL = exports.getPathFromURL = exports.getPattern = exports.splitPath = void 0;
4
4
  const URL_REGEXP = /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;
5
5
  const splitPath = (path) => {
6
6
  const paths = path.split(/\//); // faster than path.split('/')
@@ -27,7 +27,6 @@ const getPattern = (label) => {
27
27
  };
28
28
  exports.getPattern = getPattern;
29
29
  const getPathFromURL = (url) => {
30
- // XXX
31
30
  const match = url.match(URL_REGEXP);
32
31
  if (match) {
33
32
  return match[5];
@@ -43,30 +42,3 @@ const isAbsoluteURL = (url) => {
43
42
  return false;
44
43
  };
45
44
  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.13",
4
- "description": "Ultrafast web framework for Cloudflare Workers.",
3
+ "version": "0.1.0",
4
+ "description": "[炎] Ultrafast web framework for Cloudflare Workers.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "files": [