@earendil-works/pi-client 0.84.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/state.js ADDED
@@ -0,0 +1,145 @@
1
+ import { toError } from "./errors.js";
2
+ export class ClientState {
3
+ #sessionSnapshots = new Map();
4
+ #attachedSessionIds = new Set();
5
+ #snapshotListeners = new Set();
6
+ #eventListeners = new Set();
7
+ #sessionSnapshotListeners = new Map();
8
+ #sessionEventListeners = new Map();
9
+ #onListenerError;
10
+ #snapshot;
11
+ constructor(onListenerError) {
12
+ this.#onListenerError = onListenerError;
13
+ }
14
+ get snapshot() {
15
+ return this.#snapshot;
16
+ }
17
+ reset() {
18
+ this.#snapshot = undefined;
19
+ this.#sessionSnapshots.clear();
20
+ this.#attachedSessionIds.clear();
21
+ }
22
+ clearAttachments() {
23
+ this.#attachedSessionIds.clear();
24
+ }
25
+ dispose() {
26
+ this.reset();
27
+ this.#snapshotListeners.clear();
28
+ this.#eventListeners.clear();
29
+ this.#sessionSnapshotListeners.clear();
30
+ this.#sessionEventListeners.clear();
31
+ }
32
+ getSessionSnapshot(sessionId) {
33
+ return this.#sessionSnapshots.get(sessionId);
34
+ }
35
+ isSessionAttached(sessionId) {
36
+ return this.#attachedSessionIds.has(sessionId);
37
+ }
38
+ forgetSessionSnapshot(sessionId) {
39
+ const previous = this.#sessionSnapshots.get(sessionId);
40
+ this.#sessionSnapshots.delete(sessionId);
41
+ return previous;
42
+ }
43
+ restoreSessionSnapshot(snapshot) {
44
+ if (!this.#sessionSnapshots.has(snapshot.id))
45
+ this.#sessionSnapshots.set(snapshot.id, snapshot);
46
+ }
47
+ subscribe(listener) {
48
+ this.#snapshotListeners.add(listener);
49
+ return () => this.#snapshotListeners.delete(listener);
50
+ }
51
+ onEvent(listener) {
52
+ this.#eventListeners.add(listener);
53
+ return () => this.#eventListeners.delete(listener);
54
+ }
55
+ subscribeSession(sessionId, listener) {
56
+ return addMappedListener(this.#sessionSnapshotListeners, sessionId, listener);
57
+ }
58
+ onSessionEvent(sessionId, listener) {
59
+ return addMappedListener(this.#sessionEventListeners, sessionId, listener);
60
+ }
61
+ applyResult(result) {
62
+ if (result.command === "list")
63
+ return;
64
+ if (result.command === "detach") {
65
+ this.#attachedSessionIds.delete(result.sessionId);
66
+ const snapshot = this.#sessionSnapshots.get(result.sessionId);
67
+ if (snapshot)
68
+ this.#applySessionSnapshot({ ...snapshot, attached: false }, true);
69
+ return;
70
+ }
71
+ this.#applySessionSnapshot(result.session);
72
+ }
73
+ applyEvent(event) {
74
+ if (event.type === "server_snapshot")
75
+ this.applyServerSnapshot(event.snapshot);
76
+ if (event.type === "session_snapshot")
77
+ this.#applySessionSnapshot(event.snapshot);
78
+ if (event.type === "session_removed") {
79
+ this.#sessionSnapshots.delete(event.sessionId);
80
+ this.#attachedSessionIds.delete(event.sessionId);
81
+ }
82
+ this.#notify(this.#eventListeners, event);
83
+ const sessionId = getEventSessionId(event);
84
+ if (sessionId)
85
+ this.#notify(this.#sessionEventListeners.get(sessionId), event);
86
+ }
87
+ applyServerSnapshot(snapshot) {
88
+ if (this.#snapshot && snapshot.revision < this.#snapshot.revision)
89
+ return;
90
+ this.#snapshot = snapshot;
91
+ this.#notify(this.#snapshotListeners, snapshot);
92
+ }
93
+ #applySessionSnapshot(snapshot, force = false) {
94
+ const current = this.#sessionSnapshots.get(snapshot.id);
95
+ if (!force && current && snapshot.revision < current.revision)
96
+ return;
97
+ this.#sessionSnapshots.set(snapshot.id, snapshot);
98
+ if (snapshot.attached)
99
+ this.#attachedSessionIds.add(snapshot.id);
100
+ else
101
+ this.#attachedSessionIds.delete(snapshot.id);
102
+ this.#notify(this.#sessionSnapshotListeners.get(snapshot.id), snapshot);
103
+ }
104
+ #notify(listeners, value) {
105
+ for (const listener of listeners ?? []) {
106
+ try {
107
+ listener(value);
108
+ }
109
+ catch (error) {
110
+ this.#reportListenerError(error);
111
+ }
112
+ }
113
+ }
114
+ #reportListenerError(error) {
115
+ if (!this.#onListenerError)
116
+ return;
117
+ try {
118
+ this.#onListenerError(toError(error));
119
+ }
120
+ catch {
121
+ // Diagnostics cannot affect client state.
122
+ }
123
+ }
124
+ }
125
+ function addMappedListener(listenersById, id, listener) {
126
+ let listeners = listenersById.get(id);
127
+ if (!listeners) {
128
+ listeners = new Set();
129
+ listenersById.set(id, listeners);
130
+ }
131
+ listeners.add(listener);
132
+ return () => {
133
+ listeners.delete(listener);
134
+ if (listeners.size === 0)
135
+ listenersById.delete(id);
136
+ };
137
+ }
138
+ function getEventSessionId(event) {
139
+ if (event.type === "session_snapshot")
140
+ return event.snapshot.id;
141
+ if (event.type === "session_progress" || event.type === "session_removed")
142
+ return event.sessionId;
143
+ return undefined;
144
+ }
145
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAGtC,MAAM,OAAO,WAAW;IACd,iBAAiB,GAAG,IAAI,GAAG,EAA2B,CAAC;IACvD,mBAAmB,GAAG,IAAI,GAAG,EAAU,CAAC;IACxC,kBAAkB,GAAG,IAAI,GAAG,EAAsC,CAAC;IACnE,eAAe,GAAG,IAAI,GAAG,EAAgC,CAAC;IAC1D,yBAAyB,GAAG,IAAI,GAAG,EAAoD,CAAC;IACxF,sBAAsB,GAAG,IAAI,GAAG,EAA6C,CAAC;IAC9E,gBAAgB,CAAmC;IAC5D,SAAS,CAA6B;IAEtC,YAAY,eAAsC,EAAE;QACnD,IAAI,CAAC,gBAAgB,GAAG,eAAe,CAAC;IAAA,CACxC;IAED,IAAI,QAAQ,GAA+B;QAC1C,OAAO,IAAI,CAAC,SAAS,CAAC;IAAA,CACtB;IAED,KAAK,GAAS;QACb,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,iBAAiB,CAAC,KAAK,EAAE,CAAC;QAC/B,IAAI,CAAC,mBAAmB,CAAC,KAAK,EAAE,CAAC;IAAA,CACjC;IAED,gBAAgB,GAAS;QACxB,IAAI,CAAC,mBAAmB,CAAC,KAAK,EAAE,CAAC;IAAA,CACjC;IAED,OAAO,GAAS;QACf,IAAI,CAAC,KAAK,EAAE,CAAC;QACb,IAAI,CAAC,kBAAkB,CAAC,KAAK,EAAE,CAAC;QAChC,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;QAC7B,IAAI,CAAC,yBAAyB,CAAC,KAAK,EAAE,CAAC;QACvC,IAAI,CAAC,sBAAsB,CAAC,KAAK,EAAE,CAAC;IAAA,CACpC;IAED,kBAAkB,CAAC,SAAiB,EAA+B;QAClE,OAAO,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAAA,CAC7C;IAED,iBAAiB,CAAC,SAAiB,EAAW;QAC7C,OAAO,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAAA,CAC/C;IAED,qBAAqB,CAAC,SAAiB,EAA+B;QACrE,MAAM,QAAQ,GAAG,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACvD,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QACzC,OAAO,QAAQ,CAAC;IAAA,CAChB;IAED,sBAAsB,CAAC,QAAyB,EAAQ;QACvD,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YAAE,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;IAAA,CAChG;IAED,SAAS,CAAC,QAA4C,EAAe;QACpE,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACtC,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAAA,CACtD;IAED,OAAO,CAAC,QAAsC,EAAe;QAC5D,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACnC,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAAA,CACnD;IAED,gBAAgB,CAAC,SAAiB,EAAE,QAA6C,EAAe;QAC/F,OAAO,iBAAiB,CAAC,IAAI,CAAC,yBAAyB,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;IAAA,CAC9E;IAED,cAAc,CAAC,SAAiB,EAAE,QAAsC,EAAe;QACtF,OAAO,iBAAiB,CAAC,IAAI,CAAC,sBAAsB,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;IAAA,CAC3E;IAED,WAAW,CAAC,MAAqB,EAAQ;QACxC,IAAI,MAAM,CAAC,OAAO,KAAK,MAAM;YAAE,OAAO;QACtC,IAAI,MAAM,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;YACjC,IAAI,CAAC,mBAAmB,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YAClD,MAAM,QAAQ,GAAG,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YAC9D,IAAI,QAAQ;gBAAE,IAAI,CAAC,qBAAqB,CAAC,EAAE,GAAG,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,IAAI,CAAC,CAAC;YACjF,OAAO;QACR,CAAC;QACD,IAAI,CAAC,qBAAqB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAAA,CAC3C;IAED,UAAU,CAAC,KAAkB,EAAQ;QACpC,IAAI,KAAK,CAAC,IAAI,KAAK,iBAAiB;YAAE,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC/E,IAAI,KAAK,CAAC,IAAI,KAAK,kBAAkB;YAAE,IAAI,CAAC,qBAAqB,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAClF,IAAI,KAAK,CAAC,IAAI,KAAK,iBAAiB,EAAE,CAAC;YACtC,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;YAC/C,IAAI,CAAC,mBAAmB,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAClD,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,eAAe,EAAE,KAAK,CAAC,CAAC;QAC1C,MAAM,SAAS,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;QAC3C,IAAI,SAAS;YAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,sBAAsB,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,KAAK,CAAC,CAAC;IAAA,CAC/E;IAED,mBAAmB,CAAC,QAAwB,EAAQ;QACnD,IAAI,IAAI,CAAC,SAAS,IAAI,QAAQ,CAAC,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ;YAAE,OAAO;QAC1E,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC;QAC1B,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAC;IAAA,CAChD;IAED,qBAAqB,CAAC,QAAyB,EAAE,KAAK,GAAG,KAAK,EAAQ;QACrE,MAAM,OAAO,GAAG,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QACxD,IAAI,CAAC,KAAK,IAAI,OAAO,IAAI,QAAQ,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ;YAAE,OAAO;QACtE,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;QAClD,IAAI,QAAQ,CAAC,QAAQ;YAAE,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;;YAC5D,IAAI,CAAC,mBAAmB,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAClD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,yBAAyB,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;IAAA,CACxE;IAED,OAAO,CAAI,SAAmD,EAAE,KAAQ,EAAQ;QAC/E,KAAK,MAAM,QAAQ,IAAI,SAAS,IAAI,EAAE,EAAE,CAAC;YACxC,IAAI,CAAC;gBACJ,QAAQ,CAAC,KAAK,CAAC,CAAC;YACjB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBAChB,IAAI,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAC;YAClC,CAAC;QACF,CAAC;IAAA,CACD;IAED,oBAAoB,CAAC,KAAc,EAAQ;QAC1C,IAAI,CAAC,IAAI,CAAC,gBAAgB;YAAE,OAAO;QACnC,IAAI,CAAC;YACJ,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;QACvC,CAAC;QAAC,MAAM,CAAC;YACR,0CAA0C;QAC3C,CAAC;IAAA,CACD;CACD;AAED,SAAS,iBAAiB,CACzB,aAAmD,EACnD,EAAU,EACV,QAA4B,EACd;IACd,IAAI,SAAS,GAAG,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACtC,IAAI,CAAC,SAAS,EAAE,CAAC;QAChB,SAAS,GAAG,IAAI,GAAG,EAAE,CAAC;QACtB,aAAa,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;IAClC,CAAC;IACD,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACxB,OAAO,GAAG,EAAE,CAAC;QACZ,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC3B,IAAI,SAAS,CAAC,IAAI,KAAK,CAAC;YAAE,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAAA,CACnD,CAAC;AAAA,CACF;AAED,SAAS,iBAAiB,CAAC,KAAkB,EAAsB;IAClE,IAAI,KAAK,CAAC,IAAI,KAAK,kBAAkB;QAAE,OAAO,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;IAChE,IAAI,KAAK,CAAC,IAAI,KAAK,kBAAkB,IAAI,KAAK,CAAC,IAAI,KAAK,iBAAiB;QAAE,OAAO,KAAK,CAAC,SAAS,CAAC;IAClG,OAAO,SAAS,CAAC;AAAA,CACjB","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"]}
@@ -0,0 +1,17 @@
1
+ export interface ByteTransport {
2
+ /** Sends one byte chunk. Calls must be delivered in invocation order. */
3
+ send(chunk: Uint8Array): Promise<void>;
4
+ /** Closes the transport. Implementations must make repeated calls harmless. */
5
+ close(): void;
6
+ }
7
+ export interface ByteTransportHandlers {
8
+ /** Delivers an arbitrary inbound byte chunk. */
9
+ onData(chunk: Uint8Array): void;
10
+ /** Reports an orderly terminal close. */
11
+ onClose(): void;
12
+ /** Reports a terminal transport failure. */
13
+ onError(error: Error): void;
14
+ }
15
+ /** Creates a fresh connected, authenticated transport. Exactly one terminal handler is expected. */
16
+ export type ByteTransportFactory = (handlers: ByteTransportHandlers) => ByteTransport | Promise<ByteTransport>;
17
+ //# sourceMappingURL=transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC7B,yEAAyE;IACzE,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,+EAA+E;IAC/E,KAAK,IAAI,IAAI,CAAC;CACd;AAED,MAAM,WAAW,qBAAqB;IACrC,gDAAgD;IAChD,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IAChC,yCAAyC;IACzC,OAAO,IAAI,IAAI,CAAC;IAChB,4CAA4C;IAC5C,OAAO,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CAC5B;AAED,oGAAoG;AACpG,MAAM,MAAM,oBAAoB,GAAG,CAAC,QAAQ,EAAE,qBAAqB,KAAK,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC","sourcesContent":["export interface ByteTransport {\n\t/** Sends one byte chunk. Calls must be delivered in invocation order. */\n\tsend(chunk: Uint8Array): Promise<void>;\n\t/** Closes the transport. Implementations must make repeated calls harmless. */\n\tclose(): void;\n}\n\nexport interface ByteTransportHandlers {\n\t/** Delivers an arbitrary inbound byte chunk. */\n\tonData(chunk: Uint8Array): void;\n\t/** Reports an orderly terminal close. */\n\tonClose(): void;\n\t/** Reports a terminal transport failure. */\n\tonError(error: Error): void;\n}\n\n/** Creates a fresh connected, authenticated transport. Exactly one terminal handler is expected. */\nexport type ByteTransportFactory = (handlers: ByteTransportHandlers) => ByteTransport | Promise<ByteTransport>;\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"","sourcesContent":["export interface ByteTransport {\n\t/** Sends one byte chunk. Calls must be delivered in invocation order. */\n\tsend(chunk: Uint8Array): Promise<void>;\n\t/** Closes the transport. Implementations must make repeated calls harmless. */\n\tclose(): void;\n}\n\nexport interface ByteTransportHandlers {\n\t/** Delivers an arbitrary inbound byte chunk. */\n\tonData(chunk: Uint8Array): void;\n\t/** Reports an orderly terminal close. */\n\tonClose(): void;\n\t/** Reports a terminal transport failure. */\n\tonError(error: Error): void;\n}\n\n/** Creates a fresh connected, authenticated transport. Exactly one terminal handler is expected. */\nexport type ByteTransportFactory = (handlers: ByteTransportHandlers) => ByteTransport | Promise<ByteTransport>;\n"]}
@@ -0,0 +1,22 @@
1
+ import type { ModelRef, ThinkingLevel } from "@earendil-works/pi-protocol";
2
+ import type { ByteTransportFactory } from "./transport.ts";
3
+ export type ConnectionState = "disconnected" | "connecting" | "connected";
4
+ export interface ConnectionStateChange {
5
+ state: ConnectionState;
6
+ error?: Error;
7
+ }
8
+ export type Unsubscribe = () => void;
9
+ export type ListenerErrorHandler = (error: Error) => void;
10
+ export interface PiClientOptions {
11
+ transportFactory: ByteTransportFactory;
12
+ maxFrameLength?: number;
13
+ /** Reports subscriber failures without allowing them to corrupt client state. */
14
+ onListenerError?: ListenerErrorHandler;
15
+ }
16
+ export interface CreateSessionOptions {
17
+ cwd?: string;
18
+ name?: string;
19
+ model?: ModelRef;
20
+ thinkingLevel?: ThinkingLevel;
21
+ }
22
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC3E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAE3D,MAAM,MAAM,eAAe,GAAG,cAAc,GAAG,YAAY,GAAG,WAAW,CAAC;AAE1E,MAAM,WAAW,qBAAqB;IACrC,KAAK,EAAE,eAAe,CAAC;IACvB,KAAK,CAAC,EAAE,KAAK,CAAC;CACd;AAED,MAAM,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC;AACrC,MAAM,MAAM,oBAAoB,GAAG,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;AAE1D,MAAM,WAAW,eAAe;IAC/B,gBAAgB,EAAE,oBAAoB,CAAC;IACvC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iFAAiF;IACjF,eAAe,CAAC,EAAE,oBAAoB,CAAC;CACvC;AAED,MAAM,WAAW,oBAAoB;IACpC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,aAAa,CAAC,EAAE,aAAa,CAAC;CAC9B","sourcesContent":["import type { ModelRef, ThinkingLevel } from \"@earendil-works/pi-protocol\";\nimport type { ByteTransportFactory } from \"./transport.ts\";\n\nexport type ConnectionState = \"disconnected\" | \"connecting\" | \"connected\";\n\nexport interface ConnectionStateChange {\n\tstate: ConnectionState;\n\terror?: Error;\n}\n\nexport type Unsubscribe = () => void;\nexport type ListenerErrorHandler = (error: Error) => void;\n\nexport interface PiClientOptions {\n\ttransportFactory: ByteTransportFactory;\n\tmaxFrameLength?: number;\n\t/** Reports subscriber failures without allowing them to corrupt client state. */\n\tonListenerError?: ListenerErrorHandler;\n}\n\nexport interface CreateSessionOptions {\n\tcwd?: string;\n\tname?: string;\n\tmodel?: ModelRef;\n\tthinkingLevel?: ThinkingLevel;\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 { ModelRef, ThinkingLevel } from \"@earendil-works/pi-protocol\";\nimport type { ByteTransportFactory } from \"./transport.ts\";\n\nexport type ConnectionState = \"disconnected\" | \"connecting\" | \"connected\";\n\nexport interface ConnectionStateChange {\n\tstate: ConnectionState;\n\terror?: Error;\n}\n\nexport type Unsubscribe = () => void;\nexport type ListenerErrorHandler = (error: Error) => void;\n\nexport interface PiClientOptions {\n\ttransportFactory: ByteTransportFactory;\n\tmaxFrameLength?: number;\n\t/** Reports subscriber failures without allowing them to corrupt client state. */\n\tonListenerError?: ListenerErrorHandler;\n}\n\nexport interface CreateSessionOptions {\n\tcwd?: string;\n\tname?: string;\n\tmodel?: ModelRef;\n\tthinkingLevel?: ThinkingLevel;\n}\n"]}
package/dist/unix.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ import type { ByteTransportFactory } from "./transport.ts";
2
+ export interface UnixTransportOptions {
3
+ path: string;
4
+ maxPendingBytes?: number;
5
+ }
6
+ /** Creates fresh Unix-domain socket transports for PiClient connection attempts in Node-compatible runtimes. */
7
+ export declare function createUnixTransportFactory(options: UnixTransportOptions): ByteTransportFactory;
8
+ //# sourceMappingURL=unix.d.ts.map
@@ -0,0 +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"]}
package/dist/unix.js ADDED
@@ -0,0 +1,151 @@
1
+ import { createConnection } from "node:net";
2
+ import { DEFAULT_MAX_FRAME_LENGTH } from "@earendil-works/pi-protocol";
3
+ const MAX_UNIX_SOCKET_PATH_BYTES = process.platform === "linux" ? 107 : 103;
4
+ /** Creates fresh Unix-domain socket transports for PiClient connection attempts in Node-compatible runtimes. */
5
+ export function createUnixTransportFactory(options) {
6
+ if (options.path.length === 0)
7
+ 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
+ const maxPendingBytes = options.maxPendingBytes ?? DEFAULT_MAX_FRAME_LENGTH * 4;
12
+ if (!Number.isSafeInteger(maxPendingBytes) || maxPendingBytes <= 0) {
13
+ throw new TypeError("Unix transport maxPendingBytes must be a positive safe integer");
14
+ }
15
+ if (process.platform === "win32")
16
+ throw new Error("Unix transport is not supported on Windows");
17
+ return (handlers) => connectUnixSocket(options.path, maxPendingBytes, handlers);
18
+ }
19
+ function connectUnixSocket(path, maxPendingBytes, handlers) {
20
+ return new Promise((resolve, reject) => {
21
+ const socket = createConnection(path);
22
+ let connected = false;
23
+ let terminal = false;
24
+ const close = () => {
25
+ if (terminal)
26
+ return;
27
+ terminal = true;
28
+ socket.destroy();
29
+ if (connected)
30
+ handlers.onClose();
31
+ else
32
+ reject(new Error("Unix transport closed before connecting"));
33
+ };
34
+ socket.once("connect", () => {
35
+ if (terminal)
36
+ return;
37
+ connected = true;
38
+ resolve(new UnixByteTransport(socket, maxPendingBytes, () => {
39
+ terminal = true;
40
+ }));
41
+ });
42
+ socket.on("data", (chunk) => {
43
+ if (!terminal)
44
+ handlers.onData(new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength));
45
+ });
46
+ socket.once("end", close);
47
+ socket.once("close", close);
48
+ socket.once("error", (error) => {
49
+ if (terminal)
50
+ return;
51
+ terminal = true;
52
+ socket.destroy();
53
+ if (connected)
54
+ handlers.onError(error);
55
+ else
56
+ reject(error);
57
+ });
58
+ });
59
+ }
60
+ class UnixByteTransport {
61
+ #socket;
62
+ #maxPendingBytes;
63
+ #markLocalClose;
64
+ #closed = false;
65
+ #pendingBytes = 0;
66
+ #writeTail = Promise.resolve();
67
+ constructor(socket, maxPendingBytes, markLocalClose) {
68
+ this.#socket = socket;
69
+ this.#maxPendingBytes = maxPendingBytes;
70
+ this.#markLocalClose = markLocalClose;
71
+ }
72
+ send(chunk) {
73
+ if (!(chunk instanceof Uint8Array)) {
74
+ return Promise.reject(new TypeError("Unix transport chunks must be Uint8Array"));
75
+ }
76
+ if (this.#closed)
77
+ return Promise.reject(new Error("Unix transport is closed"));
78
+ if (this.#pendingBytes + chunk.byteLength > this.#maxPendingBytes) {
79
+ return Promise.reject(new Error("Unix transport exceeded its pending byte limit"));
80
+ }
81
+ this.#pendingBytes += chunk.byteLength;
82
+ const bytes = chunk.slice();
83
+ const write = this.#writeTail.then(() => this.#write(bytes));
84
+ const tracked = write.finally(() => {
85
+ this.#pendingBytes -= bytes.byteLength;
86
+ });
87
+ this.#writeTail = tracked.catch(() => { });
88
+ return tracked;
89
+ }
90
+ close() {
91
+ if (this.#closed)
92
+ return;
93
+ this.#closed = true;
94
+ this.#markLocalClose();
95
+ this.#socket.destroy();
96
+ }
97
+ #write(chunk) {
98
+ if (this.#closed || !this.#socket.writable)
99
+ return Promise.reject(new Error("Unix transport is closed"));
100
+ return new Promise((resolve, reject) => {
101
+ let callbackComplete = false;
102
+ let drainComplete = false;
103
+ let requiresDrain;
104
+ let settled = false;
105
+ const onDrain = () => {
106
+ drainComplete = true;
107
+ finish();
108
+ };
109
+ const cleanup = () => {
110
+ this.#socket.off("drain", onDrain);
111
+ this.#socket.off("close", onClose);
112
+ };
113
+ const fail = (error) => {
114
+ if (settled)
115
+ return;
116
+ settled = true;
117
+ cleanup();
118
+ reject(error);
119
+ };
120
+ const finish = () => {
121
+ if (settled || !callbackComplete || requiresDrain === undefined)
122
+ return;
123
+ if (requiresDrain && !drainComplete)
124
+ return;
125
+ settled = true;
126
+ cleanup();
127
+ resolve();
128
+ };
129
+ const onClose = () => fail(new Error("Unix transport closed during write"));
130
+ try {
131
+ this.#socket.once("close", onClose);
132
+ const accepted = this.#socket.write(chunk, (error) => {
133
+ if (error) {
134
+ fail(error);
135
+ return;
136
+ }
137
+ callbackComplete = true;
138
+ finish();
139
+ });
140
+ requiresDrain = !accepted;
141
+ if (requiresDrain)
142
+ this.#socket.once("drain", onDrain);
143
+ finish();
144
+ }
145
+ catch (error) {
146
+ fail(error instanceof Error ? error : new Error(String(error)));
147
+ }
148
+ });
149
+ }
150
+ }
151
+ //# 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 { 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"]}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@earendil-works/pi-client",
3
+ "version": "0.84.0",
4
+ "description": "Transport-neutral client for remote pi sessions over framed CBOR bytes",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./unix": {
14
+ "types": "./dist/unix.d.ts",
15
+ "import": "./dist/unix.js"
16
+ },
17
+ "./package.json": "./package.json"
18
+ },
19
+ "sideEffects": false,
20
+ "files": [
21
+ "dist",
22
+ "README.md",
23
+ "CHANGELOG.md"
24
+ ],
25
+ "scripts": {
26
+ "clean": "shx rm -rf dist",
27
+ "build": "tsgo -p tsconfig.build.json",
28
+ "test": "vitest --run",
29
+ "typecheck": "tsgo -p tsconfig.test.json",
30
+ "prepublishOnly": "npm run clean && npm run build"
31
+ },
32
+ "keywords": [
33
+ "pi",
34
+ "client",
35
+ "protocol",
36
+ "cbor",
37
+ "binary"
38
+ ],
39
+ "author": "Earendil Works",
40
+ "license": "MIT",
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+https://github.com/earendil-works/pi.git",
44
+ "directory": "packages/client"
45
+ },
46
+ "engines": {
47
+ "node": ">=22.19.0"
48
+ },
49
+ "dependencies": {
50
+ "@earendil-works/pi-protocol": "^0.84.0"
51
+ },
52
+ "devDependencies": {
53
+ "shx": "0.4.0",
54
+ "vitest": "4.1.9"
55
+ }
56
+ }