@graphrefly/graphrefly 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/compat/nestjs/index.js +1 -1
- package/dist/index.cjs +1135 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +405 -15
- package/dist/index.d.ts +405 -15
- package/dist/index.js +1141 -20
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -145,14 +145,6 @@ import {
|
|
|
145
145
|
import {
|
|
146
146
|
core_exports
|
|
147
147
|
} from "./chunk-E7OH6ZAZ.js";
|
|
148
|
-
import {
|
|
149
|
-
JsonCodec,
|
|
150
|
-
createDagCborCodec,
|
|
151
|
-
createDagCborZstdCodec,
|
|
152
|
-
graph_exports,
|
|
153
|
-
negotiateCodec,
|
|
154
|
-
replayWAL
|
|
155
|
-
} from "./chunk-A2AJJOSJ.js";
|
|
156
148
|
import {
|
|
157
149
|
cached,
|
|
158
150
|
createWatermarkController,
|
|
@@ -189,6 +181,14 @@ import {
|
|
|
189
181
|
import {
|
|
190
182
|
ResettableTimer
|
|
191
183
|
} from "./chunk-WZ2Z2CRV.js";
|
|
184
|
+
import {
|
|
185
|
+
JsonCodec,
|
|
186
|
+
createDagCborCodec,
|
|
187
|
+
createDagCborZstdCodec,
|
|
188
|
+
graph_exports,
|
|
189
|
+
negotiateCodec,
|
|
190
|
+
replayWAL
|
|
191
|
+
} from "./chunk-A2AJJOSJ.js";
|
|
192
192
|
import {
|
|
193
193
|
analyzeAndMeasure,
|
|
194
194
|
computeLineBreaks,
|
|
@@ -1045,10 +1045,12 @@ __export(patterns_exports, {
|
|
|
1045
1045
|
ai: () => ai_exports,
|
|
1046
1046
|
cqrs: () => cqrs_exports,
|
|
1047
1047
|
demoShell: () => demo_shell_exports,
|
|
1048
|
+
graphspec: () => graphspec_exports,
|
|
1048
1049
|
layout: () => reactive_layout_exports,
|
|
1049
1050
|
memory: () => memory_exports,
|
|
1050
1051
|
messaging: () => messaging_exports,
|
|
1051
|
-
orchestration: () => orchestration_exports
|
|
1052
|
+
orchestration: () => orchestration_exports,
|
|
1053
|
+
reduction: () => reduction_exports
|
|
1052
1054
|
});
|
|
1053
1055
|
|
|
1054
1056
|
// src/patterns/ai.ts
|
|
@@ -3023,6 +3025,1123 @@ function demoShell(opts) {
|
|
|
3023
3025
|
};
|
|
3024
3026
|
}
|
|
3025
3027
|
|
|
3028
|
+
// src/patterns/graphspec.ts
|
|
3029
|
+
var graphspec_exports = {};
|
|
3030
|
+
__export(graphspec_exports, {
|
|
3031
|
+
compileSpec: () => compileSpec,
|
|
3032
|
+
decompileGraph: () => decompileGraph,
|
|
3033
|
+
llmCompose: () => llmCompose,
|
|
3034
|
+
llmRefine: () => llmRefine,
|
|
3035
|
+
specDiff: () => specDiff,
|
|
3036
|
+
validateSpec: () => validateSpec
|
|
3037
|
+
});
|
|
3038
|
+
|
|
3039
|
+
// src/patterns/reduction.ts
|
|
3040
|
+
var reduction_exports = {};
|
|
3041
|
+
__export(reduction_exports, {
|
|
3042
|
+
budgetGate: () => budgetGate,
|
|
3043
|
+
feedback: () => feedback,
|
|
3044
|
+
funnel: () => funnel,
|
|
3045
|
+
scorer: () => scorer,
|
|
3046
|
+
stratify: () => stratify
|
|
3047
|
+
});
|
|
3048
|
+
function baseMeta(kind, meta) {
|
|
3049
|
+
return {
|
|
3050
|
+
reduction: true,
|
|
3051
|
+
reduction_type: kind,
|
|
3052
|
+
...meta ?? {}
|
|
3053
|
+
};
|
|
3054
|
+
}
|
|
3055
|
+
function stratify(name, source, rules, opts) {
|
|
3056
|
+
const g = new Graph(name, opts);
|
|
3057
|
+
g.add("source", source);
|
|
3058
|
+
const rulesNode = state(rules, {
|
|
3059
|
+
meta: baseMeta("stratify_rules")
|
|
3060
|
+
});
|
|
3061
|
+
g.add("rules", rulesNode);
|
|
3062
|
+
for (const rule of rules) {
|
|
3063
|
+
_addBranch(g, source, rulesNode, rule);
|
|
3064
|
+
}
|
|
3065
|
+
return g;
|
|
3066
|
+
}
|
|
3067
|
+
function _addBranch(graph, source, rulesNode, rule) {
|
|
3068
|
+
const branchName = `branch/${rule.name}`;
|
|
3069
|
+
let pendingDirty = false;
|
|
3070
|
+
const filterNode = node([source, rulesNode], () => void 0, {
|
|
3071
|
+
describeKind: "operator",
|
|
3072
|
+
meta: baseMeta("stratify_branch", { branch: rule.name }),
|
|
3073
|
+
onMessage(msg, depIndex, actions) {
|
|
3074
|
+
if (depIndex !== 0) return false;
|
|
3075
|
+
const t = msg[0];
|
|
3076
|
+
if (t === DATA) {
|
|
3077
|
+
const value = msg[1];
|
|
3078
|
+
const currentRules = rulesNode.get();
|
|
3079
|
+
const currentRule = currentRules.find((r) => r.name === rule.name);
|
|
3080
|
+
if (currentRule && currentRule.classify(value)) {
|
|
3081
|
+
pendingDirty = false;
|
|
3082
|
+
actions.emit(value);
|
|
3083
|
+
} else {
|
|
3084
|
+
if (pendingDirty) {
|
|
3085
|
+
pendingDirty = false;
|
|
3086
|
+
actions.down([[DIRTY], [RESOLVED]]);
|
|
3087
|
+
}
|
|
3088
|
+
}
|
|
3089
|
+
return true;
|
|
3090
|
+
}
|
|
3091
|
+
if (t === DIRTY) {
|
|
3092
|
+
pendingDirty = true;
|
|
3093
|
+
return true;
|
|
3094
|
+
}
|
|
3095
|
+
if (t === RESOLVED) {
|
|
3096
|
+
if (pendingDirty) {
|
|
3097
|
+
pendingDirty = false;
|
|
3098
|
+
actions.down([[DIRTY], [RESOLVED]]);
|
|
3099
|
+
} else {
|
|
3100
|
+
actions.down([[RESOLVED]]);
|
|
3101
|
+
}
|
|
3102
|
+
return true;
|
|
3103
|
+
}
|
|
3104
|
+
if (t === COMPLETE || t === ERROR) {
|
|
3105
|
+
pendingDirty = false;
|
|
3106
|
+
actions.down([msg]);
|
|
3107
|
+
return true;
|
|
3108
|
+
}
|
|
3109
|
+
return false;
|
|
3110
|
+
}
|
|
3111
|
+
});
|
|
3112
|
+
graph.add(branchName, filterNode);
|
|
3113
|
+
graph.connect("source", branchName);
|
|
3114
|
+
if (rule.ops) {
|
|
3115
|
+
const transformed = rule.ops(filterNode);
|
|
3116
|
+
const transformedName = `branch/${rule.name}/out`;
|
|
3117
|
+
graph.add(transformedName, transformed);
|
|
3118
|
+
graph.connect(branchName, transformedName);
|
|
3119
|
+
}
|
|
3120
|
+
}
|
|
3121
|
+
function funnel(name, sources, stages, opts) {
|
|
3122
|
+
if (sources.length === 0) throw new RangeError("funnel requires at least one source");
|
|
3123
|
+
if (stages.length === 0) throw new RangeError("funnel requires at least one stage");
|
|
3124
|
+
const g = new Graph(name, opts);
|
|
3125
|
+
const merged = sources.length === 1 ? sources[0] : merge(...sources);
|
|
3126
|
+
g.add("merged", merged);
|
|
3127
|
+
let prevOutputPath = "merged";
|
|
3128
|
+
for (let i = 0; i < stages.length; i++) {
|
|
3129
|
+
const stage = stages[i];
|
|
3130
|
+
const sub = new Graph(stage.name);
|
|
3131
|
+
stage.build(sub);
|
|
3132
|
+
try {
|
|
3133
|
+
sub.resolve("input");
|
|
3134
|
+
} catch {
|
|
3135
|
+
throw new Error(`funnel stage "${stage.name}" must define an "input" node`);
|
|
3136
|
+
}
|
|
3137
|
+
try {
|
|
3138
|
+
sub.resolve("output");
|
|
3139
|
+
} catch {
|
|
3140
|
+
throw new Error(`funnel stage "${stage.name}" must define an "output" node`);
|
|
3141
|
+
}
|
|
3142
|
+
g.mount(stage.name, sub);
|
|
3143
|
+
const prevNode = g.resolve(prevOutputPath);
|
|
3144
|
+
const stageInputPath = `${stage.name}::input`;
|
|
3145
|
+
const stageInput = g.resolve(stageInputPath);
|
|
3146
|
+
prevNode.subscribe((msgs) => {
|
|
3147
|
+
for (const msg of msgs) {
|
|
3148
|
+
const t = msg[0];
|
|
3149
|
+
if (t === DATA) {
|
|
3150
|
+
stageInput.down([[DATA, msg[1]]]);
|
|
3151
|
+
} else if (t === DIRTY) {
|
|
3152
|
+
stageInput.down([[DIRTY]]);
|
|
3153
|
+
} else if (t === RESOLVED) {
|
|
3154
|
+
stageInput.down([[RESOLVED]]);
|
|
3155
|
+
} else if (t === COMPLETE || t === ERROR) {
|
|
3156
|
+
stageInput.down([msg]);
|
|
3157
|
+
}
|
|
3158
|
+
}
|
|
3159
|
+
});
|
|
3160
|
+
prevOutputPath = `${stage.name}::output`;
|
|
3161
|
+
}
|
|
3162
|
+
return g;
|
|
3163
|
+
}
|
|
3164
|
+
function feedback(graph, condition, reentry, opts) {
|
|
3165
|
+
const maxIter = opts?.maxIterations ?? 10;
|
|
3166
|
+
const counterName = `__feedback_${condition}`;
|
|
3167
|
+
const counter = state(0, {
|
|
3168
|
+
meta: baseMeta("feedback_counter", {
|
|
3169
|
+
maxIterations: maxIter,
|
|
3170
|
+
feedbackFrom: condition,
|
|
3171
|
+
feedbackTo: reentry
|
|
3172
|
+
})
|
|
3173
|
+
});
|
|
3174
|
+
graph.add(counterName, counter);
|
|
3175
|
+
const condNode = graph.resolve(condition);
|
|
3176
|
+
const reentryNode = graph.resolve(reentry);
|
|
3177
|
+
let tornDown = false;
|
|
3178
|
+
let unsubCounter = null;
|
|
3179
|
+
const safeUnsub = () => {
|
|
3180
|
+
if (tornDown) return;
|
|
3181
|
+
tornDown = true;
|
|
3182
|
+
unsub();
|
|
3183
|
+
unsubCounter?.();
|
|
3184
|
+
};
|
|
3185
|
+
const unsub = condNode.subscribe((msgs) => {
|
|
3186
|
+
for (const msg of msgs) {
|
|
3187
|
+
if (msg[0] === DATA) {
|
|
3188
|
+
const currentCount = counter.get();
|
|
3189
|
+
if (currentCount >= maxIter) continue;
|
|
3190
|
+
const condValue = msg[1];
|
|
3191
|
+
if (condValue == null) continue;
|
|
3192
|
+
counter.down([[DATA, currentCount + 1]]);
|
|
3193
|
+
reentryNode.down([[DATA, condValue]]);
|
|
3194
|
+
} else if (msg[0] === COMPLETE || msg[0] === ERROR) {
|
|
3195
|
+
const terminal = msg[0] === ERROR && msg.length > 1 ? [ERROR, msg[1]] : [msg[0]];
|
|
3196
|
+
counter.down([terminal]);
|
|
3197
|
+
safeUnsub();
|
|
3198
|
+
}
|
|
3199
|
+
}
|
|
3200
|
+
});
|
|
3201
|
+
unsubCounter = counter.subscribe((msgs) => {
|
|
3202
|
+
for (const msg of msgs) {
|
|
3203
|
+
if (msg[0] === COMPLETE || msg[0] === ERROR) {
|
|
3204
|
+
safeUnsub();
|
|
3205
|
+
return;
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3208
|
+
});
|
|
3209
|
+
return graph;
|
|
3210
|
+
}
|
|
3211
|
+
function budgetGate(source, constraints, opts) {
|
|
3212
|
+
if (constraints.length === 0) throw new RangeError("budgetGate requires at least one constraint");
|
|
3213
|
+
const constraintNodes = constraints.map((c) => c.node);
|
|
3214
|
+
const allDeps = [source, ...constraintNodes];
|
|
3215
|
+
let buffer2 = [];
|
|
3216
|
+
let paused = false;
|
|
3217
|
+
let pendingResolved = false;
|
|
3218
|
+
const lockId = /* @__PURE__ */ Symbol("budget-gate");
|
|
3219
|
+
function checkBudget() {
|
|
3220
|
+
return constraints.every((c) => c.check(c.node.get()));
|
|
3221
|
+
}
|
|
3222
|
+
function flushBuffer(actions) {
|
|
3223
|
+
while (buffer2.length > 0 && checkBudget()) {
|
|
3224
|
+
const item = buffer2.shift();
|
|
3225
|
+
actions.emit(item);
|
|
3226
|
+
}
|
|
3227
|
+
if (buffer2.length === 0 && pendingResolved) {
|
|
3228
|
+
pendingResolved = false;
|
|
3229
|
+
actions.down([[RESOLVED]]);
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3232
|
+
return node(allDeps, () => void 0, {
|
|
3233
|
+
...opts,
|
|
3234
|
+
describeKind: "operator",
|
|
3235
|
+
meta: baseMeta("budget_gate", opts?.meta),
|
|
3236
|
+
onMessage(msg, depIndex, actions) {
|
|
3237
|
+
const t = msg[0];
|
|
3238
|
+
if (depIndex === 0) {
|
|
3239
|
+
if (t === DATA) {
|
|
3240
|
+
if (checkBudget() && buffer2.length === 0) {
|
|
3241
|
+
actions.emit(msg[1]);
|
|
3242
|
+
} else {
|
|
3243
|
+
buffer2.push(msg[1]);
|
|
3244
|
+
if (!paused) {
|
|
3245
|
+
paused = true;
|
|
3246
|
+
actions.up([[PAUSE, lockId]]);
|
|
3247
|
+
}
|
|
3248
|
+
}
|
|
3249
|
+
return true;
|
|
3250
|
+
}
|
|
3251
|
+
if (t === DIRTY) {
|
|
3252
|
+
actions.down([[DIRTY]]);
|
|
3253
|
+
return true;
|
|
3254
|
+
}
|
|
3255
|
+
if (t === RESOLVED) {
|
|
3256
|
+
if (buffer2.length === 0) {
|
|
3257
|
+
actions.down([[RESOLVED]]);
|
|
3258
|
+
} else {
|
|
3259
|
+
pendingResolved = true;
|
|
3260
|
+
}
|
|
3261
|
+
return true;
|
|
3262
|
+
}
|
|
3263
|
+
if (t === COMPLETE || t === ERROR) {
|
|
3264
|
+
for (const item of buffer2) {
|
|
3265
|
+
actions.emit(item);
|
|
3266
|
+
}
|
|
3267
|
+
buffer2 = [];
|
|
3268
|
+
pendingResolved = false;
|
|
3269
|
+
if (paused) {
|
|
3270
|
+
paused = false;
|
|
3271
|
+
actions.up([[RESUME, lockId]]);
|
|
3272
|
+
}
|
|
3273
|
+
actions.down([msg]);
|
|
3274
|
+
return true;
|
|
3275
|
+
}
|
|
3276
|
+
return false;
|
|
3277
|
+
}
|
|
3278
|
+
if (t === DATA || t === RESOLVED) {
|
|
3279
|
+
if (checkBudget() && buffer2.length > 0) {
|
|
3280
|
+
flushBuffer(actions);
|
|
3281
|
+
if (buffer2.length === 0 && paused) {
|
|
3282
|
+
paused = false;
|
|
3283
|
+
actions.up([[RESUME, lockId]]);
|
|
3284
|
+
}
|
|
3285
|
+
} else if (!checkBudget() && !paused && buffer2.length > 0) {
|
|
3286
|
+
paused = true;
|
|
3287
|
+
actions.up([[PAUSE, lockId]]);
|
|
3288
|
+
}
|
|
3289
|
+
return true;
|
|
3290
|
+
}
|
|
3291
|
+
if (t === DIRTY) {
|
|
3292
|
+
return true;
|
|
3293
|
+
}
|
|
3294
|
+
if (t === ERROR) {
|
|
3295
|
+
actions.down([msg]);
|
|
3296
|
+
return true;
|
|
3297
|
+
}
|
|
3298
|
+
if (t === COMPLETE) {
|
|
3299
|
+
return true;
|
|
3300
|
+
}
|
|
3301
|
+
return false;
|
|
3302
|
+
}
|
|
3303
|
+
});
|
|
3304
|
+
}
|
|
3305
|
+
function scorer(sources, weights, opts) {
|
|
3306
|
+
if (sources.length === 0) throw new RangeError("scorer requires at least one source");
|
|
3307
|
+
if (sources.length !== weights.length) {
|
|
3308
|
+
throw new RangeError("scorer requires the same number of sources and weights");
|
|
3309
|
+
}
|
|
3310
|
+
const allDeps = [...sources, ...weights];
|
|
3311
|
+
const n = sources.length;
|
|
3312
|
+
const scoreFns = opts?.scoreFns;
|
|
3313
|
+
return derived(
|
|
3314
|
+
allDeps,
|
|
3315
|
+
(vals) => {
|
|
3316
|
+
const signals = vals.slice(0, n);
|
|
3317
|
+
const weightValues = vals.slice(n);
|
|
3318
|
+
const breakdown = [];
|
|
3319
|
+
let totalScore = 0;
|
|
3320
|
+
for (let i = 0; i < n; i++) {
|
|
3321
|
+
const sig = signals[i] ?? 0;
|
|
3322
|
+
const wt = weightValues[i] ?? 0;
|
|
3323
|
+
const rawScore = scoreFns?.[i] ? scoreFns[i](sig) : sig;
|
|
3324
|
+
const weighted = rawScore * wt;
|
|
3325
|
+
breakdown.push(weighted);
|
|
3326
|
+
totalScore += weighted;
|
|
3327
|
+
}
|
|
3328
|
+
return {
|
|
3329
|
+
value: signals,
|
|
3330
|
+
score: totalScore,
|
|
3331
|
+
breakdown
|
|
3332
|
+
};
|
|
3333
|
+
},
|
|
3334
|
+
{
|
|
3335
|
+
...opts,
|
|
3336
|
+
describeKind: "derived",
|
|
3337
|
+
meta: baseMeta("scorer", opts?.meta)
|
|
3338
|
+
}
|
|
3339
|
+
);
|
|
3340
|
+
}
|
|
3341
|
+
|
|
3342
|
+
// src/patterns/graphspec.ts
|
|
3343
|
+
var VALID_NODE_TYPES2 = /* @__PURE__ */ new Set([
|
|
3344
|
+
"state",
|
|
3345
|
+
"producer",
|
|
3346
|
+
"derived",
|
|
3347
|
+
"effect",
|
|
3348
|
+
"operator",
|
|
3349
|
+
"template"
|
|
3350
|
+
]);
|
|
3351
|
+
var INNER_NODE_TYPES = /* @__PURE__ */ new Set(["state", "producer", "derived", "effect", "operator"]);
|
|
3352
|
+
function validateSpec(spec) {
|
|
3353
|
+
const errors = [];
|
|
3354
|
+
if (spec == null || typeof spec !== "object") {
|
|
3355
|
+
return { valid: false, errors: ["GraphSpec must be a non-null object"] };
|
|
3356
|
+
}
|
|
3357
|
+
const s = spec;
|
|
3358
|
+
if (typeof s.name !== "string" || s.name.length === 0) {
|
|
3359
|
+
errors.push("Missing or empty 'name' field");
|
|
3360
|
+
}
|
|
3361
|
+
if (s.nodes == null || typeof s.nodes !== "object" || Array.isArray(s.nodes)) {
|
|
3362
|
+
errors.push("Missing or invalid 'nodes' field (must be an object)");
|
|
3363
|
+
return { valid: false, errors };
|
|
3364
|
+
}
|
|
3365
|
+
const nodeNames = new Set(Object.keys(s.nodes));
|
|
3366
|
+
const nodeTypes = /* @__PURE__ */ new Map();
|
|
3367
|
+
const templateDefs = /* @__PURE__ */ new Map();
|
|
3368
|
+
if (s.templates != null && typeof s.templates === "object" && !Array.isArray(s.templates)) {
|
|
3369
|
+
for (const [tName, tRaw] of Object.entries(s.templates)) {
|
|
3370
|
+
if (tRaw != null && typeof tRaw === "object") {
|
|
3371
|
+
const t = tRaw;
|
|
3372
|
+
templateDefs.set(tName, {
|
|
3373
|
+
params: Array.isArray(t.params) ? t.params : []
|
|
3374
|
+
});
|
|
3375
|
+
}
|
|
3376
|
+
}
|
|
3377
|
+
}
|
|
3378
|
+
if (s.templates != null) {
|
|
3379
|
+
if (typeof s.templates !== "object" || Array.isArray(s.templates)) {
|
|
3380
|
+
errors.push("'templates' must be an object");
|
|
3381
|
+
} else {
|
|
3382
|
+
for (const [tName, tRaw] of Object.entries(s.templates)) {
|
|
3383
|
+
if (tRaw == null || typeof tRaw !== "object") {
|
|
3384
|
+
errors.push(`Template "${tName}": must be an object`);
|
|
3385
|
+
continue;
|
|
3386
|
+
}
|
|
3387
|
+
const t = tRaw;
|
|
3388
|
+
if (!Array.isArray(t.params)) {
|
|
3389
|
+
errors.push(`Template "${tName}": missing 'params' array`);
|
|
3390
|
+
}
|
|
3391
|
+
if (t.nodes == null || typeof t.nodes !== "object" || Array.isArray(t.nodes)) {
|
|
3392
|
+
errors.push(`Template "${tName}": missing or invalid 'nodes' object`);
|
|
3393
|
+
} else {
|
|
3394
|
+
const paramSet = new Set(Array.isArray(t.params) ? t.params : []);
|
|
3395
|
+
const innerNames = new Set(Object.keys(t.nodes));
|
|
3396
|
+
for (const [nName, nRaw] of Object.entries(t.nodes)) {
|
|
3397
|
+
if (nRaw == null || typeof nRaw !== "object") {
|
|
3398
|
+
errors.push(`Template "${tName}" node "${nName}": must be an object`);
|
|
3399
|
+
continue;
|
|
3400
|
+
}
|
|
3401
|
+
const n = nRaw;
|
|
3402
|
+
if (typeof n.type !== "string" || !INNER_NODE_TYPES.has(n.type)) {
|
|
3403
|
+
errors.push(`Template "${tName}" node "${nName}": invalid type`);
|
|
3404
|
+
}
|
|
3405
|
+
if (Array.isArray(n.deps)) {
|
|
3406
|
+
for (const dep of n.deps) {
|
|
3407
|
+
if (!innerNames.has(dep) && !paramSet.has(dep)) {
|
|
3408
|
+
errors.push(
|
|
3409
|
+
`Template "${tName}" node "${nName}": dep "${dep}" is not an inner node or param`
|
|
3410
|
+
);
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
3414
|
+
}
|
|
3415
|
+
if (typeof t.output !== "string") {
|
|
3416
|
+
errors.push(`Template "${tName}": missing 'output' string`);
|
|
3417
|
+
} else if (!t.nodes[t.output]) {
|
|
3418
|
+
errors.push(`Template "${tName}": output "${t.output}" is not a declared node`);
|
|
3419
|
+
}
|
|
3420
|
+
}
|
|
3421
|
+
}
|
|
3422
|
+
}
|
|
3423
|
+
}
|
|
3424
|
+
for (const [name, raw] of Object.entries(s.nodes)) {
|
|
3425
|
+
if (raw == null || typeof raw !== "object") {
|
|
3426
|
+
errors.push(`Node "${name}": must be an object`);
|
|
3427
|
+
continue;
|
|
3428
|
+
}
|
|
3429
|
+
const n = raw;
|
|
3430
|
+
if (typeof n.type !== "string" || !VALID_NODE_TYPES2.has(n.type)) {
|
|
3431
|
+
errors.push(
|
|
3432
|
+
`Node "${name}": invalid type "${String(n.type)}" (expected: ${[...VALID_NODE_TYPES2].join(", ")})`
|
|
3433
|
+
);
|
|
3434
|
+
continue;
|
|
3435
|
+
}
|
|
3436
|
+
nodeTypes.set(name, n.type);
|
|
3437
|
+
if (n.type === "template") {
|
|
3438
|
+
if (typeof n.template !== "string" || !templateDefs.has(n.template)) {
|
|
3439
|
+
errors.push(`Node "${name}": template "${String(n.template)}" not found in templates`);
|
|
3440
|
+
} else {
|
|
3441
|
+
if (n.bind == null || typeof n.bind !== "object" || Array.isArray(n.bind)) {
|
|
3442
|
+
errors.push(`Node "${name}": template ref requires 'bind' object`);
|
|
3443
|
+
} else {
|
|
3444
|
+
const tmpl = templateDefs.get(n.template);
|
|
3445
|
+
const bind = n.bind;
|
|
3446
|
+
for (const param of tmpl.params) {
|
|
3447
|
+
if (!(param in bind)) {
|
|
3448
|
+
errors.push(
|
|
3449
|
+
`Node "${name}": template param "${param}" is not bound (template "${n.template}")`
|
|
3450
|
+
);
|
|
3451
|
+
}
|
|
3452
|
+
}
|
|
3453
|
+
for (const [, target] of Object.entries(bind)) {
|
|
3454
|
+
if (typeof target === "string" && !nodeNames.has(target)) {
|
|
3455
|
+
errors.push(
|
|
3456
|
+
`Node "${name}": bind target "${target}" does not reference an existing node`
|
|
3457
|
+
);
|
|
3458
|
+
}
|
|
3459
|
+
}
|
|
3460
|
+
}
|
|
3461
|
+
}
|
|
3462
|
+
} else {
|
|
3463
|
+
if (Array.isArray(n.deps)) {
|
|
3464
|
+
for (const dep of n.deps) {
|
|
3465
|
+
if (dep === name) {
|
|
3466
|
+
errors.push(`Node "${name}": self-referencing dep`);
|
|
3467
|
+
} else if (!nodeNames.has(dep)) {
|
|
3468
|
+
errors.push(`Node "${name}": dep "${dep}" does not reference an existing node`);
|
|
3469
|
+
}
|
|
3470
|
+
}
|
|
3471
|
+
}
|
|
3472
|
+
if ((n.type === "derived" || n.type === "effect" || n.type === "operator") && !Array.isArray(n.deps)) {
|
|
3473
|
+
errors.push(`Node "${name}": ${n.type} node should have a 'deps' array`);
|
|
3474
|
+
}
|
|
3475
|
+
}
|
|
3476
|
+
}
|
|
3477
|
+
if (s.feedback != null) {
|
|
3478
|
+
if (!Array.isArray(s.feedback)) {
|
|
3479
|
+
errors.push("'feedback' must be an array");
|
|
3480
|
+
} else {
|
|
3481
|
+
for (let i = 0; i < s.feedback.length; i++) {
|
|
3482
|
+
const edge = s.feedback[i];
|
|
3483
|
+
if (edge == null || typeof edge !== "object") {
|
|
3484
|
+
errors.push(`Feedback [${i}]: must be an object`);
|
|
3485
|
+
continue;
|
|
3486
|
+
}
|
|
3487
|
+
const e = edge;
|
|
3488
|
+
if (typeof e.from !== "string" || !nodeNames.has(e.from)) {
|
|
3489
|
+
errors.push(
|
|
3490
|
+
`Feedback [${i}]: 'from' "${String(e.from)}" does not reference an existing node`
|
|
3491
|
+
);
|
|
3492
|
+
}
|
|
3493
|
+
if (typeof e.from === "string" && e.from === e.to) {
|
|
3494
|
+
errors.push(`Feedback [${i}]: 'from' and 'to' must be different nodes`);
|
|
3495
|
+
}
|
|
3496
|
+
if (typeof e.to !== "string" || !nodeNames.has(e.to)) {
|
|
3497
|
+
errors.push(
|
|
3498
|
+
`Feedback [${i}]: 'to' "${String(e.to)}" does not reference an existing node`
|
|
3499
|
+
);
|
|
3500
|
+
} else if (typeof e.to === "string" && nodeTypes.get(e.to) !== "state") {
|
|
3501
|
+
errors.push(
|
|
3502
|
+
`Feedback [${i}]: 'to' node "${e.to}" must be a state node (got "${nodeTypes.get(e.to) ?? "unknown"}")`
|
|
3503
|
+
);
|
|
3504
|
+
}
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
3507
|
+
}
|
|
3508
|
+
return { valid: errors.length === 0, errors };
|
|
3509
|
+
}
|
|
3510
|
+
function compileSpec(spec, opts) {
|
|
3511
|
+
const validation = validateSpec(spec);
|
|
3512
|
+
if (!validation.valid) {
|
|
3513
|
+
throw new Error(`compileSpec: invalid GraphSpec:
|
|
3514
|
+
${validation.errors.join("\n")}`);
|
|
3515
|
+
}
|
|
3516
|
+
const catalog = opts?.catalog ?? {};
|
|
3517
|
+
const g = new Graph(spec.name);
|
|
3518
|
+
const templates = spec.templates ?? {};
|
|
3519
|
+
const created = /* @__PURE__ */ new Map();
|
|
3520
|
+
const deferred = [];
|
|
3521
|
+
for (const [name, raw] of Object.entries(spec.nodes)) {
|
|
3522
|
+
if (raw.type === "template") continue;
|
|
3523
|
+
const n = raw;
|
|
3524
|
+
if (n.type === "state") {
|
|
3525
|
+
const nd = state(n.initial, {
|
|
3526
|
+
name,
|
|
3527
|
+
meta: n.meta ? { ...n.meta } : void 0
|
|
3528
|
+
});
|
|
3529
|
+
g.add(name, nd);
|
|
3530
|
+
created.set(name, nd);
|
|
3531
|
+
} else if (n.type === "producer") {
|
|
3532
|
+
const sourceFactory = n.source ? catalog.sources?.[n.source] : void 0;
|
|
3533
|
+
const fnFactory = n.fn ? catalog.fns?.[n.fn] : void 0;
|
|
3534
|
+
if (sourceFactory) {
|
|
3535
|
+
const nd = sourceFactory(n.config ?? {});
|
|
3536
|
+
g.add(name, nd);
|
|
3537
|
+
created.set(name, nd);
|
|
3538
|
+
} else if (fnFactory) {
|
|
3539
|
+
const nd = fnFactory([], n.config ?? {});
|
|
3540
|
+
g.add(name, nd);
|
|
3541
|
+
created.set(name, nd);
|
|
3542
|
+
} else {
|
|
3543
|
+
const nd = producer(() => {
|
|
3544
|
+
}, {
|
|
3545
|
+
name,
|
|
3546
|
+
meta: { ...n.meta, _specFn: n.fn, _specSource: n.source }
|
|
3547
|
+
});
|
|
3548
|
+
g.add(name, nd);
|
|
3549
|
+
created.set(name, nd);
|
|
3550
|
+
}
|
|
3551
|
+
} else {
|
|
3552
|
+
deferred.push([name, n]);
|
|
3553
|
+
}
|
|
3554
|
+
}
|
|
3555
|
+
let progressed = true;
|
|
3556
|
+
const pending = new Map(deferred);
|
|
3557
|
+
while (pending.size > 0 && progressed) {
|
|
3558
|
+
progressed = false;
|
|
3559
|
+
for (const [name, n] of [...pending.entries()]) {
|
|
3560
|
+
const deps = n.deps ?? [];
|
|
3561
|
+
if (!deps.every((dep) => created.has(dep))) continue;
|
|
3562
|
+
const resolvedDeps = deps.map((dep) => created.get(dep));
|
|
3563
|
+
const fnFactory = n.fn ? catalog.fns?.[n.fn] : void 0;
|
|
3564
|
+
let nd;
|
|
3565
|
+
if (fnFactory) {
|
|
3566
|
+
nd = fnFactory(resolvedDeps, n.config ?? {});
|
|
3567
|
+
} else if (n.type === "effect") {
|
|
3568
|
+
nd = effect(resolvedDeps, () => {
|
|
3569
|
+
});
|
|
3570
|
+
} else {
|
|
3571
|
+
nd = derived(resolvedDeps, (vals) => vals[0]);
|
|
3572
|
+
}
|
|
3573
|
+
g.add(name, nd);
|
|
3574
|
+
created.set(name, nd);
|
|
3575
|
+
pending.delete(name);
|
|
3576
|
+
progressed = true;
|
|
3577
|
+
}
|
|
3578
|
+
}
|
|
3579
|
+
if (pending.size > 0) {
|
|
3580
|
+
const unresolved = [...pending.keys()].sort().join(", ");
|
|
3581
|
+
throw new Error(`compileSpec: unresolvable deps for nodes: ${unresolved}`);
|
|
3582
|
+
}
|
|
3583
|
+
for (const [name, raw] of Object.entries(spec.nodes)) {
|
|
3584
|
+
if (raw.type !== "template") continue;
|
|
3585
|
+
const ref = raw;
|
|
3586
|
+
const tmpl = templates[ref.template];
|
|
3587
|
+
const sub = new Graph(name);
|
|
3588
|
+
const subCreated = /* @__PURE__ */ new Map();
|
|
3589
|
+
const subDeferred = [];
|
|
3590
|
+
for (const [nName, nSpec] of Object.entries(tmpl.nodes)) {
|
|
3591
|
+
const resolvedDeps = (nSpec.deps ?? []).map((dep) => {
|
|
3592
|
+
if (dep.startsWith("$") && ref.bind[dep]) {
|
|
3593
|
+
return ref.bind[dep];
|
|
3594
|
+
}
|
|
3595
|
+
return dep;
|
|
3596
|
+
});
|
|
3597
|
+
const specWithResolvedDeps = { ...nSpec, deps: resolvedDeps };
|
|
3598
|
+
if (nSpec.type === "state") {
|
|
3599
|
+
const nd = state(nSpec.initial, {
|
|
3600
|
+
name: nName,
|
|
3601
|
+
meta: nSpec.meta ? { ...nSpec.meta } : void 0
|
|
3602
|
+
});
|
|
3603
|
+
sub.add(nName, nd);
|
|
3604
|
+
subCreated.set(nName, nd);
|
|
3605
|
+
} else if (nSpec.type === "producer") {
|
|
3606
|
+
const sourceFactory = nSpec.source ? catalog.sources?.[nSpec.source] : void 0;
|
|
3607
|
+
const fnFactory = nSpec.fn ? catalog.fns?.[nSpec.fn] : void 0;
|
|
3608
|
+
if (sourceFactory) {
|
|
3609
|
+
const nd = sourceFactory(nSpec.config ?? {});
|
|
3610
|
+
sub.add(nName, nd);
|
|
3611
|
+
subCreated.set(nName, nd);
|
|
3612
|
+
} else if (fnFactory) {
|
|
3613
|
+
const nd = fnFactory([], nSpec.config ?? {});
|
|
3614
|
+
sub.add(nName, nd);
|
|
3615
|
+
subCreated.set(nName, nd);
|
|
3616
|
+
} else {
|
|
3617
|
+
const nd = producer(() => {
|
|
3618
|
+
}, {
|
|
3619
|
+
name: nName,
|
|
3620
|
+
meta: { ...nSpec.meta, _specFn: nSpec.fn, _specSource: nSpec.source }
|
|
3621
|
+
});
|
|
3622
|
+
sub.add(nName, nd);
|
|
3623
|
+
subCreated.set(nName, nd);
|
|
3624
|
+
}
|
|
3625
|
+
} else {
|
|
3626
|
+
subDeferred.push([nName, specWithResolvedDeps]);
|
|
3627
|
+
}
|
|
3628
|
+
}
|
|
3629
|
+
let subProgressed = true;
|
|
3630
|
+
const subPending = new Map(subDeferred);
|
|
3631
|
+
while (subPending.size > 0 && subProgressed) {
|
|
3632
|
+
subProgressed = false;
|
|
3633
|
+
for (const [nName, nSpec] of [...subPending.entries()]) {
|
|
3634
|
+
const deps = nSpec.deps ?? [];
|
|
3635
|
+
const allReady = deps.every((dep) => subCreated.has(dep) || created.has(dep));
|
|
3636
|
+
if (!allReady) continue;
|
|
3637
|
+
const resolvedDeps = deps.map((dep) => subCreated.get(dep) ?? created.get(dep));
|
|
3638
|
+
const fnFactory = nSpec.fn ? catalog.fns?.[nSpec.fn] : void 0;
|
|
3639
|
+
let nd;
|
|
3640
|
+
if (fnFactory) {
|
|
3641
|
+
nd = fnFactory(resolvedDeps, nSpec.config ?? {});
|
|
3642
|
+
} else if (nSpec.type === "effect") {
|
|
3643
|
+
nd = effect(resolvedDeps, () => {
|
|
3644
|
+
});
|
|
3645
|
+
} else {
|
|
3646
|
+
nd = derived(resolvedDeps, (vals) => vals[0]);
|
|
3647
|
+
}
|
|
3648
|
+
sub.add(nName, nd);
|
|
3649
|
+
subCreated.set(nName, nd);
|
|
3650
|
+
subPending.delete(nName);
|
|
3651
|
+
subProgressed = true;
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3654
|
+
if (subPending.size > 0) {
|
|
3655
|
+
const unresolved = [...subPending.keys()].sort().join(", ");
|
|
3656
|
+
throw new Error(
|
|
3657
|
+
`compileSpec: template "${ref.template}" has unresolvable deps: ${unresolved}`
|
|
3658
|
+
);
|
|
3659
|
+
}
|
|
3660
|
+
g.mount(name, sub);
|
|
3661
|
+
const outputPath = `${name}::${tmpl.output}`;
|
|
3662
|
+
created.set(name, g.resolve(outputPath));
|
|
3663
|
+
try {
|
|
3664
|
+
const outputNode = g.resolve(outputPath);
|
|
3665
|
+
outputNode.meta._templateName?.down([[DATA, ref.template]]);
|
|
3666
|
+
outputNode.meta._templateBind?.down([[DATA, ref.bind]]);
|
|
3667
|
+
} catch {
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
3670
|
+
for (const [name, raw] of Object.entries(spec.nodes)) {
|
|
3671
|
+
if (raw.type === "template") continue;
|
|
3672
|
+
const n = raw;
|
|
3673
|
+
for (const dep of n.deps ?? []) {
|
|
3674
|
+
try {
|
|
3675
|
+
g.connect(dep, name);
|
|
3676
|
+
} catch (err) {
|
|
3677
|
+
const msg = err instanceof Error ? err.message : "";
|
|
3678
|
+
if (!msg.includes("constructor deps") && !msg.includes("already")) {
|
|
3679
|
+
throw err;
|
|
3680
|
+
}
|
|
3681
|
+
}
|
|
3682
|
+
}
|
|
3683
|
+
}
|
|
3684
|
+
for (const fb of spec.feedback ?? []) {
|
|
3685
|
+
feedback(g, fb.from, fb.to, {
|
|
3686
|
+
maxIterations: fb.maxIterations
|
|
3687
|
+
});
|
|
3688
|
+
}
|
|
3689
|
+
return g;
|
|
3690
|
+
}
|
|
3691
|
+
var INTERNAL_META_KEYS = /* @__PURE__ */ new Set([
|
|
3692
|
+
"reduction",
|
|
3693
|
+
"reduction_type",
|
|
3694
|
+
"_specFn",
|
|
3695
|
+
"_specSource",
|
|
3696
|
+
"_templateName",
|
|
3697
|
+
"_templateBind",
|
|
3698
|
+
"feedbackFrom",
|
|
3699
|
+
"feedbackTo"
|
|
3700
|
+
]);
|
|
3701
|
+
function decompileGraph(graph) {
|
|
3702
|
+
const desc = graph.describe({ detail: "standard" });
|
|
3703
|
+
const nodes = {};
|
|
3704
|
+
const feedbackEdges = [];
|
|
3705
|
+
const metaSegment = `::${GRAPH_META_SEGMENT}::`;
|
|
3706
|
+
const feedbackCounterPattern = /^__feedback_(.+)$/;
|
|
3707
|
+
const feedbackConditions = /* @__PURE__ */ new Set();
|
|
3708
|
+
for (const path of Object.keys(desc.nodes)) {
|
|
3709
|
+
if (path.includes(metaSegment)) continue;
|
|
3710
|
+
const match = feedbackCounterPattern.exec(path);
|
|
3711
|
+
if (match) {
|
|
3712
|
+
feedbackConditions.add(match[1]);
|
|
3713
|
+
const meta = desc.nodes[path]?.meta;
|
|
3714
|
+
if (meta?.feedbackFrom && meta?.feedbackTo) {
|
|
3715
|
+
feedbackEdges.push({
|
|
3716
|
+
from: meta.feedbackFrom,
|
|
3717
|
+
to: meta.feedbackTo,
|
|
3718
|
+
...meta.maxIterations ? { maxIterations: meta.maxIterations } : {}
|
|
3719
|
+
});
|
|
3720
|
+
}
|
|
3721
|
+
}
|
|
3722
|
+
}
|
|
3723
|
+
for (const [path, nodeDesc] of Object.entries(desc.nodes)) {
|
|
3724
|
+
if (path.includes(metaSegment)) continue;
|
|
3725
|
+
if (feedbackCounterPattern.test(path)) continue;
|
|
3726
|
+
if (path.includes("::")) continue;
|
|
3727
|
+
const specNode = {
|
|
3728
|
+
type: nodeDesc.type
|
|
3729
|
+
};
|
|
3730
|
+
if (nodeDesc.deps.length > 0) {
|
|
3731
|
+
specNode.deps = nodeDesc.deps.filter((d) => !d.includes("::"));
|
|
3732
|
+
}
|
|
3733
|
+
if (nodeDesc.type === "state" && nodeDesc.value !== void 0) {
|
|
3734
|
+
specNode.initial = nodeDesc.value;
|
|
3735
|
+
}
|
|
3736
|
+
if (nodeDesc.meta && Object.keys(nodeDesc.meta).length > 0) {
|
|
3737
|
+
const meta = {};
|
|
3738
|
+
for (const [k, v] of Object.entries(nodeDesc.meta)) {
|
|
3739
|
+
if (!INTERNAL_META_KEYS.has(k)) meta[k] = v;
|
|
3740
|
+
}
|
|
3741
|
+
if (Object.keys(meta).length > 0) {
|
|
3742
|
+
specNode.meta = meta;
|
|
3743
|
+
}
|
|
3744
|
+
}
|
|
3745
|
+
nodes[path] = specNode;
|
|
3746
|
+
}
|
|
3747
|
+
const templates = {};
|
|
3748
|
+
const templateRefs = {};
|
|
3749
|
+
const metaDetectedSubgraphs = /* @__PURE__ */ new Set();
|
|
3750
|
+
for (const subName of desc.subgraphs) {
|
|
3751
|
+
const prefix = `${subName}::`;
|
|
3752
|
+
for (const [path, nodeDesc] of Object.entries(desc.nodes)) {
|
|
3753
|
+
if (!path.startsWith(prefix)) continue;
|
|
3754
|
+
if (path.includes(metaSegment)) continue;
|
|
3755
|
+
const meta = nodeDesc.meta;
|
|
3756
|
+
if (meta?._templateName && meta?._templateBind) {
|
|
3757
|
+
const templateName = meta._templateName;
|
|
3758
|
+
const bind = meta._templateBind;
|
|
3759
|
+
if (!templates[templateName]) {
|
|
3760
|
+
const tmplNodes = {};
|
|
3761
|
+
const tmplInnerNames = /* @__PURE__ */ new Set();
|
|
3762
|
+
const tmplPrefix = `${subName}::`;
|
|
3763
|
+
for (const [p, nd] of Object.entries(desc.nodes)) {
|
|
3764
|
+
if (!p.startsWith(tmplPrefix) || p.includes(metaSegment)) continue;
|
|
3765
|
+
const localName = p.slice(tmplPrefix.length);
|
|
3766
|
+
if (localName.includes("::")) continue;
|
|
3767
|
+
tmplInnerNames.add(localName);
|
|
3768
|
+
tmplNodes[localName] = {
|
|
3769
|
+
type: nd.type,
|
|
3770
|
+
...nd.deps.length > 0 ? {
|
|
3771
|
+
deps: nd.deps.map(
|
|
3772
|
+
(d) => d.startsWith(tmplPrefix) ? d.slice(tmplPrefix.length) : d
|
|
3773
|
+
)
|
|
3774
|
+
} : {}
|
|
3775
|
+
};
|
|
3776
|
+
}
|
|
3777
|
+
const tmplParams = [];
|
|
3778
|
+
const tmplParamMap = /* @__PURE__ */ new Map();
|
|
3779
|
+
for (const n of Object.values(tmplNodes)) {
|
|
3780
|
+
for (const dep of n.deps ?? []) {
|
|
3781
|
+
if (!tmplInnerNames.has(dep) && !tmplParamMap.has(dep)) {
|
|
3782
|
+
const param = `$${dep}`;
|
|
3783
|
+
tmplParams.push(param);
|
|
3784
|
+
tmplParamMap.set(dep, param);
|
|
3785
|
+
}
|
|
3786
|
+
}
|
|
3787
|
+
}
|
|
3788
|
+
for (const n of Object.values(tmplNodes)) {
|
|
3789
|
+
if (n.deps) n.deps = n.deps.map((d) => tmplParamMap.get(d) ?? d);
|
|
3790
|
+
}
|
|
3791
|
+
const depended = /* @__PURE__ */ new Set();
|
|
3792
|
+
for (const n of Object.values(tmplNodes)) {
|
|
3793
|
+
for (const dep of n.deps ?? []) {
|
|
3794
|
+
if (tmplInnerNames.has(dep)) depended.add(dep);
|
|
3795
|
+
}
|
|
3796
|
+
}
|
|
3797
|
+
const outputCandidates = [...tmplInnerNames].filter((n) => !depended.has(n));
|
|
3798
|
+
const tmplOutput = outputCandidates[0] ?? [...tmplInnerNames].pop();
|
|
3799
|
+
templates[templateName] = { params: tmplParams, nodes: tmplNodes, output: tmplOutput };
|
|
3800
|
+
}
|
|
3801
|
+
delete nodes[subName];
|
|
3802
|
+
templateRefs[subName] = { type: "template", template: templateName, bind };
|
|
3803
|
+
metaDetectedSubgraphs.add(subName);
|
|
3804
|
+
break;
|
|
3805
|
+
}
|
|
3806
|
+
}
|
|
3807
|
+
}
|
|
3808
|
+
const structureMap = /* @__PURE__ */ new Map();
|
|
3809
|
+
for (const subName of desc.subgraphs) {
|
|
3810
|
+
if (metaDetectedSubgraphs.has(subName)) continue;
|
|
3811
|
+
const subNodes = {};
|
|
3812
|
+
const prefix = `${subName}::`;
|
|
3813
|
+
for (const [path, nodeDesc] of Object.entries(desc.nodes)) {
|
|
3814
|
+
if (path.includes(metaSegment)) continue;
|
|
3815
|
+
if (!path.startsWith(prefix)) continue;
|
|
3816
|
+
const localName = path.slice(prefix.length);
|
|
3817
|
+
if (localName.includes("::")) continue;
|
|
3818
|
+
subNodes[localName] = {
|
|
3819
|
+
type: nodeDesc.type,
|
|
3820
|
+
...nodeDesc.deps.length > 0 ? {
|
|
3821
|
+
deps: nodeDesc.deps.map((d) => d.startsWith(prefix) ? d.slice(prefix.length) : d)
|
|
3822
|
+
} : {}
|
|
3823
|
+
};
|
|
3824
|
+
}
|
|
3825
|
+
const fingerprint = JSON.stringify(
|
|
3826
|
+
Object.fromEntries(
|
|
3827
|
+
Object.entries(subNodes).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => [k, { type: v.type, deps: v.deps ?? [] }])
|
|
3828
|
+
)
|
|
3829
|
+
);
|
|
3830
|
+
if (!structureMap.has(fingerprint)) {
|
|
3831
|
+
structureMap.set(fingerprint, []);
|
|
3832
|
+
}
|
|
3833
|
+
structureMap.get(fingerprint).push({ name: subName, nodes: subNodes });
|
|
3834
|
+
}
|
|
3835
|
+
for (const [, group] of structureMap) {
|
|
3836
|
+
if (group.length < 2) continue;
|
|
3837
|
+
const templateName = `${group[0].name}_template`;
|
|
3838
|
+
const refNodes = group[0].nodes;
|
|
3839
|
+
const innerNames = new Set(Object.keys(refNodes));
|
|
3840
|
+
const params = [];
|
|
3841
|
+
const baseParamMap = /* @__PURE__ */ new Map();
|
|
3842
|
+
for (const n of Object.values(refNodes)) {
|
|
3843
|
+
for (const dep of n.deps ?? []) {
|
|
3844
|
+
if (!innerNames.has(dep) && !baseParamMap.has(dep)) {
|
|
3845
|
+
const param = `$${dep}`;
|
|
3846
|
+
params.push(param);
|
|
3847
|
+
baseParamMap.set(dep, param);
|
|
3848
|
+
}
|
|
3849
|
+
}
|
|
3850
|
+
}
|
|
3851
|
+
const depended = /* @__PURE__ */ new Set();
|
|
3852
|
+
for (const n of Object.values(refNodes)) {
|
|
3853
|
+
for (const dep of n.deps ?? []) {
|
|
3854
|
+
if (innerNames.has(dep)) depended.add(dep);
|
|
3855
|
+
}
|
|
3856
|
+
}
|
|
3857
|
+
const outputCandidates = [...innerNames].filter((n) => !depended.has(n));
|
|
3858
|
+
const output = outputCandidates[0] ?? [...innerNames].pop();
|
|
3859
|
+
const tmplNodes = {};
|
|
3860
|
+
for (const [nName, nSpec] of Object.entries(refNodes)) {
|
|
3861
|
+
tmplNodes[nName] = {
|
|
3862
|
+
...nSpec,
|
|
3863
|
+
deps: nSpec.deps?.map((d) => baseParamMap.get(d) ?? d)
|
|
3864
|
+
};
|
|
3865
|
+
}
|
|
3866
|
+
templates[templateName] = { params, nodes: tmplNodes, output };
|
|
3867
|
+
for (const member of group) {
|
|
3868
|
+
delete nodes[member.name];
|
|
3869
|
+
const memberBind = {};
|
|
3870
|
+
const memberInnerNames = new Set(Object.keys(member.nodes));
|
|
3871
|
+
for (const n of Object.values(member.nodes)) {
|
|
3872
|
+
for (const dep of n.deps ?? []) {
|
|
3873
|
+
if (!memberInnerNames.has(dep)) {
|
|
3874
|
+
const param = baseParamMap.get(dep) ?? `$${dep}`;
|
|
3875
|
+
memberBind[param] = dep;
|
|
3876
|
+
}
|
|
3877
|
+
}
|
|
3878
|
+
}
|
|
3879
|
+
templateRefs[member.name] = {
|
|
3880
|
+
type: "template",
|
|
3881
|
+
template: templateName,
|
|
3882
|
+
bind: memberBind
|
|
3883
|
+
};
|
|
3884
|
+
}
|
|
3885
|
+
}
|
|
3886
|
+
const allNodes = {
|
|
3887
|
+
...nodes,
|
|
3888
|
+
...templateRefs
|
|
3889
|
+
};
|
|
3890
|
+
const result = { name: desc.name, nodes: allNodes };
|
|
3891
|
+
if (Object.keys(templates).length > 0) result.templates = templates;
|
|
3892
|
+
if (feedbackEdges.length > 0) result.feedback = feedbackEdges;
|
|
3893
|
+
return result;
|
|
3894
|
+
}
|
|
3895
|
+
function specDiff(specA, specB) {
|
|
3896
|
+
const entries = [];
|
|
3897
|
+
if (specA.name !== specB.name) {
|
|
3898
|
+
entries.push({
|
|
3899
|
+
type: "changed",
|
|
3900
|
+
path: "name",
|
|
3901
|
+
detail: `"${specA.name}" \u2192 "${specB.name}"`
|
|
3902
|
+
});
|
|
3903
|
+
}
|
|
3904
|
+
const nodesA = new Set(Object.keys(specA.nodes));
|
|
3905
|
+
const nodesB = new Set(Object.keys(specB.nodes));
|
|
3906
|
+
for (const name of nodesB) {
|
|
3907
|
+
if (!nodesA.has(name)) {
|
|
3908
|
+
const n = specB.nodes[name];
|
|
3909
|
+
entries.push({
|
|
3910
|
+
type: "added",
|
|
3911
|
+
path: `nodes.${name}`,
|
|
3912
|
+
detail: `type: ${n.type}`
|
|
3913
|
+
});
|
|
3914
|
+
}
|
|
3915
|
+
}
|
|
3916
|
+
for (const name of nodesA) {
|
|
3917
|
+
if (!nodesB.has(name)) {
|
|
3918
|
+
entries.push({ type: "removed", path: `nodes.${name}` });
|
|
3919
|
+
}
|
|
3920
|
+
}
|
|
3921
|
+
for (const name of nodesA) {
|
|
3922
|
+
if (!nodesB.has(name)) continue;
|
|
3923
|
+
const a = specA.nodes[name];
|
|
3924
|
+
const b = specB.nodes[name];
|
|
3925
|
+
if (JSON.stringify(a) !== JSON.stringify(b)) {
|
|
3926
|
+
const details = [];
|
|
3927
|
+
if (a.type !== b.type) details.push(`type: ${a.type} \u2192 ${b.type}`);
|
|
3928
|
+
if (JSON.stringify(a.deps) !== JSON.stringify(b.deps)) {
|
|
3929
|
+
details.push("deps changed");
|
|
3930
|
+
}
|
|
3931
|
+
if (a.fn !== b.fn) {
|
|
3932
|
+
details.push(`fn: ${a.fn} \u2192 ${b.fn}`);
|
|
3933
|
+
}
|
|
3934
|
+
if (JSON.stringify(a.config) !== JSON.stringify(b.config)) {
|
|
3935
|
+
details.push("config changed");
|
|
3936
|
+
}
|
|
3937
|
+
entries.push({
|
|
3938
|
+
type: "changed",
|
|
3939
|
+
path: `nodes.${name}`,
|
|
3940
|
+
detail: details.join("; ") || "modified"
|
|
3941
|
+
});
|
|
3942
|
+
}
|
|
3943
|
+
}
|
|
3944
|
+
const tmplA = specA.templates ?? {};
|
|
3945
|
+
const tmplB = specB.templates ?? {};
|
|
3946
|
+
const tmplNamesA = new Set(Object.keys(tmplA));
|
|
3947
|
+
const tmplNamesB = new Set(Object.keys(tmplB));
|
|
3948
|
+
for (const name of tmplNamesB) {
|
|
3949
|
+
if (!tmplNamesA.has(name)) {
|
|
3950
|
+
entries.push({ type: "added", path: `templates.${name}` });
|
|
3951
|
+
}
|
|
3952
|
+
}
|
|
3953
|
+
for (const name of tmplNamesA) {
|
|
3954
|
+
if (!tmplNamesB.has(name)) {
|
|
3955
|
+
entries.push({ type: "removed", path: `templates.${name}` });
|
|
3956
|
+
}
|
|
3957
|
+
}
|
|
3958
|
+
for (const name of tmplNamesA) {
|
|
3959
|
+
if (!tmplNamesB.has(name)) continue;
|
|
3960
|
+
if (JSON.stringify(tmplA[name]) !== JSON.stringify(tmplB[name])) {
|
|
3961
|
+
entries.push({
|
|
3962
|
+
type: "changed",
|
|
3963
|
+
path: `templates.${name}`,
|
|
3964
|
+
detail: "template definition changed"
|
|
3965
|
+
});
|
|
3966
|
+
}
|
|
3967
|
+
}
|
|
3968
|
+
const fbA = specA.feedback ?? [];
|
|
3969
|
+
const fbB = specB.feedback ?? [];
|
|
3970
|
+
const fbKeyA = new Set(fbA.map((e) => `${e.from}->${e.to}`));
|
|
3971
|
+
const fbKeyB = new Set(fbB.map((e) => `${e.from}->${e.to}`));
|
|
3972
|
+
for (const fb of fbB) {
|
|
3973
|
+
const key = `${fb.from}->${fb.to}`;
|
|
3974
|
+
if (!fbKeyA.has(key)) {
|
|
3975
|
+
entries.push({
|
|
3976
|
+
type: "added",
|
|
3977
|
+
path: `feedback.${key}`,
|
|
3978
|
+
detail: `maxIterations: ${fb.maxIterations ?? 10}`
|
|
3979
|
+
});
|
|
3980
|
+
}
|
|
3981
|
+
}
|
|
3982
|
+
for (const fb of fbA) {
|
|
3983
|
+
const key = `${fb.from}->${fb.to}`;
|
|
3984
|
+
if (!fbKeyB.has(key)) {
|
|
3985
|
+
entries.push({ type: "removed", path: `feedback.${key}` });
|
|
3986
|
+
}
|
|
3987
|
+
}
|
|
3988
|
+
for (const fb of fbA) {
|
|
3989
|
+
const key = `${fb.from}->${fb.to}`;
|
|
3990
|
+
const counterpart = fbB.find((b) => b.from === fb.from && b.to === fb.to);
|
|
3991
|
+
if (counterpart && JSON.stringify(fb) !== JSON.stringify(counterpart)) {
|
|
3992
|
+
entries.push({
|
|
3993
|
+
type: "changed",
|
|
3994
|
+
path: `feedback.${key}`,
|
|
3995
|
+
detail: `maxIterations: ${fb.maxIterations ?? 10} \u2192 ${counterpart.maxIterations ?? 10}`
|
|
3996
|
+
});
|
|
3997
|
+
}
|
|
3998
|
+
}
|
|
3999
|
+
const added = entries.filter((e) => e.type === "added").length;
|
|
4000
|
+
const removed = entries.filter((e) => e.type === "removed").length;
|
|
4001
|
+
const changed = entries.filter((e) => e.type === "changed").length;
|
|
4002
|
+
const parts = [];
|
|
4003
|
+
if (added) parts.push(`${added} added`);
|
|
4004
|
+
if (removed) parts.push(`${removed} removed`);
|
|
4005
|
+
if (changed) parts.push(`${changed} changed`);
|
|
4006
|
+
const summary = parts.length > 0 ? parts.join(", ") : "no changes";
|
|
4007
|
+
return { entries, summary };
|
|
4008
|
+
}
|
|
4009
|
+
var LLM_COMPOSE_SYSTEM_PROMPT = `You are a graph architect for GraphReFly, a reactive graph protocol.
|
|
4010
|
+
|
|
4011
|
+
Given a natural-language description, produce a JSON GraphSpec with this structure:
|
|
4012
|
+
|
|
4013
|
+
{
|
|
4014
|
+
"name": "<graph_name>",
|
|
4015
|
+
"nodes": {
|
|
4016
|
+
"<node_name>": {
|
|
4017
|
+
"type": "state" | "derived" | "producer" | "effect" | "operator",
|
|
4018
|
+
"deps": ["<dep_node_name>", ...],
|
|
4019
|
+
"fn": "<catalog_function_name>",
|
|
4020
|
+
"source": "<catalog_source_name>",
|
|
4021
|
+
"config": { ... },
|
|
4022
|
+
"initial": <value>,
|
|
4023
|
+
"meta": { "description": "<purpose>" }
|
|
4024
|
+
},
|
|
4025
|
+
"<template_instance>": {
|
|
4026
|
+
"type": "template",
|
|
4027
|
+
"template": "<template_name>",
|
|
4028
|
+
"bind": { "$param": "node_name" }
|
|
4029
|
+
}
|
|
4030
|
+
},
|
|
4031
|
+
"templates": {
|
|
4032
|
+
"<template_name>": {
|
|
4033
|
+
"params": ["$param1", "$param2"],
|
|
4034
|
+
"nodes": { ... },
|
|
4035
|
+
"output": "<output_node>"
|
|
4036
|
+
}
|
|
4037
|
+
},
|
|
4038
|
+
"feedback": [
|
|
4039
|
+
{ "from": "<condition_node>", "to": "<state_node>", "maxIterations": 10 }
|
|
4040
|
+
]
|
|
4041
|
+
}
|
|
4042
|
+
|
|
4043
|
+
Rules:
|
|
4044
|
+
- "state" nodes hold user/LLM-writable values (knobs). Use "initial" for default values.
|
|
4045
|
+
- "derived" nodes compute from deps using a named "fn".
|
|
4046
|
+
- "effect" nodes produce side effects from deps.
|
|
4047
|
+
- "producer" nodes generate values from a named "source".
|
|
4048
|
+
- Use "templates" when the same subgraph pattern repeats (e.g., per-source resilience).
|
|
4049
|
+
- Use "feedback" for bounded cycles where a derived value writes back to a state node.
|
|
4050
|
+
- meta.description is required for every node.
|
|
4051
|
+
- Return ONLY valid JSON, no markdown fences or commentary.`;
|
|
4052
|
+
function stripFences2(text) {
|
|
4053
|
+
const match = text.match(/^```(?:json)?\s*([\s\S]*?)\s*```[\s\S]*$/);
|
|
4054
|
+
return match ? match[1] : text;
|
|
4055
|
+
}
|
|
4056
|
+
async function llmCompose(problem, adapter, opts) {
|
|
4057
|
+
let systemPrompt = LLM_COMPOSE_SYSTEM_PROMPT;
|
|
4058
|
+
if (opts?.catalogDescription) {
|
|
4059
|
+
systemPrompt += `
|
|
4060
|
+
|
|
4061
|
+
Available catalog:
|
|
4062
|
+
${opts.catalogDescription}`;
|
|
4063
|
+
}
|
|
4064
|
+
if (opts?.systemPromptExtra) {
|
|
4065
|
+
systemPrompt += `
|
|
4066
|
+
|
|
4067
|
+
${opts.systemPromptExtra}`;
|
|
4068
|
+
}
|
|
4069
|
+
const messages = [
|
|
4070
|
+
{ role: "system", content: systemPrompt },
|
|
4071
|
+
{ role: "user", content: problem }
|
|
4072
|
+
];
|
|
4073
|
+
const rawResult = adapter.invoke(messages, {
|
|
4074
|
+
model: opts?.model,
|
|
4075
|
+
temperature: opts?.temperature ?? 0,
|
|
4076
|
+
maxTokens: opts?.maxTokens
|
|
4077
|
+
});
|
|
4078
|
+
const response = await rawResult;
|
|
4079
|
+
let content = response.content.trim();
|
|
4080
|
+
if (content.startsWith("```")) {
|
|
4081
|
+
content = stripFences2(content);
|
|
4082
|
+
}
|
|
4083
|
+
let parsed;
|
|
4084
|
+
try {
|
|
4085
|
+
parsed = JSON.parse(content);
|
|
4086
|
+
} catch {
|
|
4087
|
+
throw new Error(`llmCompose: LLM response is not valid JSON: ${content.slice(0, 200)}`);
|
|
4088
|
+
}
|
|
4089
|
+
const validation = validateSpec(parsed);
|
|
4090
|
+
if (!validation.valid) {
|
|
4091
|
+
throw new Error(`llmCompose: invalid GraphSpec:
|
|
4092
|
+
${validation.errors.join("\n")}`);
|
|
4093
|
+
}
|
|
4094
|
+
return parsed;
|
|
4095
|
+
}
|
|
4096
|
+
async function llmRefine(currentSpec, feedback2, adapter, opts) {
|
|
4097
|
+
let systemPrompt = LLM_COMPOSE_SYSTEM_PROMPT;
|
|
4098
|
+
if (opts?.catalogDescription) {
|
|
4099
|
+
systemPrompt += `
|
|
4100
|
+
|
|
4101
|
+
Available catalog:
|
|
4102
|
+
${opts.catalogDescription}`;
|
|
4103
|
+
}
|
|
4104
|
+
if (opts?.systemPromptExtra) {
|
|
4105
|
+
systemPrompt += `
|
|
4106
|
+
|
|
4107
|
+
${opts.systemPromptExtra}`;
|
|
4108
|
+
}
|
|
4109
|
+
const messages = [
|
|
4110
|
+
{ role: "system", content: systemPrompt },
|
|
4111
|
+
{
|
|
4112
|
+
role: "user",
|
|
4113
|
+
content: `Current GraphSpec:
|
|
4114
|
+
${JSON.stringify(currentSpec, null, 2)}
|
|
4115
|
+
|
|
4116
|
+
Modification request: ${feedback2}
|
|
4117
|
+
|
|
4118
|
+
Return the complete modified GraphSpec as JSON.`
|
|
4119
|
+
}
|
|
4120
|
+
];
|
|
4121
|
+
const rawResult = adapter.invoke(messages, {
|
|
4122
|
+
model: opts?.model,
|
|
4123
|
+
temperature: opts?.temperature ?? 0,
|
|
4124
|
+
maxTokens: opts?.maxTokens
|
|
4125
|
+
});
|
|
4126
|
+
const response = await rawResult;
|
|
4127
|
+
let content = response.content.trim();
|
|
4128
|
+
if (content.startsWith("```")) {
|
|
4129
|
+
content = stripFences2(content);
|
|
4130
|
+
}
|
|
4131
|
+
let parsed;
|
|
4132
|
+
try {
|
|
4133
|
+
parsed = JSON.parse(content);
|
|
4134
|
+
} catch {
|
|
4135
|
+
throw new Error(`llmRefine: LLM response is not valid JSON: ${content.slice(0, 200)}`);
|
|
4136
|
+
}
|
|
4137
|
+
const validation = validateSpec(parsed);
|
|
4138
|
+
if (!validation.valid) {
|
|
4139
|
+
throw new Error(`llmRefine: invalid GraphSpec:
|
|
4140
|
+
${validation.errors.join("\n")}`);
|
|
4141
|
+
}
|
|
4142
|
+
return parsed;
|
|
4143
|
+
}
|
|
4144
|
+
|
|
3026
4145
|
// src/patterns/messaging.ts
|
|
3027
4146
|
var messaging_exports = {};
|
|
3028
4147
|
__export(messaging_exports, {
|
|
@@ -3471,7 +4590,7 @@ function registerStep(graph, name, step, depPaths) {
|
|
|
3471
4590
|
graph.connect(path, name);
|
|
3472
4591
|
}
|
|
3473
4592
|
}
|
|
3474
|
-
function
|
|
4593
|
+
function baseMeta2(kind, meta) {
|
|
3475
4594
|
return {
|
|
3476
4595
|
orchestration: true,
|
|
3477
4596
|
orchestration_type: kind,
|
|
@@ -3509,7 +4628,7 @@ function task(graph, name, run, opts) {
|
|
|
3509
4628
|
...nodeOpts,
|
|
3510
4629
|
name,
|
|
3511
4630
|
describeKind: "derived",
|
|
3512
|
-
meta:
|
|
4631
|
+
meta: baseMeta2("task", opts?.meta)
|
|
3513
4632
|
}
|
|
3514
4633
|
);
|
|
3515
4634
|
registerStep(
|
|
@@ -3532,7 +4651,7 @@ function branch(graph, name, source, predicate, opts) {
|
|
|
3532
4651
|
...opts,
|
|
3533
4652
|
name,
|
|
3534
4653
|
describeKind: "derived",
|
|
3535
|
-
meta:
|
|
4654
|
+
meta: baseMeta2("branch", opts?.meta)
|
|
3536
4655
|
}
|
|
3537
4656
|
);
|
|
3538
4657
|
registerStep(graph, name, step, src.path ? [src.path] : []);
|
|
@@ -3555,7 +4674,7 @@ function gate2(graph, name, source, control, opts) {
|
|
|
3555
4674
|
...opts,
|
|
3556
4675
|
name,
|
|
3557
4676
|
describeKind: "operator",
|
|
3558
|
-
meta:
|
|
4677
|
+
meta: baseMeta2("gate", opts?.meta)
|
|
3559
4678
|
}
|
|
3560
4679
|
);
|
|
3561
4680
|
registerStep(
|
|
@@ -3583,7 +4702,7 @@ function approval(graph, name, source, approver, opts) {
|
|
|
3583
4702
|
...opts,
|
|
3584
4703
|
name,
|
|
3585
4704
|
describeKind: "operator",
|
|
3586
|
-
meta:
|
|
4705
|
+
meta: baseMeta2("approval", opts?.meta)
|
|
3587
4706
|
}
|
|
3588
4707
|
);
|
|
3589
4708
|
registerStep(
|
|
@@ -3602,7 +4721,7 @@ function forEach2(graph, name, source, run, opts) {
|
|
|
3602
4721
|
name,
|
|
3603
4722
|
describeKind: "effect",
|
|
3604
4723
|
completeWhenDepsComplete: false,
|
|
3605
|
-
meta:
|
|
4724
|
+
meta: baseMeta2("forEach", opts?.meta),
|
|
3606
4725
|
onMessage(msg, depIndex, actions) {
|
|
3607
4726
|
if (terminated) return true;
|
|
3608
4727
|
if (depIndex !== 0) {
|
|
@@ -3637,7 +4756,7 @@ function join(graph, name, deps, opts) {
|
|
|
3637
4756
|
...opts,
|
|
3638
4757
|
name,
|
|
3639
4758
|
describeKind: "derived",
|
|
3640
|
-
meta:
|
|
4759
|
+
meta: baseMeta2("join", opts?.meta)
|
|
3641
4760
|
}
|
|
3642
4761
|
);
|
|
3643
4762
|
registerStep(
|
|
@@ -3668,7 +4787,7 @@ function loop(graph, name, source, iterate, opts) {
|
|
|
3668
4787
|
...opts,
|
|
3669
4788
|
name,
|
|
3670
4789
|
describeKind: "derived",
|
|
3671
|
-
meta:
|
|
4790
|
+
meta: baseMeta2("loop", opts?.meta)
|
|
3672
4791
|
}
|
|
3673
4792
|
);
|
|
3674
4793
|
registerStep(
|
|
@@ -3693,7 +4812,7 @@ function sensor(graph, name, initial, opts) {
|
|
|
3693
4812
|
name,
|
|
3694
4813
|
initial,
|
|
3695
4814
|
describeKind: "producer",
|
|
3696
|
-
meta:
|
|
4815
|
+
meta: baseMeta2("sensor", opts?.meta)
|
|
3697
4816
|
});
|
|
3698
4817
|
registerStep(graph, name, source, []);
|
|
3699
4818
|
return {
|
|
@@ -3731,7 +4850,7 @@ function wait(graph, name, source, ms, opts) {
|
|
|
3731
4850
|
initial: src.node.get(),
|
|
3732
4851
|
describeKind: "operator",
|
|
3733
4852
|
completeWhenDepsComplete: false,
|
|
3734
|
-
meta:
|
|
4853
|
+
meta: baseMeta2("wait", opts?.meta),
|
|
3735
4854
|
onMessage(msg, depIndex, actions) {
|
|
3736
4855
|
if (terminated) return true;
|
|
3737
4856
|
if (depIndex !== 0) {
|
|
@@ -3781,7 +4900,7 @@ function onFailure(graph, name, source, recover, opts) {
|
|
|
3781
4900
|
name,
|
|
3782
4901
|
describeKind: "operator",
|
|
3783
4902
|
completeWhenDepsComplete: false,
|
|
3784
|
-
meta:
|
|
4903
|
+
meta: baseMeta2("onFailure", opts?.meta),
|
|
3785
4904
|
onMessage(msg, _depIndex, actions) {
|
|
3786
4905
|
if (terminated) return true;
|
|
3787
4906
|
if (msg[0] === ERROR) {
|
|
@@ -3920,6 +5039,7 @@ export {
|
|
|
3920
5039
|
gate,
|
|
3921
5040
|
globToRegExp,
|
|
3922
5041
|
graph_exports as graph,
|
|
5042
|
+
graphspec_exports as graphspec,
|
|
3923
5043
|
interval,
|
|
3924
5044
|
isBatching,
|
|
3925
5045
|
isKnownMessageType,
|
|
@@ -3977,6 +5097,7 @@ export {
|
|
|
3977
5097
|
reactiveLog,
|
|
3978
5098
|
reactiveMap,
|
|
3979
5099
|
reduce,
|
|
5100
|
+
reduction_exports as reduction,
|
|
3980
5101
|
repeat,
|
|
3981
5102
|
replay,
|
|
3982
5103
|
replayWAL,
|