@statelyai/sdk 0.1.1 → 0.3.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.
@@ -0,0 +1,250 @@
1
+ //#region src/clientUtils.ts
2
+ const jsonResultFormats = new Set(["digraph", "json"]);
3
+ function createRequestId() {
4
+ const cryptoObject = globalThis.crypto;
5
+ if (cryptoObject?.randomUUID) return cryptoObject.randomUUID();
6
+ return Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2);
7
+ }
8
+ function createEventRegistry() {
9
+ const listeners = {};
10
+ return {
11
+ emit(event, data) {
12
+ listeners[event]?.forEach((handler) => handler(data));
13
+ },
14
+ on(event, handler) {
15
+ if (!listeners[event]) listeners[event] = /* @__PURE__ */ new Set();
16
+ listeners[event].add(handler);
17
+ },
18
+ off(event, handler) {
19
+ listeners[event]?.delete(handler);
20
+ },
21
+ clear() {
22
+ Object.values(listeners).forEach((handlers) => handlers?.clear());
23
+ }
24
+ };
25
+ }
26
+ function parseExportedData(format, data) {
27
+ if (jsonResultFormats.has(format) && typeof data === "string") return JSON.parse(data);
28
+ return data;
29
+ }
30
+ function createPendingExportManager(sendRetrieve) {
31
+ const pendingExports = /* @__PURE__ */ new Map();
32
+ return {
33
+ start(format, callOptions, destroyedMessage, isDestroyed) {
34
+ const { timeout = 1e4, ...formatOptions } = callOptions ?? {};
35
+ return new Promise((resolve, reject) => {
36
+ if (isDestroyed()) {
37
+ reject(new Error(destroyedMessage));
38
+ return;
39
+ }
40
+ const requestId = createRequestId();
41
+ const timer = setTimeout(() => {
42
+ pendingExports.delete(requestId);
43
+ reject(/* @__PURE__ */ new Error("Export timed out"));
44
+ }, timeout);
45
+ pendingExports.set(requestId, {
46
+ resolve,
47
+ reject,
48
+ timer,
49
+ format
50
+ });
51
+ sendRetrieve({
52
+ type: "@statelyai.retrieve",
53
+ requestId,
54
+ format,
55
+ ...Object.keys(formatOptions).length > 0 && { options: formatOptions }
56
+ });
57
+ });
58
+ },
59
+ resolve(requestId, data) {
60
+ const pendingExport = pendingExports.get(requestId);
61
+ if (!pendingExport) return;
62
+ pendingExports.delete(requestId);
63
+ const { resolve, timer, format } = pendingExport;
64
+ clearTimeout(timer);
65
+ resolve(parseExportedData(format, data));
66
+ },
67
+ reject(error, requestId) {
68
+ if (requestId) {
69
+ const pendingExport = pendingExports.get(requestId);
70
+ if (!pendingExport) return;
71
+ pendingExports.delete(requestId);
72
+ clearTimeout(pendingExport.timer);
73
+ pendingExport.reject(error);
74
+ return;
75
+ }
76
+ pendingExports.forEach((pendingExport, pendingRequestId) => {
77
+ pendingExports.delete(pendingRequestId);
78
+ clearTimeout(pendingExport.timer);
79
+ pendingExport.reject(error);
80
+ });
81
+ },
82
+ clear(message) {
83
+ this.reject(new Error(message));
84
+ }
85
+ };
86
+ }
87
+ function toInitMessage(options) {
88
+ return {
89
+ type: "@statelyai.init",
90
+ machine: options.machine,
91
+ format: options.format,
92
+ mode: options.mode,
93
+ theme: options.theme,
94
+ readOnly: options.readOnly,
95
+ depth: options.depth,
96
+ leftPanels: options.panels?.leftPanels,
97
+ rightPanels: options.panels?.rightPanels,
98
+ activePanels: options.panels?.activePanels
99
+ };
100
+ }
101
+
102
+ //#endregion
103
+ //#region src/protocol.ts
104
+ const PREFIX = "@statelyai.";
105
+
106
+ //#endregion
107
+ //#region src/transport.ts
108
+ function createPostMessageTransport(options) {
109
+ const { iframe, targetOrigin } = options;
110
+ let destroyed = false;
111
+ let isReady = false;
112
+ const readyHandlers = /* @__PURE__ */ new Set();
113
+ const messageHandlers = /* @__PURE__ */ new Set();
114
+ function handleMessage(e) {
115
+ if (destroyed) return;
116
+ const expectedSource = options.source ?? iframe.contentWindow;
117
+ if (e.source !== expectedSource) return;
118
+ if (targetOrigin !== "*" && e.origin !== targetOrigin) return;
119
+ const data = e.data;
120
+ if (!data?.type?.startsWith?.(PREFIX)) return;
121
+ if (data.type === "@statelyai.ready" && !isReady) {
122
+ isReady = true;
123
+ readyHandlers.forEach((fn) => fn());
124
+ readyHandlers.clear();
125
+ }
126
+ messageHandlers.forEach((fn) => fn(data));
127
+ }
128
+ window.addEventListener("message", handleMessage);
129
+ return {
130
+ send(msg) {
131
+ if (destroyed) return;
132
+ iframe.contentWindow?.postMessage(msg, targetOrigin);
133
+ },
134
+ onMessage(handler) {
135
+ messageHandlers.add(handler);
136
+ return () => {
137
+ messageHandlers.delete(handler);
138
+ };
139
+ },
140
+ destroy() {
141
+ if (destroyed) return;
142
+ destroyed = true;
143
+ window.removeEventListener("message", handleMessage);
144
+ messageHandlers.clear();
145
+ readyHandlers.clear();
146
+ },
147
+ get ready() {
148
+ return isReady;
149
+ },
150
+ onReady(handler) {
151
+ if (isReady) {
152
+ handler();
153
+ return () => {};
154
+ }
155
+ readyHandlers.add(handler);
156
+ return () => {
157
+ readyHandlers.delete(handler);
158
+ };
159
+ }
160
+ };
161
+ }
162
+ function createWebSocketTransport(options) {
163
+ const { url, role, sessionId, metadata, reconnect = true, maxReconnectAttempts = 10 } = options;
164
+ let ws = null;
165
+ let destroyed = false;
166
+ let isReady = false;
167
+ let reconnectAttempts = 0;
168
+ let reconnectTimer = null;
169
+ const readyHandlers = /* @__PURE__ */ new Set();
170
+ const messageHandlers = /* @__PURE__ */ new Set();
171
+ function connect() {
172
+ if (destroyed) return;
173
+ ws = new WebSocket(url);
174
+ ws.onopen = () => {
175
+ reconnectAttempts = 0;
176
+ ws.send(JSON.stringify({
177
+ type: "@statelyai.register",
178
+ role,
179
+ sessionId,
180
+ ...metadata && { metadata }
181
+ }));
182
+ };
183
+ ws.onmessage = (event) => {
184
+ if (destroyed) return;
185
+ let raw;
186
+ try {
187
+ raw = JSON.parse(event.data);
188
+ } catch {
189
+ return;
190
+ }
191
+ if (!raw?.type || typeof raw.type !== "string") return;
192
+ if (!raw.type.startsWith(PREFIX)) return;
193
+ const data = raw;
194
+ if (data.type === "@statelyai.registered" && !isReady) {
195
+ isReady = true;
196
+ readyHandlers.forEach((fn) => fn());
197
+ readyHandlers.clear();
198
+ }
199
+ messageHandlers.forEach((fn) => fn(data));
200
+ };
201
+ ws.onclose = () => {
202
+ if (destroyed) return;
203
+ isReady = false;
204
+ if (reconnect && reconnectAttempts < maxReconnectAttempts) {
205
+ const delay = Math.min(1e3 * 2 ** reconnectAttempts, 3e4);
206
+ reconnectAttempts++;
207
+ reconnectTimer = setTimeout(connect, delay);
208
+ }
209
+ };
210
+ ws.onerror = () => {};
211
+ }
212
+ connect();
213
+ return {
214
+ send(msg) {
215
+ if (destroyed || !ws || ws.readyState !== WebSocket.OPEN) return;
216
+ ws.send(JSON.stringify(msg));
217
+ },
218
+ onMessage(handler) {
219
+ messageHandlers.add(handler);
220
+ return () => {
221
+ messageHandlers.delete(handler);
222
+ };
223
+ },
224
+ destroy() {
225
+ if (destroyed) return;
226
+ destroyed = true;
227
+ if (reconnectTimer) clearTimeout(reconnectTimer);
228
+ ws?.close();
229
+ ws = null;
230
+ messageHandlers.clear();
231
+ readyHandlers.clear();
232
+ },
233
+ get ready() {
234
+ return isReady;
235
+ },
236
+ onReady(handler) {
237
+ if (isReady) {
238
+ handler();
239
+ return () => {};
240
+ }
241
+ readyHandlers.add(handler);
242
+ return () => {
243
+ readyHandlers.delete(handler);
244
+ };
245
+ }
246
+ };
247
+ }
248
+
249
+ //#endregion
250
+ export { createRequestId as a, createPendingExportManager as i, createWebSocketTransport as n, toInitMessage as o, createEventRegistry as r, createPostMessageTransport as t };
package/package.json CHANGED
@@ -1,24 +1,52 @@
1
1
  {
2
2
  "name": "@statelyai/sdk",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "license": "MIT",
5
+ "files": [
6
+ "dist"
7
+ ],
5
8
  "type": "module",
6
9
  "main": "./dist/index.mjs",
7
10
  "types": "./dist/index.d.mts",
8
11
  "exports": {
9
12
  ".": {
10
- "import": "./dist/index.mjs",
11
- "types": "./dist/index.d.mts"
13
+ "types": "./dist/index.d.mts",
14
+ "import": "./dist/index.mjs"
15
+ },
16
+ "./embed": {
17
+ "types": "./dist/embed.d.mts",
18
+ "import": "./dist/embed.mjs"
19
+ },
20
+ "./inspect": {
21
+ "types": "./dist/inspect.d.mts",
22
+ "import": "./dist/inspect.mjs"
23
+ },
24
+ "./graph": {
25
+ "types": "./dist/graph.d.mts",
26
+ "import": "./dist/graph.mjs"
27
+ },
28
+ "./sync": {
29
+ "types": "./dist/sync.d.mts",
30
+ "import": "./dist/sync.mjs"
31
+ },
32
+ "./studio": {
33
+ "types": "./dist/studio.d.mts",
34
+ "import": "./dist/studio.mjs"
12
35
  }
13
36
  },
14
- "files": [
15
- "dist"
16
- ],
37
+ "dependencies": {
38
+ "@statelyai/graph": "^0.4.0"
39
+ },
17
40
  "devDependencies": {
41
+ "jsdom": "^27.4.0",
18
42
  "tsdown": "0.21.0-beta.2",
19
- "typescript": "^5.9.3"
43
+ "typescript": "^5.9.3",
44
+ "vitest": "^3.2.4"
20
45
  },
21
46
  "scripts": {
22
- "build": "tsdown"
47
+ "build": "tsdown",
48
+ "test": "vitest run",
49
+ "test:live": "vitest run src/studio.live.integration.test.ts",
50
+ "test:watch": "vitest"
23
51
  }
24
52
  }