hono 1.4.1 → 1.4.4

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
@@ -38,13 +38,13 @@ app.fire()
38
38
  **Hono is fastest**, compared to other routers for Cloudflare Workers.
39
39
 
40
40
  ```plain
41
- hono - trie-router(default) x 725,892 ops/sec ±4.02% (74 runs sampled)
42
- hono - regexp-router x 733,494 ops/sec ±3.00% (67 runs sampled)
43
- itty-router x 167,167 ops/sec ±1.25% (91 runs sampled)
44
- sunder x 327,697 ops/sec ±2.45% (92 runs sampled)
45
- worktop x 216,468 ops/sec ±3.01% (85 runs sampled)
46
- Fastest is hono - regexp-router,hono - trie-router(default)
47
- ✨ Done in 69.57s.
41
+ hono - trie-router(default) x 389,510 ops/sec ±3.16% (85 runs sampled)
42
+ hono - regexp-router x 452,290 ops/sec ±2.64% (84 runs sampled)
43
+ itty-router x 206,013 ops/sec ±3.39% (90 runs sampled)
44
+ sunder x 323,131 ops/sec ±0.75% (97 runs sampled)
45
+ worktop x 191,218 ops/sec ±2.70% (91 runs sampled)
46
+ Fastest is hono - regexp-router
47
+ ✨ Done in 43.56s.
48
48
  ```
49
49
 
50
50
  ## Why so fast?
@@ -98,7 +98,7 @@ import { basicAuth } from 'hono/basic-auth'
98
98
 
99
99
  const v1 = new Hono()
100
100
  v1.get('/posts', (c) => {
101
- return c.text('list pots')
101
+ return c.text('list posts')
102
102
  })
