@statelyai/sdk 0.17.0 → 0.20.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 (33) hide show
  1. package/README.md +38 -2
  2. package/dist/auth.d.mts +1 -1
  3. package/dist/editor-sync-types-D8_EZm0A.d.mts +94 -0
  4. package/dist/editorHostProtocol-D_h4y3rX.mjs +5 -0
  5. package/dist/editorHostSession-CZHd22pZ.mjs +478 -0
  6. package/dist/editorHostSession-jnC5mQDN.d.mts +184 -0
  7. package/dist/editorHostSession.d.mts +2 -0
  8. package/dist/editorHostSession.mjs +4 -0
  9. package/dist/embed.d.mts +3 -3
  10. package/dist/embed.mjs +33 -3
  11. package/dist/{graph-DuOeLGvh.d.mts → graph-DxeqWHC3.d.mts} +1 -1
  12. package/dist/graph.d.mts +1 -1
  13. package/dist/index.d.mts +8 -7
  14. package/dist/index.mjs +8 -6
  15. package/dist/{inspect-BZFzyeew.d.mts → inspect-CGJR4iZK.d.mts} +13 -3
  16. package/dist/{inspect-CF_6aEnv.mjs → inspect-CWwLGwLq.mjs} +58 -31
  17. package/dist/inspect.d.mts +1 -1
  18. package/dist/inspect.mjs +2 -1
  19. package/dist/patchTypes.d.mts +1 -1
  20. package/dist/protocol.d.mts +66 -50
  21. package/dist/protocol.mjs +17 -3
  22. package/dist/{studio-txdyJHD7.mjs → schemas-C2raUv-n.mjs} +1 -396
  23. package/dist/{statelyPragma-Th0gdXfx.mjs → statelyPragma-GN-bfuC_.mjs} +2 -1
  24. package/dist/{studio-Bk_u41gu.d.mts → studio-DLduJRSp.d.mts} +2 -2
  25. package/dist/studio-EKd7cQN7.mjs +397 -0
  26. package/dist/studio.d.mts +1 -1
  27. package/dist/studio.mjs +1 -1
  28. package/dist/sync.d.mts +2 -2
  29. package/dist/sync.mjs +2 -2
  30. package/dist/{transport-B_ig9Znn.mjs → version-TY9EZF0F.mjs} +39 -5
  31. package/package.json +6 -2
  32. /package/dist/{auth-2Thaw42j.d.mts → auth-B0ZTKri2.d.mts} +0 -0
  33. /package/dist/{index-ag6K2iAS.d.mts → index-CKFbG_Ix.d.mts} +0 -0
package/README.md CHANGED
@@ -37,6 +37,10 @@ embed.init({
37
37
  format: 'xstate',
38
38
  mode: 'editing',
39
39
  theme: 'dark',
40
+ documents: [
41
+ { path: 'README.md', content: '# How this workflow works' },
42
+ { path: 'src/machine.ts', content: machineSource },
43
+ ],
40
44
  });
41
45
  ```
42
46
 
@@ -70,12 +74,24 @@ embed.on('capabilityDenied', (event) => {
70
74
  Use `access-unverified` when reopening or signing in, rather than upgrading, is
71
75
  the appropriate recovery action.
72
76
 
77
+ ### Editor hosts
78
+
79
+ <!-- shared editor host session and protocol from src/editorHostSession.ts and src/editorHostProtocol.ts -->
80
+
81
+ CLI and editor-extension hosts use `EditorHostSession` from
82
+ `@statelyai/sdk/editor-host` for initialization, saves, source refreshes,
83
+ clipboard requests, and editor protocol negotiation. Use
84
+ `createRemoteEditorSyncBackend` for two-way validation of the versioned Editor
85
+ Sync HTTP contract.
86
+ Hosts keep ownership of files, credentials, UI, and transport adapters.
87
+
73
88
  Key capabilities:
74
89
 
75
90
  - **Mount or attach** to any container element or existing iframe
76
91
  - **Two-way sync** - push machine configs in, get changes and saves back via event callbacks
77
92
  - **Export** to multiple formats: XState v5, XState JSON, Mermaid, Redux, Zustand, ASL, SCXML, and more
78
93
  - **Access policies** for read-only, no-export, no-AI, shallow-viewer embeds
94
+ - **Reference documents** supplied as ordered, read-only Markdown, code, or text
79
95
  - **Comments** via Liveblocks integration (optional)
80
96
  - **Asset uploads** with built-in S3 and Supabase adapters, or bring your own upload handler
81
97
  - **Runtime settings** - toggle color mode, grid, snap lines, autolayout, and view mode on the fly
@@ -181,13 +197,33 @@ const inspector = createInspector({
181
197
  });
182
198
  ```
