@statelyai/sdk 0.12.3 → 0.13.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.
@@ -1,4 +1,4 @@
1
- import { PREFIX } from "./protocol.mjs";
1
+ import { INSPECTION_PROTOCOL_VERSION, PREFIX } from "./protocol.mjs";
2
2
 
3
3
  //#region src/clientUtils.ts
4
4
  const jsonResultFormats = new Set([
@@ -199,6 +199,7 @@ function createWebSocketTransport(options) {
199
199
  reconnectAttempts = 0;
200
200
  ws.send(JSON.stringify({
201
201
  type: "@statelyai.register",
202
+ protocolVersion: INSPECTION_PROTOCOL_VERSION,
202
203
  role,
203
204
  sessionId,
204
205
  ...metadata && { metadata }
@@ -266,6 +267,181 @@ function createWebSocketTransport(options) {
266
267
  }
267
268
  };
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;
439
+ },
440
+ get primarySessionId() {
441
+ return primarySessionId;
442
+ }
443
+ };
444
+ }
269
445
 
270
446
  //#endregion
271
- export { toInitMessage as a, createPendingExportManager as i, createWebSocketTransport as n, createEventRegistry as r, createPostMessageTransport as t };
447
+ export { createPendingExportManager as a, createEventRegistry as i, createPostMessageTransport as n, toInitMessage as o, createWebSocketTransport as r, createMultiplexedWebSocketConnection as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statelyai/sdk",
3
- "version": "0.12.3",
3
+ "version": "0.13.0",
4
4
  "license": "MIT",
5
5
  "files": [
6
6
  "dist",
@@ -58,6 +58,10 @@
58
58
  "types": "./dist/protocol.d.mts",
59
59
  "import": "./dist/protocol.mjs"
60
60
  },
61
+ "./relay": {
62
+ "types": "./dist/relay.d.mts",
63
+ "import": "./dist/relay.mjs"
64
+ },
61
65
  "./statelyai.schema.json": "./schemas/statelyai.schema.json",
62
66
  "./schemas/statelyai.schema.json": "./schemas/statelyai.schema.json",
63
67
  "./xstate.schema.json": "./schemas/xstate.schema.json",
@@ -76,6 +80,7 @@
76
80
  "jsdom": "^27.4.0",
77
81
  "tsdown": "0.21.0-beta.2",
78
82
  "vitest": "^3.2.4",
83
+ "ws": "^8.19.0",
79
84
  "@statelyai/graph-tools": "0.0.4"
80
85
  },
81
86
  "scripts": {
File without changes