hono 4.11.7 → 4.12.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 (56) hide show
  1. package/dist/adapter/aws-lambda/conninfo.js +23 -0
  2. package/dist/adapter/aws-lambda/index.js +2 -0
  3. package/dist/adapter/cloudflare-pages/conninfo.js +9 -0
  4. package/dist/adapter/cloudflare-pages/index.js +2 -0
  5. package/dist/adapter/netlify/conninfo.js +9 -0
  6. package/dist/adapter/netlify/mod.js +2 -0
  7. package/dist/cjs/adapter/aws-lambda/conninfo.js +46 -0
  8. package/dist/cjs/adapter/aws-lambda/index.js +3 -0
  9. package/dist/cjs/adapter/cloudflare-pages/conninfo.js +32 -0
  10. package/dist/cjs/adapter/cloudflare-pages/index.js +3 -0
  11. package/dist/cjs/adapter/netlify/conninfo.js +32 -0
  12. package/dist/cjs/adapter/netlify/mod.js +3 -0
  13. package/dist/cjs/client/client.js +8 -2
  14. package/dist/cjs/context.js +11 -7
  15. package/dist/cjs/helper/ssg/index.js +5 -0
  16. package/dist/cjs/helper/ssg/plugins.js +71 -0
  17. package/dist/cjs/helper/ssg/ssg.js +2 -11
  18. package/dist/cjs/jsx/context.js +4 -2
  19. package/dist/cjs/jsx/dom/render.js +7 -9
  20. package/dist/cjs/middleware/basic-auth/index.js +6 -0
  21. package/dist/cjs/middleware/bearer-auth/index.js +1 -1
  22. package/dist/cjs/middleware/language/language.js +13 -2
  23. package/dist/cjs/middleware/trailing-slash/index.js +18 -4
  24. package/dist/cjs/router/trie-router/node.js +33 -16
  25. package/dist/cjs/utils/buffer.js +29 -2
  26. package/dist/cjs/utils/url.js +4 -2
  27. package/dist/client/client.js +8 -2
  28. package/dist/context.js +11 -7
  29. package/dist/helper/ssg/index.js +3 -0
  30. package/dist/helper/ssg/plugins.js +47 -0
  31. package/dist/helper/ssg/ssg.js +2 -10
  32. package/dist/jsx/context.js +4 -2
  33. package/dist/jsx/dom/render.js +7 -9
  34. package/dist/middleware/basic-auth/index.js +6 -0
  35. package/dist/middleware/bearer-auth/index.js +1 -1
  36. package/dist/middleware/language/language.js +13 -2
  37. package/dist/middleware/trailing-slash/index.js +18 -4
  38. package/dist/router/trie-router/node.js +33 -16
  39. package/dist/types/adapter/aws-lambda/conninfo.d.ts +27 -0
  40. package/dist/types/adapter/aws-lambda/index.d.ts +1 -0
  41. package/dist/types/adapter/cloudflare-pages/conninfo.d.ts +21 -0
  42. package/dist/types/adapter/cloudflare-pages/index.d.ts +1 -0
  43. package/dist/types/adapter/netlify/conninfo.d.ts +21 -0
  44. package/dist/types/adapter/netlify/mod.d.ts +1 -0
  45. package/dist/types/client/types.d.ts +39 -1
  46. package/dist/types/context.d.ts +4 -0
  47. package/dist/types/helper/ssg/index.d.ts +1 -0
  48. package/dist/types/helper/ssg/plugins.d.ts +27 -0
  49. package/dist/types/helper/ssg/ssg.d.ts +0 -8
  50. package/dist/types/middleware/basic-auth/index.d.ts +19 -0
  51. package/dist/types/middleware/jwt/jwt.d.ts +1 -1
  52. package/dist/types/middleware/trailing-slash/index.d.ts +43 -2
  53. package/dist/types/utils/buffer.d.ts +10 -1
  54. package/dist/utils/buffer.js +29 -2
  55. package/dist/utils/url.js +4 -2
  56. package/package.json +2 -2
@@ -24,6 +24,12 @@ module.exports = __toCommonJS(node_exports);
24
24
  var import_router = require("../../router");
25
25
  var import_url = require("../../utils/url");
26
26
  const emptyParams = /* @__PURE__ */ Object.create(null);
27
+ const hasChildren = (children) => {
28
+ for (const _ in children) {
29
+ return true;
30
+ }
31
+ return false;
32
+ };
27
33
  class Node {
28
34
  #methods;
29
35
  #children;
@@ -73,8 +79,7 @@ class Node {
73
79
  });