183
199
 
200
+ For persisted actors, `resolveSessionId` can keep one producer-local identity
201
+ across sequential restore/resume incarnations. Generate a new id for a new
202
+ logical invocation, persist it beside the actor snapshot, and reuse it only
203
+ after the previous incarnation has stopped. Create a new inspector for the
204
+ restored actor system, reusing the same room and producer identities:
205
+
206
+ ```ts
207
+ const inspector = createInspector({
208
+ roomId: persistedInspectionRoomId,
209
+ producerId: persistedInspectionProducerId,
210
+ resolveSessionId: (actor, defaultSessionId) =>
211
+ actor.logic === machine ? persistedInspectionId : defaultSessionId,
212
+ });
213
+ ```
214
+
215
+ A resume must reuse the same room id, producer id, and resolved session id. Keep
216
+ the room id private because it is the room's bearer capability. A new logical
217
+ invocation keeps the producer id but receives a new resolved session id.
218
+
184
219
  Stopped actors remain in `inspector.actors` and in reconnect checkpoints, so
185
220
  completed request-scoped runs stay available for inspection.
186
221
 
187
222
  XState v5 and v6 inspection events normalize to independent actor, event,
188
223
  snapshot, and stop observations before crossing the WebSocket protocol.
189
- `actorId` is system-local and `sessionId` is process-local. A room uses
190
- `producerId + sessionId` as the global actor identity.
224
+ `actorId` is system-local. `sessionId` defaults to XState's runtime value and
225
+ may be producer-resolved. A room uses `producerId + sessionId` as the global
226
+ actor identity.
191
227
  Observations captured before the first transport registration replay in order
192
228
  after the initial actor-tree baseline. Reconnects use the latest checkpoint.
193
229
  In Node, the inspector opens in the default browser automatically. The SDK
package/dist/auth.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { i as getCredentialToken, n as StatelyCredential, r as createAuthorizationHeader, t as StatelyAuthMode } from "./auth-2Thaw42j.mjs";
1
+ import { i as getCredentialToken, n as StatelyCredential, r as createAuthorizationHeader, t as StatelyAuthMode } from "./auth-B0ZTKri2.mjs";
2
2
  export { StatelyAuthMode, StatelyCredential, createAuthorizationHeader, getCredentialToken };
