@suveren/gateway 0.6.4 → 0.6.7

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 (75) hide show
  1. package/bin/suveren-gateway.js +19 -2
  2. package/content/integrations/deploy-github.json +2 -2
  3. package/dist/mcp-server/http.mjs +6 -1
  4. package/lib/autostart-templates.d.mts +2 -0
  5. package/lib/autostart-templates.mjs +16 -3
  6. package/node_modules/@hono/node-server/README.md +47 -1
  7. package/node_modules/@hono/node-server/dist/early-hints.cjs +35 -0
  8. package/node_modules/@hono/node-server/dist/early-hints.d.cts +16 -0
  9. package/node_modules/@hono/node-server/dist/early-hints.d.mts +16 -0
  10. package/node_modules/@hono/node-server/dist/early-hints.mjs +33 -0
  11. package/node_modules/@hono/node-server/dist/index.cjs +4 -1
  12. package/node_modules/@hono/node-server/dist/index.mjs +4 -1
  13. package/node_modules/@hono/node-server/package.json +14 -1
  14. package/node_modules/express-rate-limit/dist/index.cjs +6 -1
  15. package/node_modules/express-rate-limit/dist/index.mjs +6 -1
  16. package/node_modules/express-rate-limit/package.json +6 -6
  17. package/node_modules/hono/dist/cjs/context.js +28 -13
  18. package/node_modules/hono/dist/cjs/hono-base.js +3 -2
  19. package/node_modules/hono/dist/cjs/jsx/base.js +26 -14
  20. package/node_modules/hono/dist/cjs/jsx/hooks/index.js +2 -2
  21. package/node_modules/hono/dist/cjs/middleware/cache/index.js +103 -8
  22. package/node_modules/hono/dist/cjs/middleware/compress/index.js +5 -0
  23. package/node_modules/hono/dist/cjs/middleware/cors/index.js +1 -1
  24. package/node_modules/hono/dist/cjs/middleware/etag/index.js +1 -1
  25. package/node_modules/hono/dist/cjs/middleware/jwk/jwk.js +9 -4
  26. package/node_modules/hono/dist/cjs/middleware/jwt/jwt.js +9 -4
  27. package/node_modules/hono/dist/cjs/middleware/method-not-allowed/index.js +90 -0
  28. package/node_modules/hono/dist/cjs/request.js +6 -8
  29. package/node_modules/hono/dist/cjs/router/reg-exp-router/node.js +55 -56
  30. package/node_modules/hono/dist/cjs/router/reg-exp-router/router.js +64 -85
  31. package/node_modules/hono/dist/cjs/router/reg-exp-router/trie.js +13 -5
  32. package/node_modules/hono/dist/cjs/router.js +1 -1
  33. package/node_modules/hono/dist/cjs/utils/cookie.js +1 -1
  34. package/node_modules/hono/dist/cjs/utils/url.js +7 -7
  35. package/node_modules/hono/dist/context.js +28 -13
  36. package/node_modules/hono/dist/hono-base.js +3 -2
  37. package/node_modules/hono/dist/jsx/base.js +26 -14
  38. package/node_modules/hono/dist/jsx/hooks/index.js +2 -2
  39. package/node_modules/hono/dist/middleware/cache/index.js +103 -8
  40. package/node_modules/hono/dist/middleware/compress/index.js +5 -0
  41. package/node_modules/hono/dist/middleware/cors/index.js +1 -1
  42. package/node_modules/hono/dist/middleware/etag/index.js +1 -1
  43. package/node_modules/hono/dist/middleware/jwk/jwk.js +9 -4
  44. package/node_modules/hono/dist/middleware/jwt/jwt.js +9 -4
  45. package/node_modules/hono/dist/middleware/method-not-allowed/index.js +68 -0
  46. package/node_modules/hono/dist/request.js +7 -9
  47. package/node_modules/hono/dist/router/reg-exp-router/node.js +55 -56
  48. package/node_modules/hono/dist/router/reg-exp-router/router.js +64 -85
  49. package/node_modules/hono/dist/router/reg-exp-router/trie.js +13 -5
  50. package/node_modules/hono/dist/router.js +1 -1
  51. package/node_modules/hono/dist/types/client/types.d.ts +1 -1
  52. package/node_modules/hono/dist/types/hono-base.d.ts +4 -3
  53. package/node_modules/hono/dist/types/jsx/base.d.ts +4 -2
  54. package/node_modules/hono/dist/types/jsx/dom/index.d.ts +5 -5
  55. package/node_modules/hono/dist/types/jsx/dom/intrinsic-element/components.d.ts +2 -2
  56. package/node_modules/hono/dist/types/jsx/dom/server.d.ts +5 -5
  57. package/node_modules/hono/dist/types/jsx/hooks/index.d.ts +8 -6
  58. package/node_modules/hono/dist/types/jsx/index.d.ts +5 -5
  59. package/node_modules/hono/dist/types/middleware/cache/index.d.ts +6 -4
  60. package/node_modules/hono/dist/types/middleware/cors/index.d.ts +1 -1
  61. package/node_modules/hono/dist/types/middleware/jsx-renderer/index.d.ts +2 -2
  62. package/node_modules/hono/dist/types/middleware/jwk/jwk.d.ts +2 -0
  63. package/node_modules/hono/dist/types/middleware/jwt/jwt.d.ts +2 -0
  64. package/node_modules/hono/dist/types/middleware/method-not-allowed/index.d.ts +49 -0
  65. package/node_modules/hono/dist/types/router/reg-exp-router/node.d.ts +1 -1
  66. package/node_modules/hono/dist/types/router/reg-exp-router/trie.d.ts +2 -1
  67. package/node_modules/hono/dist/types/router.d.ts +1 -1
  68. package/node_modules/hono/dist/types/utils/headers.d.ts +2 -2
  69. package/node_modules/hono/dist/types/utils/url.d.ts +1 -0
  70. package/node_modules/hono/dist/utils/cookie.js +2 -2
  71. package/node_modules/hono/dist/utils/url.js +5 -6
  72. package/node_modules/hono/package.json +9 -1
  73. package/package.json +1 -1
  74. package/profiles/deploy/0.8.profile.json +170 -0
  75. package/profiles/index.json +2 -1
