@earendil-works/pi-server 0.84.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README.md +50 -0
  3. package/dist/connection.d.ts +28 -0
  4. package/dist/connection.d.ts.map +1 -0
  5. package/dist/connection.js +4 -0
  6. package/dist/connection.js.map +1 -0
  7. package/dist/errors.d.ts +27 -0
  8. package/dist/errors.d.ts.map +1 -0
  9. package/dist/errors.js +45 -0
  10. package/dist/errors.js.map +1 -0
  11. package/dist/index.d.ts +6 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +6 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/listener.d.ts +10 -0
  16. package/dist/listener.d.ts.map +1 -0
  17. package/dist/listener.js +2 -0
  18. package/dist/listener.js.map +1 -0
  19. package/dist/protocol.d.ts +22 -0
  20. package/dist/protocol.d.ts.map +1 -0
  21. package/dist/protocol.js +266 -0
  22. package/dist/protocol.js.map +1 -0
  23. package/dist/server.d.ts +36 -0
  24. package/dist/server.d.ts.map +1 -0
  25. package/dist/server.js +365 -0
  26. package/dist/server.js.map +1 -0
  27. package/dist/sessions.d.ts +2174 -0
  28. package/dist/sessions.d.ts.map +1 -0
  29. package/dist/sessions.js +305 -0
  30. package/dist/sessions.js.map +1 -0
  31. package/dist/snapshots.d.ts +24 -0
  32. package/dist/snapshots.d.ts.map +1 -0
  33. package/dist/snapshots.js +39 -0
  34. package/dist/snapshots.js.map +1 -0
  35. package/dist/testing/client.d.ts +31 -0
  36. package/dist/testing/client.d.ts.map +1 -0
  37. package/dist/testing/client.js +118 -0
  38. package/dist/testing/client.js.map +1 -0
  39. package/dist/testing/index.d.ts +6 -0
  40. package/dist/testing/index.d.ts.map +1 -0
  41. package/dist/testing/index.js +4 -0
  42. package/dist/testing/index.js.map +1 -0
  43. package/dist/testing/server.d.ts +12 -0
  44. package/dist/testing/server.d.ts.map +1 -0
  45. package/dist/testing/server.js +17 -0
  46. package/dist/testing/server.js.map +1 -0
  47. package/dist/testing/service.d.ts +59 -0
  48. package/dist/testing/service.d.ts.map +1 -0
  49. package/dist/testing/service.js +240 -0
  50. package/dist/testing/service.js.map +1 -0
  51. package/dist/transports/unix/index.d.ts +4 -0
  52. package/dist/transports/unix/index.d.ts.map +1 -0
  53. package/dist/transports/unix/index.js +3 -0
  54. package/dist/transports/unix/index.js.map +1 -0
  55. package/dist/transports/unix/listener.d.ts +25 -0
  56. package/dist/transports/unix/listener.d.ts.map +1 -0
  57. package/dist/transports/unix/listener.js +432 -0
  58. package/dist/transports/unix/listener.js.map +1 -0
  59. package/dist/transports/unix/preset.d.ts +6 -0
  60. package/dist/transports/unix/preset.d.ts.map +1 -0
  61. package/dist/transports/unix/preset.js +21 -0
  62. package/dist/transports/unix/preset.js.map +1 -0
  63. package/dist/transports/unix/types.d.ts +15 -0
  64. package/dist/transports/unix/types.d.ts.map +1 -0
  65. package/dist/transports/unix/types.js +2 -0
  66. package/dist/transports/unix/types.js.map +1 -0
  67. package/dist/types.d.ts +56 -0
  68. package/dist/types.d.ts.map +1 -0
  69. package/dist/types.js +2 -0
  70. package/dist/types.js.map +1 -0
  71. package/package.json +57 -0
