@graphrefly/graphrefly 0.8.0 → 0.10.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/dist/compat/nestjs/index.js +1 -1
- package/dist/index.cjs +1135 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +405 -15
- package/dist/index.d.ts +405 -15
- package/dist/index.js +1141 -20
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -180,6 +180,7 @@ __export(index_exports, {
|
|
|
180
180
|
gate: () => gate,
|
|
181
181
|
globToRegExp: () => globToRegExp,
|
|
182
182
|
graph: () => graph_exports,
|
|
183
|
+
graphspec: () => graphspec_exports,
|
|
183
184
|
interval: () => interval,
|
|
184
185
|
isBatching: () => isBatching,
|
|
185
186
|
isKnownMessageType: () => isKnownMessageType,
|
|
@@ -237,6 +238,7 @@ __export(index_exports, {
|
|
|
237
238
|
reactiveLog: () => reactiveLog,
|
|
238
239
|
reactiveMap: () => reactiveMap,
|
|
239
240
|
reduce: () => reduce,
|
|
241
|
+
reduction: () => reduction_exports,
|
|
240
242
|
repeat: () => repeat,
|
|
241
243
|
replay: () => replay,
|
|
242
244
|
replayWAL: () => replayWAL,
|
|
@@ -12861,10 +12863,12 @@ __export(patterns_exports, {
|
|
|
12861
12863
|
ai: () => ai_exports,
|
|
12862
12864
|
cqrs: () => cqrs_exports,
|
|
12863
12865
|
demoShell: () => demo_shell_exports,
|
|
12866
|
+
graphspec: () => graphspec_exports,
|
|
12864
12867
|
layout: () => reactive_layout_exports,
|
|
12865
12868
|
memory: () => memory_exports,
|
|
12866
12869
|
messaging: () => messaging_exports,
|
|
12867
|
-
orchestration: () => orchestration_exports
|
|
12870
|
+
orchestration: () => orchestration_exports,
|
|
12871
|
+
reduction: () => reduction_exports
|
|
12868
12872
|
});
|
|
12869
12873
|
|
|
12870
12874
|
// src/patterns/ai.ts
|
|
@@ -15415,6 +15419,1123 @@ function demoShell(opts) {
|
|
|
15415
15419
|
};
|
|
15416
15420
|
}
|
|
15417
15421
|
|
|
15422
|
+
// src/patterns/graphspec.ts
|
|
15423
|
+
var graphspec_exports = {};
|
|
15424
|
+
__export(graphspec_exports, {
|
|
15425
|
+
compileSpec: () => compileSpec,
|
|
15426
|
+
decompileGraph: () => decompileGraph,
|
|
15427
|
+
llmCompose: () => llmCompose,
|
|
15428
|
+
llmRefine: () => llmRefine,
|
|
15429
|
+
specDiff: () => specDiff,
|
|
15430
|
+
validateSpec: () => validateSpec
|
|
15431
|
+
});
|
|
15432
|
+
|
|
15433
|
+
// src/patterns/reduction.ts
|
|
15434
|
+
var reduction_exports = {};
|
|
15435
|
+
__export(reduction_exports, {
|
|
15436
|
+
budgetGate: () => budgetGate,
|
|
15437
|
+
feedback: () => feedback,
|
|
15438
|
+
funnel: () => funnel,
|
|
15439
|
+
scorer: () => scorer,
|
|
15440
|
+
stratify: () => stratify
|
|
15441
|
+
});
|
|
15442
|
+
function baseMeta(kind, meta) {
|
|
15443
|
+
return {
|
|
15444
|
+
reduction: true,
|
|
15445
|
+
reduction_type: kind,
|
|
15446
|
+
...meta ?? {}
|
|
15447
|
+
};
|
|
15448
|
+
}
|
|
15449
|
+
function stratify(name, source, rules, opts) {
|
|
15450
|
+
const g = new Graph(name, opts);
|
|
15451
|
+
g.add("source", source);
|
|
15452
|
+
const rulesNode = state(rules, {
|
|
15453
|
+
meta: baseMeta("stratify_rules")
|
|
15454
|
+
});
|
|
15455
|
+
g.add("rules", rulesNode);
|
|
15456
|
+
for (const rule of rules) {
|
|
15457
|
+
_addBranch(g, source, rulesNode, rule);
|
|
15458
|
+
}
|
|
15459
|
+
return g;
|
|
15460
|
+
}
|
|
15461
|
+
function _addBranch(graph, source, rulesNode, rule) {
|
|
15462
|
+
const branchName = `branch/${rule.name}`;
|
|
15463
|
+
let pendingDirty = false;
|
|
15464
|
+
const filterNode = node([source, rulesNode], () => void 0, {
|
|
15465
|
+
describeKind: "operator",
|
|
15466
|
+
meta: baseMeta("stratify_branch", { branch: rule.name }),
|
|
15467
|
+
onMessage(msg, depIndex, actions) {
|
|
15468
|
+
if (depIndex !== 0) return false;
|
|
15469
|
+
const t = msg[0];
|
|
15470
|
+
if (t === DATA) {
|
|
15471
|
+
const value = msg[1];
|
|
15472
|
+
const currentRules = rulesNode.get();
|
|
15473
|
+
const currentRule = currentRules.find((r) => r.name === rule.name);
|
|
15474
|
+
if (currentRule && currentRule.classify(value)) {
|
|
15475
|
+
pendingDirty = false;
|
|
15476
|
+
actions.emit(value);
|
|
15477
|
+
} else {
|
|
15478
|
+
if (pendingDirty) {
|
|
15479
|
+
pendingDirty = false;
|
|
15480
|
+
actions.down([[DIRTY], [RESOLVED]]);
|
|
15481
|
+
}
|
|
15482
|
+
}
|
|
15483
|
+
return true;
|
|
15484
|
+
}
|
|
15485
|
+
if (t === DIRTY) {
|
|
15486
|
+
pendingDirty = true;
|
|
15487
|
+
return true;
|
|
15488
|
+
}
|
|
15489
|
+
if (t === RESOLVED) {
|
|
15490
|
+
if (pendingDirty) {
|
|
15491
|
+
pendingDirty = false;
|
|
15492
|
+
actions.down([[DIRTY], [RESOLVED]]);
|
|
15493
|
+
} else {
|
|
15494
|
+
actions.down([[RESOLVED]]);
|
|
15495
|
+
}
|
|
15496
|
+
return true;
|
|
15497
|
+
}
|
|
15498
|
+
if (t === COMPLETE || t === ERROR) {
|
|
15499
|
+
pendingDirty = false;
|
|
15500
|
+
actions.down([msg]);
|
|
15501
|
+
return true;
|
|
15502
|
+
}
|
|
15503
|
+
return false;
|
|
15504
|
+
}
|
|
15505
|
+
});
|
|
15506
|
+
graph.add(branchName, filterNode);
|
|
15507
|
+
graph.connect("source", branchName);
|
|
15508
|
+
if (rule.ops) {
|
|
15509
|
+
const transformed = rule.ops(filterNode);
|
|
15510
|
+
const transformedName = `branch/${rule.name}/out`;
|
|
15511
|
+
graph.add(transformedName, transformed);
|
|
15512
|
+
graph.connect(branchName, transformedName);
|
|
15513
|
+
}
|
|
15514
|
+
}
|
|
15515
|
+
function funnel(name, sources, stages, opts) {
|
|
15516
|
+
if (sources.length === 0) throw new RangeError("funnel requires at least one source");
|
|
15517
|
+
if (stages.length === 0) throw new RangeError("funnel requires at least one stage");
|
|
15518
|
+
const g = new Graph(name, opts);
|
|
15519
|
+
const merged = sources.length === 1 ? sources[0] : merge(...sources);
|
|
15520
|
+
g.add("merged", merged);
|
|
15521
|
+
let prevOutputPath = "merged";
|
|
15522
|
+
for (let i = 0; i < stages.length; i++) {
|
|
15523
|
+
const stage = stages[i];
|
|
15524
|
+
const sub = new Graph(stage.name);
|
|
15525
|
+
stage.build(sub);
|
|
15526
|
+
try {
|
|
15527
|
+
sub.resolve("input");
|
|
15528
|
+
} catch {
|
|
15529
|
+
throw new Error(`funnel stage "${stage.name}" must define an "input" node`);
|
|
15530
|
+
}
|
|
15531
|
+
try {
|
|
15532
|
+
sub.resolve("output");
|
|
15533
|
+
} catch {
|
|
15534
|
+
throw new Error(`funnel stage "${stage.name}" must define an "output" node`);
|
|
15535
|
+
}
|
|
15536
|
+
g.mount(stage.name, sub);
|
|
15537
|
+
const prevNode = g.resolve(prevOutputPath);
|
|
15538
|
+
const stageInputPath = `${stage.name}::input`;
|
|
15539
|
+
const stageInput = g.resolve(stageInputPath);
|
|
15540
|
+
prevNode.subscribe((msgs) => {
|
|
15541
|
+
for (const msg of msgs) {
|
|
15542
|
+
const t = msg[0];
|
|
15543
|
+
if (t === DATA) {
|
|
15544
|
+
stageInput.down([[DATA, msg[1]]]);
|
|
15545
|
+
} else if (t === DIRTY) {
|
|
15546
|
+
stageInput.down([[DIRTY]]);
|
|
15547
|
+
} else if (t === RESOLVED) {
|
|
15548
|
+
stageInput.down([[RESOLVED]]);
|
|
15549
|
+
} else if (t === COMPLETE || t === ERROR) {
|
|
15550
|
+
stageInput.down([msg]);
|
|
15551
|
+
}
|
|
15552
|
+
}
|
|
15553
|
+
});
|
|
15554
|
+
prevOutputPath = `${stage.name}::output`;
|
|
15555
|
+
}
|
|
15556
|
+
return g;
|
|
15557
|
+
}
|
|
15558
|
+
function feedback(graph, condition, reentry, opts) {
|
|
15559
|
+
const maxIter = opts?.maxIterations ?? 10;
|
|
15560
|
+
const counterName = `__feedback_${condition}`;
|
|
15561
|
+
const counter = state(0, {
|
|
15562
|
+
meta: baseMeta("feedback_counter", {
|
|
15563
|
+
maxIterations: maxIter,
|
|
15564
|
+
feedbackFrom: condition,
|
|
15565
|
+
feedbackTo: reentry
|
|
15566
|
+
})
|
|
15567
|
+
});
|
|
15568
|
+
graph.add(counterName, counter);
|
|
15569
|
+
const condNode = graph.resolve(condition);
|
|
15570
|
+
const reentryNode = graph.resolve(reentry);
|
|
15571
|
+
let tornDown = false;
|
|
15572
|
+
let unsubCounter = null;
|
|
15573
|
+
const safeUnsub = () => {
|
|
15574
|
+
if (tornDown) return;
|
|
15575
|
+
tornDown = true;
|
|
15576
|
+
unsub();
|
|
15577
|
+
unsubCounter?.();
|
|
15578
|
+
};
|
|
15579
|
+
const unsub = condNode.subscribe((msgs) => {
|
|
15580
|
+
for (const msg of msgs) {
|
|
15581
|
+
if (msg[0] === DATA) {
|
|
15582
|
+
const currentCount = counter.get();
|
|
15583
|
+
if (currentCount >= maxIter) continue;
|
|
15584
|
+
const condValue = msg[1];
|
|
15585
|
+
if (condValue == null) continue;
|
|
15586
|
+
counter.down([[DATA, currentCount + 1]]);
|
|
15587
|
+
reentryNode.down([[DATA, condValue]]);
|
|
15588
|
+
} else if (msg[0] === COMPLETE || msg[0] === ERROR) {
|
|
15589
|
+
const terminal = msg[0] === ERROR && msg.length > 1 ? [ERROR, msg[1]] : [msg[0]];
|
|
15590
|
+
counter.down([terminal]);
|
|
15591
|
+
safeUnsub();
|
|
15592
|
+
}
|
|
15593
|
+
}
|
|
15594
|
+
});
|
|
15595
|
+
unsubCounter = counter.subscribe((msgs) => {
|
|
15596
|
+
for (const msg of msgs) {
|
|
15597
|
+
if (msg[0] === COMPLETE || msg[0] === ERROR) {
|
|
15598
|
+
safeUnsub();
|
|
15599
|
+
return;
|
|
15600
|
+
}
|
|
15601
|
+
}
|
|
15602
|
+
});
|
|
15603
|
+
return graph;
|
|
15604
|
+
}
|
|
15605
|
+
function budgetGate(source, constraints, opts) {
|
|
15606
|
+
if (constraints.length === 0) throw new RangeError("budgetGate requires at least one constraint");
|
|
15607
|
+
const constraintNodes = constraints.map((c) => c.node);
|
|
15608
|
+
const allDeps = [source, ...constraintNodes];
|
|
15609
|
+
let buffer2 = [];
|
|
15610
|
+
let paused = false;
|
|
15611
|
+
let pendingResolved = false;
|
|
15612
|
+
const lockId = /* @__PURE__ */ Symbol("budget-gate");
|
|
15613
|
+
function checkBudget() {
|
|
15614
|
+
return constraints.every((c) => c.check(c.node.get()));
|
|
15615
|
+
}
|
|
15616
|
+
function flushBuffer(actions) {
|
|
15617
|
+
while (buffer2.length > 0 && checkBudget()) {
|
|
15618
|
+
const item = buffer2.shift();
|
|
15619
|
+
actions.emit(item);
|
|
15620
|
+
}
|
|
15621
|
+
if (buffer2.length === 0 && pendingResolved) {
|
|
15622
|
+
pendingResolved = false;
|
|
15623
|
+
actions.down([[RESOLVED]]);
|
|
15624
|
+
}
|
|
15625
|
+
}
|
|
15626
|
+
return node(allDeps, () => void 0, {
|
|
15627
|
+
...opts,
|
|
15628
|
+
describeKind: "operator",
|
|
15629
|
+
meta: baseMeta("budget_gate", opts?.meta),
|
|
15630
|
+
onMessage(msg, depIndex, actions) {
|
|
15631
|
+
const t = msg[0];
|
|
15632
|
+
if (depIndex === 0) {
|
|
15633
|
+
if (t === DATA) {
|
|
15634
|
+
if (checkBudget() && buffer2.length === 0) {
|
|
15635
|
+
actions.emit(msg[1]);
|
|
15636
|
+
} else {
|
|
15637
|
+
buffer2.push(msg[1]);
|
|
15638
|
+
if (!paused) {
|
|
15639
|
+
paused = true;
|
|
15640
|
+
actions.up([[PAUSE, lockId]]);
|
|
15641
|
+
}
|
|
15642
|
+
}
|
|
15643
|
+
return true;
|
|
15644
|
+
}
|
|
15645
|
+
if (t === DIRTY) {
|
|
15646
|
+
actions.down([[DIRTY]]);
|
|
15647
|
+
return true;
|
|
15648
|
+
}
|
|
15649
|
+
if (t === RESOLVED) {
|
|
15650
|
+
if (buffer2.length === 0) {
|
|
15651
|
+
actions.down([[RESOLVED]]);
|
|
15652
|
+
} else {
|
|
15653
|
+
pendingResolved = true;
|
|
15654
|
+
}
|
|
15655
|
+
return true;
|
|
15656
|
+
}
|
|
15657
|
+
if (t === COMPLETE || t === ERROR) {
|
|
15658
|
+
for (const item of buffer2) {
|
|
15659
|
+
actions.emit(item);
|
|
15660
|
+
}
|
|
15661
|
+
buffer2 = [];
|
|
15662
|
+
pendingResolved = false;
|
|
15663
|
+
if (paused) {
|
|
15664
|
+
paused = false;
|
|
15665
|
+
actions.up([[RESUME, lockId]]);
|
|
15666
|
+
}
|
|
15667
|
+
actions.down([msg]);
|
|
15668
|
+
return true;
|
|
15669
|
+
}
|
|
15670
|
+
return false;
|
|
15671
|
+
}
|
|
15672
|
+
if (t === DATA || t === RESOLVED) {
|
|
15673
|
+
if (checkBudget() && buffer2.length > 0) {
|
|
15674
|
+
flushBuffer(actions);
|
|
15675
|
+
if (buffer2.length === 0 && paused) {
|
|
15676
|
+
paused = false;
|
|
15677
|
+
actions.up([[RESUME, lockId]]);
|
|
15678
|
+
}
|
|
15679
|
+
} else if (!checkBudget() && !paused && buffer2.length > 0) {
|
|
15680
|
+
paused = true;
|
|
15681
|
+
actions.up([[PAUSE, lockId]]);
|
|
15682
|
+
}
|
|
15683
|
+
return true;
|
|
15684
|
+
}
|
|
15685
|
+
if (t === DIRTY) {
|
|
15686
|
+
return true;
|
|
15687
|
+
}
|
|
15688
|
+
if (t === ERROR) {
|
|
15689
|
+
actions.down([msg]);
|
|
15690
|
+
return true;
|
|
15691
|
+
}
|
|
15692
|
+
if (t === COMPLETE) {
|
|
15693
|
+
return true;
|
|
15694
|
+
}
|
|
15695
|
+
return false;
|
|
15696
|
+
}
|
|
15697
|
+
});
|
|
15698
|
+
}
|
|
15699
|
+
function scorer(sources, weights, opts) {
|
|
15700
|
+
if (sources.length === 0) throw new RangeError("scorer requires at least one source");
|
|
15701
|
+
if (sources.length !== weights.length) {
|
|
15702
|
+
throw new RangeError("scorer requires the same number of sources and weights");
|
|
15703
|
+
}
|
|
15704
|
+
const allDeps = [...sources, ...weights];
|
|
15705
|
+
const n = sources.length;
|
|
15706
|
+
const scoreFns = opts?.scoreFns;
|
|
15707
|
+
return derived(
|
|
15708
|
+
allDeps,
|
|
15709
|
+
(vals) => {
|
|
15710
|
+
const signals = vals.slice(0, n);
|
|
15711
|
+
const weightValues = vals.slice(n);
|
|
15712
|
+
const breakdown = [];
|
|
15713
|
+
let totalScore = 0;
|
|
15714
|
+
for (let i = 0; i < n; i++) {
|
|
15715
|
+
const sig = signals[i] ?? 0;
|
|
15716
|
+
const wt = weightValues[i] ?? 0;
|
|
15717
|
+
const rawScore = scoreFns?.[i] ? scoreFns[i](sig) : sig;
|
|
15718
|
+
const weighted = rawScore * wt;
|
|
15719
|
+
breakdown.push(weighted);
|
|
15720
|
+
totalScore += weighted;
|
|
15721
|
+
}
|
|
15722
|
+
return {
|
|
15723
|
+
value: signals,
|
|
15724
|
+
score: totalScore,
|
|
15725
|
+
breakdown
|
|
15726
|
+
};
|
|
15727
|
+
},
|
|
15728
|
+
{
|
|
15729
|
+
...opts,
|
|
15730
|
+
describeKind: "derived",
|
|
15731
|
+
meta: baseMeta("scorer", opts?.meta)
|
|
15732
|
+
}
|
|
15733
|
+
);
|
|
15734
|
+
}
|
|
15735
|
+
|
|
15736
|
+
// src/patterns/graphspec.ts
|
|
15737
|
+
var VALID_NODE_TYPES2 = /* @__PURE__ */ new Set([
|
|
15738
|
+
"state",
|
|
15739
|
+
"producer",
|
|
15740
|
+
"derived",
|
|
15741
|
+
"effect",
|
|
15742
|
+
"operator",
|
|
15743
|
+
"template"
|
|
15744
|
+
]);
|
|
15745
|
+
var INNER_NODE_TYPES = /* @__PURE__ */ new Set(["state", "producer", "derived", "effect", "operator"]);
|
|
15746
|
+
function validateSpec(spec) {
|
|
15747
|
+
const errors = [];
|
|
15748
|
+
if (spec == null || typeof spec !== "object") {
|
|
15749
|
+
return { valid: false, errors: ["GraphSpec must be a non-null object"] };
|
|
15750
|
+
}
|
|
15751
|
+
const s = spec;
|
|
15752
|
+
if (typeof s.name !== "string" || s.name.length === 0) {
|
|
15753
|
+
errors.push("Missing or empty 'name' field");
|
|
15754
|
+
}
|
|
15755
|
+
if (s.nodes == null || typeof s.nodes !== "object" || Array.isArray(s.nodes)) {
|
|
15756
|
+
errors.push("Missing or invalid 'nodes' field (must be an object)");
|
|
15757
|
+
return { valid: false, errors };
|
|
15758
|
+
}
|
|
15759
|
+
const nodeNames = new Set(Object.keys(s.nodes));
|
|
15760
|
+
const nodeTypes = /* @__PURE__ */ new Map();
|
|
15761
|
+
const templateDefs = /* @__PURE__ */ new Map();
|
|
15762
|
+
if (s.templates != null && typeof s.templates === "object" && !Array.isArray(s.templates)) {
|
|
15763
|
+
for (const [tName, tRaw] of Object.entries(s.templates)) {
|
|
15764
|
+
if (tRaw != null && typeof tRaw === "object") {
|
|
15765
|
+
const t = tRaw;
|
|
15766
|
+
templateDefs.set(tName, {
|
|
15767
|
+
params: Array.isArray(t.params) ? t.params : []
|
|
15768
|
+
});
|
|
15769
|
+
}
|
|
15770
|
+
}
|
|
15771
|
+
}
|
|
15772
|
+
if (s.templates != null) {
|
|
15773
|
+
if (typeof s.templates !== "object" || Array.isArray(s.templates)) {
|
|
15774
|
+
errors.push("'templates' must be an object");
|
|
15775
|
+
} else {
|
|
15776
|
+
for (const [tName, tRaw] of Object.entries(s.templates)) {
|
|
15777
|
+
if (tRaw == null || typeof tRaw !== "object") {
|
|
15778
|
+
errors.push(`Template "${tName}": must be an object`);
|
|
15779
|
+
continue;
|
|
15780
|
+
}
|
|
15781
|
+
const t = tRaw;
|
|
15782
|
+
if (!Array.isArray(t.params)) {
|
|
15783
|
+
errors.push(`Template "${tName}": missing 'params' array`);
|
|
15784
|
+
}
|
|
15785
|
+
if (t.nodes == null || typeof t.nodes !== "object" || Array.isArray(t.nodes)) {
|
|
15786
|
+
errors.push(`Template "${tName}": missing or invalid 'nodes' object`);
|
|
15787
|
+
} else {
|
|
15788
|
+
const paramSet = new Set(Array.isArray(t.params) ? t.params : []);
|
|
15789
|
+
const innerNames = new Set(Object.keys(t.nodes));
|
|
15790
|
+
for (const [nName, nRaw] of Object.entries(t.nodes)) {
|
|
15791
|
+
if (nRaw == null || typeof nRaw !== "object") {
|
|
15792
|
+
errors.push(`Template "${tName}" node "${nName}": must be an object`);
|
|
15793
|
+
continue;
|
|
15794
|
+
}
|
|
15795
|
+
const n = nRaw;
|
|
15796
|
+
if (typeof n.type !== "string" || !INNER_NODE_TYPES.has(n.type)) {
|
|
15797
|
+
errors.push(`Template "${tName}" node "${nName}": invalid type`);
|
|
15798
|
+
}
|
|
15799
|
+
if (Array.isArray(n.deps)) {
|
|
15800
|
+
for (const dep of n.deps) {
|
|
15801
|
+
if (!innerNames.has(dep) && !paramSet.has(dep)) {
|
|
15802
|
+
errors.push(
|
|
15803
|
+
`Template "${tName}" node "${nName}": dep "${dep}" is not an inner node or param`
|
|
15804
|
+
);
|
|
15805
|
+
}
|
|
15806
|
+
}
|
|
15807
|
+
}
|
|
15808
|
+
}
|
|
15809
|
+
if (typeof t.output !== "string") {
|
|
15810
|
+
errors.push(`Template "${tName}": missing 'output' string`);
|
|
15811
|
+
} else if (!t.nodes[t.output]) {
|
|
15812
|
+
errors.push(`Template "${tName}": output "${t.output}" is not a declared node`);
|
|
15813
|
+
}
|
|
15814
|
+
}
|
|
15815
|
+
}
|
|
15816
|
+
}
|
|
15817
|
+
}
|
|
15818
|
+
for (const [name, raw] of Object.entries(s.nodes)) {
|
|
15819
|
+
if (raw == null || typeof raw !== "object") {
|
|
15820
|
+
errors.push(`Node "${name}": must be an object`);
|
|
15821
|
+
continue;
|
|
15822
|
+
}
|
|
15823
|
+
const n = raw;
|
|
15824
|
+
if (typeof n.type !== "string" || !VALID_NODE_TYPES2.has(n.type)) {
|
|
15825
|
+
errors.push(
|
|
15826
|
+
`Node "${name}": invalid type "${String(n.type)}" (expected: ${[...VALID_NODE_TYPES2].join(", ")})`
|
|
15827
|
+
);
|
|
15828
|
+
continue;
|
|
15829
|
+
}
|
|
15830
|
+
nodeTypes.set(name, n.type);
|
|
15831
|
+
if (n.type === "template") {
|
|
15832
|
+
if (typeof n.template !== "string" || !templateDefs.has(n.template)) {
|
|
15833
|
+
errors.push(`Node "${name}": template "${String(n.template)}" not found in templates`);
|
|
15834
|
+
} else {
|
|
15835
|
+
if (n.bind == null || typeof n.bind !== "object" || Array.isArray(n.bind)) {
|
|
15836
|
+
errors.push(`Node "${name}": template ref requires 'bind' object`);
|
|
15837
|
+
} else {
|
|
15838
|
+
const tmpl = templateDefs.get(n.template);
|
|
15839
|
+
const bind = n.bind;
|
|
15840
|
+
for (const param of tmpl.params) {
|
|
15841
|
+
if (!(param in bind)) {
|
|
15842
|
+
errors.push(
|
|
15843
|
+
`Node "${name}": template param "${param}" is not bound (template "${n.template}")`
|
|
15844
|
+
);
|
|
15845
|
+
}
|
|
15846
|
+
}
|
|
15847
|
+
for (const [, target] of Object.entries(bind)) {
|
|
15848
|
+
if (typeof target === "string" && !nodeNames.has(target)) {
|
|
15849
|
+
errors.push(
|
|
15850
|
+
`Node "${name}": bind target "${target}" does not reference an existing node`
|
|
15851
|
+
);
|
|
15852
|
+
}
|
|
15853
|
+
}
|
|
15854
|
+
}
|
|
15855
|
+
}
|
|
15856
|
+
} else {
|
|
15857
|
+
if (Array.isArray(n.deps)) {
|
|
15858
|
+
for (const dep of n.deps) {
|
|
15859
|
+
if (dep === name) {
|
|
15860
|
+
errors.push(`Node "${name}": self-referencing dep`);
|
|
15861
|
+
} else if (!nodeNames.has(dep)) {
|
|
15862
|
+
errors.push(`Node "${name}": dep "${dep}" does not reference an existing node`);
|
|
15863
|
+
}
|
|
15864
|
+
}
|
|
15865
|
+
}
|
|
15866
|
+
if ((n.type === "derived" || n.type === "effect" || n.type === "operator") && !Array.isArray(n.deps)) {
|
|
15867
|
+
errors.push(`Node "${name}": ${n.type} node should have a 'deps' array`);
|
|
15868
|
+
}
|
|
15869
|
+
}
|
|
15870
|
+
}
|
|
15871
|
+
if (s.feedback != null) {
|
|
15872
|
+
if (!Array.isArray(s.feedback)) {
|
|
15873
|
+
errors.push("'feedback' must be an array");
|
|
15874
|
+
} else {
|
|
15875
|
+
for (let i = 0; i < s.feedback.length; i++) {
|
|
15876
|
+
const edge = s.feedback[i];
|
|
15877
|
+
if (edge == null || typeof edge !== "object") {
|
|
15878
|
+
errors.push(`Feedback [${i}]: must be an object`);
|
|
15879
|
+
continue;
|
|
15880
|
+
}
|
|
15881
|
+
const e = edge;
|
|
15882
|
+
if (typeof e.from !== "string" || !nodeNames.has(e.from)) {
|
|
15883
|
+
errors.push(
|
|
15884
|
+
`Feedback [${i}]: 'from' "${String(e.from)}" does not reference an existing node`
|
|
15885
|
+
);
|
|
15886
|
+
}
|
|
15887
|
+
if (typeof e.from === "string" && e.from === e.to) {
|
|
15888
|
+
errors.push(`Feedback [${i}]: 'from' and 'to' must be different nodes`);
|
|
15889
|
+
}
|
|
15890
|
+
if (typeof e.to !== "string" || !nodeNames.has(e.to)) {
|
|
15891
|
+
errors.push(
|
|
15892
|
+
`Feedback [${i}]: 'to' "${String(e.to)}" does not reference an existing node`
|
|
15893
|
+
);
|
|
15894
|
+
} else if (typeof e.to === "string" && nodeTypes.get(e.to) !== "state") {
|
|
15895
|
+
errors.push(
|
|
15896
|
+
`Feedback [${i}]: 'to' node "${e.to}" must be a state node (got "${nodeTypes.get(e.to) ?? "unknown"}")`
|
|
15897
|
+
);
|
|
15898
|
+
}
|
|
15899
|
+
}
|
|
15900
|
+
}
|
|
15901
|
+
}
|
|
15902
|
+
return { valid: errors.length === 0, errors };
|
|
15903
|
+
}
|
|
15904
|
+
function compileSpec(spec, opts) {
|
|
15905
|
+
const validation = validateSpec(spec);
|
|
15906
|
+
if (!validation.valid) {
|
|
15907
|
+
throw new Error(`compileSpec: invalid GraphSpec:
|
|
15908
|
+
${validation.errors.join("\n")}`);
|
|
15909
|
+
}
|
|
15910
|
+
const catalog = opts?.catalog ?? {};
|
|
15911
|
+
const g = new Graph(spec.name);
|
|
15912
|
+
const templates = spec.templates ?? {};
|
|
15913
|
+
const created = /* @__PURE__ */ new Map();
|
|
15914
|
+
const deferred = [];
|
|
15915
|
+
for (const [name, raw] of Object.entries(spec.nodes)) {
|
|
15916
|
+
if (raw.type === "template") continue;
|
|
15917
|
+
const n = raw;
|
|
15918
|
+
if (n.type === "state") {
|
|
15919
|
+
const nd = state(n.initial, {
|
|
15920
|
+
name,
|
|
15921
|
+
meta: n.meta ? { ...n.meta } : void 0
|
|
15922
|
+
});
|
|
15923
|
+
g.add(name, nd);
|
|
15924
|
+
created.set(name, nd);
|
|
15925
|
+
} else if (n.type === "producer") {
|
|
15926
|
+
const sourceFactory = n.source ? catalog.sources?.[n.source] : void 0;
|
|
15927
|
+
const fnFactory = n.fn ? catalog.fns?.[n.fn] : void 0;
|
|
15928
|
+
if (sourceFactory) {
|
|
15929
|
+
const nd = sourceFactory(n.config ?? {});
|
|
15930
|
+
g.add(name, nd);
|
|
15931
|
+
created.set(name, nd);
|
|
15932
|
+
} else if (fnFactory) {
|
|
15933
|
+
const nd = fnFactory([], n.config ?? {});
|
|
15934
|
+
g.add(name, nd);
|
|
15935
|
+
created.set(name, nd);
|
|
15936
|
+
} else {
|
|
15937
|
+
const nd = producer(() => {
|
|
15938
|
+
}, {
|
|
15939
|
+
name,
|
|
15940
|
+
meta: { ...n.meta, _specFn: n.fn, _specSource: n.source }
|
|
15941
|
+
});
|
|
15942
|
+
g.add(name, nd);
|
|
15943
|
+
created.set(name, nd);
|
|
15944
|
+
}
|
|
15945
|
+
} else {
|
|
15946
|
+
deferred.push([name, n]);
|
|
15947
|
+
}
|
|
15948
|
+
}
|
|
15949
|
+
let progressed = true;
|
|
15950
|
+
const pending = new Map(deferred);
|
|
15951
|
+
while (pending.size > 0 && progressed) {
|
|
15952
|
+
progressed = false;
|
|
15953
|
+
for (const [name, n] of [...pending.entries()]) {
|
|
15954
|
+
const deps = n.deps ?? [];
|
|
15955
|
+
if (!deps.every((dep) => created.has(dep))) continue;
|
|
15956
|
+
const resolvedDeps = deps.map((dep) => created.get(dep));
|
|
15957
|
+
const fnFactory = n.fn ? catalog.fns?.[n.fn] : void 0;
|
|
15958
|
+
let nd;
|
|
15959
|
+
if (fnFactory) {
|
|
15960
|
+
nd = fnFactory(resolvedDeps, n.config ?? {});
|
|
15961
|
+
} else if (n.type === "effect") {
|
|
15962
|
+
nd = effect(resolvedDeps, () => {
|
|
15963
|
+
});
|
|
15964
|
+
} else {
|
|
15965
|
+
nd = derived(resolvedDeps, (vals) => vals[0]);
|
|
15966
|
+
}
|
|
15967
|
+
g.add(name, nd);
|
|
15968
|
+
created.set(name, nd);
|
|
15969
|
+
pending.delete(name);
|
|
15970
|
+
progressed = true;
|
|
15971
|
+
}
|
|
15972
|
+
}
|
|
15973
|
+
if (pending.size > 0) {
|
|
15974
|
+
const unresolved = [...pending.keys()].sort().join(", ");
|
|
15975
|
+
throw new Error(`compileSpec: unresolvable deps for nodes: ${unresolved}`);
|
|
15976
|
+
}
|
|
15977
|
+
for (const [name, raw] of Object.entries(spec.nodes)) {
|
|
15978
|
+
if (raw.type !== "template") continue;
|
|
15979
|
+
const ref = raw;
|
|
15980
|
+
const tmpl = templates[ref.template];
|
|
15981
|
+
const sub = new Graph(name);
|
|
15982
|
+
const subCreated = /* @__PURE__ */ new Map();
|
|
15983
|
+
const subDeferred = [];
|
|
15984
|
+
for (const [nName, nSpec] of Object.entries(tmpl.nodes)) {
|
|
15985
|
+
const resolvedDeps = (nSpec.deps ?? []).map((dep) => {
|
|
15986
|
+
if (dep.startsWith("$") && ref.bind[dep]) {
|
|
15987
|
+
return ref.bind[dep];
|
|
15988
|
+
}
|
|
15989
|
+
return dep;
|
|
15990
|
+
});
|
|
15991
|
+
const specWithResolvedDeps = { ...nSpec, deps: resolvedDeps };
|
|
15992
|
+
if (nSpec.type === "state") {
|
|
15993
|
+
const nd = state(nSpec.initial, {
|
|
15994
|
+
name: nName,
|
|
15995
|
+
meta: nSpec.meta ? { ...nSpec.meta } : void 0
|
|
15996
|
+
});
|
|
15997
|
+
sub.add(nName, nd);
|
|
15998
|
+
subCreated.set(nName, nd);
|
|
15999
|
+
} else if (nSpec.type === "producer") {
|
|
16000
|
+
const sourceFactory = nSpec.source ? catalog.sources?.[nSpec.source] : void 0;
|
|
16001
|
+
const fnFactory = nSpec.fn ? catalog.fns?.[nSpec.fn] : void 0;
|
|
16002
|
+
if (sourceFactory) {
|
|
16003
|
+
const nd = sourceFactory(nSpec.config ?? {});
|
|
16004
|
+
sub.add(nName, nd);
|
|
16005
|
+
subCreated.set(nName, nd);
|
|
16006
|
+
} else if (fnFactory) {
|
|
16007
|
+
const nd = fnFactory([], nSpec.config ?? {});
|
|
16008
|
+
sub.add(nName, nd);
|
|
16009
|
+
subCreated.set(nName, nd);
|
|
16010
|
+
} else {
|
|
16011
|
+
const nd = producer(() => {
|
|
16012
|
+
}, {
|
|
16013
|
+
name: nName,
|
|
16014
|
+
meta: { ...nSpec.meta, _specFn: nSpec.fn, _specSource: nSpec.source }
|
|
16015
|
+
});
|
|
16016
|
+
sub.add(nName, nd);
|
|
16017
|
+
subCreated.set(nName, nd);
|
|
16018
|
+
}
|
|
16019
|
+
} else {
|
|
16020
|
+
subDeferred.push([nName, specWithResolvedDeps]);
|
|
16021
|
+
}
|
|
16022
|
+
}
|
|
16023
|
+
let subProgressed = true;
|
|
16024
|
+
const subPending = new Map(subDeferred);
|
|
16025
|
+
while (subPending.size > 0 && subProgressed) {
|
|
16026
|
+
subProgressed = false;
|
|
16027
|
+
for (const [nName, nSpec] of [...subPending.entries()]) {
|
|
16028
|
+
const deps = nSpec.deps ?? [];
|
|
16029
|
+
const allReady = deps.every((dep) => subCreated.has(dep) || created.has(dep));
|
|
16030
|
+
if (!allReady) continue;
|
|
16031
|
+
const resolvedDeps = deps.map((dep) => subCreated.get(dep) ?? created.get(dep));
|
|
16032
|
+
const fnFactory = nSpec.fn ? catalog.fns?.[nSpec.fn] : void 0;
|
|
16033
|
+
let nd;
|
|
16034
|
+
if (fnFactory) {
|
|
16035
|
+
nd = fnFactory(resolvedDeps, nSpec.config ?? {});
|
|
16036
|
+
} else if (nSpec.type === "effect") {
|
|
16037
|
+
nd = effect(resolvedDeps, () => {
|
|
16038
|
+
});
|
|
16039
|
+
} else {
|
|
16040
|
+
nd = derived(resolvedDeps, (vals) => vals[0]);
|
|
16041
|
+
}
|
|
16042
|
+
sub.add(nName, nd);
|
|
16043
|
+
subCreated.set(nName, nd);
|
|
16044
|
+
subPending.delete(nName);
|
|
16045
|
+
subProgressed = true;
|
|
16046
|
+
}
|
|
16047
|
+
}
|
|
16048
|
+
if (subPending.size > 0) {
|
|
16049
|
+
const unresolved = [...subPending.keys()].sort().join(", ");
|
|
16050
|
+
throw new Error(
|
|
16051
|
+
`compileSpec: template "${ref.template}" has unresolvable deps: ${unresolved}`
|
|
16052
|
+
);
|
|
16053
|
+
}
|
|
16054
|
+
g.mount(name, sub);
|
|
16055
|
+
const outputPath = `${name}::${tmpl.output}`;
|
|
16056
|
+
created.set(name, g.resolve(outputPath));
|
|
16057
|
+
try {
|
|
16058
|
+
const outputNode = g.resolve(outputPath);
|
|
16059
|
+
outputNode.meta._templateName?.down([[DATA, ref.template]]);
|
|
16060
|
+
outputNode.meta._templateBind?.down([[DATA, ref.bind]]);
|
|
16061
|
+
} catch {
|
|
16062
|
+
}
|
|
16063
|
+
}
|
|
16064
|
+
for (const [name, raw] of Object.entries(spec.nodes)) {
|
|
16065
|
+
if (raw.type === "template") continue;
|
|
16066
|
+
const n = raw;
|
|
16067
|
+
for (const dep of n.deps ?? []) {
|
|
16068
|
+
try {
|
|
16069
|
+
g.connect(dep, name);
|
|
16070
|
+
} catch (err) {
|
|
16071
|
+
const msg = err instanceof Error ? err.message : "";
|
|
16072
|
+
if (!msg.includes("constructor deps") && !msg.includes("already")) {
|
|
16073
|
+
throw err;
|
|
16074
|
+
}
|
|
16075
|
+
}
|
|
16076
|
+
}
|
|
16077
|
+
}
|
|
16078
|
+
for (const fb of spec.feedback ?? []) {
|
|
16079
|
+
feedback(g, fb.from, fb.to, {
|
|
16080
|
+
maxIterations: fb.maxIterations
|
|
16081
|
+
});
|
|
16082
|
+
}
|
|
16083
|
+
return g;
|
|
16084
|
+
}
|
|
16085
|
+
var INTERNAL_META_KEYS = /* @__PURE__ */ new Set([
|
|
16086
|
+
"reduction",
|
|
16087
|
+
"reduction_type",
|
|
16088
|
+
"_specFn",
|
|
16089
|
+
"_specSource",
|
|
16090
|
+
"_templateName",
|
|
16091
|
+
"_templateBind",
|
|
16092
|
+
"feedbackFrom",
|
|
16093
|
+
"feedbackTo"
|
|
16094
|
+
]);
|
|
16095
|
+
function decompileGraph(graph) {
|
|
16096
|
+
const desc = graph.describe({ detail: "standard" });
|
|
16097
|
+
const nodes = {};
|
|
16098
|
+
const feedbackEdges = [];
|
|
16099
|
+
const metaSegment = `::${GRAPH_META_SEGMENT}::`;
|
|
16100
|
+
const feedbackCounterPattern = /^__feedback_(.+)$/;
|
|
16101
|
+
const feedbackConditions = /* @__PURE__ */ new Set();
|
|
16102
|
+
for (const path of Object.keys(desc.nodes)) {
|
|
16103
|
+
if (path.includes(metaSegment)) continue;
|
|
16104
|
+
const match = feedbackCounterPattern.exec(path);
|
|
16105
|
+
if (match) {
|
|
16106
|
+
feedbackConditions.add(match[1]);
|
|
16107
|
+
const meta = desc.nodes[path]?.meta;
|
|
16108
|
+
if (meta?.feedbackFrom && meta?.feedbackTo) {
|
|
16109
|
+
feedbackEdges.push({
|
|
16110
|
+
from: meta.feedbackFrom,
|
|
16111
|
+
to: meta.feedbackTo,
|
|
16112
|
+
...meta.maxIterations ? { maxIterations: meta.maxIterations } : {}
|
|
16113
|
+
});
|
|
16114
|
+
}
|
|
16115
|
+
}
|
|
16116
|
+
}
|
|
16117
|
+
for (const [path, nodeDesc] of Object.entries(desc.nodes)) {
|
|
16118
|
+
if (path.includes(metaSegment)) continue;
|
|
16119
|
+
if (feedbackCounterPattern.test(path)) continue;
|
|
16120
|
+
if (path.includes("::")) continue;
|
|
16121
|
+
const specNode = {
|
|
16122
|
+
type: nodeDesc.type
|
|
16123
|
+
};
|
|
16124
|
+
if (nodeDesc.deps.length > 0) {
|
|
16125
|
+
specNode.deps = nodeDesc.deps.filter((d) => !d.includes("::"));
|
|
16126
|
+
}
|
|
16127
|
+
if (nodeDesc.type === "state" && nodeDesc.value !== void 0) {
|
|
16128
|
+
specNode.initial = nodeDesc.value;
|
|
16129
|
+
}
|
|
16130
|
+
if (nodeDesc.meta && Object.keys(nodeDesc.meta).length > 0) {
|
|
16131
|
+
const meta = {};
|
|
16132
|
+
for (const [k, v] of Object.entries(nodeDesc.meta)) {
|
|
16133
|
+
if (!INTERNAL_META_KEYS.has(k)) meta[k] = v;
|
|
16134
|
+
}
|
|
16135
|
+
if (Object.keys(meta).length > 0) {
|
|
16136
|
+
specNode.meta = meta;
|
|
16137
|
+
}
|
|
16138
|
+
}
|
|
16139
|
+
nodes[path] = specNode;
|
|
16140
|
+
}
|
|
16141
|
+
const templates = {};
|
|
16142
|
+
const templateRefs = {};
|
|
16143
|
+
const metaDetectedSubgraphs = /* @__PURE__ */ new Set();
|
|
16144
|
+
for (const subName of desc.subgraphs) {
|
|
16145
|
+
const prefix = `${subName}::`;
|
|
16146
|
+
for (const [path, nodeDesc] of Object.entries(desc.nodes)) {
|
|
16147
|
+
if (!path.startsWith(prefix)) continue;
|
|
16148
|
+
if (path.includes(metaSegment)) continue;
|
|
16149
|
+
const meta = nodeDesc.meta;
|
|
16150
|
+
if (meta?._templateName && meta?._templateBind) {
|
|
16151
|
+
const templateName = meta._templateName;
|
|
16152
|
+
const bind = meta._templateBind;
|
|
16153
|
+
if (!templates[templateName]) {
|
|
16154
|
+
const tmplNodes = {};
|
|
16155
|
+
const tmplInnerNames = /* @__PURE__ */ new Set();
|
|
16156
|
+
const tmplPrefix = `${subName}::`;
|
|
16157
|
+
for (const [p, nd] of Object.entries(desc.nodes)) {
|
|
16158
|
+
if (!p.startsWith(tmplPrefix) || p.includes(metaSegment)) continue;
|
|
16159
|
+
const localName = p.slice(tmplPrefix.length);
|
|
16160
|
+
if (localName.includes("::")) continue;
|
|
16161
|
+
tmplInnerNames.add(localName);
|
|
16162
|
+
tmplNodes[localName] = {
|
|
16163
|
+
type: nd.type,
|
|
16164
|
+
...nd.deps.length > 0 ? {
|
|
16165
|
+
deps: nd.deps.map(
|
|
16166
|
+
(d) => d.startsWith(tmplPrefix) ? d.slice(tmplPrefix.length) : d
|
|
16167
|
+
)
|
|
16168
|
+
} : {}
|
|
16169
|
+
};
|
|
16170
|
+
}
|
|
16171
|
+
const tmplParams = [];
|
|
16172
|
+
const tmplParamMap = /* @__PURE__ */ new Map();
|
|
16173
|
+
for (const n of Object.values(tmplNodes)) {
|
|
16174
|
+
for (const dep of n.deps ?? []) {
|
|
16175
|
+
if (!tmplInnerNames.has(dep) && !tmplParamMap.has(dep)) {
|
|
16176
|
+
const param = `$${dep}`;
|
|
16177
|
+
tmplParams.push(param);
|
|
16178
|
+
tmplParamMap.set(dep, param);
|
|
16179
|
+
}
|
|
16180
|
+
}
|
|
16181
|
+
}
|
|
16182
|
+
for (const n of Object.values(tmplNodes)) {
|
|
16183
|
+
if (n.deps) n.deps = n.deps.map((d) => tmplParamMap.get(d) ?? d);
|
|
16184
|
+
}
|
|
16185
|
+
const depended = /* @__PURE__ */ new Set();
|
|
16186
|
+
for (const n of Object.values(tmplNodes)) {
|
|
16187
|
+
for (const dep of n.deps ?? []) {
|
|
16188
|
+
if (tmplInnerNames.has(dep)) depended.add(dep);
|
|
16189
|
+
}
|
|
16190
|
+
}
|
|
16191
|
+
const outputCandidates = [...tmplInnerNames].filter((n) => !depended.has(n));
|
|
16192
|
+
const tmplOutput = outputCandidates[0] ?? [...tmplInnerNames].pop();
|
|
16193
|
+
templates[templateName] = { params: tmplParams, nodes: tmplNodes, output: tmplOutput };
|
|
16194
|
+
}
|
|
16195
|
+
delete nodes[subName];
|
|
16196
|
+
templateRefs[subName] = { type: "template", template: templateName, bind };
|
|
16197
|
+
metaDetectedSubgraphs.add(subName);
|
|
16198
|
+
break;
|
|
16199
|
+
}
|
|
16200
|
+
}
|
|
16201
|
+
}
|
|
16202
|
+
const structureMap = /* @__PURE__ */ new Map();
|
|
16203
|
+
for (const subName of desc.subgraphs) {
|
|
16204
|
+
if (metaDetectedSubgraphs.has(subName)) continue;
|
|
16205
|
+
const subNodes = {};
|
|
16206
|
+
const prefix = `${subName}::`;
|
|
16207
|
+
for (const [path, nodeDesc] of Object.entries(desc.nodes)) {
|
|
16208
|
+
if (path.includes(metaSegment)) continue;
|
|
16209
|
+
if (!path.startsWith(prefix)) continue;
|
|
16210
|
+
const localName = path.slice(prefix.length);
|
|
16211
|
+
if (localName.includes("::")) continue;
|
|
16212
|
+
subNodes[localName] = {
|
|
16213
|
+
type: nodeDesc.type,
|
|
16214
|
+
...nodeDesc.deps.length > 0 ? {
|
|
16215
|
+
deps: nodeDesc.deps.map((d) => d.startsWith(prefix) ? d.slice(prefix.length) : d)
|
|
16216
|
+
} : {}
|
|
16217
|
+
};
|
|
16218
|
+
}
|
|
16219
|
+
const fingerprint = JSON.stringify(
|
|
16220
|
+
Object.fromEntries(
|
|
16221
|
+
Object.entries(subNodes).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => [k, { type: v.type, deps: v.deps ?? [] }])
|
|
16222
|
+
)
|
|
16223
|
+
);
|
|
16224
|
+
if (!structureMap.has(fingerprint)) {
|
|
16225
|
+
structureMap.set(fingerprint, []);
|
|
16226
|
+
}
|
|
16227
|
+
structureMap.get(fingerprint).push({ name: subName, nodes: subNodes });
|
|
16228
|
+
}
|
|
16229
|
+
for (const [, group] of structureMap) {
|
|
16230
|
+
if (group.length < 2) continue;
|
|
16231
|
+
const templateName = `${group[0].name}_template`;
|
|
16232
|
+
const refNodes = group[0].nodes;
|
|
16233
|
+
const innerNames = new Set(Object.keys(refNodes));
|
|
16234
|
+
const params = [];
|
|
16235
|
+
const baseParamMap = /* @__PURE__ */ new Map();
|
|
16236
|
+
for (const n of Object.values(refNodes)) {
|
|
16237
|
+
for (const dep of n.deps ?? []) {
|
|
16238
|
+
if (!innerNames.has(dep) && !baseParamMap.has(dep)) {
|
|
16239
|
+
const param = `$${dep}`;
|
|
16240
|
+
params.push(param);
|
|
16241
|
+
baseParamMap.set(dep, param);
|
|
16242
|
+
}
|
|
16243
|
+
}
|
|
16244
|
+
}
|
|
16245
|
+
const depended = /* @__PURE__ */ new Set();
|
|
16246
|
+
for (const n of Object.values(refNodes)) {
|
|
16247
|
+
for (const dep of n.deps ?? []) {
|
|
16248
|
+
if (innerNames.has(dep)) depended.add(dep);
|
|
16249
|
+
}
|
|
16250
|
+
}
|
|
16251
|
+
const outputCandidates = [...innerNames].filter((n) => !depended.has(n));
|
|
16252
|
+
const output = outputCandidates[0] ?? [...innerNames].pop();
|
|
16253
|
+
const tmplNodes = {};
|
|
16254
|
+
for (const [nName, nSpec] of Object.entries(refNodes)) {
|
|
16255
|
+
tmplNodes[nName] = {
|
|
16256
|
+
...nSpec,
|
|
16257
|
+
deps: nSpec.deps?.map((d) => baseParamMap.get(d) ?? d)
|
|
16258
|
+
};
|
|
16259
|
+
}
|
|
16260
|
+
templates[templateName] = { params, nodes: tmplNodes, output };
|
|
16261
|
+
for (const member of group) {
|
|
16262
|
+
delete nodes[member.name];
|
|
16263
|
+
const memberBind = {};
|
|
16264
|
+
const memberInnerNames = new Set(Object.keys(member.nodes));
|
|
16265
|
+
for (const n of Object.values(member.nodes)) {
|
|
16266
|
+
for (const dep of n.deps ?? []) {
|
|
16267
|
+
if (!memberInnerNames.has(dep)) {
|
|
16268
|
+
const param = baseParamMap.get(dep) ?? `$${dep}`;
|
|
16269
|
+
memberBind[param] = dep;
|
|
16270
|
+
}
|
|
16271
|
+
}
|
|
16272
|
+
}
|
|
16273
|
+
templateRefs[member.name] = {
|
|
16274
|
+
type: "template",
|
|
16275
|
+
template: templateName,
|
|
16276
|
+
bind: memberBind
|
|
16277
|
+
};
|
|
16278
|
+
}
|
|
16279
|
+
}
|
|
16280
|
+
const allNodes = {
|
|
16281
|
+
...nodes,
|
|
16282
|
+
...templateRefs
|
|
16283
|
+
};
|
|
16284
|
+
const result = { name: desc.name, nodes: allNodes };
|
|
16285
|
+
if (Object.keys(templates).length > 0) result.templates = templates;
|
|
16286
|
+
if (feedbackEdges.length > 0) result.feedback = feedbackEdges;
|
|
16287
|
+
return result;
|
|
16288
|
+
}
|
|
16289
|
+
function specDiff(specA, specB) {
|
|
16290
|
+
const entries = [];
|
|
16291
|
+
if (specA.name !== specB.name) {
|
|
16292
|
+
entries.push({
|
|
16293
|
+
type: "changed",
|
|
16294
|
+
path: "name",
|
|
16295
|
+
detail: `"${specA.name}" \u2192 "${specB.name}"`
|
|
16296
|
+
});
|
|
16297
|
+
}
|
|
16298
|
+
const nodesA = new Set(Object.keys(specA.nodes));
|
|
16299
|
+
const nodesB = new Set(Object.keys(specB.nodes));
|
|
16300
|
+
for (const name of nodesB) {
|
|
16301
|
+
if (!nodesA.has(name)) {
|
|
16302
|
+
const n = specB.nodes[name];
|
|
16303
|
+
entries.push({
|
|
16304
|
+
type: "added",
|
|
16305
|
+
path: `nodes.${name}`,
|
|
16306
|
+
detail: `type: ${n.type}`
|
|
16307
|
+
});
|
|
16308
|
+
}
|
|
16309
|
+
}
|
|
16310
|
+
for (const name of nodesA) {
|
|
16311
|
+
if (!nodesB.has(name)) {
|
|
16312
|
+
entries.push({ type: "removed", path: `nodes.${name}` });
|
|
16313
|
+
}
|
|
16314
|
+
}
|
|
16315
|
+
for (const name of nodesA) {
|
|
16316
|
+
if (!nodesB.has(name)) continue;
|
|
16317
|
+
const a = specA.nodes[name];
|
|
16318
|
+
const b = specB.nodes[name];
|
|
16319
|
+
if (JSON.stringify(a) !== JSON.stringify(b)) {
|
|
16320
|
+
const details = [];
|
|
16321
|
+
if (a.type !== b.type) details.push(`type: ${a.type} \u2192 ${b.type}`);
|
|
16322
|
+
if (JSON.stringify(a.deps) !== JSON.stringify(b.deps)) {
|
|
16323
|
+
details.push("deps changed");
|
|
16324
|
+
}
|
|
16325
|
+
if (a.fn !== b.fn) {
|
|
16326
|
+
details.push(`fn: ${a.fn} \u2192 ${b.fn}`);
|
|
16327
|
+
}
|
|
16328
|
+
if (JSON.stringify(a.config) !== JSON.stringify(b.config)) {
|
|
16329
|
+
details.push("config changed");
|
|
16330
|
+
}
|
|
16331
|
+
entries.push({
|
|
16332
|
+
type: "changed",
|
|
16333
|
+
path: `nodes.${name}`,
|
|
16334
|
+
detail: details.join("; ") || "modified"
|
|
16335
|
+
});
|
|
16336
|
+
}
|
|
16337
|
+
}
|
|
16338
|
+
const tmplA = specA.templates ?? {};
|
|
16339
|
+
const tmplB = specB.templates ?? {};
|
|
16340
|
+
const tmplNamesA = new Set(Object.keys(tmplA));
|
|
16341
|
+
const tmplNamesB = new Set(Object.keys(tmplB));
|
|
16342
|
+
for (const name of tmplNamesB) {
|
|
16343
|
+
if (!tmplNamesA.has(name)) {
|
|
16344
|
+
entries.push({ type: "added", path: `templates.${name}` });
|
|
16345
|
+
}
|
|
16346
|
+
}
|
|
16347
|
+
for (const name of tmplNamesA) {
|
|
16348
|
+
if (!tmplNamesB.has(name)) {
|
|
16349
|
+
entries.push({ type: "removed", path: `templates.${name}` });
|
|
16350
|
+
}
|
|
16351
|
+
}
|
|
16352
|
+
for (const name of tmplNamesA) {
|
|
16353
|
+
if (!tmplNamesB.has(name)) continue;
|
|
16354
|
+
if (JSON.stringify(tmplA[name]) !== JSON.stringify(tmplB[name])) {
|
|
16355
|
+
entries.push({
|
|
16356
|
+
type: "changed",
|
|
16357
|
+
path: `templates.${name}`,
|
|
16358
|
+
detail: "template definition changed"
|
|
16359
|
+
});
|
|
16360
|
+
}
|
|
16361
|
+
}
|
|
16362
|
+
const fbA = specA.feedback ?? [];
|
|
16363
|
+
const fbB = specB.feedback ?? [];
|
|
16364
|
+
const fbKeyA = new Set(fbA.map((e) => `${e.from}->${e.to}`));
|
|
16365
|
+
const fbKeyB = new Set(fbB.map((e) => `${e.from}->${e.to}`));
|
|
16366
|
+
for (const fb of fbB) {
|
|
16367
|
+
const key = `${fb.from}->${fb.to}`;
|
|
16368
|
+
if (!fbKeyA.has(key)) {
|
|
16369
|
+
entries.push({
|
|
16370
|
+
type: "added",
|
|
16371
|
+
path: `feedback.${key}`,
|
|
16372
|
+
detail: `maxIterations: ${fb.maxIterations ?? 10}`
|
|
16373
|
+
});
|
|
16374
|
+
}
|
|
16375
|
+
}
|
|
16376
|
+
for (const fb of fbA) {
|
|
16377
|
+
const key = `${fb.from}->${fb.to}`;
|
|
16378
|
+
if (!fbKeyB.has(key)) {
|
|
16379
|
+
entries.push({ type: "removed", path: `feedback.${key}` });
|
|
16380
|
+
}
|
|
16381
|
+
}
|
|
16382
|
+
for (const fb of fbA) {
|
|
16383
|
+
const key = `${fb.from}->${fb.to}`;
|
|
16384
|
+
const counterpart = fbB.find((b) => b.from === fb.from && b.to === fb.to);
|
|
16385
|
+
if (counterpart && JSON.stringify(fb) !== JSON.stringify(counterpart)) {
|
|
16386
|
+
entries.push({
|
|
16387
|
+
type: "changed",
|
|
16388
|
+
path: `feedback.${key}`,
|
|
16389
|
+
detail: `maxIterations: ${fb.maxIterations ?? 10} \u2192 ${counterpart.maxIterations ?? 10}`
|
|
16390
|
+
});
|
|
16391
|
+
}
|
|
16392
|
+
}
|
|
16393
|
+
const added = entries.filter((e) => e.type === "added").length;
|
|
16394
|
+
const removed = entries.filter((e) => e.type === "removed").length;
|
|
16395
|
+
const changed = entries.filter((e) => e.type === "changed").length;
|
|
16396
|
+
const parts = [];
|
|
16397
|
+
if (added) parts.push(`${added} added`);
|
|
16398
|
+
if (removed) parts.push(`${removed} removed`);
|
|
16399
|
+
if (changed) parts.push(`${changed} changed`);
|
|
16400
|
+
const summary = parts.length > 0 ? parts.join(", ") : "no changes";
|
|
16401
|
+
return { entries, summary };
|
|
16402
|
+
}
|
|
16403
|
+
var LLM_COMPOSE_SYSTEM_PROMPT = `You are a graph architect for GraphReFly, a reactive graph protocol.
|
|
16404
|
+
|
|
16405
|
+
Given a natural-language description, produce a JSON GraphSpec with this structure:
|
|
16406
|
+
|
|
16407
|
+
{
|
|
16408
|
+
"name": "<graph_name>",
|
|
16409
|
+
"nodes": {
|
|
16410
|
+
"<node_name>": {
|
|
16411
|
+
"type": "state" | "derived" | "producer" | "effect" | "operator",
|
|
16412
|
+
"deps": ["<dep_node_name>", ...],
|
|
16413
|
+
"fn": "<catalog_function_name>",
|
|
16414
|
+
"source": "<catalog_source_name>",
|
|
16415
|
+
"config": { ... },
|
|
16416
|
+
"initial": <value>,
|
|
16417
|
+
"meta": { "description": "<purpose>" }
|
|
16418
|
+
},
|
|
16419
|
+
"<template_instance>": {
|
|
16420
|
+
"type": "template",
|
|
16421
|
+
"template": "<template_name>",
|
|
16422
|
+
"bind": { "$param": "node_name" }
|
|
16423
|
+
}
|
|
16424
|
+
},
|
|
16425
|
+
"templates": {
|
|
16426
|
+
"<template_name>": {
|
|
16427
|
+
"params": ["$param1", "$param2"],
|
|
16428
|
+
"nodes": { ... },
|
|
16429
|
+
"output": "<output_node>"
|
|
16430
|
+
}
|
|
16431
|
+
},
|
|
16432
|
+
"feedback": [
|
|
16433
|
+
{ "from": "<condition_node>", "to": "<state_node>", "maxIterations": 10 }
|
|
16434
|
+
]
|
|
16435
|
+
}
|
|
16436
|
+
|
|
16437
|
+
Rules:
|
|
16438
|
+
- "state" nodes hold user/LLM-writable values (knobs). Use "initial" for default values.
|
|
16439
|
+
- "derived" nodes compute from deps using a named "fn".
|
|
16440
|
+
- "effect" nodes produce side effects from deps.
|
|
16441
|
+
- "producer" nodes generate values from a named "source".
|
|
16442
|
+
- Use "templates" when the same subgraph pattern repeats (e.g., per-source resilience).
|
|
16443
|
+
- Use "feedback" for bounded cycles where a derived value writes back to a state node.
|
|
16444
|
+
- meta.description is required for every node.
|
|
16445
|
+
- Return ONLY valid JSON, no markdown fences or commentary.`;
|
|
16446
|
+
function stripFences2(text) {
|
|
16447
|
+
const match = text.match(/^```(?:json)?\s*([\s\S]*?)\s*```[\s\S]*$/);
|
|
16448
|
+
return match ? match[1] : text;
|
|
16449
|
+
}
|
|
16450
|
+
async function llmCompose(problem, adapter, opts) {
|
|
16451
|
+
let systemPrompt = LLM_COMPOSE_SYSTEM_PROMPT;
|
|
16452
|
+
if (opts?.catalogDescription) {
|
|
16453
|
+
systemPrompt += `
|
|
16454
|
+
|
|
16455
|
+
Available catalog:
|
|
16456
|
+
${opts.catalogDescription}`;
|
|
16457
|
+
}
|
|
16458
|
+
if (opts?.systemPromptExtra) {
|
|
16459
|
+
systemPrompt += `
|
|
16460
|
+
|
|
16461
|
+
${opts.systemPromptExtra}`;
|
|
16462
|
+
}
|
|
16463
|
+
const messages = [
|
|
16464
|
+
{ role: "system", content: systemPrompt },
|
|
16465
|
+
{ role: "user", content: problem }
|
|
16466
|
+
];
|
|
16467
|
+
const rawResult = adapter.invoke(messages, {
|
|
16468
|
+
model: opts?.model,
|
|
16469
|
+
temperature: opts?.temperature ?? 0,
|
|
16470
|
+
maxTokens: opts?.maxTokens
|
|
16471
|
+
});
|
|
16472
|
+
const response = await rawResult;
|
|
16473
|
+
let content = response.content.trim();
|
|
16474
|
+
if (content.startsWith("```")) {
|
|
16475
|
+
content = stripFences2(content);
|
|
16476
|
+
}
|
|
16477
|
+
let parsed;
|
|
16478
|
+
try {
|
|
16479
|
+
parsed = JSON.parse(content);
|
|
16480
|
+
} catch {
|
|
16481
|
+
throw new Error(`llmCompose: LLM response is not valid JSON: ${content.slice(0, 200)}`);
|
|
16482
|
+
}
|
|
16483
|
+
const validation = validateSpec(parsed);
|
|
16484
|
+
if (!validation.valid) {
|
|
16485
|
+
throw new Error(`llmCompose: invalid GraphSpec:
|
|
16486
|
+
${validation.errors.join("\n")}`);
|
|
16487
|
+
}
|
|
16488
|
+
return parsed;
|
|
16489
|
+
}
|
|
16490
|
+
async function llmRefine(currentSpec, feedback2, adapter, opts) {
|
|
16491
|
+
let systemPrompt = LLM_COMPOSE_SYSTEM_PROMPT;
|
|
16492
|
+
if (opts?.catalogDescription) {
|
|
16493
|
+
systemPrompt += `
|
|
16494
|
+
|
|
16495
|
+
Available catalog:
|
|
16496
|
+
${opts.catalogDescription}`;
|
|
16497
|
+
}
|
|
16498
|
+
if (opts?.systemPromptExtra) {
|
|
16499
|
+
systemPrompt += `
|
|
16500
|
+
|
|
16501
|
+
${opts.systemPromptExtra}`;
|
|
16502
|
+
}
|
|
16503
|
+
const messages = [
|
|
16504
|
+
{ role: "system", content: systemPrompt },
|
|
16505
|
+
{
|
|
16506
|
+
role: "user",
|
|
16507
|
+
content: `Current GraphSpec:
|
|
16508
|
+
${JSON.stringify(currentSpec, null, 2)}
|
|
16509
|
+
|
|
16510
|
+
Modification request: ${feedback2}
|
|
16511
|
+
|
|
16512
|
+
Return the complete modified GraphSpec as JSON.`
|
|
16513
|
+
}
|
|
16514
|
+
];
|
|
16515
|
+
const rawResult = adapter.invoke(messages, {
|
|
16516
|
+
model: opts?.model,
|
|
16517
|
+
temperature: opts?.temperature ?? 0,
|
|
16518
|
+
maxTokens: opts?.maxTokens
|
|
16519
|
+
});
|
|
16520
|
+
const response = await rawResult;
|
|
16521
|
+
let content = response.content.trim();
|
|
16522
|
+
if (content.startsWith("```")) {
|
|
16523
|
+
content = stripFences2(content);
|
|
16524
|
+
}
|
|
16525
|
+
let parsed;
|
|
16526
|
+
try {
|
|
16527
|
+
parsed = JSON.parse(content);
|
|
16528
|
+
} catch {
|
|
16529
|
+
throw new Error(`llmRefine: LLM response is not valid JSON: ${content.slice(0, 200)}`);
|
|
16530
|
+
}
|
|
16531
|
+
const validation = validateSpec(parsed);
|
|
16532
|
+
if (!validation.valid) {
|
|
16533
|
+
throw new Error(`llmRefine: invalid GraphSpec:
|
|
16534
|
+
${validation.errors.join("\n")}`);
|
|
16535
|
+
}
|
|
16536
|
+
return parsed;
|
|
16537
|
+
}
|
|
16538
|
+
|
|
15418
16539
|
// src/patterns/messaging.ts
|
|
15419
16540
|
var messaging_exports = {};
|
|
15420
16541
|
__export(messaging_exports, {
|
|
@@ -15863,7 +16984,7 @@ function registerStep(graph, name, step, depPaths) {
|
|
|
15863
16984
|
graph.connect(path, name);
|
|
15864
16985
|
}
|
|
15865
16986
|
}
|
|
15866
|
-
function
|
|
16987
|
+
function baseMeta2(kind, meta) {
|
|
15867
16988
|
return {
|
|
15868
16989
|
orchestration: true,
|
|
15869
16990
|
orchestration_type: kind,
|
|
@@ -15901,7 +17022,7 @@ function task(graph, name, run, opts) {
|
|
|
15901
17022
|
...nodeOpts,
|
|
15902
17023
|
name,
|
|
15903
17024
|
describeKind: "derived",
|
|
15904
|
-
meta:
|
|
17025
|
+
meta: baseMeta2("task", opts?.meta)
|
|
15905
17026
|
}
|
|
15906
17027
|
);
|
|
15907
17028
|
registerStep(
|
|
@@ -15924,7 +17045,7 @@ function branch(graph, name, source, predicate, opts) {
|
|
|
15924
17045
|
...opts,
|
|
15925
17046
|
name,
|
|
15926
17047
|
describeKind: "derived",
|
|
15927
|
-
meta:
|
|
17048
|
+
meta: baseMeta2("branch", opts?.meta)
|
|
15928
17049
|
}
|
|
15929
17050
|
);
|
|
15930
17051
|
registerStep(graph, name, step, src.path ? [src.path] : []);
|
|
@@ -15947,7 +17068,7 @@ function gate2(graph, name, source, control, opts) {
|
|
|
15947
17068
|
...opts,
|
|
15948
17069
|
name,
|
|
15949
17070
|
describeKind: "operator",
|
|
15950
|
-
meta:
|
|
17071
|
+
meta: baseMeta2("gate", opts?.meta)
|
|
15951
17072
|
}
|
|
15952
17073
|
);
|
|
15953
17074
|
registerStep(
|
|
@@ -15975,7 +17096,7 @@ function approval(graph, name, source, approver, opts) {
|
|
|
15975
17096
|
...opts,
|
|
15976
17097
|
name,
|
|
15977
17098
|
describeKind: "operator",
|
|
15978
|
-
meta:
|
|
17099
|
+
meta: baseMeta2("approval", opts?.meta)
|
|
15979
17100
|
}
|
|
15980
17101
|
);
|
|
15981
17102
|
registerStep(
|
|
@@ -15994,7 +17115,7 @@ function forEach2(graph, name, source, run, opts) {
|
|
|
15994
17115
|
name,
|
|
15995
17116
|
describeKind: "effect",
|
|
15996
17117
|
completeWhenDepsComplete: false,
|
|
15997
|
-
meta:
|
|
17118
|
+
meta: baseMeta2("forEach", opts?.meta),
|
|
15998
17119
|
onMessage(msg, depIndex, actions) {
|
|
15999
17120
|
if (terminated) return true;
|
|
16000
17121
|
if (depIndex !== 0) {
|
|
@@ -16029,7 +17150,7 @@ function join2(graph, name, deps, opts) {
|
|
|
16029
17150
|
...opts,
|
|
16030
17151
|
name,
|
|
16031
17152
|
describeKind: "derived",
|
|
16032
|
-
meta:
|
|
17153
|
+
meta: baseMeta2("join", opts?.meta)
|
|
16033
17154
|
}
|
|
16034
17155
|
);
|
|
16035
17156
|
registerStep(
|
|
@@ -16060,7 +17181,7 @@ function loop(graph, name, source, iterate, opts) {
|
|
|
16060
17181
|
...opts,
|
|
16061
17182
|
name,
|
|
16062
17183
|
describeKind: "derived",
|
|
16063
|
-
meta:
|
|
17184
|
+
meta: baseMeta2("loop", opts?.meta)
|
|
16064
17185
|
}
|
|
16065
17186
|
);
|
|
16066
17187
|
registerStep(
|
|
@@ -16085,7 +17206,7 @@ function sensor(graph, name, initial, opts) {
|
|
|
16085
17206
|
name,
|
|
16086
17207
|
initial,
|
|
16087
17208
|
describeKind: "producer",
|
|
16088
|
-
meta:
|
|
17209
|
+
meta: baseMeta2("sensor", opts?.meta)
|
|
16089
17210
|
});
|
|
16090
17211
|
registerStep(graph, name, source, []);
|
|
16091
17212
|
return {
|
|
@@ -16123,7 +17244,7 @@ function wait(graph, name, source, ms, opts) {
|
|
|
16123
17244
|
initial: src.node.get(),
|
|
16124
17245
|
describeKind: "operator",
|
|
16125
17246
|
completeWhenDepsComplete: false,
|
|
16126
|
-
meta:
|
|
17247
|
+
meta: baseMeta2("wait", opts?.meta),
|
|
16127
17248
|
onMessage(msg, depIndex, actions) {
|
|
16128
17249
|
if (terminated) return true;
|
|
16129
17250
|
if (depIndex !== 0) {
|
|
@@ -16173,7 +17294,7 @@ function onFailure(graph, name, source, recover, opts) {
|
|
|
16173
17294
|
name,
|
|
16174
17295
|
describeKind: "operator",
|
|
16175
17296
|
completeWhenDepsComplete: false,
|
|
16176
|
-
meta:
|
|
17297
|
+
meta: baseMeta2("onFailure", opts?.meta),
|
|
16177
17298
|
onMessage(msg, _depIndex, actions) {
|
|
16178
17299
|
if (terminated) return true;
|
|
16179
17300
|
if (msg[0] === ERROR) {
|
|
@@ -16803,6 +17924,7 @@ var version = "0.0.0";
|
|
|
16803
17924
|
gate,
|
|
16804
17925
|
globToRegExp,
|
|
16805
17926
|
graph,
|
|
17927
|
+
graphspec,
|
|
16806
17928
|
interval,
|
|
16807
17929
|
isBatching,
|
|
16808
17930
|
isKnownMessageType,
|
|
@@ -16860,6 +17982,7 @@ var version = "0.0.0";
|
|
|
16860
17982
|
reactiveLog,
|
|
16861
17983
|
reactiveMap,
|
|
16862
17984
|
reduce,
|
|
17985
|
+
reduction,
|
|
16863
17986
|
repeat,
|
|
16864
17987
|
replay,
|
|
16865
17988
|
replayWAL,
|