@statelyai/sdk 0.12.0 → 0.12.2

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/dist/sync.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createStatelyClient } from "./studio.mjs";
2
- import { S as upsertStatelyPragma, _ as string, b as findStatelyPragmaAttachments, d as boolean, f as discriminatedUnion, g as record, h as object, l as _enum, m as number, p as literal, t as graphToXStateTS, u as array, v as union, x as getStatelyPragma, y as unknown } from "./graphToXStateTS-D12GTMBp.mjs";
2
+ import { d as xstateSourceToConfig, f as findStatelyPragmaAttachments, i as detectXStateSourceProfile, m as upsertStatelyPragma, p as getStatelyPragma, s as graphToXStateTS, t as INLINE_EXPRESSION_DIRECTIVE_FIELDS, u as serializeJS } from "./dist-C-AdA8Iw.mjs";
3
3
  import { fromStudioMachine, toStudioMachine } from "./graph.mjs";
4
4
  import * as ts$1 from "typescript";
5
5
  import ts from "typescript";
@@ -11,8 +11,6 @@ import { promisify } from "node:util";
11
11
  import * as fs$1 from "fs";
12
12
  import * as path$1 from "path";
13
13
  import { fileURLToPath, pathToFileURL } from "url";
14
- import { createMachine, transition } from "xstate";
15
- import { getShortestPaths, getSimplePaths } from "xstate/graph";
16
14
 
17
15
  //#region ../editor-sync/src/fs.ts