@@ -13,7 +13,7 @@
13
13
  import { spawn, spawnSync } from 'node:child_process';
14
14
  import { createConnection } from 'node:net';
15
15
  import { existsSync, mkdirSync, openSync, readFileSync, writeFileSync, unlinkSync, statSync } from 'node:fs';
16
- import { homedir, platform } from 'node:os';
16
+ import { homedir, platform, userInfo } from 'node:os';
17
17
  import { dirname, join, resolve } from 'node:path';
18
18
  import { fileURLToPath } from 'node:url';
19
19
  import { buildLaunchAgentPlist, buildMacLauncher, buildSystemdUnit, buildWindowsTaskXml } from '../lib/autostart-templates.mjs';
@@ -500,6 +500,22 @@ async function serviceStatusMac() {
500
500
 
501
501
  const WIN_TASK_NAME = 'Suveren';
502
502
 
503
+ /**
504
+ * Who the login task runs as, as `DOMAIN\user` when a domain is known.
505
+ *
506
+ * Without this the task is registered for ANY user's logon, which Windows only
507
+ * lets an administrator do — a standard user just saw "Zugriff verweigert" and
508
+ * had no way to install autostart at all. USERDOMAIN is the machine name on a
509
+ * local account and the AD domain on a joined one; both are correct here.
510
+ * Falling back to the bare username is fine — Task Scheduler resolves it
511
+ * against the local machine.
512
+ */
513
+ function windowsUserId() {
514
+ const name = process.env.USERNAME || userInfo().username;
515
+ const domain = process.env.USERDOMAIN;
516
+ return domain ? `${domain}\\${name}` : name;
517
+ }
518
+
503
519
  async function serviceInstallWindows() {
504
520
  ensureDataDir();
505
521
 
@@ -513,6 +529,7 @@ async function serviceInstallWindows() {
513
529
  serverEntry: SERVER_ENTRY,
514
530
  author: 'Suveren',
515
531
  dataDir: process.env.SUVEREN_DATA_DIR ?? '',
532
+ userId: windowsUserId(),
516
533
  });
517
534
  const xmlPath = join(DATA_DIR, 'suveren-task.xml');
518
535
  writeFileSync(xmlPath, '\ufeff' + xml, { encoding: 'utf16le' });
@@ -766,7 +783,7 @@ Usage:
766
783
  suveren-gateway status Show running state + health
767
784
  suveren-gateway logs [--tail] Print or tail ~/.suveren/gateway.log
768
785
  suveren-gateway service <cmd> Run as a login service that survives reboot
769
- (install | uninstall | status; macOS today)
786
+ (install | uninstall | status)
770
787
  suveren-gateway help Print this help
771
788
 
772
789
  Environment:
@@ -79,10 +79,10 @@
79
79
  "staticExecution": {
80
80
  "action_type": "release"
81
81
  },
82
- "contentField": "deployment_url",
82
+ "contentField": "commit",
83
83
  "actionLabel": "Deployment released",
84
84
  "argNormalization": {
85
- "deployment_url": "url"
85
+ "commit": "sha"
86
86
  }
87
87
  }
