@m4ike1/ion-server 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.
Files changed (67) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/README.md +99 -0
  3. package/dist/connection.d.ts +32 -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 +25 -0
  8. package/dist/errors.d.ts.map +1 -0
  9. package/dist/errors.js +41 -0
  10. package/dist/errors.js.map +1 -0
  11. package/dist/index.d.ts +5 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +5 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/listener.d.ts +8 -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/server.d.ts +46 -0
  20. package/dist/server.d.ts.map +1 -0
  21. package/dist/server.js +547 -0
  22. package/dist/server.js.map +1 -0
  23. package/dist/session-router.d.ts +40 -0
  24. package/dist/session-router.d.ts.map +1 -0
  25. package/dist/session-router.js +264 -0
  26. package/dist/session-router.js.map +1 -0
  27. package/dist/testing/client.d.ts +35 -0
  28. package/dist/testing/client.d.ts.map +1 -0
  29. package/dist/testing/client.js +138 -0
  30. package/dist/testing/client.js.map +1 -0
  31. package/dist/testing/host.d.ts +57 -0
  32. package/dist/testing/host.d.ts.map +1 -0
  33. package/dist/testing/host.js +189 -0
  34. package/dist/testing/host.js.map +1 -0
  35. package/dist/testing/index.d.ts +6 -0
  36. package/dist/testing/index.d.ts.map +1 -0
  37. package/dist/testing/index.js +4 -0
  38. package/dist/testing/index.js.map +1 -0
  39. package/dist/testing/server.d.ts +13 -0
  40. package/dist/testing/server.d.ts.map +1 -0
  41. package/dist/testing/server.js +17 -0
  42. package/dist/testing/server.js.map +1 -0
  43. package/dist/transports/unix/address.d.ts +3 -0
  44. package/dist/transports/unix/address.d.ts.map +1 -0
  45. package/dist/transports/unix/address.js +9 -0
  46. package/dist/transports/unix/address.js.map +1 -0
  47. package/dist/transports/unix/index.d.ts +5 -0
  48. package/dist/transports/unix/index.d.ts.map +1 -0
  49. package/dist/transports/unix/index.js +4 -0
  50. package/dist/transports/unix/index.js.map +1 -0
  51. package/dist/transports/unix/listener.d.ts +24 -0
  52. package/dist/transports/unix/listener.d.ts.map +1 -0
  53. package/dist/transports/unix/listener.js +421 -0
  54. package/dist/transports/unix/listener.js.map +1 -0
  55. package/dist/transports/unix/preset.d.ts +7 -0
  56. package/dist/transports/unix/preset.d.ts.map +1 -0
  57. package/dist/transports/unix/preset.js +22 -0
  58. package/dist/transports/unix/preset.js.map +1 -0
  59. package/dist/transports/unix/types.d.ts +15 -0
  60. package/dist/transports/unix/types.d.ts.map +1 -0
  61. package/dist/transports/unix/types.js +2 -0
  62. package/dist/transports/unix/types.js.map +1 -0
  63. package/dist/types.d.ts +49 -0
  64. package/dist/types.d.ts.map +1 -0
  65. package/dist/types.js +2 -0
  66. package/dist/types.js.map +1 -0
  67. package/package.json +58 -0
