@statelyai/sdk 0.18.0 → 0.20.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.
Files changed (33) hide show
  1. package/README.md +33 -2
  2. package/dist/auth.d.mts +1 -1
  3. package/dist/editor-sync-types-C-zWaaVk.d.mts +95 -0
  4. package/dist/editorHostProtocol-D_h4y3rX.mjs +5 -0
  5. package/dist/editorHostSession-CCTwvUOB.mjs +479 -0
  6. package/dist/editorHostSession-lL7wPLnL.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 +1 -1
  10. package/dist/embed.mjs +31 -3
  11. package/dist/{graph-DuOeLGvh.d.mts → graph-yDK-MM3G.d.mts} +6 -4
  12. package/dist/graph.d.mts +1 -1
  13. package/dist/{index-ag6K2iAS.d.mts → index-BAkQThnl.d.mts} +9 -9
  14. package/dist/index.d.mts +7 -6
  15. package/dist/index.mjs +7 -5
  16. package/dist/{inspect-BZFzyeew.d.mts → inspect-CGJR4iZK.d.mts} +13 -3
  17. package/dist/{inspect-llcqYpZ1.mjs → inspect-DWu20a8g.mjs} +58 -31
  18. package/dist/inspect.d.mts +1 -1
  19. package/dist/inspect.mjs +2 -2
  20. package/dist/patchTypes.d.mts +1 -1
  21. package/dist/protocol.d.mts +44 -50
  22. package/dist/protocol.mjs +2 -2
  23. package/dist/{studio-txdyJHD7.mjs → schemas-b9wG-zYl.mjs} +15 -396
  24. package/dist/{statelyPragma-Bm9O4XV6.mjs → statelyPragma-CIzsR_Sx.mjs} +977 -934
  25. package/dist/studio-DHGspwII.mjs +397 -0
  26. package/dist/{studio-Bk_u41gu.d.mts → studio-YSppd-am.d.mts} +2 -2
  27. package/dist/studio.d.mts +1 -1
  28. package/dist/studio.mjs +1 -1
  29. package/dist/sync.d.mts +2 -2
  30. package/dist/sync.mjs +12 -5
  31. package/dist/{transport-CyCrdKyz.mjs → version-YJizbJxR.mjs} +14 -4
  32. package/package.json +7 -3
  33. /package/dist/{auth-2Thaw42j.d.mts → auth-B0ZTKri2.d.mts} +0 -0
