@earendil-works/pi-server 0.84.4 → 0.85.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +66 -37
  3. package/dist/connection.d.ts +9 -5
  4. package/dist/connection.d.ts.map +1 -1
  5. package/dist/connection.js.map +1 -1
  6. package/dist/errors.d.ts +16 -18
  7. package/dist/errors.d.ts.map +1 -1
  8. package/dist/errors.js +23 -27
  9. package/dist/errors.js.map +1 -1
  10. package/dist/index.d.ts +0 -1
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +0 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/listener.d.ts +1 -3
  15. package/dist/listener.d.ts.map +1 -1
  16. package/dist/listener.js.map +1 -1
  17. package/dist/server.d.ts +16 -6
  18. package/dist/server.d.ts.map +1 -1
  19. package/dist/server.js +268 -86
  20. package/dist/server.js.map +1 -1
  21. package/dist/session-router.d.ts +40 -0
  22. package/dist/session-router.d.ts.map +1 -0
  23. package/dist/session-router.js +264 -0
  24. package/dist/session-router.js.map +1 -0
  25. package/dist/testing/client.d.ts +6 -2
  26. package/dist/testing/client.d.ts.map +1 -1
  27. package/dist/testing/client.js +23 -3
  28. package/dist/testing/client.js.map +1 -1
  29. package/dist/testing/host.d.ts +57 -0
  30. package/dist/testing/host.d.ts.map +1 -0
  31. package/dist/testing/host.js +189 -0
  32. package/dist/testing/host.js.map +1 -0
  33. package/dist/testing/index.d.ts +1 -1
  34. package/dist/testing/index.d.ts.map +1 -1
  35. package/dist/testing/index.js +1 -1
  36. package/dist/testing/index.js.map +1 -1
  37. package/dist/testing/server.d.ts +8 -7
  38. package/dist/testing/server.d.ts.map +1 -1
  39. package/dist/testing/server.js +7 -7
  40. package/dist/testing/server.js.map +1 -1
  41. package/dist/transports/unix/address.d.ts +3 -0
  42. package/dist/transports/unix/address.d.ts.map +1 -0
  43. package/dist/transports/unix/address.js +9 -0
  44. package/dist/transports/unix/address.js.map +1 -0
  45. package/dist/transports/unix/index.d.ts +1 -0
  46. package/dist/transports/unix/index.d.ts.map +1 -1
  47. package/dist/transports/unix/index.js +1 -0
  48. package/dist/transports/unix/index.js.map +1 -1
  49. package/dist/transports/unix/listener.d.ts +2 -3
  50. package/dist/transports/unix/listener.d.ts.map +1 -1
  51. package/dist/transports/unix/listener.js +13 -24
  52. package/dist/transports/unix/listener.js.map +1 -1
  53. package/dist/transports/unix/preset.d.ts +5 -4
  54. package/dist/transports/unix/preset.d.ts.map +1 -1
  55. package/dist/transports/unix/preset.js +5 -4
  56. package/dist/transports/unix/preset.js.map +1 -1
  57. package/dist/transports/unix/types.d.ts +2 -2
  58. package/dist/transports/unix/types.d.ts.map +1 -1
  59. package/dist/transports/unix/types.js.map +1 -1
  60. package/dist/types.d.ts +40 -47
  61. package/dist/types.d.ts.map +1 -1
  62. package/dist/types.js.map +1 -1
  63. package/package.json +4 -3
  64. package/dist/protocol.d.ts +0 -22
  65. package/dist/protocol.d.ts.map +0 -1
  66. package/dist/protocol.js +0 -266
  67. package/dist/protocol.js.map +0 -1
  68. package/dist/sessions.d.ts +0 -2174
  69. package/dist/sessions.d.ts.map +0 -1
  70. package/dist/sessions.js +0 -305
  71. package/dist/sessions.js.map +0 -1
  72. package/dist/snapshots.d.ts +0 -24
  73. package/dist/snapshots.d.ts.map +0 -1
  74. package/dist/snapshots.js +0 -39
  75. package/dist/snapshots.js.map +0 -1
  76. package/dist/testing/service.d.ts +0 -59
  77. package/dist/testing/service.d.ts.map +0 -1
  78. package/dist/testing/service.js +0 -240
  79. package/dist/testing/service.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"listener.d.ts","sourceRoot":"","sources":["../../../src/transports/unix/listener.ts"],"names":[],"mappings":"AAGA,OAAO,EAA+C,KAAK,MAAM,EAAE,MAAM,UAAU,CAAC;AAGpF,OAAO,KAAK,EAAE,cAAc,EAA0B,MAAM,qBAAqB,CAAC;AAClF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAStD,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,SAAqB,GAAG,IAAI,CAK3F;AAoLD,gEAAgE;AAChE,qBAAa,kBAAmB,YAAW,cAAc;IACxD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAS;IAChD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAoC;IACrD,OAAO,CAAC,YAAY,CAAC,CAAgB;IACrC,OAAO,CAAC,YAAY,CAAC,CAAa;IAElC,YAAY,MAAM,EAAE,MAAM,EAAE,sBAAsB,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,EAIlF;IAED,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBrC;IAED,KAAK,CAAC,UAAU,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA8B5C;IAED,UAAU,IAAI,IAAI,CAMjB;IAED,OAAO,CAAC,KAAK;CAsBb;AAgGD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,gBAAgB,CAEjF","sourcesContent":["import { createHash, randomUUID } from \"node:crypto\";\nimport type { Stats } from \"node:fs\";\nimport { chmod, link, lstat, mkdir, rename, unlink } from \"node:fs/promises\";\nimport { createConnection, createServer, type Server, type Socket } from \"node:net\";\nimport { dirname, join } from \"node:path\";\nimport { DEFAULT_MAX_FRAME_LENGTH } from \"@earendil-works/pi-protocol\";\nimport type { ByteConnection, ByteConnectionAcceptor } from \"../../connection.ts\";\nimport type { PiServerListener } from \"../../listener.ts\";\nimport type { UnixListenerOptions } from \"./types.ts\";\n\nconst DEFAULT_SOCKET_MODE = 0o600;\nconst DEFAULT_GRACEFUL_CLOSE_TIMEOUT_MS = 5_000;\nconst MAX_UINT32 = 0xffff_ffff;\nconst MAX_TIMER_DELAY_MS = 2_147_483_647;\nconst SOCKET_PROBE_TIMEOUT_MS = 1_000;\nconst MAX_UNIX_SOCKET_PATH_BYTES = process.platform === \"linux\" ? 107 : 103;\n\nexport function validateUnixSocketPath(path: string, description = \"Unix socket path\"): void {\n\tif (!path) throw new TypeError(`${description} must not be empty`);\n\tif (Buffer.byteLength(path) > MAX_UNIX_SOCKET_PATH_BYTES) {\n\t\tthrow new TypeError(`${description} is too long; maximum is ${MAX_UNIX_SOCKET_PATH_BYTES} UTF-8 bytes`);\n\t}\n}\n\ninterface ResolvedUnixListenerOptions {\n\tpath: string;\n\tmode: number;\n\tgracefulCloseTimeoutMs: number;\n\tmaxPendingBytes: number;\n\tonError?: (error: Error) => void;\n}\n\ninterface FileIdentity {\n\tdev: number;\n\tino: number;\n}\nclass UnixListener implements PiServerListener {\n\tprivate readonly options: ResolvedUnixListenerOptions;\n\tprivate readonly path: string;\n\tprivate readonly mode: number;\n\tprivate readonly connections = new Set<UnixByteConnection>();\n\tprivate server?: Server;\n\tprivate socketIdentity?: FileIdentity;\n\tprivate ownedBindPath?: string;\n\tprivate boundPath?: string;\n\tprivate closing = false;\n\tprivate closePromise?: Promise<void>;\n\tprivate accept?: ByteConnectionAcceptor;\n\n\tconstructor(options: UnixListenerOptions) {\n\t\tthis.options = resolveUnixListenerOptions(options);\n\t\tthis.path = this.options.path;\n\t\tthis.mode = this.options.mode;\n\t}\n\n\tget address(): string | undefined {\n\t\treturn this.boundPath;\n\t}\n\n\tasync start(accept: ByteConnectionAcceptor): Promise<void> {\n\t\tif (this.server) throw new Error(\"Unix listener is already started\");\n\t\tif (this.closing) throw new Error(\"Unix listener is closing or closed\");\n\t\tthis.accept = accept;\n\n\t\tconst ownedBindPath = getOwnedBindPath(this.path);\n\t\tvalidateUnixSocketPath(ownedBindPath, \"PiServer private Unix bind path\");\n\t\tawait mkdir(dirname(this.path), { recursive: true, mode: 0o700 });\n\t\tawait removeStaleSocket(this.path);\n\t\tawait removeStaleSocket(ownedBindPath);\n\t\tthis.ownedBindPath = ownedBindPath;\n\t\tconst server = createServer((socket) => this.acceptSocket(socket));\n\t\tserver.on(\"error\", (error) => this.reportError(error));\n\t\tthis.server = server;\n\t\ttry {\n\t\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\t\tconst onError = (error: Error): void => {\n\t\t\t\t\tserver.off(\"listening\", onListening);\n\t\t\t\t\treject(error);\n\t\t\t\t};\n\t\t\t\tconst onListening = (): void => {\n\t\t\t\t\tserver.off(\"error\", onError);\n\t\t\t\t\tresolve();\n\t\t\t\t};\n\t\t\t\tserver.once(\"error\", onError);\n\t\t\t\tserver.once(\"listening\", onListening);\n\t\t\t\tserver.listen(ownedBindPath);\n\t\t\t});\n\t\t\tconst stats = await lstat(ownedBindPath);\n\t\t\tif (!stats.isSocket()) throw new Error(`Unix listener path is not a socket after binding: ${ownedBindPath}`);\n\t\t\tthis.socketIdentity = { dev: stats.dev, ino: stats.ino };\n\t\t\tawait link(ownedBindPath, this.path);\n\t\t\tawait setSocketMode(this.path, this.mode);\n\t\t\tthis.boundPath = this.path;\n\t\t} catch (error) {\n\t\t\tawait this.closeServerAndCleanup(server);\n\t\t\tthis.server = undefined;\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tasync close(): Promise<void> {\n\t\tif (this.closePromise) return this.closePromise;\n\t\tthis.closing = true;\n\t\tthis.closePromise = this.closeInternal();\n\t\treturn this.closePromise;\n\t}\n\n\tprivate acceptSocket(socket: Socket): void {\n\t\tif (this.closing) {\n\t\t\tsocket.destroy();\n\t\t\treturn;\n\t\t}\n\t\tconst connection = new UnixByteConnection(\n\t\t\tsocket,\n\t\t\tthis.options.gracefulCloseTimeoutMs,\n\t\t\tthis.options.maxPendingBytes,\n\t\t);\n\t\tthis.connections.add(connection);\n\t\tconst accept = this.accept;\n\t\tif (!accept) {\n\t\t\tsocket.destroy();\n\t\t\treturn;\n\t\t}\n\t\tconst handler = accept(connection);\n\t\tsocket.on(\"data\", (chunk) => {\n\t\t\thandler.onData(new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength));\n\t\t});\n\t\tsocket.on(\"error\", (error) => {\n\t\t\thandler.onError(error);\n\t\t\tsocket.destroy();\n\t\t});\n\t\tsocket.once(\"close\", () => {\n\t\t\tconnection.markClosed();\n\t\t\tthis.connections.delete(connection);\n\t\t\thandler.onClose();\n\t\t});\n\t}\n\n\tprivate async closeInternal(): Promise<void> {\n\t\tthis.boundPath = undefined;\n\t\tconst serverClosed = this.server ? this.closeServerAndCleanup(this.server) : this.cleanupOwnedSocket();\n\t\tawait Promise.all([...this.connections].map((connection) => connection.close()));\n\t\tawait serverClosed;\n\t\tif (this.ownedBindPath) await removePath(this.ownedBindPath);\n\t\tthis.ownedBindPath = undefined;\n\t\tthis.connections.clear();\n\t\tthis.server = undefined;\n\t}\n\n\tprivate async closeServerAndCleanup(server: Server): Promise<void> {\n\t\ttry {\n\t\t\tawait closeNetServer(server, (error) => this.reportError(error));\n\t\t} finally {\n\t\t\tawait this.cleanupOwnedSocket();\n\t\t\tif (this.ownedBindPath) await removePath(this.ownedBindPath);\n\t\t\tthis.ownedBindPath = undefined;\n\t\t}\n\t}\n\n\tprivate async cleanupOwnedSocket(): Promise<void> {\n\t\tconst identity = this.socketIdentity;\n\t\tthis.socketIdentity = undefined;\n\t\tif (!identity) return;\n\t\tlet current: Stats;\n\t\ttry {\n\t\t\tcurrent = await lstat(this.path);\n\t\t} catch (error) {\n\t\t\tif (isErrorCode(error, \"ENOENT\")) return;\n\t\t\tthrow error;\n\t\t}\n\t\tif (!current.isSocket() || current.dev !== identity.dev || current.ino !== identity.ino) return;\n\n\t\tconst preserved = join(dirname(this.path), `.c-${randomUUID().slice(0, 6)}`);\n\t\ttry {\n\t\t\tawait rename(this.path, preserved);\n\t\t} catch (error) {\n\t\t\tif (isErrorCode(error, \"ENOENT\")) return;\n\t\t\tthrow error;\n\t\t}\n\t\tconst moved = await lstat(preserved);\n\t\tif (moved.isSocket() && moved.dev === identity.dev && moved.ino === identity.ino) {\n\t\t\tawait removePath(preserved);\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tawait lstat(this.path);\n\t\t} catch (error) {\n\t\t\tif (isErrorCode(error, \"ENOENT\")) await rename(preserved, this.path);\n\t\t\telse throw error;\n\t\t}\n\t\tthrow new Error(`Unix listener path changed during cleanup; preserved replacement at ${preserved}`);\n\t}\n\n\tprivate reportError(error: unknown): void {\n\t\ttry {\n\t\t\tthis.options.onError?.(error instanceof Error ? error : new Error(String(error)));\n\t\t} catch {\n\t\t\t// Error observers cannot affect listener state.\n\t\t}\n\t}\n}\n\n/** @internal Exported only for transport-level verification. */\nexport class UnixByteConnection implements ByteConnection {\n\tprivate readonly socket: Socket;\n\tprivate readonly gracefulCloseTimeoutMs: number;\n\tprivate readonly maxPendingBytes: number;\n\tprivate pendingBytes = 0;\n\tprivate closedValue = false;\n\tprivate closing = false;\n\tprivate writeTail: Promise<void> = Promise.resolve();\n\tprivate closePromise?: Promise<void>;\n\tprivate resolveClose?: () => void;\n\n\tconstructor(socket: Socket, gracefulCloseTimeoutMs: number, maxPendingBytes: number) {\n\t\tthis.socket = socket;\n\t\tthis.gracefulCloseTimeoutMs = gracefulCloseTimeoutMs;\n\t\tthis.maxPendingBytes = maxPendingBytes;\n\t}\n\n\tget closed(): boolean {\n\t\treturn this.closedValue;\n\t}\n\n\tsend(chunk: Uint8Array): Promise<void> {\n\t\tif (!(chunk instanceof Uint8Array)) {\n\t\t\treturn Promise.reject(new TypeError(\"Unix connection chunks must be Uint8Array\"));\n\t\t}\n\t\tif (this.closedValue || this.closing) return Promise.reject(new Error(\"Unix connection is closed\"));\n\t\tif (this.pendingBytes + chunk.byteLength > this.maxPendingBytes) {\n\t\t\treturn Promise.reject(new Error(\"Unix connection exceeded its pending byte limit\"));\n\t\t}\n\t\tthis.pendingBytes += chunk.byteLength;\n\t\tconst bytes = chunk.slice();\n\t\tconst write = this.writeTail.then(() => this.write(bytes));\n\t\tconst tracked = write.finally(() => {\n\t\t\tthis.pendingBytes -= bytes.byteLength;\n\t\t});\n\t\tthis.writeTail = tracked.catch(() => {});\n\t\treturn tracked;\n\t}\n\n\tclose(finalChunk?: Uint8Array): Promise<void> {\n\t\tif (this.closedValue || this.socket.destroyed) {\n\t\t\tthis.markClosed();\n\t\t\treturn Promise.resolve();\n\t\t}\n\t\tif (this.closePromise) return this.closePromise;\n\t\tthis.closing = true;\n\t\tconst finalBytes = finalChunk?.slice();\n\t\tthis.closePromise = new Promise<void>((resolve) => {\n\t\t\tthis.resolveClose = resolve;\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tif (!this.socket.destroyed) this.socket.destroy();\n\t\t\t\tthis.markClosed();\n\t\t\t}, this.gracefulCloseTimeoutMs);\n\t\t\ttimer.unref();\n\t\t\tthis.socket.once(\"close\", () => clearTimeout(timer));\n\t\t\tvoid this.writeTail.then(() => {\n\t\t\t\tif (this.socket.destroyed) {\n\t\t\t\t\tthis.markClosed();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tif (finalBytes) this.socket.end(finalBytes);\n\t\t\t\t\telse this.socket.end();\n\t\t\t\t} catch {\n\t\t\t\t\tthis.socket.destroy();\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t\treturn this.closePromise;\n\t}\n\n\tmarkClosed(): void {\n\t\tif (this.closedValue) return;\n\t\tthis.closedValue = true;\n\t\tthis.closing = true;\n\t\tthis.resolveClose?.();\n\t\tthis.resolveClose = undefined;\n\t}\n\n\tprivate write(chunk: Uint8Array): Promise<void> {\n\t\tif (this.closedValue || this.closing || !this.socket.writable) {\n\t\t\treturn Promise.reject(new Error(\"Unix connection is closed\"));\n\t\t}\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\tlet settled = false;\n\t\t\tconst onClose = (): void => finish(new Error(\"Unix connection closed during write\"));\n\t\t\tconst finish = (error?: Error | null): void => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tthis.socket.off(\"close\", onClose);\n\t\t\t\tif (error) reject(error);\n\t\t\t\telse resolve();\n\t\t\t};\n\t\t\tthis.socket.once(\"close\", onClose);\n\t\t\ttry {\n\t\t\t\tthis.socket.write(chunk, finish);\n\t\t\t} catch (error) {\n\t\t\t\tfinish(error instanceof Error ? error : new Error(String(error)));\n\t\t\t}\n\t\t});\n\t}\n}\n\nfunction getOwnedBindPath(path: string): string {\n\tconst suffix = createHash(\"sha256\").update(path).digest(\"hex\").slice(0, 8);\n\treturn join(dirname(path), `.p-${suffix}`);\n}\n\nasync function removeStaleSocket(path: string): Promise<void> {\n\tlet original: Stats;\n\ttry {\n\t\toriginal = await lstat(path);\n\t} catch (error) {\n\t\tif (isErrorCode(error, \"ENOENT\")) return;\n\t\tthrow error;\n\t}\n\tif (!original.isSocket()) throw new Error(`Refusing to remove non-socket Unix listener path: ${path}`);\n\tif (await isSocketLive(path)) throw new Error(`Unix listener is already running: ${path}`);\n\n\tconst preserved = join(dirname(path), `.s-${randomUUID().slice(0, 6)}`);\n\ttry {\n\t\tawait rename(path, preserved);\n\t} catch (error) {\n\t\tif (isErrorCode(error, \"ENOENT\")) return;\n\t\tthrow error;\n\t}\n\tconst current = await lstat(preserved);\n\tif (!current.isSocket() || current.dev !== original.dev || current.ino !== original.ino) {\n\t\ttry {\n\t\t\tawait lstat(path);\n\t\t} catch (error) {\n\t\t\tif (isErrorCode(error, \"ENOENT\")) await rename(preserved, path);\n\t\t\telse throw error;\n\t\t}\n\t\tthrow new Error(`Unix listener path changed while checking for a stale socket: ${path}`);\n\t}\n\tawait removePath(preserved);\n}\n\nasync function removePath(path: string): Promise<void> {\n\ttry {\n\t\tawait unlink(path);\n\t} catch (error) {\n\t\tif (!isErrorCode(error, \"ENOENT\")) throw error;\n\t}\n}\n\nfunction isSocketLive(path: string): Promise<boolean> {\n\treturn new Promise<boolean>((resolve, reject) => {\n\t\tconst socket = createConnection(path);\n\t\tlet settled = false;\n\t\tlet timer: NodeJS.Timeout | undefined;\n\t\tconst finish = (result: boolean, error?: Error): void => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tif (timer) clearTimeout(timer);\n\t\t\tsocket.removeAllListeners();\n\t\t\tsocket.destroy();\n\t\t\tif (error) reject(error);\n\t\t\telse resolve(result);\n\t\t};\n\t\tsocket.once(\"connect\", () => finish(true));\n\t\tsocket.once(\"error\", (error: NodeJS.ErrnoException) => {\n\t\t\tif ([\"ECONNREFUSED\", \"ENOENT\", \"EPIPE\", \"ECONNRESET\"].includes(error.code ?? \"\")) {\n\t\t\t\tfinish(false);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tfinish(false, error);\n\t\t});\n\t\ttimer = setTimeout(() => finish(true), SOCKET_PROBE_TIMEOUT_MS);\n\t\ttimer.unref();\n\t});\n}\n\nasync function setSocketMode(path: string, mode: number): Promise<void> {\n\tif (process.platform === \"win32\") return;\n\ttry {\n\t\tawait chmod(path, mode);\n\t} catch (error) {\n\t\tif (!isErrorCode(error, \"ENOSYS\") && !isErrorCode(error, \"ENOTSUP\")) throw error;\n\t}\n}\n\nfunction closeNetServer(server: Server, reportError: (error: Error) => void): Promise<void> {\n\tif (!server.listening) return Promise.resolve();\n\treturn new Promise<void>((resolve) => {\n\t\tserver.close((error) => {\n\t\t\tif (error) reportError(error);\n\t\t\tresolve();\n\t\t});\n\t});\n}\n\nfunction isErrorCode(error: unknown, code: string): boolean {\n\treturn error instanceof Error && \"code\" in error && error.code === code;\n}\n\nexport function createUnixListener(options: UnixListenerOptions): PiServerListener {\n\treturn new UnixListener(options);\n}\n\nfunction resolveUnixListenerOptions(options: UnixListenerOptions): ResolvedUnixListenerOptions {\n\tvalidateUnixSocketPath(options.path, \"PiServer Unix socket path\");\n\tconst mode = options.mode ?? DEFAULT_SOCKET_MODE;\n\tif (!Number.isInteger(mode) || mode < 0 || mode > 0o777) {\n\t\tthrow new TypeError(\"PiServer Unix socket mode must be an integer between 0 and 0o777\");\n\t}\n\tconst maxFrameLength = options.maxFrameLength ?? DEFAULT_MAX_FRAME_LENGTH;\n\tif (!Number.isSafeInteger(maxFrameLength) || maxFrameLength <= 0 || maxFrameLength > MAX_UINT32) {\n\t\tthrow new TypeError(`PiServer maxFrameLength must be an integer between 1 and ${MAX_UINT32}`);\n\t}\n\tconst maxPendingBytes = options.maxPendingBytes ?? maxFrameLength * 4;\n\tif (!Number.isSafeInteger(maxPendingBytes) || maxPendingBytes < maxFrameLength + 4) {\n\t\tthrow new TypeError(\"PiServer maxPendingBytes must be a safe integer at least maxFrameLength + 4\");\n\t}\n\tconst gracefulCloseTimeoutMs = options.gracefulCloseTimeoutMs ?? DEFAULT_GRACEFUL_CLOSE_TIMEOUT_MS;\n\tif (\n\t\t!Number.isSafeInteger(gracefulCloseTimeoutMs) ||\n\t\tgracefulCloseTimeoutMs <= 0 ||\n\t\tgracefulCloseTimeoutMs > MAX_TIMER_DELAY_MS\n\t) {\n\t\tthrow new TypeError(`PiServer gracefulCloseTimeoutMs must be an integer between 1 and ${MAX_TIMER_DELAY_MS}`);\n\t}\n\treturn {\n\t\tpath: options.path,\n\t\tmode,\n\t\tmaxPendingBytes,\n\t\tgracefulCloseTimeoutMs,\n\t\tonError: options.onError,\n\t};\n}\n"]}
