hono 3.3.4 → 3.4.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.
Files changed (35) hide show
  1. package/README.md +7 -7
  2. package/dist/adapter/aws-lambda/handler.js +12 -1
  3. package/dist/adapter/netlify/handler.js +9 -0
  4. package/dist/adapter/netlify/mod.js +5 -0
  5. package/dist/cjs/adapter/aws-lambda/handler.js +13 -1
  6. package/dist/cjs/adapter/netlify/handler.js +32 -0
  7. package/dist/cjs/adapter/netlify/mod.js +28 -0
  8. package/dist/cjs/context.js +7 -9
  9. package/dist/cjs/hono-base.js +4 -4
  10. package/dist/cjs/middleware/cookie/index.js +23 -2
  11. package/dist/cjs/middleware/pretty-json/index.js +4 -1
  12. package/dist/cjs/utils/body.js +2 -2
  13. package/dist/cjs/utils/cookie.js +61 -8
  14. package/dist/cjs/utils/url.js +3 -4
  15. package/dist/cjs/validator/validator.js +2 -2
  16. package/dist/context.js +7 -9
  17. package/dist/hono-base.js +4 -4
  18. package/dist/middleware/cookie/index.js +21 -2
  19. package/dist/middleware/pretty-json/index.js +4 -1
  20. package/dist/types/adapter/aws-lambda/handler.d.ts +1 -0
  21. package/dist/types/adapter/netlify/handler.d.ts +7 -0
  22. package/dist/types/adapter/netlify/mod.d.ts +2 -0
  23. package/dist/types/context.d.ts +1 -3
  24. package/dist/types/hono-base.d.ts +10 -4
  25. package/dist/types/middleware/cookie/index.d.ts +7 -1
  26. package/dist/types/request.d.ts +1 -1
  27. package/dist/types/types.d.ts +2 -1
  28. package/dist/types/utils/body.d.ts +1 -1
  29. package/dist/types/utils/cookie.d.ts +5 -2
  30. package/dist/types/validator/validator.d.ts +1 -1
  31. package/dist/utils/body.js +2 -2
  32. package/dist/utils/cookie.js +58 -7
  33. package/dist/utils/url.js +3 -4
  34. package/dist/validator/validator.js +2 -2
  35. package/package.json +2 -2
