@statelyai/sdk 0.26.3 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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-Br_vS_lK.d.mts → inspect-D61HS-t7.d.mts} +2 -5
- package/dist/{inspect-CiPPDR7Q.mjs → inspect-_E2lVbQA.mjs} +2 -4
- 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-DD7wx7-b.mjs} +1197 -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-7SUCFMqb.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$2(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$2(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$2(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(", ")} }`;
|
|
@@ -2886,6 +3803,7 @@ function graphToMachineConfig(graph, options = {}) {
|
|
|
2886
3803
|
...inv.output !== void 0 ? { output: inv.output } : void 0
|
|
2887
3804
|
})) } : void 0,
|
|
2888
3805
|
...node.data.output !== void 0 ? { output: node.data.output } : void 0,
|
|
3806
|
+
...node.data.schemas ? { schemas: node.data.schemas } : void 0,
|
|
2889
3807
|
...tags?.length ? { tags: tags.map(tagToString) } : void 0,
|
|
2890
3808
|
...showDescriptions && node.data.description ? { description: node.data.description } : void 0,
|
|
2891
3809
|
...showMeta && node.data.meta && Object.keys(node.data.meta).length > 0 ? { meta: node.data.meta } : void 0,
|
|
@@ -3005,6 +3923,8 @@ function graphToMachineConfig(graph, options = {}) {
|
|
|
3005
3923
|
if (!rootNode) throw new Error("No root node found");
|
|
3006
3924
|
getNodeConfig(rootNode);
|
|
3007
3925
|
if (graph.data.context !== void 0 && graph.data.context !== null) config.context = graph.data.context;
|
|
3926
|
+
if (graph.data.version) config.version = graph.data.version;
|
|
3927
|
+
if (graph.data.options?.maxIterations !== void 0) config.options = { maxIterations: graph.data.options.maxIterations };
|
|
3008
3928
|
return deepSimplify(config);
|
|
3009
3929
|
}
|
|
3010
3930
|
const INLINE_EXPRESSION_DIRECTIVE_FIELDS = [
|
|
@@ -4160,16 +5080,27 @@ function isCreateMachineCall(node) {
|
|
|
4160
5080
|
return false;
|
|
4161
5081
|
}
|
|
4162
5082
|
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
|
-
|
|
5083
|
+
return name === "createMachine" || name === "createMachineFromConfig" || name === "createFSM";
|
|
5084
|
+
}
|
|
5085
|
+
function setupConfigExpressionsForCreateMachine(node, bindings) {
|
|
5086
|
+
if (!ts.isPropertyAccessExpression(node.expression)) return [];
|
|
5087
|
+
const findSetupConfigs = (value, seen = /* @__PURE__ */ new Set()) => {
|
|
5088
|
+
const receiver = unwrapExpression(value);
|
|
5089
|
+
if (ts.isIdentifier(receiver)) {
|
|
5090
|
+
if (seen.has(receiver.text)) return [];
|
|
5091
|
+
seen.add(receiver.text);
|
|
5092
|
+
const binding = bindings.get(receiver.text);
|
|
5093
|
+
return binding && !ts.isFunctionDeclaration(binding) ? findSetupConfigs(binding, seen) : [];
|
|
5094
|
+
}
|
|
5095
|
+
if (!ts.isCallExpression(receiver)) return [];
|
|
5096
|
+
if (ts.isIdentifier(receiver.expression) && receiver.expression.text === "setup") return receiver.arguments[0] ? [receiver.arguments[0]] : [];
|
|
5097
|
+
if (ts.isPropertyAccessExpression(receiver.expression) && receiver.expression.name.text === "extend") {
|
|
5098
|
+
const base = findSetupConfigs(receiver.expression.expression, seen);
|
|
5099
|
+
return receiver.arguments[0] ? [...base, receiver.arguments[0]] : base;
|
|
5100
|
+
}
|
|
5101
|
+
return [];
|
|
5102
|
+
};
|
|
5103
|
+
return findSetupConfigs(node.expression.expression);
|
|
4173
5104
|
}
|
|
4174
5105
|
function hasV6Syntax(source) {
|
|
4175
5106
|
const file = sourceFile(source);
|
|
@@ -4221,14 +5152,14 @@ function hasV6Syntax(source) {
|
|
|
4221
5152
|
}
|
|
4222
5153
|
function visitMachineCalls(node) {
|
|
4223
5154
|
if (ts.isCallExpression(node) && isCreateMachineCall(node)) {
|
|
4224
|
-
|
|
5155
|
+
const factoryName = ts.isIdentifier(node.expression) ? node.expression.text : ts.isPropertyAccessExpression(node.expression) ? node.expression.name.text : void 0;
|
|
5156
|
+
if (factoryName === "createMachineFromConfig" || factoryName === "createFSM") {
|
|
4225
5157
|
found = true;
|
|
4226
5158
|
return;
|
|
4227
5159
|
}
|
|
4228
5160
|
const config = node.arguments[0];
|
|
4229
5161
|
if (config) visitSyntax(config);
|
|
4230
|
-
const setupConfig
|
|
4231
|
-
if (setupConfig) visitSyntax(setupConfig);
|
|
5162
|
+
for (const setupConfig of setupConfigExpressionsForCreateMachine(node, bindings)) visitSyntax(setupConfig);
|
|
4232
5163
|
return;
|
|
4233
5164
|
}
|
|
4234
5165
|
ts.forEachChild(node, visitMachineCalls);
|
|
@@ -4241,24 +5172,93 @@ function detectXStateSourceProfile(source) {
|
|
|
4241
5172
|
}
|
|
4242
5173
|
function xstateSourceToConfig(source, options = {}) {
|
|
4243
5174
|
const [config] = extractXStateMachineConfigs(source, options);
|
|
4244
|
-
if (!config) throw new Error("No createMachine(...)
|
|
5175
|
+
if (!config) throw new Error("No createMachine(...), createFSM(...), or setup factory call found.");
|
|
4245
5176
|
return config;
|
|
4246
5177
|
}
|
|
4247
5178
|
/**
|
|
4248
5179
|
* Extract every XState machine config from source without executing code.
|
|
4249
5180
|
* XState 5/6 syntax is inferred structurally unless a profile is supplied.
|
|
4250
5181
|
*/
|
|
4251
|
-
function
|
|
5182
|
+
function extractXStateMachineConfigEntries(source, options = {}) {
|
|
4252
5183
|
const file = sourceFile(source);
|
|
4253
5184
|
const bindings = collectBindings(file);
|
|
4254
5185
|
const imports = collectSourceImports(file);
|
|
4255
5186
|
const found = [];
|
|
5187
|
+
let machineIndex = 0;
|
|
4256
5188
|
const profile = options.profile === "auto" || !options.profile ? detectXStateSourceProfile(source) : options.profile;
|
|
5189
|
+
const isRecordValue = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
5190
|
+
const mergeSetupStateSchemas = (machineStates, setupStates) => {
|
|
5191
|
+
if (!isRecordValue(machineStates) || !isRecordValue(setupStates)) return machineStates;
|
|
5192
|
+
return Object.fromEntries(Object.entries(machineStates).map(([key, machineState]) => {
|
|
5193
|
+
if (!isRecordValue(machineState)) return [key, machineState];
|
|
5194
|
+
const contract = isRecordValue(setupStates[key]) ? setupStates[key] : void 0;
|
|
5195
|
+
if (!contract) return [key, machineState];
|
|
5196
|
+
const contractSchemas = isRecordValue(contract.schemas) ? contract.schemas : {};
|
|
5197
|
+
const stateSchemas = isRecordValue(machineState.schemas) ? machineState.schemas : {};
|
|
5198
|
+
const schemas = Object.fromEntries([
|
|
5199
|
+
"input",
|
|
5200
|
+
"context",
|
|
5201
|
+
"output"
|
|
5202
|
+
].flatMap((schemaKey) => {
|
|
5203
|
+
const schema = stateSchemas[schemaKey] ?? contractSchemas[schemaKey];
|
|
5204
|
+
return schema === void 0 ? [] : [[schemaKey, schema]];
|
|
5205
|
+
}));
|
|
5206
|
+
return [key, {
|
|
5207
|
+
...machineState,
|
|
5208
|
+
...Object.keys(schemas).length ? { schemas } : {},
|
|
5209
|
+
states: mergeSetupStateSchemas(machineState.states, contract.states)
|
|
5210
|
+
}];
|
|
5211
|
+
}));
|
|
5212
|
+
};
|
|
5213
|
+
const mergeSetupRecords = (base, extension) => {
|
|
5214
|
+
const mergeMap = (left, right) => {
|
|
5215
|
+
if (!isRecordValue(left) && !isRecordValue(right)) return void 0;
|
|
5216
|
+
const merged = {
|
|
5217
|
+
...isRecordValue(left) ? left : {},
|
|
5218
|
+
...isRecordValue(right) ? right : {}
|
|
5219
|
+
};
|
|
5220
|
+
return Object.keys(merged).length ? merged : void 0;
|
|
5221
|
+
};
|
|
5222
|
+
const schemas = mergeMap(base.schemas, extension.schemas);
|
|
5223
|
+
if (isRecordValue(schemas)) for (const key of [
|
|
5224
|
+
"events",
|
|
5225
|
+
"internalEvents",
|
|
5226
|
+
"actions",
|
|
5227
|
+
"guards",
|
|
5228
|
+
"emitted",
|
|
5229
|
+
"children"
|
|
5230
|
+
]) {
|
|
5231
|
+
const merged = mergeMap(isRecordValue(base.schemas) ? base.schemas[key] : void 0, isRecordValue(extension.schemas) ? extension.schemas[key] : void 0);
|
|
5232
|
+
if (merged !== void 0) schemas[key] = merged;
|
|
5233
|
+
else delete schemas[key];
|
|
5234
|
+
}
|
|
5235
|
+
const result = {
|
|
5236
|
+
...base,
|
|
5237
|
+
...extension
|
|
5238
|
+
};
|
|
5239
|
+
if (schemas !== void 0) result.schemas = schemas;
|
|
5240
|
+
else delete result.schemas;
|
|
5241
|
+
for (const key of [
|
|
5242
|
+
"states",
|
|
5243
|
+
"actions",
|
|
5244
|
+
"actors",
|
|
5245
|
+
"actorSources",
|
|
5246
|
+
"guards",
|
|
5247
|
+
"delays"
|
|
5248
|
+
]) {
|
|
5249
|
+
const merged = mergeMap(base[key], extension[key]);
|
|
5250
|
+
if (merged !== void 0) result[key] = merged;
|
|
5251
|
+
else delete result[key];
|
|
5252
|
+
}
|
|
5253
|
+
return result;
|
|
5254
|
+
};
|
|
4257
5255
|
function visit(node) {
|
|
4258
5256
|
if (!ts.isCallExpression(node) || !isCreateMachineCall(node)) {
|
|
4259
5257
|
ts.forEachChild(node, visit);
|
|
4260
5258
|
return;
|
|
4261
5259
|
}
|
|
5260
|
+
const sourceMachineIndex = machineIndex;
|
|
5261
|
+
machineIndex += 1;
|
|
4262
5262
|
const arg = node.arguments[0];
|
|
4263
5263
|
if (!arg) return;
|
|
4264
5264
|
const value = expressionToValue(arg, bindings, {
|
|
@@ -4267,22 +5267,32 @@ function extractXStateMachineConfigs(source, options = {}) {
|
|
|
4267
5267
|
imports
|
|
4268
5268
|
});
|
|
4269
5269
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
4270
|
-
const
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
5270
|
+
const { states: setupStates, ...setupWithoutStates } = setupConfigExpressionsForCreateMachine(node, bindings).reduce((merged, setupArg) => {
|
|
5271
|
+
const setupValue = expressionToValue(setupArg, bindings, {
|
|
5272
|
+
v6: profile === "xstate-v6-alpha",
|
|
5273
|
+
setupValue: true,
|
|
5274
|
+
imports
|
|
5275
|
+
});
|
|
5276
|
+
if (!isRecordValue(setupValue)) return merged;
|
|
5277
|
+
return mergeSetupRecords(merged, setupValue);
|
|
5278
|
+
}, {});
|
|
5279
|
+
const machineValue = value;
|
|
4277
5280
|
found.push({
|
|
4278
|
-
|
|
4279
|
-
|
|
5281
|
+
machineIndex: sourceMachineIndex,
|
|
5282
|
+
config: {
|
|
5283
|
+
...setupWithoutStates,
|
|
5284
|
+
...machineValue,
|
|
5285
|
+
states: mergeSetupStateSchemas(machineValue.states, setupStates)
|
|
5286
|
+
}
|
|
4280
5287
|
});
|
|
4281
5288
|
}
|
|
4282
5289
|
}
|
|
4283
5290
|
visit(file);
|
|
4284
5291
|
return found;
|
|
4285
5292
|
}
|
|
5293
|
+
function extractXStateMachineConfigs(source, options = {}) {
|
|
5294
|
+
return extractXStateMachineConfigEntries(source, options).map(({ config }) => config);
|
|
5295
|
+
}
|
|
4286
5296
|
function jsonSchemaToTSType(schema) {
|
|
4287
5297
|
if (!schema) return "unknown";
|
|
4288
5298
|
if (schema.enum) return schema.enum.map((v) => typeof v === "string" ? `'${v}'` : String(v)).join(" | ");
|
|
@@ -4491,6 +5501,7 @@ function graphToXStateTS(graph, options = {}) {
|
|
|
4491
5501
|
const impls = filterInlineImplementations(graph.data.implementations);
|
|
4492
5502
|
const supportsTypeSyntax = targetLanguage === "typescript";
|
|
4493
5503
|
const hasSchemas = !!(schemas && (schemas.context || schemas.events || schemas.input || schemas.output));
|
|
5504
|
+
const hasStateSchemas = graph.nodes.some((node) => node.data.schemas);
|
|
4494
5505
|
const hasActions = !!impls?.actions.length;
|
|
4495
5506
|
const hasGuards = !!impls?.guards.length;
|
|
4496
5507
|
const hasActors = !!impls?.actors.length;
|
|
@@ -4509,10 +5520,11 @@ function graphToXStateTS(graph, options = {}) {
|
|
|
4509
5520
|
implementationImports,
|
|
4510
5521
|
codeImports,
|
|
4511
5522
|
xstateVersion,
|
|
4512
|
-
usesTypes: xstateVersion === 6 && supportsTypeSyntax && hasSchemas && xstateV6SchemaFormat === "types",
|
|
4513
|
-
usesZod: xstateVersion === 6 && hasSchemas && xstateV6SchemaFormat === "zod"
|
|
5523
|
+
usesTypes: xstateVersion === 6 && supportsTypeSyntax && (hasSchemas || hasStateSchemas) && xstateV6SchemaFormat === "types",
|
|
5524
|
+
usesZod: xstateVersion === 6 && (hasSchemas || hasStateSchemas) && xstateV6SchemaFormat === "zod"
|
|
4514
5525
|
});
|
|
4515
5526
|
const machineConfig = xstateVersion === 6 ? toV6MachineConfig(graphToMachineConfig(graph, configOptions)) : toV5MachineConfig(graphToMachineConfig(graph, configOptions));
|
|
5527
|
+
if (xstateVersion === 6) stripStateSchemas(machineConfig);
|
|
4516
5528
|
let machineExpr;
|
|
4517
5529
|
if (hasSetup) {
|
|
4518
5530
|
const setupObj = buildSetupObject(schemas, impls, {
|
|
@@ -4520,6 +5532,13 @@ function graphToXStateTS(graph, options = {}) {
|
|
|
4520
5532
|
xstateVersion,
|
|
4521
5533
|
xstateV6SchemaFormat
|
|
4522
5534
|
});
|
|
5535
|
+
if (xstateVersion === 6) {
|
|
5536
|
+
const states = buildV6SetupStates(graph, {
|
|
5537
|
+
includeTypeSyntax: supportsTypeSyntax,
|
|
5538
|
+
xstateV6SchemaFormat
|
|
5539
|
+
});
|
|
5540
|
+
if (states) setupObj.states = states;
|
|
5541
|
+
}
|
|
4523
5542
|
const configStr = serializeJS(machineConfig, 2);
|
|
4524
5543
|
machineExpr = `setup(${serializeJS(setupObj, 0)}).createMachine(${configStr})`;
|
|
4525
5544
|
} else machineExpr = `createMachine(${serializeJS(machineConfig, 0)})`;
|
|
@@ -4536,6 +5555,51 @@ function graphToXStateTS(graph, options = {}) {
|
|
|
4536
5555
|
}
|
|
4537
5556
|
return lines.join("\n") + "\n";
|
|
4538
5557
|
}
|
|
5558
|
+
function stripStateSchemas(config) {
|
|
5559
|
+
delete config.schemas;
|
|
5560
|
+
const states = config.states;
|
|
5561
|
+
if (!states || typeof states !== "object" || Array.isArray(states)) return;
|
|
5562
|
+
for (const state of Object.values(states)) if (state && typeof state === "object" && !Array.isArray(state)) stripStateSchemas(state);
|
|
5563
|
+
}
|
|
5564
|
+
function buildV6SetupStates(graph, options) {
|
|
5565
|
+
const root = graph.nodes.find((node) => node.parentId == null);
|
|
5566
|
+
if (!root) return void 0;
|
|
5567
|
+
const buildState = (nodeId) => {
|
|
5568
|
+
const node = graph.nodes.find((candidate) => candidate.id === nodeId);
|
|
5569
|
+
if (!node) return void 0;
|
|
5570
|
+
const result = {};
|
|
5571
|
+
const schemas = buildV6StateSchemasBlock(node.data.schemas, options);
|
|
5572
|
+
if (schemas) result.schemas = schemas;
|
|
5573
|
+
const childStates = Object.fromEntries(graph.nodes.filter((candidate) => candidate.parentId === node.id).flatMap((child) => {
|
|
5574
|
+
const contract = buildState(child.id);
|
|
5575
|
+
return contract ? [[child.data.key, contract]] : [];
|
|
5576
|
+
}));
|
|
5577
|
+
if (Object.keys(childStates).length) result.states = childStates;
|
|
5578
|
+
return Object.keys(result).length ? result : void 0;
|
|
5579
|
+
};
|
|
5580
|
+
const states = Object.fromEntries(graph.nodes.filter((node) => node.parentId === root.id).flatMap((node) => {
|
|
5581
|
+
const contract = buildState(node.id);
|
|
5582
|
+
return contract ? [[node.data.key, contract]] : [];
|
|
5583
|
+
}));
|
|
5584
|
+
return Object.keys(states).length ? states : void 0;
|
|
5585
|
+
}
|
|
5586
|
+
function buildV6StateSchemasBlock(schemas, options) {
|
|
5587
|
+
if (!schemas) return void 0;
|
|
5588
|
+
const result = {};
|
|
5589
|
+
const contextSchema = schemas.context;
|
|
5590
|
+
if (options.xstateV6SchemaFormat === "zod") {
|
|
5591
|
+
const context = contextSchemaToZod(contextSchema);
|
|
5592
|
+
if (context) result.context = raw(context);
|
|
5593
|
+
if (schemas.input) result.input = raw(jsonSchemaToZod(schemas.input));
|
|
5594
|
+
if (schemas.output) result.output = raw(jsonSchemaToZod(schemas.output));
|
|
5595
|
+
} else if (options.includeTypeSyntax) {
|
|
5596
|
+
const context = contextSchemaToTSType(contextSchema);
|
|
5597
|
+
if (context) result.context = raw(`types<${context}>()`);
|
|
5598
|
+
if (schemas.input) result.input = raw(`types<${jsonSchemaToTSType(schemas.input)}>()`);
|
|
5599
|
+
if (schemas.output) result.output = raw(`types<${jsonSchemaToTSType(schemas.output)}>()`);
|
|
5600
|
+
}
|
|
5601
|
+
return Object.keys(result).length ? result : void 0;
|
|
5602
|
+
}
|
|
4539
5603
|
function filterInlineImplementations(impls) {
|
|
4540
5604
|
if (!impls) return impls;
|
|
4541
5605
|
return {
|
|
@@ -5283,6 +6347,108 @@ function isCompleteFunctionExpression(code) {
|
|
|
5283
6347
|
if (!expression) return false;
|
|
5284
6348
|
return ts.isFunctionExpression(expression) || ts.isArrowFunction(expression);
|
|
5285
6349
|
}
|
|
6350
|
+
function getXStateFSMCompatibilityIssues(graph) {
|
|
6351
|
+
const root = graph.nodes.find((node) => node.parentId == null);
|
|
6352
|
+
if (!root) return ["missing root state"];
|
|
6353
|
+
const states = graph.nodes.filter((node) => node.parentId === root.id);
|
|
6354
|
+
const stateIds = new Set(states.map((node) => node.id));
|
|
6355
|
+
const issues = /* @__PURE__ */ new Set();
|
|
6356
|
+
if (!root.data.initialId || !stateIds.has(root.data.initialId)) issues.add("missing initial state");
|
|
6357
|
+
if (graph.nodes.some((node) => node.parentId !== null && node.parentId !== root.id)) issues.add("nested states");
|
|
6358
|
+
if (states.some((node) => node.data.type && node.data.type !== "normal")) issues.add("non-atomic state types");
|
|
6359
|
+
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");
|
|
6360
|
+
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");
|
|
6361
|
+
if (graph.data.version || graph.data.options) issues.add("machine version or runtime options");
|
|
6362
|
+
const eventCounts = /* @__PURE__ */ new Map();
|
|
6363
|
+
for (const edge of graph.edges.filter((edge) => stateIds.has(edge.sourceId))) {
|
|
6364
|
+
const key = `${edge.sourceId}:${edge.data.eventType}`;
|
|
6365
|
+
eventCounts.set(key, (eventCounts.get(key) ?? 0) + 1);
|
|
6366
|
+
if (!edge.data.eventType) issues.add("eventless transitions");
|
|
6367
|
+
if (!stateIds.has(edge.targetId) && edge.data.transitionType !== "targetless") issues.add("transitions outside the flat state set");
|
|
6368
|
+
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");
|
|
6369
|
+
if (isCodeExpression$1(edge.data.context)) issues.add("dynamic transition context");
|
|
6370
|
+
}
|
|
6371
|
+
if ([...eventCounts.values()].some((count) => count > 1)) issues.add("multiple transitions for one event");
|
|
6372
|
+
return [...issues];
|
|
6373
|
+
}
|
|
6374
|
+
function graphToXStateFSM(graph, options = {}) {
|
|
6375
|
+
const root = graph.nodes.find((node) => node.parentId == null);
|
|
6376
|
+
if (!root) throw new Error("No root node found");
|
|
6377
|
+
const states = graph.nodes.filter((node) => node.parentId === root.id);
|
|
6378
|
+
const stateById = new Map(states.map((node) => [node.id, node]));
|
|
6379
|
+
const initial = root.data.initialId ? stateById.get(root.data.initialId)?.data.key : void 0;
|
|
6380
|
+
if (!initial) throw new Error("Cannot export XState FSM without an initial state.");
|
|
6381
|
+
const schemaFormat = options.xstateV6SchemaFormat ?? "types";
|
|
6382
|
+
const setupConfig = buildSetupConfig(graph, states, schemaFormat);
|
|
6383
|
+
const config = {
|
|
6384
|
+
id: root.data.nodeId || graph.id,
|
|
6385
|
+
initial,
|
|
6386
|
+
context: graph.data.context ?? void 0,
|
|
6387
|
+
states: Object.fromEntries(states.map((state) => [state.data.key, buildState(graph, state, stateById)]))
|
|
6388
|
+
};
|
|
6389
|
+
const imports = [setupConfig ? "setup" : "createFSM"];
|
|
6390
|
+
if (schemaFormat === "types" && setupConfig) imports.push("types");
|
|
6391
|
+
const lines = [`import { ${imports.join(", ")} } from 'xstate/fsm';`];
|
|
6392
|
+
if (schemaFormat === "zod" && setupConfig) lines.push(`import { z } from 'zod';`);
|
|
6393
|
+
const expression = setupConfig ? `setup(${serializeJS(setupConfig, 0)}).createFSM(${serializeJS(config, 0)})` : `createFSM(${serializeJS(config, 0)})`;
|
|
6394
|
+
lines.push("", `const machine = ${expression};`);
|
|
6395
|
+
if ((options.exportStyle ?? "named") === "named") lines.push("", "export { machine };");
|
|
6396
|
+
else if (options.exportStyle === "default") lines.push("", "export default machine;");
|
|
6397
|
+
return `${lines.join("\n")}\n`;
|
|
6398
|
+
}
|
|
6399
|
+
function buildState(graph, state, stateById) {
|
|
6400
|
+
const on = {};
|
|
6401
|
+
for (const edge of graph.edges.filter((candidate) => candidate.sourceId === state.id)) {
|
|
6402
|
+
const eventType = edge.data.eventType;
|
|
6403
|
+
if (!eventType || on[eventType] !== void 0) continue;
|
|
6404
|
+
const transition = buildTransition$1(edge, stateById);
|
|
6405
|
+
if (transition !== void 0) on[eventType] = transition;
|
|
6406
|
+
}
|
|
6407
|
+
return Object.keys(on).length ? { on } : {};
|
|
6408
|
+
}
|
|
6409
|
+
function buildTransition$1(edge, stateById) {
|
|
6410
|
+
const target = edge.data.transitionType === "targetless" ? void 0 : stateById.get(edge.targetId)?.data.key;
|
|
6411
|
+
const context = isCodeExpression$1(edge.data.context) ? void 0 : edge.data.context;
|
|
6412
|
+
if (context !== void 0) return {
|
|
6413
|
+
target,
|
|
6414
|
+
context
|
|
6415
|
+
};
|
|
6416
|
+
return target;
|
|
6417
|
+
}
|
|
6418
|
+
function buildSetupConfig(graph, states, format) {
|
|
6419
|
+
const schemas = {};
|
|
6420
|
+
const rootContext = graph.data.schemas?.context;
|
|
6421
|
+
const rootEvents = graph.data.schemas?.events;
|
|
6422
|
+
if (format === "types") {
|
|
6423
|
+
const context = contextSchemaToTSType(rootContext);
|
|
6424
|
+
if (context) schemas.context = raw(`types<${context}>()`);
|
|
6425
|
+
if (rootEvents && Object.keys(rootEvents).length) schemas.events = Object.fromEntries(Object.entries(rootEvents).map(([eventType, schema]) => [eventType, raw(`types<${eventPayloadType(schema)}>()`)]));
|
|
6426
|
+
} else {
|
|
6427
|
+
const context = contextSchemaToZod(rootContext);
|
|
6428
|
+
if (context) schemas.context = raw(context);
|
|
6429
|
+
if (rootEvents && Object.keys(rootEvents).length) schemas.events = Object.fromEntries(Object.entries(rootEvents).map(([eventType, schema]) => [eventType, raw(eventSchemaToZod(schema))]));
|
|
6430
|
+
}
|
|
6431
|
+
const stateContracts = Object.fromEntries(states.flatMap((state) => {
|
|
6432
|
+
const context = format === "types" ? contextSchemaToTSType(state.data.schemas?.context) : contextSchemaToZod(state.data.schemas?.context);
|
|
6433
|
+
return context ? [[state.data.key, { schemas: { context: raw(format === "types" ? `types<${context}>()` : context) } }]] : [];
|
|
6434
|
+
}));
|
|
6435
|
+
const setup = {};
|
|
6436
|
+
if (Object.keys(schemas).length) setup.schemas = schemas;
|
|
6437
|
+
if (Object.keys(stateContracts).length) setup.states = stateContracts;
|
|
6438
|
+
return Object.keys(setup).length ? setup : void 0;
|
|
6439
|
+
}
|
|
6440
|
+
function eventPayloadType(schema) {
|
|
6441
|
+
const properties = Object.fromEntries(Object.entries(schema.properties ?? {}).filter(([key]) => key !== "type"));
|
|
6442
|
+
const required = schema.required?.filter((key) => key !== "type");
|
|
6443
|
+
return jsonSchemaToTSType({
|
|
6444
|
+
...schema,
|
|
6445
|
+
properties,
|
|
6446
|
+
required
|
|
6447
|
+
});
|
|
6448
|
+
}
|
|
6449
|
+
function isCodeExpression$1(value) {
|
|
6450
|
+
return !!value && typeof value === "object" && !Array.isArray(value) && value["@type"] === "code";
|
|
6451
|
+
}
|
|
5286
6452
|
const colorSchema = _enum([
|
|
5287
6453
|
"green",
|
|
5288
6454
|
"red",
|
|
@@ -5664,4 +6830,4 @@ function getScriptKind(fileName) {
|
|
|
5664
6830
|
}
|
|
5665
6831
|
|
|
5666
6832
|
//#endregion
|
|
5667
|
-
export { RawCode as a,
|
|
6833
|
+
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 };
|