@bian-womp/spark-workbench 0.3.10 → 0.3.12
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 +145 -140
- package/lib/cjs/index.cjs.map +1 -1
- package/lib/cjs/src/misc/WorkbenchCanvas.d.ts.map +1 -1
- package/lib/cjs/src/misc/WorkbenchStudio.d.ts.map +1 -1
- package/lib/cjs/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
- package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts +8 -2
- package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
- package/lib/esm/index.js +145 -140
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/src/misc/WorkbenchCanvas.d.ts.map +1 -1
- package/lib/esm/src/misc/WorkbenchStudio.d.ts.map +1 -1
- package/lib/esm/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
- package/lib/esm/src/runtime/RemoteGraphRunner.d.ts +8 -2
- package/lib/esm/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
- package/package.json +4 -4
package/lib/cjs/index.cjs
CHANGED
|
@@ -1387,116 +1387,145 @@ let remoteRunnerCounter = 0;
|
|
|
1387
1387
|
class RemoteGraphRunner extends AbstractGraphRunner {
|
|
1388
1388
|
/**
|
|
1389
1389
|
* Fetch full registry description from remote and register it locally.
|
|
1390
|
-
*
|
|
1390
|
+
* Simplified with straightforward retry loop.
|
|
1391
|
+
* Ensures only one fetch happens at a time, even with concurrent calls.
|
|
1391
1392
|
*/
|
|
1392
|
-
async fetchRegistry(client
|
|
1393
|
-
if (this.
|
|
1394
|
-
// Already fetching, don't start another fetch
|
|
1393
|
+
async fetchRegistry(client) {
|
|
1394
|
+
if (this.registryFetched) {
|
|
1395
1395
|
return;
|
|
1396
1396
|
}
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1397
|
+
// If already fetching, wait for that fetch to complete
|
|
1398
|
+
if (this.registryFetchPromise) {
|
|
1399
|
+
return this.registryFetchPromise;
|
|
1400
|
+
}
|
|
1401
|
+
// Create promise wrapper and assign atomically to prevent race conditions
|
|
1402
|
+
// This ensures concurrent calls will see the promise and wait for it
|
|
1403
|
+
let promise;
|
|
1404
|
+
this.registryFetchPromise = promise = (async () => {
|
|
1405
|
+
try {
|
|
1406
|
+
await this._doFetchRegistry(client);
|
|
1407
|
+
}
|
|
1408
|
+
finally {
|
|
1409
|
+
// Clear the promise after completion (success or failure)
|
|
1410
|
+
this.registryFetchPromise = undefined;
|
|
1411
|
+
}
|
|
1412
|
+
})();
|
|
1413
|
+
return promise;
|
|
1414
|
+
}
|
|
1415
|
+
/**
|
|
1416
|
+
* Internal method that performs the actual registry fetch.
|
|
1417
|
+
*/
|
|
1418
|
+
async _doFetchRegistry(client) {
|
|
1419
|
+
let lastError;
|
|
1420
|
+
for (let attempt = 1; attempt <= this.MAX_REGISTRY_FETCH_ATTEMPTS; attempt++) {
|
|
1421
|
+
try {
|
|
1422
|
+
// Add timeout to registry fetch - if it exceeds 3s, retry
|
|
1423
|
+
let timeoutId;
|
|
1424
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
1425
|
+
timeoutId = setTimeout(() => reject(new Error("Registry fetch timeout (3s exceeded)")), this.REGISTRY_FETCH_TIMEOUT_MS);
|
|
1426
|
+
});
|
|
1427
|
+
const fetchPromise = client.api.describeRegistry().finally(() => {
|
|
1428
|
+
// Clear timeout if request completes first
|
|
1429
|
+
if (timeoutId) {
|
|
1430
|
+
clearTimeout(timeoutId);
|
|
1431
|
+
timeoutId = undefined;
|
|
1432
|
+
}
|
|
1433
|
+
});
|
|
1434
|
+
const desc = await Promise.race([fetchPromise, timeoutPromise]);
|
|
1435
|
+
// Register types
|
|
1436
|
+
for (const t of desc.types) {
|
|
1437
|
+
if (t.options) {
|
|
1438
|
+
this.registry.registerEnum({
|
|
1412
1439
|
id: t.id,
|
|
1413
|
-
|
|
1414
|
-
validate: (_v) => true,
|
|
1440
|
+
options: t.options,
|
|
1415
1441
|
bakeTarget: t.bakeTarget,
|
|
1416
1442
|
});
|
|
1417
1443
|
}
|
|
1444
|
+
else {
|
|
1445
|
+
if (!this.registry.types.has(t.id)) {
|
|
1446
|
+
this.registry.registerType({
|
|
1447
|
+
id: t.id,
|
|
1448
|
+
displayName: t.displayName,
|
|
1449
|
+
validate: (_v) => true,
|
|
1450
|
+
bakeTarget: t.bakeTarget,
|
|
1451
|
+
});
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1418
1454
|
}
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1455
|
+
// Register categories
|
|
1456
|
+
for (const c of desc.categories || []) {
|
|
1457
|
+
if (!this.registry.categories.has(c.id)) {
|
|
1458
|
+
// Create placeholder category descriptor
|
|
1459
|
+
const category = {
|
|
1460
|
+
id: c.id,
|
|
1461
|
+
displayName: c.displayName,
|
|
1462
|
+
createRuntime: () => ({
|
|
1463
|
+
async onInputsChanged() { },
|
|
1464
|
+
}),
|
|
1465
|
+
policy: { asyncConcurrency: "switch" },
|
|
1466
|
+
};
|
|
1467
|
+
this.registry.categories.register(category);
|
|
1468
|
+
}
|
|
1433
1469
|
}
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
}
|
|
1470
|
+
// Register coercions
|
|
1471
|
+
for (const c of desc.coercions) {
|
|
1472
|
+
if (c.async) {
|
|
1473
|
+
this.registry.registerAsyncCoercion(c.from, c.to, async (v) => v, {
|
|
1474
|
+
nonTransitive: c.nonTransitive,
|
|
1475
|
+
});
|
|
1476
|
+
}
|
|
1477
|
+
else {
|
|
1478
|
+
this.registry.registerCoercion(c.from, c.to, (v) => v, {
|
|
1479
|
+
nonTransitive: c.nonTransitive,
|
|
1480
|
+
});
|
|
1481
|
+
}
|
|
1441
1482
|
}
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1483
|
+
// Register nodes
|
|
1484
|
+
for (const n of desc.nodes) {
|
|
1485
|
+
if (!this.registry.nodes.has(n.id)) {
|
|
1486
|
+
this.registry.registerNode({
|
|
1487
|
+
id: n.id,
|
|
1488
|
+
categoryId: n.categoryId,
|
|
1489
|
+
displayName: n.displayName,
|
|
1490
|
+
inputs: n.inputs || {},
|
|
1491
|
+
outputs: n.outputs || {},
|
|
1492
|
+
policy: n.policy || {},
|
|
1493
|
+
impl: () => { },
|
|
1494
|
+
});
|
|
1495
|
+
}
|
|
1446
1496
|
}
|
|
1497
|
+
this.registryFetched = true;
|
|
1498
|
+
this.emit("registry", this.registry);
|
|
1499
|
+
return;
|
|
1447
1500
|
}
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
if (
|
|
1451
|
-
this.
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1501
|
+
catch (err) {
|
|
1502
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
1503
|
+
if (attempt < this.MAX_REGISTRY_FETCH_ATTEMPTS) {
|
|
1504
|
+
const delayMs = this.INITIAL_RETRY_DELAY_MS * Math.pow(2, attempt - 1);
|
|
1505
|
+
console.warn(`Failed to fetch registry (attempt ${attempt}/${this.MAX_REGISTRY_FETCH_ATTEMPTS}), retrying in ${delayMs}ms...`, lastError);
|
|
1506
|
+
// Emit error event for UI feedback
|
|
1507
|
+
this.emit("error", {
|
|
1508
|
+
kind: "registry",
|
|
1509
|
+
message: `Registry fetch failed (attempt ${attempt}/${this.MAX_REGISTRY_FETCH_ATTEMPTS}), retrying...`,
|
|
1510
|
+
err: lastError,
|
|
1511
|
+
attempt,
|
|
1512
|
+
maxAttempts: this.MAX_REGISTRY_FETCH_ATTEMPTS,
|
|
1459
1513
|
});
|
|
1514
|
+
// Wait before retrying
|
|
1515
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
1460
1516
|
}
|
|
1461
1517
|
}
|
|
1462
|
-
this.registryFetched = true;
|
|
1463
|
-
this.registryFetching = false;
|
|
1464
|
-
this.emit("registry", this.registry);
|
|
1465
|
-
}
|
|
1466
|
-
catch (err) {
|
|
1467
|
-
this.registryFetching = false;
|
|
1468
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
1469
|
-
// Retry with exponential backoff if attempts remaining
|
|
1470
|
-
if (attempt < this.MAX_REGISTRY_FETCH_ATTEMPTS) {
|
|
1471
|
-
const delayMs = this.INITIAL_RETRY_DELAY_MS * Math.pow(2, attempt - 1);
|
|
1472
|
-
console.warn(`Failed to fetch registry (attempt ${attempt}/${this.MAX_REGISTRY_FETCH_ATTEMPTS}), retrying in ${delayMs}ms...`, error);
|
|
1473
|
-
// Emit error event for UI feedback
|
|
1474
|
-
this.emit("error", {
|
|
1475
|
-
kind: "registry",
|
|
1476
|
-
message: `Registry fetch failed (attempt ${attempt}/${this.MAX_REGISTRY_FETCH_ATTEMPTS}), retrying...`,
|
|
1477
|
-
err: error,
|
|
1478
|
-
attempt,
|
|
1479
|
-
maxAttempts: this.MAX_REGISTRY_FETCH_ATTEMPTS,
|
|
1480
|
-
});
|
|
1481
|
-
// Retry after delay
|
|
1482
|
-
setTimeout(() => {
|
|
1483
|
-
this.fetchRegistry(client, attempt + 1).catch(() => {
|
|
1484
|
-
// Final failure handled below
|
|
1485
|
-
});
|
|
1486
|
-
}, delayMs);
|
|
1487
|
-
}
|
|
1488
|
-
else {
|
|
1489
|
-
// Max attempts reached, emit final error
|
|
1490
|
-
console.error(`Failed to fetch registry after ${this.MAX_REGISTRY_FETCH_ATTEMPTS} attempts:`, error);
|
|
1491
|
-
this.emit("error", {
|
|
1492
|
-
kind: "registry",
|
|
1493
|
-
message: `Failed to fetch registry after ${this.MAX_REGISTRY_FETCH_ATTEMPTS} attempts. Please check your connection and try refreshing.`,
|
|
1494
|
-
err: error,
|
|
1495
|
-
attempt: this.MAX_REGISTRY_FETCH_ATTEMPTS,
|
|
1496
|
-
maxAttempts: this.MAX_REGISTRY_FETCH_ATTEMPTS,
|
|
1497
|
-
});
|
|
1498
|
-
}
|
|
1499
1518
|
}
|
|
1519
|
+
// Max attempts reached, emit final error and throw
|
|
1520
|
+
console.error(`Failed to fetch registry after ${this.MAX_REGISTRY_FETCH_ATTEMPTS} attempts:`, lastError);
|
|
1521
|
+
this.emit("error", {
|
|
1522
|
+
kind: "registry",
|
|
1523
|
+
message: `Failed to fetch registry after ${this.MAX_REGISTRY_FETCH_ATTEMPTS} attempts. Please check your connection and try refreshing.`,
|
|
1524
|
+
err: lastError,
|
|
1525
|
+
attempt: this.MAX_REGISTRY_FETCH_ATTEMPTS,
|
|
1526
|
+
maxAttempts: this.MAX_REGISTRY_FETCH_ATTEMPTS,
|
|
1527
|
+
});
|
|
1528
|
+
throw lastError;
|
|
1500
1529
|
}
|
|
1501
1530
|
/**
|
|
1502
1531
|
* Build RemoteRuntimeClient config from RemoteExecutionBackend config.
|
|
@@ -1551,7 +1580,9 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
1551
1580
|
return this.clientPromise;
|
|
1552
1581
|
const backend = this.backend;
|
|
1553
1582
|
// Create connection promise to prevent concurrent connections
|
|
1554
|
-
|
|
1583
|
+
// Create promise wrapper first, then assign immediately before async work starts
|
|
1584
|
+
let promise;
|
|
1585
|
+
this.clientPromise = promise = (async () => {
|
|
1555
1586
|
// Build client config from backend config
|
|
1556
1587
|
const clientConfig = this.buildClientConfig(backend);
|
|
1557
1588
|
// Wrap custom event handler to intercept flow-viewport events and emit viewport event
|
|
@@ -1583,23 +1614,18 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
1583
1614
|
this.client = client;
|
|
1584
1615
|
this.valueCache.clear();
|
|
1585
1616
|
this.listenersBound = false;
|
|
1586
|
-
//
|
|
1587
|
-
if (
|
|
1588
|
-
|
|
1617
|
+
// Fetch registry before returning (wait for it to complete)
|
|
1618
|
+
// Only fetch if not already fetched (handles concurrent calls)
|
|
1619
|
+
if (!this.registryFetched) {
|
|
1589
1620
|
console.info("[RemoteGraphRunner] Loading registry from remote...");
|
|
1590
|
-
this.fetchRegistry(client)
|
|
1591
|
-
|
|
1592
|
-
console.info("[RemoteGraphRunner] Loaded registry from remote");
|
|
1593
|
-
})
|
|
1594
|
-
.catch((err) => {
|
|
1595
|
-
console.error("[RemoteGraphRunner] Failed to fetch registry:", err);
|
|
1596
|
-
});
|
|
1621
|
+
await this.fetchRegistry(client);
|
|
1622
|
+
console.info("[RemoteGraphRunner] Loaded registry from remote");
|
|
1597
1623
|
}
|
|
1598
1624
|
// Clear promise on success
|
|
1599
1625
|
this.clientPromise = undefined;
|
|
1600
1626
|
return client;
|
|
1601
1627
|
})();
|
|
1602
|
-
return
|
|
1628
|
+
return promise;
|
|
1603
1629
|
}
|
|
1604
1630
|
constructor(backend) {
|
|
1605
1631
|
super(backend);
|
|
@@ -1607,9 +1633,9 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
1607
1633
|
this.valueCache = new Map();
|
|
1608
1634
|
this.listenersBound = false;
|
|
1609
1635
|
this.registryFetched = false;
|
|
1610
|
-
this.registryFetching = false;
|
|
1611
1636
|
this.MAX_REGISTRY_FETCH_ATTEMPTS = 3;
|
|
1612
1637
|
this.INITIAL_RETRY_DELAY_MS = 1000; // 1 second
|
|
1638
|
+
this.REGISTRY_FETCH_TIMEOUT_MS = 3000; // 3 seconds
|
|
1613
1639
|
// Generate readable ID for this runner instance (e.g., remote-001, remote-002)
|
|
1614
1640
|
remoteRunnerCounter++;
|
|
1615
1641
|
this.runnerId = `remote-${String(remoteRunnerCounter).padStart(3, "0")}`;
|
|
@@ -2034,7 +2060,6 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
2034
2060
|
const clientToDispose = this.client;
|
|
2035
2061
|
this.client = undefined;
|
|
2036
2062
|
this.registryFetched = false; // Reset so registry is fetched again on reconnect
|
|
2037
|
-
this.registryFetching = false; // Reset fetching state
|
|
2038
2063
|
if (clientToDispose) {
|
|
2039
2064
|
try {
|
|
2040
2065
|
await clientToDispose.dispose();
|
|
@@ -4389,7 +4414,7 @@ function WorkbenchProvider({ wb, runner, overrides, uiVersion, children, }) {
|
|
|
4389
4414
|
console.error("[WorkbenchContext] Error updating graph:", err);
|
|
4390
4415
|
}
|
|
4391
4416
|
});
|
|
4392
|
-
const
|
|
4417
|
+
const offWbSetValidation = wb.on("validationChanged", (r) => setValidation(r));
|
|
4393
4418
|
const offWbSelectionChanged = wb.on("selectionChanged", async (sel) => {
|
|
4394
4419
|
setSelectedNodeId(sel.nodes?.[0]);
|
|
4395
4420
|
setSelectedEdgeId(sel.edges?.[0]);
|
|
@@ -4462,20 +4487,8 @@ function WorkbenchProvider({ wb, runner, overrides, uiVersion, children, }) {
|
|
|
4462
4487
|
});
|
|
4463
4488
|
const offWbError = wb.on("error", add("workbench", "error"));
|
|
4464
4489
|
// Registry updates: swap registry and refresh graph validation/UI
|
|
4465
|
-
const offRunnerRegistry = runner.on("registry", async (
|
|
4466
|
-
|
|
4467
|
-
wb.setRegistry(newReg);
|
|
4468
|
-
// Trigger a graph update so the UI revalidates with new types/enums/nodes
|
|
4469
|
-
try {
|
|
4470
|
-
await runner.update(wb.def);
|
|
4471
|
-
}
|
|
4472
|
-
catch {
|
|
4473
|
-
console.error("Failed to update graph definition after registry changed");
|
|
4474
|
-
}
|
|
4475
|
-
}
|
|
4476
|
-
catch {
|
|
4477
|
-
console.error("Failed to handle registry changed event");
|
|
4478
|
-
}
|
|
4490
|
+
const offRunnerRegistry = runner.on("registry", async (registry) => {
|
|
4491
|
+
wb.setRegistry(registry);
|
|
4479
4492
|
});
|
|
4480
4493
|
const offFlowViewport = runner.on("viewport", (event) => {
|
|
4481
4494
|
const viewport = event.viewport;
|
|
@@ -4542,7 +4555,7 @@ function WorkbenchProvider({ wb, runner, overrides, uiVersion, children, }) {
|
|
|
4542
4555
|
offWbValidationChanged();
|
|
4543
4556
|
offWbError();
|
|
4544
4557
|
offWbGraphChangedForUpdate();
|
|
4545
|
-
|
|
4558
|
+
offWbSetValidation();
|
|
4546
4559
|
offWbSelectionChanged();
|
|
4547
4560
|
offRunnerRegistry();
|
|
4548
4561
|
offRunnerTransport();
|
|
@@ -5847,14 +5860,9 @@ const WorkbenchCanvasComponent = React.forwardRef((props, ref) => {
|
|
|
5847
5860
|
const { nodeTypes, resolveNodeType } = React.useMemo(() => {
|
|
5848
5861
|
// Build nodeTypes map using UI extension registry
|
|
5849
5862
|
const custom = new Map(); // Include all types present in registry AND current graph to avoid timing issues
|
|
5850
|
-
const
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
]);
|
|
5854
|
-
for (const typeId of ids) {
|
|
5855
|
-
const renderer = ui.getNodeRenderer(typeId);
|
|
5856
|
-
if (renderer)
|
|
5857
|
-
custom.set(typeId, renderer);
|
|
5863
|
+
const allNodeRenderers = ui.getAllNodeRenderers();
|
|
5864
|
+
for (const typeId of Object.keys(allNodeRenderers)) {
|
|
5865
|
+
custom.set(typeId, allNodeRenderers[typeId]);
|
|
5858
5866
|
}
|
|
5859
5867
|
const types = {
|
|
5860
5868
|
"spark-default": DefaultNode,
|
|
@@ -5867,7 +5875,7 @@ const WorkbenchCanvasComponent = React.forwardRef((props, ref) => {
|
|
|
5867
5875
|
return { nodeTypes: types, resolveNodeType: resolver };
|
|
5868
5876
|
// Include uiVersion to recompute when custom renderers are registered
|
|
5869
5877
|
// Include registryVersion to recompute when registry enums/types change
|
|
5870
|
-
}, [wb, wb.registry, registryVersion,
|
|
5878
|
+
}, [wb, wb.registry, registryVersion, ui, uiVersion]);
|
|
5871
5879
|
const edgeTypes = React.useMemo(() => {
|
|
5872
5880
|
// Use default edge renderer override if registered, otherwise use DefaultEdge
|
|
5873
5881
|
const customEdgeRenderer = ui.getEdgeRenderer();
|
|
@@ -6526,10 +6534,7 @@ function WorkbenchStudioCanvas({ autoScroll, onAutoScrollChange, example, onExam
|
|
|
6526
6534
|
return;
|
|
6527
6535
|
const setInitialGraph = async (d, inputs) => {
|
|
6528
6536
|
await wb.load(d);
|
|
6529
|
-
|
|
6530
|
-
runner.build(wb.def);
|
|
6531
|
-
}
|
|
6532
|
-
catch { }
|
|
6537
|
+
runner.build(wb.def);
|
|
6533
6538
|
if (inputs) {
|
|
6534
6539
|
for (const [nodeId, map] of Object.entries(inputs)) {
|
|
6535
6540
|
runner.setInputs(nodeId, map, { dry: true });
|
|
@@ -6573,13 +6578,13 @@ function WorkbenchStudioCanvas({ autoScroll, onAutoScrollChange, example, onExam
|
|
|
6573
6578
|
const ex = examples.find((e) => e.id === key) ?? examples[0];
|
|
6574
6579
|
if (!ex)
|
|
6575
6580
|
return;
|
|
6576
|
-
const { registry
|
|
6581
|
+
const { registry, def, inputs } = await ex.load();
|
|
6577
6582
|
// Keep registry consistent with backend:
|
|
6578
6583
|
// - For local backend, allow example to provide its own registry
|
|
6579
6584
|
// - For remote backend, registry is automatically managed by RemoteGraphRunner
|
|
6580
6585
|
if (backendKind === "local") {
|
|
6581
|
-
if (
|
|
6582
|
-
wb.setRegistry(
|
|
6586
|
+
if (registry) {
|
|
6587
|
+
wb.setRegistry(registry);
|
|
6583
6588
|
}
|
|
6584
6589
|
}
|
|
6585
6590
|
await wb.load(def);
|