package/README.md CHANGED
@@ -26,7 +26,7 @@
26
26
  [![Discord badge](https://img.shields.io/discord/1011308539819597844?label=Discord&logo=Discord)](https://discord.gg/KMh2eNSdxV)
27
27
 
28
28
  Hono - _**\[炎\] means flame🔥 in Japanese**_ - is a small, simple, and ultrafast web framework for the Edges.
29
- It works on any JavaScript runtime: Cloudflare Workers, Fastly Compute@Edge, Deno, Bun, Vercel, Lagon, AWS Lambda, and Node.js.
29
+ It works on any JavaScript runtime: Cloudflare Workers, Fastly Compute@Edge, Deno, Bun, Vercel, Lagon, AWS Lambda, Lambda@Edge, and Node.js.
30
30
 
31
31
  Fast, but not only fast.
32
32
 
@@ -49,7 +49,7 @@ npm create hono@latest my-app
49
49
 
50
50
  - **Ultrafast** 🚀 - The router `RegExpRouter` is really fast. Not using linear loops. Fast.
51
51
  - **Lightweight** 🪶 - The `hono/tiny` preset is under 12kB. Hono has zero dependencies and uses only the Web Standard API.
52
- - **Multi-runtime** 🌍 - Works on Cloudflare Workers, Fastly Compute@Edge, Deno, Bun, Lagon, AWS Lambda, or Node.js. The same code runs on all platforms.
52
+ - **Multi-runtime** 🌍 - Works on Cloudflare Workers, Fastly Compute@Edge, Deno, Bun, Lagon, AWS Lambda, Lambda@Edge, or Node.js. The same code runs on all platforms.
53
53
  - **Batteries Included** 🔋 - Hono has built-in middleware, custom middleware, and third-party middleware. Batteries included.
54
54
  - **Delightful DX** 🛠️ - Super clean APIs. First-class TypeScript support. Now, we've got "Types".
55
55
 
@@ -58,12 +58,12 @@ npm create hono@latest my-app
58
58
  **Hono is the fastest**, compared to other routers for Cloudflare Workers.
59
59
 
60
60
  ```
61
- Hono x 402,820 ops/sec ±4.78% (80 runs sampled)
62
- itty-router x 212,598 ops/sec ±3.11% (87 runs sampled)
63
- sunder x 297,036 ops/sec ±4.76% (77 runs sampled)
64
- worktop x 197,345 ops/sec ±2.40% (88 runs sampled)
61
+ Hono x 510,171 ops/sec ±4.61% (82 runs sampled)
62
+ itty-router x 285,810 ops/sec ±4.13% (85 runs sampled)
63
+ sunder x 345,272 ops/sec ±4.46% (87 runs sampled)
64
+ worktop x 203,468 ops/sec ±3.03% (91 runs sampled)
65
65
  Fastest is Hono
66
- ✨ Done in 28.06s.
66
+ ✨ Done in 28.68s.
67
67
  ```
68
68
 
69
69
  ## Documentation
@@ -11,7 +11,11 @@ var handle = (app) => {
11
11
  };
12
12
  var createResult = async (res) => {
13
13
  const contentType = res.headers.get("content-type");
14
- const isBase64Encoded = contentType && isContentTypeBinary(contentType) ? true : false;
14
+ let isBase64Encoded = contentType && isContentTypeBinary(contentType) ? true : false;
15
+ if (!isBase64Encoded) {
16
+ const contentEncoding = res.headers.get("content-encoding");
17
+ isBase64Encoded = isContentEncodingBinary(contentEncoding);
18
+ }
15
19
  let body;
16
20
  if (isBase64Encoded) {
17
21
  const buffer = await res.arrayBuffer();
@@ -66,7 +70,14 @@ var isContentTypeBinary = (contentType) => {
66
70
  contentType
67
71
  );
68
72
  };
73
+ var isContentEncodingBinary = (contentEncoding) => {
74
+ if (contentEncoding === null) {
75
+ return false;
76
+ }
77
+ return /^(gzip|deflate|compress|br)/.test(contentEncoding);
78
+ };
69
79
  export {
70
80
  handle,
81
+ isContentEncodingBinary,
71
82
  isContentTypeBinary
72
83
  };
@@ -0,0 +1,9 @@
1
+ // src/adapter/netlify/handler.ts
2
+ var handle = (app) => {
3
+ return (req, context) => {
4
+ return app.fetch(req, { context });
5
+ };
6
+ };
7
+ export {
8
+ handle
9
+ };
@@ -0,0 +1,5 @@
1
+ // src/adapter/netlify/mod.ts
2
+ import { handle } from "./handler.ts";
3
+ export {
4
+ handle
5
+ };
@@ -25,6 +25,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
25
25
  var handler_exports = {};
26
26
  __export(handler_exports, {
27
27
  handle: () => handle,
28
+ isContentEncodingBinary: () => isContentEncodingBinary,
28
29
  isContentTypeBinary: () => isContentTypeBinary
29
30
  });
30
31
  module.exports = __toCommonJS(handler_exports);
@@ -40,7 +41,11 @@ const handle = (app) => {
40
41
  };
41
42
  const createResult = async (res) => {
42
43
  const contentType = res.headers.get("content-type");
43
- const isBase64Encoded = contentType && isContentTypeBinary(contentType) ? true : false;
44
+ let isBase64Encoded = contentType && isContentTypeBinary(contentType) ? true : false;
45
+ if (!isBase64Encoded) {
46
+ const contentEncoding = res.headers.get("content-encoding");
47
+ isBase64Encoded = isContentEncodingBinary(contentEncoding);
48
+ }
44
49
  let body;
45
50
  if (isBase64Encoded) {
46
51
  const buffer = await res.arrayBuffer();
@@ -95,8 +100,15 @@ const isContentTypeBinary = (contentType) => {
95
100
  contentType
96
101
  );
97
102
  };
103
+ const isContentEncodingBinary = (contentEncoding) => {
104
+ if (contentEncoding === null) {
105
+ return false;
106
+ }
107
+ return /^(gzip|deflate|compress|br)/.test(contentEncoding);
108
+ };
98
109
  // Annotate the CommonJS export names for ESM import in node:
99
110
  0 && (module.exports = {
100
111
  handle,
112
+ isContentEncodingBinary,
101
113
  isContentTypeBinary
102
114
  });
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var handler_exports = {};
20
+ __export(handler_exports, {
21
+ handle: () => handle
22
+ });
23
+ module.exports = __toCommonJS(handler_exports);
24
+ const handle = (app) => {
25
+ return (req, context) => {
26
+ return app.fetch(req, { context });
27
+ };
28
+ };
29
+ // Annotate the CommonJS export names for ESM import in node:
30
+ 0 && (module.exports = {
31
+ handle
32
+ });
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var mod_exports = {};
20
+ __export(mod_exports, {
21
+ handle: () => import_handler.handle
22
+ });
23
+ module.exports = __toCommonJS(mod_exports);
24
+ var import_handler = require("./handler.ts");
25
+ // Annotate the CommonJS export names for ESM import in node:
26
+ 0 && (module.exports = {
27
+ handle
28
+ });
@@ -30,11 +30,10 @@ class Context {
30
30
  this.finalized = false;
31
31
  this.error = void 0;
32
32
  this._status = 200;
33
- this._pre = false;
34
- this._preS = 2;
35
33
  this._h = void 0;
36
34
  this._pH = void 0;
37
35
  this._path = "/";
36
+ this._init = true;
38
37
  this.notFoundHandler = () => new Response();
39
38
  this.header = (name, value, options) => {
40
39
  if (value === void 0) {
@@ -50,6 +49,7 @@ class Context {
50
49
  }
51
50
  if (options?.append) {
52
51
  if (!this._h) {
52
+ this._init = false;
53
53
  this._h = new Headers(this._pH);
54
54
  this._pH = {};
55
55
  }
@@ -80,12 +80,8 @@ class Context {
80
80
  this.get = (key) => {
81
81
  return this._map ? this._map[key] : void 0;
82
82
  };
83
- this.pretty = (prettyJSON, space = 2) => {
84
- this._pre = prettyJSON;
85
- this._preS = space;
86
- };
87
83
  this.newResponse = (data, arg, headers) => {
88
- if (!headers && !this._h && !this._res && !arg && this._status === 200) {
84
+ if (this._init && !headers && !arg && this._status === 200) {
89
85
  return new Response(data, {
90
86
  headers: this._pH
91
87
  });
@@ -133,7 +129,7 @@ class Context {
133
129
  };
134
130
  this.text = (text, arg, headers) => {
135
131
  if (!this._pH) {
136
- if (!headers && !this._res && !this._h && !arg) {
132
+ if (this._init && !headers && !arg) {
137
133
  return new Response(text);
138
134
  }
139
135
  this._pH = {};
@@ -144,7 +140,7 @@ class Context {
144
140
  return typeof arg === "number" ? this.newResponse(text, arg, headers) : this.newResponse(text, arg);
145
141
  };
146
142
  this.json = (object, arg, headers) => {
147
- const body = this._pre ? JSON.stringify(object, null, this._preS) : JSON.stringify(object);
143
+ const body = JSON.stringify(object);
148
144
  this._pH ?? (this._pH = {});
149
145
  this._pH["content-type"] = "application/json; charset=UTF-8";
150
146
  return typeof arg === "number" ? this.newResponse(body, arg, headers) : this.newResponse(body, arg);
@@ -209,9 +205,11 @@ class Context {
209
205
  }
210
206
  }
211
207
  get res() {
208
+ this._init = false;
212
209
  return this._res || (this._res = new Response("404 Not Found", { status: 404 }));
213
210
  }
214
211
  set res(_res) {
212
+ this._init = false;
215
213
  if (this._res && _res) {
216
214
  this._res.headers.delete("content-type");
217
215
  this._res.headers.forEach((v, k) => {
@@ -59,21 +59,21 @@ class Hono extends defineDynamicClass() {
59
59
  this.fetch = (request, Env, executionCtx) => {
60
60
  return this.dispatch(request, executionCtx, Env, request.method);
61
61
  };
62
- this.request = async (input, requestInit) => {
62
+ this.request = (input, requestInit) => {
63
63
  if (input instanceof Request) {
64
64
  if (requestInit !== void 0) {
65
65
  input = new Request(input, requestInit);
66
66
  }
67
- return await this.fetch(input);
67
+ return this.fetch(input);
68
68
  }
69
69
  input = input.toString();
70
70
  const path = /^https?:\/\//.test(input) ? input : `http://localhost${(0, import_url.mergePath)("/", input)}`;
71
71
  const req = new Request(path, requestInit);
72
- return await this.fetch(req);
72
+ return this.fetch(req);
73
73
  };
74
74
  this.fire = () => {
75
75
  addEventListener("fetch", (event) => {
76
- void event.respondWith(this.handleEvent(event));
76
+ event.respondWith(this.dispatch(event.request, event, void 0, event.request.method));
77
77
  });
78
78
  };
79
79
  const allMethods = [...import_router.METHODS, import_router.METHOD_NAME_ALL_LOWERCASE];
@@ -20,7 +20,9 @@ var cookie_exports = {};
20
20
  __export(cookie_exports, {
21
21
  deleteCookie: () => deleteCookie,
22
22
  getCookie: () => getCookie,
23
- setCookie: () => setCookie
23
+ getSignedCookie: () => getSignedCookie,
24
+ setCookie: () => setCookie,
25
+ setSignedCookie: () => setSignedCookie
24
26
  });
25
27
  module.exports = __toCommonJS(cookie_exports);
26
28
  var import_cookie = require("../../utils/cookie");
@@ -37,10 +39,27 @@ const getCookie = (c, key) => {
37
39
  const obj = (0, import_cookie.parse)(cookie);
38
40
  return obj;
39
41
  };
42
+ const getSignedCookie = async (c, secret, key) => {
43
+ const cookie = c.req.raw.headers.get("Cookie");
44
+ if (typeof key === "string") {
45
+ if (!cookie)
46
+ return void 0;
47
+ const obj2 = await (0, import_cookie.parseSigned)(cookie, secret, key);
48
+ return obj2[key];
49
+ }
50
+ if (!cookie)
51
+ return {};
52
+ const obj = await (0, import_cookie.parseSigned)(cookie, secret);
53
+ return obj;
54
+ };
40
55
  const setCookie = (c, name, value, opt) => {
41
56
  const cookie = (0, import_cookie.serialize)(name, value, opt);
42
57
  c.header("set-cookie", cookie, { append: true });
43
58
  };
59
+ const setSignedCookie = async (c, name, value, secret, opt) => {
60
+ const cookie = await (0, import_cookie.serializeSigned)(name, value, secret, opt);
61
+ c.header("set-cookie", cookie, { append: true });
62
+ };
44
63
  const deleteCookie = (c, name, opt) => {
45
64
  setCookie(c, name, "", { ...opt, maxAge: 0 });
46
65
  };
@@ -48,5 +67,7 @@ const deleteCookie = (c, name, opt) => {
48
67
  0 && (module.exports = {
49
68
  deleteCookie,
50
69
  getCookie,
51
- setCookie
70
+ getSignedCookie,
71
+ setCookie,
72
+ setSignedCookie
52
73
  });
@@ -24,8 +24,11 @@ module.exports = __toCommonJS(pretty_json_exports);
24
24
  const prettyJSON = (options = { space: 2 }) => {
25
25
  return async (c, next) => {
26
26
  const pretty = c.req.query("pretty") || c.req.query("pretty") === "" ? true : false;
27
- c.pretty(pretty, options.space);
28
27
  await next();
28
+ if (pretty && c.res.headers.get("Content-Type")?.startsWith("application/json")) {
29
+ const obj = await c.res.json();
30
+ c.res = new Response(JSON.stringify(obj, null, options.space), c.res);
31
+ }
29
32
  };
30
33
  };
31
34
  // Annotate the CommonJS export names for ESM import in node:
@@ -21,7 +21,7 @@ __export(body_exports, {
21
21
  parseBody: () => parseBody
22
22
  });
23
23
  module.exports = __toCommonJS(body_exports);
24
- async function parseBody(r) {
24
+ const parseBody = async (r) => {
25
25
  let body = {};
26
26
  const contentType = r.headers.get("Content-Type");
27
27
  if (contentType && (contentType.startsWith("multipart/form-data") || contentType.startsWith("application/x-www-form-urlencoded"))) {
@@ -32,7 +32,7 @@ async function parseBody(r) {
32
32
  body = form;
33
33
  }
34
34
  return body;
35
- }
35
+ };
36
36
  // Annotate the CommonJS export names for ESM import in node:
37
37
  0 && (module.exports = {
38
38
  parseBody
@@ -19,21 +19,62 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
19
19
  var cookie_exports = {};
20
20
  __export(cookie_exports, {
21
21
  parse: () => parse,
22
- serialize: () => serialize
22
+ parseSigned: () => parseSigned,
23
+ serialize: () => serialize,
24
+ serializeSigned: () => serializeSigned
23
25
  });
24
26
  module.exports = __toCommonJS(cookie_exports);
25
27
  var import_url = require("./url");
26
- const parse = (cookie) => {
28
+ const makeSignature = async (value, secret) => {
29
+ const algorithm = { name: "HMAC", hash: "SHA-256" };
30
+ const encoder = new TextEncoder();
31
+ const key = await crypto.subtle.importKey("raw", encoder.encode(secret), algorithm, false, [
32
+ "sign",
33
+ "verify"
34
+ ]);
35
+ const signature = await crypto.subtle.sign(algorithm.name, key, encoder.encode(value));
36
+ return btoa(String.fromCharCode(...new Uint8Array(signature)));
37
+ };
38
+ const _parseCookiePairs = (cookie, name) => {
27
39
  const pairs = cookie.split(/;\s*/g);
40
+ const cookiePairs = pairs.map((pairStr) => pairStr.split(/\s*=\s*([^\s]+)/));
41
+ if (!name)
42
+ return cookiePairs;
43
+ return cookiePairs.filter((pair) => pair[0] === name);
44
+ };
45
+ const parse = (cookie, name) => {
28
46
  const parsedCookie = {};
29
- for (let i = 0, len = pairs.length; i < len; i++) {
30
- const pair = pairs[i].split(/\s*=\s*([^\s]+)/);
31
- parsedCookie[pair[0]] = (0, import_url.decodeURIComponent_)(pair[1]);
47
+ const unsingedCookies = _parseCookiePairs(cookie, name).filter((pair) => {
48
+ if (pair[1].split(".").length === 2)
49
+ return false;
50
+ return true;
51
+ });
52
+ for (let [key, value] of unsingedCookies) {
53
+ value = (0, import_url.decodeURIComponent_)(value);
54
+ parsedCookie[key] = value;
32
55
  }
33
56
  return parsedCookie;
34
57
  };
35
- const serialize = (name, value, opt = {}) => {
36
- value = encodeURIComponent(value);
58
+ const parseSigned = async (cookie, secret, name) => {
59
+ const parsedCookie = {};
60
+ const signedCookies = _parseCookiePairs(cookie, name).filter((pair) => {
61
+ if (pair[1].split(".").length !== 2)
62
+ return false;
63
+ return true;
64
+ });
65
+ for (let [key, value] of signedCookies) {
66
+ value = (0, import_url.decodeURIComponent_)(value);
67
+ const signedPair = value.split(".");
68
+ const signatureToCompare = await makeSignature(signedPair[0], secret);
69
+ if (signedPair[1] !== signatureToCompare) {
70
+ parsedCookie[key] = false;
71
+ continue;
72
+ }
73
+ parsedCookie[key] = signedPair[0];
74
+ }
75
+ return parsedCookie;
76
+ };
77
+ const _serialize = (name, value, opt = {}) => {
37
78
  let cookie = `${name}=${value}`;
38
79
  if (opt && typeof opt.maxAge === "number" && opt.maxAge >= 0) {
39
80
  cookie += `; Max-Age=${Math.floor(opt.maxAge)}`;
@@ -58,8 +99,20 @@ const serialize = (name, value, opt = {}) => {
58
99
  }
59
100
  return cookie;
60
101
  };
102
+ const serialize = (name, value, opt = {}) => {
103
+ value = encodeURIComponent(value);
104
+ return _serialize(name, value, opt);
105
+ };
106
+ const serializeSigned = async (name, value, secret, opt = {}) => {
107
+ const signature = await makeSignature(value, secret);
108
+ value = `${value}.${signature}`;
109
+ value = encodeURIComponent(value);
110
+ return _serialize(name, value, opt);
111
+ };
61
112
  // Annotate the CommonJS export names for ESM import in node:
62
113
  0 && (module.exports = {
63
114
  parse,
64
- serialize
115
+ parseSigned,
116
+ serialize,
117
+ serializeSigned
65
118
  });
@@ -87,9 +87,8 @@ const getPattern = (label) => {
87
87
  return null;
88
88
  };
89
89
  const getPath = (request) => {
90
- const url = request.url;
91
- const queryIndex = url.indexOf("?", 8);
92
- return url.slice(url.indexOf("/", 8), queryIndex === -1 ? void 0 : queryIndex);
90
+ const match = request.url.match(/^https?:\/\/[^/]+(\/[^?]*)/);
91
+ return match ? match[1] : "";
93
92
  };
94
93
  const getQueryStrings = (url) => {
95
94
  const queryIndex = url.indexOf("?", 8);
@@ -136,7 +135,7 @@ const _decodeURI = (value) => {
136
135
  if (value.indexOf("+") !== -1) {
137
136
  value = value.replace(/\+/g, " ");
138
137
  }
139
- return value.indexOf("%") === -1 ? value : decodeURIComponent_(value);
138
+ return /%/.test(value) ? decodeURIComponent_(value) : value;
140
139
  };
141
140
  const _getQueryParam = (url, key, multiple) => {
142
141
  let encoded;
@@ -57,8 +57,8 @@ const validator = (target, validationFunc) => {
57
57
  value = c.req.param();
58
58
  break;
59
59
  }
60
- const res = validationFunc(value, c);
61
- if (res instanceof Response || res instanceof Promise) {
60
+ const res = await validationFunc(value, c);
61
+ if (res instanceof Response) {
62
62
  return res;
63
63
  }
64
64
  c.req.addValidatedData(target, res);
package/dist/context.js CHANGED
@@ -8,11 +8,10 @@ var Context = class {
8
8
  this.finalized = false;
9
9
  this.error = void 0;
10
10
  this._status = 200;
11
- this._pre = false;
12
- this._preS = 2;
13
11
  this._h = void 0;
14
12
  this._pH = void 0;
15
13
  this._path = "/";
14
+ this._init = true;
16
15
  this.notFoundHandler = () => new Response();
17
16
  this.header = (name, value, options) => {
18
17
  if (value === void 0) {
@@ -28,6 +27,7 @@ var Context = class {
28
27
  }
29
28
  if (options?.append) {
30
29
  if (!this._h) {
30
+ this._init = false;
31
31
  this._h = new Headers(this._pH);
32
32
  this._pH = {};
33
33
  }
@@ -58,12 +58,8 @@ var Context = class {
58
58
  this.get = (key) => {
59
59
  return this._map ? this._map[key] : void 0;
60
60
  };
61
- this.pretty = (prettyJSON, space = 2) => {
62
- this._pre = prettyJSON;
63
- this._preS = space;
64
- };
65
61
  this.newResponse = (data, arg, headers) => {
66
- if (!headers && !this._h && !this._res && !arg && this._status === 200) {
62
+ if (this._init && !headers && !arg && this._status === 200) {
67
63
  return new Response(data, {
68
64
  headers: this._pH
69
65
  });
@@ -111,7 +107,7 @@ var Context = class {
111
107
  };
112
108
  this.text = (text, arg, headers) => {
113
109
  if (!this._pH) {
114
- if (!headers && !this._res && !this._h && !arg) {
110
+ if (this._init && !headers && !arg) {
115
111
  return new Response(text);
116
112
  }
117
113
  this._pH = {};
@@ -122,7 +118,7 @@ var Context = class {
122
118
  return typeof arg === "number" ? this.newResponse(text, arg, headers) : this.newResponse(text, arg);
123
119
  };
124
120
  this.json = (object, arg, headers) => {
125
- const body = this._pre ? JSON.stringify(object, null, this._preS) : JSON.stringify(object);
121
+ const body = JSON.stringify(object);
126
122
  this._pH ?? (this._pH = {});
127
123
  this._pH["content-type"] = "application/json; charset=UTF-8";
128
124
  return typeof arg === "number" ? this.newResponse(body, arg, headers) : this.newResponse(body, arg);
@@ -187,9 +183,11 @@ var Context = class {
187
183
  }
188
184
  }
189
185
  get res() {
186
+ this._init = false;
190
187
  return this._res || (this._res = new Response("404 Not Found", { status: 404 }));
191
188
  }
192
189
  set res(_res) {
190
+ this._init = false;
193
191
  if (this._res && _res) {
194
192
  this._res.headers.delete("content-type");
195
193
  this._res.headers.forEach((v, k) => {
package/dist/hono-base.js CHANGED
@@ -37,21 +37,21 @@ var Hono = class extends defineDynamicClass() {
37
37
  this.fetch = (request, Env, executionCtx) => {
38
38
  return this.dispatch(request, executionCtx, Env, request.method);
39
39
  };
40
- this.request = async (input, requestInit) => {
40
+ this.request = (input, requestInit) => {
41
41
  if (input instanceof Request) {
42
42
  if (requestInit !== void 0) {
43
43
  input = new Request(input, requestInit);
44
44
  }
45
- return await this.fetch(input);
45
+ return this.fetch(input);
46
46
  }
47
47
  input = input.toString();
48
48
  const path = /^https?:\/\//.test(input) ? input : `http://localhost${mergePath("/", input)}`;
49
49
  const req = new Request(path, requestInit);
50
- return await this.fetch(req);
50
+ return this.fetch(req);
51
51
  };
52
52
  this.fire = () => {
53
53
  addEventListener("fetch", (event) => {
54
- void event.respondWith(this.handleEvent(event));
54
+ event.respondWith(this.dispatch(event.request, event, void 0, event.request.method));
55
55
  });
56
56
  };
57
57
  const allMethods = [...METHODS, METHOD_NAME_ALL_LOWERCASE];
@@ -1,5 +1,5 @@
1
1
  // src/middleware/cookie/index.ts
2
- import { parse, serialize } from "../../utils/cookie.js";
2
+ import { parse, parseSigned, serialize, serializeSigned } from "../../utils/cookie.js";
3
3
  var getCookie = (c, key) => {
4
4
  const cookie = c.req.raw.headers.get("Cookie");
5
5
  if (typeof key === "string") {
@@ -13,15 +13,34 @@ var getCookie = (c, key) => {
13
13
  const obj = parse(cookie);
14
14
  return obj;
15
15
  };
16
+ var getSignedCookie = async (c, secret, key) => {
17
+ const cookie = c.req.raw.headers.get("Cookie");
18
+ if (typeof key === "string") {
19
+ if (!cookie)
20
+ return void 0;
21
+ const obj2 = await parseSigned(cookie, secret, key);
22
+ return obj2[key];
23
+ }
24
+ if (!cookie)
25
+ return {};
26
+ const obj = await parseSigned(cookie, secret);
27
+ return obj;
28
+ };
16
29
  var setCookie = (c, name, value, opt) => {
17
30
  const cookie = serialize(name, value, opt);
18
31
  c.header("set-cookie", cookie, { append: true });
19
32
  };
33
+ var setSignedCookie = async (c, name, value, secret, opt) => {
34
+ const cookie = await serializeSigned(name, value, secret, opt);
35
+ c.header("set-cookie", cookie, { append: true });
36
+ };
20
37
  var deleteCookie = (c, name, opt) => {
21
38
  setCookie(c, name, "", { ...opt, maxAge: 0 });
22
39
  };
23
40
  export {
24
41
  deleteCookie,
25
42
  getCookie,
26
- setCookie
43
+ getSignedCookie,
44
+ setCookie,
45
+ setSignedCookie
27
46
  };
@@ -2,8 +2,11 @@
2
2
  var prettyJSON = (options = { space: 2 }) => {
3
3
  return async (c, next) => {
4
4
  const pretty = c.req.query("pretty") || c.req.query("pretty") === "" ? true : false;
5
- c.pretty(pretty, options.space);
6
5
  await next();
6
+ if (pretty && c.res.headers.get("Content-Type")?.startsWith("application/json")) {
7
+ const obj = await c.res.json();
8
+ c.res = new Response(JSON.stringify(obj, null, options.space), c.res);
9
+ }
7
10
  };
8
11
  };
9
12
  export {
@@ -46,4 +46,5 @@ interface APIGatewayProxyResult {
46
46
  */
47
47
  export declare const handle: <E extends Env = Env, S = {}, BasePath extends string = "/">(app: Hono<E, S, BasePath>) => (event: APIGatewayProxyEvent | APIGatewayProxyEventV2 | LambdaFunctionUrlEvent) => Promise<APIGatewayProxyResult>;
48
48
  export declare const isContentTypeBinary: (contentType: string) => boolean;
49
+ export declare const isContentEncodingBinary: (contentEncoding: string | null) => boolean;
49
50
  export {};
@@ -0,0 +1,7 @@
1
+ import type { Context } from 'https://edge.netlify.com/';
2
+ export declare type Env = {
3
+ Bindings: {
4
+ context: Context;
5
+ };
6
+ };
7
+ export declare const handle: (app: Hono<any, any>) => (req: Request, context: Context) => any;
@@ -0,0 +1,2 @@
1
+ export { handle } from './handler.ts';
2
+ export type { Env } from './handler.ts';
@@ -57,14 +57,13 @@ export declare class Context<E extends Env = any, P extends string = any, I exte
57
57
  private _req?;
58
58
  private _status;
59
59
  private _exCtx;
60
- private _pre;
61
- private _preS;
62
60
  private _map;
63
61
  private _h;
64
62
  private _pH;
65
63
  private _res;
66
64
  private _path;
67
65
  private _params?;
66
+ private _init;
68
67
  private rawRequest?;
69
68
  private notFoundHandler;
70
69
  constructor(req: Request, options?: ContextOptions<E>);
@@ -79,7 +78,6 @@ export declare class Context<E extends Env = any, P extends string = any, I exte
79
78
  status: (status: StatusCode) => void;
80
79
  set: Set<E>;
81
80
  get: Get<E>;
82
- pretty: (prettyJSON: boolean, space?: number) => void;
83
81
  newResponse: NewResponse;
84
82
  body: BodyRespond;
85
83
  text: TextRespond;
@@ -34,9 +34,10 @@ declare class Hono<E extends Env = Env, S = {}, BasePath extends string = '/'> e
34
34
  private notFoundHandler;
35
35
  private errorHandler;
36
36
  route<SubPath extends string, SubEnv extends Env, SubSchema, SubBasePath extends string>(path: SubPath, app: Hono<SubEnv, SubSchema, SubBasePath>): Hono<E, RemoveBlankRecord<MergeSchemaPath<SubSchema, SubPath> | S>, BasePath>;
37
- /** @deprecated
38
- * Use `basePath` instead of `route` with one argument.
39
- * The `route` with one argument has been removed in v4.
37
+ /** @description
38
+ * Use `basePath` instead of `route` when passing **one** argument, such as `app.route('/api')`.
39
+ * The use of `route` with **one** argument has been removed in v4.
40
+ * However, you can still use `route` with **two** arguments, like `app.route('/api', subApp)`."
40
41
  */
41
42
  route<SubPath extends string>(path: SubPath): Hono<E, RemoveBlankRecord<S>, BasePath>;
42
43
  basePath<SubPath extends string>(path: SubPath): Hono<E, S, MergePath<BasePath, SubPath>>;
@@ -60,9 +61,14 @@ declare class Hono<E extends Env = Env, S = {}, BasePath extends string = '/'> e
60
61
  private matchRoute;
61
62
  private handleError;
62
63
  private dispatch;
64
+ /**
65
+ * @deprecate
66
+ * `app.handleEvent()` will be removed in v4.
67
+ * Use `app.fetch()` instead of `app.handleEvent()`.
68
+ */
63
69
  handleEvent: (event: FetchEventLike) => Response | Promise<Response>;
64
70
  fetch: (request: Request, Env?: E['Bindings'] | {}, executionCtx?: ExecutionContext) => Response | Promise<Response>;
65
- request: (input: Request | string | URL, requestInit?: RequestInit) => Promise<Response>;
71
+ request: (input: Request | string | URL, requestInit?: RequestInit) => Response | Promise<Response>;
66
72
  fire: () => void;
67
73
  }
68
74
  export { Hono as HonoBase };
@@ -1,10 +1,16 @@
1
1
  import type { Context } from '../../context';
2
- import type { CookieOptions, Cookie } from '../../utils/cookie';
2
+ import type { CookieOptions, Cookie, SignedCookie } from '../../utils/cookie';
3
3
  interface GetCookie {
4
4
  (c: Context, key: string): string | undefined;
5
5
  (c: Context): Cookie;
6
6
  }
7
+ interface GetSignedCookie {
8
+ (c: Context, sercet: string, key: string): Promise<string | undefined | false>;
9
+ (c: Context, secret: string): Promise<SignedCookie>;
10
+ }
7
11
  export declare const getCookie: GetCookie;
12
+ export declare const getSignedCookie: GetSignedCookie;
8
13
  export declare const setCookie: (c: Context, name: string, value: string, opt?: CookieOptions) => void;
14
+ export declare const setSignedCookie: (c: Context, name: string, value: string, secret: string, opt?: CookieOptions) => Promise<void>;
9
15
  export declare const deleteCookie: (c: Context, name: string, opt?: CookieOptions) => void;
10
16
  export {};
@@ -36,7 +36,7 @@ export declare class HonoRequest<P extends string = '/', I extends Input['out']
36
36
  * app.get('/', (c) => c.json(getCookie(c)))
37
37
  */
38
38
  cookie(): Cookie;
39
- parseBody(): Promise<BodyData>;
39
+ parseBody<T extends BodyData = BodyData>(): Promise<T>;
40
40
  json<T = any>(): Promise<T>;
41
41
  text(): Promise<string>;
42
42
  arrayBuffer(): Promise<ArrayBuffer>;
@@ -36,7 +36,8 @@ export interface HandlerInterface<E extends Env = Env, M extends string = any, S
36
36
  H<E, ExtractKey<S>, I5, O>
37
37
  ]): Hono<E, RemoveBlankRecord<S | Schema<M, ExtractKey<S>, I5['in'], O>>, BasePath>;
38
38
  <I extends Input = {}, O = {}>(...handlers: Handler<E, ExtractKey<S>, I, O>[]): Hono<E, RemoveBlankRecord<S | Schema<M, ExtractKey<S>, I['in'], O>>, BasePath>;
39
- <P extends string, O = {}, I extends Input = {}>(path: P, ...handlers: [H<E, P, I, O>, H<E, P, I, O>]): Hono<E, RemoveBlankRecord<S | Schema<M, MergePath<BasePath, P>, I['in'], O>>, BasePath>;
39
+ <P extends string, O = {}, I extends Input = {}>(path: P, handler: H<E, MergePath<BasePath, P>, I, O>): Hono<E, RemoveBlankRecord<S | Schema<M, MergePath<BasePath, P>, I['in'], O>>, BasePath>;
40
+ <P extends string, O = {}, I extends Input = {}>(path: P, ...handlers: [H<E, MergePath<BasePath, P>, I, O>, H<E, MergePath<BasePath, P>, I, O>]): Hono<E, RemoveBlankRecord<S | Schema<M, MergePath<BasePath, P>, I['in'], O>>, BasePath>;
40
41
  <P extends string, O = {}, I extends Input = {}, I2 extends Input = I, I3 extends Input = I & I2>(path: P, ...handlers: [
41
42
  H<E, MergePath<BasePath, P>, I, O>,
42
43
  H<E, MergePath<BasePath, P>, I2, O>,
@@ -1,2 +1,2 @@
1
1
  export declare type BodyData = Record<string, string | File>;
2
- export declare function parseBody(r: Request | Response): Promise<BodyData>;
2
+ export declare const parseBody: <T extends BodyData = BodyData>(r: Request | Response) => Promise<T>;
@@ -1,4 +1,5 @@
1
1
  export declare type Cookie = Record<string, string>;
2
+ export declare type SignedCookie = Record<string, string | false>;
2
3
  export declare type CookieOptions = {
3
4
  domain?: string;
4
5
  expires?: Date;
@@ -6,8 +7,10 @@ export declare type CookieOptions = {
6
7
  maxAge?: number;
7
8
  path?: string;
8
9
  secure?: boolean;
9
- signed?: boolean;
10
+ signingSecret?: string;
10
11
  sameSite?: 'Strict' | 'Lax' | 'None';
11
12
  };
12
- export declare const parse: (cookie: string) => Cookie;
13
+ export declare const parse: (cookie: string, name?: string) => Cookie;
14
+ export declare const parseSigned: (cookie: string, secret: string, name?: string) => Promise<SignedCookie>;
13
15
  export declare const serialize: (name: string, value: string, opt?: CookieOptions) => string;
16
+ export declare const serializeSigned: (name: string, value: string, secret: string, opt?: CookieOptions) => Promise<string>;
@@ -2,7 +2,7 @@ import type { Context } from '../context';
2
2
  import type { Env, ValidationTargets, MiddlewareHandler } from '../types';
3
3
  declare type ValidationTargetKeysWithBody = 'form' | 'json';
4
4
  declare type ValidationTargetByMethod<M> = M extends 'get' | 'head' ? Exclude<keyof ValidationTargets, ValidationTargetKeysWithBody> : keyof ValidationTargets;
5
- export declare type ValidationFunction<InputType, OutputType, E extends Env = {}, P extends string = string> = (value: InputType, c: Context<E, P>) => OutputType | Response | Promise<Response>;
5
+ export declare type ValidationFunction<InputType, OutputType, E extends Env = {}, P extends string = string> = (value: InputType, c: Context<E, P>) => OutputType | Response | Promise<OutputType> | Promise<Response>;
6
6
  export declare const validator: <InputType, P extends string, M extends string, U extends ValidationTargetByMethod<M>, OutputType = ValidationTargets[U], P2 extends string = P, V extends {
7
7
  in: { [K in U]: unknown extends InputType ? OutputType : InputType; };
8
8
  out: { [K_1 in U]: OutputType; };
@@ -1,5 +1,5 @@
1
1
  // src/utils/body.ts
2
- async function parseBody(r) {
2
+ var parseBody = async (r) => {
3
3
  let body = {};
4
4
  const contentType = r.headers.get("Content-Type");
5
5
  if (contentType && (contentType.startsWith("multipart/form-data") || contentType.startsWith("application/x-www-form-urlencoded"))) {
@@ -10,7 +10,7 @@ async function parseBody(r) {
10
10
  body = form;
11
11
  }
12
12
  return body;
13
- }
13
+ };
14
14
  export {
15
15
  parseBody
16
16
  };
@@ -1,16 +1,55 @@
1
1
  // src/utils/cookie.ts
2
2
  import { decodeURIComponent_ } from "./url.js";
3
- var parse = (cookie) => {
3
+ var makeSignature = async (value, secret) => {
4
+ const algorithm = { name: "HMAC", hash: "SHA-256" };
5
+ const encoder = new TextEncoder();
6
+ const key = await crypto.subtle.importKey("raw", encoder.encode(secret), algorithm, false, [
7
+ "sign",
8
+ "verify"
9
+ ]);
10
+ const signature = await crypto.subtle.sign(algorithm.name, key, encoder.encode(value));
11
+ return btoa(String.fromCharCode(...new Uint8Array(signature)));
12
+ };
13
+ var _parseCookiePairs = (cookie, name) => {
4
14
  const pairs = cookie.split(/;\s*/g);
15
+ const cookiePairs = pairs.map((pairStr) => pairStr.split(/\s*=\s*([^\s]+)/));
16
+ if (!name)
17
+ return cookiePairs;
18
+ return cookiePairs.filter((pair) => pair[0] === name);
19
+ };
20
+ var parse = (cookie, name) => {
5
21
  const parsedCookie = {};
6
- for (let i = 0, len = pairs.length; i < len; i++) {
7
- const pair = pairs[i].split(/\s*=\s*([^\s]+)/);
8
- parsedCookie[pair[0]] = decodeURIComponent_(pair[1]);
22
+ const unsingedCookies = _parseCookiePairs(cookie, name).filter((pair) => {
23
+ if (pair[1].split(".").length === 2)
24
+ return false;
25
+ return true;
26
+ });
27
+ for (let [key, value] of unsingedCookies) {
28
+ value = decodeURIComponent_(value);
29
+ parsedCookie[key] = value;
9
30
  }
10
31
  return parsedCookie;
11
32
  };
12
- var serialize = (name, value, opt = {}) => {
13
- value = encodeURIComponent(value);
33
+ var parseSigned = async (cookie, secret, name) => {
34
+ const parsedCookie = {};
35
+ const signedCookies = _parseCookiePairs(cookie, name).filter((pair) => {
36
+ if (pair[1].split(".").length !== 2)
37
+ return false;
38
+ return true;
39
+ });
40
+ for (let [key, value] of signedCookies) {
41
+ value = decodeURIComponent_(value);
42
+ const signedPair = value.split(".");
43
+ const signatureToCompare = await makeSignature(signedPair[0], secret);
44
+ if (signedPair[1] !== signatureToCompare) {
45
+ parsedCookie[key] = false;
46
+ continue;
47
+ }
48
+ parsedCookie[key] = signedPair[0];
49
+ }
50
+ return parsedCookie;
51
+ };
52
+ var _serialize = (name, value, opt = {}) => {
14
53
  let cookie = `${name}=${value}`;
15
54
  if (opt && typeof opt.maxAge === "number" && opt.maxAge >= 0) {
16
55
  cookie += `; Max-Age=${Math.floor(opt.maxAge)}`;
@@ -35,7 +74,19 @@ var serialize = (name, value, opt = {}) => {
35
74
  }
36
75
  return cookie;
37
76
  };
77
+ var serialize = (name, value, opt = {}) => {
78
+ value = encodeURIComponent(value);
79
+ return _serialize(name, value, opt);
80
+ };
81
+ var serializeSigned = async (name, value, secret, opt = {}) => {
82
+ const signature = await makeSignature(value, secret);
83
+ value = `${value}.${signature}`;
84
+ value = encodeURIComponent(value);
85
+ return _serialize(name, value, opt);
86
+ };
38
87
  export {
39
88
  parse,
40
- serialize
89
+ parseSigned,
90
+ serialize,
91
+ serializeSigned
41
92
  };
package/dist/utils/url.js CHANGED
@@ -55,9 +55,8 @@ var getPattern = (label) => {
55
55
  return null;
56
56
  };
57
57
  var getPath = (request) => {
58
- const url = request.url;
59
- const queryIndex = url.indexOf("?", 8);
60
- return url.slice(url.indexOf("/", 8), queryIndex === -1 ? void 0 : queryIndex);
58
+ const match = request.url.match(/^https?:\/\/[^/]+(\/[^?]*)/);
59
+ return match ? match[1] : "";
61
60
  };
62
61
  var getQueryStrings = (url) => {
63
62
  const queryIndex = url.indexOf("?", 8);
@@ -104,7 +103,7 @@ var _decodeURI = (value) => {
104
103
  if (value.indexOf("+") !== -1) {
105
104
  value = value.replace(/\+/g, " ");
106
105
  }
107
- return value.indexOf("%") === -1 ? value : decodeURIComponent_(value);
106
+ return /%/.test(value) ? decodeURIComponent_(value) : value;
108
107
  };
109
108
  var _getQueryParam = (url, key, multiple) => {
110
109
  let encoded;
@@ -35,8 +35,8 @@ var validator = (target, validationFunc) => {
35
35
  value = c.req.param();
36
36
  break;
37
37
  }
38
- const res = validationFunc(value, c);
39
- if (res instanceof Response || res instanceof Promise) {
38
+ const res = await validationFunc(value, c);
39
+ if (res instanceof Response) {
40
40
  return res;
41
41
  }
42
42
  c.req.addValidatedData(target, res);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hono",
3
- "version": "3.3.4",
3
+ "version": "3.4.0",
4
4
  "description": "Ultrafast web framework for the Edges",
5
5
  "main": "dist/cjs/index.js",
6
6
  "type": "module",
@@ -14,7 +14,7 @@
14
14
  "test:deno": "env NAME=Deno deno test --allow-read --allow-env runtime_tests/deno",
15
15
  "test:bun": "env NAME=Bun bun test --jsx-import-source ../../src/middleware/jsx runtime_tests/bun/index.test.tsx",
16
16
  "test:fastly": "jest --config ./runtime_tests/fastly/jest.config.js",
17
- "test:lagon": "start-server-and-test \"lagon dev runtime_tests/lagon/index.ts -e runtime_tests/lagon/.env.lagon\" http://127.0.0.1:1234 \"yarn jest runtime_tests/lagon/index.test.ts --testMatch '**/*.test.ts'\"",
17
+ "test:lagon": "start-server-and-test \"lagon dev runtime_tests/lagon/index.ts -e runtime_tests/lagon/.env.lagon\" http://127.0.0.1:1234 \"yarn jest runtime_tests/lagon/index.test.ts --roots runtime_tests/lagon --testMatch '**/*.test.ts'\"",
18
18
  "test:node": "env NAME=Node jest --config ./runtime_tests/node/jest.config.js",
19
19
  "test:wrangler": "jest --config ./runtime_tests/wrangler/jest.config.js",
20
20
  "test:lambda": "env NAME=Node jest --config ./runtime_tests/lambda/jest.config.js",