yaml-flow 4.0.0 → 5.1.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/browser/board-livegraph-runtime.js +1453 -0
- package/browser/board-livegraph-runtime.js.map +1 -0
- package/browser/card-compute.js +36 -17
- package/browser/live-cards.js +848 -109
- package/browser/live-cards.schema.json +46 -21
- package/dist/board-livegraph-runtime/index.cjs +1448 -0
- package/dist/board-livegraph-runtime/index.cjs.map +1 -0
- package/dist/board-livegraph-runtime/index.d.cts +101 -0
- package/dist/board-livegraph-runtime/index.d.ts +101 -0
- package/dist/board-livegraph-runtime/index.js +1441 -0
- package/dist/board-livegraph-runtime/index.js.map +1 -0
- package/dist/card-compute/index.cjs +159 -44
- package/dist/card-compute/index.cjs.map +1 -1
- package/dist/card-compute/index.d.cts +36 -11
- package/dist/card-compute/index.d.ts +36 -11
- package/dist/card-compute/index.js +156 -44
- package/dist/card-compute/index.js.map +1 -1
- package/dist/cli/board-live-cards-cli.cjs +476 -105
- package/dist/cli/board-live-cards-cli.cjs.map +1 -1
- package/dist/cli/board-live-cards-cli.d.cts +8 -16
- package/dist/cli/board-live-cards-cli.d.ts +8 -16
- package/dist/cli/board-live-cards-cli.js +476 -106
- package/dist/cli/board-live-cards-cli.js.map +1 -1
- package/dist/continuous-event-graph/index.cjs +74 -33
- package/dist/continuous-event-graph/index.cjs.map +1 -1
- package/dist/continuous-event-graph/index.d.cts +7 -23
- package/dist/continuous-event-graph/index.d.ts +7 -23
- package/dist/continuous-event-graph/index.js +73 -32
- package/dist/continuous-event-graph/index.js.map +1 -1
- package/dist/index.cjs +1440 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -3
- package/dist/index.d.ts +21 -3
- package/dist/index.js +1434 -56
- package/dist/index.js.map +1 -1
- package/dist/journal-DRfJiheM.d.cts +28 -0
- package/dist/journal-NLYuqege.d.ts +28 -0
- package/dist/{journal-B_2JnBMF.d.ts → live-cards-bridge-Or7fdEJV.d.ts} +5 -32
- package/dist/{journal-BJDjWb5Q.d.cts → live-cards-bridge-vGJ6tMzN.d.cts} +5 -32
- package/dist/schedule-CMcZe5Ny.d.ts +21 -0
- package/dist/schedule-CiucyCan.d.cts +21 -0
- package/examples/browser/boards/portfolio-tracker/cards/holdings-table.json +1 -1
- package/examples/browser/boards/portfolio-tracker/cards/portfolio-form.json +3 -3
- package/examples/browser/boards/portfolio-tracker/cards/portfolio-value.json +1 -1
- package/examples/browser/boards/portfolio-tracker/cards/price-fetch.json +3 -3
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-task-executor.cjs +96 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker.js +33 -5
- package/examples/browser/livecards-browser/index.html +37 -684
- package/examples/cli/step-machine-cli/portfolio-tracker/cards/holdings-table.json +1 -1
- package/examples/cli/step-machine-cli/portfolio-tracker/cards/portfolio-form.json +3 -3
- package/examples/cli/step-machine-cli/portfolio-tracker/cards/portfolio-value.json +1 -1
- package/examples/cli/step-machine-cli/portfolio-tracker/cards/price-fetch.json +3 -3
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/update-holdings-cli.js +2 -2
- package/examples/example-board/board.yaml +23 -0
- package/examples/example-board/bootstrap_payload.json +1 -0
- package/examples/example-board/cards/card-chain-region-alert.json +39 -0
- package/examples/example-board/cards/card-chain-region-totals.json +26 -0
- package/examples/example-board/cards/card-chain-top-region.json +24 -0
- package/examples/example-board/cards/card-ex-actions.json +32 -0
- package/examples/example-board/cards/card-ex-chart.json +30 -0
- package/examples/example-board/cards/card-ex-filter.json +36 -0
- package/examples/example-board/cards/card-ex-filtered-by-preference.json +59 -0
- package/examples/example-board/cards/card-ex-form.json +91 -0
- package/examples/example-board/cards/card-ex-list.json +22 -0
- package/examples/example-board/cards/card-ex-markdown.json +17 -0
- package/examples/example-board/cards/card-ex-metric.json +19 -0
- package/examples/example-board/cards/card-ex-narrative.json +36 -0
- package/examples/example-board/cards/card-ex-source-http.json +28 -0
- package/examples/example-board/cards/card-ex-source.json +21 -0
- package/examples/example-board/cards/card-ex-status.json +35 -0
- package/examples/example-board/cards/card-ex-table.json +30 -0
- package/examples/example-board/cards/card-ex-todo.json +29 -0
- package/examples/example-board/demo-chat-handler.js +69 -0
- package/examples/example-board/demo-server-config.json +7 -0
- package/examples/example-board/demo-server.js +124 -0
- package/examples/example-board/demo-shell-browser.html +806 -0
- package/examples/example-board/demo-shell-with-server.html +280 -0
- package/examples/example-board/demo-shell.html +62 -0
- package/examples/example-board/demo-task-executor.js +255 -0
- package/examples/example-board/mock.db +15 -0
- package/examples/example-board/reusable-board-runtime-client.js +265 -0
- package/examples/example-board/reusable-runtime-artifacts-adapter.js +233 -0
- package/examples/example-board/reusable-server-runtime.js +1341 -0
- package/examples/index.html +16 -9
- package/examples/npm-libs/continuous-event-graph/live-cards-board.ts +17 -17
- package/examples/npm-libs/continuous-event-graph/live-portfolio-dashboard.ts +23 -23
- package/examples/step-machine-cli/portfolio-tracker/cards/holdings-table.json +1 -1
- package/examples/step-machine-cli/portfolio-tracker/cards/portfolio-form.json +3 -3
- package/examples/step-machine-cli/portfolio-tracker/cards/portfolio-value.json +1 -1
- package/examples/step-machine-cli/portfolio-tracker/cards/price-fetch.json +1 -1
- package/examples/step-machine-cli/portfolio-tracker/portfolio-tracker-task-executor.cjs +96 -0
- package/package.json +16 -2
- package/schema/card-runtime.schema.json +25 -0
- package/schema/live-cards.schema.json +46 -21
- package/browser/ingest-board.js +0 -296
- package/examples/ingest.js +0 -733
|
@@ -0,0 +1,1453 @@
|
|
|
1
|
+
var BoardLiveGraph = (function (exports) {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
6
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, key + "" , value);
|
|
7
|
+
|
|
8
|
+
// jsonata-global-shim:jsonata-shim
|
|
9
|
+
var _jsonata = typeof globalThis !== "undefined" && globalThis.jsonata || typeof window !== "undefined" && window.jsonata;
|
|
10
|
+
var jsonata_shim_default = _jsonata;
|
|
11
|
+
|
|
12
|
+
// src/card-compute/index.ts
|
|
13
|
+
function deepGet(obj, path) {
|
|
14
|
+
if (!path || !obj) return void 0;
|
|
15
|
+
const parts = path.split(".");
|
|
16
|
+
let cur = obj;
|
|
17
|
+
for (let i = 0; i < parts.length; i++) {
|
|
18
|
+
if (cur == null) return void 0;
|
|
19
|
+
cur = cur[parts[i]];
|
|
20
|
+
}
|
|
21
|
+
return cur;
|
|
22
|
+
}
|
|
23
|
+
function deepSet(obj, path, value) {
|
|
24
|
+
const parts = path.split(".");
|
|
25
|
+
let cur = obj;
|
|
26
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
27
|
+
if (cur[parts[i]] == null || typeof cur[parts[i]] !== "object") cur[parts[i]] = {};
|
|
28
|
+
cur = cur[parts[i]];
|
|
29
|
+
}
|
|
30
|
+
cur[parts[parts.length - 1]] = value;
|
|
31
|
+
}
|
|
32
|
+
async function run(node, options) {
|
|
33
|
+
if (!node?.compute?.length) return node;
|
|
34
|
+
if (!node.card_data) node.card_data = {};
|
|
35
|
+
node.computed_values = {};
|
|
36
|
+
node._sourcesData = options?.sourcesData ?? {};
|
|
37
|
+
const ctx = {
|
|
38
|
+
card_data: node.card_data,
|
|
39
|
+
requires: node.requires ?? {},
|
|
40
|
+
fetched_sources: node._sourcesData,
|
|
41
|
+
computed_values: node.computed_values
|
|
42
|
+
};
|
|
43
|
+
for (const step of node.compute) {
|
|
44
|
+
try {
|
|
45
|
+
const val = await jsonata_shim_default(step.expr).evaluate(ctx);
|
|
46
|
+
deepSet(node.computed_values, step.bindTo, val);
|
|
47
|
+
ctx.computed_values = node.computed_values;
|
|
48
|
+
} catch (err) {
|
|
49
|
+
console.error(`CardCompute.run error on "${node.id ?? "?"}.${step.bindTo}":`, err);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return node;
|
|
53
|
+
}
|
|
54
|
+
async function evalExpr(expr, node) {
|
|
55
|
+
const ctx = {
|
|
56
|
+
card_data: node.card_data ?? {},
|
|
57
|
+
requires: node.requires ?? {},
|
|
58
|
+
fetched_sources: node._sourcesData ?? {},
|
|
59
|
+
computed_values: node.computed_values ?? {}
|
|
60
|
+
};
|
|
61
|
+
return jsonata_shim_default(expr).evaluate(ctx);
|
|
62
|
+
}
|
|
63
|
+
function resolve(node, path) {
|
|
64
|
+
if (path.startsWith("fetched_sources.")) {
|
|
65
|
+
return deepGet(node._sourcesData ?? {}, path.slice("fetched_sources.".length));
|
|
66
|
+
}
|
|
67
|
+
return deepGet(node, path);
|
|
68
|
+
}
|
|
69
|
+
var VALID_ELEMENT_KINDS = /* @__PURE__ */ new Set([
|
|
70
|
+
"metric",
|
|
71
|
+
"table",
|
|
72
|
+
"chart",
|
|
73
|
+
"form",
|
|
74
|
+
"filter",
|
|
75
|
+
"list",
|
|
76
|
+
"notes",
|
|
77
|
+
"todo",
|
|
78
|
+
"alert",
|
|
79
|
+
"narrative",
|
|
80
|
+
"badge",
|
|
81
|
+
"text",
|
|
82
|
+
"markdown",
|
|
83
|
+
"custom"
|
|
84
|
+
]);
|
|
85
|
+
var ALLOWED_KEYS = /* @__PURE__ */ new Set(["id", "meta", "requires", "provides", "view", "card_data", "compute", "sources"]);
|
|
86
|
+
function validateNode(node) {
|
|
87
|
+
const errors = [];
|
|
88
|
+
if (!node || typeof node !== "object" || Array.isArray(node)) {
|
|
89
|
+
return { ok: false, errors: ["Node must be a non-null object"] };
|
|
90
|
+
}
|
|
91
|
+
const n = node;
|
|
92
|
+
if (typeof n.id !== "string" || !n.id) errors.push("id: required, must be a non-empty string");
|
|
93
|
+
for (const key of Object.keys(n)) {
|
|
94
|
+
if (!ALLOWED_KEYS.has(key)) errors.push(`Unknown top-level key: "${key}"`);
|
|
95
|
+
}
|
|
96
|
+
if (n.card_data == null || typeof n.card_data !== "object" || Array.isArray(n.card_data)) {
|
|
97
|
+
errors.push("card_data: required, must be an object");
|
|
98
|
+
}
|
|
99
|
+
if (n.meta != null) {
|
|
100
|
+
if (typeof n.meta !== "object" || Array.isArray(n.meta)) {
|
|
101
|
+
errors.push("meta: must be an object");
|
|
102
|
+
} else {
|
|
103
|
+
const meta = n.meta;
|
|
104
|
+
if (meta.title != null && typeof meta.title !== "string") errors.push("meta.title: must be a string");
|
|
105
|
+
if (meta.tags != null && !Array.isArray(meta.tags)) errors.push("meta.tags: must be an array");
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (n.requires != null && !Array.isArray(n.requires)) errors.push("requires: must be an array of strings");
|
|
109
|
+
if (n.provides != null) {
|
|
110
|
+
if (!Array.isArray(n.provides)) {
|
|
111
|
+
errors.push("provides: must be an array of { bindTo, src } bindings");
|
|
112
|
+
} else {
|
|
113
|
+
n.provides.forEach((p, i) => {
|
|
114
|
+
if (!p || typeof p !== "object" || Array.isArray(p)) {
|
|
115
|
+
errors.push(`provides[${i}]: must be an object with bindTo and src`);
|
|
116
|
+
} else {
|
|
117
|
+
const b = p;
|
|
118
|
+
if (typeof b.bindTo !== "string" || !b.bindTo) errors.push(`provides[${i}]: missing required "bindTo" string`);
|
|
119
|
+
if (typeof b.src !== "string" || !b.src) errors.push(`provides[${i}]: missing required "src" string`);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (n.compute != null) {
|
|
125
|
+
if (!Array.isArray(n.compute)) {
|
|
126
|
+
errors.push("compute: must be an array of compute steps");
|
|
127
|
+
} else {
|
|
128
|
+
n.compute.forEach((step, i) => {
|
|
129
|
+
if (!step || typeof step !== "object" || Array.isArray(step)) {
|
|
130
|
+
errors.push(`compute[${i}]: must be a compute step object`);
|
|
131
|
+
} else {
|
|
132
|
+
const s = step;
|
|
133
|
+
if (typeof s.bindTo !== "string" || !s.bindTo) errors.push(`compute[${i}]: missing required "bindTo" property`);
|
|
134
|
+
if (typeof s.expr !== "string" || !s.expr) errors.push(`compute[${i}]: missing required "expr" string (JSONata expression)`);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (n.sources != null) {
|
|
140
|
+
if (!Array.isArray(n.sources)) {
|
|
141
|
+
errors.push("sources: must be an array");
|
|
142
|
+
} else {
|
|
143
|
+
n.sources.forEach((src, i) => {
|
|
144
|
+
if (!src || typeof src !== "object" || Array.isArray(src)) {
|
|
145
|
+
errors.push(`sources[${i}]: must be an object`);
|
|
146
|
+
} else {
|
|
147
|
+
const s = src;
|
|
148
|
+
if (typeof s.bindTo !== "string" || !s.bindTo) errors.push(`sources[${i}]: missing required "bindTo" property`);
|
|
149
|
+
if (s.outputFile != null && typeof s.outputFile !== "string") errors.push(`sources[${i}]: outputFile must be a string`);
|
|
150
|
+
if (s.optionalForCompletionGating != null && typeof s.optionalForCompletionGating !== "boolean") {
|
|
151
|
+
errors.push(`sources[${i}]: optionalForCompletionGating must be a boolean`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (n.view != null) {
|
|
158
|
+
if (typeof n.view !== "object" || Array.isArray(n.view)) {
|
|
159
|
+
errors.push("view: must be an object");
|
|
160
|
+
} else {
|
|
161
|
+
const view = n.view;
|
|
162
|
+
if (!Array.isArray(view.elements) || view.elements.length === 0) {
|
|
163
|
+
errors.push("view.elements: required, must be a non-empty array");
|
|
164
|
+
} else {
|
|
165
|
+
view.elements.forEach((elem, i) => {
|
|
166
|
+
if (!elem || typeof elem !== "object") {
|
|
167
|
+
errors.push(`view.elements[${i}]: must be an object`);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (!elem.kind || typeof elem.kind !== "string") {
|
|
171
|
+
errors.push(`view.elements[${i}].kind: required, must be a string`);
|
|
172
|
+
} else if (!VALID_ELEMENT_KINDS.has(elem.kind)) {
|
|
173
|
+
errors.push(`view.elements[${i}].kind: unknown kind "${elem.kind}". Valid: ${[...VALID_ELEMENT_KINDS].join(", ")}`);
|
|
174
|
+
}
|
|
175
|
+
if (elem.data != null && (typeof elem.data !== "object" || Array.isArray(elem.data))) {
|
|
176
|
+
errors.push(`view.elements[${i}].data: must be an object`);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
if (view.layout != null && (typeof view.layout !== "object" || Array.isArray(view.layout))) errors.push("view.layout: must be an object");
|
|
181
|
+
if (view.features != null && (typeof view.features !== "object" || Array.isArray(view.features))) errors.push("view.features: must be an object");
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return { ok: errors.length === 0, errors };
|
|
185
|
+
}
|
|
186
|
+
function enrichSources(sources, context) {
|
|
187
|
+
if (!sources || sources.length === 0) return [];
|
|
188
|
+
return sources.map((src) => ({
|
|
189
|
+
...src,
|
|
190
|
+
_requires: context.requires ?? {},
|
|
191
|
+
_sourcesData: context.sourcesData ?? {},
|
|
192
|
+
_computed_values: context.computed_values ?? {}
|
|
193
|
+
}));
|
|
194
|
+
}
|
|
195
|
+
var CardCompute = {
|
|
196
|
+
run,
|
|
197
|
+
eval: evalExpr,
|
|
198
|
+
resolve,
|
|
199
|
+
validate: validateNode,
|
|
200
|
+
enrichSources
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
// src/event-graph/constants.ts
|
|
204
|
+
var TASK_STATUS = {
|
|
205
|
+
RUNNING: "running",
|
|
206
|
+
COMPLETED: "completed",
|
|
207
|
+
FAILED: "failed",
|
|
208
|
+
INACTIVATED: "inactivated"
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// src/event-graph/graph-helpers.ts
|
|
212
|
+
function getProvides(task) {
|
|
213
|
+
if (!task) return [];
|
|
214
|
+
if (Array.isArray(task.provides)) return task.provides;
|
|
215
|
+
return [];
|
|
216
|
+
}
|
|
217
|
+
function getRequires(task) {
|
|
218
|
+
if (!task) return [];
|
|
219
|
+
if (Array.isArray(task.requires)) return task.requires;
|
|
220
|
+
return [];
|
|
221
|
+
}
|
|
222
|
+
function getAllTasks(graph) {
|
|
223
|
+
return graph.tasks ?? {};
|
|
224
|
+
}
|
|
225
|
+
function isNonActiveTask(taskState) {
|
|
226
|
+
if (!taskState) return false;
|
|
227
|
+
return taskState.status === TASK_STATUS.FAILED || taskState.status === TASK_STATUS.INACTIVATED;
|
|
228
|
+
}
|
|
229
|
+
function getRefreshStrategy(taskConfig, graphSettings) {
|
|
230
|
+
return taskConfig.refreshStrategy ?? graphSettings?.refreshStrategy ?? "data-changed";
|
|
231
|
+
}
|
|
232
|
+
function getMaxExecutions(taskConfig) {
|
|
233
|
+
return taskConfig.maxExecutions;
|
|
234
|
+
}
|
|
235
|
+
function computeAvailableOutputs(graph, taskStates) {
|
|
236
|
+
const outputs = /* @__PURE__ */ new Set();
|
|
237
|
+
for (const [taskName, taskState] of Object.entries(taskStates)) {
|
|
238
|
+
if (taskState.status === TASK_STATUS.COMPLETED) {
|
|
239
|
+
const taskConfig = graph.tasks[taskName];
|
|
240
|
+
if (taskConfig) {
|
|
241
|
+
const provides = getProvides(taskConfig);
|
|
242
|
+
provides.forEach((output) => outputs.add(output));
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return Array.from(outputs);
|
|
247
|
+
}
|
|
248
|
+
function groupTasksByProvides(candidateTaskNames, tasks) {
|
|
249
|
+
const outputGroups = {};
|
|
250
|
+
candidateTaskNames.forEach((taskName) => {
|
|
251
|
+
const task = tasks[taskName];
|
|
252
|
+
if (!task) return;
|
|
253
|
+
const provides = getProvides(task);
|
|
254
|
+
provides.forEach((output) => {
|
|
255
|
+
if (!outputGroups[output]) {
|
|
256
|
+
outputGroups[output] = [];
|
|
257
|
+
}
|
|
258
|
+
outputGroups[output].push(taskName);
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
return outputGroups;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// src/event-graph/task-transitions.ts
|
|
265
|
+
function applyTaskStart(state, taskName) {
|
|
266
|
+
const existingTask = state.tasks[taskName] ?? createDefaultGraphEngineStore();
|
|
267
|
+
const updatedTask = {
|
|
268
|
+
...existingTask,
|
|
269
|
+
status: "running",
|
|
270
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
271
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
272
|
+
progress: 0,
|
|
273
|
+
error: void 0
|
|
274
|
+
};
|
|
275
|
+
return {
|
|
276
|
+
...state,
|
|
277
|
+
tasks: { ...state.tasks, [taskName]: updatedTask },
|
|
278
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
function applyTaskCompletion(state, graph, taskName, result, dataHash, data) {
|
|
282
|
+
const existingTask = state.tasks[taskName] ?? createDefaultGraphEngineStore();
|
|
283
|
+
const taskConfig = graph.tasks[taskName];
|
|
284
|
+
if (!taskConfig) {
|
|
285
|
+
throw new Error(`Task "${taskName}" not found in graph`);
|
|
286
|
+
}
|
|
287
|
+
let outputTokens;
|
|
288
|
+
if (result && taskConfig.on && taskConfig.on[result]) {
|
|
289
|
+
outputTokens = taskConfig.on[result];
|
|
290
|
+
} else {
|
|
291
|
+
outputTokens = getProvides(taskConfig);
|
|
292
|
+
}
|
|
293
|
+
const lastConsumedHashes = { ...existingTask.lastConsumedHashes };
|
|
294
|
+
const requires = taskConfig.requires ?? [];
|
|
295
|
+
for (const token of requires) {
|
|
296
|
+
for (const [otherName, otherConfig] of Object.entries(graph.tasks)) {
|
|
297
|
+
if (getProvides(otherConfig).includes(token)) {
|
|
298
|
+
const otherState = state.tasks[otherName];
|
|
299
|
+
if (otherState?.lastDataHash) {
|
|
300
|
+
lastConsumedHashes[token] = otherState.lastDataHash;
|
|
301
|
+
}
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
const updatedTask = {
|
|
307
|
+
...existingTask,
|
|
308
|
+
status: "completed",
|
|
309
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
310
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
311
|
+
executionCount: existingTask.executionCount + 1,
|
|
312
|
+
lastEpoch: existingTask.executionCount + 1,
|
|
313
|
+
lastDataHash: dataHash,
|
|
314
|
+
data,
|
|
315
|
+
lastConsumedHashes,
|
|
316
|
+
error: void 0
|
|
317
|
+
};
|
|
318
|
+
const newOutputs = [.../* @__PURE__ */ new Set([...state.availableOutputs, ...outputTokens])];
|
|
319
|
+
return {
|
|
320
|
+
...state,
|
|
321
|
+
tasks: { ...state.tasks, [taskName]: updatedTask },
|
|
322
|
+
availableOutputs: newOutputs,
|
|
323
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
function applyTaskFailure(state, graph, taskName, error) {
|
|
327
|
+
const existingTask = state.tasks[taskName] ?? createDefaultGraphEngineStore();
|
|
328
|
+
const taskConfig = graph.tasks[taskName];
|
|
329
|
+
if (taskConfig?.retry) {
|
|
330
|
+
const retryCount = existingTask.retryCount + 1;
|
|
331
|
+
if (retryCount <= taskConfig.retry.max_attempts) {
|
|
332
|
+
const updatedTask2 = {
|
|
333
|
+
...existingTask,
|
|
334
|
+
status: "not-started",
|
|
335
|
+
retryCount,
|
|
336
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
337
|
+
error
|
|
338
|
+
};
|
|
339
|
+
return {
|
|
340
|
+
...state,
|
|
341
|
+
tasks: { ...state.tasks, [taskName]: updatedTask2 },
|
|
342
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
const updatedTask = {
|
|
347
|
+
...existingTask,
|
|
348
|
+
status: "failed",
|
|
349
|
+
failedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
350
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
351
|
+
error,
|
|
352
|
+
executionCount: existingTask.executionCount + 1
|
|
353
|
+
};
|
|
354
|
+
let newOutputs = state.availableOutputs;
|
|
355
|
+
if (taskConfig?.on_failure && taskConfig.on_failure.length > 0) {
|
|
356
|
+
newOutputs = [.../* @__PURE__ */ new Set([...state.availableOutputs, ...taskConfig.on_failure])];
|
|
357
|
+
}
|
|
358
|
+
if (taskConfig?.circuit_breaker && updatedTask.executionCount >= taskConfig.circuit_breaker.max_executions) {
|
|
359
|
+
const breakTokens = taskConfig.circuit_breaker.on_break;
|
|
360
|
+
newOutputs = [.../* @__PURE__ */ new Set([...newOutputs, ...breakTokens])];
|
|
361
|
+
}
|
|
362
|
+
return {
|
|
363
|
+
...state,
|
|
364
|
+
tasks: { ...state.tasks, [taskName]: updatedTask },
|
|
365
|
+
availableOutputs: newOutputs,
|
|
366
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
function applyTaskProgress(state, taskName, message, progress) {
|
|
370
|
+
const existingTask = state.tasks[taskName] ?? createDefaultGraphEngineStore();
|
|
371
|
+
const updatedTask = {
|
|
372
|
+
...existingTask,
|
|
373
|
+
progress: typeof progress === "number" ? progress : existingTask.progress,
|
|
374
|
+
messages: [
|
|
375
|
+
...existingTask.messages ?? [],
|
|
376
|
+
...message ? [{ message, timestamp: (/* @__PURE__ */ new Date()).toISOString(), status: existingTask.status }] : []
|
|
377
|
+
],
|
|
378
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
379
|
+
};
|
|
380
|
+
return {
|
|
381
|
+
...state,
|
|
382
|
+
tasks: { ...state.tasks, [taskName]: updatedTask },
|
|
383
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
function applyTaskRestart(state, taskName) {
|
|
387
|
+
const existingTask = state.tasks[taskName];
|
|
388
|
+
if (!existingTask) return state;
|
|
389
|
+
const updatedTask = {
|
|
390
|
+
...existingTask,
|
|
391
|
+
status: "not-started",
|
|
392
|
+
startedAt: void 0,
|
|
393
|
+
completedAt: void 0,
|
|
394
|
+
failedAt: void 0,
|
|
395
|
+
error: void 0,
|
|
396
|
+
data: void 0,
|
|
397
|
+
progress: null,
|
|
398
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
399
|
+
};
|
|
400
|
+
return {
|
|
401
|
+
...state,
|
|
402
|
+
tasks: { ...state.tasks, [taskName]: updatedTask },
|
|
403
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
function createDefaultGraphEngineStore() {
|
|
407
|
+
return {
|
|
408
|
+
status: "not-started",
|
|
409
|
+
executionCount: 0,
|
|
410
|
+
retryCount: 0,
|
|
411
|
+
lastEpoch: 0,
|
|
412
|
+
messages: [],
|
|
413
|
+
progress: null
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// src/continuous-event-graph/core.ts
|
|
418
|
+
function createLiveGraph(config, executionId) {
|
|
419
|
+
const id = executionId ?? `live-${Date.now()}`;
|
|
420
|
+
const tasks = {};
|
|
421
|
+
for (const taskName of Object.keys(config.tasks)) {
|
|
422
|
+
tasks[taskName] = createDefaultGraphEngineStore2();
|
|
423
|
+
}
|
|
424
|
+
const state = {
|
|
425
|
+
status: "running",
|
|
426
|
+
tasks,
|
|
427
|
+
availableOutputs: [],
|
|
428
|
+
stuckDetection: { is_stuck: false, stuck_description: null, outputs_unresolvable: [], tasks_blocked: [] },
|
|
429
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
430
|
+
executionId: id,
|
|
431
|
+
executionConfig: {
|
|
432
|
+
executionMode: config.settings.execution_mode ?? "eligibility-mode",
|
|
433
|
+
conflictStrategy: config.settings.conflict_strategy ?? "alphabetical",
|
|
434
|
+
completionStrategy: config.settings.completion
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
return { config, state };
|
|
438
|
+
}
|
|
439
|
+
function applyEvent(live, event) {
|
|
440
|
+
const { config, state } = live;
|
|
441
|
+
if ("executionId" in event && event.executionId && event.executionId !== state.executionId) {
|
|
442
|
+
return live;
|
|
443
|
+
}
|
|
444
|
+
switch (event.type) {
|
|
445
|
+
// --- Execution state transitions ---
|
|
446
|
+
case "task-started":
|
|
447
|
+
return { config, state: applyTaskStart(state, event.taskName) };
|
|
448
|
+
case "task-completed":
|
|
449
|
+
return { config, state: applyTaskCompletion(state, config, event.taskName, event.result, event.dataHash, event.data) };
|
|
450
|
+
case "task-failed":
|
|
451
|
+
return { config, state: applyTaskFailure(state, config, event.taskName, event.error) };
|
|
452
|
+
case "task-progress":
|
|
453
|
+
return { config, state: applyTaskProgress(state, event.taskName, event.message, event.progress) };
|
|
454
|
+
case "task-restart":
|
|
455
|
+
return { config, state: applyTaskRestart(state, event.taskName) };
|
|
456
|
+
case "inject-tokens":
|
|
457
|
+
return {
|
|
458
|
+
config,
|
|
459
|
+
state: {
|
|
460
|
+
...state,
|
|
461
|
+
availableOutputs: [.../* @__PURE__ */ new Set([...state.availableOutputs, ...event.tokens])],
|
|
462
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
463
|
+
}
|
|
464
|
+
};
|
|
465
|
+
case "agent-action":
|
|
466
|
+
return { config, state: applyAgentAction(state, event.action) };
|
|
467
|
+
// --- Structural mutations ---
|
|
468
|
+
case "task-upsert":
|
|
469
|
+
return addNode(live, event.taskName, event.taskConfig);
|
|
470
|
+
case "task-removal":
|
|
471
|
+
return removeNode(live, event.taskName);
|
|
472
|
+
case "node-requires-add":
|
|
473
|
+
return addRequires(live, event.nodeName, event.tokens);
|
|
474
|
+
case "node-requires-remove":
|
|
475
|
+
return removeRequires(live, event.nodeName, event.tokens);
|
|
476
|
+
case "node-provides-add":
|
|
477
|
+
return addProvides(live, event.nodeName, event.tokens);
|
|
478
|
+
case "node-provides-remove":
|
|
479
|
+
return removeProvides(live, event.nodeName, event.tokens);
|
|
480
|
+
default:
|
|
481
|
+
return live;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
function applyEvents(live, events) {
|
|
485
|
+
return events.reduce((current, event) => applyEvent(current, event), live);
|
|
486
|
+
}
|
|
487
|
+
function addNode(live, name, taskConfig) {
|
|
488
|
+
const exists = !!live.config.tasks[name];
|
|
489
|
+
return {
|
|
490
|
+
config: {
|
|
491
|
+
...live.config,
|
|
492
|
+
tasks: { ...live.config.tasks, [name]: taskConfig }
|
|
493
|
+
},
|
|
494
|
+
state: {
|
|
495
|
+
...live.state,
|
|
496
|
+
tasks: {
|
|
497
|
+
...live.state.tasks,
|
|
498
|
+
[name]: exists ? live.state.tasks[name] : createDefaultGraphEngineStore2()
|
|
499
|
+
},
|
|
500
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
501
|
+
}
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
function removeNode(live, name) {
|
|
505
|
+
if (!live.config.tasks[name]) return live;
|
|
506
|
+
const { [name]: _removedConfig, ...remainingTasks } = live.config.tasks;
|
|
507
|
+
const { [name]: _removedState, ...remainingStates } = live.state.tasks;
|
|
508
|
+
return {
|
|
509
|
+
config: {
|
|
510
|
+
...live.config,
|
|
511
|
+
tasks: remainingTasks
|
|
512
|
+
},
|
|
513
|
+
state: {
|
|
514
|
+
...live.state,
|
|
515
|
+
tasks: remainingStates,
|
|
516
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
function addRequires(live, nodeName, tokens) {
|
|
521
|
+
const task = live.config.tasks[nodeName];
|
|
522
|
+
if (!task) return live;
|
|
523
|
+
const current = getRequires(task);
|
|
524
|
+
const toAdd = tokens.filter((t) => !current.includes(t));
|
|
525
|
+
if (toAdd.length === 0) return live;
|
|
526
|
+
return {
|
|
527
|
+
config: {
|
|
528
|
+
...live.config,
|
|
529
|
+
tasks: {
|
|
530
|
+
...live.config.tasks,
|
|
531
|
+
[nodeName]: { ...task, requires: [...current, ...toAdd] }
|
|
532
|
+
}
|
|
533
|
+
},
|
|
534
|
+
state: live.state
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
function removeRequires(live, nodeName, tokens) {
|
|
538
|
+
const task = live.config.tasks[nodeName];
|
|
539
|
+
if (!task) return live;
|
|
540
|
+
const current = getRequires(task);
|
|
541
|
+
const remaining = current.filter((t) => !tokens.includes(t));
|
|
542
|
+
if (remaining.length === current.length) return live;
|
|
543
|
+
return {
|
|
544
|
+
config: {
|
|
545
|
+
...live.config,
|
|
546
|
+
tasks: {
|
|
547
|
+
...live.config.tasks,
|
|
548
|
+
[nodeName]: { ...task, requires: remaining }
|
|
549
|
+
}
|
|
550
|
+
},
|
|
551
|
+
state: live.state
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
function addProvides(live, nodeName, tokens) {
|
|
555
|
+
const task = live.config.tasks[nodeName];
|
|
556
|
+
if (!task) return live;
|
|
557
|
+
const current = getProvides(task);
|
|
558
|
+
const toAdd = tokens.filter((t) => !current.includes(t));
|
|
559
|
+
if (toAdd.length === 0) return live;
|
|
560
|
+
return {
|
|
561
|
+
config: {
|
|
562
|
+
...live.config,
|
|
563
|
+
tasks: {
|
|
564
|
+
...live.config.tasks,
|
|
565
|
+
[nodeName]: { ...task, provides: [...current, ...toAdd] }
|
|
566
|
+
}
|
|
567
|
+
},
|
|
568
|
+
state: live.state
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
function removeProvides(live, nodeName, tokens) {
|
|
572
|
+
const task = live.config.tasks[nodeName];
|
|
573
|
+
if (!task) return live;
|
|
574
|
+
const current = getProvides(task);
|
|
575
|
+
const remaining = current.filter((t) => !tokens.includes(t));
|
|
576
|
+
if (remaining.length === current.length) return live;
|
|
577
|
+
return {
|
|
578
|
+
config: {
|
|
579
|
+
...live.config,
|
|
580
|
+
tasks: {
|
|
581
|
+
...live.config.tasks,
|
|
582
|
+
[nodeName]: { ...task, provides: remaining }
|
|
583
|
+
}
|
|
584
|
+
},
|
|
585
|
+
state: live.state
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
function snapshot(live) {
|
|
589
|
+
return {
|
|
590
|
+
version: 1,
|
|
591
|
+
config: live.config,
|
|
592
|
+
state: live.state,
|
|
593
|
+
snapshotAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
function createDefaultGraphEngineStore2() {
|
|
597
|
+
return {
|
|
598
|
+
status: "not-started",
|
|
599
|
+
executionCount: 0,
|
|
600
|
+
retryCount: 0,
|
|
601
|
+
lastEpoch: 0,
|
|
602
|
+
messages: [],
|
|
603
|
+
progress: null
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
function applyAgentAction(state, action) {
|
|
607
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
608
|
+
switch (action) {
|
|
609
|
+
case "stop":
|
|
610
|
+
return { ...state, status: "stopped", lastUpdated: now };
|
|
611
|
+
case "pause":
|
|
612
|
+
return { ...state, status: "paused", lastUpdated: now };
|
|
613
|
+
case "resume":
|
|
614
|
+
return { ...state, status: "running", lastUpdated: now };
|
|
615
|
+
default:
|
|
616
|
+
return state;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// src/continuous-event-graph/schedule.ts
|
|
621
|
+
function schedule(live) {
|
|
622
|
+
const { config, state } = live;
|
|
623
|
+
const graphTasks = getAllTasks(config);
|
|
624
|
+
const taskNames = Object.keys(graphTasks);
|
|
625
|
+
if (taskNames.length === 0) {
|
|
626
|
+
return { eligible: [], pending: [], unresolved: [], blocked: [], conflicts: {} };
|
|
627
|
+
}
|
|
628
|
+
const producerMap = buildProducerMap(graphTasks);
|
|
629
|
+
const computedOutputs = computeAvailableOutputs(config, state.tasks);
|
|
630
|
+
const availableOutputs = /* @__PURE__ */ new Set([...computedOutputs, ...state.availableOutputs]);
|
|
631
|
+
const eligible = [];
|
|
632
|
+
const pending = [];
|
|
633
|
+
const unresolved = [];
|
|
634
|
+
const blocked = [];
|
|
635
|
+
for (const [taskName, taskConfig] of Object.entries(graphTasks)) {
|
|
636
|
+
const taskState = state.tasks[taskName];
|
|
637
|
+
const strategy = getRefreshStrategy(taskConfig, config.settings);
|
|
638
|
+
const rerunnable = strategy !== "once";
|
|
639
|
+
if (taskState?.status === TASK_STATUS.RUNNING || isNonActiveTask(taskState)) {
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
const maxExec = getMaxExecutions(taskConfig);
|
|
643
|
+
if (maxExec !== void 0 && taskState && taskState.executionCount >= maxExec) {
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
if (taskConfig.circuit_breaker && taskState && taskState.executionCount >= taskConfig.circuit_breaker.max_executions) {
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
if (!rerunnable && taskState?.status === TASK_STATUS.COMPLETED) {
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
if (rerunnable && taskState?.status === TASK_STATUS.COMPLETED) {
|
|
653
|
+
const requires2 = getRequires(taskConfig);
|
|
654
|
+
let shouldSkip = false;
|
|
655
|
+
switch (strategy) {
|
|
656
|
+
case "data-changed": {
|
|
657
|
+
if (requires2.length > 0) {
|
|
658
|
+
const hasChangedData = requires2.some((req) => {
|
|
659
|
+
for (const [otherName, otherConfig] of Object.entries(graphTasks)) {
|
|
660
|
+
if (getProvides(otherConfig).includes(req)) {
|
|
661
|
+
const otherState = state.tasks[otherName];
|
|
662
|
+
if (!otherState) continue;
|
|
663
|
+
const consumed = taskState.lastConsumedHashes?.[req];
|
|
664
|
+
if (otherState.lastDataHash == null) {
|
|
665
|
+
return otherState.executionCount > taskState.lastEpoch;
|
|
666
|
+
}
|
|
667
|
+
return otherState.lastDataHash !== consumed;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
return false;
|
|
671
|
+
});
|
|
672
|
+
if (!hasChangedData) shouldSkip = true;
|
|
673
|
+
} else {
|
|
674
|
+
shouldSkip = true;
|
|
675
|
+
}
|
|
676
|
+
break;
|
|
677
|
+
}
|
|
678
|
+
case "epoch-changed": {
|
|
679
|
+
if (requires2.length > 0) {
|
|
680
|
+
const hasRefreshed = requires2.some((req) => {
|
|
681
|
+
for (const [otherName, otherConfig] of Object.entries(graphTasks)) {
|
|
682
|
+
if (getProvides(otherConfig).includes(req)) {
|
|
683
|
+
const otherState = state.tasks[otherName];
|
|
684
|
+
if (otherState && otherState.executionCount > taskState.lastEpoch) return true;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
return false;
|
|
688
|
+
});
|
|
689
|
+
if (!hasRefreshed) shouldSkip = true;
|
|
690
|
+
} else {
|
|
691
|
+
shouldSkip = true;
|
|
692
|
+
}
|
|
693
|
+
break;
|
|
694
|
+
}
|
|
695
|
+
case "time-based": {
|
|
696
|
+
const interval = taskConfig.refreshInterval ?? 0;
|
|
697
|
+
if (interval <= 0) {
|
|
698
|
+
shouldSkip = true;
|
|
699
|
+
break;
|
|
700
|
+
}
|
|
701
|
+
const completedAt = taskState.completedAt;
|
|
702
|
+
if (!completedAt) {
|
|
703
|
+
shouldSkip = true;
|
|
704
|
+
break;
|
|
705
|
+
}
|
|
706
|
+
const elapsedSec = (Date.now() - Date.parse(completedAt)) / 1e3;
|
|
707
|
+
if (elapsedSec < interval) shouldSkip = true;
|
|
708
|
+
break;
|
|
709
|
+
}
|
|
710
|
+
case "manual":
|
|
711
|
+
shouldSkip = true;
|
|
712
|
+
break;
|
|
713
|
+
}
|
|
714
|
+
if (shouldSkip) continue;
|
|
715
|
+
}
|
|
716
|
+
const requires = getRequires(taskConfig);
|
|
717
|
+
if (requires.length === 0) {
|
|
718
|
+
eligible.push(taskName);
|
|
719
|
+
continue;
|
|
720
|
+
}
|
|
721
|
+
const missingTokens = [];
|
|
722
|
+
const pendingTokens = [];
|
|
723
|
+
const failedTokenInfo = [];
|
|
724
|
+
for (const token of requires) {
|
|
725
|
+
if (availableOutputs.has(token)) continue;
|
|
726
|
+
const producers = producerMap[token] || [];
|
|
727
|
+
if (producers.length === 0) {
|
|
728
|
+
missingTokens.push(token);
|
|
729
|
+
} else {
|
|
730
|
+
const allFailed = producers.every((p) => isNonActiveTask(state.tasks[p]));
|
|
731
|
+
if (allFailed) {
|
|
732
|
+
failedTokenInfo.push({ token, failedProducer: producers[0] });
|
|
733
|
+
} else {
|
|
734
|
+
pendingTokens.push(token);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
if (missingTokens.length > 0) {
|
|
739
|
+
unresolved.push({ taskName, missingTokens });
|
|
740
|
+
} else if (failedTokenInfo.length > 0) {
|
|
741
|
+
blocked.push({
|
|
742
|
+
taskName,
|
|
743
|
+
failedTokens: failedTokenInfo.map((f) => f.token),
|
|
744
|
+
failedProducers: [...new Set(failedTokenInfo.map((f) => f.failedProducer))]
|
|
745
|
+
});
|
|
746
|
+
} else if (pendingTokens.length > 0) {
|
|
747
|
+
pending.push({ taskName, waitingOn: pendingTokens });
|
|
748
|
+
} else {
|
|
749
|
+
eligible.push(taskName);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
const conflicts = {};
|
|
753
|
+
if (eligible.length > 1) {
|
|
754
|
+
const outputGroups = groupTasksByProvides(eligible, graphTasks);
|
|
755
|
+
for (const [outputKey, groupTasks] of Object.entries(outputGroups)) {
|
|
756
|
+
if (groupTasks.length > 1) {
|
|
757
|
+
conflicts[outputKey] = groupTasks;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
return { eligible, pending, unresolved, blocked, conflicts };
|
|
762
|
+
}
|
|
763
|
+
function buildProducerMap(tasks) {
|
|
764
|
+
const map = {};
|
|
765
|
+
for (const [name, config] of Object.entries(tasks)) {
|
|
766
|
+
for (const token of getProvides(config)) {
|
|
767
|
+
if (!map[token]) map[token] = [];
|
|
768
|
+
map[token].push(name);
|
|
769
|
+
}
|
|
770
|
+
if (config.on) {
|
|
771
|
+
for (const tokens of Object.values(config.on)) {
|
|
772
|
+
for (const token of tokens) {
|
|
773
|
+
if (!map[token]) map[token] = [];
|
|
774
|
+
if (!map[token].includes(name)) map[token].push(name);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
if (config.on_failure) {
|
|
779
|
+
for (const token of config.on_failure) {
|
|
780
|
+
if (!map[token]) map[token] = [];
|
|
781
|
+
if (!map[token].includes(name)) map[token].push(name);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
return map;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// src/continuous-event-graph/journal.ts
|
|
789
|
+
var MemoryJournal = class {
|
|
790
|
+
constructor() {
|
|
791
|
+
__publicField(this, "buffer", []);
|
|
792
|
+
}
|
|
793
|
+
append(event) {
|
|
794
|
+
this.buffer.push(event);
|
|
795
|
+
}
|
|
796
|
+
drain() {
|
|
797
|
+
const events = this.buffer;
|
|
798
|
+
this.buffer = [];
|
|
799
|
+
return events;
|
|
800
|
+
}
|
|
801
|
+
get size() {
|
|
802
|
+
return this.buffer.length;
|
|
803
|
+
}
|
|
804
|
+
};
|
|
805
|
+
|
|
806
|
+
// src/continuous-event-graph/reactive.ts
|
|
807
|
+
function computeDataHash(data) {
|
|
808
|
+
const json = stableStringify(data);
|
|
809
|
+
return fnv1a64Hex(json);
|
|
810
|
+
}
|
|
811
|
+
function stableStringify(value) {
|
|
812
|
+
if (value === null || value === void 0 || typeof value !== "object") {
|
|
813
|
+
return JSON.stringify(value);
|
|
814
|
+
}
|
|
815
|
+
if (Array.isArray(value)) {
|
|
816
|
+
return "[" + value.map(stableStringify).join(",") + "]";
|
|
817
|
+
}
|
|
818
|
+
const obj = value;
|
|
819
|
+
const keys = Object.keys(obj).sort();
|
|
820
|
+
return "{" + keys.map((k) => JSON.stringify(k) + ":" + stableStringify(obj[k])).join(",") + "}";
|
|
821
|
+
}
|
|
822
|
+
function fnv1a64Hex(input) {
|
|
823
|
+
let hash = 0xcbf29ce484222325n;
|
|
824
|
+
const prime = 0x100000001b3n;
|
|
825
|
+
const mod = 0xffffffffffffffffn;
|
|
826
|
+
for (let i = 0; i < input.length; i++) {
|
|
827
|
+
hash ^= BigInt(input.charCodeAt(i));
|
|
828
|
+
hash = hash * prime & mod;
|
|
829
|
+
}
|
|
830
|
+
return hash.toString(16).padStart(16, "0");
|
|
831
|
+
}
|
|
832
|
+
function base64UrlEncode(input) {
|
|
833
|
+
if (typeof Buffer !== "undefined") {
|
|
834
|
+
return Buffer.from(input, "utf8").toString("base64url");
|
|
835
|
+
}
|
|
836
|
+
if (typeof btoa === "function") {
|
|
837
|
+
const bytes = new TextEncoder().encode(input);
|
|
838
|
+
let binary = "";
|
|
839
|
+
for (const b of bytes) binary += String.fromCharCode(b);
|
|
840
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
841
|
+
}
|
|
842
|
+
throw new Error("No base64 encoder available in this runtime");
|
|
843
|
+
}
|
|
844
|
+
function base64UrlDecode(input) {
|
|
845
|
+
if (typeof Buffer !== "undefined") {
|
|
846
|
+
return Buffer.from(input, "base64url").toString("utf8");
|
|
847
|
+
}
|
|
848
|
+
if (typeof atob === "function") {
|
|
849
|
+
const base64 = input.replace(/-/g, "+").replace(/_/g, "/");
|
|
850
|
+
const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
|
|
851
|
+
const binary = atob(padded);
|
|
852
|
+
const bytes = new Uint8Array(binary.length);
|
|
853
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
854
|
+
return new TextDecoder().decode(bytes);
|
|
855
|
+
}
|
|
856
|
+
throw new Error("No base64 decoder available in this runtime");
|
|
857
|
+
}
|
|
858
|
+
function encodeCallbackToken(taskName) {
|
|
859
|
+
const payload = JSON.stringify({ t: taskName, n: Date.now().toString(36) + Math.random().toString(36).slice(2, 6) });
|
|
860
|
+
return base64UrlEncode(payload);
|
|
861
|
+
}
|
|
862
|
+
function decodeCallbackToken(token) {
|
|
863
|
+
try {
|
|
864
|
+
const payload = JSON.parse(base64UrlDecode(token));
|
|
865
|
+
if (typeof payload?.t === "string") return { taskName: payload.t };
|
|
866
|
+
return null;
|
|
867
|
+
} catch {
|
|
868
|
+
return null;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
function createReactiveGraph(configOrLive, options, executionId) {
|
|
872
|
+
const {
|
|
873
|
+
handlers: initialHandlers,
|
|
874
|
+
onDrain
|
|
875
|
+
} = options;
|
|
876
|
+
const inputQueue = new MemoryJournal();
|
|
877
|
+
let live = "state" in configOrLive && "config" in configOrLive ? configOrLive : createLiveGraph(configOrLive, executionId);
|
|
878
|
+
let disposed = false;
|
|
879
|
+
const handlers = new Map(Object.entries(initialHandlers));
|
|
880
|
+
const internalJournal = new MemoryJournal();
|
|
881
|
+
let draining = false;
|
|
882
|
+
let drainQueued = false;
|
|
883
|
+
function drain() {
|
|
884
|
+
if (disposed) return;
|
|
885
|
+
if (draining) {
|
|
886
|
+
drainQueued = true;
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
draining = true;
|
|
890
|
+
try {
|
|
891
|
+
do {
|
|
892
|
+
drainQueued = false;
|
|
893
|
+
drainOnce();
|
|
894
|
+
} while (drainQueued);
|
|
895
|
+
} finally {
|
|
896
|
+
draining = false;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
function drainOnce() {
|
|
900
|
+
const internalEvents = internalJournal.drain();
|
|
901
|
+
const inputEvents = inputQueue.drain();
|
|
902
|
+
const events = [...internalEvents, ...inputEvents];
|
|
903
|
+
if (events.length > 0) {
|
|
904
|
+
live = applyEvents(live, events);
|
|
905
|
+
}
|
|
906
|
+
const result = schedule(live);
|
|
907
|
+
if (events.length > 0) {
|
|
908
|
+
onDrain?.(events, live, result);
|
|
909
|
+
}
|
|
910
|
+
for (const taskName of result.eligible) {
|
|
911
|
+
dispatchTask(taskName);
|
|
912
|
+
}
|
|
913
|
+
for (const event of events) {
|
|
914
|
+
if (event.type === "task-progress") {
|
|
915
|
+
const { taskName, update } = event;
|
|
916
|
+
const taskConfig = live.config.tasks[taskName];
|
|
917
|
+
if (!taskConfig) continue;
|
|
918
|
+
const taskState = live.state.tasks[taskName];
|
|
919
|
+
if (!taskState || taskState.status !== "running") continue;
|
|
920
|
+
const callbackToken = encodeCallbackToken(taskName);
|
|
921
|
+
runPipeline(taskName, callbackToken, update).catch((error) => {
|
|
922
|
+
if (disposed) return;
|
|
923
|
+
internalJournal.append({
|
|
924
|
+
type: "task-failed",
|
|
925
|
+
taskName,
|
|
926
|
+
error: error.message ?? String(error),
|
|
927
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
928
|
+
});
|
|
929
|
+
drain();
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
function resolveUpstreamState(taskName) {
|
|
935
|
+
const taskConfig = live.config.tasks[taskName];
|
|
936
|
+
const requires = taskConfig.requires ?? [];
|
|
937
|
+
const tokenToTask = /* @__PURE__ */ new Map();
|
|
938
|
+
for (const [name, cfg] of Object.entries(live.config.tasks)) {
|
|
939
|
+
for (const token of cfg.provides ?? []) {
|
|
940
|
+
tokenToTask.set(token, name);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
const state = {};
|
|
944
|
+
for (const token of requires) {
|
|
945
|
+
const producerTask = tokenToTask.get(token);
|
|
946
|
+
if (producerTask) {
|
|
947
|
+
state[token] = live.state.tasks[producerTask]?.data;
|
|
948
|
+
} else {
|
|
949
|
+
state[token] = void 0;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
return state;
|
|
953
|
+
}
|
|
954
|
+
async function runPipeline(taskName, callbackToken, update) {
|
|
955
|
+
const taskConfig = live.config.tasks[taskName];
|
|
956
|
+
const handlerNames = taskConfig.taskHandlers ?? [];
|
|
957
|
+
const upstreamState = resolveUpstreamState(taskName);
|
|
958
|
+
for (const handlerName of handlerNames) {
|
|
959
|
+
const handler = handlers.get(handlerName);
|
|
960
|
+
if (!handler) {
|
|
961
|
+
throw new Error(`Handler '${handlerName}' not found in registry (task '${taskName}')`);
|
|
962
|
+
}
|
|
963
|
+
const input = {
|
|
964
|
+
nodeId: taskName,
|
|
965
|
+
state: upstreamState,
|
|
966
|
+
taskState: live.state.tasks[taskName],
|
|
967
|
+
config: taskConfig,
|
|
968
|
+
callbackToken,
|
|
969
|
+
update
|
|
970
|
+
};
|
|
971
|
+
const status = await handler(input);
|
|
972
|
+
if (status === "task-initiate-failure") {
|
|
973
|
+
throw new Error(`Handler '${handlerName}' returned task-initiate-failure (task '${taskName}')`);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
function dispatchTask(taskName) {
|
|
978
|
+
const taskConfig = live.config.tasks[taskName];
|
|
979
|
+
const handlerNames = taskConfig?.taskHandlers;
|
|
980
|
+
if (!handlerNames || handlerNames.length === 0) {
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
internalJournal.append({
|
|
984
|
+
type: "task-started",
|
|
985
|
+
taskName,
|
|
986
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
987
|
+
});
|
|
988
|
+
drain();
|
|
989
|
+
const callbackToken = encodeCallbackToken(taskName);
|
|
990
|
+
runPipeline(taskName, callbackToken).catch((error) => {
|
|
991
|
+
if (disposed) return;
|
|
992
|
+
internalJournal.append({
|
|
993
|
+
type: "task-failed",
|
|
994
|
+
taskName,
|
|
995
|
+
error: error.message ?? String(error),
|
|
996
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
997
|
+
});
|
|
998
|
+
drain();
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
return {
|
|
1002
|
+
push(event) {
|
|
1003
|
+
if (disposed) return;
|
|
1004
|
+
if (event.type === "task-completed" && event.data && !event.dataHash) {
|
|
1005
|
+
event = { ...event, dataHash: computeDataHash(event.data) };
|
|
1006
|
+
}
|
|
1007
|
+
inputQueue.append(event);
|
|
1008
|
+
drain();
|
|
1009
|
+
},
|
|
1010
|
+
pushAll(events) {
|
|
1011
|
+
if (disposed) return;
|
|
1012
|
+
for (const event of events) {
|
|
1013
|
+
if (event.type === "task-completed" && event.data && !event.dataHash) {
|
|
1014
|
+
inputQueue.append({ ...event, dataHash: computeDataHash(event.data) });
|
|
1015
|
+
} else {
|
|
1016
|
+
inputQueue.append(event);
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
drain();
|
|
1020
|
+
},
|
|
1021
|
+
resolveCallback(callbackToken, data, errors) {
|
|
1022
|
+
if (disposed) return;
|
|
1023
|
+
const decoded = decodeCallbackToken(callbackToken);
|
|
1024
|
+
if (!decoded) return;
|
|
1025
|
+
const { taskName } = decoded;
|
|
1026
|
+
if (!live.config.tasks[taskName]) return;
|
|
1027
|
+
if (errors && errors.length > 0) {
|
|
1028
|
+
inputQueue.append({
|
|
1029
|
+
type: "task-failed",
|
|
1030
|
+
taskName,
|
|
1031
|
+
error: errors.join("; "),
|
|
1032
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1033
|
+
});
|
|
1034
|
+
} else {
|
|
1035
|
+
const dataHash = data && Object.keys(data).length > 0 ? computeDataHash(data) : void 0;
|
|
1036
|
+
inputQueue.append({
|
|
1037
|
+
type: "task-completed",
|
|
1038
|
+
taskName,
|
|
1039
|
+
data,
|
|
1040
|
+
dataHash,
|
|
1041
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
drain();
|
|
1045
|
+
},
|
|
1046
|
+
addNode(name, taskConfig) {
|
|
1047
|
+
if (disposed) return;
|
|
1048
|
+
inputQueue.append({ type: "task-upsert", taskName: name, taskConfig, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1049
|
+
drain();
|
|
1050
|
+
},
|
|
1051
|
+
removeNode(name) {
|
|
1052
|
+
if (disposed) return;
|
|
1053
|
+
inputQueue.append({ type: "task-removal", taskName: name, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1054
|
+
drain();
|
|
1055
|
+
},
|
|
1056
|
+
addRequires(nodeName, tokens) {
|
|
1057
|
+
if (disposed) return;
|
|
1058
|
+
inputQueue.append({ type: "node-requires-add", nodeName, tokens, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1059
|
+
drain();
|
|
1060
|
+
},
|
|
1061
|
+
removeRequires(nodeName, tokens) {
|
|
1062
|
+
if (disposed) return;
|
|
1063
|
+
inputQueue.append({ type: "node-requires-remove", nodeName, tokens, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1064
|
+
drain();
|
|
1065
|
+
},
|
|
1066
|
+
addProvides(nodeName, tokens) {
|
|
1067
|
+
if (disposed) return;
|
|
1068
|
+
inputQueue.append({ type: "node-provides-add", nodeName, tokens, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1069
|
+
drain();
|
|
1070
|
+
},
|
|
1071
|
+
removeProvides(nodeName, tokens) {
|
|
1072
|
+
if (disposed) return;
|
|
1073
|
+
inputQueue.append({ type: "node-provides-remove", nodeName, tokens, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1074
|
+
drain();
|
|
1075
|
+
},
|
|
1076
|
+
registerHandler(name, fn) {
|
|
1077
|
+
handlers.set(name, fn);
|
|
1078
|
+
},
|
|
1079
|
+
unregisterHandler(name) {
|
|
1080
|
+
handlers.delete(name);
|
|
1081
|
+
},
|
|
1082
|
+
retrigger(taskName) {
|
|
1083
|
+
if (disposed) return;
|
|
1084
|
+
if (!live.config.tasks[taskName]) return;
|
|
1085
|
+
inputQueue.append({
|
|
1086
|
+
type: "task-restart",
|
|
1087
|
+
taskName,
|
|
1088
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1089
|
+
});
|
|
1090
|
+
drain();
|
|
1091
|
+
},
|
|
1092
|
+
retriggerAll(taskNames) {
|
|
1093
|
+
if (disposed) return;
|
|
1094
|
+
for (const name of taskNames) {
|
|
1095
|
+
if (!live.config.tasks[name]) continue;
|
|
1096
|
+
inputQueue.append({
|
|
1097
|
+
type: "task-restart",
|
|
1098
|
+
taskName: name,
|
|
1099
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1102
|
+
drain();
|
|
1103
|
+
},
|
|
1104
|
+
snapshot() {
|
|
1105
|
+
return snapshot(live);
|
|
1106
|
+
},
|
|
1107
|
+
getState() {
|
|
1108
|
+
return live;
|
|
1109
|
+
},
|
|
1110
|
+
getSchedule() {
|
|
1111
|
+
return schedule(live);
|
|
1112
|
+
},
|
|
1113
|
+
dispose() {
|
|
1114
|
+
disposed = true;
|
|
1115
|
+
}
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
// src/board-livegraph-runtime/index.ts
|
|
1120
|
+
function deepClone(value) {
|
|
1121
|
+
return JSON.parse(JSON.stringify(value));
|
|
1122
|
+
}
|
|
1123
|
+
function toTaskConfig(card) {
|
|
1124
|
+
const provides = card.provides && card.provides.length > 0 ? card.provides.map((p) => p.bindTo) : [card.id];
|
|
1125
|
+
return {
|
|
1126
|
+
requires: card.requires && card.requires.length > 0 ? [...card.requires] : void 0,
|
|
1127
|
+
provides,
|
|
1128
|
+
taskHandlers: [card.id],
|
|
1129
|
+
description: card.meta?.title ?? card.id
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
function buildTokenProviders(cards) {
|
|
1133
|
+
const tokenToCardId = /* @__PURE__ */ new Map();
|
|
1134
|
+
for (const [cardId, card] of cards.entries()) {
|
|
1135
|
+
const bindings = card.provides && card.provides.length > 0 ? card.provides : [{ bindTo: cardId, src: "card_data" }];
|
|
1136
|
+
for (const binding of bindings) tokenToCardId.set(binding.bindTo, cardId);
|
|
1137
|
+
}
|
|
1138
|
+
return tokenToCardId;
|
|
1139
|
+
}
|
|
1140
|
+
function validateRequires(cards, changedCardId) {
|
|
1141
|
+
const tokenProviders = buildTokenProviders(cards);
|
|
1142
|
+
const card = cards.get(changedCardId);
|
|
1143
|
+
if (!card) return;
|
|
1144
|
+
for (const req of card.requires ?? []) {
|
|
1145
|
+
if (!tokenProviders.has(req)) {
|
|
1146
|
+
throw new Error(`Card "${changedCardId}" requires token "${req}" but no card provides it`);
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
var LocalStorageService = {
|
|
1151
|
+
// Keys
|
|
1152
|
+
CARD_PREFIX: "yf:cards:",
|
|
1153
|
+
RUNTIME_OUT_PREFIX: "yf:runtime-out:cards:",
|
|
1154
|
+
STATUS_KEY: "yf:runtime-out:status",
|
|
1155
|
+
// Read/write cards (mirrors tmp/cards/<id>.json)
|
|
1156
|
+
writeCard(cardId, cardObject) {
|
|
1157
|
+
try {
|
|
1158
|
+
localStorage.setItem(this.CARD_PREFIX + cardId, JSON.stringify(cardObject));
|
|
1159
|
+
} catch (e) {
|
|
1160
|
+
console.warn(`Failed to write card ${cardId} to localStorage:`, e);
|
|
1161
|
+
}
|
|
1162
|
+
},
|
|
1163
|
+
readCard(cardId) {
|
|
1164
|
+
try {
|
|
1165
|
+
const raw = localStorage.getItem(this.CARD_PREFIX + cardId);
|
|
1166
|
+
return raw ? JSON.parse(raw) : null;
|
|
1167
|
+
} catch (e) {
|
|
1168
|
+
console.warn(`Failed to read card ${cardId} from localStorage:`, e);
|
|
1169
|
+
return null;
|
|
1170
|
+
}
|
|
1171
|
+
},
|
|
1172
|
+
readAllCards(cardIds) {
|
|
1173
|
+
const result = {};
|
|
1174
|
+
for (const id of cardIds) {
|
|
1175
|
+
const card = this.readCard(id);
|
|
1176
|
+
if (card) result[id] = card;
|
|
1177
|
+
}
|
|
1178
|
+
return result;
|
|
1179
|
+
},
|
|
1180
|
+
// Read/write computed artifacts (mirrors runtime-out/cards/<id>.computed.json)
|
|
1181
|
+
writeComputedArtifact(artifact) {
|
|
1182
|
+
if (!artifact || !artifact.card_id) return;
|
|
1183
|
+
try {
|
|
1184
|
+
localStorage.setItem(
|
|
1185
|
+
this.RUNTIME_OUT_PREFIX + String(artifact.card_id),
|
|
1186
|
+
JSON.stringify(artifact)
|
|
1187
|
+
);
|
|
1188
|
+
} catch (e) {
|
|
1189
|
+
console.warn(`Failed to write computed artifact ${artifact.card_id}:`, e);
|
|
1190
|
+
}
|
|
1191
|
+
},
|
|
1192
|
+
readComputedArtifact(cardId) {
|
|
1193
|
+
try {
|
|
1194
|
+
const raw = localStorage.getItem(this.RUNTIME_OUT_PREFIX + cardId);
|
|
1195
|
+
return raw ? JSON.parse(raw) : null;
|
|
1196
|
+
} catch (e) {
|
|
1197
|
+
console.warn(`Failed to read computed artifact ${cardId}:`, e);
|
|
1198
|
+
return null;
|
|
1199
|
+
}
|
|
1200
|
+
},
|
|
1201
|
+
readAllComputedArtifacts(cardIds) {
|
|
1202
|
+
const result = {};
|
|
1203
|
+
for (const id of cardIds) {
|
|
1204
|
+
const artifact = this.readComputedArtifact(id);
|
|
1205
|
+
if (artifact) result[id] = artifact;
|
|
1206
|
+
}
|
|
1207
|
+
return result;
|
|
1208
|
+
},
|
|
1209
|
+
// Read/write board status snapshot (mirrors runtime-out/board-livegraph-status.json)
|
|
1210
|
+
writeStatusSnapshot(snapshot2) {
|
|
1211
|
+
try {
|
|
1212
|
+
localStorage.setItem(this.STATUS_KEY, JSON.stringify(snapshot2));
|
|
1213
|
+
} catch (e) {
|
|
1214
|
+
console.warn("Failed to write status snapshot to localStorage:", e);
|
|
1215
|
+
}
|
|
1216
|
+
},
|
|
1217
|
+
readStatusSnapshot() {
|
|
1218
|
+
try {
|
|
1219
|
+
const raw = localStorage.getItem(this.STATUS_KEY);
|
|
1220
|
+
return raw ? JSON.parse(raw) : null;
|
|
1221
|
+
} catch (e) {
|
|
1222
|
+
console.warn("Failed to read status snapshot from localStorage:", e);
|
|
1223
|
+
return null;
|
|
1224
|
+
}
|
|
1225
|
+
},
|
|
1226
|
+
// Clear all (useful for reset/demo)
|
|
1227
|
+
clear() {
|
|
1228
|
+
const keysToDelete = [];
|
|
1229
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
1230
|
+
const key = localStorage.key(i);
|
|
1231
|
+
if (key && (key.startsWith(this.CARD_PREFIX) || key.startsWith(this.RUNTIME_OUT_PREFIX) || key === this.STATUS_KEY)) {
|
|
1232
|
+
keysToDelete.push(key);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
for (const key of keysToDelete) {
|
|
1236
|
+
localStorage.removeItem(key);
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
};
|
|
1240
|
+
function createBoardLiveGraphRuntime(input, options = {}) {
|
|
1241
|
+
const boardMeta = Array.isArray(input) ? {} : {
|
|
1242
|
+
id: input.id,
|
|
1243
|
+
title: input.title,
|
|
1244
|
+
mode: input.mode,
|
|
1245
|
+
positions: input.positions,
|
|
1246
|
+
settings: input.settings
|
|
1247
|
+
};
|
|
1248
|
+
const initialCards = Array.isArray(input) ? input : input.nodes;
|
|
1249
|
+
const cards = /* @__PURE__ */ new Map();
|
|
1250
|
+
for (const card of initialCards) {
|
|
1251
|
+
if (cards.has(card.id)) throw new Error(`Duplicate card ID: "${card.id}"`);
|
|
1252
|
+
cards.set(card.id, deepClone(card));
|
|
1253
|
+
}
|
|
1254
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1255
|
+
const taskExecutor = options.taskExecutor;
|
|
1256
|
+
const sourceAdapters = options.sourceAdapters ?? {};
|
|
1257
|
+
const defaultSourceAdapter = options.defaultSourceAdapter;
|
|
1258
|
+
let graphRef = null;
|
|
1259
|
+
const notifyListeners = (events, graph2) => {
|
|
1260
|
+
const update = {
|
|
1261
|
+
events,
|
|
1262
|
+
graph: graph2,
|
|
1263
|
+
nodes: getRenderableNodes()
|
|
1264
|
+
};
|
|
1265
|
+
for (const listener of listeners) listener(update);
|
|
1266
|
+
};
|
|
1267
|
+
const makeHandler = (cardId) => {
|
|
1268
|
+
return async (inputArgs) => {
|
|
1269
|
+
const card = cards.get(cardId);
|
|
1270
|
+
if (!card) return "task-initiate-failure";
|
|
1271
|
+
const requiresData = {};
|
|
1272
|
+
for (const token of card.requires ?? []) {
|
|
1273
|
+
const upstream = inputArgs.state[token];
|
|
1274
|
+
if (!upstream || typeof upstream !== "object") continue;
|
|
1275
|
+
const providesData2 = upstream.provides_data;
|
|
1276
|
+
if (!providesData2 || typeof providesData2 !== "object") continue;
|
|
1277
|
+
if (!Object.prototype.hasOwnProperty.call(providesData2, token)) continue;
|
|
1278
|
+
requiresData[token] = providesData2[token];
|
|
1279
|
+
}
|
|
1280
|
+
const sourcesData = {};
|
|
1281
|
+
if (card.sources && card.sources.length > 0) {
|
|
1282
|
+
const adapter = sourceAdapters[cardId] ?? defaultSourceAdapter;
|
|
1283
|
+
const fetched = taskExecutor ? await taskExecutor({ card, input: inputArgs }) : adapter ? await adapter({ card, input: inputArgs }) : void 0;
|
|
1284
|
+
if (fetched && typeof fetched === "object") {
|
|
1285
|
+
for (const src of card.sources) {
|
|
1286
|
+
if (Object.prototype.hasOwnProperty.call(fetched, src.bindTo)) {
|
|
1287
|
+
sourcesData[src.bindTo] = fetched[src.bindTo];
|
|
1288
|
+
} else if (card.sources.length === 1) {
|
|
1289
|
+
sourcesData[src.bindTo] = fetched;
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
const computeNode = {
|
|
1295
|
+
id: card.id,
|
|
1296
|
+
card_data: deepClone(card.card_data ?? {}),
|
|
1297
|
+
requires: requiresData,
|
|
1298
|
+
sources: card.sources,
|
|
1299
|
+
compute: card.compute
|
|
1300
|
+
};
|
|
1301
|
+
computeNode._sourcesData = sourcesData;
|
|
1302
|
+
if (computeNode.compute && computeNode.compute.length > 0) {
|
|
1303
|
+
await CardCompute.run(computeNode, { sourcesData });
|
|
1304
|
+
}
|
|
1305
|
+
const providesData = {};
|
|
1306
|
+
if (card.provides && card.provides.length > 0) {
|
|
1307
|
+
for (const { bindTo, src } of card.provides) {
|
|
1308
|
+
providesData[bindTo] = CardCompute.resolve(computeNode, src);
|
|
1309
|
+
}
|
|
1310
|
+
} else {
|
|
1311
|
+
providesData[card.id] = {
|
|
1312
|
+
...computeNode.card_data ?? {},
|
|
1313
|
+
...computeNode.computed_values ?? {},
|
|
1314
|
+
...computeNode._sourcesData ?? {}
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
const resultData = {
|
|
1318
|
+
provides_data: providesData,
|
|
1319
|
+
card_data: computeNode.card_data ?? {},
|
|
1320
|
+
computed_values: computeNode.computed_values ?? {},
|
|
1321
|
+
fetched_sources: sourcesData,
|
|
1322
|
+
requires: requiresData
|
|
1323
|
+
};
|
|
1324
|
+
graphRef?.resolveCallback(inputArgs.callbackToken, resultData);
|
|
1325
|
+
return "task-initiated";
|
|
1326
|
+
};
|
|
1327
|
+
};
|
|
1328
|
+
const tasks = {};
|
|
1329
|
+
const handlers = {};
|
|
1330
|
+
for (const [cardId, card] of cards.entries()) {
|
|
1331
|
+
validateRequires(cards, cardId);
|
|
1332
|
+
tasks[cardId] = toTaskConfig(card);
|
|
1333
|
+
handlers[cardId] = makeHandler(cardId);
|
|
1334
|
+
}
|
|
1335
|
+
const config = {
|
|
1336
|
+
id: boardMeta.id ?? `browser-board-${Date.now()}`,
|
|
1337
|
+
settings: {
|
|
1338
|
+
completion: "manual",
|
|
1339
|
+
execution_mode: "eligibility-mode",
|
|
1340
|
+
...boardMeta.settings ?? {},
|
|
1341
|
+
...options.graphSettings ?? {}
|
|
1342
|
+
},
|
|
1343
|
+
tasks
|
|
1344
|
+
};
|
|
1345
|
+
const userOnDrain = options.reactiveOptions?.onDrain;
|
|
1346
|
+
const graph = createReactiveGraph(
|
|
1347
|
+
config,
|
|
1348
|
+
{
|
|
1349
|
+
...options.reactiveOptions ?? {},
|
|
1350
|
+
handlers,
|
|
1351
|
+
onDrain: (events, live, scheduleResult) => {
|
|
1352
|
+
userOnDrain?.(events, live, scheduleResult);
|
|
1353
|
+
notifyListeners(events, live);
|
|
1354
|
+
}
|
|
1355
|
+
},
|
|
1356
|
+
options.executionId
|
|
1357
|
+
);
|
|
1358
|
+
graphRef = graph;
|
|
1359
|
+
function getRenderableNodes() {
|
|
1360
|
+
const live = graph.getState();
|
|
1361
|
+
const out = [];
|
|
1362
|
+
for (const [cardId, baseCard] of cards.entries()) {
|
|
1363
|
+
const data = live.state.tasks[cardId]?.data;
|
|
1364
|
+
const runtimeState = live.state.tasks[cardId];
|
|
1365
|
+
const mergedCardData = {
|
|
1366
|
+
...baseCard.card_data ?? {},
|
|
1367
|
+
...data && typeof data.card_data === "object" ? data.card_data : {}
|
|
1368
|
+
};
|
|
1369
|
+
const cardStatus = runtimeState?.status === "running" ? "loading" : runtimeState?.status;
|
|
1370
|
+
const cardDataForView = {
|
|
1371
|
+
...mergedCardData,
|
|
1372
|
+
...cardStatus ? { status: cardStatus } : {},
|
|
1373
|
+
...runtimeState?.lastUpdated ? { lastRun: runtimeState.lastUpdated } : {},
|
|
1374
|
+
...runtimeState?.status === "failed" && runtimeState.error ? { error: runtimeState.error } : {}
|
|
1375
|
+
};
|
|
1376
|
+
out.push({
|
|
1377
|
+
id: cardId,
|
|
1378
|
+
card: deepClone(baseCard),
|
|
1379
|
+
card_data: cardDataForView,
|
|
1380
|
+
fetched_sources: data && typeof data.fetched_sources === "object" ? deepClone(data.fetched_sources) : {},
|
|
1381
|
+
requires: data && typeof data.requires === "object" ? deepClone(data.requires) : {},
|
|
1382
|
+
computed_values: data && typeof data.computed_values === "object" ? deepClone(data.computed_values) : {},
|
|
1383
|
+
runtime_state: runtimeState ? deepClone(runtimeState) : {}
|
|
1384
|
+
});
|
|
1385
|
+
}
|
|
1386
|
+
return out;
|
|
1387
|
+
}
|
|
1388
|
+
const runtime = {
|
|
1389
|
+
getGraph: () => graph,
|
|
1390
|
+
getState: () => graph.getState(),
|
|
1391
|
+
getSchedule: () => graph.getSchedule(),
|
|
1392
|
+
getNodes: () => getRenderableNodes(),
|
|
1393
|
+
getBoard: () => ({
|
|
1394
|
+
...boardMeta,
|
|
1395
|
+
nodes: getRenderableNodes()
|
|
1396
|
+
}),
|
|
1397
|
+
subscribe(listener) {
|
|
1398
|
+
listeners.add(listener);
|
|
1399
|
+
listener({ events: [], graph: graph.getState(), nodes: getRenderableNodes() });
|
|
1400
|
+
return () => listeners.delete(listener);
|
|
1401
|
+
},
|
|
1402
|
+
addCard(card) {
|
|
1403
|
+
if (cards.has(card.id)) throw new Error(`Card "${card.id}" already exists`);
|
|
1404
|
+
cards.set(card.id, deepClone(card));
|
|
1405
|
+
validateRequires(cards, card.id);
|
|
1406
|
+
graph.registerHandler(card.id, makeHandler(card.id));
|
|
1407
|
+
graph.addNode(card.id, toTaskConfig(card));
|
|
1408
|
+
},
|
|
1409
|
+
upsertCard(card) {
|
|
1410
|
+
cards.set(card.id, deepClone(card));
|
|
1411
|
+
validateRequires(cards, card.id);
|
|
1412
|
+
graph.registerHandler(card.id, makeHandler(card.id));
|
|
1413
|
+
graph.addNode(card.id, toTaskConfig(card));
|
|
1414
|
+
},
|
|
1415
|
+
removeCard(cardId) {
|
|
1416
|
+
cards.delete(cardId);
|
|
1417
|
+
graph.unregisterHandler(cardId);
|
|
1418
|
+
graph.removeNode(cardId);
|
|
1419
|
+
},
|
|
1420
|
+
patchCardState(cardId, patch) {
|
|
1421
|
+
const card = cards.get(cardId);
|
|
1422
|
+
if (!card) throw new Error(`Card "${cardId}" not found`);
|
|
1423
|
+
card.card_data = { ...card.card_data ?? {}, ...patch };
|
|
1424
|
+
graph.retrigger(cardId);
|
|
1425
|
+
},
|
|
1426
|
+
retrigger(cardId) {
|
|
1427
|
+
graph.retrigger(cardId);
|
|
1428
|
+
},
|
|
1429
|
+
retriggerAll() {
|
|
1430
|
+
graph.retriggerAll(Array.from(cards.keys()));
|
|
1431
|
+
},
|
|
1432
|
+
push(event) {
|
|
1433
|
+
graph.push(event);
|
|
1434
|
+
},
|
|
1435
|
+
pushAll(events) {
|
|
1436
|
+
graph.pushAll(events);
|
|
1437
|
+
},
|
|
1438
|
+
dispose() {
|
|
1439
|
+
listeners.clear();
|
|
1440
|
+
graph.dispose();
|
|
1441
|
+
}
|
|
1442
|
+
};
|
|
1443
|
+
return runtime;
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
exports.LocalStorageService = LocalStorageService;
|
|
1447
|
+
exports.createBoardLiveGraphRuntime = createBoardLiveGraphRuntime;
|
|
1448
|
+
|
|
1449
|
+
return exports;
|
|
1450
|
+
|
|
1451
|
+
})({});
|
|
1452
|
+
//# sourceMappingURL=board-livegraph-runtime.js.map
|
|
1453
|
+
//# sourceMappingURL=board-livegraph-runtime.js.map
|