1
+ {"version":3,"file":"listener.d.ts","sourceRoot":"","sources":["../../../src/transports/unix/listener.ts"],"names":[],"mappings":"AAGA,OAAO,EAA+C,KAAK,MAAM,EAAE,MAAM,UAAU,CAAC;AAGpF,OAAO,KAAK,EAAE,cAAc,EAA0B,MAAM,qBAAqB,CAAC;AAClF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAqLtD,gEAAgE;AAChE,qBAAa,kBAAmB,YAAW,cAAc;IACxD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAS;IAChD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAoC;IACrD,OAAO,CAAC,YAAY,CAAC,CAAgB;IACrC,OAAO,CAAC,YAAY,CAAC,CAAa;IAElC,YAAY,MAAM,EAAE,MAAM,EAAE,sBAAsB,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,EAIlF;IAED,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBrC;IAED,KAAK,CAAC,UAAU,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA8B5C;IAED,UAAU,IAAI,IAAI,CAMjB;IAED,OAAO,CAAC,KAAK;CAsBb;AAgGD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,cAAc,CAE/E","sourcesContent":["import { createHash, randomUUID } from \"node:crypto\";\nimport type { Stats } from \"node:fs\";\nimport { chmod, link, lstat, mkdir, rename, unlink } from \"node:fs/promises\";\nimport { createConnection, createServer, type Server, type Socket } from \"node:net\";\nimport { dirname, join } from \"node:path\";\nimport { DEFAULT_MAX_FRAME_LENGTH } from \"@earendil-works/pi-protocol\";\nimport type { ByteConnection, ByteConnectionAcceptor } from \"../../connection.ts\";\nimport type { ServerListener } from \"../../listener.ts\";\nimport type { UnixListenerOptions } from \"./types.ts\";\n\nconst DEFAULT_SOCKET_MODE = 0o600;\nconst DEFAULT_GRACEFUL_CLOSE_TIMEOUT_MS = 5_000;\nconst MAX_UINT32 = 0xffff_ffff;\nconst MAX_TIMER_DELAY_MS = 2_147_483_647;\nconst SOCKET_PROBE_TIMEOUT_MS = 1_000;\n\ninterface ResolvedUnixListenerOptions {\n\tpath: string;\n\tmode: number;\n\tgracefulCloseTimeoutMs: number;\n\tmaxPendingBytes: number;\n\tonError?: (error: Error) => void;\n}\n\ninterface FileIdentity {\n\tdev: number;\n\tino: number;\n}\nclass UnixListener implements ServerListener {\n\tprivate readonly options: ResolvedUnixListenerOptions;\n\tprivate readonly path: string;\n\tprivate readonly mode: number;\n\tprivate readonly connections = new Set<UnixByteConnection>();\n\tprivate server?: Server;\n\tprivate socketIdentity?: FileIdentity;\n\tprivate ownedBindPath?: string;\n\tprivate closing = false;\n\tprivate closePromise?: Promise<void>;\n\tprivate accept?: ByteConnectionAcceptor;\n\n\tconstructor(options: UnixListenerOptions) {\n\t\tthis.options = resolveUnixListenerOptions(options);\n\t\tthis.path = this.options.path;\n\t\tthis.mode = this.options.mode;\n\t}\n\n\tasync start(accept: ByteConnectionAcceptor): Promise<void> {\n\t\tif (this.server) throw new Error(\"Unix listener is already started\");\n\t\tif (this.closing) throw new Error(\"Unix listener is closing or closed\");\n\t\tthis.accept = accept;\n\n\t\tconst ownedBindPath = getOwnedBindPath(this.path);\n\t\tawait mkdir(dirname(this.path), { recursive: true, mode: 0o700 });\n\t\tawait removeStaleSocket(this.path);\n\t\tawait removeStaleSocket(ownedBindPath);\n\t\tthis.ownedBindPath = ownedBindPath;\n\t\tconst server = createServer((socket) => this.acceptSocket(socket));\n\t\tserver.on(\"error\", (error) => this.reportError(error));\n\t\tthis.server = server;\n\t\ttry {\n\t\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\t\tconst onError = (error: Error): void => {\n\t\t\t\t\tserver.off(\"listening\", onListening);\n\t\t\t\t\treject(error);\n\t\t\t\t};\n\t\t\t\tconst onListening = (): void => {\n\t\t\t\t\tserver.off(\"error\", onError);\n\t\t\t\t\tresolve();\n\t\t\t\t};\n\t\t\t\tserver.once(\"error\", onError);\n\t\t\t\tserver.once(\"listening\", onListening);\n\t\t\t\tserver.listen(ownedBindPath);\n\t\t\t});\n\t\t\tconst stats = await lstat(ownedBindPath);\n\t\t\tif (!stats.isSocket()) throw new Error(`Unix listener path is not a socket after binding: ${ownedBindPath}`);\n\t\t\tthis.socketIdentity = { dev: stats.dev, ino: stats.ino };\n\t\t\tawait link(ownedBindPath, this.path);\n\t\t\tawait setSocketMode(this.path, this.mode);\n\t\t\tawait removePath(ownedBindPath);\n\t\t\tthis.ownedBindPath = undefined;\n\t\t} catch (error) {\n\t\t\tawait this.closeServerAndCleanup(server);\n\t\t\tthis.server = undefined;\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tasync close(): Promise<void> {\n\t\tif (this.closePromise) return this.closePromise;\n\t\tthis.closing = true;\n\t\tthis.closePromise = this.closeInternal();\n\t\treturn this.closePromise;\n\t}\n\n\tprivate acceptSocket(socket: Socket): void {\n\t\tif (this.closing) {\n\t\t\tsocket.destroy();\n\t\t\treturn;\n\t\t}\n\t\tconst connection = new UnixByteConnection(\n\t\t\tsocket,\n\t\t\tthis.options.gracefulCloseTimeoutMs,\n\t\t\tthis.options.maxPendingBytes,\n\t\t);\n\t\tthis.connections.add(connection);\n\t\tconst accept = this.accept;\n\t\tif (!accept) {\n\t\t\tsocket.destroy();\n\t\t\treturn;\n\t\t}\n\t\tconst handler = accept(connection);\n\t\tsocket.on(\"data\", (chunk) => {\n\t\t\thandler.onData(new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength));\n\t\t});\n\t\tsocket.on(\"error\", (error) => {\n\t\t\thandler.onError(error);\n\t\t\tsocket.destroy();\n\t\t});\n\t\tsocket.once(\"close\", () => {\n\t\t\tconnection.markClosed();\n\t\t\tthis.connections.delete(connection);\n\t\t\thandler.onClose();\n\t\t});\n\t}\n\n\tprivate async closeInternal(): Promise<void> {\n\t\tconst serverClosed = this.server ? this.closeServerAndCleanup(this.server) : this.cleanupOwnedSocket();\n\t\tawait Promise.all([...this.connections].map((connection) => connection.close()));\n\t\tawait serverClosed;\n\t\tif (this.ownedBindPath) await removePath(this.ownedBindPath);\n\t\tthis.ownedBindPath = undefined;\n\t\tthis.connections.clear();\n\t\tthis.server = undefined;\n\t}\n\n\tprivate async closeServerAndCleanup(server: Server): Promise<void> {\n\t\ttry {\n\t\t\tawait closeNetServer(server, (error) => this.reportError(error));\n\t\t} finally {\n\t\t\t// Remove an unpublished startup bind path before the public route.\n\t\t\tif (this.ownedBindPath) await removePath(this.ownedBindPath);\n\t\t\tthis.ownedBindPath = undefined;\n\t\t\tawait this.cleanupOwnedSocket();\n\t\t}\n\t}\n\n\tprivate async cleanupOwnedSocket(): Promise<void> {\n\t\tconst identity = this.socketIdentity;\n\t\tthis.socketIdentity = undefined;\n\t\tif (!identity) return;\n\t\tlet current: Stats;\n\t\ttry {\n\t\t\tcurrent = await lstat(this.path);\n\t\t} catch (error) {\n\t\t\tif (isErrorCode(error, \"ENOENT\")) return;\n\t\t\tthrow error;\n\t\t}\n\t\tif (!current.isSocket() || current.dev !== identity.dev || current.ino !== identity.ino) return;\n\n\t\tconst preserved = join(dirname(this.path), `cleanup-${randomUUID().slice(0, 6)}`);\n\t\ttry {\n\t\t\tawait rename(this.path, preserved);\n\t\t} catch (error) {\n\t\t\tif (isErrorCode(error, \"ENOENT\")) return;\n\t\t\tthrow error;\n\t\t}\n\t\tconst moved = await lstat(preserved);\n\t\tif (moved.isSocket() && moved.dev === identity.dev && moved.ino === identity.ino) {\n\t\t\tawait removePath(preserved);\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tawait lstat(this.path);\n\t\t} catch (error) {\n\t\t\tif (isErrorCode(error, \"ENOENT\")) await rename(preserved, this.path);\n\t\t\telse throw error;\n\t\t}\n\t\tthrow new Error(`Unix listener path changed during cleanup; preserved replacement at ${preserved}`);\n\t}\n\n\tprivate reportError(error: unknown): void {\n\t\ttry {\n\t\t\tthis.options.onError?.(error instanceof Error ? error : new Error(String(error)));\n\t\t} catch {\n\t\t\t// Error observers cannot affect listener state.\n\t\t}\n\t}\n}\n\n/** @internal Exported only for transport-level verification. */\nexport class UnixByteConnection implements ByteConnection {\n\tprivate readonly socket: Socket;\n\tprivate readonly gracefulCloseTimeoutMs: number;\n\tprivate readonly maxPendingBytes: number;\n\tprivate pendingBytes = 0;\n\tprivate closedValue = false;\n\tprivate closing = false;\n\tprivate writeTail: Promise<void> = Promise.resolve();\n\tprivate closePromise?: Promise<void>;\n\tprivate resolveClose?: () => void;\n\n\tconstructor(socket: Socket, gracefulCloseTimeoutMs: number, maxPendingBytes: number) {\n\t\tthis.socket = socket;\n\t\tthis.gracefulCloseTimeoutMs = gracefulCloseTimeoutMs;\n\t\tthis.maxPendingBytes = maxPendingBytes;\n\t}\n\n\tget closed(): boolean {\n\t\treturn this.closedValue;\n\t}\n\n\tsend(chunk: Uint8Array): Promise<void> {\n\t\tif (!(chunk instanceof Uint8Array)) {\n\t\t\treturn Promise.reject(new TypeError(\"Unix connection chunks must be Uint8Array\"));\n\t\t}\n\t\tif (this.closedValue || this.closing) return Promise.reject(new Error(\"Unix connection is closed\"));\n\t\tif (this.pendingBytes + chunk.byteLength > this.maxPendingBytes) {\n\t\t\treturn Promise.reject(new Error(\"Unix connection exceeded its pending byte limit\"));\n\t\t}\n\t\tthis.pendingBytes += chunk.byteLength;\n\t\tconst bytes = chunk.slice();\n\t\tconst write = this.writeTail.then(() => this.write(bytes));\n\t\tconst tracked = write.finally(() => {\n\t\t\tthis.pendingBytes -= bytes.byteLength;\n\t\t});\n\t\tthis.writeTail = tracked.catch(() => {});\n\t\treturn tracked;\n\t}\n\n\tclose(finalChunk?: Uint8Array): Promise<void> {\n\t\tif (this.closedValue || this.socket.destroyed) {\n\t\t\tthis.markClosed();\n\t\t\treturn Promise.resolve();\n\t\t}\n\t\tif (this.closePromise) return this.closePromise;\n\t\tthis.closing = true;\n\t\tconst finalBytes = finalChunk?.slice();\n\t\tthis.closePromise = new Promise<void>((resolve) => {\n\t\t\tthis.resolveClose = resolve;\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tif (!this.socket.destroyed) this.socket.destroy();\n\t\t\t\tthis.markClosed();\n\t\t\t}, this.gracefulCloseTimeoutMs);\n\t\t\ttimer.unref();\n\t\t\tthis.socket.once(\"close\", () => clearTimeout(timer));\n\t\t\tvoid this.writeTail.then(() => {\n\t\t\t\tif (this.socket.destroyed) {\n\t\t\t\t\tthis.markClosed();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tif (finalBytes) this.socket.end(finalBytes);\n\t\t\t\t\telse this.socket.end();\n\t\t\t\t} catch {\n\t\t\t\t\tthis.socket.destroy();\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t\treturn this.closePromise;\n\t}\n\n\tmarkClosed(): void {\n\t\tif (this.closedValue) return;\n\t\tthis.closedValue = true;\n\t\tthis.closing = true;\n\t\tthis.resolveClose?.();\n\t\tthis.resolveClose = undefined;\n\t}\n\n\tprivate write(chunk: Uint8Array): Promise<void> {\n\t\tif (this.closedValue || this.closing || !this.socket.writable) {\n\t\t\treturn Promise.reject(new Error(\"Unix connection is closed\"));\n\t\t}\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\tlet settled = false;\n\t\t\tconst onClose = (): void => finish(new Error(\"Unix connection closed during write\"));\n\t\t\tconst finish = (error?: Error | null): void => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tthis.socket.off(\"close\", onClose);\n\t\t\t\tif (error) reject(error);\n\t\t\t\telse resolve();\n\t\t\t};\n\t\t\tthis.socket.once(\"close\", onClose);\n\t\t\ttry {\n\t\t\t\tthis.socket.write(chunk, finish);\n\t\t\t} catch (error) {\n\t\t\t\tfinish(error instanceof Error ? error : new Error(String(error)));\n\t\t\t}\n\t\t});\n\t}\n}\n\nfunction getOwnedBindPath(path: string): string {\n\tconst suffix = createHash(\"sha256\").update(path).digest(\"hex\").slice(0, 8);\n\treturn join(dirname(path), `bind-${suffix}`);\n}\n\nasync function removeStaleSocket(path: string): Promise<void> {\n\tlet original: Stats;\n\ttry {\n\t\toriginal = await lstat(path);\n\t} catch (error) {\n\t\tif (isErrorCode(error, \"ENOENT\")) return;\n\t\tthrow error;\n\t}\n\tif (!original.isSocket()) throw new Error(`Refusing to remove non-socket Unix listener path: ${path}`);\n\tif (await isSocketLive(path)) throw new Error(`Unix listener is already running: ${path}`);\n\n\tconst preserved = join(dirname(path), `stale-${randomUUID().slice(0, 6)}`);\n\ttry {\n\t\tawait rename(path, preserved);\n\t} catch (error) {\n\t\tif (isErrorCode(error, \"ENOENT\")) return;\n\t\tthrow error;\n\t}\n\tconst current = await lstat(preserved);\n\tif (!current.isSocket() || current.dev !== original.dev || current.ino !== original.ino) {\n\t\ttry {\n\t\t\tawait lstat(path);\n\t\t} catch (error) {\n\t\t\tif (isErrorCode(error, \"ENOENT\")) await rename(preserved, path);\n\t\t\telse throw error;\n\t\t}\n\t\tthrow new Error(`Unix listener path changed while checking for a stale socket: ${path}`);\n\t}\n\tawait removePath(preserved);\n}\n\nasync function removePath(path: string): Promise<void> {\n\ttry {\n\t\tawait unlink(path);\n\t} catch (error) {\n\t\tif (!isErrorCode(error, \"ENOENT\")) throw error;\n\t}\n}\n\nfunction isSocketLive(path: string): Promise<boolean> {\n\treturn new Promise<boolean>((resolve, reject) => {\n\t\tconst socket = createConnection(path);\n\t\tlet settled = false;\n\t\tlet timer: NodeJS.Timeout | undefined;\n\t\tconst finish = (result: boolean, error?: Error): void => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tif (timer) clearTimeout(timer);\n\t\t\tsocket.removeAllListeners();\n\t\t\tsocket.destroy();\n\t\t\tif (error) reject(error);\n\t\t\telse resolve(result);\n\t\t};\n\t\tsocket.once(\"connect\", () => finish(true));\n\t\tsocket.once(\"error\", (error: NodeJS.ErrnoException) => {\n\t\t\tif ([\"ECONNREFUSED\", \"ENOENT\", \"EPIPE\", \"ECONNRESET\"].includes(error.code ?? \"\")) {\n\t\t\t\tfinish(false);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tfinish(false, error);\n\t\t});\n\t\ttimer = setTimeout(() => finish(true), SOCKET_PROBE_TIMEOUT_MS);\n\t\ttimer.unref();\n\t});\n}\n\nasync function setSocketMode(path: string, mode: number): Promise<void> {\n\tif (process.platform === \"win32\") return;\n\ttry {\n\t\tawait chmod(path, mode);\n\t} catch (error) {\n\t\tif (!isErrorCode(error, \"ENOSYS\") && !isErrorCode(error, \"ENOTSUP\")) throw error;\n\t}\n}\n\nfunction closeNetServer(server: Server, reportError: (error: Error) => void): Promise<void> {\n\tif (!server.listening) return Promise.resolve();\n\treturn new Promise<void>((resolve) => {\n\t\tserver.close((error) => {\n\t\t\tif (error) reportError(error);\n\t\t\tresolve();\n\t\t});\n\t});\n}\n\nfunction isErrorCode(error: unknown, code: string): boolean {\n\treturn error instanceof Error && \"code\" in error && error.code === code;\n}\n\nexport function createUnixListener(options: UnixListenerOptions): ServerListener {\n\treturn new UnixListener(options);\n}\n\nfunction resolveUnixListenerOptions(options: UnixListenerOptions): ResolvedUnixListenerOptions {\n\tif (!options.path) throw new TypeError(\"Server Unix socket path must not be empty\");\n\tconst mode = options.mode ?? DEFAULT_SOCKET_MODE;\n\tif (!Number.isInteger(mode) || mode < 0 || mode > 0o777) {\n\t\tthrow new TypeError(\"Server Unix socket mode must be an integer between 0 and 0o777\");\n\t}\n\tconst maxFrameLength = options.maxFrameLength ?? DEFAULT_MAX_FRAME_LENGTH;\n\tif (!Number.isSafeInteger(maxFrameLength) || maxFrameLength <= 0 || maxFrameLength > MAX_UINT32) {\n\t\tthrow new TypeError(`Server maxFrameLength must be an integer between 1 and ${MAX_UINT32}`);\n\t}\n\tconst maxPendingBytes = options.maxPendingBytes ?? maxFrameLength * 4;\n\tif (!Number.isSafeInteger(maxPendingBytes) || maxPendingBytes < maxFrameLength + 4) {\n\t\tthrow new TypeError(\"Server maxPendingBytes must be a safe integer at least maxFrameLength + 4\");\n\t}\n\tconst gracefulCloseTimeoutMs = options.gracefulCloseTimeoutMs ?? DEFAULT_GRACEFUL_CLOSE_TIMEOUT_MS;\n\tif (\n\t\t!Number.isSafeInteger(gracefulCloseTimeoutMs) ||\n\t\tgracefulCloseTimeoutMs <= 0 ||\n\t\tgracefulCloseTimeoutMs > MAX_TIMER_DELAY_MS\n\t) {\n\t\tthrow new TypeError(`Server gracefulCloseTimeoutMs must be an integer between 1 and ${MAX_TIMER_DELAY_MS}`);\n\t}\n\treturn {\n\t\tpath: options.path,\n\t\tmode,\n\t\tmaxPendingBytes,\n\t\tgracefulCloseTimeoutMs,\n\t\tonError: options.onError,\n\t};\n}\n"]}
@@ -8,14 +8,6 @@ const DEFAULT_GRACEFUL_CLOSE_TIMEOUT_MS = 5_000;
8
8
  const MAX_UINT32 = 0xffff_ffff;