88
88
  }
@@ -1288,8 +1288,13 @@ function normalizeUrl(value) {
1288
1288
  const port2 = url.port && !defaultPort ? `:${url.port}` : "";
1289
1289
  return `${scheme}://${host}${port2}`;
1290
1290
  }
1291
+ function normalizeSha(value) {
1292
+ const trimmed = value.trim();
1293
+ return /^[0-9a-fA-F]{7,64}$/.test(trimmed) ? trimmed.toLowerCase() : value;
1294
+ }
1291
1295
  var NORMALIZERS = {
1292
- url: normalizeUrl
1296
+ url: normalizeUrl,
1297
+ sha: normalizeSha
1293
1298
  };
1294
1299
  function normalizeIncomingArgs(tool, args) {
1295
1300
  const declared = tool?.gating?.argNormalization;
@@ -34,4 +34,6 @@ export declare function buildWindowsTaskXml(opts: {
34
34
  serverEntry: string;
35
35
  author: string;
36
36
  dataDir?: string;
37
+ /** `DOMAIN\\user`. Omitting it registers an any-user task, which needs admin. */
38
+ userId?: string;
37
39
  }): string;
@@ -148,8 +148,15 @@ WantedBy=default.target
148
148
  * LogonType=InteractiveToken keeps it in the user's own session with no stored
149
149
  * password and no admin rights. Hidden + no execution time limit stop it
150
150
  * flashing a console window or being killed after the default 72 hours.
151
+ *
152
+ * `userId` is what makes it unprivileged, and leaving it out is not a cosmetic
153
+ * omission: a LogonTrigger with no UserId means "at ANY user's logon", which
154
+ * only an administrator may register. A standard user got `Zugriff verweigert`
155
+ * from schtasks — the install was impossible for exactly the audience the
156
+ * user-level design was for. Naming the user scopes both the trigger and the
157
+ * principal to that account, which needs no elevation.
151
158
  */
152
- export function buildWindowsTaskXml({ nodePath, serverEntry, author, dataDir }) {
159
+ export function buildWindowsTaskXml({ nodePath, serverEntry, author, dataDir, userId }) {
153
160
  // Task Scheduler requires \Command to be the executable and \Arguments the
154
161
  // rest; quoting the script path handles spaces (e.g. under "Program Files").
155
162
  // Task Scheduler XML carries no environment block, so the marker that this
@@ -157,6 +164,12 @@ export function buildWindowsTaskXml({ nodePath, serverEntry, author, dataDir })
157
164
  const args = `"${serverEntry}" --autostart`;
158
165
  const envNote = dataDir ? `Suveren data directory: ${dataDir}` : 'Suveren gateway';
159
166
 
167
+ // Element order is fixed by the Task Scheduler XSD, and schtasks rejects the
168
+ // file outright when it is wrong: inside Principal, UserId precedes LogonType;
169
+ // inside LogonTrigger it follows Enabled.
170
+ const principalUser = userId ? ` <UserId>${escapeXml(userId)}</UserId>\n` : '';
171
+ const triggerUser = userId ? ` <UserId>${escapeXml(userId)}</UserId>\n` : '';
172
+
160
173
  return `<?xml version="1.0" encoding="UTF-16"?>
161
174
  <Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
162
175
  <RegistrationInfo>
@@ -166,11 +179,11 @@ export function buildWindowsTaskXml({ nodePath, serverEntry, author, dataDir })
166
179
  <Triggers>
167
180
  <LogonTrigger>
168
181
  <Enabled>true</Enabled>
169
- </LogonTrigger>
182
+ ${triggerUser} </LogonTrigger>
170
183
  </Triggers>
171
184
  <Principals>
172
185
  <Principal id="Author">
173
- <LogonType>InteractiveToken</LogonType>
186
+ ${principalUser} <LogonType>InteractiveToken</LogonType>
174
187
  <RunLevel>LeastPrivilege</RunLevel>
175
188
  </Principal>
176
189
  </Principals>
@@ -86,7 +86,7 @@ app.get(
86
86
  }))
87
87
  )
88
88
 
89
- const wss = new WebSocketServer({ noServer: true }) // important to create with `noServer: true`
89
+ const wss = new WebSocketServer({ noServer: true })
90
90
  serve({
91
91
  fetch: app.fetch,
92
92
  websocket: { server: wss },
@@ -332,6 +332,52 @@ type Http2Bindings = {
332
332
  }
333
333
  ```
334
334
 
335
+ ## Early Hints Middleware
336
+
337
+ You can send HTTP 103 Early Hints to instruct browsers to preload or preconnect resources before the final response is prepared. The middleware is supported under Node.js bindings (HTTP/1.1 and HTTP/2).
338
+
339
+ ### Usage
340
+
341
+ Import `earlyHints` from `@hono/node-server/early-hints`:
342
+
343
+ #### Static links
344
+
345
+ ```ts
346
+ import { serve } from '@hono/node-server'
347
+ import { earlyHints } from '@hono/node-server/early-hints'
348
+ import { Hono } from 'hono'
349
+
350
+ const app = new Hono()
351
+
352
+ app.use(
353
+ earlyHints({
354
+ link: '</styles.css>; rel=preload; as=style',
355
+ })
356
+ )
357
+
358
+ app.get('/', (c) => {
359
+ return c.html('<!DOCTYPE html><html><body><h1>Hello Hono!</h1></body></html>')
360
+ })
361
+
362
+ serve(app)
363
+ ```
364
+
365
+ #### Dynamic links
366
+
367
+ ```ts
368
+ app.use(
369
+ earlyHints({
370
+ link: (c) =>
371
+ c.req.query('theme') === 'dark'
372
+ ? '</dark.css>; rel=preload; as=style'
373
+ : '</light.css>; rel=preload; as=style',
374
+ })
375
+ )
376
+ ```
377
+
378
+ > [!NOTE]
379
+ > Early Hints are sent only for requests that look like document navigations. If `Sec-Fetch-Mode` or `Sec-Fetch-Dest` is present with a value other than `navigate` or `document`, for example a `fetch()` or XHR call from a browser, a subresource request, or an iframe navigation, the middleware skips the hints and continues to the handler. Requests without these headers, such as `curl` or `fetch()` from a JavaScript runtime, are treated as navigations and do receive Early Hints.
380
+
335
381
  ## Direct response from Node.js API
336
382
 
337
383
  You can directly respond to the client from the Node.js API.
@@ -0,0 +1,35 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+
3
+ //#region src/early-hints.ts
4
+ /**
5
+ * Early Hints middleware for Node.js
6
+ * Automatically sends a 103 Early Hints informational response with the specified Link header(s).
7
+ *
8
+ * @param options EarlyHintsOptions
9
+ * @returns MiddlewareHandler
10
+ */
11
+ const earlyHints = (options) => {
12
+ let warned = false;
13
+ return async (c, next) => {
14
+ const mode = c.req.header("Sec-Fetch-Mode");
15
+ const dest = c.req.header("Sec-Fetch-Dest");
16
+ if (mode && mode !== "navigate" || dest && dest !== "document") return next();
17
+ const env = c.env || {};
18
+ const outgoing = (env.server ? env.server : env)?.outgoing;
19
+ if (typeof outgoing?.writeEarlyHints !== "function") {
20
+ if (!warned) {
21
+ console.warn("Early Hints Middleware is not supported because writeEarlyHints is not defined.");
22
+ warned = true;
23
+ }
24
+ return await next();
25
+ }
26
+ if (!outgoing.headersSent) {
27
+ const link = typeof options.link === "function" ? options.link(c) : options.link;
28
+ if (link !== void 0 && (Array.isArray(link) ? link.length > 0 : Boolean(link))) outgoing.writeEarlyHints({ link });
29
+ }
30
+ await next();
31
+ };
32
+ };
33
+
34
+ //#endregion
35
+ exports.earlyHints = earlyHints;
@@ -0,0 +1,16 @@
1
+ import { Context, Env, MiddlewareHandler } from "hono";
2
+
3
+ //#region src/early-hints.d.ts
4
+ type EarlyHintsOptions<E extends Env = Env> = {
5
+ link: string | string[] | ((c: Context<E>) => string | string[] | undefined);
6
+ };
7
+ /**
8
+ * Early Hints middleware for Node.js
9
+ * Automatically sends a 103 Early Hints informational response with the specified Link header(s).
10
+ *
11
+ * @param options EarlyHintsOptions
12
+ * @returns MiddlewareHandler
13
+ */
14
+ declare const earlyHints: <E extends Env = any>(options: EarlyHintsOptions<E>) => MiddlewareHandler<E>;
15
+ //#endregion
16
+ export { EarlyHintsOptions, earlyHints };
@@ -0,0 +1,16 @@
1
+ import { Context, Env, MiddlewareHandler } from "hono";
2
+
3
+ //#region src/early-hints.d.ts
4
+ type EarlyHintsOptions<E extends Env = Env> = {
5
+ link: string | string[] | ((c: Context<E>) => string | string[] | undefined);
6
+ };
7
+ /**
8
+ * Early Hints middleware for Node.js
9
+ * Automatically sends a 103 Early Hints informational response with the specified Link header(s).
10
+ *
11
+ * @param options EarlyHintsOptions
12
+ * @returns MiddlewareHandler
13
+ */
14
+ declare const earlyHints: <E extends Env = any>(options: EarlyHintsOptions<E>) => MiddlewareHandler<E>;
15
+ //#endregion
16
+ export { EarlyHintsOptions, earlyHints };
@@ -0,0 +1,33 @@
1
+ //#region src/early-hints.ts
2
+ /**
3
+ * Early Hints middleware for Node.js
4
+ * Automatically sends a 103 Early Hints informational response with the specified Link header(s).
5
+ *
6
+ * @param options EarlyHintsOptions
7
+ * @returns MiddlewareHandler
8
+ */
9
+ const earlyHints = (options) => {
10
+ let warned = false;
11
+ return async (c, next) => {
12
+ const mode = c.req.header("Sec-Fetch-Mode");
13
+ const dest = c.req.header("Sec-Fetch-Dest");
14
+ if (mode && mode !== "navigate" || dest && dest !== "document") return next();
15
+ const env = c.env || {};
16
+ const outgoing = (env.server ? env.server : env)?.outgoing;
17
+ if (typeof outgoing?.writeEarlyHints !== "function") {
18
+ if (!warned) {
19
+ console.warn("Early Hints Middleware is not supported because writeEarlyHints is not defined.");
20
+ warned = true;
21
+ }
22
+ return await next();
23
+ }
24
+ if (!outgoing.headersSent) {
25
+ const link = typeof options.link === "function" ? options.link(c) : options.link;
26
+ if (link !== void 0 && (Array.isArray(link) ? link.length > 0 : Boolean(link))) outgoing.writeEarlyHints({ link });
27
+ }
28
+ await next();
29
+ };
30
+ };
31
+
32
+ //#endregion
33
+ export { earlyHints };
@@ -684,7 +684,10 @@ const drainIncoming = (incoming) => {
684
684
  const forceClose = () => {
685
685
  cleanup();
686
686
  const socket = incoming.socket;
687
- if (socket && !socket.destroyed) socket.destroySoon();
687
+ if (socket && !socket.destroyed) {
688
+ if (typeof socket.destroySoon === "function") socket.destroySoon();
689
+ else if (typeof socket.destroy === "function") socket.destroy();
690
+ }
688
691
  };
689
692
  const timer = setTimeout(forceClose, DRAIN_TIMEOUT_MS);
690
693
  timer.unref?.();
@@ -683,7 +683,10 @@ const drainIncoming = (incoming) => {
683
683
  const forceClose = () => {
684
684
  cleanup();
685
685
  const socket = incoming.socket;
686
- if (socket && !socket.destroyed) socket.destroySoon();
686
+ if (socket && !socket.destroyed) {
687
+ if (typeof socket.destroySoon === "function") socket.destroySoon();
688
+ else if (typeof socket.destroy === "function") socket.destroy();
689
+ }
687
690
  };
688
691
  const timer = setTimeout(forceClose, DRAIN_TIMEOUT_MS);
689
692
  timer.unref?.();
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hono/node-server",
3
- "version": "2.0.12",
3
+ "version": "2.1.0",
4
4
  "description": "Node.js Adapter for Hono",
5
5
  "main": "dist/index.mjs",
6
6
  "type": "module",
@@ -48,6 +48,16 @@
48
48
  "types": "./dist/conninfo.d.cts",
49
49
  "default": "./dist/conninfo.cjs"
50
50
  }
51
+ },
52
+ "./early-hints": {
53
+ "import": {
54
+ "types": "./dist/early-hints.d.mts",
55
+ "default": "./dist/early-hints.mjs"
56
+ },
57
+ "require": {
58
+ "types": "./dist/early-hints.d.cts",
59
+ "default": "./dist/early-hints.cjs"
60
+ }
51
61
  }
52
62
  },
53
63
  "typesVersions": {
@@ -63,6 +73,9 @@
63
73
  ],
64
74
  "conninfo": [
65
75
  "./dist/conninfo.d.mts"
76
+ ],
77
+ "early-hints": [
78
+ "./dist/early-hints.d.mts"
66
79
  ]
67
80
  }
68
81
  },
