@znight/http 0.1.0 → 0.1.2

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.
@@ -1,4 +1,4 @@
1
- import { d as writeNodeReply, f as bufferInjectStream, i as nodeAdapterRequest, m as injectNodeStream, n as closeServer, t as ConnectionTracker, u as writeNodeFailure, y as serverAddress } from "./node-io-BY1ZDLRQ.mjs";
1
+ import { d as writeNodeReply, f as bufferInjectStream, i as nodeAdapterRequest, m as injectNodeStream, n as closeServer, t as ConnectionTracker, u as writeNodeFailure, y as serverAddress } from "./node-io-CVnqpVNS.mjs";
2
2
  import { createServer } from "node:http";
3
3
  // src/router.ts
4
4
  function createNode() {
@@ -180,4 +180,4 @@ var NodeHttpAdapter = class {
180
180
  };
181
181
  export { RouteTable as n, nodeAdapter as t };
182
182
 
183
- //# sourceMappingURL=node-BUnkdSsa.mjs.map
183
+ //# sourceMappingURL=node-Clm1Ia7T.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"node-BUnkdSsa.mjs","names":[],"sources":["../src/router.ts","../src/adapters/node.ts"],"sourcesContent":["import type { RoutePath } from './path.ts'\n\ninterface RouteNode<T> {\n statics: Map<string, RouteNode<T>> | undefined\n param: RouteNode<T> | undefined\n /** Values of routes ending with a wildcard at this position, by method. */\n wildcard: Map<string, T> | undefined\n /** Values of routes ending at this node, by method. */\n values: Map<string, T> | undefined\n}\n\nfunction createNode<T>(): RouteNode<T> {\n return { statics: undefined, param: undefined, wildcard: undefined, values: undefined }\n}\n\n/**\n * A segment trie mapping `(method, route path)` to values. Lookups prefer static segments over\n * parameters over the wildcard, with backtracking (the same semantics as find-my-way), and are\n * case-sensitive on the raw request path. Used by the built-in Node adapter for routing and by the\n * plugin to tell `404 Not Found` from `405 Method Not Allowed`.\n */\nexport class RouteTable<T> {\n readonly #root: RouteNode<T> = createNode()\n #size = 0\n\n /** Number of `(method, route)` entries. */\n get size(): number {\n return this.#size\n }\n\n /**\n * Adds a value for `method` (upper case) and `route`.\n *\n * @returns the value already registered for the same method and shape, in which case nothing is\n * added.\n */\n add(method: string, route: RoutePath, value: T): T | undefined {\n let node = this.#root\n for (const segment of route.segments) {\n if (segment.kind === 'static') {\n node.statics ??= new Map()\n let child = node.statics.get(segment.value)\n if (child === undefined) {\n child = createNode()\n node.statics.set(segment.value, child)\n }\n node = child\n } else if (segment.kind === 'param') {\n node.param ??= createNode()\n node = node.param\n } else {\n node.wildcard ??= new Map()\n return this.#set(node.wildcard, method, value)\n }\n }\n node.values ??= new Map()\n return this.#set(node.values, method, value)\n }\n\n #set(map: Map<string, T>, method: string, value: T): T | undefined {\n const existing = map.get(method)\n if (existing !== undefined) return existing\n map.set(method, value)\n this.#size++\n return undefined\n }\n\n /** The value of the most specific route matching `method` and the raw `path` (no query string). */\n find(method: string, path: string): T | undefined {\n if (path.charCodeAt(0) !== 47 /* / */) return undefined\n if (path === '/') return this.#root.values?.get(method) ?? this.#root.wildcard?.get(method)\n return search(this.#root, path.split('/'), 1, method)\n }\n\n /** Methods of every route matching the raw `path`, sorted. */\n allowedMethods(path: string): string[] {\n const methods = new Set<string>()\n if (path.charCodeAt(0) === 47 /* / */) {\n if (path === '/') {\n addKeys(methods, this.#root.values)\n addKeys(methods, this.#root.wildcard)\n } else {\n collect(this.#root, path.split('/'), 1, methods)\n }\n }\n return [...methods].sort()\n }\n}\n\nfunction search<T>(node: RouteNode<T>, segments: readonly string[], index: number, method: string): T | undefined {\n if (index === segments.length) return node.values?.get(method)\n const segment = segments[index] as string\n const child = node.statics?.get(segment)\n if (child !== undefined) {\n const found = search(child, segments, index + 1, method)\n if (found !== undefined) return found\n }\n if (segment !== '' && node.param !== undefined) {\n const found = search(node.param, segments, index + 1, method)\n if (found !== undefined) return found\n }\n return node.wildcard?.get(method)\n}\n\nfunction collect<T>(node: RouteNode<T>, segments: readonly string[], index: number, methods: Set<string>): void {\n if (index === segments.length) {\n addKeys(methods, node.values)\n return\n }\n const segment = segments[index] as string\n const child = node.statics?.get(segment)\n if (child !== undefined) collect(child, segments, index + 1, methods)\n if (segment !== '' && node.param !== undefined) collect(node.param, segments, index + 1, methods)\n addKeys(methods, node.wildcard)\n}\n\nfunction addKeys(target: Set<string>, source: Map<string, unknown> | undefined): void {\n if (source !== undefined) for (const key of source.keys()) target.add(key)\n}\n","/**\n * `@znight/http/node`: the built-in, zero-dependency adapter on `node:http` (default adapter of\n * `HttpPlugin`). Works on Node.js and Bun. `inject()` uses the optional peer `light-my-request`.\n */\nimport type {\n AdapterCloseOptions,\n AdapterHandler,\n AdapterRoute,\n FallbackHandler,\n HttpAdapter,\n HttpServerAddress,\n InjectRequest,\n InjectResponse,\n InjectStreamRequest,\n InjectStreamResponse,\n} from '../adapter.ts'\n\nimport { createServer, type IncomingMessage, type Server, type ServerOptions, type ServerResponse } from 'node:http'\n\nimport { RouteTable } from '../router.ts'\nimport { ConnectionTracker, closeServer, nodeAdapterRequest, writeNodeFailure, writeNodeReply } from './node-io.ts'\nimport { bufferInjectStream, injectNodeStream, serverAddress } from './shared.ts'\n\n/** Options of {@link nodeAdapter}. */\nexport interface NodeAdapterOptions {\n /** Options of `http.createServer()` (timeouts, `maxHeaderSize`...). Ignored with {@link server}. */\n readonly serverOptions?: ServerOptions\n /**\n * An existing server (e.g. `https.createServer({ key, cert })`). The adapter adds its `request`\n * listener on `listen()` and closes the server on shutdown.\n */\n readonly server?: Server\n}\n\n/** The built-in Node.js adapter. */\nexport interface NodeAdapter extends HttpAdapter {\n readonly name: 'node'\n /** Request listener serving the routes, to mount the application in a server you manage. */\n readonly handler: (request: IncomingMessage, response: ServerResponse) => void\n /** The server, once listening (or the one given in the options). */\n readonly server: Server | undefined\n}\n\n/**\n * Creates the built-in `node:http` adapter: a segment-trie router (static > `:param` > `*`, with\n * backtracking), zero dependencies.\n *\n * ```ts\n * HttpPlugin.configure({ adapter: nodeAdapter({ serverOptions: { requestTimeout: 30_000 } }) })\n * ```\n */\nexport function nodeAdapter(options: NodeAdapterOptions = {}): NodeAdapter {\n return new NodeHttpAdapter(options)\n}\n\nclass NodeHttpAdapter implements NodeAdapter {\n readonly name = 'node' as const\n readonly #routes = new RouteTable<AdapterHandler>()\n readonly #options: NodeAdapterOptions\n #fallback: FallbackHandler | undefined\n #server: Server | undefined\n #tracker: ConnectionTracker | undefined\n #closing: Promise<void> | undefined\n\n constructor(options: NodeAdapterOptions) {\n this.#options = options\n this.#server = options.server\n }\n\n get server(): Server | undefined {\n return this.#server\n }\n\n registerRoute(route: AdapterRoute): void {\n if (this.#routes.add(route.method, route.path, route.handler) !== undefined) {\n throw new Error(`Duplicate route ${route.method} ${route.path.path}`)\n }\n }\n\n setFallbackHandler(handler: FallbackHandler): void {\n this.#fallback = handler\n }\n\n readonly handler = (request: IncomingMessage, response: ServerResponse): void => {\n const url = request.url ?? '/'\n const query = url.indexOf('?')\n const handler = this.#routes.find(request.method ?? 'GET', query === -1 ? url : url.slice(0, query))\n const adapterRequest = nodeAdapterRequest(request, response)\n let reply: ReturnType<AdapterHandler>\n if (handler !== undefined) reply = handler(adapterRequest)\n else if (this.#fallback === undefined) {\n writeNodeFailure(response)\n return\n } else reply = this.#fallback(adapterRequest)\n reply.then(\n result => writeNodeReply(response, result),\n () => writeNodeFailure(response),\n )\n }\n\n async listen(port: number, host: string): Promise<HttpServerAddress> {\n const server = this.#server ?? createServer(this.#options.serverOptions ?? {})\n this.#server = server\n this.#tracker ??= new ConnectionTracker(server)\n this.#closing = undefined\n if (!server.listeners('request').includes(this.handler)) server.on('request', this.handler)\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 server = this.#server\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 return injectNodeStream('node', this.handler as (req: never, res: never) => void, request)\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"node-Clm1Ia7T.mjs","names":[],"sources":["../src/router.ts","../src/adapters/node.ts"],"sourcesContent":["import type { RoutePath } from './path.ts'\n\ninterface RouteNode<T> {\n statics: Map<string, RouteNode<T>> | undefined\n param: RouteNode<T> | undefined\n /** Values of routes ending with a wildcard at this position, by method. */\n wildcard: Map<string, T> | undefined\n /** Values of routes ending at this node, by method. */\n values: Map<string, T> | undefined\n}\n\nfunction createNode<T>(): RouteNode<T> {\n return { statics: undefined, param: undefined, wildcard: undefined, values: undefined }\n}\n\n/**\n * A segment trie mapping `(method, route path)` to values. Lookups prefer static segments over\n * parameters over the wildcard, with backtracking (the same semantics as find-my-way), and are\n * case-sensitive on the raw request path. Used by the built-in Node adapter for routing and by the\n * plugin to tell `404 Not Found` from `405 Method Not Allowed`.\n */\nexport class RouteTable<T> {\n readonly #root: RouteNode<T> = createNode()\n #size = 0\n\n /** Number of `(method, route)` entries. */\n get size(): number {\n return this.#size\n }\n\n /**\n * Adds a value for `method` (upper case) and `route`.\n *\n * @returns the value already registered for the same method and shape, in which case nothing is\n * added.\n */\n add(method: string, route: RoutePath, value: T): T | undefined {\n let node = this.#root\n for (const segment of route.segments) {\n if (segment.kind === 'static') {\n node.statics ??= new Map()\n let child = node.statics.get(segment.value)\n if (child === undefined) {\n child = createNode()\n node.statics.set(segment.value, child)\n }\n node = child\n } else if (segment.kind === 'param') {\n node.param ??= createNode()\n node = node.param\n } else {\n node.wildcard ??= new Map()\n return this.#set(node.wildcard, method, value)\n }\n }\n node.values ??= new Map()\n return this.#set(node.values, method, value)\n }\n\n #set(map: Map<string, T>, method: string, value: T): T | undefined {\n const existing = map.get(method)\n if (existing !== undefined) return existing\n map.set(method, value)\n this.#size++\n return undefined\n }\n\n /** The value of the most specific route matching `method` and the raw `path` (no query string). */\n find(method: string, path: string): T | undefined {\n if (path.charCodeAt(0) !== 47 /* / */) return undefined\n if (path === '/') return this.#root.values?.get(method) ?? this.#root.wildcard?.get(method)\n return search(this.#root, path.split('/'), 1, method)\n }\n\n /** Methods of every route matching the raw `path`, sorted. */\n allowedMethods(path: string): string[] {\n const methods = new Set<string>()\n if (path.charCodeAt(0) === 47 /* / */) {\n if (path === '/') {\n addKeys(methods, this.#root.values)\n addKeys(methods, this.#root.wildcard)\n } else {\n collect(this.#root, path.split('/'), 1, methods)\n }\n }\n return [...methods].sort()\n }\n}\n\nfunction search<T>(node: RouteNode<T>, segments: readonly string[], index: number, method: string): T | undefined {\n if (index === segments.length) return node.values?.get(method)\n const segment = segments[index] as string\n const child = node.statics?.get(segment)\n if (child !== undefined) {\n const found = search(child, segments, index + 1, method)\n if (found !== undefined) return found\n }\n if (segment !== '' && node.param !== undefined) {\n const found = search(node.param, segments, index + 1, method)\n if (found !== undefined) return found\n }\n return node.wildcard?.get(method)\n}\n\nfunction collect<T>(node: RouteNode<T>, segments: readonly string[], index: number, methods: Set<string>): void {\n if (index === segments.length) {\n addKeys(methods, node.values)\n return\n }\n const segment = segments[index] as string\n const child = node.statics?.get(segment)\n if (child !== undefined) collect(child, segments, index + 1, methods)\n if (segment !== '' && node.param !== undefined) collect(node.param, segments, index + 1, methods)\n addKeys(methods, node.wildcard)\n}\n\nfunction addKeys(target: Set<string>, source: Map<string, unknown> | undefined): void {\n if (source !== undefined) for (const key of source.keys()) target.add(key)\n}\n","/**\n * `@znight/http/node`: the built-in, zero-dependency adapter on `node:http` (default adapter of\n * `HttpPlugin`). Works on Node.js and Bun. `inject()` uses the optional peer `light-my-request`.\n */\nimport type {\n AdapterCloseOptions,\n AdapterHandler,\n AdapterRoute,\n FallbackHandler,\n HttpAdapter,\n HttpServerAddress,\n InjectRequest,\n InjectResponse,\n InjectStreamRequest,\n InjectStreamResponse,\n} from '../adapter.ts'\n\nimport { createServer, type IncomingMessage, type Server, type ServerOptions, type ServerResponse } from 'node:http'\n\nimport { RouteTable } from '../router.ts'\nimport { ConnectionTracker, closeServer, nodeAdapterRequest, writeNodeFailure, writeNodeReply } from './node-io.ts'\nimport { bufferInjectStream, injectNodeStream, serverAddress } from './shared.ts'\n\n/** Options of {@link nodeAdapter}. */\nexport interface NodeAdapterOptions {\n /** Options of `http.createServer()` (timeouts, `maxHeaderSize`...). Ignored with {@link server}. */\n readonly serverOptions?: ServerOptions\n /**\n * An existing server (e.g. `https.createServer({ key, cert })`). The adapter adds its `request`\n * listener on `listen()` and closes the server on shutdown.\n */\n readonly server?: Server\n}\n\n/** The built-in Node.js adapter. */\nexport interface NodeAdapter extends HttpAdapter {\n readonly name: 'node'\n /** Request listener serving the routes, to mount the application in a server you manage. */\n readonly handler: (request: IncomingMessage, response: ServerResponse) => void\n /** The server, once listening (or the one given in the options). */\n readonly server: Server | undefined\n}\n\n/**\n * Creates the built-in `node:http` adapter: a segment-trie router (static > `:param` > `*`, with\n * backtracking), zero dependencies.\n *\n * ```ts\n * HttpPlugin.configure({ adapter: nodeAdapter({ serverOptions: { requestTimeout: 30_000 } }) })\n * ```\n */\nexport function nodeAdapter(options: NodeAdapterOptions = {}): NodeAdapter {\n return new NodeHttpAdapter(options)\n}\n\nclass NodeHttpAdapter implements NodeAdapter {\n readonly name = 'node' as const\n readonly #routes = new RouteTable<AdapterHandler>()\n readonly #options: NodeAdapterOptions\n #fallback: FallbackHandler | undefined\n #server: Server | undefined\n #tracker: ConnectionTracker | undefined\n #closing: Promise<void> | undefined\n\n constructor(options: NodeAdapterOptions) {\n this.#options = options\n this.#server = options.server\n }\n\n get server(): Server | undefined {\n return this.#server\n }\n\n registerRoute(route: AdapterRoute): void {\n if (this.#routes.add(route.method, route.path, route.handler) !== undefined) {\n throw new Error(`Duplicate route ${route.method} ${route.path.path}`)\n }\n }\n\n setFallbackHandler(handler: FallbackHandler): void {\n this.#fallback = handler\n }\n\n readonly handler = (request: IncomingMessage, response: ServerResponse): void => {\n const url = request.url ?? '/'\n const query = url.indexOf('?')\n const handler = this.#routes.find(request.method ?? 'GET', query === -1 ? url : url.slice(0, query))\n const adapterRequest = nodeAdapterRequest(request, response)\n let reply: ReturnType<AdapterHandler>\n if (handler !== undefined) reply = handler(adapterRequest)\n else if (this.#fallback === undefined) {\n writeNodeFailure(response)\n return\n } else reply = this.#fallback(adapterRequest)\n reply.then(\n result => writeNodeReply(response, result),\n () => writeNodeFailure(response),\n )\n }\n\n async listen(port: number, host: string): Promise<HttpServerAddress> {\n const server = this.#server ?? createServer(this.#options.serverOptions ?? {})\n this.#server = server\n this.#tracker ??= new ConnectionTracker(server)\n this.#closing = undefined\n if (!server.listeners('request').includes(this.handler)) server.on('request', this.handler)\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 server = this.#server\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 return injectNodeStream('node', this.handler as (req: never, res: never) => void, request)\n }\n}\n"],"mappings":";;;AAWA,SAAS,aAA8B;CACrC,OAAO;EAAE,SAAS,KAAA;EAAW,OAAO,KAAA;EAAW,UAAU,KAAA;EAAW,QAAQ,KAAA;CAAU;AACxF;;;;;;;AAQA,IAAa,aAAb,MAA2B;CACzB,QAA+B,WAAW;CAC1C,QAAQ;;CAGR,IAAI,OAAe;EACjB,OAAO,KAAK;CACd;;;;;;;CAQA,IAAI,QAAgB,OAAkB,OAAyB;EAC7D,IAAI,OAAO,KAAK;EAChB,KAAK,MAAM,WAAW,MAAM,UAC1B,IAAI,QAAQ,SAAS,UAAU;GAC7B,KAAK,4BAAY,IAAI,IAAI;GACzB,IAAI,QAAQ,KAAK,QAAQ,IAAI,QAAQ,KAAK;GAC1C,IAAI,UAAU,KAAA,GAAW;IACvB,QAAQ,WAAW;IACnB,KAAK,QAAQ,IAAI,QAAQ,OAAO,KAAK;GACvC;GACA,OAAO;EACT,OAAO,IAAI,QAAQ,SAAS,SAAS;GACnC,KAAK,UAAU,WAAW;GAC1B,OAAO,KAAK;EACd,OAAO;GACL,KAAK,6BAAa,IAAI,IAAI;GAC1B,OAAO,KAAK,KAAK,KAAK,UAAU,QAAQ,KAAK;EAC/C;EAEF,KAAK,2BAAW,IAAI,IAAI;EACxB,OAAO,KAAK,KAAK,KAAK,QAAQ,QAAQ,KAAK;CAC7C;CAEA,KAAK,KAAqB,QAAgB,OAAyB;EACjE,MAAM,WAAW,IAAI,IAAI,MAAM;EAC/B,IAAI,aAAa,KAAA,GAAW,OAAO;EACnC,IAAI,IAAI,QAAQ,KAAK;EACrB,KAAK;CAEP;;CAGA,KAAK,QAAgB,MAA6B;EAChD,IAAI,KAAK,WAAW,CAAC,MAAM,IAAY,OAAO,KAAA;EAC9C,IAAI,SAAS,KAAK,OAAO,KAAK,MAAM,QAAQ,IAAI,MAAM,KAAK,KAAK,MAAM,UAAU,IAAI,MAAM;EAC1F,OAAO,OAAO,KAAK,OAAO,KAAK,MAAM,GAAG,GAAG,GAAG,MAAM;CACtD;;CAGA,eAAe,MAAwB;EACrC,MAAM,0BAAU,IAAI,IAAY;EAChC,IAAI,KAAK,WAAW,CAAC,MAAM,IAAY;GACrC,IAAI,SAAS,KAAK;IAChB,QAAQ,SAAS,KAAK,MAAM,MAAM;IAClC,QAAQ,SAAS,KAAK,MAAM,QAAQ;GACtC,OACE,QAAQ,KAAK,OAAO,KAAK,MAAM,GAAG,GAAG,GAAG,OAAO;EAEnD;EACA,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,KAAK;CAC3B;AACF;AAEA,SAAS,OAAU,MAAoB,UAA6B,OAAe,QAA+B;CAChH,IAAI,UAAU,SAAS,QAAQ,OAAO,KAAK,QAAQ,IAAI,MAAM;CAC7D,MAAM,UAAU,SAAS;CACzB,MAAM,QAAQ,KAAK,SAAS,IAAI,OAAO;CACvC,IAAI,UAAU,KAAA,GAAW;EACvB,MAAM,QAAQ,OAAO,OAAO,UAAU,QAAQ,GAAG,MAAM;EACvD,IAAI,UAAU,KAAA,GAAW,OAAO;CAClC;CACA,IAAI,YAAY,MAAM,KAAK,UAAU,KAAA,GAAW;EAC9C,MAAM,QAAQ,OAAO,KAAK,OAAO,UAAU,QAAQ,GAAG,MAAM;EAC5D,IAAI,UAAU,KAAA,GAAW,OAAO;CAClC;CACA,OAAO,KAAK,UAAU,IAAI,MAAM;AAClC;AAEA,SAAS,QAAW,MAAoB,UAA6B,OAAe,SAA4B;CAC9G,IAAI,UAAU,SAAS,QAAQ;EAC7B,QAAQ,SAAS,KAAK,MAAM;EAC5B;CACF;CACA,MAAM,UAAU,SAAS;CACzB,MAAM,QAAQ,KAAK,SAAS,IAAI,OAAO;CACvC,IAAI,UAAU,KAAA,GAAW,QAAQ,OAAO,UAAU,QAAQ,GAAG,OAAO;CACpE,IAAI,YAAY,MAAM,KAAK,UAAU,KAAA,GAAW,QAAQ,KAAK,OAAO,UAAU,QAAQ,GAAG,OAAO;CAChG,QAAQ,SAAS,KAAK,QAAQ;AAChC;AAEA,SAAS,QAAQ,QAAqB,QAAgD;CACpF,IAAI,WAAW,KAAA,GAAW,KAAK,MAAM,OAAO,OAAO,KAAK,GAAG,OAAO,IAAI,GAAG;AAC3E;;;;;;;;;;ACnEA,SAAgB,YAAY,UAA8B,CAAC,GAAgB;CACzE,OAAO,IAAI,gBAAgB,OAAO;AACpC;AAEA,IAAM,kBAAN,MAA6C;CAC3C,OAAgB;CAChB,UAAmB,IAAI,WAA2B;CAClD;CACA;CACA;CACA;CACA;CAEA,YAAY,SAA6B;EACvC,KAAK,WAAW;EAChB,KAAK,UAAU,QAAQ;CACzB;CAEA,IAAI,SAA6B;EAC/B,OAAO,KAAK;CACd;CAEA,cAAc,OAA2B;EACvC,IAAI,KAAK,QAAQ,IAAI,MAAM,QAAQ,MAAM,MAAM,MAAM,OAAO,MAAM,KAAA,GAChE,MAAM,IAAI,MAAM,mBAAmB,MAAM,OAAO,GAAG,MAAM,KAAK,MAAM;CAExE;CAEA,mBAAmB,SAAgC;EACjD,KAAK,YAAY;CACnB;CAEA,WAAoB,SAA0B,aAAmC;EAC/E,MAAM,MAAM,QAAQ,OAAO;EAC3B,MAAM,QAAQ,IAAI,QAAQ,GAAG;EAC7B,MAAM,UAAU,KAAK,QAAQ,KAAK,QAAQ,UAAU,OAAO,UAAU,KAAK,MAAM,IAAI,MAAM,GAAG,KAAK,CAAC;EACnG,MAAM,iBAAiB,mBAAmB,SAAS,QAAQ;EAC3D,IAAI;EACJ,IAAI,YAAY,KAAA,GAAW,QAAQ,QAAQ,cAAc;OACpD,IAAI,KAAK,cAAc,KAAA,GAAW;GACrC,iBAAiB,QAAQ;GACzB;EACF,OAAO,QAAQ,KAAK,UAAU,cAAc;EAC5C,MAAM,MACJ,WAAU,eAAe,UAAU,MAAM,SACnC,iBAAiB,QAAQ,CACjC;CACF;CAEA,MAAM,OAAO,MAAc,MAA0C;EACnE,MAAM,SAAS,KAAK,WAAW,aAAa,KAAK,SAAS,iBAAiB,CAAC,CAAC;EAC7E,KAAK,UAAU;EACf,KAAK,aAAa,IAAI,kBAAkB,MAAM;EAC9C,KAAK,WAAW,KAAA;EAChB,IAAI,CAAC,OAAO,UAAU,SAAS,CAAC,CAAC,SAAS,KAAK,OAAO,GAAG,OAAO,GAAG,WAAW,KAAK,OAAO;EAC1F,MAAM,IAAI,SAAe,SAAS,WAAW;GAC3C,MAAM,WAAW,UAAuB,OAAO,KAAK;GACpD,OAAO,KAAK,SAAS,OAAO;GAC5B,OAAO,OAAO,MAAM,YAAY;IAC9B,OAAO,IAAI,SAAS,OAAO;IAC3B,QAAQ;GACV,CAAC;EACH,CAAC;EACD,MAAM,UAAU,OAAO,QAAQ;EAC/B,IAAI,YAAY,QAAQ,OAAO,YAAY,UAAU,OAAO,cAAc,MAAM,IAAI;EACpF,OAAO,cAAc,QAAQ,SAAS,QAAQ,MAAM,QAAQ,MAAM;CACpE;CAEA,MAAM,UAA+B,CAAC,GAAkB;EACtD,MAAM,SAAS,KAAK;EACpB,MAAM,UAAU,KAAK;EACrB,IAAI,WAAW,KAAA,KAAa,YAAY,KAAA,GAAW,OAAO,QAAQ,QAAQ;EAC1E,KAAK,WAAW,YAAY,QAAQ,SAAS,KAAK,UAAU,QAAQ,UAAU,IAAI;EAClF,OAAO,KAAK;CACd;CAEA,OAAO,SAAiD;EACtD,OAAO,oBAAmB,WAAU,KAAK,aAAa,MAAM,GAAG,OAAO;CACxE;CAEA,aAAa,SAA6D;EACxE,OAAO,iBAAiB,QAAQ,KAAK,SAA6C,OAAO;CAC3F;AACF"}
@@ -1401,25 +1401,48 @@ function nodeBodyStream(request, limit) {
1401
1401
  }
1402
1402
  /** Wraps a Node.js request/response pair as an {@link AdapterRequest}. */
1403
1403
  function nodeAdapterRequest(request, response, options = {}) {
1404
- let body;
1405
- return {
1406
- method: request.method ?? "GET",
1407
- url: options.url ?? request.url ?? "/",
1408
- headers: normalizeNodeHeaders(request.headers),
1409
- ip: options.ip ?? request.socket?.remoteAddress,
1410
- remoteAddress: request.socket?.remoteAddress,
1411
- encrypted: isEncrypted(request),
1412
- raw: options.raw ?? request,
1413
- rawResponse: options.rawResponse ?? response,
1414
- readBody: (limit) => {
1415
- body ??= readNodeBody(request, limit);
1416
- return body;
1417
- },
1418
- streamBody: (limit) => nodeBodyStream(request, limit),
1419
- rawStatus: () => response.statusCode,
1420
- onAbort: (listener) => onResponseAbort(response, listener)
1421
- };
1404
+ return new NodeAdapterRequest(request, response, options);
1422
1405
  }
1406
+ /** A Node.js request/response pair as an {@link AdapterRequest} (a class: no closure per request). */
1407
+ var NodeAdapterRequest = class {
1408
+ method;
1409
+ url;
1410
+ headers;
1411
+ ip;
1412
+ remoteAddress;
1413
+ encrypted;
1414
+ raw;
1415
+ rawResponse;
1416
+ #request;
1417
+ #response;
1418
+ #body;
1419
+ constructor(request, response, options) {
1420
+ const remoteAddress = request.socket?.remoteAddress;
1421
+ this.method = request.method ?? "GET";
1422
+ this.url = options.url ?? request.url ?? "/";
1423
+ this.headers = normalizeNodeHeaders(request.headers);
1424
+ this.ip = options.ip ?? remoteAddress;
1425
+ this.remoteAddress = remoteAddress;
1426
+ this.encrypted = isEncrypted(request);
1427
+ this.raw = options.raw ?? request;
1428
+ this.rawResponse = options.rawResponse ?? response;
1429
+ this.#request = request;
1430
+ this.#response = response;
1431
+ }
1432
+ readBody(limit) {
1433
+ this.#body ??= readNodeBody(this.#request, limit);
1434
+ return this.#body;
1435
+ }
1436
+ streamBody(limit) {
1437
+ return nodeBodyStream(this.#request, limit);
1438
+ }
1439
+ rawStatus() {
1440
+ return this.#response.statusCode;
1441
+ }
1442
+ onAbort(listener) {
1443
+ onResponseAbort(this.#response, listener);
1444
+ }
1445
+ };
1423
1446
  /**
1424
1447
  * Calls `listener` once when `response` closes before it was fully written (the client left): on
1425
1448
  * the next microtask when it is already destroyed, never after a complete response.
@@ -1439,10 +1462,10 @@ function isEncrypted(request) {
1439
1462
  return request.socket?.encrypted === true;
1440
1463
  }
1441
1464
  /**
1442
- * Writes a reply to a `ServerResponse`. Resolves when the response is complete (streams included);
1443
- * never rejects (a failing stream destroys the response).
1465
+ * Writes a reply to a `ServerResponse`, piping a stream body (a failing stream destroys the
1466
+ * response). Never throws.
1444
1467
  */
1445
- async function writeNodeReply(response, reply) {
1468
+ function writeNodeReply(response, reply) {
1446
1469
  if (reply.raw === true) {
1447
1470
  discardStream(reply.body instanceof ReadableStream ? reply.body : null);
1448
1471
  return;
@@ -1472,14 +1495,17 @@ async function writeNodeReply(response, reply) {
1472
1495
  }
1473
1496
  if (reply.eventStream === true && response.isLightMyRequest !== true) response.setTimeout(0);
1474
1497
  if (body instanceof Uint8Array) {
1475
- response.end(body);
1498
+ response.end(reply.text ?? body);
1476
1499
  return;
1477
1500
  }
1478
- const stream = body instanceof Blob ? body.stream() : body;
1501
+ const fail = () => {
1502
+ if (!response.destroyed) response.destroy();
1503
+ };
1479
1504
  try {
1480
- await pipeline(Readable.fromWeb(stream), response);
1505
+ const stream = body instanceof Blob ? body.stream() : body;
1506
+ pipeline(Readable.fromWeb(stream), response).catch(fail);
1481
1507
  } catch {
1482
- if (!response.destroyed) response.destroy();
1508
+ fail();
1483
1509
  }
1484
1510
  }
1485
1511
  /**
@@ -1563,4 +1589,4 @@ function closeServer(server, tracker, closing, force) {
1563
1589
  }
1564
1590
  export { UnprocessableEntityException as $, serializeCookie as A, ConflictException as B, decodeCookieValue as C, parseCookieHeader as D, isCookieName as E, isStreamSource as F, InternalServerErrorException as G, GatewayTimeoutException as H, toByteStream as I, NotImplementedException as J, MethodNotAllowedException as K, trackStream as L, captureContext as M, discardStream as N, parseSetCookie as O, isByteStream as P, UnauthorizedException as Q, BadGatewayException as R, checkCookie as S, givenCookieKey as T, GoneException as U, ForbiddenException as V, HttpException as W, ServiceUnavailableException as X, PayloadTooLargeException as Y, TooManyRequestsException as Z, lightStreamResponse as _, nodeBodyStream as a, isBodylessStatus as at, webBodyStream as b, readNodeBody as c, writeNodeReply as d, UnsupportedMediaTypeException as et, bufferInjectStream as f, lightInjectOptions as g, isEventStreamType as h, nodeAdapterRequest as i, toIssueDetail as it, serializeResponseCookie as j, serializeClearCookie as k, withNativeCookies as l, injectNodeStream as m, closeServer as n, isHttpException as nt, normalizeNodeHeaders as o, isValidStatus as ot, injectFetchStream as p, NotFoundException as q, isEncrypted as r, issue as rt, onResponseAbort as s, statusText as st, ConnectionTracker as t, ValidationException as tt, writeNodeFailure as u, readWebStream as v, encodeCookieValue as w, DEFAULT_COOKIE_OPTIONS as x, serverAddress as y, BadRequestException as z };
1565
1591
 
1566
- //# sourceMappingURL=node-io-BY1ZDLRQ.mjs.map
1592
+ //# sourceMappingURL=node-io-CVnqpVNS.mjs.map