74
80
  return curNode;
75
81
  }
76
- #getHandlerSets(node, method, nodeParams, params) {
77
- const handlerSets = [];
82
+ #pushHandlerSets(handlerSets, node, method, nodeParams, params) {
78
83
  for (let i = 0, len = node.#methods.length; i < len; i++) {
79
84
  const m = node.#methods[i];
80
85
  const handlerSet = m[method] || m[import_router.METHOD_NAME_ALL];
@@ -92,7 +97,6 @@ class Node {
92
97
  }
93
98
  }
94
99
  }
95
- return handlerSets;
96
100
  }
97
101
  search(method, path) {
98
102
  const handlerSets = [];
@@ -101,7 +105,9 @@ class Node {
101
105
  let curNodes = [curNode];
102
106
  const parts = (0, import_url.splitPath)(path);
103
107
  const curNodesQueue = [];
104
- for (let i = 0, len = parts.length; i < len; i++) {
108
+ const len = parts.length;
109
+ let partOffsets = null;
110
+ for (let i = 0; i < len; i++) {
105
111
  const part = parts[i];
106
112
  const isLast = i === len - 1;
107
113
  const tempNodes = [];
@@ -112,11 +118,9 @@ class Node {
112
118
  nextNode.#params = node.#params;
113
119
  if (isLast) {
114
120
  if (nextNode.#children["*"]) {
115
- handlerSets.push(
116
- ...this.#getHandlerSets(nextNode.#children["*"], method, node.#params)
117
- );
121
+ this.#pushHandlerSets(handlerSets, nextNode.#children["*"], method, node.#params);
118
122
  }
119
- handlerSets.push(...this.#getHandlerSets(nextNode, method, node.#params));
123
+ this.#pushHandlerSets(handlerSets, nextNode, method, node.#params);
120
124
  } else {
121
125
  tempNodes.push(nextNode);
122
126
  }
@@ -127,7 +131,7 @@ class Node {
127
131
  if (pattern === "*") {
128
132
  const astNode = node.#children["*"];
129
133
  if (astNode) {
130
- handlerSets.push(...this.#getHandlerSets(astNode, method, node.#params));
134
+ this.#pushHandlerSets(handlerSets, astNode, method, node.#params);
131
135
  astNode.#params = params;
132
136
  tempNodes.push(astNode);
133
137
  }
@@ -138,13 +142,21 @@ class Node {
138
142
  continue;
139
143
  }
140
144
  const child = node.#children[key];
141
- const restPathString = parts.slice(i).join("/");
142
145
  if (matcher instanceof RegExp) {
146
+ if (partOffsets === null) {
147
+ partOffsets = new Array(len);
148
+ let offset = path[0] === "/" ? 1 : 0;
149
+ for (let p = 0; p < len; p++) {
150
+ partOffsets[p] = offset;
151
+ offset += parts[p].length + 1;
152
+ }
153
+ }
154
+ const restPathString = path.substring(partOffsets[i]);
143
155
  const m = matcher.exec(restPathString);
144
156
  if (m) {
145
157
  params[name] = m[0];
146
- handlerSets.push(...this.#getHandlerSets(child, method, node.#params, params));
147
- if (Object.keys(child.#children).length) {
158
+ this.#pushHandlerSets(handlerSets, child, method, node.#params, params);
159
+ if (hasChildren(child.#children)) {
148
160
  child.#params = params;
149
161
  const componentCount = m[0].match(/\//)?.length ?? 0;
150
162
  const targetCurNodes = curNodesQueue[componentCount] ||= [];
@@ -156,10 +168,14 @@ class Node {
156
168
  if (matcher === true || matcher.test(part)) {
157
169
  params[name] = part;
158
170
  if (isLast) {
159
- handlerSets.push(...this.#getHandlerSets(child, method, params, node.#params));
171
+ this.#pushHandlerSets(handlerSets, child, method, params, node.#params);
160
172
  if (child.#children["*"]) {
161
- handlerSets.push(
162
- ...this.#getHandlerSets(child.#children["*"], method, params, node.#params)
173
+ this.#pushHandlerSets(
174
+ handlerSets,
175
+ child.#children["*"],
176
+ method,
177
+ params,
178
+ node.#params
163
179
  );
164
180
  }
165
181
  } else {
@@ -169,7 +185,8 @@ class Node {
169
185
  }
170
186
  }
171
187
  }
172
- curNodes = tempNodes.concat(curNodesQueue.shift() ?? []);
188
+ const shifted = curNodesQueue.shift();
189
+ curNodes = shifted ? tempNodes.concat(shifted) : tempNodes;
173
190
  }
174
191
  if (handlerSets.length > 1) {
175
192
  handlerSets.sort((a, b) => {
@@ -42,15 +42,42 @@ const equal = (a, b) => {
42
42
  }
43
43
  return true;
44
44
  };
45
+ const constantTimeEqualString = (a, b) => {
46
+ const aLen = a.length;
47
+ const bLen = b.length;
48
+ const maxLen = Math.max(aLen, bLen);
49
+ let out = aLen ^ bLen;
50
+ for (let i = 0; i < maxLen; i++) {
51
+ const aChar = i < aLen ? a.charCodeAt(i) : 0;
52
+ const bChar = i < bLen ? b.charCodeAt(i) : 0;
53
+ out |= aChar ^ bChar;
54
+ }
55
+ return out === 0;
56
+ };
57
+ const timingSafeEqualString = async (a, b, hashFunction) => {
58
+ if (!hashFunction) {
59
+ hashFunction = import_crypto.sha256;
60
+ }
61
+ const [sa, sb] = await Promise.all([hashFunction(a), hashFunction(b)]);
62
+ if (sa == null || sb == null || typeof sa !== "string" || typeof sb !== "string") {
63
+ return false;
64
+ }
65
+ const hashEqual = constantTimeEqualString(sa, sb);
66
+ const originalEqual = constantTimeEqualString(a, b);
67
+ return hashEqual && originalEqual;
68
+ };
45
69
  const timingSafeEqual = async (a, b, hashFunction) => {
70
+ if (typeof a === "string" && typeof b === "string") {
71
+ return timingSafeEqualString(a, b, hashFunction);
72
+ }
46
73
  if (!hashFunction) {
47
74
  hashFunction = import_crypto.sha256;
48
75
  }
49
76
  const [sa, sb] = await Promise.all([hashFunction(a), hashFunction(b)]);
50
- if (!sa || !sb) {
77
+ if (!sa || !sb || typeof sa !== "string" || typeof sb !== "string") {
51
78
  return false;
52
79
  }
53
- return sa === sb && a === b;
80
+ return timingSafeEqualString(sa, sb);
54
81
  };
55
82
  const bufferToString = (buffer) => {
56
83
  if (buffer instanceof ArrayBuffer) {
@@ -106,9 +106,11 @@ const getPath = (request) => {
106
106
  const charCode = url.charCodeAt(i);
107
107
  if (charCode === 37) {
108
108
  const queryIndex = url.indexOf("?", i);
109
- const path = url.slice(start, queryIndex === -1 ? void 0 : queryIndex);
109
+ const hashIndex = url.indexOf("#", i);
110
+ const end = queryIndex === -1 ? hashIndex === -1 ? void 0 : hashIndex : hashIndex === -1 ? queryIndex : Math.min(queryIndex, hashIndex);
111
+ const path = url.slice(start, end);
110
112
  return tryDecodeURI(path.includes("%25") ? path.replace(/%25/g, "%2525") : path);
111
- } else if (charCode === 63) {
113
+ } else if (charCode === 63 || charCode === 35) {
112
114
  break;
113
115
  }
114
116
  }
@@ -47,6 +47,9 @@ var ClientRequestImpl = class {
47
47
  if (args.form) {
48
48
  const form = new FormData();
49
49
  for (const [k, v] of Object.entries(args.form)) {
50
+ if (v === void 0) {
51
+ continue;
52
+ }
50
53
  if (Array.isArray(v)) {
51
54
  for (const v2 of v) {
52
55
  form.append(k, v2);
@@ -122,7 +125,7 @@ var hc = (baseUrl, options) => createProxy(function proxyCallback(opts) {
122
125
  }
123
126
  const path = parts.join("/");
124
127
  const url = mergePath(baseUrl, path);
125
- if (method === "url") {
128
+ if (method === "url" || method === "path") {
126
129
  let result = url;
127
130
  if (opts.args[0]) {
128
131
  if (opts.args[0].param) {
@@ -133,7 +136,10 @@ var hc = (baseUrl, options) => createProxy(function proxyCallback(opts) {
133
136
  }
134
137
  }
135
138
  result = removeIndexString(result);
136
- return new URL(result);
139
+ if (method === "url") {
140
+ return new URL(result);
141
+ }
142
+ return result.slice(baseUrl.replace(/\/+$/, "").length).replace(/^\/?/, "/");
137
143
  }
138
144
  if (method === "ws") {
139
145
  const webSocketUrl = replaceUrlProtocol(
package/dist/context.js CHANGED
@@ -8,6 +8,7 @@ var setDefaultContentType = (contentType, headers) => {
8
8
  ...headers
9
9
  };
10
10
  };
11
+ var createResponseInstance = (body, init) => new Response(body, init);
11
12
  var Context = class {
12
13
  #rawRequest;
13
14
  #req;
@@ -106,7 +107,7 @@ var Context = class {
106
107
  * The Response object for the current request.
107
108
  */
108
109
  get res() {
109
- return this.#res ||= new Response(null, {
110
+ return this.#res ||= createResponseInstance(null, {
110
111
  headers: this.#preparedHeaders ??= new Headers()
111
112
  });
112
113
  }
@@ -117,7 +118,7 @@ var Context = class {
117
118
  */
118
119
  set res(_res) {
119
120
  if (this.#res && _res) {
120
- _res = new Response(_res.body, _res);
121
+ _res = createResponseInstance(_res.body, _res);
121
122
  for (const [k, v] of this.#res.headers.entries()) {
122
123
  if (k === "content-type") {
123
124
  continue;
@@ -207,7 +208,7 @@ var Context = class {
207
208
  */
208
209
  header = (name, value, options) => {
209
210
  if (this.finalized) {
210
- this.#res = new Response(this.#res.body, this.#res);
211
+ this.#res = createResponseInstance(this.#res.body, this.#res);
211
212
  }
212
213
  const headers = this.#res ? this.#res.headers : this.#preparedHeaders ??= new Headers();
213
214
  if (value === void 0) {
@@ -296,7 +297,7 @@ var Context = class {
296
297
  }
297
298
  }
298
299
  const status = typeof arg === "number" ? arg : arg?.status ?? this.#status;
299
- return new Response(data, { status, headers: responseHeaders });
300
+ return createResponseInstance(data, { status, headers: responseHeaders });
300
301
  }
301
302
  newResponse = (...args) => this.#newResponse(...args);
302
303
  /**
@@ -321,6 +322,9 @@ var Context = class {
321
322
  * ```
322
323
  */
323
324
  body = (data, arg, headers) => this.#newResponse(data, arg, headers);
325
+ #useFastPath() {
326
+ return !this.#preparedHeaders && !this.#status && !this.finalized;
327
+ }
324
328
  /**
325
329
  * `.text()` can render text as `Content-Type:text/plain`.
326
330
  *
@@ -334,7 +338,7 @@ var Context = class {
334
338
  * ```
335
339
  */
336
340
  text = (text, arg, headers) => {
337
- return !this.#preparedHeaders && !this.#status && !arg && !headers && !this.finalized ? new Response(text) : this.#newResponse(
341
+ return this.#useFastPath() && !arg && !headers ? createResponseInstance(text) : this.#newResponse(
338
342
  text,
339
343
  arg,
340
344
  setDefaultContentType(TEXT_PLAIN, headers)
@@ -353,7 +357,7 @@ var Context = class {
353
357
  * ```
354
358
  */
355
359
  json = (object, arg, headers) => {
356
- return this.#newResponse(
360
+ return this.#useFastPath() && !arg && !headers ? Response.json(object) : this.#newResponse(
357
361
  JSON.stringify(object),
358
362
  arg,
359
363
  setDefaultContentType("application/json", headers)
@@ -401,7 +405,7 @@ var Context = class {
401
405
  * ```
402
406
  */
403
407
  notFound = () => {
404
- this.#notFoundHandler ??= () => new Response();
408
+ this.#notFoundHandler ??= () => createResponseInstance();
405
409
  return this.#notFoundHandler(this);
406
410
  };
407
411
  };
@@ -7,10 +7,13 @@ import {
7
7
  disableSSG,
8
8
  onlySSG
9
9
  } from "./middleware.js";
10
+ import { defaultPlugin, redirectPlugin } from "./plugins.js";
10
11
  export {
11
12
  X_HONO_DISABLE_SSG_HEADER_KEY,
13
+ defaultPlugin,
12
14
  disableSSG,
13
15
  isSSGContext,
14
16
  onlySSG,
17
+ redirectPlugin,
15
18
  ssgParams
16
19
  };
@@ -0,0 +1,47 @@
1
+ // src/helper/ssg/plugins.ts
2
+ import { html } from "../html/index.js";
3
+ var defaultPlugin = () => {
4
+ return {
5
+ afterResponseHook: (res) => {
6
+ if (res.status !== 200) {
7
+ return false;
8
+ }
9
+ return res;
10
+ }
11
+ };
12
+ };
13
+ var REDIRECT_STATUS_CODES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
14
+ var generateRedirectHtml = (location) => {
15
+ const content = html`<!DOCTYPE html>
16
+ <title>Redirecting to: ${location}</title>
17
+ <meta http-equiv="refresh" content="0;url=${location}" />
18
+ <meta name="robots" content="noindex" />
19
+ <link rel="canonical" href="${location}" />
20
+ <body>
21
+ <a href="${location}">Redirecting to <code>${location}</code></a>
22
+ </body>
23
+ `;
24
+ return content.toString().replace(/\n/g, "");
25
+ };
26
+ var redirectPlugin = () => {
27
+ return {
28
+ afterResponseHook: (res) => {
29
+ if (REDIRECT_STATUS_CODES.has(res.status)) {
30
+ const location = res.headers.get("Location");
31
+ if (!location) {
32
+ return false;
33
+ }
34
+ const htmlBody = generateRedirectHtml(location);
35
+ return new Response(htmlBody, {
36
+ status: 200,
37
+ headers: { "Content-Type": "text/html; charset=utf-8" }
38
+ });
39
+ }
40
+ return res;
41
+ }
42
+ };
43
+ };
44
+ export {
45
+ defaultPlugin,
46
+ redirectPlugin
47
+ };
@@ -3,6 +3,7 @@ import { replaceUrlParam } from "../../client/utils.js";
3
3
  import { createPool } from "../../utils/concurrent.js";
4
4
  import { getExtension } from "../../utils/mime.js";
5
5
  import { SSG_CONTEXT, X_HONO_DISABLE_SSG_HEADER_KEY } from "./middleware.js";
6
+ import { defaultPlugin } from "./plugins.js";
6
7
  import { dirname, filterStaticGenerateRoutes, isDynamicRoute, joinPaths } from "./utils.js";
7
8
  var DEFAULT_CONCURRENCY = 2;
8
9
  var DEFAULT_CONTENT_TYPE = "text/plain";
@@ -184,19 +185,11 @@ var saveContentToFile = async (data, fsModule, outDir, extensionMap) => {
184
185
  }
185
186
  return filePath;
186
187
  };
187
- var defaultPlugin = {
188
- afterResponseHook: (res) => {
189
- if (res.status !== 200) {
190
- return false;
191
- }
192
- return res;
193
- }
194
- };
195
188
  var toSSG = async (app, fs, options) => {
196
189
  let result;
197
190
  const getInfoPromises = [];
198
191
  const savePromises = [];
199
- const plugins = options?.plugins || [defaultPlugin];
192
+ const plugins = options?.plugins || [defaultPlugin()];
200
193
  const beforeRequestHooks = [];
201
194
  const afterResponseHooks = [];
202
195
  const afterGenerateHooks = [];
@@ -288,7 +281,6 @@ export {
288
281
  combineAfterResponseHooks,
289
282
  combineBeforeRequestHooks,
290
283
  defaultExtensionMap,
291
- defaultPlugin,
292
284
  fetchRoutesContent,
293
285
  saveContentToFile,
294
286
  toSSG
@@ -11,12 +11,14 @@ var createContext = (defaultValue) => {
11
11
  let string;
12
12
  try {
13
13
  string = props.children ? (Array.isArray(props.children) ? new JSXFragmentNode("", {}, props.children) : props.children).toString() : "";
14
- } finally {
14
+ } catch (e) {
15
15
  values.pop();
16
+ throw e;
16
17
  }
17
18
  if (string instanceof Promise) {
18
- return string.then((resString) => raw(resString, resString.callbacks));
19
+ return string.finally(() => values.pop()).then((resString) => raw(resString, resString.callbacks));
19
20
  } else {
21
+ values.pop();
20
22
  return raw(string);
21
23
  }
22
24
  });
@@ -184,14 +184,10 @@ var getNextChildren = (node, container, nextChildren, childrenToRemove, callback
184
184
  });
185
185
  };
186
186
  var findInsertBefore = (node) => {
187
- for (; ; node = node.tag === HONO_PORTAL_ELEMENT || !node.vC || !node.pP ? node.nN : node.vC[0]) {
188
- if (!node) {
189
- return null;
190
- }
191
- if (node.tag !== HONO_PORTAL_ELEMENT && node.e) {
192
- return node.e;
193
- }
187
+ while (node && (node.tag === HONO_PORTAL_ELEMENT || !node.e)) {
188
+ node = node.tag === HONO_PORTAL_ELEMENT || !node.vC?.[0] ? node.nN : node.vC[0];
194
189
  }
190
+ return node?.e;
195
191
  };
196
192
  var removeNode = (node) => {
197
193
  if (!isNodeString(node)) {
@@ -289,7 +285,7 @@ var applyNodeObject = (node, container, isNew) => {
289
285
  }
290
286
  }
291
287
  if (node.pP) {
292
- delete node.pP;
288
+ node.pP = void 0;
293
289
  }
294
290
  if (callbacks.length) {
295
291
  const useLayoutEffectCbs = [];
@@ -330,7 +326,9 @@ var build = (context, node, children) => {
330
326
  let prevNode;
331
327
  for (let i = 0; i < children.length; i++) {
332
328
  if (Array.isArray(children[i])) {
333
- children.splice(i, 1, ...children[i].flat());
329
+ children.splice(i, 1, ...children[i].flat(Infinity));
330
+ i--;
331
+ continue;
334
332
  }
335
333
  let child = buildNode(children[i]);
336
334
  if (child) {
@@ -24,6 +24,9 @@ var basicAuth = (options, ...users) => {
24
24
  if (requestUser) {
25
25
  if (verifyUserInOptions) {
26
26
  if (await options.verifyUser(requestUser.username, requestUser.password, ctx)) {
27
+ if (options.onAuthSuccess) {
28
+ await options.onAuthSuccess(ctx, requestUser.username);
29
+ }
27
30
  await next();
28
31
  return;
29
32
  }
@@ -34,6 +37,9 @@ var basicAuth = (options, ...users) => {
34
37
  timingSafeEqual(user.password, requestUser.password, options.hashFunction)
35
38
  ]);
36
39
  if (usernameEqual && passwordEqual) {
40
+ if (options.onAuthSuccess) {
41
+ await options.onAuthSuccess(ctx, requestUser.username);
42
+ }
37
43
  await next();
38
44
  return;
39
45
  }
@@ -16,7 +16,7 @@ var bearerAuth = (options) => {
16
16
  }
17
17
  const realm = options.realm?.replace(/"/g, '\\"');
18
18
  const prefixRegexStr = options.prefix === "" ? "" : `${options.prefix} +`;
19
- const regexp = new RegExp(`^${prefixRegexStr}(${TOKEN_STRINGS}) *$`);
19
+ const regexp = new RegExp(`^${prefixRegexStr}(${TOKEN_STRINGS}) *$`, "i");
20
20
  const wwwAuthenticatePrefix = options.prefix === "" ? "" : `${options.prefix} `;
21
21
  const throwHTTPException = async (c, status, wwwAuthenticateHeader, messageOption) => {
22
22
  const wwwAuthenticateHeaderValue = typeof wwwAuthenticateHeader === "function" ? await wwwAuthenticateHeader(c) : wwwAuthenticateHeader;
@@ -35,8 +35,19 @@ var normalizeLanguage = (lang, options) => {
35
35
  const compSupported = options.supportedLanguages.map(
36
36
  (l) => options.ignoreCase ? l.toLowerCase() : l
37
37
  );
38
- const matchedLang = compSupported.find((l) => l === compLang);
39
- return matchedLang ? options.supportedLanguages[compSupported.indexOf(matchedLang)] : void 0;
38
+ const exactIndex = compSupported.indexOf(compLang);
39
+ if (exactIndex !== -1) {
40
+ return options.supportedLanguages[exactIndex];
41
+ }
42
+ const parts = compLang.split("-");
43
+ for (let i = parts.length - 1; i > 0; i--) {
44
+ const candidate = parts.slice(0, i).join("-");
45
+ const prefixIndex = compSupported.indexOf(candidate);
46
+ if (prefixIndex !== -1) {
47
+ return options.supportedLanguages[prefixIndex];
48
+ }
49
+ }
50
+ return void 0;
40
51
  } catch {
41
52
  return void 0;
42
53
  }
@@ -1,18 +1,32 @@
1
1
  // src/middleware/trailing-slash/index.ts
2
- var trimTrailingSlash = () => {
2
+ var trimTrailingSlash = (options) => {
3
3
  return async function trimTrailingSlash2(c, next) {
4
+ if (options?.alwaysRedirect) {
5
+ if ((c.req.method === "GET" || c.req.method === "HEAD") && c.req.path !== "/" && c.req.path.at(-1) === "/") {
6
+ const url = new URL(c.req.url);
7
+ url.pathname = url.pathname.substring(0, url.pathname.length - 1);
8
+ return c.redirect(url.toString(), 301);
9
+ }
10
+ }
4
11
  await next();
5
- if (c.res.status === 404 && (c.req.method === "GET" || c.req.method === "HEAD") && c.req.path !== "/" && c.req.path.at(-1) === "/") {
12
+ if (!options?.alwaysRedirect && c.res.status === 404 && (c.req.method === "GET" || c.req.method === "HEAD") && c.req.path !== "/" && c.req.path.at(-1) === "/") {
6
13
  const url = new URL(c.req.url);
7
14
  url.pathname = url.pathname.substring(0, url.pathname.length - 1);
8
15
  c.res = c.redirect(url.toString(), 301);
9
16
  }
10
17
  };
11
18
  };
12
- var appendTrailingSlash = () => {
19
+ var appendTrailingSlash = (options) => {
13
20
  return async function appendTrailingSlash2(c, next) {
21
+ if (options?.alwaysRedirect) {
22
+ if ((c.req.method === "GET" || c.req.method === "HEAD") && c.req.path.at(-1) !== "/") {
23
+ const url = new URL(c.req.url);
24
+ url.pathname += "/";
25
+ return c.redirect(url.toString(), 301);
26
+ }
27
+ }
14
28
  await next();
15
- if (c.res.status === 404 && (c.req.method === "GET" || c.req.method === "HEAD") && c.req.path.at(-1) !== "/") {
29
+ if (!options?.alwaysRedirect && c.res.status === 404 && (c.req.method === "GET" || c.req.method === "HEAD") && c.req.path.at(-1) !== "/") {
16
30
  const url = new URL(c.req.url);
17
31
  url.pathname += "/";
18
32
  c.res = c.redirect(url.toString(), 301);
@@ -2,6 +2,12 @@
2
2
  import { METHOD_NAME_ALL } from "../../router.js";
3
3
  import { getPattern, splitPath, splitRoutingPath } from "../../utils/url.js";
4
4
  var emptyParams = /* @__PURE__ */ Object.create(null);
5
+ var hasChildren = (children) => {
6
+ for (const _ in children) {
7
+ return true;
8
+ }
9
+ return false;
10
+ };
5
11
  var Node = class _Node {
6
12
  #methods;
7
13
  #children;
@@ -51,8 +57,7 @@ var Node = class _Node {
51
57
  });
52
58
  return curNode;
53
59
  }
54
- #getHandlerSets(node, method, nodeParams, params) {
55
- const handlerSets = [];
60
+ #pushHandlerSets(handlerSets, node, method, nodeParams, params) {
56
61
  for (let i = 0, len = node.#methods.length; i < len; i++) {
57
62
  const m = node.#methods[i];
58
63
  const handlerSet = m[method] || m[METHOD_NAME_ALL];
@@ -70,7 +75,6 @@ var Node = class _Node {
70
75
  }
71
76
  }
72
77
  }
73
- return handlerSets;
74
78
  }
75
79
  search(method, path) {
76
80
  const handlerSets = [];
@@ -79,7 +83,9 @@ var Node = class _Node {
79
83
  let curNodes = [curNode];
80
84
  const parts = splitPath(path);
81
85
  const curNodesQueue = [];
82
- for (let i = 0, len = parts.length; i < len; i++) {
86
+ const len = parts.length;
87
+ let partOffsets = null;
88
+ for (let i = 0; i < len; i++) {
83
89
  const part = parts[i];
84
90
  const isLast = i === len - 1;
85
91
  const tempNodes = [];
@@ -90,11 +96,9 @@ var Node = class _Node {
90
96
  nextNode.#params = node.#params;
91
97
  if (isLast) {
92
98
  if (nextNode.#children["*"]) {
93
- handlerSets.push(
94
- ...this.#getHandlerSets(nextNode.#children["*"], method, node.#params)
95
- );
99
+ this.#pushHandlerSets(handlerSets, nextNode.#children["*"], method, node.#params);
96
100
  }
97
- handlerSets.push(...this.#getHandlerSets(nextNode, method, node.#params));
101
+ this.#pushHandlerSets(handlerSets, nextNode, method, node.#params);
98
102
  } else {
99
103
  tempNodes.push(nextNode);
100
104
  }
@@ -105,7 +109,7 @@ var Node = class _Node {
105
109
  if (pattern === "*") {
106
110
  const astNode = node.#children["*"];
107
111
  if (astNode) {
108
- handlerSets.push(...this.#getHandlerSets(astNode, method, node.#params));
112
+ this.#pushHandlerSets(handlerSets, astNode, method, node.#params);
109
113
  astNode.#params = params;
110
114
  tempNodes.push(astNode);
111
115
  }
@@ -116,13 +120,21 @@ var Node = class _Node {
116
120
  continue;
117
121
  }
118
122
  const child = node.#children[key];
119
- const restPathString = parts.slice(i).join("/");
120
123
  if (matcher instanceof RegExp) {
124
+ if (partOffsets === null) {
125
+ partOffsets = new Array(len);
126
+ let offset = path[0] === "/" ? 1 : 0;
127
+ for (let p = 0; p < len; p++) {
128
+ partOffsets[p] = offset;
129
+ offset += parts[p].length + 1;
130
+ }
131
+ }
132
+ const restPathString = path.substring(partOffsets[i]);
121
133
  const m = matcher.exec(restPathString);
122
134
  if (m) {
123
135
  params[name] = m[0];
124
- handlerSets.push(...this.#getHandlerSets(child, method, node.#params, params));
125
- if (Object.keys(child.#children).length) {
136
+ this.#pushHandlerSets(handlerSets, child, method, node.#params, params);
137
+ if (hasChildren(child.#children)) {
126
138
  child.#params = params;
127
139
  const componentCount = m[0].match(/\//)?.length ?? 0;
128
140
  const targetCurNodes = curNodesQueue[componentCount] ||= [];
@@ -134,10 +146,14 @@ var Node = class _Node {
134
146
  if (matcher === true || matcher.test(part)) {
135
147
  params[name] = part;
136
148
  if (isLast) {
137
- handlerSets.push(...this.#getHandlerSets(child, method, params, node.#params));
149
+ this.#pushHandlerSets(handlerSets, child, method, params, node.#params);
138
150
  if (child.#children["*"]) {
139
- handlerSets.push(
140
- ...this.#getHandlerSets(child.#children["*"], method, params, node.#params)
151
+ this.#pushHandlerSets(
152
+ handlerSets,
153
+ child.#children["*"],
154
+ method,
155
+ params,
156
+ node.#params
141
157
  );
142
158
  }
143
159
  } else {
@@ -147,7 +163,8 @@ var Node = class _Node {
147
163
  }
148
164
  }
149
165
  }
150
- curNodes = tempNodes.concat(curNodesQueue.shift() ?? []);
166
+ const shifted = curNodesQueue.shift();
167
+ curNodes = shifted ? tempNodes.concat(shifted) : tempNodes;
151
168
  }
152
169
  if (handlerSets.length > 1) {
153
170
  handlerSets.sort((a, b) => {
@@ -0,0 +1,27 @@
1
+ import type { GetConnInfo } from '../../helper/conninfo';
2
+ /**
3
+ * Get connection information from AWS Lambda
4
+ *
5
+ * Extracts client IP from various Lambda event sources:
6
+ * - API Gateway v1 (REST API): requestContext.identity.sourceIp
7
+ * - API Gateway v2 (HTTP API/Function URLs): requestContext.http.sourceIp
8
+ * - ALB: Falls back to x-forwarded-for header
9
+ *
10
+ * @param c - Context
11
+ * @returns Connection information including remote address
12
+ * @example
13
+ * ```ts
14
+ * import { Hono } from 'hono'
15
+ * import { handle, getConnInfo } from 'hono/aws-lambda'
16
+ *
17
+ * const app = new Hono()
18
+ *
19
+ * app.get('/', (c) => {
20
+ * const info = getConnInfo(c)
21
+ * return c.text(`Your IP: ${info.remote.address}`)
22
+ * })
23
+ *
24
+ * export const handler = handle(app)
25
+ * ```
26
+ */
27
+ export declare const getConnInfo: GetConnInfo;