103
103
  .post(basicAuth({ username, password }), (c) => {
104
104
  return c.text('created!', 201)
@@ -644,7 +644,7 @@ npx wrangler publish ./src/index.ts
644
644
 
645
645
  You can start making your Cloudflare Workers application with [the starter template](https://github.com/honojs/hono-minimal). It is really minimal using TypeScript, esbuild, Miniflare, and Jest.
646
646
 
647
- To generate a project skelton, run this command.
647
+ To generate a project skeleton, run this command.
648
648
 
649
649
  ```
650
650
  npx create-cloudflare my-app https://github.com/honojs/hono-minimal
package/dist/compose.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  import type { ErrorHandler, NotFoundHandler } from './hono';
2
- export declare const compose: <C>(middleware: Function[], onError?: ErrorHandler, onNotFound?: NotFoundHandler) => (context: C, next?: Function) => Promise<C>;
2
+ export declare const compose: <C>(middleware: Function[], onError?: ErrorHandler<import("./context").Env> | undefined, onNotFound?: NotFoundHandler<import("./context").Env> | undefined) => (context: C, next?: Function | undefined) => Promise<C>;
package/dist/compose.js CHANGED
@@ -13,29 +13,26 @@ const compose = (middleware, onError, onNotFound) => {
13
13
  }
14
14
  let handler = middleware[i];
15
15
  index = i;
16
- if (i === middleware.length)
16
+ if (i === middleware.length && next)
17
17
  handler = next;
18
- if (handler === undefined) {
19
- if (context instanceof context_1.Context && context.res.finalized === false) {
18
+ if (!handler) {
19
+ if (context instanceof context_1.Context && context.finalized === false && onNotFound) {
20
20
  context.res = onNotFound(context);
21
- context.res.finalized = true;
22
21
  }
23
22
  return Promise.resolve(context);
24
23
  }
25
- return Promise.resolve(handler(context, dispatch.bind(null, i + 1)))
24
+ return Promise.resolve(handler(context, () => dispatch(i + 1)))
26
25
  .then(async (res) => {
27
26
  // If handler return Response like `return c.text('foo')`
28
27
  if (res && context instanceof context_1.Context) {
29
28
  context.res = res;
30
- context.res.finalized = true;
31
29
  }
32
30
  return context;
33
31
  })
34
32
  .catch((err) => {
35
- if (onError && context instanceof context_1.Context) {
33
+ if (context instanceof context_1.Context && onError) {
36
34
  if (err instanceof Error) {
37
35
  context.res = onError(err, context);
38
- context.res.finalized = true;
39
36
  }
40
37
  return context;
41
38
  }
package/dist/context.d.ts CHANGED
@@ -1,35 +1,35 @@
1
1
  /// <reference types="@cloudflare/workers-types" />
2
2
  import type { StatusCode } from './utils/http-status';
3
3
  declare type Headers = Record<string, string>;
4
- declare type Data = string | ArrayBuffer | ReadableStream;
4
+ export declare type Data = string | ArrayBuffer | ReadableStream;
5
5
  export declare type Env = Record<string, any>;
6
6
  export declare class Context<RequestParamKeyType extends string = string, E = Env> {
7
7
  req: Request<RequestParamKeyType>;
8
- res: Response;
9
8
  env: E;
10
- event: FetchEvent;
9
+ event: FetchEvent | undefined;
10
+ finalized: boolean;
11
11
  private _status;
12
12
  private _pretty;
13
13
  private _prettySpace;
14
14
  private _map;
15
+ private _headers;
16
+ private _res;
17
+ private notFoundHandler;
15
18
  render: (template: string, params?: object, options?: object) => Promise<Response>;
16
- notFound: () => Response | Promise<Response>;
17
- constructor(req: Request<RequestParamKeyType>, opts?: {
18
- env: E;
19
- event: FetchEvent;
20
- res?: Response;
21
- });
22
- private initRequest;
19
+ constructor(req: Request<RequestParamKeyType>, env?: E | undefined, event?: FetchEvent | undefined, notFoundHandler?: (c: Context<string, E>) => Response);
20
+ get res(): Response;
21
+ set res(_res: Response);
23
22
  header(name: string, value: string): void;
24
23
  status(status: StatusCode): void;
25
24
  set(key: string, value: any): void;
26
25
  get(key: string): any;
27
26
  pretty(prettyJSON: boolean, space?: number): void;
28
- newResponse(data: Data, init?: ResponseInit): Response;
27
+ newResponse(data: Data | null, status: StatusCode, headers?: Headers): Response;
29
28
  body(data: Data | null, status?: StatusCode, headers?: Headers): Response;
30
29
  text(text: string, status?: StatusCode, headers?: Headers): Response;
31
30
  json(object: object, status?: StatusCode, headers?: Headers): Response;
32
31
  html(html: string, status?: StatusCode, headers?: Headers): Response;
33
32
  redirect(location: string, status?: StatusCode): Response;
33
+ notFound(): Response | Promise<Response>;
34
34
  }
35
35
  export {};
package/dist/context.js CHANGED
@@ -3,103 +3,69 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Context = void 0;
4
4
  const url_1 = require("./utils/url");
5
5
  class Context {
6
- constructor(req, opts) {
6
+ constructor(req, env = undefined, event = undefined, notFoundHandler = () => new Response()) {
7
7
  this._status = 200;
8
8
  this._pretty = false;
9
9
  this._prettySpace = 2;
10
- this.req = this.initRequest(req);
11
- this._map = {};
12
- Object.assign(this, opts);
13
- if (!this.res) {
14
- const res = new Response(null, { status: 404 });
15
- res.finalized = false;
16
- this.res = res;
10
+ this.req = req;
11
+ if (env) {
12
+ this.env = env;
17
13
  }
14
+ this.event = event;
15
+ this.notFoundHandler = notFoundHandler;
16
+ this.finalized = false;
18
17
  }
19
- initRequest(req) {
20
- req.header = ((name) => {
21
- if (name) {
22
- return req.headers.get(name);
23
- }
24
- else {
25
- const result = {};
26
- for (const [key, value] of req.headers) {
27
- result[key] = value;
28
- }
29
- return result;
30
- }
31
- });
32
- req.query = ((key) => {
33
- const url = new URL(req.url);
34
- if (key) {
35
- return url.searchParams.get(key);
36
- }
37
- else {
38
- const result = {};
39
- for (const key of url.searchParams.keys()) {
40
- result[key] = url.searchParams.get(key);
41
- }
42
- return result;
43
- }
44
- });
45
- req.queries = ((key) => {
46
- const url = new URL(req.url);
47
- if (key) {
48
- return url.searchParams.getAll(key);
49
- }
50
- else {
51
- const result = {};
52
- for (const key of url.searchParams.keys()) {
53
- result[key] = url.searchParams.getAll(key);
54
- }
55
- return result;
56
- }
57
- });
58
- return req;
18
+ get res() {
19
+ return (this._res || (this._res = new Response()));
20
+ }
21
+ set res(_res) {
22
+ this._res = _res;
23
+ this.finalized = true;
59
24
  }
60
25
  header(name, value) {
61
- this.res.headers.set(name, value);
26
+ this._headers || (this._headers = {});
27
+ this._headers[name] = value;
28
+ if (this.finalized) {
29
+ this.res.headers.set(name, value);
30
+ }
62
31
  }
63
32
  status(status) {
64
33
  this._status = status;
65
34
  }
66
35
  set(key, value) {
36
+ this._map || (this._map = {});
67
37
  this._map[key] = value;
68
38
  }
69
39
  get(key) {
40
+ if (!this._map) {
41
+ return undefined;
42
+ }
70
43
  return this._map[key];
71
44
  }
72
45
  pretty(prettyJSON, space = 2) {
73
46
  this._pretty = prettyJSON;
74
47
  this._prettySpace = space;
75
48
  }
76
- newResponse(data, init = {}) {
77
- init.status = init.status || this._status || 200;
78
- let headers = {};
79
- this.res.headers.forEach((v, k) => {
80
- headers[k] = v;
49
+ newResponse(data, status, headers = {}) {
50
+ const _headers = { ...this._headers, ...headers };
51
+ if (this._res) {
52
+ this._res.headers.forEach((v, k) => {
53
+ _headers[k] = v;
54
+ });
55
+ }
56
+ return new Response(data, {
57
+ status: status || this._status || 200,
58
+ headers: _headers,
81
59
  });
82
- init.headers = Object.assign(headers, init.headers);
83
- headers = {};
84
- return new Response(data, init);
85
60
  }
86
61
  body(data, status = this._status, headers = {}) {
87
- return this.newResponse(data, {
88
- status: status,
89
- headers: headers,
90
- });
62
+ return this.newResponse(data, status, headers);
91
63
  }
92
64
  text(text, status = this._status, headers = {}) {
93
- if (typeof text !== 'string') {
94
- throw new TypeError('text method arg must be a string!');
95
- }
96
65
  headers['Content-Type'] || (headers['Content-Type'] = 'text/plain; charset=UTF-8');
97
66
  return this.body(text, status, headers);
98
67
  }
99
68
  json(object, status = this._status, headers = {}) {
100
- if (typeof object !== 'object') {
101
- throw new TypeError('json method arg must be an object!');
102
- }
103
69
  const body = this._pretty
104
70
  ? JSON.stringify(object, null, this._prettySpace)
105
71
  : JSON.stringify(object);
@@ -107,27 +73,21 @@ class Context {
107
73
  return this.body(body, status, headers);
108
74
  }
109
75
  html(html, status = this._status, headers = {}) {
110
- if (typeof html !== 'string') {
111
- throw new TypeError('html method arg must be a string!');
112
- }
113
76
  headers['Content-Type'] || (headers['Content-Type'] = 'text/html; charset=UTF-8');
114
77
  return this.body(html, status, headers);
115
78
  }
116
79
  redirect(location, status = 302) {
117
- if (typeof location !== 'string') {
118
- throw new TypeError('location must be a string!');
119
- }
120
80
  if (!(0, url_1.isAbsoluteURL)(location)) {
121
81
  const url = new URL(this.req.url);
122
82
  url.pathname = location;
123
83
  location = url.toString();
124
84
  }
125
- return this.newResponse(null, {
126
- status: status,
127
- headers: {
128
- Location: location,
129
- },
85
+ return this.newResponse(null, status, {
86
+ Location: location,
130
87
  });
131
88
  }
89
+ notFound() {
90
+ return this.notFoundHandler(this);
91
+ }
132
92
  }
133
93
  exports.Context = Context;
package/dist/hono.d.ts CHANGED
@@ -2,37 +2,14 @@
2
2
  import { Context } from './context';
3
3
  import type { Env } from './context';
4
4
  import type { Router } from './router';
5
- declare global {
6
- interface Request<ParamKeyType extends string = string> {
7
- param: {
8
- (key: ParamKeyType): string;
9
- (): Record<ParamKeyType, string>;
10
- };
11
- query: {
12
- (key: string): string;
13
- (): Record<string, string>;
14
- };
15
- queries: {
16
- (key: string): string[];
17
- (): Record<string, string[]>;
18
- };
19
- header: {
20
- (name: string): string;
21
- (): Record<string, string>;
22
- };
23
- }
24
- interface Response {
25
- finalized: boolean;
26
- }
27
- }
28
- export declare type Handler<RequestParamKeyType extends string = string, E = Env> = (c: Context<RequestParamKeyType, E>, next: Next) => Response | Promise<Response> | void | Promise<void>;
5
+ export declare type Handler<RequestParamKeyType extends string = string, E = Env> = (c: Context<RequestParamKeyType, E>, next: Next) => Response | Promise<Response> | Promise<void> | Promise<Response | undefined>;
29
6
  export declare type NotFoundHandler<E = Env> = (c: Context<string, E>) => Response;
30
7
  export declare type ErrorHandler<E = Env> = (err: Error, c: Context<string, E>) => Response;
31
8
  export declare type Next = () => Promise<void>;
32
9
  declare type ParamKeyName<NameWithPattern> = NameWithPattern extends `${infer Name}{${infer _Pattern}` ? Name : NameWithPattern;
33
10
  declare type ParamKey<Component> = Component extends `:${infer NameWithPattern}` ? ParamKeyName<NameWithPattern> : never;
34
11
  declare type ParamKeys<Path> = Path extends `${infer Component}/${infer Rest}` ? ParamKey<Component> | ParamKeys<Rest> : ParamKey<Path>;
35
- interface HandlerInterface<T extends string, E = Env, U = Hono<E, T>> {
12
+ interface HandlerInterface<T extends string, E extends Env = Env, U = Hono<E, T>> {
36
13
  <Path extends string>(path: Path, ...handlers: Handler<ParamKeys<Path> extends never ? string : ParamKeys<Path>, E>[]): U;
37
14
  (path: string, ...handlers: Handler<string, E>[]): U;
38
15
  <Path extends string>(...handlers: Handler<ParamKeys<Path> extends never ? string : ParamKeys<Path>, E>[]): U;
@@ -44,21 +21,20 @@ interface Route<E extends Env> {
44
21
  handler: Handler<string, E>;
45
22
  }
46
23
  declare const Hono_base: new <E_1 extends Env, T extends string, U>() => {
47
- delete: HandlerInterface<T, E_1, U>;
48
- get: HandlerInterface<T, E_1, U>;
49
24
  all: HandlerInterface<T, E_1, U>;
25
+ get: HandlerInterface<T, E_1, U>;
50
26
  post: HandlerInterface<T, E_1, U>;
51
27
  put: HandlerInterface<T, E_1, U>;
28
+ delete: HandlerInterface<T, E_1, U>;
52
29
  head: HandlerInterface<T, E_1, U>;
53
30
  options: HandlerInterface<T, E_1, U>;
54
31
  patch: HandlerInterface<T, E_1, U>;
55
32
  };
56
- export declare class Hono<E = Env, P extends string = '/'> extends Hono_base<E, P, Hono<E, P>> {
33
+ export declare class Hono<E extends Env = Env, P extends string = '/'> extends Hono_base<E, P, Hono<E, P>> {
57
34
  readonly router: Router<Handler<string, E>>;
58
35
  readonly strict: boolean;
59
36
  private _tempPath;
60
37
  private path;
61
- private _cachedResponse;
62
38
  routes: Route<E>[];
63
39
  constructor(init?: Partial<Pick<Hono, 'router' | 'strict'>>);
64
40
  private notFoundHandler;
package/dist/hono.js CHANGED
@@ -3,8 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Hono = void 0;
4
4
  const compose_1 = require("./compose");
5
5
  const context_1 = require("./context");
6
+ const request_1 = require("./request");
6
7
  const router_1 = require("./router");
7
- const router_2 = require("./router");
8
8
  const trie_router_1 = require("./router/trie-router"); // Default Router
9
9
  const url_1 = require("./utils/url");
10
10
  const methods = ['get', 'post', 'put', 'delete', 'head', 'options', 'patch'];
@@ -17,6 +17,7 @@ class Hono extends defineDynamicClass() {
17
17
  super();
18
18
  this.router = new trie_router_1.TrieRouter();
19
19
  this.strict = true; // strict routing - default is true
20
+ this._tempPath = '';
20
21
  this.path = '/';
21
22
  this.routes = [];
22
23
  this.notFoundHandler = (c) => {
@@ -28,7 +29,8 @@ class Hono extends defineDynamicClass() {
28
29
  const message = 'Internal Server Error';
29
30
  return c.text(message, 500);
30
31
  };
31
- const allMethods = [...methods, router_2.METHOD_NAME_ALL_LOWERCASE];
32
+ (0, request_1.extendRequestPrototype)(); // FIXME: should be executed at a better timing
33
+ const allMethods = [...methods, router_1.METHOD_NAME_ALL_LOWERCASE];
32
34
  allMethods.map((method) => {
33
35
  this[method] = (args1, ...args) => {
34
36
  if (typeof args1 === 'string') {
@@ -46,9 +48,6 @@ class Hono extends defineDynamicClass() {
46
48
  };
47
49
  });
48
50
  Object.assign(this, init);
49
- this._tempPath = null;
50
- this._cachedResponse = new Response(null, { status: 404 });
51
- this._cachedResponse.finalized = false;
52
51
  }
53
52
  route(path, app) {
54
53
  this._tempPath = path;
@@ -56,7 +55,7 @@ class Hono extends defineDynamicClass() {
56
55
  app.routes.map((r) => {
57
56
  this.addRoute(r.method, r.path, r.handler);
58
57
  });
59
- this._tempPath = null;
58
+ this._tempPath = '';
60
59
  }
61
60
  return this;
62
61
  }
@@ -89,30 +88,16 @@ class Hono extends defineDynamicClass() {
89
88
  const r = { path: path, method: method, handler: handler };
90
89
  this.routes.push(r);
91
90
  }
92
- async matchRoute(method, path) {
91
+ matchRoute(method, path) {
93
92
  return this.router.match(method, path);
94
93
  }
95
94
  async dispatch(request, event, env) {
96
- const path = (0, url_1.getPathFromURL)(request.url, { strict: this.strict });
95
+ const path = (0, url_1.getPathFromURL)(request.url, this.strict);
97
96
  const method = request.method;
98
- const result = await this.matchRoute(method, path);
99
- request.param = ((key) => {
100
- if (result) {
101
- if (key) {
102
- return result.params[key];
103
- }
104
- else {
105
- return result.params;
106
- }
107
- }
108
- });
97
+ const result = this.matchRoute(method, path);
98
+ request.paramData = result?.params;
109
99
  const handlers = result ? result.handlers : [this.notFoundHandler];
110
- const c = new context_1.Context(request, {
111
- env: env,
112
- event: event,
113
- res: this._cachedResponse,
114
- });
115
- c.notFound = () => this.notFoundHandler(c);
100
+ const c = new context_1.Context(request, env, event, this.notFoundHandler);
116
101
  const composed = (0, compose_1.compose)(handlers, this.errorHandler, this.notFoundHandler);
117
102
  let context;
118
103
  try {
@@ -122,9 +107,8 @@ class Hono extends defineDynamicClass() {
122
107
  if (err instanceof Error) {
123
108
  return this.errorHandler(err, c);
124
109
  }
110
+ throw err;
125
111
  }
126
- if (!context.res)
127
- return context.notFound();
128
112
  return context.res;
129
113
  }
130
114
  async handleEvent(event) {
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ /// <reference path="request.d.ts" />
1
2
  export { Hono } from './hono';
2
3
  export type { Handler, Next } from './hono';
3
4
  export { Context } from './context';
package/dist/index.js CHANGED
@@ -1,4 +1,6 @@
1
1
  "use strict";
2
+ // eslint-disable-next-line @typescript-eslint/triple-slash-reference
3
+ /// <reference path="./request.ts" /> Import "declare global" for the Request interface.
2
4
  Object.defineProperty(exports, "__esModule", { value: true });
3
5
  exports.Context = exports.Hono = void 0;
4
6
  var hono_1 = require("./hono");
@@ -15,7 +15,7 @@ const auth = (req) => {
15
15
  if (!req.headers || typeof req.headers !== 'object') {
16
16
  throw new TypeError('argument req is required to have headers property');
17
17
  }
18
- const match = CREDENTIALS_REGEXP.exec(req.headers.get('Authorization'));
18
+ const match = CREDENTIALS_REGEXP.exec(req.headers.get('Authorization') || '');
19
19
  if (!match) {
20
20
  return undefined;
21
21
  }
@@ -49,7 +49,7 @@ const basicAuth = (options, ...users) => {
49
49
  ctx.res = new Response('Unauthorized', {
50
50
  status: 401,
51
51
  headers: {
52
- 'WWW-Authenticate': 'Basic realm="' + options.realm.replace(/"/g, '\\"') + '"',
52
+ 'WWW-Authenticate': 'Basic realm="' + options.realm?.replace(/"/g, '\\"') + '"',
53
53
  },
54
54
  });
55
55
  };
@@ -4,7 +4,7 @@ exports.cookie = void 0;
4
4
  const cookie = () => {
5
5
  return async (c, next) => {
6
6
  c.req.cookie = ((name) => {
7
- const cookie = c.req.headers.get('Cookie');
7
+ const cookie = c.req.headers.get('Cookie') || '';
8
8
  const obj = parse(cookie);
9
9
  if (name) {
10
10
  const value = obj[name];
@@ -8,5 +8,5 @@ declare type CORSOptions = {
8
8
  credentials?: boolean;
9
9
  exposeHeaders?: string[];
10
10
  };
11
- export declare const cors: (options?: CORSOptions) => (c: Context, next: Next) => Promise<void>;
11
+ export declare const cors: (options?: CORSOptions | undefined) => (c: Context, next: Next) => Promise<void>;
12
12
  export {};
@@ -8,7 +8,10 @@ const cors = (options) => {
8
8
  allowHeaders: [],
9
9
  exposeHeaders: [],
10
10
  };
11
- const opts = Object.assign(Object.assign({}, defaults), options);
11
+ const opts = {
12
+ ...defaults,
13
+ ...options,
14
+ };
12
15
  return async (c, next) => {
13
16
  await next();
14
17
  function set(key, value) {
@@ -23,7 +26,7 @@ const cors = (options) => {
23
26
  if (opts.credentials) {
24
27
  set('Access-Control-Allow-Credentials', 'true');
25
28
  }
26
- if (opts.exposeHeaders.length) {
29
+ if (opts.exposeHeaders?.length) {
27
30
  set('Access-Control-Expose-Headers', opts.exposeHeaders.join(','));
28
31
  }
29
32
  if (c.req.method === 'OPTIONS') {
@@ -31,17 +34,17 @@ const cors = (options) => {
31
34
  if (opts.maxAge != null) {
32
35
  set('Access-Control-Max-Age', opts.maxAge.toString());
33
36
  }
34
- if (opts.allowMethods.length) {
37
+ if (opts.allowMethods?.length) {
35
38
  set('Access-Control-Allow-Methods', opts.allowMethods.join(','));
36
39
  }
37
40
  let headers = opts.allowHeaders;
38
- if (!headers.length) {
41
+ if (!headers?.length) {
39
42
  const requestHeaders = c.req.headers.get('Access-Control-Request-Headers');
40
43
  if (requestHeaders) {
41
44
  headers = requestHeaders.split(/\s*,\s*/);
42
45
  }
43
46
  }
44
- if (headers.length) {
47
+ if (headers?.length) {
45
48
  set('Access-Control-Allow-Headers', headers.join(','));
46
49
  set('Vary', 'Access-Control-Request-Headers');
47
50
  }
@@ -7,8 +7,9 @@ const etag = (options = { weak: false }) => {
7
7
  return async (c, next) => {
8
8
  const ifNoneMatch = c.req.header('If-None-Match') || c.req.header('if-none-match');
9
9
  await next();
10
- const clone = c.res.clone();
11
- const body = await (0, body_1.parseBody)(c.res);
10
+ const res = c.res;
11
+ const clone = res.clone();
12
+ const body = await (0, body_1.parseBody)(res);
12
13
  const hash = await (0, crypto_1.sha1)(body);
13
14
  const etag = options.weak ? `W/"${hash}"` : `"${hash}"`;
14
15
  if (ifNoneMatch && ifNoneMatch === etag) {
@@ -18,7 +18,7 @@ export interface GraphQLParams {
18
18
  raw: boolean;
19
19
  }
20
20
  export declare const getGraphQLParams: (request: Request) => Promise<GraphQLParams>;
21
- export declare const errorMessages: (messages: string[], graphqlErrors?: readonly GraphQLError[] | readonly GraphQLFormattedError[]) => {
21
+ export declare const errorMessages: (messages: string[], graphqlErrors?: readonly GraphQLError[] | readonly GraphQLFormattedError[] | undefined) => {
22
22
  errors: readonly GraphQLError[] | readonly GraphQLFormattedError[];
23
23
  } | {
24
24
  errors: {
@@ -6,11 +6,10 @@ exports.errorMessages = exports.getGraphQLParams = exports.graphqlServer = void
6
6
  const graphql_1 = require("graphql");
7
7
  const parse_body_1 = require("./parse-body");
8
8
  const graphqlServer = (options) => {
9
- var _a, _b;
10
9
  const schema = options.schema;
11
10
  const rootValue = options.rootValue;
12
- const pretty = (_a = options.pretty) !== null && _a !== void 0 ? _a : false;
13
- const validationRules = (_b = options.validationRules) !== null && _b !== void 0 ? _b : [];
11
+ const pretty = options.pretty ?? false;
12
+ const validationRules = options.validationRules ?? [];
14
13
  // const showGraphiQL = options.graphiql ?? false
15
14
  return async (c, next) => {
16
15
  // GraphQL HTTP only supports GET and POST methods.
@@ -28,6 +27,7 @@ const graphqlServer = (options) => {
28
27
  console.error(`${e.stack || e.message}`);
29
28
  return c.json((0, exports.errorMessages)([e.message], [e]), 400);
30
29
  }
30
+ throw e;
31
31
  }
32
32
  const { query, variables, operationName } = params;
33
33
  if (query == null) {
@@ -51,6 +51,7 @@ const graphqlServer = (options) => {
51
51
  });
52
52
  return c.json((0, exports.errorMessages)(['GraphQL syntax error.'], [e]), 400);
53
53
  }
54
+ throw syntaxError;
54
55
  }
55
56
  // Validate AST, reporting any errors.
56
57
  const validationErrors = (0, graphql_1.validate)(schema, documentAST, [...graphql_1.specifiedRules, ...validationRules]);
@@ -94,9 +95,12 @@ const graphqlServer = (options) => {
94
95
  // Return 400: Bad Request if any execution context errors exist.
95
96
  return c.json((0, exports.errorMessages)(['GraphQL execution context error.'], [e]), 400);
96
97
  }
98
+ throw contextError;
97
99
  }
98
- if (result.data == null) {
99
- return c.json((0, exports.errorMessages)([result.errors.toString()], result.errors), 500);
100
+ if (!result.data) {
101
+ if (result.errors) {
102
+ return c.json((0, exports.errorMessages)([result.errors.toString()], result.errors), 500);
103
+ }
100
104
  }
101
105
  /*
102
106
  Now, does not support GraphiQL
@@ -117,21 +121,20 @@ const graphqlServer = (options) => {
117
121
  };
118
122
  exports.graphqlServer = graphqlServer;
119
123
  const getGraphQLParams = async (request) => {
120
- var _a, _b, _c;
121
124
  const urlData = new URLSearchParams(request.url.split('?')[1]);
122
125
  const bodyData = await (0, parse_body_1.parseBody)(request);
123
126
  // GraphQL Query string.
124
- let query = (_a = urlData.get('query')) !== null && _a !== void 0 ? _a : bodyData.query;
127
+ let query = urlData.get('query') ?? bodyData.query;
125
128
  if (typeof query !== 'string') {
126
129
  query = null;
127
130
  }
128
131
  // Parse the variables if needed.
129
- let variables = ((_b = urlData.get('variables')) !== null && _b !== void 0 ? _b : bodyData.variables);
132
+ let variables = (urlData.get('variables') ?? bodyData.variables);
130
133
  if (typeof variables === 'string') {
131
134
  try {
132
135
  variables = JSON.parse(variables);
133
136
  }
134
- catch (_d) {
137
+ catch {
135
138
  throw Error('Variables are invalid JSON.');
136
139
  }
137
140
  }
@@ -139,7 +142,7 @@ const getGraphQLParams = async (request) => {
139
142
  variables = null;
140
143
  }
141
144
  // Name of GraphQL operation to execute.
142
- let operationName = (_c = urlData.get('operationName')) !== null && _c !== void 0 ? _c : bodyData.operationName;
145
+ let operationName = urlData.get('operationName') ?? bodyData.operationName;
143
146
  if (typeof operationName !== 'string') {
144
147
  operationName = null;
145
148
  }
@@ -12,7 +12,7 @@ const jwt = (options) => {
12
12
  ctx.res = new Response('Unauthorized', {
13
13
  status: 401,
14
14
  headers: {
15
- 'WWW-Authenticate': 'Basic ${options.secret}',
15
+ 'WWW-Authenticate': `Bearer realm="${ctx.req.url}",error="invalid_request",error_description="no authorization included in request"`,
16
16
  },
17
17
  });
18
18
  return;
@@ -22,7 +22,7 @@ const jwt = (options) => {
22
22
  ctx.res = new Response('Unauthorized', {
23
23
  status: 401,
24
24
  headers: {
25
- 'WWW-Authenticate': 'Basic ${options.secret}',
25
+ 'WWW-Authenticate': `Bearer realm="${ctx.req.url}",error="invalid_request",error_description="no authorization included in request"`,
26
26
  },
27
27
  });
28
28
  return;
@@ -40,7 +40,7 @@ const jwt = (options) => {
40
40
  status: 401,
41
41
  statusText: msg,
42
42
  headers: {
43
- 'WWW-Authenticate': 'Bearer ${options.secret}',
43
+ 'WWW-Authenticate': `Bearer realm="${ctx.req.url}",error="invalid_token",error_description="token verification failure"`,
44
44
  },
45
45
  });
46
46
  return;
@@ -1,8 +1,9 @@
1
1
  /// <reference types="@cloudflare/workers-types" />
2
+ import type { Env } from '../../context';
2
3
  import type { Handler } from '../../hono';
3
4
  export declare type ServeStaticOptions = {
4
5
  root: string;
5
6
  manifest?: object | string;
6
7
  namespace?: KVNamespace;
7
8
  };
8
- export declare const serveStatic: (options?: ServeStaticOptions) => Handler;
9
+ export declare const serveStatic: (options?: ServeStaticOptions) => Handler<string, Env>;
@@ -8,7 +8,7 @@ const DEFAULT_DOCUMENT = 'index.html';
8
8
  const serveStatic = (options = { root: '' }) => {
9
9
  return async (c, next) => {
10
10
  // Do nothing if Response is already set
11
- if (c.res && c.res.finalized) {
11
+ if (c.res && c.finalized) {
12
12
  await next();
13
13
  }
14
14
  const url = new URL(c.req.url);
@@ -33,6 +33,7 @@ const serveStatic = (options = { root: '' }) => {
33
33
  console.warn(`Static file: ${path} is not found`);
34
34
  await next();
35
35
  }
36
+ return;
36
37
  };
37
38
  };
38
39
  exports.serveStatic = serveStatic;
@@ -0,0 +1,22 @@
1
+ declare global {
2
+ interface Request<ParamKeyType extends string = string> {
3
+ param: {
4
+ (key: ParamKeyType): string;
5
+ (): Record<ParamKeyType, string>;
6
+ };
7
+ paramData?: Record<ParamKeyType, string>;
8
+ query: {
9
+ (key: string): string;
10
+ (): Record<string, string>;
11
+ };
12
+ queries: {
13
+ (key: string): string[];
14
+ (): Record<string, string[]>;
15
+ };
16
+ header: {
17
+ (name: string): string;
18
+ (): Record<string, string>;
19
+ };
20
+ }
21
+ }
22
+ export declare function extendRequestPrototype(): void;
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extendRequestPrototype = void 0;
4
+ function extendRequestPrototype() {
5
+ if (!!Request.prototype.param) {
6
+ // already extended
7
+ return;
8
+ }
9
+ Request.prototype.param = function (key) {
10
+ if (this.paramData) {
11
+ if (key) {
12
+ return this.paramData[key];
13
+ }
14
+ else {
15
+ return this.paramData;
16
+ }
17
+ }
18
+ return null;
19
+ };
20
+ Request.prototype.header = function (name) {
21
+ if (name) {
22
+ return this.headers.get(name);
23
+ }
24
+ else {
25
+ const result = {};
26
+ for (const [key, value] of this.headers) {
27
+ result[key] = value;
28
+ }
29
+ return result;
30
+ }
31
+ };
32
+ Request.prototype.query = function (key) {
33
+ const url = new URL(this.url);
34
+ if (key) {
35
+ return url.searchParams.get(key);
36
+ }
37
+ else {
38
+ const result = {};
39
+ for (const key of url.searchParams.keys()) {
40
+ result[key] = url.searchParams.get(key) || '';
41
+ }
42
+ return result;
43
+ }
44
+ };
45
+ Request.prototype.queries = function (key) {
46
+ const url = new URL(this.url);
47
+ if (key) {
48
+ return url.searchParams.getAll(key);
49
+ }
50
+ else {
51
+ const result = {};
52
+ for (const key of url.searchParams.keys()) {
53
+ result[key] = url.searchParams.getAll(key);
54
+ }
55
+ return result;
56
+ }
57
+ };
58
+ }
59
+ exports.extendRequestPrototype = extendRequestPrototype;
@@ -240,6 +240,7 @@ class RegExpRouter {
240
240
  return this.match(method, path);
241
241
  }
242
242
  buildAllMatchers() {
243
+ // @ts-ignore
243
244
  this.routeData.routes.sort(({ hint: a }, { hint: b }) => {
244
245
  if (a.componentsLength !== b.componentsLength) {
245
246
  return a.componentsLength - b.componentsLength;
@@ -265,6 +266,7 @@ class RegExpRouter {
265
266
  const primaryMatchers = {};
266
267
  const secondaryMatchers = {};
267
268
  let hasAmbiguous = false;
269
+ // @ts-ignore
268
270
  this.routeData.methods.forEach((method) => {
269
271
  let _hasAmbiguous;
270
272
  [primaryMatchers[method], secondaryMatchers[method], _hasAmbiguous] =
@@ -280,6 +282,7 @@ class RegExpRouter {
280
282
  var _a, _b;
281
283
  let hasAmbiguous = false;
282
284
  const targetMethods = new Set([method, router_1.METHOD_NAME_ALL]);
285
+ // @ts-ignore
283
286
  const routes = this.routeData.routes.filter(({ method }) => targetMethods.has(method));
284
287
  // Reset temporary data per method
285
288
  for (let i = 0, len = routes.length; i < len; i++) {
@@ -15,6 +15,8 @@ class Trie {
15
15
  * - character
16
16
  */
17
17
  const tokens = path.match(/(?::[^\/]+)|(?:\/\*$)|./g);
18
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
19
+ // @ts-ignore
18
20
  this.root.insert(tokens, index, paramMap, this.context);
19
21
  return paramMap;
20
22
  }
@@ -11,10 +11,10 @@ export declare class Node<T> {
11
11
  patterns: Pattern[];
12
12
  order: number;
13
13
  name: string;
14
+ handlerSetCache: Record<string, HandlerSet<T>[]>;
14
15
  constructor(method?: string, handler?: T, children?: Record<string, Node<T>>);
15
16
  insert(method: string, path: string, handler: T): Node<T>;
16
17
  private getHandlerSets;
17
- private next;
18
- search(method: string, path: string): Result<T>;
18
+ search(method: string, path: string): Result<T> | null;
19
19
  }
20
20
  export {};
@@ -28,6 +28,7 @@ class Node {
28
28
  this.methods = [m];
29
29
  }
30
30
  this.patterns = [];
31
+ this.handlerSetCache = {};
31
32
  }
32
33
  insert(method, path, handler) {
33
34
  this.name = `${method} ${path}`;
@@ -82,89 +83,76 @@ class Node {
82
83
  return curNode;
83
84
  }
84
85
  getHandlerSets(node, method, wildcard) {
85
- const handlerSets = [];
86
- node.methods.map((m) => {
87
- const handlerSet = m[method] || m[router_1.METHOD_NAME_ALL];
88
- if (handlerSet !== undefined) {
89
- const hs = Object.assign({}, handlerSet);
90
- if (wildcard) {
91
- hs.score = handlerSet.score - 1;
92
- }
93
- handlerSets.push(hs);
94
- return;
95
- }
96
- });
97
- return handlerSets;
98
- }
99
- next(node, part, method, isLast) {
100
- const handlerSets = [];
101
- const nextNodes = [];
102
- const params = {};
103
- for (let j = 0, len = node.patterns.length; j < len; j++) {
104
- const pattern = node.patterns[j];
105
- // Wildcard
106
- // '/hello/*/foo' => match /hello/bar/foo
107
- if (pattern === '*') {
108
- const astNode = node.children['*'];
109
- if (astNode) {
110
- handlerSets.push(...this.getHandlerSets(astNode, method));
111
- nextNodes.push(astNode);
112
- }
113
- }
114
- if (part === '')
115
- continue;
116
- // Named match
117
- // `/posts/:id` => match /posts/123
118
- const [key, name, matcher] = pattern;
119
- if (matcher === true || (matcher instanceof RegExp && matcher.test(part))) {
120
- if (typeof key === 'string') {
121
- if (isLast === true) {
122
- handlerSets.push(...this.getHandlerSets(node.children[key], method));
86
+ var _a, _b;
87
+ return ((_a = node.handlerSetCache)[_b = `${method}:${wildcard ? '1' : '0'}`] || (_a[_b] = (() => {
88
+ const handlerSets = [];
89
+ node.methods.map((m) => {
90
+ const handlerSet = m[method] || m[router_1.METHOD_NAME_ALL];
91
+ if (handlerSet !== undefined) {
92
+ const hs = { ...handlerSet };
93
+ if (wildcard) {
94
+ hs.score = handlerSet.score - 1;
123
95
  }
124
- nextNodes.push(node.children[key]);
125
- }
126
- if (typeof name === 'string') {
127
- params[name] = part;
128
- }
129
- }
130
- }
131
- const nextNode = node.children[part];
132
- if (nextNode) {
133
- if (isLast === true) {
134
- // '/hello/*' => match '/hello'
135
- if (nextNode.children['*']) {
136
- handlerSets.push(...this.getHandlerSets(nextNode.children['*'], method, true));
96
+ handlerSets.push(hs);
97
+ return;
137
98
  }
138
- handlerSets.push(...this.getHandlerSets(nextNode, method));
139
- }
140
- nextNodes.push(nextNode);
141
- }
142
- const next = {
143
- nodes: nextNodes,
144
- handlerSets: handlerSets,
145
- params: params,
146
- };
147
- return next;
99
+ });
100
+ return handlerSets;
101
+ })()));
148
102
  }
149
103
  search(method, path) {
150
104
  const handlerSets = [];
151
- let params = {};
105
+ const params = {};
152
106
  // eslint-disable-next-line @typescript-eslint/no-this-alias
153
107
  const curNode = this;
154
108
  let curNodes = [curNode];
155
109
  const parts = (0, url_1.splitPath)(path);
156
110
  for (let i = 0, len = parts.length; i < len; i++) {
157
- const p = parts[i];
111
+ const part = parts[i];
158
112
  const isLast = i === len - 1;
159
113
  const tempNodes = [];
160
114
  for (let j = 0, len2 = curNodes.length; j < len2; j++) {
161
- const res = this.next(curNodes[j], p, method, isLast);
162
- if (res.nodes.length === 0) {
163
- continue;
115
+ const node = curNodes[j];
116
+ for (let k = 0, len3 = node.patterns.length; k < len3; k++) {
117
+ const pattern = node.patterns[k];
118
+ // Wildcard
119
+ // '/hello/*/foo' => match /hello/bar/foo
120
+ if (pattern === '*') {
121
+ const astNode = node.children['*'];
122
+ if (astNode) {
123
+ handlerSets.push(...this.getHandlerSets(astNode, method));
124
+ tempNodes.push(astNode);
125
+ }
126
+ continue;
127
+ }
128
+ if (part === '')
129
+ continue;
130
+ // Named match
131
+ // `/posts/:id` => match /posts/123
132
+ const [key, name, matcher] = pattern;
133
+ if (matcher === true || (matcher instanceof RegExp && matcher.test(part))) {
134
+ if (typeof key === 'string') {
135
+ if (isLast === true) {
136
+ handlerSets.push(...this.getHandlerSets(node.children[key], method));
137
+ }
138
+ tempNodes.push(node.children[key]);
139
+ }
140
+ if (typeof name === 'string') {
141
+ params[name] = part;
142
+ }
143
+ }
144
+ }
145
+ const nextNode = node.children[part];
146
+ if (nextNode) {
147
+ if (isLast === true) {
148
+ // '/hello/*' => match '/hello'
149
+ if (nextNode.children['*']) {
150
+ handlerSets.push(...this.getHandlerSets(nextNode.children['*'], method, true));
151
+ }
152
+ handlerSets.push(...this.getHandlerSets(nextNode, method));
153
+ }
154
+ tempNodes.push(nextNode);
164
155
  }
165
- handlerSets.push(...res.handlerSets);
166
- params = Object.assign(params, res.params);
167
- tempNodes.push(...res.nodes);
168
156
  }
169
157
  curNodes = tempNodes;
170
158
  }
@@ -4,5 +4,5 @@ export declare class TrieRouter<T> implements Router<T> {
4
4
  node: Node<T>;
5
5
  constructor();
6
6
  add(method: string, path: string, handler: T): void;
7
- match(method: string, path: string): Result<T>;
7
+ match(method: string, path: string): Result<T> | null;
8
8
  }
@@ -20,5 +20,6 @@ const parseBody = async (r) => {
20
20
  }, form);
21
21
  return data;
22
22
  }
23
+ return r.arrayBuffer();
23
24
  };
24
25
  exports.parseBody = parseBody;
@@ -1,3 +1,3 @@
1
1
  export declare const equal: (a: ArrayBuffer, b: ArrayBuffer) => boolean;
2
- export declare const timingSafeEqual: (a: string | object | boolean, b: string | object | boolean, hashFunction?: Function) => Promise<boolean>;
2
+ export declare const timingSafeEqual: (a: string | object | boolean, b: string | object | boolean, hashFunction?: Function | undefined) => Promise<boolean>;
3
3
  export declare const bufferToString: (buffer: ArrayBuffer) => string;
@@ -3,7 +3,7 @@ export declare type KVAssetOptions = {
3
3
  manifest?: object | string;
4
4
  namespace?: KVNamespace;
5
5
  };
6
- export declare const getContentFromKVAsset: (path: string, options?: KVAssetOptions) => Promise<ArrayBuffer>;
6
+ export declare const getContentFromKVAsset: (path: string, options?: KVAssetOptions | undefined) => Promise<ArrayBuffer | null>;
7
7
  declare type FilePathOptions = {
8
8
  filename: string;
9
9
  root?: string;
@@ -28,7 +28,7 @@ const getContentFromKVAsset = async (path, options) => {
28
28
  }
29
29
  const key = ASSET_MANIFEST[path] || path;
30
30
  if (!key) {
31
- return;
31
+ return null;
32
32
  }
33
33
  let content = await ASSET_NAMESPACE.get(key, { type: 'arrayBuffer' });
34
34
  if (content) {
@@ -3,7 +3,7 @@ declare type Algorithm = {
3
3
  alias: string;
4
4
  };
5
5
  declare type Data = string | object | boolean;
6
- export declare const sha256: (data: Data) => Promise<string>;
7
- export declare const sha1: (data: Data) => Promise<string>;
8
- export declare const createHash: (data: Data, algorithm: Algorithm) => Promise<string>;
6
+ export declare const sha256: (data: Data) => Promise<string | null>;
7
+ export declare const sha1: (data: Data) => Promise<string | null>;
8
+ export declare const createHash: (data: Data, algorithm: Algorithm) => Promise<string | null>;
9
9
  export {};
@@ -23,5 +23,6 @@ const createHash = async (data, algorithm) => {
23
23
  .join('');
24
24
  return hash;
25
25
  }
26
+ return null;
26
27
  };
27
28
  exports.createHash = createHash;
@@ -10,7 +10,7 @@ const encodeBase64 = (str) => {
10
10
  const bytes = encoder.encode(str);
11
11
  return btoa(String.fromCharCode(...bytes));
12
12
  }
13
- catch (_a) { }
13
+ catch { }
14
14
  try {
15
15
  return Buffer.from(str).toString('base64');
16
16
  }
@@ -30,7 +30,7 @@ const decodeBase64 = (str) => {
30
30
  const decoder = new TextDecoder();
31
31
  return decoder.decode(bytes);
32
32
  }
33
- catch (_a) { }
33
+ catch { }
34
34
  try {
35
35
  return Buffer.from(str, 'base64').toString();
36
36
  }
@@ -71,6 +71,7 @@ const arrayBufferToBase64 = async (buf) => {
71
71
  return Buffer.from(String.fromCharCode(...new Uint8Array(buf))).toString('base64');
72
72
  }
73
73
  catch (e) { }
74
+ return '';
74
75
  };
75
76
  exports.arrayBufferToBase64 = arrayBufferToBase64;
76
77
  const arrayBufferToBase64URL = async (buf) => {
@@ -1 +1 @@
1
- export declare const getMimeType: (filename: string) => string;
1
+ export declare const getMimeType: (filename: string) => string | undefined;
@@ -1,10 +1,6 @@
1
1
  export declare type Pattern = readonly [string, string, RegExp | true] | '*';
2
2
  export declare const splitPath: (path: string) => string[];
3
3
  export declare const getPattern: (label: string) => Pattern | null;
4
- declare type Params = {
5
- strict: boolean;
6
- };
7
- export declare const getPathFromURL: (url: string, params?: Params) => string;
4
+ export declare const getPathFromURL: (url: string, strict?: boolean) => string;
8
5
  export declare const isAbsoluteURL: (url: string) => boolean;
9
6
  export declare const mergePath: (...paths: string[]) => string;
10
- export {};
package/dist/utils/url.js CHANGED
@@ -34,17 +34,15 @@ const getPattern = (label) => {
34
34
  return null;
35
35
  };
36
36
  exports.getPattern = getPattern;
37
- const getPathFromURL = (url, params = { strict: true }) => {
37
+ const getPathFromURL = (url, strict = true) => {
38
+ const queryIndex = url.indexOf('?');
39
+ const result = url.substring(url.indexOf('/', 8), queryIndex === -1 ? url.length : queryIndex);
38
40
  // if strict routing is false => `/hello/hey/` and `/hello/hey` are treated the same
39
41
  // default is true
40
- if (params.strict === false && url.endsWith('/')) {
41
- url = url.slice(0, -1);
42
+ if (strict === false && result.endsWith('/')) {
43
+ return result.slice(0, -1);
42
44
  }
43
- const match = url.match(URL_REGEXP);
44
- if (match) {
45
- return match[1];
46
- }
47
- return '';
45
+ return result;
48
46
  };
49
47
  exports.getPathFromURL = getPathFromURL;
50
48
  const isAbsoluteURL = (url) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hono",
3
- "version": "1.4.1",
3
+ "version": "1.4.4",
4
4
  "description": "Ultrafast web framework for Cloudflare Workers.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -133,7 +133,7 @@
133
133
  "form-data": "^4.0.0",
134
134
  "graphql": "^16.4.0",
135
135
  "jest": "27.5.1",
136
- "jest-environment-miniflare": "^2.4.0",
136
+ "jest-environment-miniflare": "^2.5.0",
137
137
  "mustache": "^4.2.0",
138
138
  "prettier": "^2.6.2",
139
139
  "prettier-plugin-md-nocjsp": "^1.2.0",