@m4ike1/ion-client 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/unix.js ADDED
@@ -0,0 +1,281 @@
1
+ import { lstat, readdir } from "node:fs/promises";
2
+ import { createConnection } from "node:net";
3
+ import { join } from "node:path";
4
+ import { DEFAULT_MAX_FRAME_LENGTH, isServerId, ProtocolValidationError } from "@m4ike1/ion-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. */
69
+ export function createUnixTransportFactory(options) {
70
+ const maxPendingBytes = validateUnixTransportOptions(options);
71
+ return (handlers) => connectUnixSocket(options.path, maxPendingBytes, handlers);
72
+ }
73
+ function validateUnixTransportOptions(options) {
74
+ if (options.path.length === 0)
75
+ throw new TypeError("Unix transport path must not be empty");
76
+ const maxPendingBytes = options.maxPendingBytes ?? DEFAULT_MAX_FRAME_LENGTH * 4;
77
+ if (!Number.isSafeInteger(maxPendingBytes) || maxPendingBytes <= 0) {
78
+ throw new TypeError("Unix transport maxPendingBytes must be a positive safe integer");
79
+ }
80
+ if (process.platform === "win32")
81
+ throw new Error("Unix transport is not supported on Windows");
82
+ return maxPendingBytes;
83
+ }
84
+ function connectUnixSocket(path, maxPendingBytes, handlers, onSocket) {
85
+ return new Promise((resolve, reject) => {
86
+ const socket = createConnection(path);
87
+ onSocket?.(socket);
88
+ let connected = false;
89
+ let terminal = false;
90
+ const close = () => {
91
+ if (terminal)
92
+ return;
93
+ terminal = true;
94
+ socket.destroy();
95
+ if (connected)
96
+ handlers.onClose();
97
+ else
98
+ reject(new Error("Unix transport closed before connecting"));
99
+ };
100
+ socket.once("connect", () => {
101
+ if (terminal)
102
+ return;
103
+ connected = true;
104
+ resolve(new UnixByteTransport(socket, maxPendingBytes, () => {
105
+ terminal = true;
106
+ }));
107
+ });
108
+ socket.on("data", (chunk) => {
109
+ if (!terminal)
110
+ handlers.onData(new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength));
111
+ });
112
+ socket.once("end", close);
113
+ socket.once("close", close);
114
+ socket.once("error", (error) => {
115
+ if (terminal)
116
+ return;
117
+ terminal = true;
118
+ socket.destroy();
119
+ if (connected)
120
+ handlers.onError(error);
121
+ else
122
+ reject(error);
123
+ });
124
+ });
125
+ }
126
+ class UnixByteTransport {
127
+ #socket;
128
+ #maxPendingBytes;
129
+ #markLocalClose;
130
+ #closed = false;
131
+ #pendingBytes = 0;
132
+ #writeTail = Promise.resolve();
133
+ constructor(socket, maxPendingBytes, markLocalClose) {
134
+ this.#socket = socket;
135
+ this.#maxPendingBytes = maxPendingBytes;
136
+ this.#markLocalClose = markLocalClose;
137
+ }
138
+ send(chunk) {
139
+ if (!(chunk instanceof Uint8Array)) {
140
+ return Promise.reject(new TypeError("Unix transport chunks must be Uint8Array"));
141
+ }
142
+ if (this.#closed)
143
+ return Promise.reject(new Error("Unix transport is closed"));
144
+ if (this.#pendingBytes + chunk.byteLength > this.#maxPendingBytes) {
145
+ return Promise.reject(new Error("Unix transport exceeded its pending byte limit"));
146
+ }
147
+ this.#pendingBytes += chunk.byteLength;
148
+ const bytes = chunk.slice();
149
+ const write = this.#writeTail.then(() => this.#write(bytes));
150
+ const tracked = write.finally(() => {
151
+ this.#pendingBytes -= bytes.byteLength;
152
+ });
153
+ this.#writeTail = tracked.catch(() => { });
154
+ return tracked;
155
+ }
156
+ close() {
157
+ if (this.#closed)
158
+ return;
159
+ this.#closed = true;
160
+ this.#markLocalClose();
161
+ this.#socket.destroy();
162
+ }
163
+ #write(chunk) {
164
+ if (this.#closed || !this.#socket.writable)
165
+ return Promise.reject(new Error("Unix transport is closed"));
166
+ return new Promise((resolve, reject) => {
167
+ let callbackComplete = false;
168
+ let drainComplete = false;
169
+ let requiresDrain;
170
+ let settled = false;
171
+ const onDrain = () => {
172
+ drainComplete = true;
173
+ finish();
174
+ };
175
+ const cleanup = () => {
176
+ this.#socket.off("drain", onDrain);
177
+ this.#socket.off("close", onClose);
178
+ };
179
+ const fail = (error) => {
180
+ if (settled)
181
+ return;
182
+ settled = true;
183
+ cleanup();
184
+ reject(error);
185
+ };
186
+ const finish = () => {
187
+ if (settled || !callbackComplete || requiresDrain === undefined)
188
+ return;
189
+ if (requiresDrain && !drainComplete)
190
+ return;
191
+ settled = true;
192
+ cleanup();
193
+ resolve();
194
+ };
195
+ const onClose = () => fail(new Error("Unix transport closed during write"));
196
+ try {
197
+ this.#socket.once("close", onClose);
198
+ const accepted = this.#socket.write(chunk, (error) => {
199
+ if (error) {
200
+ fail(error);
201
+ return;
202
+ }
203
+ callbackComplete = true;
204
+ finish();
205
+ });
206
+ requiresDrain = !accepted;
207
+ if (requiresDrain)
208
+ this.#socket.once("drain", onDrain);
209
+ finish();
210
+ }
211
+ catch (error) {
212
+ fail(error instanceof Error ? error : new Error(String(error)));
213
+ }
214
+ });
215
+ }
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
+ }
281
+ //# sourceMappingURL=unix.js.map
@@ -0,0 +1 @@
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 { DEFAULT_MAX_FRAME_LENGTH, isServerId, ProtocolValidationError, type ServerId } from \"@m4ike1/ion-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 ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@m4ike1/ion-client",
3
+ "version": "0.1.0",
4
+ "description": "Transport-neutral client for remote ion sessions over framed CBOR bytes",
5
+ "keywords": [
6
+ "binary",
7
+ "cbor",
8
+ "client",
9
+ "ion",
10
+ "protocol"
11
+ ],
12
+ "license": "MIT",
13
+ "author": "Earendil Works",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/maikel/ion.git",
17
+ "directory": "packages/client"
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "README.md",
22
+ "CHANGELOG.md"
23
+ ],
24
+ "type": "module",
25
+ "sideEffects": false,
26
+ "main": "./dist/index.js",
27
+ "types": "./dist/index.d.ts",
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/index.d.ts",
31
+ "import": "./dist/index.js"
32
+ },
33
+ "./unix": {
34
+ "types": "./dist/unix.d.ts",
35
+ "import": "./dist/unix.js"
36
+ },
37
+ "./package.json": "./package.json"
38
+ },
39
+ "scripts": {
40
+ "clean": "shx rm -rf dist",
41
+ "build": "tsgo -p tsconfig.build.json",
42
+ "test": "vp test --run",
43
+ "typecheck": "tsgo -p tsconfig.test.json",
44
+ "prepublishOnly": "npm run clean && npm run build"
45
+ },
46
+ "dependencies": {
47
+ "@m4ike1/chord": "^0.1.0",
48
+ "@m4ike1/ion-protocol": "^0.1.0"
49
+ },
50
+ "devDependencies": {
51
+ "shx": "0.4.0",
52
+ "vitest": "4.1.11"
53
+ },
54
+ "engines": {
55
+ "node": ">=22.19.0"
56
+ }
57
+ }