@graphrefly/graphrefly 0.9.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 +2 -2
- package/dist/index.cjs +2246 -1397
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +217 -2
- package/dist/index.d.ts +217 -2
- package/dist/index.js +1418 -570
- 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,
|
|
@@ -12862,6 +12863,7 @@ __export(patterns_exports, {
|
|
|
12862
12863
|
ai: () => ai_exports,
|
|
12863
12864
|
cqrs: () => cqrs_exports,
|
|
12864
12865
|
demoShell: () => demo_shell_exports,
|
|
12866
|
+
graphspec: () => graphspec_exports,
|
|
12865
12867
|
layout: () => reactive_layout_exports,
|
|
12866
12868
|
memory: () => memory_exports,
|
|
12867
12869
|
messaging: () => messaging_exports,
|
|
@@ -15417,1544 +15419,2390 @@ function demoShell(opts) {
|
|
|
15417
15419
|
};
|
|
15418
15420
|
}
|
|
15419
15421
|
|
|
15420
|
-
// src/patterns/
|
|
15421
|
-
var
|
|
15422
|
-
__export(
|
|
15423
|
-
|
|
15424
|
-
|
|
15425
|
-
|
|
15426
|
-
|
|
15427
|
-
|
|
15428
|
-
|
|
15429
|
-
jobQueue: () => jobQueue,
|
|
15430
|
-
subscription: () => subscription,
|
|
15431
|
-
topic: () => topic,
|
|
15432
|
-
topicBridge: () => topicBridge
|
|
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
|
|
15433
15431
|
});
|
|
15434
|
-
|
|
15435
|
-
|
|
15436
|
-
|
|
15437
|
-
|
|
15438
|
-
|
|
15439
|
-
|
|
15440
|
-
|
|
15441
|
-
|
|
15442
|
-
|
|
15443
|
-
|
|
15444
|
-
|
|
15445
|
-
function messagingMeta(kind, extra) {
|
|
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) {
|
|
15446
15443
|
return {
|
|
15447
|
-
|
|
15448
|
-
|
|
15449
|
-
...
|
|
15444
|
+
reduction: true,
|
|
15445
|
+
reduction_type: kind,
|
|
15446
|
+
...meta ?? {}
|
|
15450
15447
|
};
|
|
15451
15448
|
}
|
|
15452
|
-
|
|
15453
|
-
|
|
15454
|
-
|
|
15455
|
-
|
|
15456
|
-
|
|
15457
|
-
|
|
15458
|
-
|
|
15459
|
-
|
|
15460
|
-
|
|
15461
|
-
this.add("events", this.events);
|
|
15462
|
-
this.latest = derived(
|
|
15463
|
-
[this.events],
|
|
15464
|
-
([snapshot]) => {
|
|
15465
|
-
const entries = snapshot.value.entries;
|
|
15466
|
-
return entries.length === 0 ? void 0 : entries[entries.length - 1];
|
|
15467
|
-
},
|
|
15468
|
-
{
|
|
15469
|
-
name: "latest",
|
|
15470
|
-
describeKind: "derived",
|
|
15471
|
-
meta: messagingMeta("topic_latest"),
|
|
15472
|
-
initial: void 0
|
|
15473
|
-
}
|
|
15474
|
-
);
|
|
15475
|
-
this.add("latest", this.latest);
|
|
15476
|
-
this.connect("events", "latest");
|
|
15477
|
-
this._keepaliveDisposers.push(keepalive4(this.latest));
|
|
15478
|
-
}
|
|
15479
|
-
destroy() {
|
|
15480
|
-
for (const dispose of this._keepaliveDisposers) dispose();
|
|
15481
|
-
this._keepaliveDisposers.length = 0;
|
|
15482
|
-
super.destroy();
|
|
15483
|
-
}
|
|
15484
|
-
publish(value) {
|
|
15485
|
-
this._log.append(value);
|
|
15486
|
-
}
|
|
15487
|
-
retained() {
|
|
15488
|
-
const snapshot = this.events.get();
|
|
15489
|
-
return snapshot.value.entries;
|
|
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);
|
|
15490
15458
|
}
|
|
15491
|
-
|
|
15492
|
-
|
|
15493
|
-
|
|
15494
|
-
|
|
15495
|
-
|
|
15496
|
-
|
|
15497
|
-
|
|
15498
|
-
|
|
15499
|
-
|
|
15500
|
-
|
|
15501
|
-
|
|
15502
|
-
|
|
15503
|
-
|
|
15504
|
-
|
|
15505
|
-
|
|
15506
|
-
|
|
15507
|
-
|
|
15508
|
-
|
|
15509
|
-
|
|
15510
|
-
|
|
15511
|
-
|
|
15512
|
-
|
|
15513
|
-
|
|
15514
|
-
|
|
15515
|
-
|
|
15516
|
-
[this.source, this.cursor],
|
|
15517
|
-
([sourceSnapshot, cursor]) => {
|
|
15518
|
-
const entries = sourceSnapshot.value.entries;
|
|
15519
|
-
const start = Math.max(0, Math.trunc(cursor ?? 0));
|
|
15520
|
-
return entries.slice(start);
|
|
15521
|
-
},
|
|
15522
|
-
{
|
|
15523
|
-
name: "available",
|
|
15524
|
-
describeKind: "derived",
|
|
15525
|
-
meta: messagingMeta("subscription_available"),
|
|
15526
|
-
initial: []
|
|
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;
|
|
15527
15484
|
}
|
|
15528
|
-
|
|
15529
|
-
|
|
15530
|
-
|
|
15531
|
-
this.connect("source", "available");
|
|
15532
|
-
this.connect("cursor", "available");
|
|
15533
|
-
this._keepaliveDisposers.push(keepalive4(this.source));
|
|
15534
|
-
this._keepaliveDisposers.push(keepalive4(this.available));
|
|
15535
|
-
}
|
|
15536
|
-
destroy() {
|
|
15537
|
-
for (const dispose of this._keepaliveDisposers) dispose();
|
|
15538
|
-
this._keepaliveDisposers.length = 0;
|
|
15539
|
-
super.destroy();
|
|
15540
|
-
}
|
|
15541
|
-
ack(count) {
|
|
15542
|
-
const available = this.available.get();
|
|
15543
|
-
const requested = count === void 0 ? available.length : requireNonNegativeInt(count, "subscription ack count");
|
|
15544
|
-
const step = Math.min(requested, available.length);
|
|
15545
|
-
if (step <= 0) return this.cursor.get();
|
|
15546
|
-
const next = this.cursor.get() + step;
|
|
15547
|
-
this.cursor.down([[DATA, next]]);
|
|
15548
|
-
return next;
|
|
15549
|
-
}
|
|
15550
|
-
pull(limit, opts = {}) {
|
|
15551
|
-
const available = this.available.get();
|
|
15552
|
-
const max = limit === void 0 ? available.length : requireNonNegativeInt(limit, "subscription pull limit");
|
|
15553
|
-
const out = available.slice(0, max);
|
|
15554
|
-
if (opts.ack && out.length > 0) this.ack(out.length);
|
|
15555
|
-
return out;
|
|
15556
|
-
}
|
|
15557
|
-
};
|
|
15558
|
-
var JobQueueGraph = class extends Graph {
|
|
15559
|
-
_pending;
|
|
15560
|
-
_jobs;
|
|
15561
|
-
_keepaliveDisposers = [];
|
|
15562
|
-
_seq = 0;
|
|
15563
|
-
pending;
|
|
15564
|
-
jobs;
|
|
15565
|
-
depth;
|
|
15566
|
-
constructor(name, opts = {}) {
|
|
15567
|
-
super(name, opts.graph);
|
|
15568
|
-
this._pending = reactiveList([], { name: "pending" });
|
|
15569
|
-
this._jobs = reactiveMap({ name: "jobs" });
|
|
15570
|
-
this.pending = this._pending.items;
|
|
15571
|
-
this.jobs = this._jobs.node;
|
|
15572
|
-
this.add("pending", this.pending);
|
|
15573
|
-
this.add("jobs", this.jobs);
|
|
15574
|
-
this.depth = derived(
|
|
15575
|
-
[this.pending],
|
|
15576
|
-
([snapshot]) => snapshot.value.items.length,
|
|
15577
|
-
{
|
|
15578
|
-
name: "depth",
|
|
15579
|
-
describeKind: "derived",
|
|
15580
|
-
meta: messagingMeta("queue_depth"),
|
|
15581
|
-
initial: 0
|
|
15485
|
+
if (t === DIRTY) {
|
|
15486
|
+
pendingDirty = true;
|
|
15487
|
+
return true;
|
|
15582
15488
|
}
|
|
15583
|
-
|
|
15584
|
-
|
|
15585
|
-
|
|
15586
|
-
|
|
15587
|
-
|
|
15588
|
-
|
|
15589
|
-
|
|
15590
|
-
|
|
15591
|
-
|
|
15592
|
-
|
|
15593
|
-
|
|
15594
|
-
|
|
15595
|
-
|
|
15596
|
-
|
|
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;
|
|
15597
15504
|
}
|
|
15598
|
-
|
|
15599
|
-
|
|
15600
|
-
|
|
15601
|
-
|
|
15602
|
-
|
|
15603
|
-
|
|
15604
|
-
|
|
15605
|
-
|
|
15606
|
-
this._pending.append(id);
|
|
15607
|
-
return id;
|
|
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);
|
|
15608
15513
|
}
|
|
15609
|
-
|
|
15610
|
-
|
|
15611
|
-
|
|
15612
|
-
|
|
15613
|
-
|
|
15614
|
-
|
|
15615
|
-
|
|
15616
|
-
|
|
15617
|
-
|
|
15618
|
-
|
|
15619
|
-
|
|
15620
|
-
|
|
15621
|
-
|
|
15622
|
-
|
|
15623
|
-
|
|
15624
|
-
};
|
|
15625
|
-
this._jobs.set(id, inflight);
|
|
15626
|
-
out.push(inflight);
|
|
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`);
|
|
15627
15530
|
}
|
|
15628
|
-
|
|
15629
|
-
|
|
15630
|
-
|
|
15631
|
-
|
|
15632
|
-
if (!job || job.state !== "inflight") return false;
|
|
15633
|
-
this._jobs.delete(id);
|
|
15634
|
-
return true;
|
|
15635
|
-
}
|
|
15636
|
-
nack(id, opts = {}) {
|
|
15637
|
-
const job = this._jobs.get(id);
|
|
15638
|
-
if (!job || job.state !== "inflight") return false;
|
|
15639
|
-
if (opts.requeue ?? true) {
|
|
15640
|
-
this._jobs.set(id, { ...job, state: "queued" });
|
|
15641
|
-
this._pending.append(id);
|
|
15642
|
-
return true;
|
|
15531
|
+
try {
|
|
15532
|
+
sub.resolve("output");
|
|
15533
|
+
} catch {
|
|
15534
|
+
throw new Error(`funnel stage "${stage.name}" must define an "output" node`);
|
|
15643
15535
|
}
|
|
15644
|
-
|
|
15645
|
-
|
|
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`;
|
|
15646
15555
|
}
|
|
15647
|
-
|
|
15648
|
-
|
|
15649
|
-
|
|
15650
|
-
|
|
15651
|
-
|
|
15652
|
-
|
|
15653
|
-
|
|
15654
|
-
|
|
15655
|
-
|
|
15656
|
-
|
|
15657
|
-
|
|
15658
|
-
|
|
15659
|
-
|
|
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
|
+
}
|
|
15660
15593
|
}
|
|
15661
|
-
|
|
15662
|
-
|
|
15663
|
-
|
|
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
|
+
}
|
|
15664
15601
|
}
|
|
15665
|
-
|
|
15666
|
-
|
|
15667
|
-
|
|
15668
|
-
|
|
15669
|
-
|
|
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);
|
|
15670
15620
|
}
|
|
15671
|
-
|
|
15672
|
-
|
|
15673
|
-
|
|
15674
|
-
|
|
15675
|
-
|
|
15676
|
-
|
|
15677
|
-
|
|
15678
|
-
|
|
15679
|
-
|
|
15680
|
-
|
|
15681
|
-
|
|
15682
|
-
|
|
15683
|
-
|
|
15684
|
-
|
|
15685
|
-
|
|
15686
|
-
|
|
15687
|
-
|
|
15688
|
-
|
|
15689
|
-
|
|
15690
|
-
|
|
15691
|
-
for (let i = 0; i < this._stageNames.length; i += 1) {
|
|
15692
|
-
const stage = this._stageNames[i];
|
|
15693
|
-
const current = this.queue(stage);
|
|
15694
|
-
const next = i + 1 < this._stageNames.length ? this.queue(this._stageNames[i + 1]) : null;
|
|
15695
|
-
const pump = node(
|
|
15696
|
-
[current.pending],
|
|
15697
|
-
() => {
|
|
15698
|
-
let moved = 0;
|
|
15699
|
-
while (moved < maxPerPump) {
|
|
15700
|
-
const claim = current.claim(1);
|
|
15701
|
-
if (claim.length === 0) break;
|
|
15702
|
-
const job = claim[0];
|
|
15703
|
-
if (!job) break;
|
|
15704
|
-
if (next) {
|
|
15705
|
-
next.enqueue(job.payload, {
|
|
15706
|
-
metadata: {
|
|
15707
|
-
...job.metadata,
|
|
15708
|
-
job_flow_from: stage
|
|
15709
|
-
}
|
|
15710
|
-
});
|
|
15711
|
-
} else {
|
|
15712
|
-
this._completed.append(job);
|
|
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]]);
|
|
15713
15641
|
}
|
|
15714
|
-
current.ack(job.id);
|
|
15715
|
-
moved += 1;
|
|
15716
15642
|
}
|
|
15717
|
-
|
|
15718
|
-
{
|
|
15719
|
-
name: `pump_${stage}`,
|
|
15720
|
-
describeKind: "effect",
|
|
15721
|
-
meta: messagingMeta("job_flow_pump")
|
|
15643
|
+
return true;
|
|
15722
15644
|
}
|
|
15723
|
-
|
|
15724
|
-
|
|
15725
|
-
|
|
15726
|
-
|
|
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;
|
|
15727
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");
|
|
15728
15703
|
}
|
|
15729
|
-
|
|
15730
|
-
|
|
15731
|
-
|
|
15732
|
-
|
|
15733
|
-
|
|
15734
|
-
|
|
15735
|
-
|
|
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"] };
|
|
15736
15750
|
}
|
|
15737
|
-
|
|
15738
|
-
|
|
15739
|
-
|
|
15740
|
-
return q;
|
|
15751
|
+
const s = spec;
|
|
15752
|
+
if (typeof s.name !== "string" || s.name.length === 0) {
|
|
15753
|
+
errors.push("Missing or empty 'name' field");
|
|
15741
15754
|
}
|
|
15742
|
-
|
|
15743
|
-
|
|
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 };
|
|
15744
15758
|
}
|
|
15745
|
-
|
|
15746
|
-
|
|
15747
|
-
|
|
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
|
+
}
|
|
15748
15771
|
}
|
|
15749
|
-
|
|
15750
|
-
|
|
15751
|
-
|
|
15752
|
-
|
|
15753
|
-
|
|
15754
|
-
|
|
15755
|
-
|
|
15756
|
-
|
|
15757
|
-
this._sourceSub = subscription(`${name}-subscription`, sourceTopic, {
|
|
15758
|
-
cursor: opts.cursor
|
|
15759
|
-
});
|
|
15760
|
-
this._target = targetTopic;
|
|
15761
|
-
this.mount("subscription", this._sourceSub);
|
|
15762
|
-
this.bridgedCount = state(0, {
|
|
15763
|
-
name: "bridgedCount",
|
|
15764
|
-
describeKind: "state",
|
|
15765
|
-
meta: messagingMeta("topic_bridge_count")
|
|
15766
|
-
});
|
|
15767
|
-
this.add("bridgedCount", this.bridgedCount);
|
|
15768
|
-
const maxPerPump = Math.max(
|
|
15769
|
-
1,
|
|
15770
|
-
requireNonNegativeInt(opts.maxPerPump ?? DEFAULT_MAX_PER_PUMP, "topic bridge maxPerPump")
|
|
15771
|
-
);
|
|
15772
|
-
const mapValue = opts.map ?? ((value) => value);
|
|
15773
|
-
const pump = node(
|
|
15774
|
-
[this._sourceSub.available],
|
|
15775
|
-
() => {
|
|
15776
|
-
const available = this._sourceSub.pull(maxPerPump, { ack: true });
|
|
15777
|
-
if (available.length === 0) return;
|
|
15778
|
-
let bridged = 0;
|
|
15779
|
-
for (const value of available) {
|
|
15780
|
-
const mapped = mapValue(value);
|
|
15781
|
-
if (mapped === void 0) continue;
|
|
15782
|
-
this._target.publish(mapped);
|
|
15783
|
-
bridged += 1;
|
|
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;
|
|
15784
15780
|
}
|
|
15785
|
-
|
|
15786
|
-
|
|
15787
|
-
|
|
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
|
+
}
|
|
15788
15814
|
}
|
|
15789
|
-
},
|
|
15790
|
-
{
|
|
15791
|
-
name: "pump",
|
|
15792
|
-
describeKind: "effect",
|
|
15793
|
-
meta: messagingMeta("topic_bridge_pump")
|
|
15794
15815
|
}
|
|
15795
|
-
|
|
15796
|
-
this.add("pump", pump);
|
|
15797
|
-
this.connect("subscription::available", "pump");
|
|
15798
|
-
this._keepaliveDisposers.push(keepalive4(pump));
|
|
15799
|
-
}
|
|
15800
|
-
destroy() {
|
|
15801
|
-
for (const dispose of this._keepaliveDisposers) dispose();
|
|
15802
|
-
this._keepaliveDisposers.length = 0;
|
|
15803
|
-
super.destroy();
|
|
15816
|
+
}
|
|
15804
15817
|
}
|
|
15805
|
-
|
|
15806
|
-
|
|
15807
|
-
|
|
15808
|
-
|
|
15809
|
-
|
|
15810
|
-
|
|
15811
|
-
|
|
15812
|
-
|
|
15813
|
-
|
|
15814
|
-
|
|
15815
|
-
|
|
15816
|
-
|
|
15817
|
-
|
|
15818
|
-
|
|
15819
|
-
|
|
15820
|
-
}
|
|
15821
|
-
|
|
15822
|
-
|
|
15823
|
-
|
|
15824
|
-
|
|
15825
|
-
|
|
15826
|
-
|
|
15827
|
-
|
|
15828
|
-
|
|
15829
|
-
|
|
15830
|
-
|
|
15831
|
-
|
|
15832
|
-
|
|
15833
|
-
|
|
15834
|
-
|
|
15835
|
-
|
|
15836
|
-
|
|
15837
|
-
}
|
|
15838
|
-
|
|
15839
|
-
|
|
15840
|
-
|
|
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
|
+
}
|
|
15841
15870
|
}
|
|
15842
|
-
|
|
15843
|
-
|
|
15844
|
-
|
|
15845
|
-
|
|
15846
|
-
|
|
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
|
+
}
|
|
15847
15901
|
}
|
|
15848
|
-
return {
|
|
15902
|
+
return { valid: errors.length === 0, errors };
|
|
15849
15903
|
}
|
|
15850
|
-
function
|
|
15851
|
-
const
|
|
15852
|
-
|
|
15853
|
-
|
|
15854
|
-
|
|
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));
|
|
15855
16057
|
try {
|
|
15856
|
-
|
|
16058
|
+
const outputNode = g.resolve(outputPath);
|
|
16059
|
+
outputNode.meta._templateName?.down([[DATA, ref.template]]);
|
|
16060
|
+
outputNode.meta._templateBind?.down([[DATA, ref.bind]]);
|
|
15857
16061
|
} catch {
|
|
15858
16062
|
}
|
|
15859
16063
|
}
|
|
15860
|
-
|
|
15861
|
-
|
|
15862
|
-
|
|
15863
|
-
|
|
15864
|
-
|
|
15865
|
-
|
|
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
|
+
}
|
|
15866
16077
|
}
|
|
16078
|
+
for (const fb of spec.feedback ?? []) {
|
|
16079
|
+
feedback(g, fb.from, fb.to, {
|
|
16080
|
+
maxIterations: fb.maxIterations
|
|
16081
|
+
});
|
|
16082
|
+
}
|
|
16083
|
+
return g;
|
|
15867
16084
|
}
|
|
15868
|
-
|
|
15869
|
-
|
|
15870
|
-
|
|
15871
|
-
|
|
15872
|
-
|
|
15873
|
-
|
|
15874
|
-
|
|
15875
|
-
|
|
15876
|
-
|
|
15877
|
-
|
|
15878
|
-
|
|
15879
|
-
|
|
15880
|
-
};
|
|
15881
|
-
|
|
15882
|
-
|
|
15883
|
-
|
|
15884
|
-
|
|
15885
|
-
|
|
15886
|
-
|
|
15887
|
-
|
|
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
|
+
}
|
|
15888
16116
|
}
|
|
15889
|
-
|
|
15890
|
-
|
|
15891
|
-
|
|
15892
|
-
|
|
15893
|
-
|
|
15894
|
-
|
|
15895
|
-
|
|
15896
|
-
|
|
15897
|
-
|
|
15898
|
-
|
|
15899
|
-
|
|
15900
|
-
|
|
15901
|
-
|
|
15902
|
-
{
|
|
15903
|
-
|
|
15904
|
-
|
|
15905
|
-
|
|
15906
|
-
|
|
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
|
+
}
|
|
15907
16200
|
}
|
|
15908
|
-
|
|
15909
|
-
|
|
15910
|
-
|
|
15911
|
-
|
|
15912
|
-
|
|
15913
|
-
|
|
15914
|
-
|
|
15915
|
-
|
|
15916
|
-
|
|
15917
|
-
|
|
15918
|
-
|
|
15919
|
-
|
|
15920
|
-
|
|
15921
|
-
|
|
15922
|
-
|
|
15923
|
-
|
|
15924
|
-
|
|
15925
|
-
{
|
|
15926
|
-
...opts,
|
|
15927
|
-
name,
|
|
15928
|
-
describeKind: "derived",
|
|
15929
|
-
meta: baseMeta("branch", opts?.meta)
|
|
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
|
+
};
|
|
15930
16218
|
}
|
|
15931
|
-
|
|
15932
|
-
|
|
15933
|
-
|
|
15934
|
-
|
|
15935
|
-
|
|
15936
|
-
|
|
15937
|
-
|
|
15938
|
-
|
|
15939
|
-
|
|
15940
|
-
|
|
15941
|
-
|
|
15942
|
-
|
|
15943
|
-
|
|
15944
|
-
|
|
15945
|
-
|
|
15946
|
-
|
|
15947
|
-
|
|
15948
|
-
{
|
|
15949
|
-
|
|
15950
|
-
|
|
15951
|
-
|
|
15952
|
-
|
|
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
|
+
};
|
|
15953
16259
|
}
|
|
15954
|
-
|
|
15955
|
-
|
|
15956
|
-
|
|
15957
|
-
|
|
15958
|
-
|
|
15959
|
-
|
|
15960
|
-
|
|
15961
|
-
|
|
15962
|
-
}
|
|
15963
|
-
|
|
15964
|
-
|
|
15965
|
-
|
|
15966
|
-
const isApproved = opts?.isApproved ?? ((value) => Boolean(value));
|
|
15967
|
-
const step = node(
|
|
15968
|
-
[src.node, ctrl.node],
|
|
15969
|
-
(_deps, actions) => {
|
|
15970
|
-
if (!isApproved(ctrl.node.get())) {
|
|
15971
|
-
actions.down([[RESOLVED]]);
|
|
15972
|
-
return void 0;
|
|
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
|
+
}
|
|
15973
16272
|
}
|
|
15974
|
-
|
|
15975
|
-
|
|
15976
|
-
|
|
15977
|
-
|
|
15978
|
-
|
|
15979
|
-
describeKind: "operator",
|
|
15980
|
-
meta: baseMeta("approval", opts?.meta)
|
|
16273
|
+
templateRefs[member.name] = {
|
|
16274
|
+
type: "template",
|
|
16275
|
+
template: templateName,
|
|
16276
|
+
bind: memberBind
|
|
16277
|
+
};
|
|
15981
16278
|
}
|
|
15982
|
-
|
|
15983
|
-
|
|
15984
|
-
|
|
15985
|
-
|
|
15986
|
-
|
|
15987
|
-
|
|
15988
|
-
);
|
|
15989
|
-
|
|
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;
|
|
15990
16288
|
}
|
|
15991
|
-
function
|
|
15992
|
-
const
|
|
15993
|
-
|
|
15994
|
-
|
|
15995
|
-
|
|
15996
|
-
|
|
15997
|
-
|
|
15998
|
-
|
|
15999
|
-
|
|
16000
|
-
|
|
16001
|
-
|
|
16002
|
-
|
|
16003
|
-
|
|
16004
|
-
|
|
16005
|
-
|
|
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");
|
|
16006
16324
|
}
|
|
16007
|
-
if (
|
|
16008
|
-
|
|
16009
|
-
run(msg[1], actions);
|
|
16010
|
-
actions.down([msg]);
|
|
16011
|
-
} catch (err) {
|
|
16012
|
-
terminated = true;
|
|
16013
|
-
actions.down([[ERROR, err]]);
|
|
16014
|
-
}
|
|
16015
|
-
return true;
|
|
16325
|
+
if (a.fn !== b.fn) {
|
|
16326
|
+
details.push(`fn: ${a.fn} \u2192 ${b.fn}`);
|
|
16016
16327
|
}
|
|
16017
|
-
|
|
16018
|
-
|
|
16019
|
-
|
|
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
|
+
});
|
|
16020
16336
|
}
|
|
16021
|
-
}
|
|
16022
|
-
|
|
16023
|
-
|
|
16024
|
-
|
|
16025
|
-
|
|
16026
|
-
const
|
|
16027
|
-
|
|
16028
|
-
|
|
16029
|
-
(values) => values,
|
|
16030
|
-
{
|
|
16031
|
-
...opts,
|
|
16032
|
-
name,
|
|
16033
|
-
describeKind: "derived",
|
|
16034
|
-
meta: baseMeta("join", opts?.meta)
|
|
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}` });
|
|
16035
16345
|
}
|
|
16036
|
-
|
|
16037
|
-
|
|
16038
|
-
|
|
16039
|
-
|
|
16040
|
-
|
|
16041
|
-
|
|
16042
|
-
)
|
|
16043
|
-
|
|
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 };
|
|
16044
16402
|
}
|
|
16045
|
-
|
|
16046
|
-
|
|
16047
|
-
|
|
16048
|
-
|
|
16049
|
-
|
|
16050
|
-
|
|
16051
|
-
|
|
16052
|
-
|
|
16053
|
-
|
|
16054
|
-
|
|
16055
|
-
|
|
16056
|
-
|
|
16057
|
-
|
|
16058
|
-
|
|
16059
|
-
|
|
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>" }
|
|
16060
16418
|
},
|
|
16061
|
-
{
|
|
16062
|
-
|
|
16063
|
-
|
|
16064
|
-
|
|
16065
|
-
meta: baseMeta("loop", opts?.meta)
|
|
16419
|
+
"<template_instance>": {
|
|
16420
|
+
"type": "template",
|
|
16421
|
+
"template": "<template_name>",
|
|
16422
|
+
"bind": { "$param": "node_name" }
|
|
16066
16423
|
}
|
|
16067
|
-
|
|
16068
|
-
|
|
16069
|
-
|
|
16070
|
-
|
|
16071
|
-
|
|
16072
|
-
|
|
16073
|
-
|
|
16074
|
-
|
|
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
|
+
]
|
|
16075
16435
|
}
|
|
16076
|
-
|
|
16077
|
-
|
|
16078
|
-
|
|
16079
|
-
|
|
16080
|
-
|
|
16081
|
-
|
|
16082
|
-
|
|
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;
|
|
16083
16449
|
}
|
|
16084
|
-
function
|
|
16085
|
-
|
|
16086
|
-
|
|
16087
|
-
|
|
16088
|
-
|
|
16089
|
-
|
|
16090
|
-
|
|
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
|
|
16091
16471
|
});
|
|
16092
|
-
|
|
16093
|
-
|
|
16094
|
-
|
|
16095
|
-
|
|
16096
|
-
|
|
16097
|
-
|
|
16098
|
-
|
|
16099
|
-
|
|
16100
|
-
|
|
16101
|
-
|
|
16102
|
-
|
|
16103
|
-
|
|
16104
|
-
|
|
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;
|
|
16105
16489
|
}
|
|
16106
|
-
function
|
|
16107
|
-
|
|
16108
|
-
|
|
16109
|
-
|
|
16110
|
-
|
|
16111
|
-
|
|
16112
|
-
|
|
16113
|
-
|
|
16114
|
-
|
|
16115
|
-
|
|
16116
|
-
|
|
16117
|
-
|
|
16118
|
-
|
|
16119
|
-
|
|
16120
|
-
|
|
16121
|
-
},
|
|
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 },
|
|
16122
16505
|
{
|
|
16123
|
-
|
|
16124
|
-
|
|
16125
|
-
|
|
16126
|
-
|
|
16127
|
-
|
|
16128
|
-
|
|
16129
|
-
|
|
16130
|
-
if (terminated) return true;
|
|
16131
|
-
if (depIndex !== 0) {
|
|
16132
|
-
actions.down([msg]);
|
|
16133
|
-
if (msg[0] === COMPLETE || msg[0] === ERROR) terminated = true;
|
|
16134
|
-
return true;
|
|
16135
|
-
}
|
|
16136
|
-
if (msg[0] === DATA) {
|
|
16137
|
-
const id = setTimeout(() => {
|
|
16138
|
-
timers.delete(id);
|
|
16139
|
-
actions.down([msg]);
|
|
16140
|
-
if (completed && timers.size === 0) {
|
|
16141
|
-
actions.down([[COMPLETE]]);
|
|
16142
|
-
}
|
|
16143
|
-
}, ms);
|
|
16144
|
-
timers.add(id);
|
|
16145
|
-
return true;
|
|
16146
|
-
}
|
|
16147
|
-
if (msg[0] === COMPLETE) {
|
|
16148
|
-
terminated = true;
|
|
16149
|
-
completed = true;
|
|
16150
|
-
if (timers.size === 0) {
|
|
16151
|
-
actions.down([[COMPLETE]]);
|
|
16152
|
-
}
|
|
16153
|
-
return true;
|
|
16154
|
-
}
|
|
16155
|
-
if (msg[0] === ERROR) {
|
|
16156
|
-
terminated = true;
|
|
16157
|
-
for (const id of timers) clearTimeout(id);
|
|
16158
|
-
timers.clear();
|
|
16159
|
-
actions.down([msg]);
|
|
16160
|
-
return true;
|
|
16161
|
-
}
|
|
16162
|
-
actions.down([msg]);
|
|
16163
|
-
return true;
|
|
16164
|
-
}
|
|
16165
|
-
}
|
|
16166
|
-
);
|
|
16167
|
-
registerStep(graph, name, step, src.path ? [src.path] : []);
|
|
16168
|
-
return step;
|
|
16169
|
-
}
|
|
16170
|
-
function onFailure(graph, name, source, recover, opts) {
|
|
16171
|
-
const src = resolveDep(graph, source);
|
|
16172
|
-
let terminated = false;
|
|
16173
|
-
const step = node([src.node], () => void 0, {
|
|
16174
|
-
...opts,
|
|
16175
|
-
name,
|
|
16176
|
-
describeKind: "operator",
|
|
16177
|
-
completeWhenDepsComplete: false,
|
|
16178
|
-
meta: baseMeta("onFailure", opts?.meta),
|
|
16179
|
-
onMessage(msg, _depIndex, actions) {
|
|
16180
|
-
if (terminated) return true;
|
|
16181
|
-
if (msg[0] === ERROR) {
|
|
16182
|
-
try {
|
|
16183
|
-
actions.emit(recover(msg[1], actions));
|
|
16184
|
-
} catch (err) {
|
|
16185
|
-
terminated = true;
|
|
16186
|
-
actions.down([[ERROR, err]]);
|
|
16187
|
-
}
|
|
16188
|
-
return true;
|
|
16189
|
-
}
|
|
16190
|
-
actions.down([msg]);
|
|
16191
|
-
if (msg[0] === COMPLETE) terminated = true;
|
|
16192
|
-
return true;
|
|
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.`
|
|
16193
16513
|
}
|
|
16514
|
+
];
|
|
16515
|
+
const rawResult = adapter.invoke(messages, {
|
|
16516
|
+
model: opts?.model,
|
|
16517
|
+
temperature: opts?.temperature ?? 0,
|
|
16518
|
+
maxTokens: opts?.maxTokens
|
|
16194
16519
|
});
|
|
16195
|
-
|
|
16196
|
-
|
|
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;
|
|
16197
16537
|
}
|
|
16198
16538
|
|
|
16199
|
-
// src/patterns/
|
|
16200
|
-
var
|
|
16201
|
-
__export(
|
|
16202
|
-
|
|
16203
|
-
|
|
16204
|
-
|
|
16205
|
-
|
|
16206
|
-
|
|
16207
|
-
|
|
16208
|
-
|
|
16209
|
-
|
|
16210
|
-
|
|
16211
|
-
|
|
16212
|
-
computeTotalHeight: () => computeTotalHeight,
|
|
16213
|
-
measureBlock: () => measureBlock,
|
|
16214
|
-
measureBlocks: () => measureBlocks,
|
|
16215
|
-
reactiveBlockLayout: () => reactiveBlockLayout,
|
|
16216
|
-
reactiveLayout: () => reactiveLayout
|
|
16539
|
+
// src/patterns/messaging.ts
|
|
16540
|
+
var messaging_exports = {};
|
|
16541
|
+
__export(messaging_exports, {
|
|
16542
|
+
JobFlowGraph: () => JobFlowGraph,
|
|
16543
|
+
JobQueueGraph: () => JobQueueGraph,
|
|
16544
|
+
SubscriptionGraph: () => SubscriptionGraph,
|
|
16545
|
+
TopicBridgeGraph: () => TopicBridgeGraph,
|
|
16546
|
+
TopicGraph: () => TopicGraph,
|
|
16547
|
+
jobFlow: () => jobFlow,
|
|
16548
|
+
jobQueue: () => jobQueue,
|
|
16549
|
+
subscription: () => subscription,
|
|
16550
|
+
topic: () => topic,
|
|
16551
|
+
topicBridge: () => topicBridge
|
|
16217
16552
|
});
|
|
16218
|
-
|
|
16219
|
-
|
|
16220
|
-
|
|
16221
|
-
|
|
16222
|
-
code >= 1155 && code <= 1161 || // Cyrillic combining marks
|
|
16223
|
-
code >= 1425 && code <= 1469 || // Hebrew combining marks
|
|
16224
|
-
code >= 1552 && code <= 1562 || // Arabic combining marks
|
|
16225
|
-
code >= 1611 && code <= 1631 || // Arabic combining marks
|
|
16226
|
-
code >= 1648 && code === 1648 || // Arabic superscript alef
|
|
16227
|
-
code >= 1750 && code <= 1756 || // Arabic combining marks
|
|
16228
|
-
code >= 1759 && code <= 1764 || // Arabic combining marks
|
|
16229
|
-
code >= 1767 && code <= 1768 || // Arabic combining marks
|
|
16230
|
-
code >= 1770 && code <= 1773 || // Arabic combining marks
|
|
16231
|
-
code >= 1840 && code <= 1866 || // Syriac combining marks
|
|
16232
|
-
code >= 1958 && code <= 1968 || // Thaana combining marks
|
|
16233
|
-
code >= 2304 && code <= 2307 || // Devanagari combining marks
|
|
16234
|
-
code >= 2362 && code <= 2383 || // Devanagari combining marks
|
|
16235
|
-
code >= 2385 && code <= 2391 || // Devanagari combining marks
|
|
16236
|
-
code >= 2402 && code <= 2403 || // Devanagari combining marks
|
|
16237
|
-
code >= 2433 && code <= 2435 || // Bengali combining marks
|
|
16238
|
-
code >= 2492 && code <= 2509 || // Bengali combining marks
|
|
16239
|
-
code >= 2561 && code <= 2563 || // Gurmukhi combining marks
|
|
16240
|
-
code >= 2620 && code <= 2641 || // Gurmukhi combining marks
|
|
16241
|
-
code >= 2672 && code <= 2673 || // Gurmukhi combining marks
|
|
16242
|
-
code >= 2677 && code === 2677 || // Gurmukhi combining mark
|
|
16243
|
-
code >= 3633 && code === 3633 || // Thai combining mark
|
|
16244
|
-
code >= 3636 && code <= 3642 || // Thai combining marks
|
|
16245
|
-
code >= 3655 && code <= 3662 || // Thai combining marks
|
|
16246
|
-
code >= 3761 && code === 3761 || // Lao combining mark
|
|
16247
|
-
code >= 3764 && code <= 3772 || // Lao combining marks
|
|
16248
|
-
code >= 3784 && code <= 3790 || // Lao combining marks
|
|
16249
|
-
code >= 7616 && code <= 7679 || // Combining Diacritical Marks Supplement
|
|
16250
|
-
code >= 8400 && code <= 8447 || // Combining Diacritical Marks for Symbols
|
|
16251
|
-
code >= 65024 && code <= 65039 || // Variation Selectors
|
|
16252
|
-
code >= 65056 && code <= 65071 || // Combining Half Marks
|
|
16253
|
-
code === 8205) {
|
|
16254
|
-
return 0;
|
|
16553
|
+
var DEFAULT_MAX_PER_PUMP = 2147483647;
|
|
16554
|
+
function requireNonNegativeInt(value, label) {
|
|
16555
|
+
if (!Number.isFinite(value) || !Number.isInteger(value) || value < 0) {
|
|
16556
|
+
throw new Error(`${label} must be a non-negative integer`);
|
|
16255
16557
|
}
|
|
16256
|
-
|
|
16257
|
-
|
|
16258
|
-
|
|
16259
|
-
|
|
16260
|
-
|
|
16261
|
-
|
|
16262
|
-
|
|
16263
|
-
|
|
16264
|
-
|
|
16265
|
-
|
|
16266
|
-
|
|
16267
|
-
|
|
16268
|
-
|
|
16269
|
-
|
|
16270
|
-
|
|
16271
|
-
|
|
16272
|
-
|
|
16273
|
-
|
|
16274
|
-
|
|
16275
|
-
|
|
16276
|
-
|
|
16277
|
-
|
|
16278
|
-
|
|
16279
|
-
|
|
16280
|
-
|
|
16281
|
-
|
|
16282
|
-
|
|
16283
|
-
|
|
16284
|
-
|
|
16285
|
-
|
|
16286
|
-
|
|
16287
|
-
|
|
16288
|
-
|
|
16289
|
-
|
|
16290
|
-
|
|
16291
|
-
|
|
16292
|
-
|
|
16293
|
-
|
|
16294
|
-
|
|
16295
|
-
|
|
16296
|
-
|
|
16297
|
-
|
|
16298
|
-
|
|
16299
|
-
|
|
16300
|
-
|
|
16301
|
-
|
|
16302
|
-
|
|
16303
|
-
|
|
16304
|
-
|
|
16305
|
-
|
|
16306
|
-
|
|
16307
|
-
|
|
16308
|
-
|
|
16309
|
-
|
|
16310
|
-
|
|
16311
|
-
|
|
16312
|
-
|
|
16313
|
-
|
|
16314
|
-
|
|
16315
|
-
|
|
16316
|
-
|
|
16317
|
-
|
|
16318
|
-
|
|
16319
|
-
|
|
16320
|
-
|
|
16321
|
-
|
|
16322
|
-
|
|
16323
|
-
|
|
16324
|
-
|
|
16325
|
-
|
|
16326
|
-
|
|
16558
|
+
return value;
|
|
16559
|
+
}
|
|
16560
|
+
function keepalive4(n) {
|
|
16561
|
+
return n.subscribe(() => {
|
|
16562
|
+
});
|
|
16563
|
+
}
|
|
16564
|
+
function messagingMeta(kind, extra) {
|
|
16565
|
+
return {
|
|
16566
|
+
messaging: true,
|
|
16567
|
+
messaging_type: kind,
|
|
16568
|
+
...extra ?? {}
|
|
16569
|
+
};
|
|
16570
|
+
}
|
|
16571
|
+
var TopicGraph = class extends Graph {
|
|
16572
|
+
_log;
|
|
16573
|
+
_keepaliveDisposers = [];
|
|
16574
|
+
events;
|
|
16575
|
+
latest;
|
|
16576
|
+
constructor(name, opts = {}) {
|
|
16577
|
+
super(name, opts.graph);
|
|
16578
|
+
this._log = reactiveLog([], { name: "events", maxSize: opts.retainedLimit });
|
|
16579
|
+
this.events = this._log.entries;
|
|
16580
|
+
this.add("events", this.events);
|
|
16581
|
+
this.latest = derived(
|
|
16582
|
+
[this.events],
|
|
16583
|
+
([snapshot]) => {
|
|
16584
|
+
const entries = snapshot.value.entries;
|
|
16585
|
+
return entries.length === 0 ? void 0 : entries[entries.length - 1];
|
|
16586
|
+
},
|
|
16587
|
+
{
|
|
16588
|
+
name: "latest",
|
|
16589
|
+
describeKind: "derived",
|
|
16590
|
+
meta: messagingMeta("topic_latest"),
|
|
16591
|
+
initial: void 0
|
|
16592
|
+
}
|
|
16593
|
+
);
|
|
16594
|
+
this.add("latest", this.latest);
|
|
16595
|
+
this.connect("events", "latest");
|
|
16596
|
+
this._keepaliveDisposers.push(keepalive4(this.latest));
|
|
16597
|
+
}
|
|
16598
|
+
destroy() {
|
|
16599
|
+
for (const dispose of this._keepaliveDisposers) dispose();
|
|
16600
|
+
this._keepaliveDisposers.length = 0;
|
|
16601
|
+
super.destroy();
|
|
16602
|
+
}
|
|
16603
|
+
publish(value) {
|
|
16604
|
+
this._log.append(value);
|
|
16605
|
+
}
|
|
16606
|
+
retained() {
|
|
16607
|
+
const snapshot = this.events.get();
|
|
16608
|
+
return snapshot.value.entries;
|
|
16609
|
+
}
|
|
16610
|
+
};
|
|
16611
|
+
var SubscriptionGraph = class extends Graph {
|
|
16612
|
+
_keepaliveDisposers = [];
|
|
16613
|
+
source;
|
|
16614
|
+
cursor;
|
|
16615
|
+
available;
|
|
16616
|
+
constructor(name, topicGraph, opts = {}) {
|
|
16617
|
+
super(name, opts.graph);
|
|
16618
|
+
const initialCursor = requireNonNegativeInt(opts.cursor ?? 0, "subscription cursor");
|
|
16619
|
+
this.mount("topic", topicGraph);
|
|
16620
|
+
const topicEvents = topicGraph.events;
|
|
16621
|
+
this.source = derived([topicEvents], ([snapshot]) => snapshot, {
|
|
16622
|
+
name: "source",
|
|
16623
|
+
describeKind: "derived",
|
|
16624
|
+
meta: messagingMeta("subscription_source"),
|
|
16625
|
+
initial: topicEvents.get()
|
|
16626
|
+
});
|
|
16627
|
+
this.add("source", this.source);
|
|
16628
|
+
this.cursor = state(initialCursor, {
|
|
16629
|
+
name: "cursor",
|
|
16630
|
+
describeKind: "state",
|
|
16631
|
+
meta: messagingMeta("subscription_cursor")
|
|
16632
|
+
});
|
|
16633
|
+
this.add("cursor", this.cursor);
|
|
16634
|
+
this.available = derived(
|
|
16635
|
+
[this.source, this.cursor],
|
|
16636
|
+
([sourceSnapshot, cursor]) => {
|
|
16637
|
+
const entries = sourceSnapshot.value.entries;
|
|
16638
|
+
const start = Math.max(0, Math.trunc(cursor ?? 0));
|
|
16639
|
+
return entries.slice(start);
|
|
16640
|
+
},
|
|
16641
|
+
{
|
|
16642
|
+
name: "available",
|
|
16643
|
+
describeKind: "derived",
|
|
16644
|
+
meta: messagingMeta("subscription_available"),
|
|
16645
|
+
initial: []
|
|
16646
|
+
}
|
|
16647
|
+
);
|
|
16648
|
+
this.add("available", this.available);
|
|
16649
|
+
this.connect("topic::events", "source");
|
|
16650
|
+
this.connect("source", "available");
|
|
16651
|
+
this.connect("cursor", "available");
|
|
16652
|
+
this._keepaliveDisposers.push(keepalive4(this.source));
|
|
16653
|
+
this._keepaliveDisposers.push(keepalive4(this.available));
|
|
16654
|
+
}
|
|
16655
|
+
destroy() {
|
|
16656
|
+
for (const dispose of this._keepaliveDisposers) dispose();
|
|
16657
|
+
this._keepaliveDisposers.length = 0;
|
|
16658
|
+
super.destroy();
|
|
16659
|
+
}
|
|
16660
|
+
ack(count) {
|
|
16661
|
+
const available = this.available.get();
|
|
16662
|
+
const requested = count === void 0 ? available.length : requireNonNegativeInt(count, "subscription ack count");
|
|
16663
|
+
const step = Math.min(requested, available.length);
|
|
16664
|
+
if (step <= 0) return this.cursor.get();
|
|
16665
|
+
const next = this.cursor.get() + step;
|
|
16666
|
+
this.cursor.down([[DATA, next]]);
|
|
16667
|
+
return next;
|
|
16668
|
+
}
|
|
16669
|
+
pull(limit, opts = {}) {
|
|
16670
|
+
const available = this.available.get();
|
|
16671
|
+
const max = limit === void 0 ? available.length : requireNonNegativeInt(limit, "subscription pull limit");
|
|
16672
|
+
const out = available.slice(0, max);
|
|
16673
|
+
if (opts.ack && out.length > 0) this.ack(out.length);
|
|
16674
|
+
return out;
|
|
16675
|
+
}
|
|
16676
|
+
};
|
|
16677
|
+
var JobQueueGraph = class extends Graph {
|
|
16678
|
+
_pending;
|
|
16679
|
+
_jobs;
|
|
16680
|
+
_keepaliveDisposers = [];
|
|
16681
|
+
_seq = 0;
|
|
16682
|
+
pending;
|
|
16683
|
+
jobs;
|
|
16684
|
+
depth;
|
|
16685
|
+
constructor(name, opts = {}) {
|
|
16686
|
+
super(name, opts.graph);
|
|
16687
|
+
this._pending = reactiveList([], { name: "pending" });
|
|
16688
|
+
this._jobs = reactiveMap({ name: "jobs" });
|
|
16689
|
+
this.pending = this._pending.items;
|
|
16690
|
+
this.jobs = this._jobs.node;
|
|
16691
|
+
this.add("pending", this.pending);
|
|
16692
|
+
this.add("jobs", this.jobs);
|
|
16693
|
+
this.depth = derived(
|
|
16694
|
+
[this.pending],
|
|
16695
|
+
([snapshot]) => snapshot.value.items.length,
|
|
16696
|
+
{
|
|
16697
|
+
name: "depth",
|
|
16698
|
+
describeKind: "derived",
|
|
16699
|
+
meta: messagingMeta("queue_depth"),
|
|
16700
|
+
initial: 0
|
|
16701
|
+
}
|
|
16702
|
+
);
|
|
16703
|
+
this.add("depth", this.depth);
|
|
16704
|
+
this.connect("pending", "depth");
|
|
16705
|
+
this._keepaliveDisposers.push(keepalive4(this.depth));
|
|
16706
|
+
}
|
|
16707
|
+
destroy() {
|
|
16708
|
+
for (const dispose of this._keepaliveDisposers) dispose();
|
|
16709
|
+
this._keepaliveDisposers.length = 0;
|
|
16710
|
+
super.destroy();
|
|
16711
|
+
}
|
|
16712
|
+
enqueue(payload, opts = {}) {
|
|
16713
|
+
const id = opts.id ?? `${this.name}-${++this._seq}`;
|
|
16714
|
+
if (this._jobs.get(id) !== void 0) {
|
|
16715
|
+
throw new Error(`jobQueue("${this.name}"): duplicate job id "${id}"`);
|
|
16716
|
+
}
|
|
16717
|
+
const job = {
|
|
16718
|
+
id,
|
|
16719
|
+
payload,
|
|
16720
|
+
attempts: 0,
|
|
16721
|
+
metadata: Object.freeze({ ...opts.metadata ?? {} }),
|
|
16722
|
+
state: "queued"
|
|
16723
|
+
};
|
|
16724
|
+
this._jobs.set(id, job);
|
|
16725
|
+
this._pending.append(id);
|
|
16726
|
+
return id;
|
|
16327
16727
|
}
|
|
16328
|
-
|
|
16329
|
-
|
|
16330
|
-
|
|
16331
|
-
|
|
16332
|
-
|
|
16333
|
-
|
|
16728
|
+
claim(limit = 1) {
|
|
16729
|
+
const max = requireNonNegativeInt(limit, "job queue claim limit");
|
|
16730
|
+
if (max === 0) return [];
|
|
16731
|
+
const out = [];
|
|
16732
|
+
while (out.length < max) {
|
|
16733
|
+
const snapshot = this.pending.get();
|
|
16734
|
+
const ids = snapshot.value.items;
|
|
16735
|
+
if (ids.length === 0) break;
|
|
16736
|
+
const id = this._pending.pop(0);
|
|
16737
|
+
const job = this._jobs.get(id);
|
|
16738
|
+
if (!job || job.state !== "queued") continue;
|
|
16739
|
+
const inflight = {
|
|
16740
|
+
...job,
|
|
16741
|
+
state: "inflight",
|
|
16742
|
+
attempts: job.attempts + 1
|
|
16743
|
+
};
|
|
16744
|
+
this._jobs.set(id, inflight);
|
|
16745
|
+
out.push(inflight);
|
|
16746
|
+
}
|
|
16747
|
+
return out;
|
|
16334
16748
|
}
|
|
16335
|
-
|
|
16336
|
-
|
|
16337
|
-
|
|
16338
|
-
|
|
16339
|
-
|
|
16340
|
-
this.cellPx = opts?.cellPx ?? 8;
|
|
16749
|
+
ack(id) {
|
|
16750
|
+
const job = this._jobs.get(id);
|
|
16751
|
+
if (!job || job.state !== "inflight") return false;
|
|
16752
|
+
this._jobs.delete(id);
|
|
16753
|
+
return true;
|
|
16341
16754
|
}
|
|
16342
|
-
|
|
16343
|
-
|
|
16755
|
+
nack(id, opts = {}) {
|
|
16756
|
+
const job = this._jobs.get(id);
|
|
16757
|
+
if (!job || job.state !== "inflight") return false;
|
|
16758
|
+
if (opts.requeue ?? true) {
|
|
16759
|
+
this._jobs.set(id, { ...job, state: "queued" });
|
|
16760
|
+
this._pending.append(id);
|
|
16761
|
+
return true;
|
|
16762
|
+
}
|
|
16763
|
+
this._jobs.delete(id);
|
|
16764
|
+
return true;
|
|
16344
16765
|
}
|
|
16345
16766
|
};
|
|
16346
|
-
var
|
|
16347
|
-
|
|
16348
|
-
|
|
16349
|
-
|
|
16350
|
-
|
|
16351
|
-
|
|
16352
|
-
|
|
16353
|
-
|
|
16354
|
-
|
|
16355
|
-
|
|
16356
|
-
|
|
16357
|
-
|
|
16358
|
-
);
|
|
16767
|
+
var JobFlowGraph = class extends Graph {
|
|
16768
|
+
_stageNames;
|
|
16769
|
+
_queues = /* @__PURE__ */ new Map();
|
|
16770
|
+
_keepaliveDisposers = [];
|
|
16771
|
+
_completed;
|
|
16772
|
+
completed;
|
|
16773
|
+
completedCount;
|
|
16774
|
+
constructor(name, opts = {}) {
|
|
16775
|
+
super(name, opts.graph);
|
|
16776
|
+
const stages = (opts.stages ?? ["incoming", "processing", "done"]).map((v) => v.trim());
|
|
16777
|
+
if (stages.length < 2) {
|
|
16778
|
+
throw new Error(`jobFlow("${name}"): requires at least 2 stages`);
|
|
16359
16779
|
}
|
|
16360
|
-
|
|
16361
|
-
|
|
16362
|
-
|
|
16363
|
-
const fontMap = this.metrics[font];
|
|
16364
|
-
if (fontMap) {
|
|
16365
|
-
const w = fontMap[text];
|
|
16366
|
-
if (w !== void 0) return { width: w };
|
|
16780
|
+
const unique = new Set(stages);
|
|
16781
|
+
if (unique.size !== stages.length) {
|
|
16782
|
+
throw new Error(`jobFlow("${name}"): stage names must be unique`);
|
|
16367
16783
|
}
|
|
16368
|
-
|
|
16369
|
-
|
|
16370
|
-
|
|
16371
|
-
);
|
|
16784
|
+
this._stageNames = Object.freeze([...stages]);
|
|
16785
|
+
for (const stage of this._stageNames) {
|
|
16786
|
+
const q = jobQueue(`${name}-${stage}`);
|
|
16787
|
+
this._queues.set(stage, q);
|
|
16788
|
+
this.mount(stage, q);
|
|
16372
16789
|
}
|
|
16373
|
-
|
|
16374
|
-
|
|
16375
|
-
|
|
16376
|
-
|
|
16377
|
-
|
|
16378
|
-
|
|
16379
|
-
|
|
16790
|
+
this._completed = reactiveLog([], { name: "completed" });
|
|
16791
|
+
this.completed = this._completed.entries;
|
|
16792
|
+
this.add("completed", this.completed);
|
|
16793
|
+
this.completedCount = derived(
|
|
16794
|
+
[this.completed],
|
|
16795
|
+
([snapshot]) => snapshot.value.entries.length,
|
|
16796
|
+
{
|
|
16797
|
+
name: "completedCount",
|
|
16798
|
+
describeKind: "derived",
|
|
16799
|
+
meta: messagingMeta("job_flow_completed_count"),
|
|
16800
|
+
initial: 0
|
|
16380
16801
|
}
|
|
16802
|
+
);
|
|
16803
|
+
this.add("completedCount", this.completedCount);
|
|
16804
|
+
this.connect("completed", "completedCount");
|
|
16805
|
+
this._keepaliveDisposers.push(keepalive4(this.completedCount));
|
|
16806
|
+
const maxPerPump = Math.max(
|
|
16807
|
+
1,
|
|
16808
|
+
requireNonNegativeInt(opts.maxPerPump ?? DEFAULT_MAX_PER_PUMP, "job flow maxPerPump")
|
|
16809
|
+
);
|
|
16810
|
+
for (let i = 0; i < this._stageNames.length; i += 1) {
|
|
16811
|
+
const stage = this._stageNames[i];
|
|
16812
|
+
const current = this.queue(stage);
|
|
16813
|
+
const next = i + 1 < this._stageNames.length ? this.queue(this._stageNames[i + 1]) : null;
|
|
16814
|
+
const pump = node(
|
|
16815
|
+
[current.pending],
|
|
16816
|
+
() => {
|
|
16817
|
+
let moved = 0;
|
|
16818
|
+
while (moved < maxPerPump) {
|
|
16819
|
+
const claim = current.claim(1);
|
|
16820
|
+
if (claim.length === 0) break;
|
|
16821
|
+
const job = claim[0];
|
|
16822
|
+
if (!job) break;
|
|
16823
|
+
if (next) {
|
|
16824
|
+
next.enqueue(job.payload, {
|
|
16825
|
+
metadata: {
|
|
16826
|
+
...job.metadata,
|
|
16827
|
+
job_flow_from: stage
|
|
16828
|
+
}
|
|
16829
|
+
});
|
|
16830
|
+
} else {
|
|
16831
|
+
this._completed.append(job);
|
|
16832
|
+
}
|
|
16833
|
+
current.ack(job.id);
|
|
16834
|
+
moved += 1;
|
|
16835
|
+
}
|
|
16836
|
+
},
|
|
16837
|
+
{
|
|
16838
|
+
name: `pump_${stage}`,
|
|
16839
|
+
describeKind: "effect",
|
|
16840
|
+
meta: messagingMeta("job_flow_pump")
|
|
16841
|
+
}
|
|
16842
|
+
);
|
|
16843
|
+
this.add(`pump_${stage}`, pump);
|
|
16844
|
+
this.connect(`${stage}::pending`, `pump_${stage}`);
|
|
16845
|
+
this._keepaliveDisposers.push(keepalive4(pump));
|
|
16381
16846
|
}
|
|
16382
|
-
return { width: total };
|
|
16383
16847
|
}
|
|
16384
|
-
|
|
16385
|
-
|
|
16386
|
-
|
|
16387
|
-
|
|
16388
|
-
emojiCorrection;
|
|
16389
|
-
constructor(opts) {
|
|
16390
|
-
this.emojiCorrection = opts?.emojiCorrection ?? 1;
|
|
16848
|
+
destroy() {
|
|
16849
|
+
for (const dispose of this._keepaliveDisposers) dispose();
|
|
16850
|
+
this._keepaliveDisposers.length = 0;
|
|
16851
|
+
super.destroy();
|
|
16391
16852
|
}
|
|
16392
|
-
|
|
16393
|
-
|
|
16394
|
-
if (typeof OffscreenCanvas === "undefined") {
|
|
16395
|
-
throw new Error(
|
|
16396
|
-
"CanvasMeasureAdapter requires a browser environment with OffscreenCanvas support. Use CliMeasureAdapter or NodeCanvasMeasureAdapter for Node.js."
|
|
16397
|
-
);
|
|
16398
|
-
}
|
|
16399
|
-
const canvas = new OffscreenCanvas(0, 0);
|
|
16400
|
-
const ctx = canvas.getContext("2d");
|
|
16401
|
-
if (!ctx) throw new Error("CanvasMeasureAdapter: failed to get 2d context");
|
|
16402
|
-
this.ctx = ctx;
|
|
16403
|
-
}
|
|
16404
|
-
return this.ctx;
|
|
16853
|
+
stages() {
|
|
16854
|
+
return this._stageNames;
|
|
16405
16855
|
}
|
|
16406
|
-
|
|
16407
|
-
const
|
|
16408
|
-
if (
|
|
16409
|
-
|
|
16410
|
-
this.currentFont = font;
|
|
16411
|
-
}
|
|
16412
|
-
let width = ctx.measureText(text).width;
|
|
16413
|
-
if (this.emojiCorrection !== 1 && /\p{Emoji_Presentation}/u.test(text)) {
|
|
16414
|
-
width *= this.emojiCorrection;
|
|
16415
|
-
}
|
|
16416
|
-
return { width };
|
|
16856
|
+
queue(stage) {
|
|
16857
|
+
const q = this._queues.get(stage);
|
|
16858
|
+
if (!q) throw new Error(`jobFlow("${this.name}"): unknown stage "${stage}"`);
|
|
16859
|
+
return q;
|
|
16417
16860
|
}
|
|
16418
|
-
|
|
16419
|
-
this.
|
|
16861
|
+
enqueue(payload, opts = {}) {
|
|
16862
|
+
return this.queue(this._stageNames[0]).enqueue(payload, opts);
|
|
16863
|
+
}
|
|
16864
|
+
retainedCompleted() {
|
|
16865
|
+
const snapshot = this.completed.get();
|
|
16866
|
+
return snapshot.value.entries;
|
|
16867
|
+
}
|
|
16868
|
+
};
|
|
16869
|
+
var TopicBridgeGraph = class extends Graph {
|
|
16870
|
+
_sourceSub;
|
|
16871
|
+
_target;
|
|
16872
|
+
_keepaliveDisposers = [];
|
|
16873
|
+
bridgedCount;
|
|
16874
|
+
constructor(name, sourceTopic, targetTopic, opts = {}) {
|
|
16875
|
+
super(name, opts.graph);
|
|
16876
|
+
this._sourceSub = subscription(`${name}-subscription`, sourceTopic, {
|
|
16877
|
+
cursor: opts.cursor
|
|
16878
|
+
});
|
|
16879
|
+
this._target = targetTopic;
|
|
16880
|
+
this.mount("subscription", this._sourceSub);
|
|
16881
|
+
this.bridgedCount = state(0, {
|
|
16882
|
+
name: "bridgedCount",
|
|
16883
|
+
describeKind: "state",
|
|
16884
|
+
meta: messagingMeta("topic_bridge_count")
|
|
16885
|
+
});
|
|
16886
|
+
this.add("bridgedCount", this.bridgedCount);
|
|
16887
|
+
const maxPerPump = Math.max(
|
|
16888
|
+
1,
|
|
16889
|
+
requireNonNegativeInt(opts.maxPerPump ?? DEFAULT_MAX_PER_PUMP, "topic bridge maxPerPump")
|
|
16890
|
+
);
|
|
16891
|
+
const mapValue = opts.map ?? ((value) => value);
|
|
16892
|
+
const pump = node(
|
|
16893
|
+
[this._sourceSub.available],
|
|
16894
|
+
() => {
|
|
16895
|
+
const available = this._sourceSub.pull(maxPerPump, { ack: true });
|
|
16896
|
+
if (available.length === 0) return;
|
|
16897
|
+
let bridged = 0;
|
|
16898
|
+
for (const value of available) {
|
|
16899
|
+
const mapped = mapValue(value);
|
|
16900
|
+
if (mapped === void 0) continue;
|
|
16901
|
+
this._target.publish(mapped);
|
|
16902
|
+
bridged += 1;
|
|
16903
|
+
}
|
|
16904
|
+
if (bridged > 0) {
|
|
16905
|
+
const current = this.bridgedCount.get();
|
|
16906
|
+
this.bridgedCount.down([[DATA, current + bridged]]);
|
|
16907
|
+
}
|
|
16908
|
+
},
|
|
16909
|
+
{
|
|
16910
|
+
name: "pump",
|
|
16911
|
+
describeKind: "effect",
|
|
16912
|
+
meta: messagingMeta("topic_bridge_pump")
|
|
16913
|
+
}
|
|
16914
|
+
);
|
|
16915
|
+
this.add("pump", pump);
|
|
16916
|
+
this.connect("subscription::available", "pump");
|
|
16917
|
+
this._keepaliveDisposers.push(keepalive4(pump));
|
|
16918
|
+
}
|
|
16919
|
+
destroy() {
|
|
16920
|
+
for (const dispose of this._keepaliveDisposers) dispose();
|
|
16921
|
+
this._keepaliveDisposers.length = 0;
|
|
16922
|
+
super.destroy();
|
|
16420
16923
|
}
|
|
16421
16924
|
};
|
|
16422
|
-
|
|
16423
|
-
|
|
16424
|
-
|
|
16425
|
-
|
|
16426
|
-
|
|
16427
|
-
|
|
16925
|
+
function topic(name, opts) {
|
|
16926
|
+
return new TopicGraph(name, opts);
|
|
16927
|
+
}
|
|
16928
|
+
function subscription(name, topicGraph, opts) {
|
|
16929
|
+
return new SubscriptionGraph(name, topicGraph, opts);
|
|
16930
|
+
}
|
|
16931
|
+
function jobQueue(name, opts) {
|
|
16932
|
+
return new JobQueueGraph(name, opts);
|
|
16933
|
+
}
|
|
16934
|
+
function jobFlow(name, opts) {
|
|
16935
|
+
return new JobFlowGraph(name, opts);
|
|
16936
|
+
}
|
|
16937
|
+
function topicBridge(name, sourceTopic, targetTopic, opts) {
|
|
16938
|
+
return new TopicBridgeGraph(name, sourceTopic, targetTopic, opts);
|
|
16939
|
+
}
|
|
16940
|
+
|
|
16941
|
+
// src/patterns/orchestration.ts
|
|
16942
|
+
var orchestration_exports = {};
|
|
16943
|
+
__export(orchestration_exports, {
|
|
16944
|
+
approval: () => approval,
|
|
16945
|
+
branch: () => branch,
|
|
16946
|
+
forEach: () => forEach2,
|
|
16947
|
+
gate: () => gate2,
|
|
16948
|
+
join: () => join2,
|
|
16949
|
+
loop: () => loop,
|
|
16950
|
+
onFailure: () => onFailure,
|
|
16951
|
+
pipeline: () => pipeline,
|
|
16952
|
+
sensor: () => sensor,
|
|
16953
|
+
subPipeline: () => subPipeline,
|
|
16954
|
+
task: () => task,
|
|
16955
|
+
wait: () => wait
|
|
16956
|
+
});
|
|
16957
|
+
function resolveDep(graph, dep) {
|
|
16958
|
+
if (typeof dep === "string") {
|
|
16959
|
+
return { node: graph.resolve(dep), path: dep };
|
|
16428
16960
|
}
|
|
16429
|
-
|
|
16430
|
-
|
|
16431
|
-
|
|
16432
|
-
|
|
16433
|
-
|
|
16434
|
-
this.ctx = ctx;
|
|
16435
|
-
}
|
|
16436
|
-
return this.ctx;
|
|
16961
|
+
const path = findRegisteredNodePath(graph, dep);
|
|
16962
|
+
if (!path) {
|
|
16963
|
+
throw new Error(
|
|
16964
|
+
"orchestration dep node must already be registered in the graph so explicit edges can be recorded; pass a string path or register the node first"
|
|
16965
|
+
);
|
|
16437
16966
|
}
|
|
16438
|
-
|
|
16439
|
-
|
|
16440
|
-
|
|
16441
|
-
|
|
16442
|
-
|
|
16967
|
+
return { node: dep, path };
|
|
16968
|
+
}
|
|
16969
|
+
function findRegisteredNodePath(graph, target) {
|
|
16970
|
+
const described = graph.describe();
|
|
16971
|
+
const metaSegment = `::${GRAPH_META_SEGMENT}::`;
|
|
16972
|
+
for (const path of Object.keys(described.nodes).sort()) {
|
|
16973
|
+
if (path.includes(metaSegment)) continue;
|
|
16974
|
+
try {
|
|
16975
|
+
if (graph.resolve(path) === target) return path;
|
|
16976
|
+
} catch {
|
|
16443
16977
|
}
|
|
16444
|
-
return { width: ctx.measureText(text).width };
|
|
16445
|
-
}
|
|
16446
|
-
clearCache() {
|
|
16447
|
-
this.currentFont = "";
|
|
16448
16978
|
}
|
|
16449
|
-
|
|
16450
|
-
|
|
16451
|
-
|
|
16452
|
-
|
|
16453
|
-
|
|
16454
|
-
|
|
16455
|
-
if (parts.length >= 4) {
|
|
16456
|
-
const w = Number.parseFloat(parts[2]);
|
|
16457
|
-
const h = Number.parseFloat(parts[3]);
|
|
16458
|
-
if (Number.isFinite(w) && Number.isFinite(h) && w > 0 && h > 0) {
|
|
16459
|
-
return { width: w, height: h };
|
|
16460
|
-
}
|
|
16461
|
-
throw new Error(
|
|
16462
|
-
"SvgBoundsAdapter: viewBox width/height are missing, non-finite, or not positive"
|
|
16463
|
-
);
|
|
16464
|
-
}
|
|
16465
|
-
}
|
|
16466
|
-
const widthMatch = content.match(/<svg[^>]*\bwidth\s*=\s*["']?([\d.]+)/);
|
|
16467
|
-
const heightMatch = content.match(/<svg[^>]*\bheight\s*=\s*["']?([\d.]+)/);
|
|
16468
|
-
if (widthMatch && heightMatch) {
|
|
16469
|
-
const w = Number.parseFloat(widthMatch[1]);
|
|
16470
|
-
const h = Number.parseFloat(heightMatch[1]);
|
|
16471
|
-
if (Number.isFinite(w) && Number.isFinite(h) && w > 0 && h > 0) {
|
|
16472
|
-
return { width: w, height: h };
|
|
16473
|
-
}
|
|
16474
|
-
throw new Error(
|
|
16475
|
-
"SvgBoundsAdapter: svg width/height attributes are non-finite or not positive"
|
|
16476
|
-
);
|
|
16477
|
-
}
|
|
16478
|
-
throw new Error(
|
|
16479
|
-
"SvgBoundsAdapter: cannot determine dimensions \u2014 SVG has no viewBox or width/height attributes"
|
|
16480
|
-
);
|
|
16979
|
+
return void 0;
|
|
16980
|
+
}
|
|
16981
|
+
function registerStep(graph, name, step, depPaths) {
|
|
16982
|
+
graph.add(name, step);
|
|
16983
|
+
for (const path of depPaths) {
|
|
16984
|
+
graph.connect(path, name);
|
|
16481
16985
|
}
|
|
16482
|
-
}
|
|
16483
|
-
|
|
16484
|
-
|
|
16485
|
-
|
|
16486
|
-
|
|
16986
|
+
}
|
|
16987
|
+
function baseMeta2(kind, meta) {
|
|
16988
|
+
return {
|
|
16989
|
+
orchestration: true,
|
|
16990
|
+
orchestration_type: kind,
|
|
16991
|
+
...meta ?? {}
|
|
16992
|
+
};
|
|
16993
|
+
}
|
|
16994
|
+
function coerceLoopIterations(raw) {
|
|
16995
|
+
const parseString = (value) => {
|
|
16996
|
+
const trimmed = value.trim();
|
|
16997
|
+
if (trimmed.length === 0) return 0;
|
|
16998
|
+
return Number(trimmed);
|
|
16999
|
+
};
|
|
17000
|
+
let parsed;
|
|
17001
|
+
if (typeof raw === "string") {
|
|
17002
|
+
parsed = parseString(raw);
|
|
17003
|
+
} else if (raw === null) {
|
|
17004
|
+
parsed = 0;
|
|
17005
|
+
} else {
|
|
17006
|
+
parsed = Number(raw);
|
|
16487
17007
|
}
|
|
16488
|
-
|
|
16489
|
-
|
|
16490
|
-
|
|
16491
|
-
|
|
17008
|
+
if (!Number.isFinite(parsed)) return 1;
|
|
17009
|
+
return Math.max(0, Math.trunc(parsed));
|
|
17010
|
+
}
|
|
17011
|
+
function pipeline(name, opts) {
|
|
17012
|
+
return new Graph(name, opts);
|
|
17013
|
+
}
|
|
17014
|
+
function task(graph, name, run, opts) {
|
|
17015
|
+
const depRefs = opts?.deps ?? [];
|
|
17016
|
+
const deps = depRefs.map((dep) => resolveDep(graph, dep));
|
|
17017
|
+
const { deps: _deps, ...nodeOpts } = opts ?? {};
|
|
17018
|
+
const step = node(
|
|
17019
|
+
deps.map((d) => d.node),
|
|
17020
|
+
run,
|
|
17021
|
+
{
|
|
17022
|
+
...nodeOpts,
|
|
17023
|
+
name,
|
|
17024
|
+
describeKind: "derived",
|
|
17025
|
+
meta: baseMeta2("task", opts?.meta)
|
|
16492
17026
|
}
|
|
16493
|
-
|
|
16494
|
-
|
|
16495
|
-
|
|
16496
|
-
|
|
16497
|
-
|
|
16498
|
-
|
|
16499
|
-
|
|
16500
|
-
|
|
16501
|
-
|
|
16502
|
-
|
|
16503
|
-
|
|
16504
|
-
|
|
16505
|
-
|
|
16506
|
-
|
|
16507
|
-
|
|
16508
|
-
|
|
16509
|
-
|
|
16510
|
-
|
|
16511
|
-
|
|
16512
|
-
|
|
16513
|
-
|
|
16514
|
-
|
|
16515
|
-
height,
|
|
16516
|
-
textSegments: segments,
|
|
16517
|
-
textLineBreaks: lineBreaks,
|
|
16518
|
-
textCharPositions: charPositions
|
|
16519
|
-
};
|
|
17027
|
+
);
|
|
17028
|
+
registerStep(
|
|
17029
|
+
graph,
|
|
17030
|
+
name,
|
|
17031
|
+
step,
|
|
17032
|
+
deps.flatMap((d) => d.path ? [d.path] : [])
|
|
17033
|
+
);
|
|
17034
|
+
return step;
|
|
17035
|
+
}
|
|
17036
|
+
function branch(graph, name, source, predicate, opts) {
|
|
17037
|
+
const src = resolveDep(graph, source);
|
|
17038
|
+
const step = node(
|
|
17039
|
+
[src.node],
|
|
17040
|
+
([value]) => ({
|
|
17041
|
+
branch: predicate(value) ? "then" : "else",
|
|
17042
|
+
value
|
|
17043
|
+
}),
|
|
17044
|
+
{
|
|
17045
|
+
...opts,
|
|
17046
|
+
name,
|
|
17047
|
+
describeKind: "derived",
|
|
17048
|
+
meta: baseMeta2("branch", opts?.meta)
|
|
16520
17049
|
}
|
|
16521
|
-
|
|
16522
|
-
|
|
16523
|
-
|
|
16524
|
-
|
|
16525
|
-
|
|
16526
|
-
|
|
16527
|
-
|
|
16528
|
-
|
|
16529
|
-
|
|
16530
|
-
|
|
16531
|
-
|
|
16532
|
-
|
|
16533
|
-
|
|
16534
|
-
|
|
17050
|
+
);
|
|
17051
|
+
registerStep(graph, name, step, src.path ? [src.path] : []);
|
|
17052
|
+
return step;
|
|
17053
|
+
}
|
|
17054
|
+
function gate2(graph, name, source, control, opts) {
|
|
17055
|
+
const src = resolveDep(graph, source);
|
|
17056
|
+
const ctrl = resolveDep(graph, control);
|
|
17057
|
+
const step = node(
|
|
17058
|
+
[src.node, ctrl.node],
|
|
17059
|
+
(_deps, actions) => {
|
|
17060
|
+
const opened = ctrl.node.get();
|
|
17061
|
+
if (!opened) {
|
|
17062
|
+
actions.down([[RESOLVED]]);
|
|
17063
|
+
return void 0;
|
|
16535
17064
|
}
|
|
16536
|
-
|
|
16537
|
-
|
|
16538
|
-
|
|
17065
|
+
return src.node.get();
|
|
17066
|
+
},
|
|
17067
|
+
{
|
|
17068
|
+
...opts,
|
|
17069
|
+
name,
|
|
17070
|
+
describeKind: "operator",
|
|
17071
|
+
meta: baseMeta2("gate", opts?.meta)
|
|
17072
|
+
}
|
|
17073
|
+
);
|
|
17074
|
+
registerStep(
|
|
17075
|
+
graph,
|
|
17076
|
+
name,
|
|
17077
|
+
step,
|
|
17078
|
+
[src.path, ctrl.path].filter((v) => typeof v === "string")
|
|
17079
|
+
);
|
|
17080
|
+
return step;
|
|
17081
|
+
}
|
|
17082
|
+
function approval(graph, name, source, approver, opts) {
|
|
17083
|
+
const src = resolveDep(graph, source);
|
|
17084
|
+
const ctrl = resolveDep(graph, approver);
|
|
17085
|
+
const isApproved = opts?.isApproved ?? ((value) => Boolean(value));
|
|
17086
|
+
const step = node(
|
|
17087
|
+
[src.node, ctrl.node],
|
|
17088
|
+
(_deps, actions) => {
|
|
17089
|
+
if (!isApproved(ctrl.node.get())) {
|
|
17090
|
+
actions.down([[RESOLVED]]);
|
|
17091
|
+
return void 0;
|
|
16539
17092
|
}
|
|
16540
|
-
return
|
|
17093
|
+
return src.node.get();
|
|
17094
|
+
},
|
|
17095
|
+
{
|
|
17096
|
+
...opts,
|
|
17097
|
+
name,
|
|
17098
|
+
describeKind: "operator",
|
|
17099
|
+
meta: baseMeta2("approval", opts?.meta)
|
|
16541
17100
|
}
|
|
16542
|
-
|
|
16543
|
-
|
|
16544
|
-
|
|
16545
|
-
|
|
16546
|
-
|
|
16547
|
-
|
|
16548
|
-
|
|
16549
|
-
|
|
16550
|
-
|
|
16551
|
-
|
|
16552
|
-
|
|
16553
|
-
|
|
17101
|
+
);
|
|
17102
|
+
registerStep(
|
|
17103
|
+
graph,
|
|
17104
|
+
name,
|
|
17105
|
+
step,
|
|
17106
|
+
[src.path, ctrl.path].filter((v) => typeof v === "string")
|
|
17107
|
+
);
|
|
17108
|
+
return step;
|
|
17109
|
+
}
|
|
17110
|
+
function forEach2(graph, name, source, run, opts) {
|
|
17111
|
+
const src = resolveDep(graph, source);
|
|
17112
|
+
let terminated = false;
|
|
17113
|
+
const step = node([src.node], () => void 0, {
|
|
17114
|
+
...opts,
|
|
17115
|
+
name,
|
|
17116
|
+
describeKind: "effect",
|
|
17117
|
+
completeWhenDepsComplete: false,
|
|
17118
|
+
meta: baseMeta2("forEach", opts?.meta),
|
|
17119
|
+
onMessage(msg, depIndex, actions) {
|
|
17120
|
+
if (terminated) return true;
|
|
17121
|
+
if (depIndex !== 0) {
|
|
17122
|
+
actions.down([msg]);
|
|
17123
|
+
if (msg[0] === COMPLETE || msg[0] === ERROR) terminated = true;
|
|
17124
|
+
return true;
|
|
16554
17125
|
}
|
|
16555
|
-
if (
|
|
16556
|
-
|
|
16557
|
-
|
|
17126
|
+
if (msg[0] === DATA) {
|
|
17127
|
+
try {
|
|
17128
|
+
run(msg[1], actions);
|
|
17129
|
+
actions.down([msg]);
|
|
17130
|
+
} catch (err) {
|
|
17131
|
+
terminated = true;
|
|
17132
|
+
actions.down([[ERROR, err]]);
|
|
17133
|
+
}
|
|
17134
|
+
return true;
|
|
16558
17135
|
}
|
|
16559
|
-
|
|
17136
|
+
actions.down([msg]);
|
|
17137
|
+
if (msg[0] === COMPLETE || msg[0] === ERROR) terminated = true;
|
|
17138
|
+
return true;
|
|
16560
17139
|
}
|
|
16561
|
-
}
|
|
17140
|
+
});
|
|
17141
|
+
registerStep(graph, name, step, src.path ? [src.path] : []);
|
|
17142
|
+
return step;
|
|
16562
17143
|
}
|
|
16563
|
-
function
|
|
16564
|
-
|
|
16565
|
-
|
|
17144
|
+
function join2(graph, name, deps, opts) {
|
|
17145
|
+
const resolved = deps.map((dep) => resolveDep(graph, dep));
|
|
17146
|
+
const step = node(
|
|
17147
|
+
resolved.map((d) => d.node),
|
|
17148
|
+
(values) => values,
|
|
17149
|
+
{
|
|
17150
|
+
...opts,
|
|
17151
|
+
name,
|
|
17152
|
+
describeKind: "derived",
|
|
17153
|
+
meta: baseMeta2("join", opts?.meta)
|
|
17154
|
+
}
|
|
17155
|
+
);
|
|
17156
|
+
registerStep(
|
|
17157
|
+
graph,
|
|
17158
|
+
name,
|
|
17159
|
+
step,
|
|
17160
|
+
resolved.flatMap((d) => d.path ? [d.path] : [])
|
|
16566
17161
|
);
|
|
17162
|
+
return step;
|
|
16567
17163
|
}
|
|
16568
|
-
function
|
|
16569
|
-
const
|
|
16570
|
-
|
|
16571
|
-
|
|
16572
|
-
|
|
16573
|
-
|
|
16574
|
-
|
|
17164
|
+
function loop(graph, name, source, iterate, opts) {
|
|
17165
|
+
const src = resolveDep(graph, source);
|
|
17166
|
+
const iterRef = opts?.iterations;
|
|
17167
|
+
const iterDep = typeof iterRef === "number" || iterRef === void 0 ? void 0 : resolveDep(graph, iterRef);
|
|
17168
|
+
const staticIterations = typeof iterRef === "number" ? iterRef : void 0;
|
|
17169
|
+
const step = node(
|
|
17170
|
+
iterDep ? [src.node, iterDep.node] : [src.node],
|
|
17171
|
+
(_deps, actions) => {
|
|
17172
|
+
let current = src.node.get();
|
|
17173
|
+
const rawCount = staticIterations ?? iterDep?.node.get() ?? 1;
|
|
17174
|
+
const count = coerceLoopIterations(rawCount);
|
|
17175
|
+
for (let i = 0; i < count; i += 1) {
|
|
17176
|
+
current = iterate(current, i, actions);
|
|
17177
|
+
}
|
|
17178
|
+
return current;
|
|
17179
|
+
},
|
|
17180
|
+
{
|
|
17181
|
+
...opts,
|
|
17182
|
+
name,
|
|
17183
|
+
describeKind: "derived",
|
|
17184
|
+
meta: baseMeta2("loop", opts?.meta)
|
|
17185
|
+
}
|
|
17186
|
+
);
|
|
17187
|
+
registerStep(
|
|
17188
|
+
graph,
|
|
17189
|
+
name,
|
|
17190
|
+
step,
|
|
17191
|
+
[src.path, iterDep?.path].filter((v) => typeof v === "string")
|
|
17192
|
+
);
|
|
17193
|
+
return step;
|
|
17194
|
+
}
|
|
17195
|
+
function subPipeline(graph, name, childOrBuild, opts) {
|
|
17196
|
+
const child = childOrBuild instanceof Graph ? childOrBuild : pipeline(name, opts);
|
|
17197
|
+
if (typeof childOrBuild === "function") {
|
|
17198
|
+
childOrBuild(child);
|
|
16575
17199
|
}
|
|
16576
|
-
|
|
17200
|
+
graph.mount(name, child);
|
|
17201
|
+
return child;
|
|
16577
17202
|
}
|
|
16578
|
-
function
|
|
16579
|
-
|
|
16580
|
-
|
|
16581
|
-
|
|
17203
|
+
function sensor(graph, name, initial, opts) {
|
|
17204
|
+
const source = node([], () => void 0, {
|
|
17205
|
+
...opts,
|
|
17206
|
+
name,
|
|
17207
|
+
initial,
|
|
17208
|
+
describeKind: "producer",
|
|
17209
|
+
meta: baseMeta2("sensor", opts?.meta)
|
|
17210
|
+
});
|
|
17211
|
+
registerStep(graph, name, source, []);
|
|
17212
|
+
return {
|
|
17213
|
+
node: source,
|
|
17214
|
+
push(value) {
|
|
17215
|
+
source.down([[DATA, value]]);
|
|
17216
|
+
},
|
|
17217
|
+
error(err) {
|
|
17218
|
+
source.down([[ERROR, err]]);
|
|
17219
|
+
},
|
|
17220
|
+
complete() {
|
|
17221
|
+
source.down([[COMPLETE]]);
|
|
17222
|
+
}
|
|
17223
|
+
};
|
|
16582
17224
|
}
|
|
16583
|
-
function
|
|
16584
|
-
const
|
|
16585
|
-
|
|
16586
|
-
|
|
16587
|
-
|
|
16588
|
-
|
|
16589
|
-
|
|
16590
|
-
|
|
16591
|
-
|
|
16592
|
-
|
|
16593
|
-
|
|
16594
|
-
|
|
16595
|
-
|
|
16596
|
-
|
|
16597
|
-
|
|
16598
|
-
const t0 = monotonicNs();
|
|
16599
|
-
const result = measureBlocks(
|
|
16600
|
-
blocksVal,
|
|
16601
|
-
mwVal,
|
|
16602
|
-
adapters,
|
|
16603
|
-
measureCache,
|
|
16604
|
-
defaultFont,
|
|
16605
|
-
defaultLineHeight
|
|
16606
|
-
);
|
|
16607
|
-
const elapsed = monotonicNs() - t0;
|
|
16608
|
-
const meta = measuredBlocksNode.meta;
|
|
16609
|
-
if (meta) {
|
|
16610
|
-
emitWithBatch((msgs) => meta["block-count"]?.down(msgs), [[DATA, result.length]], 3);
|
|
16611
|
-
emitWithBatch((msgs) => meta["layout-time-ns"]?.down(msgs), [[DATA, elapsed]], 3);
|
|
16612
|
-
}
|
|
16613
|
-
return result;
|
|
17225
|
+
function wait(graph, name, source, ms, opts) {
|
|
17226
|
+
const src = resolveDep(graph, source);
|
|
17227
|
+
const timers = /* @__PURE__ */ new Set();
|
|
17228
|
+
let terminated = false;
|
|
17229
|
+
let completed = false;
|
|
17230
|
+
const step = node(
|
|
17231
|
+
[src.node],
|
|
17232
|
+
() => {
|
|
17233
|
+
for (const id of timers) clearTimeout(id);
|
|
17234
|
+
timers.clear();
|
|
17235
|
+
return () => {
|
|
17236
|
+
for (const id of timers) clearTimeout(id);
|
|
17237
|
+
timers.clear();
|
|
17238
|
+
terminated = true;
|
|
17239
|
+
};
|
|
16614
17240
|
},
|
|
16615
17241
|
{
|
|
16616
|
-
|
|
16617
|
-
|
|
16618
|
-
|
|
16619
|
-
|
|
16620
|
-
|
|
16621
|
-
|
|
17242
|
+
...opts,
|
|
17243
|
+
name,
|
|
17244
|
+
initial: src.node.get(),
|
|
17245
|
+
describeKind: "operator",
|
|
17246
|
+
completeWhenDepsComplete: false,
|
|
17247
|
+
meta: baseMeta2("wait", opts?.meta),
|
|
17248
|
+
onMessage(msg, depIndex, actions) {
|
|
17249
|
+
if (terminated) return true;
|
|
17250
|
+
if (depIndex !== 0) {
|
|
17251
|
+
actions.down([msg]);
|
|
17252
|
+
if (msg[0] === COMPLETE || msg[0] === ERROR) terminated = true;
|
|
17253
|
+
return true;
|
|
16622
17254
|
}
|
|
16623
|
-
|
|
16624
|
-
|
|
16625
|
-
|
|
16626
|
-
|
|
16627
|
-
|
|
16628
|
-
|
|
16629
|
-
|
|
16630
|
-
|
|
16631
|
-
|
|
16632
|
-
|
|
16633
|
-
|
|
16634
|
-
|
|
17255
|
+
if (msg[0] === DATA) {
|
|
17256
|
+
const id = setTimeout(() => {
|
|
17257
|
+
timers.delete(id);
|
|
17258
|
+
actions.down([msg]);
|
|
17259
|
+
if (completed && timers.size === 0) {
|
|
17260
|
+
actions.down([[COMPLETE]]);
|
|
17261
|
+
}
|
|
17262
|
+
}, ms);
|
|
17263
|
+
timers.add(id);
|
|
17264
|
+
return true;
|
|
17265
|
+
}
|
|
17266
|
+
if (msg[0] === COMPLETE) {
|
|
17267
|
+
terminated = true;
|
|
17268
|
+
completed = true;
|
|
17269
|
+
if (timers.size === 0) {
|
|
17270
|
+
actions.down([[COMPLETE]]);
|
|
17271
|
+
}
|
|
17272
|
+
return true;
|
|
17273
|
+
}
|
|
17274
|
+
if (msg[0] === ERROR) {
|
|
17275
|
+
terminated = true;
|
|
17276
|
+
for (const id of timers) clearTimeout(id);
|
|
17277
|
+
timers.clear();
|
|
17278
|
+
actions.down([msg]);
|
|
17279
|
+
return true;
|
|
16635
17280
|
}
|
|
17281
|
+
actions.down([msg]);
|
|
16636
17282
|
return true;
|
|
16637
17283
|
}
|
|
16638
17284
|
}
|
|
16639
17285
|
);
|
|
16640
|
-
|
|
16641
|
-
|
|
16642
|
-
|
|
16643
|
-
|
|
16644
|
-
|
|
16645
|
-
|
|
16646
|
-
|
|
16647
|
-
|
|
16648
|
-
|
|
16649
|
-
|
|
16650
|
-
|
|
16651
|
-
|
|
16652
|
-
|
|
16653
|
-
|
|
16654
|
-
|
|
16655
|
-
|
|
16656
|
-
|
|
17286
|
+
registerStep(graph, name, step, src.path ? [src.path] : []);
|
|
17287
|
+
return step;
|
|
17288
|
+
}
|
|
17289
|
+
function onFailure(graph, name, source, recover, opts) {
|
|
17290
|
+
const src = resolveDep(graph, source);
|
|
17291
|
+
let terminated = false;
|
|
17292
|
+
const step = node([src.node], () => void 0, {
|
|
17293
|
+
...opts,
|
|
17294
|
+
name,
|
|
17295
|
+
describeKind: "operator",
|
|
17296
|
+
completeWhenDepsComplete: false,
|
|
17297
|
+
meta: baseMeta2("onFailure", opts?.meta),
|
|
17298
|
+
onMessage(msg, _depIndex, actions) {
|
|
17299
|
+
if (terminated) return true;
|
|
17300
|
+
if (msg[0] === ERROR) {
|
|
17301
|
+
try {
|
|
17302
|
+
actions.emit(recover(msg[1], actions));
|
|
17303
|
+
} catch (err) {
|
|
17304
|
+
terminated = true;
|
|
17305
|
+
actions.down([[ERROR, err]]);
|
|
16657
17306
|
}
|
|
16658
17307
|
return true;
|
|
16659
17308
|
}
|
|
17309
|
+
actions.down([msg]);
|
|
17310
|
+
if (msg[0] === COMPLETE) terminated = true;
|
|
17311
|
+
return true;
|
|
16660
17312
|
}
|
|
16661
|
-
);
|
|
16662
|
-
|
|
16663
|
-
|
|
16664
|
-
([flow]) => computeTotalHeight(flow),
|
|
16665
|
-
{ name: "total-height" }
|
|
16666
|
-
);
|
|
16667
|
-
g.add("blocks", blocksNode);
|
|
16668
|
-
g.add("max-width", maxWidthNode);
|
|
16669
|
-
g.add("gap", gapNode);
|
|
16670
|
-
g.add("measured-blocks", measuredBlocksNode);
|
|
16671
|
-
g.add("block-flow", blockFlowNode);
|
|
16672
|
-
g.add("total-height", totalHeightNode);
|
|
16673
|
-
g.connect("blocks", "measured-blocks");
|
|
16674
|
-
g.connect("max-width", "measured-blocks");
|
|
16675
|
-
g.connect("measured-blocks", "block-flow");
|
|
16676
|
-
g.connect("gap", "block-flow");
|
|
16677
|
-
g.connect("block-flow", "total-height");
|
|
16678
|
-
return {
|
|
16679
|
-
graph: g,
|
|
16680
|
-
setBlocks: (blocks) => g.set("blocks", blocks),
|
|
16681
|
-
setMaxWidth: (mw) => g.set("max-width", Math.max(0, mw)),
|
|
16682
|
-
setGap: (gap) => g.set("gap", gap),
|
|
16683
|
-
measuredBlocks: measuredBlocksNode,
|
|
16684
|
-
blockFlow: blockFlowNode,
|
|
16685
|
-
totalHeight: totalHeightNode
|
|
16686
|
-
};
|
|
17313
|
+
});
|
|
17314
|
+
registerStep(graph, name, step, src.path ? [src.path] : []);
|
|
17315
|
+
return step;
|
|
16687
17316
|
}
|
|
16688
17317
|
|
|
16689
|
-
// src/patterns/
|
|
16690
|
-
var
|
|
16691
|
-
__export(
|
|
16692
|
-
|
|
16693
|
-
|
|
16694
|
-
|
|
16695
|
-
|
|
16696
|
-
|
|
17318
|
+
// src/patterns/reactive-layout/index.ts
|
|
17319
|
+
var reactive_layout_exports = {};
|
|
17320
|
+
__export(reactive_layout_exports, {
|
|
17321
|
+
CanvasMeasureAdapter: () => CanvasMeasureAdapter,
|
|
17322
|
+
CliMeasureAdapter: () => CliMeasureAdapter,
|
|
17323
|
+
ImageSizeAdapter: () => ImageSizeAdapter,
|
|
17324
|
+
NodeCanvasMeasureAdapter: () => NodeCanvasMeasureAdapter,
|
|
17325
|
+
PrecomputedAdapter: () => PrecomputedAdapter,
|
|
17326
|
+
SvgBoundsAdapter: () => SvgBoundsAdapter,
|
|
17327
|
+
analyzeAndMeasure: () => analyzeAndMeasure,
|
|
17328
|
+
computeBlockFlow: () => computeBlockFlow,
|
|
17329
|
+
computeCharPositions: () => computeCharPositions,
|
|
17330
|
+
computeLineBreaks: () => computeLineBreaks,
|
|
17331
|
+
computeTotalHeight: () => computeTotalHeight,
|
|
17332
|
+
measureBlock: () => measureBlock,
|
|
17333
|
+
measureBlocks: () => measureBlocks,
|
|
17334
|
+
reactiveBlockLayout: () => reactiveBlockLayout,
|
|
17335
|
+
reactiveLayout: () => reactiveLayout
|
|
16697
17336
|
});
|
|
16698
|
-
|
|
16699
|
-
|
|
16700
|
-
|
|
16701
|
-
|
|
16702
|
-
|
|
16703
|
-
|
|
17337
|
+
|
|
17338
|
+
// src/patterns/reactive-layout/measurement-adapters.ts
|
|
17339
|
+
function cellWidth(code) {
|
|
17340
|
+
if (code >= 768 && code <= 879 || // Combining Diacritical Marks
|
|
17341
|
+
code >= 1155 && code <= 1161 || // Cyrillic combining marks
|
|
17342
|
+
code >= 1425 && code <= 1469 || // Hebrew combining marks
|
|
17343
|
+
code >= 1552 && code <= 1562 || // Arabic combining marks
|
|
17344
|
+
code >= 1611 && code <= 1631 || // Arabic combining marks
|
|
17345
|
+
code >= 1648 && code === 1648 || // Arabic superscript alef
|
|
17346
|
+
code >= 1750 && code <= 1756 || // Arabic combining marks
|
|
17347
|
+
code >= 1759 && code <= 1764 || // Arabic combining marks
|
|
17348
|
+
code >= 1767 && code <= 1768 || // Arabic combining marks
|
|
17349
|
+
code >= 1770 && code <= 1773 || // Arabic combining marks
|
|
17350
|
+
code >= 1840 && code <= 1866 || // Syriac combining marks
|
|
17351
|
+
code >= 1958 && code <= 1968 || // Thaana combining marks
|
|
17352
|
+
code >= 2304 && code <= 2307 || // Devanagari combining marks
|
|
17353
|
+
code >= 2362 && code <= 2383 || // Devanagari combining marks
|
|
17354
|
+
code >= 2385 && code <= 2391 || // Devanagari combining marks
|
|
17355
|
+
code >= 2402 && code <= 2403 || // Devanagari combining marks
|
|
17356
|
+
code >= 2433 && code <= 2435 || // Bengali combining marks
|
|
17357
|
+
code >= 2492 && code <= 2509 || // Bengali combining marks
|
|
17358
|
+
code >= 2561 && code <= 2563 || // Gurmukhi combining marks
|
|
17359
|
+
code >= 2620 && code <= 2641 || // Gurmukhi combining marks
|
|
17360
|
+
code >= 2672 && code <= 2673 || // Gurmukhi combining marks
|
|
17361
|
+
code >= 2677 && code === 2677 || // Gurmukhi combining mark
|
|
17362
|
+
code >= 3633 && code === 3633 || // Thai combining mark
|
|
17363
|
+
code >= 3636 && code <= 3642 || // Thai combining marks
|
|
17364
|
+
code >= 3655 && code <= 3662 || // Thai combining marks
|
|
17365
|
+
code >= 3761 && code === 3761 || // Lao combining mark
|
|
17366
|
+
code >= 3764 && code <= 3772 || // Lao combining marks
|
|
17367
|
+
code >= 3784 && code <= 3790 || // Lao combining marks
|
|
17368
|
+
code >= 7616 && code <= 7679 || // Combining Diacritical Marks Supplement
|
|
17369
|
+
code >= 8400 && code <= 8447 || // Combining Diacritical Marks for Symbols
|
|
17370
|
+
code >= 65024 && code <= 65039 || // Variation Selectors
|
|
17371
|
+
code >= 65056 && code <= 65071 || // Combining Half Marks
|
|
17372
|
+
code === 8205) {
|
|
17373
|
+
return 0;
|
|
17374
|
+
}
|
|
17375
|
+
if (code >= 4352 && code <= 4447 || // Hangul Jamo
|
|
17376
|
+
code >= 8986 && code <= 8987 || // Watch, Hourglass
|
|
17377
|
+
code >= 9001 && code <= 9002 || // Angle brackets
|
|
17378
|
+
code >= 9193 && code <= 9203 || // Media control symbols
|
|
17379
|
+
code >= 9208 && code <= 9210 || // Media control symbols
|
|
17380
|
+
code >= 9725 && code <= 9726 || // Medium squares
|
|
17381
|
+
code >= 9748 && code <= 9749 || // Umbrella, Hot Beverage
|
|
17382
|
+
code >= 9800 && code <= 9811 || // Zodiac symbols
|
|
17383
|
+
code === 9855 || // Wheelchair
|
|
17384
|
+
code === 9875 || // Anchor
|
|
17385
|
+
code === 9889 || // High Voltage
|
|
17386
|
+
code >= 9898 && code <= 9899 || // Medium circles
|
|
17387
|
+
code >= 9917 && code <= 9918 || // Soccer, Baseball
|
|
17388
|
+
code >= 9924 && code <= 9925 || // Snowman, Sun behind cloud
|
|
17389
|
+
code === 9934 || // Ophiuchus
|
|
17390
|
+
code === 9940 || // No Entry
|
|
17391
|
+
code === 9962 || // Church
|
|
17392
|
+
code >= 9970 && code <= 9971 || // Fountain, Golf
|
|
17393
|
+
code === 9973 || // Sailboat
|
|
17394
|
+
code === 9978 || // Tent
|
|
17395
|
+
code === 9981 || // Fuel Pump
|
|
17396
|
+
code === 9986 || // Scissors
|
|
17397
|
+
code === 9989 || // Check Mark
|
|
17398
|
+
code >= 9992 && code <= 9997 || // Airplane...Writing Hand
|
|
17399
|
+
code === 9999 || // Pencil
|
|
17400
|
+
code >= 10067 && code <= 10069 || // Question marks
|
|
17401
|
+
code === 10071 || // Exclamation
|
|
17402
|
+
code >= 10133 && code <= 10135 || // Plus, Minus, Divide
|
|
17403
|
+
code === 10160 || // Curly Loop
|
|
17404
|
+
code === 10175 || // Double Curly Loop
|
|
17405
|
+
code >= 10548 && code <= 10549 || // Arrows
|
|
17406
|
+
code >= 11013 && code <= 11015 || // Arrows
|
|
17407
|
+
code >= 11035 && code <= 11036 || // Squares
|
|
17408
|
+
code === 11088 || // Star
|
|
17409
|
+
code === 11093 || // Circle
|
|
17410
|
+
code >= 11904 && code <= 12350 || // CJK Radicals, Symbols, Punctuation
|
|
17411
|
+
code >= 12352 && code <= 12447 || // Hiragana
|
|
17412
|
+
code >= 12448 && code <= 12543 || // Katakana
|
|
17413
|
+
code >= 12549 && code <= 12591 || // Bopomofo
|
|
17414
|
+
code >= 12593 && code <= 12686 || // Hangul Compatibility Jamo
|
|
17415
|
+
code >= 12688 && code <= 12771 || // Kanbun, CJK Strokes
|
|
17416
|
+
code >= 12784 && code <= 12830 || // Katakana Phonetic Extensions
|
|
17417
|
+
code >= 12832 && code <= 12871 || // Enclosed CJK
|
|
17418
|
+
code >= 12880 && code <= 19903 || // CJK Extensions + Unified block
|
|
17419
|
+
code >= 19968 && code <= 40959 || // CJK Unified Ideographs
|
|
17420
|
+
code >= 43360 && code <= 43388 || // Hangul Jamo Extended-A
|
|
17421
|
+
code >= 44032 && code <= 55203 || // Hangul Syllables
|
|
17422
|
+
code >= 63744 && code <= 64255 || // CJK Compatibility Ideographs
|
|
17423
|
+
code >= 65040 && code <= 65049 || // Vertical forms
|
|
17424
|
+
code >= 65072 && code <= 65131 || // CJK Compatibility Forms
|
|
17425
|
+
code >= 65281 && code <= 65376 || // Fullwidth Forms (excl. halfwidth)
|
|
17426
|
+
code >= 65504 && code <= 65510 || // Fullwidth Signs
|
|
17427
|
+
code >= 126980 && code === 126980 || // Mahjong Red Dragon
|
|
17428
|
+
code === 127183 || // Joker
|
|
17429
|
+
code >= 127344 && code <= 127345 || // A/B buttons
|
|
17430
|
+
code === 127358 || // O button
|
|
17431
|
+
code === 127359 || // P button
|
|
17432
|
+
code === 127374 || // AB button
|
|
17433
|
+
code >= 127377 && code <= 127386 || // Squared symbols
|
|
17434
|
+
code >= 127456 && code <= 127487 || // Regional Indicator Symbols
|
|
17435
|
+
code >= 127488 && code <= 127490 || // Enclosed ideographic
|
|
17436
|
+
code === 127514 || // Squared CJK
|
|
17437
|
+
code === 127535 || // Squared CJK
|
|
17438
|
+
code >= 127538 && code <= 127546 || // Squared CJK
|
|
17439
|
+
code >= 127568 && code <= 127569 || // Circled ideographic
|
|
17440
|
+
code >= 127744 && code <= 129535 || // Misc Symbols / Emoticons / Emoji
|
|
17441
|
+
code >= 129536 && code <= 129791 || // Chess, Symbols Extended-A
|
|
17442
|
+
code >= 129792 && code <= 130047 || // Symbols for Legacy Computing
|
|
17443
|
+
code >= 131072 && code <= 196605 || // CJK Extension B-F (excl. nonchars)
|
|
17444
|
+
code >= 196608 && code <= 262141) {
|
|
17445
|
+
return 2;
|
|
17446
|
+
}
|
|
17447
|
+
return 1;
|
|
16704
17448
|
}
|
|
16705
|
-
function
|
|
16706
|
-
|
|
16707
|
-
|
|
16708
|
-
|
|
16709
|
-
meta: baseMeta2("stratify_rules")
|
|
16710
|
-
});
|
|
16711
|
-
g.add("rules", rulesNode);
|
|
16712
|
-
for (const rule of rules) {
|
|
16713
|
-
_addBranch(g, source, rulesNode, rule);
|
|
17449
|
+
function countCells(text) {
|
|
17450
|
+
let cells = 0;
|
|
17451
|
+
for (const ch of text) {
|
|
17452
|
+
cells += cellWidth(ch.codePointAt(0));
|
|
16714
17453
|
}
|
|
16715
|
-
return
|
|
17454
|
+
return cells;
|
|
16716
17455
|
}
|
|
16717
|
-
|
|
16718
|
-
|
|
16719
|
-
|
|
16720
|
-
|
|
16721
|
-
|
|
16722
|
-
|
|
16723
|
-
|
|
16724
|
-
|
|
16725
|
-
|
|
16726
|
-
|
|
16727
|
-
|
|
16728
|
-
|
|
16729
|
-
|
|
16730
|
-
|
|
16731
|
-
|
|
16732
|
-
|
|
16733
|
-
|
|
16734
|
-
|
|
16735
|
-
|
|
16736
|
-
|
|
16737
|
-
|
|
17456
|
+
var CliMeasureAdapter = class {
|
|
17457
|
+
cellPx;
|
|
17458
|
+
constructor(opts) {
|
|
17459
|
+
this.cellPx = opts?.cellPx ?? 8;
|
|
17460
|
+
}
|
|
17461
|
+
measureSegment(text, _font) {
|
|
17462
|
+
return { width: countCells(text) * this.cellPx };
|
|
17463
|
+
}
|
|
17464
|
+
};
|
|
17465
|
+
var PrecomputedAdapterKeyError = class extends Error {
|
|
17466
|
+
name = "KeyError";
|
|
17467
|
+
};
|
|
17468
|
+
var PrecomputedAdapter = class {
|
|
17469
|
+
metrics;
|
|
17470
|
+
fallback;
|
|
17471
|
+
constructor(opts) {
|
|
17472
|
+
this.metrics = opts.metrics;
|
|
17473
|
+
const fb = opts.fallback ?? "per-char";
|
|
17474
|
+
if (fb !== "per-char" && fb !== "error") {
|
|
17475
|
+
throw new Error(
|
|
17476
|
+
`fallback must be 'per-char' or 'error', got ${JSON.stringify(opts.fallback)}`
|
|
17477
|
+
);
|
|
17478
|
+
}
|
|
17479
|
+
this.fallback = fb;
|
|
17480
|
+
}
|
|
17481
|
+
measureSegment(text, font) {
|
|
17482
|
+
const fontMap = this.metrics[font];
|
|
17483
|
+
if (fontMap) {
|
|
17484
|
+
const w = fontMap[text];
|
|
17485
|
+
if (w !== void 0) return { width: w };
|
|
17486
|
+
}
|
|
17487
|
+
if (this.fallback === "error") {
|
|
17488
|
+
throw new PrecomputedAdapterKeyError(
|
|
17489
|
+
`PrecomputedAdapter: no metrics for segment ${JSON.stringify(text)} in font ${JSON.stringify(font)}`
|
|
17490
|
+
);
|
|
17491
|
+
}
|
|
17492
|
+
let total = 0;
|
|
17493
|
+
if (fontMap) {
|
|
17494
|
+
for (const ch of text) {
|
|
17495
|
+
const cw = fontMap[ch];
|
|
17496
|
+
if (cw !== void 0) {
|
|
17497
|
+
total += cw;
|
|
16738
17498
|
}
|
|
16739
|
-
return true;
|
|
16740
17499
|
}
|
|
16741
|
-
|
|
16742
|
-
|
|
16743
|
-
|
|
17500
|
+
}
|
|
17501
|
+
return { width: total };
|
|
17502
|
+
}
|
|
17503
|
+
};
|
|
17504
|
+
var CanvasMeasureAdapter = class {
|
|
17505
|
+
ctx = null;
|
|
17506
|
+
currentFont = "";
|
|
17507
|
+
emojiCorrection;
|
|
17508
|
+
constructor(opts) {
|
|
17509
|
+
this.emojiCorrection = opts?.emojiCorrection ?? 1;
|
|
17510
|
+
}
|
|
17511
|
+
getContext() {
|
|
17512
|
+
if (!this.ctx) {
|
|
17513
|
+
if (typeof OffscreenCanvas === "undefined") {
|
|
17514
|
+
throw new Error(
|
|
17515
|
+
"CanvasMeasureAdapter requires a browser environment with OffscreenCanvas support. Use CliMeasureAdapter or NodeCanvasMeasureAdapter for Node.js."
|
|
17516
|
+
);
|
|
16744
17517
|
}
|
|
16745
|
-
|
|
16746
|
-
|
|
16747
|
-
|
|
16748
|
-
|
|
16749
|
-
|
|
16750
|
-
|
|
17518
|
+
const canvas = new OffscreenCanvas(0, 0);
|
|
17519
|
+
const ctx = canvas.getContext("2d");
|
|
17520
|
+
if (!ctx) throw new Error("CanvasMeasureAdapter: failed to get 2d context");
|
|
17521
|
+
this.ctx = ctx;
|
|
17522
|
+
}
|
|
17523
|
+
return this.ctx;
|
|
17524
|
+
}
|
|
17525
|
+
measureSegment(text, font) {
|
|
17526
|
+
const ctx = this.getContext();
|
|
17527
|
+
if (font !== this.currentFont) {
|
|
17528
|
+
ctx.font = font;
|
|
17529
|
+
this.currentFont = font;
|
|
17530
|
+
}
|
|
17531
|
+
let width = ctx.measureText(text).width;
|
|
17532
|
+
if (this.emojiCorrection !== 1 && /\p{Emoji_Presentation}/u.test(text)) {
|
|
17533
|
+
width *= this.emojiCorrection;
|
|
17534
|
+
}
|
|
17535
|
+
return { width };
|
|
17536
|
+
}
|
|
17537
|
+
clearCache() {
|
|
17538
|
+
this.currentFont = "";
|
|
17539
|
+
}
|
|
17540
|
+
};
|
|
17541
|
+
var NodeCanvasMeasureAdapter = class {
|
|
17542
|
+
ctx = null;
|
|
17543
|
+
currentFont = "";
|
|
17544
|
+
canvasModule;
|
|
17545
|
+
constructor(canvasModule) {
|
|
17546
|
+
this.canvasModule = canvasModule;
|
|
17547
|
+
}
|
|
17548
|
+
getContext() {
|
|
17549
|
+
if (!this.ctx) {
|
|
17550
|
+
const canvas = this.canvasModule.createCanvas(0, 0);
|
|
17551
|
+
const ctx = canvas.getContext("2d");
|
|
17552
|
+
if (!ctx) throw new Error("NodeCanvasMeasureAdapter: failed to get 2d context");
|
|
17553
|
+
this.ctx = ctx;
|
|
17554
|
+
}
|
|
17555
|
+
return this.ctx;
|
|
17556
|
+
}
|
|
17557
|
+
measureSegment(text, font) {
|
|
17558
|
+
const ctx = this.getContext();
|
|
17559
|
+
if (font !== this.currentFont) {
|
|
17560
|
+
ctx.font = font;
|
|
17561
|
+
this.currentFont = font;
|
|
17562
|
+
}
|
|
17563
|
+
return { width: ctx.measureText(text).width };
|
|
17564
|
+
}
|
|
17565
|
+
clearCache() {
|
|
17566
|
+
this.currentFont = "";
|
|
17567
|
+
}
|
|
17568
|
+
};
|
|
17569
|
+
var SvgBoundsAdapter = class {
|
|
17570
|
+
measureSvg(content) {
|
|
17571
|
+
const viewBoxMatch = content.match(/viewBox\s*=\s*["']([^"']+)["']/);
|
|
17572
|
+
if (viewBoxMatch) {
|
|
17573
|
+
const parts = viewBoxMatch[1].trim().split(/[\s,]+/);
|
|
17574
|
+
if (parts.length >= 4) {
|
|
17575
|
+
const w = Number.parseFloat(parts[2]);
|
|
17576
|
+
const h = Number.parseFloat(parts[3]);
|
|
17577
|
+
if (Number.isFinite(w) && Number.isFinite(h) && w > 0 && h > 0) {
|
|
17578
|
+
return { width: w, height: h };
|
|
16751
17579
|
}
|
|
16752
|
-
|
|
17580
|
+
throw new Error(
|
|
17581
|
+
"SvgBoundsAdapter: viewBox width/height are missing, non-finite, or not positive"
|
|
17582
|
+
);
|
|
16753
17583
|
}
|
|
16754
|
-
|
|
16755
|
-
|
|
16756
|
-
|
|
16757
|
-
|
|
17584
|
+
}
|
|
17585
|
+
const widthMatch = content.match(/<svg[^>]*\bwidth\s*=\s*["']?([\d.]+)/);
|
|
17586
|
+
const heightMatch = content.match(/<svg[^>]*\bheight\s*=\s*["']?([\d.]+)/);
|
|
17587
|
+
if (widthMatch && heightMatch) {
|
|
17588
|
+
const w = Number.parseFloat(widthMatch[1]);
|
|
17589
|
+
const h = Number.parseFloat(heightMatch[1]);
|
|
17590
|
+
if (Number.isFinite(w) && Number.isFinite(h) && w > 0 && h > 0) {
|
|
17591
|
+
return { width: w, height: h };
|
|
16758
17592
|
}
|
|
16759
|
-
|
|
17593
|
+
throw new Error(
|
|
17594
|
+
"SvgBoundsAdapter: svg width/height attributes are non-finite or not positive"
|
|
17595
|
+
);
|
|
16760
17596
|
}
|
|
16761
|
-
|
|
16762
|
-
|
|
16763
|
-
|
|
16764
|
-
if (rule.ops) {
|
|
16765
|
-
const transformed = rule.ops(filterNode);
|
|
16766
|
-
const transformedName = `branch/${rule.name}/out`;
|
|
16767
|
-
graph.add(transformedName, transformed);
|
|
16768
|
-
graph.connect(branchName, transformedName);
|
|
17597
|
+
throw new Error(
|
|
17598
|
+
"SvgBoundsAdapter: cannot determine dimensions \u2014 SVG has no viewBox or width/height attributes"
|
|
17599
|
+
);
|
|
16769
17600
|
}
|
|
16770
|
-
}
|
|
16771
|
-
|
|
16772
|
-
|
|
16773
|
-
|
|
16774
|
-
|
|
16775
|
-
|
|
16776
|
-
|
|
16777
|
-
|
|
16778
|
-
|
|
16779
|
-
|
|
16780
|
-
const sub = new Graph(stage.name);
|
|
16781
|
-
stage.build(sub);
|
|
16782
|
-
try {
|
|
16783
|
-
sub.resolve("input");
|
|
16784
|
-
} catch {
|
|
16785
|
-
throw new Error(`funnel stage "${stage.name}" must define an "input" node`);
|
|
17601
|
+
};
|
|
17602
|
+
var ImageSizeAdapter = class {
|
|
17603
|
+
sizes;
|
|
17604
|
+
constructor(sizes) {
|
|
17605
|
+
this.sizes = new Map(Object.entries(sizes));
|
|
17606
|
+
}
|
|
17607
|
+
measureImage(src) {
|
|
17608
|
+
const dims = this.sizes.get(src);
|
|
17609
|
+
if (!dims) {
|
|
17610
|
+
throw new Error(`ImageSizeAdapter: no dimensions registered for ${JSON.stringify(src)}`);
|
|
16786
17611
|
}
|
|
16787
|
-
|
|
16788
|
-
|
|
16789
|
-
|
|
16790
|
-
|
|
17612
|
+
return { width: dims.width, height: dims.height };
|
|
17613
|
+
}
|
|
17614
|
+
};
|
|
17615
|
+
|
|
17616
|
+
// src/patterns/reactive-layout/reactive-block-layout.ts
|
|
17617
|
+
function measureBlock(block, maxWidth, adapters, measureCache, defaultFont, defaultLineHeight, index) {
|
|
17618
|
+
switch (block.type) {
|
|
17619
|
+
case "text": {
|
|
17620
|
+
const font = block.font ?? defaultFont;
|
|
17621
|
+
const lineHeight = block.lineHeight ?? defaultLineHeight;
|
|
17622
|
+
const segments = analyzeAndMeasure(block.text, font, adapters.text, measureCache);
|
|
17623
|
+
const lineBreaks = computeLineBreaks(segments, maxWidth, adapters.text, font, measureCache);
|
|
17624
|
+
const charPositions = computeCharPositions(lineBreaks, segments, lineHeight);
|
|
17625
|
+
const height = lineBreaks.lineCount * lineHeight;
|
|
17626
|
+
let width = 0;
|
|
17627
|
+
for (const line of lineBreaks.lines) {
|
|
17628
|
+
if (line.width > width) width = line.width;
|
|
17629
|
+
}
|
|
17630
|
+
return {
|
|
17631
|
+
index,
|
|
17632
|
+
type: "text",
|
|
17633
|
+
width: Math.min(width, maxWidth),
|
|
17634
|
+
height,
|
|
17635
|
+
textSegments: segments,
|
|
17636
|
+
textLineBreaks: lineBreaks,
|
|
17637
|
+
textCharPositions: charPositions
|
|
17638
|
+
};
|
|
16791
17639
|
}
|
|
16792
|
-
|
|
16793
|
-
|
|
16794
|
-
|
|
16795
|
-
|
|
16796
|
-
|
|
16797
|
-
|
|
16798
|
-
|
|
16799
|
-
|
|
16800
|
-
|
|
16801
|
-
|
|
16802
|
-
|
|
16803
|
-
|
|
16804
|
-
|
|
16805
|
-
|
|
16806
|
-
|
|
16807
|
-
|
|
17640
|
+
case "image": {
|
|
17641
|
+
let w;
|
|
17642
|
+
let h;
|
|
17643
|
+
if (block.naturalWidth != null && block.naturalHeight != null) {
|
|
17644
|
+
w = block.naturalWidth;
|
|
17645
|
+
h = block.naturalHeight;
|
|
17646
|
+
} else if (adapters.image) {
|
|
17647
|
+
const dims = adapters.image.measureImage(block.src);
|
|
17648
|
+
w = dims.width;
|
|
17649
|
+
h = dims.height;
|
|
17650
|
+
} else {
|
|
17651
|
+
throw new Error(
|
|
17652
|
+
`Image block at index ${index} has no naturalWidth/naturalHeight and no ImageMeasurer adapter`
|
|
17653
|
+
);
|
|
17654
|
+
}
|
|
17655
|
+
if (w > maxWidth) {
|
|
17656
|
+
h = h * maxWidth / w;
|
|
17657
|
+
w = maxWidth;
|
|
17658
|
+
}
|
|
17659
|
+
return { index, type: "image", width: w, height: h };
|
|
17660
|
+
}
|
|
17661
|
+
case "svg": {
|
|
17662
|
+
let w;
|
|
17663
|
+
let h;
|
|
17664
|
+
if (block.viewBox) {
|
|
17665
|
+
w = block.viewBox.width;
|
|
17666
|
+
h = block.viewBox.height;
|
|
17667
|
+
} else if (adapters.svg) {
|
|
17668
|
+
const dims = adapters.svg.measureSvg(block.content);
|
|
17669
|
+
w = dims.width;
|
|
17670
|
+
h = dims.height;
|
|
17671
|
+
} else {
|
|
17672
|
+
throw new Error(`SVG block at index ${index} has no viewBox and no SvgMeasurer adapter`);
|
|
16808
17673
|
}
|
|
16809
|
-
|
|
16810
|
-
|
|
16811
|
-
|
|
16812
|
-
return g;
|
|
16813
|
-
}
|
|
16814
|
-
function feedback(graph, condition, reentry, opts) {
|
|
16815
|
-
const maxIter = opts?.maxIterations ?? 10;
|
|
16816
|
-
const counterName = `__feedback_${condition}`;
|
|
16817
|
-
const counter = state(0, {
|
|
16818
|
-
meta: baseMeta2("feedback_counter", { maxIterations: maxIter })
|
|
16819
|
-
});
|
|
16820
|
-
graph.add(counterName, counter);
|
|
16821
|
-
const condNode = graph.resolve(condition);
|
|
16822
|
-
const reentryNode = graph.resolve(reentry);
|
|
16823
|
-
condNode.subscribe((msgs) => {
|
|
16824
|
-
for (const msg of msgs) {
|
|
16825
|
-
if (msg[0] === DATA) {
|
|
16826
|
-
const currentCount = counter.get();
|
|
16827
|
-
if (currentCount >= maxIter) continue;
|
|
16828
|
-
const condValue = msg[1];
|
|
16829
|
-
if (condValue == null) continue;
|
|
16830
|
-
counter.down([[DATA, currentCount + 1]]);
|
|
16831
|
-
reentryNode.down([[DATA, condValue]]);
|
|
17674
|
+
if (w > maxWidth) {
|
|
17675
|
+
h = h * maxWidth / w;
|
|
17676
|
+
w = maxWidth;
|
|
16832
17677
|
}
|
|
17678
|
+
return { index, type: "svg", width: w, height: h };
|
|
16833
17679
|
}
|
|
16834
|
-
});
|
|
16835
|
-
return graph;
|
|
16836
|
-
}
|
|
16837
|
-
function budgetGate(source, constraints, opts) {
|
|
16838
|
-
if (constraints.length === 0) throw new RangeError("budgetGate requires at least one constraint");
|
|
16839
|
-
const constraintNodes = constraints.map((c) => c.node);
|
|
16840
|
-
const allDeps = [source, ...constraintNodes];
|
|
16841
|
-
let buffer2 = [];
|
|
16842
|
-
let paused = false;
|
|
16843
|
-
const lockId = /* @__PURE__ */ Symbol("budget-gate");
|
|
16844
|
-
function checkBudget() {
|
|
16845
|
-
return constraints.every((c) => c.check(c.node.get()));
|
|
16846
17680
|
}
|
|
16847
|
-
|
|
16848
|
-
|
|
16849
|
-
|
|
16850
|
-
|
|
16851
|
-
|
|
17681
|
+
}
|
|
17682
|
+
function measureBlocks(blocks, maxWidth, adapters, measureCache, defaultFont, defaultLineHeight) {
|
|
17683
|
+
return blocks.map(
|
|
17684
|
+
(block, i) => measureBlock(block, maxWidth, adapters, measureCache, defaultFont, defaultLineHeight, i)
|
|
17685
|
+
);
|
|
17686
|
+
}
|
|
17687
|
+
function computeBlockFlow(measured, gap) {
|
|
17688
|
+
const result = [];
|
|
17689
|
+
let y = 0;
|
|
17690
|
+
for (let i = 0; i < measured.length; i++) {
|
|
17691
|
+
const m = measured[i];
|
|
17692
|
+
result.push({ ...m, x: 0, y });
|
|
17693
|
+
y += m.height + (i < measured.length - 1 ? gap : 0);
|
|
16852
17694
|
}
|
|
16853
|
-
return
|
|
16854
|
-
|
|
16855
|
-
|
|
16856
|
-
|
|
16857
|
-
|
|
16858
|
-
|
|
16859
|
-
|
|
16860
|
-
|
|
16861
|
-
|
|
16862
|
-
|
|
16863
|
-
|
|
16864
|
-
|
|
16865
|
-
|
|
16866
|
-
|
|
16867
|
-
|
|
16868
|
-
|
|
16869
|
-
|
|
16870
|
-
|
|
16871
|
-
|
|
16872
|
-
|
|
16873
|
-
|
|
16874
|
-
|
|
16875
|
-
|
|
16876
|
-
|
|
16877
|
-
|
|
16878
|
-
|
|
16879
|
-
|
|
16880
|
-
|
|
16881
|
-
|
|
16882
|
-
|
|
16883
|
-
|
|
16884
|
-
|
|
16885
|
-
|
|
16886
|
-
|
|
16887
|
-
|
|
16888
|
-
|
|
16889
|
-
|
|
16890
|
-
|
|
16891
|
-
|
|
16892
|
-
|
|
17695
|
+
return result;
|
|
17696
|
+
}
|
|
17697
|
+
function computeTotalHeight(flow) {
|
|
17698
|
+
if (flow.length === 0) return 0;
|
|
17699
|
+
const last2 = flow[flow.length - 1];
|
|
17700
|
+
return last2.y + last2.height;
|
|
17701
|
+
}
|
|
17702
|
+
function reactiveBlockLayout(opts) {
|
|
17703
|
+
const {
|
|
17704
|
+
adapters,
|
|
17705
|
+
name = "reactive-block-layout",
|
|
17706
|
+
defaultFont = "16px sans-serif",
|
|
17707
|
+
defaultLineHeight = 20
|
|
17708
|
+
} = opts;
|
|
17709
|
+
const g = new Graph(name);
|
|
17710
|
+
const measureCache = /* @__PURE__ */ new Map();
|
|
17711
|
+
const blocksNode = state(opts.blocks ?? [], { name: "blocks" });
|
|
17712
|
+
const maxWidthNode = state(Math.max(0, opts.maxWidth ?? 800), { name: "max-width" });
|
|
17713
|
+
const gapNode = state(opts.gap ?? 0, { name: "gap" });
|
|
17714
|
+
const measuredBlocksNode = derived(
|
|
17715
|
+
[blocksNode, maxWidthNode],
|
|
17716
|
+
([blocksVal, mwVal]) => {
|
|
17717
|
+
const t0 = monotonicNs();
|
|
17718
|
+
const result = measureBlocks(
|
|
17719
|
+
blocksVal,
|
|
17720
|
+
mwVal,
|
|
17721
|
+
adapters,
|
|
17722
|
+
measureCache,
|
|
17723
|
+
defaultFont,
|
|
17724
|
+
defaultLineHeight
|
|
17725
|
+
);
|
|
17726
|
+
const elapsed = monotonicNs() - t0;
|
|
17727
|
+
const meta = measuredBlocksNode.meta;
|
|
17728
|
+
if (meta) {
|
|
17729
|
+
emitWithBatch((msgs) => meta["block-count"]?.down(msgs), [[DATA, result.length]], 3);
|
|
17730
|
+
emitWithBatch((msgs) => meta["layout-time-ns"]?.down(msgs), [[DATA, elapsed]], 3);
|
|
17731
|
+
}
|
|
17732
|
+
return result;
|
|
17733
|
+
},
|
|
17734
|
+
{
|
|
17735
|
+
name: "measured-blocks",
|
|
17736
|
+
meta: { "block-count": 0, "layout-time-ns": 0 },
|
|
17737
|
+
onMessage(msg, _depIndex, _actions) {
|
|
17738
|
+
if (msg[0] === INVALIDATE || msg[0] === TEARDOWN) {
|
|
17739
|
+
measureCache.clear();
|
|
17740
|
+
adapters.text.clearCache?.();
|
|
16893
17741
|
}
|
|
16894
17742
|
return false;
|
|
16895
|
-
}
|
|
16896
|
-
|
|
16897
|
-
|
|
16898
|
-
|
|
16899
|
-
|
|
16900
|
-
|
|
16901
|
-
|
|
16902
|
-
|
|
16903
|
-
|
|
16904
|
-
|
|
16905
|
-
|
|
17743
|
+
},
|
|
17744
|
+
equals: (a, b) => {
|
|
17745
|
+
const ma = a;
|
|
17746
|
+
const mb = b;
|
|
17747
|
+
if (ma == null || mb == null) return ma === mb;
|
|
17748
|
+
if (ma.length !== mb.length) return false;
|
|
17749
|
+
for (let i = 0; i < ma.length; i++) {
|
|
17750
|
+
const ba = ma[i];
|
|
17751
|
+
const bb = mb[i];
|
|
17752
|
+
if (ba.type !== bb.type || ba.width !== bb.width || ba.height !== bb.height || ba.index !== bb.index)
|
|
17753
|
+
return false;
|
|
16906
17754
|
}
|
|
16907
17755
|
return true;
|
|
16908
17756
|
}
|
|
16909
|
-
if (t === DIRTY) {
|
|
16910
|
-
return true;
|
|
16911
|
-
}
|
|
16912
|
-
if (t === ERROR) {
|
|
16913
|
-
actions.down([msg]);
|
|
16914
|
-
return true;
|
|
16915
|
-
}
|
|
16916
|
-
if (t === COMPLETE) {
|
|
16917
|
-
return true;
|
|
16918
|
-
}
|
|
16919
|
-
return false;
|
|
16920
17757
|
}
|
|
16921
|
-
|
|
16922
|
-
|
|
16923
|
-
|
|
16924
|
-
|
|
16925
|
-
|
|
16926
|
-
throw new RangeError("scorer requires the same number of sources and weights");
|
|
16927
|
-
}
|
|
16928
|
-
const allDeps = [...sources, ...weights];
|
|
16929
|
-
const n = sources.length;
|
|
16930
|
-
const scoreFns = opts?.scoreFns;
|
|
16931
|
-
return derived(
|
|
16932
|
-
allDeps,
|
|
16933
|
-
(vals) => {
|
|
16934
|
-
const signals = vals.slice(0, n);
|
|
16935
|
-
const weightValues = vals.slice(n);
|
|
16936
|
-
const breakdown = [];
|
|
16937
|
-
let totalScore = 0;
|
|
16938
|
-
for (let i = 0; i < n; i++) {
|
|
16939
|
-
const sig = signals[i] ?? 0;
|
|
16940
|
-
const wt = weightValues[i] ?? 0;
|
|
16941
|
-
const rawScore = scoreFns?.[i] ? scoreFns[i](sig) : sig;
|
|
16942
|
-
const weighted = rawScore * wt;
|
|
16943
|
-
breakdown.push(weighted);
|
|
16944
|
-
totalScore += weighted;
|
|
16945
|
-
}
|
|
16946
|
-
return {
|
|
16947
|
-
value: signals,
|
|
16948
|
-
score: totalScore,
|
|
16949
|
-
breakdown
|
|
16950
|
-
};
|
|
17758
|
+
);
|
|
17759
|
+
const blockFlowNode = derived(
|
|
17760
|
+
[measuredBlocksNode, gapNode],
|
|
17761
|
+
([measured, gapVal]) => {
|
|
17762
|
+
return computeBlockFlow(measured, gapVal);
|
|
16951
17763
|
},
|
|
16952
17764
|
{
|
|
16953
|
-
|
|
16954
|
-
|
|
16955
|
-
|
|
17765
|
+
name: "block-flow",
|
|
17766
|
+
equals: (a, b) => {
|
|
17767
|
+
const fa = a;
|
|
17768
|
+
const fb = b;
|
|
17769
|
+
if (fa == null || fb == null) return fa === fb;
|
|
17770
|
+
if (fa.length !== fb.length) return false;
|
|
17771
|
+
for (let i = 0; i < fa.length; i++) {
|
|
17772
|
+
const pa = fa[i];
|
|
17773
|
+
const pb = fb[i];
|
|
17774
|
+
if (pa.x !== pb.x || pa.y !== pb.y || pa.width !== pb.width || pa.height !== pb.height)
|
|
17775
|
+
return false;
|
|
17776
|
+
}
|
|
17777
|
+
return true;
|
|
17778
|
+
}
|
|
16956
17779
|
}
|
|
16957
17780
|
);
|
|
17781
|
+
const totalHeightNode = derived(
|
|
17782
|
+
[blockFlowNode],
|
|
17783
|
+
([flow]) => computeTotalHeight(flow),
|
|
17784
|
+
{ name: "total-height" }
|
|
17785
|
+
);
|
|
17786
|
+
g.add("blocks", blocksNode);
|
|
17787
|
+
g.add("max-width", maxWidthNode);
|
|
17788
|
+
g.add("gap", gapNode);
|
|
17789
|
+
g.add("measured-blocks", measuredBlocksNode);
|
|
17790
|
+
g.add("block-flow", blockFlowNode);
|
|
17791
|
+
g.add("total-height", totalHeightNode);
|
|
17792
|
+
g.connect("blocks", "measured-blocks");
|
|
17793
|
+
g.connect("max-width", "measured-blocks");
|
|
17794
|
+
g.connect("measured-blocks", "block-flow");
|
|
17795
|
+
g.connect("gap", "block-flow");
|
|
17796
|
+
g.connect("block-flow", "total-height");
|
|
17797
|
+
return {
|
|
17798
|
+
graph: g,
|
|
17799
|
+
setBlocks: (blocks) => g.set("blocks", blocks),
|
|
17800
|
+
setMaxWidth: (mw) => g.set("max-width", Math.max(0, mw)),
|
|
17801
|
+
setGap: (gap) => g.set("gap", gap),
|
|
17802
|
+
measuredBlocks: measuredBlocksNode,
|
|
17803
|
+
blockFlow: blockFlowNode,
|
|
17804
|
+
totalHeight: totalHeightNode
|
|
17805
|
+
};
|
|
16958
17806
|
}
|
|
16959
17807
|
|
|
16960
17808
|
// src/index.ts
|
|
@@ -17076,6 +17924,7 @@ var version = "0.0.0";
|
|
|
17076
17924
|
gate,
|
|
17077
17925
|
globToRegExp,
|
|
17078
17926
|
graph,
|
|
17927
|
+
graphspec,
|
|
17079
17928
|
interval,
|
|
17080
17929
|
isBatching,
|
|
17081
17930
|
isKnownMessageType,
|