@statelyai/sdk 0.26.3 → 0.27.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -2
- package/dist/auth.d.mts +1 -1
- package/dist/{editorHostSession-DFjM-RoT.d.mts → editorHostSession-CWAuoMtC.d.mts} +9 -3
- package/dist/{editorHostSession-CwGeXJ6g.mjs → editorHostSession-shrjfosC.mjs} +6 -3
- package/dist/editorHostSession.d.mts +1 -1
- package/dist/editorHostSession.mjs +1 -1
- package/dist/embed.d.mts +6 -3
- package/dist/embed.mjs +23 -1
- package/dist/embedPanel-Bpbq4sfJ.d.mts +13 -0
- package/dist/{graph-DoUReqxz.d.mts → graph-BwHnhzpC.d.mts} +48 -6
- package/dist/graph.d.mts +1 -1
- package/dist/graph.mjs +36 -4
- package/dist/{index-DKRB-u93.d.mts → index-D2I1qPsf.d.mts} +19 -3
- package/dist/index.d.mts +8 -7
- package/dist/index.mjs +6 -6
- package/dist/{inspect-CiPPDR7Q.mjs → inspect-CebH2l6F.mjs} +3 -5
- package/dist/{inspect-Br_vS_lK.d.mts → inspect-D61HS-t7.d.mts} +2 -5
- package/dist/inspect.d.mts +1 -1
- package/dist/inspect.mjs +2 -2
- package/dist/patchTypes.d.mts +1 -1
- package/dist/protocol.d.mts +46 -15
- package/dist/{statelyPragma-B-axv279.mjs → statelyPragma-ByF0-L-N.mjs} +1198 -31
- package/dist/{studio-B6P177N3.d.mts → studio-B9-1Ynrx.d.mts} +4 -2
- package/dist/{studio-iNSHtNmP.mjs → studio-CNkxMZyz.mjs} +4 -1
- package/dist/studio.d.mts +1 -1
- package/dist/studio.mjs +1 -1
- package/dist/sync.d.mts +12 -4
- package/dist/sync.mjs +2920 -262
- package/dist/{version-At1lONTa.mjs → version-usAEzut8.mjs} +19 -7
- package/package.json +3 -3
- package/schemas/xstate.schema.json +41 -5
- /package/dist/{auth-B0ZTKri2.d.mts → auth-BPyCCHYo.d.mts} +0 -0
- /package/dist/{editor-sync-types-C-zWaaVk.d.mts → editor-sync-types-CTKArS8n.d.mts} +0 -0
|
@@ -2334,6 +2334,57 @@ var index_vite_proxy_tmp_default = import_js_yaml.default;
|
|
|
2334
2334
|
|
|
2335
2335
|
//#endregion
|
|
2336
2336
|
//#region ../graph-tools/dist/index.mjs
|
|
2337
|
+
/**
|
|
2338
|
+
* Convert a resolved {@link GraphNode} back into a {@link NodeConfig}.
|
|
2339
|
+
*
|
|
2340
|
+
* Faithful and complete: round-tripping through `createGraphNode` yields a
|
|
2341
|
+
* deep-equal node. Optional fields are only included when present; ports are
|
|
2342
|
+
* deep-copied so the config does not share port objects with the source node.
|
|
2343
|
+
*/
|
|
2344
|
+
function toNodeConfig(node) {
|
|
2345
|
+
const config = { id: node.id };
|
|
2346
|
+
if (node.parentId != null) config.parentId = node.parentId;
|
|
2347
|
+
if (node.initialNodeId != null) config.initialNodeId = node.initialNodeId;
|
|
2348
|
+
if (node.label != null) config.label = node.label;
|
|
2349
|
+
if (node.data != null) config.data = node.data;
|
|
2350
|
+
if (node.ports !== void 0) config.ports = node.ports.map((p) => ({ ...p }));
|
|
2351
|
+
if (node.x !== void 0) config.x = node.x;
|
|
2352
|
+
if (node.y !== void 0) config.y = node.y;
|
|
2353
|
+
if (node.width !== void 0) config.width = node.width;
|
|
2354
|
+
if (node.height !== void 0) config.height = node.height;
|
|
2355
|
+
if (node.shape !== void 0) config.shape = node.shape;
|
|
2356
|
+
if (node.color !== void 0) config.color = node.color;
|
|
2357
|
+
if (node.style !== void 0) config.style = node.style;
|
|
2358
|
+
return config;
|
|
2359
|
+
}
|
|
2360
|
+
/**
|
|
2361
|
+
* Convert a resolved {@link GraphEdge} back into an {@link EdgeConfig}.
|
|
2362
|
+
*
|
|
2363
|
+
* Faithful and complete: round-tripping through `createGraphEdge` yields a
|
|
2364
|
+
* deep-equal edge. Optional fields are only included when present.
|
|
2365
|
+
*/
|
|
2366
|
+
function toEdgeConfig(edge) {
|
|
2367
|
+
const config = {
|
|
2368
|
+
id: edge.id,
|
|
2369
|
+
sourceId: edge.sourceId,
|
|
2370
|
+
targetId: edge.targetId
|
|
2371
|
+
};
|
|
2372
|
+
if (edge.label != null) config.label = edge.label;
|
|
2373
|
+
if (edge.data != null) config.data = edge.data;
|
|
2374
|
+
if (edge.weight !== void 0) config.weight = edge.weight;
|
|
2375
|
+
if (edge.mode !== void 0) config.mode = edge.mode;
|
|
2376
|
+
if (edge.sourcePort !== void 0) config.sourcePort = edge.sourcePort;
|
|
2377
|
+
if (edge.targetPort !== void 0) config.targetPort = edge.targetPort;
|
|
2378
|
+
if (edge.points !== void 0) config.points = edge.points.map((p) => ({ ...p }));
|
|
2379
|
+
if (edge.routing !== void 0) config.routing = edge.routing;
|
|
2380
|
+
if (edge.x !== void 0) config.x = edge.x;
|
|
2381
|
+
if (edge.y !== void 0) config.y = edge.y;
|
|
2382
|
+
if (edge.width !== void 0) config.width = edge.width;
|
|
2383
|
+
if (edge.height !== void 0) config.height = edge.height;
|
|
2384
|
+
if (edge.color !== void 0) config.color = edge.color;
|
|
2385
|
+
if (edge.style !== void 0) config.style = edge.style;
|
|
2386
|
+
return config;
|
|
2387
|
+
}
|
|
2337
2388
|
const LAYOUT_KEYS = {
|
|
2338
2389
|
node: [
|
|
2339
2390
|
"x",
|
|
@@ -2356,6 +2407,872 @@ const LAYOUT_KEYS = {
|
|
|
2356
2407
|
]
|
|
2357
2408
|
};
|
|
2358
2409
|
new Set(LAYOUT_KEYS.node), new Set(LAYOUT_KEYS.edge);
|
|
2410
|
+
/** Shallow-compare two values, returning true if they differ. */
|
|
2411
|
+
function differs(a, b) {
|
|
2412
|
+
if (a === b) return false;
|
|
2413
|
+
if (a == null || b == null) return a !== b;
|
|
2414
|
+
if (typeof a === "object" && typeof b === "object") return JSON.stringify(a) !== JSON.stringify(b);
|
|
2415
|
+
return true;
|
|
2416
|
+
}
|
|
2417
|
+
const NODE_COMPARE_KEYS = [
|
|
2418
|
+
"parentId",
|
|
2419
|
+
"initialNodeId",
|
|
2420
|
+
"label",
|
|
2421
|
+
"data",
|
|
2422
|
+
"ports",
|
|
2423
|
+
"x",
|
|
2424
|
+
"y",
|
|
2425
|
+
"width",
|
|
2426
|
+
"height",
|
|
2427
|
+
"shape",
|
|
2428
|
+
"color",
|
|
2429
|
+
"style"
|
|
2430
|
+
];
|
|
2431
|
+
const EDGE_COMPARE_KEYS = [
|
|
2432
|
+
"sourceId",
|
|
2433
|
+
"targetId",
|
|
2434
|
+
"label",
|
|
2435
|
+
"data",
|
|
2436
|
+
"weight",
|
|
2437
|
+
"mode",
|
|
2438
|
+
"sourcePort",
|
|
2439
|
+
"targetPort",
|
|
2440
|
+
"points",
|
|
2441
|
+
"routing",
|
|
2442
|
+
"x",
|
|
2443
|
+
"y",
|
|
2444
|
+
"width",
|
|
2445
|
+
"height",
|
|
2446
|
+
"color",
|
|
2447
|
+
"style"
|
|
2448
|
+
];
|
|
2449
|
+
/**
|
|
2450
|
+
* Compute a structured diff from graph `a` to graph `b` by matching IDs.
|
|
2451
|
+
*
|
|
2452
|
+
* @example
|
|
2453
|
+
* ```ts
|
|
2454
|
+
* import { createGraph, getDiff } from '@statelyai/graph';
|
|
2455
|
+
*
|
|
2456
|
+
* const a = createGraph({ nodes: [{ id: 'n1' }], edges: [] });
|
|
2457
|
+
* const b = createGraph({ nodes: [{ id: 'n1', label: 'hello' }, { id: 'n2' }], edges: [] });
|
|
2458
|
+
*
|
|
2459
|
+
* const diff = getDiff(a, b);
|
|
2460
|
+
* // diff.nodes.added → [{ id: 'n2' }]
|
|
2461
|
+
* // diff.nodes.updated → [{ id: 'n1', old: { label: '' }, new: { label: 'hello' } }]
|
|
2462
|
+
* ```
|
|
2463
|
+
*/
|
|
2464
|
+
function getDiff(a, b) {
|
|
2465
|
+
const aNodeMap = new Map(a.nodes.map((n) => [n.id, n]));
|
|
2466
|
+
const bNodeMap = new Map(b.nodes.map((n) => [n.id, n]));
|
|
2467
|
+
const aEdgeMap = new Map(a.edges.map((e) => [e.id, e]));
|
|
2468
|
+
const bEdgeMap = new Map(b.edges.map((e) => [e.id, e]));
|
|
2469
|
+
const diff = {
|
|
2470
|
+
nodes: {
|
|
2471
|
+
added: [],
|
|
2472
|
+
removed: [],
|
|
2473
|
+
updated: []
|
|
2474
|
+
},
|
|
2475
|
+
edges: {
|
|
2476
|
+
added: [],
|
|
2477
|
+
removed: [],
|
|
2478
|
+
updated: []
|
|
2479
|
+
}
|
|
2480
|
+
};
|
|
2481
|
+
for (const [id, nodeB] of bNodeMap) {
|
|
2482
|
+
const nodeA = aNodeMap.get(id);
|
|
2483
|
+
if (!nodeA) diff.nodes.added.push(toNodeConfig(nodeB));
|
|
2484
|
+
else {
|
|
2485
|
+
const oldPartial = {};
|
|
2486
|
+
const newPartial = {};
|
|
2487
|
+
for (const key of NODE_COMPARE_KEYS) {
|
|
2488
|
+
const oldValue = nodeA[key] ?? null;
|
|
2489
|
+
const newValue = nodeB[key] ?? null;
|
|
2490
|
+
if (differs(oldValue, newValue)) {
|
|
2491
|
+
oldPartial[key] = oldValue;
|
|
2492
|
+
newPartial[key] = newValue;
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
if (Object.keys(oldPartial).length > 0) diff.nodes.updated.push({
|
|
2496
|
+
id,
|
|
2497
|
+
old: oldPartial,
|
|
2498
|
+
new: newPartial
|
|
2499
|
+
});
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
for (const [id, nodeA] of aNodeMap) if (!bNodeMap.has(id)) diff.nodes.removed.push(toNodeConfig(nodeA));
|
|
2503
|
+
for (const [id, edgeB] of bEdgeMap) {
|
|
2504
|
+
const edgeA = aEdgeMap.get(id);
|
|
2505
|
+
if (!edgeA) diff.edges.added.push(toEdgeConfig(edgeB));
|
|
2506
|
+
else {
|
|
2507
|
+
const oldPartial = {};
|
|
2508
|
+
const newPartial = {};
|
|
2509
|
+
for (const key of EDGE_COMPARE_KEYS) {
|
|
2510
|
+
const oldValue = edgeA[key] ?? null;
|
|
2511
|
+
const newValue = edgeB[key] ?? null;
|
|
2512
|
+
if (differs(oldValue, newValue)) {
|
|
2513
|
+
oldPartial[key] = oldValue;
|
|
2514
|
+
newPartial[key] = newValue;
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
if (Object.keys(oldPartial).length > 0) diff.edges.updated.push({
|
|
2518
|
+
id,
|
|
2519
|
+
old: oldPartial,
|
|
2520
|
+
new: newPartial
|
|
2521
|
+
});
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
for (const [id, edgeA] of aEdgeMap) if (!bEdgeMap.has(id)) diff.edges.removed.push(toEdgeConfig(edgeA));
|
|
2525
|
+
return diff;
|
|
2526
|
+
}
|
|
2527
|
+
/**
|
|
2528
|
+
* Check if a diff has zero changes.
|
|
2529
|
+
*
|
|
2530
|
+
* @example
|
|
2531
|
+
* ```ts
|
|
2532
|
+
* import { createGraph, getDiff, isEmptyDiff } from '@statelyai/graph';
|
|
2533
|
+
*
|
|
2534
|
+
* const g = createGraph({ nodes: [{ id: 'n1' }], edges: [] });
|
|
2535
|
+
* const diff = getDiff(g, g);
|
|
2536
|
+
* isEmptyDiff(diff); // true
|
|
2537
|
+
* ```
|
|
2538
|
+
*/
|
|
2539
|
+
function isEmptyDiff(diff) {
|
|
2540
|
+
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;
|
|
2541
|
+
}
|
|
2542
|
+
function summarizeGraphDiff(diff) {
|
|
2543
|
+
return {
|
|
2544
|
+
hasChanges: !isEmptyDiff(diff),
|
|
2545
|
+
nodeChanges: diff.nodes.added.length + diff.nodes.removed.length + diff.nodes.updated.length,
|
|
2546
|
+
edgeChanges: diff.edges.added.length + diff.edges.removed.length + diff.edges.updated.length
|
|
2547
|
+
};
|
|
2548
|
+
}
|
|
2549
|
+
function nodeDepth$1(graph, node) {
|
|
2550
|
+
let depth = 0;
|
|
2551
|
+
let parentId = node.parentId;
|
|
2552
|
+
while (parentId) {
|
|
2553
|
+
depth++;
|
|
2554
|
+
parentId = graph.nodes.find((candidate) => candidate.id === parentId)?.parentId;
|
|
2555
|
+
}
|
|
2556
|
+
return depth;
|
|
2557
|
+
}
|
|
2558
|
+
function nodeKey(node) {
|
|
2559
|
+
return node.data?.key || node.id;
|
|
2560
|
+
}
|
|
2561
|
+
function edgeSignature(edge) {
|
|
2562
|
+
return [
|
|
2563
|
+
edge.sourceId,
|
|
2564
|
+
edge.data?.eventType,
|
|
2565
|
+
edge.targetId,
|
|
2566
|
+
edge.data?.guard?.type ?? ""
|
|
2567
|
+
].join("->");
|
|
2568
|
+
}
|
|
2569
|
+
function getStableNodeId(node, parentStableId, siblingIndex, preferredIds) {
|
|
2570
|
+
const preferredId = preferredIds?.get(node.id);
|
|
2571
|
+
if (preferredId !== void 0) return preferredId;
|
|
2572
|
+
if (node.parentId === null) return "root";
|
|
2573
|
+
const key = nodeKey(node);
|
|
2574
|
+
const suffix = siblingIndex === 0 ? key : `${key}_${siblingIndex + 1}`;
|
|
2575
|
+
return parentStableId ? `${parentStableId}.${suffix}` : suffix;
|
|
2576
|
+
}
|
|
2577
|
+
function getStableEdgeId(edge, siblingIndex, preferredIds) {
|
|
2578
|
+
const preferredId = preferredIds?.get(edge.id);
|
|
2579
|
+
if (preferredId !== void 0) return preferredId;
|
|
2580
|
+
const suffix = siblingIndex === 0 ? "" : `.${siblingIndex + 1}`;
|
|
2581
|
+
return edgeSignature(edge).concat(suffix);
|
|
2582
|
+
}
|
|
2583
|
+
function removePresentationFields(entity, kind) {
|
|
2584
|
+
const mutableEntity = entity;
|
|
2585
|
+
for (const key of LAYOUT_KEYS[kind]) delete mutableEntity[key];
|
|
2586
|
+
const data = mutableEntity.data;
|
|
2587
|
+
if (data && typeof data === "object" && !Array.isArray(data)) delete data.color;
|
|
2588
|
+
}
|
|
2589
|
+
/**
|
|
2590
|
+
* Rewrites entity ids to structural ("semantic") ids so two graphs that model
|
|
2591
|
+
* the same machine compare equal regardless of how their ids were minted.
|
|
2592
|
+
*
|
|
2593
|
+
* Invariant: node and edge array order is preserved exactly. Callers
|
|
2594
|
+
* (`alignCompareIds` in the editor's /v1/diff route) rely on
|
|
2595
|
+
* `normalized.nodes[i]` describing `graph.nodes[i]` to translate a semantic id
|
|
2596
|
+
* back to the caller's original id. Never sort, filter, or reorder the arrays
|
|
2597
|
+
* on the returned graph; sorting for traversal must be done on a copy, as the
|
|
2598
|
+
* depth ordering below does.
|
|
2599
|
+
*
|
|
2600
|
+
* Presentation-only fields defined by `LAYOUT_KEYS`, plus entity data colors,
|
|
2601
|
+
* are removed. When preferred ids are supplied, matching raw ids retain the
|
|
2602
|
+
* base graph's semantic identity so a stable-id rename is reported as an
|
|
2603
|
+
* update.
|
|
2604
|
+
*
|
|
2605
|
+
* Entity `data` is treated as optional throughout: sparse graphs (a node with
|
|
2606
|
+
* `data: {}`, an edge with `data: null`) normalize instead of throwing.
|
|
2607
|
+
*/
|
|
2608
|
+
function normalizeGraphForSemanticDiff(graph, preferredIds) {
|
|
2609
|
+
const normalized = structuredClone(graph);
|
|
2610
|
+
const nodeIdMap = /* @__PURE__ */ new Map();
|
|
2611
|
+
const siblingKeys = /* @__PURE__ */ new Map();
|
|
2612
|
+
const reservedNodeIds = new Set(preferredIds?.nodes.values());
|
|
2613
|
+
const claimedNodeIds = /* @__PURE__ */ new Set();
|
|
2614
|
+
const nodesByDepth = [...normalized.nodes].sort((a, b) => nodeDepth$1(normalized, a) - nodeDepth$1(normalized, b));
|
|
2615
|
+
for (const node of nodesByDepth) {
|
|
2616
|
+
const parentStableId = node.parentId ? nodeIdMap.get(node.parentId) ?? node.parentId : null;
|
|
2617
|
+
const key = `${parentStableId ?? ""}:${nodeKey(node)}`;
|
|
2618
|
+
const siblingIndex = siblingKeys.get(key) ?? 0;
|
|
2619
|
+
siblingKeys.set(key, siblingIndex + 1);
|
|
2620
|
+
const proposedId = getStableNodeId(node, parentStableId, siblingIndex, preferredIds?.nodes);
|
|
2621
|
+
let stableId = proposedId;
|
|
2622
|
+
let collisionIndex = 2;
|
|
2623
|
+
while (!preferredIds?.nodes.has(node.id) && (reservedNodeIds.has(stableId) || claimedNodeIds.has(stableId))) stableId = `${proposedId}:unmatched:${collisionIndex++}`;
|
|
2624
|
+
claimedNodeIds.add(stableId);
|
|
2625
|
+
nodeIdMap.set(node.id, stableId);
|
|
2626
|
+
}
|
|
2627
|
+
for (const node of normalized.nodes) {
|
|
2628
|
+
removePresentationFields(node, "node");
|
|
2629
|
+
node.id = nodeIdMap.get(node.id) ?? node.id;
|
|
2630
|
+
if (node.parentId) node.parentId = nodeIdMap.get(node.parentId) ?? node.parentId;
|
|
2631
|
+
if (node.initialNodeId) node.initialNodeId = nodeIdMap.get(node.initialNodeId) ?? node.initialNodeId;
|
|
2632
|
+
if (node.data?.initialId) node.data.initialId = nodeIdMap.get(node.data.initialId) ?? node.data.initialId;
|
|
2633
|
+
}
|
|
2634
|
+
const edgeSiblingKeys = /* @__PURE__ */ new Map();
|
|
2635
|
+
const reservedEdgeIds = new Set(preferredIds?.edges.values());
|
|
2636
|
+
const claimedEdgeIds = /* @__PURE__ */ new Set();
|
|
2637
|
+
for (const edge of normalized.edges) {
|
|
2638
|
+
removePresentationFields(edge, "edge");
|
|
2639
|
+
edge.sourceId = nodeIdMap.get(edge.sourceId) ?? edge.sourceId;
|
|
2640
|
+
edge.targetId = nodeIdMap.get(edge.targetId) ?? edge.targetId;
|
|
2641
|
+
const key = edgeSignature(edge);
|
|
2642
|
+
const siblingIndex = edgeSiblingKeys.get(key) ?? 0;
|
|
2643
|
+
edgeSiblingKeys.set(key, siblingIndex + 1);
|
|
2644
|
+
const proposedId = getStableEdgeId(edge, siblingIndex, preferredIds?.edges);
|
|
2645
|
+
let stableId = proposedId;
|
|
2646
|
+
let collisionIndex = 2;
|
|
2647
|
+
while (!preferredIds?.edges.has(edge.id) && (reservedEdgeIds.has(stableId) || claimedEdgeIds.has(stableId))) stableId = `${proposedId}:unmatched:${collisionIndex++}`;
|
|
2648
|
+
claimedEdgeIds.add(stableId);
|
|
2649
|
+
edge.id = stableId;
|
|
2650
|
+
}
|
|
2651
|
+
if (normalized.initialNodeId) normalized.initialNodeId = nodeIdMap.get(normalized.initialNodeId) ?? normalized.initialNodeId;
|
|
2652
|
+
return normalized;
|
|
2653
|
+
}
|
|
2654
|
+
function diffGraphsSemantically(baseGraph, compareGraph) {
|
|
2655
|
+
const base = normalizeGraphForSemanticDiff(baseGraph);
|
|
2656
|
+
const compareNodeIds = new Set(compareGraph.nodes.map((node) => node.id));
|
|
2657
|
+
const compareEdgeIds = new Set(compareGraph.edges.map((edge) => edge.id));
|
|
2658
|
+
const compare = normalizeGraphForSemanticDiff(compareGraph, {
|
|
2659
|
+
nodes: new Map(baseGraph.nodes.map((node, index) => [node.id, base.nodes[index].id]).filter(([id]) => compareNodeIds.has(id))),
|
|
2660
|
+
edges: new Map(baseGraph.edges.map((edge, index) => [edge.id, base.edges[index].id]).filter(([id]) => compareEdgeIds.has(id)))
|
|
2661
|
+
});
|
|
2662
|
+
const diff = getDiff(base, compare);
|
|
2663
|
+
return {
|
|
2664
|
+
base,
|
|
2665
|
+
compare,
|
|
2666
|
+
diff,
|
|
2667
|
+
summary: summarizeGraphDiff(diff)
|
|
2668
|
+
};
|
|
2669
|
+
}
|
|
2670
|
+
function stableStringify(value) {
|
|
2671
|
+
return JSON.stringify(value, (_key, item) => {
|
|
2672
|
+
if (item && typeof item === "object" && !Array.isArray(item)) return Object.fromEntries(Object.entries(item).sort(([left], [right]) => left.localeCompare(right)));
|
|
2673
|
+
return item;
|
|
2674
|
+
});
|
|
2675
|
+
}
|
|
2676
|
+
function isEqual(a, b) {
|
|
2677
|
+
return stableStringify(a) === stableStringify(b);
|
|
2678
|
+
}
|
|
2679
|
+
function toStateColor(value) {
|
|
2680
|
+
return value ?? null;
|
|
2681
|
+
}
|
|
2682
|
+
function toTransitionColor(value) {
|
|
2683
|
+
return value ?? null;
|
|
2684
|
+
}
|
|
2685
|
+
function nodeDepth(graph, nodeId) {
|
|
2686
|
+
const nodeById = new Map(graph.nodes.map((node) => [node.id, node]));
|
|
2687
|
+
let depth = 0;
|
|
2688
|
+
let current = nodeById.get(nodeId);
|
|
2689
|
+
while (current?.parentId) {
|
|
2690
|
+
depth += 1;
|
|
2691
|
+
current = nodeById.get(current.parentId);
|
|
2692
|
+
}
|
|
2693
|
+
return depth;
|
|
2694
|
+
}
|
|
2695
|
+
function isRootNode(node) {
|
|
2696
|
+
return node.parentId == null;
|
|
2697
|
+
}
|
|
2698
|
+
function buildStateUpdatePatch(prevNode, nextNode) {
|
|
2699
|
+
const patch = {
|
|
2700
|
+
op: "updateState",
|
|
2701
|
+
stateId: nextNode.id
|
|
2702
|
+
};
|
|
2703
|
+
let changed = false;
|
|
2704
|
+
if (!prevNode || prevNode.data.key !== nextNode.data.key) {
|
|
2705
|
+
patch.key = nextNode.data.key;
|
|
2706
|
+
changed = true;
|
|
2707
|
+
}
|
|
2708
|
+
if (!prevNode || prevNode.data.type !== nextNode.data.type) {
|
|
2709
|
+
patch.type = nextNode.data.type ?? null;
|
|
2710
|
+
changed = true;
|
|
2711
|
+
}
|
|
2712
|
+
if (!prevNode || prevNode.data.description !== nextNode.data.description) {
|
|
2713
|
+
patch.stateDescription = nextNode.data.description ?? null;
|
|
2714
|
+
changed = true;
|
|
2715
|
+
}
|
|
2716
|
+
if (!prevNode || prevNode.data.initialId !== nextNode.data.initialId) {
|
|
2717
|
+
patch.initialId = nextNode.data.initialId ?? null;
|
|
2718
|
+
changed = true;
|
|
2719
|
+
}
|
|
2720
|
+
if (!prevNode || !isEqual(prevNode.data.initialInput, nextNode.data.initialInput)) {
|
|
2721
|
+
patch.initialInput = nextNode.data.initialInput ?? null;
|
|
2722
|
+
changed = true;
|
|
2723
|
+
}
|
|
2724
|
+
if (!prevNode || !isEqual(prevNode.data.output, nextNode.data.output)) {
|
|
2725
|
+
patch.output = nextNode.data.output ?? null;
|
|
2726
|
+
changed = true;
|
|
2727
|
+
}
|
|
2728
|
+
if (!prevNode || !isEqual(prevNode.data.timeout, nextNode.data.timeout)) {
|
|
2729
|
+
patch.timeout = nextNode.data.timeout ?? null;
|
|
2730
|
+
changed = true;
|
|
2731
|
+
}
|
|
2732
|
+
if (!prevNode || !isEqual(prevNode.data.route, nextNode.data.route)) {
|
|
2733
|
+
patch.route = nextNode.data.route ?? null;
|
|
2734
|
+
changed = true;
|
|
2735
|
+
}
|
|
2736
|
+
if (!prevNode || prevNode.data.color !== nextNode.data.color) {
|
|
2737
|
+
patch.color = toStateColor(nextNode.data.color);
|
|
2738
|
+
changed = true;
|
|
2739
|
+
}
|
|
2740
|
+
if (!prevNode || !isEqual(prevNode.data.meta, nextNode.data.meta)) {
|
|
2741
|
+
patch.meta = nextNode.data.meta ?? null;
|
|
2742
|
+
changed = true;
|
|
2743
|
+
}
|
|
2744
|
+
if (!prevNode || prevNode.data.history !== nextNode.data.history) {
|
|
2745
|
+
patch.history = nextNode.data.history === true ? "shallow" : nextNode.data.history === false ? null : nextNode.data.history ?? null;
|
|
2746
|
+
changed = true;
|
|
2747
|
+
}
|
|
2748
|
+
return changed ? patch : null;
|
|
2749
|
+
}
|
|
2750
|
+
function buildTransitionUpdatePatch(prevEdge, nextEdge) {
|
|
2751
|
+
const patch = {
|
|
2752
|
+
op: "updateTransition",
|
|
2753
|
+
transitionId: nextEdge.id
|
|
2754
|
+
};
|
|
2755
|
+
let changed = false;
|
|
2756
|
+
if (!prevEdge || prevEdge.sourceId !== nextEdge.sourceId) {
|
|
2757
|
+
patch.sourceId = nextEdge.sourceId;
|
|
2758
|
+
changed = true;
|
|
2759
|
+
}
|
|
2760
|
+
if (!prevEdge || prevEdge.targetId !== nextEdge.targetId) {
|
|
2761
|
+
patch.targetId = nextEdge.targetId;
|
|
2762
|
+
changed = true;
|
|
2763
|
+
}
|
|
2764
|
+
if (!prevEdge || prevEdge.data.eventType !== nextEdge.data.eventType) {
|
|
2765
|
+
patch.eventType = nextEdge.data.eventType;
|
|
2766
|
+
changed = true;
|
|
2767
|
+
}
|
|
2768
|
+
if (!prevEdge || !isEqual(prevEdge.data.matches, nextEdge.data.matches)) {
|
|
2769
|
+
patch.matches = nextEdge.data.matches ?? null;
|
|
2770
|
+
changed = true;
|
|
2771
|
+
}
|
|
2772
|
+
if (!prevEdge || prevEdge.data.transitionType !== nextEdge.data.transitionType) {
|
|
2773
|
+
patch.transitionType = nextEdge.data.transitionType ?? "normal";
|
|
2774
|
+
changed = true;
|
|
2775
|
+
}
|
|
2776
|
+
if (!prevEdge || prevEdge.data.description !== nextEdge.data.description) {
|
|
2777
|
+
patch.transitionDescription = nextEdge.data.description ?? null;
|
|
2778
|
+
changed = true;
|
|
2779
|
+
}
|
|
2780
|
+
if (!prevEdge || !isEqual(prevEdge.data.input, nextEdge.data.input)) {
|
|
2781
|
+
patch.input = nextEdge.data.input ?? null;
|
|
2782
|
+
changed = true;
|
|
2783
|
+
}
|
|
2784
|
+
if (!prevEdge || prevEdge.data.color !== nextEdge.data.color) {
|
|
2785
|
+
patch.color = toTransitionColor(nextEdge.data.color);
|
|
2786
|
+
changed = true;
|
|
2787
|
+
}
|
|
2788
|
+
if (!prevEdge || !isEqual(prevEdge.data.meta, nextEdge.data.meta)) {
|
|
2789
|
+
patch.meta = nextEdge.data.meta ?? null;
|
|
2790
|
+
changed = true;
|
|
2791
|
+
}
|
|
2792
|
+
return changed ? patch : null;
|
|
2793
|
+
}
|
|
2794
|
+
function diffTags(patches, nodeId, prevTags, nextTags) {
|
|
2795
|
+
const getTagName = (tag) => typeof tag === "string" ? tag : tag.name;
|
|
2796
|
+
const prev = new Set(prevTags.map(getTagName));
|
|
2797
|
+
const next = new Set(nextTags.map(getTagName));
|
|
2798
|
+
for (const tag of next) if (!prev.has(tag)) patches.push({
|
|
2799
|
+
op: "createTag",
|
|
2800
|
+
nodeId,
|
|
2801
|
+
tag: { name: tag }
|
|
2802
|
+
});
|
|
2803
|
+
for (const tag of prev) if (!next.has(tag)) patches.push({
|
|
2804
|
+
op: "deleteTag",
|
|
2805
|
+
nodeId,
|
|
2806
|
+
tagName: tag
|
|
2807
|
+
});
|
|
2808
|
+
}
|
|
2809
|
+
function requiresAppendOnlyListRebuild(prevIds, nextIds) {
|
|
2810
|
+
const prevIdSet = new Set(prevIds);
|
|
2811
|
+
const nextIdSet = new Set(nextIds);
|
|
2812
|
+
const retainedPrevIds = prevIds.filter((id) => nextIdSet.has(id));
|
|
2813
|
+
const retainedNextIds = nextIds.filter((id) => prevIdSet.has(id));
|
|
2814
|
+
let sawNewId = false;
|
|
2815
|
+
for (const id of nextIds) if (prevIdSet.has(id)) {
|
|
2816
|
+
if (sawNewId) return true;
|
|
2817
|
+
} else sawNewId = true;
|
|
2818
|
+
return !isEqual(retainedPrevIds, retainedNextIds);
|
|
2819
|
+
}
|
|
2820
|
+
function appendCreateInvokePatch(patches, nodeId, invoke) {
|
|
2821
|
+
patches.push({
|
|
2822
|
+
op: "createInvoke",
|
|
2823
|
+
nodeId,
|
|
2824
|
+
invoke: {
|
|
2825
|
+
id: invoke.id,
|
|
2826
|
+
...invoke.invocationId ? { invocationId: invoke.invocationId } : {},
|
|
2827
|
+
src: invoke.src,
|
|
2828
|
+
...invoke.srcExpression ? { srcExpression: invoke.srcExpression } : {},
|
|
2829
|
+
...invoke.code ? { code: invoke.code } : {},
|
|
2830
|
+
...invoke.registryKey ? { registryKey: invoke.registryKey } : {},
|
|
2831
|
+
...invoke.timeout !== void 0 ? { timeout: invoke.timeout } : {},
|
|
2832
|
+
...invoke.input ? { input: invoke.input } : {},
|
|
2833
|
+
...invoke.output ? { output: invoke.output } : {}
|
|
2834
|
+
}
|
|
2835
|
+
});
|
|
2836
|
+
}
|
|
2837
|
+
function diffInvokes(patches, nodeId, prevInvokes, nextInvokes) {
|
|
2838
|
+
if (requiresAppendOnlyListRebuild(prevInvokes.map((invoke) => invoke.id), nextInvokes.map((invoke) => invoke.id))) {
|
|
2839
|
+
for (const invoke of prevInvokes) patches.push({
|
|
2840
|
+
op: "deleteInvoke",
|
|
2841
|
+
nodeId,
|
|
2842
|
+
invokeId: invoke.id
|
|
2843
|
+
});
|
|
2844
|
+
for (const invoke of nextInvokes) appendCreateInvokePatch(patches, nodeId, invoke);
|
|
2845
|
+
return;
|
|
2846
|
+
}
|
|
2847
|
+
const prevById = new Map(prevInvokes.map((invoke) => [invoke.id, invoke]));
|
|
2848
|
+
const nextById = new Map(nextInvokes.map((invoke) => [invoke.id, invoke]));
|
|
2849
|
+
for (const invoke of nextInvokes) {
|
|
2850
|
+
const previous = prevById.get(invoke.id);
|
|
2851
|
+
if (!previous) {
|
|
2852
|
+
appendCreateInvokePatch(patches, nodeId, invoke);
|
|
2853
|
+
continue;
|
|
2854
|
+
}
|
|
2855
|
+
const data = {};
|
|
2856
|
+
if (previous.invocationId !== invoke.invocationId) data.invocationId = invoke.invocationId ?? null;
|
|
2857
|
+
if (previous.src !== invoke.src) data.src = invoke.src;
|
|
2858
|
+
if (previous.code !== invoke.code) data.code = invoke.code ?? null;
|
|
2859
|
+
if (!isEqual(previous.srcExpression, invoke.srcExpression)) data.srcExpression = invoke.srcExpression ?? null;
|
|
2860
|
+
if (previous.registryKey !== invoke.registryKey) data.registryKey = invoke.registryKey ?? null;
|
|
2861
|
+
if (!isEqual(previous.timeout, invoke.timeout)) data.timeout = invoke.timeout ?? null;
|
|
2862
|
+
if (!isEqual(previous.input, invoke.input)) data.input = invoke.input ?? null;
|
|
2863
|
+
if (!isEqual(previous.output, invoke.output)) data.output = invoke.output ?? null;
|
|
2864
|
+
if (Object.keys(data).length > 0) patches.push({
|
|
2865
|
+
op: "updateInvoke",
|
|
2866
|
+
nodeId,
|
|
2867
|
+
invokeId: invoke.id,
|
|
2868
|
+
data
|
|
2869
|
+
});
|
|
2870
|
+
}
|
|
2871
|
+
for (const invoke of prevInvokes) if (!nextById.has(invoke.id)) patches.push({
|
|
2872
|
+
op: "deleteInvoke",
|
|
2873
|
+
nodeId,
|
|
2874
|
+
invokeId: invoke.id
|
|
2875
|
+
});
|
|
2876
|
+
}
|
|
2877
|
+
function diffActions(patches, location, prevActions, nextActions) {
|
|
2878
|
+
const actionId = (action, index) => action.id ?? `${index}:${action.type}`;
|
|
2879
|
+
const prevIds = prevActions.map(actionId);
|
|
2880
|
+
const nextIds = nextActions.map(actionId);
|
|
2881
|
+
if (requiresAppendOnlyListRebuild(prevIds, nextIds)) {
|
|
2882
|
+
prevActions.forEach((action, index) => {
|
|
2883
|
+
patches.push({
|
|
2884
|
+
op: "deleteAction",
|
|
2885
|
+
location,
|
|
2886
|
+
actionId: actionId(action, index)
|
|
2887
|
+
});
|
|
2888
|
+
});
|
|
2889
|
+
nextActions.forEach((action, index) => {
|
|
2890
|
+
patches.push({
|
|
2891
|
+
op: "createAction",
|
|
2892
|
+
location,
|
|
2893
|
+
index,
|
|
2894
|
+
action: {
|
|
2895
|
+
id: action.id,
|
|
2896
|
+
type: action.type,
|
|
2897
|
+
params: action.params
|
|
2898
|
+
}
|
|
2899
|
+
});
|
|
2900
|
+
});
|
|
2901
|
+
return;
|
|
2902
|
+
}
|
|
2903
|
+
const prevById = new Map(prevActions.map((action, index) => [actionId(action, index), action]));
|
|
2904
|
+
const nextIdSet = new Set(nextIds);
|
|
2905
|
+
prevActions.forEach((action, index) => {
|
|
2906
|
+
const id = actionId(action, index);
|
|
2907
|
+
if (!nextIdSet.has(id)) patches.push({
|
|
2908
|
+
op: "deleteAction",
|
|
2909
|
+
location,
|
|
2910
|
+
actionId: id
|
|
2911
|
+
});
|
|
2912
|
+
});
|
|
2913
|
+
nextActions.forEach((action, index) => {
|
|
2914
|
+
const id = actionId(action, index);
|
|
2915
|
+
const previous = prevById.get(id);
|
|
2916
|
+
if (!previous) {
|
|
2917
|
+
patches.push({
|
|
2918
|
+
op: "createAction",
|
|
2919
|
+
location,
|
|
2920
|
+
index,
|
|
2921
|
+
action: {
|
|
2922
|
+
id: action.id,
|
|
2923
|
+
type: action.type,
|
|
2924
|
+
params: action.params
|
|
2925
|
+
}
|
|
2926
|
+
});
|
|
2927
|
+
return;
|
|
2928
|
+
}
|
|
2929
|
+
const data = {};
|
|
2930
|
+
if (previous.type !== action.type) data.type = action.type;
|
|
2931
|
+
if (!isEqual(previous.params, action.params)) data.params = action.params ?? {};
|
|
2932
|
+
if (Object.keys(data).length > 0) patches.push({
|
|
2933
|
+
op: "updateAction",
|
|
2934
|
+
location,
|
|
2935
|
+
actionId: id,
|
|
2936
|
+
data
|
|
2937
|
+
});
|
|
2938
|
+
});
|
|
2939
|
+
}
|
|
2940
|
+
function implementationList$1(graph, sourceType) {
|
|
2941
|
+
const implementations = graph.data?.implementations;
|
|
2942
|
+
if (!implementations) return [];
|
|
2943
|
+
switch (sourceType) {
|
|
2944
|
+
case "action": return implementations.actions ?? [];
|
|
2945
|
+
case "guard": return implementations.guards ?? [];
|
|
2946
|
+
case "actor": return implementations.actors ?? [];
|
|
2947
|
+
case "delay": return implementations.delays ?? [];
|
|
2948
|
+
}
|
|
2949
|
+
}
|
|
2950
|
+
function collectUnsupportedChanges(prevGraph, nextGraph) {
|
|
2951
|
+
const unsupportedChanges = [];
|
|
2952
|
+
const prevNodeById = new Map(prevGraph.nodes.map((node) => [node.id, node]));
|
|
2953
|
+
for (const node of nextGraph.nodes) {
|
|
2954
|
+
const previous = prevNodeById.get(node.id);
|
|
2955
|
+
if (previous && (previous.parentId ?? null) !== (node.parentId ?? null)) unsupportedChanges.push(`State "${node.id}" moved to a different parent.`);
|
|
2956
|
+
}
|
|
2957
|
+
for (const sourceType of [
|
|
2958
|
+
"action",
|
|
2959
|
+
"guard",
|
|
2960
|
+
"actor",
|
|
2961
|
+
"delay"
|
|
2962
|
+
]) {
|
|
2963
|
+
const previousById = new Map(implementationList$1(prevGraph, sourceType).map((implementation) => [implementation.id, implementation]));
|
|
2964
|
+
for (const implementation of implementationList$1(nextGraph, sourceType)) {
|
|
2965
|
+
const previous = previousById.get(implementation.id);
|
|
2966
|
+
const unsupportedMetadata = {
|
|
2967
|
+
description: implementation.description,
|
|
2968
|
+
lang: implementation.code?.lang,
|
|
2969
|
+
deps: implementation.code?.deps,
|
|
2970
|
+
..."paramsSchema" in implementation ? { paramsSchema: implementation.paramsSchema } : {},
|
|
2971
|
+
..."inputSchema" in implementation ? { inputSchema: implementation.inputSchema } : {},
|
|
2972
|
+
..."outputSchema" in implementation ? { outputSchema: implementation.outputSchema } : {}
|
|
2973
|
+
};
|
|
2974
|
+
const previousUnsupportedMetadata = previous ? {
|
|
2975
|
+
description: previous.description,
|
|
2976
|
+
lang: previous.code?.lang,
|
|
2977
|
+
deps: previous.code?.deps,
|
|
2978
|
+
..."paramsSchema" in previous ? { paramsSchema: previous.paramsSchema } : {},
|
|
2979
|
+
..."inputSchema" in previous ? { inputSchema: previous.inputSchema } : {},
|
|
2980
|
+
..."outputSchema" in previous ? { outputSchema: previous.outputSchema } : {}
|
|
2981
|
+
} : {};
|
|
2982
|
+
if (previous ? !isEqual(previousUnsupportedMetadata, unsupportedMetadata) : Object.values(unsupportedMetadata).some((value) => value !== void 0 && value !== null)) unsupportedChanges.push(`${sourceType} implementation "${implementation.id}" has metadata that source patches cannot represent.`);
|
|
2983
|
+
}
|
|
2984
|
+
}
|
|
2985
|
+
return unsupportedChanges;
|
|
2986
|
+
}
|
|
2987
|
+
function diffImplementations(patches, sourceType, prevGraph, nextGraph) {
|
|
2988
|
+
const prevById = new Map(implementationList$1(prevGraph, sourceType).map((implementation) => [implementation.id, implementation]));
|
|
2989
|
+
const nextById = new Map(implementationList$1(nextGraph, sourceType).map((implementation) => [implementation.id, implementation]));
|
|
2990
|
+
for (const implementation of nextById.values()) {
|
|
2991
|
+
const previous = prevById.get(implementation.id);
|
|
2992
|
+
const code = implementation.code?.body;
|
|
2993
|
+
if (!previous) {
|
|
2994
|
+
patches.push({
|
|
2995
|
+
op: "createImplementation",
|
|
2996
|
+
sourceType,
|
|
2997
|
+
implementation: {
|
|
2998
|
+
id: implementation.id,
|
|
2999
|
+
name: implementation.name,
|
|
3000
|
+
...code !== void 0 ? { code } : void 0
|
|
3001
|
+
}
|
|
3002
|
+
});
|
|
3003
|
+
continue;
|
|
3004
|
+
}
|
|
3005
|
+
const data = {};
|
|
3006
|
+
if (previous.name !== implementation.name) data.name = implementation.name;
|
|
3007
|
+
if (previous.code?.body !== implementation.code?.body) data.code = code ?? null;
|
|
3008
|
+
if (Object.keys(data).length > 0) patches.push({
|
|
3009
|
+
op: "updateImplementation",
|
|
3010
|
+
sourceType,
|
|
3011
|
+
implementationId: implementation.id,
|
|
3012
|
+
data
|
|
3013
|
+
});
|
|
3014
|
+
}
|
|
3015
|
+
for (const implementation of prevById.values()) if (!nextById.has(implementation.id)) patches.push({
|
|
3016
|
+
op: "deleteImplementation",
|
|
3017
|
+
sourceType,
|
|
3018
|
+
implementationId: implementation.id
|
|
3019
|
+
});
|
|
3020
|
+
}
|
|
3021
|
+
function appendCreateTransitionPatches(patches, edge) {
|
|
3022
|
+
patches.push({
|
|
3023
|
+
op: "createTransition",
|
|
3024
|
+
id: edge.id,
|
|
3025
|
+
sourceId: edge.sourceId,
|
|
3026
|
+
targetId: edge.targetId,
|
|
3027
|
+
eventType: edge.data.eventType,
|
|
3028
|
+
...edge.data.matches ? { matches: edge.data.matches } : {},
|
|
3029
|
+
transitionType: edge.data.transitionType,
|
|
3030
|
+
...edge.data.guard ? { guard: edge.data.guard } : {},
|
|
3031
|
+
...edge.data.color ? { color: toTransitionColor(edge.data.color) } : {},
|
|
3032
|
+
...edge.data.description ? { transitionDescription: edge.data.description } : {},
|
|
3033
|
+
...edge.data.input !== void 0 ? { input: edge.data.input } : {}
|
|
3034
|
+
});
|
|
3035
|
+
if (edge.data.meta) patches.push({
|
|
3036
|
+
op: "updateTransition",
|
|
3037
|
+
transitionId: edge.id,
|
|
3038
|
+
meta: edge.data.meta
|
|
3039
|
+
});
|
|
3040
|
+
diffActions(patches, {
|
|
3041
|
+
edgeId: edge.id,
|
|
3042
|
+
group: "transition"
|
|
3043
|
+
}, [], edge.data.actions ?? []);
|
|
3044
|
+
}
|
|
3045
|
+
/** Transitions are ordered relative to their (source state, event type) peers. */
|
|
3046
|
+
function transitionPriorityGroup(edge) {
|
|
3047
|
+
return `${edge.sourceId}\0${edge.data?.eventType ?? ""}`;
|
|
3048
|
+
}
|
|
3049
|
+
function groupEdgeIdsByPriority(edges) {
|
|
3050
|
+
const groups = /* @__PURE__ */ new Map();
|
|
3051
|
+
for (const edge of edges) {
|
|
3052
|
+
const key = transitionPriorityGroup(edge);
|
|
3053
|
+
const ids = groups.get(key);
|
|
3054
|
+
if (ids) ids.push(edge.id);
|
|
3055
|
+
else groups.set(key, [edge.id]);
|
|
3056
|
+
}
|
|
3057
|
+
return groups;
|
|
3058
|
+
}
|
|
3059
|
+
/**
|
|
3060
|
+
* Returns the ids of transitions that must be deleted and recreated because
|
|
3061
|
+
* their priority group cannot be reached by appends and deletions alone.
|
|
3062
|
+
*
|
|
3063
|
+
* An edge is rebuilt when either the group it came from or the group it moves
|
|
3064
|
+
* to needs a rebuild, so a rebuilt group is always torn down and recreated as
|
|
3065
|
+
* a whole.
|
|
3066
|
+
*/
|
|
3067
|
+
function collectRebuiltEdgeIds(prevGraph, nextGraph) {
|
|
3068
|
+
const prevGroups = groupEdgeIdsByPriority(prevGraph.edges);
|
|
3069
|
+
const nextGroups = groupEdgeIdsByPriority(nextGraph.edges);
|
|
3070
|
+
const rebuiltGroups = /* @__PURE__ */ new Set();
|
|
3071
|
+
for (const key of new Set([...prevGroups.keys(), ...nextGroups.keys()])) if (requiresAppendOnlyListRebuild(prevGroups.get(key) ?? [], nextGroups.get(key) ?? [])) rebuiltGroups.add(key);
|
|
3072
|
+
const rebuiltEdgeIds = /* @__PURE__ */ new Set();
|
|
3073
|
+
for (const edge of [...prevGraph.edges, ...nextGraph.edges]) if (rebuiltGroups.has(transitionPriorityGroup(edge))) rebuiltEdgeIds.add(edge.id);
|
|
3074
|
+
return rebuiltEdgeIds;
|
|
3075
|
+
}
|
|
3076
|
+
function graphChangeToPatchesResult(prevGraph, nextGraph) {
|
|
3077
|
+
const patches = [];
|
|
3078
|
+
const unsupportedChanges = collectUnsupportedChanges(prevGraph, nextGraph);
|
|
3079
|
+
const prevNodeById = new Map(prevGraph.nodes.map((node) => [node.id, node]));
|
|
3080
|
+
const nextNodeById = new Map(nextGraph.nodes.map((node) => [node.id, node]));
|
|
3081
|
+
const prevEdgeById = new Map(prevGraph.edges.map((edge) => [edge.id, edge]));
|
|
3082
|
+
const nextEdgeById = new Map(nextGraph.edges.map((edge) => [edge.id, edge]));
|
|
3083
|
+
const createdNodes = nextGraph.nodes.filter((node) => !isRootNode(node) && !prevNodeById.has(node.id)).sort((a, b) => nodeDepth(nextGraph, a.id) - nodeDepth(nextGraph, b.id));
|
|
3084
|
+
for (const node of createdNodes) {
|
|
3085
|
+
const x = node.position?.x ?? node.x;
|
|
3086
|
+
const y = node.position?.y ?? node.y;
|
|
3087
|
+
patches.push({
|
|
3088
|
+
op: "createState",
|
|
3089
|
+
id: node.id,
|
|
3090
|
+
parentId: node.parentId,
|
|
3091
|
+
key: node.data.key,
|
|
3092
|
+
type: node.data.type,
|
|
3093
|
+
...x !== void 0 ? { x } : {},
|
|
3094
|
+
...y !== void 0 ? { y } : {},
|
|
3095
|
+
color: toStateColor(node.data.color),
|
|
3096
|
+
initial: nextNodeById.get(node.parentId)?.data.initialId === node.id
|
|
3097
|
+
});
|
|
3098
|
+
const stateUpdate = buildStateUpdatePatch(void 0, node);
|
|
3099
|
+
if (stateUpdate) patches.push(stateUpdate);
|
|
3100
|
+
diffTags(patches, node.id, [], node.data.tags ?? []);
|
|
3101
|
+
diffInvokes(patches, node.id, [], node.data.invokes ?? []);
|
|
3102
|
+
diffActions(patches, {
|
|
3103
|
+
nodeId: node.id,
|
|
3104
|
+
group: "entry"
|
|
3105
|
+
}, [], node.data.entry ?? []);
|
|
3106
|
+
diffActions(patches, {
|
|
3107
|
+
nodeId: node.id,
|
|
3108
|
+
group: "exit"
|
|
3109
|
+
}, [], node.data.exit ?? []);
|
|
3110
|
+
}
|
|
3111
|
+
for (const node of prevGraph.nodes.filter((node) => !isRootNode(node) && !nextNodeById.has(node.id)).sort((a, b) => nodeDepth(prevGraph, b.id) - nodeDepth(prevGraph, a.id))) patches.push({
|
|
3112
|
+
op: "deleteState",
|
|
3113
|
+
stateId: node.id
|
|
3114
|
+
});
|
|
3115
|
+
for (const node of nextGraph.nodes) {
|
|
3116
|
+
const previous = prevNodeById.get(node.id);
|
|
3117
|
+
if (!previous) continue;
|
|
3118
|
+
const stateUpdate = buildStateUpdatePatch(previous, node);
|
|
3119
|
+
if (stateUpdate) patches.push(stateUpdate);
|
|
3120
|
+
diffTags(patches, node.id, previous.data.tags ?? [], node.data.tags ?? []);
|
|
3121
|
+
diffInvokes(patches, node.id, previous.data.invokes ?? [], node.data.invokes ?? []);
|
|
3122
|
+
diffActions(patches, {
|
|
3123
|
+
nodeId: node.id,
|
|
3124
|
+
group: "entry"
|
|
3125
|
+
}, previous.data.entry ?? [], node.data.entry ?? []);
|
|
3126
|
+
diffActions(patches, {
|
|
3127
|
+
nodeId: node.id,
|
|
3128
|
+
group: "exit"
|
|
3129
|
+
}, previous.data.exit ?? [], node.data.exit ?? []);
|
|
3130
|
+
}
|
|
3131
|
+
const rebuiltEdgeIds = collectRebuiltEdgeIds(prevGraph, nextGraph);
|
|
3132
|
+
const isRebuilt = (edge) => rebuiltEdgeIds.has(edge.id);
|
|
3133
|
+
for (const edge of prevGraph.edges) if (isRebuilt(edge)) patches.push({
|
|
3134
|
+
op: "deleteTransition",
|
|
3135
|
+
transitionId: edge.id
|
|
3136
|
+
});
|
|
3137
|
+
for (const edge of nextGraph.edges) if (isRebuilt(edge)) appendCreateTransitionPatches(patches, edge);
|
|
3138
|
+
for (const edge of nextGraph.edges) {
|
|
3139
|
+
if (isRebuilt(edge)) continue;
|
|
3140
|
+
const previous = prevEdgeById.get(edge.id);
|
|
3141
|
+
if (!previous) {
|
|
3142
|
+
appendCreateTransitionPatches(patches, edge);
|
|
3143
|
+
continue;
|
|
3144
|
+
}
|
|
3145
|
+
const transitionUpdate = buildTransitionUpdatePatch(previous, edge);
|
|
3146
|
+
if (transitionUpdate) patches.push(transitionUpdate);
|
|
3147
|
+
if (!previous.data.guard && edge.data.guard) patches.push({
|
|
3148
|
+
op: "setGuard",
|
|
3149
|
+
edgeId: edge.id,
|
|
3150
|
+
guard: edge.data.guard
|
|
3151
|
+
});
|
|
3152
|
+
else if (previous.data.guard && !edge.data.guard) patches.push({
|
|
3153
|
+
op: "deleteGuard",
|
|
3154
|
+
edgeId: edge.id
|
|
3155
|
+
});
|
|
3156
|
+
else if (!isEqual(previous.data.guard, edge.data.guard) && edge.data.guard) patches.push({
|
|
3157
|
+
op: "setGuard",
|
|
3158
|
+
edgeId: edge.id,
|
|
3159
|
+
guard: edge.data.guard
|
|
3160
|
+
});
|
|
3161
|
+
diffActions(patches, {
|
|
3162
|
+
edgeId: edge.id,
|
|
3163
|
+
group: "transition"
|
|
3164
|
+
}, previous.data.actions ?? [], edge.data.actions ?? []);
|
|
3165
|
+
}
|
|
3166
|
+
for (const edge of prevGraph.edges) if (!isRebuilt(edge) && !nextEdgeById.has(edge.id)) patches.push({
|
|
3167
|
+
op: "deleteTransition",
|
|
3168
|
+
transitionId: edge.id
|
|
3169
|
+
});
|
|
3170
|
+
if (!isEqual(prevGraph.data.context, nextGraph.data.context)) patches.push({
|
|
3171
|
+
op: "setContext",
|
|
3172
|
+
context: nextGraph.data.context ?? null
|
|
3173
|
+
});
|
|
3174
|
+
if (!isEqual(prevGraph.data.schemas, nextGraph.data.schemas)) patches.push({
|
|
3175
|
+
op: "updateSchemas",
|
|
3176
|
+
schemas: {
|
|
3177
|
+
context: nextGraph.data.schemas?.context ?? null,
|
|
3178
|
+
events: nextGraph.data.schemas?.events ?? null,
|
|
3179
|
+
input: nextGraph.data.schemas?.input ?? null,
|
|
3180
|
+
output: nextGraph.data.schemas?.output ?? null
|
|
3181
|
+
}
|
|
3182
|
+
});
|
|
3183
|
+
diffImplementations(patches, "action", prevGraph, nextGraph);
|
|
3184
|
+
diffImplementations(patches, "guard", prevGraph, nextGraph);
|
|
3185
|
+
diffImplementations(patches, "actor", prevGraph, nextGraph);
|
|
3186
|
+
diffImplementations(patches, "delay", prevGraph, nextGraph);
|
|
3187
|
+
return {
|
|
3188
|
+
patches,
|
|
3189
|
+
unsupportedChanges
|
|
3190
|
+
};
|
|
3191
|
+
}
|
|
3192
|
+
const UNMATCHED_ID_PREFIX = "__diff_unmatched_";
|
|
3193
|
+
function createIdAllocator(reserved) {
|
|
3194
|
+
const taken = new Set(reserved);
|
|
3195
|
+
let counter = 0;
|
|
3196
|
+
return {
|
|
3197
|
+
claim(preferredId) {
|
|
3198
|
+
if (!taken.has(preferredId)) {
|
|
3199
|
+
taken.add(preferredId);
|
|
3200
|
+
return preferredId;
|
|
3201
|
+
}
|
|
3202
|
+
let candidate = `${UNMATCHED_ID_PREFIX}${counter++}`;
|
|
3203
|
+
while (taken.has(candidate)) candidate = `${UNMATCHED_ID_PREFIX}${counter++}`;
|
|
3204
|
+
taken.add(candidate);
|
|
3205
|
+
return candidate;
|
|
3206
|
+
},
|
|
3207
|
+
reserve(id) {
|
|
3208
|
+
taken.add(id);
|
|
3209
|
+
},
|
|
3210
|
+
isTaken(id) {
|
|
3211
|
+
return taken.has(id);
|
|
3212
|
+
}
|
|
3213
|
+
};
|
|
3214
|
+
}
|
|
3215
|
+
function assignAlignedIds(compareEntities, baseIds, baseIdBySemanticId) {
|
|
3216
|
+
const baseIdSet = new Set(baseIds);
|
|
3217
|
+
const assigned = /* @__PURE__ */ new Map();
|
|
3218
|
+
const allocator = createIdAllocator([]);
|
|
3219
|
+
for (const entity of compareEntities) if (baseIdSet.has(entity.id)) {
|
|
3220
|
+
allocator.reserve(entity.id);
|
|
3221
|
+
assigned.set(entity.id, entity.id);
|
|
3222
|
+
}
|
|
3223
|
+
for (const entity of compareEntities) {
|
|
3224
|
+
if (assigned.has(entity.id)) continue;
|
|
3225
|
+
const matchedBaseId = baseIdBySemanticId.get(entity.semanticId);
|
|
3226
|
+
if (matchedBaseId !== void 0 && !allocator.isTaken(matchedBaseId)) {
|
|
3227
|
+
allocator.reserve(matchedBaseId);
|
|
3228
|
+
assigned.set(entity.id, matchedBaseId);
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
3231
|
+
for (const entity of compareEntities) {
|
|
3232
|
+
if (assigned.has(entity.id)) continue;
|
|
3233
|
+
assigned.set(entity.id, allocator.claim(entity.id));
|
|
3234
|
+
}
|
|
3235
|
+
return assigned;
|
|
3236
|
+
}
|
|
3237
|
+
/**
|
|
3238
|
+
* Rewrites compare entity ids to the corresponding base ids.
|
|
3239
|
+
*
|
|
3240
|
+
* Shared raw ids win, then semantic ids, then collision-free compare ids.
|
|
3241
|
+
* Normalization must preserve entity array order so semantic ids can be mapped
|
|
3242
|
+
* back to the caller's original graph.
|
|
3243
|
+
*/
|
|
3244
|
+
function alignCompareGraphIds(base, compare, semanticBase, semanticCompare) {
|
|
3245
|
+
const baseNodeIds = new Map(semanticBase.nodes.map((node, index) => [node.id, base.nodes[index].id]));
|
|
3246
|
+
const baseEdgeIds = new Map(semanticBase.edges.map((edge, index) => [edge.id, base.edges[index].id]));
|
|
3247
|
+
const compareNodeIds = assignAlignedIds(compare.nodes.map((node, index) => ({
|
|
3248
|
+
id: node.id,
|
|
3249
|
+
semanticId: semanticCompare.nodes[index].id
|
|
3250
|
+
})), base.nodes.map((node) => node.id), baseNodeIds);
|
|
3251
|
+
const compareEdgeIds = assignAlignedIds(compare.edges.map((edge, index) => ({
|
|
3252
|
+
id: edge.id,
|
|
3253
|
+
semanticId: semanticCompare.edges[index].id
|
|
3254
|
+
})), base.edges.map((edge) => edge.id), baseEdgeIds);
|
|
3255
|
+
const mapNodeId = (id) => compareNodeIds.get(id) ?? id;
|
|
3256
|
+
const aligned = structuredClone(compare);
|
|
3257
|
+
if (aligned.initialNodeId) aligned.initialNodeId = mapNodeId(aligned.initialNodeId);
|
|
3258
|
+
for (const node of aligned.nodes) {
|
|
3259
|
+
if (node.parentId) node.parentId = mapNodeId(node.parentId);
|
|
3260
|
+
if (node.initialNodeId) node.initialNodeId = mapNodeId(node.initialNodeId);
|
|
3261
|
+
if (node.data?.initialId) node.data.initialId = mapNodeId(node.data.initialId);
|
|
3262
|
+
node.id = mapNodeId(node.id);
|
|
3263
|
+
}
|
|
3264
|
+
for (const edge of aligned.edges) {
|
|
3265
|
+
edge.sourceId = mapNodeId(edge.sourceId);
|
|
3266
|
+
edge.targetId = mapNodeId(edge.targetId);
|
|
3267
|
+
edge.id = compareEdgeIds.get(edge.id) ?? edge.id;
|
|
3268
|
+
}
|
|
3269
|
+
for (const annotation of aligned.annotations ?? []) {
|
|
3270
|
+
const annotationData = annotation.data;
|
|
3271
|
+
if (!annotationData?.referenceIds) continue;
|
|
3272
|
+
annotationData.referenceIds = annotationData.referenceIds.map((id) => annotation.type === "edge" ? compareEdgeIds.get(id) ?? id : mapNodeId(id));
|
|
3273
|
+
}
|
|
3274
|
+
return aligned;
|
|
3275
|
+
}
|
|
2359
3276
|
function isAutoGeneratedId(id) {
|
|
2360
3277
|
return !!id && id.startsWith("$auto-");
|
|
2361
3278
|
}
|
|
@@ -2472,7 +3389,7 @@ function stripExportDefault(code) {
|
|
|
2472
3389
|
return trimmed;
|
|
2473
3390
|
}
|
|
2474
3391
|
const XSTATE_V5_PROFILE = "@statelyai/xstate@5.30.0";
|
|
2475
|
-
const XSTATE_V6_PROFILE = "@statelyai/xstate@6.0.0-alpha.
|
|
3392
|
+
const XSTATE_V6_PROFILE = "@statelyai/xstate@6.0.0-alpha.51";
|
|
2476
3393
|
const LEGACY_MACHINE_PROFILES = ["xstate-v5", "xstate-v6-alpha"];
|
|
2477
3394
|
function normalizeMachineProfile(profile) {
|
|
2478
3395
|
return profile === "xstate-v5" ? XSTATE_V5_PROFILE : profile === "xstate-v6-alpha" ? XSTATE_V6_PROFILE : profile;
|
|
@@ -2618,7 +3535,7 @@ function deepSimplify(config) {
|
|
|
2618
3535
|
return config;
|
|
2619
3536
|
}
|
|
2620
3537
|
function buildMeta(meta, color) {
|
|
2621
|
-
if (isCodeExpression$
|
|
3538
|
+
if (isCodeExpression$3(meta)) return meta;
|
|
2622
3539
|
const result = {};
|
|
2623
3540
|
if (meta) {
|
|
2624
3541
|
for (const [k, v] of Object.entries(meta)) if (v !== void 0 && v !== null) result[k] = v;
|
|
@@ -2626,7 +3543,7 @@ function buildMeta(meta, color) {
|
|
|
2626
3543
|
if (color) result["@statelyai.color"] = color;
|
|
2627
3544
|
return Object.keys(result).length > 0 ? result : void 0;
|
|
2628
3545
|
}
|
|
2629
|
-
function isCodeExpression$
|
|
3546
|
+
function isCodeExpression$3(value) {
|
|
2630
3547
|
return Boolean(value && typeof value === "object" && !Array.isArray(value) && value["@type"] === "code" && typeof value.expr === "string");
|
|
2631
3548
|
}
|
|
2632
3549
|
/** Format event param — supports { type: string } (new) and plain string (legacy) */
|
|
@@ -2637,7 +3554,7 @@ function formatEventParam(event) {
|
|
|
2637
3554
|
}
|
|
2638
3555
|
function formatStringParam(value) {
|
|
2639
3556
|
if (typeof value === "string") return `'${value}'`;
|
|
2640
|
-
return isCodeExpression$
|
|
3557
|
+
return isCodeExpression$3(value) ? serializeJS(value) : "undefined";
|
|
2641
3558
|
}
|
|
2642
3559
|
function formatInlineObject(value) {
|
|
2643
3560
|
return `{ ${Object.entries(value).filter(([, item]) => item !== void 0).map(([key, item]) => `${formatObjectKey(key)}: ${serializeJS(item)}`).join(", ")} }`;
|
|
@@ -2775,6 +3692,7 @@ const XSTATE_BUILT_IN_ACTIONS = {
|
|
|
2775
3692
|
importKind: "named",
|
|
2776
3693
|
paramsSchema: builtInActionParamSchemas["xstate.assign"],
|
|
2777
3694
|
emit(params) {
|
|
3695
|
+
if (isCodeExpression$3(params?.assignment)) return `assign(${serializeJS(params.assignment)})`;
|
|
2778
3696
|
const assignments = params?.assignment && typeof params.assignment === "object" && !Array.isArray(params.assignment) ? params.assignment : params;
|
|
2779
3697
|
const entries = Object.entries(assignments ?? {}).filter(([k, v]) => k && v !== void 0);
|
|
2780
3698
|
if (entries.length === 0) return "assign({})";
|
|
@@ -2886,6 +3804,7 @@ function graphToMachineConfig(graph, options = {}) {
|
|
|
2886
3804
|
...inv.output !== void 0 ? { output: inv.output } : void 0
|
|
2887
3805
|
})) } : void 0,
|
|
2888
3806
|
...node.data.output !== void 0 ? { output: node.data.output } : void 0,
|
|
3807
|
+
...node.data.schemas ? { schemas: node.data.schemas } : void 0,
|
|
2889
3808
|
...tags?.length ? { tags: tags.map(tagToString) } : void 0,
|
|
2890
3809
|
...showDescriptions && node.data.description ? { description: node.data.description } : void 0,
|
|
2891
3810
|
...showMeta && node.data.meta && Object.keys(node.data.meta).length > 0 ? { meta: node.data.meta } : void 0,
|
|
@@ -3005,6 +3924,8 @@ function graphToMachineConfig(graph, options = {}) {
|
|
|
3005
3924
|
if (!rootNode) throw new Error("No root node found");
|
|
3006
3925
|
getNodeConfig(rootNode);
|
|
3007
3926
|
if (graph.data.context !== void 0 && graph.data.context !== null) config.context = graph.data.context;
|
|
3927
|
+
if (graph.data.version) config.version = graph.data.version;
|
|
3928
|
+
if (graph.data.options?.maxIterations !== void 0) config.options = { maxIterations: graph.data.options.maxIterations };
|
|
3008
3929
|
return deepSimplify(config);
|
|
3009
3930
|
}
|
|
3010
3931
|
const INLINE_EXPRESSION_DIRECTIVE_FIELDS = [
|
|
@@ -4160,16 +5081,27 @@ function isCreateMachineCall(node) {
|
|
|
4160
5081
|
return false;
|
|
4161
5082
|
}
|
|
4162
5083
|
function isMachineFactoryName(name) {
|
|
4163
|
-
return name === "createMachine" || name === "createMachineFromConfig";
|
|
4164
|
-
}
|
|
4165
|
-
function
|
|
4166
|
-
if (!ts.isPropertyAccessExpression(node.expression)) return
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
5084
|
+
return name === "createMachine" || name === "createMachineFromConfig" || name === "createFSM";
|
|
5085
|
+
}
|
|
5086
|
+
function setupConfigExpressionsForCreateMachine(node, bindings) {
|
|
5087
|
+
if (!ts.isPropertyAccessExpression(node.expression)) return [];
|
|
5088
|
+
const findSetupConfigs = (value, seen = /* @__PURE__ */ new Set()) => {
|
|
5089
|
+
const receiver = unwrapExpression(value);
|
|
5090
|
+
if (ts.isIdentifier(receiver)) {
|
|
5091
|
+
if (seen.has(receiver.text)) return [];
|
|
5092
|
+
seen.add(receiver.text);
|
|
5093
|
+
const binding = bindings.get(receiver.text);
|
|
5094
|
+
return binding && !ts.isFunctionDeclaration(binding) ? findSetupConfigs(binding, seen) : [];
|
|
5095
|
+
}
|
|
5096
|
+
if (!ts.isCallExpression(receiver)) return [];
|
|
5097
|
+
if (ts.isIdentifier(receiver.expression) && receiver.expression.text === "setup") return receiver.arguments[0] ? [receiver.arguments[0]] : [];
|
|
5098
|
+
if (ts.isPropertyAccessExpression(receiver.expression) && receiver.expression.name.text === "extend") {
|
|
5099
|
+
const base = findSetupConfigs(receiver.expression.expression, seen);
|
|
5100
|
+
return receiver.arguments[0] ? [...base, receiver.arguments[0]] : base;
|
|
5101
|
+
}
|
|
5102
|
+
return [];
|
|
5103
|
+
};
|
|
5104
|
+
return findSetupConfigs(node.expression.expression);
|
|
4173
5105
|
}
|
|
4174
5106
|
function hasV6Syntax(source) {
|
|
4175
5107
|
const file = sourceFile(source);
|
|
@@ -4221,14 +5153,14 @@ function hasV6Syntax(source) {
|
|
|
4221
5153
|
}
|
|
4222
5154
|
function visitMachineCalls(node) {
|
|
4223
5155
|
if (ts.isCallExpression(node) && isCreateMachineCall(node)) {
|
|
4224
|
-
|
|
5156
|
+
const factoryName = ts.isIdentifier(node.expression) ? node.expression.text : ts.isPropertyAccessExpression(node.expression) ? node.expression.name.text : void 0;
|
|
5157
|
+
if (factoryName === "createMachineFromConfig" || factoryName === "createFSM") {
|
|
4225
5158
|
found = true;
|
|
4226
5159
|
return;
|
|
4227
5160
|
}
|
|
4228
5161
|
const config = node.arguments[0];
|
|
4229
5162
|
if (config) visitSyntax(config);
|
|
4230
|
-
const setupConfig
|
|
4231
|
-
if (setupConfig) visitSyntax(setupConfig);
|
|
5163
|
+
for (const setupConfig of setupConfigExpressionsForCreateMachine(node, bindings)) visitSyntax(setupConfig);
|
|
4232
5164
|
return;
|
|
4233
5165
|
}
|
|
4234
5166
|
ts.forEachChild(node, visitMachineCalls);
|
|
@@ -4241,24 +5173,93 @@ function detectXStateSourceProfile(source) {
|
|
|
4241
5173
|
}
|
|
4242
5174
|
function xstateSourceToConfig(source, options = {}) {
|
|
4243
5175
|
const [config] = extractXStateMachineConfigs(source, options);
|
|
4244
|
-
if (!config) throw new Error("No createMachine(...)
|
|
5176
|
+
if (!config) throw new Error("No createMachine(...), createFSM(...), or setup factory call found.");
|
|
4245
5177
|
return config;
|
|
4246
5178
|
}
|
|
4247
5179
|
/**
|
|
4248
5180
|
* Extract every XState machine config from source without executing code.
|
|
4249
5181
|
* XState 5/6 syntax is inferred structurally unless a profile is supplied.
|
|
4250
5182
|
*/
|
|
4251
|
-
function
|
|
5183
|
+
function extractXStateMachineConfigEntries(source, options = {}) {
|
|
4252
5184
|
const file = sourceFile(source);
|
|
4253
5185
|
const bindings = collectBindings(file);
|
|
4254
5186
|
const imports = collectSourceImports(file);
|
|
4255
5187
|
const found = [];
|
|
5188
|
+
let machineIndex = 0;
|
|
4256
5189
|
const profile = options.profile === "auto" || !options.profile ? detectXStateSourceProfile(source) : options.profile;
|
|
5190
|
+
const isRecordValue = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
5191
|
+
const mergeSetupStateSchemas = (machineStates, setupStates) => {
|
|
5192
|
+
if (!isRecordValue(machineStates) || !isRecordValue(setupStates)) return machineStates;
|
|
5193
|
+
return Object.fromEntries(Object.entries(machineStates).map(([key, machineState]) => {
|
|
5194
|
+
if (!isRecordValue(machineState)) return [key, machineState];
|
|
5195
|
+
const contract = isRecordValue(setupStates[key]) ? setupStates[key] : void 0;
|
|
5196
|
+
if (!contract) return [key, machineState];
|
|
5197
|
+
const contractSchemas = isRecordValue(contract.schemas) ? contract.schemas : {};
|
|
5198
|
+
const stateSchemas = isRecordValue(machineState.schemas) ? machineState.schemas : {};
|
|
5199
|
+
const schemas = Object.fromEntries([
|
|
5200
|
+
"input",
|
|
5201
|
+
"context",
|
|
5202
|
+
"output"
|
|
5203
|
+
].flatMap((schemaKey) => {
|
|
5204
|
+
const schema = stateSchemas[schemaKey] ?? contractSchemas[schemaKey];
|
|
5205
|
+
return schema === void 0 ? [] : [[schemaKey, schema]];
|
|
5206
|
+
}));
|
|
5207
|
+
return [key, {
|
|
5208
|
+
...machineState,
|
|
5209
|
+
...Object.keys(schemas).length ? { schemas } : {},
|
|
5210
|
+
states: mergeSetupStateSchemas(machineState.states, contract.states)
|
|
5211
|
+
}];
|
|
5212
|
+
}));
|
|
5213
|
+
};
|
|
5214
|
+
const mergeSetupRecords = (base, extension) => {
|
|
5215
|
+
const mergeMap = (left, right) => {
|
|
5216
|
+
if (!isRecordValue(left) && !isRecordValue(right)) return void 0;
|
|
5217
|
+
const merged = {
|
|
5218
|
+
...isRecordValue(left) ? left : {},
|
|
5219
|
+
...isRecordValue(right) ? right : {}
|
|
5220
|
+
};
|
|
5221
|
+
return Object.keys(merged).length ? merged : void 0;
|
|
5222
|
+
};
|
|
5223
|
+
const schemas = mergeMap(base.schemas, extension.schemas);
|
|
5224
|
+
if (isRecordValue(schemas)) for (const key of [
|
|
5225
|
+
"events",
|
|
5226
|
+
"internalEvents",
|
|
5227
|
+
"actions",
|
|
5228
|
+
"guards",
|
|
5229
|
+
"emitted",
|
|
5230
|
+
"children"
|
|
5231
|
+
]) {
|
|
5232
|
+
const merged = mergeMap(isRecordValue(base.schemas) ? base.schemas[key] : void 0, isRecordValue(extension.schemas) ? extension.schemas[key] : void 0);
|
|
5233
|
+
if (merged !== void 0) schemas[key] = merged;
|
|
5234
|
+
else delete schemas[key];
|
|
5235
|
+
}
|
|
5236
|
+
const result = {
|
|
5237
|
+
...base,
|
|
5238
|
+
...extension
|
|
5239
|
+
};
|
|
5240
|
+
if (schemas !== void 0) result.schemas = schemas;
|
|
5241
|
+
else delete result.schemas;
|
|
5242
|
+
for (const key of [
|
|
5243
|
+
"states",
|
|
5244
|
+
"actions",
|
|
5245
|
+
"actors",
|
|
5246
|
+
"actorSources",
|
|
5247
|
+
"guards",
|
|
5248
|
+
"delays"
|
|
5249
|
+
]) {
|
|
5250
|
+
const merged = mergeMap(base[key], extension[key]);
|
|
5251
|
+
if (merged !== void 0) result[key] = merged;
|
|
5252
|
+
else delete result[key];
|
|
5253
|
+
}
|
|
5254
|
+
return result;
|
|
5255
|
+
};
|
|
4257
5256
|
function visit(node) {
|
|
4258
5257
|
if (!ts.isCallExpression(node) || !isCreateMachineCall(node)) {
|
|
4259
5258
|
ts.forEachChild(node, visit);
|
|
4260
5259
|
return;
|
|
4261
5260
|
}
|
|
5261
|
+
const sourceMachineIndex = machineIndex;
|
|
5262
|
+
machineIndex += 1;
|
|
4262
5263
|
const arg = node.arguments[0];
|
|
4263
5264
|
if (!arg) return;
|
|
4264
5265
|
const value = expressionToValue(arg, bindings, {
|
|
@@ -4267,22 +5268,32 @@ function extractXStateMachineConfigs(source, options = {}) {
|
|
|
4267
5268
|
imports
|
|
4268
5269
|
});
|
|
4269
5270
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
4270
|
-
const
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
5271
|
+
const { states: setupStates, ...setupWithoutStates } = setupConfigExpressionsForCreateMachine(node, bindings).reduce((merged, setupArg) => {
|
|
5272
|
+
const setupValue = expressionToValue(setupArg, bindings, {
|
|
5273
|
+
v6: profile === "xstate-v6-alpha",
|
|
5274
|
+
setupValue: true,
|
|
5275
|
+
imports
|
|
5276
|
+
});
|
|
5277
|
+
if (!isRecordValue(setupValue)) return merged;
|
|
5278
|
+
return mergeSetupRecords(merged, setupValue);
|
|
5279
|
+
}, {});
|
|
5280
|
+
const machineValue = value;
|
|
4277
5281
|
found.push({
|
|
4278
|
-
|
|
4279
|
-
|
|
5282
|
+
machineIndex: sourceMachineIndex,
|
|
5283
|
+
config: {
|
|
5284
|
+
...setupWithoutStates,
|
|
5285
|
+
...machineValue,
|
|
5286
|
+
states: mergeSetupStateSchemas(machineValue.states, setupStates)
|
|
5287
|
+
}
|
|
4280
5288
|
});
|
|
4281
5289
|
}
|
|
4282
5290
|
}
|
|
4283
5291
|
visit(file);
|
|
4284
5292
|
return found;
|
|
4285
5293
|
}
|
|
5294
|
+
function extractXStateMachineConfigs(source, options = {}) {
|
|
5295
|
+
return extractXStateMachineConfigEntries(source, options).map(({ config }) => config);
|
|
5296
|
+
}
|
|
4286
5297
|
function jsonSchemaToTSType(schema) {
|
|
4287
5298
|
if (!schema) return "unknown";
|
|
4288
5299
|
if (schema.enum) return schema.enum.map((v) => typeof v === "string" ? `'${v}'` : String(v)).join(" | ");
|
|
@@ -4491,6 +5502,7 @@ function graphToXStateTS(graph, options = {}) {
|
|
|
4491
5502
|
const impls = filterInlineImplementations(graph.data.implementations);
|
|
4492
5503
|
const supportsTypeSyntax = targetLanguage === "typescript";
|
|
4493
5504
|
const hasSchemas = !!(schemas && (schemas.context || schemas.events || schemas.input || schemas.output));
|
|
5505
|
+
const hasStateSchemas = graph.nodes.some((node) => node.data.schemas);
|
|
4494
5506
|
const hasActions = !!impls?.actions.length;
|
|
4495
5507
|
const hasGuards = !!impls?.guards.length;
|
|
4496
5508
|
const hasActors = !!impls?.actors.length;
|
|
@@ -4509,10 +5521,11 @@ function graphToXStateTS(graph, options = {}) {
|
|
|
4509
5521
|
implementationImports,
|
|
4510
5522
|
codeImports,
|
|
4511
5523
|
xstateVersion,
|
|
4512
|
-
usesTypes: xstateVersion === 6 && supportsTypeSyntax && hasSchemas && xstateV6SchemaFormat === "types",
|
|
4513
|
-
usesZod: xstateVersion === 6 && hasSchemas && xstateV6SchemaFormat === "zod"
|
|
5524
|
+
usesTypes: xstateVersion === 6 && supportsTypeSyntax && (hasSchemas || hasStateSchemas) && xstateV6SchemaFormat === "types",
|
|
5525
|
+
usesZod: xstateVersion === 6 && (hasSchemas || hasStateSchemas) && xstateV6SchemaFormat === "zod"
|
|
4514
5526
|
});
|
|
4515
5527
|
const machineConfig = xstateVersion === 6 ? toV6MachineConfig(graphToMachineConfig(graph, configOptions)) : toV5MachineConfig(graphToMachineConfig(graph, configOptions));
|
|
5528
|
+
if (xstateVersion === 6) stripStateSchemas(machineConfig);
|
|
4516
5529
|
let machineExpr;
|
|
4517
5530
|
if (hasSetup) {
|
|
4518
5531
|
const setupObj = buildSetupObject(schemas, impls, {
|
|
@@ -4520,6 +5533,13 @@ function graphToXStateTS(graph, options = {}) {
|
|
|
4520
5533
|
xstateVersion,
|
|
4521
5534
|
xstateV6SchemaFormat
|
|
4522
5535
|
});
|
|
5536
|
+
if (xstateVersion === 6) {
|
|
5537
|
+
const states = buildV6SetupStates(graph, {
|
|
5538
|
+
includeTypeSyntax: supportsTypeSyntax,
|
|
5539
|
+
xstateV6SchemaFormat
|
|
5540
|
+
});
|
|
5541
|
+
if (states) setupObj.states = states;
|
|
5542
|
+
}
|
|
4523
5543
|
const configStr = serializeJS(machineConfig, 2);
|
|
4524
5544
|
machineExpr = `setup(${serializeJS(setupObj, 0)}).createMachine(${configStr})`;
|
|
4525
5545
|
} else machineExpr = `createMachine(${serializeJS(machineConfig, 0)})`;
|
|
@@ -4536,6 +5556,51 @@ function graphToXStateTS(graph, options = {}) {
|
|
|
4536
5556
|
}
|
|
4537
5557
|
return lines.join("\n") + "\n";
|
|
4538
5558
|
}
|
|
5559
|
+
function stripStateSchemas(config) {
|
|
5560
|
+
delete config.schemas;
|
|
5561
|
+
const states = config.states;
|
|
5562
|
+
if (!states || typeof states !== "object" || Array.isArray(states)) return;
|
|
5563
|
+
for (const state of Object.values(states)) if (state && typeof state === "object" && !Array.isArray(state)) stripStateSchemas(state);
|
|
5564
|
+
}
|
|
5565
|
+
function buildV6SetupStates(graph, options) {
|
|
5566
|
+
const root = graph.nodes.find((node) => node.parentId == null);
|
|
5567
|
+
if (!root) return void 0;
|
|
5568
|
+
const buildState = (nodeId) => {
|
|
5569
|
+
const node = graph.nodes.find((candidate) => candidate.id === nodeId);
|
|
5570
|
+
if (!node) return void 0;
|
|
5571
|
+
const result = {};
|
|
5572
|
+
const schemas = buildV6StateSchemasBlock(node.data.schemas, options);
|
|
5573
|
+
if (schemas) result.schemas = schemas;
|
|
5574
|
+
const childStates = Object.fromEntries(graph.nodes.filter((candidate) => candidate.parentId === node.id).flatMap((child) => {
|
|
5575
|
+
const contract = buildState(child.id);
|
|
5576
|
+
return contract ? [[child.data.key, contract]] : [];
|
|
5577
|
+
}));
|
|
5578
|
+
if (Object.keys(childStates).length) result.states = childStates;
|
|
5579
|
+
return Object.keys(result).length ? result : void 0;
|
|
5580
|
+
};
|
|
5581
|
+
const states = Object.fromEntries(graph.nodes.filter((node) => node.parentId === root.id).flatMap((node) => {
|
|
5582
|
+
const contract = buildState(node.id);
|
|
5583
|
+
return contract ? [[node.data.key, contract]] : [];
|
|
5584
|
+
}));
|
|
5585
|
+
return Object.keys(states).length ? states : void 0;
|
|
5586
|
+
}
|
|
5587
|
+
function buildV6StateSchemasBlock(schemas, options) {
|
|
5588
|
+
if (!schemas) return void 0;
|
|
5589
|
+
const result = {};
|
|
5590
|
+
const contextSchema = schemas.context;
|
|
5591
|
+
if (options.xstateV6SchemaFormat === "zod") {
|
|
5592
|
+
const context = contextSchemaToZod(contextSchema);
|
|
5593
|
+
if (context) result.context = raw(context);
|
|
5594
|
+
if (schemas.input) result.input = raw(jsonSchemaToZod(schemas.input));
|
|
5595
|
+
if (schemas.output) result.output = raw(jsonSchemaToZod(schemas.output));
|
|
5596
|
+
} else if (options.includeTypeSyntax) {
|
|
5597
|
+
const context = contextSchemaToTSType(contextSchema);
|
|
5598
|
+
if (context) result.context = raw(`types<${context}>()`);
|
|
5599
|
+
if (schemas.input) result.input = raw(`types<${jsonSchemaToTSType(schemas.input)}>()`);
|
|
5600
|
+
if (schemas.output) result.output = raw(`types<${jsonSchemaToTSType(schemas.output)}>()`);
|
|
5601
|
+
}
|
|
5602
|
+
return Object.keys(result).length ? result : void 0;
|
|
5603
|
+
}
|
|
4539
5604
|
function filterInlineImplementations(impls) {
|
|
4540
5605
|
if (!impls) return impls;
|
|
4541
5606
|
return {
|
|
@@ -5283,6 +6348,108 @@ function isCompleteFunctionExpression(code) {
|
|
|
5283
6348
|
if (!expression) return false;
|
|
5284
6349
|
return ts.isFunctionExpression(expression) || ts.isArrowFunction(expression);
|
|
5285
6350
|
}
|
|
6351
|
+
function getXStateFSMCompatibilityIssues(graph) {
|
|
6352
|
+
const root = graph.nodes.find((node) => node.parentId == null);
|
|
6353
|
+
if (!root) return ["missing root state"];
|
|
6354
|
+
const states = graph.nodes.filter((node) => node.parentId === root.id);
|
|
6355
|
+
const stateIds = new Set(states.map((node) => node.id));
|
|
6356
|
+
const issues = /* @__PURE__ */ new Set();
|
|
6357
|
+
if (!root.data.initialId || !stateIds.has(root.data.initialId)) issues.add("missing initial state");
|
|
6358
|
+
if (graph.nodes.some((node) => node.parentId !== null && node.parentId !== root.id)) issues.add("nested states");
|
|
6359
|
+
if (states.some((node) => node.data.type && node.data.type !== "normal")) issues.add("non-atomic state types");
|
|
6360
|
+
if (graph.nodes.some((node) => node.data.entry?.length || node.data.exit?.length || node.data.invokes?.length || node.data.timeout !== void 0 || node.data.route !== void 0 || node.data.output !== void 0 || node.data.initialInput !== void 0)) issues.add("state effects, invokes, timeouts, routes, or outputs");
|
|
6361
|
+
if (graph.nodes.some((node) => node.data.schemas?.input || node.data.schemas?.output) || graph.data.schemas?.input || graph.data.schemas?.output) issues.add("input or output schemas");
|
|
6362
|
+
if (graph.data.version || graph.data.options) issues.add("machine version or runtime options");
|
|
6363
|
+
const eventCounts = /* @__PURE__ */ new Map();
|
|
6364
|
+
for (const edge of graph.edges.filter((edge) => stateIds.has(edge.sourceId))) {
|
|
6365
|
+
const key = `${edge.sourceId}:${edge.data.eventType}`;
|
|
6366
|
+
eventCounts.set(key, (eventCounts.get(key) ?? 0) + 1);
|
|
6367
|
+
if (!edge.data.eventType) issues.add("eventless transitions");
|
|
6368
|
+
if (!stateIds.has(edge.targetId) && edge.data.transitionType !== "targetless") issues.add("transitions outside the flat state set");
|
|
6369
|
+
if (edge.data.guard || edge.data.actions?.length || edge.data.matches || edge.data.input !== void 0 || edge.data.transitionType === "reenter") issues.add("guards, actions, matching, transition input, or reentry");
|
|
6370
|
+
if (isCodeExpression$2(edge.data.context)) issues.add("dynamic transition context");
|
|
6371
|
+
}
|
|
6372
|
+
if ([...eventCounts.values()].some((count) => count > 1)) issues.add("multiple transitions for one event");
|
|
6373
|
+
return [...issues];
|
|
6374
|
+
}
|
|
6375
|
+
function graphToXStateFSM(graph, options = {}) {
|
|
6376
|
+
const root = graph.nodes.find((node) => node.parentId == null);
|
|
6377
|
+
if (!root) throw new Error("No root node found");
|
|
6378
|
+
const states = graph.nodes.filter((node) => node.parentId === root.id);
|
|
6379
|
+
const stateById = new Map(states.map((node) => [node.id, node]));
|
|
6380
|
+
const initial = root.data.initialId ? stateById.get(root.data.initialId)?.data.key : void 0;
|
|
6381
|
+
if (!initial) throw new Error("Cannot export XState FSM without an initial state.");
|
|
6382
|
+
const schemaFormat = options.xstateV6SchemaFormat ?? "types";
|
|
6383
|
+
const setupConfig = buildSetupConfig(graph, states, schemaFormat);
|
|
6384
|
+
const config = {
|
|
6385
|
+
id: root.data.nodeId || graph.id,
|
|
6386
|
+
initial,
|
|
6387
|
+
context: graph.data.context ?? void 0,
|
|
6388
|
+
states: Object.fromEntries(states.map((state) => [state.data.key, buildState(graph, state, stateById)]))
|
|
6389
|
+
};
|
|
6390
|
+
const imports = [setupConfig ? "setup" : "createFSM"];
|
|
6391
|
+
if (schemaFormat === "types" && setupConfig) imports.push("types");
|
|
6392
|
+
const lines = [`import { ${imports.join(", ")} } from 'xstate/fsm';`];
|
|
6393
|
+
if (schemaFormat === "zod" && setupConfig) lines.push(`import { z } from 'zod';`);
|
|
6394
|
+
const expression = setupConfig ? `setup(${serializeJS(setupConfig, 0)}).createFSM(${serializeJS(config, 0)})` : `createFSM(${serializeJS(config, 0)})`;
|
|
6395
|
+
lines.push("", `const machine = ${expression};`);
|
|
6396
|
+
if ((options.exportStyle ?? "named") === "named") lines.push("", "export { machine };");
|
|
6397
|
+
else if (options.exportStyle === "default") lines.push("", "export default machine;");
|
|
6398
|
+
return `${lines.join("\n")}\n`;
|
|
6399
|
+
}
|
|
6400
|
+
function buildState(graph, state, stateById) {
|
|
6401
|
+
const on = {};
|
|
6402
|
+
for (const edge of graph.edges.filter((candidate) => candidate.sourceId === state.id)) {
|
|
6403
|
+
const eventType = edge.data.eventType;
|
|
6404
|
+
if (!eventType || on[eventType] !== void 0) continue;
|
|
6405
|
+
const transition = buildTransition$1(edge, stateById);
|
|
6406
|
+
if (transition !== void 0) on[eventType] = transition;
|
|
6407
|
+
}
|
|
6408
|
+
return Object.keys(on).length ? { on } : {};
|
|
6409
|
+
}
|
|
6410
|
+
function buildTransition$1(edge, stateById) {
|
|
6411
|
+
const target = edge.data.transitionType === "targetless" ? void 0 : stateById.get(edge.targetId)?.data.key;
|
|
6412
|
+
const context = isCodeExpression$2(edge.data.context) ? void 0 : edge.data.context;
|
|
6413
|
+
if (context !== void 0) return {
|
|
6414
|
+
target,
|
|
6415
|
+
context
|
|
6416
|
+
};
|
|
6417
|
+
return target;
|
|
6418
|
+
}
|
|
6419
|
+
function buildSetupConfig(graph, states, format) {
|
|
6420
|
+
const schemas = {};
|
|
6421
|
+
const rootContext = graph.data.schemas?.context;
|
|
6422
|
+
const rootEvents = graph.data.schemas?.events;
|
|
6423
|
+
if (format === "types") {
|
|
6424
|
+
const context = contextSchemaToTSType(rootContext);
|
|
6425
|
+
if (context) schemas.context = raw(`types<${context}>()`);
|
|
6426
|
+
if (rootEvents && Object.keys(rootEvents).length) schemas.events = Object.fromEntries(Object.entries(rootEvents).map(([eventType, schema]) => [eventType, raw(`types<${eventPayloadType(schema)}>()`)]));
|
|
6427
|
+
} else {
|
|
6428
|
+
const context = contextSchemaToZod(rootContext);
|
|
6429
|
+
if (context) schemas.context = raw(context);
|
|
6430
|
+
if (rootEvents && Object.keys(rootEvents).length) schemas.events = Object.fromEntries(Object.entries(rootEvents).map(([eventType, schema]) => [eventType, raw(eventSchemaToZod(schema))]));
|
|
6431
|
+
}
|
|
6432
|
+
const stateContracts = Object.fromEntries(states.flatMap((state) => {
|
|
6433
|
+
const context = format === "types" ? contextSchemaToTSType(state.data.schemas?.context) : contextSchemaToZod(state.data.schemas?.context);
|
|
6434
|
+
return context ? [[state.data.key, { schemas: { context: raw(format === "types" ? `types<${context}>()` : context) } }]] : [];
|
|
6435
|
+
}));
|
|
6436
|
+
const setup = {};
|
|
6437
|
+
if (Object.keys(schemas).length) setup.schemas = schemas;
|
|
6438
|
+
if (Object.keys(stateContracts).length) setup.states = stateContracts;
|
|
6439
|
+
return Object.keys(setup).length ? setup : void 0;
|
|
6440
|
+
}
|
|
6441
|
+
function eventPayloadType(schema) {
|
|
6442
|
+
const properties = Object.fromEntries(Object.entries(schema.properties ?? {}).filter(([key]) => key !== "type"));
|
|
6443
|
+
const required = schema.required?.filter((key) => key !== "type");
|
|
6444
|
+
return jsonSchemaToTSType({
|
|
6445
|
+
...schema,
|
|
6446
|
+
properties,
|
|
6447
|
+
required
|
|
6448
|
+
});
|
|
6449
|
+
}
|
|
6450
|
+
function isCodeExpression$2(value) {
|
|
6451
|
+
return !!value && typeof value === "object" && !Array.isArray(value) && value["@type"] === "code";
|
|
6452
|
+
}
|
|
5286
6453
|
const colorSchema = _enum([
|
|
5287
6454
|
"green",
|
|
5288
6455
|
"red",
|
|
@@ -5664,4 +6831,4 @@ function getScriptKind(fileName) {
|
|
|
5664
6831
|
}
|
|
5665
6832
|
|
|
5666
6833
|
//#endregion
|
|
5667
|
-
export { RawCode as a,
|
|
6834
|
+
export { normalizeMachineProfile as _, RawCode as a, xstateSourceToConfig as b, detectXStateSourceProfile as c, getXStateFSMCompatibilityIssues as d, graphChangeToPatchesResult as f, jsonSchemaToTSType as g, graphToXStateTS as h, INLINE_EXPRESSION_DIRECTIVE_FIELDS as i, diffGraphsSemantically as l, graphToXStateFSM as m, getStatelyPragma as n, alignCompareGraphIds as o, graphToMachineConfig as p, upsertStatelyPragma as r, contextSchemaToTSType as s, findStatelyPragmaAttachments as t, eventsSchemaToTSType as u, raw as v, serializeJS as y };
|