9
9
  const MAX_TIMER_DELAY_MS = 2_147_483_647;
10
10
  const SOCKET_PROBE_TIMEOUT_MS = 1_000;
11
- const MAX_UNIX_SOCKET_PATH_BYTES = process.platform === "linux" ? 107 : 103;
12
- export function validateUnixSocketPath(path, description = "Unix socket path") {
13
- if (!path)
14
- throw new TypeError(`${description} must not be empty`);
15
- if (Buffer.byteLength(path) > MAX_UNIX_SOCKET_PATH_BYTES) {
16
- throw new TypeError(`${description} is too long; maximum is ${MAX_UNIX_SOCKET_PATH_BYTES} UTF-8 bytes`);
17
- }
18
- }
19
11
  class UnixListener {
20
12
  options;
21
13
  path;
@@ -24,7 +16,6 @@ class UnixListener {
24
16
  server;
25
17
  socketIdentity;
26
18
  ownedBindPath;
27
- boundPath;
28
19
  closing = false;
29
20
  closePromise;
30
21
  accept;
@@ -33,9 +24,6 @@ class UnixListener {
33
24
  this.path = this.options.path;
34
25
  this.mode = this.options.mode;
35
26
  }
36
- get address() {
37
- return this.boundPath;
38
- }
39
27
  async start(accept) {
40
28
  if (this.server)
41
29
  throw new Error("Unix listener is already started");
@@ -43,7 +31,6 @@ class UnixListener {
43
31
  throw new Error("Unix listener is closing or closed");
44
32
  this.accept = accept;
45
33
  const ownedBindPath = getOwnedBindPath(this.path);
46
- validateUnixSocketPath(ownedBindPath, "PiServer private Unix bind path");
47
34
  await mkdir(dirname(this.path), { recursive: true, mode: 0o700 });
48
35
  await removeStaleSocket(this.path);
49
36
  await removeStaleSocket(ownedBindPath);
@@ -71,7 +58,8 @@ class UnixListener {
71
58
  this.socketIdentity = { dev: stats.dev, ino: stats.ino };
72
59
  await link(ownedBindPath, this.path);
73
60
  await setSocketMode(this.path, this.mode);
74
- this.boundPath = this.path;
61
+ await removePath(ownedBindPath);
62
+ this.ownedBindPath = undefined;
75
63
  }
76
64
  catch (error) {
77
65
  await this.closeServerAndCleanup(server);
@@ -113,7 +101,6 @@ class UnixListener {
113
101
  });
114
102
  }
115
103
  async closeInternal() {
116
- this.boundPath = undefined;
117
104
  const serverClosed = this.server ? this.closeServerAndCleanup(this.server) : this.cleanupOwnedSocket();
118
105
  await Promise.all([...this.connections].map((connection) => connection.close()));
119
106
  await serverClosed;
@@ -128,10 +115,11 @@ class UnixListener {
128
115
  await closeNetServer(server, (error) => this.reportError(error));
129
116
  }
130
117
  finally {
131
- await this.cleanupOwnedSocket();
118
+ // Remove an unpublished startup bind path before the public route.
132
119
  if (this.ownedBindPath)
133
120
  await removePath(this.ownedBindPath);
134
121
  this.ownedBindPath = undefined;
122
+ await this.cleanupOwnedSocket();
135
123
  }
136
124
  }
137
125
  async cleanupOwnedSocket() {
@@ -150,7 +138,7 @@ class UnixListener {
150
138
  }
151
139
  if (!current.isSocket() || current.dev !== identity.dev || current.ino !== identity.ino)
152
140
  return;
153
- const preserved = join(dirname(this.path), `.c-${randomUUID().slice(0, 6)}`);
141
+ const preserved = join(dirname(this.path), `cleanup-${randomUUID().slice(0, 6)}`);
154
142
  try {
155
143
  await rename(this.path, preserved);
156
144
  }
@@ -294,7 +282,7 @@ export class UnixByteConnection {
294
282
  }
295
283
  function getOwnedBindPath(path) {
296
284
  const suffix = createHash("sha256").update(path).digest("hex").slice(0, 8);
297
- return join(dirname(path), `.p-${suffix}`);
285
+ return join(dirname(path), `bind-${suffix}`);
298
286
  }
299
287
  async function removeStaleSocket(path) {
300
288
  let original;
@@ -310,7 +298,7 @@ async function removeStaleSocket(path) {
310
298
  throw new Error(`Refusing to remove non-socket Unix listener path: ${path}`);
311
299
  if (await isSocketLive(path))
312
300
  throw new Error(`Unix listener is already running: ${path}`);
313
- const preserved = join(dirname(path), `.s-${randomUUID().slice(0, 6)}`);
301
+ const preserved = join(dirname(path), `stale-${randomUUID().slice(0, 6)}`);
314
302
  try {
315
303
  await rename(path, preserved);
316
304
  }
@@ -402,24 +390,25 @@ export function createUnixListener(options) {
402
390
  return new UnixListener(options);
403
391
  }
404
392
  function resolveUnixListenerOptions(options) {
405
- validateUnixSocketPath(options.path, "PiServer Unix socket path");
393
+ if (!options.path)
394
+ throw new TypeError("Server Unix socket path must not be empty");
406
395
  const mode = options.mode ?? DEFAULT_SOCKET_MODE;
407
396
  if (!Number.isInteger(mode) || mode < 0 || mode > 0o777) {
408
- throw new TypeError("PiServer Unix socket mode must be an integer between 0 and 0o777");
397
+ throw new TypeError("Server Unix socket mode must be an integer between 0 and 0o777");
409
398
  }
410
399
  const maxFrameLength = options.maxFrameLength ?? DEFAULT_MAX_FRAME_LENGTH;
411
400
  if (!Number.isSafeInteger(maxFrameLength) || maxFrameLength <= 0 || maxFrameLength > MAX_UINT32) {
412
- throw new TypeError(`PiServer maxFrameLength must be an integer between 1 and ${MAX_UINT32}`);
401
+ throw new TypeError(`Server maxFrameLength must be an integer between 1 and ${MAX_UINT32}`);
413
402
  }
414
403
  const maxPendingBytes = options.maxPendingBytes ?? maxFrameLength * 4;
415
404
  if (!Number.isSafeInteger(maxPendingBytes) || maxPendingBytes < maxFrameLength + 4) {
416
- throw new TypeError("PiServer maxPendingBytes must be a safe integer at least maxFrameLength + 4");
405
+ throw new TypeError("Server maxPendingBytes must be a safe integer at least maxFrameLength + 4");
417
406
  }
418
407
  const gracefulCloseTimeoutMs = options.gracefulCloseTimeoutMs ?? DEFAULT_GRACEFUL_CLOSE_TIMEOUT_MS;
419
408
  if (!Number.isSafeInteger(gracefulCloseTimeoutMs) ||
420
409
  gracefulCloseTimeoutMs <= 0 ||
421
410
  gracefulCloseTimeoutMs > MAX_TIMER_DELAY_MS) {
422
- throw new TypeError(`PiServer gracefulCloseTimeoutMs must be an integer between 1 and ${MAX_TIMER_DELAY_MS}`);
411
+ throw new TypeError(`Server gracefulCloseTimeoutMs must be an integer between 1 and ${MAX_TIMER_DELAY_MS}`);
423
412
  }
424
413
  return {
425
414
  path: options.path,
@@ -1 +1 @@
1
- {"version":3,"file":"listener.js","sourceRoot":"","sources":["../../../src/transports/unix/listener.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash, randomUUID } from \"node:crypto\";\nimport type { Stats } from \"node:fs\";\nimport { chmod, link, lstat, mkdir, rename, unlink } from \"node:fs/promises\";\nimport { createConnection, createServer, type Server, type Socket } from \"node:net\";\nimport { dirname, join } from \"node:path\";\nimport { DEFAULT_MAX_FRAME_LENGTH } from \"@earendil-works/pi-protocol\";\nimport type { ByteConnection, ByteConnectionAcceptor } from \"../../connection.ts\";\nimport type { PiServerListener } from \"../../listener.ts\";\nimport type { UnixListenerOptions } from \"./types.ts\";\n\nconst DEFAULT_SOCKET_MODE = 0o600;\nconst DEFAULT_GRACEFUL_CLOSE_TIMEOUT_MS = 5_000;\nconst MAX_UINT32 = 0xffff_ffff;\nconst MAX_TIMER_DELAY_MS = 2_147_483_647;\nconst SOCKET_PROBE_TIMEOUT_MS = 1_000;\nconst MAX_UNIX_SOCKET_PATH_BYTES = process.platform === \"linux\" ? 107 : 103;\n\nexport function validateUnixSocketPath(path: string, description = \"Unix socket path\"): void {\n\tif (!path) throw new TypeError(`${description} must not be empty`);\n\tif (Buffer.byteLength(path) > MAX_UNIX_SOCKET_PATH_BYTES) {\n\t\tthrow new TypeError(`${description} is too long; maximum is ${MAX_UNIX_SOCKET_PATH_BYTES} UTF-8 bytes`);\n\t}\n}\n\ninterface ResolvedUnixListenerOptions {\n\tpath: string;\n\tmode: number;\n\tgracefulCloseTimeoutMs: number;\n\tmaxPendingBytes: number;\n\tonError?: (error: Error) => void;\n}\n\ninterface FileIdentity {\n\tdev: number;\n\tino: number;\n}\nclass UnixListener implements PiServerListener {\n\tprivate readonly options: ResolvedUnixListenerOptions;\n\tprivate readonly path: string;\n\tprivate readonly mode: number;\n\tprivate readonly connections = new Set<UnixByteConnection>();\n\tprivate server?: Server;\n\tprivate socketIdentity?: FileIdentity;\n\tprivate ownedBindPath?: string;\n\tprivate boundPath?: string;\n\tprivate closing = false;\n\tprivate closePromise?: Promise<void>;\n\tprivate accept?: ByteConnectionAcceptor;\n\n\tconstructor(options: UnixListenerOptions) {\n\t\tthis.options = resolveUnixListenerOptions(options);\n\t\tthis.path = this.options.path;\n\t\tthis.mode = this.options.mode;\n\t}\n\n\tget address(): string | undefined {\n\t\treturn this.boundPath;\n\t}\n\n\tasync start(accept: ByteConnectionAcceptor): Promise<void> {\n\t\tif (this.server) throw new Error(\"Unix listener is already started\");\n\t\tif (this.closing) throw new Error(\"Unix listener is closing or closed\");\n\t\tthis.accept = accept;\n\n\t\tconst ownedBindPath = getOwnedBindPath(this.path);\n\t\tvalidateUnixSocketPath(ownedBindPath, \"PiServer private Unix bind path\");\n\t\tawait mkdir(dirname(this.path), { recursive: true, mode: 0o700 });\n\t\tawait removeStaleSocket(this.path);\n\t\tawait removeStaleSocket(ownedBindPath);\n\t\tthis.ownedBindPath = ownedBindPath;\n\t\tconst server = createServer((socket) => this.acceptSocket(socket));\n\t\tserver.on(\"error\", (error) => this.reportError(error));\n\t\tthis.server = server;\n\t\ttry {\n\t\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\t\tconst onError = (error: Error): void => {\n\t\t\t\t\tserver.off(\"listening\", onListening);\n\t\t\t\t\treject(error);\n\t\t\t\t};\n\t\t\t\tconst onListening = (): void => {\n\t\t\t\t\tserver.off(\"error\", onError);\n\t\t\t\t\tresolve();\n\t\t\t\t};\n\t\t\t\tserver.once(\"error\", onError);\n\t\t\t\tserver.once(\"listening\", onListening);\n\t\t\t\tserver.listen(ownedBindPath);\n\t\t\t});\n\t\t\tconst stats = await lstat(ownedBindPath);\n\t\t\tif (!stats.isSocket()) throw new Error(`Unix listener path is not a socket after binding: ${ownedBindPath}`);\n\t\t\tthis.socketIdentity = { dev: stats.dev, ino: stats.ino };\n\t\t\tawait link(ownedBindPath, this.path);\n\t\t\tawait setSocketMode(this.path, this.mode);\n\t\t\tthis.boundPath = this.path;\n\t\t} catch (error) {\n\t\t\tawait this.closeServerAndCleanup(server);\n\t\t\tthis.server = undefined;\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tasync close(): Promise<void> {\n\t\tif (this.closePromise) return this.closePromise;\n\t\tthis.closing = true;\n\t\tthis.closePromise = this.closeInternal();\n\t\treturn this.closePromise;\n\t}\n\n\tprivate acceptSocket(socket: Socket): void {\n\t\tif (this.closing) {\n\t\t\tsocket.destroy();\n\t\t\treturn;\n\t\t}\n\t\tconst connection = new UnixByteConnection(\n\t\t\tsocket,\n\t\t\tthis.options.gracefulCloseTimeoutMs,\n\t\t\tthis.options.maxPendingBytes,\n\t\t);\n\t\tthis.connections.add(connection);\n\t\tconst accept = this.accept;\n\t\tif (!accept) {\n\t\t\tsocket.destroy();\n\t\t\treturn;\n\t\t}\n\t\tconst handler = accept(connection);\n\t\tsocket.on(\"data\", (chunk) => {\n\t\t\thandler.onData(new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength));\n\t\t});\n\t\tsocket.on(\"error\", (error) => {\n\t\t\thandler.onError(error);\n\t\t\tsocket.destroy();\n\t\t});\n\t\tsocket.once(\"close\", () => {\n\t\t\tconnection.markClosed();\n\t\t\tthis.connections.delete(connection);\n\t\t\thandler.onClose();\n\t\t});\n\t}\n\n\tprivate async closeInternal(): Promise<void> {\n\t\tthis.boundPath = undefined;\n\t\tconst serverClosed = this.server ? this.closeServerAndCleanup(this.server) : this.cleanupOwnedSocket();\n\t\tawait Promise.all([...this.connections].map((connection) => connection.close()));\n\t\tawait serverClosed;\n\t\tif (this.ownedBindPath) await removePath(this.ownedBindPath);\n\t\tthis.ownedBindPath = undefined;\n\t\tthis.connections.clear();\n\t\tthis.server = undefined;\n\t}\n\n\tprivate async closeServerAndCleanup(server: Server): Promise<void> {\n\t\ttry {\n\t\t\tawait closeNetServer(server, (error) => this.reportError(error));\n\t\t} finally {\n\t\t\tawait this.cleanupOwnedSocket();\n\t\t\tif (this.ownedBindPath) await removePath(this.ownedBindPath);\n\t\t\tthis.ownedBindPath = undefined;\n\t\t}\n\t}\n\n\tprivate async cleanupOwnedSocket(): Promise<void> {\n\t\tconst identity = this.socketIdentity;\n\t\tthis.socketIdentity = undefined;\n\t\tif (!identity) return;\n\t\tlet current: Stats;\n\t\ttry {\n\t\t\tcurrent = await lstat(this.path);\n\t\t} catch (error) {\n\t\t\tif (isErrorCode(error, \"ENOENT\")) return;\n\t\t\tthrow error;\n\t\t}\n\t\tif (!current.isSocket() || current.dev !== identity.dev || current.ino !== identity.ino) return;\n\n\t\tconst preserved = join(dirname(this.path), `.c-${randomUUID().slice(0, 6)}`);\n\t\ttry {\n\t\t\tawait rename(this.path, preserved);\n\t\t} catch (error) {\n\t\t\tif (isErrorCode(error, \"ENOENT\")) return;\n\t\t\tthrow error;\n\t\t}\n\t\tconst moved = await lstat(preserved);\n\t\tif (moved.isSocket() && moved.dev === identity.dev && moved.ino === identity.ino) {\n\t\t\tawait removePath(preserved);\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tawait lstat(this.path);\n\t\t} catch (error) {\n\t\t\tif (isErrorCode(error, \"ENOENT\")) await rename(preserved, this.path);\n\t\t\telse throw error;\n\t\t}\n\t\tthrow new Error(`Unix listener path changed during cleanup; preserved replacement at ${preserved}`);\n\t}\n\n\tprivate reportError(error: unknown): void {\n\t\ttry {\n\t\t\tthis.options.onError?.(error instanceof Error ? error : new Error(String(error)));\n\t\t} catch {\n\t\t\t// Error observers cannot affect listener state.\n\t\t}\n\t}\n}\n\n/** @internal Exported only for transport-level verification. */\nexport class UnixByteConnection implements ByteConnection {\n\tprivate readonly socket: Socket;\n\tprivate readonly gracefulCloseTimeoutMs: number;\n\tprivate readonly maxPendingBytes: number;\n\tprivate pendingBytes = 0;\n\tprivate closedValue = false;\n\tprivate closing = false;\n\tprivate writeTail: Promise<void> = Promise.resolve();\n\tprivate closePromise?: Promise<void>;\n\tprivate resolveClose?: () => void;\n\n\tconstructor(socket: Socket, gracefulCloseTimeoutMs: number, maxPendingBytes: number) {\n\t\tthis.socket = socket;\n\t\tthis.gracefulCloseTimeoutMs = gracefulCloseTimeoutMs;\n\t\tthis.maxPendingBytes = maxPendingBytes;\n\t}\n\n\tget closed(): boolean {\n\t\treturn this.closedValue;\n\t}\n\n\tsend(chunk: Uint8Array): Promise<void> {\n\t\tif (!(chunk instanceof Uint8Array)) {\n\t\t\treturn Promise.reject(new TypeError(\"Unix connection chunks must be Uint8Array\"));\n\t\t}\n\t\tif (this.closedValue || this.closing) return Promise.reject(new Error(\"Unix connection is closed\"));\n\t\tif (this.pendingBytes + chunk.byteLength > this.maxPendingBytes) {\n\t\t\treturn Promise.reject(new Error(\"Unix connection exceeded its pending byte limit\"));\n\t\t}\n\t\tthis.pendingBytes += chunk.byteLength;\n\t\tconst bytes = chunk.slice();\n\t\tconst write = this.writeTail.then(() => this.write(bytes));\n\t\tconst tracked = write.finally(() => {\n\t\t\tthis.pendingBytes -= bytes.byteLength;\n\t\t});\n\t\tthis.writeTail = tracked.catch(() => {});\n\t\treturn tracked;\n\t}\n\n\tclose(finalChunk?: Uint8Array): Promise<void> {\n\t\tif (this.closedValue || this.socket.destroyed) {\n\t\t\tthis.markClosed();\n\t\t\treturn Promise.resolve();\n\t\t}\n\t\tif (this.closePromise) return this.closePromise;\n\t\tthis.closing = true;\n\t\tconst finalBytes = finalChunk?.slice();\n\t\tthis.closePromise = new Promise<void>((resolve) => {\n\t\t\tthis.resolveClose = resolve;\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tif (!this.socket.destroyed) this.socket.destroy();\n\t\t\t\tthis.markClosed();\n\t\t\t}, this.gracefulCloseTimeoutMs);\n\t\t\ttimer.unref();\n\t\t\tthis.socket.once(\"close\", () => clearTimeout(timer));\n\t\t\tvoid this.writeTail.then(() => {\n\t\t\t\tif (this.socket.destroyed) {\n\t\t\t\t\tthis.markClosed();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tif (finalBytes) this.socket.end(finalBytes);\n\t\t\t\t\telse this.socket.end();\n\t\t\t\t} catch {\n\t\t\t\t\tthis.socket.destroy();\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t\treturn this.closePromise;\n\t}\n\n\tmarkClosed(): void {\n\t\tif (this.closedValue) return;\n\t\tthis.closedValue = true;\n\t\tthis.closing = true;\n\t\tthis.resolveClose?.();\n\t\tthis.resolveClose = undefined;\n\t}\n\n\tprivate write(chunk: Uint8Array): Promise<void> {\n\t\tif (this.closedValue || this.closing || !this.socket.writable) {\n\t\t\treturn Promise.reject(new Error(\"Unix connection is closed\"));\n\t\t}\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\tlet settled = false;\n\t\t\tconst onClose = (): void => finish(new Error(\"Unix connection closed during write\"));\n\t\t\tconst finish = (error?: Error | null): void => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tthis.socket.off(\"close\", onClose);\n\t\t\t\tif (error) reject(error);\n\t\t\t\telse resolve();\n\t\t\t};\n\t\t\tthis.socket.once(\"close\", onClose);\n\t\t\ttry {\n\t\t\t\tthis.socket.write(chunk, finish);\n\t\t\t} catch (error) {\n\t\t\t\tfinish(error instanceof Error ? error : new Error(String(error)));\n\t\t\t}\n\t\t});\n\t}\n}\n\nfunction getOwnedBindPath(path: string): string {\n\tconst suffix = createHash(\"sha256\").update(path).digest(\"hex\").slice(0, 8);\n\treturn join(dirname(path), `.p-${suffix}`);\n}\n\nasync function removeStaleSocket(path: string): Promise<void> {\n\tlet original: Stats;\n\ttry {\n\t\toriginal = await lstat(path);\n\t} catch (error) {\n\t\tif (isErrorCode(error, \"ENOENT\")) return;\n\t\tthrow error;\n\t}\n\tif (!original.isSocket()) throw new Error(`Refusing to remove non-socket Unix listener path: ${path}`);\n\tif (await isSocketLive(path)) throw new Error(`Unix listener is already running: ${path}`);\n\n\tconst preserved = join(dirname(path), `.s-${randomUUID().slice(0, 6)}`);\n\ttry {\n\t\tawait rename(path, preserved);\n\t} catch (error) {\n\t\tif (isErrorCode(error, \"ENOENT\")) return;\n\t\tthrow error;\n\t}\n\tconst current = await lstat(preserved);\n\tif (!current.isSocket() || current.dev !== original.dev || current.ino !== original.ino) {\n\t\ttry {\n\t\t\tawait lstat(path);\n\t\t} catch (error) {\n\t\t\tif (isErrorCode(error, \"ENOENT\")) await rename(preserved, path);\n\t\t\telse throw error;\n\t\t}\n\t\tthrow new Error(`Unix listener path changed while checking for a stale socket: ${path}`);\n\t}\n\tawait removePath(preserved);\n}\n\nasync function removePath(path: string): Promise<void> {\n\ttry {\n\t\tawait unlink(path);\n\t} catch (error) {\n\t\tif (!isErrorCode(error, \"ENOENT\")) throw error;\n\t}\n}\n\nfunction isSocketLive(path: string): Promise<boolean> {\n\treturn new Promise<boolean>((resolve, reject) => {\n\t\tconst socket = createConnection(path);\n\t\tlet settled = false;\n\t\tlet timer: NodeJS.Timeout | undefined;\n\t\tconst finish = (result: boolean, error?: Error): void => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tif (timer) clearTimeout(timer);\n\t\t\tsocket.removeAllListeners();\n\t\t\tsocket.destroy();\n\t\t\tif (error) reject(error);\n\t\t\telse resolve(result);\n\t\t};\n\t\tsocket.once(\"connect\", () => finish(true));\n\t\tsocket.once(\"error\", (error: NodeJS.ErrnoException) => {\n\t\t\tif ([\"ECONNREFUSED\", \"ENOENT\", \"EPIPE\", \"ECONNRESET\"].includes(error.code ?? \"\")) {\n\t\t\t\tfinish(false);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tfinish(false, error);\n\t\t});\n\t\ttimer = setTimeout(() => finish(true), SOCKET_PROBE_TIMEOUT_MS);\n\t\ttimer.unref();\n\t});\n}\n\nasync function setSocketMode(path: string, mode: number): Promise<void> {\n\tif (process.platform === \"win32\") return;\n\ttry {\n\t\tawait chmod(path, mode);\n\t} catch (error) {\n\t\tif (!isErrorCode(error, \"ENOSYS\") && !isErrorCode(error, \"ENOTSUP\")) throw error;\n\t}\n}\n\nfunction closeNetServer(server: Server, reportError: (error: Error) => void): Promise<void> {\n\tif (!server.listening) return Promise.resolve();\n\treturn new Promise<void>((resolve) => {\n\t\tserver.close((error) => {\n\t\t\tif (error) reportError(error);\n\t\t\tresolve();\n\t\t});\n\t});\n}\n\nfunction isErrorCode(error: unknown, code: string): boolean {\n\treturn error instanceof Error && \"code\" in error && error.code === code;\n}\n\nexport function createUnixListener(options: UnixListenerOptions): PiServerListener {\n\treturn new UnixListener(options);\n}\n\nfunction resolveUnixListenerOptions(options: UnixListenerOptions): ResolvedUnixListenerOptions {\n\tvalidateUnixSocketPath(options.path, \"PiServer Unix socket path\");\n\tconst mode = options.mode ?? DEFAULT_SOCKET_MODE;\n\tif (!Number.isInteger(mode) || mode < 0 || mode > 0o777) {\n\t\tthrow new TypeError(\"PiServer Unix socket mode must be an integer between 0 and 0o777\");\n\t}\n\tconst maxFrameLength = options.maxFrameLength ?? DEFAULT_MAX_FRAME_LENGTH;\n\tif (!Number.isSafeInteger(maxFrameLength) || maxFrameLength <= 0 || maxFrameLength > MAX_UINT32) {\n\t\tthrow new TypeError(`PiServer maxFrameLength must be an integer between 1 and ${MAX_UINT32}`);\n\t}\n\tconst maxPendingBytes = options.maxPendingBytes ?? maxFrameLength * 4;\n\tif (!Number.isSafeInteger(maxPendingBytes) || maxPendingBytes < maxFrameLength + 4) {\n\t\tthrow new TypeError(\"PiServer maxPendingBytes must be a safe integer at least maxFrameLength + 4\");\n\t}\n\tconst gracefulCloseTimeoutMs = options.gracefulCloseTimeoutMs ?? DEFAULT_GRACEFUL_CLOSE_TIMEOUT_MS;\n\tif (\n\t\t!Number.isSafeInteger(gracefulCloseTimeoutMs) ||\n\t\tgracefulCloseTimeoutMs <= 0 ||\n\t\tgracefulCloseTimeoutMs > MAX_TIMER_DELAY_MS\n\t) {\n\t\tthrow new TypeError(`PiServer gracefulCloseTimeoutMs must be an integer between 1 and ${MAX_TIMER_DELAY_MS}`);\n\t}\n\treturn {\n\t\tpath: options.path,\n\t\tmode,\n\t\tmaxPendingBytes,\n\t\tgracefulCloseTimeoutMs,\n\t\tonError: options.onError,\n\t};\n}\n"]}
1
+ {"version":3,"file":"listener.js","sourceRoot":"","sources":["../../../src/transports/unix/listener.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash, randomUUID } from \"node:crypto\";\nimport type { Stats } from \"node:fs\";\nimport { chmod, link, lstat, mkdir, rename, unlink } from \"node:fs/promises\";\nimport { createConnection, createServer, type Server, type Socket } from \"node:net\";\nimport { dirname, join } from \"node:path\";\nimport { DEFAULT_MAX_FRAME_LENGTH } from \"@earendil-works/pi-protocol\";\nimport type { ByteConnection, ByteConnectionAcceptor } from \"../../connection.ts\";\nimport type { ServerListener } from \"../../listener.ts\";\nimport type { UnixListenerOptions } from \"./types.ts\";\n\nconst DEFAULT_SOCKET_MODE = 0o600;\nconst DEFAULT_GRACEFUL_CLOSE_TIMEOUT_MS = 5_000;\nconst MAX_UINT32 = 0xffff_ffff;\nconst MAX_TIMER_DELAY_MS = 2_147_483_647;\nconst SOCKET_PROBE_TIMEOUT_MS = 1_000;\n\ninterface ResolvedUnixListenerOptions {\n\tpath: string;\n\tmode: number;\n\tgracefulCloseTimeoutMs: number;\n\tmaxPendingBytes: number;\n\tonError?: (error: Error) => void;\n}\n\ninterface FileIdentity {\n\tdev: number;\n\tino: number;\n}\nclass UnixListener implements ServerListener {\n\tprivate readonly options: ResolvedUnixListenerOptions;\n\tprivate readonly path: string;\n\tprivate readonly mode: number;\n\tprivate readonly connections = new Set<UnixByteConnection>();\n\tprivate server?: Server;\n\tprivate socketIdentity?: FileIdentity;\n\tprivate ownedBindPath?: string;\n\tprivate closing = false;\n\tprivate closePromise?: Promise<void>;\n\tprivate accept?: ByteConnectionAcceptor;\n\n\tconstructor(options: UnixListenerOptions) {\n\t\tthis.options = resolveUnixListenerOptions(options);\n\t\tthis.path = this.options.path;\n\t\tthis.mode = this.options.mode;\n\t}\n\n\tasync start(accept: ByteConnectionAcceptor): Promise<void> {\n\t\tif (this.server) throw new Error(\"Unix listener is already started\");\n\t\tif (this.closing) throw new Error(\"Unix listener is closing or closed\");\n\t\tthis.accept = accept;\n\n\t\tconst ownedBindPath = getOwnedBindPath(this.path);\n\t\tawait mkdir(dirname(this.path), { recursive: true, mode: 0o700 });\n\t\tawait removeStaleSocket(this.path);\n\t\tawait removeStaleSocket(ownedBindPath);\n\t\tthis.ownedBindPath = ownedBindPath;\n\t\tconst server = createServer((socket) => this.acceptSocket(socket));\n\t\tserver.on(\"error\", (error) => this.reportError(error));\n\t\tthis.server = server;\n\t\ttry {\n\t\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\t\tconst onError = (error: Error): void => {\n\t\t\t\t\tserver.off(\"listening\", onListening);\n\t\t\t\t\treject(error);\n\t\t\t\t};\n\t\t\t\tconst onListening = (): void => {\n\t\t\t\t\tserver.off(\"error\", onError);\n\t\t\t\t\tresolve();\n\t\t\t\t};\n\t\t\t\tserver.once(\"error\", onError);\n\t\t\t\tserver.once(\"listening\", onListening);\n\t\t\t\tserver.listen(ownedBindPath);\n\t\t\t});\n\t\t\tconst stats = await lstat(ownedBindPath);\n\t\t\tif (!stats.isSocket()) throw new Error(`Unix listener path is not a socket after binding: ${ownedBindPath}`);\n\t\t\tthis.socketIdentity = { dev: stats.dev, ino: stats.ino };\n\t\t\tawait link(ownedBindPath, this.path);\n\t\t\tawait setSocketMode(this.path, this.mode);\n\t\t\tawait removePath(ownedBindPath);\n\t\t\tthis.ownedBindPath = undefined;\n\t\t} catch (error) {\n\t\t\tawait this.closeServerAndCleanup(server);\n\t\t\tthis.server = undefined;\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tasync close(): Promise<void> {\n\t\tif (this.closePromise) return this.closePromise;\n\t\tthis.closing = true;\n\t\tthis.closePromise = this.closeInternal();\n\t\treturn this.closePromise;\n\t}\n\n\tprivate acceptSocket(socket: Socket): void {\n\t\tif (this.closing) {\n\t\t\tsocket.destroy();\n\t\t\treturn;\n\t\t}\n\t\tconst connection = new UnixByteConnection(\n\t\t\tsocket,\n\t\t\tthis.options.gracefulCloseTimeoutMs,\n\t\t\tthis.options.maxPendingBytes,\n\t\t);\n\t\tthis.connections.add(connection);\n\t\tconst accept = this.accept;\n\t\tif (!accept) {\n\t\t\tsocket.destroy();\n\t\t\treturn;\n\t\t}\n\t\tconst handler = accept(connection);\n\t\tsocket.on(\"data\", (chunk) => {\n\t\t\thandler.onData(new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength));\n\t\t});\n\t\tsocket.on(\"error\", (error) => {\n\t\t\thandler.onError(error);\n\t\t\tsocket.destroy();\n\t\t});\n\t\tsocket.once(\"close\", () => {\n\t\t\tconnection.markClosed();\n\t\t\tthis.connections.delete(connection);\n\t\t\thandler.onClose();\n\t\t});\n\t}\n\n\tprivate async closeInternal(): Promise<void> {\n\t\tconst serverClosed = this.server ? this.closeServerAndCleanup(this.server) : this.cleanupOwnedSocket();\n\t\tawait Promise.all([...this.connections].map((connection) => connection.close()));\n\t\tawait serverClosed;\n\t\tif (this.ownedBindPath) await removePath(this.ownedBindPath);\n\t\tthis.ownedBindPath = undefined;\n\t\tthis.connections.clear();\n\t\tthis.server = undefined;\n\t}\n\n\tprivate async closeServerAndCleanup(server: Server): Promise<void> {\n\t\ttry {\n\t\t\tawait closeNetServer(server, (error) => this.reportError(error));\n\t\t} finally {\n\t\t\t// Remove an unpublished startup bind path before the public route.\n\t\t\tif (this.ownedBindPath) await removePath(this.ownedBindPath);\n\t\t\tthis.ownedBindPath = undefined;\n\t\t\tawait this.cleanupOwnedSocket();\n\t\t}\n\t}\n\n\tprivate async cleanupOwnedSocket(): Promise<void> {\n\t\tconst identity = this.socketIdentity;\n\t\tthis.socketIdentity = undefined;\n\t\tif (!identity) return;\n\t\tlet current: Stats;\n\t\ttry {\n\t\t\tcurrent = await lstat(this.path);\n\t\t} catch (error) {\n\t\t\tif (isErrorCode(error, \"ENOENT\")) return;\n\t\t\tthrow error;\n\t\t}\n\t\tif (!current.isSocket() || current.dev !== identity.dev || current.ino !== identity.ino) return;\n\n\t\tconst preserved = join(dirname(this.path), `cleanup-${randomUUID().slice(0, 6)}`);\n\t\ttry {\n\t\t\tawait rename(this.path, preserved);\n\t\t} catch (error) {\n\t\t\tif (isErrorCode(error, \"ENOENT\")) return;\n\t\t\tthrow error;\n\t\t}\n\t\tconst moved = await lstat(preserved);\n\t\tif (moved.isSocket() && moved.dev === identity.dev && moved.ino === identity.ino) {\n\t\t\tawait removePath(preserved);\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tawait lstat(this.path);\n\t\t} catch (error) {\n\t\t\tif (isErrorCode(error, \"ENOENT\")) await rename(preserved, this.path);\n\t\t\telse throw error;\n\t\t}\n\t\tthrow new Error(`Unix listener path changed during cleanup; preserved replacement at ${preserved}`);\n\t}\n\n\tprivate reportError(error: unknown): void {\n\t\ttry {\n\t\t\tthis.options.onError?.(error instanceof Error ? error : new Error(String(error)));\n\t\t} catch {\n\t\t\t// Error observers cannot affect listener state.\n\t\t}\n\t}\n}\n\n/** @internal Exported only for transport-level verification. */\nexport class UnixByteConnection implements ByteConnection {\n\tprivate readonly socket: Socket;\n\tprivate readonly gracefulCloseTimeoutMs: number;\n\tprivate readonly maxPendingBytes: number;\n\tprivate pendingBytes = 0;\n\tprivate closedValue = false;\n\tprivate closing = false;\n\tprivate writeTail: Promise<void> = Promise.resolve();\n\tprivate closePromise?: Promise<void>;\n\tprivate resolveClose?: () => void;\n\n\tconstructor(socket: Socket, gracefulCloseTimeoutMs: number, maxPendingBytes: number) {\n\t\tthis.socket = socket;\n\t\tthis.gracefulCloseTimeoutMs = gracefulCloseTimeoutMs;\n\t\tthis.maxPendingBytes = maxPendingBytes;\n\t}\n\n\tget closed(): boolean {\n\t\treturn this.closedValue;\n\t}\n\n\tsend(chunk: Uint8Array): Promise<void> {\n\t\tif (!(chunk instanceof Uint8Array)) {\n\t\t\treturn Promise.reject(new TypeError(\"Unix connection chunks must be Uint8Array\"));\n\t\t}\n\t\tif (this.closedValue || this.closing) return Promise.reject(new Error(\"Unix connection is closed\"));\n\t\tif (this.pendingBytes + chunk.byteLength > this.maxPendingBytes) {\n\t\t\treturn Promise.reject(new Error(\"Unix connection exceeded its pending byte limit\"));\n\t\t}\n\t\tthis.pendingBytes += chunk.byteLength;\n\t\tconst bytes = chunk.slice();\n\t\tconst write = this.writeTail.then(() => this.write(bytes));\n\t\tconst tracked = write.finally(() => {\n\t\t\tthis.pendingBytes -= bytes.byteLength;\n\t\t});\n\t\tthis.writeTail = tracked.catch(() => {});\n\t\treturn tracked;\n\t}\n\n\tclose(finalChunk?: Uint8Array): Promise<void> {\n\t\tif (this.closedValue || this.socket.destroyed) {\n\t\t\tthis.markClosed();\n\t\t\treturn Promise.resolve();\n\t\t}\n\t\tif (this.closePromise) return this.closePromise;\n\t\tthis.closing = true;\n\t\tconst finalBytes = finalChunk?.slice();\n\t\tthis.closePromise = new Promise<void>((resolve) => {\n\t\t\tthis.resolveClose = resolve;\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tif (!this.socket.destroyed) this.socket.destroy();\n\t\t\t\tthis.markClosed();\n\t\t\t}, this.gracefulCloseTimeoutMs);\n\t\t\ttimer.unref();\n\t\t\tthis.socket.once(\"close\", () => clearTimeout(timer));\n\t\t\tvoid this.writeTail.then(() => {\n\t\t\t\tif (this.socket.destroyed) {\n\t\t\t\t\tthis.markClosed();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tif (finalBytes) this.socket.end(finalBytes);\n\t\t\t\t\telse this.socket.end();\n\t\t\t\t} catch {\n\t\t\t\t\tthis.socket.destroy();\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t\treturn this.closePromise;\n\t}\n\n\tmarkClosed(): void {\n\t\tif (this.closedValue) return;\n\t\tthis.closedValue = true;\n\t\tthis.closing = true;\n\t\tthis.resolveClose?.();\n\t\tthis.resolveClose = undefined;\n\t}\n\n\tprivate write(chunk: Uint8Array): Promise<void> {\n\t\tif (this.closedValue || this.closing || !this.socket.writable) {\n\t\t\treturn Promise.reject(new Error(\"Unix connection is closed\"));\n\t\t}\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\tlet settled = false;\n\t\t\tconst onClose = (): void => finish(new Error(\"Unix connection closed during write\"));\n\t\t\tconst finish = (error?: Error | null): void => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tthis.socket.off(\"close\", onClose);\n\t\t\t\tif (error) reject(error);\n\t\t\t\telse resolve();\n\t\t\t};\n\t\t\tthis.socket.once(\"close\", onClose);\n\t\t\ttry {\n\t\t\t\tthis.socket.write(chunk, finish);\n\t\t\t} catch (error) {\n\t\t\t\tfinish(error instanceof Error ? error : new Error(String(error)));\n\t\t\t}\n\t\t});\n\t}\n}\n\nfunction getOwnedBindPath(path: string): string {\n\tconst suffix = createHash(\"sha256\").update(path).digest(\"hex\").slice(0, 8);\n\treturn join(dirname(path), `bind-${suffix}`);\n}\n\nasync function removeStaleSocket(path: string): Promise<void> {\n\tlet original: Stats;\n\ttry {\n\t\toriginal = await lstat(path);\n\t} catch (error) {\n\t\tif (isErrorCode(error, \"ENOENT\")) return;\n\t\tthrow error;\n\t}\n\tif (!original.isSocket()) throw new Error(`Refusing to remove non-socket Unix listener path: ${path}`);\n\tif (await isSocketLive(path)) throw new Error(`Unix listener is already running: ${path}`);\n\n\tconst preserved = join(dirname(path), `stale-${randomUUID().slice(0, 6)}`);\n\ttry {\n\t\tawait rename(path, preserved);\n\t} catch (error) {\n\t\tif (isErrorCode(error, \"ENOENT\")) return;\n\t\tthrow error;\n\t}\n\tconst current = await lstat(preserved);\n\tif (!current.isSocket() || current.dev !== original.dev || current.ino !== original.ino) {\n\t\ttry {\n\t\t\tawait lstat(path);\n\t\t} catch (error) {\n\t\t\tif (isErrorCode(error, \"ENOENT\")) await rename(preserved, path);\n\t\t\telse throw error;\n\t\t}\n\t\tthrow new Error(`Unix listener path changed while checking for a stale socket: ${path}`);\n\t}\n\tawait removePath(preserved);\n}\n\nasync function removePath(path: string): Promise<void> {\n\ttry {\n\t\tawait unlink(path);\n\t} catch (error) {\n\t\tif (!isErrorCode(error, \"ENOENT\")) throw error;\n\t}\n}\n\nfunction isSocketLive(path: string): Promise<boolean> {\n\treturn new Promise<boolean>((resolve, reject) => {\n\t\tconst socket = createConnection(path);\n\t\tlet settled = false;\n\t\tlet timer: NodeJS.Timeout | undefined;\n\t\tconst finish = (result: boolean, error?: Error): void => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tif (timer) clearTimeout(timer);\n\t\t\tsocket.removeAllListeners();\n\t\t\tsocket.destroy();\n\t\t\tif (error) reject(error);\n\t\t\telse resolve(result);\n\t\t};\n\t\tsocket.once(\"connect\", () => finish(true));\n\t\tsocket.once(\"error\", (error: NodeJS.ErrnoException) => {\n\t\t\tif ([\"ECONNREFUSED\", \"ENOENT\", \"EPIPE\", \"ECONNRESET\"].includes(error.code ?? \"\")) {\n\t\t\t\tfinish(false);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tfinish(false, error);\n\t\t});\n\t\ttimer = setTimeout(() => finish(true), SOCKET_PROBE_TIMEOUT_MS);\n\t\ttimer.unref();\n\t});\n}\n\nasync function setSocketMode(path: string, mode: number): Promise<void> {\n\tif (process.platform === \"win32\") return;\n\ttry {\n\t\tawait chmod(path, mode);\n\t} catch (error) {\n\t\tif (!isErrorCode(error, \"ENOSYS\") && !isErrorCode(error, \"ENOTSUP\")) throw error;\n\t}\n}\n\nfunction closeNetServer(server: Server, reportError: (error: Error) => void): Promise<void> {\n\tif (!server.listening) return Promise.resolve();\n\treturn new Promise<void>((resolve) => {\n\t\tserver.close((error) => {\n\t\t\tif (error) reportError(error);\n\t\t\tresolve();\n\t\t});\n\t});\n}\n\nfunction isErrorCode(error: unknown, code: string): boolean {\n\treturn error instanceof Error && \"code\" in error && error.code === code;\n}\n\nexport function createUnixListener(options: UnixListenerOptions): ServerListener {\n\treturn new UnixListener(options);\n}\n\nfunction resolveUnixListenerOptions(options: UnixListenerOptions): ResolvedUnixListenerOptions {\n\tif (!options.path) throw new TypeError(\"Server Unix socket path must not be empty\");\n\tconst mode = options.mode ?? DEFAULT_SOCKET_MODE;\n\tif (!Number.isInteger(mode) || mode < 0 || mode > 0o777) {\n\t\tthrow new TypeError(\"Server Unix socket mode must be an integer between 0 and 0o777\");\n\t}\n\tconst maxFrameLength = options.maxFrameLength ?? DEFAULT_MAX_FRAME_LENGTH;\n\tif (!Number.isSafeInteger(maxFrameLength) || maxFrameLength <= 0 || maxFrameLength > MAX_UINT32) {\n\t\tthrow new TypeError(`Server maxFrameLength must be an integer between 1 and ${MAX_UINT32}`);\n\t}\n\tconst maxPendingBytes = options.maxPendingBytes ?? maxFrameLength * 4;\n\tif (!Number.isSafeInteger(maxPendingBytes) || maxPendingBytes < maxFrameLength + 4) {\n\t\tthrow new TypeError(\"Server maxPendingBytes must be a safe integer at least maxFrameLength + 4\");\n\t}\n\tconst gracefulCloseTimeoutMs = options.gracefulCloseTimeoutMs ?? DEFAULT_GRACEFUL_CLOSE_TIMEOUT_MS;\n\tif (\n\t\t!Number.isSafeInteger(gracefulCloseTimeoutMs) ||\n\t\tgracefulCloseTimeoutMs <= 0 ||\n\t\tgracefulCloseTimeoutMs > MAX_TIMER_DELAY_MS\n\t) {\n\t\tthrow new TypeError(`Server gracefulCloseTimeoutMs must be an integer between 1 and ${MAX_TIMER_DELAY_MS}`);\n\t}\n\treturn {\n\t\tpath: options.path,\n\t\tmode,\n\t\tmaxPendingBytes,\n\t\tgracefulCloseTimeoutMs,\n\t\tonError: options.onError,\n\t};\n}\n"]}
@@ -1,6 +1,7 @@
1
- import { PiServer } from "../../server.ts";
2
- import type { PiServerService } from "../../types.ts";
1
+ import type { SessionMetadata } from "@earendil-works/pi-agent-core";
2
+ import { Server } from "../../server.ts";
3
+ import type { ServerHost } from "../../types.ts";
3
4
  import type { UnixServerOptions } from "./types.ts";
4
- /** Compose PiServer with one Unix-domain socket listener. */
5
- export declare function createUnixServer(service: PiServerService, options: UnixServerOptions): PiServer;
5
+ /** Compose Server with one Unix-domain socket listener. */
6
+ export declare function createUnixServer<TMetadata extends SessionMetadata>(host: ServerHost<TMetadata>, options: UnixServerOptions): Server<TMetadata>;
6
7
  //# sourceMappingURL=preset.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"preset.d.ts","sourceRoot":"","sources":["../../../src/transports/unix/preset.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEtD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD,6DAA6D;AAC7D,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,iBAAiB,GAAG,QAAQ,CAgB/F","sourcesContent":["import { PiServer } from \"../../server.ts\";\nimport type { PiServerService } from \"../../types.ts\";\nimport { createUnixListener } from \"./listener.ts\";\nimport type { UnixServerOptions } from \"./types.ts\";\n\n/** Compose PiServer with one Unix-domain socket listener. */\nexport function createUnixServer(service: PiServerService, options: UnixServerOptions): PiServer {\n\tconst listener = createUnixListener({\n\t\tpath: options.path,\n\t\tmode: options.mode,\n\t\tmaxFrameLength: options.maxFrameLength,\n\t\tmaxPendingBytes: options.maxPendingBytes,\n\t\tgracefulCloseTimeoutMs: options.gracefulCloseTimeoutMs,\n\t\tonError: options.onError,\n\t});\n\treturn new PiServer(service, {\n\t\tlisteners: [listener],\n\t\tmaxFrameLength: options.maxFrameLength,\n\t\thandshakeTimeoutMs: options.handshakeTimeoutMs,\n\t\tserverId: options.serverId,\n\t\tonError: options.onError,\n\t});\n}\n"]}
1
+ {"version":3,"file":"preset.d.ts","sourceRoot":"","sources":["../../../src/transports/unix/preset.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD,2DAA2D;AAC3D,wBAAgB,gBAAgB,CAAC,SAAS,SAAS,eAAe,EACjE,IAAI,EAAE,UAAU,CAAC,SAAS,CAAC,EAC3B,OAAO,EAAE,iBAAiB,GACxB,MAAM,CAAC,SAAS,CAAC,CAiBnB","sourcesContent":["import type { SessionMetadata } from \"@earendil-works/pi-agent-core\";\nimport { Server } from \"../../server.ts\";\nimport type { ServerHost } from \"../../types.ts\";\nimport { createUnixListener } from \"./listener.ts\";\nimport type { UnixServerOptions } from \"./types.ts\";\n\n/** Compose Server with one Unix-domain socket listener. */\nexport function createUnixServer<TMetadata extends SessionMetadata>(\n\thost: ServerHost<TMetadata>,\n\toptions: UnixServerOptions,\n): Server<TMetadata> {\n\tconst listener = createUnixListener({\n\t\tpath: options.path,\n\t\tmode: options.mode,\n\t\tmaxFrameLength: options.maxFrameLength,\n\t\tmaxPendingBytes: options.maxPendingBytes,\n\t\tgracefulCloseTimeoutMs: options.gracefulCloseTimeoutMs,\n\t\tonError: options.onError,\n\t});\n\treturn new Server(host, {\n\t\tlisteners: [listener],\n\t\tmaxFrameLength: options.maxFrameLength,\n\t\thandshakeTimeoutMs: options.handshakeTimeoutMs,\n\t\tonConnectionCountChanged: options.onConnectionCountChanged,\n\t\tserverId: options.serverId,\n\t\tonError: options.onError,\n\t});\n}\n"]}
@@ -1,7 +1,7 @@
1
- import { PiServer } from "../../server.js";
1
+ import { Server } from "../../server.js";
2
2
  import { createUnixListener } from "./listener.js";
3
- /** Compose PiServer with one Unix-domain socket listener. */
4
- export function createUnixServer(service, options) {
3
+ /** Compose Server with one Unix-domain socket listener. */
4
+ export function createUnixServer(host, options) {
5
5
  const listener = createUnixListener({
6
6
  path: options.path,
7
7
  mode: options.mode,
@@ -10,10 +10,11 @@ export function createUnixServer(service, options) {
10
10
  gracefulCloseTimeoutMs: options.gracefulCloseTimeoutMs,
11
11
  onError: options.onError,
12
12
  });
13
- return new PiServer(service, {
13
+ return new Server(host, {
14
14
  listeners: [listener],
15
15
  maxFrameLength: options.maxFrameLength,
16
16
  handshakeTimeoutMs: options.handshakeTimeoutMs,
17
+ onConnectionCountChanged: options.onConnectionCountChanged,
17
18
  serverId: options.serverId,
18
19
  onError: options.onError,
19
20
  });
@@ -1 +1 @@
1
- {"version":3,"file":"preset.js","sourceRoot":"","sources":["../../../src/transports/unix/preset.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAE3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAGnD,6DAA6D;AAC7D,MAAM,UAAU,gBAAgB,CAAC,OAAwB,EAAE,OAA0B,EAAY;IAChG,MAAM,QAAQ,GAAG,kBAAkB,CAAC;QACnC,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,cAAc,EAAE,OAAO,CAAC,cAAc;QACtC,eAAe,EAAE,OAAO,CAAC,eAAe;QACxC,sBAAsB,EAAE,OAAO,CAAC,sBAAsB;QACtD,OAAO,EAAE,OAAO,CAAC,OAAO;KACxB,CAAC,CAAC;IACH,OAAO,IAAI,QAAQ,CAAC,OAAO,EAAE;QAC5B,SAAS,EAAE,CAAC,QAAQ,CAAC;QACrB,cAAc,EAAE,OAAO,CAAC,cAAc;QACtC,kBAAkB,EAAE,OAAO,CAAC,kBAAkB;QAC9C,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,OAAO,EAAE,OAAO,CAAC,OAAO;KACxB,CAAC,CAAC;AAAA,CACH","sourcesContent":["import { PiServer } from \"../../server.ts\";\nimport type { PiServerService } from \"../../types.ts\";\nimport { createUnixListener } from \"./listener.ts\";\nimport type { UnixServerOptions } from \"./types.ts\";\n\n/** Compose PiServer with one Unix-domain socket listener. */\nexport function createUnixServer(service: PiServerService, options: UnixServerOptions): PiServer {\n\tconst listener = createUnixListener({\n\t\tpath: options.path,\n\t\tmode: options.mode,\n\t\tmaxFrameLength: options.maxFrameLength,\n\t\tmaxPendingBytes: options.maxPendingBytes,\n\t\tgracefulCloseTimeoutMs: options.gracefulCloseTimeoutMs,\n\t\tonError: options.onError,\n\t});\n\treturn new PiServer(service, {\n\t\tlisteners: [listener],\n\t\tmaxFrameLength: options.maxFrameLength,\n\t\thandshakeTimeoutMs: options.handshakeTimeoutMs,\n\t\tserverId: options.serverId,\n\t\tonError: options.onError,\n\t});\n}\n"]}
1
+ {"version":3,"file":"preset.js","sourceRoot":"","sources":["../../../src/transports/unix/preset.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAEzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAGnD,2DAA2D;AAC3D,MAAM,UAAU,gBAAgB,CAC/B,IAA2B,EAC3B,OAA0B,EACN;IACpB,MAAM,QAAQ,GAAG,kBAAkB,CAAC;QACnC,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,cAAc,EAAE,OAAO,CAAC,cAAc;QACtC,eAAe,EAAE,OAAO,CAAC,eAAe;QACxC,sBAAsB,EAAE,OAAO,CAAC,sBAAsB;QACtD,OAAO,EAAE,OAAO,CAAC,OAAO;KACxB,CAAC,CAAC;IACH,OAAO,IAAI,MAAM,CAAC,IAAI,EAAE;QACvB,SAAS,EAAE,CAAC,QAAQ,CAAC;QACrB,cAAc,EAAE,OAAO,CAAC,cAAc;QACtC,kBAAkB,EAAE,OAAO,CAAC,kBAAkB;QAC9C,wBAAwB,EAAE,OAAO,CAAC,wBAAwB;QAC1D,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,OAAO,EAAE,OAAO,CAAC,OAAO;KACxB,CAAC,CAAC;AAAA,CACH","sourcesContent":["import type { SessionMetadata } from \"@earendil-works/pi-agent-core\";\nimport { Server } from \"../../server.ts\";\nimport type { ServerHost } from \"../../types.ts\";\nimport { createUnixListener } from \"./listener.ts\";\nimport type { UnixServerOptions } from \"./types.ts\";\n\n/** Compose Server with one Unix-domain socket listener. */\nexport function createUnixServer<TMetadata extends SessionMetadata>(\n\thost: ServerHost<TMetadata>,\n\toptions: UnixServerOptions,\n): Server<TMetadata> {\n\tconst listener = createUnixListener({\n\t\tpath: options.path,\n\t\tmode: options.mode,\n\t\tmaxFrameLength: options.maxFrameLength,\n\t\tmaxPendingBytes: options.maxPendingBytes,\n\t\tgracefulCloseTimeoutMs: options.gracefulCloseTimeoutMs,\n\t\tonError: options.onError,\n\t});\n\treturn new Server(host, {\n\t\tlisteners: [listener],\n\t\tmaxFrameLength: options.maxFrameLength,\n\t\thandshakeTimeoutMs: options.handshakeTimeoutMs,\n\t\tonConnectionCountChanged: options.onConnectionCountChanged,\n\t\tserverId: options.serverId,\n\t\tonError: options.onError,\n\t});\n}\n"]}
@@ -1,4 +1,4 @@
1
- import type { PiServerOptions } from "../../types.ts";
1
+ import type { ServerOptions } from "../../types.ts";
2
2
  export interface UnixListenerOptions {
3
3
  path: string;
4
4
  /** Socket filesystem permissions. Defaults to owner read/write only (0o600). */
@@ -10,6 +10,6 @@ export interface UnixListenerOptions {
10
10
  maxFrameLength?: number;
11
11
  onError?: (error: Error) => void;
12
12
  }
13
- export interface UnixServerOptions extends Omit<PiServerOptions, "listeners">, UnixListenerOptions {
13
+ export interface UnixServerOptions extends Omit<ServerOptions, "listeners">, UnixListenerOptions {
14
14
  }
15
15
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/transports/unix/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEtD,MAAM,WAAW,mBAAmB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,gFAAgF;IAChF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qFAAqF;IACrF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,0FAA0F;IAC1F,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CACjC;AAED,MAAM,WAAW,iBAAkB,SAAQ,IAAI,CAAC,eAAe,EAAE,WAAW,CAAC,EAAE,mBAAmB;CAAG","sourcesContent":["import type { PiServerOptions } from \"../../types.ts\";\n\nexport interface UnixListenerOptions {\n\tpath: string;\n\t/** Socket filesystem permissions. Defaults to owner read/write only (0o600). */\n\tmode?: number;\n\t/** Maximum framed bytes queued per connection before a slow peer is disconnected. */\n\tmaxPendingBytes?: number;\n\tgracefulCloseTimeoutMs?: number;\n\t/** Used to derive and validate maxPendingBytes. Must match the server when customized. */\n\tmaxFrameLength?: number;\n\tonError?: (error: Error) => void;\n}\n\nexport interface UnixServerOptions extends Omit<PiServerOptions, \"listeners\">, UnixListenerOptions {}\n"]}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/transports/unix/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD,MAAM,WAAW,mBAAmB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,gFAAgF;IAChF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qFAAqF;IACrF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,0FAA0F;IAC1F,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CACjC;AAED,MAAM,WAAW,iBAAkB,SAAQ,IAAI,CAAC,aAAa,EAAE,WAAW,CAAC,EAAE,mBAAmB;CAAG","sourcesContent":["import type { ServerOptions } from \"../../types.ts\";\n\nexport interface UnixListenerOptions {\n\tpath: string;\n\t/** Socket filesystem permissions. Defaults to owner read/write only (0o600). */\n\tmode?: number;\n\t/** Maximum framed bytes queued per connection before a slow peer is disconnected. */\n\tmaxPendingBytes?: number;\n\tgracefulCloseTimeoutMs?: number;\n\t/** Used to derive and validate maxPendingBytes. Must match the server when customized. */\n\tmaxFrameLength?: number;\n\tonError?: (error: Error) => void;\n}\n\nexport interface UnixServerOptions extends Omit<ServerOptions, \"listeners\">, UnixListenerOptions {}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/transports/unix/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { PiServerOptions } from \"../../types.ts\";\n\nexport interface UnixListenerOptions {\n\tpath: string;\n\t/** Socket filesystem permissions. Defaults to owner read/write only (0o600). */\n\tmode?: number;\n\t/** Maximum framed bytes queued per connection before a slow peer is disconnected. */\n\tmaxPendingBytes?: number;\n\tgracefulCloseTimeoutMs?: number;\n\t/** Used to derive and validate maxPendingBytes. Must match the server when customized. */\n\tmaxFrameLength?: number;\n\tonError?: (error: Error) => void;\n}\n\nexport interface UnixServerOptions extends Omit<PiServerOptions, \"listeners\">, UnixListenerOptions {}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/transports/unix/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { ServerOptions } from \"../../types.ts\";\n\nexport interface UnixListenerOptions {\n\tpath: string;\n\t/** Socket filesystem permissions. Defaults to owner read/write only (0o600). */\n\tmode?: number;\n\t/** Maximum framed bytes queued per connection before a slow peer is disconnected. */\n\tmaxPendingBytes?: number;\n\tgracefulCloseTimeoutMs?: number;\n\t/** Used to derive and validate maxPendingBytes. Must match the server when customized. */\n\tmaxFrameLength?: number;\n\tonError?: (error: Error) => void;\n}\n\nexport interface UnixServerOptions extends Omit<ServerOptions, \"listeners\">, UnixListenerOptions {}\n"]}
package/dist/types.d.ts CHANGED
@@ -1,56 +1,49 @@
1
- import type { Command, ModelMetadata, ModelRef, SessionMetadata, SessionPhase, SessionSnapshot, ThinkingLevel, TranscriptProgress } from "@earendil-works/pi-protocol";
2
- import type { PiServerError } from "./errors.ts";
3
- import type { PiServerListener } from "./listener.ts";
4
- export interface PiServerOptions {
5
- listeners: readonly PiServerListener[];
1
+ import type { JsonValue, ServiceCall, ServiceProviderUpdate } from "@earendil-works/chord";
2
+ import type { Context, SessionMetadata } from "@earendil-works/pi-agent-core";
3
+ import type { ServerListener } from "./listener.ts";
4
+ export interface ServerOptions {
5
+ listeners: readonly ServerListener[];
6
+ /** Stable logical server identity supplied by the installation or profile. */
7
+ serverId: string;
6
8
  maxFrameLength?: number;
7
9
  handshakeTimeoutMs?: number;
8
- serverId?: string;
10
+ onConnectionCountChanged?: (count: number) => void;
9
11
  onError?: (error: Error) => void;
10
12
  }
11
13
  export type MaybePromise<T> = T | Promise<T>;
12
- export type PromptInput = Omit<Extract<Command, {
13
- command: "prompt";
14
- }>, "command" | "sessionId">;
15
- export type SteerInput = Omit<Extract<Command, {
16
- command: "steer";
17
- }>, "command" | "sessionId">;
18
- export interface CreateSessionOptions {
19
- /** A collision-resistant ID assigned by PiServer. The service must persist this exact ID. */
20
- id: string;
21
- cwd?: string;
22
- name?: string;
23
- model?: ModelRef;
24
- thinkingLevel?: ThinkingLevel;
14
+ /** One presentation connection's live capability for a hosted Session. */
15
+ export interface RoutedSessionAttachment {
16
+ /** Route one contract-agnostic service operation to the attached Session endpoint. */
17
+ invokeService(call: ServiceCall, publish: (subscriptionId: string, update: ServiceProviderUpdate, context: Context) => MaybePromise<void>, context: Context): Promise<JsonValue | undefined>;
18
+ release(context: Context): MaybePromise<void>;
25
19
  }
26
- export type PiSessionRuntimeEvent = {
27
- type: "snapshot";
28
- } | {
29
- type: "progress";
30
- progress: TranscriptProgress;
31
- } | {
32
- type: "error";
33
- error: PiServerError;
34
- };
35
- /** One acquired durable session. Conflicting operations must reject rather than queue. */
36
- export interface PiSessionRuntime {
37
- snapshot(): MaybePromise<SessionSnapshot>;
38
- getPhase(): SessionPhase;
39
- prompt(input: PromptInput): Promise<void>;
40
- steer(input: SteerInput): Promise<void>;
41
- abort(): Promise<void>;
42
- setModel(model: ModelRef): Promise<void>;
43
- setThinking(thinkingLevel: ThinkingLevel): Promise<void>;
44
- subscribe(listener: (event: PiSessionRuntimeEvent) => void): () => void;
45
- dispose(): Promise<void>;
20
+ /** Presentation-scoped routing capabilities available to server service implementations. */
21
+ export interface RoutedServerPresentation {
22
+ attachSession(sessionId: string, context: Context): Promise<void>;
23
+ detachSession(context: Context): Promise<void>;
24
+ /** Release routed attachments and handles before the application deletes durable metadata. */
25
+ prepareSessionRemoval(sessionId: string, context: Context): Promise<void>;
46
26
  }
47
- /** Service boundary for durable sessions and exclusively acquired runtimes. */
48
- export interface PiServerService {
49
- listSessions(): Promise<SessionMetadata[]>;
50
- listModels(): Promise<ModelMetadata[]>;
51
- createSession(options: CreateSessionOptions): Promise<PiSessionRuntime>;
52
- openSession(sessionId: string): Promise<PiSessionRuntime>;
27
+ /** One connection's server-scoped service endpoint. */
28
+ export interface RoutedServerServiceAttachment {
29
+ invokeService(call: ServiceCall, publish: (subscriptionId: string, update: ServiceProviderUpdate, context: Context) => MaybePromise<void>, context: Context): Promise<JsonValue | undefined>;
30
+ release(context: Context): MaybePromise<void>;
31
+ }
32
+ export interface RoutedServerServiceHost {
33
+ attachClient(presentation: RoutedServerPresentation, context: Context): MaybePromise<RoutedServerServiceAttachment>;
34
+ }
35
+ /** A process-safe handle that acquires presentation-scoped Session capabilities. */
36
+ export interface RoutedSessionHandle {
37
+ attachClient(context: Context): MaybePromise<RoutedSessionAttachment>;
38
+ /** Resolves with an error for unexpected termination, or undefined after an expected close. */
39
+ readonly terminated?: Promise<Error | undefined>;
40
+ close(context: Context): Promise<void>;
41
+ }
42
+ /** Application capabilities used by server-wide management and Session routing. */
43
+ export interface ServerHost<TMetadata extends SessionMetadata = SessionMetadata> {
44
+ readonly serverServices: RoutedServerServiceHost;
45
+ /** Resolve one durable Session ID or throw a bounded routing error. */
46
+ resolveSession(sessionId: string, context: Context): Promise<TMetadata>;
47
+ openSession(metadata: TMetadata, context: Context): Promise<RoutedSessionHandle>;
53
48
  }
54
- export type SessionRuntime = PiSessionRuntime;
55
- export type SessionRuntimeEvent = PiSessionRuntimeEvent;
56
49
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,OAAO,EACP,aAAa,EACb,QAAQ,EACR,eAAe,EACf,YAAY,EACZ,eAAe,EACf,aAAa,EACb,kBAAkB,EAClB,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEtD,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACvC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CACjC;AAED,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;AAE7C,MAAM,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE;IAAE,OAAO,EAAE,QAAQ,CAAA;CAAE,CAAC,EAAE,SAAS,GAAG,WAAW,CAAC,CAAC;AACjG,MAAM,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,EAAE,SAAS,GAAG,WAAW,CAAC,CAAC;AAE/F,MAAM,WAAW,oBAAoB;IACpC,6FAA6F;IAC7F,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,aAAa,CAAC,EAAE,aAAa,CAAC;CAC9B;AAED,MAAM,MAAM,qBAAqB,GAC9B;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GACpB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,kBAAkB,CAAA;CAAE,GAClD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,aAAa,CAAA;CAAE,CAAC;AAE3C,0FAA0F;AAC1F,MAAM,WAAW,gBAAgB;IAChC,QAAQ,IAAI,YAAY,CAAC,eAAe,CAAC,CAAC;IAC1C,QAAQ,IAAI,YAAY,CAAC;IACzB,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,WAAW,CAAC,aAAa,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzD,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IACxE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB;AAED,+EAA+E;AAC/E,MAAM,WAAW,eAAe;IAC/B,YAAY,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;IAC3C,UAAU,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;IACvC,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACxE,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;CAC1D;AAED,MAAM,MAAM,cAAc,GAAG,gBAAgB,CAAC;AAC9C,MAAM,MAAM,mBAAmB,GAAG,qBAAqB,CAAC","sourcesContent":["import type {\n\tCommand,\n\tModelMetadata,\n\tModelRef,\n\tSessionMetadata,\n\tSessionPhase,\n\tSessionSnapshot,\n\tThinkingLevel,\n\tTranscriptProgress,\n} from \"@earendil-works/pi-protocol\";\nimport type { PiServerError } from \"./errors.ts\";\nimport type { PiServerListener } from \"./listener.ts\";\n\nexport interface PiServerOptions {\n\tlisteners: readonly PiServerListener[];\n\tmaxFrameLength?: number;\n\thandshakeTimeoutMs?: number;\n\tserverId?: string;\n\tonError?: (error: Error) => void;\n}\n\nexport type MaybePromise<T> = T | Promise<T>;\n\nexport type PromptInput = Omit<Extract<Command, { command: \"prompt\" }>, \"command\" | \"sessionId\">;\nexport type SteerInput = Omit<Extract<Command, { command: \"steer\" }>, \"command\" | \"sessionId\">;\n\nexport interface CreateSessionOptions {\n\t/** A collision-resistant ID assigned by PiServer. The service must persist this exact ID. */\n\tid: string;\n\tcwd?: string;\n\tname?: string;\n\tmodel?: ModelRef;\n\tthinkingLevel?: ThinkingLevel;\n}\n\nexport type PiSessionRuntimeEvent =\n\t| { type: \"snapshot\" }\n\t| { type: \"progress\"; progress: TranscriptProgress }\n\t| { type: \"error\"; error: PiServerError };\n\n/** One acquired durable session. Conflicting operations must reject rather than queue. */\nexport interface PiSessionRuntime {\n\tsnapshot(): MaybePromise<SessionSnapshot>;\n\tgetPhase(): SessionPhase;\n\tprompt(input: PromptInput): Promise<void>;\n\tsteer(input: SteerInput): Promise<void>;\n\tabort(): Promise<void>;\n\tsetModel(model: ModelRef): Promise<void>;\n\tsetThinking(thinkingLevel: ThinkingLevel): Promise<void>;\n\tsubscribe(listener: (event: PiSessionRuntimeEvent) => void): () => void;\n\tdispose(): Promise<void>;\n}\n\n/** Service boundary for durable sessions and exclusively acquired runtimes. */\nexport interface PiServerService {\n\tlistSessions(): Promise<SessionMetadata[]>;\n\tlistModels(): Promise<ModelMetadata[]>;\n\tcreateSession(options: CreateSessionOptions): Promise<PiSessionRuntime>;\n\topenSession(sessionId: string): Promise<PiSessionRuntime>;\n}\n\nexport type SessionRuntime = PiSessionRuntime;\nexport type SessionRuntimeEvent = PiSessionRuntimeEvent;\n"]}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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","sourcesContent":["import type { JsonValue, ServiceCall, ServiceProviderUpdate } from \"@earendil-works/chord\";\nimport type { Context, SessionMetadata } from \"@earendil-works/pi-agent-core\";\nimport type { ServerListener } from \"./listener.ts\";\n\nexport interface ServerOptions {\n\tlisteners: readonly ServerListener[];\n\t/** Stable logical server identity supplied by the installation or profile. */\n\tserverId: string;\n\tmaxFrameLength?: number;\n\thandshakeTimeoutMs?: number;\n\tonConnectionCountChanged?: (count: number) => void;\n\tonError?: (error: Error) => void;\n}\n\nexport type MaybePromise<T> = T | Promise<T>;\n\n/** One presentation connection's live capability for a hosted Session. */\nexport interface RoutedSessionAttachment {\n\t/** Route one contract-agnostic service operation to the attached Session endpoint. */\n\tinvokeService(\n\t\tcall: ServiceCall,\n\t\tpublish: (subscriptionId: string, update: ServiceProviderUpdate, context: Context) => MaybePromise<void>,\n\t\tcontext: Context,\n\t): Promise<JsonValue | undefined>;\n\trelease(context: Context): MaybePromise<void>;\n}\n\n/** Presentation-scoped routing capabilities available to server service implementations. */\nexport interface RoutedServerPresentation {\n\tattachSession(sessionId: string, context: Context): Promise<void>;\n\tdetachSession(context: Context): Promise<void>;\n\t/** Release routed attachments and handles before the application deletes durable metadata. */\n\tprepareSessionRemoval(sessionId: string, context: Context): Promise<void>;\n}\n\n/** One connection's server-scoped service endpoint. */\nexport interface RoutedServerServiceAttachment {\n\tinvokeService(\n\t\tcall: ServiceCall,\n\t\tpublish: (subscriptionId: string, update: ServiceProviderUpdate, context: Context) => MaybePromise<void>,\n\t\tcontext: Context,\n\t): Promise<JsonValue | undefined>;\n\trelease(context: Context): MaybePromise<void>;\n}\n\nexport interface RoutedServerServiceHost {\n\tattachClient(presentation: RoutedServerPresentation, context: Context): MaybePromise<RoutedServerServiceAttachment>;\n}\n\n/** A process-safe handle that acquires presentation-scoped Session capabilities. */\nexport interface RoutedSessionHandle {\n\tattachClient(context: Context): MaybePromise<RoutedSessionAttachment>;\n\t/** Resolves with an error for unexpected termination, or undefined after an expected close. */\n\treadonly terminated?: Promise<Error | undefined>;\n\tclose(context: Context): Promise<void>;\n}\n\n/** Application capabilities used by server-wide management and Session routing. */\nexport interface ServerHost<TMetadata extends SessionMetadata = SessionMetadata> {\n\treadonly serverServices: RoutedServerServiceHost;\n\t/** Resolve one durable Session ID or throw a bounded routing error. */\n\tresolveSession(sessionId: string, context: Context): Promise<TMetadata>;\n\topenSession(metadata: TMetadata, context: Context): Promise<RoutedSessionHandle>;\n}\n"]}