@statelyai/sdk 0.15.0 → 0.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -8
- package/dist/bin.d.mts +1 -0
- package/dist/bin.mjs +35 -0
- package/dist/embed.mjs +1 -1
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +4 -4
- package/dist/{inspect-DenVP7IE.d.mts → inspect-BScvqVcM.d.mts} +56 -64
- package/dist/{inspect-DWDTNMEc.mjs → inspect-C3Y_o_Np.mjs} +123 -73
- package/dist/inspect.d.mts +1 -1
- package/dist/inspect.mjs +1 -1
- package/dist/node-relay.d.mts +22 -0
- package/dist/node-relay.mjs +84 -0
- package/dist/protocol.d.mts +108 -118
- package/dist/protocol.mjs +33 -2
- package/dist/relay.d.mts +10 -11
- package/dist/relay.mjs +83 -62
- package/dist/studio.d.mts +1 -1
- package/dist/sync.d.mts +1 -1
- package/dist/{transport-D2Ud05yg.mjs → transport-B_ig9Znn.mjs} +56 -198
- package/package.json +15 -4
- /package/dist/{studio-Da5pyxv6.d.mts → studio-Bk_u41gu.d.mts} +0 -0
package/dist/relay.mjs
CHANGED
|
@@ -1,6 +1,29 @@
|
|
|
1
1
|
import { INSPECTION_PROTOCOL_VERSION, PREFIX } from "./protocol.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/relay.ts
|
|
4
|
+
const REPLAYABLE_MESSAGE_TYPES = new Set([
|
|
5
|
+
"@statelyai.init",
|
|
6
|
+
"@statelyai.inspectSnapshot",
|
|
7
|
+
"@statelyai.system.init",
|
|
8
|
+
"@statelyai.system.actorRegistered",
|
|
9
|
+
"@statelyai.system.actorEvent",
|
|
10
|
+
"@statelyai.system.actorSnapshot",
|
|
11
|
+
"@statelyai.system.actorStopped"
|
|
12
|
+
]);
|
|
13
|
+
const FORWARD_ONLY_MESSAGE_TYPES = new Set([
|
|
14
|
+
"@statelyai.system.selectActor",
|
|
15
|
+
"@statelyai.retrieve",
|
|
16
|
+
"@statelyai.retrieved",
|
|
17
|
+
"@statelyai.error",
|
|
18
|
+
"@statelyai.ready",
|
|
19
|
+
"@statelyai.loaded",
|
|
20
|
+
"@statelyai.capabilityDenied",
|
|
21
|
+
"@statelyai.register",
|
|
22
|
+
"@statelyai.registered"
|
|
23
|
+
]);
|
|
24
|
+
function isSupportedMessageType(type) {
|
|
25
|
+
return REPLAYABLE_MESSAGE_TYPES.has(type) || FORWARD_ONLY_MESSAGE_TYPES.has(type);
|
|
26
|
+
}
|
|
4
27
|
function parseMessage(raw) {
|
|
5
28
|
if (raw instanceof ArrayBuffer) raw = new TextDecoder().decode(raw);
|
|
6
29
|
if (typeof raw === "string") try {
|
|
@@ -12,6 +35,22 @@ function parseMessage(raw) {
|
|
|
12
35
|
const message = raw;
|
|
13
36
|
return typeof message.type === "string" && message.type.startsWith(PREFIX) ? message : null;
|
|
14
37
|
}
|
|
38
|
+
function validationError(message) {
|
|
39
|
+
if (!isSupportedMessageType(message.type)) return `Unsupported inspection message type ${String(message.type)}`;
|
|
40
|
+
const hasString = (key) => typeof message[key] === "string" && message[key] !== "";
|
|
41
|
+
switch (message.type) {
|
|
42
|
+
case "@statelyai.system.init": return message.protocolVersion === INSPECTION_PROTOCOL_VERSION && Array.isArray(message.actors) ? null : "system.init requires the current protocolVersion and actors array";
|
|
43
|
+
case "@statelyai.system.selectActor":
|
|
44
|
+
case "@statelyai.system.actorStopped": return hasString("sessionId") ? null : `${message.type} requires sessionId`;
|
|
45
|
+
case "@statelyai.system.actorRegistered": return hasString("sessionId") && hasString("actorId") ? null : "actorRegistered requires sessionId and actorId";
|
|
46
|
+
case "@statelyai.system.actorEvent": return hasString("sessionId") && "event" in message ? null : "actorEvent requires sessionId and event";
|
|
47
|
+
case "@statelyai.system.actorSnapshot": return hasString("sessionId") && "snapshot" in message ? null : "actorSnapshot requires sessionId and snapshot";
|
|
48
|
+
case "@statelyai.retrieve":
|
|
49
|
+
case "@statelyai.retrieved": return hasString("requestId") ? null : `${message.type} requires requestId`;
|
|
50
|
+
case "@statelyai.error": return hasString("code") && hasString("message") ? null : "error requires code and message";
|
|
51
|
+
default: return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
15
54
|
function errorMessage(code, message) {
|
|
16
55
|
return {
|
|
17
56
|
type: "@statelyai.error",
|
|
@@ -30,10 +69,10 @@ function compactReplay(replay, message, max) {
|
|
|
30
69
|
const index = replay.findIndex((entry) => entry.type === "@statelyai.init");
|
|
31
70
|
if (index >= 0) replay.splice(index, 1);
|
|
32
71
|
} else if (type === "@statelyai.system.actorSnapshot" || type === "@statelyai.inspectSnapshot") {
|
|
33
|
-
const
|
|
72
|
+
const sessionId = record.sessionId;
|
|
34
73
|
const index = replay.findIndex((entry) => {
|
|
35
74
|
const candidate = entry;
|
|
36
|
-
return candidate.type === type && candidate.
|
|
75
|
+
return candidate.type === type && candidate.sessionId === sessionId;
|
|
37
76
|
});
|
|
38
77
|
if (index >= 0) replay.splice(index, 1);
|
|
39
78
|
}
|
|
@@ -41,110 +80,92 @@ function compactReplay(replay, message, max) {
|
|
|
41
80
|
if (replay.length > max) replay.splice(0, replay.length - max);
|
|
42
81
|
}
|
|
43
82
|
/**
|
|
44
|
-
* Runtime-neutral
|
|
83
|
+
* Runtime-neutral implementation of one inspection room.
|
|
45
84
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
85
|
+
* The hosting adapter selects the room from the WebSocket URL. This module
|
|
86
|
+
* owns protocol registration, multi-producer/viewer routing, and bounded
|
|
87
|
+
* per-producer replay.
|
|
49
88
|
*/
|
|
50
89
|
function createInspectionRelay(options = {}) {
|
|
51
90
|
const maxReplayMessages = options.maxReplayMessages ?? 200;
|
|
52
|
-
const
|
|
91
|
+
const maxReplayProducers = Math.max(1, options.maxReplayProducers ?? 100);
|
|
53
92
|
const peers = /* @__PURE__ */ new Map();
|
|
54
|
-
const
|
|
93
|
+
const producers = new Map(Object.entries(options.snapshot?.producers ?? {}).slice(-maxReplayProducers).map(([id, replay]) => [id, [...replay]]));
|
|
55
94
|
const send = (peerId, message) => ({
|
|
56
95
|
type: "send",
|
|
57
96
|
peerId,
|
|
58
97
|
message
|
|
59
98
|
});
|
|
60
|
-
function
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
sessions.delete(sessionId);
|
|
68
|
-
sessions.set(sessionId, replay);
|
|
69
|
-
while (sessions.size > maxReplaySessions) {
|
|
70
|
-
const oldestSessionId = sessions.keys().next().value;
|
|
71
|
-
if (oldestSessionId === void 0) break;
|
|
72
|
-
sessions.delete(oldestSessionId);
|
|
99
|
+
function retainReplay(producerId, replay) {
|
|
100
|
+
producers.delete(producerId);
|
|
101
|
+
producers.set(producerId, replay);
|
|
102
|
+
while (producers.size > maxReplayProducers) {
|
|
103
|
+
const oldestProducerId = producers.keys().next().value;
|
|
104
|
+
if (oldestProducerId === void 0) break;
|
|
105
|
+
producers.delete(oldestProducerId);
|
|
73
106
|
}
|
|
74
107
|
}
|
|
75
108
|
return {
|
|
76
109
|
connect(peer) {
|
|
77
|
-
peers.set(peer.id, {
|
|
78
|
-
...peer,
|
|
79
|
-
sessionIds: Array.from(new Set([peer.primarySessionId, ...peer.sessionIds]))
|
|
80
|
-
});
|
|
110
|
+
peers.set(peer.id, { ...peer });
|
|
81
111
|
},
|
|
82
112
|
disconnect(peerId) {
|
|
83
113
|
peers.delete(peerId);
|
|
84
114
|
},
|
|
85
115
|
clear() {
|
|
86
116
|
peers.clear();
|
|
87
|
-
|
|
117
|
+
producers.clear();
|
|
88
118
|
},
|
|
89
119
|
peer(peerId) {
|
|
90
120
|
const peer = peers.get(peerId);
|
|
91
|
-
return peer ? {
|
|
92
|
-
...peer,
|
|
93
|
-
sessionIds: [...peer.sessionIds]
|
|
94
|
-
} : void 0;
|
|
121
|
+
return peer ? { ...peer } : void 0;
|
|
95
122
|
},
|
|
96
123
|
receive(peerId, raw) {
|
|
97
124
|
const message = parseMessage(raw);
|
|
98
|
-
if (!message) return [];
|
|
125
|
+
if (!message) return [send(peerId, errorMessage("invalid_message", "Expected a JSON protocol message"))];
|
|
126
|
+
const invalid = validationError(message);
|
|
127
|
+
if (invalid) return [send(peerId, errorMessage("invalid_message", invalid))];
|
|
99
128
|
if (message.type === "@statelyai.register") {
|
|
100
129
|
const role = message.role;
|
|
101
|
-
const sessionId = message.sessionId;
|
|
102
130
|
const protocolVersion = message.protocolVersion;
|
|
103
|
-
|
|
104
|
-
if (
|
|
131
|
+
const producerId = message.producerId;
|
|
132
|
+
if (role !== "producer" && role !== "viewer" || role === "producer" && (typeof producerId !== "string" || producerId.length === 0)) return [send(peerId, errorMessage("invalid_registration", "Invalid relay registration"))];
|
|
133
|
+
if (protocolVersion !== INSPECTION_PROTOCOL_VERSION) return [send(peerId, errorMessage("unsupported_protocol", `Protocol ${String(protocolVersion)} is not supported; expected ${INSPECTION_PROTOCOL_VERSION}`))];
|
|
105
134
|
const peer = {
|
|
106
135
|
id: peerId,
|
|
107
136
|
role,
|
|
108
|
-
|
|
109
|
-
sessionIds: [sessionId]
|
|
137
|
+
...role === "producer" && { producerId }
|
|
110
138
|
};
|
|
111
139
|
peers.set(peerId, peer);
|
|
112
140
|
const effects = [send(peerId, {
|
|
113
141
|
type: "@statelyai.registered",
|
|
114
142
|
protocolVersion: INSPECTION_PROTOCOL_VERSION,
|
|
115
|
-
|
|
143
|
+
...peer.producerId && { producerId: peer.producerId }
|
|
116
144
|
})];
|
|
117
|
-
if (role === "
|
|
145
|
+
if (role === "viewer") for (const replay of producers.values()) effects.push(...replay.map((entry) => send(peerId, entry)));
|
|
118
146
|
return effects;
|
|
119
147
|
}
|
|
120
148
|
const sender = peers.get(peerId);
|
|
121
149
|
if (!sender) return [send(peerId, errorMessage("not_registered", "Register before sending messages"))];
|
|
122
|
-
if (
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (!sender.sessionIds.includes(sessionId)) return [send(peerId, errorMessage("unknown_session", "Peer is not registered for session"))];
|
|
135
|
-
const stamped = {
|
|
136
|
-
...message,
|
|
137
|
-
sessionId
|
|
138
|
-
};
|
|
139
|
-
if (sender.role === "client") {
|
|
140
|
-
const replay = sessions.get(sessionId) ?? [];
|
|
141
|
-
compactReplay(replay, stamped, maxReplayMessages);
|
|
142
|
-
retainReplay(sessionId, replay);
|
|
150
|
+
if (sender.role === "producer") {
|
|
151
|
+
const producerId = sender.producerId;
|
|
152
|
+
const stamped = {
|
|
153
|
+
...message,
|
|
154
|
+
producerId
|
|
155
|
+
};
|
|
156
|
+
if (REPLAYABLE_MESSAGE_TYPES.has(message.type)) {
|
|
157
|
+
const replay = producers.get(producerId) ?? [];
|
|
158
|
+
compactReplay(replay, stamped, maxReplayMessages);
|
|
159
|
+
retainReplay(producerId, replay);
|
|
160
|
+
}
|
|
161
|
+
return Array.from(peers.values()).filter((peer) => peer.role === "viewer").map((peer) => send(peer.id, stamped));
|
|
143
162
|
}
|
|
144
|
-
|
|
163
|
+
const targetProducerId = typeof message.producerId === "string" ? message.producerId : void 0;
|
|
164
|
+
return Array.from(peers.values()).filter((peer) => peer.role === "producer" && (!targetProducerId || peer.producerId === targetProducerId)).map((peer) => send(peer.id, message));
|
|
145
165
|
},
|
|
146
|
-
snapshot() {
|
|
147
|
-
|
|
166
|
+
snapshot(producerId) {
|
|
167
|
+
const entries = producerId ? producers.has(producerId) ? [[producerId, producers.get(producerId)]] : [] : Array.from(producers.entries());
|
|
168
|
+
return { producers: Object.fromEntries(entries.map(([producerId, replay]) => [producerId, [...replay]])) };
|
|
148
169
|
}
|
|
149
170
|
};
|
|
150
171
|
}
|
package/dist/studio.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as Machine, C as StudioMachineRecord, D as VerifyApiKeyResponse, E as UpdateProjectInput, F as ProjectVisibility, I as VerifyResult, L as XStateVersion, M as Project, N as ProjectMachineSummary, O as createStatelyClient, P as ProjectSummary, R as ResourceErrorCode, S as StudioClientOptions, T as UpdateMachineInput, _ as ProjectMachine, a as CreateMachineTemplate, b as StudioApiErrorOptions, c as EnsureProjectInput, d as ExtractedMachine, f as GetMachineOptions, g as ProjectData, h as LegacyUpdateMachineInput, i as CreateMachineInput, j as MachineVersion, k as ExtractedMachines, l as ExtractMachinesInput, m as LegacyProjectVisibility, n as CreateMachineFromDefinitionInput, o as CreateProjectInput, p as LegacyExtractMachinesOptions, r as CreateMachineFromTemplateInput, s as DEFAULT_STATELY_BASE_URL, t as ConnectedRepo, u as ExtractMachinesResponse, v as RepoType, w as StudioRequestOptions, x as StudioClient, y as StudioApiError } from "./studio-
|
|
1
|
+
import { A as Machine, C as StudioMachineRecord, D as VerifyApiKeyResponse, E as UpdateProjectInput, F as ProjectVisibility, I as VerifyResult, L as XStateVersion, M as Project, N as ProjectMachineSummary, O as createStatelyClient, P as ProjectSummary, R as ResourceErrorCode, S as StudioClientOptions, T as UpdateMachineInput, _ as ProjectMachine, a as CreateMachineTemplate, b as StudioApiErrorOptions, c as EnsureProjectInput, d as ExtractedMachine, f as GetMachineOptions, g as ProjectData, h as LegacyUpdateMachineInput, i as CreateMachineInput, j as MachineVersion, k as ExtractedMachines, l as ExtractMachinesInput, m as LegacyProjectVisibility, n as CreateMachineFromDefinitionInput, o as CreateProjectInput, p as LegacyExtractMachinesOptions, r as CreateMachineFromTemplateInput, s as DEFAULT_STATELY_BASE_URL, t as ConnectedRepo, u as ExtractMachinesResponse, v as RepoType, w as StudioRequestOptions, x as StudioClient, y as StudioApiError } from "./studio-Bk_u41gu.mjs";
|
|
2
2
|
export { ConnectedRepo, CreateMachineFromDefinitionInput, CreateMachineFromTemplateInput, CreateMachineInput, CreateMachineTemplate, CreateProjectInput, DEFAULT_STATELY_BASE_URL, EnsureProjectInput, ExtractMachinesInput, ExtractMachinesResponse, ExtractedMachine, ExtractedMachines, GetMachineOptions, LegacyExtractMachinesOptions, LegacyProjectVisibility, LegacyUpdateMachineInput, Machine, MachineVersion, Project, ProjectData, ProjectMachine, ProjectMachineSummary, ProjectSummary, ProjectVisibility, RepoType, ResourceErrorCode, StudioApiError, StudioApiErrorOptions, StudioClient, StudioClientOptions, StudioMachineRecord, StudioRequestOptions, UpdateMachineInput, UpdateProjectInput, VerifyApiKeyResponse, VerifyResult, XStateVersion, createStatelyClient };
|
package/dist/sync.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { b as DigraphConfig, i as StatelyGraph } from "./graph-DuOeLGvh.mjs";
|
|
2
|
-
import { C as StudioMachineRecord, L as XStateVersion, g as ProjectData, o as CreateProjectInput, x as StudioClient } from "./studio-
|
|
2
|
+
import { C as StudioMachineRecord, L as XStateVersion, g as ProjectData, o as CreateProjectInput, x as StudioClient } from "./studio-Bk_u41gu.mjs";
|
|
3
3
|
import { GraphDiff } from "@statelyai/graph";
|
|
4
4
|
|
|
5
5
|
//#region src/sync.d.ts
|
|
@@ -157,9 +157,7 @@ function createPostMessageTransport(options) {
|
|
|
157
157
|
},
|
|
158
158
|
onMessage(handler) {
|
|
159
159
|
messageHandlers.add(handler);
|
|
160
|
-
return () =>
|
|
161
|
-
messageHandlers.delete(handler);
|
|
162
|
-
};
|
|
160
|
+
return () => messageHandlers.delete(handler);
|
|
163
161
|
},
|
|
164
162
|
destroy() {
|
|
165
163
|
if (destroyed) return;
|
|
@@ -177,31 +175,52 @@ function createPostMessageTransport(options) {
|
|
|
177
175
|
return () => {};
|
|
178
176
|
}
|
|
179
177
|
readyHandlers.add(handler);
|
|
180
|
-
return () =>
|
|
181
|
-
readyHandlers.delete(handler);
|
|
182
|
-
};
|
|
178
|
+
return () => readyHandlers.delete(handler);
|
|
183
179
|
}
|
|
184
180
|
};
|
|
185
181
|
}
|
|
186
182
|
function createWebSocketTransport(options) {
|
|
187
|
-
const { url, role,
|
|
183
|
+
const { url, role, producerId, metadata, reconnect = true, maxReconnectAttempts = 10 } = options;
|
|
184
|
+
if (role === "producer" && !producerId) throw new Error("A producerId is required for producer transports.");
|
|
188
185
|
let ws = null;
|
|
189
186
|
let destroyed = false;
|
|
187
|
+
let terminalFailure = false;
|
|
190
188
|
let isReady = false;
|
|
191
189
|
let reconnectAttempts = 0;
|
|
192
190
|
let reconnectTimer = null;
|
|
193
191
|
const readyHandlers = /* @__PURE__ */ new Set();
|
|
194
192
|
const messageHandlers = /* @__PURE__ */ new Set();
|
|
193
|
+
const errorHandlers = /* @__PURE__ */ new Set();
|
|
194
|
+
function fail(failure) {
|
|
195
|
+
errorHandlers.forEach((handler) => handler(failure));
|
|
196
|
+
}
|
|
197
|
+
function scheduleReconnect() {
|
|
198
|
+
if (!reconnect || reconnectAttempts >= maxReconnectAttempts) {
|
|
199
|
+
terminalFailure = true;
|
|
200
|
+
fail({
|
|
201
|
+
code: "connection_failed",
|
|
202
|
+
message: `Unable to connect to inspection relay ${url}`
|
|
203
|
+
});
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const delay = Math.min(1e3 * 2 ** reconnectAttempts, 3e4);
|
|
207
|
+
reconnectAttempts++;
|
|
208
|
+
reconnectTimer = setTimeout(connect, delay);
|
|
209
|
+
}
|
|
195
210
|
function connect() {
|
|
196
|
-
if (destroyed) return;
|
|
197
|
-
|
|
211
|
+
if (destroyed || terminalFailure) return;
|
|
212
|
+
try {
|
|
213
|
+
ws = new WebSocket(url);
|
|
214
|
+
} catch {
|
|
215
|
+
scheduleReconnect();
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
198
218
|
ws.onopen = () => {
|
|
199
|
-
reconnectAttempts = 0;
|
|
200
219
|
ws.send(JSON.stringify({
|
|
201
220
|
type: "@statelyai.register",
|
|
202
221
|
protocolVersion: INSPECTION_PROTOCOL_VERSION,
|
|
203
222
|
role,
|
|
204
|
-
|
|
223
|
+
...producerId && { producerId },
|
|
205
224
|
...metadata && { metadata }
|
|
206
225
|
}));
|
|
207
226
|
};
|
|
@@ -217,20 +236,33 @@ function createWebSocketTransport(options) {
|
|
|
217
236
|
if (!raw.type.startsWith(PREFIX)) return;
|
|
218
237
|
const data = raw;
|
|
219
238
|
if (data.type === "@statelyai.registered") {
|
|
239
|
+
if (data.protocolVersion !== INSPECTION_PROTOCOL_VERSION) {
|
|
240
|
+
terminalFailure = true;
|
|
241
|
+
fail({
|
|
242
|
+
code: "unsupported_protocol",
|
|
243
|
+
message: `Relay selected protocol ${data.protocolVersion}; expected ${INSPECTION_PROTOCOL_VERSION}`
|
|
244
|
+
});
|
|
245
|
+
ws?.close(1002, "Unsupported inspection protocol");
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
reconnectAttempts = 0;
|
|
220
249
|
const wasReady = isReady;
|
|
221
250
|
isReady = true;
|
|
222
251
|
if (!wasReady) readyHandlers.forEach((fn) => fn());
|
|
252
|
+
} else if (data.type === "@statelyai.error" && !isReady) {
|
|
253
|
+
terminalFailure = true;
|
|
254
|
+
fail({
|
|
255
|
+
code: data.code,
|
|
256
|
+
message: data.message
|
|
257
|
+
});
|
|
258
|
+
ws?.close(1002, data.message.slice(0, 123));
|
|
223
259
|
}
|
|
224
260
|
messageHandlers.forEach((fn) => fn(data));
|
|
225
261
|
};
|
|
226
262
|
ws.onclose = () => {
|
|
227
|
-
if (destroyed) return;
|
|
263
|
+
if (destroyed || terminalFailure) return;
|
|
228
264
|
isReady = false;
|
|
229
|
-
|
|
230
|
-
const delay = Math.min(1e3 * 2 ** reconnectAttempts, 3e4);
|
|
231
|
-
reconnectAttempts++;
|
|
232
|
-
reconnectTimer = setTimeout(connect, delay);
|
|
233
|
-
}
|
|
265
|
+
scheduleReconnect();
|
|
234
266
|
};
|
|
235
267
|
ws.onerror = () => {};
|
|
236
268
|
}
|
|
@@ -242,9 +274,7 @@ function createWebSocketTransport(options) {
|
|
|
242
274
|
},
|
|
243
275
|
onMessage(handler) {
|
|
244
276
|
messageHandlers.add(handler);
|
|
245
|
-
return () =>
|
|
246
|
-
messageHandlers.delete(handler);
|
|
247
|
-
};
|
|
277
|
+
return () => messageHandlers.delete(handler);
|
|
248
278
|
},
|
|
249
279
|
destroy() {
|
|
250
280
|
if (destroyed) return;
|
|
@@ -254,6 +284,7 @@ function createWebSocketTransport(options) {
|
|
|
254
284
|
ws = null;
|
|
255
285
|
messageHandlers.clear();
|
|
256
286
|
readyHandlers.clear();
|
|
287
|
+
errorHandlers.clear();
|
|
257
288
|
},
|
|
258
289
|
get ready() {
|
|
259
290
|
return isReady && !!ws && ws.readyState === WebSocket.OPEN;
|
|
@@ -261,187 +292,14 @@ function createWebSocketTransport(options) {
|
|
|
261
292
|
onReady(handler) {
|
|
262
293
|
readyHandlers.add(handler);
|
|
263
294
|
if (isReady) handler();
|
|
264
|
-
return () =>
|
|
265
|
-
readyHandlers.delete(handler);
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
function createMultiplexedWebSocketConnection(options) {
|
|
271
|
-
const { url, role, sessionId: primarySessionId, metadata: primaryMetadata, reconnect = true, maxReconnectAttempts = 10 } = options;
|
|
272
|
-
let ws = null;
|
|
273
|
-
let destroyed = false;
|
|
274
|
-
let connectionReady = false;
|
|
275
|
-
let reconnectAttempts = 0;
|
|
276
|
-
let reconnectTimer = null;
|
|
277
|
-
const pendingOutbound = [];
|
|
278
|
-
const channels = /* @__PURE__ */ new Map();
|
|
279
|
-
function ensureChannel(opts) {
|
|
280
|
-
const existing = channels.get(opts.sessionId);
|
|
281
|
-
if (existing) return existing;
|
|
282
|
-
const state = {
|
|
283
|
-
sessionId: opts.sessionId,
|
|
284
|
-
metadata: opts.metadata,
|
|
285
|
-
ready: false,
|
|
286
|
-
readyHandlers: /* @__PURE__ */ new Set(),
|
|
287
|
-
messageHandlers: /* @__PURE__ */ new Set(),
|
|
288
|
-
registered: false,
|
|
289
|
-
transport: null
|
|
290
|
-
};
|
|
291
|
-
state.transport = {
|
|
292
|
-
send(msg) {
|
|
293
|
-
if (destroyed) return;
|
|
294
|
-
const stamped = {
|
|
295
|
-
...msg,
|
|
296
|
-
sessionId: state.sessionId
|
|
297
|
-
};
|
|
298
|
-
if (ws && ws.readyState === WebSocket.OPEN && state.registered) {
|
|
299
|
-
ws.send(JSON.stringify(stamped));
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
pendingOutbound.push(stamped);
|
|
303
|
-
},
|
|
304
|
-
onMessage(handler) {
|
|
305
|
-
state.messageHandlers.add(handler);
|
|
306
|
-
return () => {
|
|
307
|
-
state.messageHandlers.delete(handler);
|
|
308
|
-
};
|
|
309
|
-
},
|
|
310
|
-
destroy() {
|
|
311
|
-
state.messageHandlers.clear();
|
|
312
|
-
state.readyHandlers.clear();
|
|
313
|
-
channels.delete(state.sessionId);
|
|
314
|
-
},
|
|
315
|
-
get ready() {
|
|
316
|
-
return state.ready;
|
|
317
|
-
},
|
|
318
|
-
onReady(handler) {
|
|
319
|
-
state.readyHandlers.add(handler);
|
|
320
|
-
if (state.ready) handler();
|
|
321
|
-
return () => {
|
|
322
|
-
state.readyHandlers.delete(handler);
|
|
323
|
-
};
|
|
324
|
-
}
|
|
325
|
-
};
|
|
326
|
-
channels.set(state.sessionId, state);
|
|
327
|
-
if (connectionReady && ws && ws.readyState === WebSocket.OPEN) registerChannel(state);
|
|
328
|
-
return state;
|
|
329
|
-
}
|
|
330
|
-
function registerChannel(state) {
|
|
331
|
-
if (!ws || ws.readyState !== WebSocket.OPEN) return;
|
|
332
|
-
if (state.registered) return;
|
|
333
|
-
if (state.sessionId === primarySessionId) return;
|
|
334
|
-
ws.send(JSON.stringify({
|
|
335
|
-
type: "@statelyai.addSession",
|
|
336
|
-
sessionId: state.sessionId,
|
|
337
|
-
...state.metadata && { metadata: state.metadata }
|
|
338
|
-
}));
|
|
339
|
-
}
|
|
340
|
-
function markChannelReady(sessionId) {
|
|
341
|
-
const state = channels.get(sessionId);
|
|
342
|
-
if (!state || state.ready) return;
|
|
343
|
-
state.ready = true;
|
|
344
|
-
state.registered = true;
|
|
345
|
-
state.readyHandlers.forEach((fn) => fn());
|
|
346
|
-
flushPending();
|
|
347
|
-
}
|
|
348
|
-
function flushPending() {
|
|
349
|
-
if (!ws || ws.readyState !== WebSocket.OPEN) return;
|
|
350
|
-
const remaining = [];
|
|
351
|
-
for (const msg of pendingOutbound) {
|
|
352
|
-
const sessionId = msg.sessionId ?? primarySessionId;
|
|
353
|
-
const state = channels.get(sessionId);
|
|
354
|
-
if (state && state.registered) ws.send(JSON.stringify(msg));
|
|
355
|
-
else remaining.push(msg);
|
|
356
|
-
}
|
|
357
|
-
pendingOutbound.length = 0;
|
|
358
|
-
pendingOutbound.push(...remaining);
|
|
359
|
-
}
|
|
360
|
-
function connect() {
|
|
361
|
-
if (destroyed) return;
|
|
362
|
-
ws = new WebSocket(url);
|
|
363
|
-
ws.onopen = () => {
|
|
364
|
-
reconnectAttempts = 0;
|
|
365
|
-
ws.send(JSON.stringify({
|
|
366
|
-
type: "@statelyai.register",
|
|
367
|
-
protocolVersion: INSPECTION_PROTOCOL_VERSION,
|
|
368
|
-
role,
|
|
369
|
-
sessionId: primarySessionId,
|
|
370
|
-
...primaryMetadata && { metadata: primaryMetadata }
|
|
371
|
-
}));
|
|
372
|
-
};
|
|
373
|
-
ws.onmessage = (event) => {
|
|
374
|
-
if (destroyed) return;
|
|
375
|
-
let raw;
|
|
376
|
-
try {
|
|
377
|
-
raw = JSON.parse(event.data);
|
|
378
|
-
} catch {
|
|
379
|
-
return;
|
|
380
|
-
}
|
|
381
|
-
if (!raw?.type || typeof raw.type !== "string") return;
|
|
382
|
-
if (!raw.type.startsWith(PREFIX)) return;
|
|
383
|
-
const data = raw;
|
|
384
|
-
if (data.type === "@statelyai.registered") {
|
|
385
|
-
connectionReady = true;
|
|
386
|
-
markChannelReady(primarySessionId);
|
|
387
|
-
for (const state of channels.values()) if (!state.registered && state.sessionId !== primarySessionId) registerChannel(state);
|
|
388
|
-
flushPending();
|
|
389
|
-
return;
|
|
390
|
-
}
|
|
391
|
-
if (data.type === "@statelyai.sessionAdded") {
|
|
392
|
-
const added = data.sessionId;
|
|
393
|
-
if (added) markChannelReady(added);
|
|
394
|
-
return;
|
|
395
|
-
}
|
|
396
|
-
const sessionId = raw.sessionId ?? primarySessionId;
|
|
397
|
-
const state = channels.get(sessionId);
|
|
398
|
-
if (state) state.messageHandlers.forEach((fn) => fn(data));
|
|
399
|
-
};
|
|
400
|
-
ws.onclose = () => {
|
|
401
|
-
if (destroyed) return;
|
|
402
|
-
connectionReady = false;
|
|
403
|
-
for (const state of channels.values()) {
|
|
404
|
-
state.ready = false;
|
|
405
|
-
state.registered = false;
|
|
406
|
-
}
|
|
407
|
-
if (reconnect && reconnectAttempts < maxReconnectAttempts) {
|
|
408
|
-
const delay = Math.min(1e3 * 2 ** reconnectAttempts, 3e4);
|
|
409
|
-
reconnectAttempts++;
|
|
410
|
-
reconnectTimer = setTimeout(connect, delay);
|
|
411
|
-
}
|
|
412
|
-
};
|
|
413
|
-
ws.onerror = () => {};
|
|
414
|
-
}
|
|
415
|
-
ensureChannel({
|
|
416
|
-
sessionId: primarySessionId,
|
|
417
|
-
metadata: primaryMetadata
|
|
418
|
-
});
|
|
419
|
-
connect();
|
|
420
|
-
return {
|
|
421
|
-
openChannel(opts) {
|
|
422
|
-
return ensureChannel(opts).transport;
|
|
423
|
-
},
|
|
424
|
-
destroy() {
|
|
425
|
-
if (destroyed) return;
|
|
426
|
-
destroyed = true;
|
|
427
|
-
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
428
|
-
ws?.close();
|
|
429
|
-
ws = null;
|
|
430
|
-
for (const state of channels.values()) {
|
|
431
|
-
state.messageHandlers.clear();
|
|
432
|
-
state.readyHandlers.clear();
|
|
433
|
-
}
|
|
434
|
-
channels.clear();
|
|
435
|
-
pendingOutbound.length = 0;
|
|
436
|
-
},
|
|
437
|
-
get ready() {
|
|
438
|
-
return connectionReady;
|
|
295
|
+
return () => readyHandlers.delete(handler);
|
|
439
296
|
},
|
|
440
|
-
|
|
441
|
-
|
|
297
|
+
onError(handler) {
|
|
298
|
+
errorHandlers.add(handler);
|
|
299
|
+
return () => errorHandlers.delete(handler);
|
|
442
300
|
}
|
|
443
301
|
};
|
|
444
302
|
}
|
|
445
303
|
|
|
446
304
|
//#endregion
|
|
447
|
-
export {
|
|
305
|
+
export { toInitMessage as a, createPendingExportManager as i, createWebSocketTransport as n, createEventRegistry as r, createPostMessageTransport as t };
|
package/package.json
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@statelyai/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.1",
|
|
4
4
|
"license": "MIT",
|
|
5
|
+
"bin": {
|
|
6
|
+
"stately-sdk": "./dist/bin.mjs"
|
|
7
|
+
},
|
|
5
8
|
"files": [
|
|
6
9
|
"dist",
|
|
7
10
|
"schemas"
|
|
@@ -62,6 +65,10 @@
|
|
|
62
65
|
"types": "./dist/relay.d.mts",
|
|
63
66
|
"import": "./dist/relay.mjs"
|
|
64
67
|
},
|
|
68
|
+
"./node-relay": {
|
|
69
|
+
"types": "./dist/node-relay.d.mts",
|
|
70
|
+
"import": "./dist/node-relay.mjs"
|
|
71
|
+
},
|
|
65
72
|
"./statelyai.schema.json": "./schemas/statelyai.schema.json",
|
|
66
73
|
"./schemas/statelyai.schema.json": "./schemas/statelyai.schema.json",
|
|
67
74
|
"./xstate.schema.json": "./schemas/xstate.schema.json",
|
|
@@ -73,16 +80,20 @@
|
|
|
73
80
|
"@statelyai/graph": "^1.0.0",
|
|
74
81
|
"esbuild": "^0.27.0",
|
|
75
82
|
"typescript": "^5.9.3",
|
|
83
|
+
"ws": "^8.19.0",
|
|
76
84
|
"xstate": "^5.0.0"
|
|
77
85
|
},
|
|
78
86
|
"devDependencies": {
|
|
79
87
|
"@types/json-schema": "^7.0.15",
|
|
88
|
+
"@types/ws": "^8.18.1",
|
|
80
89
|
"jsdom": "^27.4.0",
|
|
81
90
|
"tsdown": "0.21.0-beta.2",
|
|
82
91
|
"vitest": "^3.2.4",
|
|
83
|
-
"
|
|
84
|
-
"@statelyai/graph-tools": "0.0.7"
|
|
85
|
-
|
|
92
|
+
"@statelyai/api-contract": "0.0.1",
|
|
93
|
+
"@statelyai/graph-tools": "0.0.7"
|
|
94
|
+
},
|
|
95
|
+
"engines": {
|
|
96
|
+
"node": ">=22.4"
|
|
86
97
|
},
|
|
87
98
|
"scripts": {
|
|
88
99
|
"build": "tsdown",
|
|
File without changes
|