@@ -0,0 +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"]}
@@ -0,0 +1,432 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { chmod, link, lstat, mkdir, rename, unlink } from "node:fs/promises";
3
+ import { createConnection, createServer } from "node:net";
4
+ import { dirname, join } from "node:path";
5
+ import { DEFAULT_MAX_FRAME_LENGTH } from "@earendil-works/pi-protocol";
6
+ const DEFAULT_SOCKET_MODE = 0o600;
7
+ const DEFAULT_GRACEFUL_CLOSE_TIMEOUT_MS = 5_000;
8
+ const MAX_UINT32 = 0xffff_ffff;
9
+ const MAX_TIMER_DELAY_MS = 2_147_483_647;
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
+ class UnixListener {
20
+ options;
21
+ path;
22
+ mode;
23
+ connections = new Set();
24
+ server;
25
+ socketIdentity;
26
+ ownedBindPath;
27
+ boundPath;
28
+ closing = false;
29
+ closePromise;
30
+ accept;
31
+ constructor(options) {
32
+ this.options = resolveUnixListenerOptions(options);
33
+ this.path = this.options.path;
34
+ this.mode = this.options.mode;
35
+ }
36
+ get address() {
37
+ return this.boundPath;
38
+ }
39
+ async start(accept) {
40
+ if (this.server)
41
+ throw new Error("Unix listener is already started");
42
+ if (this.closing)
43
+ throw new Error("Unix listener is closing or closed");
44
+ this.accept = accept;
45
+ const ownedBindPath = getOwnedBindPath(this.path);
46
+ validateUnixSocketPath(ownedBindPath, "PiServer private Unix bind path");
47
+ await mkdir(dirname(this.path), { recursive: true, mode: 0o700 });
48
+ await removeStaleSocket(this.path);
49
+ await removeStaleSocket(ownedBindPath);
50
+ this.ownedBindPath = ownedBindPath;
51
+ const server = createServer((socket) => this.acceptSocket(socket));
52
+ server.on("error", (error) => this.reportError(error));
53
+ this.server = server;
54
+ try {
55
+ await new Promise((resolve, reject) => {
56
+ const onError = (error) => {
57
+ server.off("listening", onListening);
58
+ reject(error);
59
+ };
60
+ const onListening = () => {
61
+ server.off("error", onError);
62
+ resolve();
63
+ };
64
+ server.once("error", onError);
65
+ server.once("listening", onListening);
66
+ server.listen(ownedBindPath);
67
+ });
68
+ const stats = await lstat(ownedBindPath);
69
+ if (!stats.isSocket())
70
+ throw new Error(`Unix listener path is not a socket after binding: ${ownedBindPath}`);
71
+ this.socketIdentity = { dev: stats.dev, ino: stats.ino };
72
+ await link(ownedBindPath, this.path);
73
+ await setSocketMode(this.path, this.mode);
74
+ this.boundPath = this.path;
75
+ }
76
+ catch (error) {
77
+ await this.closeServerAndCleanup(server);
78
+ this.server = undefined;
79
+ throw error;
80
+ }
81
+ }
82
+ async close() {
83
+ if (this.closePromise)
84
+ return this.closePromise;
85
+ this.closing = true;
86
+ this.closePromise = this.closeInternal();
87
+ return this.closePromise;
88
+ }
89
+ acceptSocket(socket) {
90
+ if (this.closing) {
91
+ socket.destroy();
92
+ return;
93
+ }
94
+ const connection = new UnixByteConnection(socket, this.options.gracefulCloseTimeoutMs, this.options.maxPendingBytes);
95
+ this.connections.add(connection);
96
+ const accept = this.accept;
97
+ if (!accept) {
98
+ socket.destroy();
99
+ return;
100
+ }
101
+ const handler = accept(connection);
102
+ socket.on("data", (chunk) => {
103
+ handler.onData(new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength));
104
+ });
105
+ socket.on("error", (error) => {
106
+ handler.onError(error);
107
+ socket.destroy();
108
+ });
109
+ socket.once("close", () => {
110
+ connection.markClosed();
111
+ this.connections.delete(connection);
112
+ handler.onClose();
113
+ });
114
+ }
115
+ async closeInternal() {
116
+ this.boundPath = undefined;
117
+ const serverClosed = this.server ? this.closeServerAndCleanup(this.server) : this.cleanupOwnedSocket();
118
+ await Promise.all([...this.connections].map((connection) => connection.close()));
119
+ await serverClosed;
120
+ if (this.ownedBindPath)
121
+ await removePath(this.ownedBindPath);
122
+ this.ownedBindPath = undefined;
123
+ this.connections.clear();
124
+ this.server = undefined;
125
+ }
126
+ async closeServerAndCleanup(server) {
127
+ try {
128
+ await closeNetServer(server, (error) => this.reportError(error));
129
+ }
130
+ finally {
131
+ await this.cleanupOwnedSocket();
132
+ if (this.ownedBindPath)
133
+ await removePath(this.ownedBindPath);
134
+ this.ownedBindPath = undefined;
135
+ }
136
+ }
137
+ async cleanupOwnedSocket() {
138
+ const identity = this.socketIdentity;
139
+ this.socketIdentity = undefined;
140
+ if (!identity)
141
+ return;
142
+ let current;
143
+ try {
144
+ current = await lstat(this.path);
145
+ }
146
+ catch (error) {
147
+ if (isErrorCode(error, "ENOENT"))
148
+ return;
149
+ throw error;
150
+ }
151
+ if (!current.isSocket() || current.dev !== identity.dev || current.ino !== identity.ino)
152
+ return;
153
+ const preserved = join(dirname(this.path), `.c-${randomUUID().slice(0, 6)}`);
154
+ try {
155
+ await rename(this.path, preserved);
156
+ }
157
+ catch (error) {
158
+ if (isErrorCode(error, "ENOENT"))
159
+ return;
160
+ throw error;
161
+ }
162
+ const moved = await lstat(preserved);
163
+ if (moved.isSocket() && moved.dev === identity.dev && moved.ino === identity.ino) {
164
+ await removePath(preserved);
165
+ return;
166
+ }
167
+ try {
168
+ await lstat(this.path);
169
+ }
170
+ catch (error) {
171
+ if (isErrorCode(error, "ENOENT"))
172
+ await rename(preserved, this.path);
173
+ else
174
+ throw error;
175
+ }
176
+ throw new Error(`Unix listener path changed during cleanup; preserved replacement at ${preserved}`);
177
+ }
178
+ reportError(error) {
179
+ try {
180
+ this.options.onError?.(error instanceof Error ? error : new Error(String(error)));
181
+ }
182
+ catch {
183
+ // Error observers cannot affect listener state.
184
+ }
185
+ }
186
+ }
187
+ /** @internal Exported only for transport-level verification. */
188
+ export class UnixByteConnection {
189
+ socket;
190
+ gracefulCloseTimeoutMs;
191
+ maxPendingBytes;
192
+ pendingBytes = 0;
193
+ closedValue = false;
194
+ closing = false;
195
+ writeTail = Promise.resolve();
196
+ closePromise;
197
+ resolveClose;
198
+ constructor(socket, gracefulCloseTimeoutMs, maxPendingBytes) {
199
+ this.socket = socket;
200
+ this.gracefulCloseTimeoutMs = gracefulCloseTimeoutMs;
201
+ this.maxPendingBytes = maxPendingBytes;
202
+ }
203
+ get closed() {
204
+ return this.closedValue;
205
+ }
206
+ send(chunk) {
207
+ if (!(chunk instanceof Uint8Array)) {
208
+ return Promise.reject(new TypeError("Unix connection chunks must be Uint8Array"));
209
+ }
210
+ if (this.closedValue || this.closing)
211
+ return Promise.reject(new Error("Unix connection is closed"));
212
+ if (this.pendingBytes + chunk.byteLength > this.maxPendingBytes) {
213
+ return Promise.reject(new Error("Unix connection exceeded its pending byte limit"));
214
+ }
215
+ this.pendingBytes += chunk.byteLength;
216
+ const bytes = chunk.slice();
217
+ const write = this.writeTail.then(() => this.write(bytes));
218
+ const tracked = write.finally(() => {
219
+ this.pendingBytes -= bytes.byteLength;
220
+ });
221
+ this.writeTail = tracked.catch(() => { });
222
+ return tracked;
223
+ }
224
+ close(finalChunk) {
225
+ if (this.closedValue || this.socket.destroyed) {
226
+ this.markClosed();
227
+ return Promise.resolve();
228
+ }
229
+ if (this.closePromise)
230
+ return this.closePromise;
231
+ this.closing = true;
232
+ const finalBytes = finalChunk?.slice();
233
+ this.closePromise = new Promise((resolve) => {
234
+ this.resolveClose = resolve;
235
+ const timer = setTimeout(() => {
236
+ if (!this.socket.destroyed)
237
+ this.socket.destroy();
238
+ this.markClosed();
239
+ }, this.gracefulCloseTimeoutMs);
240
+ timer.unref();
241
+ this.socket.once("close", () => clearTimeout(timer));
242
+ void this.writeTail.then(() => {
243
+ if (this.socket.destroyed) {
244
+ this.markClosed();
245
+ return;
246
+ }
247
+ try {
248
+ if (finalBytes)
249
+ this.socket.end(finalBytes);
250
+ else
251
+ this.socket.end();
252
+ }
253
+ catch {
254
+ this.socket.destroy();
255
+ }
256
+ });
257
+ });
258
+ return this.closePromise;
259
+ }
260
+ markClosed() {
261
+ if (this.closedValue)
262
+ return;
263
+ this.closedValue = true;
264
+ this.closing = true;
265
+ this.resolveClose?.();
266
+ this.resolveClose = undefined;
267
+ }
268
+ write(chunk) {
269
+ if (this.closedValue || this.closing || !this.socket.writable) {
270
+ return Promise.reject(new Error("Unix connection is closed"));
271
+ }
272
+ return new Promise((resolve, reject) => {
273
+ let settled = false;
274
+ const onClose = () => finish(new Error("Unix connection closed during write"));
275
+ const finish = (error) => {
276
+ if (settled)
277
+ return;
278
+ settled = true;
279
+ this.socket.off("close", onClose);
280
+ if (error)
281
+ reject(error);
282
+ else
283
+ resolve();
284
+ };
285
+ this.socket.once("close", onClose);
286
+ try {
287
+ this.socket.write(chunk, finish);
288
+ }
289
+ catch (error) {
290
+ finish(error instanceof Error ? error : new Error(String(error)));
291
+ }
292
+ });
293
+ }
294
+ }
295
+ function getOwnedBindPath(path) {
296
+ const suffix = createHash("sha256").update(path).digest("hex").slice(0, 8);
297
+ return join(dirname(path), `.p-${suffix}`);
298
+ }
299
+ async function removeStaleSocket(path) {
300
+ let original;
301
+ try {
302
+ original = await lstat(path);
303
+ }
304
+ catch (error) {
305
+ if (isErrorCode(error, "ENOENT"))
306
+ return;
307
+ throw error;
308
+ }
309
+ if (!original.isSocket())
310
+ throw new Error(`Refusing to remove non-socket Unix listener path: ${path}`);
311
+ if (await isSocketLive(path))
312
+ throw new Error(`Unix listener is already running: ${path}`);
313
+ const preserved = join(dirname(path), `.s-${randomUUID().slice(0, 6)}`);
314
+ try {
315
+ await rename(path, preserved);
316
+ }
317
+ catch (error) {
318
+ if (isErrorCode(error, "ENOENT"))
319
+ return;
320
+ throw error;
321
+ }
322
+ const current = await lstat(preserved);
323
+ if (!current.isSocket() || current.dev !== original.dev || current.ino !== original.ino) {
324
+ try {
325
+ await lstat(path);
326
+ }
327
+ catch (error) {
328
+ if (isErrorCode(error, "ENOENT"))
329
+ await rename(preserved, path);
330
+ else
331
+ throw error;
332
+ }
333
+ throw new Error(`Unix listener path changed while checking for a stale socket: ${path}`);
334
+ }
335
+ await removePath(preserved);
336
+ }
337
+ async function removePath(path) {
338
+ try {
339
+ await unlink(path);
340
+ }
341
+ catch (error) {
342
+ if (!isErrorCode(error, "ENOENT"))
343
+ throw error;
344
+ }
345
+ }
346
+ function isSocketLive(path) {
347
+ return new Promise((resolve, reject) => {
348
+ const socket = createConnection(path);
349
+ let settled = false;
350
+ let timer;
351
+ const finish = (result, error) => {
352
+ if (settled)
353
+ return;
354
+ settled = true;
355
+ if (timer)
356
+ clearTimeout(timer);
357
+ socket.removeAllListeners();
358
+ socket.destroy();
359
+ if (error)
360
+ reject(error);
361
+ else
362
+ resolve(result);
363
+ };
364
+ socket.once("connect", () => finish(true));
365
+ socket.once("error", (error) => {
366
+ if (["ECONNREFUSED", "ENOENT", "EPIPE", "ECONNRESET"].includes(error.code ?? "")) {
367
+ finish(false);
368
+ return;
369
+ }
370
+ finish(false, error);
371
+ });
372
+ timer = setTimeout(() => finish(true), SOCKET_PROBE_TIMEOUT_MS);
373
+ timer.unref();
374
+ });
375
+ }
376
+ async function setSocketMode(path, mode) {
377
+ if (process.platform === "win32")
378
+ return;
379
+ try {
380
+ await chmod(path, mode);
381
+ }
382
+ catch (error) {
383
+ if (!isErrorCode(error, "ENOSYS") && !isErrorCode(error, "ENOTSUP"))
384
+ throw error;
385
+ }
386
+ }
387
+ function closeNetServer(server, reportError) {
388
+ if (!server.listening)
389
+ return Promise.resolve();
390
+ return new Promise((resolve) => {
391
+ server.close((error) => {
392
+ if (error)
393
+ reportError(error);
394
+ resolve();
395
+ });
396
+ });
397
+ }
398
+ function isErrorCode(error, code) {
399
+ return error instanceof Error && "code" in error && error.code === code;
400
+ }
401
+ export function createUnixListener(options) {
402
+ return new UnixListener(options);
403
+ }
404
+ function resolveUnixListenerOptions(options) {
405
+ validateUnixSocketPath(options.path, "PiServer Unix socket path");
406
+ const mode = options.mode ?? DEFAULT_SOCKET_MODE;
407
+ if (!Number.isInteger(mode) || mode < 0 || mode > 0o777) {
408
+ throw new TypeError("PiServer Unix socket mode must be an integer between 0 and 0o777");
409
+ }
410
+ const maxFrameLength = options.maxFrameLength ?? DEFAULT_MAX_FRAME_LENGTH;
411
+ if (!Number.isSafeInteger(maxFrameLength) || maxFrameLength <= 0 || maxFrameLength > MAX_UINT32) {
412
+ throw new TypeError(`PiServer maxFrameLength must be an integer between 1 and ${MAX_UINT32}`);
413
+ }
414
+ const maxPendingBytes = options.maxPendingBytes ?? maxFrameLength * 4;
415
+ if (!Number.isSafeInteger(maxPendingBytes) || maxPendingBytes < maxFrameLength + 4) {
416
+ throw new TypeError("PiServer maxPendingBytes must be a safe integer at least maxFrameLength + 4");
417
+ }
418
+ const gracefulCloseTimeoutMs = options.gracefulCloseTimeoutMs ?? DEFAULT_GRACEFUL_CLOSE_TIMEOUT_MS;
419
+ if (!Number.isSafeInteger(gracefulCloseTimeoutMs) ||
420
+ gracefulCloseTimeoutMs <= 0 ||
421
+ gracefulCloseTimeoutMs > MAX_TIMER_DELAY_MS) {
422
+ throw new TypeError(`PiServer gracefulCloseTimeoutMs must be an integer between 1 and ${MAX_TIMER_DELAY_MS}`);
423
+ }
424
+ return {
425
+ path: options.path,
426
+ mode,
427
+ maxPendingBytes,
428
+ gracefulCloseTimeoutMs,
429
+ onError: options.onError,
430
+ };
431
+ }
432
+ //# sourceMappingURL=listener.js.map
@@ -0,0 +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"]}
@@ -0,0 +1,6 @@
1
+ import { PiServer } from "../../server.ts";
2
+ import type { PiServerService } from "../../types.ts";
3
+ 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;
6
+ //# sourceMappingURL=preset.d.ts.map
@@ -0,0 +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"]}
@@ -0,0 +1,21 @@
1
+ import { PiServer } from "../../server.js";
2
+ import { createUnixListener } from "./listener.js";
3
+ /** Compose PiServer with one Unix-domain socket listener. */
4
+ export function createUnixServer(service, options) {
5
+ const listener = createUnixListener({
6
+ path: options.path,
7
+ mode: options.mode,
8
+ maxFrameLength: options.maxFrameLength,
9
+ maxPendingBytes: options.maxPendingBytes,
10
+ gracefulCloseTimeoutMs: options.gracefulCloseTimeoutMs,
11
+ onError: options.onError,
12
+ });
13
+ return new PiServer(service, {
14
+ listeners: [listener],
15
+ maxFrameLength: options.maxFrameLength,
16
+ handshakeTimeoutMs: options.handshakeTimeoutMs,
17
+ serverId: options.serverId,
18
+ onError: options.onError,
19
+ });
20
+ }
21
+ //# sourceMappingURL=preset.js.map
@@ -0,0 +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"]}
@@ -0,0 +1,15 @@
1
+ import type { PiServerOptions } from "../../types.ts";
2
+ export interface UnixListenerOptions {
3
+ path: string;
4
+ /** Socket filesystem permissions. Defaults to owner read/write only (0o600). */
5
+ mode?: number;
6
+ /** Maximum framed bytes queued per connection before a slow peer is disconnected. */
7
+ maxPendingBytes?: number;
8
+ gracefulCloseTimeoutMs?: number;
9
+ /** Used to derive and validate maxPendingBytes. Must match the server when customized. */
10
+ maxFrameLength?: number;
11
+ onError?: (error: Error) => void;
12
+ }
13
+ export interface UnixServerOptions extends Omit<PiServerOptions, "listeners">, UnixListenerOptions {
14
+ }
15
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +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"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +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"]}