@znight/http 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/hono.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { N as discardStream, a as nodeBodyStream, b as webBodyStream, c as readNodeBody, f as bufferInjectStream, n as closeServer, p as injectFetchStream, s as onResponseAbort, t as ConnectionTracker, v as readWebStream, y as serverAddress } from "./node-io-CVnqpVNS.mjs";
2
- import { r as isBun } from "./utils-DCbu3a4w.mjs";
1
+ import { F as discardStream, S as webBodyStream, V as BadRequestException, a as nodeBodyStream, b as readWebStream, c as readNodeBody, f as bufferInjectStream, m as injectFetchStream, n as closeServer, p as exceedsDeclaredLength, s as onResponseAbort, t as ConnectionTracker, x as serverAddress, y as payloadTooLarge } from "./node-io-BkdRSQeg.mjs";
2
+ import { i as isBun } from "./utils-DIWdzVht.mjs";
3
3
  import { toHonoPath } from "./path.mjs";
4
4
  import { Hono } from "hono";
5
5
  // src/adapters/hono.ts
@@ -17,6 +17,43 @@ function honoAdapter(options = {}) {
17
17
  }
18
18
  /** Default `idleTimeout` of `Bun.serve()`, in seconds. */
19
19
  const BUN_IDLE_TIMEOUT = 10;