@@ -44,10 +44,15 @@ module.exports = __toCommonJS(index_exports);
44
44
  // source/ip-key-generator.ts
45
45
  var import_node_net = require("node:net");
46
46
  var import_ip_address = require("ip-address");
47
+ var ipv4CompatibleSubnet = new import_ip_address.Address6("::/96");
47
48
  function ipKeyGenerator(ip, ipv6Subnet = 56) {
48
49
  if ((0, import_node_net.isIPv6)(ip)) {
49
50
  const address = new import_ip_address.Address6(ip);
50
- if (address.is4()) return address.to4().correctForm();
51
+ if (address.isMapped4() || // The deprecated IPv4-compatible notation keeps its existing behavior.
52
+ // Its range is shared with addresses that embed no IPv4 address at all
53
+ // (`::`, `::1`), so here the notation is what tells them apart.
54
+ address.is4() && address.isInSubnet(ipv4CompatibleSubnet))
55
+ return address.to4().correctForm();
51
56
  if (ipv6Subnet) {
52
57
  const subnet = new import_ip_address.Address6(`${ip}/${ipv6Subnet}`);
53
58
  return subnet.networkForm();
@@ -1,10 +1,15 @@
1
1
  // source/ip-key-generator.ts
2
2
  import { isIPv6 } from "node:net";
3
3
  import { Address6 } from "ip-address";
4
+ var ipv4CompatibleSubnet = new Address6("::/96");
4
5
  function ipKeyGenerator(ip, ipv6Subnet = 56) {
5
6
  if (isIPv6(ip)) {
6
7
  const address = new Address6(ip);
7
- if (address.is4()) return address.to4().correctForm();
8
+ if (address.isMapped4() || // The deprecated IPv4-compatible notation keeps its existing behavior.
9
+ // Its range is shared with addresses that embed no IPv4 address at all
10
+ // (`::`, `::1`), so here the notation is what tells them apart.
11
+ address.is4() && address.isInSubnet(ipv4CompatibleSubnet))
12
+ return address.to4().correctForm();
8
13
  if (ipv6Subnet) {
9
14
  const subnet = new Address6(`${ip}/${ipv6Subnet}`);
10
15
  return subnet.networkForm();
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "express-rate-limit",
3
- "version": "8.6.1",
3
+ "version": "8.6.2",
4
4
  "description": "Basic IP rate-limiting middleware for Express. Use to limit repeated requests to public APIs and/or endpoints such as password reset.",
5
5
  "author": {
6
6
  "name": "Nathan Friedly",
@@ -90,7 +90,7 @@
90
90
  "@types/debug": "4.1.13",
91
91
  "@types/express": "5.0.6",
92
92
  "@types/jest": "30.0.0",
93
- "@types/node": "26.1.1",
93
+ "@types/node": "26.1.2",
94
94
  "@types/supertest": "7.2.1",
95
95
  "del-cli": "7.0.0",
96
96
  "dts-bundle-generator": "8.1.2",
@@ -98,13 +98,13 @@
98
98
  "express": "5.2.1",
99
99
  "husky": "9.1.7",
100
100
  "jest": "30.4.2",
101
- "lint-staged": "17.1.0",
102
- "mintlify": "4.2.715",
101
+ "lint-staged": "17.2.0",
102
+ "mintlify": "4.2.743",
103
103
  "npm-run-all": "4.1.5",
104
- "prettier": "3.9.5",
104
+ "prettier": "3.9.6",
105
105
  "ratelimit-header-parser": "0.1.0",
106
106
  "supertest": "7.2.2",
107
- "ts-jest": "29.4.11",
107
+ "ts-jest": "29.4.12",
108
108
  "ts-node": "10.9.2",
109
109
  "typescript": "5.9.3"
110
110
  },
@@ -295,11 +295,11 @@ class Context {
295
295
  return Object.fromEntries(this.#var);
296
296
  }
297
297
  #newResponse(data, arg, headers) {
298
- const responseHeaders = this.#res ? new Headers(this.#res.headers) : this.#preparedHeaders ?? new Headers();
299
- if (typeof arg === "object" && "headers" in arg) {
300
- const argHeaders = arg.headers instanceof Headers ? arg.headers : new Headers(arg.headers);
301
- for (const [key, value] of argHeaders) {
302
- if (key.toLowerCase() === "set-cookie") {
298
+ let responseHeaders = this.#res ? new Headers(this.#res.headers) : this.#preparedHeaders;
299
+ if (typeof arg === "object" && arg.headers) {
300
+ responseHeaders ??= new Headers();
301
+ for (const [key, value] of new Headers(arg.headers)) {
302
+ if (key === "set-cookie") {
303
303
  responseHeaders.append(key, value);
304
304
  } else {
305
305
  responseHeaders.set(key, value);
@@ -307,19 +307,34 @@ class Context {
307
307
  }
308
308
  }
309
309
  if (headers) {
310
- for (const [k, v] of Object.entries(headers)) {
311
- if (typeof v === "string") {
312
- responseHeaders.set(k, v);
313
- } else {
314
- responseHeaders.delete(k);
315
- for (const v2 of v) {
316
- responseHeaders.append(k, v2);
310
+ if (!responseHeaders) {
311
+ let count = 0;
312
+ for (const k in headers) {
313
+ if (++count > 1 || typeof headers[k] !== "string") {
314
+ responseHeaders = new Headers();
315
+ break;
316
+ }
317
+ }
318
+ }
319
+ if (responseHeaders) {
320
+ for (const k in headers) {
321
+ const v = headers[k];
322
+ if (typeof v === "string") {
323
+ responseHeaders.set(k, v);
324
+ } else {
325
+ responseHeaders.delete(k);
326
+ for (const v2 of v) {
327
+ responseHeaders.append(k, v2);
328
+ }
317
329
  }
318
330
  }
319
331
  }
320
332
  }
321
333
  const status = typeof arg === "number" ? arg : arg?.status ?? this.#status;
322
- return createResponseInstance(data, { status, headers: responseHeaders });
334
+ return createResponseInstance(data, {
335
+ status,
336
+ headers: responseHeaders ?? headers
337
+ });
323
338
  }
324
339
  newResponse = (...args) => this.#newResponse(...args);
325
340
  /**
@@ -43,6 +43,7 @@ class Hono {
43
43
  delete;
44
44
  options;
45
45
  patch;
46
+ query;
46
47
  all;
47
48
  on;
48
49
  use;
@@ -342,8 +343,8 @@ class Hono {
342
343
  * @see {@link https://hono.dev/docs/api/hono#fetch}
343
344
  *
344
345
  * @param {Request} request - request Object of request
345
- * @param {Env} Env - env Object
346
- * @param {ExecutionContext} - context of execution
346
+ * @param {Env} env - env Object
347
+ * @param {ExecutionContext} executionCtx - context of execution
347
348
  * @returns {Response | Promise<Response>} response of request
348
349
  *
349
350
  */
@@ -100,6 +100,18 @@ const booleanAttributes = [
100
100
  "reversed",
101
101
  "selected"
102
102
  ];
103
+ const resolveFunctionComponentResult = (result, suspendedContext) => result.then((resolved) => {
104
+ if (!Array.isArray(resolved) && !(resolved instanceof JSXNode)) {
105
+ return resolved;
106
+ }
107
+ const children = Array.isArray(resolved) ? resolved : [resolved];
108
+ const render = () => {
109
+ const buffer = [""];
110
+ childrenToStringToBuffer(children, buffer);
111
+ return buffer.length === 1 ? (0, import_html.raw)(buffer[0], buffer.callbacks) : (0, import_html2.stringBufferToString)(buffer, buffer.callbacks);
112
+ };
113
+ return suspendedContext ? suspendedContext(render) : (0, import_context.runWithRenderContext)(render);
114
+ });
103
115
  const childrenToStringToBuffer = (children, buffer) => {
104
116
  for (let i = 0, len = children.length; i < len; i++) {
105
117
  const child = children[i];
@@ -109,9 +121,17 @@ const childrenToStringToBuffer = (children, buffer) => {
109
121
  continue;
110
122
  } else if (child instanceof JSXNode) {
111
123
  child.toStringToBuffer(buffer);
112
- } else if (typeof child === "number" || child.isEscaped) {
124
+ } else if (typeof child === "number") {
113
125
  ;
114
126
  buffer[0] += child;
127
+ } else if (child.isEscaped) {
128
+ ;
129
+ buffer[0] += child;
130
+ const callbacks = child.callbacks;
131
+ if (callbacks) {
132
+ buffer.callbacks ||= [];
133
+ buffer.callbacks.push(...callbacks);
134
+ }
115
135
  } else if (child instanceof Promise) {
116
136
  buffer.unshift("", child);
117
137
  } else {
@@ -125,7 +145,6 @@ class JSXNode {
125
145
  key;
126
146
  children;
127
147
  isEscaped = true;
128
- suspendedContext;
129
148
  constructor(tag, props, children) {
130
149
  if (typeof tag !== "function" && !(0, import_utils.isValidTagName)(tag)) {
131
150
  throw new Error(`Invalid JSX tag name: ${tag}`);
@@ -148,7 +167,7 @@ class JSXNode {
148
167
  this.toStringToBuffer(buffer);
149
168
  return buffer.length === 1 ? "callbacks" in buffer ? (0, import_html2.resolveCallbackSync)((0, import_html.raw)(buffer[0], buffer.callbacks)).toString() : buffer[0] : (0, import_html2.stringBufferToString)(buffer, buffer.callbacks);
150
169
  };
151
- return this.suspendedContext ? this.suspendedContext(render) : (0, import_context.runWithRenderContext)(render);
170
+ return (0, import_context.runWithRenderContext)(render);
152
171
  }
153
172
  toStringToBuffer(buffer) {
154
173
  const tag = this.tag;
@@ -222,21 +241,14 @@ class JSXFunctionNode extends JSXNode {
222
241
  return;
223
242
  } else if (res instanceof Promise) {
224
243
  if (import_context.globalContexts.length === 0) {
225
- buffer.unshift("", res);
244
+ buffer.unshift("", resolveFunctionComponentResult(res));
226
245
  } else {
227
- const suspendedContext = (0, import_context.captureRenderContext)();
228
- buffer.unshift(
229
- "",
230
- res.then((childRes) => {
231
- if (childRes instanceof JSXNode) {
232
- childRes.suspendedContext = suspendedContext;
233
- }
234
- return childRes;
235
- })
236
- );
246
+ buffer.unshift("", resolveFunctionComponentResult(res, (0, import_context.captureRenderContext)()));
237
247
  }
238
248
  } else if (res instanceof JSXNode) {
239
249
  res.toStringToBuffer(buffer);
250
+ } else if (Array.isArray(res)) {
251
+ childrenToStringToBuffer(res, buffer);
240
252
  } else if (typeof res === "number" || res.isEscaped) {
241
253
  buffer[0] += res;
242
254
  if (res.callbacks) {
@@ -263,7 +263,7 @@ const useCallback = (callback, deps) => {
263
263
  }
264
264
  return callback;
265
265
  };
266
- const useRef = (initialValue) => {
266
+ function useRef(initialValue) {
267
267
  const buildData = import_render.buildDataStack.at(-1);
268
268
  if (!buildData) {
269
269
  return { current: initialValue };
@@ -272,7 +272,7 @@ const useRef = (initialValue) => {
272
272
  const refArray = node[import_constants.DOM_STASH][1][STASH_REF] ||= [];
273
273
  const hookIndex = node[import_constants.DOM_STASH][0]++;
274
274
  return refArray[hookIndex] ||= { current: initialValue };
275
- };
275
+ }
276
276
  const use = (promise) => {
277
277
  const cachedRes = resolvedPromiseValueMap.get(promise);
278
278
  if (cachedRes) {