posipaki 0.48.0 → 0.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{actor-C8DzeQeT.js → actor-Df5v2zuv.js} +38 -34
- package/dist/actor-Df5v2zuv.js.map +1 -0
- package/dist/hooks-DO-efRM_.d.ts.map +1 -1
- package/dist/{index-B3FcBNz7.d.ts → index-DFqA3rI6.d.ts} +2 -2
- package/dist/{index-B3FcBNz7.d.ts.map → index-DFqA3rI6.d.ts.map} +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/port/index.d.ts +2 -2
- package/dist/port/index.js +2 -2
- package/dist/port/node.d.ts +23 -23
- package/dist/port/node.d.ts.map +1 -1
- package/dist/port/node.js +94 -61
- package/dist/port/node.js.map +1 -1
- package/dist/{port-DowG9tgl.js → port-BR7f7BDY.js} +22 -7
- package/dist/port-BR7f7BDY.js.map +1 -0
- package/dist/{reactor-CWPBP7qN.d.ts → reactor-4MaIKVnw.d.ts} +21 -10
- package/dist/reactor-4MaIKVnw.d.ts.map +1 -0
- package/dist/remote/gateway-cli.js +20 -1
- package/dist/remote/gateway-cli.js.map +1 -1
- package/dist/remote/index.d.ts +2 -2
- package/dist/remote/index.js +1 -1
- package/dist/remote/node.d.ts +7 -4
- package/dist/remote/node.d.ts.map +1 -1
- package/dist/remote/node.js +32 -14
- package/dist/remote/node.js.map +1 -1
- package/dist/{version-BsegZXH2.js → version-DyYl4K-F.js} +2 -2
- package/dist/{version-BsegZXH2.js.map → version-DyYl4K-F.js.map} +1 -1
- package/dist/{websocket-DgE0_5HU.js → websocket-Ce4oq-7O.js} +30 -7
- package/dist/websocket-Ce4oq-7O.js.map +1 -0
- package/dist/{websocket-AU2aygxx.d.ts → websocket-DNSNv77Y.d.ts} +2 -2
- package/dist/{websocket-AU2aygxx.d.ts.map → websocket-DNSNv77Y.d.ts.map} +1 -1
- package/package.json +1 -1
- package/dist/actor-C8DzeQeT.js.map +0 -1
- package/dist/port-DowG9tgl.js.map +0 -1
- package/dist/reactor-CWPBP7qN.d.ts.map +0 -1
- package/dist/websocket-DgE0_5HU.js.map +0 -1
package/dist/port/node.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node.js","names":[],"sources":["../../src/port/node/spawn.ts","../../src/port/node/process.ts","../../src/port/node/listener.ts"],"sourcesContent":["// ── a spawned command as a port ─────────────────────────────────────────────\n//\n// The child's stdout is the channel's frames, its stdin is what `send` writes, and the way it ended is the\n// channel's close reason. Nothing else crosses: a child's stderr is discarded, because stderr is a stream the\n// child writes while it runs and not a fact about how it ended. `spawnPorts` is the same process with both of\n// its output streams.\n//\n// Closing is stdin's end of stream first. A program that reads its input exits on EOF, which is the polite\n// half; the process is killed `killAfterMs` later if it is still there, which is the other half.\nimport { spawn, type ChildProcess } from \"node:child_process\";\nimport type { Readable } from \"node:stream\";\nimport { held } from \"../held.js\";\nimport type { Channel } from \"../channel.js\";\n/** How a process ended: it exited with a code, or a signal terminated it. */\nexport type ProcessEnd =\n | { readonly kind: \"exit\"; readonly code: number }\n | { readonly kind: \"signal\"; readonly signal: string };\nexport interface SpawnPortOptions {\n cwd?: string;\n env?: NodeJS.ProcessEnv;\n /** How long the process may live after its stdin is closed. Default 5000 ms, then SIGKILL. */\n killAfterMs?: number;\n}\nexport interface ProcessPorts {\n readonly stdout: Channel<Uint8Array, ProcessEnd>;\n readonly stderr: Channel<Uint8Array, ProcessEnd>;\n}\nconst DEFAULT_KILL_AFTER_MS = 5_000;\n/** A command as a channel of bytes: stdout in, stdin out, and its exit when it closes. */\nexport function spawnPort(\n command: string[],\n options: SpawnPortOptions = {},\n): Promise<Channel<Uint8Array, ProcessEnd>> {\n return spawnPorts(command, options).then((ports) => ports.stdout);\n}\n/** One process, its stdout and its stderr, each a channel that ends with the same exit. */\nexport async function spawnPorts(\n command: string[],\n options: SpawnPortOptions = {},\n): Promise<ProcessPorts> {\n const [file, ...args] = command;\n if (file === undefined) throw new Error(\"spawnPort: there is no command to run\");\n const child = spawn(file, args, { cwd: options.cwd, env: options.env });\n await started(child, file);\n const out = bytesOf(child.stdout, \"stdout\");\n const err = bytesOf(child.stderr, \"stderr\");\n let exit: ProcessEnd | null = null;\n let ended = false;\n const closers = new Set<(reason: ProcessEnd) => void>();\n const failers = new Set<(error: unknown) => void>();\n const end = (why: ProcessEnd): void => {\n if (ended) return;\n ended = true;\n exit = why;\n for (const closer of [...closers]) closer(why);\n };\n child.on(\"error\", (error) => {\n for (const failer of [...failers]) failer(error);\n });\n // `close` rather than `exit`: it follows the end of both streams, so no frame is lost behind the ending.\n // Node gives a code or a signal and never both; that is the OS's one status word, split in two.\n child.on(\"close\", (code, signal) =>\n end(code !== null ? { kind: \"exit\", code } : { kind: \"signal\", signal: signal ?? \"unknown\" }),\n );\n const close = (reason?: string): Promise<void> => {\n return shutdown(child, options.killAfterMs ?? DEFAULT_KILL_AFTER_MS).catch(() => undefined);\n };\n const onClose = (handler: (reason: ProcessEnd) => void): (() => void) => {\n if (exit !== null) {\n handler(exit);\n return () => {};\n }\n closers.add(handler);\n return () => {\n closers.delete(handler);\n };\n };\n const onError = (handler: (error: unknown) => void): (() => void) => {\n failers.add(handler);\n return () => {\n failers.delete(handler);\n };\n };\n const stderr: Channel<Uint8Array, ProcessEnd> = {\n send: () => Promise.reject(new Error(\"stderr carries the process's output; it has no input\")),\n onFrame: err.onFrame,\n onClose,\n onError,\n close,\n };\n return {\n stdout: { send: (frame) => write(child, frame), onFrame: out.onFrame, onClose, onError, close },\n stderr,\n };\n}\n/** Resolves once the child exists, rejects when the command could not be started at all. */\nfunction started(child: ChildProcess, file: string): Promise<void> {\n return new Promise((resolve, reject) => {\n child.once(\"spawn\", () => resolve());\n child.once(\"error\", (error: Error) =>\n reject(new Error(`spawnPort: could not start ${file}: ${error.message}`)),\n );\n });\n}\n/** One output stream as the frames of a channel, kept until somebody reads them. */\nfunction bytesOf(stream: Readable | null, label: string): Channel<Uint8Array, ProcessEnd> {\n const readers = new Set<(frame: Uint8Array) => void>();\n const frames: Channel<Uint8Array, ProcessEnd> = {\n send: () => Promise.reject(new Error(`${label} carries the process's output; it has no input`)),\n onFrame(handler) {\n readers.add(handler);\n return () => {\n readers.delete(handler);\n };\n },\n // The stream ending is not the process ending: the port reports that, from the process's own exit.\n onClose: () => () => {},\n onError: () => () => {},\n close: () => Promise.resolve(),\n };\n stream?.on(\"data\", (chunk: Buffer) => {\n const bytes = new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);\n for (const reader of [...readers]) reader(bytes);\n });\n return held(frames);\n}\n/** Writes to the child's stdin, and settles when the write is out. */\nfunction write(child: ChildProcess, frame: Uint8Array): Promise<void> {\n const stdin = child.stdin;\n if (stdin === null || stdin.destroyed) return Promise.reject(new Error(\"the process's stdin is closed\"));\n return new Promise((resolve, reject) => {\n stdin.write(frame, (error) => (error === null || error === undefined ? resolve() : reject(error)));\n });\n}\n/** Ends stdin, and kills what is still there after the grace. */\nasync function shutdown(child: ChildProcess, killAfterMs: number): Promise<void> {\n if (child.exitCode !== null || child.signalCode !== null) return;\n const closed = new Promise<void>((resolve) => {\n child.once(\"close\", () => resolve());\n });\n child.stdin?.end();\n const timer = setTimeout(() => {\n if (child.exitCode === null && child.signalCode === null) child.kill(\"SIGKILL\");\n }, killAfterMs);\n timer.unref?.();\n await closed;\n clearTimeout(timer);\n}\n","// ── the two ports that wrap a process ───────────────────────────────────────\n//\n// Both are `portActor` over `spawnPort`: one carries the child's octets as they are, the other puts the splitter\n// underneath and carries lines. The names are the contract — an actor definition is spawned by name.\n\nimport type { Message } from \"../../types.js\";\nimport { portActor } from \"../actor.js\";\nimport type { Channel, Part } from \"../channel.js\";\nimport { splitChannel } from \"../split.js\";\nimport { spawnPort, type ProcessEnd } from \"./spawn.js\";\n\n/** How a process port is started. */\nexport interface ProcessPortArgs {\n command: string[];\n cwd?: string;\n env?: NodeJS.ProcessEnv;\n /** How long the process may live after its stdin is closed. Default 5000 ms, then SIGKILL. */\n killAfterMs?: number;\n}\n\n\n\n/** Parts as their text: what a line port carries. Octets that came with a line are not text and are dropped. */\nfunction lines(parts: Channel<Part, ProcessEnd>): Channel<string, ProcessEnd> {\n const readers = new Set<(line: string) => void>();\n const closers = new Set<(reason: ProcessEnd) => void>();\n const failers = new Set<(error: unknown) => void>();\n\n parts.onFrame(({ line }) => {\n for (const reader of [...readers]) reader(line);\n });\n parts.onClose((reason) => {\n for (const closer of [...closers]) closer(reason);\n });\n parts.onError((error) => {\n for (const failer of [...failers]) failer(error);\n });\n\n return {\n send: (line) => parts.send({ line, bytes: null }),\n onFrame(handler) {\n readers.add(handler);\n return () => {\n readers.delete(handler);\n };\n },\n onClose(handler) {\n closers.add(handler);\n return () => {\n closers.delete(handler);\n };\n },\n onError(handler) {\n failers.add(handler);\n return () => {\n failers.delete(handler);\n };\n },\n close: (reason) => parts.close(reason),\n };\n}\n\n/** The child's octets, frame for frame. */\nexport const BinaryPort = portActor<ProcessPortArgs, Uint8Array, ProcessEnd>({\n name: \"binary-port\",\n open: (args) => spawnPort(args.command, args),\n});\n\n/** The same child, cut into lines. */\nexport const Utf8Port = portActor<ProcessPortArgs, string, ProcessEnd>({\n name: \"utf8-port\",\n open: async (args) => lines(splitChannel(await spawnPort(args.command, args))),\n});\n","// ── an HTTP server as a port listener ───────────────────────────────────────\n//\n// One request is one pair of ports: the request's body is what the far side sends, the response's body is what we\n// send back, and the `Response` is built here because this is the only place that knows HTTP. A handler above\n// sees a pair and nothing about the protocol.\n//\n// The body is built before the handler runs, because a server must answer with a `Response` in the same turn it is\n// asked: writes go into the response's stream, and closing the outbound port ends it.\n\nimport type { Channel } from \"../channel.js\";\nimport { held } from \"../held.js\";\nimport type { PortListener, PortPair } from \"../reactor.js\";\n\nexport interface HttpListenerOptions<Meta> {\n port?: number;\n hostname?: string;\n /** Admission before a body is made: what it returns is the pair's meta; throwing refuses the request. */\n identify?(request: Request): Meta | Promise<Meta>;\n}\n\n/** A port listener that is also an HTTP server: the port it bound, and the pairs it hands out. */\nexport interface HttpListener<Meta> extends PortListener<Uint8Array, Uint8Array, Meta> {\n readonly port: number;\n}\n\n/** The Bun server this wraps, as little of it as is used here. */\ninterface BunServer {\n readonly port: number;\n stop(force?: boolean): void | Promise<void>;\n}\n\ninterface BunRuntime {\n serve(options: {\n port: number;\n hostname?: string;\n fetch(request: Request): Response | Promise<Response>;\n }): BunServer;\n}\n\n/**\n * A server whose requests arrive as pairs of ports.\n *\n * `identify` runs before a pair exists, so a refused request costs no stream and the caller reads a status with\n * the reason in it. The request body is the inbound port; the response body is the outbound one.\n */\nexport function httpListener<Meta = unknown>(options: HttpListenerOptions<Meta> = {}): HttpListener<Meta> {\n const runtime = (globalThis as { Bun?: BunRuntime }).Bun;\n if (runtime === undefined) throw new Error(\"httpListener: this needs Bun's server\");\n\n const waiting: Array<{ accept: (pair: PortPair<Uint8Array, Uint8Array, Meta, void>) => void; refuse: (error: Error) => void }> = [];\n const buffered: Array<PortPair<Uint8Array, Uint8Array, Meta, void>> = [];\n let closed = false;\n let server: BunServer | null = null;\n\n const take = (pair: PortPair<Uint8Array, Uint8Array, Meta, void>): void => {\n const next = waiting.shift();\n if (next === undefined) {\n buffered.push(pair);\n return;\n }\n next.accept(pair);\n };\n\n const bound = runtime.serve({\n port: options.port ?? 0,\n hostname: options.hostname,\n async fetch(request: Request): Promise<Response> {\n if (closed) return new Response(\"listener is closed\", { status: 503 });\n let meta: Meta;\n try {\n meta = options.identify === undefined ? (undefined as Meta) : await options.identify(request);\n } catch (error) {\n return new Response(error instanceof Error ? error.message : String(error), { status: 401 });\n }\n const outbound = responsePort();\n take({ inbound: requestPort(request), outbound: outbound.channel, meta });\n return new Response(outbound.body);\n },\n });\n server = bound;\n\n return {\n get port() {\n return server === null ? 0 : server.port;\n },\n accept(): Promise<PortPair<Uint8Array, Uint8Array, Meta, void>> {\n const next = buffered.shift();\n if (next !== undefined) return Promise.resolve(next);\n if (closed) return Promise.reject(new Error(\"httpListener: closed\"));\n return new Promise((accept, refuse) => {\n waiting.push({ accept, refuse });\n });\n },\n async close(): Promise<void> {\n closed = true;\n for (const next of waiting.splice(0)) next.refuse(new Error(\"httpListener: closed\"));\n await server?.stop(true);\n server = null;\n },\n };\n}\n\n/** The request's body as the inbound port: frames as they arrive, ended when the body is. */\nfunction requestPort(request: Request): Channel<Uint8Array, void> {\n const readers = new Set<(frame: Uint8Array) => void>();\n const closers = new Set<(reason: void) => void>();\n let ended = false;\n\n const end = (): void => {\n if (ended) return;\n ended = true;\n for (const closer of [...closers]) closer(undefined);\n };\n\n const inbound: Channel<Uint8Array, void> = {\n send: () => Promise.reject(new Error(\"a request body has no input; that side is the caller's\")),\n onFrame(handler) {\n readers.add(handler);\n return () => {\n readers.delete(handler);\n };\n },\n onClose(handler) {\n if (ended) {\n handler(undefined);\n return () => {};\n }\n closers.add(handler);\n return () => {\n closers.delete(handler);\n };\n },\n onError() {\n return () => {};\n },\n close: async () => {\n await request.body?.cancel().catch(() => undefined);\n end();\n },\n };\n\n const reader = request.body?.getReader();\n // Held before the first read: a body can arrive in the same turn the pair is handed out.\n const port = held(inbound);\n void (async () => {\n if (reader === undefined) {\n end();\n return;\n }\n for (;;) {\n const chunk = await reader.read();\n if (chunk.done) {\n end();\n return;\n }\n for (const target of [...readers]) target(chunk.value);\n }\n })();\n\n return port;\n}\n\n/** The response's channel, and the body a `Response` is built from. One stream, written by the channel. */\nfunction responsePort(): { channel: Channel<Uint8Array, void>; body: ReadableStream<Uint8Array> } {\n const readers = new Set<(frame: Uint8Array) => void>();\n const closers = new Set<(reason: void) => void>();\n let ended = false;\n\n const end = (): void => {\n if (ended) return;\n ended = true;\n for (const closer of [...closers]) closer(undefined);\n };\n\n const body = new ReadableStream<Uint8Array>({\n start(controller) {\n readers.add((frame) => controller.enqueue(frame));\n closers.add(() => {\n try {\n controller.close();\n } catch {\n // Already closed by the platform: nothing here has an opinion about it.\n }\n });\n },\n cancel() {\n // The caller stopped reading: the channel is over from this side too.\n end();\n },\n });\n\n const channel: Channel<Uint8Array, void> = {\n async send(frame) {\n if (ended) throw new Error(\"this response is over\");\n for (const reader of [...readers]) reader(frame);\n },\n onFrame() {\n return () => {};\n },\n onClose(handler) {\n if (ended) {\n handler(undefined);\n return () => {};\n }\n closers.add(handler);\n return () => {\n closers.delete(handler);\n };\n },\n onError() {\n return () => {};\n },\n async close() {\n end();\n },\n };\n\n return { channel, body };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"node.js","names":[],"sources":["../../src/port/node/spawn.ts","../../src/port/node/listener.ts","../../src/port/node/process.ts"],"sourcesContent":["// ── a spawned command as a port ─────────────────────────────────────────────\n//\n// The child's stdout is the channel's frames, its stdin is what `send` writes, and the way it ended is the\n// channel's close reason. Nothing else crosses: a child's stderr is discarded, because stderr is a stream the\n// child writes while it runs and not a fact about how it ended. `spawnPorts` is the same process with both of\n// its output streams.\n//\n// Closing is stdin's end of stream first. A program that reads its input exits on EOF, which is the polite\n// half; the process is killed `killAfterMs` later if it is still there, which is the other half.\nimport { spawn, type ChildProcess } from \"node:child_process\";\nimport type { Readable } from \"node:stream\";\nimport { held } from \"../held.js\";\nimport type { Channel } from \"../channel.js\";\n/** How a process ended: it exited with a code, or a signal terminated it. */\nexport type ProcessEnd =\n | { readonly kind: \"exit\"; readonly code: number }\n | { readonly kind: \"signal\"; readonly signal: string };\nexport interface SpawnPortOptions {\n cwd?: string;\n env?: NodeJS.ProcessEnv;\n /** How long the process may live after its stdin is closed. Default 5000 ms, then SIGKILL. */\n killAfterMs?: number;\n}\nexport interface ProcessPorts {\n readonly stdout: Channel<Uint8Array, ProcessEnd>;\n readonly stderr: Channel<Uint8Array, ProcessEnd>;\n}\nconst DEFAULT_KILL_AFTER_MS = 5_000;\n/** A command as a channel of bytes: stdout in, stdin out, and its exit when it closes. */\nexport function spawnPort(\n command: string[],\n options: SpawnPortOptions = {},\n): Promise<Channel<Uint8Array, ProcessEnd>> {\n return spawnPorts(command, options).then((ports) => ports.stdout);\n}\n/** One process, its stdout and its stderr, each a channel that ends with the same exit. */\nexport async function spawnPorts(\n command: string[],\n options: SpawnPortOptions = {},\n): Promise<ProcessPorts> {\n const [file, ...args] = command;\n if (file === undefined) throw new Error(\"spawnPort: there is no command to run\");\n const child = spawn(file, args, { cwd: options.cwd, env: options.env, detached: true });\n await started(child, file);\n const out = bytesOf(child.stdout, \"stdout\");\n const err = bytesOf(child.stderr, \"stderr\");\n let exit: ProcessEnd | null = null;\n let ended = false;\n const closers = new Set<(reason: ProcessEnd) => void>();\n const failers = new Set<(error: unknown) => void>();\n const end = (why: ProcessEnd): void => {\n if (ended) return;\n ended = true;\n exit = why;\n for (const closer of [...closers]) closer(why);\n };\n child.on(\"error\", (error) => {\n for (const failer of [...failers]) failer(error);\n });\n // `close` rather than `exit`: it follows the end of both streams, so no frame is lost behind the ending.\n // Node gives a code or a signal and never both; that is the OS's one status word, split in two.\n child.on(\"close\", (code, signal) =>\n end(code !== null ? { kind: \"exit\", code } : { kind: \"signal\", signal: signal ?? \"unknown\" }),\n );\n const close = (reason?: string): Promise<void> => {\n return shutdown(child, options.killAfterMs ?? DEFAULT_KILL_AFTER_MS).catch(() => undefined);\n };\n const onClose = (handler: (reason: ProcessEnd) => void): (() => void) => {\n if (exit !== null) {\n handler(exit);\n return () => {};\n }\n closers.add(handler);\n return () => {\n closers.delete(handler);\n };\n };\n const onError = (handler: (error: unknown) => void): (() => void) => {\n failers.add(handler);\n return () => {\n failers.delete(handler);\n };\n };\n // What only a process knows, and the one command it takes: a signal for the process and its children.\n const facts = {\n info: { pid: child.pid },\n control: (command: Record<string, unknown>): void => {\n const signal = command.signal;\n if (typeof signal === \"string\") signalGroup(child, signal as NodeJS.Signals);\n },\n };\n const stderr: Channel<Uint8Array, ProcessEnd> = {\n send: () => Promise.reject(new Error(\"stderr carries the process's output; it has no input\")),\n onFrame: err.onFrame,\n onClose,\n onError,\n close,\n ...facts,\n };\n const stdout: Channel<Uint8Array, ProcessEnd> = {\n send: (frame: Uint8Array) => write(child, frame),\n onFrame: out.onFrame,\n onClose,\n onError,\n close,\n ...facts,\n };\n return { stdout, stderr };\n}\n/** Resolves once the child exists, rejects when the command could not be started at all. */\nfunction started(child: ChildProcess, file: string): Promise<void> {\n return new Promise((resolve, reject) => {\n child.once(\"spawn\", () => resolve());\n child.once(\"error\", (error: Error) =>\n reject(new Error(`spawnPort: could not start ${file}: ${error.message}`)),\n );\n });\n}\n/** One output stream as the frames of a channel, kept until somebody reads them. */\nfunction bytesOf(stream: Readable | null, label: string): Channel<Uint8Array, ProcessEnd> {\n const readers = new Set<(frame: Uint8Array) => void>();\n const frames: Channel<Uint8Array, ProcessEnd> = {\n send: () => Promise.reject(new Error(`${label} carries the process's output; it has no input`)),\n onFrame(handler) {\n readers.add(handler);\n return () => {\n readers.delete(handler);\n };\n },\n // The stream ending is not the process ending: the port reports that, from the process's own exit.\n onClose: () => () => {},\n onError: () => () => {},\n close: () => Promise.resolve(),\n info: {},\n control: () => {},\n };\n stream?.on(\"data\", (chunk: Buffer) => {\n const bytes = new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);\n for (const reader of [...readers]) reader(bytes);\n });\n return held(frames);\n}\n/** Writes to the child's stdin, and settles when the write is out. */\nfunction write(child: ChildProcess, frame: Uint8Array): Promise<void> {\n const stdin = child.stdin;\n if (stdin === null || stdin.destroyed) return Promise.reject(new Error(\"the process's stdin is closed\"));\n return new Promise((resolve, reject) => {\n stdin.write(frame, (error) => (error === null || error === undefined ? resolve() : reject(error)));\n });\n}\n/** Signal the child's process group, or the child alone when it has none. */\nfunction signalGroup(child: ChildProcess, signal: NodeJS.Signals): void {\n const pid = child.pid;\n if (pid === undefined) return;\n try {\n // The negative id is the group a detached child leads: its own children are in it too.\n process.kill(-pid, signal);\n return;\n } catch {\n /* no group of that number: this is one process after all */\n }\n try {\n child.kill(signal);\n } catch {\n /* gone */\n }\n}\n\n/** Ends stdin, and kills what is still there after the grace. A caller in a hurry sends `signal` first. */\nasync function shutdown(child: ChildProcess, killAfterMs: number): Promise<void> {\n if (child.exitCode !== null || child.signalCode !== null) return;\n const closed = new Promise<void>((resolve) => {\n child.once(\"close\", () => resolve());\n });\n child.stdin?.end();\n const timer = setTimeout(() => {\n if (child.exitCode === null && child.signalCode === null) signalGroup(child, \"SIGKILL\");\n }, killAfterMs);\n timer.unref?.();\n await closed;\n clearTimeout(timer);\n}\n","// ── an HTTP server as a port listener ───────────────────────────────────────\n//\n// One request is one pair of ports: the request's body is what the far side sends, the response's body is what we\n// send back, and the `Response` is built here because this is the only place that knows HTTP. A handler above\n// sees a pair and nothing about the protocol.\n//\n// The body is built before the handler runs, because a server must answer with a `Response` in the same turn it is\n// asked: writes go into the response's stream, and closing the outbound port ends it.\n\nimport type { Channel } from \"../channel.js\";\nimport { held } from \"../held.js\";\nimport type { PortListener, PortPair } from \"../reactor.js\";\n\nexport interface HttpListenerOptions<Meta> {\n port?: number;\n hostname?: string;\n /** Admission before a body is made: what it returns is the pair's meta; throwing refuses the request. */\n identify?(request: Request): Meta | Promise<Meta>;\n}\n\n/** A port listener that is also an HTTP server: the port it bound, and the pairs it hands out. */\nexport interface HttpListener<Meta> extends PortListener<Uint8Array, Uint8Array, Meta> {\n readonly port: number;\n}\n\n/** The Bun server this wraps, as little of it as is used here. */\ninterface BunServer {\n readonly port: number;\n stop(force?: boolean): void | Promise<void>;\n}\n\ninterface BunRuntime {\n serve(options: {\n port: number;\n hostname?: string;\n fetch(request: Request): Response | Promise<Response>;\n }): BunServer;\n}\n\n/**\n * A server whose requests arrive as pairs of ports.\n *\n * `identify` runs before a pair exists, so a refused request costs no stream and the caller reads a status with\n * the reason in it. The request body is the inbound port; the response body is the outbound one.\n */\nexport function httpListener<Meta = unknown>(options: HttpListenerOptions<Meta> = {}): HttpListener<Meta> {\n const runtime = (globalThis as { Bun?: BunRuntime }).Bun;\n if (runtime === undefined) throw new Error(\"httpListener: this needs Bun's server\");\n\n const waiting: Array<{ accept: (pair: PortPair<Uint8Array, Uint8Array, Meta, void>) => void; refuse: (error: Error) => void }> = [];\n const buffered: Array<PortPair<Uint8Array, Uint8Array, Meta, void>> = [];\n let closed = false;\n let server: BunServer | null = null;\n\n const take = (pair: PortPair<Uint8Array, Uint8Array, Meta, void>): void => {\n const next = waiting.shift();\n if (next === undefined) {\n buffered.push(pair);\n return;\n }\n next.accept(pair);\n };\n\n const bound = runtime.serve({\n port: options.port ?? 0,\n hostname: options.hostname,\n async fetch(request: Request): Promise<Response> {\n if (closed) return new Response(\"listener is closed\", { status: 503 });\n let meta: Meta;\n try {\n meta = options.identify === undefined ? (undefined as Meta) : await options.identify(request);\n } catch (error) {\n return new Response(error instanceof Error ? error.message : String(error), { status: 401 });\n }\n const outbound = responsePort();\n take({ inbound: requestPort(request), outbound: outbound.channel, meta });\n return new Response(outbound.body);\n },\n });\n server = bound;\n\n return {\n get port() {\n return server === null ? 0 : server.port;\n },\n accept(): Promise<PortPair<Uint8Array, Uint8Array, Meta, void>> {\n const next = buffered.shift();\n if (next !== undefined) return Promise.resolve(next);\n if (closed) return Promise.reject(new Error(\"httpListener: closed\"));\n return new Promise((accept, refuse) => {\n waiting.push({ accept, refuse });\n });\n },\n async close(): Promise<void> {\n closed = true;\n for (const next of waiting.splice(0)) next.refuse(new Error(\"httpListener: closed\"));\n await server?.stop(true);\n server = null;\n },\n };\n}\n\n/** The request's body as the inbound port: frames as they arrive, ended when the body is. */\nfunction requestPort(request: Request): Channel<Uint8Array, void> {\n const readers = new Set<(frame: Uint8Array) => void>();\n const closers = new Set<(reason: void) => void>();\n let ended = false;\n\n const end = (): void => {\n if (ended) return;\n ended = true;\n for (const closer of [...closers]) closer(undefined);\n };\n\n const inbound: Channel<Uint8Array, void> = {\n send: () => Promise.reject(new Error(\"a request body has no input; that side is the caller's\")),\n onFrame(handler) {\n readers.add(handler);\n return () => {\n readers.delete(handler);\n };\n },\n onClose(handler) {\n if (ended) {\n handler(undefined);\n return () => {};\n }\n closers.add(handler);\n return () => {\n closers.delete(handler);\n };\n },\n onError() {\n return () => {};\n },\n close: async () => {\n await request.body?.cancel().catch(() => undefined);\n end();\n },\n info: { method: request.method, url: request.url },\n control: () => {},\n };\n\n const reader = request.body?.getReader();\n // Held before the first read: a body can arrive in the same turn the pair is handed out.\n const port = held(inbound);\n void (async () => {\n if (reader === undefined) {\n end();\n return;\n }\n for (;;) {\n const chunk = await reader.read();\n if (chunk.done) {\n end();\n return;\n }\n for (const target of [...readers]) target(chunk.value);\n }\n })();\n\n return port;\n}\n\n/** The response's channel, and the body a `Response` is built from. One stream, written by the channel. */\nfunction responsePort(): { channel: Channel<Uint8Array, void>; body: ReadableStream<Uint8Array> } {\n const readers = new Set<(frame: Uint8Array) => void>();\n const closers = new Set<(reason: void) => void>();\n let ended = false;\n\n const end = (): void => {\n if (ended) return;\n ended = true;\n for (const closer of [...closers]) closer(undefined);\n };\n\n const body = new ReadableStream<Uint8Array>({\n start(controller) {\n readers.add((frame) => controller.enqueue(frame));\n closers.add(() => {\n try {\n controller.close();\n } catch {\n // Already closed by the platform: nothing here has an opinion about it.\n }\n });\n },\n cancel() {\n // The caller stopped reading: the channel is over from this side too.\n end();\n },\n });\n\n const channel: Channel<Uint8Array, void> = {\n async send(frame) {\n if (ended) throw new Error(\"this response is over\");\n for (const reader of [...readers]) reader(frame);\n },\n onFrame() {\n return () => {};\n },\n onClose(handler) {\n if (ended) {\n handler(undefined);\n return () => {};\n }\n closers.add(handler);\n return () => {\n closers.delete(handler);\n };\n },\n onError() {\n return () => {};\n },\n async close() {\n end();\n },\n info: {},\n control: () => {},\n };\n\n return { channel, body };\n}\n","// ── the two ports that wrap a process ───────────────────────────────────────\n//\n// Both are `portActor` over `spawnPorts`: one carries the child's octets as they are, the other puts the splitter\n// underneath and carries lines. The names are the contract — an actor definition is spawned by name.\n\nimport type { Message } from \"../../types.js\";\nimport { portActor } from \"../actor.js\";\nimport type { Channel, Part } from \"../channel.js\";\nimport { splitChannel } from \"../split.js\";\nimport { spawnPort, type ProcessEnd } from \"./spawn.js\";\n\n/** How a process port is started. */\nexport interface ProcessPortArgs {\n command: string[];\n cwd?: string;\n env?: NodeJS.ProcessEnv;\n /** How long the process may live after its stdin is closed. Default 5000 ms, then SIGKILL. */\n killAfterMs?: number;\n}\n\n\n\n/** Parts as their text: what a line port carries. Octets that came with a line are not text and are dropped. */\nfunction lines(parts: Channel<Part, ProcessEnd>): Channel<string, ProcessEnd> {\n const readers = new Set<(line: string) => void>();\n const closers = new Set<(reason: ProcessEnd) => void>();\n const failers = new Set<(error: unknown) => void>();\n\n parts.onFrame(({ line }) => {\n for (const reader of [...readers]) reader(line);\n });\n parts.onClose((reason) => {\n for (const closer of [...closers]) closer(reason);\n });\n parts.onError((error) => {\n for (const failer of [...failers]) failer(error);\n });\n\n return {\n send: (line) => parts.send({ line, bytes: null }),\n onFrame(handler) {\n readers.add(handler);\n return () => {\n readers.delete(handler);\n };\n },\n onClose(handler) {\n closers.add(handler);\n return () => {\n closers.delete(handler);\n };\n },\n onError(handler) {\n failers.add(handler);\n return () => {\n failers.delete(handler);\n };\n },\n close: (reason) => parts.close(reason),\n info: parts.info,\n control: (command) => parts.control(command),\n };\n}\n\n\n/** The child's octets, frame for frame. */\nexport const BinaryPort = portActor<ProcessPortArgs, Uint8Array, ProcessEnd>({\n name: \"binary-port\",\n open: (args) => spawnPort(args.command, args),\n});\n\n/** The same child cut into lines: the facts come with it, because the medium is the same one. */\nexport const Utf8Port = portActor<ProcessPortArgs, string, ProcessEnd>({\n name: \"utf8-port\",\n open: async (args) => lines(splitChannel(await spawnPort(args.command, args))),\n});\n"],"mappings":";;;AA2BA,MAAM,wBAAwB;;AAE9B,SAAgB,UACd,SACA,UAA4B,CAAC,GACa;CAC1C,OAAO,WAAW,SAAS,OAAO,CAAC,CAAC,MAAM,UAAU,MAAM,MAAM;AAClE;;AAEA,eAAsB,WACpB,SACA,UAA4B,CAAC,GACN;CACvB,MAAM,CAAC,MAAM,GAAG,QAAQ;CACxB,IAAI,SAAS,KAAA,GAAW,MAAM,IAAI,MAAM,uCAAuC;CAC/E,MAAM,QAAQ,MAAM,MAAM,MAAM;EAAE,KAAK,QAAQ;EAAK,KAAK,QAAQ;EAAK,UAAU;CAAK,CAAC;CACtF,MAAM,QAAQ,OAAO,IAAI;CACzB,MAAM,MAAM,QAAQ,MAAM,QAAQ,QAAQ;CAC1C,MAAM,MAAM,QAAQ,MAAM,QAAQ,QAAQ;CAC1C,IAAI,OAA0B;CAC9B,IAAI,QAAQ;CACZ,MAAM,0BAAU,IAAI,IAAkC;CACtD,MAAM,0BAAU,IAAI,IAA8B;CAClD,MAAM,OAAO,QAA0B;EACrC,IAAI,OAAO;EACX,QAAQ;EACR,OAAO;EACP,KAAK,MAAM,UAAU,CAAC,GAAG,OAAO,GAAG,OAAO,GAAG;CAC/C;CACA,MAAM,GAAG,UAAU,UAAU;EAC3B,KAAK,MAAM,UAAU,CAAC,GAAG,OAAO,GAAG,OAAO,KAAK;CACjD,CAAC;CAGD,MAAM,GAAG,UAAU,MAAM,WACvB,IAAI,SAAS,OAAO;EAAE,MAAM;EAAQ;CAAK,IAAI;EAAE,MAAM;EAAU,QAAQ,UAAU;CAAU,CAAC,CAC9F;CACA,MAAM,SAAS,WAAmC;EAChD,OAAO,SAAS,OAAO,QAAQ,eAAe,qBAAqB,CAAC,CAAC,YAAY,KAAA,CAAS;CAC5F;CACA,MAAM,WAAW,YAAwD;EACvE,IAAI,SAAS,MAAM;GACjB,QAAQ,IAAI;GACZ,aAAa,CAAC;EAChB;EACA,QAAQ,IAAI,OAAO;EACnB,aAAa;GACX,QAAQ,OAAO,OAAO;EACxB;CACF;CACA,MAAM,WAAW,YAAoD;EACnE,QAAQ,IAAI,OAAO;EACnB,aAAa;GACX,QAAQ,OAAO,OAAO;EACxB;CACF;CAEA,MAAM,QAAQ;EACZ,MAAM,EAAE,KAAK,MAAM,IAAI;EACvB,UAAU,YAA2C;GACnD,MAAM,SAAS,QAAQ;GACvB,IAAI,OAAO,WAAW,UAAU,YAAY,OAAO,MAAwB;EAC7E;CACF;CACA,MAAM,SAA0C;EAC9C,YAAY,QAAQ,uBAAO,IAAI,MAAM,sDAAsD,CAAC;EAC5F,SAAS,IAAI;EACb;EACA;EACA;EACA,GAAG;CACL;CASA,OAAO;EAAE,QAAA;GAPP,OAAO,UAAsB,MAAM,OAAO,KAAK;GAC/C,SAAS,IAAI;GACb;GACA;GACA;GACA,GAAG;EAES;EAAG;CAAO;AAC1B;;AAEA,SAAS,QAAQ,OAAqB,MAA6B;CACjE,OAAO,IAAI,SAAS,SAAS,WAAW;EACtC,MAAM,KAAK,eAAe,QAAQ,CAAC;EACnC,MAAM,KAAK,UAAU,UACnB,uBAAO,IAAI,MAAM,8BAA8B,KAAK,IAAI,MAAM,SAAS,CAAC,CAC1E;CACF,CAAC;AACH;;AAEA,SAAS,QAAQ,QAAyB,OAAgD;CACxF,MAAM,0BAAU,IAAI,IAAiC;CACrD,MAAM,SAA0C;EAC9C,YAAY,QAAQ,uBAAO,IAAI,MAAM,GAAG,MAAM,+CAA+C,CAAC;EAC9F,QAAQ,SAAS;GACf,QAAQ,IAAI,OAAO;GACnB,aAAa;IACX,QAAQ,OAAO,OAAO;GACxB;EACF;EAEA,qBAAqB,CAAC;EACtB,qBAAqB,CAAC;EACtB,aAAa,QAAQ,QAAQ;EAC7B,MAAM,CAAC;EACP,eAAe,CAAC;CAClB;CACA,QAAQ,GAAG,SAAS,UAAkB;EACpC,MAAM,QAAQ,IAAI,WAAW,MAAM,QAAQ,MAAM,YAAY,MAAM,UAAU;EAC7E,KAAK,MAAM,UAAU,CAAC,GAAG,OAAO,GAAG,OAAO,KAAK;CACjD,CAAC;CACD,OAAO,KAAK,MAAM;AACpB;;AAEA,SAAS,MAAM,OAAqB,OAAkC;CACpE,MAAM,QAAQ,MAAM;CACpB,IAAI,UAAU,QAAQ,MAAM,WAAW,OAAO,QAAQ,uBAAO,IAAI,MAAM,+BAA+B,CAAC;CACvG,OAAO,IAAI,SAAS,SAAS,WAAW;EACtC,MAAM,MAAM,QAAQ,UAAW,UAAU,QAAQ,UAAU,KAAA,IAAY,QAAQ,IAAI,OAAO,KAAK,CAAE;CACnG,CAAC;AACH;;AAEA,SAAS,YAAY,OAAqB,QAA8B;CACtE,MAAM,MAAM,MAAM;CAClB,IAAI,QAAQ,KAAA,GAAW;CACvB,IAAI;EAEF,QAAQ,KAAK,CAAC,KAAK,MAAM;EACzB;CACF,QAAQ,CAER;CACA,IAAI;EACF,MAAM,KAAK,MAAM;CACnB,QAAQ,CAER;AACF;;AAGA,eAAe,SAAS,OAAqB,aAAoC;CAC/E,IAAI,MAAM,aAAa,QAAQ,MAAM,eAAe,MAAM;CAC1D,MAAM,SAAS,IAAI,SAAe,YAAY;EAC5C,MAAM,KAAK,eAAe,QAAQ,CAAC;CACrC,CAAC;CACD,MAAM,OAAO,IAAI;CACjB,MAAM,QAAQ,iBAAiB;EAC7B,IAAI,MAAM,aAAa,QAAQ,MAAM,eAAe,MAAM,YAAY,OAAO,SAAS;CACxF,GAAG,WAAW;CACd,MAAM,QAAQ;CACd,MAAM;CACN,aAAa,KAAK;AACpB;;;;;;;;;ACxIA,SAAgB,aAA6B,UAAqC,CAAC,GAAuB;CACxG,MAAM,UAAW,WAAoC;CACrD,IAAI,YAAY,KAAA,GAAW,MAAM,IAAI,MAAM,uCAAuC;CAElF,MAAM,UAA2H,CAAC;CAClI,MAAM,WAAgE,CAAC;CACvE,IAAI,SAAS;CACb,IAAI,SAA2B;CAE/B,MAAM,QAAQ,SAA6D;EACzE,MAAM,OAAO,QAAQ,MAAM;EAC3B,IAAI,SAAS,KAAA,GAAW;GACtB,SAAS,KAAK,IAAI;GAClB;EACF;EACA,KAAK,OAAO,IAAI;CAClB;CAkBA,SAhBc,QAAQ,MAAM;EAC1B,MAAM,QAAQ,QAAQ;EACtB,UAAU,QAAQ;EAClB,MAAM,MAAM,SAAqC;GAC/C,IAAI,QAAQ,OAAO,IAAI,SAAS,sBAAsB,EAAE,QAAQ,IAAI,CAAC;GACrE,IAAI;GACJ,IAAI;IACF,OAAO,QAAQ,aAAa,KAAA,IAAa,KAAA,IAAqB,MAAM,QAAQ,SAAS,OAAO;GAC9F,SAAS,OAAO;IACd,OAAO,IAAI,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAAG,EAAE,QAAQ,IAAI,CAAC;GAC7F;GACA,MAAM,WAAW,aAAa;GAC9B,KAAK;IAAE,SAAS,YAAY,OAAO;IAAG,UAAU,SAAS;IAAS;GAAK,CAAC;GACxE,OAAO,IAAI,SAAS,SAAS,IAAI;EACnC;CACF,CACa;CAEb,OAAO;EACL,IAAI,OAAO;GACT,OAAO,WAAW,OAAO,IAAI,OAAO;EACtC;EACA,SAAgE;GAC9D,MAAM,OAAO,SAAS,MAAM;GAC5B,IAAI,SAAS,KAAA,GAAW,OAAO,QAAQ,QAAQ,IAAI;GACnD,IAAI,QAAQ,OAAO,QAAQ,uBAAO,IAAI,MAAM,sBAAsB,CAAC;GACnE,OAAO,IAAI,SAAS,QAAQ,WAAW;IACrC,QAAQ,KAAK;KAAE;KAAQ;IAAO,CAAC;GACjC,CAAC;EACH;EACA,MAAM,QAAuB;GAC3B,SAAS;GACT,KAAK,MAAM,QAAQ,QAAQ,OAAO,CAAC,GAAG,KAAK,uBAAO,IAAI,MAAM,sBAAsB,CAAC;GACnF,MAAM,QAAQ,KAAK,IAAI;GACvB,SAAS;EACX;CACF;AACF;;AAGA,SAAS,YAAY,SAA6C;CAChE,MAAM,0BAAU,IAAI,IAAiC;CACrD,MAAM,0BAAU,IAAI,IAA4B;CAChD,IAAI,QAAQ;CAEZ,MAAM,YAAkB;EACtB,IAAI,OAAO;EACX,QAAQ;EACR,KAAK,MAAM,UAAU,CAAC,GAAG,OAAO,GAAG,OAAO,KAAA,CAAS;CACrD;CAEA,MAAM,UAAqC;EACzC,YAAY,QAAQ,uBAAO,IAAI,MAAM,wDAAwD,CAAC;EAC9F,QAAQ,SAAS;GACf,QAAQ,IAAI,OAAO;GACnB,aAAa;IACX,QAAQ,OAAO,OAAO;GACxB;EACF;EACA,QAAQ,SAAS;GACf,IAAI,OAAO;IACT,QAAQ,KAAA,CAAS;IACjB,aAAa,CAAC;GAChB;GACA,QAAQ,IAAI,OAAO;GACnB,aAAa;IACX,QAAQ,OAAO,OAAO;GACxB;EACF;EACA,UAAU;GACR,aAAa,CAAC;EAChB;EACA,OAAO,YAAY;GACjB,MAAM,QAAQ,MAAM,OAAO,CAAC,CAAC,YAAY,KAAA,CAAS;GAClD,IAAI;EACN;EACA,MAAM;GAAE,QAAQ,QAAQ;GAAQ,KAAK,QAAQ;EAAI;EACjD,eAAe,CAAC;CAClB;CAEA,MAAM,SAAS,QAAQ,MAAM,UAAU;CAEvC,MAAM,OAAO,KAAK,OAAO;CACzB,CAAM,YAAY;EAChB,IAAI,WAAW,KAAA,GAAW;GACxB,IAAI;GACJ;EACF;EACA,SAAS;GACP,MAAM,QAAQ,MAAM,OAAO,KAAK;GAChC,IAAI,MAAM,MAAM;IACd,IAAI;IACJ;GACF;GACA,KAAK,MAAM,UAAU,CAAC,GAAG,OAAO,GAAG,OAAO,MAAM,KAAK;EACvD;CACF,EAAA,CAAG;CAEH,OAAO;AACT;;AAGA,SAAS,eAAyF;CAChG,MAAM,0BAAU,IAAI,IAAiC;CACrD,MAAM,0BAAU,IAAI,IAA4B;CAChD,IAAI,QAAQ;CAEZ,MAAM,YAAkB;EACtB,IAAI,OAAO;EACX,QAAQ;EACR,KAAK,MAAM,UAAU,CAAC,GAAG,OAAO,GAAG,OAAO,KAAA,CAAS;CACrD;CA+CA,OAAO;EAAE,SAAA;GA3BP,MAAM,KAAK,OAAO;IAChB,IAAI,OAAO,MAAM,IAAI,MAAM,uBAAuB;IAClD,KAAK,MAAM,UAAU,CAAC,GAAG,OAAO,GAAG,OAAO,KAAK;GACjD;GACA,UAAU;IACR,aAAa,CAAC;GAChB;GACA,QAAQ,SAAS;IACf,IAAI,OAAO;KACT,QAAQ,KAAA,CAAS;KACjB,aAAa,CAAC;IAChB;IACA,QAAQ,IAAI,OAAO;IACnB,aAAa;KACX,QAAQ,OAAO,OAAO;IACxB;GACF;GACA,UAAU;IACR,aAAa,CAAC;GAChB;GACA,MAAM,QAAQ;IACZ,IAAI;GACN;GACA,MAAM,CAAC;GACP,eAAe,CAAC;EAGH;EAAG,MAAA,IA7CD,eAA2B;GAC1C,MAAM,YAAY;IAChB,QAAQ,KAAK,UAAU,WAAW,QAAQ,KAAK,CAAC;IAChD,QAAQ,UAAU;KAChB,IAAI;MACF,WAAW,MAAM;KACnB,QAAQ,CAER;IACF,CAAC;GACH;GACA,SAAS;IAEP,IAAI;GACN;EACF,CA8BqB;CAAE;AACzB;;;;ACvMA,SAAS,MAAM,OAA+D;CAC5E,MAAM,0BAAU,IAAI,IAA4B;CAChD,MAAM,0BAAU,IAAI,IAAkC;CACtD,MAAM,0BAAU,IAAI,IAA8B;CAElD,MAAM,SAAS,EAAE,WAAW;EAC1B,KAAK,MAAM,UAAU,CAAC,GAAG,OAAO,GAAG,OAAO,IAAI;CAChD,CAAC;CACD,MAAM,SAAS,WAAW;EACxB,KAAK,MAAM,UAAU,CAAC,GAAG,OAAO,GAAG,OAAO,MAAM;CAClD,CAAC;CACD,MAAM,SAAS,UAAU;EACvB,KAAK,MAAM,UAAU,CAAC,GAAG,OAAO,GAAG,OAAO,KAAK;CACjD,CAAC;CAED,OAAO;EACL,OAAO,SAAS,MAAM,KAAK;GAAE;GAAM,OAAO;EAAK,CAAC;EAChD,QAAQ,SAAS;GACf,QAAQ,IAAI,OAAO;GACnB,aAAa;IACX,QAAQ,OAAO,OAAO;GACxB;EACF;EACA,QAAQ,SAAS;GACf,QAAQ,IAAI,OAAO;GACnB,aAAa;IACX,QAAQ,OAAO,OAAO;GACxB;EACF;EACA,QAAQ,SAAS;GACf,QAAQ,IAAI,OAAO;GACnB,aAAa;IACX,QAAQ,OAAO,OAAO;GACxB;EACF;EACA,QAAQ,WAAW,MAAM,MAAM,MAAM;EACrC,MAAM,MAAM;EACZ,UAAU,YAAY,MAAM,QAAQ,OAAO;CAC7C;AACF;;AAIA,MAAa,aAAa,UAAmD;CAC3E,MAAM;CACN,OAAO,SAAS,UAAU,KAAK,SAAS,IAAI;AAC9C,CAAC;;AAGD,MAAa,WAAW,UAA+C;CACrE,MAAM;CACN,MAAM,OAAO,SAAS,MAAM,aAAa,MAAM,UAAU,KAAK,SAAS,IAAI,CAAC,CAAC;AAC/E,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as held } from "./actor-
|
|
1
|
+
import { n as held } from "./actor-Df5v2zuv.js";
|
|
2
2
|
//#region src/port/channel.ts
|
|
3
3
|
/** What is thrown when a frame is sent on a channel that is over. */
|
|
4
4
|
var ChannelClosed = class extends Error {
|
|
@@ -61,6 +61,8 @@ function memoryChannelPair() {
|
|
|
61
61
|
closed[peer] = reason;
|
|
62
62
|
for (const handler of [...theirs.closes]) handler(reason);
|
|
63
63
|
},
|
|
64
|
+
info: {},
|
|
65
|
+
control: () => {},
|
|
64
66
|
fail: (error) => {
|
|
65
67
|
if (closed[side] !== null) return;
|
|
66
68
|
const peer = other(side);
|
|
@@ -193,7 +195,9 @@ function streamChannel(pair) {
|
|
|
193
195
|
close: () => {
|
|
194
196
|
shut();
|
|
195
197
|
return Promise.resolve();
|
|
196
|
-
}
|
|
198
|
+
},
|
|
199
|
+
info: {},
|
|
200
|
+
control: () => {}
|
|
197
201
|
};
|
|
198
202
|
}
|
|
199
203
|
//#endregion
|
|
@@ -261,7 +265,9 @@ function all(sources) {
|
|
|
261
265
|
async close(reason) {
|
|
262
266
|
ended = true;
|
|
263
267
|
await Promise.all(names.map((name) => sources[name].close(reason)));
|
|
264
|
-
}
|
|
268
|
+
},
|
|
269
|
+
info: {},
|
|
270
|
+
control: () => {}
|
|
265
271
|
};
|
|
266
272
|
}
|
|
267
273
|
/**
|
|
@@ -330,7 +336,9 @@ function settled(sources) {
|
|
|
330
336
|
async close(reason) {
|
|
331
337
|
ended = true;
|
|
332
338
|
await Promise.all(names.map((name) => sources[name].close(reason)));
|
|
333
|
-
}
|
|
339
|
+
},
|
|
340
|
+
info: {},
|
|
341
|
+
control: () => {}
|
|
334
342
|
};
|
|
335
343
|
}
|
|
336
344
|
//#endregion
|
|
@@ -384,7 +392,9 @@ async function fetchPort(url, options = {}) {
|
|
|
384
392
|
async close() {
|
|
385
393
|
controller.abort();
|
|
386
394
|
await reader?.cancel().catch(() => void 0);
|
|
387
|
-
}
|
|
395
|
+
},
|
|
396
|
+
info: { url },
|
|
397
|
+
control: () => {}
|
|
388
398
|
};
|
|
389
399
|
const reader = response.body?.getReader();
|
|
390
400
|
const port = held(frames);
|
|
@@ -484,10 +494,15 @@ function duplexChannel(pair) {
|
|
|
484
494
|
onError: (handler) => pair.inbound.onError(handler),
|
|
485
495
|
close: async (reason) => {
|
|
486
496
|
await pair.outbound.close(reason);
|
|
487
|
-
}
|
|
497
|
+
},
|
|
498
|
+
info: {
|
|
499
|
+
...pair.inbound.info,
|
|
500
|
+
...pair.outbound.info
|
|
501
|
+
},
|
|
502
|
+
control: (command) => pair.inbound.control(command)
|
|
488
503
|
};
|
|
489
504
|
}
|
|
490
505
|
//#endregion
|
|
491
506
|
export { settled as a, ChannelClosed as c, all as i, portReactor as n, streamChannel as o, fetchPort as r, memoryChannelPair as s, duplexChannel as t };
|
|
492
507
|
|
|
493
|
-
//# sourceMappingURL=port-
|
|
508
|
+
//# sourceMappingURL=port-BR7f7BDY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"port-BR7f7BDY.js","names":[],"sources":["../src/port/channel.ts","../src/port/memory.ts","../src/port/stream.ts","../src/port/combine.ts","../src/port/fetch.ts","../src/port/reactor.ts","../src/port/duplex.ts"],"sourcesContent":["// ── A channel ───────────────────────────────────────────────────────────────\n//\n// Carries values of one type between two ends. The type parameter is what crosses: bytes, a line and the\n// octets it announced, a decoded frame. A medium produces one; a protocol adapter wraps one into another.\n//\n// It ends one of three ways, and each has its own callback:\n//\n// close() this side ends it; the far end hears a close, this side's callers hear nothing\n// onClose the far end is gone, with the reason it gave\n// onError the wire failed; once, and always before onClose\n//\n// `close()` is idempotent. `send()` rejects once the channel is over, from either side.\n\n/**\n * A line of a channel that carries lines, and the octets it announced.\n *\n * A line may announce octets after it, as a stream chunk does on the remote seam, so a part is the line and\n * what it announced. A medium that moves whole messages, a websocket for instance, produces parts with no\n * octets. A byte medium produces none until the splitter has run.\n */\nexport interface Part {\n /** One line, without its terminator. */\n line: string;\n /** The octets the line announced, when it announced any. */\n bytes: Uint8Array | null;\n}\n\n/**\n * Values of one type, between two ends.\n *\n * `C` is what the far end says when it closes — a sentence for a medium, or whatever the medium has to say for\n * itself: a spawned process ends with an exit status, and that is the fact worth having. `close()` takes a\n * sentence from this side, because the close we ask for is ours.\n */\nexport interface Channel<F, C = string> {\n /**\n * Put a frame on it.\n *\n * Rejects when the channel is over, from either side: there is no reader left, and a resolved promise\n * would say the frame had been taken.\n */\n send(frame: F): Promise<void>;\n /** A frame arrived. The returned function stops this reader and no other. */\n onFrame(handler: (frame: F) => void): () => void;\n /** The far end closed, and this is what it said. Once, after any frames already read. */\n onClose(handler: (reason: C) => void): () => void;\n /** The wire failed. Once, and always followed by a close. */\n onError(handler: (error: unknown) => void): () => void;\n /**\n * Close it from this side.\n *\n * Idempotent: a second call does nothing. The far end hears a close; the callers on this side do not,\n * because they are the ones who asked.\n */\n close(reason?: string): Promise<void>;\n /** What the medium knows about itself: a process its pid, a socket its peer. Empty when it knows nothing. */\n readonly info: Record<string, unknown>;\n /**\n * Ask the medium to act on what is behind the channel — signal a process, resize a terminal.\n *\n * A medium with nothing behind it does nothing. The command is the medium's own vocabulary; the channel\n * only carries it.\n */\n control(command: Record<string, unknown>): void;\n}\n\n/** What is thrown when a frame is sent on a channel that is over. */\nexport class ChannelClosed extends Error {\n constructor(reason: string) {\n super(`the channel is closed: ${reason}`);\n this.name = \"ChannelClosed\";\n }\n}\n","// ── A channel with no medium ────────────────────────────────────────────────\n//\n// Two ends over nothing: what one sends, the other reads. For testing an adapter or a protocol without a\n// socket, a child or a file, and for exercising the endings without faking one.\n//\n// No buffer: a frame reaches the other end before `send` settles.\n\nimport { ChannelClosed, type Channel } from \"./channel.js\";\n\n/** One end of a pair. `fail` is the hook this double adds: it breaks the wire the way a socket would. */\nexport interface MemoryEnd<F> extends Channel<F> {\n /** Break the wire: the far end hears an error, and then a close. */\n fail(error: unknown): void;\n}\n\nexport interface ChannelPair<F> {\n near: MemoryEnd<F>;\n far: MemoryEnd<F>;\n}\n\ntype Side = \"near\" | \"far\";\n\ninterface End<F> {\n frames: Set<(frame: F) => void>;\n closes: Set<(reason: string) => void>;\n errors: Set<(error: unknown) => void>;\n}\n\nfunction end<F>(): End<F> {\n return { frames: new Set(), closes: new Set(), errors: new Set() };\n}\n\n/** The two halves of one channel. */\nexport function memoryChannelPair<F>(): ChannelPair<F> {\n const near = end<F>();\n const far = end<F>();\n const closed: Record<Side, string | null> = { near: null, far: null };\n const other = (side: Side): Side => (side === \"near\" ? \"far\" : \"near\");\n\n /** One side's own state and the other side's, which is all a channel is. */\n const make = (side: Side): MemoryEnd<F> => {\n const mine = side === \"near\" ? near : far;\n const theirs = side === \"near\" ? far : near;\n return {\n send: async (frame: F) => {\n const ended = closed[side] ?? closed[other(side)];\n if (ended !== null) throw new ChannelClosed(ended);\n for (const handler of [...theirs.frames]) handler(frame);\n },\n onFrame: (handler) => {\n mine.frames.add(handler);\n return () => {\n mine.frames.delete(handler);\n };\n },\n onClose: (handler) => {\n mine.closes.add(handler);\n return () => {\n mine.closes.delete(handler);\n };\n },\n onError: (handler) => {\n mine.errors.add(handler);\n return () => {\n mine.errors.delete(handler);\n };\n },\n close: async (reason = \"closed here\") => {\n if (closed[side] !== null) return;\n const peer = other(side);\n closed[side] = reason;\n if (closed[peer] !== null) return;\n closed[peer] = reason;\n for (const handler of [...theirs.closes]) handler(reason);\n },\n info: {},\n control: () => {},\n fail: (error: unknown) => {\n if (closed[side] !== null) return;\n const peer = other(side);\n if (closed[peer] !== null) return;\n for (const handler of [...theirs.errors]) handler(error);\n for (const handler of [...theirs.closes]) handler(\"the wire failed\");\n closed[side] = \"the wire failed\";\n closed[peer] = \"the wire failed\";\n },\n };\n };\n\n return { near: make(\"near\"), far: make(\"far\") };\n}\n","// ── a stream pair as a channel of bytes ────────────────────────────────\n//\n// Chunks out as they arrive, chunks in as they are written, and an ending. Framing, splitting and the meaning\n// of a frame compose on top of this.\n//\n// A send settles when the stream has flushed the chunk, not when it has accepted it into a buffer, so a writer\n// cannot run ahead of the pipe without waiting.\n//\n// Two of the three endings are discovered here rather than declared: a read side that ends is a peer that\n// closed, and a broken stream is a wire that failed.\n\nimport type { Readable, Writable } from \"node:stream\";\n\nimport { ChannelClosed, type Channel } from \"./channel.js\";\n\n/** What the channel talks over: something to read from, something to write to. */\nexport interface StreamPair {\n read: Readable;\n write: Writable;\n}\n\n/** A write error that means the peer has already hung up. */\nfunction peerGone(code: unknown): boolean {\n return (\n code === \"EPIPE\" ||\n code === \"ERR_STREAM_DESTROYED\" ||\n code === \"ERR_STREAM_WRITE_AFTER_END\" ||\n code === \"ERR_STREAM_ALREADY_FINISHED\"\n );\n}\n\nfunction asBytes(chunk: Buffer | string): Uint8Array {\n if (typeof chunk === \"string\") return new Uint8Array(Buffer.from(chunk, \"utf8\"));\n return new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);\n}\n\n/**\n * A channel of bytes over a stream pair.\n *\n * `close()` ends the write side, so the far end hears an end of stream, and destroys the read side, so no\n * more chunks arrive after this side has said it is done. A peer that goes away is a close of this side's\n * own readers, and the reason says which of the two it was.\n */\nexport function streamChannel(pair: StreamPair): Channel<Uint8Array> {\n const frames = new Set<(frame: Uint8Array) => void>();\n const closers = new Set<(reason: string) => void>();\n const failers = new Set<(error: unknown) => void>();\n /** What this side's readers were told, and null while they have been told nothing. */\n let reported: string | null = null;\n let ended = false;\n\n const release = (): void => {\n // Best effort: a stream whose peer is gone throws from `destroy` and from `end`, and teardown is not\n // the place to find out.\n try {\n pair.read.destroy();\n } catch {\n /* already gone */\n }\n try {\n if (!pair.write.writableEnded) pair.write.end();\n } catch {\n /* already gone */\n }\n };\n\n /** End it from this side: the far end hears it, and this side's readers do not — they asked. */\n const shut = (): void => {\n if (ended) return;\n ended = true;\n release();\n };\n\n /** The far end is gone, and this side's readers hear it. */\n const finish = (reason: string): void => {\n if (ended) return;\n ended = true;\n reported = reason;\n release();\n for (const closer of [...closers]) closer(reason);\n };\n\n /** The wire failed: this side hears the error once, and then a close. */\n const fail = (error: unknown): void => {\n if (ended) return;\n ended = true;\n reported = \"the wire failed\";\n release();\n for (const failer of [...failers]) failer(error);\n for (const closer of [...closers]) closer(\"the wire failed\");\n };\n\n pair.read.on(\"data\", (chunk: Buffer | string) => {\n if (ended) return;\n const bytes = asBytes(chunk);\n for (const reader of [...frames]) reader(bytes);\n });\n pair.read.on(\"end\", () => finish(\"the far end closed\"));\n // A stream destroyed without an end — a socket whose peer vanished — says so only here.\n pair.read.on(\"close\", () => finish(\"the far end closed\"));\n pair.read.on(\"error\", (error: Error) => fail(error));\n pair.write.on(\"error\", (error: Error) => fail(error));\n\n const send = (frame: Uint8Array): Promise<void> =>\n new Promise<void>((resolve, reject) => {\n if (ended) {\n reject(new ChannelClosed(reported ?? \"closed here\"));\n return;\n }\n const done = (err?: Error | null): void => {\n if (err === undefined || err === null) {\n resolve();\n return;\n }\n if (peerGone((err as { code?: unknown }).code)) {\n finish(\"the peer is gone\");\n reject(new ChannelClosed(\"the peer is gone\"));\n return;\n }\n fail(err);\n reject(err);\n };\n try {\n pair.write.write(frame, done);\n } catch (err) {\n fail(err);\n reject(err);\n }\n });\n\n return {\n send,\n onFrame: (handler) => {\n frames.add(handler);\n return () => {\n frames.delete(handler);\n };\n },\n onClose: (handler) => {\n if (reported !== null) {\n handler(reported);\n return () => {};\n }\n closers.add(handler);\n return () => {\n closers.delete(handler);\n };\n },\n onError: (handler) => {\n failers.add(handler);\n return () => {\n failers.delete(handler);\n };\n },\n close: () => {\n shut();\n return Promise.resolve();\n },\n info: {},\n control: () => {},\n };\n}\n","// ── several channels as one ─────────────────────────────────────────────────\n//\n// Two combinators, named after the promise pair because the choice is the same one: do you stop at the first\n// ending, or do you wait for every channel to say how it ended?\n//\n// `all` is the first: a frame from any source arrives tagged with the source, and the first ending ends the\n// combined channel with that source's reason. `settled` is the second: every ending arrives as a frame of its\n// own, tagged the same way, and the combined channel closes once all of them have.\n//\n// Neither sends. A combined channel is made of others, so a frame put on it has no destination; `send`\n// rejects and says so, and `close` closes every source.\n\nimport type { Channel } from \"./channel.js\";\n\n/** A frame, and which of the combined channels it arrived on. */\nexport interface Sourced<T, F> {\n readonly source: T;\n readonly frame: F;\n}\n\n/** An ending, and which of the combined channels ended. */\nexport interface Ended<T, C> {\n readonly source: T;\n readonly ending: C;\n}\n\nfunction sendRefusal<T>(sources: T[]): Error {\n return new Error(`this channel is made of ${sources.length}: send on one of them, not on the combination`);\n}\n\n/**\n * Every frame from every source, tagged; the first ending ends it.\n *\n * The ending is the first one to arrive, and the others are ignored afterwards. This is what a process wants\n * by default: its stdout ending means the process is gone, and that is the news.\n */\nexport function all<T extends string, F, C>(sources: Record<T, Channel<F, C>>): Channel<Sourced<T, F>, C> {\n const names = Object.keys(sources) as T[];\n const readers = new Set<(frame: Sourced<T, F>) => void>();\n const closers = new Set<(reason: C) => void>();\n const failers = new Set<(error: unknown) => void>();\n let ended = false;\n /** What the first ending said, for a reader that subscribes after it. */\n let ending: C | undefined;\n\n const end = (reason: C): void => {\n if (ended) return;\n ended = true;\n ending = reason;\n for (const closer of [...closers]) closer(reason);\n };\n\n for (const name of names) {\n const source = sources[name];\n source.onFrame((frame) => {\n for (const reader of [...readers]) reader({ source: name, frame });\n });\n source.onClose(end);\n source.onError((error) => {\n for (const failer of [...failers]) failer(error);\n });\n }\n\n return {\n send: () => Promise.reject(sendRefusal(names)),\n onFrame(handler) {\n readers.add(handler);\n return () => {\n readers.delete(handler);\n };\n },\n onClose(handler) {\n if (ending !== undefined) {\n handler(ending);\n return () => {};\n }\n closers.add(handler);\n return () => {\n closers.delete(handler);\n };\n },\n onError(handler) {\n failers.add(handler);\n return () => {\n failers.delete(handler);\n };\n },\n async close(reason?: string) {\n ended = true;\n await Promise.all(names.map((name) => sources[name].close(reason)));\n },\n info: {},\n control: () => {},\n };\n}\n\n/**\n * Every frame from every source, tagged, and every ending as a frame of its own.\n *\n * It closes once all of them have ended, and that close carries nothing: the endings already arrived as frames,\n * each one saying which source it belongs to.\n */\nexport function settled<T extends string, F, C>(\n sources: Record<T, Channel<F, C>>,\n): Channel<Sourced<T, F> | Ended<T, C>, void> {\n const names = Object.keys(sources) as T[];\n const readers = new Set<(frame: Sourced<T, F> | Ended<T, C>) => void>();\n const closers = new Set<(reason: void) => void>();\n const failers = new Set<(error: unknown) => void>();\n let remaining = names.length;\n let ended = false;\n\n const emit = (frame: Sourced<T, F> | Ended<T, C>): void => {\n for (const reader of [...readers]) reader(frame);\n };\n\n const end = (): void => {\n if (ended) return;\n ended = true;\n for (const closer of [...closers]) closer(undefined);\n };\n\n for (const name of names) {\n const source = sources[name];\n source.onFrame((frame) => emit({ source: name, frame }));\n source.onClose((ending) => {\n emit({ source: name, ending });\n remaining -= 1;\n if (remaining === 0) end();\n });\n source.onError((error) => {\n for (const failer of [...failers]) failer(error);\n });\n }\n\n return {\n send: () => Promise.reject(sendRefusal(names)),\n onFrame(handler) {\n readers.add(handler);\n return () => {\n readers.delete(handler);\n };\n },\n onClose(handler) {\n if (ended) {\n handler(undefined);\n return () => {};\n }\n closers.add(handler);\n return () => {\n closers.delete(handler);\n };\n },\n onError(handler) {\n failers.add(handler);\n return () => {\n failers.delete(handler);\n };\n },\n async close(reason?: string) {\n ended = true;\n await Promise.all(names.map((name) => sources[name].close(reason)));\n },\n info: {},\n control: () => {},\n };\n}\n","// ── an HTTP request as a port ───────────────────────────────────────────────\n//\n// The response body is the channel's frames, `close()` aborts the request, and the ending is what the response\n// said about itself. A request that never got a response is a rejection, the way a spawner that cannot connect\n// is: a non-2xx, a refused connection, a promise that never settles.\n\nimport type { Channel } from \"./channel.js\";\nimport { held } from \"./held.js\";\n\n/** What a response said when its body was over. */\nexport interface HttpEnd {\n /** The status the far end answered with. The frames that came before it are its body. */\n readonly status: number;\n}\n\nexport interface FetchPortOptions {\n /** Passed to `fetch`: method, headers, body. The request's own signal is not read; `close()` aborts. */\n init?: RequestInit;\n /** How long to wait for the response head. No bound by default: a stream may be long. */\n connectMs?: number;\n}\n\n/**\n * A URL as a channel of the body it answers with.\n *\n * Frames are the chunks of the body as they arrive, so a caller reads a stream rather than waiting for a whole\n * response. `close()` aborts the request, and the far side hears it.\n */\nexport async function fetchPort(url: string, options: FetchPortOptions = {}): Promise<Channel<Uint8Array, HttpEnd>> {\n const controller = new AbortController();\n const response = await answered(url, { ...options.init, signal: controller.signal }, options.connectMs);\n\n const readers = new Set<(frame: Uint8Array) => void>();\n const closers = new Set<(reason: HttpEnd) => void>();\n const failers = new Set<(error: unknown) => void>();\n const ending: HttpEnd = { status: response.status };\n let ended = false;\n\n const end = (): void => {\n if (ended) return;\n ended = true;\n for (const closer of [...closers]) closer(ending);\n };\n\n const frames: Channel<Uint8Array, HttpEnd> = {\n send: () => Promise.reject(new Error(\"a response body has no input; send on the request instead\")),\n onFrame(handler) {\n readers.add(handler);\n return () => {\n readers.delete(handler);\n };\n },\n onClose(handler) {\n if (ended) {\n handler(ending);\n return () => {};\n }\n closers.add(handler);\n return () => {\n closers.delete(handler);\n };\n },\n onError(handler) {\n failers.add(handler);\n return () => {\n failers.delete(handler);\n };\n },\n async close() {\n controller.abort();\n await reader?.cancel().catch(() => undefined);\n },\n info: { url },\n control: () => {},\n };\n\n const reader = response.body?.getReader();\n // Wrapped before the first read: a body can deliver a chunk in the same turn the port is handed over.\n const port = held(frames);\n\n const pump = async (): Promise<void> => {\n if (reader === undefined) {\n end();\n return;\n }\n try {\n for (;;) {\n const chunk = await reader.read();\n if (chunk.done) {\n end();\n return;\n }\n for (const target of [...readers]) target(chunk.value);\n }\n } catch (error) {\n for (const failer of [...failers]) failer(error);\n end();\n }\n };\n void pump();\n\n return port;\n}\n\n/** The response head, or a rejection that says why there is none. */\nasync function answered(url: string, init: RequestInit, connectMs: number | undefined): Promise<Response> {\n const attempt = fetch(url, init).catch((error: unknown) => Promise.reject(named(url, error)));\n const response =\n connectMs === undefined\n ? await attempt\n : await Promise.race([\n attempt,\n new Promise<never>((_resolve, reject) =>\n setTimeout(() => reject(new Error(`fetchPort: ${url} answered nothing within ${connectMs}ms`)), connectMs),\n ),\n ]);\n\n if (!response.ok) {\n const body = await response.text().catch(() => \"\");\n throw new Error(`fetchPort: ${url} answered ${response.status}${body === \"\" ? \"\" : `: ${body.slice(0, 200)}`}`);\n }\n return response;\n}\n\nfunction named(url: string, error: unknown): Error {\n return new Error(`fetchPort: ${url}: ${error instanceof Error ? error.message : String(error)}`);\n}\n","// ── the loop, at the port level ─────────────────────────────────────────────\n//\n// A listener yields a pair of ports per accept, and one handler runs on that pair. The same loop the connection\n// reactor runs, one level down: there the unit is a connection that speaks a protocol, here it is a pair of\n// channels and nothing above them.\n\nimport type { Channel } from \"./channel.js\";\n\n/** One accepted exchange: what the far side sends, and what we send back. */\nexport interface PortPair<In, Out, Meta = unknown, C = void> {\n readonly inbound: Channel<In, C>;\n readonly outbound: Channel<Out, C>;\n /** Whatever the listener resolved about the caller: admission, a path, a token's subject. */\n readonly meta: Meta;\n}\n\n/** Where pairs arrive. The listener owns the medium and its admission. */\nexport interface PortListener<In, Out, Meta = unknown, C = void> {\n accept(): Promise<PortPair<In, Out, Meta, C>>;\n close(): Promise<void>;\n}\n\nexport interface PortReactorOptions<In, Out, Meta, C = void> {\n /** Called once a pair has been accepted and its handler has been given it. */\n onSession?(pair: PortPair<In, Out, Meta, C>): void;\n /** Called when a session is over, with whatever the handler threw. */\n onClosed?(pair: PortPair<In, Out, Meta, C>, error: unknown): void;\n /** A handler that throws is reported here, and the loop goes on. */\n onError?(error: unknown): void;\n}\n\nexport interface PortReactor {\n /** How many handlers are running now. */\n readonly live: number;\n /** Close the listener, stop accepting, and wait for the handlers to finish. */\n stop(): Promise<void>;\n}\n\n/**\n * One handler per accepted pair, in arrival order.\n *\n * The loop ends when the listener's `accept()` rejects, which a closed listener does. A handler that throws is\n * reported and its session ends; the loop carries on with the next pair.\n */\nexport function portReactor<In, Out, Meta, C = void>(\n listener: PortListener<In, Out, Meta, C>,\n handle: (pair: PortPair<In, Out, Meta, C>) => void | Promise<void>,\n options: PortReactorOptions<In, Out, Meta, C> = {},\n): PortReactor {\n const running = new Set<Promise<void>>();\n let stopped = false;\n\n const serve = (pair: PortPair<In, Out, Meta, C>): void => {\n const session = (async () => {\n options.onSession?.(pair);\n try {\n await handle(pair);\n } catch (error) {\n options.onError?.(error);\n options.onClosed?.(pair, error);\n return;\n }\n options.onClosed?.(pair, undefined);\n })();\n running.add(session);\n void session.finally(() => running.delete(session));\n };\n\n const loop = (async () => {\n for (;;) {\n let pair: PortPair<In, Out, Meta, C>;\n try {\n pair = await listener.accept();\n } catch {\n return;\n }\n if (stopped) return;\n serve(pair);\n }\n })();\n\n return {\n get live() {\n return running.size;\n },\n async stop() {\n stopped = true;\n await listener.close();\n await loop;\n await Promise.all([...running]);\n },\n };\n}\n","// ── a pair of channels as one ───────────────────────────────────────────────\n//\n// A medium that has an inbound side and an outbound side — a request and its answer, a socket's reader and its\n// writer — arrives as two channels. A channel has one frame type, so this is only for a pair whose two sides\n// carry the same one: frames arrive from the inbound side, `send` writes to the outbound side, `close()` ends it.\n//\n// The ending is the inbound side's: when the far side has sent everything, this channel closes with whatever that\n// side said. Closing it here closes the outbound side, which is how an answer is finished.\n\nimport type { Channel } from \"./channel.js\";\n\n/** A pair whose two sides carry the same frame type, read as one channel. */\nexport function duplexChannel<F, C = void>(pair: {\n readonly inbound: Channel<F, C>;\n readonly outbound: Channel<F, C>;\n}): Channel<F, C> {\n return {\n send: (frame) => pair.outbound.send(frame),\n onFrame: (handler) => pair.inbound.onFrame(handler),\n onClose: (handler) => pair.inbound.onClose(handler),\n onError: (handler) => pair.inbound.onError(handler),\n close: async (reason) => {\n await pair.outbound.close(reason);\n },\n info: { ...pair.inbound.info, ...pair.outbound.info },\n control: (command) => pair.inbound.control(command),\n };\n}\n"],"mappings":"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"}
|
|
@@ -42,6 +42,15 @@ interface Channel<F, C = string> {
|
|
|
42
42
|
* because they are the ones who asked.
|
|
43
43
|
*/
|
|
44
44
|
close(reason?: string): Promise<void>;
|
|
45
|
+
/** What the medium knows about itself: a process its pid, a socket its peer. Empty when it knows nothing. */
|
|
46
|
+
readonly info: Record<string, unknown>;
|
|
47
|
+
/**
|
|
48
|
+
* Ask the medium to act on what is behind the channel — signal a process, resize a terminal.
|
|
49
|
+
*
|
|
50
|
+
* A medium with nothing behind it does nothing. The command is the medium's own vocabulary; the channel
|
|
51
|
+
* only carries it.
|
|
52
|
+
*/
|
|
53
|
+
control(command: Record<string, unknown>): void;
|
|
45
54
|
}
|
|
46
55
|
/** What is thrown when a frame is sent on a channel that is over. */
|
|
47
56
|
declare class ChannelClosed extends Error {
|
|
@@ -72,29 +81,31 @@ interface PortPublic<C> {
|
|
|
72
81
|
alive: boolean;
|
|
73
82
|
/** How it ended, or null while it has not. */
|
|
74
83
|
end: C | null;
|
|
84
|
+
/** What the medium knows about itself — a process its pid — as the channel reported it. */
|
|
85
|
+
info: Record<string, unknown>;
|
|
75
86
|
}
|
|
76
|
-
|
|
87
|
+
/** What a medium's actor is handed: a name, and a way to open the channel it will own. */
|
|
88
|
+
interface PortActorSpec<Args, F, C = string> {
|
|
77
89
|
name: string;
|
|
78
90
|
/** Opens the channel this actor owns. Called once, when the actor starts. */
|
|
79
91
|
open(args: Args): Promise<Channel<F, C>>;
|
|
80
|
-
/** How a signal reaches the medium. A process sends it; a connection with no process behind it has none. */
|
|
81
|
-
signal?(channel: Channel<F, C>, signal: string): void;
|
|
82
92
|
}
|
|
83
|
-
type PortState<C> = {
|
|
93
|
+
type PortState<F, C> = {
|
|
84
94
|
public: PortPublic<C>;
|
|
85
95
|
private: {
|
|
86
|
-
channel: Channel<
|
|
96
|
+
channel: Channel<F, C>;
|
|
87
97
|
args: unknown;
|
|
88
98
|
};
|
|
89
99
|
};
|
|
90
100
|
/**
|
|
91
101
|
* An actor over one channel.
|
|
92
102
|
*
|
|
93
|
-
* In `SEND {data}` writes to it and `SIGNAL {signal}` asks the medium to act on what is behind it
|
|
94
|
-
* `STOP` ends the actor, which closes the channel. Out `DATA {data}`
|
|
95
|
-
* left in `state.end
|
|
103
|
+
* In `SEND {data}` writes to it and `SIGNAL {signal}` asks the medium to act on what is behind it, through
|
|
104
|
+
* the channel's own `control`; posipaki's `STOP` ends the actor, which closes the channel. Out `DATA {data}`
|
|
105
|
+
* for every frame. The channel's ending is left in `state.end`, what the medium knows about itself in
|
|
106
|
+
* `state.info`, and then the actor stops.
|
|
96
107
|
*/
|
|
97
|
-
declare function portActor<Args, F, C>(spec: PortActorSpec<Args, F, C>): ActorDefinition<Args, PortState<C>, PortIn<F>, PortOut<F>, {}>;
|
|
108
|
+
declare function portActor<Args, F, C = string>(spec: PortActorSpec<Args, F, C>): ActorDefinition<Args, PortState<F, C>, PortIn<F>, PortOut<F>, {}>;
|
|
98
109
|
//#endregion
|
|
99
110
|
//#region src/port/reactor.d.ts
|
|
100
111
|
/** One accepted exchange: what the far side sends, and what we send back. */
|
|
@@ -132,4 +143,4 @@ interface PortReactor {
|
|
|
132
143
|
declare function portReactor<In, Out, Meta, C = void>(listener: PortListener<In, Out, Meta, C>, handle: (pair: PortPair<In, Out, Meta, C>) => void | Promise<void>, options?: PortReactorOptions<In, Out, Meta, C>): PortReactor;
|
|
133
144
|
//#endregion
|
|
134
145
|
export { portReactor as a, PortIn as c, SendMessage as d, SignalMessage as f, Part as g, ChannelClosed as h, PortReactorOptions as i, PortOut as l, Channel as m, PortPair as n, DataMessage as o, portActor as p, PortReactor as r, PortActorSpec as s, PortListener as t, PortPublic as u };
|
|
135
|
-
//# sourceMappingURL=reactor-
|
|
146
|
+
//# sourceMappingURL=reactor-4MaIKVnw.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reactor-4MaIKVnw.d.ts","names":[],"sources":["../src/port/channel.ts","../src/port/actor.ts","../src/port/reactor.ts"],"mappings":";;;;;;;;;;UAoBiB;;EAEf;;EAEA,OAAO;;;;;;;;;UAUQ,QAAQ,GAAG;;;;;;;EAO1B,KAAK,OAAO,IAAI;;EAEhB,QAAQ,UAAU,OAAO;;EAEzB,QAAQ,UAAU,QAAQ;;EAE1B,QAAQ,UAAU;;;;;;;EAOlB,MAAM,kBAAkB;;WAEf,MAAM;;;;;;;EAOf,QAAQ,SAAS;;;cAIN,sBAAsB;EACjC,YAAY;;;;;UCrDG,YAAY,WAAW;EACtC;EACA,MAAM;;;UAIS,sBAAsB;EACrC;EACA;;;UAIe,YAAY,WAAW;EACtC;EACA,MAAM;;KAGI,OAAO,KAAK,YAAY,KAAK;KAC7B,QAAQ,KAAK,YAAY;;UAGpB,WAAW;;EAE1B;;EAEA,KAAK;;EAEL,MAAM;;;UAIS,cAAc,MAAM,GAAG;EACtC;;EAEA,KAAK,MAAM,OAAO,QAAQ,QAAQ,GAAG;;KAGlC,UAAU,GAAG;EAChB,QAAQ,WAAW;EACnB;IAAW,SAAS,QAAQ,GAAG;IAAI;;;;;;;;;;;iBAWrB,UAAU,MAAM,GAAG,YACjC,MAAM,cAAc,MAAM,GAAG,KAC5B,gBAAgB,MAAM,UAAU,GAAG,IAAI,OAAO,IAAI,QAAQ;;;;UC1D5C,SAAS,IAAI,KAAK,gBAAgB;WACxC,SAAS,QAAQ,IAAI;WACrB,UAAU,QAAQ,KAAK;;WAEvB,MAAM;;;UAIA,aAAa,IAAI,KAAK,gBAAgB;EACrD,UAAU,QAAQ,SAAS,IAAI,KAAK,MAAM;EAC1C,SAAS;;UAGM,mBAAmB,IAAI,KAAK,MAAM;;EAEjD,WAAW,MAAM,SAAS,IAAI,KAAK,MAAM;;EAEzC,UAAU,MAAM,SAAS,IAAI,KAAK,MAAM,IAAI;;EAE5C,SAAS;;UAGM;;WAEN;;EAET,QAAQ;;;;;;;;iBASM,YAAY,IAAI,KAAK,MAAM,UACzC,UAAU,aAAa,IAAI,KAAK,MAAM,IACtC,SAAS,MAAM,SAAS,IAAI,KAAK,MAAM,cAAc,eACrD,UAAS,mBAAmB,IAAI,KAAK,MAAM,KAC1C"}
|
|
@@ -99,9 +99,18 @@ function announcedOctets(frame) {
|
|
|
99
99
|
function json1Channel(transport) {
|
|
100
100
|
const frameReaders = /* @__PURE__ */ new Set();
|
|
101
101
|
const segmentReaders = /* @__PURE__ */ new Set();
|
|
102
|
+
/** What arrived with nobody reading it. A connection is live from the moment it is made, and a
|
|
103
|
+
* first stage that relays it has nowhere to hand a frame until the payload it relays to exists. */
|
|
104
|
+
const heldFrames = [];
|
|
105
|
+
const heldSegments = [];
|
|
102
106
|
/** The head whose octets are on their way: set between the line and the run it announced. */
|
|
103
107
|
let expecting = null;
|
|
104
108
|
const deliver = (frame) => {
|
|
109
|
+
if (frameReaders.size === 0) {
|
|
110
|
+
heldFrames.push(frame);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
heldFrames.length = 0;
|
|
105
114
|
for (const reader of [...frameReaders]) reader(frame);
|
|
106
115
|
};
|
|
107
116
|
transport.onMessage((str) => {
|
|
@@ -121,6 +130,11 @@ function json1Channel(transport) {
|
|
|
121
130
|
deliver(badFrame(`${bytes.length} octets arrived that no frame announced`, ""));
|
|
122
131
|
return;
|
|
123
132
|
}
|
|
133
|
+
if (segmentReaders.size === 0) {
|
|
134
|
+
heldSegments.push([frame, bytes]);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
heldSegments.length = 0;
|
|
124
138
|
for (const reader of [...segmentReaders]) reader(frame, bytes);
|
|
125
139
|
});
|
|
126
140
|
return {
|
|
@@ -129,6 +143,7 @@ function json1Channel(transport) {
|
|
|
129
143
|
},
|
|
130
144
|
onMessage(handler) {
|
|
131
145
|
frameReaders.add(handler);
|
|
146
|
+
for (const frame of heldFrames) handler(frame);
|
|
132
147
|
return () => {
|
|
133
148
|
frameReaders.delete(handler);
|
|
134
149
|
};
|
|
@@ -139,6 +154,7 @@ function json1Channel(transport) {
|
|
|
139
154
|
},
|
|
140
155
|
onSegment(handler) {
|
|
141
156
|
segmentReaders.add(handler);
|
|
157
|
+
for (const [frame, bytes] of heldSegments) handler(frame, bytes);
|
|
142
158
|
return () => {
|
|
143
159
|
segmentReaders.delete(handler);
|
|
144
160
|
};
|
|
@@ -193,6 +209,9 @@ function channelThrough(base, onFrame) {
|
|
|
193
209
|
if (base.onSegment) wrapped.onSegment = (handler) => base.onSegment?.(handler) ?? (() => {});
|
|
194
210
|
return wrapped;
|
|
195
211
|
}
|
|
212
|
+
function isProto(msg) {
|
|
213
|
+
return "$proto" in msg;
|
|
214
|
+
}
|
|
196
215
|
const ERROR_KEY = "$error";
|
|
197
216
|
/** The frame that says the wire is over: the reason, and the connection ends with it. */
|
|
198
217
|
function errorFrame$1(reason) {
|
|
@@ -1595,7 +1614,7 @@ async function runGateway(argv = process.argv.slice(2)) {
|
|
|
1595
1614
|
};
|
|
1596
1615
|
let offered = false;
|
|
1597
1616
|
fromClient.onMessage((frame) => {
|
|
1598
|
-
if (!offered) {
|
|
1617
|
+
if (!offered && !isProto(frame)) {
|
|
1599
1618
|
offered = true;
|
|
1600
1619
|
offerOutput();
|
|
1601
1620
|
}
|