package/README.md CHANGED
@@ -74,6 +74,17 @@ embed.on('capabilityDenied', (event) => {
74
74
  Use `access-unverified` when reopening or signing in, rather than upgrading, is
75
75
  the appropriate recovery action.
76
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
+
77
88
  Key capabilities:
78
89
 
79
90
  - **Mount or attach** to any container element or existing iframe
@@ -186,13 +197,33 @@ const inspector = createInspector({
186
197
  });
187
198
  ```
188
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
+
189
219
  Stopped actors remain in `inspector.actors` and in reconnect checkpoints, so
190
220
  completed request-scoped runs stay available for inspection.
191
221
 
192
222
  XState v5 and v6 inspection events normalize to independent actor, event,
193
223
  snapshot, and stop observations before crossing the WebSocket protocol.
194
- `actorId` is system-local and `sessionId` is process-local. A room uses
195
- `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.
196
227
  Observations captured before the first transport registration replay in order
197
228
  after the initial actor-tree baseline. Reconnects use the latest checkpoint.
198
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,95 @@
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
+ profileRef?: `@statelyai/xstate@${string}`;
48
+ root?: EditorSyncSourceLocation;
49
+ statesObject?: EditorSyncStateSourceLocation;
50
+ states: EditorSyncStateSourceLocation[];
51
+ staticValues?: Array<EditorSyncSourceLocation & {
52
+ value: string;
53
+ }>;
54
+ transitionFunctions?: Array<EditorSyncSourceLocation & {
55
+ symbol: string;
56
+ sourcePaths: string[][];
57
+ }>;
58
+ }
59
+ interface EditorSyncSourceDocument {
60
+ uri: string;
61
+ fileName: string;
62
+ text: string;
63
+ }
64
+ interface EditorSyncDocumentContext {
65
+ rootUri: string;
66
+ rootFileName: string;
67
+ rootDir: string;
68
+ documents: EditorSyncSourceDocument[];
69
+ }
70
+ type EditorSyncParseRequest = EditorSyncDocumentContext;
71
+ interface EditorSyncParseResponse {
72
+ machine: unknown;
73
+ format: string;
74
+ sourceLocations?: MachineSourceLocations;
75
+ access?: EditorSyncAccess;
76
+ }
77
+ interface EditorSyncApplyRequest extends EditorSyncDocumentContext {
78
+ machineConfig?: unknown;
79
+ patches?: unknown[];
80
+ prevGraph?: unknown;
81
+ nextGraph?: unknown;
82
+ sourceLocations?: MachineSourceLocations;
83
+ serialized?: Record<string, string>;
84
+ preserveStatelyMetadata?: boolean;
85
+ }
86
+ interface EditorSyncTextReplacement {
87
+ uri?: string;
88
+ range: EditorSyncSourceRange;
89
+ newText: string;
90
+ }
91
+ interface EditorSyncApplyResponse {
92
+ replacements: EditorSyncTextReplacement[];
93
+ } //#endregion
94
+ //#endregion
95
+ 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,479 @@
1
+ import { t as EDITOR_PROTOCOL_VERSION } from "./editorHostProtocol-D_h4y3rX.mjs";
2
+ import { c as object, f as unknown, i as custom, l as record, n as array, o as literal, r as boolean, s as number, t as _enum, u as string } from "./schemas-b9wG-zYl.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
+ profileRef: custom((value) => typeof value === "string" && value.startsWith("@statelyai/xstate@")).optional(),
60
+ root: editorSyncSourceLocationSchema.optional(),
61
+ statesObject: editorSyncStateSourceLocationSchema.optional(),
62
+ states: array(editorSyncStateSourceLocationSchema),
63
+ staticValues: array(editorSyncSourceLocationSchema.extend({ value: string() })).optional(),
64
+ transitionFunctions: array(editorSyncSourceLocationSchema.extend({
65
+ symbol: string(),
66
+ sourcePaths: array(array(string()))
67
+ })).optional()
68
+ }).passthrough();
69
+ const editorSyncSourceDocumentSchema = object({
70
+ uri: string(),
71
+ fileName: string(),
72
+ text: string()
73
+ });
74
+ const editorSyncDocumentContextSchema = object({
75
+ rootUri: string(),
76
+ rootFileName: string(),
77
+ rootDir: string(),
78
+ documents: array(editorSyncSourceDocumentSchema)
79
+ });
80
+ const editorSyncParseResponseSchema = object({
81
+ machine: unknown(),
82
+ format: string(),
83
+ sourceLocations: machineSourceLocationsSchema.optional(),
84
+ access: editorSyncAccessSchema.optional()
85
+ }).passthrough();
86
+ const editorSyncApplyRequestSchema = editorSyncDocumentContextSchema.extend({
87
+ machineConfig: unknown().optional(),
88
+ patches: array(unknown()).optional(),
89
+ prevGraph: unknown().optional(),
90
+ nextGraph: unknown().optional(),
91
+ sourceLocations: machineSourceLocationsSchema.optional(),
92
+ serialized: record(string(), string()).optional(),
93
+ preserveStatelyMetadata: boolean().optional()
94
+ }).passthrough();
95
+ const editorSyncTextReplacementSchema = object({
96
+ uri: string().optional(),
97
+ range: editorSyncSourceRangeSchema,
98
+ newText: string()
99
+ });
100
+ const editorSyncApplyResponseSchema = object({ replacements: array(editorSyncTextReplacementSchema) });
101
+ const editorSyncAccessResponseSchema = object({ access: editorSyncAccessSchema });
102
+
103
+ //#endregion
104
+ //#region src/editorHostSession.ts
105
+ function createRemoteEditorSyncBackend(options) {
106
+ const fetchImpl = options.fetch ?? globalThis.fetch;
107
+ async function post(pathname, body) {
108
+ const url = new URL(pathname, `${options.editorUrl.replace(/\/+$/, "")}/`);
109
+ const headers = {
110
+ "Content-Type": "application/json",
111
+ [EDITOR_SYNC_PROTOCOL_HEADER]: String(EDITOR_SYNC_PROTOCOL_VERSION)
112
+ };
113
+ if (options.apiKey) headers.Authorization = `Bearer ${options.apiKey}`;
114
+ const response = await fetchImpl(url, {
115
+ method: "POST",
116
+ headers,
117
+ body: JSON.stringify(body)
118
+ });
119
+ assertEditorSyncResponseProtocol(response);
120
+ const data = await response.json().catch(() => null);
121
+ if (!response.ok) {
122
+ const message = data && typeof data === "object" && "error" in data && typeof data.error === "string" ? data.error : `HTTP ${response.status}`;
123
+ throw new Error(message);
124
+ }
125
+ return data;
126
+ }
127
+ return {
128
+ async parse(request) {
129
+ return editorSyncParseResponseSchema.parse(await post("/api/editor-sync/parse", request));
130
+ },
131
+ async apply(request) {
132
+ return editorSyncApplyResponseSchema.parse(await post("/api/editor-sync/apply", request));
133
+ }
134
+ };
135
+ }
136
+ async function fetchEditorSyncAccess(options) {
137
+ const fetchImpl = options.fetch ?? globalThis.fetch;
138
+ const url = new URL("/api/editor-sync/access", `${options.editorUrl.replace(/\/+$/, "")}/`);
139
+ const headers = { [EDITOR_SYNC_PROTOCOL_HEADER]: String(EDITOR_SYNC_PROTOCOL_VERSION) };
140
+ if (options.apiKey) headers.Authorization = `Bearer ${options.apiKey}`;
141
+ const response = await fetchImpl(url, {
142
+ method: "GET",
143
+ headers
144
+ });
145
+ assertEditorSyncResponseProtocol(response);
146
+ const data = await response.json().catch(() => null);
147
+ if (!response.ok) {
148
+ const message = data && typeof data === "object" && "error" in data && typeof data.error === "string" ? data.error : `HTTP ${response.status}`;
149
+ throw new Error(message);
150
+ }
151
+ return editorSyncAccessResponseSchema.parse(data).access;
152
+ }
153
+ function assertEditorSyncResponseProtocol(response) {
154
+ const selectedVersion = response.headers.get(EDITOR_SYNC_PROTOCOL_HEADER);
155
+ if (selectedVersion === null || selectedVersion === String(EDITOR_SYNC_PROTOCOL_VERSION)) return;
156
+ throw new Error(`Editor Sync protocol ${selectedVersion} is unsupported; this client supports ${EDITOR_SYNC_PROTOCOL_VERSION}.`);
157
+ }
158
+ var EditorHostSession = class {
159
+ editorProtocolState = "awaiting";
160
+ currentGraph;
161
+ lastPersistedGraph;
162
+ currentFormat;
163
+ currentSourceLocations;
164
+ access;
165
+ watchedSourceUris = /* @__PURE__ */ new Set();
166
+ selfEditCount = 0;
167
+ watcher;
168
+ retrieveRequestCount = 0;
169
+ pendingRetrievals = /* @__PURE__ */ new Map();
170
+ constructor(options) {
171
+ this.options = options;
172
+ this.watcher = options.source.watchSavedDocuments?.((uri) => this.handleSavedDocument(uri));
173
+ }
174
+ dispose() {
175
+ this.watcher?.dispose();
176
+ for (const pending of this.pendingRetrievals.values()) pending.reject(/* @__PURE__ */ new Error("Editor session disposed before export completed."));
177
+ this.pendingRetrievals.clear();
178
+ }
179
+ notifySavedDocument(uri = this.options.source.rootUri) {
180
+ return this.handleSavedDocument(uri);
181
+ }
182
+ async receive(message) {
183
+ if (message.type !== "@statelyai.ready" && this.editorProtocolState !== "compatible") return;
184
+ switch (message.type) {
185
+ case "@statelyai.ready":
186
+ if (this.editorProtocolState === "incompatible") break;
187
+ if (message.protocolVersion !== void 0 && message.protocolVersion !== EDITOR_PROTOCOL_VERSION) {
188
+ this.editorProtocolState = "incompatible";
189
+ this.options.send({
190
+ type: "@statelyai.error",
191
+ code: "unsupported_protocol",
192
+ message: `Editor protocol ${message.protocolVersion} is unsupported; this client supports ${EDITOR_PROTOCOL_VERSION}.`
193
+ });
194
+ break;
195
+ }
196
+ this.editorProtocolState = "compatible";
197
+ try {
198
+ if (message.protocolVersion !== void 0) this.options.send({
199
+ type: "@statelyai.handshake",
200
+ protocolVersion: EDITOR_PROTOCOL_VERSION,
201
+ client: this.options.client
202
+ });
203
+ await this.initialize();
204
+ } catch (error) {
205
+ const message = formatError(error, "Failed to initialize the visual editor.");
206
+ this.options.send({
207
+ type: "@statelyai.error",
208
+ code: "initialization_failed",
209
+ message
210
+ });
211
+ this.options.showError?.(message);
212
+ }
213
+ break;
214
+ case "@statelyai.loaded":
215
+ this.currentGraph = message.graph;
216
+ this.lastPersistedGraph = message.graph;
217
+ this.currentSourceLocations = mergeSourceLocations(this.currentSourceLocations, message.sourceLocations);
218
+ break;
219
+ case "@statelyai.change":
220
+ this.currentGraph = message.graph;
221
+ this.currentSourceLocations = mergeSourceLocations(this.currentSourceLocations, message.sourceLocations);
222
+ break;
223
+ case "@statelyai.save":
224
+ try {
225
+ await this.save(message);
226
+ } catch (error) {
227
+ const text = formatError(error, "Failed to apply Viz changes.");
228
+ this.options.send({
229
+ type: "@statelyai.toast",
230
+ message: text,
231
+ toastType: "error"
232
+ });
233
+ this.options.showError?.(text);
234
+ }
235
+ break;
236
+ case "@statelyai.retrieved":
237
+ this.resolveRetrieved(message.requestId, message.data);
238
+ break;
239
+ case "@statelyai.error":
240
+ if (message.requestId) {
241
+ const pending = this.pendingRetrievals.get(message.requestId);
242
+ if (pending) {
243
+ this.pendingRetrievals.delete(message.requestId);
244
+ pending.reject(new Error(message.message));
245
+ break;
246
+ }
247
+ }
248
+ this.options.showError?.(message.message);
249
+ break;
250
+ case "@statelyai.clipboardRead": {
251
+ let text = "";
252
+ try {
253
+ text = await this.options.readClipboard?.() ?? "";
254
+ } catch {}
255
+ this.options.send({
256
+ type: "@statelyai.clipboardReadResponse",
257
+ requestId: message.requestId,
258
+ text
259
+ });
260
+ break;
261
+ }
262
+ case "@statelyai.clipboardWrite": {
263
+ let ok = false;
264
+ try {
265
+ await this.options.writeClipboard?.(message.text);
266
+ ok = Boolean(this.options.writeClipboard);
267
+ } catch {}
268
+ this.options.send({
269
+ type: "@statelyai.clipboardWriteResponse",
270
+ requestId: message.requestId,
271
+ ok
272
+ });
273
+ break;
274
+ }
275
+ }
276
+ }
277
+ async initialize() {
278
+ const documents = await this.options.source.collectDocuments();
279
+ const update = await this.options.sync.parse({
280
+ rootUri: this.options.source.rootUri,
281
+ rootFileName: this.options.source.rootFileName,
282
+ rootDir: this.options.source.rootDir,
283
+ documents
284
+ });
285
+ this.currentFormat = update.format;
286
+ this.currentSourceLocations = update.sourceLocations;
287
+ this.access = update.access ?? this.options.access;
288
+ this.updateWatchedSourceUris(update.sourceLocations);
289
+ if (this.options.apiKey) this.options.send({
290
+ type: "@statelyai.setApiKey",
291
+ apiKey: this.options.apiKey
292
+ });
293
+ this.options.send({
294
+ type: "@statelyai.init",
295
+ machine: update.machine,
296
+ format: update.format,
297
+ sourceLocations: update.sourceLocations,
298
+ mode: this.access?.readOnly ? "viewing" : "editing",
299
+ readOnly: this.access?.readOnly,
300
+ readOnlyReason: this.access?.readOnlyReason,
301
+ capabilities: this.access?.capabilities,
302
+ theme: this.options.getTheme?.() ?? "light",
303
+ unsavedIndicator: this.options.init?.unsavedIndicator ?? { enabled: true },
304
+ settings: this.options.init?.settings,
305
+ leftPanels: this.options.init?.leftPanels ?? [],
306
+ rightPanels: this.options.init?.rightPanels ?? [],
307
+ activePanels: this.options.init?.activePanels ?? []
308
+ });
309
+ }
310
+ async save(message) {
311
+ this.currentSourceLocations = mergeSourceLocations(this.currentSourceLocations, message.sourceLocations);
312
+ this.updateWatchedSourceUris(this.currentSourceLocations);
313
+ if (this.access?.readOnly) {
314
+ const text = "Your Stately plan allows read-only editor sync.";
315
+ this.options.send({
316
+ type: "@statelyai.toast",
317
+ message: text,
318
+ toastType: "error"
319
+ });
320
+ this.options.showError?.(text);
321
+ return;
322
+ }
323
+ const validationErrors = (message.validations ?? []).filter((validation) => validation.level === "error");
324
+ if (validationErrors.length > 0) {
325
+ const text = formatValidationErrorMessage(validationErrors);
326
+ this.options.send({
327
+ type: "@statelyai.toast",
328
+ message: text,
329
+ toastType: "error"
330
+ });
331
+ this.options.showError?.(text);
332
+ return;
333
+ }
334
+ const previousGraph = this.lastPersistedGraph ?? this.currentGraph;
335
+ if (!message.patches?.length && graphsEqual(previousGraph, message.graph)) {
336
+ this.currentGraph = message.graph;
337
+ this.lastPersistedGraph = message.graph;
338
+ this.options.send({
339
+ type: "@statelyai.saved",
340
+ graph: message.graph
341
+ });
342
+ return;
343
+ }
344
+ const documents = await this.options.source.collectDocuments(sourceLocationUris(this.currentSourceLocations));
345
+ const serialized = await this.getSerializedPayload();
346
+ const response = await this.options.sync.apply({
347
+ rootUri: this.options.source.rootUri,
348
+ rootFileName: this.options.source.rootFileName,
349
+ rootDir: this.options.source.rootDir,
350
+ documents,
351
+ machineConfig: message.machineConfig,
352
+ patches: message.patches,
353
+ prevGraph: previousGraph,
354
+ nextGraph: message.graph,
355
+ sourceLocations: this.currentSourceLocations,
356
+ serialized,
357
+ preserveStatelyMetadata: this.options.source.getPreserveStatelyMetadata?.(this.options.source.rootUri) ?? true
358
+ });
359
+ if (response.replacements.length === 0) {
360
+ const text = "Unable to apply Viz changes to the source file automatically.";
361
+ this.options.send({
362
+ type: "@statelyai.toast",
363
+ message: text,
364
+ toastType: "error"
365
+ });
366
+ this.options.showError?.(text);
367
+ return;
368
+ }
369
+ this.selfEditCount++;
370
+ try {
371
+ const touchedUris = await this.options.source.applyReplacements(response.replacements, this.options.source.rootUri);
372
+ await this.options.source.saveDocuments?.(touchedUris);
373
+ } finally {
374
+ this.selfEditCount--;
375
+ }
376
+ this.currentGraph = message.graph;
377
+ this.lastPersistedGraph = message.graph;
378
+ this.options.send({
379
+ type: "@statelyai.saved",
380
+ graph: message.graph
381
+ });
382
+ }
383
+ async handleSavedDocument(uri) {
384
+ if (this.editorProtocolState !== "compatible") return;
385
+ if (uri !== this.options.source.rootUri && !this.watchedSourceUris.has(uri) || this.selfEditCount > 0) return;
386
+ try {
387
+ const documents = await this.options.source.collectDocuments(sourceLocationUris(this.currentSourceLocations));
388
+ const update = await this.options.sync.parse({
389
+ rootUri: this.options.source.rootUri,
390
+ rootFileName: this.options.source.rootFileName,
391
+ rootDir: this.options.source.rootDir,
392
+ documents
393
+ });
394
+ this.currentFormat = update.format;
395
+ this.currentSourceLocations = update.sourceLocations;
396
+ this.access = update.access ?? this.access;
397
+ this.updateWatchedSourceUris(update.sourceLocations);
398
+ this.options.send({
399
+ type: "@statelyai.update",
400
+ machine: update.machine,
401
+ format: update.format,
402
+ sourceLocations: update.sourceLocations
403
+ });
404
+ } catch (error) {
405
+ this.options.showError?.(formatError(error, "Failed to read the saved document."));
406
+ }
407
+ }
408
+ updateWatchedSourceUris(sourceLocations) {
409
+ this.watchedSourceUris.clear();
410
+ for (const uri of stateSourceLocationUris(sourceLocations)) if (uri !== this.options.source.rootUri) this.watchedSourceUris.add(uri);
411
+ }
412
+ async getSerializedPayload() {
413
+ const format = this.currentFormat;
414
+ if (!format || format === "xstate") return void 0;
415
+ try {
416
+ return { [format]: await this.requestSerialized(format) };
417
+ } catch {
418
+ return;
419
+ }
420
+ }
421
+ requestSerialized(format) {
422
+ const requestId = `retrieve-${++this.retrieveRequestCount}`;
423
+ return new Promise((resolve, reject) => {
424
+ this.pendingRetrievals.set(requestId, {
425
+ resolve,
426
+ reject
427
+ });
428
+ this.options.send({
429
+ type: "@statelyai.retrieve",
430
+ requestId,
431
+ format
432
+ });
433
+ });
434
+ }
435
+ resolveRetrieved(requestId, data) {
436
+ const pending = this.pendingRetrievals.get(requestId);
437
+ if (!pending) return;
438
+ this.pendingRetrievals.delete(requestId);
439
+ pending.resolve(typeof data === "string" ? data : JSON.stringify(data, null, 2));
440
+ }
441
+ };
442
+ function graphsEqual(left, right) {
443
+ if (left === void 0 || right === void 0) return false;
444
+ try {
445
+ return JSON.stringify(left) === JSON.stringify(right);
446
+ } catch {
447
+ return false;
448
+ }
449
+ }
450
+ function mergeSourceLocations(previous, next) {
451
+ if (!next) return previous;
452
+ if (!next.profile && previous?.profile) return {
453
+ ...next,
454
+ profile: previous.profile
455
+ };
456
+ return next;
457
+ }
458
+ function formatError(error, fallback) {
459
+ return error instanceof Error ? error.message : fallback;
460
+ }
461
+ function formatValidationErrorMessage(errors) {
462
+ const first = errors[0];
463
+ if (errors.length === 1 && first) return `Cannot save Viz changes: ${first.message}`;
464
+ return `Cannot save Viz changes: ${errors.length} validation errors. First: ${first?.message ?? "Unknown validation error"}`;
465
+ }
466
+ function sourceLocationUris(sourceLocations) {
467
+ return [...new Set([
468
+ sourceLocations?.root?.uri,
469
+ ...(sourceLocations?.states ?? []).map((location) => location.uri),
470
+ ...(sourceLocations?.staticValues ?? []).map((location) => location.uri),
471
+ ...(sourceLocations?.transitionFunctions ?? []).map((location) => location.uri)
472
+ ].filter((uri) => Boolean(uri)))];
473
+ }
474
+ function stateSourceLocationUris(sourceLocations) {
475
+ return [...new Set((sourceLocations?.states ?? []).map((location) => location.uri).filter(Boolean))];
476
+ }
477
+
478
+ //#endregion
479
+ export { createRemoteEditorSyncBackend as n, fetchEditorSyncAccess as r, EditorHostSession as t };