18
16
  function createTextDocument(fileName, text, uri = path$1.isAbsolute(fileName) ? pathToFileURL(fileName).toString() : fileName) {
@@ -109,3575 +107,6 @@ function getLineStarts(text) {
109
107
  return lineStarts;
110
108
  }
111
109
 
112
- //#endregion
113
- //#region ../graph-tools/dist/index.mjs
114
- /**
115
- * Converts a graph to an adjacency list representation.
116
- *
117
- * @example
118
- * ```ts
119
- * import { createGraph, toAdjacencyList } from '@statelyai/graph';
120
- *
121
- * const graph = createGraph({
122
- * nodes: { a: {}, b: {}, c: {} },
123
- * edges: [{ source: 'a', target: 'b' }, { source: 'a', target: 'c' }],
124
- * });
125
- *
126
- * toAdjacencyList(graph);
127
- * // { a: ['b', 'c'], b: [], c: [] }
128
- * ```
129
- */
130
- function toAdjacencyList(graph) {
131
- const adj = {};
132
- for (const node of graph.nodes) adj[node.id] = [];
133
- for (const edge of graph.edges) {
134
- adj[edge.sourceId]?.push(edge.targetId);
135
- if (graph.mode !== "directed") adj[edge.targetId]?.push(edge.sourceId);
136
- }
137
- return adj;
138
- }
139
- /**
140
- * Parses an adjacency list into a graph.
141
- *
142
- * @example
143
- * ```ts
144
- * import { fromAdjacencyList } from '@statelyai/graph';
145
- *
146
- * const graph = fromAdjacencyList({
147
- * a: ['b', 'c'],
148
- * b: ['c'],
149
- * c: [],
150
- * });
151
- *
152
- * graph.nodes; // [{id: 'a', ...}, {id: 'b', ...}, {id: 'c', ...}]
153
- * graph.edges; // [{sourceId: 'a', targetId: 'b', ...}, ...]
154
- * ```
155
- */
156
- function fromAdjacencyList(adj, options) {
157
- const directed = options?.directed ?? true;
158
- const seen = /* @__PURE__ */ new Set();
159
- const nodes = Object.keys(adj).map((id) => ({
160
- type: "node",
161
- id,
162
- parentId: null,
163
- initialNodeId: null,
164
- label: "",
165
- data: void 0
166
- }));
167
- const edges = [];
168
- let edgeIdx = 0;
169
- for (const [sourceId, targets] of Object.entries(adj)) for (const targetId of targets) {
170
- const key = directed ? `${sourceId}->${targetId}` : [sourceId, targetId].sort().join("<->");
171
- if (!seen.has(key)) {
172
- seen.add(key);
173
- edges.push({
174
- type: "edge",
175
- id: `e${edgeIdx++}`,
176
- sourceId,
177
- targetId,
178
- label: "",
179
- data: void 0
180
- });
181
- }
182
- }
183
- return {
184
- id: options?.id ?? "",
185
- mode: directed ? "directed" : "undirected",
186
- initialNodeId: null,
187
- nodes,
188
- edges,
189
- data: void 0
190
- };
191
- }
192
- /**
193
- * Converts a graph to an edge list of `[source, target]` tuples.
194
- *
195
- * @example
196
- * ```ts
197
- * import { createGraph, toEdgeList } from '@statelyai/graph';
198
- *
199
- * const graph = createGraph({
200
- * nodes: { a: {}, b: {}, c: {} },
201
- * edges: [{ source: 'a', target: 'b' }, { source: 'b', target: 'c' }],
202
- * });
203
- *
204
- * toEdgeList(graph);
205
- * // [['a', 'b'], ['b', 'c']]
206
- * ```
207
- */
208
- function toEdgeList(graph) {
209
- return graph.edges.map((e) => [e.sourceId, e.targetId]);
210
- }
211
- /**
212
- * Parses an edge list of `[source, target]` tuples into a graph.
213
- *
214
- * @example
215
- * ```ts
216
- * import { fromEdgeList } from '@statelyai/graph';
217
- *
218
- * const graph = fromEdgeList([
219
- * ['a', 'b'],
220
- * ['b', 'c'],
221
- * ]);
222
- *
223
- * graph.nodes; // [{id: 'a', ...}, {id: 'b', ...}, {id: 'c', ...}]
224
- * graph.edges; // [{sourceId: 'a', targetId: 'b', ...}, ...]
225
- * ```
226
- */
227
- function fromEdgeList(edges, options) {
228
- const directed = options?.directed ?? true;
229
- const nodeIds = /* @__PURE__ */ new Set();
230
- for (const [source, target] of edges) {
231
- nodeIds.add(source);
232
- nodeIds.add(target);
233
- }
234
- const nodes = [...nodeIds].map((id) => ({
235
- type: "node",
236
- id,
237
- parentId: null,
238
- initialNodeId: null,
239
- label: "",
240
- data: void 0
241
- }));
242
- const edgeObjects = edges.map(([sourceId, targetId], i) => ({
243
- type: "edge",
244
- id: `e${i}`,
245
- sourceId,
246
- targetId,
247
- label: "",
248
- data: void 0
249
- }));
250
- return {
251
- id: options?.id ?? "",
252
- mode: directed ? "directed" : "undirected",
253
- initialNodeId: null,
254
- nodes,
255
- edges: edgeObjects,
256
- data: void 0
257
- };
258
- }
259
- /**
260
- * Create a `GraphFormatConverter` from a pair of `to`/`from` functions.
261
- *
262
- * @example
263
- * ```ts
264
- * import { createFormatConverter } from '@statelyai/graph';
265
- *
266
- * const yamlConverter = createFormatConverter(
267
- * (graph) => toYAML(graph),
268
- * (yaml) => fromYAML(yaml),
269
- * );
270
- *
271
- * const yaml = yamlConverter.to(graph);
272
- * const graph = yamlConverter.from(yaml);
273
- * ```
274
- */
275
- function createFormatConverter$1(to, from) {
276
- return {
277
- to,
278
- from
279
- };
280
- }
281
- createFormatConverter$1(toAdjacencyList, fromAdjacencyList);
282
- createFormatConverter$1(toEdgeList, fromEdgeList);
283
- const LAYOUT_KEYS = {
284
- node: [
285
- "x",
286
- "y",
287
- "width",
288
- "height",
289
- "style",
290
- "color",
291
- "shape"
292
- ],
293
- edge: [
294
- "x",
295
- "y",
296
- "width",
297
- "height",
298
- "style",
299
- "color"
300
- ]
301
- };
302
- new Set(LAYOUT_KEYS.node), new Set(LAYOUT_KEYS.edge);
303
- function nodeToConfig$1(node) {
304
- const config = { id: node.id };
305
- if (node.parentId) config.parentId = node.parentId;
306
- if (node.initialNodeId) config.initialNodeId = node.initialNodeId;
307
- if (node.label !== "") config.label = node.label;
308
- if (node.data !== void 0) config.data = node.data;
309
- if (node.x !== void 0) config.x = node.x;
310
- if (node.y !== void 0) config.y = node.y;
311
- if (node.width !== void 0) config.width = node.width;
312
- if (node.height !== void 0) config.height = node.height;
313
- if (node.shape !== void 0) config.shape = node.shape;
314
- if (node.color !== void 0) config.color = node.color;
315
- if (node.style !== void 0) config.style = node.style;
316
- return config;
317
- }
318
- function edgeToConfig$1(edge) {
319
- const config = {
320
- id: edge.id,
321
- sourceId: edge.sourceId,
322
- targetId: edge.targetId
323
- };
324
- if (edge.label !== "") config.label = edge.label;
325
- if (edge.data !== void 0) config.data = edge.data;
326
- if (edge.x !== void 0) config.x = edge.x;
327
- if (edge.y !== void 0) config.y = edge.y;
328
- if (edge.width !== void 0) config.width = edge.width;
329
- if (edge.height !== void 0) config.height = edge.height;
330
- if (edge.color !== void 0) config.color = edge.color;
331
- if (edge.style !== void 0) config.style = edge.style;
332
- return config;
333
- }
334
- /** Shallow-compare two values, returning true if they differ. */
335
- function differs(a, b) {
336
- if (a === b) return false;
337
- if (a == null || b == null) return a !== b;
338
- if (typeof a === "object" && typeof b === "object") return JSON.stringify(a) !== JSON.stringify(b);
339
- return true;
340
- }
341
- const NODE_COMPARE_KEYS = [
342
- "parentId",
343
- "initialNodeId",
344
- "label",
345
- "data",
346
- "x",
347
- "y",
348
- "width",
349
- "height",
350
- "shape",
351
- "color",
352
- "style"
353
- ];
354
- const EDGE_COMPARE_KEYS = [
355
- "sourceId",
356
- "targetId",
357
- "label",
358
- "data",
359
- "x",
360
- "y",
361
- "width",
362
- "height",
363
- "color",
364
- "style"
365
- ];
366
- /**
367
- * Compute a structured diff from graph `a` to graph `b` by matching IDs.
368
- *
369
- * @example
370
- * ```ts
371
- * import { createGraph, getDiff } from '@statelyai/graph';
372
- *
373
- * const a = createGraph({ nodes: [{ id: 'n1' }], edges: [] });
374
- * const b = createGraph({ nodes: [{ id: 'n1', label: 'hello' }, { id: 'n2' }], edges: [] });
375
- *
376
- * const diff = getDiff(a, b);
377
- * // diff.nodes.added → [{ id: 'n2' }]
378
- * // diff.nodes.updated → [{ id: 'n1', old: { label: '' }, new: { label: 'hello' } }]
379
- * ```
380
- */
381
- function getDiff$1(a, b) {
382
- const aNodeMap = new Map(a.nodes.map((n) => [n.id, n]));
383
- const bNodeMap = new Map(b.nodes.map((n) => [n.id, n]));
384
- const aEdgeMap = new Map(a.edges.map((e) => [e.id, e]));
385
- const bEdgeMap = new Map(b.edges.map((e) => [e.id, e]));
386
- const diff = {
387
- nodes: {
388
- added: [],
389
- removed: [],
390
- updated: []
391
- },
392
- edges: {
393
- added: [],
394
- removed: [],
395
- updated: []
396
- }
397
- };
398
- for (const [id, nodeB] of bNodeMap) {
399
- const nodeA = aNodeMap.get(id);
400
- if (!nodeA) diff.nodes.added.push(nodeToConfig$1(nodeB));
401
- else {
402
- const oldPartial = {};
403
- const newPartial = {};
404
- for (const key of NODE_COMPARE_KEYS) if (differs(nodeA[key], nodeB[key])) {
405
- oldPartial[key] = nodeA[key];
406
- newPartial[key] = nodeB[key];
407
- }
408
- if (Object.keys(oldPartial).length > 0) diff.nodes.updated.push({
409
- id,
410
- old: oldPartial,
411
- new: newPartial
412
- });
413
- }
414
- }
415
- for (const [id, nodeA] of aNodeMap) if (!bNodeMap.has(id)) diff.nodes.removed.push(nodeToConfig$1(nodeA));
416
- for (const [id, edgeB] of bEdgeMap) {
417
- const edgeA = aEdgeMap.get(id);
418
- if (!edgeA) diff.edges.added.push(edgeToConfig$1(edgeB));
419
- else {
420
- const oldPartial = {};
421
- const newPartial = {};
422
- for (const key of EDGE_COMPARE_KEYS) if (differs(edgeA[key], edgeB[key])) {
423
- oldPartial[key] = edgeA[key];
424
- newPartial[key] = edgeB[key];
425
- }
426
- if (Object.keys(oldPartial).length > 0) diff.edges.updated.push({
427
- id,
428
- old: oldPartial,
429
- new: newPartial
430
- });
431
- }
432
- }
433
- for (const [id, edgeA] of aEdgeMap) if (!bEdgeMap.has(id)) diff.edges.removed.push(edgeToConfig$1(edgeA));
434
- return diff;
435
- }
436
- /**
437
- * Check if a diff has zero changes.
438
- *
439
- * @example
440
- * ```ts
441
- * import { createGraph, getDiff, isEmptyDiff } from '@statelyai/graph';
442
- *
443
- * const g = createGraph({ nodes: [{ id: 'n1' }], edges: [] });
444
- * const diff = getDiff(g, g);
445
- * isEmptyDiff(diff); // true
446
- * ```
447
- */
448
- function isEmptyDiff$1(diff) {
449
- return diff.nodes.added.length === 0 && diff.nodes.removed.length === 0 && diff.nodes.updated.length === 0 && diff.edges.added.length === 0 && diff.edges.removed.length === 0 && diff.edges.updated.length === 0;
450
- }
451
- function summarizeGraphDiff(diff) {
452
- return {
453
- hasChanges: !isEmptyDiff$1(diff),
454
- nodeChanges: diff.nodes.added.length + diff.nodes.removed.length + diff.nodes.updated.length,
455
- edgeChanges: diff.edges.added.length + diff.edges.removed.length + diff.edges.updated.length
456
- };
457
- }
458
- function nodeDepth(graph, node) {
459
- let depth = 0;
460
- let parentId = node.parentId;
461
- while (parentId) {
462
- depth++;
463
- parentId = graph.nodes.find((candidate) => candidate.id === parentId)?.parentId;
464
- }
465
- return depth;
466
- }
467
- function getStableNodeId(node, parentStableId, siblingIndex) {
468
- if (node.parentId === null) return "root";
469
- const key = node.data.key || node.id;
470
- const suffix = siblingIndex === 0 ? key : `${key}_${siblingIndex + 1}`;
471
- return parentStableId ? `${parentStableId}.${suffix}` : suffix;
472
- }
473
- function getStableEdgeId(edge, siblingIndex) {
474
- const suffix = siblingIndex === 0 ? "" : `.${siblingIndex + 1}`;
475
- return [
476
- edge.sourceId,
477
- edge.data.eventType,
478
- edge.targetId,
479
- edge.data.guard?.type ?? ""
480
- ].join("->").concat(suffix);
481
- }
482
- function normalizeGraphForSemanticDiff(graph) {
483
- const normalized = structuredClone(graph);
484
- const nodeIdMap = /* @__PURE__ */ new Map();
485
- const siblingKeys = /* @__PURE__ */ new Map();
486
- const nodesByDepth = [...normalized.nodes].sort((a, b) => nodeDepth(normalized, a) - nodeDepth(normalized, b));
487
- for (const node of nodesByDepth) {
488
- const parentStableId = node.parentId ? nodeIdMap.get(node.parentId) ?? node.parentId : null;
489
- const key = `${parentStableId ?? ""}:${node.data.key || node.id}`;
490
- const siblingIndex = siblingKeys.get(key) ?? 0;
491
- siblingKeys.set(key, siblingIndex + 1);
492
- nodeIdMap.set(node.id, getStableNodeId(node, parentStableId, siblingIndex));
493
- }
494
- for (const node of normalized.nodes) {
495
- node.id = nodeIdMap.get(node.id) ?? node.id;
496
- if (node.parentId) node.parentId = nodeIdMap.get(node.parentId) ?? node.parentId;
497
- if (node.initialNodeId) node.initialNodeId = nodeIdMap.get(node.initialNodeId) ?? node.initialNodeId;
498
- if (node.data.initialId) node.data.initialId = nodeIdMap.get(node.data.initialId) ?? node.data.initialId;
499
- }
500
- const edgeSiblingKeys = /* @__PURE__ */ new Map();
501
- for (const edge of normalized.edges) {
502
- edge.sourceId = nodeIdMap.get(edge.sourceId) ?? edge.sourceId;
503
- edge.targetId = nodeIdMap.get(edge.targetId) ?? edge.targetId;
504
- const key = [
505
- edge.sourceId,
506
- edge.data.eventType,
507
- edge.targetId,
508
- edge.data.guard?.type ?? ""
509
- ].join("->");
510
- const siblingIndex = edgeSiblingKeys.get(key) ?? 0;
511
- edgeSiblingKeys.set(key, siblingIndex + 1);
512
- edge.id = getStableEdgeId(edge, siblingIndex);
513
- }
514
- if (normalized.initialNodeId) normalized.initialNodeId = nodeIdMap.get(normalized.initialNodeId) ?? normalized.initialNodeId;
515
- return normalized;
516
- }
517
- function diffGraphsSemantically(baseGraph, compareGraph) {
518
- const base = normalizeGraphForSemanticDiff(baseGraph);
519
- const compare = normalizeGraphForSemanticDiff(compareGraph);
520
- const diff = getDiff$1(base, compare);
521
- return {
522
- base,
523
- compare,
524
- diff,
525
- summary: summarizeGraphDiff(diff)
526
- };
527
- }
528
- function hasEntityId(graph, id) {
529
- return graph.nodes.some((node) => node.id === id) || graph.edges.some((edge) => edge.id === id);
530
- }
531
- function uniqueId(graph, prefix, preferred) {
532
- if (preferred && !hasEntityId(graph, preferred)) return preferred;
533
- let index = 1;
534
- let id = `${prefix}${index}`;
535
- while (hasEntityId(graph, id)) {
536
- index++;
537
- id = `${prefix}${index}`;
538
- }
539
- return id;
540
- }
541
- function resolveLocation(location, resolve) {
542
- return "nodeId" in location ? {
543
- ...location,
544
- nodeId: resolve(location.nodeId)
545
- } : {
546
- ...location,
547
- edgeId: resolve(location.edgeId)
548
- };
549
- }
550
- function getActionArray(graph, location) {
551
- if ("nodeId" in location) {
552
- const node = graph.nodes.find((candidate) => candidate.id === location.nodeId);
553
- return node ? node.data[location.group] : null;
554
- }
555
- return graph.edges.find((candidate) => candidate.id === location.edgeId)?.data.actions ?? null;
556
- }
557
- function hasActionId(graph, id) {
558
- for (const node of graph.nodes) {
559
- if (node.data.entry.some((action) => action.id === id)) return true;
560
- if (node.data.exit.some((action) => action.id === id)) return true;
561
- }
562
- for (const edge of graph.edges) if (edge.data.actions.some((action) => action.id === id)) return true;
563
- return false;
564
- }
565
- function hasInvokeId(graph, id) {
566
- return graph.nodes.some((node) => node.data.invokes.some((invoke) => invoke.id === id));
567
- }
568
- function createNodeInGraph(graph, opts) {
569
- if (hasEntityId(graph, opts.id)) return;
570
- const parentNode = graph.nodes.find((node) => node.id === opts.parentId);
571
- if (!parentNode) return;
572
- graph.nodes.push({
573
- type: "node",
574
- id: opts.id,
575
- parentId: opts.parentId,
576
- position: {
577
- x: opts.x ?? 0,
578
- y: opts.y ?? 0
579
- },
580
- x: opts.x ?? 0,
581
- y: opts.y ?? 0,
582
- width: 0,
583
- height: 0,
584
- dx: 0,
585
- dy: 0,
586
- label: opts.key,
587
- data: {
588
- key: opts.key,
589
- type: opts.type ?? null,
590
- history: false,
591
- entry: [],
592
- exit: [],
593
- meta: null,
594
- tags: [],
595
- description: void 0,
596
- initialId: null,
597
- invokes: []
598
- }
599
- });
600
- if (!parentNode.data.initialId) {
601
- parentNode.data.initialId = opts.id;
602
- parentNode.initialNodeId = opts.id;
603
- }
604
- }
605
- function updateNodeInGraph(graph, nodeId, data) {
606
- const node = graph.nodes.find((candidate) => candidate.id === nodeId);
607
- if (!node) return;
608
- if (data.key !== void 0) {
609
- data.key = data.key.trim();
610
- node.label = data.key;
611
- }
612
- if (data.initialId !== void 0) node.initialNodeId = data.initialId;
613
- Object.assign(node.data, data);
614
- }
615
- function createEdgeInGraph(graph, opts) {
616
- if (hasEntityId(graph, opts.id)) return;
617
- const sourceNode = graph.nodes.find((node) => node.id === opts.sourceId);
618
- if (!sourceNode) return;
619
- graph.edges.push({
620
- type: "edge",
621
- id: opts.id,
622
- sourceId: opts.sourceId,
623
- targetId: opts.targetId,
624
- label: opts.eventType,
625
- position: {
626
- x: sourceNode.position?.x ?? sourceNode.x ?? 0,
627
- y: (sourceNode.position?.y ?? sourceNode.y ?? 0) + (sourceNode.height ?? 0) + 60
628
- },
629
- x: sourceNode.position?.x ?? sourceNode.x ?? 0,
630
- y: (sourceNode.position?.y ?? sourceNode.y ?? 0) + (sourceNode.height ?? 0) + 60,
631
- width: 0,
632
- height: 0,
633
- dx: 0,
634
- dy: 0,
635
- data: {
636
- eventType: opts.eventType,
637
- transitionType: opts.transitionType ?? "normal",
638
- guard: null,
639
- description: null,
640
- actions: [],
641
- meta: null
642
- }
643
- });
644
- }
645
- function updateEdgeInGraph(graph, edgeId, data) {
646
- const edge = graph.edges.find((candidate) => candidate.id === edgeId);
647
- if (!edge) return;
648
- if (data.sourceId !== void 0) edge.sourceId = data.sourceId;
649
- if (data.targetId !== void 0) edge.targetId = data.targetId;
650
- if (data.eventType !== void 0) {
651
- data.eventType = data.eventType.trim();
652
- edge.label = data.eventType;
653
- }
654
- const { sourceId: _sourceId, targetId: _targetId, ...edgeData } = data;
655
- Object.assign(edge.data, edgeData);
656
- }
657
- function deleteEdgeFromGraph(graph, edgeId) {
658
- const index = graph.edges.findIndex((edge) => edge.id === edgeId);
659
- if (index >= 0) graph.edges.splice(index, 1);
660
- }
661
- function deleteEntitiesFromGraph(graph, entityIds) {
662
- const deletedNodeIds = /* @__PURE__ */ new Set();
663
- const nodesToDelete = /* @__PURE__ */ new Set();
664
- for (const id of entityIds) {
665
- const node = graph.nodes.find((candidate) => candidate.id === id);
666
- if (node && node.parentId !== null) {
667
- nodesToDelete.add(id);
668
- const collectDescendants = (parentId) => {
669
- for (const child of graph.nodes) if (child.parentId === parentId && !nodesToDelete.has(child.id)) {
670
- nodesToDelete.add(child.id);
671
- collectDescendants(child.id);
672
- }
673
- };
674
- collectDescendants(id);
675
- }
676
- }
677
- for (const id of nodesToDelete) {
678
- const index = graph.nodes.findIndex((node) => node.id === id);
679
- if (index === -1) continue;
680
- const nodeToDelete = graph.nodes[index];
681
- const parentNode = graph.nodes.find((node) => node.id === nodeToDelete.parentId);
682
- graph.nodes.splice(index, 1);
683
- deletedNodeIds.add(id);
684
- if (parentNode && parentNode.data.initialId === id) {
685
- const childNodes = graph.nodes.filter((node) => node.parentId === parentNode.id);
686
- parentNode.data.initialId = childNodes[0]?.id ?? null;
687
- parentNode.initialNodeId = parentNode.data.initialId;
688
- }
689
- }
690
- graph.edges = graph.edges.filter((edge) => !entityIds.has(edge.id) && !deletedNodeIds.has(edge.sourceId) && !deletedNodeIds.has(edge.targetId));
691
- graph.annotations = graph.annotations.filter((annotation) => !entityIds.has(annotation.id));
692
- }
693
- function ensureImplementations(graph) {
694
- graph.data.implementations ??= {
695
- actions: [],
696
- guards: [],
697
- actors: [],
698
- delays: [],
699
- tags: []
700
- };
701
- return graph.data.implementations;
702
- }
703
- function implementationList(graph, sourceType) {
704
- const implementations = ensureImplementations(graph);
705
- if (sourceType === "action") return implementations.actions;
706
- if (sourceType === "guard") return implementations.guards;
707
- if (sourceType === "actor") return implementations.actors;
708
- return implementations.delays;
709
- }
710
- function applyGraphPatches(graph, patches) {
711
- const nextGraph = structuredClone(graph);
712
- const idMap = /* @__PURE__ */ new Map();
713
- const resolve = (id) => idMap.get(id) ?? id;
714
- for (const patch of patches) switch (patch.op) {
715
- case "createState": {
716
- const realId = uniqueId(nextGraph, "node-", patch.id);
717
- if (patch.id) idMap.set(patch.id, realId);
718
- createNodeInGraph(nextGraph, {
719
- id: realId,
720
- parentId: resolve(patch.parentId),
721
- key: patch.key,
722
- x: patch.x,
723
- y: patch.y,
724
- type: patch.type
725
- });
726
- const node = nextGraph.nodes.find((candidate) => candidate.id === realId);
727
- if (node) {
728
- if (patch.color !== void 0) node.data.color = patch.color ?? void 0;
729
- if (patch.initial) {
730
- const parentNode = nextGraph.nodes.find((candidate) => candidate.id === node.parentId);
731
- if (parentNode) {
732
- parentNode.data.initialId = realId;
733
- parentNode.initialNodeId = realId;
734
- }
735
- }
736
- }
737
- break;
738
- }
739
- case "updateState": {
740
- const nodeData = {};
741
- if (patch.key !== void 0) nodeData.key = patch.key;
742
- if (patch.type !== void 0) nodeData.type = patch.type;
743
- if (patch.stateDescription !== void 0) nodeData.description = patch.stateDescription;
744
- if (patch.initialId !== void 0) nodeData.initialId = patch.initialId ? resolve(patch.initialId) : patch.initialId;
745
- if (patch.color !== void 0) nodeData.color = patch.color ?? void 0;
746
- if (patch.meta !== void 0) nodeData.meta = patch.meta;
747
- if (patch.history !== void 0) nodeData.history = patch.history === null ? false : patch.history;
748
- updateNodeInGraph(nextGraph, resolve(patch.stateId), nodeData);
749
- break;
750
- }
751
- case "deleteState":
752
- deleteEntitiesFromGraph(nextGraph, new Set([resolve(patch.stateId)]));
753
- break;
754
- case "createTransition": {
755
- const realId = uniqueId(nextGraph, "edge-", patch.id);
756
- if (patch.id) idMap.set(patch.id, realId);
757
- createEdgeInGraph(nextGraph, {
758
- id: realId,
759
- sourceId: resolve(patch.sourceId),
760
- targetId: resolve(patch.targetId),
761
- eventType: patch.eventType,
762
- transitionType: patch.transitionType
763
- });
764
- const edge = nextGraph.edges.find((candidate) => candidate.id === realId);
765
- if (edge) {
766
- if (patch.guard) edge.data.guard = patch.guard;
767
- if (patch.color !== void 0) edge.data.color = patch.color ?? void 0;
768
- if (patch.transitionDescription !== void 0) edge.data.description = patch.transitionDescription;
769
- }
770
- break;
771
- }
772
- case "updateTransition": {
773
- const edgeData = {};
774
- if (patch.sourceId !== void 0) edgeData.sourceId = resolve(patch.sourceId);
775
- if (patch.targetId !== void 0) edgeData.targetId = resolve(patch.targetId);
776
- if (patch.eventType !== void 0) edgeData.eventType = patch.eventType;
777
- if (patch.transitionType !== void 0) edgeData.transitionType = patch.transitionType;
778
- if (patch.transitionDescription !== void 0) edgeData.description = patch.transitionDescription;
779
- if (patch.color !== void 0) edgeData.color = patch.color ?? void 0;
780
- if (patch.meta !== void 0) edgeData.meta = patch.meta;
781
- updateEdgeInGraph(nextGraph, resolve(patch.transitionId), edgeData);
782
- break;
783
- }
784
- case "deleteTransition":
785
- deleteEdgeFromGraph(nextGraph, resolve(patch.transitionId));
786
- break;
787
- case "createAction": {
788
- const realId = patch.action.id ? uniqueId(nextGraph, "action-", patch.action.id) : `action-${Math.max(1, patches.indexOf(patch) + 1)}`;
789
- if (patch.action.id) idMap.set(patch.action.id, realId);
790
- if (!patch.action.type.trim()) break;
791
- if (hasActionId(nextGraph, realId)) break;
792
- const actions = getActionArray(nextGraph, resolveLocation(patch.location, resolve));
793
- if (!actions) break;
794
- const action = {
795
- ...patch.action,
796
- id: realId,
797
- params: patch.action.params ?? {}
798
- };
799
- if (patch.index !== void 0) actions.splice(patch.index, 0, action);
800
- else actions.push(action);
801
- break;
802
- }
803
- case "updateAction": {
804
- const action = getActionArray(nextGraph, resolveLocation(patch.location, resolve))?.find((candidate) => candidate.id === resolve(patch.actionId));
805
- if (action) Object.assign(action, patch.data);
806
- break;
807
- }
808
- case "deleteAction": {
809
- const actions = getActionArray(nextGraph, resolveLocation(patch.location, resolve));
810
- if (!actions) break;
811
- const index = actions.findIndex((candidate) => candidate.id === resolve(patch.actionId));
812
- if (index >= 0) actions.splice(index, 1);
813
- break;
814
- }
815
- case "createInvoke": {
816
- const realId = patch.invoke.id ? uniqueId(nextGraph, "invoke-", patch.invoke.id) : `invoke-${Math.max(1, patches.indexOf(patch) + 1)}`;
817
- if (patch.invoke.id) idMap.set(patch.invoke.id, realId);
818
- if (!patch.invoke.src.trim()) break;
819
- if (hasInvokeId(nextGraph, realId)) break;
820
- nextGraph.nodes.find((candidate) => candidate.id === resolve(patch.nodeId))?.data.invokes.push({
821
- ...patch.invoke,
822
- id: realId
823
- });
824
- break;
825
- }
826
- case "updateInvoke": {
827
- const invoke = nextGraph.nodes.find((candidate) => candidate.id === resolve(patch.nodeId))?.data.invokes.find((candidate) => candidate.id === resolve(patch.invokeId));
828
- if (invoke) Object.assign(invoke, patch.data);
829
- break;
830
- }
831
- case "deleteInvoke": {
832
- const node = nextGraph.nodes.find((candidate) => candidate.id === resolve(patch.nodeId));
833
- if (!node) break;
834
- const index = node.data.invokes.findIndex((candidate) => candidate.id === resolve(patch.invokeId));
835
- if (index >= 0) node.data.invokes.splice(index, 1);
836
- break;
837
- }
838
- case "createImplementation": {
839
- const code = patch.implementation.code;
840
- const base = {
841
- id: patch.implementation.id,
842
- name: patch.implementation.name ?? patch.implementation.id,
843
- description: null,
844
- code: code ? {
845
- body: code,
846
- lang: "ts",
847
- deps: {}
848
- } : null
849
- };
850
- implementationList(nextGraph, patch.sourceType).push(patch.sourceType === "actor" ? {
851
- ...base,
852
- inputSchema: null,
853
- outputSchema: null
854
- } : {
855
- ...base,
856
- paramsSchema: null
857
- });
858
- break;
859
- }
860
- case "updateImplementation": {
861
- const item = implementationList(nextGraph, patch.sourceType).find((implementation) => implementation.id === resolve(patch.implementationId));
862
- if (!item) break;
863
- if (patch.data.name !== void 0) {
864
- item.name = patch.data.name;
865
- item.id = patch.data.name;
866
- }
867
- if (patch.data.code !== void 0) item.code = {
868
- body: patch.data.code,
869
- lang: "ts",
870
- deps: {}
871
- };
872
- break;
873
- }
874
- case "deleteImplementation": {
875
- const list = implementationList(nextGraph, patch.sourceType);
876
- const index = list.findIndex((implementation) => implementation.id === resolve(patch.implementationId));
877
- if (index >= 0) list.splice(index, 1);
878
- break;
879
- }
880
- case "setGuard": {
881
- if (!patch.guard.type.trim()) break;
882
- const edge = nextGraph.edges.find((candidate) => candidate.id === resolve(patch.edgeId));
883
- if (edge) edge.data.guard = patch.guard;
884
- break;
885
- }
886
- case "deleteGuard": {
887
- const edge = nextGraph.edges.find((candidate) => candidate.id === resolve(patch.edgeId));
888
- if (edge) edge.data.guard = null;
889
- break;
890
- }
891
- case "createTag": {
892
- const node = nextGraph.nodes.find((candidate) => candidate.id === resolve(patch.nodeId));
893
- if (node && !node.data.tags.some((tag) => tag.name === patch.tag.name)) node.data.tags.push(patch.tag);
894
- break;
895
- }
896
- case "deleteTag": {
897
- const node = nextGraph.nodes.find((candidate) => candidate.id === resolve(patch.nodeId));
898
- if (!node) break;
899
- const index = node.data.tags.findIndex((tag) => tag.name === patch.tagName);
900
- if (index >= 0) node.data.tags.splice(index, 1);
901
- break;
902
- }
903
- case "createAnnotation": {
904
- const realId = uniqueId(nextGraph, "annotation-", patch.id);
905
- if (patch.id) idMap.set(patch.id, realId);
906
- nextGraph.annotations.push({
907
- type: "node",
908
- id: realId,
909
- parentId: patch.parentId,
910
- position: {
911
- x: patch.x ?? 0,
912
- y: patch.y ?? 0
913
- },
914
- x: patch.x ?? 0,
915
- y: patch.y ?? 0,
916
- dx: 0,
917
- dy: 0,
918
- width: 0,
919
- height: 0,
920
- content: patch.content,
921
- data: { referenceIds: [] }
922
- });
923
- break;
924
- }
925
- case "updateAnnotation": {
926
- const annotation = nextGraph.annotations.find((candidate) => candidate.id === resolve(patch.annotationId));
927
- if (annotation && patch.data.content !== void 0) annotation.content = patch.data.content;
928
- break;
929
- }
930
- case "deleteAnnotation":
931
- deleteEntitiesFromGraph(nextGraph, new Set([resolve(patch.annotationId)]));
932
- break;
933
- case "setContext":
934
- nextGraph.data.context = patch.context;
935
- break;
936
- case "updateSchemas":
937
- nextGraph.data.schemas ??= {
938
- context: null,
939
- events: null,
940
- input: null,
941
- output: null
942
- };
943
- if (patch.schemas.context !== void 0) nextGraph.data.schemas.context = patch.schemas.context;
944
- if (patch.schemas.events !== void 0) nextGraph.data.schemas.events = patch.schemas.events;
945
- if (patch.schemas.input !== void 0) nextGraph.data.schemas.input = patch.schemas.input;
946
- if (patch.schemas.output !== void 0) nextGraph.data.schemas.output = patch.schemas.output;
947
- break;
948
- }
949
- return nextGraph;
950
- }
951
- function isAutoGeneratedId(id) {
952
- return !!id && id.startsWith("$auto-");
953
- }
954
- function getAuthoredNodeId(node) {
955
- const authored = node.data.nodeId ?? void 0;
956
- return authored && !isAutoGeneratedId(authored) ? authored : void 0;
957
- }
958
- function getRootEmittedId(graph, node) {
959
- if (graph.id === "root" && node.data.key && node.data.key !== "root") return getAuthoredNodeId(node) ?? node.data.key;
960
- return getAuthoredNodeId(node) ?? (isAutoGeneratedId(graph.id) ? void 0 : graph.id);
961
- }
962
- function getDefaultPathId(graph, node) {
963
- if (!node.parentId) return getRootEmittedId(graph, node) ?? "(machine)";
964
- const parentNode = graph.nodes.find((candidate) => candidate.id === node.parentId);
965
- if (!parentNode) return node.data.key;
966
- return `${getResolvedNodeId(graph, parentNode)}.${node.data.key}`;
967
- }
968
- function getEmittedNodeId(graph, node) {
969
- const authored = getAuthoredNodeId(node);
970
- if (!node.parentId) return getRootEmittedId(graph, node);
971
- if (!authored) return void 0;
972
- return authored === getDefaultPathId(graph, node) ? void 0 : authored;
973
- }
974
- function getResolvedNodeId(graph, node) {
975
- return getEmittedNodeId(graph, node) ?? getDefaultPathId(graph, node);
976
- }
977
- /**
978
- * Serializes JavaScript values as JS source code (not JSON).
979
- * - Unquoted keys for valid identifiers
980
- * - Single-quoted strings
981
- * - Omits undefined values
982
- * - Supports RawCode for verbatim expressions
983
- * - Supports inline expression directives for verbatim expressions
984
- */
985
- const VALID_IDENT = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
986
- var RawCode = class {
987
- constructor(code) {
988
- this.code = code;
989
- }
990
- };
991
- function serializeJS(value, indent = 0, step = 2) {
992
- if (value instanceof RawCode) return value.code;
993
- if (isInlineExpressionDirective(value)) return value.expr;
994
- if (value === void 0) return "undefined";
995
- if (value === null) return "null";
996
- if (typeof value === "string") return `'${escapeString(value)}'`;
997
- if (typeof value === "number" || typeof value === "boolean") return String(value);
998
- if (Array.isArray(value)) return serializeArray(value, indent, step);
999
- if (typeof value === "object") return serializeObject(value, indent, step);
1000
- return String(value);
1001
- }
1002
- function escapeString(s) {
1003
- return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
1004
- }
1005
- function serializeArray(arr, indent, step) {
1006
- const filtered = arr.filter((v) => v !== void 0);
1007
- if (filtered.length === 0) return "[]";
1008
- const inner = indent + step;
1009
- const pad = " ".repeat(inner);
1010
- const closePad = " ".repeat(indent);
1011
- return `[\n${filtered.map((v) => `${pad}${serializeJS(v, inner, step)}`).join(",\n")},\n${closePad}]`;
1012
- }
1013
- function serializeObject(obj, indent, step) {
1014
- const entries = Object.entries(obj).filter(([, v]) => v !== void 0);
1015
- if (entries.length === 0) return "{}";
1016
- const inner = indent + step;
1017
- const pad = " ".repeat(inner);
1018
- const closePad = " ".repeat(indent);
1019
- return `{\n${entries.map(([key, val]) => {
1020
- const k = VALID_IDENT.test(key) ? key : `'${escapeString(key)}'`;
1021
- const serialized = serializeJS(val, inner, step);
1022
- if (isRawExpression(val) && serialized.includes("\n")) return `${pad}${k}: ${indentRawCode(serialized, inner)}`;
1023
- return `${pad}${k}: ${serialized}`;
1024
- }).join(",\n")},\n${closePad}}`;
1025
- }
1026
- function isRawExpression(value) {
1027
- return value instanceof RawCode || isInlineExpressionDirective(value);
1028
- }
1029
- function isInlineExpressionDirective(value) {
1030
- if (!value || typeof value !== "object" || Array.isArray(value)) return false;
1031
- const directive = value;
1032
- return directive["@type"] === "code" && typeof directive.expr === "string";
1033
- }
1034
- function indentRawCode(code, indent) {
1035
- const lines = code.split("\n");
1036
- if (lines.length <= 1) return code;
1037
- const pad = " ".repeat(indent);
1038
- return [lines[0], ...lines.slice(1).map((line) => line.trim() === "" ? "" : `${pad}${line}`)].join("\n");
1039
- }
1040
- /**
1041
- * Strips `export default` wrapper from an expression string.
1042
- * E.g. `"export default assign({ ... })"` → `"assign({ ... })"`
1043
- */
1044
- function stripExportDefault(code) {
1045
- const trimmed = code.trim();
1046
- if (trimmed.startsWith("export default ")) return trimmed.slice(15).replace(/;$/, "");
1047
- return trimmed;
1048
- }
1049
- /**
1050
- * Graph-native transition references. Transitions point at invoke/action/state
1051
- * blocks by graph id (`@statelyai.*`); the literal `xstate.*` event is produced
1052
- * here at codegen time. Literal `xstate.*` strings (pre-resolved, e.g. baked by
1053
- * an import) are passed through unchanged.
1054
- */
1055
- const STATELY_PREFIX = "@statelyai";
1056
- /** Legacy prefix accepted on read for graphs persisted before the rename. */
1057
- const LEGACY_STATELY_PREFIX = "@stately";
1058
- const STATELY_REF_PREFIX = {
1059
- invokeDone: "@statelyai.invoke.done",
1060
- invokeError: "@statelyai.invoke.error",
1061
- invokeSnapshot: "@statelyai.invoke.snapshot",
1062
- actionDone: "@statelyai.action.done",
1063
- actionError: "@statelyai.action.error",
1064
- stateDone: "@statelyai.state.done"
1065
- };
1066
- function parseStatelyRef(eventType) {
1067
- if (eventType.startsWith(`${LEGACY_STATELY_PREFIX}.`)) eventType = `${STATELY_PREFIX}${eventType.slice(8)}`;
1068
- if (!eventType.startsWith(`${STATELY_PREFIX}.`)) return null;
1069
- for (const kind of Object.keys(STATELY_REF_PREFIX)) {
1070
- const prefix = STATELY_REF_PREFIX[kind];
1071
- if (eventType === prefix) return {
1072
- kind,
1073
- blockId: ""
1074
- };
1075
- if (eventType.startsWith(`${prefix}.`)) return {
1076
- kind,
1077
- blockId: eventType.slice(prefix.length + 1)
1078
- };
1079
- }
1080
- return null;
1081
- }
1082
- /** Find an invoke block by its graph id, or by its emitted id (legacy literals). */
1083
- function findInvokeByActorId(node, actorId) {
1084
- const invokes = node.data.invokes ?? [];
1085
- return invokes.find((inv) => inv.id === actorId) ?? invokes.find((inv) => (inv.invocationId ?? inv.id) === actorId);
1086
- }
1087
- /** Resolve a spawn action block id to its spawned actor id (`params.id`). */
1088
- function resolveSpawnActorId$1(node, actionId) {
1089
- const spawnId = [...node.data.entry ?? [], ...node.data.exit ?? []].find((action) => action.id === actionId)?.params?.["id"];
1090
- return typeof spawnId === "string" && spawnId ? spawnId : actionId;
1091
- }
1092
- /**
1093
- * Resolve an edge's target into the string XState expects on the source state.
1094
- * Prefer a relative key path (resolved against the source's parent, or the root
1095
- * itself for root transitions) so output reads like a hand-written machine —
1096
- * e.g. a sibling's child becomes `Unlocked.Manual`. Fall back to an absolute
1097
- * `#id` reference only when the target lives outside the source's subtree.
1098
- */
1099
- function resolveTransitionTarget(graph, sourceNode, targetNode) {
1100
- const isRootSource = sourceNode.parentId == null;
1101
- const baseId = sourceNode.parentId ?? sourceNode.id;
1102
- const keys = [];
1103
- let current = targetNode;
1104
- while (current) {
1105
- keys.unshift(current.data.key);
1106
- if (current.parentId === baseId) {
1107
- if (isRootSource) return `.${keys.join(".")}`;
1108
- return keys.join(".");
1109
- }
1110
- const parentId = current.parentId;
1111
- current = graph.nodes.find((n) => n.id === parentId);
1112
- }
1113
- return `#${getAbsoluteTargetId(graph, targetNode)}`;
1114
- }
1115
- function getAbsoluteTargetId(graph, targetNode) {
1116
- const resolved = getResolvedNodeId(graph, targetNode);
1117
- if (!resolved.startsWith("root.")) return resolved;
1118
- const rootNode = graph.nodes.find((node) => node.parentId === null);
1119
- const rootKey = rootNode?.data.nodeId && !isAutoGeneratedId(rootNode.data.nodeId) ? rootNode.data.nodeId : rootNode?.data.key;
1120
- return rootKey && rootKey !== "root" ? `${rootKey}.${resolved.slice(5)}` : resolved;
1121
- }
1122
- /** Unwrap single-element arrays to a single value (XState single-or-array convention) */
1123
- function singleOrArray(arr) {
1124
- return arr.length === 1 ? arr[0] : arr;
1125
- }
1126
- /** Unwrap each value in a record of arrays */
1127
- function singleOrArrayRecord(record) {
1128
- const result = {};
1129
- for (const [key, arr] of Object.entries(record)) result[key] = singleOrArray(arr);
1130
- return result;
1131
- }
1132
- /**
1133
- * Remove empty arrays/objects/strings, unwrap single-element arrays.
1134
- * Matches studio's simplifyAttributes behavior.
1135
- */
1136
- function simplifyAttributes(obj) {
1137
- for (const key of Object.keys(obj)) {
1138
- const value = obj[key];
1139
- if (value === void 0 || value === null) delete obj[key];
1140
- else if (Array.isArray(value)) {
1141
- if (value.length === 0) delete obj[key];
1142
- else if (value.length === 1) obj[key] = value[0];
1143
- } else if (typeof value === "object" && Object.keys(value).length === 0) delete obj[key];
1144
- else if (typeof value === "string" && value === "") delete obj[key];
1145
- }
1146
- return obj;
1147
- }
1148
- /**
1149
- * Recursively apply simplifyAttributes to a config tree.
1150
- * Cleans up the config and all nested state nodes, transitions, and invokes.
1151
- */
1152
- function deepSimplify(config) {
1153
- for (const mapKey of ["on", "after"]) {
1154
- const map = config[mapKey];
1155
- if (map && typeof map === "object" && !Array.isArray(map)) {
1156
- for (const [, val] of Object.entries(map)) if (val && typeof val === "object" && !Array.isArray(val)) simplifyAttributes(val);
1157
- else if (Array.isArray(val)) {
1158
- for (const item of val) if (item && typeof item === "object") simplifyAttributes(item);
1159
- }
1160
- }
1161
- }
1162
- for (const key of ["always", "onDone"]) {
1163
- const val = config[key];
1164
- if (val && typeof val === "object" && !Array.isArray(val)) simplifyAttributes(val);
1165
- else if (Array.isArray(val)) {
1166
- for (const item of val) if (item && typeof item === "object") simplifyAttributes(item);
1167
- }
1168
- }
1169
- const invoke = config.invoke;
1170
- if (invoke && typeof invoke === "object" && !Array.isArray(invoke)) simplifyAttributes(invoke);
1171
- else if (Array.isArray(invoke)) {
1172
- for (const item of invoke) if (item && typeof item === "object") simplifyAttributes(item);
1173
- }
1174
- simplifyAttributes(config);
1175
- if (config.states && typeof config.states === "object") for (const stateConfig of Object.values(config.states)) deepSimplify(stateConfig);
1176
- return config;
1177
- }
1178
- function buildMeta(meta, color) {
1179
- if (isCodeExpression(meta)) return meta;
1180
- const result = {};
1181
- if (meta) {
1182
- for (const [k, v] of Object.entries(meta)) if (v !== void 0 && v !== null) result[k] = v;
1183
- }
1184
- if (color) result["@statelyai.color"] = color;
1185
- return Object.keys(result).length > 0 ? result : void 0;
1186
- }
1187
- function isCodeExpression(value) {
1188
- return Boolean(value && typeof value === "object" && !Array.isArray(value) && value["@type"] === "code" && typeof value.expr === "string");
1189
- }
1190
- /** Format event param — supports { type: string } (new) and plain string (legacy) */
1191
- function formatEventParam(event) {
1192
- if (event && typeof event === "object" && "type" in event) return formatInlineObject(event);
1193
- if (typeof event === "string") return `{ type: '${event}' }`;
1194
- return "undefined";
1195
- }
1196
- function formatStringParam(value) {
1197
- return typeof value === "string" ? `'${value}'` : "undefined";
1198
- }
1199
- function formatInlineObject(value) {
1200
- return `{ ${Object.entries(value).filter(([, item]) => item !== void 0).map(([key, item]) => `${formatObjectKey(key)}: ${serializeJS(item)}`).join(", ")} }`;
1201
- }
1202
- function formatObjectKey(key) {
1203
- return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
1204
- }
1205
- const eventParamSchema = union([string(), object({ type: string() }).catchall(unknown())]);
1206
- const delayParamSchema = union([number(), string()]);
1207
- const builtInActionParamSchemas = {
1208
- "xstate.enqueue": object({
1209
- code: string().optional(),
1210
- lang: string().optional()
1211
- }),
1212
- "xstate.raise": object({
1213
- event: eventParamSchema.optional(),
1214
- id: string().optional(),
1215
- delay: delayParamSchema.optional()
1216
- }),
1217
- "xstate.sendTo": object({
1218
- to: string().optional(),
1219
- event: eventParamSchema.optional(),
1220
- id: string().optional(),
1221
- delay: delayParamSchema.optional()
1222
- }),
1223
- "xstate.cancel": object({ sendId: string().optional() }),
1224
- "xstate.emit": object({ event: eventParamSchema.optional() }),
1225
- "xstate.spawnChild": object({
1226
- src: string().optional(),
1227
- id: string().optional(),
1228
- systemId: string().optional()
1229
- }),
1230
- "xstate.stopChild": object({ actorRef: string().optional() }),
1231
- "xstate.log": object({ label: string().optional() }),
1232
- "xstate.assign": object({ assignment: record(string(), unknown()).optional() }).catchall(unknown())
1233
- };
1234
- const XSTATE_BUILT_IN_ACTIONS = {
1235
- "xstate.enqueue": {
1236
- package: "xstate",
1237
- version: "^6",
1238
- import: "enqueue",
1239
- importKind: "named",
1240
- paramsSchema: builtInActionParamSchemas["xstate.enqueue"],
1241
- emit(params) {
1242
- return typeof params?.code === "string" ? stripExportDefault(params.code) : "";
1243
- }
1244
- },
1245
- "xstate.raise": {
1246
- package: "xstate",
1247
- version: "^5",
1248
- import: "raise",
1249
- importKind: "named",
1250
- paramsSchema: builtInActionParamSchemas["xstate.raise"],
1251
- emit(params) {
1252
- const eventStr = formatEventParam(params?.event);
1253
- const opts = [];
1254
- if (params?.id) opts.push(`id: '${params.id}'`);
1255
- if (params?.delay != null) opts.push(`delay: ${JSON.stringify(params.delay)}`);
1256
- return `raise(${eventStr}${opts.length > 0 ? `, { ${opts.join(", ")} }` : ""})`;
1257
- }
1258
- },
1259
- "xstate.sendTo": {
1260
- package: "xstate",
1261
- version: "^5",
1262
- import: "sendTo",
1263
- importKind: "named",
1264
- paramsSchema: builtInActionParamSchemas["xstate.sendTo"],
1265
- emit(params) {
1266
- const to = formatStringParam(params?.to);
1267
- const eventStr = formatEventParam(params?.event);
1268
- const opts = [];
1269
- if (params?.id) opts.push(`id: '${params.id}'`);
1270
- if (params?.delay != null) opts.push(`delay: ${JSON.stringify(params.delay)}`);
1271
- return `sendTo(${to}, ${eventStr}${opts.length > 0 ? `, { ${opts.join(", ")} }` : ""})`;
1272
- }
1273
- },
1274
- "xstate.cancel": {
1275
- package: "xstate",
1276
- version: "^5",
1277
- import: "cancel",
1278
- importKind: "named",
1279
- paramsSchema: builtInActionParamSchemas["xstate.cancel"],
1280
- emit(params) {
1281
- return `cancel(${formatStringParam(params?.sendId)})`;
1282
- }
1283
- },
1284
- "xstate.emit": {
1285
- package: "xstate",
1286
- version: "^5",
1287
- import: "emit",
1288
- importKind: "named",
1289
- paramsSchema: builtInActionParamSchemas["xstate.emit"],
1290
- emit(params) {
1291
- return `emit(${formatEventParam(params?.event)})`;
1292
- }
1293
- },
1294
- "xstate.spawnChild": {
1295
- package: "xstate",
1296
- version: "^5",
1297
- import: "spawnChild",
1298
- importKind: "named",
1299
- paramsSchema: builtInActionParamSchemas["xstate.spawnChild"],
1300
- emit(params) {
1301
- const src = formatStringParam(params?.src);
1302
- const opts = [];
1303
- if (params?.id) opts.push(`id: '${params.id}'`);
1304
- if (params?.systemId) opts.push(`systemId: '${params.systemId}'`);
1305
- return `spawnChild(${src}${opts.length > 0 ? `, { ${opts.join(", ")} }` : ""})`;
1306
- }
1307
- },
1308
- "xstate.stopChild": {
1309
- package: "xstate",
1310
- version: "^5",
1311
- import: "stopChild",
1312
- importKind: "named",
1313
- paramsSchema: builtInActionParamSchemas["xstate.stopChild"],
1314
- emit(params) {
1315
- return `stopChild(${formatStringParam(params?.actorRef)})`;
1316
- }
1317
- },
1318
- "xstate.log": {
1319
- package: "xstate",
1320
- version: "^5",
1321
- import: "log",
1322
- importKind: "named",
1323
- paramsSchema: builtInActionParamSchemas["xstate.log"],
1324
- emit(params) {
1325
- return `log(${(params?.label ? `'${params.label}'` : void 0) ?? ""})`;
1326
- }
1327
- },
1328
- "xstate.assign": {
1329
- package: "xstate",
1330
- version: "^5",
1331
- import: "assign",
1332
- importKind: "named",
1333
- paramsSchema: builtInActionParamSchemas["xstate.assign"],
1334
- emit(params) {
1335
- const assignments = params?.assignment && typeof params.assignment === "object" && !Array.isArray(params.assignment) ? params.assignment : params;
1336
- const entries = Object.entries(assignments ?? {}).filter(([k, v]) => k && v !== void 0);
1337
- if (entries.length === 0) return "assign({})";
1338
- return `assign({ ${entries.map(([k, v]) => `${JSON.stringify(k)}: ${formatAssignValue(v)}`).join(", ")} })`;
1339
- }
1340
- }
1341
- };
1342
- function isBuiltInActionType(type) {
1343
- return type in XSTATE_BUILT_IN_ACTIONS;
1344
- }
1345
- function emitBuiltInAction(type, params) {
1346
- return XSTATE_BUILT_IN_ACTIONS[type].emit(params);
1347
- }
1348
- function actionCodeExpression(expr, source) {
1349
- return {
1350
- "@type": "code",
1351
- lang: "ts",
1352
- expr,
1353
- imports: [{
1354
- package: source.package,
1355
- version: source.version,
1356
- import: source.import,
1357
- importKind: source.importKind
1358
- }]
1359
- };
1360
- }
1361
- function inlineCodeExpression$1(expr) {
1362
- return {
1363
- "@type": "code",
1364
- lang: "ts",
1365
- expr: stripExportDefault(expr)
1366
- };
1367
- }
1368
- /**
1369
- * Serialize an edge guard for a machine config. Codegen emits inline guards as
1370
- * a code-expression directive so the source round-trips. The simulation passes
1371
- * `asTypeRef` to get a plain `{ type }` reference instead (it stubs guards by
1372
- * type and can't evaluate the source) — see MachineConfigOptions.
1373
- */
1374
- function serializeGuard(guard, asTypeRef) {
1375
- if (asTypeRef) return guard.params ? {
1376
- type: guard.type,
1377
- params: guard.params
1378
- } : { type: guard.type };
1379
- return guard.code ? inlineCodeExpression$1(guard.code) : guard;
1380
- }
1381
- /**
1382
- * Converts a built-in xstate ActionItem to a code expression directive,
1383
- * or returns a plain { type, params } object for user-defined actions.
1384
- */
1385
- function serializeActionItem(action) {
1386
- const { type, params } = action;
1387
- if (type === "xstate.enqueue" && typeof params?.code === "string") return inlineCodeExpression$1(params.code);
1388
- const exprCode = type === "xstate.expr" && typeof params?.code === "string" ? params.code : void 0;
1389
- if (isBuiltInActionType(type)) {
1390
- const builtInAction = XSTATE_BUILT_IN_ACTIONS[type];
1391
- return actionCodeExpression(emitBuiltInAction(type, params), builtInAction);
1392
- }
1393
- if (exprCode) return inlineCodeExpression$1(exprCode);
1394
- if (!params || Object.keys(params).length === 0) return { type };
1395
- return {
1396
- type,
1397
- params
1398
- };
1399
- }
1400
- function formatAssignValue(value) {
1401
- if (typeof value !== "string") return JSON.stringify(value);
1402
- const templateExpression = value.match(/^\{\{([\s\S]*)\}\}$/);
1403
- if (templateExpression) return templateExpression[1].trim();
1404
- return JSON.stringify(value);
1405
- }
1406
- /** Normalize tag to string — handles both `string` and `{ name: string }` */
1407
- function tagToString(tag) {
1408
- return typeof tag === "string" ? tag : tag.name;
1409
- }
1410
- function graphToMachineConfig(graph, options = {}) {
1411
- const { showDescriptions = true, showMeta = true, inlineGuardsAsTypeRef = false } = options;
1412
- const config = {};
1413
- function getNodeConfig(node) {
1414
- const nodeConfig = node.parentId ? {} : config;
1415
- const resolvedNodeId = getEmittedNodeId(graph, node);
1416
- const initialNode = node.data.initialId ? graph.nodes.find((n) => n.id === node.data.initialId) : null;
1417
- const nodeType = [
1418
- "final",
1419
- "history",
1420
- "parallel",
1421
- "choice"
1422
- ].includes(node.data.type) ? node.data.type : void 0;
1423
- const tags = node.data.tags;
1424
- Object.assign(nodeConfig, {
1425
- id: resolvedNodeId,
1426
- type: nodeType === "normal" ? void 0 : nodeType ?? void 0,
1427
- initial: initialNode?.data.key,
1428
- ...node.data.entry?.length ? { entry: node.data.entry.map(serializeActionItem) } : void 0,
1429
- ...node.data.exit?.length ? { exit: node.data.exit.map(serializeActionItem) } : void 0,
1430
- ...node.data.invokes?.length ? { invoke: node.data.invokes.map((inv) => ({
1431
- src: inv.src,
1432
- id: inv.invocationId ?? inv.id,
1433
- ...inv.input ? { input: inv.input } : void 0,
1434
- ...inv.output ? { output: inv.output } : void 0
1435
- })) } : void 0,
1436
- ...node.data.output !== void 0 ? { output: node.data.output } : void 0,
1437
- ...tags?.length ? { tags: tags.map(tagToString) } : void 0,
1438
- ...showDescriptions && node.data.description ? { description: node.data.description } : void 0,
1439
- ...showMeta && node.data.meta && Object.keys(node.data.meta).length > 0 ? { meta: node.data.meta } : void 0,
1440
- ...node.data.history ? { history: node.data.history } : void 0
1441
- });
1442
- const childNodes = graph.nodes.filter((n) => n.parentId === node.id && !n.data.temp);
1443
- const edges = graph.edges.filter((edge) => edge.sourceId === node.id && !edge.data.temp);
1444
- if (edges.length > 0) {
1445
- const on = {};
1446
- const after = {};
1447
- const alwaysArr = [];
1448
- const choiceArr = [];
1449
- const onDone = {};
1450
- const invokeMap = {};
1451
- for (const edge of edges) {
1452
- const targetNode = graph.nodes.find((n) => n.id === edge.targetId);
1453
- const resolvedTarget = targetNode ? resolveTransitionTarget(graph, node, targetNode) : "";
1454
- const type = edge.data.eventType ?? "";
1455
- const transitionMeta = showMeta ? buildMeta(edge.data.meta, edge.data.color) : void 0;
1456
- const transitionObject = {
1457
- target: edge.data.transitionType === "targetless" ? void 0 : `${resolvedTarget}`,
1458
- ...edge.data.transitionType === "reenter" ? { reenter: true } : void 0,
1459
- guard: edge.data.guard ? serializeGuard(edge.data.guard, inlineGuardsAsTypeRef) : void 0,
1460
- ...edge.data.context !== void 0 ? { context: edge.data.context } : void 0,
1461
- actions: edge.data.actions?.length ? edge.data.actions.map(serializeActionItem) : void 0,
1462
- description: edge.data.description ?? void 0,
1463
- ...transitionMeta ? { meta: transitionMeta } : void 0
1464
- };
1465
- const pushOn = (key) => {
1466
- if (!on[key]) on[key] = [];
1467
- on[key].push(transitionObject);
1468
- };
1469
- const nestInvoke = (blockId, slot) => {
1470
- invokeMap[blockId] = {
1471
- ...invokeMap[blockId],
1472
- [slot]: (invokeMap[blockId]?.[slot] ?? []).concat(transitionObject)
1473
- };
1474
- };
1475
- const ref = parseStatelyRef(type);
1476
- if (ref) if (ref.kind === "invokeDone") nestInvoke(ref.blockId, "onDone");
1477
- else if (ref.kind === "invokeError") nestInvoke(ref.blockId, "onError");
1478
- else if (ref.kind === "invokeSnapshot") nestInvoke(ref.blockId, "onSnapshot");
1479
- else if (ref.kind === "stateDone") {
1480
- if (!onDone[type]) onDone[type] = [];
1481
- onDone[type].push(transitionObject);
1482
- } else pushOn(`xstate.${ref.kind === "actionDone" ? "done" : "error"}.actor.${resolveSpawnActorId$1(node, ref.blockId)}`);
1483
- else if (type === "" && node.data.type === "choice") choiceArr.push(transitionObject);
1484
- else if (type === "") alwaysArr.push(transitionObject);
1485
- else if (type.startsWith("xstate.after.")) {
1486
- const delayKey = type.slice(13).split(".")[0];
1487
- if (!after[delayKey]) after[delayKey] = [];
1488
- after[delayKey].push(transitionObject);
1489
- } else if (type === "*") pushOn("*");
1490
- else if (type.startsWith("xstate.done.state")) {
1491
- if (!onDone[type]) onDone[type] = [];
1492
- onDone[type].push(transitionObject);
1493
- } else if (type.startsWith("xstate.done.actor.") || type.startsWith("xstate.error.actor.") || type.startsWith("xstate.snapshot.actor.")) {
1494
- const verb = type.startsWith("xstate.done.actor.") ? "onDone" : type.startsWith("xstate.error.actor.") ? "onError" : "onSnapshot";
1495
- const invoke = findInvokeByActorId(node, type.slice(type.lastIndexOf(".actor.") + 7));
1496
- if (invoke) nestInvoke(invoke.id, verb);
1497
- else pushOn(type);
1498
- } else pushOn(type);
1499
- }
1500
- if (Object.keys(on).length > 0) nodeConfig.on = singleOrArrayRecord(on);
1501
- if (Object.keys(after).length > 0) nodeConfig.after = singleOrArrayRecord(after);
1502
- if (alwaysArr.length > 0) nodeConfig.always = singleOrArray(alwaysArr);
1503
- if (choiceArr.length > 0) nodeConfig.choice = singleOrArray(choiceArr);
1504
- if (Object.keys(onDone).length > 0) nodeConfig.onDone = singleOrArray(Object.values(onDone).flat());
1505
- if (Object.keys(invokeMap).length > 0 && Array.isArray(nodeConfig.invoke)) nodeConfig.invoke = nodeConfig.invoke.map((inv, index) => {
1506
- const mapped = invokeMap[node.data.invokes?.[index]?.id ?? inv.id];
1507
- if (!mapped) return inv;
1508
- return {
1509
- ...inv,
1510
- ...mapped.onDone ? { onDone: singleOrArray(mapped.onDone) } : void 0,
1511
- ...mapped.onError ? { onError: singleOrArray(mapped.onError) } : void 0,
1512
- ...mapped.onSnapshot ? { onSnapshot: singleOrArray(mapped.onSnapshot) } : void 0
1513
- };
1514
- });
1515
- }
1516
- if (Array.isArray(nodeConfig.invoke)) nodeConfig.invoke = nodeConfig.invoke.map((inv) => {
1517
- if (inv.id && !isAutoGeneratedId(inv.id)) return inv;
1518
- const { id: _id, ...rest } = inv;
1519
- return rest;
1520
- });
1521
- if (childNodes.length > 0) {
1522
- nodeConfig.states = {};
1523
- for (const childState of childNodes) nodeConfig.states[childState.data.key] = getNodeConfig(childState);
1524
- }
1525
- return nodeConfig;
1526
- }
1527
- const rootNode = graph.nodes.find((node) => node.data.parentId === null || node.parentId == null);
1528
- if (!rootNode) throw new Error("No root node found");
1529
- getNodeConfig(rootNode);
1530
- if (graph.data.context !== void 0 && graph.data.context !== null) config.context = graph.data.context;
1531
- return deepSimplify(config);
1532
- }
1533
- const REF_PREFIX = {
1534
- invokeDone: "@statelyai.invoke.done",
1535
- invokeError: "@statelyai.invoke.error",
1536
- invokeSnapshot: "@statelyai.invoke.snapshot",
1537
- actionDone: "@statelyai.action.done",
1538
- actionError: "@statelyai.action.error",
1539
- stateDone: "@statelyai.state.done"
1540
- };
1541
- function parseRef(eventType) {
1542
- if (eventType.startsWith("@stately.")) eventType = `@statelyai${eventType.slice(8)}`;
1543
- if (!eventType.startsWith("@statelyai.")) return null;
1544
- for (const kind of Object.keys(REF_PREFIX)) {
1545
- const prefix = REF_PREFIX[kind];
1546
- if (eventType === prefix) return {
1547
- kind,
1548
- blockId: ""
1549
- };
1550
- if (eventType.startsWith(`${prefix}.`)) return {
1551
- kind,
1552
- blockId: eventType.slice(prefix.length + 1)
1553
- };
1554
- }
1555
- return null;
1556
- }
1557
- function getSourceNode(graph, edge) {
1558
- return graph.nodes.find((node) => node.id === edge.sourceId);
1559
- }
1560
- function resolveSpawnActorId(sourceNode, actionId) {
1561
- const actorId = [...sourceNode?.data.entry ?? [], ...sourceNode?.data.exit ?? []].find((action) => action.id === actionId)?.params?.["id"];
1562
- return typeof actorId === "string" && actorId ? actorId : actionId;
1563
- }
1564
- function getTransitionRuntimeEventType(graph, edge) {
1565
- const eventType = edge.data.eventType;
1566
- const sourceNode = getSourceNode(graph, edge);
1567
- if (eventType.startsWith("xstate.after.")) {
1568
- const delayKey = eventType.slice(13).split(".")[0];
1569
- return sourceNode ? `xstate.after.${delayKey}.${getResolvedNodeId(graph, sourceNode)}` : eventType;
1570
- }
1571
- const ref = parseRef(eventType);
1572
- if (!ref) return eventType;
1573
- if (ref.kind === "stateDone") {
1574
- const node = graph.nodes.find((candidate) => candidate.id === ref.blockId) ?? sourceNode;
1575
- return `xstate.done.state.${node ? getResolvedNodeId(graph, node) : ref.blockId}`;
1576
- }
1577
- if (ref.kind === "invokeDone" || ref.kind === "invokeError" || ref.kind === "invokeSnapshot") {
1578
- const invokes = sourceNode?.data.invokes ?? [];
1579
- const index = invokes.findIndex((invoke) => invoke.id === ref.blockId);
1580
- const actorId = (index >= 0 ? invokes[index] : void 0)?.invocationId || (sourceNode && index >= 0 ? `${index}.${getResolvedNodeId(graph, sourceNode)}` : ref.blockId);
1581
- return `xstate.${ref.kind === "invokeDone" ? "done" : ref.kind === "invokeError" ? "error" : "snapshot"}.actor.${actorId}`;
1582
- }
1583
- return `xstate.${ref.kind === "actionDone" ? "done" : "error"}.actor.${resolveSpawnActorId(sourceNode, ref.blockId)}`;
1584
- }
1585
- function transitionMatchesRuntimeEvent(graph, edge, eventType) {
1586
- return getTransitionRuntimeEventType(graph, edge) === eventType;
1587
- }
1588
- /**
1589
- * Map an XState state node (identified by its array of keys from the root) to
1590
- * the corresponding graph node id. The runtime snapshot's `_nodes[].id` are
1591
- * XState resolved ids (delimited paths), which do not match the graph's opaque
1592
- * node ids; consumers (highlighting, edge lookup) need graph node ids.
1593
- */
1594
- function getGraphNodeIdForPath(graph, path) {
1595
- const root = graph.nodes.find((n) => n.parentId === null);
1596
- if (!root) return null;
1597
- if (path.length === 0) return root.id;
1598
- let parentId = root.id;
1599
- let nodeId = null;
1600
- for (const key of path) {
1601
- const child = graph.nodes.find((n) => n.parentId === parentId && n.data.key === key);
1602
- if (!child) return null;
1603
- nodeId = child.id;
1604
- parentId = child.id;
1605
- }
1606
- return nodeId;
1607
- }
1608
- function getStateIds(graph, snapshot) {
1609
- const nodes = snapshot._nodes;
1610
- if (!nodes) return [];
1611
- return nodes.map((n) => getGraphNodeIdForPath(graph, n.path)).filter((id) => id !== null);
1612
- }
1613
- function getLeafStateIds(graph, snapshot) {
1614
- const nodes = snapshot._nodes;
1615
- if (!nodes) return [];
1616
- const leaves = nodes.filter((n) => n.path.length > 0 && (n.type === "atomic" || n.type === "final"));
1617
- return (leaves.length > 0 ? leaves : nodes).map((n) => getGraphNodeIdForPath(graph, n.path)).filter((id) => id !== null);
1618
- }
1619
- function getStatePath(snapshot) {
1620
- const nodes = snapshot._nodes;
1621
- if (!nodes?.length) return "(initial)";
1622
- const withPath = nodes.filter((n) => n.path.length > 0);
1623
- if (!withPath.length) return "(initial)";
1624
- const leaves = withPath.filter((n) => n.type === "atomic" || n.type === "final");
1625
- return (leaves.length > 0 ? leaves : withPath).map((n) => n.path.join(".")).join(", ");
1626
- }
1627
- function findEdgeId(graph, sourceStateIds, event) {
1628
- const guardType = event["@xstate.guard"];
1629
- for (const sourceId of sourceStateIds) {
1630
- const edge = graph.edges.find((candidate) => candidate.sourceId === sourceId && transitionMatchesRuntimeEvent(graph, candidate, event.type) && (candidate.data.guard?.type ?? null) === (guardType ?? null));
1631
- if (edge) return edge.id;
1632
- }
1633
- for (const sourceId of sourceStateIds) {
1634
- const edge = graph.edges.find((candidate) => candidate.sourceId === sourceId && transitionMatchesRuntimeEvent(graph, candidate, event.type));
1635
- if (edge) return edge.id;
1636
- }
1637
- return null;
1638
- }
1639
- function formatEventLabel(event) {
1640
- if (event.type === "") return "always";
1641
- if (event.type.startsWith("xstate.after.")) return `after ${event.type.slice(13).split(".")[0]}`;
1642
- const guardType = event["@xstate.guard"];
1643
- if (guardType) return `${event.type} [${guardType}]`;
1644
- return event.type;
1645
- }
1646
- function formatEdgeLabel(graph, edgeId) {
1647
- const edge = graph.edges.find((candidate) => candidate.id === edgeId);
1648
- if (!edge) return null;
1649
- const baseLabel = formatEventLabel({ type: edge.data.eventType });
1650
- const guardType = edge.data.guard?.type;
1651
- if (guardType) return `${baseLabel} [${guardType}]`;
1652
- return baseLabel;
1653
- }
1654
- function getFallbackTransitionLabels(graph, finalStateIds) {
1655
- return getFallbackEventlessEdges(graph, finalStateIds).map((edge) => formatEdgeLabel(graph, edge.id)).filter((label) => !!label);
1656
- }
1657
- function getFallbackEventlessEdges(graph, finalStateIds) {
1658
- const finalLeafIdSet = new Set(finalStateIds);
1659
- return graph.edges.filter((edge) => finalLeafIdSet.has(edge.targetId) && edge.data.eventType === "");
1660
- }
1661
- function getPathName(finalStatePath, finalStateIds, steps, graph) {
1662
- let transitionLabels = steps.map((step) => {
1663
- if (step.edgeId) return formatEdgeLabel(graph, step.edgeId);
1664
- if (step.eventType && step.eventType !== "xstate.init") return formatEventLabel({ type: step.eventType });
1665
- return null;
1666
- }).filter((label) => !!label);
1667
- if (transitionLabels.length === 0) transitionLabels = getFallbackTransitionLabels(graph, finalStateIds);
1668
- if (transitionLabels.length === 0) return {
1669
- name: finalStatePath,
1670
- viaLabel: null
1671
- };
1672
- return {
1673
- name: finalStatePath,
1674
- viaLabel: `via ${transitionLabels.join(" -> ")}`
1675
- };
1676
- }
1677
- function convertPath(path, graph, index) {
1678
- const steps = [];
1679
- const firstStep = path.steps[0];
1680
- if (firstStep) {
1681
- const allIds = getStateIds(graph, firstStep.state);
1682
- steps.push({
1683
- stateIds: getLeafStateIds(graph, firstStep.state),
1684
- statePath: getStatePath(firstStep.state),
1685
- eventType: firstStep.event.type,
1686
- edgeId: findEdgeId(graph, allIds, firstStep.event)
1687
- });
1688
- }
1689
- for (let i = 1; i < path.steps.length; i++) {
1690
- const prevStep = path.steps[i - 1];
1691
- const step = path.steps[i];
1692
- const prevStateIds = getStateIds(graph, prevStep.state);
1693
- steps.push({
1694
- stateIds: getLeafStateIds(graph, step.state),
1695
- statePath: getStatePath(step.state),
1696
- eventType: step.event.type,
1697
- edgeId: findEdgeId(graph, prevStateIds, step.event)
1698
- });
1699
- }
1700
- const finalStatePath = getStatePath(path.state);
1701
- const finalStateIds = getLeafStateIds(graph, path.state);
1702
- const title = getPathName(finalStatePath, finalStateIds, steps, graph);
1703
- return {
1704
- id: `path-${index}`,
1705
- name: title.name,
1706
- viaLabel: title.viaLabel,
1707
- steps,
1708
- finalStatePath,
1709
- finalStateIds
1710
- };
1711
- }
1712
- function serializeMachineTransition(_state, event, prevState) {
1713
- if (!event) return "";
1714
- const prevStateString = prevState ? ` from ${JSON.stringify(prevState.value)}` : "";
1715
- return ` via ${JSON.stringify(event)}${prevStateString}`;
1716
- }
1717
- function getGraphPathSimImplementations(graph, guardState) {
1718
- const guards = {};
1719
- for (const edge of graph.edges) if (edge.data.guard) {
1720
- const guardType = edge.data.guard.type;
1721
- if (guardState) guards[guardType] = () => guardState[guardType] ?? false;
1722
- else guards[guardType] = ({ event }) => {
1723
- if (event["@xstate.guard"] === guardType) return true;
1724
- return false;
1725
- };
1726
- }
1727
- return { guards };
1728
- }
1729
- function getTraversalEvents(state, graph) {
1730
- const events = [];
1731
- for (const edge of graph.edges) {
1732
- const eventType = getTransitionRuntimeEventType(graph, edge);
1733
- const event = { type: eventType };
1734
- if (edge.data.guard?.type) event["@xstate.guard"] = edge.data.guard.type;
1735
- if (eventType && !state.can(event)) continue;
1736
- events.push(event);
1737
- }
1738
- const seen = /* @__PURE__ */ new Set();
1739
- return events.filter((event) => {
1740
- const key = JSON.stringify(event);
1741
- if (seen.has(key)) return false;
1742
- seen.add(key);
1743
- return true;
1744
- });
1745
- }
1746
- function getStepKey(step) {
1747
- return JSON.stringify({
1748
- stateIds: step.stateIds,
1749
- statePath: step.statePath,
1750
- eventType: step.eventType,
1751
- edgeId: step.edgeId
1752
- });
1753
- }
1754
- function getLeafGraphNodeIds(graph) {
1755
- const activeNodes = graph.nodes.filter((node) => !node.data.temp);
1756
- const parentIds = new Set(activeNodes.map((node) => node.parentId).filter(Boolean));
1757
- return activeNodes.filter((node) => !parentIds.has(node.id)).map((node) => node.id);
1758
- }
1759
- function getCoverage(graph, paths) {
1760
- const leafNodeIds = new Set(getLeafGraphNodeIds(graph));
1761
- const coveredStateIds = /* @__PURE__ */ new Set();
1762
- const coveredEdgeIds = /* @__PURE__ */ new Set();
1763
- for (const path of paths) {
1764
- let pathHasExplicitEdge = false;
1765
- for (const step of path.steps) {
1766
- for (const stateId of step.stateIds) if (leafNodeIds.has(stateId)) coveredStateIds.add(stateId);
1767
- if (step.edgeId) {
1768
- pathHasExplicitEdge = true;
1769
- coveredEdgeIds.add(step.edgeId);
1770
- }
1771
- }
1772
- for (const finalStateId of path.finalStateIds) if (leafNodeIds.has(finalStateId)) coveredStateIds.add(finalStateId);
1773
- if (!pathHasExplicitEdge) for (const edge of getFallbackEventlessEdges(graph, path.finalStateIds)) coveredEdgeIds.add(edge.id);
1774
- }
1775
- return {
1776
- states: {
1777
- covered: coveredStateIds.size,
1778
- total: leafNodeIds.size
1779
- },
1780
- transitions: {
1781
- covered: coveredEdgeIds.size,
1782
- total: graph.edges.filter((edge) => !edge.data.temp).length
1783
- }
1784
- };
1785
- }
1786
- function deduplicateGeneratedPaths(paths) {
1787
- const sorted = [...paths].sort((a, b) => b.steps.length - a.steps.length);
1788
- const unique = [];
1789
- for (const path of sorted) if (!unique.some((existing) => {
1790
- if (path.steps.length >= existing.steps.length) return false;
1791
- return path.steps.every((step, index) => getStepKey(step) === getStepKey(existing.steps[index]));
1792
- })) unique.push(path);
1793
- return unique;
1794
- }
1795
- function generateGraphPathsData(graph, options) {
1796
- const machineConfig = graphToMachineConfig(graph);
1797
- const implementations = getGraphPathSimImplementations(graph);
1798
- const machine = createMachine(machineConfig).provide(implementations);
1799
- let converted = (options.strategy === "shortest" ? getShortestPaths : getSimplePaths)(machine, {
1800
- events: (state) => getTraversalEvents(state, graph),
1801
- serializeState: (state, event, prevState) => {
1802
- const serializedState = JSON.stringify(state.value);
1803
- if (!options.preferTransitionCoverage) return serializedState;
1804
- return `${serializedState}|${serializeMachineTransition(state, event, prevState)}`;
1805
- }
1806
- }).map((path, index) => convertPath(path, graph, index));
1807
- if (options.reduceDuplicates) converted = deduplicateGeneratedPaths(converted);
1808
- if (options.limit) converted = converted.slice(0, options.limit);
1809
- return {
1810
- paths: converted,
1811
- coverage: getCoverage(graph, converted)
1812
- };
1813
- }
1814
- function toArray(value) {
1815
- return Array.isArray(value) ? value : value === void 0 ? [] : [value];
1816
- }
1817
- function actionItems(value) {
1818
- return toArray(value).map((item, index) => {
1819
- if (typeof item === "string") return {
1820
- id: item,
1821
- type: item
1822
- };
1823
- if (item && typeof item === "object" && "type" in item) {
1824
- const action = item;
1825
- if (typeof action.type === "string") return {
1826
- id: action.type || `action-${index}`,
1827
- type: action.type,
1828
- params: action.params
1829
- };
1830
- }
1831
- return null;
1832
- }).filter((item) => item !== null);
1833
- }
1834
- function invokeItems(value) {
1835
- return toArray(value).map((item, index) => {
1836
- if (typeof item === "string") return {
1837
- id: item || `invoke-${index}`,
1838
- src: item
1839
- };
1840
- if (!item || typeof item !== "object") return null;
1841
- const invoke = item;
1842
- if (typeof invoke.src !== "string") return null;
1843
- return {
1844
- id: typeof invoke.id === "string" ? invoke.id : invoke.src,
1845
- src: invoke.src,
1846
- ...invoke.input !== void 0 ? { input: invoke.input } : {},
1847
- ...invoke.output !== void 0 ? { output: invoke.output } : {}
1848
- };
1849
- }).filter((item) => item !== null);
1850
- }
1851
- function transitionTargets(target) {
1852
- if (typeof target === "string") return [target];
1853
- if (Array.isArray(target)) return target.filter((item) => typeof item === "string");
1854
- return [];
1855
- }
1856
- function targetToNodeId(sourceId, target, rootKey) {
1857
- if (!target) return sourceId;
1858
- let clean = target.startsWith("#") ? target.slice(1) : target;
1859
- if (target.startsWith("#")) {
1860
- const rootPrefix = `${rootKey}.`;
1861
- if (clean === rootKey) return "root";
1862
- if (clean.startsWith(rootPrefix)) clean = clean.slice(rootPrefix.length);
1863
- return clean ? `root.${clean}` : "root";
1864
- }
1865
- if (clean.includes(".")) return `root.${clean}`;
1866
- return `${sourceId.split(".").slice(0, -1).join(".") || "root"}.${clean}`;
1867
- }
1868
- function transitionObjects(value) {
1869
- return toArray(value).map((item) => {
1870
- if (typeof item === "string") return { target: item };
1871
- if (item && typeof item === "object") return item;
1872
- return {};
1873
- });
1874
- }
1875
- function guardObject(value) {
1876
- if (!value) return null;
1877
- if (typeof value === "string") return { type: value };
1878
- if (typeof value === "object" && "type" in value && typeof value.type === "string") {
1879
- const guard = value;
1880
- return {
1881
- type: guard.type,
1882
- code: guard.code,
1883
- params: guard.params
1884
- };
1885
- }
1886
- return null;
1887
- }
1888
- function machineConfigToGraph(config) {
1889
- createMachine(config);
1890
- const rootKey = String(config.id ?? "machine");
1891
- const graph = {
1892
- id: "root",
1893
- nodes: [],
1894
- edges: [],
1895
- data: {
1896
- implementations: {
1897
- actions: [],
1898
- guards: [],
1899
- actors: [],
1900
- delays: []
1901
- },
1902
- schemas: config.schemas ?? null
1903
- }
1904
- };
1905
- function visit(state, key, parentId) {
1906
- const id = parentId ? `${parentId}.${key}` : "root";
1907
- const initialId = state.initial ? `${id}.${state.initial}` : null;
1908
- graph.nodes.push({
1909
- id,
1910
- parentId,
1911
- data: {
1912
- key,
1913
- type: state.type === "parallel" || state.type === "final" || state.type === "history" || state.type === "choice" ? state.type : null,
1914
- initialId,
1915
- history: state.history ?? false,
1916
- ...state.output !== void 0 ? { output: state.output } : {},
1917
- entry: actionItems(state.entry),
1918
- exit: actionItems(state.exit),
1919
- invokes: invokeItems(state.invoke),
1920
- tags: Array.isArray(state.tags) ? state.tags : typeof state.tags === "string" ? [state.tags] : [],
1921
- description: state.description ?? null,
1922
- meta: state.meta ?? null
1923
- }
1924
- });
1925
- for (const [eventType, value] of Object.entries(state.on ?? {})) for (const [index, transition] of transitionObjects(value).entries()) {
1926
- const target = transitionTargets(transition.target)[0];
1927
- graph.edges.push({
1928
- id: `${id}-${eventType}-${index}`,
1929
- sourceId: id,
1930
- targetId: targetToNodeId(id, target, rootKey),
1931
- data: {
1932
- eventType,
1933
- transitionType: target ? transition.reenter ? "reenter" : "normal" : "targetless",
1934
- guard: guardObject(transition.guard),
1935
- ...transition.context !== void 0 ? { context: transition.context } : {},
1936
- actions: actionItems(transition.actions),
1937
- description: typeof transition.description === "string" ? transition.description : null,
1938
- meta: transition.meta && typeof transition.meta === "object" ? transition.meta : null
1939
- }
1940
- });
1941
- }
1942
- for (const [delay, value] of Object.entries(state.after ?? {})) for (const [index, transition] of transitionObjects(value).entries()) {
1943
- const target = transitionTargets(transition.target)[0];
1944
- graph.edges.push({
1945
- id: `${id}-after-${delay}-${index}`,
1946
- sourceId: id,
1947
- targetId: targetToNodeId(id, target, rootKey),
1948
- data: {
1949
- eventType: `xstate.after.${delay}.${id}`,
1950
- transitionType: target ? "normal" : "targetless",
1951
- guard: guardObject(transition.guard),
1952
- ...transition.context !== void 0 ? { context: transition.context } : {},
1953
- actions: actionItems(transition.actions),
1954
- description: typeof transition.description === "string" ? transition.description : null,
1955
- meta: null
1956
- }
1957
- });
1958
- }
1959
- for (const [index, transition] of transitionObjects(state.always).entries()) {
1960
- const target = transitionTargets(transition.target)[0];
1961
- graph.edges.push({
1962
- id: `${id}-always-${index}`,
1963
- sourceId: id,
1964
- targetId: targetToNodeId(id, target, rootKey),
1965
- data: {
1966
- eventType: "",
1967
- transitionType: target ? "normal" : "targetless",
1968
- guard: guardObject(transition.guard),
1969
- ...transition.context !== void 0 ? { context: transition.context } : {},
1970
- actions: actionItems(transition.actions),
1971
- description: typeof transition.description === "string" ? transition.description : null,
1972
- meta: null
1973
- }
1974
- });
1975
- }
1976
- for (const [index, transition] of transitionObjects(state.choice).entries()) {
1977
- const target = transitionTargets(transition.target)[0];
1978
- graph.edges.push({
1979
- id: `${id}-choice-${index}`,
1980
- sourceId: id,
1981
- targetId: targetToNodeId(id, target, rootKey),
1982
- data: {
1983
- eventType: "",
1984
- transitionType: target ? "normal" : "targetless",
1985
- guard: guardObject(transition.guard),
1986
- ...transition.context !== void 0 ? { context: transition.context } : {},
1987
- actions: actionItems(transition.actions),
1988
- description: typeof transition.description === "string" ? transition.description : null,
1989
- meta: transition.meta && typeof transition.meta === "object" ? transition.meta : null
1990
- }
1991
- });
1992
- }
1993
- for (const [childKey, child] of Object.entries(state.states ?? {})) visit(child, childKey, id);
1994
- }
1995
- visit(config, rootKey, null);
1996
- return graph;
1997
- }
1998
- const XSTATE_V6_FN_TRANSITION_META = "@statelyai.xstate.v6.fnTransition";
1999
- const XSTATE_V6_FN_SOURCE_META = "@statelyai.xstate.v6.fnSource";
2000
- function sourceFile(source) {
2001
- return ts.createSourceFile("machine.ts", source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
2002
- }
2003
- function propName(name) {
2004
- if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) return name.text;
2005
- return null;
2006
- }
2007
- function unwrapExpression$2(node) {
2008
- if (ts.isAsExpression(node) || ts.isSatisfiesExpression(node) || ts.isNonNullExpression(node) || ts.isParenthesizedExpression(node)) return unwrapExpression$2(node.expression);
2009
- return node;
2010
- }
2011
- function propertyContext(parent, key) {
2012
- if (parent.transitionMap) return {
2013
- v6: parent.v6,
2014
- transitionValue: true
2015
- };
2016
- if (parent.transitionValue && key === "actions") return {
2017
- v6: parent.v6,
2018
- actionValue: true
2019
- };
2020
- if (parent.transitionValue && key === "context") return {
2021
- v6: parent.v6,
2022
- assignmentMap: true,
2023
- transitionContext: true
2024
- };
2025
- if (parent.assignmentMap) return {
2026
- v6: parent.v6,
2027
- assignmentValue: true
2028
- };
2029
- if (key === "meta") return {
2030
- v6: parent.v6,
2031
- metaValue: true
2032
- };
2033
- if (parent.transitionValue && key === "to") return {
2034
- v6: parent.v6,
2035
- transitionValue: true
2036
- };
2037
- if (key === "entry" || key === "exit") return {
2038
- v6: parent.v6,
2039
- actionValue: true
2040
- };
2041
- if (key === "input" || key === "output") return {
2042
- v6: parent.v6,
2043
- ioValue: true
2044
- };
2045
- if (key === "on" || key === "after") return {
2046
- v6: parent.v6,
2047
- transitionMap: true
2048
- };
2049
- if (key === "always" || key === "choice" || key === "onDone" || key === "onError" || key === "onSnapshot" || key === "onTimeout") return {
2050
- v6: parent.v6,
2051
- transitionValue: true
2052
- };
2053
- return { v6: parent.v6 };
2054
- }
2055
- function fallbackFunctionTransition(fn, actions = []) {
2056
- return {
2057
- ...actions.length ? { actions } : void 0,
2058
- meta: {
2059
- [XSTATE_V6_FN_TRANSITION_META]: true,
2060
- [XSTATE_V6_FN_SOURCE_META]: fn.getText()
2061
- }
2062
- };
2063
- }
2064
- function conditionText(cond) {
2065
- return cond.negated ? `!(${cond.text})` : cond.text;
2066
- }
2067
- function simplifyConditions(conds) {
2068
- const seen = /* @__PURE__ */ new Set();
2069
- return conds.filter((cond) => {
2070
- if (cond.negated) return false;
2071
- const key = conditionText(cond);
2072
- if (seen.has(key)) return false;
2073
- seen.add(key);
2074
- return true;
2075
- });
2076
- }
2077
- function hashString(value) {
2078
- let hash = 5381;
2079
- for (let i = 0; i < value.length; i++) hash = hash * 33 ^ value.charCodeAt(i);
2080
- return (hash >>> 0).toString(36);
2081
- }
2082
- function inlineGuardCode(params, condition) {
2083
- return `${params} => ${condition}`;
2084
- }
2085
- function guardFromConditions(conds, params) {
2086
- const simplified = simplifyConditions(conds);
2087
- if (simplified.length === 0) return void 0;
2088
- const code = inlineGuardCode(params, simplified.map(conditionText).join(" && "));
2089
- return {
2090
- type: `inline:${hashString(code)}`,
2091
- code
2092
- };
2093
- }
2094
- function wrapAssignExpression(expr) {
2095
- return `{{ ${expr.trim()} }}`;
2096
- }
2097
- function hasSpreadAssignment(node) {
2098
- return node.properties.some((property) => ts.isSpreadAssignment(property));
2099
- }
2100
- function mergeActions(left, right) {
2101
- return [...left ?? [], ...right];
2102
- }
2103
- function transitionFromValue(value, actions) {
2104
- if (typeof value === "string") {
2105
- if (actions.length === 0) return value;
2106
- return {
2107
- target: value,
2108
- actions
2109
- };
2110
- }
2111
- if (!value || typeof value !== "object" || Array.isArray(value)) return actions.length ? { actions } : null;
2112
- const record = { ...value };
2113
- if (actions.length) record.actions = mergeActions(Array.isArray(record.actions) ? record.actions : record.actions ? [record.actions] : void 0, actions);
2114
- return record;
2115
- }
2116
- function expressionBranches(expression, bindings, depth) {
2117
- const node = unwrapExpression$2(expression);
2118
- if (ts.isConditionalExpression(node)) {
2119
- const cond = node.condition.getText();
2120
- return [...expressionBranches(node.whenTrue, bindings, depth + 1).map((branch) => ({
2121
- ...branch,
2122
- conds: [{ text: cond }, ...branch.conds]
2123
- })), ...expressionBranches(node.whenFalse, bindings, depth + 1).map((branch) => ({
2124
- ...branch,
2125
- conds: [{
2126
- text: cond,
2127
- negated: true
2128
- }, ...branch.conds]
2129
- }))];
2130
- }
2131
- if (ts.isBinaryExpression(node) && (node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken || node.operatorToken.kind === ts.SyntaxKind.BarBarToken)) {
2132
- const cond = node.left.getText();
2133
- if (node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken) return [...expressionBranches(node.right, bindings, depth + 1).map((branch) => ({
2134
- ...branch,
2135
- conds: [{ text: cond }, ...branch.conds]
2136
- })), {
2137
- conds: [{
2138
- text: cond,
2139
- negated: true
2140
- }],
2141
- actions: []
2142
- }];
2143
- return [{
2144
- conds: [{ text: cond }],
2145
- actions: [],
2146
- result: node.left.getText()
2147
- }, ...expressionBranches(node.right, bindings, depth + 1).map((branch) => ({
2148
- ...branch,
2149
- conds: [{
2150
- text: cond,
2151
- negated: true
2152
- }, ...branch.conds]
2153
- }))];
2154
- }
2155
- if (ts.isObjectLiteralExpression(node)) {
2156
- const targetProperty = node.properties.find((property) => ts.isPropertyAssignment(property) && propName(property.name) === "target");
2157
- const target = targetProperty ? unwrapExpression$2(targetProperty.initializer) : null;
2158
- if (target && (ts.isConditionalExpression(target) || ts.isBinaryExpression(target) && (target.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken || target.operatorToken.kind === ts.SyntaxKind.BarBarToken))) return expressionBranches(target, bindings, depth + 1).map((branch) => ({
2159
- ...branch,
2160
- result: objectLiteralToValue(node, bindings, {
2161
- v6: true,
2162
- transitionValue: true
2163
- }, depth + 1, new Map([["target", branch.result]]))
2164
- }));
2165
- }
2166
- return [{
2167
- conds: [],
2168
- actions: [],
2169
- result: expressionToValue(node, bindings, {
2170
- v6: true,
2171
- transitionValue: true
2172
- }, depth + 1)
2173
- }];
2174
- }
2175
- function mergeBranchPrefix(prefix, branch) {
2176
- return {
2177
- conds: [...prefix.conds, ...branch.conds],
2178
- actions: [...prefix.actions, ...branch.actions],
2179
- result: branch.result,
2180
- unknown: prefix.unknown || branch.unknown
2181
- };
2182
- }
2183
- function expressionStatementAction(statement, enqName, bindings) {
2184
- const expression = unwrapExpression$2(statement.expression);
2185
- if (!ts.isCallExpression(expression)) return null;
2186
- const callee = expression.expression;
2187
- if (ts.isIdentifier(callee) && callee.text === enqName) return {
2188
- type: "xstate.expr",
2189
- params: {
2190
- code: expression.getText(),
2191
- lang: "ts"
2192
- }
2193
- };
2194
- if (!ts.isPropertyAccessExpression(callee)) return null;
2195
- if (!ts.isIdentifier(callee.expression) || callee.expression.text !== enqName) return null;
2196
- const method = callee.name.text;
2197
- const args = expression.arguments;
2198
- const value = (index) => {
2199
- const arg = args[index];
2200
- return arg && ts.isExpression(arg) ? expressionToValue(arg, bindings, {}, 0) : void 0;
2201
- };
2202
- const spreadOpts = (index) => {
2203
- const opts = value(index);
2204
- return opts && typeof opts === "object" && !Array.isArray(opts) ? opts : {};
2205
- };
2206
- if (method === "emit") return {
2207
- type: "xstate.emit",
2208
- params: { event: value(0) }
2209
- };
2210
- if (method === "raise") return {
2211
- type: "xstate.raise",
2212
- params: {
2213
- event: value(0),
2214
- ...spreadOpts(1)
2215
- }
2216
- };
2217
- if (method === "sendTo") return {
2218
- type: "xstate.sendTo",
2219
- params: {
2220
- to: value(0),
2221
- event: value(1),
2222
- ...spreadOpts(2)
2223
- }
2224
- };
2225
- if (method === "cancel") return {
2226
- type: "xstate.cancel",
2227
- params: { sendId: value(0) }
2228
- };
2229
- if (method === "log") return {
2230
- type: "xstate.log",
2231
- params: { label: value(0) }
2232
- };
2233
- if (method === "spawn") return {
2234
- type: "xstate.spawnChild",
2235
- params: {
2236
- src: value(0),
2237
- ...spreadOpts(1)
2238
- }
2239
- };
2240
- if (method === "stop") return {
2241
- type: "xstate.stopChild",
2242
- params: { actorRef: value(0) }
2243
- };
2244
- return {
2245
- type: "xstate.expr",
2246
- params: {
2247
- code: expression.getText(),
2248
- lang: "ts"
2249
- }
2250
- };
2251
- }
2252
- function statementBranches(statements, bindings, enqName, prefixes = [{
2253
- conds: [],
2254
- actions: []
2255
- }]) {
2256
- let open = prefixes;
2257
- const done = [];
2258
- const scopedBindings = new Map(bindings);
2259
- for (const statement of statements) {
2260
- const nextOpen = [];
2261
- for (const prefix of open) {
2262
- if (ts.isVariableStatement(statement)) {
2263
- for (const declaration of statement.declarationList.declarations) if (ts.isIdentifier(declaration.name) && declaration.initializer) scopedBindings.set(declaration.name.text, declaration.initializer);
2264
- nextOpen.push(prefix);
2265
- continue;
2266
- }
2267
- if (ts.isReturnStatement(statement)) {
2268
- if (!statement.expression) {
2269
- done.push({
2270
- ...prefix,
2271
- result: void 0
2272
- });
2273
- continue;
2274
- }
2275
- for (const branch of expressionBranches(statement.expression, scopedBindings, 0)) done.push(mergeBranchPrefix(prefix, branch));
2276
- continue;
2277
- }
2278
- if (ts.isExpressionStatement(statement)) {
2279
- const action = expressionStatementAction(statement, enqName, scopedBindings);
2280
- if (action) {
2281
- nextOpen.push({
2282
- ...prefix,
2283
- actions: [...prefix.actions, action]
2284
- });
2285
- continue;
2286
- }
2287
- }
2288
- if (ts.isIfStatement(statement)) {
2289
- const cond = statement.expression.getText();
2290
- const thenBranches = statementBranches(ts.isBlock(statement.thenStatement) ? statement.thenStatement.statements : [statement.thenStatement], scopedBindings, enqName, [{
2291
- ...prefix,
2292
- conds: [...prefix.conds, { text: cond }]
2293
- }]);
2294
- const elseStatements = statement.elseStatement ? ts.isBlock(statement.elseStatement) ? statement.elseStatement.statements : [statement.elseStatement] : [];
2295
- const elseBranches = elseStatements.length > 0 ? statementBranches(elseStatements, scopedBindings, enqName, [{
2296
- ...prefix,
2297
- conds: [...prefix.conds, {
2298
- text: cond,
2299
- negated: true
2300
- }]
2301
- }]) : [{
2302
- ...prefix,
2303
- conds: [...prefix.conds, {
2304
- text: cond,
2305
- negated: true
2306
- }]
2307
- }];
2308
- for (const branch of [...thenBranches, ...elseBranches]) if ("result" in branch) done.push(branch);
2309
- else nextOpen.push(branch);
2310
- continue;
2311
- }
2312
- if (ts.isSwitchStatement(statement)) {
2313
- const expr = statement.expression.getText();
2314
- const prior = [];
2315
- for (const clause of statement.caseBlock.clauses) {
2316
- const ownCond = ts.isCaseClause(clause) ? { text: `${expr} === ${clause.expression.getText()}` } : null;
2317
- const conds = ownCond ? [...prior, ownCond] : prior.length ? [...prior] : [];
2318
- const branches = statementBranches(clause.statements, scopedBindings, enqName, [{
2319
- ...prefix,
2320
- conds: [...prefix.conds, ...conds]
2321
- }]);
2322
- for (const branch of branches) if ("result" in branch) done.push(branch);
2323
- else nextOpen.push(branch);
2324
- if (ownCond) prior.push({
2325
- ...ownCond,
2326
- negated: true
2327
- });
2328
- }
2329
- continue;
2330
- }
2331
- nextOpen.push({
2332
- ...prefix,
2333
- unknown: true
2334
- });
2335
- }
2336
- open = nextOpen;
2337
- }
2338
- return [...done, ...open];
2339
- }
2340
- function enqueueParamName(fn) {
2341
- const param = fn.parameters[1];
2342
- return param && ts.isIdentifier(param.name) ? param.name.text : "enq";
2343
- }
2344
- function guardParams(fn) {
2345
- const param = fn.parameters[0];
2346
- if (!param) return "()";
2347
- return `(${param.getText()})`;
2348
- }
2349
- function normalizeFunctionTransition(fn, bindings, depth) {
2350
- const preservedActions = fn.body && ts.isBlock(fn.body) && fn.body.statements.some((statement) => ts.isExpressionStatement(statement)) ? [enqueueAction(fn)] : [];
2351
- const transitions = (ts.isArrowFunction(fn) && !ts.isBlock(fn.body) ? expressionBranches(fn.body, bindings, depth + 1) : fn.body && ts.isBlock(fn.body) ? statementBranches(fn.body.statements, bindings, enqueueParamName(fn)) : []).filter((branch) => !branch.unknown).map((branch) => {
2352
- const transition = transitionFromValue(branch.result, branch.actions.length ? preservedActions : []);
2353
- if (!transition) return null;
2354
- if (typeof transition === "string") {
2355
- const guard = guardFromConditions(branch.conds, guardParams(fn));
2356
- return guard ? {
2357
- target: transition,
2358
- guard
2359
- } : transition;
2360
- }
2361
- const guard = guardFromConditions(branch.conds, guardParams(fn));
2362
- return guard ? {
2363
- ...transition,
2364
- guard
2365
- } : transition;
2366
- }).filter((transition) => Boolean(transition));
2367
- if (transitions.length === 1) return transitions[0];
2368
- if (transitions.length > 1) return transitions;
2369
- return fallbackFunctionTransition(fn, preservedActions);
2370
- }
2371
- function inlineAction(fn) {
2372
- return {
2373
- type: "xstate.expr",
2374
- params: {
2375
- code: fn.getText(),
2376
- lang: "ts"
2377
- }
2378
- };
2379
- }
2380
- function enqueueAction(fn) {
2381
- return {
2382
- type: "xstate.enqueue",
2383
- params: {
2384
- code: fn.getText(),
2385
- lang: "ts"
2386
- }
2387
- };
2388
- }
2389
- function inlineCodeExpression(expr) {
2390
- return {
2391
- "@type": "code",
2392
- lang: "ts",
2393
- expr
2394
- };
2395
- }
2396
- function normalizeFunctionAction(fn, _bindings) {
2397
- return enqueueAction(fn);
2398
- }
2399
- function objectLiteralToValue(node, bindings, context, depth, overrides = /* @__PURE__ */ new Map()) {
2400
- const result = {};
2401
- for (const property of node.properties) {
2402
- if (ts.isSpreadAssignment(property)) {
2403
- const spread = expressionToValue(property.expression, bindings, context, depth + 1);
2404
- if (spread && typeof spread === "object" && !Array.isArray(spread)) Object.assign(result, spread);
2405
- continue;
2406
- }
2407
- if (!ts.isPropertyAssignment(property) && !ts.isShorthandPropertyAssignment(property)) continue;
2408
- const key = propName(property.name);
2409
- if (!key) continue;
2410
- if (overrides.has(key)) {
2411
- result[key] = overrides.get(key);
2412
- continue;
2413
- }
2414
- if (ts.isShorthandPropertyAssignment(property)) {
2415
- result[key] = expressionToValue(property.name, bindings, propertyContext(context, key), depth + 1);
2416
- continue;
2417
- }
2418
- result[key] = expressionToValue(property.initializer, bindings, propertyContext(context, key), depth + 1);
2419
- }
2420
- if (context.transitionValue && "to" in result) {
2421
- const to = result.to;
2422
- const { to: _to, ...rest } = result;
2423
- if (typeof to === "string") return {
2424
- ...rest,
2425
- target: to
2426
- };
2427
- if (Array.isArray(to)) return to.map((item) => typeof item === "string" ? {
2428
- ...rest,
2429
- target: item
2430
- } : item && typeof item === "object" ? {
2431
- ...rest,
2432
- ...item
2433
- } : rest);
2434
- if (to && typeof to === "object" && !Array.isArray(to)) return {
2435
- ...rest,
2436
- ...to
2437
- };
2438
- }
2439
- return result;
2440
- }
2441
- function expressionToValue(expression, bindings, context = {}, depth = 0) {
2442
- if (depth > 30) return void 0;
2443
- const node = unwrapExpression$2(expression);
2444
- if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) return context.ioValue ? inlineCodeExpression(JSON.stringify(node.text)) : node.text;
2445
- if (ts.isNumericLiteral(node)) return context.ioValue ? inlineCodeExpression(node.text) : Number(node.text);
2446
- if (node.kind === ts.SyntaxKind.TrueKeyword) return context.ioValue ? inlineCodeExpression("true") : true;
2447
- if (node.kind === ts.SyntaxKind.FalseKeyword) return context.ioValue ? inlineCodeExpression("false") : false;
2448
- if (node.kind === ts.SyntaxKind.NullKeyword) return context.ioValue ? inlineCodeExpression("null") : null;
2449
- if (context.assignmentValue) {
2450
- if (ts.isIdentifier(node) && node.text === "undefined") return void 0;
2451
- return wrapAssignExpression(node.getText());
2452
- }
2453
- if (context.transitionContext && !ts.isObjectLiteralExpression(node)) return inlineCodeExpression(node.getText());
2454
- if (context.ioValue && !ts.isObjectLiteralExpression(node)) return inlineCodeExpression(node.getText());
2455
- if (context.transitionContext && ts.isObjectLiteralExpression(node) && hasSpreadAssignment(node)) return inlineCodeExpression(node.getText());
2456
- if (ts.isIdentifier(node)) {
2457
- if (node.text === "undefined") return void 0;
2458
- const binding = bindings.get(node.text);
2459
- return binding ? expressionToValue(binding, bindings, context, depth + 1) : context.metaValue ? inlineCodeExpression(node.getText()) : node.text;
2460
- }
2461
- if (ts.isObjectLiteralExpression(node)) return objectLiteralToValue(node, bindings, context, depth);
2462
- if (ts.isArrayLiteralExpression(node)) {
2463
- const values = node.elements.map((element) => expressionToValue(element, bindings, context, depth + 1));
2464
- return context.actionValue ? values.flat() : values;
2465
- }
2466
- if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {
2467
- if (context.metaValue) return inlineCodeExpression(node.getText());
2468
- if (context.v6 && context.transitionValue) return normalizeFunctionTransition(node, bindings, depth);
2469
- if (context.v6 && context.actionValue) return normalizeFunctionAction(node, bindings);
2470
- return node.getText();
2471
- }
2472
- if (ts.isCallExpression(node)) {
2473
- if (context.metaValue) return inlineCodeExpression(node.getText());
2474
- if (context.actionValue) return inlineAction(node);
2475
- const callee = node.expression.getText();
2476
- const type = callee.includes(".") ? callee.split(".").pop() : callee;
2477
- return { type: type === "assign" ? "xstate.assign" : type };
2478
- }
2479
- return context.metaValue ? inlineCodeExpression(node.getText()) : node.getText();
2480
- }
2481
- function collectBindings$1(file) {
2482
- const bindings = /* @__PURE__ */ new Map();
2483
- for (const statement of file.statements) {
2484
- if (!ts.isVariableStatement(statement)) continue;
2485
- for (const declaration of statement.declarationList.declarations) if (ts.isIdentifier(declaration.name) && declaration.initializer) bindings.set(declaration.name.text, declaration.initializer);
2486
- }
2487
- return bindings;
2488
- }
2489
- function isCreateMachineCall(node) {
2490
- const expression = node.expression;
2491
- if (ts.isIdentifier(expression)) return expression.text === "createMachine";
2492
- if (ts.isPropertyAccessExpression(expression)) return expression.name.text === "createMachine";
2493
- return false;
2494
- }
2495
- function hasV6Syntax(source) {
2496
- const file = sourceFile(source);
2497
- let found = false;
2498
- function visit(node) {
2499
- if (found) return;
2500
- if (ts.isPropertyAssignment(node) && (ts.isIdentifier(node.name) || ts.isStringLiteral(node.name))) {
2501
- const key = node.name.text;
2502
- if (key === "triggers" || key === "internalEvents" || key === "timeout" || key === "onTimeout" || key === "choice" || key === "route") {
2503
- found = true;
2504
- return;
2505
- }
2506
- if ((key === "on" || key === "after" || key === "always" || key === "choice" || key === "onDone" || key === "onError" || key === "onSnapshot" || key === "onTimeout" || key === "entry" || key === "exit" || key === "actions" && !ts.isObjectLiteralExpression(node.initializer)) && node.initializer.getText().includes("=>")) {
2507
- found = true;
2508
- return;
2509
- }
2510
- }
2511
- ts.forEachChild(node, visit);
2512
- }
2513
- visit(file);
2514
- return found;
2515
- }
2516
- function detectXStateSourceProfile(source) {
2517
- return hasV6Syntax(source) ? "xstate-v6-alpha" : "xstate-v5";
2518
- }
2519
- function xstateSourceToConfig(source, options = {}) {
2520
- const file = sourceFile(source);
2521
- const bindings = collectBindings$1(file);
2522
- let found = null;
2523
- const profile = options.profile === "auto" || !options.profile ? detectXStateSourceProfile(source) : options.profile;
2524
- function visit(node) {
2525
- if (found || !ts.isCallExpression(node) || !isCreateMachineCall(node)) {
2526
- ts.forEachChild(node, visit);
2527
- return;
2528
- }
2529
- const arg = node.arguments[0];
2530
- if (!arg) return;
2531
- const value = expressionToValue(arg, bindings, { v6: profile === "xstate-v6-alpha" });
2532
- if (value && typeof value === "object" && !Array.isArray(value)) found = value;
2533
- }
2534
- visit(file);
2535
- if (!found) throw new Error("No createMachine(...) or setup(...).createMachine(...) call found.");
2536
- return found;
2537
- }
2538
- function parseJsonLike(input) {
2539
- return JSON.parse(input);
2540
- }
2541
- function parseSimpleYaml(input) {
2542
- const lines = input.split(/\r?\n/).filter((line) => line.trim() && !line.trim().startsWith("#"));
2543
- const root = {};
2544
- const stack = [{
2545
- indent: -1,
2546
- value: root
2547
- }];
2548
- for (const line of lines) {
2549
- const indent = line.match(/^\s*/)?.[0].length ?? 0;
2550
- const trimmed = line.trim();
2551
- const match = /^([^:]+):(.*)$/.exec(trimmed);
2552
- if (!match) continue;
2553
- const key = match[1].trim();
2554
- const raw = match[2].trim();
2555
- while (stack.length > 1 && indent <= stack[stack.length - 1].indent) stack.pop();
2556
- const parent = stack[stack.length - 1].value;
2557
- if (!raw) {
2558
- const child = {};
2559
- parent[key] = child;
2560
- stack.push({
2561
- indent,
2562
- value: child
2563
- });
2564
- } else if (raw === "true" || raw === "false") parent[key] = raw === "true";
2565
- else if (/^-?\d+(?:\.\d+)?$/.test(raw)) parent[key] = Number(raw);
2566
- else parent[key] = raw.replace(/^['"]|['"]$/g, "");
2567
- }
2568
- return root;
2569
- }
2570
- function parseMermaid(input) {
2571
- const states = /* @__PURE__ */ new Set();
2572
- const transitions = [];
2573
- let initial;
2574
- for (const line of input.split(/\r?\n/)) {
2575
- const trimmed = line.trim();
2576
- if (!trimmed || /^(stateDiagram|stateDiagram-v2|flowchart|graph)\b/.test(trimmed)) continue;
2577
- const stateMatch = /^\[\*\]\s*-->\s*([A-Za-z0-9_.-]+)/.exec(trimmed);
2578
- if (stateMatch?.[1]) {
2579
- initial = stateMatch[1];
2580
- states.add(initial);
2581
- continue;
2582
- }
2583
- const transitionMatch = /^([A-Za-z0-9_.-]+)\s*(?:-->|--)\s*(?:\|([^|]+)\|)?\s*([A-Za-z0-9_.-]+)(?:\s*:\s*(.+))?/.exec(trimmed);
2584
- if (!transitionMatch) continue;
2585
- const source = transitionMatch[1];
2586
- const target = transitionMatch[3];
2587
- const event = transitionMatch[2] ?? transitionMatch[4];
2588
- states.add(source);
2589
- states.add(target);
2590
- transitions.push({
2591
- source,
2592
- target,
2593
- event: event?.trim()
2594
- });
2595
- initial ??= source;
2596
- }
2597
- const stateConfigs = {};
2598
- for (const state of states) stateConfigs[state] = {};
2599
- for (const transition of transitions) {
2600
- const source = stateConfigs[transition.source];
2601
- const on = source.on ??= {};
2602
- on[transition.event || "NEXT"] = transition.target;
2603
- }
2604
- return {
2605
- id: "machine",
2606
- initial: initial ?? [...states][0] ?? "idle",
2607
- states: Object.keys(stateConfigs).length ? stateConfigs : { idle: {} }
2608
- };
2609
- }
2610
- function inferMachineFormat(machine, format) {
2611
- if (format) return format;
2612
- if (typeof machine !== "string") return "json";
2613
- const trimmed = machine.trim();
2614
- if (trimmed.startsWith("{")) return "json";
2615
- if (/^(stateDiagram|flowchart|graph)\b/m.test(trimmed)) return "mermaid";
2616
- if (/^<\?xml|^<scxml/m.test(trimmed)) return "scxml";
2617
- if (/^\w+:/m.test(trimmed)) return "yaml";
2618
- return "xstate";
2619
- }
2620
- function machineInputToConfig(input) {
2621
- const format = inferMachineFormat(input.machine, input.format);
2622
- if (typeof input.machine !== "string") {
2623
- createMachine(input.machine);
2624
- return input.machine;
2625
- }
2626
- if (format === "json") {
2627
- const config = parseJsonLike(input.machine);
2628
- createMachine(config);
2629
- return config;
2630
- }
2631
- if (format === "yaml") {
2632
- const config = parseSimpleYaml(input.machine);
2633
- createMachine(config);
2634
- return config;
2635
- }
2636
- if (format === "mermaid") {
2637
- const config = parseMermaid(input.machine);
2638
- createMachine(config);
2639
- return config;
2640
- }
2641
- if (format === "xstate") {
2642
- const config = xstateSourceToConfig(input.machine, { profile: input.profile ?? (input.xstateVersion === 6 ? "xstate-v6-alpha" : input.xstateVersion === 5 ? "xstate-v5" : "auto") });
2643
- createMachine(config);
2644
- return config;
2645
- }
2646
- throw new Error(`${format} source parsing is not supported by graph-tools v1 yet.`);
2647
- }
2648
- function machineInputToGraph(input) {
2649
- return machineConfigToGraph(machineInputToConfig(input));
2650
- }
2651
- function graphToMermaid(graph) {
2652
- const machine = graphToMachineConfig(graph);
2653
- const lines = ["stateDiagram-v2"];
2654
- if (machine.initial) lines.push(` [*] --> ${machine.initial}`);
2655
- for (const [stateKey, state] of Object.entries(machine.states ?? {})) for (const [event, transition] of Object.entries(state.on ?? {})) {
2656
- const target = typeof transition === "string" ? transition : Array.isArray(transition) ? void 0 : transition.target;
2657
- if (target) lines.push(` ${stateKey} --> ${target} : ${event}`);
2658
- }
2659
- return lines.join("\n");
2660
- }
2661
- function toYaml(value, indent = 0) {
2662
- const pad = " ".repeat(indent);
2663
- if (!value || typeof value !== "object" || Array.isArray(value)) return `${JSON.stringify(value)}`;
2664
- return Object.entries(value).map(([key, item]) => {
2665
- if (item && typeof item === "object" && !Array.isArray(item)) return `${pad}${key}:\n${toYaml(item, indent + 2)}`;
2666
- return `${pad}${key}: ${typeof item === "string" ? item : JSON.stringify(item)}`;
2667
- }).join("\n");
2668
- }
2669
- function graphToFormat(graph, format) {
2670
- const config = graphToMachineConfig(graph);
2671
- if (format === "xstate") return `import { createMachine } from 'xstate';\n\nexport const machine = createMachine(${serializeJS(config, 2)});\n`;
2672
- if (format === "json") return config;
2673
- if (format === "yaml") return toYaml(config);
2674
- if (format === "mermaid") return graphToMermaid(graph);
2675
- if (format === "scxml") {
2676
- const machine = config;
2677
- return `<scxml version="1.0" initial="${machine.initial ?? ""}" name="${machine.id ?? "machine"}"></scxml>`;
2678
- }
2679
- return config;
2680
- }
2681
- const colorSchema = _enum([
2682
- "green",
2683
- "red",
2684
- "purple",
2685
- "blue",
2686
- "orange",
2687
- "yellow",
2688
- "pink",
2689
- "teal"
2690
- ]);
2691
- const stateTypeSchema = _enum([
2692
- "normal",
2693
- "parallel",
2694
- "history",
2695
- "final"
2696
- ]).nullable();
2697
- const actionLocationSchema = union([object({
2698
- nodeId: string(),
2699
- group: _enum(["entry", "exit"])
2700
- }), object({
2701
- edgeId: string(),
2702
- group: literal("transition")
2703
- })]);
2704
- const createStatePatchSchema = object({
2705
- op: literal("createState"),
2706
- description: string().optional(),
2707
- id: string().optional(),
2708
- parentId: string(),
2709
- key: string(),
2710
- type: stateTypeSchema.optional(),
2711
- x: number().optional(),
2712
- y: number().optional(),
2713
- color: colorSchema.nullable().optional(),
2714
- initial: boolean().optional()
2715
- });
2716
- const updateStatePatchSchema = object({
2717
- op: literal("updateState"),
2718
- description: string().optional(),
2719
- stateId: string(),
2720
- key: string().optional(),
2721
- type: stateTypeSchema.optional(),
2722
- stateDescription: string().optional(),
2723
- initialId: string().nullable().optional(),
2724
- color: colorSchema.nullable().optional(),
2725
- meta: record(string(), unknown()).nullable().optional(),
2726
- history: _enum(["shallow", "deep"]).nullable().optional()
2727
- });
2728
- const deleteStatePatchSchema = object({
2729
- op: literal("deleteState"),
2730
- description: string().optional(),
2731
- stateId: string()
2732
- });
2733
- const guardSchema = object({
2734
- type: string(),
2735
- code: string().optional(),
2736
- params: record(string(), unknown()).optional()
2737
- });
2738
- const createTransitionPatchSchema = object({
2739
- op: literal("createTransition"),
2740
- description: string().optional(),
2741
- id: string().optional(),
2742
- sourceId: string(),
2743
- targetId: string(),
2744
- eventType: string(),
2745
- transitionType: _enum([
2746
- "normal",
2747
- "targetless",
2748
- "reenter"
2749
- ]).optional(),
2750
- guard: guardSchema.optional(),
2751
- color: colorSchema.nullable().optional(),
2752
- transitionDescription: string().nullable().optional()
2753
- });
2754
- const updateTransitionPatchSchema = object({
2755
- op: literal("updateTransition"),
2756
- description: string().optional(),
2757
- transitionId: string(),
2758
- sourceId: string().optional(),
2759
- targetId: string().optional(),
2760
- eventType: string().optional(),
2761
- transitionType: _enum([
2762
- "normal",
2763
- "targetless",
2764
- "reenter"
2765
- ]).optional(),
2766
- transitionDescription: string().nullable().optional(),
2767
- color: colorSchema.nullable().optional(),
2768
- meta: record(string(), unknown()).nullable().optional()
2769
- });
2770
- const deleteTransitionPatchSchema = object({
2771
- op: literal("deleteTransition"),
2772
- description: string().optional(),
2773
- transitionId: string()
2774
- });
2775
- const createActionPatchSchema = object({
2776
- op: literal("createAction"),
2777
- description: string().optional(),
2778
- location: actionLocationSchema,
2779
- action: object({
2780
- id: string().optional(),
2781
- type: string(),
2782
- params: record(string(), unknown()).optional()
2783
- }),
2784
- index: number().optional()
2785
- });
2786
- const updateActionPatchSchema = object({
2787
- op: literal("updateAction"),
2788
- description: string().optional(),
2789
- location: actionLocationSchema,
2790
- actionId: string(),
2791
- data: object({
2792
- type: string().optional(),
2793
- params: record(string(), unknown()).optional()
2794
- })
2795
- });
2796
- const deleteActionPatchSchema = object({
2797
- op: literal("deleteAction"),
2798
- description: string().optional(),
2799
- location: actionLocationSchema,
2800
- actionId: string()
2801
- });
2802
- const setGuardPatchSchema = object({
2803
- op: literal("setGuard"),
2804
- description: string().optional(),
2805
- edgeId: string(),
2806
- guard: guardSchema
2807
- });
2808
- const deleteGuardPatchSchema = object({
2809
- op: literal("deleteGuard"),
2810
- description: string().optional(),
2811
- edgeId: string()
2812
- });
2813
- const machinePatchSchema = discriminatedUnion("op", [
2814
- createStatePatchSchema,
2815
- updateStatePatchSchema,
2816
- deleteStatePatchSchema,
2817
- createTransitionPatchSchema,
2818
- updateTransitionPatchSchema,
2819
- deleteTransitionPatchSchema,
2820
- createActionPatchSchema,
2821
- updateActionPatchSchema,
2822
- deleteActionPatchSchema,
2823
- setGuardPatchSchema,
2824
- deleteGuardPatchSchema
2825
- ]);
2826
- const validationLevelSchema = _enum([
2827
- "info",
2828
- "warning",
2829
- "error"
2830
- ]);
2831
- const validationIssueKindSchema = _enum([
2832
- "correctness",
2833
- "reachability",
2834
- "optimization",
2835
- "maintainability",
2836
- "security"
2837
- ]);
2838
- const validationIssueCodeSchema = _enum([
2839
- "parse_error",
2840
- "unsupported_format",
2841
- "empty_state_key",
2842
- "invalid_state_key",
2843
- "duplicate_state_key",
2844
- "history_initial",
2845
- "missing_initial",
2846
- "invalid_initial",
2847
- "unreachable_state",
2848
- "final_state_invokes",
2849
- "invalid_state_children",
2850
- "empty_parallel",
2851
- "single_child_compound",
2852
- "noop_transient_state",
2853
- "on_done_unreachable",
2854
- "duplicate_transition",
2855
- "invalid_delay",
2856
- "repeated_guard",
2857
- "transition_never_taken",
2858
- "missing_guard",
2859
- "redundant_transition",
2860
- "eventless_cycle",
2861
- "missing_delay_implementation",
2862
- "invalid_final_transition",
2863
- "invalid_history_transition",
2864
- "parallel_region_transition",
2865
- "duplicate_source_name",
2866
- "undefined_action",
2867
- "undefined_guard",
2868
- "undefined_actor"
2869
- ]);
2870
- const validationFixKindSchema = _enum([
2871
- "patch",
2872
- "rewrite",
2873
- "advice",
2874
- "proposedPatch"
2875
- ]);
2876
- const validationFixSourceSchema = _enum(["deterministic", "llm"]);
2877
- const AUTO_NAME_PREFIXES = [
2878
- ":invocation:",
2879
- "inline:",
2880
- "$auto-"
2881
- ];
2882
- const AUTO_NAME_SUBSTRINGS = [
2883
- ":invocation[",
2884
- "#transition[",
2885
- "#actor["
2886
- ];
2887
- function isAutoGeneratedInlineName(name) {
2888
- return AUTO_NAME_PREFIXES.some((prefix) => name.startsWith(prefix)) || AUTO_NAME_SUBSTRINGS.some((part) => name.includes(part));
2889
- }
2890
- function getImplementations(graph) {
2891
- return graph.data.implementations ?? {
2892
- actions: [],
2893
- guards: [],
2894
- actors: [],
2895
- delays: []
2896
- };
2897
- }
2898
- function issue(input) {
2899
- const nodeIds = input.nodeIds ?? [];
2900
- const edgeIds = input.edgeIds ?? [];
2901
- return {
2902
- code: input.code,
2903
- kind: input.kind,
2904
- level: input.level,
2905
- message: input.message,
2906
- target: {
2907
- nodeIds,
2908
- edgeIds
2909
- },
2910
- metadata: input.metadata,
2911
- fixes: input.fixes,
2912
- source: "deterministic",
2913
- nodeIds,
2914
- edgeIds
2915
- };
2916
- }
2917
- function deleteTransitionFix(edge, title) {
2918
- return {
2919
- kind: "patch",
2920
- source: "deterministic",
2921
- title,
2922
- patches: [{
2923
- op: "deleteTransition",
2924
- transitionId: edge.id
2925
- }],
2926
- confidence: .9
2927
- };
2928
- }
2929
- function setInitialFix(node, child, title) {
2930
- return {
2931
- kind: "patch",
2932
- source: "deterministic",
2933
- title,
2934
- patches: [{
2935
- op: "updateState",
2936
- stateId: node.id,
2937
- initialId: child.id
2938
- }],
2939
- confidence: .85
2940
- };
2941
- }
2942
- function findDuplicateNodes(graph, node, parent) {
2943
- if (!parent) return [];
2944
- return graph.nodes.filter((n) => n.parentId === parent.id && n.id !== node.id).filter((childNode) => childNode.data.key === node.data.key);
2945
- }
2946
- function findDuplicateEdges(graph, edge) {
2947
- return graph.edges.filter((graphEdge) => graphEdge.id !== edge.id && !graphEdge.data.guard && !edge.data.guard && graphEdge.sourceId === edge.sourceId && graphEdge.data.eventType === edge.data.eventType);
2948
- }
2949
- function hasUnconditionalAlways(graph, sourceId) {
2950
- return graph.edges.some((e) => e.sourceId === sourceId && e.data.eventType === "" && !e.data.guard && e.targetId !== e.sourceId);
2951
- }
2952
- function isTransitionPreempted(graph, edge) {
2953
- if (edge.data.eventType === "") return false;
2954
- return hasUnconditionalAlways(graph, edge.sourceId);
2955
- }
2956
- function computeReachableGraphNodes(graph) {
2957
- const rootNode = graph.nodes.find((n) => n.parentId === null);
2958
- if (!rootNode) return /* @__PURE__ */ new Set();
2959
- const visited = /* @__PURE__ */ new Set();
2960
- function getNodeInitialStates(node) {
2961
- const children = graph.nodes.filter((n) => n.parentId === node.id);
2962
- if (!children.length) return [];
2963
- if (node.data.type === "parallel") return children;
2964
- const initial = children.find((n) => node.data.initialId === n.id);
2965
- return initial ? [initial] : [];
2966
- }
2967
- function dfs(node) {
2968
- if (visited.has(node.id)) return;
2969
- visited.add(node.id);
2970
- getNodeInitialStates(node).forEach((n) => dfs(n));
2971
- if (node.data.type === "parallel") graph.nodes.filter((n) => n.parentId === node.id).forEach((region) => {
2972
- getNodeInitialStates(region).forEach((n) => dfs(n));
2973
- });
2974
- if (node.data.type === "history") {
2975
- const parent = graph.nodes.find((n) => n.id === node.parentId);
2976
- if (parent) if (parent.data.type === "parallel") graph.nodes.filter((n) => n.parentId === parent.id).forEach((region) => {
2977
- getNodeInitialStates(region).forEach((n) => dfs(n));
2978
- });
2979
- else getNodeInitialStates(parent).forEach((n) => dfs(n));
2980
- }
2981
- graph.edges.filter((edge) => edge.sourceId === node.id && !isTransitionPreempted(graph, edge)).forEach((edge) => {
2982
- const target = graph.nodes.find((n) => n.id === edge.targetId);
2983
- if (target) dfs(target);
2984
- });
2985
- let ancestor = graph.nodes.find((n) => n.id === node.parentId);
2986
- while (ancestor) {
2987
- graph.edges.filter((edge) => edge.sourceId === ancestor.id && !isTransitionPreempted(graph, edge)).forEach((edge) => {
2988
- const target = graph.nodes.find((n) => n.id === edge.targetId);
2989
- if (target) dfs(target);
2990
- });
2991
- ancestor = graph.nodes.find((n) => n.id === ancestor.parentId);
2992
- }
2993
- }
2994
- dfs(rootNode);
2995
- const reachableWithParents = new Set(visited);
2996
- for (const nodeId of visited) {
2997
- let node = graph.nodes.find((n) => n.id === nodeId);
2998
- while (node?.parentId) {
2999
- reachableWithParents.add(node.parentId);
3000
- node = graph.nodes.find((n) => n.id === node.parentId);
3001
- }
3002
- }
3003
- return reachableWithParents;
3004
- }
3005
- function getNodeErrors(graph, node) {
3006
- const errors = [];
3007
- if (node.data.key.length === 0) errors.push({
3008
- code: "empty_state_key",
3009
- kind: "correctness",
3010
- message: "State key cannot be empty"
3011
- });
3012
- const parent = graph.nodes.find((n) => n.id === node.parentId);
3013
- const isInitial = parent?.data.initialId === node.id;
3014
- const isHistoryNode = node.data.type === "history";
3015
- if (isInitial && isHistoryNode) errors.push({
3016
- code: "history_initial",
3017
- kind: "correctness",
3018
- message: "A history node cannot be the initial node. This will cause an infinite loop."
3019
- });
3020
- if (findDuplicateNodes(graph, node, parent).length > 0) errors.push({
3021
- code: "duplicate_state_key",
3022
- kind: "correctness",
3023
- message: "A state with that name already exists"
3024
- });
3025
- if (node.data.key.includes("#")) errors.push({
3026
- code: "invalid_state_key",
3027
- kind: "correctness",
3028
- message: "State key cannot contain \"#\"",
3029
- metadata: { character: "#" }
3030
- });
3031
- const children = graph.nodes.filter((n) => n.parentId === node.id);
3032
- if (children.length > 0 && node.data.type !== "parallel" && node.data.type !== "final" && node.data.type !== "history") {
3033
- const isTargeted = graph.edges.some((edge) => edge.targetId === node.id);
3034
- if (!node.data.initialId && isTargeted) errors.push({
3035
- code: "missing_initial",
3036
- kind: "correctness",
3037
- message: "Compound state must have an initial state",
3038
- metadata: {
3039
- reason: "targeted_compound",
3040
- childCount: children.length
3041
- },
3042
- fixes: children.length === 1 ? [setInitialFix(node, children[0], `Set ${children[0].data.key} as initial state`)] : void 0
3043
- });
3044
- else if (node.data.initialId) {
3045
- if (!children.some((n) => n.id === node.data.initialId)) errors.push({
3046
- code: "invalid_initial",
3047
- kind: "correctness",
3048
- message: "Initial state does not exist",
3049
- metadata: {
3050
- initialId: node.data.initialId,
3051
- childCount: children.length
3052
- },
3053
- fixes: children.length === 1 ? [setInitialFix(node, children[0], `Set ${children[0].data.key} as initial state`)] : void 0
3054
- });
3055
- }
3056
- }
3057
- return errors.length > 0 ? errors : null;
3058
- }
3059
- function getNodeWarningsWithReachability(graph, node, reachableNodes) {
3060
- const warnings = [];
3061
- if (!reachableNodes.has(node.id) && node.data.type !== "history") warnings.push({
3062
- code: "unreachable_state",
3063
- kind: "reachability",
3064
- message: "Unreachable state"
3065
- });
3066
- if (node.data.type === "final" && (node.data.invokes?.length ?? 0) > 0) warnings.push({
3067
- code: "final_state_invokes",
3068
- kind: "correctness",
3069
- message: "Final state cannot have invocations"
3070
- });
3071
- if (node.data.type && ["final", "history"].includes(node.data.type) && graph.nodes.filter((n) => n.parentId === node.id).length > 0) warnings.push({
3072
- code: "invalid_state_children",
3073
- kind: "correctness",
3074
- message: `${node.data.type.charAt(0).toUpperCase() + node.data.type.slice(1)} state cannot have child states`,
3075
- metadata: { stateType: node.data.type }
3076
- });
3077
- if (node.data.type === "parallel" && graph.nodes.filter((n) => n.parentId === node.id).length === 0) warnings.push({
3078
- code: "empty_parallel",
3079
- kind: "correctness",
3080
- message: "Parallel state should have child regions"
3081
- });
3082
- const children = graph.nodes.filter((n) => n.parentId === node.id);
3083
- if (children.length === 1 && node.data.type !== "parallel" && node.data.type !== "final" && node.data.type !== "history") warnings.push({
3084
- code: "single_child_compound",
3085
- kind: "optimization",
3086
- message: "State with single child can be simplified"
3087
- });
3088
- if (node.data.type !== "parallel" && node.data.type !== "final" && node.data.type !== "history" && children.length === 0 && (node.data.entry?.length ?? 0) === 0 && (node.data.exit?.length ?? 0) === 0 && (node.data.invokes?.length ?? 0) === 0) {
3089
- const alwaysEdges = graph.edges.filter((e) => e.sourceId === node.id && e.data.eventType === "");
3090
- const hasUnconditionalNoop = alwaysEdges.some((e) => !e.data.guard && e.targetId !== e.sourceId && (e.data.actions?.length ?? 0) === 0);
3091
- const hasMeaningfulAlways = alwaysEdges.some((e) => !!e.data.guard || (e.data.actions?.length ?? 0) > 0);
3092
- if (hasUnconditionalNoop && !hasMeaningfulAlways) warnings.push({
3093
- code: "noop_transient_state",
3094
- kind: "optimization",
3095
- message: "State has no effect: an unconditional transition exits it immediately, and it has no actions or invocations"
3096
- });
3097
- }
3098
- return warnings.length > 0 ? warnings : null;
3099
- }
3100
- function getOnDoneWarnings(graph, reachableNodes) {
3101
- const results = [];
3102
- for (const node of graph.nodes) {
3103
- const doneEdges = graph.edges.filter((edge) => edge.sourceId === node.id && (edge.data.eventType.startsWith("@statelyai.state.done.") || edge.data.eventType.startsWith("xstate.done.state.")));
3104
- if (doneEdges.length === 0) continue;
3105
- const edgeIds = doneEdges.map((e) => e.id);
3106
- if (node.data.type === "parallel") {
3107
- const regions = graph.nodes.filter((n) => n.parentId === node.id);
3108
- const regionsWithoutFinal = [];
3109
- const regionsWithoutReachableFinal = [];
3110
- for (const region of regions) {
3111
- const finalChildren = graph.nodes.filter((n) => n.parentId === region.id).filter((child) => child.data.type === "final");
3112
- if (finalChildren.length === 0) regionsWithoutFinal.push(region);
3113
- else if (!finalChildren.some((child) => reachableNodes.has(child.id))) regionsWithoutReachableFinal.push(region);
3114
- }
3115
- if (regionsWithoutFinal.length > 0) results.push(issue({
3116
- code: "on_done_unreachable",
3117
- kind: "reachability",
3118
- level: "warning",
3119
- nodeIds: [node.id],
3120
- edgeIds,
3121
- message: "onDone transitions require each parallel region to have a final child state",
3122
- metadata: { reason: "parallel_region_missing_final" }
3123
- }));
3124
- else if (regionsWithoutReachableFinal.length > 0) results.push(issue({
3125
- code: "on_done_unreachable",
3126
- kind: "reachability",
3127
- level: "warning",
3128
- nodeIds: [node.id],
3129
- edgeIds,
3130
- message: "onDone transitions will never trigger: a parallel region has no reachable final child state",
3131
- metadata: { reason: "parallel_region_final_unreachable" }
3132
- }));
3133
- } else {
3134
- const finalChildren = graph.nodes.filter((n) => n.parentId === node.id).filter((child) => child.data.type === "final");
3135
- if (finalChildren.length === 0) results.push(issue({
3136
- code: "on_done_unreachable",
3137
- kind: "reachability",
3138
- level: "warning",
3139
- nodeIds: [node.id],
3140
- edgeIds,
3141
- message: "onDone transitions require a final child state to trigger",
3142
- metadata: { reason: "missing_final_child" }
3143
- }));
3144
- else if (!finalChildren.some((child) => reachableNodes.has(child.id))) results.push(issue({
3145
- code: "on_done_unreachable",
3146
- kind: "reachability",
3147
- level: "warning",
3148
- nodeIds: [node.id],
3149
- edgeIds,
3150
- message: "onDone transitions will never trigger: no reachable final child state",
3151
- metadata: { reason: "final_child_unreachable" }
3152
- }));
3153
- }
3154
- }
3155
- return results;
3156
- }
3157
- function getEventlessCycleErrors(graph) {
3158
- const eventlessEdges = graph.edges.filter((edge) => edge.data.eventType === "" && !edge.data.guard);
3159
- const bySource = /* @__PURE__ */ new Map();
3160
- for (const edge of eventlessEdges) {
3161
- const edges = bySource.get(edge.sourceId) ?? [];
3162
- edges.push(edge);
3163
- bySource.set(edge.sourceId, edges);
3164
- }
3165
- const results = [];
3166
- const reported = /* @__PURE__ */ new Set();
3167
- for (const startEdge of eventlessEdges) {
3168
- const stack = [{
3169
- nodeId: startEdge.targetId,
3170
- path: [startEdge]
3171
- }];
3172
- const seen = /* @__PURE__ */ new Set();
3173
- while (stack.length > 0) {
3174
- const current = stack.pop();
3175
- if (current.nodeId === startEdge.sourceId) {
3176
- const edgeIds = current.path.map((edge) => edge.id);
3177
- const key = [...edgeIds].sort().join("|");
3178
- if (!reported.has(key)) {
3179
- reported.add(key);
3180
- results.push(issue({
3181
- code: "eventless_cycle",
3182
- kind: "correctness",
3183
- level: "error",
3184
- edgeIds,
3185
- nodeIds: Array.from(new Set(current.path.flatMap((edge) => [edge.sourceId, edge.targetId]))),
3186
- message: "Unguarded eventless transitions form an infinite transition loop",
3187
- metadata: { edgeIds }
3188
- }));
3189
- }
3190
- continue;
3191
- }
3192
- if (seen.has(current.nodeId)) continue;
3193
- seen.add(current.nodeId);
3194
- for (const nextEdge of bySource.get(current.nodeId) ?? []) stack.push({
3195
- nodeId: nextEdge.targetId,
3196
- path: [...current.path, nextEdge]
3197
- });
3198
- }
3199
- }
3200
- return results;
3201
- }
3202
- function getEdgeErrors(graph, edge) {
3203
- const errors = [];
3204
- const duplicates = findDuplicateEdges(graph, edge);
3205
- if (duplicates.length > 0) errors.push({
3206
- code: "duplicate_transition",
3207
- kind: "correctness",
3208
- message: "Two events with the same name and source state are not allowed",
3209
- fixes: graph.edges.findIndex((e) => e.id === edge.id) > graph.edges.findIndex((e) => e.id === duplicates[0].id) ? [deleteTransitionFix(edge, "Delete duplicate transition")] : void 0
3210
- });
3211
- if (edge.data.eventType.startsWith("xstate.after.")) {
3212
- const delayPart = edge.data.eventType.replace(/^xstate\.after\./, "").split(".")[0];
3213
- if (delayPart !== void 0) {
3214
- const numericValue = Number(delayPart);
3215
- if (!Number.isNaN(numericValue) && numericValue < 0) errors.push({
3216
- code: "invalid_delay",
3217
- kind: "correctness",
3218
- message: "Delay value cannot be negative",
3219
- metadata: { delay: numericValue }
3220
- });
3221
- }
3222
- }
3223
- return errors.length > 0 ? errors : null;
3224
- }
3225
- function getEdgeWarnings(graph, edge) {
3226
- const warnings = [];
3227
- const group = graph.edges.filter((e) => e.sourceId === edge.sourceId && e.data.eventType === edge.data.eventType);
3228
- if (edge.data.guard && group.length > 1) {
3229
- const guardType = edge.data.guard.type;
3230
- const withRepeatedGuard = group.filter((e) => e.data.guard?.type === guardType);
3231
- if (withRepeatedGuard.length > 1) warnings.push({
3232
- code: "repeated_guard",
3233
- kind: "maintainability",
3234
- message: `Found ${withRepeatedGuard.length} guards with the name \`${guardType}\``,
3235
- metadata: {
3236
- guardType,
3237
- count: withRepeatedGuard.length
3238
- }
3239
- });
3240
- }
3241
- const edgeIndex = group.findIndex((e) => e.id === edge.id);
3242
- if (edgeIndex > 0 && group.slice(0, edgeIndex).some((e) => !e.data.guard) || isTransitionPreempted(graph, edge)) warnings.push({
3243
- code: "transition_never_taken",
3244
- kind: "reachability",
3245
- message: "This transition will never be taken",
3246
- fixes: edgeIndex > 0 && group.slice(0, edgeIndex).some((e) => !e.data.guard) ? [deleteTransitionFix(edge, "Delete unreachable transition")] : void 0
3247
- });
3248
- if (group.length > 1 && !edge.data.guard && edgeIndex < group.length - 1) warnings.push({
3249
- code: "missing_guard",
3250
- kind: "correctness",
3251
- message: "This transition is missing a guard, so transitions after it will never be taken"
3252
- });
3253
- if (edgeIndex > 0) {
3254
- const prevEdge = group[edgeIndex - 1];
3255
- const sameTarget = prevEdge.targetId === edge.targetId;
3256
- const sameActions = JSON.stringify(prevEdge.data.actions) === JSON.stringify(edge.data.actions);
3257
- if (sameTarget && sameActions) warnings.push({
3258
- code: "redundant_transition",
3259
- kind: "optimization",
3260
- message: "Redundant transition: same target and actions as previous transition",
3261
- fixes: [deleteTransitionFix(edge, "Delete redundant transition")]
3262
- });
3263
- }
3264
- if (edge.data.eventType.startsWith("xstate.after.")) {
3265
- const delayPart = edge.data.eventType.replace(/^xstate\.after\./, "").split(".")[0];
3266
- if (delayPart !== void 0 && Number.isNaN(Number(delayPart))) {
3267
- if (!getImplementations(graph).delays.find((d) => d.name === delayPart)?.code?.body?.trim()) warnings.push({
3268
- code: "missing_delay_implementation",
3269
- kind: "correctness",
3270
- message: `Missing delay implementation for "${delayPart}"`,
3271
- metadata: { delay: delayPart }
3272
- });
3273
- }
3274
- }
3275
- const sourceNode = graph.nodes.find((n) => n.id === edge.sourceId);
3276
- if (sourceNode && sourceNode.data.type === "final") warnings.push({
3277
- code: "invalid_final_transition",
3278
- kind: "correctness",
3279
- message: "Final state cannot transition to other states",
3280
- fixes: [deleteTransitionFix(edge, "Delete transition from final state")]
3281
- });
3282
- if (sourceNode && sourceNode.data.type === "history") warnings.push({
3283
- code: "invalid_history_transition",
3284
- kind: "correctness",
3285
- message: "History state cannot have transitions",
3286
- fixes: [deleteTransitionFix(edge, "Delete transition from history state")]
3287
- });
3288
- const targetNode = graph.nodes.find((n) => n.id === edge.targetId);
3289
- if (sourceNode && targetNode && sourceNode.parentId === targetNode.parentId && sourceNode.id !== targetNode.id) {
3290
- const parentNode = graph.nodes.find((n) => n.id === sourceNode.parentId);
3291
- if (parentNode && parentNode.data.type === "parallel") warnings.push({
3292
- code: "parallel_region_transition",
3293
- kind: "correctness",
3294
- message: "Parallel regions should communicate via events, not transitions"
3295
- });
3296
- }
3297
- return warnings.length > 0 ? warnings : null;
3298
- }
3299
- function getSourceWarnings(graph) {
3300
- const results = [];
3301
- const impls = getImplementations(graph);
3302
- const groups = [
3303
- {
3304
- type: "action",
3305
- items: impls.actions
3306
- },
3307
- {
3308
- type: "guard",
3309
- items: impls.guards
3310
- },
3311
- {
3312
- type: "actor",
3313
- items: impls.actors
3314
- },
3315
- {
3316
- type: "delay",
3317
- items: impls.delays
3318
- }
3319
- ];
3320
- for (const group of groups) {
3321
- const seen = /* @__PURE__ */ new Map();
3322
- for (const item of group.items) {
3323
- if (!item.name) continue;
3324
- seen.set(item.name, (seen.get(item.name) ?? 0) + 1);
3325
- }
3326
- for (const [name, count] of seen) if (count > 1) results.push(issue({
3327
- code: "duplicate_source_name",
3328
- kind: "maintainability",
3329
- level: "warning",
3330
- message: `Duplicate ${group.type} source name "${name}" (${count} occurrences)`,
3331
- metadata: {
3332
- sourceType: group.type,
3333
- name,
3334
- count
3335
- }
3336
- }));
3337
- }
3338
- return results;
3339
- }
3340
- function getSourceRestrictionErrors(graph, sourceAllowed) {
3341
- if (!sourceAllowed) return [];
3342
- const results = [];
3343
- const impls = getImplementations(graph);
3344
- const actionIds = new Set(impls.actions.map((a) => a.id));
3345
- const guardIds = new Set(impls.guards.map((g) => g.id));
3346
- const actorIds = new Set(impls.actors.map((a) => a.id));
3347
- const isInvalidActionType = (type) => {
3348
- if (!type) return true;
3349
- if (actionIds.has(type)) return false;
3350
- if (type.startsWith("xstate.")) return false;
3351
- if (isAutoGeneratedInlineName(type)) return false;
3352
- return true;
3353
- };
3354
- const formatActionMsg = (type) => type ? `Action "${type}" is not a defined action implementation` : "Action is not selected";
3355
- if (sourceAllowed.actions === "predefined") {
3356
- for (const node of graph.nodes) {
3357
- for (const action of node.data.entry ?? []) if (isInvalidActionType(action.type)) results.push(issue({
3358
- code: "undefined_action",
3359
- kind: "correctness",
3360
- level: "error",
3361
- nodeIds: [node.id],
3362
- message: formatActionMsg(action.type)
3363
- }));
3364
- for (const action of node.data.exit ?? []) if (isInvalidActionType(action.type)) results.push(issue({
3365
- code: "undefined_action",
3366
- kind: "correctness",
3367
- level: "error",
3368
- nodeIds: [node.id],
3369
- message: formatActionMsg(action.type)
3370
- }));
3371
- }
3372
- for (const edge of graph.edges) for (const action of edge.data.actions ?? []) if (isInvalidActionType(action.type)) results.push(issue({
3373
- code: "undefined_action",
3374
- kind: "correctness",
3375
- level: "error",
3376
- edgeIds: [edge.id],
3377
- message: formatActionMsg(action.type)
3378
- }));
3379
- }
3380
- if (sourceAllowed.guards === "predefined") for (const edge of graph.edges) {
3381
- const guard = edge.data.guard;
3382
- if (!guard) continue;
3383
- const type = guard.type;
3384
- if (!type || !guardIds.has(type) && !type.startsWith("inline:") && !isAutoGeneratedInlineName(type)) results.push(issue({
3385
- code: "undefined_guard",
3386
- kind: "correctness",
3387
- level: "error",
3388
- edgeIds: [edge.id],
3389
- message: type ? `Guard "${type}" is not a defined guard implementation` : "Guard is not selected"
3390
- }));
3391
- }
3392
- if (sourceAllowed.actors === "predefined") for (const node of graph.nodes) for (const invoke of node.data.invokes ?? []) {
3393
- const src = invoke.src;
3394
- if (!src || !actorIds.has(src) && !isAutoGeneratedInlineName(src)) results.push(issue({
3395
- code: "undefined_actor",
3396
- kind: "correctness",
3397
- level: "error",
3398
- nodeIds: [node.id],
3399
- message: src ? `Actor "${src}" is not a defined actor implementation` : "Actor is not selected"
3400
- }));
3401
- }
3402
- return results;
3403
- }
3404
- function pushMessages(results, messages, level, nodeIds, edgeIds) {
3405
- messages?.forEach((message) => {
3406
- results.push(issue({
3407
- ...message,
3408
- level,
3409
- nodeIds,
3410
- edgeIds
3411
- }));
3412
- });
3413
- }
3414
- function validateGraph(graph, sourceAllowed) {
3415
- const results = [];
3416
- const nonTempNodes = graph.nodes.filter((n) => !n.data?.temp);
3417
- const nonTempEdges = graph.edges.filter((e) => !e.data?.temp);
3418
- const nonTempGraph = {
3419
- ...graph,
3420
- nodes: nonTempNodes,
3421
- edges: nonTempEdges
3422
- };
3423
- const reachableNodes = computeReachableGraphNodes(nonTempGraph);
3424
- for (const node of nonTempNodes) {
3425
- pushMessages(results, getNodeErrors(nonTempGraph, node), "error", [node.id], []);
3426
- pushMessages(results, getNodeWarningsWithReachability(nonTempGraph, node, reachableNodes), "warning", [node.id], []);
3427
- }
3428
- for (const edge of nonTempEdges) {
3429
- pushMessages(results, getEdgeErrors(nonTempGraph, edge), "error", [], [edge.id]);
3430
- pushMessages(results, getEdgeWarnings(nonTempGraph, edge), "warning", [], [edge.id]);
3431
- }
3432
- results.push(...getOnDoneWarnings(nonTempGraph, reachableNodes));
3433
- results.push(...getEventlessCycleErrors(nonTempGraph));
3434
- results.push(...getSourceWarnings(nonTempGraph));
3435
- results.push(...getSourceRestrictionErrors(nonTempGraph, sourceAllowed));
3436
- return results;
3437
- }
3438
- function validationOk(issues) {
3439
- return !issues.some((item) => item.level === "error");
3440
- }
3441
- const validationIssueTargetSchema = object({
3442
- nodeIds: array(string()).optional(),
3443
- edgeIds: array(string()).optional(),
3444
- source: object({
3445
- path: string().optional(),
3446
- start: number().optional(),
3447
- end: number().optional()
3448
- }).optional()
3449
- });
3450
- const validationFixSchema = object({
3451
- kind: validationFixKindSchema,
3452
- source: validationFixSourceSchema,
3453
- title: string(),
3454
- patches: array(unknown()).optional(),
3455
- output: unknown().optional(),
3456
- diff: unknown().optional(),
3457
- confidence: number().optional(),
3458
- rationale: string().optional(),
3459
- rank: number().optional(),
3460
- postValidation: object({
3461
- ok: boolean(),
3462
- remainingIssueCodes: array(validationIssueCodeSchema)
3463
- }).optional()
3464
- });
3465
- const validationIssueSchema = object({
3466
- code: validationIssueCodeSchema,
3467
- kind: validationIssueKindSchema,
3468
- level: validationLevelSchema,
3469
- message: string(),
3470
- target: validationIssueTargetSchema,
3471
- metadata: record(string(), unknown()).optional(),
3472
- fixes: array(validationFixSchema).optional(),
3473
- source: _enum(["deterministic", "llm"]).optional(),
3474
- nodeIds: array(string()),
3475
- edgeIds: array(string())
3476
- });
3477
- const machineFormatSchema = _enum([
3478
- "xstate",
3479
- "json",
3480
- "yaml",
3481
- "scxml",
3482
- "mermaid"
3483
- ]);
3484
- const xstateSourceProfileSchema = _enum([
3485
- "auto",
3486
- "xstate-v5",
3487
- "xstate-v6-alpha"
3488
- ]);
3489
- const machineInputSchema = object({
3490
- machine: union([record(string(), unknown()), string()]),
3491
- format: machineFormatSchema.optional(),
3492
- profile: xstateSourceProfileSchema.optional(),
3493
- xstateVersion: union([literal(5), literal(6)]).optional()
3494
- });
3495
- const graphSchema = record(string(), unknown());
3496
- function asError(error) {
3497
- return { message: error instanceof Error ? error.message : String(error) };
3498
- }
3499
- function simImplementations(graph, guardState) {
3500
- const guards = {};
3501
- for (const edge of graph.edges) if (edge.data.guard) {
3502
- const guardType = edge.data.guard.type;
3503
- guards[guardType] = ({ event }) => {
3504
- if (event["@xstate.guard"] === guardType) return true;
3505
- return guardState?.[guardType] ?? false;
3506
- };
3507
- }
3508
- return { guards };
3509
- }
3510
- const operations = {
3511
- validate_machine: {
3512
- description: "Validate a machine without writing files.",
3513
- input: machineInputSchema,
3514
- output: object({
3515
- ok: boolean(),
3516
- issues: array(validationIssueSchema)
3517
- }),
3518
- async handler(input) {
3519
- try {
3520
- const issues = validateGraph(machineInputToGraph(input));
3521
- return {
3522
- ok: validationOk(issues),
3523
- issues
3524
- };
3525
- } catch (error) {
3526
- return {
3527
- ok: false,
3528
- issues: [{
3529
- code: "parse_error",
3530
- kind: "correctness",
3531
- level: "error",
3532
- message: asError(error).message,
3533
- target: {
3534
- nodeIds: [],
3535
- edgeIds: []
3536
- },
3537
- source: "deterministic",
3538
- nodeIds: [],
3539
- edgeIds: []
3540
- }]
3541
- };
3542
- }
3543
- }
3544
- },
3545
- convert_to_graph: {
3546
- description: "Convert a machine into an internal graph for patching.",
3547
- input: machineInputSchema,
3548
- output: object({ graph: graphSchema }),
3549
- async handler(input) {
3550
- return { graph: machineInputToGraph(input) };
3551
- }
3552
- },
3553
- convert_format: {
3554
- description: "Convert a machine or graph to a machine format.",
3555
- input: object({
3556
- machine: union([record(string(), unknown()), string()]).optional(),
3557
- graph: graphSchema.optional(),
3558
- format: machineFormatSchema.optional(),
3559
- to: machineFormatSchema
3560
- }).refine((value) => Boolean(value.machine) !== Boolean(value.graph), { message: "Provide exactly one of machine or graph." }),
3561
- output: object({
3562
- format: machineFormatSchema,
3563
- output: union([record(string(), unknown()), string()])
3564
- }),
3565
- async handler(input) {
3566
- const graph = input.graph ?? machineInputToGraph({
3567
- machine: input.machine,
3568
- format: input.format
3569
- });
3570
- return {
3571
- format: input.to,
3572
- output: graphToFormat(graph, input.to)
3573
- };
3574
- }
3575
- },
3576
- apply_patch: {
3577
- description: "Apply semantic machine patches to a graph without writing files.",
3578
- input: object({
3579
- graph: graphSchema,
3580
- patches: array(machinePatchSchema),
3581
- outputFormat: machineFormatSchema.default("json")
3582
- }),
3583
- output: union([object({
3584
- ok: literal(true),
3585
- graph: graphSchema,
3586
- machine: union([record(string(), unknown()), string()]),
3587
- format: machineFormatSchema,
3588
- diff: unknown(),
3589
- patches: array(machinePatchSchema)
3590
- }), object({
3591
- ok: literal(false),
3592
- reason: string(),
3593
- errors: array(object({ message: string() }))
3594
- })]),
3595
- async handler(input) {
3596
- try {
3597
- const graph = applyGraphPatches(input.graph, input.patches);
3598
- createMachine(graphToMachineConfig(graph));
3599
- const diff = diffGraphsSemantically(input.graph, graph).diff;
3600
- return {
3601
- ok: true,
3602
- graph,
3603
- machine: graphToFormat(graph, input.outputFormat),
3604
- format: input.outputFormat,
3605
- diff,
3606
- patches: input.patches
3607
- };
3608
- } catch (error) {
3609
- return {
3610
- ok: false,
3611
- reason: "validation",
3612
- errors: [asError(error)]
3613
- };
3614
- }
3615
- }
3616
- },
3617
- diff_machines: {
3618
- description: "Compute semantic diff between two machines.",
3619
- input: object({
3620
- left: machineInputSchema,
3621
- right: machineInputSchema,
3622
- includePatches: boolean().optional()
3623
- }),
3624
- output: object({
3625
- diff: unknown(),
3626
- patches: array(machinePatchSchema).optional()
3627
- }),
3628
- async handler(input) {
3629
- return { diff: diffGraphsSemantically(machineInputToGraph(input.left), machineInputToGraph(input.right)).diff };
3630
- }
3631
- },
3632
- generate_paths: {
3633
- description: "Generate paths through a machine.",
3634
- input: machineInputSchema.extend({
3635
- targetPath: array(string()).optional(),
3636
- kind: _enum(["shortest", "simple"]).default("shortest"),
3637
- max: number().int().positive().optional()
3638
- }),
3639
- output: object({ paths: array(unknown()) }),
3640
- async handler(input) {
3641
- const result = generateGraphPathsData(machineInputToGraph(input), {
3642
- strategy: input.kind,
3643
- reduceDuplicates: false,
3644
- preferTransitionCoverage: false,
3645
- limit: input.max
3646
- });
3647
- return { paths: input.targetPath ? result.paths.filter((path) => path.finalStatePath === input.targetPath.join(".")) : result.paths };
3648
- }
3649
- },
3650
- simulate: {
3651
- description: "Simulate one event from a machine state.",
3652
- input: machineInputSchema.extend({
3653
- stateValue: unknown(),
3654
- event: object({ type: string() }).passthrough(),
3655
- guardState: record(string(), boolean()).optional()
3656
- }),
3657
- output: object({
3658
- nextStateValue: unknown(),
3659
- actions: array(unknown()),
3660
- changed: boolean(),
3661
- microsteps: array(unknown()).optional()
3662
- }),
3663
- async handler(input) {
3664
- const graph = machineInputToGraph(input);
3665
- const machine = createMachine(graphToMachineConfig(graph)).provide(simImplementations(graph, input.guardState));
3666
- const snapshot = machine.resolveState({
3667
- value: input.stateValue,
3668
- context: {},
3669
- status: "active"
3670
- });
3671
- const [nextSnapshot, actions] = transition(machine, snapshot, input.event);
3672
- return {
3673
- nextStateValue: nextSnapshot.value,
3674
- actions,
3675
- changed: JSON.stringify(nextSnapshot.value) !== JSON.stringify(snapshot.value)
3676
- };
3677
- }
3678
- }
3679
- };
3680
-
3681
110
  //#endregion
3682
111
  //#region ../editor-sync/src/internal/sourceLocations.ts
3683
112
  const sourceFileUris = /* @__PURE__ */ new WeakMap();
@@ -4283,10 +712,6 @@ function offsetAtRangePosition(text, line, character) {
4283
712
  return text.length;
4284
713
  }
4285
714
 
4286
- //#endregion
4287
- //#region ../graph-tools/src/codegenTypes.ts
4288
- const INLINE_EXPRESSION_DIRECTIVE_FIELDS = ["input", "output"];
4289
-
4290
715
  //#endregion
4291
716
  //#region src/sync.ts
4292
717
  const execFileAsync = promisify(execFile);