@bian-womp/spark-graph 0.2.16 → 0.2.17
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/lib/cjs/index.cjs +101 -173
- package/lib/cjs/index.cjs.map +1 -1
- package/lib/cjs/src/builder/GraphBuilder.d.ts.map +1 -1
- package/lib/esm/index.js +101 -173
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/src/builder/GraphBuilder.d.ts.map +1 -1
- package/package.json +2 -2
package/lib/cjs/index.cjs
CHANGED
|
@@ -1440,35 +1440,56 @@ class GraphBuilder {
|
|
|
1440
1440
|
const issues = [];
|
|
1441
1441
|
const nodeIds = new Set();
|
|
1442
1442
|
const edgeIds = new Set();
|
|
1443
|
+
// Precompute effective handle maps (registry statics merged with per-node resolvedHandles)
|
|
1444
|
+
const nodeById = new Map();
|
|
1445
|
+
const effByNodeId = new Map();
|
|
1446
|
+
const getEffectiveHandles = (n) => {
|
|
1447
|
+
if (!n)
|
|
1448
|
+
return { inputs: {}, outputs: {} };
|
|
1449
|
+
const desc = this.registry.nodes.get(n.typeId);
|
|
1450
|
+
const resolved = n.resolvedHandles || {};
|
|
1451
|
+
const inputs = { ...desc?.inputs, ...resolved.inputs };
|
|
1452
|
+
const outputs = { ...desc?.outputs, ...resolved.outputs };
|
|
1453
|
+
return { inputs, outputs };
|
|
1454
|
+
};
|
|
1455
|
+
const normOut = (decl) => Array.isArray(decl) ? decl : decl ? [decl] : [];
|
|
1456
|
+
const inferEdgeType = (srcDeclared, dstDeclared, explicit) => {
|
|
1457
|
+
if (explicit)
|
|
1458
|
+
return explicit;
|
|
1459
|
+
if (Array.isArray(srcDeclared) && dstDeclared)
|
|
1460
|
+
return dstDeclared;
|
|
1461
|
+
if (srcDeclared)
|
|
1462
|
+
return Array.isArray(srcDeclared) ? srcDeclared[0] : srcDeclared;
|
|
1463
|
+
return undefined;
|
|
1464
|
+
};
|
|
1465
|
+
const canFlow = (from, to) => {
|
|
1466
|
+
if (!to || !from)
|
|
1467
|
+
return true;
|
|
1468
|
+
const arr = Array.isArray(from) ? from : [from];
|
|
1469
|
+
return arr.every((s) => s === to || !!this.registry.canCoerce(s, to));
|
|
1470
|
+
};
|
|
1471
|
+
const pushIssue = (level, code, message, data) => {
|
|
1472
|
+
issues.push({ level, code, message, data });
|
|
1473
|
+
};
|
|
1443
1474
|
// nodes exist, ids unique, and categories registered
|
|
1444
1475
|
for (const n of def.nodes) {
|
|
1445
1476
|
if (nodeIds.has(n.nodeId)) {
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
code: "NODE_ID_DUP",
|
|
1449
|
-
message: `Duplicate nodeId ${n.nodeId}`,
|
|
1450
|
-
data: { nodeId: n.nodeId },
|
|
1477
|
+
pushIssue("error", "NODE_ID_DUP", `Duplicate nodeId ${n.nodeId}`, {
|
|
1478
|
+
nodeId: n.nodeId,
|
|
1451
1479
|
});
|
|
1452
1480
|
}
|
|
1453
1481
|
else {
|
|
1454
1482
|
nodeIds.add(n.nodeId);
|
|
1455
1483
|
}
|
|
1484
|
+
nodeById.set(n.nodeId, n);
|
|
1485
|
+
effByNodeId.set(n.nodeId, getEffectiveHandles(n));
|
|
1456
1486
|
const nodeType = this.registry.nodes.get(n.typeId);
|
|
1457
1487
|
if (!nodeType) {
|
|
1458
|
-
|
|
1459
|
-
level: "error",
|
|
1460
|
-
code: "NODE_TYPE_MISSING",
|
|
1461
|
-
message: `Unknown node type ${n.typeId}`,
|
|
1462
|
-
data: { typeId: n.typeId, nodeId: n.nodeId },
|
|
1463
|
-
});
|
|
1488
|
+
pushIssue("error", "NODE_TYPE_MISSING", `Unknown node type ${n.typeId}`, { typeId: n.typeId, nodeId: n.nodeId });
|
|
1464
1489
|
continue;
|
|
1465
1490
|
}
|
|
1466
1491
|
if (!this.registry.categories.has(nodeType.categoryId)) {
|
|
1467
|
-
|
|
1468
|
-
level: "error",
|
|
1469
|
-
code: "CATEGORY_MISSING",
|
|
1470
|
-
message: `Unknown category ${nodeType.categoryId} for node type ${n.typeId}`,
|
|
1471
|
-
});
|
|
1492
|
+
pushIssue("error", "CATEGORY_MISSING", `Unknown category ${nodeType.categoryId} for node type ${n.typeId}`);
|
|
1472
1493
|
}
|
|
1473
1494
|
}
|
|
1474
1495
|
// edges validation: nodes exist, handles exist, type exists
|
|
@@ -1477,186 +1498,96 @@ class GraphBuilder {
|
|
|
1477
1498
|
const inboundArrayOk = new Set();
|
|
1478
1499
|
for (const e of def.edges) {
|
|
1479
1500
|
if (edgeIds.has(e.id)) {
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
code: "EDGE_ID_DUP",
|
|
1483
|
-
message: `Duplicate edge id ${e.id}`,
|
|
1484
|
-
data: { edgeId: e.id },
|
|
1501
|
+
pushIssue("error", "EDGE_ID_DUP", `Duplicate edge id ${e.id}`, {
|
|
1502
|
+
edgeId: e.id,
|
|
1485
1503
|
});
|
|
1486
1504
|
}
|
|
1487
1505
|
else {
|
|
1488
1506
|
edgeIds.add(e.id);
|
|
1489
1507
|
}
|
|
1490
|
-
const srcNode =
|
|
1491
|
-
const dstNode =
|
|
1508
|
+
const srcNode = nodeById.get(e.source.nodeId);
|
|
1509
|
+
const dstNode = nodeById.get(e.target.nodeId);
|
|
1492
1510
|
if (!srcNode)
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
code: "EDGE_SOURCE_MISSING",
|
|
1496
|
-
message: `Edge ${e.id} source node missing`,
|
|
1497
|
-
data: { edgeId: e.id },
|
|
1511
|
+
pushIssue("error", "EDGE_SOURCE_MISSING", `Edge ${e.id} source node missing`, {
|
|
1512
|
+
edgeId: e.id,
|
|
1498
1513
|
});
|
|
1499
1514
|
if (!dstNode)
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
code: "EDGE_TARGET_MISSING",
|
|
1503
|
-
message: `Edge ${e.id} target node missing`,
|
|
1504
|
-
data: { edgeId: e.id },
|
|
1515
|
+
pushIssue("error", "EDGE_TARGET_MISSING", `Edge ${e.id} target node missing`, {
|
|
1516
|
+
edgeId: e.id,
|
|
1505
1517
|
});
|
|
1506
|
-
//
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
if (!effectiveTypeId) {
|
|
1521
|
-
if (Array.isArray(_srcDeclared) && _dstDeclared) {
|
|
1522
|
-
// When source is a union and target input type is known, adopt the input type
|
|
1523
|
-
// so validation checks are performed against the target, not an arbitrary variant.
|
|
1524
|
-
effectiveTypeId = _dstDeclared;
|
|
1525
|
-
}
|
|
1526
|
-
else if (_srcDeclared) {
|
|
1527
|
-
effectiveTypeId = Array.isArray(_srcDeclared)
|
|
1528
|
-
? _srcDeclared[0]
|
|
1529
|
-
: _srcDeclared;
|
|
1530
|
-
}
|
|
1531
|
-
}
|
|
1518
|
+
// Effective handle declarations
|
|
1519
|
+
const srcEff = effByNodeId.get(e.source.nodeId) || {
|
|
1520
|
+
outputs: {},
|
|
1521
|
+
};
|
|
1522
|
+
const dstEff = effByNodeId.get(e.target.nodeId) || {
|
|
1523
|
+
inputs: {}};
|
|
1524
|
+
const _srcDeclared = srcNode
|
|
1525
|
+
? srcEff.outputs[e.source.handle]
|
|
1526
|
+
: undefined;
|
|
1527
|
+
const _dstDeclared = dstNode
|
|
1528
|
+
? getInputTypeId(dstEff.inputs, e.target.handle)
|
|
1529
|
+
: undefined;
|
|
1530
|
+
// Effective edge type
|
|
1531
|
+
const effectiveTypeId = inferEdgeType(_srcDeclared, _dstDeclared, e.typeId);
|
|
1532
1532
|
const type = effectiveTypeId
|
|
1533
1533
|
? this.registry.types.get(effectiveTypeId)
|
|
1534
1534
|
: undefined;
|
|
1535
1535
|
if (!type) {
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
code: "TYPE_MISSING",
|
|
1539
|
-
message: `Edge ${e.id} type missing or unknown`,
|
|
1540
|
-
data: { edgeId: e.id },
|
|
1536
|
+
pushIssue("error", "TYPE_MISSING", `Edge ${e.id} type missing or unknown`, {
|
|
1537
|
+
edgeId: e.id,
|
|
1541
1538
|
});
|
|
1542
1539
|
}
|
|
1543
1540
|
if (srcNode) {
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
issues.push({
|
|
1547
|
-
level: "error",
|
|
1548
|
-
code: "OUTPUT_MISSING",
|
|
1549
|
-
message: `Edge ${e.id} source output ${e.source.handle} missing on ${srcNode.typeId}`,
|
|
1550
|
-
data: {
|
|
1551
|
-
edgeId: e.id,
|
|
1552
|
-
nodeId: srcNode.nodeId,
|
|
1553
|
-
output: e.source.handle,
|
|
1554
|
-
},
|
|
1555
|
-
});
|
|
1541
|
+
if (!(e.source.handle in srcEff.outputs)) {
|
|
1542
|
+
pushIssue("error", "OUTPUT_MISSING", `Edge ${e.id} source output ${e.source.handle} missing on ${srcNode.typeId}`, { edgeId: e.id, nodeId: srcNode.nodeId, output: e.source.handle });
|
|
1556
1543
|
}
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
issues.push({
|
|
1569
|
-
level: "error",
|
|
1570
|
-
code: "TYPE_MISMATCH_OUTPUT",
|
|
1571
|
-
message: `Edge ${e.id} type ${effectiveTypeId} mismatches source output ${srcNode.typeId}.${e.source.handle} (${s}) and no coercion exists`,
|
|
1572
|
-
data: {
|
|
1573
|
-
edgeId: e.id,
|
|
1574
|
-
nodeId: srcNode.nodeId,
|
|
1575
|
-
output: e.source.handle,
|
|
1576
|
-
declared: s,
|
|
1577
|
-
effectiveTypeId,
|
|
1578
|
-
},
|
|
1579
|
-
});
|
|
1580
|
-
}
|
|
1581
|
-
}
|
|
1582
|
-
}
|
|
1544
|
+
const declaredArr = normOut(srcEff.outputs[e.source.handle]);
|
|
1545
|
+
if (declaredArr.length > 0 &&
|
|
1546
|
+
effectiveTypeId &&
|
|
1547
|
+
!canFlow(declaredArr, effectiveTypeId)) {
|
|
1548
|
+
pushIssue("error", "TYPE_MISMATCH_OUTPUT", `Edge ${e.id} type ${effectiveTypeId} mismatches source output ${srcNode.typeId}.${e.source.handle} (${declaredArr.join("|")}) and no coercion exists`, {
|
|
1549
|
+
edgeId: e.id,
|
|
1550
|
+
nodeId: srcNode.nodeId,
|
|
1551
|
+
output: e.source.handle,
|
|
1552
|
+
declared: declaredArr.join("|"),
|
|
1553
|
+
effectiveTypeId,
|
|
1554
|
+
});
|
|
1583
1555
|
}
|
|
1584
1556
|
}
|
|
1585
1557
|
if (dstNode) {
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
data: {
|
|
1593
|
-
edgeId: e.id,
|
|
1594
|
-
nodeId: dstNode.nodeId,
|
|
1595
|
-
input: e.target.handle,
|
|
1596
|
-
},
|
|
1597
|
-
});
|
|
1558
|
+
if (!(e.target.handle in dstEff.inputs)) {
|
|
1559
|
+
pushIssue("error", "INPUT_MISSING", `Edge ${e.id} target input ${e.target.handle} missing on ${dstNode.typeId}`, { edgeId: e.id, nodeId: dstNode.nodeId, input: e.target.handle });
|
|
1560
|
+
}
|
|
1561
|
+
// Private inputs should not accept edges
|
|
1562
|
+
if (isInputPrivate(dstEff.inputs, e.target.handle)) {
|
|
1563
|
+
pushIssue("error", "INPUT_PRIVATE", `Edge ${e.id} targets private input ${dstNode.typeId}.${e.target.handle}`, { edgeId: e.id, nodeId: dstNode.nodeId, input: e.target.handle });
|
|
1598
1564
|
}
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
if (
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1565
|
+
const declaredIn = getInputTypeId(dstEff.inputs, e.target.handle);
|
|
1566
|
+
if (declaredIn && effectiveTypeId) {
|
|
1567
|
+
if (srcNode) {
|
|
1568
|
+
const srcDeclared = srcEff.outputs[e.source.handle];
|
|
1569
|
+
const srcArr = normOut(srcDeclared).length
|
|
1570
|
+
? normOut(srcDeclared)
|
|
1571
|
+
: [effectiveTypeId];
|
|
1572
|
+
if (!canFlow(srcArr, declaredIn)) {
|
|
1573
|
+
pushIssue("error", "TYPE_MISMATCH_INPUT", `Edge ${e.id} output type ${srcArr.join("|")} not convertible to target input ${dstNode.typeId}.${e.target.handle} (${declaredIn})`, {
|
|
1607
1574
|
edgeId: e.id,
|
|
1608
1575
|
nodeId: dstNode.nodeId,
|
|
1609
1576
|
input: e.target.handle,
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
}
|
|
1613
|
-
const declaredIn = getInputTypeId(dstType.inputs, e.target.handle);
|
|
1614
|
-
if (declaredIn && effectiveTypeId) {
|
|
1615
|
-
// If source is a union, ensure each variant can reach declaredIn
|
|
1616
|
-
if (srcNode) {
|
|
1617
|
-
const srcType = this.registry.nodes.get(srcNode.typeId);
|
|
1618
|
-
const srcDeclared = srcType?.outputs[e.source.handle];
|
|
1619
|
-
const srcArr = Array.isArray(srcDeclared)
|
|
1620
|
-
? srcDeclared
|
|
1621
|
-
: srcDeclared
|
|
1622
|
-
? [srcDeclared]
|
|
1623
|
-
: effectiveTypeId
|
|
1624
|
-
? [effectiveTypeId]
|
|
1625
|
-
: [];
|
|
1626
|
-
for (const s of srcArr) {
|
|
1627
|
-
if (s !== declaredIn &&
|
|
1628
|
-
!this.registry.canCoerce(s, declaredIn)) {
|
|
1629
|
-
issues.push({
|
|
1630
|
-
level: "error",
|
|
1631
|
-
code: "TYPE_MISMATCH_INPUT",
|
|
1632
|
-
message: `Edge ${e.id} output type ${s} not convertible to target input ${dstNode.typeId}.${e.target.handle} (${declaredIn})`,
|
|
1633
|
-
data: {
|
|
1634
|
-
edgeId: e.id,
|
|
1635
|
-
nodeId: dstNode.nodeId,
|
|
1636
|
-
input: e.target.handle,
|
|
1637
|
-
declared: declaredIn,
|
|
1638
|
-
effectiveTypeId: s,
|
|
1639
|
-
},
|
|
1640
|
-
});
|
|
1641
|
-
}
|
|
1642
|
-
}
|
|
1643
|
-
}
|
|
1644
|
-
else if (declaredIn !== effectiveTypeId &&
|
|
1645
|
-
!this.registry.canCoerce(effectiveTypeId, declaredIn)) {
|
|
1646
|
-
issues.push({
|
|
1647
|
-
level: "error",
|
|
1648
|
-
code: "TYPE_MISMATCH_INPUT",
|
|
1649
|
-
message: `Edge ${e.id} type ${effectiveTypeId} mismatches target input ${dstNode.typeId}.${e.target.handle} (${declaredIn}) and no coercion exists`,
|
|
1650
|
-
data: {
|
|
1651
|
-
edgeId: e.id,
|
|
1652
|
-
nodeId: dstNode.nodeId,
|
|
1653
|
-
input: e.target.handle,
|
|
1654
|
-
declared: declaredIn,
|
|
1655
|
-
effectiveTypeId,
|
|
1656
|
-
},
|
|
1577
|
+
declared: declaredIn,
|
|
1578
|
+
effectiveTypeId: srcArr.join("|"),
|
|
1657
1579
|
});
|
|
1658
1580
|
}
|
|
1659
1581
|
}
|
|
1582
|
+
else if (!canFlow(effectiveTypeId, declaredIn)) {
|
|
1583
|
+
pushIssue("error", "TYPE_MISMATCH_INPUT", `Edge ${e.id} type ${effectiveTypeId} mismatches target input ${dstNode.typeId}.${e.target.handle} (${declaredIn}) and no coercion exists`, {
|
|
1584
|
+
edgeId: e.id,
|
|
1585
|
+
nodeId: dstNode.nodeId,
|
|
1586
|
+
input: e.target.handle,
|
|
1587
|
+
declared: declaredIn,
|
|
1588
|
+
effectiveTypeId,
|
|
1589
|
+
});
|
|
1590
|
+
}
|
|
1660
1591
|
}
|
|
1661
1592
|
}
|
|
1662
1593
|
// Track multiple inbound edges targeting the same input handle
|
|
@@ -1664,10 +1595,7 @@ class GraphBuilder {
|
|
|
1664
1595
|
inboundCounts.set(inboundKey, (inboundCounts.get(inboundKey) ?? 0) + 1);
|
|
1665
1596
|
// If the target input is declared as an array type, allow multi-inbound (runtime will append)
|
|
1666
1597
|
if (dstNode) {
|
|
1667
|
-
const
|
|
1668
|
-
const declaredIn = dstType
|
|
1669
|
-
? getInputTypeId(dstType.inputs, e.target.handle)
|
|
1670
|
-
: undefined;
|
|
1598
|
+
const declaredIn = getInputTypeId((effByNodeId.get(dstNode.nodeId) || { inputs: {} }).inputs, e.target.handle);
|
|
1671
1599
|
if (typeof declaredIn === "string" && declaredIn.endsWith("[]")) {
|
|
1672
1600
|
inboundArrayOk.add(inboundKey);
|
|
1673
1601
|
}
|