20
+ /**
21
+ * A streamed response body built for a request of `Bun.serve()` (see `#bunStream`); with middleware,
22
+ * pending until `#bunFetch` takes it or it ends.
23
+ */
24
+ var BunBody = class {
25
+ /** The `Blob` (file) the body reads. */
26
+ blob;
27
+ #settle;
28
+ #onEnd;
29
+ #ended = false;
30
+ /** `settle` is called once, when the body is taken by `#bunFetch` or ends, whichever comes first. */
31
+ constructor(blob, settle) {
32
+ this.blob = blob;
33
+ this.#settle = settle;
34
+ }
35
+ /** Whether the body ended (read to its end, failed or cancelled). */
36
+ get ended() {
37
+ return this.#ended;
38
+ }
39
+ /** The body is no longer pending (taken by `#bunFetch`). */
40
+ settle() {
41
+ const settle = this.#settle;
42
+ this.#settle = void 0;
43
+ settle?.();
44
+ }
45
+ /** Sets the function called when the body ends. */
46
+ onEnd(callback) {
47
+ this.#onEnd = callback;
48
+ }
49
+ /** The body ended (read to its end, failed or cancelled). */
50
+ end() {
51
+ if (this.#ended) return;
52
+ this.#ended = true;
53
+ this.settle();
54
+ this.#onEnd?.();
55
+ }
56
+ };
20
57
  /** Response bodies built while serving with `Bun.serve()`, by body (Hono may copy a `Response`, not its body). */
21
58
  const BUN_BODIES = /* @__PURE__ */ new WeakMap();
22
59
  /** Bindings of injected requests (the remote address the other adapters report for `inject()`). */
@@ -27,9 +64,19 @@ var HonoHttpAdapter = class {
27
64
  #serveOptions;
28
65
  /** Number of routes of `app` registered by the adapter (see {@link #middleware}). */
29
66
  #routes = 0;
67
+ /** The GET and HEAD handlers of each path shape, served by one route of `app` (see {@link registerRoute}). */
68
+ #reads = /* @__PURE__ */ new Map();
30
69
  #fallback;
31
70
  #prepared = false;
32
71
  #bun;
72
+ /**
73
+ * Streamed bodies of `Bun.serve()` replies (with middleware) neither taken by `#bunFetch` nor ended
74
+ * (see {@link BunBody}): while there is none, `#bunFetch` returns responses untouched (reading
75
+ * `response.body` would turn a text body into a stream that Bun then sends as one).
76
+ */
77
+ #pendingBodies = 0;
78
+ /** Idle timeout of the `Bun.serve()` server, in seconds (`0`: none). */
79
+ #idleTimeout = BUN_IDLE_TIMEOUT;
33
80
  #node;
34
81
  #tracker;
35
82
  #closing;
@@ -44,11 +91,29 @@ var HonoHttpAdapter = class {
44
91
  registerRoute(route) {
45
92
  const handler = route.handler;
46
93
  const path = toHonoPath(route.path);
47
- this.#routes++;
48
- if (route.method === "HEAD") {
49
- this.app.get(path, (c, next) => c.req.raw.method === "HEAD" ? this.#serve(c, handler) : next().then(() => c.res));
94
+ if (route.method === "GET" || route.method === "HEAD") {
95
+ const existing = this.#reads.get(route.path.shape);
96
+ if (existing !== void 0) {
97
+ if (route.method === "GET") existing.get = handler;
98
+ else existing.head = handler;
99
+ return;
100
+ }
101
+ const reads = route.method === "GET" ? {
102
+ get: handler,
103
+ head: void 0
104
+ } : {
105
+ get: void 0,
106
+ head: handler
107
+ };
108
+ this.#reads.set(route.path.shape, reads);
109
+ this.#routes++;
110
+ this.app.get(path, (c, next) => {
111
+ const read = c.req.raw.method === "HEAD" ? reads.head : reads.get;
112
+ return read === void 0 ? next().then(() => c.res) : this.#serve(c, read);
113
+ });
50
114
  return;
51
115
  }
116
+ this.#routes++;
52
117
  this.app.on(route.method, path, (c) => this.#serve(c, handler));
53
118
  }
54
119
  setFallbackHandler(handler) {
@@ -61,13 +126,20 @@ var HonoHttpAdapter = class {
61
126
  this.app.onError((error, c) => this.#serveFallback(c, error));
62
127
  }
63
128
  async #serve(c, handler) {
64
- const request = adapterRequest(c);
65
- const reply = this.#fallback !== void 0 && pathOf(request.url) !== pathOf(pathAndQuery(c.req.raw.url)) ? await this.#fallback(request) : await handler(request);
129
+ const request = this.#request(c);
130
+ const reply = this.#fallback !== void 0 && request.rawTarget && pathOf(request.url) !== pathOf(pathAndQuery(c.req.raw.url)) ? await this.#fallback(request) : await handler(request);
66
131
  return this.#toResponse(c, reply);
67
132
  }
68
133
  async #serveFallback(c, error) {
69
134
  if (this.#fallback === void 0) throw error ?? /* @__PURE__ */ new Error("No fallback handler");
70
- return this.#toResponse(c, await this.#fallback(adapterRequest(c), error));
135
+ return this.#toResponse(c, await this.#fallback(this.#request(c), error));
136
+ }
137
+ #request(c) {
138
+ return new HonoAdapterRequest(c, this.#servedByBun(c) ? this.#bun : void 0);
139
+ }
140
+ /** Whether the request of `c` was received by the `Bun.serve()` server of the adapter. */
141
+ #servedByBun(c) {
142
+ return this.#bun !== void 0 && c.env === this.#bun;
71
143
  }
72
144
  /**
73
145
  * Whether `app` has routes the adapter did not register (middleware, other routes): only they can
@@ -78,6 +150,7 @@ var HonoHttpAdapter = class {
78
150
  }
79
151
  #toResponse(c, reply) {
80
152
  const middleware = this.#middleware();
153
+ if (this.#servedByBun(c)) return reply.body instanceof ReadableStream ? this.#bunStream(c, reply, middleware) : toResponse(c, reply, middleware);
81
154
  const outgoing = nodeOutgoing(c);
82
155
  if (outgoing !== void 0) {
83
156
  const body = reply.body instanceof ReadableStream ? reply.body : null;
@@ -93,54 +166,63 @@ var HonoHttpAdapter = class {
93
166
  });
94
167
  if (reply.eventStream === true && typeof outgoing.setTimeout === "function") outgoing.setTimeout(0);
95
168
  }
96
- if (this.#bun === void 0 || !(reply.body instanceof ReadableStream)) return toResponse(c, reply, middleware);
169
+ return toResponse(c, reply, middleware);
170
+ }
171
+ /**
172
+ * The response of a streamed reply to a request of `Bun.serve()`: the end of its body is observed
173
+ * (idle timeout, native blob: see {@link #bunSend}).
174
+ */
175
+ #bunStream(c, reply, middleware) {
97
176
  const reader = reply.body.getReader();
98
- let ended = false;
99
- let onEnd = () => void 0;
100
- const end = () => {
101
- if (ended) return;
102
- ended = true;
103
- onEnd();
104
- };
177
+ const served = new BunBody(reply.blob, middleware ? () => {
178
+ this.#pendingBodies--;
179
+ } : void 0);
105
180
  const body = new ReadableStream({
106
181
  async pull(controller) {
107
182
  let result;
108
183
  try {
109
184
  result = await reader.read();
110
185
  } catch (error) {
111
- end();
186
+ served.end();
112
187
  throw error;
113
188
  }
114
189
  if (result.done) {
115
- end();
190
+ served.end();
116
191
  controller.close();
117
192
  return;
118
193
  }
119
194
  controller.enqueue(result.value);
120
195
  },
121
196
  cancel(reason) {
122
- end();
197
+ served.end();
123
198
  return reader.cancel(reason);
124
199
  }
125
200
  }, { highWaterMark: 0 });
126
- BUN_BODIES.set(body, {
127
- blob: reply.blob,
128
- onEnd: (callback) => {
129
- onEnd = callback;
130
- }
131
- });
132
- return toResponse(c, {
201
+ const response = toResponse(c, {
133
202
  ...reply,
134
203
  body
135
204
  }, middleware);
205
+ if (!middleware) return served.ended ? response : this.#bunSend(response, body, served, c.req.raw);
206
+ this.#pendingBodies++;
207
+ BUN_BODIES.set(body, served);
208
+ return response;
136
209
  }
137
- /** The fetch handler given to `Bun.serve()`. */
138
- async #bunFetch(request, server, idleTimeout) {
210
+ /** The fetch handler given to `Bun.serve()` when `app` has middleware (see {@link #bunStream}). */
211
+ async #bunFetch(request, server) {
139
212
  const response = await this.app.fetch(request, server);
213
+ if (this.#pendingBodies === 0) return response;
140
214
  const body = response.body;
141
215
  const served = body === null ? void 0 : BUN_BODIES.get(body);
142
216
  if (body === null || served === void 0) return response;
143
217
  BUN_BODIES.delete(body);
218
+ served.settle();
219
+ return this.#bunSend(response, body, served, request);
220
+ }
221
+ /**
222
+ * The response Bun sends for a streamed reply, once final: a file `Blob` is sent natively, a
223
+ * stream with the idle timeout of the connection lifted.
224
+ */
225
+ #bunSend(response, body, served, request) {
144
226
  if (served.blob !== void 0 && !body.locked) {
145
227
  const native = new Response(served.blob, {
146
228
  status: response.status,
@@ -150,7 +232,9 @@ var HonoHttpAdapter = class {
150
232
  setImmediate(() => discardStream(body));
151
233
  return native;
152
234
  }
153
- if (idleTimeout > 0 && typeof server.timeout === "function") {
235
+ const server = this.#bun;
236
+ const idleTimeout = this.#idleTimeout;
237
+ if (idleTimeout > 0 && !served.ended && typeof server?.timeout === "function") {
154
238
  server.timeout(request, 0);
155
239
  served.onEnd(() => {
156
240
  try {
@@ -166,13 +250,13 @@ var HonoHttpAdapter = class {
166
250
  const bun = globalThis.Bun;
167
251
  if (isBun() && bun !== void 0) {
168
252
  const options = this.#serveOptions;
169
- const idleTimeout = typeof options.idleTimeout === "number" ? options.idleTimeout : BUN_IDLE_TIMEOUT;
253
+ this.#idleTimeout = typeof options.idleTimeout === "number" ? options.idleTimeout : BUN_IDLE_TIMEOUT;
170
254
  const server = bun.serve({
171
255
  maxRequestBodySize: Number.MAX_SAFE_INTEGER,
172
256
  ...options,
173
257
  port,
174
258
  hostname: host,
175
- fetch: (request, server) => this.#bunFetch(request, server, idleTimeout)
259
+ fetch: (request, server) => this.#middleware() ? this.#bunFetch(request, server) : this.app.fetch(request, server)
176
260
  });
177
261
  this.#bun = server;
178
262
  return serverAddress(server.hostname, server.port);
@@ -219,9 +303,6 @@ var HonoHttpAdapter = class {
219
303
  return injectFetchStream((request) => this.app.request(request, void 0, INJECT_ENV), request);
220
304
  }
221
305
  };
222
- function adapterRequest(c) {
223
- return new HonoAdapterRequest(c);
224
- }
225
306
  /** A Hono request as an {@link AdapterRequest} (a class: no closure per request). */
226
307
  var HonoAdapterRequest = class {
227
308
  method;
@@ -232,6 +313,8 @@ var HonoAdapterRequest = class {
232
313
  encrypted;
233
314
  raw;
234
315
  rawResponse;
316
+ /** `url` is the raw request target (Node.js), not the WHATWG-normalised URL of `raw`. */
317
+ rawTarget;
235
318
  /** The Node.js request of `@hono/node-server`, absent on Bun and for `inject()`. */
236
319
  #incoming;
237
320
  /**
@@ -239,30 +322,58 @@ var HonoAdapterRequest = class {
239
322
  * stream are built for it.
240
323
  */
241
324
  #http1;
325
+ /** Received by the `Bun.serve()` server of the adapter: its body is framed by the HTTP parser. */
326
+ #bun;
242
327
  #body;
243
- constructor(c) {
328
+ constructor(c, bun) {
244
329
  const request = c.req.raw;
330
+ this.method = request.method;
331
+ this.raw = request;
332
+ this.rawResponse = c;
333
+ this.#bun = bun !== void 0;
334
+ if (bun !== void 0) {
335
+ const url = request.url;
336
+ const address = bun.requestIP?.(request)?.address ?? void 0;
337
+ this.url = pathAndQuery(url);
338
+ this.rawTarget = false;
339
+ this.headers = fetchHeaders(request.headers);
340
+ this.ip = address;
341
+ this.remoteAddress = address;
342
+ this.encrypted = url.startsWith("https:");
343
+ this.#incoming = void 0;
344
+ this.#http1 = void 0;
345
+ return;
346
+ }
245
347
  const incoming = nodeIncoming(c);
246
348
  const http1 = incoming !== void 0 && incoming.httpVersionMajor < 2 ? incoming : void 0;
247
349
  const address = remoteAddress(c);
248
- this.method = request.method;
249
- this.url = pathAndQuery(typeof incoming?.url === "string" && incoming.url !== "" ? incoming.url : request.url);
350
+ const target = incoming?.url;
351
+ this.rawTarget = typeof target === "string" && target !== "";
352
+ this.url = pathAndQuery(this.rawTarget ? target : request.url);
250
353
  this.headers = (http1 === void 0 ? void 0 : distinctHeaders(http1)) ?? fetchHeaders(request.headers);
251
354
  this.ip = address;
252
355
  this.remoteAddress = address;
253
356
  this.encrypted = incoming === void 0 ? request.url.startsWith("https:") : incoming.socket?.encrypted === true;
254
- this.raw = request;
255
- this.rawResponse = c;
256
357
  this.#incoming = incoming;
257
358
  this.#http1 = http1;
258
359
  }
259
360
  readBody(limit) {
260
361
  if (this.#body === void 0) {
261
362
  const http1 = this.#http1;
262
- this.#body = http1 === void 0 ? readWebStream(() => this.raw.body, limit, this.headers["content-length"]) : readNodeBody(http1, limit);
363
+ if (http1 !== void 0) this.#body = readNodeBody(http1, limit);
364
+ else if (this.#bun && this.#framed()) this.#body = readFramedBody(this.raw, limit, this.headers["content-length"]);
365
+ else this.#body = readWebStream(() => this.raw.body, limit, this.headers["content-length"]);
263
366
  }
264
367
  return this.#body;
265
368
  }
369
+ /**
370
+ * Whether the whole body can be read at once from `Bun.serve()`: its length is declared (the
371
+ * HTTP parser then reads no more) and nothing read it yet.
372
+ */
373
+ #framed() {
374
+ const headers = this.headers;
375
+ return headers["content-length"] !== void 0 && headers["transfer-encoding"] === void 0 && !this.raw.bodyUsed;
376
+ }
266
377
  streamBody(limit) {
267
378
  const http1 = this.#http1;
268
379
  if (http1 !== void 0) return nodeBodyStream(http1, limit);
@@ -273,7 +384,7 @@ var HonoAdapterRequest = class {
273
384
  return incoming === void 0 ? this.raw.body !== null : incoming.httpVersionMajor >= 2 && incoming.stream?.endAfterHeaders === false;
274
385
  }
275
386
  onAbort(listener) {
276
- const outgoing = nodeOutgoing(this.rawResponse);
387
+ const outgoing = this.#bun ? void 0 : nodeOutgoing(this.rawResponse);
277
388
  if (outgoing !== void 0) {
278
389
  onResponseAbort(outgoing, listener);
279
390
  return;
@@ -286,10 +397,29 @@ var HonoAdapterRequest = class {
286
397
  /** The headers of a Fetch request: lower-case names, repeated headers joined (`Headers` semantics). */
287
398
  function fetchHeaders(source) {
288
399
  const headers = {};
289
- for (const [name, value] of source) headers[name] = value;
400
+ source.forEach((value, name) => {
401
+ headers[name] = value;
402
+ });
290
403
  return headers;
291
404
  }
292
405
  /**
406
+ * Reads at once (`request.bytes()`, no stream) the body of a request received by `Bun.serve()`
407
+ * whose `content-length` is declared: Bun reads exactly that many bytes, so checking the declared
408
+ * length enforces `limit`. Failures are those of {@link readWebStream}: 413 above `limit`, 400 when
409
+ * the client aborts.
410
+ */
411
+ async function readFramedBody(request, limit, contentLength) {
412
+ if (exceedsDeclaredLength(contentLength, limit)) throw payloadTooLarge(limit);
413
+ let bytes;
414
+ try {
415
+ bytes = await request.bytes();
416
+ } catch (error) {
417
+ throw new BadRequestException("Failed to read the request body", void 0, { cause: error });
418
+ }
419
+ if (bytes.byteLength > limit) throw payloadTooLarge(limit);
420
+ return bytes;
421
+ }
422
+ /**
293
423
  * The headers of a Node.js request when none is repeated: then they hold the values of its Fetch
294
424
  * `Headers` (Node.js keeps the first of some repeated headers where `Headers` join them all).
295
425
  */
@@ -364,7 +494,8 @@ function toResponse(c, reply, middleware) {
364
494
  }
365
495
  /** Whether every header has a single value (a record of strings is a valid `HeadersInit`). */
366
496
  function isStringRecord(headers) {
367
- for (const name in headers) if (typeof headers[name] !== "string") return false;
497
+ const names = Object.keys(headers);
498
+ for (let index = 0; index < names.length; index++) if (typeof headers[names[index]] !== "string") return false;
368
499
  return true;
369
500
  }
370
501
  /**
package/dist/hono.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"hono.mjs","names":[],"sources":["../src/adapters/hono.ts"],"sourcesContent":["/**\n * `@znight/http/hono`: serves Znight routes with [Hono](https://hono.dev) 4 (optional peer\n * dependency `hono`). On Bun the server is `Bun.serve()`; on Node.js it is `@hono/node-server`\n * (optional peer dependency, loaded on `listen()`). `inject()` uses `app.request()`.\n */\nimport type { IncomingMessage, Server, ServerResponse } from 'node:http'\nimport type {\n AdapterCloseOptions,\n AdapterHandler,\n AdapterReply,\n AdapterRequest,\n AdapterRoute,\n FallbackHandler,\n HttpAdapter,\n HttpServerAddress,\n InjectRequest,\n InjectResponse,\n InjectStreamRequest,\n InjectStreamResponse,\n} from '../adapter.ts'\nimport type { HeaderValue } from '../exceptions.ts'\n\nimport { type Context, Hono, type Next } from 'hono'\n\nimport { toHonoPath } from '../path.ts'\nimport { discardStream } from '../stream.ts'\nimport { isBun } from '../utils.ts'\nimport { ConnectionTracker, closeServer, nodeBodyStream, onResponseAbort, readNodeBody } from './node-io.ts'\nimport { bufferInjectStream, injectFetchStream, readWebStream, serverAddress, webBodyStream } from './shared.ts'\n\n/** Options of {@link honoAdapter}. */\nexport interface HonoAdapterOptions {\n /**\n * An existing application: its middleware (`app.use(...)`) runs before the Znight routes. Znight\n * sets its `notFound` and `onError` handlers.\n */\n readonly app?: Hono\n /**\n * Options of `Bun.serve()` on Bun (`tls`, `idleTimeout`, `development`, `error`...); Znight sets\n * `port`, `hostname` and `fetch`. Ignored on Node.js.\n */\n readonly serveOptions?: HonoServeOptions\n}\n\n/**\n * Options of `Bun.serve()` (see the Bun documentation; Bun types are not required). Defaults set by\n * the adapter: `maxRequestBodySize` unlimited (Znight enforces `bodyLimit` / `@BodyLimit` itself)\n * and Bun's `idleTimeout` (10 s), lifted while a streamed response is sent.\n */\nexport interface HonoServeOptions {\n /** Seconds a connection may stay idle (`0`: never closed). Default: 10. */\n readonly idleTimeout?: number\n /** Request bodies above this size are refused by Bun (413). Default: unlimited. */\n readonly maxRequestBodySize?: number\n /** TLS options (`{ key, cert }`). */\n readonly tls?: unknown\n /** Any other `Bun.serve()` option. */\n readonly [option: string]: unknown\n}\n\n/** The Hono adapter. */\nexport interface HonoAdapter extends HttpAdapter {\n readonly name: 'hono'\n /** The Hono application. */\n readonly app: Hono\n /** Fetch handler of the application (`export default { fetch: adapter.fetch }` on edge runtimes). */\n readonly fetch: (request: Request, env?: unknown) => Response | Promise<Response>\n}\n\n/**\n * Creates a Hono adapter.\n *\n * ```ts\n * import { honoAdapter } from '@znight/http/hono'\n *\n * HttpPlugin.configure({ adapter: honoAdapter() })\n * ```\n */\nexport function honoAdapter(options: HonoAdapterOptions = {}): HonoAdapter {\n return new HonoHttpAdapter(options)\n}\n\n/** The parts of a Bun server used here (Bun types are not available to the package sources). */\ninterface BunServer {\n readonly port: number\n readonly hostname: string\n stop(closeActiveConnections?: boolean): Promise<void> | void\n requestIP?(request: Request): { readonly address: string } | null\n /** Idle timeout of one request, in seconds (`0`: none). */\n timeout?(request: Request, seconds: number): void\n}\n\ninterface BunRuntime {\n serve(\n options: HonoServeOptions & {\n port: number\n hostname: string\n fetch: (request: Request, server: BunServer) => Response | Promise<Response>\n },\n ): BunServer\n}\n\n/** Default `idleTimeout` of `Bun.serve()`, in seconds. */\nconst BUN_IDLE_TIMEOUT = 10\n\n/** A response body built while serving with `Bun.serve()` (see `#bunFetch`). */\ninterface BunBody {\n /** The `Blob` (file) the body reads. */\n readonly blob: Blob | undefined\n /** Sets the function called when the body ends (read to its end, failed or cancelled). */\n onEnd(callback: () => void): void\n}\n\n/** Response bodies built while serving with `Bun.serve()`, by body (Hono may copy a `Response`, not its body). */\nconst BUN_BODIES = new WeakMap<ReadableStream<Uint8Array>, BunBody>()\n\n/** The Node.js request behind a request of `@hono/node-server`. */\ninterface NodeIncoming {\n readonly url?: string\n readonly socket?: { readonly encrypted?: unknown }\n readonly httpVersionMajor: number\n readonly stream?: { readonly endAfterHeaders?: boolean }\n}\n\n/** Bindings of injected requests (the remote address the other adapters report for `inject()`). */\nconst INJECT_ENV = Object.freeze({ incoming: Object.freeze({ socket: Object.freeze({ remoteAddress: '127.0.0.1' }) }) })\n\ntype HonoMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS'\n\nclass HonoHttpAdapter implements HonoAdapter {\n readonly name = 'hono' as const\n readonly app: Hono\n readonly #serveOptions: HonoServeOptions\n /** Number of routes of `app` registered by the adapter (see {@link #middleware}). */\n #routes = 0\n #fallback: FallbackHandler | undefined\n #prepared = false\n #bun: BunServer | undefined\n #node: Server | undefined\n #tracker: ConnectionTracker | undefined\n #closing: Promise<void> | undefined\n\n constructor(options: HonoAdapterOptions) {\n this.app = options.app ?? new Hono()\n this.#serveOptions = options.serveOptions ?? {}\n }\n\n readonly fetch = (request: Request, env?: unknown): Response | Promise<Response> => {\n this.#prepare()\n return this.app.fetch(request, env)\n }\n\n registerRoute(route: AdapterRoute): void {\n const handler = route.handler\n const path = toHonoPath(route.path)\n this.#routes++\n if (route.method === 'HEAD') {\n // Hono routes HEAD requests to GET handlers: serve them from a GET handler that only answers HEAD\n this.app.get(path, (c: Context, next: Next) =>\n c.req.raw.method === 'HEAD' ? this.#serve(c, handler) : next().then(() => c.res),\n )\n return\n }\n this.app.on(route.method as HonoMethod, path, (c: Context) => this.#serve(c, handler))\n }\n\n setFallbackHandler(handler: FallbackHandler): void {\n this.#fallback = handler\n }\n\n #prepare(): void {\n if (this.#prepared) return\n this.#prepared = true\n this.app.notFound(c => this.#serveFallback(c, undefined))\n this.app.onError((error, c) => this.#serveFallback(c, error))\n }\n\n async #serve(c: Context, handler: AdapterHandler): Promise<Response> {\n const request = adapterRequest(c)\n // Hono routes the WHATWG-normalised URL (`/a/../b` → `/b`, `%2e%2e` resolved): when the raw\n // target differs (Node.js), let the plugin route the raw path, like on the other adapters\n const reply =\n this.#fallback !== undefined && pathOf(request.url) !== pathOf(pathAndQuery(c.req.raw.url))\n ? await this.#fallback(request)\n : await handler(request)\n return this.#toResponse(c, reply)\n }\n\n async #serveFallback(c: Context, error: unknown): Promise<Response> {\n if (this.#fallback === undefined) throw error ?? new Error('No fallback handler')\n return this.#toResponse(c, await this.#fallback(adapterRequest(c), error))\n }\n\n /**\n * Whether `app` has routes the adapter did not register (middleware, other routes): only they can\n * set headers on the context or read the response.\n */\n #middleware(): boolean {\n return this.app.routes.length !== this.#routes\n }\n\n #toResponse(c: Context, reply: AdapterReply): Response {\n const middleware = this.#middleware()\n const outgoing = nodeOutgoing(c)\n if (outgoing !== undefined) {\n const body = reply.body instanceof ReadableStream ? reply.body : null\n if (outgoing.destroyed) {\n // @hono/node-server: the client left before the reply was ready (an async pre-flight). The\n // server would neither read nor cancel the body: release it (event source, request scope)\n discardStream(body)\n return toResponse(c, { ...reply, body: null }, middleware)\n }\n if (body !== null) {\n // the client may leave before @hono/node-server reads the body (its own listener then comes\n // too late): a body nobody locked is cancelled when the response closes unfinished\n outgoing.once('close', () => {\n if (!outgoing.writableFinished) discardStream(body)\n })\n }\n // a quiet event stream must outlive a socket timeout\n if (reply.eventStream === true && typeof outgoing.setTimeout === 'function') outgoing.setTimeout(0)\n }\n if (this.#bun === undefined || !(reply.body instanceof ReadableStream)) return toResponse(c, reply, middleware)\n // served by Bun.serve(): observe the end of the body (idle timeout, native blob: see #bunFetch)\n const reader = reply.body.getReader()\n let ended = false\n let onEnd = (): void => undefined\n const end = (): void => {\n if (ended) return\n ended = true\n onEnd()\n }\n const body = new ReadableStream<Uint8Array>(\n {\n async pull(controller) {\n let result: Awaited<ReturnType<typeof reader.read>>\n try {\n result = await reader.read()\n } catch (error) {\n end()\n throw error\n }\n if (result.done) {\n end() // before closing: Bun ignores timeout() once the response is complete\n controller.close()\n return\n }\n controller.enqueue(result.value)\n },\n cancel(reason) {\n end()\n return reader.cancel(reason)\n },\n },\n { highWaterMark: 0 },\n )\n BUN_BODIES.set(body, {\n blob: reply.blob,\n onEnd: callback => {\n onEnd = callback\n },\n })\n return toResponse(c, { ...reply, body }, middleware)\n }\n\n /** The fetch handler given to `Bun.serve()`. */\n async #bunFetch(request: Request, server: BunServer, idleTimeout: number): Promise<Response> {\n const response = await this.app.fetch(request, server)\n const body = response.body\n const served = body === null ? undefined : BUN_BODIES.get(body)\n if (body === null || served === undefined) return response\n BUN_BODIES.delete(body)\n if (served.blob !== undefined && !body.locked) {\n // a Blob is sent by Bun itself (sendfile, exact Content-Length; a stream would be chunked). Bun\n // opens its file as soon as it takes the response: release the stream (and with it the request\n // scope, which may delete the file) right after\n const native = new Response(served.blob, {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n })\n setImmediate(() => discardStream(body))\n return native\n }\n if (idleTimeout > 0 && typeof server.timeout === 'function') {\n // the idle timeout of Bun.serve() also cuts a response whose stream pauses (SSE, slow producers):\n // lift it while the stream is sent, then restore it for the keep-alive connection\n server.timeout(request, 0)\n served.onEnd(() => {\n try {\n server.timeout?.(request, idleTimeout)\n } catch {\n // the connection is gone\n }\n })\n }\n return response\n }\n\n async listen(port: number, host: string): Promise<HttpServerAddress> {\n this.#prepare()\n this.#closing = undefined\n const bun = (globalThis as { Bun?: BunRuntime }).Bun\n if (isBun() && bun !== undefined) {\n const options = this.#serveOptions\n const idleTimeout = typeof options.idleTimeout === 'number' ? options.idleTimeout : BUN_IDLE_TIMEOUT\n const server = bun.serve({\n // Znight enforces the body limits (route @BodyLimit, plugin bodyLimit): not Bun's 128 MiB default\n maxRequestBodySize: Number.MAX_SAFE_INTEGER,\n ...options,\n port,\n hostname: host,\n fetch: (request, server) => this.#bunFetch(request, server, idleTimeout),\n })\n this.#bun = server\n return serverAddress(server.hostname, server.port)\n }\n let nodeServer: typeof import('@hono/node-server')\n try {\n nodeServer = await import('@hono/node-server')\n } catch (error) {\n throw new Error(\n 'honoAdapter on Node.js requires the optional peer dependency \"@hono/node-server\" (npm i @hono/node-server)',\n { cause: error },\n )\n }\n const server = nodeServer.createAdaptorServer({ fetch: this.app.fetch }) as Server\n this.#node = server\n this.#tracker = new ConnectionTracker(server)\n await new Promise<void>((resolve, reject) => {\n const onError = (error: Error): void => reject(error)\n server.once('error', onError)\n server.listen(port, host, () => {\n server.off('error', onError)\n resolve()\n })\n })\n const address = server.address()\n if (address === null || typeof address === 'string') return serverAddress(host, port)\n return serverAddress(address.address, address.port, address.family)\n }\n\n close(options: AdapterCloseOptions = {}): Promise<void> {\n const bun = this.#bun\n if (bun !== undefined) {\n if (options.force === true) {\n this.#closing = Promise.resolve(bun.stop(true))\n } else {\n this.#closing ??= Promise.resolve(bun.stop(false))\n }\n return this.#closing\n }\n const server = this.#node\n const tracker = this.#tracker\n if (server === undefined || tracker === undefined) return Promise.resolve()\n this.#closing = closeServer(server, tracker, this.#closing, options.force === true)\n return this.#closing\n }\n\n inject(request: InjectRequest): Promise<InjectResponse> {\n return bufferInjectStream(stream => this.injectStream(stream), request)\n }\n\n injectStream(request: InjectStreamRequest): Promise<InjectStreamResponse> {\n this.#prepare()\n return injectFetchStream(request => this.app.request(request, undefined, INJECT_ENV), request)\n }\n}\n\nfunction adapterRequest(c: Context): AdapterRequest {\n return new HonoAdapterRequest(c)\n}\n\n/** A Hono request as an {@link AdapterRequest} (a class: no closure per request). */\nclass HonoAdapterRequest implements AdapterRequest {\n readonly method: string\n readonly url: string\n readonly headers: Readonly<Record<string, string | undefined>>\n readonly ip: string | undefined\n readonly remoteAddress: string | undefined\n readonly encrypted: boolean\n readonly raw: Request\n readonly rawResponse: Context\n /** The Node.js request of `@hono/node-server`, absent on Bun and for `inject()`. */\n readonly #incoming: NodeIncoming | undefined\n /**\n * The same request over HTTP/1.1, read directly (Node.js streams): no Fetch `Request` and body\n * stream are built for it.\n */\n readonly #http1: IncomingMessage | undefined\n #body: Promise<Uint8Array> | undefined\n\n constructor(c: Context) {\n const request = c.req.raw\n const incoming = nodeIncoming(c)\n const http1 = incoming !== undefined && incoming.httpVersionMajor < 2 ? (incoming as IncomingMessage) : undefined\n const address = remoteAddress(c)\n this.method = request.method\n // the raw target on Node.js; on Bun only the normalised URL is known\n this.url = pathAndQuery(typeof incoming?.url === 'string' && incoming.url !== '' ? incoming.url : request.url)\n this.headers = (http1 === undefined ? undefined : distinctHeaders(http1)) ?? fetchHeaders(request.headers)\n this.ip = address\n this.remoteAddress = address\n this.encrypted = incoming === undefined ? request.url.startsWith('https:') : incoming.socket?.encrypted === true\n this.raw = request\n this.rawResponse = c\n this.#incoming = incoming\n this.#http1 = http1\n }\n\n readBody(limit: number): Promise<Uint8Array> {\n if (this.#body === undefined) {\n const http1 = this.#http1\n this.#body =\n http1 === undefined\n ? readWebStream(() => this.raw.body, limit, this.headers['content-length'])\n : readNodeBody(http1, limit)\n }\n return this.#body\n }\n\n streamBody(limit: number): ReadableStream<Uint8Array> {\n const http1 = this.#http1\n if (http1 !== undefined) return nodeBodyStream(http1, limit)\n return webBodyStream(() => this.raw.body, limit, this.headers['content-length'])\n }\n\n // asked only without content-length / transfer-encoding: a request read from an HTTP/1.1\n // connection then has no body (`@hono/node-server` still gives every POST a body stream), an\n // HTTP/2 one unless its HEADERS frame ended the stream; a Web `Request` (Bun, inject) says it\n hasBody(): boolean {\n const incoming = this.#incoming\n return incoming === undefined\n ? this.raw.body !== null\n : incoming.httpVersionMajor >= 2 && incoming.stream?.endAfterHeaders === false\n }\n\n onAbort(listener: () => void): void {\n const outgoing = nodeOutgoing(this.rawResponse)\n if (outgoing !== undefined) {\n onResponseAbort(outgoing as unknown as ServerResponse, listener)\n return\n }\n // Bun.serve() and inject(): the request signal aborts when the client leaves\n const signal = this.raw.signal\n if (signal.aborted) queueMicrotask(listener)\n else signal.addEventListener('abort', listener, { once: true })\n }\n}\n\n/** The headers of a Fetch request: lower-case names, repeated headers joined (`Headers` semantics). */\nfunction fetchHeaders(source: Headers): Record<string, string> {\n const headers: Record<string, string> = {}\n for (const [name, value] of source) headers[name] = value\n return headers\n}\n\n/**\n * The headers of a Node.js request when none is repeated: then they hold the values of its Fetch\n * `Headers` (Node.js keeps the first of some repeated headers where `Headers` join them all).\n */\nfunction distinctHeaders(request: IncomingMessage): Record<string, string | undefined> | undefined {\n const headers = request.headers\n let count = 0\n for (const name in headers) {\n if (typeof headers[name] !== 'string') return undefined\n count++\n }\n return count * 2 === request.rawHeaders.length ? (headers as Record<string, string | undefined>) : undefined\n}\n\n/** The part of a Node.js `ServerResponse` used here. */\ninterface NodeOutgoing {\n readonly writableFinished: boolean\n readonly destroyed: boolean\n once(event: 'close', listener: () => void): unknown\n setTimeout?(ms: number): unknown\n}\n\n/** The Node.js response of `@hono/node-server` (`c.env.outgoing`), absent on Bun and for `inject()`. */\nfunction nodeOutgoing(c: Context): NodeOutgoing | undefined {\n const outgoing = (c.env as { outgoing?: Partial<NodeOutgoing> } | undefined)?.outgoing\n return typeof outgoing?.once === 'function' ? (outgoing as NodeOutgoing) : undefined\n}\n\n/** The Node.js request of `@hono/node-server` (`c.env.incoming`), absent on Bun and for `inject()`. */\nfunction nodeIncoming(c: Context): NodeIncoming | undefined {\n const incoming = (c.env as { incoming?: Partial<NodeIncoming> } | undefined)?.incoming\n return typeof incoming?.httpVersionMajor === 'number' ? (incoming as NodeIncoming) : undefined\n}\n\nfunction pathAndQuery(url: string): string {\n if (url.startsWith('/')) return url\n const scheme = url.indexOf('://')\n const start = url.indexOf('/', scheme === -1 ? 0 : scheme + 3)\n return start === -1 ? '/' : url.slice(start)\n}\n\nfunction pathOf(url: string): string {\n const query = url.indexOf('?')\n return query === -1 ? url : url.slice(0, query)\n}\n\nfunction remoteAddress(c: Context): string | undefined {\n const env = c.env as\n | { incoming?: { socket?: { remoteAddress?: string } }; requestIP?: BunServer['requestIP'] }\n | undefined\n if (env === undefined || env === null) return undefined\n if (typeof env.requestIP === 'function') return env.requestIP(c.req.raw)?.address ?? undefined\n return env.incoming?.socket?.remoteAddress\n}\n\n/**\n * The `Response` of a reply. Without `middleware` (see `#middleware`), nothing set headers on the\n * context and nothing reads the response: headers given as a record of strings are sent as is by\n * `@hono/node-server` (no `Headers` object), and a text body as a string.\n */\nfunction toResponse(c: Context, reply: AdapterReply, middleware: boolean): Response {\n if (reply.raw === true) {\n discardStream(reply.body instanceof ReadableStream ? reply.body : null)\n return c.res\n }\n if (!middleware && isStringRecord(reply.headers)) {\n return new Response(reply.text ?? reply.body, {\n status: reply.status,\n headers: reply.headers as Readonly<Record<string, string>>,\n })\n }\n const headers = new Headers()\n try {\n for (const name of Object.keys(reply.headers)) {\n const value = reply.headers[name]\n if (typeof value === 'string') headers.set(name, value)\n else if (value !== undefined) for (const item of value) headers.append(name, item)\n }\n } catch {\n // a header Headers refuses (the plugin validates them): never rethrow into Hono's onError, which\n // would run the pipeline again; release the body and answer a plain 500, like writeNodeReply\n discardStream(reply.body instanceof ReadableStream ? reply.body : null)\n return new Response('{\"statusCode\":500,\"error\":\"Internal Server Error\"}', {\n status: 500,\n headers: { 'content-type': 'application/json; charset=utf-8' },\n })\n }\n if (middleware) keepContextCookies(c, headers)\n return new Response(reply.text ?? reply.body, { status: reply.status, headers })\n}\n\n/** Whether every header has a single value (a record of strings is a valid `HeadersInit`). */\nfunction isStringRecord(headers: Readonly<Record<string, HeaderValue>>): boolean {\n for (const name in headers) if (typeof headers[name] !== 'string') return false\n return true\n}\n\n/**\n * Keeps the cookies Hono middleware set before the Znight route (`setCookie(c, ...)`,\n * `c.header('set-cookie', ..., { append: true })`) before the reply's own, like the other adapters.\n * Hono drops the headers of the context when a handler returns its own `Response` (or, when a\n * middleware read `c.res`, replaces the response's cookies with the context's): the reply's cookies\n * are added to the context too, so that both ways end with every cookie, in order.\n */\nfunction keepContextCookies(c: Context, headers: Headers): void {\n let context: string[]\n try {\n // the headers of the context without changing it (`c.res` would create the context response)\n context = c.newResponse(null).headers.getSetCookie()\n } catch {\n return // a status a bodyless Response refuses (`c.status(101)`): nothing to keep\n }\n if (context.length === 0) return\n const own = headers.getSetCookie()\n for (const line of own) c.header('set-cookie', line, { append: true })\n headers.delete('set-cookie')\n for (const line of context) headers.append('set-cookie', line)\n for (const line of own) headers.append('set-cookie', line)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"hono.mjs","names":[],"sources":["../src/adapters/hono.ts"],"sourcesContent":["/**\n * `@znight/http/hono`: serves Znight routes with [Hono](https://hono.dev) 4 (optional peer\n * dependency `hono`). On Bun the server is `Bun.serve()`; on Node.js it is `@hono/node-server`\n * (optional peer dependency, loaded on `listen()`). `inject()` uses `app.request()`.\n */\nimport type { IncomingMessage, Server, ServerResponse } from 'node:http'\nimport type {\n AdapterCloseOptions,\n AdapterHandler,\n AdapterReply,\n AdapterRequest,\n AdapterRoute,\n FallbackHandler,\n HttpAdapter,\n HttpServerAddress,\n InjectRequest,\n InjectResponse,\n InjectStreamRequest,\n InjectStreamResponse,\n} from '../adapter.ts'\nimport type { HeaderValue } from '../exceptions.ts'\n\nimport { type Context, Hono, type Next } from 'hono'\n\nimport { BadRequestException } from '../exceptions.ts'\nimport { toHonoPath } from '../path.ts'\nimport { discardStream } from '../stream.ts'\nimport { isBun } from '../utils.ts'\nimport { ConnectionTracker, closeServer, nodeBodyStream, onResponseAbort, readNodeBody } from './node-io.ts'\nimport {\n bufferInjectStream,\n exceedsDeclaredLength,\n injectFetchStream,\n payloadTooLarge,\n readWebStream,\n serverAddress,\n webBodyStream,\n} from './shared.ts'\n\n/** Options of {@link honoAdapter}. */\nexport interface HonoAdapterOptions {\n /**\n * An existing application: its middleware (`app.use(...)`) runs before the Znight routes. Znight\n * sets its `notFound` and `onError` handlers.\n */\n readonly app?: Hono\n /**\n * Options of `Bun.serve()` on Bun (`tls`, `idleTimeout`, `development`, `error`...); Znight sets\n * `port`, `hostname` and `fetch`. Ignored on Node.js.\n */\n readonly serveOptions?: HonoServeOptions\n}\n\n/**\n * Options of `Bun.serve()` (see the Bun documentation; Bun types are not required). Defaults set by\n * the adapter: `maxRequestBodySize` unlimited (Znight enforces `bodyLimit` / `@BodyLimit` itself)\n * and Bun's `idleTimeout` (10 s), lifted while a streamed response is sent.\n */\nexport interface HonoServeOptions {\n /** Seconds a connection may stay idle (`0`: never closed). Default: 10. */\n readonly idleTimeout?: number\n /** Request bodies above this size are refused by Bun (413). Default: unlimited. */\n readonly maxRequestBodySize?: number\n /** TLS options (`{ key, cert }`). */\n readonly tls?: unknown\n /** Any other `Bun.serve()` option. */\n readonly [option: string]: unknown\n}\n\n/** The Hono adapter. */\nexport interface HonoAdapter extends HttpAdapter {\n readonly name: 'hono'\n /** The Hono application. */\n readonly app: Hono\n /** Fetch handler of the application (`export default { fetch: adapter.fetch }` on edge runtimes). */\n readonly fetch: (request: Request, env?: unknown) => Response | Promise<Response>\n}\n\n/**\n * Creates a Hono adapter.\n *\n * ```ts\n * import { honoAdapter } from '@znight/http/hono'\n *\n * HttpPlugin.configure({ adapter: honoAdapter() })\n * ```\n */\nexport function honoAdapter(options: HonoAdapterOptions = {}): HonoAdapter {\n return new HonoHttpAdapter(options)\n}\n\n/** The parts of a Bun server used here (Bun types are not available to the package sources). */\ninterface BunServer {\n readonly port: number\n readonly hostname: string\n stop(closeActiveConnections?: boolean): Promise<void> | void\n requestIP?(request: Request): { readonly address: string } | null\n /** Idle timeout of one request, in seconds (`0`: none). */\n timeout?(request: Request, seconds: number): void\n}\n\ninterface BunRuntime {\n serve(\n options: HonoServeOptions & {\n port: number\n hostname: string\n fetch: (request: Request, server: BunServer) => Response | Promise<Response>\n },\n ): BunServer\n}\n\n/** Default `idleTimeout` of `Bun.serve()`, in seconds. */\nconst BUN_IDLE_TIMEOUT = 10\n\n/**\n * A streamed response body built for a request of `Bun.serve()` (see `#bunStream`); with middleware,\n * pending until `#bunFetch` takes it or it ends.\n */\nclass BunBody {\n /** The `Blob` (file) the body reads. */\n readonly blob: Blob | undefined\n #settle: (() => void) | undefined\n #onEnd: (() => void) | undefined\n #ended = false\n\n /** `settle` is called once, when the body is taken by `#bunFetch` or ends, whichever comes first. */\n constructor(blob: Blob | undefined, settle: (() => void) | undefined) {\n this.blob = blob\n this.#settle = settle\n }\n\n /** Whether the body ended (read to its end, failed or cancelled). */\n get ended(): boolean {\n return this.#ended\n }\n\n /** The body is no longer pending (taken by `#bunFetch`). */\n settle(): void {\n const settle = this.#settle\n this.#settle = undefined\n settle?.()\n }\n\n /** Sets the function called when the body ends. */\n onEnd(callback: () => void): void {\n this.#onEnd = callback\n }\n\n /** The body ended (read to its end, failed or cancelled). */\n end(): void {\n if (this.#ended) return\n this.#ended = true\n this.settle()\n this.#onEnd?.()\n }\n}\n\n/** Response bodies built while serving with `Bun.serve()`, by body (Hono may copy a `Response`, not its body). */\nconst BUN_BODIES = new WeakMap<ReadableStream<Uint8Array>, BunBody>()\n\n/** The Node.js request behind a request of `@hono/node-server`. */\ninterface NodeIncoming {\n readonly url?: string\n readonly socket?: { readonly encrypted?: unknown }\n readonly httpVersionMajor: number\n readonly stream?: { readonly endAfterHeaders?: boolean }\n}\n\n/** Bindings of injected requests (the remote address the other adapters report for `inject()`). */\nconst INJECT_ENV = Object.freeze({ incoming: Object.freeze({ socket: Object.freeze({ remoteAddress: '127.0.0.1' }) }) })\n\ntype HonoMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS'\n\n/** The handlers of the GET and HEAD routes of one path. */\ninterface ReadHandlers {\n get: AdapterHandler | undefined\n head: AdapterHandler | undefined\n}\n\nclass HonoHttpAdapter implements HonoAdapter {\n readonly name = 'hono' as const\n readonly app: Hono\n readonly #serveOptions: HonoServeOptions\n /** Number of routes of `app` registered by the adapter (see {@link #middleware}). */\n #routes = 0\n /** The GET and HEAD handlers of each path shape, served by one route of `app` (see {@link registerRoute}). */\n readonly #reads = new Map<string, ReadHandlers>()\n #fallback: FallbackHandler | undefined\n #prepared = false\n #bun: BunServer | undefined\n /**\n * Streamed bodies of `Bun.serve()` replies (with middleware) neither taken by `#bunFetch` nor ended\n * (see {@link BunBody}): while there is none, `#bunFetch` returns responses untouched (reading\n * `response.body` would turn a text body into a stream that Bun then sends as one).\n */\n #pendingBodies = 0\n /** Idle timeout of the `Bun.serve()` server, in seconds (`0`: none). */\n #idleTimeout = BUN_IDLE_TIMEOUT\n #node: Server | undefined\n #tracker: ConnectionTracker | undefined\n #closing: Promise<void> | undefined\n\n constructor(options: HonoAdapterOptions) {\n this.app = options.app ?? new Hono()\n this.#serveOptions = options.serveOptions ?? {}\n }\n\n readonly fetch = (request: Request, env?: unknown): Response | Promise<Response> => {\n this.#prepare()\n return this.app.fetch(request, env)\n }\n\n registerRoute(route: AdapterRoute): void {\n const handler = route.handler\n const path = toHonoPath(route.path)\n if (route.method === 'GET' || route.method === 'HEAD') {\n // Hono routes HEAD requests to GET handlers: one GET handler per path serves both methods (the\n // plugin registers HEAD routes first), so that a request matches a single handler and Hono\n // skips its middleware composition. Paths are keyed by their shape (`/users/:` for\n // `/users/:id` and `/users/:userId`): a GET route joins the HEAD route matching the same\n // requests, at its rank (Znight reads the parameters itself, never Hono's names)\n const existing = this.#reads.get(route.path.shape)\n if (existing !== undefined) {\n if (route.method === 'GET') existing.get = handler\n else existing.head = handler\n return\n }\n const reads: ReadHandlers =\n route.method === 'GET' ? { get: handler, head: undefined } : { get: undefined, head: handler }\n this.#reads.set(route.path.shape, reads)\n this.#routes++\n this.app.get(path, (c: Context, next: Next) => {\n const read = c.req.raw.method === 'HEAD' ? reads.head : reads.get\n return read === undefined ? next().then(() => c.res) : this.#serve(c, read)\n })\n return\n }\n this.#routes++\n this.app.on(route.method as HonoMethod, path, (c: Context) => this.#serve(c, handler))\n }\n\n setFallbackHandler(handler: FallbackHandler): void {\n this.#fallback = handler\n }\n\n #prepare(): void {\n if (this.#prepared) return\n this.#prepared = true\n this.app.notFound(c => this.#serveFallback(c, undefined))\n this.app.onError((error, c) => this.#serveFallback(c, error))\n }\n\n async #serve(c: Context, handler: AdapterHandler): Promise<Response> {\n const request = this.#request(c)\n // Hono routes the WHATWG-normalised URL (`/a/../b` → `/b`, `%2e%2e` resolved): when the raw\n // target differs (Node.js; on Bun only the normalised URL is known), let the plugin route the raw\n // path, like on the other adapters\n const reply =\n this.#fallback !== undefined && request.rawTarget && pathOf(request.url) !== pathOf(pathAndQuery(c.req.raw.url))\n ? await this.#fallback(request)\n : await handler(request)\n return this.#toResponse(c, reply)\n }\n\n async #serveFallback(c: Context, error: unknown): Promise<Response> {\n if (this.#fallback === undefined) throw error ?? new Error('No fallback handler')\n return this.#toResponse(c, await this.#fallback(this.#request(c), error))\n }\n\n #request(c: Context): HonoAdapterRequest {\n return new HonoAdapterRequest(c, this.#servedByBun(c) ? this.#bun : undefined)\n }\n\n /** Whether the request of `c` was received by the `Bun.serve()` server of the adapter. */\n #servedByBun(c: Context): boolean {\n return this.#bun !== undefined && c.env === this.#bun\n }\n\n /**\n * Whether `app` has routes the adapter did not register (middleware, other routes): only they can\n * set headers on the context or read the response.\n */\n #middleware(): boolean {\n return this.app.routes.length !== this.#routes\n }\n\n #toResponse(c: Context, reply: AdapterReply): Response {\n const middleware = this.#middleware()\n if (this.#servedByBun(c)) {\n return reply.body instanceof ReadableStream\n ? this.#bunStream(c, reply, middleware)\n : toResponse(c, reply, middleware)\n }\n const outgoing = nodeOutgoing(c)\n if (outgoing !== undefined) {\n const body = reply.body instanceof ReadableStream ? reply.body : null\n if (outgoing.destroyed) {\n // @hono/node-server: the client left before the reply was ready (an async pre-flight). The\n // server would neither read nor cancel the body: release it (event source, request scope)\n discardStream(body)\n return toResponse(c, { ...reply, body: null }, middleware)\n }\n if (body !== null) {\n // the client may leave before @hono/node-server reads the body (its own listener then comes\n // too late): a body nobody locked is cancelled when the response closes unfinished\n outgoing.once('close', () => {\n if (!outgoing.writableFinished) discardStream(body)\n })\n }\n // a quiet event stream must outlive a socket timeout\n if (reply.eventStream === true && typeof outgoing.setTimeout === 'function') outgoing.setTimeout(0)\n }\n return toResponse(c, reply, middleware)\n }\n\n /**\n * The response of a streamed reply to a request of `Bun.serve()`: the end of its body is observed\n * (idle timeout, native blob: see {@link #bunSend}).\n */\n #bunStream(c: Context, reply: AdapterReply, middleware: boolean): Response {\n const reader = (reply.body as ReadableStream<Uint8Array>).getReader()\n const served = new BunBody(\n reply.blob,\n middleware\n ? () => {\n this.#pendingBodies--\n }\n : undefined,\n )\n const body = new ReadableStream<Uint8Array>(\n {\n async pull(controller) {\n let result: Awaited<ReturnType<typeof reader.read>>\n try {\n result = await reader.read()\n } catch (error) {\n served.end()\n throw error\n }\n if (result.done) {\n served.end() // before closing: Bun ignores timeout() once the response is complete\n controller.close()\n return\n }\n controller.enqueue(result.value)\n },\n cancel(reason) {\n served.end()\n return reader.cancel(reason)\n },\n },\n { highWaterMark: 0 },\n )\n const response = toResponse(c, { ...reply, body }, middleware)\n if (!middleware) {\n // nothing else handles the request (see the fetch handler given to Bun.serve()): Bun sends this\n // response, unless it does not carry the body (an invalid header: the body was discarded)\n return served.ended ? response : this.#bunSend(response, body, served, c.req.raw)\n }\n // middleware may copy the response (not its body) or replace it: #bunFetch finds the body\n this.#pendingBodies++\n BUN_BODIES.set(body, served)\n return response\n }\n\n /** The fetch handler given to `Bun.serve()` when `app` has middleware (see {@link #bunStream}). */\n async #bunFetch(request: Request, server: BunServer): Promise<Response> {\n const response = await this.app.fetch(request, server)\n // only a streamed reply needs more: leave the other responses untouched (see #pendingBodies)\n if (this.#pendingBodies === 0) return response\n const body = response.body\n const served = body === null ? undefined : BUN_BODIES.get(body)\n if (body === null || served === undefined) return response\n BUN_BODIES.delete(body)\n served.settle()\n return this.#bunSend(response, body, served, request)\n }\n\n /**\n * The response Bun sends for a streamed reply, once final: a file `Blob` is sent natively, a\n * stream with the idle timeout of the connection lifted.\n */\n #bunSend(response: Response, body: ReadableStream<Uint8Array>, served: BunBody, request: Request): Response {\n if (served.blob !== undefined && !body.locked) {\n // a Blob is sent by Bun itself (sendfile, exact Content-Length; a stream would be chunked). Bun\n // opens its file as soon as it takes the response: release the stream (and with it the request\n // scope, which may delete the file) right after\n const native = new Response(served.blob, {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n })\n setImmediate(() => discardStream(body))\n return native\n }\n const server = this.#bun\n const idleTimeout = this.#idleTimeout\n if (idleTimeout > 0 && !served.ended && typeof server?.timeout === 'function') {\n // the idle timeout of Bun.serve() also cuts a response whose stream pauses (SSE, slow producers):\n // lift it while the stream is sent, then restore it for the keep-alive connection (a body that\n // already ended would never restore it)\n server.timeout(request, 0)\n served.onEnd(() => {\n try {\n server.timeout?.(request, idleTimeout)\n } catch {\n // the connection is gone\n }\n })\n }\n return response\n }\n\n async listen(port: number, host: string): Promise<HttpServerAddress> {\n this.#prepare()\n this.#closing = undefined\n const bun = (globalThis as { Bun?: BunRuntime }).Bun\n if (isBun() && bun !== undefined) {\n const options = this.#serveOptions\n this.#idleTimeout = typeof options.idleTimeout === 'number' ? options.idleTimeout : BUN_IDLE_TIMEOUT\n const server = bun.serve({\n // Znight enforces the body limits (route @BodyLimit, plugin bodyLimit): not Bun's 128 MiB default\n maxRequestBodySize: Number.MAX_SAFE_INTEGER,\n ...options,\n port,\n hostname: host,\n // without middleware, the response of a Znight route is the one Bun sends (no wrapper needed)\n fetch: (request, server) =>\n this.#middleware() ? this.#bunFetch(request, server) : this.app.fetch(request, server),\n })\n this.#bun = server\n return serverAddress(server.hostname, server.port)\n }\n let nodeServer: typeof import('@hono/node-server')\n try {\n nodeServer = await import('@hono/node-server')\n } catch (error) {\n throw new Error(\n 'honoAdapter on Node.js requires the optional peer dependency \"@hono/node-server\" (npm i @hono/node-server)',\n { cause: error },\n )\n }\n const server = nodeServer.createAdaptorServer({ fetch: this.app.fetch }) as Server\n this.#node = server\n this.#tracker = new ConnectionTracker(server)\n await new Promise<void>((resolve, reject) => {\n const onError = (error: Error): void => reject(error)\n server.once('error', onError)\n server.listen(port, host, () => {\n server.off('error', onError)\n resolve()\n })\n })\n const address = server.address()\n if (address === null || typeof address === 'string') return serverAddress(host, port)\n return serverAddress(address.address, address.port, address.family)\n }\n\n close(options: AdapterCloseOptions = {}): Promise<void> {\n const bun = this.#bun\n if (bun !== undefined) {\n if (options.force === true) {\n this.#closing = Promise.resolve(bun.stop(true))\n } else {\n this.#closing ??= Promise.resolve(bun.stop(false))\n }\n return this.#closing\n }\n const server = this.#node\n const tracker = this.#tracker\n if (server === undefined || tracker === undefined) return Promise.resolve()\n this.#closing = closeServer(server, tracker, this.#closing, options.force === true)\n return this.#closing\n }\n\n inject(request: InjectRequest): Promise<InjectResponse> {\n return bufferInjectStream(stream => this.injectStream(stream), request)\n }\n\n injectStream(request: InjectStreamRequest): Promise<InjectStreamResponse> {\n this.#prepare()\n return injectFetchStream(request => this.app.request(request, undefined, INJECT_ENV), request)\n }\n}\n\n/** A Hono request as an {@link AdapterRequest} (a class: no closure per request). */\nclass HonoAdapterRequest implements AdapterRequest {\n readonly method: string\n readonly url: string\n readonly headers: Readonly<Record<string, string | undefined>>\n readonly ip: string | undefined\n readonly remoteAddress: string | undefined\n readonly encrypted: boolean\n readonly raw: Request\n readonly rawResponse: Context\n /** `url` is the raw request target (Node.js), not the WHATWG-normalised URL of `raw`. */\n readonly rawTarget: boolean\n /** The Node.js request of `@hono/node-server`, absent on Bun and for `inject()`. */\n readonly #incoming: NodeIncoming | undefined\n /**\n * The same request over HTTP/1.1, read directly (Node.js streams): no Fetch `Request` and body\n * stream are built for it.\n */\n readonly #http1: IncomingMessage | undefined\n /** Received by the `Bun.serve()` server of the adapter: its body is framed by the HTTP parser. */\n readonly #bun: boolean\n #body: Promise<Uint8Array> | undefined\n\n constructor(c: Context, bun: BunServer | undefined) {\n const request = c.req.raw\n this.method = request.method\n this.raw = request\n this.rawResponse = c\n this.#bun = bun !== undefined\n if (bun !== undefined) {\n // Bun.serve(): only the normalised URL is known; the address is asked now (Bun no longer knows\n // it once the response is sent)\n const url = request.url\n const address = bun.requestIP?.(request)?.address ?? undefined\n this.url = pathAndQuery(url)\n this.rawTarget = false\n this.headers = fetchHeaders(request.headers)\n this.ip = address\n this.remoteAddress = address\n this.encrypted = url.startsWith('https:')\n this.#incoming = undefined\n this.#http1 = undefined\n return\n }\n const incoming = nodeIncoming(c)\n const http1 = incoming !== undefined && incoming.httpVersionMajor < 2 ? (incoming as IncomingMessage) : undefined\n const address = remoteAddress(c)\n const target = incoming?.url\n // the raw target on Node.js; for a Web request only the normalised URL is known\n this.rawTarget = typeof target === 'string' && target !== ''\n this.url = pathAndQuery(this.rawTarget ? (target as string) : request.url)\n this.headers = (http1 === undefined ? undefined : distinctHeaders(http1)) ?? fetchHeaders(request.headers)\n this.ip = address\n this.remoteAddress = address\n this.encrypted = incoming === undefined ? request.url.startsWith('https:') : incoming.socket?.encrypted === true\n this.#incoming = incoming\n this.#http1 = http1\n }\n\n readBody(limit: number): Promise<Uint8Array> {\n if (this.#body === undefined) {\n const http1 = this.#http1\n if (http1 !== undefined) this.#body = readNodeBody(http1, limit)\n else if (this.#bun && this.#framed()) this.#body = readFramedBody(this.raw, limit, this.headers['content-length'])\n else this.#body = readWebStream(() => this.raw.body, limit, this.headers['content-length'])\n }\n return this.#body\n }\n\n /**\n * Whether the whole body can be read at once from `Bun.serve()`: its length is declared (the\n * HTTP parser then reads no more) and nothing read it yet.\n */\n #framed(): boolean {\n const headers = this.headers\n return headers['content-length'] !== undefined && headers['transfer-encoding'] === undefined && !this.raw.bodyUsed\n }\n\n streamBody(limit: number): ReadableStream<Uint8Array> {\n const http1 = this.#http1\n if (http1 !== undefined) return nodeBodyStream(http1, limit)\n return webBodyStream(() => this.raw.body, limit, this.headers['content-length'])\n }\n\n // asked only without content-length / transfer-encoding: a request read from an HTTP/1.1\n // connection then has no body (`@hono/node-server` still gives every POST a body stream), an\n // HTTP/2 one unless its HEADERS frame ended the stream; a Web `Request` (Bun, inject) says it\n hasBody(): boolean {\n const incoming = this.#incoming\n return incoming === undefined\n ? this.raw.body !== null\n : incoming.httpVersionMajor >= 2 && incoming.stream?.endAfterHeaders === false\n }\n\n onAbort(listener: () => void): void {\n const outgoing = this.#bun ? undefined : nodeOutgoing(this.rawResponse)\n if (outgoing !== undefined) {\n onResponseAbort(outgoing as unknown as ServerResponse, listener)\n return\n }\n // Bun.serve() and inject(): the request signal aborts when the client leaves\n const signal = this.raw.signal\n if (signal.aborted) queueMicrotask(listener)\n else signal.addEventListener('abort', listener, { once: true })\n }\n}\n\n/** The headers of a Fetch request: lower-case names, repeated headers joined (`Headers` semantics). */\nfunction fetchHeaders(source: Headers): Record<string, string> {\n const headers: Record<string, string> = {}\n // forEach: no iterator nor entry array per header\n source.forEach((value, name) => {\n headers[name] = value\n })\n return headers\n}\n\n/**\n * Reads at once (`request.bytes()`, no stream) the body of a request received by `Bun.serve()`\n * whose `content-length` is declared: Bun reads exactly that many bytes, so checking the declared\n * length enforces `limit`. Failures are those of {@link readWebStream}: 413 above `limit`, 400 when\n * the client aborts.\n */\nasync function readFramedBody(request: Request, limit: number, contentLength: string | undefined): Promise<Uint8Array> {\n if (exceedsDeclaredLength(contentLength, limit)) throw payloadTooLarge(limit)\n let bytes: Uint8Array\n try {\n bytes = await request.bytes()\n } catch (error) {\n throw new BadRequestException('Failed to read the request body', undefined, { cause: error })\n }\n if (bytes.byteLength > limit) throw payloadTooLarge(limit)\n return bytes\n}\n\n/**\n * The headers of a Node.js request when none is repeated: then they hold the values of its Fetch\n * `Headers` (Node.js keeps the first of some repeated headers where `Headers` join them all).\n */\nfunction distinctHeaders(request: IncomingMessage): Record<string, string | undefined> | undefined {\n const headers = request.headers\n let count = 0\n for (const name in headers) {\n if (typeof headers[name] !== 'string') return undefined\n count++\n }\n return count * 2 === request.rawHeaders.length ? (headers as Record<string, string | undefined>) : undefined\n}\n\n/** The part of a Node.js `ServerResponse` used here. */\ninterface NodeOutgoing {\n readonly writableFinished: boolean\n readonly destroyed: boolean\n once(event: 'close', listener: () => void): unknown\n setTimeout?(ms: number): unknown\n}\n\n/** The Node.js response of `@hono/node-server` (`c.env.outgoing`), absent on Bun and for `inject()`. */\nfunction nodeOutgoing(c: Context): NodeOutgoing | undefined {\n const outgoing = (c.env as { outgoing?: Partial<NodeOutgoing> } | undefined)?.outgoing\n return typeof outgoing?.once === 'function' ? (outgoing as NodeOutgoing) : undefined\n}\n\n/** The Node.js request of `@hono/node-server` (`c.env.incoming`), absent on Bun and for `inject()`. */\nfunction nodeIncoming(c: Context): NodeIncoming | undefined {\n const incoming = (c.env as { incoming?: Partial<NodeIncoming> } | undefined)?.incoming\n return typeof incoming?.httpVersionMajor === 'number' ? (incoming as NodeIncoming) : undefined\n}\n\nfunction pathAndQuery(url: string): string {\n if (url.startsWith('/')) return url\n const scheme = url.indexOf('://')\n const start = url.indexOf('/', scheme === -1 ? 0 : scheme + 3)\n return start === -1 ? '/' : url.slice(start)\n}\n\nfunction pathOf(url: string): string {\n const query = url.indexOf('?')\n return query === -1 ? url : url.slice(0, query)\n}\n\nfunction remoteAddress(c: Context): string | undefined {\n const env = c.env as\n | { incoming?: { socket?: { remoteAddress?: string } }; requestIP?: BunServer['requestIP'] }\n | undefined\n if (env === undefined || env === null) return undefined\n if (typeof env.requestIP === 'function') return env.requestIP(c.req.raw)?.address ?? undefined\n return env.incoming?.socket?.remoteAddress\n}\n\n/**\n * The `Response` of a reply. Without `middleware` (see `#middleware`), nothing set headers on the\n * context and nothing reads the response: headers given as a record of strings are sent as is by\n * `@hono/node-server` (no `Headers` object), and a text body as a string.\n */\nfunction toResponse(c: Context, reply: AdapterReply, middleware: boolean): Response {\n if (reply.raw === true) {\n discardStream(reply.body instanceof ReadableStream ? reply.body : null)\n return c.res\n }\n if (!middleware && isStringRecord(reply.headers)) {\n return new Response(reply.text ?? reply.body, {\n status: reply.status,\n headers: reply.headers as Readonly<Record<string, string>>,\n })\n }\n const headers = new Headers()\n try {\n for (const name of Object.keys(reply.headers)) {\n const value = reply.headers[name]\n if (typeof value === 'string') headers.set(name, value)\n else if (value !== undefined) for (const item of value) headers.append(name, item)\n }\n } catch {\n // a header Headers refuses (the plugin validates them): never rethrow into Hono's onError, which\n // would run the pipeline again; release the body and answer a plain 500, like writeNodeReply\n discardStream(reply.body instanceof ReadableStream ? reply.body : null)\n return new Response('{\"statusCode\":500,\"error\":\"Internal Server Error\"}', {\n status: 500,\n headers: { 'content-type': 'application/json; charset=utf-8' },\n })\n }\n if (middleware) keepContextCookies(c, headers)\n return new Response(reply.text ?? reply.body, { status: reply.status, headers })\n}\n\n/** Whether every header has a single value (a record of strings is a valid `HeadersInit`). */\nfunction isStringRecord(headers: Readonly<Record<string, HeaderValue>>): boolean {\n // Object.keys: faster than for...in on the null-prototype records of the pipeline (V8)\n const names = Object.keys(headers)\n for (let index = 0; index < names.length; index++) {\n if (typeof headers[names[index] as string] !== 'string') return false\n }\n return true\n}\n\n/**\n * Keeps the cookies Hono middleware set before the Znight route (`setCookie(c, ...)`,\n * `c.header('set-cookie', ..., { append: true })`) before the reply's own, like the other adapters.\n * Hono drops the headers of the context when a handler returns its own `Response` (or, when a\n * middleware read `c.res`, replaces the response's cookies with the context's): the reply's cookies\n * are added to the context too, so that both ways end with every cookie, in order.\n */\nfunction keepContextCookies(c: Context, headers: Headers): void {\n let context: string[]\n try {\n // the headers of the context without changing it (`c.res` would create the context response)\n context = c.newResponse(null).headers.getSetCookie()\n } catch {\n return // a status a bodyless Response refuses (`c.status(101)`): nothing to keep\n }\n if (context.length === 0) return\n const own = headers.getSetCookie()\n for (const line of own) c.header('set-cookie', line, { append: true })\n headers.delete('set-cookie')\n for (const line of context) headers.append('set-cookie', line)\n for (const line of own) headers.append('set-cookie', line)\n}\n"],"mappings":"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"}
package/dist/index.d.mts CHANGED
@@ -1234,13 +1234,21 @@ export declare const HttpTokens: {
1234
1234
  /** The {@link HttpPlugin} instance (singleton). */
1235
1235
  readonly Plugin: TypedToken<HttpPlugin>;
1236
1236
  };
1237
+ /** Internal helpers (not part of the public API). */
1238
+ /**
1239
+ * Whether `value` is a promise-like object (has a callable `then`). The guard keeps the thenable
1240
+ * part of the value's type (`Promise<R>` of `R | Promise<R>`, even when `R` is `object` or `any`), so
1241
+ * that generated code awaiting it gets `R`.
1242
+ */
1243
+ export declare function isThenable<T>(value: T): value is unknown extends T ? T & PromiseLike<unknown> : Extract<T, PromiseLike<unknown>>;
1237
1244
  /**
1238
1245
  * Validates `value` with a [Standard Schema](https://standardschema.dev) (Zod, Valibot, ArkType...)
1239
1246
  * and returns the schema **output** (defaults applied, transforms run, unknown keys stripped when
1240
1247
  * the schema strips them).
1241
1248
  *
1242
1249
  * Returns synchronously when the schema validates synchronously, a promise otherwise (async
1243
- * refinements): generated code always `await`s it.
1250
+ * refinements): generated code calls the handler directly with the value, and `await`s the value
1251
+ * when it is a thenable (`isThenable`).
1244
1252
  *
1245
1253
  * @param location what is validated, used in the error message: `body`, `query`, `query "page"`,
1246
1254
  * `param "id"`, `headers`, `cookie "sid"`, `cookies`...