@@ -0,0 +1,421 @@
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 "@m4ike1/ion-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
+ class UnixListener {
12
+ options;
13
+ path;
14
+ mode;
15
+ connections = new Set();
16
+ server;
17
+ socketIdentity;
18
+ ownedBindPath;
19
+ closing = false;
20
+ closePromise;
21
+ accept;
22
+ constructor(options) {
23
+ this.options = resolveUnixListenerOptions(options);
24
+ this.path = this.options.path;
25
+ this.mode = this.options.mode;
26
+ }
27
+ async start(accept) {
28
+ if (this.server)
29
+ throw new Error("Unix listener is already started");
30
+ if (this.closing)
31
+ throw new Error("Unix listener is closing or closed");
32
+ this.accept = accept;
33
+ const ownedBindPath = getOwnedBindPath(this.path);
34
+ await mkdir(dirname(this.path), { recursive: true, mode: 0o700 });
35
+ await removeStaleSocket(this.path);
36
+ await removeStaleSocket(ownedBindPath);
37
+ this.ownedBindPath = ownedBindPath;
38
+ const server = createServer((socket) => this.acceptSocket(socket));
39
+ server.on("error", (error) => this.reportError(error));
40
+ this.server = server;
41
+ try {
42
+ await new Promise((resolve, reject) => {
43
+ const onError = (error) => {
44
+ server.off("listening", onListening);
45
+ reject(error);
46
+ };
47
+ const onListening = () => {
48
+ server.off("error", onError);
49
+ resolve();
50
+ };
51
+ server.once("error", onError);
52
+ server.once("listening", onListening);
53
+ server.listen(ownedBindPath);
54
+ });
55
+ const stats = await lstat(ownedBindPath);
56
+ if (!stats.isSocket())
57
+ throw new Error(`Unix listener path is not a socket after binding: ${ownedBindPath}`);
58
+ this.socketIdentity = { dev: stats.dev, ino: stats.ino };
59
+ await link(ownedBindPath, this.path);
60
+ await setSocketMode(this.path, this.mode);
61
+ await removePath(ownedBindPath);
62
+ this.ownedBindPath = undefined;
63
+ }
64
+ catch (error) {
65
+ await this.closeServerAndCleanup(server);
66
+ this.server = undefined;
67
+ throw error;
68
+ }
69
+ }
70
+ async close() {
71
+ if (this.closePromise)
72
+ return this.closePromise;
73
+ this.closing = true;
74
+ this.closePromise = this.closeInternal();
75
+ return this.closePromise;
76
+ }
77
+ acceptSocket(socket) {
78
+ if (this.closing) {
79
+ socket.destroy();
80
+ return;
81
+ }
82
+ const connection = new UnixByteConnection(socket, this.options.gracefulCloseTimeoutMs, this.options.maxPendingBytes);
83
+ this.connections.add(connection);
84
+ const accept = this.accept;
85
+ if (!accept) {
86
+ socket.destroy();
87
+ return;
88
+ }
89
+ const handler = accept(connection);
90
+ socket.on("data", (chunk) => {
91
+ handler.onData(new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength));
92
+ });
93
+ socket.on("error", (error) => {
94
+ handler.onError(error);
95
+ socket.destroy();
96
+ });
97
+ socket.once("close", () => {
98
+ connection.markClosed();
99
+ this.connections.delete(connection);
100
+ handler.onClose();
101
+ });
102
+ }
103
+ async closeInternal() {
104
+ const serverClosed = this.server ? this.closeServerAndCleanup(this.server) : this.cleanupOwnedSocket();
105
+ await Promise.all([...this.connections].map((connection) => connection.close()));
106
+ await serverClosed;
107
+ if (this.ownedBindPath)
108
+ await removePath(this.ownedBindPath);
109
+ this.ownedBindPath = undefined;
110
+ this.connections.clear();
111
+ this.server = undefined;
112
+ }
113
+ async closeServerAndCleanup(server) {
114
+ try {
115
+ await closeNetServer(server, (error) => this.reportError(error));
116
+ }
117
+ finally {
118
+ // Remove an unpublished startup bind path before the public route.
119
+ if (this.ownedBindPath)
120
+ await removePath(this.ownedBindPath);
121
+ this.ownedBindPath = undefined;
122
+ await this.cleanupOwnedSocket();
123
+ }
124
+ }
125
+ async cleanupOwnedSocket() {
126
+ const identity = this.socketIdentity;
127
+ this.socketIdentity = undefined;
128
+ if (!identity)
129
+ return;
130
+ let current;
131
+ try {
132
+ current = await lstat(this.path);
133
+ }
134
+ catch (error) {
135
+ if (isErrorCode(error, "ENOENT"))
136
+ return;
137
+ throw error;
138
+ }
139
+ if (!current.isSocket() || current.dev !== identity.dev || current.ino !== identity.ino)
140
+ return;
141
+ const preserved = join(dirname(this.path), `cleanup-${randomUUID().slice(0, 6)}`);
142
+ try {
143
+ await rename(this.path, preserved);
144
+ }
145
+ catch (error) {
146
+ if (isErrorCode(error, "ENOENT"))
147
+ return;
148
+ throw error;
149
+ }
150
+ const moved = await lstat(preserved);
151
+ if (moved.isSocket() && moved.dev === identity.dev && moved.ino === identity.ino) {
152
+ await removePath(preserved);
153
+ return;
154
+ }
155
+ try {
156
+ await lstat(this.path);
157
+ }
158
+ catch (error) {
159
+ if (isErrorCode(error, "ENOENT"))
160
+ await rename(preserved, this.path);
161
+ else
162
+ throw error;
163
+ }
164
+ throw new Error(`Unix listener path changed during cleanup; preserved replacement at ${preserved}`);
165
+ }
166
+ reportError(error) {
167
+ try {
168
+ this.options.onError?.(error instanceof Error ? error : new Error(String(error)));
169
+ }
170
+ catch {
171
+ // Error observers cannot affect listener state.
172
+ }
173
+ }
174
+ }
175
+ /** @internal Exported only for transport-level verification. */
176
+ export class UnixByteConnection {
177
+ socket;
178
+ gracefulCloseTimeoutMs;
179
+ maxPendingBytes;
180
+ pendingBytes = 0;
181
+ closedValue = false;
182
+ closing = false;
183
+ writeTail = Promise.resolve();
184
+ closePromise;
185
+ resolveClose;
186
+ constructor(socket, gracefulCloseTimeoutMs, maxPendingBytes) {
187
+ this.socket = socket;
188
+ this.gracefulCloseTimeoutMs = gracefulCloseTimeoutMs;
189
+ this.maxPendingBytes = maxPendingBytes;
190
+ }
191
+ get closed() {
192
+ return this.closedValue;
193
+ }
194
+ send(chunk) {
195
+ if (!(chunk instanceof Uint8Array)) {
196
+ return Promise.reject(new TypeError("Unix connection chunks must be Uint8Array"));
197
+ }
198
+ if (this.closedValue || this.closing)
199
+ return Promise.reject(new Error("Unix connection is closed"));
200
+ if (this.pendingBytes + chunk.byteLength > this.maxPendingBytes) {
201
+ return Promise.reject(new Error("Unix connection exceeded its pending byte limit"));
202
+ }
203
+ this.pendingBytes += chunk.byteLength;
204
+ const bytes = chunk.slice();
205
+ const write = this.writeTail.then(() => this.write(bytes));
206
+ const tracked = write.finally(() => {
207
+ this.pendingBytes -= bytes.byteLength;
208
+ });
209
+ this.writeTail = tracked.catch(() => { });
210
+ return tracked;
211
+ }
212
+ close(finalChunk) {
213
+ if (this.closedValue || this.socket.destroyed) {
214
+ this.markClosed();
215
+ return Promise.resolve();
216
+ }
217
+ if (this.closePromise)
218
+ return this.closePromise;
219
+ this.closing = true;
220
+ const finalBytes = finalChunk?.slice();
221
+ this.closePromise = new Promise((resolve) => {
222
+ this.resolveClose = resolve;
223
+ const timer = setTimeout(() => {
224
+ if (!this.socket.destroyed)
225
+ this.socket.destroy();
226
+ this.markClosed();
227
+ }, this.gracefulCloseTimeoutMs);
228
+ timer.unref();
229
+ this.socket.once("close", () => clearTimeout(timer));
230
+ void this.writeTail.then(() => {
231
+ if (this.socket.destroyed) {
232
+ this.markClosed();
233
+ return;
234
+ }
235
+ try {
236
+ if (finalBytes)
237
+ this.socket.end(finalBytes);
238
+ else
239
+ this.socket.end();
240
+ }
241
+ catch {
242
+ this.socket.destroy();
243
+ }
244
+ });
245
+ });
246
+ return this.closePromise;
247
+ }
248
+ markClosed() {
249
+ if (this.closedValue)
250
+ return;
251
+ this.closedValue = true;
252
+ this.closing = true;
253
+ this.resolveClose?.();
254
+ this.resolveClose = undefined;
255
+ }
256
+ write(chunk) {
257
+ if (this.closedValue || this.closing || !this.socket.writable) {
258
+ return Promise.reject(new Error("Unix connection is closed"));
259
+ }
260
+ return new Promise((resolve, reject) => {
261
+ let settled = false;
262
+ const onClose = () => finish(new Error("Unix connection closed during write"));
263
+ const finish = (error) => {
264
+ if (settled)
265
+ return;
266
+ settled = true;
267
+ this.socket.off("close", onClose);
268
+ if (error)
269
+ reject(error);
270
+ else
271
+ resolve();
272
+ };
273
+ this.socket.once("close", onClose);
274
+ try {
275
+ this.socket.write(chunk, finish);
276
+ }
277
+ catch (error) {
278
+ finish(error instanceof Error ? error : new Error(String(error)));
279
+ }
280
+ });
281
+ }
282
+ }
283
+ function getOwnedBindPath(path) {
284
+ const suffix = createHash("sha256").update(path).digest("hex").slice(0, 8);
285
+ return join(dirname(path), `bind-${suffix}`);
286
+ }
287
+ async function removeStaleSocket(path) {
288
+ let original;
289
+ try {
290
+ original = await lstat(path);
291
+ }
292
+ catch (error) {
293
+ if (isErrorCode(error, "ENOENT"))
294
+ return;
295
+ throw error;
296
+ }
297
+ if (!original.isSocket())
298
+ throw new Error(`Refusing to remove non-socket Unix listener path: ${path}`);
299
+ if (await isSocketLive(path))
300
+ throw new Error(`Unix listener is already running: ${path}`);
301
+ const preserved = join(dirname(path), `stale-${randomUUID().slice(0, 6)}`);
302
+ try {
303
+ await rename(path, preserved);
304
+ }
305
+ catch (error) {
306
+ if (isErrorCode(error, "ENOENT"))
307
+ return;
308
+ throw error;
309
+ }
310
+ const current = await lstat(preserved);
311
+ if (!current.isSocket() || current.dev !== original.dev || current.ino !== original.ino) {
312
+ try {
313
+ await lstat(path);
314
+ }
315
+ catch (error) {
316
+ if (isErrorCode(error, "ENOENT"))
317
+ await rename(preserved, path);
318
+ else
319
+ throw error;
320
+ }
321
+ throw new Error(`Unix listener path changed while checking for a stale socket: ${path}`);
322
+ }
323
+ await removePath(preserved);
324
+ }
325
+ async function removePath(path) {
326
+ try {
327
+ await unlink(path);
328
+ }
329
+ catch (error) {
330
+ if (!isErrorCode(error, "ENOENT"))
331
+ throw error;
332
+ }
333
+ }
334
+ function isSocketLive(path) {
335
+ return new Promise((resolve, reject) => {
336
+ const socket = createConnection(path);
337
+ let settled = false;
338
+ let timer;
339
+ const finish = (result, error) => {
340
+ if (settled)
341
+ return;
342
+ settled = true;
343
+ if (timer)
344
+ clearTimeout(timer);
345
+ socket.removeAllListeners();
346
+ socket.destroy();
347
+ if (error)
348
+ reject(error);
349
+ else
350
+ resolve(result);
351
+ };
352
+ socket.once("connect", () => finish(true));
353
+ socket.once("error", (error) => {
354
+ if (["ECONNREFUSED", "ENOENT", "EPIPE", "ECONNRESET"].includes(error.code ?? "")) {
355
+ finish(false);
356
+ return;
357
+ }
358
+ finish(false, error);
359
+ });
360
+ timer = setTimeout(() => finish(true), SOCKET_PROBE_TIMEOUT_MS);
361
+ timer.unref();
362
+ });
363
+ }
364
+ async function setSocketMode(path, mode) {
365
+ if (process.platform === "win32")
366
+ return;
367
+ try {
368
+ await chmod(path, mode);
369
+ }
370
+ catch (error) {
371
+ if (!isErrorCode(error, "ENOSYS") && !isErrorCode(error, "ENOTSUP"))
372
+ throw error;
373
+ }
374
+ }
375
+ function closeNetServer(server, reportError) {
376
+ if (!server.listening)
377
+ return Promise.resolve();
378
+ return new Promise((resolve) => {
379
+ server.close((error) => {
380
+ if (error)
381
+ reportError(error);
382
+ resolve();
383
+ });
384
+ });
385
+ }
386
+ function isErrorCode(error, code) {
387
+ return error instanceof Error && "code" in error && error.code === code;
388
+ }
389
+ export function createUnixListener(options) {
390
+ return new UnixListener(options);
391
+ }
392
+ function resolveUnixListenerOptions(options) {
393
+ if (!options.path)
394
+ throw new TypeError("Server Unix socket path must not be empty");
395
+ const mode = options.mode ?? DEFAULT_SOCKET_MODE;
396
+ if (!Number.isInteger(mode) || mode < 0 || mode > 0o777) {
397
+ throw new TypeError("Server Unix socket mode must be an integer between 0 and 0o777");
398
+ }
399
+ const maxFrameLength = options.maxFrameLength ?? DEFAULT_MAX_FRAME_LENGTH;
400
+ if (!Number.isSafeInteger(maxFrameLength) || maxFrameLength <= 0 || maxFrameLength > MAX_UINT32) {
401
+ throw new TypeError(`Server maxFrameLength must be an integer between 1 and ${MAX_UINT32}`);
402
+ }
403
+ const maxPendingBytes = options.maxPendingBytes ?? maxFrameLength * 4;
404
+ if (!Number.isSafeInteger(maxPendingBytes) || maxPendingBytes < maxFrameLength + 4) {
405
+ throw new TypeError("Server maxPendingBytes must be a safe integer at least maxFrameLength + 4");
406
+ }
407
+ const gracefulCloseTimeoutMs = options.gracefulCloseTimeoutMs ?? DEFAULT_GRACEFUL_CLOSE_TIMEOUT_MS;
408
+ if (!Number.isSafeInteger(gracefulCloseTimeoutMs) ||
409
+ gracefulCloseTimeoutMs <= 0 ||
410
+ gracefulCloseTimeoutMs > MAX_TIMER_DELAY_MS) {
411
+ throw new TypeError(`Server gracefulCloseTimeoutMs must be an integer between 1 and ${MAX_TIMER_DELAY_MS}`);
412
+ }
413
+ return {
414
+ path: options.path,
415
+ mode,
416
+ maxPendingBytes,
417
+ gracefulCloseTimeoutMs,
418
+ onError: options.onError,
419
+ };
420
+ }
421
+ //# 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 \"@m4ike1/ion-protocol\";\nimport type { ByteConnection, ByteConnectionAcceptor } from \"../../connection.ts\";\nimport type { ServerListener } from \"../../listener.ts\";\nimport type { UnixListenerOptions } from \"./types.ts\";\n\nconst DEFAULT_SOCKET_MODE = 0o600;\nconst DEFAULT_GRACEFUL_CLOSE_TIMEOUT_MS = 5_000;\nconst MAX_UINT32 = 0xffff_ffff;\nconst MAX_TIMER_DELAY_MS = 2_147_483_647;\nconst SOCKET_PROBE_TIMEOUT_MS = 1_000;\n\ninterface ResolvedUnixListenerOptions {\n\tpath: string;\n\tmode: number;\n\tgracefulCloseTimeoutMs: number;\n\tmaxPendingBytes: number;\n\tonError?: (error: Error) => void;\n}\n\ninterface FileIdentity {\n\tdev: number;\n\tino: number;\n}\nclass UnixListener implements ServerListener {\n\tprivate readonly options: ResolvedUnixListenerOptions;\n\tprivate readonly path: string;\n\tprivate readonly mode: number;\n\tprivate readonly connections = new Set<UnixByteConnection>();\n\tprivate server?: Server;\n\tprivate socketIdentity?: FileIdentity;\n\tprivate ownedBindPath?: string;\n\tprivate closing = false;\n\tprivate closePromise?: Promise<void>;\n\tprivate accept?: ByteConnectionAcceptor;\n\n\tconstructor(options: UnixListenerOptions) {\n\t\tthis.options = resolveUnixListenerOptions(options);\n\t\tthis.path = this.options.path;\n\t\tthis.mode = this.options.mode;\n\t}\n\n\tasync start(accept: ByteConnectionAcceptor): Promise<void> {\n\t\tif (this.server) throw new Error(\"Unix listener is already started\");\n\t\tif (this.closing) throw new Error(\"Unix listener is closing or closed\");\n\t\tthis.accept = accept;\n\n\t\tconst ownedBindPath = getOwnedBindPath(this.path);\n\t\tawait mkdir(dirname(this.path), { recursive: true, mode: 0o700 });\n\t\tawait removeStaleSocket(this.path);\n\t\tawait removeStaleSocket(ownedBindPath);\n\t\tthis.ownedBindPath = ownedBindPath;\n\t\tconst server = createServer((socket) => this.acceptSocket(socket));\n\t\tserver.on(\"error\", (error) => this.reportError(error));\n\t\tthis.server = server;\n\t\ttry {\n\t\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\t\tconst onError = (error: Error): void => {\n\t\t\t\t\tserver.off(\"listening\", onListening);\n\t\t\t\t\treject(error);\n\t\t\t\t};\n\t\t\t\tconst onListening = (): void => {\n\t\t\t\t\tserver.off(\"error\", onError);\n\t\t\t\t\tresolve();\n\t\t\t\t};\n\t\t\t\tserver.once(\"error\", onError);\n\t\t\t\tserver.once(\"listening\", onListening);\n\t\t\t\tserver.listen(ownedBindPath);\n\t\t\t});\n\t\t\tconst stats = await lstat(ownedBindPath);\n\t\t\tif (!stats.isSocket()) throw new Error(`Unix listener path is not a socket after binding: ${ownedBindPath}`);\n\t\t\tthis.socketIdentity = { dev: stats.dev, ino: stats.ino };\n\t\t\tawait link(ownedBindPath, this.path);\n\t\t\tawait setSocketMode(this.path, this.mode);\n\t\t\tawait removePath(ownedBindPath);\n\t\t\tthis.ownedBindPath = undefined;\n\t\t} catch (error) {\n\t\t\tawait this.closeServerAndCleanup(server);\n\t\t\tthis.server = undefined;\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tasync close(): Promise<void> {\n\t\tif (this.closePromise) return this.closePromise;\n\t\tthis.closing = true;\n\t\tthis.closePromise = this.closeInternal();\n\t\treturn this.closePromise;\n\t}\n\n\tprivate acceptSocket(socket: Socket): void {\n\t\tif (this.closing) {\n\t\t\tsocket.destroy();\n\t\t\treturn;\n\t\t}\n\t\tconst connection = new UnixByteConnection(\n\t\t\tsocket,\n\t\t\tthis.options.gracefulCloseTimeoutMs,\n\t\t\tthis.options.maxPendingBytes,\n\t\t);\n\t\tthis.connections.add(connection);\n\t\tconst accept = this.accept;\n\t\tif (!accept) {\n\t\t\tsocket.destroy();\n\t\t\treturn;\n\t\t}\n\t\tconst handler = accept(connection);\n\t\tsocket.on(\"data\", (chunk) => {\n\t\t\thandler.onData(new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength));\n\t\t});\n\t\tsocket.on(\"error\", (error) => {\n\t\t\thandler.onError(error);\n\t\t\tsocket.destroy();\n\t\t});\n\t\tsocket.once(\"close\", () => {\n\t\t\tconnection.markClosed();\n\t\t\tthis.connections.delete(connection);\n\t\t\thandler.onClose();\n\t\t});\n\t}\n\n\tprivate async closeInternal(): Promise<void> {\n\t\tconst serverClosed = this.server ? this.closeServerAndCleanup(this.server) : this.cleanupOwnedSocket();\n\t\tawait Promise.all([...this.connections].map((connection) => connection.close()));\n\t\tawait serverClosed;\n\t\tif (this.ownedBindPath) await removePath(this.ownedBindPath);\n\t\tthis.ownedBindPath = undefined;\n\t\tthis.connections.clear();\n\t\tthis.server = undefined;\n\t}\n\n\tprivate async closeServerAndCleanup(server: Server): Promise<void> {\n\t\ttry {\n\t\t\tawait closeNetServer(server, (error) => this.reportError(error));\n\t\t} finally {\n\t\t\t// Remove an unpublished startup bind path before the public route.\n\t\t\tif (this.ownedBindPath) await removePath(this.ownedBindPath);\n\t\t\tthis.ownedBindPath = undefined;\n\t\t\tawait this.cleanupOwnedSocket();\n\t\t}\n\t}\n\n\tprivate async cleanupOwnedSocket(): Promise<void> {\n\t\tconst identity = this.socketIdentity;\n\t\tthis.socketIdentity = undefined;\n\t\tif (!identity) return;\n\t\tlet current: Stats;\n\t\ttry {\n\t\t\tcurrent = await lstat(this.path);\n\t\t} catch (error) {\n\t\t\tif (isErrorCode(error, \"ENOENT\")) return;\n\t\t\tthrow error;\n\t\t}\n\t\tif (!current.isSocket() || current.dev !== identity.dev || current.ino !== identity.ino) return;\n\n\t\tconst preserved = join(dirname(this.path), `cleanup-${randomUUID().slice(0, 6)}`);\n\t\ttry {\n\t\t\tawait rename(this.path, preserved);\n\t\t} catch (error) {\n\t\t\tif (isErrorCode(error, \"ENOENT\")) return;\n\t\t\tthrow error;\n\t\t}\n\t\tconst moved = await lstat(preserved);\n\t\tif (moved.isSocket() && moved.dev === identity.dev && moved.ino === identity.ino) {\n\t\t\tawait removePath(preserved);\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tawait lstat(this.path);\n\t\t} catch (error) {\n\t\t\tif (isErrorCode(error, \"ENOENT\")) await rename(preserved, this.path);\n\t\t\telse throw error;\n\t\t}\n\t\tthrow new Error(`Unix listener path changed during cleanup; preserved replacement at ${preserved}`);\n\t}\n\n\tprivate reportError(error: unknown): void {\n\t\ttry {\n\t\t\tthis.options.onError?.(error instanceof Error ? error : new Error(String(error)));\n\t\t} catch {\n\t\t\t// Error observers cannot affect listener state.\n\t\t}\n\t}\n}\n\n/** @internal Exported only for transport-level verification. */\nexport class UnixByteConnection implements ByteConnection {\n\tprivate readonly socket: Socket;\n\tprivate readonly gracefulCloseTimeoutMs: number;\n\tprivate readonly maxPendingBytes: number;\n\tprivate pendingBytes = 0;\n\tprivate closedValue = false;\n\tprivate closing = false;\n\tprivate writeTail: Promise<void> = Promise.resolve();\n\tprivate closePromise?: Promise<void>;\n\tprivate resolveClose?: () => void;\n\n\tconstructor(socket: Socket, gracefulCloseTimeoutMs: number, maxPendingBytes: number) {\n\t\tthis.socket = socket;\n\t\tthis.gracefulCloseTimeoutMs = gracefulCloseTimeoutMs;\n\t\tthis.maxPendingBytes = maxPendingBytes;\n\t}\n\n\tget closed(): boolean {\n\t\treturn this.closedValue;\n\t}\n\n\tsend(chunk: Uint8Array): Promise<void> {\n\t\tif (!(chunk instanceof Uint8Array)) {\n\t\t\treturn Promise.reject(new TypeError(\"Unix connection chunks must be Uint8Array\"));\n\t\t}\n\t\tif (this.closedValue || this.closing) return Promise.reject(new Error(\"Unix connection is closed\"));\n\t\tif (this.pendingBytes + chunk.byteLength > this.maxPendingBytes) {\n\t\t\treturn Promise.reject(new Error(\"Unix connection exceeded its pending byte limit\"));\n\t\t}\n\t\tthis.pendingBytes += chunk.byteLength;\n\t\tconst bytes = chunk.slice();\n\t\tconst write = this.writeTail.then(() => this.write(bytes));\n\t\tconst tracked = write.finally(() => {\n\t\t\tthis.pendingBytes -= bytes.byteLength;\n\t\t});\n\t\tthis.writeTail = tracked.catch(() => {});\n\t\treturn tracked;\n\t}\n\n\tclose(finalChunk?: Uint8Array): Promise<void> {\n\t\tif (this.closedValue || this.socket.destroyed) {\n\t\t\tthis.markClosed();\n\t\t\treturn Promise.resolve();\n\t\t}\n\t\tif (this.closePromise) return this.closePromise;\n\t\tthis.closing = true;\n\t\tconst finalBytes = finalChunk?.slice();\n\t\tthis.closePromise = new Promise<void>((resolve) => {\n\t\t\tthis.resolveClose = resolve;\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tif (!this.socket.destroyed) this.socket.destroy();\n\t\t\t\tthis.markClosed();\n\t\t\t}, this.gracefulCloseTimeoutMs);\n\t\t\ttimer.unref();\n\t\t\tthis.socket.once(\"close\", () => clearTimeout(timer));\n\t\t\tvoid this.writeTail.then(() => {\n\t\t\t\tif (this.socket.destroyed) {\n\t\t\t\t\tthis.markClosed();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tif (finalBytes) this.socket.end(finalBytes);\n\t\t\t\t\telse this.socket.end();\n\t\t\t\t} catch {\n\t\t\t\t\tthis.socket.destroy();\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t\treturn this.closePromise;\n\t}\n\n\tmarkClosed(): void {\n\t\tif (this.closedValue) return;\n\t\tthis.closedValue = true;\n\t\tthis.closing = true;\n\t\tthis.resolveClose?.();\n\t\tthis.resolveClose = undefined;\n\t}\n\n\tprivate write(chunk: Uint8Array): Promise<void> {\n\t\tif (this.closedValue || this.closing || !this.socket.writable) {\n\t\t\treturn Promise.reject(new Error(\"Unix connection is closed\"));\n\t\t}\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\tlet settled = false;\n\t\t\tconst onClose = (): void => finish(new Error(\"Unix connection closed during write\"));\n\t\t\tconst finish = (error?: Error | null): void => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tthis.socket.off(\"close\", onClose);\n\t\t\t\tif (error) reject(error);\n\t\t\t\telse resolve();\n\t\t\t};\n\t\t\tthis.socket.once(\"close\", onClose);\n\t\t\ttry {\n\t\t\t\tthis.socket.write(chunk, finish);\n\t\t\t} catch (error) {\n\t\t\t\tfinish(error instanceof Error ? error : new Error(String(error)));\n\t\t\t}\n\t\t});\n\t}\n}\n\nfunction getOwnedBindPath(path: string): string {\n\tconst suffix = createHash(\"sha256\").update(path).digest(\"hex\").slice(0, 8);\n\treturn join(dirname(path), `bind-${suffix}`);\n}\n\nasync function removeStaleSocket(path: string): Promise<void> {\n\tlet original: Stats;\n\ttry {\n\t\toriginal = await lstat(path);\n\t} catch (error) {\n\t\tif (isErrorCode(error, \"ENOENT\")) return;\n\t\tthrow error;\n\t}\n\tif (!original.isSocket()) throw new Error(`Refusing to remove non-socket Unix listener path: ${path}`);\n\tif (await isSocketLive(path)) throw new Error(`Unix listener is already running: ${path}`);\n\n\tconst preserved = join(dirname(path), `stale-${randomUUID().slice(0, 6)}`);\n\ttry {\n\t\tawait rename(path, preserved);\n\t} catch (error) {\n\t\tif (isErrorCode(error, \"ENOENT\")) return;\n\t\tthrow error;\n\t}\n\tconst current = await lstat(preserved);\n\tif (!current.isSocket() || current.dev !== original.dev || current.ino !== original.ino) {\n\t\ttry {\n\t\t\tawait lstat(path);\n\t\t} catch (error) {\n\t\t\tif (isErrorCode(error, \"ENOENT\")) await rename(preserved, path);\n\t\t\telse throw error;\n\t\t}\n\t\tthrow new Error(`Unix listener path changed while checking for a stale socket: ${path}`);\n\t}\n\tawait removePath(preserved);\n}\n\nasync function removePath(path: string): Promise<void> {\n\ttry {\n\t\tawait unlink(path);\n\t} catch (error) {\n\t\tif (!isErrorCode(error, \"ENOENT\")) throw error;\n\t}\n}\n\nfunction isSocketLive(path: string): Promise<boolean> {\n\treturn new Promise<boolean>((resolve, reject) => {\n\t\tconst socket = createConnection(path);\n\t\tlet settled = false;\n\t\tlet timer: NodeJS.Timeout | undefined;\n\t\tconst finish = (result: boolean, error?: Error): void => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tif (timer) clearTimeout(timer);\n\t\t\tsocket.removeAllListeners();\n\t\t\tsocket.destroy();\n\t\t\tif (error) reject(error);\n\t\t\telse resolve(result);\n\t\t};\n\t\tsocket.once(\"connect\", () => finish(true));\n\t\tsocket.once(\"error\", (error: NodeJS.ErrnoException) => {\n\t\t\tif ([\"ECONNREFUSED\", \"ENOENT\", \"EPIPE\", \"ECONNRESET\"].includes(error.code ?? \"\")) {\n\t\t\t\tfinish(false);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tfinish(false, error);\n\t\t});\n\t\ttimer = setTimeout(() => finish(true), SOCKET_PROBE_TIMEOUT_MS);\n\t\ttimer.unref();\n\t});\n}\n\nasync function setSocketMode(path: string, mode: number): Promise<void> {\n\tif (process.platform === \"win32\") return;\n\ttry {\n\t\tawait chmod(path, mode);\n\t} catch (error) {\n\t\tif (!isErrorCode(error, \"ENOSYS\") && !isErrorCode(error, \"ENOTSUP\")) throw error;\n\t}\n}\n\nfunction closeNetServer(server: Server, reportError: (error: Error) => void): Promise<void> {\n\tif (!server.listening) return Promise.resolve();\n\treturn new Promise<void>((resolve) => {\n\t\tserver.close((error) => {\n\t\t\tif (error) reportError(error);\n\t\t\tresolve();\n\t\t});\n\t});\n}\n\nfunction isErrorCode(error: unknown, code: string): boolean {\n\treturn error instanceof Error && \"code\" in error && error.code === code;\n}\n\nexport function createUnixListener(options: UnixListenerOptions): ServerListener {\n\treturn new UnixListener(options);\n}\n\nfunction resolveUnixListenerOptions(options: UnixListenerOptions): ResolvedUnixListenerOptions {\n\tif (!options.path) throw new TypeError(\"Server Unix socket path must not be empty\");\n\tconst mode = options.mode ?? DEFAULT_SOCKET_MODE;\n\tif (!Number.isInteger(mode) || mode < 0 || mode > 0o777) {\n\t\tthrow new TypeError(\"Server Unix socket mode must be an integer between 0 and 0o777\");\n\t}\n\tconst maxFrameLength = options.maxFrameLength ?? DEFAULT_MAX_FRAME_LENGTH;\n\tif (!Number.isSafeInteger(maxFrameLength) || maxFrameLength <= 0 || maxFrameLength > MAX_UINT32) {\n\t\tthrow new TypeError(`Server maxFrameLength must be an integer between 1 and ${MAX_UINT32}`);\n\t}\n\tconst maxPendingBytes = options.maxPendingBytes ?? maxFrameLength * 4;\n\tif (!Number.isSafeInteger(maxPendingBytes) || maxPendingBytes < maxFrameLength + 4) {\n\t\tthrow new TypeError(\"Server maxPendingBytes must be a safe integer at least maxFrameLength + 4\");\n\t}\n\tconst gracefulCloseTimeoutMs = options.gracefulCloseTimeoutMs ?? DEFAULT_GRACEFUL_CLOSE_TIMEOUT_MS;\n\tif (\n\t\t!Number.isSafeInteger(gracefulCloseTimeoutMs) ||\n\t\tgracefulCloseTimeoutMs <= 0 ||\n\t\tgracefulCloseTimeoutMs > MAX_TIMER_DELAY_MS\n\t) {\n\t\tthrow new TypeError(`Server gracefulCloseTimeoutMs must be an integer between 1 and ${MAX_TIMER_DELAY_MS}`);\n\t}\n\treturn {\n\t\tpath: options.path,\n\t\tmode,\n\t\tmaxPendingBytes,\n\t\tgracefulCloseTimeoutMs,\n\t\tonError: options.onError,\n\t};\n}\n"]}
@@ -0,0 +1,7 @@
1
+ import type { SessionMetadata } from "@m4ike1/ion-agent-core";
2
+ import { Server } from "../../server.ts";
3
+ import type { ServerHost } from "../../types.ts";
4
+ import type { UnixServerOptions } from "./types.ts";
5
+ /** Compose Server with one Unix-domain socket listener. */
6
+ export declare function createUnixServer<TMetadata extends SessionMetadata>(host: ServerHost<TMetadata>, options: UnixServerOptions): Server<TMetadata>;
7
+ //# 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,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD,2DAA2D;AAC3D,wBAAgB,gBAAgB,CAAC,SAAS,SAAS,eAAe,EACjE,IAAI,EAAE,UAAU,CAAC,SAAS,CAAC,EAC3B,OAAO,EAAE,iBAAiB,GACxB,MAAM,CAAC,SAAS,CAAC,CAiBnB","sourcesContent":["import type { SessionMetadata } from \"@m4ike1/ion-agent-core\";\nimport { Server } from \"../../server.ts\";\nimport type { ServerHost } from \"../../types.ts\";\nimport { createUnixListener } from \"./listener.ts\";\nimport type { UnixServerOptions } from \"./types.ts\";\n\n/** Compose Server with one Unix-domain socket listener. */\nexport function createUnixServer<TMetadata extends SessionMetadata>(\n\thost: ServerHost<TMetadata>,\n\toptions: UnixServerOptions,\n): Server<TMetadata> {\n\tconst listener = createUnixListener({\n\t\tpath: options.path,\n\t\tmode: options.mode,\n\t\tmaxFrameLength: options.maxFrameLength,\n\t\tmaxPendingBytes: options.maxPendingBytes,\n\t\tgracefulCloseTimeoutMs: options.gracefulCloseTimeoutMs,\n\t\tonError: options.onError,\n\t});\n\treturn new Server(host, {\n\t\tlisteners: [listener],\n\t\tmaxFrameLength: options.maxFrameLength,\n\t\thandshakeTimeoutMs: options.handshakeTimeoutMs,\n\t\tonConnectionCountChanged: options.onConnectionCountChanged,\n\t\tserverId: options.serverId,\n\t\tonError: options.onError,\n\t});\n}\n"]}
@@ -0,0 +1,22 @@
1
+ import { Server } from "../../server.js";
2
+ import { createUnixListener } from "./listener.js";
3
+ /** Compose Server with one Unix-domain socket listener. */
4
+ export function createUnixServer(host, 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 Server(host, {
14
+ listeners: [listener],
15
+ maxFrameLength: options.maxFrameLength,
16
+ handshakeTimeoutMs: options.handshakeTimeoutMs,
17
+ onConnectionCountChanged: options.onConnectionCountChanged,
18
+ serverId: options.serverId,
19
+ onError: options.onError,
20
+ });
21
+ }
22
+ //# sourceMappingURL=preset.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preset.js","sourceRoot":"","sources":["../../../src/transports/unix/preset.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAEzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAGnD,2DAA2D;AAC3D,MAAM,UAAU,gBAAgB,CAC/B,IAA2B,EAC3B,OAA0B,EACN;IACpB,MAAM,QAAQ,GAAG,kBAAkB,CAAC;QACnC,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,cAAc,EAAE,OAAO,CAAC,cAAc;QACtC,eAAe,EAAE,OAAO,CAAC,eAAe;QACxC,sBAAsB,EAAE,OAAO,CAAC,sBAAsB;QACtD,OAAO,EAAE,OAAO,CAAC,OAAO;KACxB,CAAC,CAAC;IACH,OAAO,IAAI,MAAM,CAAC,IAAI,EAAE;QACvB,SAAS,EAAE,CAAC,QAAQ,CAAC;QACrB,cAAc,EAAE,OAAO,CAAC,cAAc;QACtC,kBAAkB,EAAE,OAAO,CAAC,kBAAkB;QAC9C,wBAAwB,EAAE,OAAO,CAAC,wBAAwB;QAC1D,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,OAAO,EAAE,OAAO,CAAC,OAAO;KACxB,CAAC,CAAC;AAAA,CACH","sourcesContent":["import type { SessionMetadata } from \"@m4ike1/ion-agent-core\";\nimport { Server } from \"../../server.ts\";\nimport type { ServerHost } from \"../../types.ts\";\nimport { createUnixListener } from \"./listener.ts\";\nimport type { UnixServerOptions } from \"./types.ts\";\n\n/** Compose Server with one Unix-domain socket listener. */\nexport function createUnixServer<TMetadata extends SessionMetadata>(\n\thost: ServerHost<TMetadata>,\n\toptions: UnixServerOptions,\n): Server<TMetadata> {\n\tconst listener = createUnixListener({\n\t\tpath: options.path,\n\t\tmode: options.mode,\n\t\tmaxFrameLength: options.maxFrameLength,\n\t\tmaxPendingBytes: options.maxPendingBytes,\n\t\tgracefulCloseTimeoutMs: options.gracefulCloseTimeoutMs,\n\t\tonError: options.onError,\n\t});\n\treturn new Server(host, {\n\t\tlisteners: [listener],\n\t\tmaxFrameLength: options.maxFrameLength,\n\t\thandshakeTimeoutMs: options.handshakeTimeoutMs,\n\t\tonConnectionCountChanged: options.onConnectionCountChanged,\n\t\tserverId: options.serverId,\n\t\tonError: options.onError,\n\t});\n}\n"]}
@@ -0,0 +1,15 @@
1
+ import type { ServerOptions } 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<ServerOptions, "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,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD,MAAM,WAAW,mBAAmB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,gFAAgF;IAChF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qFAAqF;IACrF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,0FAA0F;IAC1F,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CACjC;AAED,MAAM,WAAW,iBAAkB,SAAQ,IAAI,CAAC,aAAa,EAAE,WAAW,CAAC,EAAE,mBAAmB;CAAG","sourcesContent":["import type { ServerOptions } from \"../../types.ts\";\n\nexport interface UnixListenerOptions {\n\tpath: string;\n\t/** Socket filesystem permissions. Defaults to owner read/write only (0o600). */\n\tmode?: number;\n\t/** Maximum framed bytes queued per connection before a slow peer is disconnected. */\n\tmaxPendingBytes?: number;\n\tgracefulCloseTimeoutMs?: number;\n\t/** Used to derive and validate maxPendingBytes. Must match the server when customized. */\n\tmaxFrameLength?: number;\n\tonError?: (error: Error) => void;\n}\n\nexport interface UnixServerOptions extends Omit<ServerOptions, \"listeners\">, UnixListenerOptions {}\n"]}
@@ -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 { ServerOptions } from \"../../types.ts\";\n\nexport interface UnixListenerOptions {\n\tpath: string;\n\t/** Socket filesystem permissions. Defaults to owner read/write only (0o600). */\n\tmode?: number;\n\t/** Maximum framed bytes queued per connection before a slow peer is disconnected. */\n\tmaxPendingBytes?: number;\n\tgracefulCloseTimeoutMs?: number;\n\t/** Used to derive and validate maxPendingBytes. Must match the server when customized. */\n\tmaxFrameLength?: number;\n\tonError?: (error: Error) => void;\n}\n\nexport interface UnixServerOptions extends Omit<ServerOptions, \"listeners\">, UnixListenerOptions {}\n"]}
@@ -0,0 +1,49 @@
1
+ import type { JsonValue, ServiceCall, ServiceProviderUpdate } from "@m4ike1/chord";
2
+ import type { Context, SessionMetadata } from "@m4ike1/ion-agent-core";
3
+ import type { ServerListener } from "./listener.ts";
4
+ export interface ServerOptions {
5
+ listeners: readonly ServerListener[];
6
+ /** Stable logical server identity supplied by the installation or profile. */
7
+ serverId: string;
8
+ maxFrameLength?: number;
9
+ handshakeTimeoutMs?: number;
10
+ onConnectionCountChanged?: (count: number) => void;
11
+ onError?: (error: Error) => void;
12
+ }
13
+ export type MaybePromise<T> = T | Promise<T>;
14
+ /** One presentation connection's live capability for a hosted Session. */
15
+ export interface RoutedSessionAttachment {
16
+ /** Route one contract-agnostic service operation to the attached Session endpoint. */
17
+ invokeService(call: ServiceCall, publish: (subscriptionId: string, update: ServiceProviderUpdate, context: Context) => MaybePromise<void>, context: Context): Promise<JsonValue | undefined>;
18
+ release(context: Context): MaybePromise<void>;
19
+ }
20
+ /** Presentation-scoped routing capabilities available to server service implementations. */
21
+ export interface RoutedServerPresentation {
22
+ attachSession(sessionId: string, context: Context): Promise<void>;
23
+ detachSession(context: Context): Promise<void>;
24
+ /** Release routed attachments and handles before the application deletes durable metadata. */
25
+ prepareSessionRemoval(sessionId: string, context: Context): Promise<void>;
26
+ }
27
+ /** One connection's server-scoped service endpoint. */
28
+ export interface RoutedServerServiceAttachment {
29
+ invokeService(call: ServiceCall, publish: (subscriptionId: string, update: ServiceProviderUpdate, context: Context) => MaybePromise<void>, context: Context): Promise<JsonValue | undefined>;
30
+ release(context: Context): MaybePromise<void>;
31
+ }
32
+ export interface RoutedServerServiceHost {
33
+ attachClient(presentation: RoutedServerPresentation, context: Context): MaybePromise<RoutedServerServiceAttachment>;
34
+ }
35
+ /** A process-safe handle that acquires presentation-scoped Session capabilities. */
36
+ export interface RoutedSessionHandle {
37
+ attachClient(context: Context): MaybePromise<RoutedSessionAttachment>;
38
+ /** Resolves with an error for unexpected termination, or undefined after an expected close. */
39
+ readonly terminated?: Promise<Error | undefined>;
40
+ close(context: Context): Promise<void>;
41
+ }
42
+ /** Application capabilities used by server-wide management and Session routing. */
43
+ export interface ServerHost<TMetadata extends SessionMetadata = SessionMetadata> {
44
+ readonly serverServices: RoutedServerServiceHost;
45
+ /** Resolve one durable Session ID or throw a bounded routing error. */
46
+ resolveSession(sessionId: string, context: Context): Promise<TMetadata>;
47
+ openSession(metadata: TMetadata, context: Context): Promise<RoutedSessionHandle>;
48
+ }
49
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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","sourcesContent":["import type { JsonValue, ServiceCall, ServiceProviderUpdate } from \"@m4ike1/chord\";\nimport type { Context, SessionMetadata } from \"@m4ike1/ion-agent-core\";\nimport type { ServerListener } from \"./listener.ts\";\n\nexport interface ServerOptions {\n\tlisteners: readonly ServerListener[];\n\t/** Stable logical server identity supplied by the installation or profile. */\n\tserverId: string;\n\tmaxFrameLength?: number;\n\thandshakeTimeoutMs?: number;\n\tonConnectionCountChanged?: (count: number) => void;\n\tonError?: (error: Error) => void;\n}\n\nexport type MaybePromise<T> = T | Promise<T>;\n\n/** One presentation connection's live capability for a hosted Session. */\nexport interface RoutedSessionAttachment {\n\t/** Route one contract-agnostic service operation to the attached Session endpoint. */\n\tinvokeService(\n\t\tcall: ServiceCall,\n\t\tpublish: (subscriptionId: string, update: ServiceProviderUpdate, context: Context) => MaybePromise<void>,\n\t\tcontext: Context,\n\t): Promise<JsonValue | undefined>;\n\trelease(context: Context): MaybePromise<void>;\n}\n\n/** Presentation-scoped routing capabilities available to server service implementations. */\nexport interface RoutedServerPresentation {\n\tattachSession(sessionId: string, context: Context): Promise<void>;\n\tdetachSession(context: Context): Promise<void>;\n\t/** Release routed attachments and handles before the application deletes durable metadata. */\n\tprepareSessionRemoval(sessionId: string, context: Context): Promise<void>;\n}\n\n/** One connection's server-scoped service endpoint. */\nexport interface RoutedServerServiceAttachment {\n\tinvokeService(\n\t\tcall: ServiceCall,\n\t\tpublish: (subscriptionId: string, update: ServiceProviderUpdate, context: Context) => MaybePromise<void>,\n\t\tcontext: Context,\n\t): Promise<JsonValue | undefined>;\n\trelease(context: Context): MaybePromise<void>;\n}\n\nexport interface RoutedServerServiceHost {\n\tattachClient(presentation: RoutedServerPresentation, context: Context): MaybePromise<RoutedServerServiceAttachment>;\n}\n\n/** A process-safe handle that acquires presentation-scoped Session capabilities. */\nexport interface RoutedSessionHandle {\n\tattachClient(context: Context): MaybePromise<RoutedSessionAttachment>;\n\t/** Resolves with an error for unexpected termination, or undefined after an expected close. */\n\treadonly terminated?: Promise<Error | undefined>;\n\tclose(context: Context): Promise<void>;\n}\n\n/** Application capabilities used by server-wide management and Session routing. */\nexport interface ServerHost<TMetadata extends SessionMetadata = SessionMetadata> {\n\treadonly serverServices: RoutedServerServiceHost;\n\t/** Resolve one durable Session ID or throw a bounded routing error. */\n\tresolveSession(sessionId: string, context: Context): Promise<TMetadata>;\n\topenSession(metadata: TMetadata, context: Context): Promise<RoutedSessionHandle>;\n}\n"]}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { JsonValue, ServiceCall, ServiceProviderUpdate } from \"@m4ike1/chord\";\nimport type { Context, SessionMetadata } from \"@m4ike1/ion-agent-core\";\nimport type { ServerListener } from \"./listener.ts\";\n\nexport interface ServerOptions {\n\tlisteners: readonly ServerListener[];\n\t/** Stable logical server identity supplied by the installation or profile. */\n\tserverId: string;\n\tmaxFrameLength?: number;\n\thandshakeTimeoutMs?: number;\n\tonConnectionCountChanged?: (count: number) => void;\n\tonError?: (error: Error) => void;\n}\n\nexport type MaybePromise<T> = T | Promise<T>;\n\n/** One presentation connection's live capability for a hosted Session. */\nexport interface RoutedSessionAttachment {\n\t/** Route one contract-agnostic service operation to the attached Session endpoint. */\n\tinvokeService(\n\t\tcall: ServiceCall,\n\t\tpublish: (subscriptionId: string, update: ServiceProviderUpdate, context: Context) => MaybePromise<void>,\n\t\tcontext: Context,\n\t): Promise<JsonValue | undefined>;\n\trelease(context: Context): MaybePromise<void>;\n}\n\n/** Presentation-scoped routing capabilities available to server service implementations. */\nexport interface RoutedServerPresentation {\n\tattachSession(sessionId: string, context: Context): Promise<void>;\n\tdetachSession(context: Context): Promise<void>;\n\t/** Release routed attachments and handles before the application deletes durable metadata. */\n\tprepareSessionRemoval(sessionId: string, context: Context): Promise<void>;\n}\n\n/** One connection's server-scoped service endpoint. */\nexport interface RoutedServerServiceAttachment {\n\tinvokeService(\n\t\tcall: ServiceCall,\n\t\tpublish: (subscriptionId: string, update: ServiceProviderUpdate, context: Context) => MaybePromise<void>,\n\t\tcontext: Context,\n\t): Promise<JsonValue | undefined>;\n\trelease(context: Context): MaybePromise<void>;\n}\n\nexport interface RoutedServerServiceHost {\n\tattachClient(presentation: RoutedServerPresentation, context: Context): MaybePromise<RoutedServerServiceAttachment>;\n}\n\n/** A process-safe handle that acquires presentation-scoped Session capabilities. */\nexport interface RoutedSessionHandle {\n\tattachClient(context: Context): MaybePromise<RoutedSessionAttachment>;\n\t/** Resolves with an error for unexpected termination, or undefined after an expected close. */\n\treadonly terminated?: Promise<Error | undefined>;\n\tclose(context: Context): Promise<void>;\n}\n\n/** Application capabilities used by server-wide management and Session routing. */\nexport interface ServerHost<TMetadata extends SessionMetadata = SessionMetadata> {\n\treadonly serverServices: RoutedServerServiceHost;\n\t/** Resolve one durable Session ID or throw a bounded routing error. */\n\tresolveSession(sessionId: string, context: Context): Promise<TMetadata>;\n\topenSession(metadata: TMetadata, context: Context): Promise<RoutedSessionHandle>;\n}\n"]}