@@ -0,0 +1,94 @@
1
+ //#region ../api-contract/dist/editor-sync-types.d.mts
2
+ //#region src/editor-sync-types.d.ts
3
+ interface EditorCapabilities {
4
+ edit?: boolean;
5
+ export?: boolean;
6
+ ai?: boolean;
7
+ simulate?: boolean;
8
+ inspect?: boolean;
9
+ navigateHierarchy?: boolean;
10
+ maxDepth?: number;
11
+ panels?: string[];
12
+ commands?: string[];
13
+ }
14
+ interface EditorSyncAccess {
15
+ level: 'full' | 'restricted';
16
+ readOnly: boolean;
17
+ readOnlyReason?: 'free-plan' | 'access-unverified';
18
+ capabilities: EditorCapabilities;
19
+ }
20
+ interface EditorSyncSourceRange {
21
+ startLine: number;
22
+ startChar: number;
23
+ endLine: number;
24
+ endChar: number;
25
+ }
26
+ interface EditorSyncSourceLocation {
27
+ uri: string;
28
+ range: EditorSyncSourceRange;
29
+ }
30
+ interface EditorSyncStateSourceLocation extends EditorSyncSourceLocation {
31
+ path: string[];
32
+ kind: 'inline' | 'objectReference' | 'createStateConfig';
33
+ symbol?: string;
34
+ keyRange?: EditorSyncSourceRange;
35
+ referenceRange?: EditorSyncSourceRange;
36
+ keySource?: {
37
+ kind: 'const' | 'enumMember' | 'computedLiteral';
38
+ symbol?: string;
39
+ valueRange?: EditorSyncSourceRange;
40
+ machineLocal?: boolean;
41
+ };
42
+ keyReplacementText?: string;
43
+ }
44
+ interface MachineSourceLocations {
45
+ version: 1;
46
+ profile?: 'xstate-v5' | 'xstate-v6-alpha';
47
+ root?: EditorSyncSourceLocation;
48
+ statesObject?: EditorSyncStateSourceLocation;
49
+ states: EditorSyncStateSourceLocation[];
50
+ staticValues?: Array<EditorSyncSourceLocation & {
51
+ value: string;
52
+ }>;
53
+ transitionFunctions?: Array<EditorSyncSourceLocation & {
54
+ symbol: string;
55
+ sourcePaths: string[][];
56
+ }>;
57
+ }
58
+ interface EditorSyncSourceDocument {
59
+ uri: string;
60
+ fileName: string;
61
+ text: string;
62
+ }
63
+ interface EditorSyncDocumentContext {
64
+ rootUri: string;
65
+ rootFileName: string;
66
+ rootDir: string;
67
+ documents: EditorSyncSourceDocument[];
68
+ }
69
+ type EditorSyncParseRequest = EditorSyncDocumentContext;
70
+ interface EditorSyncParseResponse {
71
+ machine: unknown;
72
+ format: string;
73
+ sourceLocations?: MachineSourceLocations;
74
+ access?: EditorSyncAccess;
75
+ }
76
+ interface EditorSyncApplyRequest extends EditorSyncDocumentContext {
77
+ machineConfig?: unknown;
78
+ patches?: unknown[];
79
+ prevGraph?: unknown;
80
+ nextGraph?: unknown;
81
+ sourceLocations?: MachineSourceLocations;
82
+ serialized?: Record<string, string>;
83
+ preserveStatelyMetadata?: boolean;
84
+ }
85
+ interface EditorSyncTextReplacement {
86
+ uri?: string;
87
+ range: EditorSyncSourceRange;
88
+ newText: string;
89
+ }
90
+ interface EditorSyncApplyResponse {
91
+ replacements: EditorSyncTextReplacement[];
92
+ } //#endregion
93
+ //#endregion
94
+ export { EditorSyncParseRequest as a, EditorSyncSourceLocation as c, EditorSyncTextReplacement as d, MachineSourceLocations as f, EditorSyncApplyResponse as i, EditorSyncSourceRange as l, EditorSyncAccess as n, EditorSyncParseResponse as o, EditorSyncApplyRequest as r, EditorSyncSourceDocument as s, EditorCapabilities as t, EditorSyncStateSourceLocation as u };
@@ -0,0 +1,5 @@
1
+ //#region src/editorHostProtocol.ts
2
+ const EDITOR_PROTOCOL_VERSION = 1;
3
+
4
+ //#endregion
5
+ export { EDITOR_PROTOCOL_VERSION as t };
@@ -0,0 +1,478 @@
1
+ import { t as EDITOR_PROTOCOL_VERSION } from "./editorHostProtocol-D_h4y3rX.mjs";
2
+ import { a as literal, c as record, d as unknown, l as string, n as array, o as number, r as boolean, s as object, t as _enum } from "./schemas-C2raUv-n.mjs";
3
+
4
+ //#region ../api-contract/dist/editor-sync.mjs
5
+ const EDITOR_SYNC_PROTOCOL_VERSION = 1;
6
+ const EDITOR_SYNC_PROTOCOL_HEADER = "X-Stately-Editor-Sync-Version";
7
+ const editorCapabilitiesSchema = object({
8
+ edit: boolean().optional(),
9
+ export: boolean().optional(),
10
+ ai: boolean().optional(),
11
+ simulate: boolean().optional(),
12
+ inspect: boolean().optional(),
13
+ navigateHierarchy: boolean().optional(),
14
+ maxDepth: number().optional(),
15
+ panels: array(string()).optional(),
16
+ commands: array(string()).optional()
17
+ }).passthrough();
18
+ const editorSyncAccessSchema = object({
19
+ level: _enum(["full", "restricted"]),
20
+ readOnly: boolean(),
21
+ readOnlyReason: _enum(["free-plan", "access-unverified"]).optional(),
22
+ capabilities: editorCapabilitiesSchema
23
+ }).passthrough();
24
+ const editorSyncSourceRangeSchema = object({
25
+ startLine: number(),
26
+ startChar: number(),
27
+ endLine: number(),
28
+ endChar: number()
29
+ });
30
+ const editorSyncSourceLocationSchema = object({
31
+ uri: string(),
32
+ range: editorSyncSourceRangeSchema
33
+ });
34
+ const editorSyncStateSourceLocationSchema = editorSyncSourceLocationSchema.extend({
35
+ path: array(string()),
36
+ kind: _enum([
37
+ "inline",
38
+ "objectReference",
39
+ "createStateConfig"
40
+ ]),
41
+ symbol: string().optional(),
42
+ keyRange: editorSyncSourceRangeSchema.optional(),
43
+ referenceRange: editorSyncSourceRangeSchema.optional(),
44
+ keySource: object({
45
+ kind: _enum([
46
+ "const",
47
+ "enumMember",
48
+ "computedLiteral"
49
+ ]),
50
+ symbol: string().optional(),
51
+ valueRange: editorSyncSourceRangeSchema.optional(),
52
+ machineLocal: boolean().optional()
53
+ }).optional(),
54
+ keyReplacementText: string().optional()
55
+ });
56
+ const machineSourceLocationsSchema = object({
57
+ version: literal(1),
58
+ profile: _enum(["xstate-v5", "xstate-v6-alpha"]).optional(),
59
+ root: editorSyncSourceLocationSchema.optional(),
60
+ statesObject: editorSyncStateSourceLocationSchema.optional(),
61
+ states: array(editorSyncStateSourceLocationSchema),
62
+ staticValues: array(editorSyncSourceLocationSchema.extend({ value: string() })).optional(),
63
+ transitionFunctions: array(editorSyncSourceLocationSchema.extend({
64
+ symbol: string(),
65
+ sourcePaths: array(array(string()))
66
+ })).optional()
67
+ }).passthrough();
68
+ const editorSyncSourceDocumentSchema = object({
69
+ uri: string(),
70
+ fileName: string(),
71
+ text: string()
72
+ });
73
+ const editorSyncDocumentContextSchema = object({
74
+ rootUri: string(),
75
+ rootFileName: string(),
76
+ rootDir: string(),
77
+ documents: array(editorSyncSourceDocumentSchema)
78
+ });
79
+ const editorSyncParseResponseSchema = object({
80
+ machine: unknown(),
81
+ format: string(),
82
+ sourceLocations: machineSourceLocationsSchema.optional(),
83
+ access: editorSyncAccessSchema.optional()
84
+ }).passthrough();
85
+ const editorSyncApplyRequestSchema = editorSyncDocumentContextSchema.extend({
86
+ machineConfig: unknown().optional(),
87
+ patches: array(unknown()).optional(),
88
+ prevGraph: unknown().optional(),
89
+ nextGraph: unknown().optional(),
90
+ sourceLocations: machineSourceLocationsSchema.optional(),
91
+ serialized: record(string(), string()).optional(),
92
+ preserveStatelyMetadata: boolean().optional()
93
+ }).passthrough();
94
+ const editorSyncTextReplacementSchema = object({
95
+ uri: string().optional(),
96
+ range: editorSyncSourceRangeSchema,
97
+ newText: string()
98
+ });
99
+ const editorSyncApplyResponseSchema = object({ replacements: array(editorSyncTextReplacementSchema) });
100
+ const editorSyncAccessResponseSchema = object({ access: editorSyncAccessSchema });
101
+
102
+ //#endregion
103
+ //#region src/editorHostSession.ts
104
+ function createRemoteEditorSyncBackend(options) {
105
+ const fetchImpl = options.fetch ?? globalThis.fetch;
106
+ async function post(pathname, body) {
107
+ const url = new URL(pathname, `${options.editorUrl.replace(/\/+$/, "")}/`);
108
+ const headers = {
109
+ "Content-Type": "application/json",
110
+ [EDITOR_SYNC_PROTOCOL_HEADER]: String(EDITOR_SYNC_PROTOCOL_VERSION)
111
+ };
112
+ if (options.apiKey) headers.Authorization = `Bearer ${options.apiKey}`;
113
+ const response = await fetchImpl(url, {
114
+ method: "POST",
115
+ headers,
116
+ body: JSON.stringify(body)
117
+ });
118
+ assertEditorSyncResponseProtocol(response);
119
+ const data = await response.json().catch(() => null);
120
+ if (!response.ok) {
121
+ const message = data && typeof data === "object" && "error" in data && typeof data.error === "string" ? data.error : `HTTP ${response.status}`;
122
+ throw new Error(message);
123
+ }
124
+ return data;
125
+ }
126
+ return {
127
+ async parse(request) {
128
+ return editorSyncParseResponseSchema.parse(await post("/api/editor-sync/parse", request));
129
+ },
130
+ async apply(request) {
131
+ return editorSyncApplyResponseSchema.parse(await post("/api/editor-sync/apply", request));
132
+ }
133
+ };
134
+ }
135
+ async function fetchEditorSyncAccess(options) {
136
+ const fetchImpl = options.fetch ?? globalThis.fetch;
137
+ const url = new URL("/api/editor-sync/access", `${options.editorUrl.replace(/\/+$/, "")}/`);
138
+ const headers = { [EDITOR_SYNC_PROTOCOL_HEADER]: String(EDITOR_SYNC_PROTOCOL_VERSION) };
139
+ if (options.apiKey) headers.Authorization = `Bearer ${options.apiKey}`;
140
+ const response = await fetchImpl(url, {
141
+ method: "GET",
142
+ headers
143
+ });
144
+ assertEditorSyncResponseProtocol(response);
145
+ const data = await response.json().catch(() => null);
146
+ if (!response.ok) {
147
+ const message = data && typeof data === "object" && "error" in data && typeof data.error === "string" ? data.error : `HTTP ${response.status}`;
148
+ throw new Error(message);
149
+ }
150
+ return editorSyncAccessResponseSchema.parse(data).access;
151
+ }
152
+ function assertEditorSyncResponseProtocol(response) {
153
+ const selectedVersion = response.headers.get(EDITOR_SYNC_PROTOCOL_HEADER);
154
+ if (selectedVersion === null || selectedVersion === String(EDITOR_SYNC_PROTOCOL_VERSION)) return;
155
+ throw new Error(`Editor Sync protocol ${selectedVersion} is unsupported; this client supports ${EDITOR_SYNC_PROTOCOL_VERSION}.`);
156
+ }
157
+ var EditorHostSession = class {
158
+ editorProtocolState = "awaiting";
159
+ currentGraph;
160
+ lastPersistedGraph;
161
+ currentFormat;
162
+ currentSourceLocations;
163
+ access;
164
+ watchedSourceUris = /* @__PURE__ */ new Set();
165
+ selfEditCount = 0;
166
+ watcher;
167
+ retrieveRequestCount = 0;
168
+ pendingRetrievals = /* @__PURE__ */ new Map();
169
+ constructor(options) {
170
+ this.options = options;
171
+ this.watcher = options.source.watchSavedDocuments?.((uri) => this.handleSavedDocument(uri));
172
+ }
173
+ dispose() {
174
+ this.watcher?.dispose();
175
+ for (const pending of this.pendingRetrievals.values()) pending.reject(/* @__PURE__ */ new Error("Editor session disposed before export completed."));
176
+ this.pendingRetrievals.clear();
177
+ }
178
+ notifySavedDocument(uri = this.options.source.rootUri) {
179
+ return this.handleSavedDocument(uri);
180
+ }
181
+ async receive(message) {
182
+ if (message.type !== "@statelyai.ready" && this.editorProtocolState !== "compatible") return;
183
+ switch (message.type) {
184
+ case "@statelyai.ready":
185
+ if (this.editorProtocolState === "incompatible") break;
186
+ if (message.protocolVersion !== void 0 && message.protocolVersion !== EDITOR_PROTOCOL_VERSION) {
187
+ this.editorProtocolState = "incompatible";
188
+ this.options.send({
189
+ type: "@statelyai.error",
190
+ code: "unsupported_protocol",
191
+ message: `Editor protocol ${message.protocolVersion} is unsupported; this client supports ${EDITOR_PROTOCOL_VERSION}.`
192
+ });
193
+ break;
194
+ }
195
+ this.editorProtocolState = "compatible";
196
+ try {
197
+ if (message.protocolVersion !== void 0) this.options.send({
198
+ type: "@statelyai.handshake",
199
+ protocolVersion: EDITOR_PROTOCOL_VERSION,
200
+ client: this.options.client
201
+ });
202
+ await this.initialize();
203
+ } catch (error) {
204
+ const message = formatError(error, "Failed to initialize the visual editor.");
205
+ this.options.send({
206
+ type: "@statelyai.error",
207
+ code: "initialization_failed",
208
+ message
209
+ });
210
+ this.options.showError?.(message);
211
+ }
212
+ break;
213
+ case "@statelyai.loaded":
214
+ this.currentGraph = message.graph;
215
+ this.lastPersistedGraph = message.graph;
216
+ this.currentSourceLocations = mergeSourceLocations(this.currentSourceLocations, message.sourceLocations);
217
+ break;
218
+ case "@statelyai.change":
219
+ this.currentGraph = message.graph;
220
+ this.currentSourceLocations = mergeSourceLocations(this.currentSourceLocations, message.sourceLocations);
221
+ break;
222
+ case "@statelyai.save":
223
+ try {
224
+ await this.save(message);
225
+ } catch (error) {
226
+ const text = formatError(error, "Failed to apply Viz changes.");
227
+ this.options.send({
228
+ type: "@statelyai.toast",
229
+ message: text,
230
+ toastType: "error"
231
+ });
232
+ this.options.showError?.(text);
233
+ }
234
+ break;
235
+ case "@statelyai.retrieved":
236
+ this.resolveRetrieved(message.requestId, message.data);
237
+ break;
238
+ case "@statelyai.error":
239
+ if (message.requestId) {
240
+ const pending = this.pendingRetrievals.get(message.requestId);
241
+ if (pending) {
242
+ this.pendingRetrievals.delete(message.requestId);
243
+ pending.reject(new Error(message.message));
244
+ break;
245
+ }
246
+ }
247
+ this.options.showError?.(message.message);
248
+ break;
249
+ case "@statelyai.clipboardRead": {
250
+ let text = "";
251
+ try {
252
+ text = await this.options.readClipboard?.() ?? "";
253
+ } catch {}
254
+ this.options.send({
255
+ type: "@statelyai.clipboardReadResponse",
256
+ requestId: message.requestId,
257
+ text
258
+ });
259
+ break;
260
+ }
261
+ case "@statelyai.clipboardWrite": {
262
+ let ok = false;
263
+ try {
264
+ await this.options.writeClipboard?.(message.text);
265
+ ok = Boolean(this.options.writeClipboard);
266
+ } catch {}
267
+ this.options.send({
268
+ type: "@statelyai.clipboardWriteResponse",
269
+ requestId: message.requestId,
270
+ ok
271
+ });
272
+ break;
273
+ }
274
+ }
275
+ }
276
+ async initialize() {
277
+ const documents = await this.options.source.collectDocuments();
278
+ const update = await this.options.sync.parse({
279
+ rootUri: this.options.source.rootUri,
280
+ rootFileName: this.options.source.rootFileName,
281
+ rootDir: this.options.source.rootDir,
282
+ documents
283
+ });
284
+ this.currentFormat = update.format;
285
+ this.currentSourceLocations = update.sourceLocations;
286
+ this.access = update.access ?? this.options.access;
287
+ this.updateWatchedSourceUris(update.sourceLocations);
288
+ if (this.options.apiKey) this.options.send({
289
+ type: "@statelyai.setApiKey",
290
+ apiKey: this.options.apiKey
291
+ });
292
+ this.options.send({
293
+ type: "@statelyai.init",
294
+ machine: update.machine,
295
+ format: update.format,
296
+ sourceLocations: update.sourceLocations,
297
+ mode: this.access?.readOnly ? "viewing" : "editing",
298
+ readOnly: this.access?.readOnly,
299
+ readOnlyReason: this.access?.readOnlyReason,
300
+ capabilities: this.access?.capabilities,
301
+ theme: this.options.getTheme?.() ?? "light",
302
+ unsavedIndicator: this.options.init?.unsavedIndicator ?? { enabled: true },
303
+ settings: this.options.init?.settings,
304
+ leftPanels: this.options.init?.leftPanels ?? [],
305
+ rightPanels: this.options.init?.rightPanels ?? [],
306
+ activePanels: this.options.init?.activePanels ?? []
307
+ });
308
+ }
309
+ async save(message) {
310
+ this.currentSourceLocations = mergeSourceLocations(this.currentSourceLocations, message.sourceLocations);
311
+ this.updateWatchedSourceUris(this.currentSourceLocations);
312
+ if (this.access?.readOnly) {
313
+ const text = "Your Stately plan allows read-only editor sync.";
314
+ this.options.send({
315
+ type: "@statelyai.toast",
316
+ message: text,
317
+ toastType: "error"
318
+ });
319
+ this.options.showError?.(text);
320
+ return;
321
+ }
322
+ const validationErrors = (message.validations ?? []).filter((validation) => validation.level === "error");
323
+ if (validationErrors.length > 0) {
324
+ const text = formatValidationErrorMessage(validationErrors);
325
+ this.options.send({
326
+ type: "@statelyai.toast",
327
+ message: text,
328
+ toastType: "error"
329
+ });
330
+ this.options.showError?.(text);
331
+ return;
332
+ }
333
+ const previousGraph = this.lastPersistedGraph ?? this.currentGraph;
334
+ if (!message.patches?.length && graphsEqual(previousGraph, message.graph)) {
335
+ this.currentGraph = message.graph;
336
+ this.lastPersistedGraph = message.graph;
337
+ this.options.send({
338
+ type: "@statelyai.saved",
339
+ graph: message.graph
340
+ });
341
+ return;
342
+ }
343
+ const documents = await this.options.source.collectDocuments(sourceLocationUris(this.currentSourceLocations));
344
+ const serialized = await this.getSerializedPayload();
345
+ const response = await this.options.sync.apply({
346
+ rootUri: this.options.source.rootUri,
347
+ rootFileName: this.options.source.rootFileName,
348
+ rootDir: this.options.source.rootDir,
349
+ documents,
350
+ machineConfig: message.machineConfig,
351
+ patches: message.patches,
352
+ prevGraph: previousGraph,
353
+ nextGraph: message.graph,
354
+ sourceLocations: this.currentSourceLocations,
355
+ serialized,
356
+ preserveStatelyMetadata: this.options.source.getPreserveStatelyMetadata?.(this.options.source.rootUri) ?? true
357
+ });
358
+ if (response.replacements.length === 0) {
359
+ const text = "Unable to apply Viz changes to the source file automatically.";
360
+ this.options.send({
361
+ type: "@statelyai.toast",
362
+ message: text,
363
+ toastType: "error"
364
+ });
365
+ this.options.showError?.(text);
366
+ return;
367
+ }
368
+ this.selfEditCount++;
369
+ try {
370
+ const touchedUris = await this.options.source.applyReplacements(response.replacements, this.options.source.rootUri);
371
+ await this.options.source.saveDocuments?.(touchedUris);
372
+ } finally {
373
+ this.selfEditCount--;
374
+ }
375
+ this.currentGraph = message.graph;
376
+ this.lastPersistedGraph = message.graph;
377
+ this.options.send({
378
+ type: "@statelyai.saved",
379
+ graph: message.graph
380
+ });
381
+ }
382
+ async handleSavedDocument(uri) {
383
+ if (this.editorProtocolState !== "compatible") return;
384
+ if (uri !== this.options.source.rootUri && !this.watchedSourceUris.has(uri) || this.selfEditCount > 0) return;
385
+ try {
386
+ const documents = await this.options.source.collectDocuments(sourceLocationUris(this.currentSourceLocations));
387
+ const update = await this.options.sync.parse({
388
+ rootUri: this.options.source.rootUri,
389
+ rootFileName: this.options.source.rootFileName,
390
+ rootDir: this.options.source.rootDir,
391
+ documents
392
+ });
393
+ this.currentFormat = update.format;
394
+ this.currentSourceLocations = update.sourceLocations;
395
+ this.access = update.access ?? this.access;
396
+ this.updateWatchedSourceUris(update.sourceLocations);
397
+ this.options.send({
398
+ type: "@statelyai.update",
399
+ machine: update.machine,
400
+ format: update.format,
401
+ sourceLocations: update.sourceLocations
402
+ });
403
+ } catch (error) {
404
+ this.options.showError?.(formatError(error, "Failed to read the saved document."));
405
+ }
406
+ }
407
+ updateWatchedSourceUris(sourceLocations) {
408
+ this.watchedSourceUris.clear();
409
+ for (const uri of stateSourceLocationUris(sourceLocations)) if (uri !== this.options.source.rootUri) this.watchedSourceUris.add(uri);
410
+ }
411
+ async getSerializedPayload() {
412
+ const format = this.currentFormat;
413
+ if (!format || format === "xstate") return void 0;
414
+ try {
415
+ return { [format]: await this.requestSerialized(format) };
416
+ } catch {
417
+ return;
418
+ }
419
+ }
420
+ requestSerialized(format) {
421
+ const requestId = `retrieve-${++this.retrieveRequestCount}`;
422
+ return new Promise((resolve, reject) => {
423
+ this.pendingRetrievals.set(requestId, {
424
+ resolve,
425
+ reject
426
+ });
427
+ this.options.send({
428
+ type: "@statelyai.retrieve",
429
+ requestId,
430
+ format
431
+ });
432
+ });
433
+ }
434
+ resolveRetrieved(requestId, data) {
435
+ const pending = this.pendingRetrievals.get(requestId);
436
+ if (!pending) return;
437
+ this.pendingRetrievals.delete(requestId);
438
+ pending.resolve(typeof data === "string" ? data : JSON.stringify(data, null, 2));
439
+ }
440
+ };
441
+ function graphsEqual(left, right) {
442
+ if (left === void 0 || right === void 0) return false;
443
+ try {
444
+ return JSON.stringify(left) === JSON.stringify(right);
445
+ } catch {
446
+ return false;
447
+ }
448
+ }
449
+ function mergeSourceLocations(previous, next) {
450
+ if (!next) return previous;
451
+ if (!next.profile && previous?.profile) return {
452
+ ...next,
453
+ profile: previous.profile
454
+ };
455
+ return next;
456
+ }
457
+ function formatError(error, fallback) {
458
+ return error instanceof Error ? error.message : fallback;
459
+ }
460
+ function formatValidationErrorMessage(errors) {
461
+ const first = errors[0];
462
+ if (errors.length === 1 && first) return `Cannot save Viz changes: ${first.message}`;
463
+ return `Cannot save Viz changes: ${errors.length} validation errors. First: ${first?.message ?? "Unknown validation error"}`;
464
+ }
465
+ function sourceLocationUris(sourceLocations) {
466
+ return [...new Set([
467
+ sourceLocations?.root?.uri,
468
+ ...(sourceLocations?.states ?? []).map((location) => location.uri),
469
+ ...(sourceLocations?.staticValues ?? []).map((location) => location.uri),
470
+ ...(sourceLocations?.transitionFunctions ?? []).map((location) => location.uri)
471
+ ].filter((uri) => Boolean(uri)))];
472
+ }
473
+ function stateSourceLocationUris(sourceLocations) {
474
+ return [...new Set((sourceLocations?.states ?? []).map((location) => location.uri).filter(Boolean))];
475
+ }
476
+
477
+ //#endregion
478
+ export { createRemoteEditorSyncBackend as n, fetchEditorSyncAccess as r, EditorHostSession as t };