@earendil-works/pi-client 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.
- package/CHANGELOG.md +4 -0
- package/README.md +37 -28
- package/dist/client.d.ts +20 -15
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +248 -245
- package/dist/client.js.map +1 -1
- package/dist/connection.d.ts +4 -3
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +13 -9
- package/dist/connection.js.map +1 -1
- package/dist/errors.d.ts +6 -15
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +10 -28
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +3 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +14 -8
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/unix.d.ts +14 -1
- package/dist/unix.d.ts.map +1 -1
- package/dist/unix.js +138 -8
- package/dist/unix.js.map +1 -1
- package/package.json +3 -2
- package/dist/session-handle.d.ts +0 -54
- package/dist/session-handle.d.ts.map +0 -1
- package/dist/session-handle.js +0 -51
- package/dist/session-handle.js.map +0 -1
- package/dist/state.d.ts +0 -22
- package/dist/state.d.ts.map +0 -1
- package/dist/state.js +0 -145
- package/dist/state.js.map +0 -1
package/dist/unix.d.ts
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
|
+
import { type ServerId } from "@earendil-works/pi-protocol";
|
|
1
2
|
import type { ByteTransportFactory } from "./transport.ts";
|
|
2
3
|
export interface UnixTransportOptions {
|
|
3
4
|
path: string;
|
|
4
5
|
maxPendingBytes?: number;
|
|
5
6
|
}
|
|
6
|
-
|
|
7
|
+
export interface UnixServerRoute {
|
|
8
|
+
serverId: ServerId;
|
|
9
|
+
path: string;
|
|
10
|
+
}
|
|
11
|
+
export interface DiscoverUnixServersOptions {
|
|
12
|
+
/** Directory containing server-addressed Unix sockets. */
|
|
13
|
+
directory: string;
|
|
14
|
+
/** Maximum time for each connection and handshake. Defaults to 1,000 ms. */
|
|
15
|
+
timeoutMs?: number;
|
|
16
|
+
}
|
|
17
|
+
/** Discover reachable local servers by probing server-addressed Unix sockets. */
|
|
18
|
+
export declare function discoverUnixServers(options: DiscoverUnixServersOptions): Promise<UnixServerRoute[]>;
|
|
19
|
+
/** Creates fresh Unix-domain socket transports for Client connection attempts in Node-compatible runtimes. */
|
|
7
20
|
export declare function createUnixTransportFactory(options: UnixTransportOptions): ByteTransportFactory;
|
|
8
21
|
//# sourceMappingURL=unix.d.ts.map
|
package/dist/unix.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unix.d.ts","sourceRoot":"","sources":["../src/unix.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAiB,oBAAoB,EAAyB,MAAM,gBAAgB,CAAC;AAIjG,MAAM,WAAW,oBAAoB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,gHAAgH;AAChH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,oBAAoB,GAAG,oBAAoB,CAW9F","sourcesContent":["import { createConnection, type Socket } from \"node:net\";\nimport { DEFAULT_MAX_FRAME_LENGTH } from \"@earendil-works/pi-protocol\";\nimport type { ByteTransport, ByteTransportFactory, ByteTransportHandlers } from \"./transport.ts\";\n\nconst MAX_UNIX_SOCKET_PATH_BYTES = process.platform === \"linux\" ? 107 : 103;\n\nexport interface UnixTransportOptions {\n\tpath: string;\n\tmaxPendingBytes?: number;\n}\n\n/** Creates fresh Unix-domain socket transports for PiClient connection attempts in Node-compatible runtimes. */\nexport function createUnixTransportFactory(options: UnixTransportOptions): ByteTransportFactory {\n\tif (options.path.length === 0) throw new TypeError(\"Unix transport path must not be empty\");\n\tif (Buffer.byteLength(options.path) > MAX_UNIX_SOCKET_PATH_BYTES) {\n\t\tthrow new TypeError(`Unix transport path is too long; maximum is ${MAX_UNIX_SOCKET_PATH_BYTES} UTF-8 bytes`);\n\t}\n\tconst maxPendingBytes = options.maxPendingBytes ?? DEFAULT_MAX_FRAME_LENGTH * 4;\n\tif (!Number.isSafeInteger(maxPendingBytes) || maxPendingBytes <= 0) {\n\t\tthrow new TypeError(\"Unix transport maxPendingBytes must be a positive safe integer\");\n\t}\n\tif (process.platform === \"win32\") throw new Error(\"Unix transport is not supported on Windows\");\n\treturn (handlers) => connectUnixSocket(options.path, maxPendingBytes, handlers);\n}\n\nfunction connectUnixSocket(\n\tpath: string,\n\tmaxPendingBytes: number,\n\thandlers: ByteTransportHandlers,\n): Promise<ByteTransport> {\n\treturn new Promise<ByteTransport>((resolve, reject) => {\n\t\tconst socket = createConnection(path);\n\t\tlet connected = false;\n\t\tlet terminal = false;\n\n\t\tconst close = (): void => {\n\t\t\tif (terminal) return;\n\t\t\tterminal = true;\n\t\t\tsocket.destroy();\n\t\t\tif (connected) handlers.onClose();\n\t\t\telse reject(new Error(\"Unix transport closed before connecting\"));\n\t\t};\n\n\t\tsocket.once(\"connect\", () => {\n\t\t\tif (terminal) return;\n\t\t\tconnected = true;\n\t\t\tresolve(\n\t\t\t\tnew UnixByteTransport(socket, maxPendingBytes, () => {\n\t\t\t\t\tterminal = true;\n\t\t\t\t}),\n\t\t\t);\n\t\t});\n\t\tsocket.on(\"data\", (chunk) => {\n\t\t\tif (!terminal) handlers.onData(new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength));\n\t\t});\n\t\tsocket.once(\"end\", close);\n\t\tsocket.once(\"close\", close);\n\t\tsocket.once(\"error\", (error) => {\n\t\t\tif (terminal) return;\n\t\t\tterminal = true;\n\t\t\tsocket.destroy();\n\t\t\tif (connected) handlers.onError(error);\n\t\t\telse reject(error);\n\t\t});\n\t});\n}\n\nclass UnixByteTransport implements ByteTransport {\n\treadonly #socket: Socket;\n\treadonly #maxPendingBytes: number;\n\treadonly #markLocalClose: () => void;\n\t#closed = false;\n\t#pendingBytes = 0;\n\t#writeTail: Promise<void> = Promise.resolve();\n\n\tconstructor(socket: Socket, maxPendingBytes: number, markLocalClose: () => void) {\n\t\tthis.#socket = socket;\n\t\tthis.#maxPendingBytes = maxPendingBytes;\n\t\tthis.#markLocalClose = markLocalClose;\n\t}\n\n\tsend(chunk: Uint8Array): Promise<void> {\n\t\tif (!(chunk instanceof Uint8Array)) {\n\t\t\treturn Promise.reject(new TypeError(\"Unix transport chunks must be Uint8Array\"));\n\t\t}\n\t\tif (this.#closed) return Promise.reject(new Error(\"Unix transport is closed\"));\n\t\tif (this.#pendingBytes + chunk.byteLength > this.#maxPendingBytes) {\n\t\t\treturn Promise.reject(new Error(\"Unix transport 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(): void {\n\t\tif (this.#closed) return;\n\t\tthis.#closed = true;\n\t\tthis.#markLocalClose();\n\t\tthis.#socket.destroy();\n\t}\n\n\t#write(chunk: Uint8Array): Promise<void> {\n\t\tif (this.#closed || !this.#socket.writable) return Promise.reject(new Error(\"Unix transport is closed\"));\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\tlet callbackComplete = false;\n\t\t\tlet drainComplete = false;\n\t\t\tlet requiresDrain: boolean | undefined;\n\t\t\tlet settled = false;\n\n\t\t\tconst onDrain = (): void => {\n\t\t\t\tdrainComplete = true;\n\t\t\t\tfinish();\n\t\t\t};\n\t\t\tconst cleanup = (): void => {\n\t\t\t\tthis.#socket.off(\"drain\", onDrain);\n\t\t\t\tthis.#socket.off(\"close\", onClose);\n\t\t\t};\n\t\t\tconst fail = (error: Error): void => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tcleanup();\n\t\t\t\treject(error);\n\t\t\t};\n\t\t\tconst finish = (): void => {\n\t\t\t\tif (settled || !callbackComplete || requiresDrain === undefined) return;\n\t\t\t\tif (requiresDrain && !drainComplete) return;\n\t\t\t\tsettled = true;\n\t\t\t\tcleanup();\n\t\t\t\tresolve();\n\t\t\t};\n\t\t\tconst onClose = (): void => fail(new Error(\"Unix transport closed during write\"));\n\n\t\t\ttry {\n\t\t\t\tthis.#socket.once(\"close\", onClose);\n\t\t\t\tconst accepted = this.#socket.write(chunk, (error) => {\n\t\t\t\t\tif (error) {\n\t\t\t\t\t\tfail(error);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tcallbackComplete = true;\n\t\t\t\t\tfinish();\n\t\t\t\t});\n\t\t\t\trequiresDrain = !accepted;\n\t\t\t\tif (requiresDrain) this.#socket.once(\"drain\", onDrain);\n\t\t\t\tfinish();\n\t\t\t} catch (error) {\n\t\t\t\tfail(error instanceof Error ? error : new Error(String(error)));\n\t\t\t}\n\t\t});\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"unix.d.ts","sourceRoot":"","sources":["../src/unix.ts"],"names":[],"mappings":"AAGA,OAAO,EAIN,KAAK,QAAQ,EACb,MAAM,6BAA6B,CAAC;AAGrC,OAAO,KAAK,EAAiB,oBAAoB,EAAyB,MAAM,gBAAgB,CAAC;AAOjG,MAAM,WAAW,oBAAoB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC/B,QAAQ,EAAE,QAAQ,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,0BAA0B;IAC1C,0DAA0D;IAC1D,SAAS,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,iFAAiF;AACjF,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAgDzG;AAED,8GAA8G;AAC9G,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,oBAAoB,GAAG,oBAAoB,CAG9F","sourcesContent":["import { lstat, readdir } from \"node:fs/promises\";\nimport { createConnection, type Socket } from \"node:net\";\nimport { join } from \"node:path\";\nimport {\n\tDEFAULT_MAX_FRAME_LENGTH,\n\tisServerId,\n\tProtocolValidationError,\n\ttype ServerId,\n} from \"@earendil-works/pi-protocol\";\nimport { Client } from \"./client.ts\";\nimport { DisconnectedError, ServerError } from \"./errors.ts\";\nimport type { ByteTransport, ByteTransportFactory, ByteTransportHandlers } from \"./transport.ts\";\n\nconst DEFAULT_DISCOVERY_TIMEOUT_MS = 1_000;\nconst MAX_TIMER_DELAY_MS = 2_147_483_647;\nconst UNIX_SOCKET_SUFFIX = \".sock\";\nconst MAX_CONCURRENT_DISCOVERY_PROBES = 16;\n\nexport interface UnixTransportOptions {\n\tpath: string;\n\tmaxPendingBytes?: number;\n}\n\nexport interface UnixServerRoute {\n\tserverId: ServerId;\n\tpath: string;\n}\n\nexport interface DiscoverUnixServersOptions {\n\t/** Directory containing server-addressed Unix sockets. */\n\tdirectory: string;\n\t/** Maximum time for each connection and handshake. Defaults to 1,000 ms. */\n\ttimeoutMs?: number;\n}\n\n/** Discover reachable local servers by probing server-addressed Unix sockets. */\nexport async function discoverUnixServers(options: DiscoverUnixServersOptions): Promise<UnixServerRoute[]> {\n\tif (process.platform === \"win32\") throw new Error(\"Unix transport is not supported on Windows\");\n\tconst directory = options.directory;\n\tconst timeoutMs = options.timeoutMs ?? DEFAULT_DISCOVERY_TIMEOUT_MS;\n\tif (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > MAX_TIMER_DELAY_MS) {\n\t\tthrow new TypeError(`Unix discovery timeoutMs must be an integer between 1 and ${MAX_TIMER_DELAY_MS}`);\n\t}\n\n\tlet names: string[];\n\ttry {\n\t\tnames = await readdir(directory);\n\t} catch (error) {\n\t\tif (isErrorCode(error, \"ENOENT\")) return [];\n\t\tthrow error;\n\t}\n\n\tconst candidates = names.flatMap((name): UnixServerRoute[] => {\n\t\tif (!name.endsWith(UNIX_SOCKET_SUFFIX)) return [];\n\t\tconst serverId = name.slice(0, -UNIX_SOCKET_SUFFIX.length);\n\t\treturn isServerId(serverId) ? [{ serverId, path: join(directory, name) }] : [];\n\t});\n\tconst routes: UnixServerRoute[] = [];\n\tlet nextIndex = 0;\n\tlet failure: { error: unknown } | undefined;\n\tconst workerCount = Math.min(MAX_CONCURRENT_DISCOVERY_PROBES, candidates.length);\n\tawait Promise.all(\n\t\tArray.from({ length: workerCount }, async () => {\n\t\t\twhile (!failure) {\n\t\t\t\tconst candidate = candidates[nextIndex++];\n\t\t\t\tif (!candidate) return;\n\t\t\t\ttry {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tif (!(await lstat(candidate.path)).isSocket()) continue;\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t// A socket can disappear between readdir and lstat during normal server shutdown.\n\t\t\t\t\t\tif (isErrorCode(error, \"ENOENT\")) continue;\n\t\t\t\t\t\tthrow error;\n\t\t\t\t\t}\n\t\t\t\t\tconst route = await probeUnixServer(candidate, timeoutMs);\n\t\t\t\t\tif (route) routes.push(route);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tfailure ??= { error };\n\t\t\t\t}\n\t\t\t}\n\t\t}),\n\t);\n\tif (failure) throw failure.error;\n\treturn routes.sort((left, right) => left.serverId.localeCompare(right.serverId));\n}\n\n/** Creates fresh Unix-domain socket transports for Client connection attempts in Node-compatible runtimes. */\nexport function createUnixTransportFactory(options: UnixTransportOptions): ByteTransportFactory {\n\tconst maxPendingBytes = validateUnixTransportOptions(options);\n\treturn (handlers) => connectUnixSocket(options.path, maxPendingBytes, handlers);\n}\n\nfunction validateUnixTransportOptions(options: UnixTransportOptions): number {\n\tif (options.path.length === 0) throw new TypeError(\"Unix transport path must not be empty\");\n\tconst maxPendingBytes = options.maxPendingBytes ?? DEFAULT_MAX_FRAME_LENGTH * 4;\n\tif (!Number.isSafeInteger(maxPendingBytes) || maxPendingBytes <= 0) {\n\t\tthrow new TypeError(\"Unix transport maxPendingBytes must be a positive safe integer\");\n\t}\n\tif (process.platform === \"win32\") throw new Error(\"Unix transport is not supported on Windows\");\n\treturn maxPendingBytes;\n}\n\nfunction connectUnixSocket(\n\tpath: string,\n\tmaxPendingBytes: number,\n\thandlers: ByteTransportHandlers,\n\tonSocket?: (socket: Socket) => void,\n): Promise<ByteTransport> {\n\treturn new Promise<ByteTransport>((resolve, reject) => {\n\t\tconst socket = createConnection(path);\n\t\tonSocket?.(socket);\n\t\tlet connected = false;\n\t\tlet terminal = false;\n\n\t\tconst close = (): void => {\n\t\t\tif (terminal) return;\n\t\t\tterminal = true;\n\t\t\tsocket.destroy();\n\t\t\tif (connected) handlers.onClose();\n\t\t\telse reject(new Error(\"Unix transport closed before connecting\"));\n\t\t};\n\n\t\tsocket.once(\"connect\", () => {\n\t\t\tif (terminal) return;\n\t\t\tconnected = true;\n\t\t\tresolve(\n\t\t\t\tnew UnixByteTransport(socket, maxPendingBytes, () => {\n\t\t\t\t\tterminal = true;\n\t\t\t\t}),\n\t\t\t);\n\t\t});\n\t\tsocket.on(\"data\", (chunk) => {\n\t\t\tif (!terminal) handlers.onData(new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength));\n\t\t});\n\t\tsocket.once(\"end\", close);\n\t\tsocket.once(\"close\", close);\n\t\tsocket.once(\"error\", (error) => {\n\t\t\tif (terminal) return;\n\t\t\tterminal = true;\n\t\t\tsocket.destroy();\n\t\t\tif (connected) handlers.onError(error);\n\t\t\telse reject(error);\n\t\t});\n\t});\n}\n\nclass UnixByteTransport implements ByteTransport {\n\treadonly #socket: Socket;\n\treadonly #maxPendingBytes: number;\n\treadonly #markLocalClose: () => void;\n\t#closed = false;\n\t#pendingBytes = 0;\n\t#writeTail: Promise<void> = Promise.resolve();\n\n\tconstructor(socket: Socket, maxPendingBytes: number, markLocalClose: () => void) {\n\t\tthis.#socket = socket;\n\t\tthis.#maxPendingBytes = maxPendingBytes;\n\t\tthis.#markLocalClose = markLocalClose;\n\t}\n\n\tsend(chunk: Uint8Array): Promise<void> {\n\t\tif (!(chunk instanceof Uint8Array)) {\n\t\t\treturn Promise.reject(new TypeError(\"Unix transport chunks must be Uint8Array\"));\n\t\t}\n\t\tif (this.#closed) return Promise.reject(new Error(\"Unix transport is closed\"));\n\t\tif (this.#pendingBytes + chunk.byteLength > this.#maxPendingBytes) {\n\t\t\treturn Promise.reject(new Error(\"Unix transport 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(): void {\n\t\tif (this.#closed) return;\n\t\tthis.#closed = true;\n\t\tthis.#markLocalClose();\n\t\tthis.#socket.destroy();\n\t}\n\n\t#write(chunk: Uint8Array): Promise<void> {\n\t\tif (this.#closed || !this.#socket.writable) return Promise.reject(new Error(\"Unix transport is closed\"));\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\tlet callbackComplete = false;\n\t\t\tlet drainComplete = false;\n\t\t\tlet requiresDrain: boolean | undefined;\n\t\t\tlet settled = false;\n\n\t\t\tconst onDrain = (): void => {\n\t\t\t\tdrainComplete = true;\n\t\t\t\tfinish();\n\t\t\t};\n\t\t\tconst cleanup = (): void => {\n\t\t\t\tthis.#socket.off(\"drain\", onDrain);\n\t\t\t\tthis.#socket.off(\"close\", onClose);\n\t\t\t};\n\t\t\tconst fail = (error: Error): void => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tcleanup();\n\t\t\t\treject(error);\n\t\t\t};\n\t\t\tconst finish = (): void => {\n\t\t\t\tif (settled || !callbackComplete || requiresDrain === undefined) return;\n\t\t\t\tif (requiresDrain && !drainComplete) return;\n\t\t\t\tsettled = true;\n\t\t\t\tcleanup();\n\t\t\t\tresolve();\n\t\t\t};\n\t\t\tconst onClose = (): void => fail(new Error(\"Unix transport closed during write\"));\n\n\t\t\ttry {\n\t\t\t\tthis.#socket.once(\"close\", onClose);\n\t\t\t\tconst accepted = this.#socket.write(chunk, (error) => {\n\t\t\t\t\tif (error) {\n\t\t\t\t\t\tfail(error);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tcallbackComplete = true;\n\t\t\t\t\tfinish();\n\t\t\t\t});\n\t\t\t\trequiresDrain = !accepted;\n\t\t\t\tif (requiresDrain) this.#socket.once(\"drain\", onDrain);\n\t\t\t\tfinish();\n\t\t\t} catch (error) {\n\t\t\t\tfail(error instanceof Error ? error : new Error(String(error)));\n\t\t\t}\n\t\t});\n\t}\n}\n\nasync function probeUnixServer(route: UnixServerRoute, timeoutMs: number): Promise<UnixServerRoute | undefined> {\n\tconst maxPendingBytes = validateUnixTransportOptions({ path: route.path });\n\tlet socket: Socket | undefined;\n\tconst client = new Client({\n\t\tserverId: route.serverId,\n\t\ttransportFactory: (handlers) =>\n\t\t\tconnectUnixSocket(route.path, maxPendingBytes, handlers, (created) => {\n\t\t\t\tsocket = created;\n\t\t\t}),\n\t});\n\tlet timeout: ReturnType<typeof setTimeout> | undefined;\n\ttry {\n\t\tawait Promise.race([\n\t\t\tclient.connect(),\n\t\t\tnew Promise<never>((_, reject) => {\n\t\t\t\ttimeout = setTimeout(() => {\n\t\t\t\t\tsocket?.destroy();\n\t\t\t\t\treject(new UnixDiscoveryTimeoutError());\n\t\t\t\t}, timeoutMs);\n\t\t\t\ttimeout.unref();\n\t\t\t}),\n\t\t]);\n\t\treturn route;\n\t} catch (error) {\n\t\t// Missing/refused sockets are stale or shutting down. Protocol failures mean\n\t\t// the endpoint is not the advertised server. Both are safe to omit.\n\t\tif (\n\t\t\terror instanceof UnixDiscoveryTimeoutError ||\n\t\t\terror instanceof ProtocolValidationError ||\n\t\t\t(error instanceof DisconnectedError && error.cause === undefined) ||\n\t\t\t(error instanceof ServerError && error.code === \"version\") ||\n\t\t\tisErrorCode(error, \"ENOENT\") ||\n\t\t\tisErrorCode(error, \"ECONNREFUSED\") ||\n\t\t\tisErrorCode(error, \"ECONNRESET\") ||\n\t\t\tisErrorCode(error, \"EPIPE\") ||\n\t\t\tisErrorCode(error, \"ETIMEDOUT\")\n\t\t) {\n\t\t\treturn undefined;\n\t\t}\n\t\tthrow error;\n\t} finally {\n\t\tif (timeout) clearTimeout(timeout);\n\t\tawait client.dispose();\n\t\tconst activeSocket = socket;\n\t\tif (activeSocket && !activeSocket.destroyed) activeSocket.destroy();\n\t\tif (activeSocket && !activeSocket.closed) {\n\t\t\tawait new Promise<void>((resolve) => activeSocket.once(\"close\", resolve));\n\t\t}\n\t}\n}\n\nclass UnixDiscoveryTimeoutError extends Error {}\n\nfunction isErrorCode(error: unknown, code: string): boolean {\n\tlet current = error;\n\tconst seen = new Set<unknown>();\n\twhile (current instanceof Error && !seen.has(current)) {\n\t\tseen.add(current);\n\t\tif (\"code\" in current && current.code === code) return true;\n\t\tcurrent = current.cause;\n\t}\n\treturn false;\n}\n"]}
|
package/dist/unix.js
CHANGED
|
@@ -1,24 +1,90 @@
|
|
|
1
|
+
import { lstat, readdir } from "node:fs/promises";
|
|
1
2
|
import { createConnection } from "node:net";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { DEFAULT_MAX_FRAME_LENGTH, isServerId, ProtocolValidationError, } from "@earendil-works/pi-protocol";
|
|
5
|
+
import { Client } from "./client.js";
|
|
6
|
+
import { DisconnectedError, ServerError } from "./errors.js";
|
|
7
|
+
const DEFAULT_DISCOVERY_TIMEOUT_MS = 1_000;
|
|
8
|
+
const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
9
|
+
const UNIX_SOCKET_SUFFIX = ".sock";
|
|
10
|
+
const MAX_CONCURRENT_DISCOVERY_PROBES = 16;
|
|
11
|
+
/** Discover reachable local servers by probing server-addressed Unix sockets. */
|
|
12
|
+
export async function discoverUnixServers(options) {
|
|
13
|
+
if (process.platform === "win32")
|
|
14
|
+
throw new Error("Unix transport is not supported on Windows");
|
|
15
|
+
const directory = options.directory;
|
|
16
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_DISCOVERY_TIMEOUT_MS;
|
|
17
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > MAX_TIMER_DELAY_MS) {
|
|
18
|
+
throw new TypeError(`Unix discovery timeoutMs must be an integer between 1 and ${MAX_TIMER_DELAY_MS}`);
|
|
19
|
+
}
|
|
20
|
+
let names;
|
|
21
|
+
try {
|
|
22
|
+
names = await readdir(directory);
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
if (isErrorCode(error, "ENOENT"))
|
|
26
|
+
return [];
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
const candidates = names.flatMap((name) => {
|
|
30
|
+
if (!name.endsWith(UNIX_SOCKET_SUFFIX))
|
|
31
|
+
return [];
|
|
32
|
+
const serverId = name.slice(0, -UNIX_SOCKET_SUFFIX.length);
|
|
33
|
+
return isServerId(serverId) ? [{ serverId, path: join(directory, name) }] : [];
|
|
34
|
+
});
|
|
35
|
+
const routes = [];
|
|
36
|
+
let nextIndex = 0;
|
|
37
|
+
let failure;
|
|
38
|
+
const workerCount = Math.min(MAX_CONCURRENT_DISCOVERY_PROBES, candidates.length);
|
|
39
|
+
await Promise.all(Array.from({ length: workerCount }, async () => {
|
|
40
|
+
while (!failure) {
|
|
41
|
+
const candidate = candidates[nextIndex++];
|
|
42
|
+
if (!candidate)
|
|
43
|
+
return;
|
|
44
|
+
try {
|
|
45
|
+
try {
|
|
46
|
+
if (!(await lstat(candidate.path)).isSocket())
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
// A socket can disappear between readdir and lstat during normal server shutdown.
|
|
51
|
+
if (isErrorCode(error, "ENOENT"))
|
|
52
|
+
continue;
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
const route = await probeUnixServer(candidate, timeoutMs);
|
|
56
|
+
if (route)
|
|
57
|
+
routes.push(route);
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
failure ??= { error };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}));
|
|
64
|
+
if (failure)
|
|
65
|
+
throw failure.error;
|
|
66
|
+
return routes.sort((left, right) => left.serverId.localeCompare(right.serverId));
|
|
67
|
+
}
|
|
68
|
+
/** Creates fresh Unix-domain socket transports for Client connection attempts in Node-compatible runtimes. */
|
|
5
69
|
export function createUnixTransportFactory(options) {
|
|
70
|
+
const maxPendingBytes = validateUnixTransportOptions(options);
|
|
71
|
+
return (handlers) => connectUnixSocket(options.path, maxPendingBytes, handlers);
|
|
72
|
+
}
|
|
73
|
+
function validateUnixTransportOptions(options) {
|
|
6
74
|
if (options.path.length === 0)
|
|
7
75
|
throw new TypeError("Unix transport path must not be empty");
|
|
8
|
-
if (Buffer.byteLength(options.path) > MAX_UNIX_SOCKET_PATH_BYTES) {
|
|
9
|
-
throw new TypeError(`Unix transport path is too long; maximum is ${MAX_UNIX_SOCKET_PATH_BYTES} UTF-8 bytes`);
|
|
10
|
-
}
|
|
11
76
|
const maxPendingBytes = options.maxPendingBytes ?? DEFAULT_MAX_FRAME_LENGTH * 4;
|
|
12
77
|
if (!Number.isSafeInteger(maxPendingBytes) || maxPendingBytes <= 0) {
|
|
13
78
|
throw new TypeError("Unix transport maxPendingBytes must be a positive safe integer");
|
|
14
79
|
}
|
|
15
80
|
if (process.platform === "win32")
|
|
16
81
|
throw new Error("Unix transport is not supported on Windows");
|
|
17
|
-
return
|
|
82
|
+
return maxPendingBytes;
|
|
18
83
|
}
|
|
19
|
-
function connectUnixSocket(path, maxPendingBytes, handlers) {
|
|
84
|
+
function connectUnixSocket(path, maxPendingBytes, handlers, onSocket) {
|
|
20
85
|
return new Promise((resolve, reject) => {
|
|
21
86
|
const socket = createConnection(path);
|
|
87
|
+
onSocket?.(socket);
|
|
22
88
|
let connected = false;
|
|
23
89
|
let terminal = false;
|
|
24
90
|
const close = () => {
|
|
@@ -148,4 +214,68 @@ class UnixByteTransport {
|
|
|
148
214
|
});
|
|
149
215
|
}
|
|
150
216
|
}
|
|
217
|
+
async function probeUnixServer(route, timeoutMs) {
|
|
218
|
+
const maxPendingBytes = validateUnixTransportOptions({ path: route.path });
|
|
219
|
+
let socket;
|
|
220
|
+
const client = new Client({
|
|
221
|
+
serverId: route.serverId,
|
|
222
|
+
transportFactory: (handlers) => connectUnixSocket(route.path, maxPendingBytes, handlers, (created) => {
|
|
223
|
+
socket = created;
|
|
224
|
+
}),
|
|
225
|
+
});
|
|
226
|
+
let timeout;
|
|
227
|
+
try {
|
|
228
|
+
await Promise.race([
|
|
229
|
+
client.connect(),
|
|
230
|
+
new Promise((_, reject) => {
|
|
231
|
+
timeout = setTimeout(() => {
|
|
232
|
+
socket?.destroy();
|
|
233
|
+
reject(new UnixDiscoveryTimeoutError());
|
|
234
|
+
}, timeoutMs);
|
|
235
|
+
timeout.unref();
|
|
236
|
+
}),
|
|
237
|
+
]);
|
|
238
|
+
return route;
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
// Missing/refused sockets are stale or shutting down. Protocol failures mean
|
|
242
|
+
// the endpoint is not the advertised server. Both are safe to omit.
|
|
243
|
+
if (error instanceof UnixDiscoveryTimeoutError ||
|
|
244
|
+
error instanceof ProtocolValidationError ||
|
|
245
|
+
(error instanceof DisconnectedError && error.cause === undefined) ||
|
|
246
|
+
(error instanceof ServerError && error.code === "version") ||
|
|
247
|
+
isErrorCode(error, "ENOENT") ||
|
|
248
|
+
isErrorCode(error, "ECONNREFUSED") ||
|
|
249
|
+
isErrorCode(error, "ECONNRESET") ||
|
|
250
|
+
isErrorCode(error, "EPIPE") ||
|
|
251
|
+
isErrorCode(error, "ETIMEDOUT")) {
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
|
254
|
+
throw error;
|
|
255
|
+
}
|
|
256
|
+
finally {
|
|
257
|
+
if (timeout)
|
|
258
|
+
clearTimeout(timeout);
|
|
259
|
+
await client.dispose();
|
|
260
|
+
const activeSocket = socket;
|
|
261
|
+
if (activeSocket && !activeSocket.destroyed)
|
|
262
|
+
activeSocket.destroy();
|
|
263
|
+
if (activeSocket && !activeSocket.closed) {
|
|
264
|
+
await new Promise((resolve) => activeSocket.once("close", resolve));
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
class UnixDiscoveryTimeoutError extends Error {
|
|
269
|
+
}
|
|
270
|
+
function isErrorCode(error, code) {
|
|
271
|
+
let current = error;
|
|
272
|
+
const seen = new Set();
|
|
273
|
+
while (current instanceof Error && !seen.has(current)) {
|
|
274
|
+
seen.add(current);
|
|
275
|
+
if ("code" in current && current.code === code)
|
|
276
|
+
return true;
|
|
277
|
+
current = current.cause;
|
|
278
|
+
}
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
151
281
|
//# sourceMappingURL=unix.js.map
|
package/dist/unix.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unix.js","sourceRoot":"","sources":["../src/unix.ts"],"names":[],"mappings":"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","sourcesContent":["import { createConnection, type Socket } from \"node:net\";\nimport { DEFAULT_MAX_FRAME_LENGTH } from \"@earendil-works/pi-protocol\";\nimport type { ByteTransport, ByteTransportFactory, ByteTransportHandlers } from \"./transport.ts\";\n\nconst MAX_UNIX_SOCKET_PATH_BYTES = process.platform === \"linux\" ? 107 : 103;\n\nexport interface UnixTransportOptions {\n\tpath: string;\n\tmaxPendingBytes?: number;\n}\n\n/** Creates fresh Unix-domain socket transports for PiClient connection attempts in Node-compatible runtimes. */\nexport function createUnixTransportFactory(options: UnixTransportOptions): ByteTransportFactory {\n\tif (options.path.length === 0) throw new TypeError(\"Unix transport path must not be empty\");\n\tif (Buffer.byteLength(options.path) > MAX_UNIX_SOCKET_PATH_BYTES) {\n\t\tthrow new TypeError(`Unix transport path is too long; maximum is ${MAX_UNIX_SOCKET_PATH_BYTES} UTF-8 bytes`);\n\t}\n\tconst maxPendingBytes = options.maxPendingBytes ?? DEFAULT_MAX_FRAME_LENGTH * 4;\n\tif (!Number.isSafeInteger(maxPendingBytes) || maxPendingBytes <= 0) {\n\t\tthrow new TypeError(\"Unix transport maxPendingBytes must be a positive safe integer\");\n\t}\n\tif (process.platform === \"win32\") throw new Error(\"Unix transport is not supported on Windows\");\n\treturn (handlers) => connectUnixSocket(options.path, maxPendingBytes, handlers);\n}\n\nfunction connectUnixSocket(\n\tpath: string,\n\tmaxPendingBytes: number,\n\thandlers: ByteTransportHandlers,\n): Promise<ByteTransport> {\n\treturn new Promise<ByteTransport>((resolve, reject) => {\n\t\tconst socket = createConnection(path);\n\t\tlet connected = false;\n\t\tlet terminal = false;\n\n\t\tconst close = (): void => {\n\t\t\tif (terminal) return;\n\t\t\tterminal = true;\n\t\t\tsocket.destroy();\n\t\t\tif (connected) handlers.onClose();\n\t\t\telse reject(new Error(\"Unix transport closed before connecting\"));\n\t\t};\n\n\t\tsocket.once(\"connect\", () => {\n\t\t\tif (terminal) return;\n\t\t\tconnected = true;\n\t\t\tresolve(\n\t\t\t\tnew UnixByteTransport(socket, maxPendingBytes, () => {\n\t\t\t\t\tterminal = true;\n\t\t\t\t}),\n\t\t\t);\n\t\t});\n\t\tsocket.on(\"data\", (chunk) => {\n\t\t\tif (!terminal) handlers.onData(new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength));\n\t\t});\n\t\tsocket.once(\"end\", close);\n\t\tsocket.once(\"close\", close);\n\t\tsocket.once(\"error\", (error) => {\n\t\t\tif (terminal) return;\n\t\t\tterminal = true;\n\t\t\tsocket.destroy();\n\t\t\tif (connected) handlers.onError(error);\n\t\t\telse reject(error);\n\t\t});\n\t});\n}\n\nclass UnixByteTransport implements ByteTransport {\n\treadonly #socket: Socket;\n\treadonly #maxPendingBytes: number;\n\treadonly #markLocalClose: () => void;\n\t#closed = false;\n\t#pendingBytes = 0;\n\t#writeTail: Promise<void> = Promise.resolve();\n\n\tconstructor(socket: Socket, maxPendingBytes: number, markLocalClose: () => void) {\n\t\tthis.#socket = socket;\n\t\tthis.#maxPendingBytes = maxPendingBytes;\n\t\tthis.#markLocalClose = markLocalClose;\n\t}\n\n\tsend(chunk: Uint8Array): Promise<void> {\n\t\tif (!(chunk instanceof Uint8Array)) {\n\t\t\treturn Promise.reject(new TypeError(\"Unix transport chunks must be Uint8Array\"));\n\t\t}\n\t\tif (this.#closed) return Promise.reject(new Error(\"Unix transport is closed\"));\n\t\tif (this.#pendingBytes + chunk.byteLength > this.#maxPendingBytes) {\n\t\t\treturn Promise.reject(new Error(\"Unix transport 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(): void {\n\t\tif (this.#closed) return;\n\t\tthis.#closed = true;\n\t\tthis.#markLocalClose();\n\t\tthis.#socket.destroy();\n\t}\n\n\t#write(chunk: Uint8Array): Promise<void> {\n\t\tif (this.#closed || !this.#socket.writable) return Promise.reject(new Error(\"Unix transport is closed\"));\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\tlet callbackComplete = false;\n\t\t\tlet drainComplete = false;\n\t\t\tlet requiresDrain: boolean | undefined;\n\t\t\tlet settled = false;\n\n\t\t\tconst onDrain = (): void => {\n\t\t\t\tdrainComplete = true;\n\t\t\t\tfinish();\n\t\t\t};\n\t\t\tconst cleanup = (): void => {\n\t\t\t\tthis.#socket.off(\"drain\", onDrain);\n\t\t\t\tthis.#socket.off(\"close\", onClose);\n\t\t\t};\n\t\t\tconst fail = (error: Error): void => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tcleanup();\n\t\t\t\treject(error);\n\t\t\t};\n\t\t\tconst finish = (): void => {\n\t\t\t\tif (settled || !callbackComplete || requiresDrain === undefined) return;\n\t\t\t\tif (requiresDrain && !drainComplete) return;\n\t\t\t\tsettled = true;\n\t\t\t\tcleanup();\n\t\t\t\tresolve();\n\t\t\t};\n\t\t\tconst onClose = (): void => fail(new Error(\"Unix transport closed during write\"));\n\n\t\t\ttry {\n\t\t\t\tthis.#socket.once(\"close\", onClose);\n\t\t\t\tconst accepted = this.#socket.write(chunk, (error) => {\n\t\t\t\t\tif (error) {\n\t\t\t\t\t\tfail(error);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tcallbackComplete = true;\n\t\t\t\t\tfinish();\n\t\t\t\t});\n\t\t\t\trequiresDrain = !accepted;\n\t\t\t\tif (requiresDrain) this.#socket.once(\"drain\", onDrain);\n\t\t\t\tfinish();\n\t\t\t} catch (error) {\n\t\t\t\tfail(error instanceof Error ? error : new Error(String(error)));\n\t\t\t}\n\t\t});\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"unix.js","sourceRoot":"","sources":["../src/unix.ts"],"names":[],"mappings":"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","sourcesContent":["import { lstat, readdir } from \"node:fs/promises\";\nimport { createConnection, type Socket } from \"node:net\";\nimport { join } from \"node:path\";\nimport {\n\tDEFAULT_MAX_FRAME_LENGTH,\n\tisServerId,\n\tProtocolValidationError,\n\ttype ServerId,\n} from \"@earendil-works/pi-protocol\";\nimport { Client } from \"./client.ts\";\nimport { DisconnectedError, ServerError } from \"./errors.ts\";\nimport type { ByteTransport, ByteTransportFactory, ByteTransportHandlers } from \"./transport.ts\";\n\nconst DEFAULT_DISCOVERY_TIMEOUT_MS = 1_000;\nconst MAX_TIMER_DELAY_MS = 2_147_483_647;\nconst UNIX_SOCKET_SUFFIX = \".sock\";\nconst MAX_CONCURRENT_DISCOVERY_PROBES = 16;\n\nexport interface UnixTransportOptions {\n\tpath: string;\n\tmaxPendingBytes?: number;\n}\n\nexport interface UnixServerRoute {\n\tserverId: ServerId;\n\tpath: string;\n}\n\nexport interface DiscoverUnixServersOptions {\n\t/** Directory containing server-addressed Unix sockets. */\n\tdirectory: string;\n\t/** Maximum time for each connection and handshake. Defaults to 1,000 ms. */\n\ttimeoutMs?: number;\n}\n\n/** Discover reachable local servers by probing server-addressed Unix sockets. */\nexport async function discoverUnixServers(options: DiscoverUnixServersOptions): Promise<UnixServerRoute[]> {\n\tif (process.platform === \"win32\") throw new Error(\"Unix transport is not supported on Windows\");\n\tconst directory = options.directory;\n\tconst timeoutMs = options.timeoutMs ?? DEFAULT_DISCOVERY_TIMEOUT_MS;\n\tif (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > MAX_TIMER_DELAY_MS) {\n\t\tthrow new TypeError(`Unix discovery timeoutMs must be an integer between 1 and ${MAX_TIMER_DELAY_MS}`);\n\t}\n\n\tlet names: string[];\n\ttry {\n\t\tnames = await readdir(directory);\n\t} catch (error) {\n\t\tif (isErrorCode(error, \"ENOENT\")) return [];\n\t\tthrow error;\n\t}\n\n\tconst candidates = names.flatMap((name): UnixServerRoute[] => {\n\t\tif (!name.endsWith(UNIX_SOCKET_SUFFIX)) return [];\n\t\tconst serverId = name.slice(0, -UNIX_SOCKET_SUFFIX.length);\n\t\treturn isServerId(serverId) ? [{ serverId, path: join(directory, name) }] : [];\n\t});\n\tconst routes: UnixServerRoute[] = [];\n\tlet nextIndex = 0;\n\tlet failure: { error: unknown } | undefined;\n\tconst workerCount = Math.min(MAX_CONCURRENT_DISCOVERY_PROBES, candidates.length);\n\tawait Promise.all(\n\t\tArray.from({ length: workerCount }, async () => {\n\t\t\twhile (!failure) {\n\t\t\t\tconst candidate = candidates[nextIndex++];\n\t\t\t\tif (!candidate) return;\n\t\t\t\ttry {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tif (!(await lstat(candidate.path)).isSocket()) continue;\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t// A socket can disappear between readdir and lstat during normal server shutdown.\n\t\t\t\t\t\tif (isErrorCode(error, \"ENOENT\")) continue;\n\t\t\t\t\t\tthrow error;\n\t\t\t\t\t}\n\t\t\t\t\tconst route = await probeUnixServer(candidate, timeoutMs);\n\t\t\t\t\tif (route) routes.push(route);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tfailure ??= { error };\n\t\t\t\t}\n\t\t\t}\n\t\t}),\n\t);\n\tif (failure) throw failure.error;\n\treturn routes.sort((left, right) => left.serverId.localeCompare(right.serverId));\n}\n\n/** Creates fresh Unix-domain socket transports for Client connection attempts in Node-compatible runtimes. */\nexport function createUnixTransportFactory(options: UnixTransportOptions): ByteTransportFactory {\n\tconst maxPendingBytes = validateUnixTransportOptions(options);\n\treturn (handlers) => connectUnixSocket(options.path, maxPendingBytes, handlers);\n}\n\nfunction validateUnixTransportOptions(options: UnixTransportOptions): number {\n\tif (options.path.length === 0) throw new TypeError(\"Unix transport path must not be empty\");\n\tconst maxPendingBytes = options.maxPendingBytes ?? DEFAULT_MAX_FRAME_LENGTH * 4;\n\tif (!Number.isSafeInteger(maxPendingBytes) || maxPendingBytes <= 0) {\n\t\tthrow new TypeError(\"Unix transport maxPendingBytes must be a positive safe integer\");\n\t}\n\tif (process.platform === \"win32\") throw new Error(\"Unix transport is not supported on Windows\");\n\treturn maxPendingBytes;\n}\n\nfunction connectUnixSocket(\n\tpath: string,\n\tmaxPendingBytes: number,\n\thandlers: ByteTransportHandlers,\n\tonSocket?: (socket: Socket) => void,\n): Promise<ByteTransport> {\n\treturn new Promise<ByteTransport>((resolve, reject) => {\n\t\tconst socket = createConnection(path);\n\t\tonSocket?.(socket);\n\t\tlet connected = false;\n\t\tlet terminal = false;\n\n\t\tconst close = (): void => {\n\t\t\tif (terminal) return;\n\t\t\tterminal = true;\n\t\t\tsocket.destroy();\n\t\t\tif (connected) handlers.onClose();\n\t\t\telse reject(new Error(\"Unix transport closed before connecting\"));\n\t\t};\n\n\t\tsocket.once(\"connect\", () => {\n\t\t\tif (terminal) return;\n\t\t\tconnected = true;\n\t\t\tresolve(\n\t\t\t\tnew UnixByteTransport(socket, maxPendingBytes, () => {\n\t\t\t\t\tterminal = true;\n\t\t\t\t}),\n\t\t\t);\n\t\t});\n\t\tsocket.on(\"data\", (chunk) => {\n\t\t\tif (!terminal) handlers.onData(new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength));\n\t\t});\n\t\tsocket.once(\"end\", close);\n\t\tsocket.once(\"close\", close);\n\t\tsocket.once(\"error\", (error) => {\n\t\t\tif (terminal) return;\n\t\t\tterminal = true;\n\t\t\tsocket.destroy();\n\t\t\tif (connected) handlers.onError(error);\n\t\t\telse reject(error);\n\t\t});\n\t});\n}\n\nclass UnixByteTransport implements ByteTransport {\n\treadonly #socket: Socket;\n\treadonly #maxPendingBytes: number;\n\treadonly #markLocalClose: () => void;\n\t#closed = false;\n\t#pendingBytes = 0;\n\t#writeTail: Promise<void> = Promise.resolve();\n\n\tconstructor(socket: Socket, maxPendingBytes: number, markLocalClose: () => void) {\n\t\tthis.#socket = socket;\n\t\tthis.#maxPendingBytes = maxPendingBytes;\n\t\tthis.#markLocalClose = markLocalClose;\n\t}\n\n\tsend(chunk: Uint8Array): Promise<void> {\n\t\tif (!(chunk instanceof Uint8Array)) {\n\t\t\treturn Promise.reject(new TypeError(\"Unix transport chunks must be Uint8Array\"));\n\t\t}\n\t\tif (this.#closed) return Promise.reject(new Error(\"Unix transport is closed\"));\n\t\tif (this.#pendingBytes + chunk.byteLength > this.#maxPendingBytes) {\n\t\t\treturn Promise.reject(new Error(\"Unix transport 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(): void {\n\t\tif (this.#closed) return;\n\t\tthis.#closed = true;\n\t\tthis.#markLocalClose();\n\t\tthis.#socket.destroy();\n\t}\n\n\t#write(chunk: Uint8Array): Promise<void> {\n\t\tif (this.#closed || !this.#socket.writable) return Promise.reject(new Error(\"Unix transport is closed\"));\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\tlet callbackComplete = false;\n\t\t\tlet drainComplete = false;\n\t\t\tlet requiresDrain: boolean | undefined;\n\t\t\tlet settled = false;\n\n\t\t\tconst onDrain = (): void => {\n\t\t\t\tdrainComplete = true;\n\t\t\t\tfinish();\n\t\t\t};\n\t\t\tconst cleanup = (): void => {\n\t\t\t\tthis.#socket.off(\"drain\", onDrain);\n\t\t\t\tthis.#socket.off(\"close\", onClose);\n\t\t\t};\n\t\t\tconst fail = (error: Error): void => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tcleanup();\n\t\t\t\treject(error);\n\t\t\t};\n\t\t\tconst finish = (): void => {\n\t\t\t\tif (settled || !callbackComplete || requiresDrain === undefined) return;\n\t\t\t\tif (requiresDrain && !drainComplete) return;\n\t\t\t\tsettled = true;\n\t\t\t\tcleanup();\n\t\t\t\tresolve();\n\t\t\t};\n\t\t\tconst onClose = (): void => fail(new Error(\"Unix transport closed during write\"));\n\n\t\t\ttry {\n\t\t\t\tthis.#socket.once(\"close\", onClose);\n\t\t\t\tconst accepted = this.#socket.write(chunk, (error) => {\n\t\t\t\t\tif (error) {\n\t\t\t\t\t\tfail(error);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tcallbackComplete = true;\n\t\t\t\t\tfinish();\n\t\t\t\t});\n\t\t\t\trequiresDrain = !accepted;\n\t\t\t\tif (requiresDrain) this.#socket.once(\"drain\", onDrain);\n\t\t\t\tfinish();\n\t\t\t} catch (error) {\n\t\t\t\tfail(error instanceof Error ? error : new Error(String(error)));\n\t\t\t}\n\t\t});\n\t}\n}\n\nasync function probeUnixServer(route: UnixServerRoute, timeoutMs: number): Promise<UnixServerRoute | undefined> {\n\tconst maxPendingBytes = validateUnixTransportOptions({ path: route.path });\n\tlet socket: Socket | undefined;\n\tconst client = new Client({\n\t\tserverId: route.serverId,\n\t\ttransportFactory: (handlers) =>\n\t\t\tconnectUnixSocket(route.path, maxPendingBytes, handlers, (created) => {\n\t\t\t\tsocket = created;\n\t\t\t}),\n\t});\n\tlet timeout: ReturnType<typeof setTimeout> | undefined;\n\ttry {\n\t\tawait Promise.race([\n\t\t\tclient.connect(),\n\t\t\tnew Promise<never>((_, reject) => {\n\t\t\t\ttimeout = setTimeout(() => {\n\t\t\t\t\tsocket?.destroy();\n\t\t\t\t\treject(new UnixDiscoveryTimeoutError());\n\t\t\t\t}, timeoutMs);\n\t\t\t\ttimeout.unref();\n\t\t\t}),\n\t\t]);\n\t\treturn route;\n\t} catch (error) {\n\t\t// Missing/refused sockets are stale or shutting down. Protocol failures mean\n\t\t// the endpoint is not the advertised server. Both are safe to omit.\n\t\tif (\n\t\t\terror instanceof UnixDiscoveryTimeoutError ||\n\t\t\terror instanceof ProtocolValidationError ||\n\t\t\t(error instanceof DisconnectedError && error.cause === undefined) ||\n\t\t\t(error instanceof ServerError && error.code === \"version\") ||\n\t\t\tisErrorCode(error, \"ENOENT\") ||\n\t\t\tisErrorCode(error, \"ECONNREFUSED\") ||\n\t\t\tisErrorCode(error, \"ECONNRESET\") ||\n\t\t\tisErrorCode(error, \"EPIPE\") ||\n\t\t\tisErrorCode(error, \"ETIMEDOUT\")\n\t\t) {\n\t\t\treturn undefined;\n\t\t}\n\t\tthrow error;\n\t} finally {\n\t\tif (timeout) clearTimeout(timeout);\n\t\tawait client.dispose();\n\t\tconst activeSocket = socket;\n\t\tif (activeSocket && !activeSocket.destroyed) activeSocket.destroy();\n\t\tif (activeSocket && !activeSocket.closed) {\n\t\t\tawait new Promise<void>((resolve) => activeSocket.once(\"close\", resolve));\n\t\t}\n\t}\n}\n\nclass UnixDiscoveryTimeoutError extends Error {}\n\nfunction isErrorCode(error: unknown, code: string): boolean {\n\tlet current = error;\n\tconst seen = new Set<unknown>();\n\twhile (current instanceof Error && !seen.has(current)) {\n\t\tseen.add(current);\n\t\tif (\"code\" in current && current.code === code) return true;\n\t\tcurrent = current.cause;\n\t}\n\treturn false;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@earendil-works/pi-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.85.1",
|
|
4
4
|
"description": "Transport-neutral client for remote pi sessions over framed CBOR bytes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -47,7 +47,8 @@
|
|
|
47
47
|
"node": ">=22.19.0"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@earendil-works/
|
|
50
|
+
"@earendil-works/chord": "^0.85.1",
|
|
51
|
+
"@earendil-works/pi-protocol": "^0.85.1"
|
|
51
52
|
},
|
|
52
53
|
"devDependencies": {
|
|
53
54
|
"shx": "0.4.0",
|
package/dist/session-handle.d.ts
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import type { Command, ModelRef, ResultForCommand, ServerEvent, SessionSnapshot, ThinkingLevel } from "@earendil-works/pi-protocol";
|
|
2
|
-
import type { Unsubscribe } from "./types.ts";
|
|
3
|
-
type SessionCommand = Extract<Command, {
|
|
4
|
-
sessionId: string;
|
|
5
|
-
}>;
|
|
6
|
-
export type SessionLeaseMode = "shared" | "exclusive";
|
|
7
|
-
export interface AcquireSessionOptions {
|
|
8
|
-
mode: SessionLeaseMode;
|
|
9
|
-
}
|
|
10
|
-
export interface SessionLease extends AsyncDisposable {
|
|
11
|
-
readonly id: string;
|
|
12
|
-
readonly active: boolean;
|
|
13
|
-
readonly attached: boolean;
|
|
14
|
-
readonly snapshot: SessionSnapshot | undefined;
|
|
15
|
-
subscribe(listener: (snapshot: SessionSnapshot) => void): Unsubscribe;
|
|
16
|
-
onEvent(listener: (event: ServerEvent) => void): Unsubscribe;
|
|
17
|
-
detach(): Promise<void>;
|
|
18
|
-
dispose(): Promise<void>;
|
|
19
|
-
prompt(text: string): Promise<SessionSnapshot>;
|
|
20
|
-
steer(text: string): Promise<SessionSnapshot>;
|
|
21
|
-
abort(): Promise<SessionSnapshot>;
|
|
22
|
-
setModel(model: ModelRef): Promise<SessionSnapshot>;
|
|
23
|
-
setThinking(thinkingLevel: ThinkingLevel): Promise<SessionSnapshot>;
|
|
24
|
-
}
|
|
25
|
-
export type PiSessionHandle = SessionLease;
|
|
26
|
-
export interface SessionHandleCallbacks {
|
|
27
|
-
isAttached(): boolean;
|
|
28
|
-
getSnapshot(): SessionSnapshot | undefined;
|
|
29
|
-
subscribe(listener: (snapshot: SessionSnapshot) => void): Unsubscribe;
|
|
30
|
-
onEvent(listener: (event: ServerEvent) => void): Unsubscribe;
|
|
31
|
-
detach(): Promise<void>;
|
|
32
|
-
dispose(): Promise<void>;
|
|
33
|
-
request<const TCommand extends SessionCommand>(command: TCommand): Promise<ResultForCommand<TCommand>>;
|
|
34
|
-
}
|
|
35
|
-
export declare class SessionHandle implements SessionLease {
|
|
36
|
-
#private;
|
|
37
|
-
readonly id: string;
|
|
38
|
-
constructor(id: string, callbacks: SessionHandleCallbacks);
|
|
39
|
-
get attached(): boolean;
|
|
40
|
-
get active(): boolean;
|
|
41
|
-
get snapshot(): SessionSnapshot | undefined;
|
|
42
|
-
subscribe(listener: (snapshot: SessionSnapshot) => void): Unsubscribe;
|
|
43
|
-
onEvent(listener: (event: ServerEvent) => void): Unsubscribe;
|
|
44
|
-
detach(): Promise<void>;
|
|
45
|
-
dispose(): Promise<void>;
|
|
46
|
-
[Symbol.asyncDispose](): Promise<void>;
|
|
47
|
-
prompt(text: string): Promise<SessionSnapshot>;
|
|
48
|
-
steer(text: string): Promise<SessionSnapshot>;
|
|
49
|
-
abort(): Promise<SessionSnapshot>;
|
|
50
|
-
setModel(model: ModelRef): Promise<SessionSnapshot>;
|
|
51
|
-
setThinking(thinkingLevel: ThinkingLevel): Promise<SessionSnapshot>;
|
|
52
|
-
}
|
|
53
|
-
export {};
|
|
54
|
-
//# sourceMappingURL=session-handle.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"session-handle.d.ts","sourceRoot":"","sources":["../src/session-handle.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n\tCommand,\n\tModelRef,\n\tResultForCommand,\n\tServerEvent,\n\tSessionSnapshot,\n\tThinkingLevel,\n} from \"@earendil-works/pi-protocol\";\nimport type { Unsubscribe } from \"./types.ts\";\n\ntype SessionCommand = Extract<Command, { sessionId: string }>;\n\nexport type SessionLeaseMode = \"shared\" | \"exclusive\";\n\nexport interface AcquireSessionOptions {\n\tmode: SessionLeaseMode;\n}\n\nexport interface SessionLease extends AsyncDisposable {\n\treadonly id: string;\n\treadonly active: boolean;\n\treadonly attached: boolean;\n\treadonly snapshot: SessionSnapshot | undefined;\n\tsubscribe(listener: (snapshot: SessionSnapshot) => void): Unsubscribe;\n\tonEvent(listener: (event: ServerEvent) => void): Unsubscribe;\n\tdetach(): Promise<void>;\n\tdispose(): Promise<void>;\n\tprompt(text: string): Promise<SessionSnapshot>;\n\tsteer(text: string): Promise<SessionSnapshot>;\n\tabort(): Promise<SessionSnapshot>;\n\tsetModel(model: ModelRef): Promise<SessionSnapshot>;\n\tsetThinking(thinkingLevel: ThinkingLevel): Promise<SessionSnapshot>;\n}\n\nexport type PiSessionHandle = SessionLease;\n\nexport interface SessionHandleCallbacks {\n\tisAttached(): boolean;\n\tgetSnapshot(): SessionSnapshot | undefined;\n\tsubscribe(listener: (snapshot: SessionSnapshot) => void): Unsubscribe;\n\tonEvent(listener: (event: ServerEvent) => void): Unsubscribe;\n\tdetach(): Promise<void>;\n\tdispose(): Promise<void>;\n\trequest<const TCommand extends SessionCommand>(command: TCommand): Promise<ResultForCommand<TCommand>>;\n}\n\nexport class SessionHandle implements SessionLease {\n\treadonly id: string;\n\treadonly #callbacks: SessionHandleCallbacks;\n\n\tconstructor(id: string, callbacks: SessionHandleCallbacks) {\n\t\tthis.id = id;\n\t\tthis.#callbacks = callbacks;\n\t}\n\n\tget attached(): boolean {\n\t\treturn this.#callbacks.isAttached();\n\t}\n\n\tget active(): boolean {\n\t\treturn this.attached;\n\t}\n\n\tget snapshot(): SessionSnapshot | undefined {\n\t\treturn this.#callbacks.getSnapshot();\n\t}\n\n\tsubscribe(listener: (snapshot: SessionSnapshot) => void): Unsubscribe {\n\t\treturn this.#callbacks.subscribe(listener);\n\t}\n\n\tonEvent(listener: (event: ServerEvent) => void): Unsubscribe {\n\t\treturn this.#callbacks.onEvent(listener);\n\t}\n\n\tasync detach(): Promise<void> {\n\t\tawait this.#callbacks.detach();\n\t}\n\n\tdispose(): Promise<void> {\n\t\treturn this.#callbacks.dispose();\n\t}\n\n\t[Symbol.asyncDispose](): Promise<void> {\n\t\treturn this.dispose();\n\t}\n\n\tasync prompt(text: string): Promise<SessionSnapshot> {\n\t\treturn (await this.#request({ command: \"prompt\", sessionId: this.id, text })).session;\n\t}\n\n\tasync steer(text: string): Promise<SessionSnapshot> {\n\t\treturn (await this.#request({ command: \"steer\", sessionId: this.id, text })).session;\n\t}\n\n\tasync abort(): Promise<SessionSnapshot> {\n\t\treturn (await this.#request({ command: \"abort\", sessionId: this.id })).session;\n\t}\n\n\tasync setModel(model: ModelRef): Promise<SessionSnapshot> {\n\t\treturn (await this.#request({ command: \"set_model\", sessionId: this.id, model })).session;\n\t}\n\n\tasync setThinking(thinkingLevel: ThinkingLevel): Promise<SessionSnapshot> {\n\t\treturn (await this.#request({ command: \"set_thinking\", sessionId: this.id, thinkingLevel })).session;\n\t}\n\n\t#request<const TCommand extends SessionCommand>(command: TCommand): Promise<ResultForCommand<TCommand>> {\n\t\treturn this.#callbacks.request(command);\n\t}\n}\n"]}
|
package/dist/session-handle.js
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
export class SessionHandle {
|
|
2
|
-
id;
|
|
3
|
-
#callbacks;
|
|
4
|
-
constructor(id, callbacks) {
|
|
5
|
-
this.id = id;
|
|
6
|
-
this.#callbacks = callbacks;
|
|
7
|
-
}
|
|
8
|
-
get attached() {
|
|
9
|
-
return this.#callbacks.isAttached();
|
|
10
|
-
}
|
|
11
|
-
get active() {
|
|
12
|
-
return this.attached;
|
|
13
|
-
}
|
|
14
|
-
get snapshot() {
|
|
15
|
-
return this.#callbacks.getSnapshot();
|
|
16
|
-
}
|
|
17
|
-
subscribe(listener) {
|
|
18
|
-
return this.#callbacks.subscribe(listener);
|
|
19
|
-
}
|
|
20
|
-
onEvent(listener) {
|
|
21
|
-
return this.#callbacks.onEvent(listener);
|
|
22
|
-
}
|
|
23
|
-
async detach() {
|
|
24
|
-
await this.#callbacks.detach();
|
|
25
|
-
}
|
|
26
|
-
dispose() {
|
|
27
|
-
return this.#callbacks.dispose();
|
|
28
|
-
}
|
|
29
|
-
[Symbol.asyncDispose]() {
|
|
30
|
-
return this.dispose();
|
|
31
|
-
}
|
|
32
|
-
async prompt(text) {
|
|
33
|
-
return (await this.#request({ command: "prompt", sessionId: this.id, text })).session;
|
|
34
|
-
}
|
|
35
|
-
async steer(text) {
|
|
36
|
-
return (await this.#request({ command: "steer", sessionId: this.id, text })).session;
|
|
37
|
-
}
|
|
38
|
-
async abort() {
|
|
39
|
-
return (await this.#request({ command: "abort", sessionId: this.id })).session;
|
|
40
|
-
}
|
|
41
|
-
async setModel(model) {
|
|
42
|
-
return (await this.#request({ command: "set_model", sessionId: this.id, model })).session;
|
|
43
|
-
}
|
|
44
|
-
async setThinking(thinkingLevel) {
|
|
45
|
-
return (await this.#request({ command: "set_thinking", sessionId: this.id, thinkingLevel })).session;
|
|
46
|
-
}
|
|
47
|
-
#request(command) {
|
|
48
|
-
return this.#callbacks.request(command);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
//# sourceMappingURL=session-handle.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"session-handle.js","sourceRoot":"","sources":["../src/session-handle.ts"],"names":[],"mappings":"AA8CA,MAAM,OAAO,aAAa;IAChB,EAAE,CAAS;IACX,UAAU,CAAyB;IAE5C,YAAY,EAAU,EAAE,SAAiC,EAAE;QAC1D,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;IAAA,CAC5B;IAED,IAAI,QAAQ,GAAY;QACvB,OAAO,IAAI,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC;IAAA,CACpC;IAED,IAAI,MAAM,GAAY;QACrB,OAAO,IAAI,CAAC,QAAQ,CAAC;IAAA,CACrB;IAED,IAAI,QAAQ,GAAgC;QAC3C,OAAO,IAAI,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC;IAAA,CACrC;IAED,SAAS,CAAC,QAA6C,EAAe;QACrE,OAAO,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IAAA,CAC3C;IAED,OAAO,CAAC,QAAsC,EAAe;QAC5D,OAAO,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAAA,CACzC;IAED,KAAK,CAAC,MAAM,GAAkB;QAC7B,MAAM,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC;IAAA,CAC/B;IAED,OAAO,GAAkB;QACxB,OAAO,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;IAAA,CACjC;IAED,CAAC,MAAM,CAAC,YAAY,CAAC,GAAkB;QACtC,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC;IAAA,CACtB;IAED,KAAK,CAAC,MAAM,CAAC,IAAY,EAA4B;QACpD,OAAO,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;IAAA,CACtF;IAED,KAAK,CAAC,KAAK,CAAC,IAAY,EAA4B;QACnD,OAAO,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;IAAA,CACrF;IAED,KAAK,CAAC,KAAK,GAA6B;QACvC,OAAO,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;IAAA,CAC/E;IAED,KAAK,CAAC,QAAQ,CAAC,KAAe,EAA4B;QACzD,OAAO,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;IAAA,CAC1F;IAED,KAAK,CAAC,WAAW,CAAC,aAA4B,EAA4B;QACzE,OAAO,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,IAAI,CAAC,EAAE,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;IAAA,CACrG;IAED,QAAQ,CAAwC,OAAiB,EAAuC;QACvG,OAAO,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAAA,CACxC;CACD","sourcesContent":["import type {\n\tCommand,\n\tModelRef,\n\tResultForCommand,\n\tServerEvent,\n\tSessionSnapshot,\n\tThinkingLevel,\n} from \"@earendil-works/pi-protocol\";\nimport type { Unsubscribe } from \"./types.ts\";\n\ntype SessionCommand = Extract<Command, { sessionId: string }>;\n\nexport type SessionLeaseMode = \"shared\" | \"exclusive\";\n\nexport interface AcquireSessionOptions {\n\tmode: SessionLeaseMode;\n}\n\nexport interface SessionLease extends AsyncDisposable {\n\treadonly id: string;\n\treadonly active: boolean;\n\treadonly attached: boolean;\n\treadonly snapshot: SessionSnapshot | undefined;\n\tsubscribe(listener: (snapshot: SessionSnapshot) => void): Unsubscribe;\n\tonEvent(listener: (event: ServerEvent) => void): Unsubscribe;\n\tdetach(): Promise<void>;\n\tdispose(): Promise<void>;\n\tprompt(text: string): Promise<SessionSnapshot>;\n\tsteer(text: string): Promise<SessionSnapshot>;\n\tabort(): Promise<SessionSnapshot>;\n\tsetModel(model: ModelRef): Promise<SessionSnapshot>;\n\tsetThinking(thinkingLevel: ThinkingLevel): Promise<SessionSnapshot>;\n}\n\nexport type PiSessionHandle = SessionLease;\n\nexport interface SessionHandleCallbacks {\n\tisAttached(): boolean;\n\tgetSnapshot(): SessionSnapshot | undefined;\n\tsubscribe(listener: (snapshot: SessionSnapshot) => void): Unsubscribe;\n\tonEvent(listener: (event: ServerEvent) => void): Unsubscribe;\n\tdetach(): Promise<void>;\n\tdispose(): Promise<void>;\n\trequest<const TCommand extends SessionCommand>(command: TCommand): Promise<ResultForCommand<TCommand>>;\n}\n\nexport class SessionHandle implements SessionLease {\n\treadonly id: string;\n\treadonly #callbacks: SessionHandleCallbacks;\n\n\tconstructor(id: string, callbacks: SessionHandleCallbacks) {\n\t\tthis.id = id;\n\t\tthis.#callbacks = callbacks;\n\t}\n\n\tget attached(): boolean {\n\t\treturn this.#callbacks.isAttached();\n\t}\n\n\tget active(): boolean {\n\t\treturn this.attached;\n\t}\n\n\tget snapshot(): SessionSnapshot | undefined {\n\t\treturn this.#callbacks.getSnapshot();\n\t}\n\n\tsubscribe(listener: (snapshot: SessionSnapshot) => void): Unsubscribe {\n\t\treturn this.#callbacks.subscribe(listener);\n\t}\n\n\tonEvent(listener: (event: ServerEvent) => void): Unsubscribe {\n\t\treturn this.#callbacks.onEvent(listener);\n\t}\n\n\tasync detach(): Promise<void> {\n\t\tawait this.#callbacks.detach();\n\t}\n\n\tdispose(): Promise<void> {\n\t\treturn this.#callbacks.dispose();\n\t}\n\n\t[Symbol.asyncDispose](): Promise<void> {\n\t\treturn this.dispose();\n\t}\n\n\tasync prompt(text: string): Promise<SessionSnapshot> {\n\t\treturn (await this.#request({ command: \"prompt\", sessionId: this.id, text })).session;\n\t}\n\n\tasync steer(text: string): Promise<SessionSnapshot> {\n\t\treturn (await this.#request({ command: \"steer\", sessionId: this.id, text })).session;\n\t}\n\n\tasync abort(): Promise<SessionSnapshot> {\n\t\treturn (await this.#request({ command: \"abort\", sessionId: this.id })).session;\n\t}\n\n\tasync setModel(model: ModelRef): Promise<SessionSnapshot> {\n\t\treturn (await this.#request({ command: \"set_model\", sessionId: this.id, model })).session;\n\t}\n\n\tasync setThinking(thinkingLevel: ThinkingLevel): Promise<SessionSnapshot> {\n\t\treturn (await this.#request({ command: \"set_thinking\", sessionId: this.id, thinkingLevel })).session;\n\t}\n\n\t#request<const TCommand extends SessionCommand>(command: TCommand): Promise<ResultForCommand<TCommand>> {\n\t\treturn this.#callbacks.request(command);\n\t}\n}\n"]}
|
package/dist/state.d.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import type { CommandResult, ServerEvent, ServerSnapshot, SessionSnapshot } from "@earendil-works/pi-protocol";
|
|
2
|
-
import type { ListenerErrorHandler, Unsubscribe } from "./types.ts";
|
|
3
|
-
export declare class ClientState {
|
|
4
|
-
#private;
|
|
5
|
-
constructor(onListenerError?: ListenerErrorHandler);
|
|
6
|
-
get snapshot(): ServerSnapshot | undefined;
|
|
7
|
-
reset(): void;
|
|
8
|
-
clearAttachments(): void;
|
|
9
|
-
dispose(): void;
|
|
10
|
-
getSessionSnapshot(sessionId: string): SessionSnapshot | undefined;
|
|
11
|
-
isSessionAttached(sessionId: string): boolean;
|
|
12
|
-
forgetSessionSnapshot(sessionId: string): SessionSnapshot | undefined;
|
|
13
|
-
restoreSessionSnapshot(snapshot: SessionSnapshot): void;
|
|
14
|
-
subscribe(listener: (snapshot: ServerSnapshot) => void): Unsubscribe;
|
|
15
|
-
onEvent(listener: (event: ServerEvent) => void): Unsubscribe;
|
|
16
|
-
subscribeSession(sessionId: string, listener: (snapshot: SessionSnapshot) => void): Unsubscribe;
|
|
17
|
-
onSessionEvent(sessionId: string, listener: (event: ServerEvent) => void): Unsubscribe;
|
|
18
|
-
applyResult(result: CommandResult): void;
|
|
19
|
-
applyEvent(event: ServerEvent): void;
|
|
20
|
-
applyServerSnapshot(snapshot: ServerSnapshot): void;
|
|
21
|
-
}
|
|
22
|
-
//# sourceMappingURL=state.d.ts.map
|
package/dist/state.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAE/G,OAAO,KAAK,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEpE,qBAAa,WAAW;;IAUvB,YAAY,eAAe,CAAC,EAAE,oBAAoB,EAEjD;IAED,IAAI,QAAQ,IAAI,cAAc,GAAG,SAAS,CAEzC;IAED,KAAK,IAAI,IAAI,CAIZ;IAED,gBAAgB,IAAI,IAAI,CAEvB;IAED,OAAO,IAAI,IAAI,CAMd;IAED,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAEjE;IAED,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAE5C;IAED,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAIpE;IAED,sBAAsB,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI,CAEtD;IAED,SAAS,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,cAAc,KAAK,IAAI,GAAG,WAAW,CAGnE;IAED,OAAO,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,GAAG,WAAW,CAG3D;IAED,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,QAAQ,EAAE,eAAe,KAAK,IAAI,GAAG,WAAW,CAE9F;IAED,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,GAAG,WAAW,CAErF;IAED,WAAW,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CASvC;IAED,UAAU,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAUnC;IAED,mBAAmB,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAIlD;CA6BD","sourcesContent":["import type { CommandResult, ServerEvent, ServerSnapshot, SessionSnapshot } from \"@earendil-works/pi-protocol\";\nimport { toError } from \"./errors.ts\";\nimport type { ListenerErrorHandler, Unsubscribe } from \"./types.ts\";\n\nexport class ClientState {\n\treadonly #sessionSnapshots = new Map<string, SessionSnapshot>();\n\treadonly #attachedSessionIds = new Set<string>();\n\treadonly #snapshotListeners = new Set<(snapshot: ServerSnapshot) => void>();\n\treadonly #eventListeners = new Set<(event: ServerEvent) => void>();\n\treadonly #sessionSnapshotListeners = new Map<string, Set<(snapshot: SessionSnapshot) => void>>();\n\treadonly #sessionEventListeners = new Map<string, Set<(event: ServerEvent) => void>>();\n\treadonly #onListenerError: ListenerErrorHandler | undefined;\n\t#snapshot: ServerSnapshot | undefined;\n\n\tconstructor(onListenerError?: ListenerErrorHandler) {\n\t\tthis.#onListenerError = onListenerError;\n\t}\n\n\tget snapshot(): ServerSnapshot | undefined {\n\t\treturn this.#snapshot;\n\t}\n\n\treset(): void {\n\t\tthis.#snapshot = undefined;\n\t\tthis.#sessionSnapshots.clear();\n\t\tthis.#attachedSessionIds.clear();\n\t}\n\n\tclearAttachments(): void {\n\t\tthis.#attachedSessionIds.clear();\n\t}\n\n\tdispose(): void {\n\t\tthis.reset();\n\t\tthis.#snapshotListeners.clear();\n\t\tthis.#eventListeners.clear();\n\t\tthis.#sessionSnapshotListeners.clear();\n\t\tthis.#sessionEventListeners.clear();\n\t}\n\n\tgetSessionSnapshot(sessionId: string): SessionSnapshot | undefined {\n\t\treturn this.#sessionSnapshots.get(sessionId);\n\t}\n\n\tisSessionAttached(sessionId: string): boolean {\n\t\treturn this.#attachedSessionIds.has(sessionId);\n\t}\n\n\tforgetSessionSnapshot(sessionId: string): SessionSnapshot | undefined {\n\t\tconst previous = this.#sessionSnapshots.get(sessionId);\n\t\tthis.#sessionSnapshots.delete(sessionId);\n\t\treturn previous;\n\t}\n\n\trestoreSessionSnapshot(snapshot: SessionSnapshot): void {\n\t\tif (!this.#sessionSnapshots.has(snapshot.id)) this.#sessionSnapshots.set(snapshot.id, snapshot);\n\t}\n\n\tsubscribe(listener: (snapshot: ServerSnapshot) => void): Unsubscribe {\n\t\tthis.#snapshotListeners.add(listener);\n\t\treturn () => this.#snapshotListeners.delete(listener);\n\t}\n\n\tonEvent(listener: (event: ServerEvent) => void): Unsubscribe {\n\t\tthis.#eventListeners.add(listener);\n\t\treturn () => this.#eventListeners.delete(listener);\n\t}\n\n\tsubscribeSession(sessionId: string, listener: (snapshot: SessionSnapshot) => void): Unsubscribe {\n\t\treturn addMappedListener(this.#sessionSnapshotListeners, sessionId, listener);\n\t}\n\n\tonSessionEvent(sessionId: string, listener: (event: ServerEvent) => void): Unsubscribe {\n\t\treturn addMappedListener(this.#sessionEventListeners, sessionId, listener);\n\t}\n\n\tapplyResult(result: CommandResult): void {\n\t\tif (result.command === \"list\") return;\n\t\tif (result.command === \"detach\") {\n\t\t\tthis.#attachedSessionIds.delete(result.sessionId);\n\t\t\tconst snapshot = this.#sessionSnapshots.get(result.sessionId);\n\t\t\tif (snapshot) this.#applySessionSnapshot({ ...snapshot, attached: false }, true);\n\t\t\treturn;\n\t\t}\n\t\tthis.#applySessionSnapshot(result.session);\n\t}\n\n\tapplyEvent(event: ServerEvent): void {\n\t\tif (event.type === \"server_snapshot\") this.applyServerSnapshot(event.snapshot);\n\t\tif (event.type === \"session_snapshot\") this.#applySessionSnapshot(event.snapshot);\n\t\tif (event.type === \"session_removed\") {\n\t\t\tthis.#sessionSnapshots.delete(event.sessionId);\n\t\t\tthis.#attachedSessionIds.delete(event.sessionId);\n\t\t}\n\t\tthis.#notify(this.#eventListeners, event);\n\t\tconst sessionId = getEventSessionId(event);\n\t\tif (sessionId) this.#notify(this.#sessionEventListeners.get(sessionId), event);\n\t}\n\n\tapplyServerSnapshot(snapshot: ServerSnapshot): void {\n\t\tif (this.#snapshot && snapshot.revision < this.#snapshot.revision) return;\n\t\tthis.#snapshot = snapshot;\n\t\tthis.#notify(this.#snapshotListeners, snapshot);\n\t}\n\n\t#applySessionSnapshot(snapshot: SessionSnapshot, force = false): void {\n\t\tconst current = this.#sessionSnapshots.get(snapshot.id);\n\t\tif (!force && current && snapshot.revision < current.revision) return;\n\t\tthis.#sessionSnapshots.set(snapshot.id, snapshot);\n\t\tif (snapshot.attached) this.#attachedSessionIds.add(snapshot.id);\n\t\telse this.#attachedSessionIds.delete(snapshot.id);\n\t\tthis.#notify(this.#sessionSnapshotListeners.get(snapshot.id), snapshot);\n\t}\n\n\t#notify<T>(listeners: Iterable<(value: T) => void> | undefined, value: T): void {\n\t\tfor (const listener of listeners ?? []) {\n\t\t\ttry {\n\t\t\t\tlistener(value);\n\t\t\t} catch (error) {\n\t\t\t\tthis.#reportListenerError(error);\n\t\t\t}\n\t\t}\n\t}\n\n\t#reportListenerError(error: unknown): void {\n\t\tif (!this.#onListenerError) return;\n\t\ttry {\n\t\t\tthis.#onListenerError(toError(error));\n\t\t} catch {\n\t\t\t// Diagnostics cannot affect client state.\n\t\t}\n\t}\n}\n\nfunction addMappedListener<T>(\n\tlistenersById: Map<string, Set<(value: T) => void>>,\n\tid: string,\n\tlistener: (value: T) => void,\n): Unsubscribe {\n\tlet listeners = listenersById.get(id);\n\tif (!listeners) {\n\t\tlisteners = new Set();\n\t\tlistenersById.set(id, listeners);\n\t}\n\tlisteners.add(listener);\n\treturn () => {\n\t\tlisteners.delete(listener);\n\t\tif (listeners.size === 0) listenersById.delete(id);\n\t};\n}\n\nfunction getEventSessionId(event: ServerEvent): string | undefined {\n\tif (event.type === \"session_snapshot\") return event.snapshot.id;\n\tif (event.type === \"session_progress\" || event.type === \"session_removed\") return event.sessionId;\n\treturn undefined;\n}\n"]}
|