yaml-flow 2.8.0 → 3.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/README.md +189 -3
- package/dist/{constants-BEbO2_OK.d.ts → constants-B2zqu10b.d.ts} +32 -52
- package/dist/{constants-BNjeIlZ8.d.cts → constants-DJZU1pwJ.d.cts} +32 -52
- package/dist/continuous-event-graph/index.cjs +1388 -52
- package/dist/continuous-event-graph/index.cjs.map +1 -1
- package/dist/continuous-event-graph/index.d.cts +643 -5
- package/dist/continuous-event-graph/index.d.ts +643 -5
- package/dist/continuous-event-graph/index.js +1374 -53
- package/dist/continuous-event-graph/index.js.map +1 -1
- package/dist/event-graph/index.cjs +6817 -53
- package/dist/event-graph/index.cjs.map +1 -1
- package/dist/event-graph/index.d.cts +15 -6
- package/dist/event-graph/index.d.ts +15 -6
- package/dist/event-graph/index.js +6808 -51
- package/dist/event-graph/index.js.map +1 -1
- package/dist/index.cjs +1827 -441
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -5
- package/dist/index.d.ts +6 -5
- package/dist/index.js +1808 -440
- package/dist/index.js.map +1 -1
- package/dist/inference/index.cjs +46 -13
- package/dist/inference/index.cjs.map +1 -1
- package/dist/inference/index.d.cts +2 -2
- package/dist/inference/index.d.ts +2 -2
- package/dist/inference/index.js +46 -13
- package/dist/inference/index.js.map +1 -1
- package/dist/step-machine/index.cjs +6600 -0
- package/dist/step-machine/index.cjs.map +1 -1
- package/dist/step-machine/index.d.cts +26 -1
- package/dist/step-machine/index.d.ts +26 -1
- package/dist/step-machine/index.js +6596 -1
- package/dist/step-machine/index.js.map +1 -1
- package/dist/{types-C2lOwquM.d.cts → types-BwvgvlOO.d.cts} +2 -2
- package/dist/{types-mS_pPftm.d.ts → types-ClRA8hzC.d.ts} +2 -2
- package/dist/{types-DAI_a2as.d.ts → types-DEj7OakX.d.cts} +29 -10
- package/dist/{types-DAI_a2as.d.cts → types-DEj7OakX.d.ts} +29 -10
- package/dist/validate-DEZ2Ymdb.d.ts +53 -0
- package/dist/validate-DqKTZg_o.d.cts +53 -0
- package/package.json +1 -1
- package/schema/event-graph.schema.json +254 -0
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import { existsSync, writeFileSync, appendFileSync, readFileSync } from 'fs';
|
|
2
|
+
import { createHash } from 'crypto';
|
|
3
|
+
import { exec } from 'child_process';
|
|
4
|
+
import 'ajv-formats';
|
|
5
|
+
|
|
1
6
|
// src/event-graph/constants.ts
|
|
2
7
|
var TASK_STATUS = {
|
|
3
8
|
NOT_STARTED: "not-started",
|
|
@@ -25,15 +30,11 @@ function isNonActiveTask(taskState) {
|
|
|
25
30
|
if (!taskState) return false;
|
|
26
31
|
return taskState.status === TASK_STATUS.FAILED || taskState.status === TASK_STATUS.INACTIVATED;
|
|
27
32
|
}
|
|
28
|
-
function
|
|
29
|
-
return taskConfig.
|
|
33
|
+
function getRefreshStrategy(taskConfig, graphSettings) {
|
|
34
|
+
return taskConfig.refreshStrategy ?? graphSettings?.refreshStrategy ?? "data-changed";
|
|
30
35
|
}
|
|
31
|
-
function
|
|
32
|
-
|
|
33
|
-
if (typeof taskConfig.repeatable === "object" && taskConfig.repeatable !== null) {
|
|
34
|
-
return taskConfig.repeatable.max;
|
|
35
|
-
}
|
|
36
|
-
return void 0;
|
|
36
|
+
function getMaxExecutions(taskConfig) {
|
|
37
|
+
return taskConfig.maxExecutions;
|
|
37
38
|
}
|
|
38
39
|
function computeAvailableOutputs(graph, taskStates) {
|
|
39
40
|
const outputs = /* @__PURE__ */ new Set();
|
|
@@ -66,7 +67,7 @@ function groupTasksByProvides(candidateTaskNames, tasks) {
|
|
|
66
67
|
|
|
67
68
|
// src/event-graph/task-transitions.ts
|
|
68
69
|
function applyTaskStart(state, taskName) {
|
|
69
|
-
const existingTask = state.tasks[taskName] ??
|
|
70
|
+
const existingTask = state.tasks[taskName] ?? createDefaultGraphEngineStore();
|
|
70
71
|
const updatedTask = {
|
|
71
72
|
...existingTask,
|
|
72
73
|
status: "running",
|
|
@@ -81,8 +82,8 @@ function applyTaskStart(state, taskName) {
|
|
|
81
82
|
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
82
83
|
};
|
|
83
84
|
}
|
|
84
|
-
function applyTaskCompletion(state, graph, taskName, result) {
|
|
85
|
-
const existingTask = state.tasks[taskName] ??
|
|
85
|
+
function applyTaskCompletion(state, graph, taskName, result, dataHash, data) {
|
|
86
|
+
const existingTask = state.tasks[taskName] ?? createDefaultGraphEngineStore();
|
|
86
87
|
const taskConfig = graph.tasks[taskName];
|
|
87
88
|
if (!taskConfig) {
|
|
88
89
|
throw new Error(`Task "${taskName}" not found in graph`);
|
|
@@ -93,6 +94,19 @@ function applyTaskCompletion(state, graph, taskName, result) {
|
|
|
93
94
|
} else {
|
|
94
95
|
outputTokens = getProvides(taskConfig);
|
|
95
96
|
}
|
|
97
|
+
const lastConsumedHashes = { ...existingTask.lastConsumedHashes };
|
|
98
|
+
const requires = taskConfig.requires ?? [];
|
|
99
|
+
for (const token of requires) {
|
|
100
|
+
for (const [otherName, otherConfig] of Object.entries(graph.tasks)) {
|
|
101
|
+
if (getProvides(otherConfig).includes(token)) {
|
|
102
|
+
const otherState = state.tasks[otherName];
|
|
103
|
+
if (otherState?.lastDataHash) {
|
|
104
|
+
lastConsumedHashes[token] = otherState.lastDataHash;
|
|
105
|
+
}
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
96
110
|
const updatedTask = {
|
|
97
111
|
...existingTask,
|
|
98
112
|
status: "completed",
|
|
@@ -100,11 +114,11 @@ function applyTaskCompletion(state, graph, taskName, result) {
|
|
|
100
114
|
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
101
115
|
executionCount: existingTask.executionCount + 1,
|
|
102
116
|
lastEpoch: existingTask.executionCount + 1,
|
|
117
|
+
lastDataHash: dataHash,
|
|
118
|
+
data,
|
|
119
|
+
lastConsumedHashes,
|
|
103
120
|
error: void 0
|
|
104
121
|
};
|
|
105
|
-
if (isRepeatableTask(taskConfig)) {
|
|
106
|
-
updatedTask.status = "not-started";
|
|
107
|
-
}
|
|
108
122
|
const newOutputs = [.../* @__PURE__ */ new Set([...state.availableOutputs, ...outputTokens])];
|
|
109
123
|
return {
|
|
110
124
|
...state,
|
|
@@ -114,7 +128,7 @@ function applyTaskCompletion(state, graph, taskName, result) {
|
|
|
114
128
|
};
|
|
115
129
|
}
|
|
116
130
|
function applyTaskFailure(state, graph, taskName, error) {
|
|
117
|
-
const existingTask = state.tasks[taskName] ??
|
|
131
|
+
const existingTask = state.tasks[taskName] ?? createDefaultGraphEngineStore();
|
|
118
132
|
const taskConfig = graph.tasks[taskName];
|
|
119
133
|
if (taskConfig?.retry) {
|
|
120
134
|
const retryCount = existingTask.retryCount + 1;
|
|
@@ -157,7 +171,7 @@ function applyTaskFailure(state, graph, taskName, error) {
|
|
|
157
171
|
};
|
|
158
172
|
}
|
|
159
173
|
function applyTaskProgress(state, taskName, message, progress) {
|
|
160
|
-
const existingTask = state.tasks[taskName] ??
|
|
174
|
+
const existingTask = state.tasks[taskName] ?? createDefaultGraphEngineStore();
|
|
161
175
|
const updatedTask = {
|
|
162
176
|
...existingTask,
|
|
163
177
|
progress: typeof progress === "number" ? progress : existingTask.progress,
|
|
@@ -173,7 +187,27 @@ function applyTaskProgress(state, taskName, message, progress) {
|
|
|
173
187
|
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
174
188
|
};
|
|
175
189
|
}
|
|
176
|
-
function
|
|
190
|
+
function applyTaskRestart(state, taskName) {
|
|
191
|
+
const existingTask = state.tasks[taskName];
|
|
192
|
+
if (!existingTask) return state;
|
|
193
|
+
const updatedTask = {
|
|
194
|
+
...existingTask,
|
|
195
|
+
status: "not-started",
|
|
196
|
+
startedAt: void 0,
|
|
197
|
+
completedAt: void 0,
|
|
198
|
+
failedAt: void 0,
|
|
199
|
+
error: void 0,
|
|
200
|
+
data: void 0,
|
|
201
|
+
progress: null,
|
|
202
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
203
|
+
};
|
|
204
|
+
return {
|
|
205
|
+
...state,
|
|
206
|
+
tasks: { ...state.tasks, [taskName]: updatedTask },
|
|
207
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function createDefaultGraphEngineStore() {
|
|
177
211
|
return {
|
|
178
212
|
status: "not-started",
|
|
179
213
|
executionCount: 0,
|
|
@@ -189,7 +223,7 @@ function createLiveGraph(config, executionId) {
|
|
|
189
223
|
const id = executionId ?? `live-${Date.now()}`;
|
|
190
224
|
const tasks = {};
|
|
191
225
|
for (const taskName of Object.keys(config.tasks)) {
|
|
192
|
-
tasks[taskName] =
|
|
226
|
+
tasks[taskName] = createDefaultGraphEngineStore2();
|
|
193
227
|
}
|
|
194
228
|
const state = {
|
|
195
229
|
status: "running",
|
|
@@ -217,7 +251,7 @@ function applyEvent(live, event) {
|
|
|
217
251
|
newState = applyTaskStart(state, event.taskName);
|
|
218
252
|
break;
|
|
219
253
|
case "task-completed":
|
|
220
|
-
newState = applyTaskCompletion(state, config, event.taskName, event.result);
|
|
254
|
+
newState = applyTaskCompletion(state, config, event.taskName, event.result, event.dataHash, event.data);
|
|
221
255
|
break;
|
|
222
256
|
case "task-failed":
|
|
223
257
|
newState = applyTaskFailure(state, config, event.taskName, event.error);
|
|
@@ -225,6 +259,9 @@ function applyEvent(live, event) {
|
|
|
225
259
|
case "task-progress":
|
|
226
260
|
newState = applyTaskProgress(state, event.taskName, event.message, event.progress);
|
|
227
261
|
break;
|
|
262
|
+
case "task-restart":
|
|
263
|
+
newState = applyTaskRestart(state, event.taskName);
|
|
264
|
+
break;
|
|
228
265
|
case "inject-tokens":
|
|
229
266
|
newState = {
|
|
230
267
|
...state,
|
|
@@ -240,6 +277,9 @@ function applyEvent(live, event) {
|
|
|
240
277
|
}
|
|
241
278
|
return { config, state: newState };
|
|
242
279
|
}
|
|
280
|
+
function applyEvents(live, events) {
|
|
281
|
+
return events.reduce((current, event) => applyEvent(current, event), live);
|
|
282
|
+
}
|
|
243
283
|
function addNode(live, name, taskConfig) {
|
|
244
284
|
if (live.config.tasks[name]) return live;
|
|
245
285
|
return {
|
|
@@ -249,7 +289,7 @@ function addNode(live, name, taskConfig) {
|
|
|
249
289
|
},
|
|
250
290
|
state: {
|
|
251
291
|
...live.state,
|
|
252
|
-
tasks: { ...live.state.tasks, [name]:
|
|
292
|
+
tasks: { ...live.state.tasks, [name]: createDefaultGraphEngineStore2() },
|
|
253
293
|
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
254
294
|
}
|
|
255
295
|
};
|
|
@@ -366,7 +406,7 @@ function resetNode(live, name) {
|
|
|
366
406
|
...live.state,
|
|
367
407
|
tasks: {
|
|
368
408
|
...live.state.tasks,
|
|
369
|
-
[name]:
|
|
409
|
+
[name]: createDefaultGraphEngineStore2()
|
|
370
410
|
},
|
|
371
411
|
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
372
412
|
}
|
|
@@ -405,7 +445,7 @@ function enableNode(live, name) {
|
|
|
405
445
|
function getNode(live, name) {
|
|
406
446
|
const config = live.config.tasks[name];
|
|
407
447
|
if (!config) return void 0;
|
|
408
|
-
const state = live.state.tasks[name] ??
|
|
448
|
+
const state = live.state.tasks[name] ?? createDefaultGraphEngineStore2();
|
|
409
449
|
return { name, config, state };
|
|
410
450
|
}
|
|
411
451
|
function snapshot(live) {
|
|
@@ -443,7 +483,7 @@ function restore(data) {
|
|
|
443
483
|
}
|
|
444
484
|
return { config, state };
|
|
445
485
|
}
|
|
446
|
-
function
|
|
486
|
+
function createDefaultGraphEngineStore2() {
|
|
447
487
|
return {
|
|
448
488
|
status: "not-started",
|
|
449
489
|
executionCount: 0,
|
|
@@ -484,38 +524,84 @@ function schedule(live) {
|
|
|
484
524
|
const blocked = [];
|
|
485
525
|
for (const [taskName, taskConfig] of Object.entries(graphTasks)) {
|
|
486
526
|
const taskState = state.tasks[taskName];
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
527
|
+
const strategy = getRefreshStrategy(taskConfig, config.settings);
|
|
528
|
+
const rerunnable = strategy !== "once";
|
|
529
|
+
if (taskState?.status === TASK_STATUS.RUNNING || isNonActiveTask(taskState)) {
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
532
|
+
const maxExec = getMaxExecutions(taskConfig);
|
|
533
|
+
if (maxExec !== void 0 && taskState && taskState.executionCount >= maxExec) {
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
536
|
+
if (taskConfig.circuit_breaker && taskState && taskState.executionCount >= taskConfig.circuit_breaker.max_executions) {
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
if (!rerunnable && taskState?.status === TASK_STATUS.COMPLETED) {
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
if (rerunnable && taskState?.status === TASK_STATUS.COMPLETED) {
|
|
543
|
+
const requires2 = getRequires(taskConfig);
|
|
544
|
+
let shouldSkip = false;
|
|
545
|
+
switch (strategy) {
|
|
546
|
+
case "data-changed": {
|
|
547
|
+
if (requires2.length > 0) {
|
|
548
|
+
const hasChangedData = requires2.some((req) => {
|
|
549
|
+
for (const [otherName, otherConfig] of Object.entries(graphTasks)) {
|
|
550
|
+
if (getProvides(otherConfig).includes(req)) {
|
|
551
|
+
const otherState = state.tasks[otherName];
|
|
552
|
+
if (!otherState) continue;
|
|
553
|
+
const consumed = taskState.lastConsumedHashes?.[req];
|
|
554
|
+
if (otherState.lastDataHash == null) {
|
|
555
|
+
return otherState.executionCount > taskState.lastEpoch;
|
|
556
|
+
}
|
|
557
|
+
return otherState.lastDataHash !== consumed;
|
|
558
|
+
}
|
|
510
559
|
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
560
|
+
return false;
|
|
561
|
+
});
|
|
562
|
+
if (!hasChangedData) shouldSkip = true;
|
|
563
|
+
} else {
|
|
564
|
+
shouldSkip = true;
|
|
565
|
+
}
|
|
566
|
+
break;
|
|
567
|
+
}
|
|
568
|
+
case "epoch-changed": {
|
|
569
|
+
if (requires2.length > 0) {
|
|
570
|
+
const hasRefreshed = requires2.some((req) => {
|
|
571
|
+
for (const [otherName, otherConfig] of Object.entries(graphTasks)) {
|
|
572
|
+
if (getProvides(otherConfig).includes(req)) {
|
|
573
|
+
const otherState = state.tasks[otherName];
|
|
574
|
+
if (otherState && otherState.executionCount > taskState.lastEpoch) return true;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
return false;
|
|
578
|
+
});
|
|
579
|
+
if (!hasRefreshed) shouldSkip = true;
|
|
580
|
+
} else {
|
|
581
|
+
shouldSkip = true;
|
|
582
|
+
}
|
|
583
|
+
break;
|
|
584
|
+
}
|
|
585
|
+
case "time-based": {
|
|
586
|
+
const interval = taskConfig.refreshInterval ?? 0;
|
|
587
|
+
if (interval <= 0) {
|
|
588
|
+
shouldSkip = true;
|
|
589
|
+
break;
|
|
590
|
+
}
|
|
591
|
+
const completedAt = taskState.completedAt;
|
|
592
|
+
if (!completedAt) {
|
|
593
|
+
shouldSkip = true;
|
|
594
|
+
break;
|
|
595
|
+
}
|
|
596
|
+
const elapsedSec = (Date.now() - Date.parse(completedAt)) / 1e3;
|
|
597
|
+
if (elapsedSec < interval) shouldSkip = true;
|
|
598
|
+
break;
|
|
517
599
|
}
|
|
600
|
+
case "manual":
|
|
601
|
+
shouldSkip = true;
|
|
602
|
+
break;
|
|
518
603
|
}
|
|
604
|
+
if (shouldSkip) continue;
|
|
519
605
|
}
|
|
520
606
|
const requires = getRequires(taskConfig);
|
|
521
607
|
if (requires.length === 0) {
|
|
@@ -910,7 +996,1242 @@ function getDownstream(live, nodeName) {
|
|
|
910
996
|
}));
|
|
911
997
|
return { nodeName, nodes, tokens: [...tokenSet] };
|
|
912
998
|
}
|
|
999
|
+
var MemoryJournal = class {
|
|
1000
|
+
buffer = [];
|
|
1001
|
+
append(event) {
|
|
1002
|
+
this.buffer.push(event);
|
|
1003
|
+
}
|
|
1004
|
+
drain() {
|
|
1005
|
+
const events = this.buffer;
|
|
1006
|
+
this.buffer = [];
|
|
1007
|
+
return events;
|
|
1008
|
+
}
|
|
1009
|
+
get size() {
|
|
1010
|
+
return this.buffer.length;
|
|
1011
|
+
}
|
|
1012
|
+
};
|
|
1013
|
+
var FileJournal = class {
|
|
1014
|
+
constructor(path) {
|
|
1015
|
+
this.path = path;
|
|
1016
|
+
if (!existsSync(path)) {
|
|
1017
|
+
writeFileSync(path, "", "utf-8");
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
path;
|
|
1021
|
+
pending = 0;
|
|
1022
|
+
append(event) {
|
|
1023
|
+
appendFileSync(this.path, JSON.stringify(event) + "\n", "utf-8");
|
|
1024
|
+
this.pending++;
|
|
1025
|
+
}
|
|
1026
|
+
drain() {
|
|
1027
|
+
const content = readFileSync(this.path, "utf-8").trim();
|
|
1028
|
+
writeFileSync(this.path, "", "utf-8");
|
|
1029
|
+
this.pending = 0;
|
|
1030
|
+
if (!content) return [];
|
|
1031
|
+
return content.split("\n").map((line) => JSON.parse(line));
|
|
1032
|
+
}
|
|
1033
|
+
get size() {
|
|
1034
|
+
try {
|
|
1035
|
+
const content = readFileSync(this.path, "utf-8").trim();
|
|
1036
|
+
if (!content) return 0;
|
|
1037
|
+
return content.split("\n").length;
|
|
1038
|
+
} catch {
|
|
1039
|
+
return this.pending;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
};
|
|
1043
|
+
function computeDataHash(data) {
|
|
1044
|
+
const json = stableStringify(data);
|
|
1045
|
+
return createHash("sha256").update(json).digest("hex").slice(0, 16);
|
|
1046
|
+
}
|
|
1047
|
+
function stableStringify(value) {
|
|
1048
|
+
if (value === null || value === void 0 || typeof value !== "object") {
|
|
1049
|
+
return JSON.stringify(value);
|
|
1050
|
+
}
|
|
1051
|
+
if (Array.isArray(value)) {
|
|
1052
|
+
return "[" + value.map(stableStringify).join(",") + "]";
|
|
1053
|
+
}
|
|
1054
|
+
const obj = value;
|
|
1055
|
+
const keys = Object.keys(obj).sort();
|
|
1056
|
+
return "{" + keys.map((k) => JSON.stringify(k) + ":" + stableStringify(obj[k])).join(",") + "}";
|
|
1057
|
+
}
|
|
1058
|
+
function encodeCallbackToken(taskName) {
|
|
1059
|
+
const payload = JSON.stringify({ t: taskName, n: Date.now().toString(36) + Math.random().toString(36).slice(2, 6) });
|
|
1060
|
+
return Buffer.from(payload).toString("base64url");
|
|
1061
|
+
}
|
|
1062
|
+
function decodeCallbackToken(token) {
|
|
1063
|
+
try {
|
|
1064
|
+
const payload = JSON.parse(Buffer.from(token, "base64url").toString());
|
|
1065
|
+
if (typeof payload?.t === "string") return { taskName: payload.t };
|
|
1066
|
+
return null;
|
|
1067
|
+
} catch {
|
|
1068
|
+
return null;
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
function createReactiveGraph(config, options, executionId) {
|
|
1072
|
+
const {
|
|
1073
|
+
handlers: initialHandlers,
|
|
1074
|
+
journal = new MemoryJournal(),
|
|
1075
|
+
onDrain
|
|
1076
|
+
} = options;
|
|
1077
|
+
let live = createLiveGraph(config, executionId);
|
|
1078
|
+
let disposed = false;
|
|
1079
|
+
const handlers = new Map(Object.entries(initialHandlers));
|
|
1080
|
+
let draining = false;
|
|
1081
|
+
let drainQueued = false;
|
|
1082
|
+
function drain() {
|
|
1083
|
+
if (disposed) return;
|
|
1084
|
+
if (draining) {
|
|
1085
|
+
drainQueued = true;
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
draining = true;
|
|
1089
|
+
try {
|
|
1090
|
+
do {
|
|
1091
|
+
drainQueued = false;
|
|
1092
|
+
drainOnce();
|
|
1093
|
+
} while (drainQueued);
|
|
1094
|
+
} finally {
|
|
1095
|
+
draining = false;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
function drainOnce() {
|
|
1099
|
+
const events = journal.drain();
|
|
1100
|
+
if (events.length > 0) {
|
|
1101
|
+
live = applyEvents(live, events);
|
|
1102
|
+
}
|
|
1103
|
+
const result = schedule(live);
|
|
1104
|
+
if (events.length > 0) {
|
|
1105
|
+
onDrain?.(events, live, result);
|
|
1106
|
+
}
|
|
1107
|
+
for (const taskName of result.eligible) {
|
|
1108
|
+
dispatchTask(taskName);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
function resolveUpstreamState(taskName) {
|
|
1112
|
+
const taskConfig = live.config.tasks[taskName];
|
|
1113
|
+
const requires = taskConfig.requires ?? [];
|
|
1114
|
+
const tokenToTask = /* @__PURE__ */ new Map();
|
|
1115
|
+
for (const [name, cfg] of Object.entries(live.config.tasks)) {
|
|
1116
|
+
for (const token of cfg.provides ?? []) {
|
|
1117
|
+
tokenToTask.set(token, name);
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
const state = {};
|
|
1121
|
+
for (const token of requires) {
|
|
1122
|
+
const producerTask = tokenToTask.get(token);
|
|
1123
|
+
if (producerTask) {
|
|
1124
|
+
state[token] = live.state.tasks[producerTask]?.data;
|
|
1125
|
+
} else {
|
|
1126
|
+
state[token] = void 0;
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
return state;
|
|
1130
|
+
}
|
|
1131
|
+
async function runPipeline(taskName, callbackToken) {
|
|
1132
|
+
const taskConfig = live.config.tasks[taskName];
|
|
1133
|
+
const handlerNames = taskConfig.taskHandlers ?? [];
|
|
1134
|
+
const upstreamState = resolveUpstreamState(taskName);
|
|
1135
|
+
for (const handlerName of handlerNames) {
|
|
1136
|
+
const handler = handlers.get(handlerName);
|
|
1137
|
+
if (!handler) {
|
|
1138
|
+
throw new Error(`Handler '${handlerName}' not found in registry (task '${taskName}')`);
|
|
1139
|
+
}
|
|
1140
|
+
const input = {
|
|
1141
|
+
nodeId: taskName,
|
|
1142
|
+
state: upstreamState,
|
|
1143
|
+
taskState: live.state.tasks[taskName],
|
|
1144
|
+
config: taskConfig,
|
|
1145
|
+
callbackToken
|
|
1146
|
+
};
|
|
1147
|
+
const status = await handler(input);
|
|
1148
|
+
if (status === "task-initiate-failure") {
|
|
1149
|
+
throw new Error(`Handler '${handlerName}' returned task-initiate-failure (task '${taskName}')`);
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
function dispatchTask(taskName) {
|
|
1154
|
+
const taskConfig = live.config.tasks[taskName];
|
|
1155
|
+
const handlerNames = taskConfig?.taskHandlers;
|
|
1156
|
+
if (!handlerNames || handlerNames.length === 0) {
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
journal.append({
|
|
1160
|
+
type: "task-started",
|
|
1161
|
+
taskName,
|
|
1162
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1163
|
+
});
|
|
1164
|
+
const callbackToken = encodeCallbackToken(taskName);
|
|
1165
|
+
runPipeline(taskName, callbackToken).catch((error) => {
|
|
1166
|
+
if (disposed) return;
|
|
1167
|
+
journal.append({
|
|
1168
|
+
type: "task-failed",
|
|
1169
|
+
taskName,
|
|
1170
|
+
error: error.message ?? String(error),
|
|
1171
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1172
|
+
});
|
|
1173
|
+
drain();
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
return {
|
|
1177
|
+
push(event) {
|
|
1178
|
+
if (disposed) return;
|
|
1179
|
+
if (event.type === "task-completed" && event.data && !event.dataHash) {
|
|
1180
|
+
event = { ...event, dataHash: computeDataHash(event.data) };
|
|
1181
|
+
}
|
|
1182
|
+
journal.append(event);
|
|
1183
|
+
drain();
|
|
1184
|
+
},
|
|
1185
|
+
pushAll(events) {
|
|
1186
|
+
if (disposed) return;
|
|
1187
|
+
for (const event of events) {
|
|
1188
|
+
if (event.type === "task-completed" && event.data && !event.dataHash) {
|
|
1189
|
+
journal.append({ ...event, dataHash: computeDataHash(event.data) });
|
|
1190
|
+
} else {
|
|
1191
|
+
journal.append(event);
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
drain();
|
|
1195
|
+
},
|
|
1196
|
+
resolveCallback(callbackToken, data, errors) {
|
|
1197
|
+
if (disposed) return;
|
|
1198
|
+
const decoded = decodeCallbackToken(callbackToken);
|
|
1199
|
+
if (!decoded) return;
|
|
1200
|
+
const { taskName } = decoded;
|
|
1201
|
+
if (!live.config.tasks[taskName]) return;
|
|
1202
|
+
if (errors && errors.length > 0) {
|
|
1203
|
+
journal.append({
|
|
1204
|
+
type: "task-failed",
|
|
1205
|
+
taskName,
|
|
1206
|
+
error: errors.join("; "),
|
|
1207
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1208
|
+
});
|
|
1209
|
+
} else {
|
|
1210
|
+
const dataHash = data && Object.keys(data).length > 0 ? computeDataHash(data) : void 0;
|
|
1211
|
+
journal.append({
|
|
1212
|
+
type: "task-completed",
|
|
1213
|
+
taskName,
|
|
1214
|
+
data,
|
|
1215
|
+
dataHash,
|
|
1216
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
1219
|
+
drain();
|
|
1220
|
+
},
|
|
1221
|
+
addNode(name, taskConfig) {
|
|
1222
|
+
if (disposed) return;
|
|
1223
|
+
live = addNode(live, name, taskConfig);
|
|
1224
|
+
drain();
|
|
1225
|
+
},
|
|
1226
|
+
removeNode(name) {
|
|
1227
|
+
if (disposed) return;
|
|
1228
|
+
live = removeNode(live, name);
|
|
1229
|
+
},
|
|
1230
|
+
addRequires(nodeName, tokens) {
|
|
1231
|
+
if (disposed) return;
|
|
1232
|
+
live = addRequires(live, nodeName, tokens);
|
|
1233
|
+
drain();
|
|
1234
|
+
},
|
|
1235
|
+
removeRequires(nodeName, tokens) {
|
|
1236
|
+
if (disposed) return;
|
|
1237
|
+
live = removeRequires(live, nodeName, tokens);
|
|
1238
|
+
drain();
|
|
1239
|
+
},
|
|
1240
|
+
addProvides(nodeName, tokens) {
|
|
1241
|
+
if (disposed) return;
|
|
1242
|
+
live = addProvides(live, nodeName, tokens);
|
|
1243
|
+
drain();
|
|
1244
|
+
},
|
|
1245
|
+
removeProvides(nodeName, tokens) {
|
|
1246
|
+
if (disposed) return;
|
|
1247
|
+
live = removeProvides(live, nodeName, tokens);
|
|
1248
|
+
},
|
|
1249
|
+
registerHandler(name, fn) {
|
|
1250
|
+
handlers.set(name, fn);
|
|
1251
|
+
},
|
|
1252
|
+
unregisterHandler(name) {
|
|
1253
|
+
handlers.delete(name);
|
|
1254
|
+
},
|
|
1255
|
+
retrigger(taskName) {
|
|
1256
|
+
if (disposed) return;
|
|
1257
|
+
if (!live.config.tasks[taskName]) return;
|
|
1258
|
+
journal.append({
|
|
1259
|
+
type: "task-restart",
|
|
1260
|
+
taskName,
|
|
1261
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1262
|
+
});
|
|
1263
|
+
drain();
|
|
1264
|
+
},
|
|
1265
|
+
retriggerAll(taskNames) {
|
|
1266
|
+
if (disposed) return;
|
|
1267
|
+
for (const name of taskNames) {
|
|
1268
|
+
if (!live.config.tasks[name]) continue;
|
|
1269
|
+
journal.append({
|
|
1270
|
+
type: "task-restart",
|
|
1271
|
+
taskName: name,
|
|
1272
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1275
|
+
drain();
|
|
1276
|
+
},
|
|
1277
|
+
getState() {
|
|
1278
|
+
return live;
|
|
1279
|
+
},
|
|
1280
|
+
getSchedule() {
|
|
1281
|
+
return schedule(live);
|
|
1282
|
+
},
|
|
1283
|
+
dispose() {
|
|
1284
|
+
disposed = true;
|
|
1285
|
+
}
|
|
1286
|
+
};
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
// src/continuous-event-graph/validate.ts
|
|
1290
|
+
function validateLiveGraph(live) {
|
|
1291
|
+
const issues = [];
|
|
1292
|
+
const { config, state } = live;
|
|
1293
|
+
const tasks = getAllTasks(config);
|
|
1294
|
+
const taskNames = Object.keys(tasks);
|
|
1295
|
+
for (const name of taskNames) {
|
|
1296
|
+
if (!state.tasks[name]) {
|
|
1297
|
+
issues.push({
|
|
1298
|
+
severity: "error",
|
|
1299
|
+
code: "MISSING_STATE",
|
|
1300
|
+
message: `Task "${name}" exists in config but has no state entry`,
|
|
1301
|
+
tasks: [name]
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
for (const name of Object.keys(state.tasks)) {
|
|
1306
|
+
if (!tasks[name]) {
|
|
1307
|
+
issues.push({
|
|
1308
|
+
severity: "warning",
|
|
1309
|
+
code: "ORPHAN_STATE",
|
|
1310
|
+
message: `State entry "${name}" has no corresponding task config`,
|
|
1311
|
+
tasks: [name]
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
for (const name of taskNames) {
|
|
1316
|
+
const ts = state.tasks[name];
|
|
1317
|
+
if (!ts) continue;
|
|
1318
|
+
if (ts.status === TASK_STATUS.RUNNING && !ts.startedAt) {
|
|
1319
|
+
issues.push({
|
|
1320
|
+
severity: "warning",
|
|
1321
|
+
code: "RUNNING_WITHOUT_START",
|
|
1322
|
+
message: `Task "${name}" is running but has no startedAt timestamp`,
|
|
1323
|
+
tasks: [name]
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
if (ts.status === TASK_STATUS.COMPLETED && !ts.completedAt) {
|
|
1327
|
+
issues.push({
|
|
1328
|
+
severity: "warning",
|
|
1329
|
+
code: "COMPLETED_WITHOUT_TIMESTAMP",
|
|
1330
|
+
message: `Task "${name}" is completed but has no completedAt timestamp`,
|
|
1331
|
+
tasks: [name]
|
|
1332
|
+
});
|
|
1333
|
+
}
|
|
1334
|
+
if (ts.status === TASK_STATUS.FAILED) {
|
|
1335
|
+
if (!ts.failedAt) {
|
|
1336
|
+
issues.push({
|
|
1337
|
+
severity: "warning",
|
|
1338
|
+
code: "FAILED_WITHOUT_INFO",
|
|
1339
|
+
message: `Task "${name}" is failed but has no failedAt timestamp`,
|
|
1340
|
+
tasks: [name]
|
|
1341
|
+
});
|
|
1342
|
+
}
|
|
1343
|
+
if (!ts.error) {
|
|
1344
|
+
issues.push({
|
|
1345
|
+
severity: "info",
|
|
1346
|
+
code: "FAILED_WITHOUT_INFO",
|
|
1347
|
+
message: `Task "${name}" is failed but has no error message`,
|
|
1348
|
+
tasks: [name]
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
const expectedOutputs = /* @__PURE__ */ new Set();
|
|
1354
|
+
for (const name of taskNames) {
|
|
1355
|
+
const ts = state.tasks[name];
|
|
1356
|
+
if (ts?.status === TASK_STATUS.COMPLETED) {
|
|
1357
|
+
for (const token of getProvides(tasks[name])) {
|
|
1358
|
+
expectedOutputs.add(token);
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
const actualOutputs = new Set(state.availableOutputs);
|
|
1363
|
+
const allProducible = /* @__PURE__ */ new Set();
|
|
1364
|
+
for (const taskConfig of Object.values(tasks)) {
|
|
1365
|
+
for (const t of getProvides(taskConfig)) allProducible.add(t);
|
|
1366
|
+
if (taskConfig.on) {
|
|
1367
|
+
for (const tokens of Object.values(taskConfig.on)) {
|
|
1368
|
+
for (const t of tokens) allProducible.add(t);
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
if (taskConfig.on_failure) {
|
|
1372
|
+
for (const t of taskConfig.on_failure) allProducible.add(t);
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
for (const token of actualOutputs) {
|
|
1376
|
+
if (!expectedOutputs.has(token) && !allProducible.has(token)) {
|
|
1377
|
+
issues.push({
|
|
1378
|
+
severity: "info",
|
|
1379
|
+
code: "INJECTED_TOKEN",
|
|
1380
|
+
message: `Token "${token}" is available but no task in the graph can produce it (likely injected)`,
|
|
1381
|
+
tokens: [token]
|
|
1382
|
+
});
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
for (const token of expectedOutputs) {
|
|
1386
|
+
if (!actualOutputs.has(token)) {
|
|
1387
|
+
issues.push({
|
|
1388
|
+
severity: "warning",
|
|
1389
|
+
code: "MISSING_OUTPUT",
|
|
1390
|
+
message: `Token "${token}" should be available (its producer completed) but is not in availableOutputs`,
|
|
1391
|
+
tokens: [token]
|
|
1392
|
+
});
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
for (const name of taskNames) {
|
|
1396
|
+
const ts = state.tasks[name];
|
|
1397
|
+
if (!ts) continue;
|
|
1398
|
+
if (ts.executionCount < 0) {
|
|
1399
|
+
issues.push({
|
|
1400
|
+
severity: "error",
|
|
1401
|
+
code: "INVALID_EXECUTION_COUNT",
|
|
1402
|
+
message: `Task "${name}" has negative execution count: ${ts.executionCount}`,
|
|
1403
|
+
tasks: [name]
|
|
1404
|
+
});
|
|
1405
|
+
}
|
|
1406
|
+
const maxExec = tasks[name].maxExecutions;
|
|
1407
|
+
if (maxExec !== void 0 && ts.executionCount > maxExec) {
|
|
1408
|
+
issues.push({
|
|
1409
|
+
severity: "error",
|
|
1410
|
+
code: "EXCEEDED_MAX_EXECUTIONS",
|
|
1411
|
+
message: `Task "${name}" executed ${ts.executionCount} times, exceeding maxExecutions of ${maxExec}`,
|
|
1412
|
+
tasks: [name]
|
|
1413
|
+
});
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
return buildResult(issues);
|
|
1417
|
+
}
|
|
1418
|
+
function validateReactiveGraph(input) {
|
|
1419
|
+
const { graph, handlers } = input;
|
|
1420
|
+
const live = graph.getState();
|
|
1421
|
+
const issues = [];
|
|
1422
|
+
const tasks = getAllTasks(live.config);
|
|
1423
|
+
const taskNames = Object.keys(tasks);
|
|
1424
|
+
const handlerNames = new Set(Object.keys(handlers));
|
|
1425
|
+
const referencedHandlers = /* @__PURE__ */ new Set();
|
|
1426
|
+
for (const name of taskNames) {
|
|
1427
|
+
const taskHandlers = tasks[name].taskHandlers;
|
|
1428
|
+
if (taskHandlers) {
|
|
1429
|
+
for (const h of taskHandlers) {
|
|
1430
|
+
referencedHandlers.add(h);
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
for (const name of taskNames) {
|
|
1435
|
+
const taskHandlers = tasks[name].taskHandlers;
|
|
1436
|
+
if (!taskHandlers) continue;
|
|
1437
|
+
for (const h of taskHandlers) {
|
|
1438
|
+
if (!handlers[h]) {
|
|
1439
|
+
issues.push({
|
|
1440
|
+
severity: "error",
|
|
1441
|
+
code: "MISSING_HANDLER",
|
|
1442
|
+
message: `Task "${name}" references handler "${h}" but it is not in the registry`,
|
|
1443
|
+
tasks: [name]
|
|
1444
|
+
});
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
for (const name of handlerNames) {
|
|
1449
|
+
if (!referencedHandlers.has(name)) {
|
|
1450
|
+
issues.push({
|
|
1451
|
+
severity: "warning",
|
|
1452
|
+
code: "ORPHAN_HANDLER",
|
|
1453
|
+
message: `Handler "${name}" is registered but not referenced by any task's taskHandlers`,
|
|
1454
|
+
tasks: [name]
|
|
1455
|
+
});
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
const liveResult = validateLiveGraph(live);
|
|
1459
|
+
issues.push(...liveResult.issues);
|
|
1460
|
+
return buildResult(issues);
|
|
1461
|
+
}
|
|
1462
|
+
function buildResult(issues) {
|
|
1463
|
+
const errors = issues.filter((i) => i.severity === "error");
|
|
1464
|
+
const warnings = issues.filter((i) => i.severity === "warning");
|
|
1465
|
+
return {
|
|
1466
|
+
valid: errors.length === 0,
|
|
1467
|
+
issues,
|
|
1468
|
+
errors,
|
|
1469
|
+
warnings
|
|
1470
|
+
};
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
// src/continuous-event-graph/mutate.ts
|
|
1474
|
+
function mutateGraph(live, mutations) {
|
|
1475
|
+
let current = live;
|
|
1476
|
+
for (const mutation of mutations) {
|
|
1477
|
+
current = applySingleMutation(current, mutation);
|
|
1478
|
+
}
|
|
1479
|
+
return current;
|
|
1480
|
+
}
|
|
1481
|
+
function applySingleMutation(live, mutation) {
|
|
1482
|
+
switch (mutation.type) {
|
|
1483
|
+
case "add-node":
|
|
1484
|
+
return addNode(live, mutation.name, mutation.config);
|
|
1485
|
+
case "remove-node":
|
|
1486
|
+
return removeNode(live, mutation.name);
|
|
1487
|
+
case "add-requires":
|
|
1488
|
+
return addRequires(live, mutation.taskName, mutation.tokens);
|
|
1489
|
+
case "remove-requires":
|
|
1490
|
+
return removeRequires(live, mutation.taskName, mutation.tokens);
|
|
1491
|
+
case "add-provides":
|
|
1492
|
+
return addProvides(live, mutation.taskName, mutation.tokens);
|
|
1493
|
+
case "remove-provides":
|
|
1494
|
+
return removeProvides(live, mutation.taskName, mutation.tokens);
|
|
1495
|
+
case "inject-tokens":
|
|
1496
|
+
return injectTokens(live, mutation.tokens);
|
|
1497
|
+
case "drain-tokens":
|
|
1498
|
+
return drainTokens(live, mutation.tokens);
|
|
1499
|
+
case "reset-node":
|
|
1500
|
+
return resetNode(live, mutation.name);
|
|
1501
|
+
case "disable-node":
|
|
1502
|
+
return disableNode(live, mutation.name);
|
|
1503
|
+
case "enable-node":
|
|
1504
|
+
return enableNode(live, mutation.name);
|
|
1505
|
+
case "apply-events":
|
|
1506
|
+
return applyEvents(live, mutation.events);
|
|
1507
|
+
default:
|
|
1508
|
+
throw new Error(`Unknown mutation type: ${mutation.type}`);
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
function createCallbackHandler(fn, getResolve) {
|
|
1512
|
+
return async (input) => {
|
|
1513
|
+
const { callbackToken } = input;
|
|
1514
|
+
Promise.resolve(fn(input)).then((data) => getResolve()(callbackToken, data)).catch((err) => getResolve()(callbackToken, {}, [err instanceof Error ? err.message : String(err)]));
|
|
1515
|
+
return "task-initiated";
|
|
1516
|
+
};
|
|
1517
|
+
}
|
|
1518
|
+
function createFireAndForgetHandler(fn, getResolve) {
|
|
1519
|
+
return async (input) => {
|
|
1520
|
+
const { callbackToken } = input;
|
|
1521
|
+
Promise.resolve(fn(input)).then(() => getResolve()(callbackToken, {})).catch(() => getResolve()(callbackToken, {}));
|
|
1522
|
+
return "task-initiated";
|
|
1523
|
+
};
|
|
1524
|
+
}
|
|
1525
|
+
function createShellHandler(options) {
|
|
1526
|
+
const {
|
|
1527
|
+
command: commandTemplate,
|
|
1528
|
+
cwd,
|
|
1529
|
+
env,
|
|
1530
|
+
timeoutMs = 3e4,
|
|
1531
|
+
exitCodeMap,
|
|
1532
|
+
captureOutput = false,
|
|
1533
|
+
getResolve
|
|
1534
|
+
} = options;
|
|
1535
|
+
return async (input) => {
|
|
1536
|
+
const { callbackToken, nodeId } = input;
|
|
1537
|
+
const command = commandTemplate.replace(/\$\{taskName\}/g, nodeId);
|
|
1538
|
+
exec(
|
|
1539
|
+
command,
|
|
1540
|
+
{
|
|
1541
|
+
cwd,
|
|
1542
|
+
env: env ? { ...process.env, ...env } : void 0,
|
|
1543
|
+
timeout: timeoutMs,
|
|
1544
|
+
maxBuffer: 10 * 1024 * 1024
|
|
1545
|
+
// 10MB
|
|
1546
|
+
},
|
|
1547
|
+
(error, stdout, stderr) => {
|
|
1548
|
+
const exitCode = error?.code ?? (error ? 1 : 0);
|
|
1549
|
+
if (exitCode !== 0 && !exitCodeMap?.[exitCode]) {
|
|
1550
|
+
getResolve()(callbackToken, {}, [`Command exited with code ${exitCode}: ${stderr || error?.message}`]);
|
|
1551
|
+
return;
|
|
1552
|
+
}
|
|
1553
|
+
const data = {};
|
|
1554
|
+
if (captureOutput) {
|
|
1555
|
+
data.stdout = stdout;
|
|
1556
|
+
data.stderr = stderr;
|
|
1557
|
+
data.exitCode = exitCode;
|
|
1558
|
+
}
|
|
1559
|
+
getResolve()(callbackToken, data);
|
|
1560
|
+
}
|
|
1561
|
+
);
|
|
1562
|
+
return "task-initiated";
|
|
1563
|
+
};
|
|
1564
|
+
}
|
|
1565
|
+
function detectRuntime(scriptPath) {
|
|
1566
|
+
if (scriptPath.endsWith(".js") || scriptPath.endsWith(".mjs") || scriptPath.endsWith(".ts")) return "node";
|
|
1567
|
+
if (scriptPath.endsWith(".py")) return "python3";
|
|
1568
|
+
if (scriptPath.endsWith(".sh")) return "bash";
|
|
1569
|
+
return "bash";
|
|
1570
|
+
}
|
|
1571
|
+
function createScriptHandler(options) {
|
|
1572
|
+
const {
|
|
1573
|
+
scriptPath,
|
|
1574
|
+
runtime,
|
|
1575
|
+
args = [],
|
|
1576
|
+
cwd,
|
|
1577
|
+
timeoutMs = 6e4,
|
|
1578
|
+
captureOutput = false,
|
|
1579
|
+
getResolve
|
|
1580
|
+
} = options;
|
|
1581
|
+
const resolvedRuntime = runtime ?? detectRuntime(scriptPath);
|
|
1582
|
+
const shellArgs = [ctx_taskName_placeholder, ...args].join(" ");
|
|
1583
|
+
const command = `${resolvedRuntime} ${scriptPath} ${shellArgs}`;
|
|
1584
|
+
return createShellHandler({
|
|
1585
|
+
command: command.replace(ctx_taskName_placeholder, "${taskName}"),
|
|
1586
|
+
cwd,
|
|
1587
|
+
timeoutMs,
|
|
1588
|
+
captureOutput,
|
|
1589
|
+
getResolve
|
|
1590
|
+
});
|
|
1591
|
+
}
|
|
1592
|
+
var ctx_taskName_placeholder = "__TASK_NAME__";
|
|
1593
|
+
function createWebhookHandler(options) {
|
|
1594
|
+
const {
|
|
1595
|
+
url: urlTemplate,
|
|
1596
|
+
method = "POST",
|
|
1597
|
+
headers = {},
|
|
1598
|
+
timeoutMs = 3e4,
|
|
1599
|
+
failOnNon2xx = true,
|
|
1600
|
+
getResolve
|
|
1601
|
+
} = options;
|
|
1602
|
+
return async (input) => {
|
|
1603
|
+
const { callbackToken, nodeId, config } = input;
|
|
1604
|
+
const url = urlTemplate.replace(/\$\{taskName\}/g, nodeId);
|
|
1605
|
+
const body = JSON.stringify({
|
|
1606
|
+
taskName: nodeId,
|
|
1607
|
+
callbackToken,
|
|
1608
|
+
config
|
|
1609
|
+
});
|
|
1610
|
+
const controller = new AbortController();
|
|
1611
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
1612
|
+
fetch(url, {
|
|
1613
|
+
method,
|
|
1614
|
+
headers: { "Content-Type": "application/json", ...headers },
|
|
1615
|
+
body,
|
|
1616
|
+
signal: controller.signal
|
|
1617
|
+
}).then(async (response) => {
|
|
1618
|
+
clearTimeout(timer);
|
|
1619
|
+
if (failOnNon2xx && !response.ok) {
|
|
1620
|
+
const text = await response.text().catch(() => "");
|
|
1621
|
+
getResolve()(callbackToken, {}, [`HTTP ${response.status}: ${text}`]);
|
|
1622
|
+
return;
|
|
1623
|
+
}
|
|
1624
|
+
const data = await response.json().catch(() => ({}));
|
|
1625
|
+
getResolve()(callbackToken, data);
|
|
1626
|
+
}).catch((err) => {
|
|
1627
|
+
clearTimeout(timer);
|
|
1628
|
+
getResolve()(callbackToken, {}, [err instanceof Error ? err.message : String(err)]);
|
|
1629
|
+
});
|
|
1630
|
+
return "task-initiated";
|
|
1631
|
+
};
|
|
1632
|
+
}
|
|
1633
|
+
function createNoopHandler(getResolve, staticData) {
|
|
1634
|
+
return async (input) => {
|
|
1635
|
+
getResolve()(input.callbackToken, staticData ?? {});
|
|
1636
|
+
return "task-initiated";
|
|
1637
|
+
};
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
// src/card-compute/index.ts
|
|
1641
|
+
function deepGet(obj, path) {
|
|
1642
|
+
if (!path || !obj) return void 0;
|
|
1643
|
+
const parts = path.split(".");
|
|
1644
|
+
let cur = obj;
|
|
1645
|
+
for (let i = 0; i < parts.length; i++) {
|
|
1646
|
+
if (cur == null) return void 0;
|
|
1647
|
+
cur = cur[parts[i]];
|
|
1648
|
+
}
|
|
1649
|
+
return cur;
|
|
1650
|
+
}
|
|
1651
|
+
function deepSet(obj, path, value) {
|
|
1652
|
+
const parts = path.split(".");
|
|
1653
|
+
let cur = obj;
|
|
1654
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
1655
|
+
if (cur[parts[i]] == null || typeof cur[parts[i]] !== "object") cur[parts[i]] = {};
|
|
1656
|
+
cur = cur[parts[i]];
|
|
1657
|
+
}
|
|
1658
|
+
cur[parts[parts.length - 1]] = value;
|
|
1659
|
+
}
|
|
1660
|
+
var _fns = {};
|
|
1661
|
+
_fns.sum = (input, _e, opts) => {
|
|
1662
|
+
const a = Array.isArray(input) ? input : [];
|
|
1663
|
+
return opts.field ? a.reduce((s, r) => s + (Number(r[opts.field]) || 0), 0) : a.reduce((s, v) => s + (Number(v) || 0), 0);
|
|
1664
|
+
};
|
|
1665
|
+
_fns.avg = (input, _e, opts) => {
|
|
1666
|
+
const s = _fns.sum(input, _e, opts);
|
|
1667
|
+
const n = Array.isArray(input) ? input.length : 1;
|
|
1668
|
+
return n ? s / n : 0;
|
|
1669
|
+
};
|
|
1670
|
+
_fns.min = (input, _e, opts) => {
|
|
1671
|
+
const a = Array.isArray(input) ? input : [];
|
|
1672
|
+
const vals = opts.field ? a.map((r) => Number(r[opts.field])) : a.map(Number);
|
|
1673
|
+
return vals.length ? Math.min(...vals) : 0;
|
|
1674
|
+
};
|
|
1675
|
+
_fns.max = (input, _e, opts) => {
|
|
1676
|
+
const a = Array.isArray(input) ? input : [];
|
|
1677
|
+
const vals = opts.field ? a.map((r) => Number(r[opts.field])) : a.map(Number);
|
|
1678
|
+
return vals.length ? Math.max(...vals) : 0;
|
|
1679
|
+
};
|
|
1680
|
+
_fns.count = (input) => Array.isArray(input) ? input.length : input != null ? 1 : 0;
|
|
1681
|
+
_fns.first = (input) => Array.isArray(input) ? input[0] : input;
|
|
1682
|
+
_fns.last = (input) => Array.isArray(input) ? input[input.length - 1] : input;
|
|
1683
|
+
_fns.add = (input) => {
|
|
1684
|
+
const a = Array.isArray(input) ? input : [];
|
|
1685
|
+
return a.reduce((s, v) => s + Number(v), 0);
|
|
1686
|
+
};
|
|
1687
|
+
_fns.sub = (input) => {
|
|
1688
|
+
const a = Array.isArray(input) ? input : [];
|
|
1689
|
+
return a.length >= 2 ? Number(a[0]) - Number(a[1]) : 0;
|
|
1690
|
+
};
|
|
1691
|
+
_fns.mul = (input) => {
|
|
1692
|
+
const a = Array.isArray(input) ? input : [];
|
|
1693
|
+
return a.reduce((s, v) => s * Number(v), 1);
|
|
1694
|
+
};
|
|
1695
|
+
_fns.div = (input) => {
|
|
1696
|
+
const a = Array.isArray(input) ? input : [];
|
|
1697
|
+
return a.length >= 2 && Number(a[1]) !== 0 ? Number(a[0]) / Number(a[1]) : 0;
|
|
1698
|
+
};
|
|
1699
|
+
_fns.round = (input, _e, opts) => {
|
|
1700
|
+
const decimals = opts.decimals != null ? opts.decimals : 0;
|
|
1701
|
+
const factor = Math.pow(10, decimals);
|
|
1702
|
+
return Math.round(Number(input) * factor) / factor;
|
|
1703
|
+
};
|
|
1704
|
+
_fns.abs = (input) => Math.abs(Number(input));
|
|
1705
|
+
_fns.mod = (input) => {
|
|
1706
|
+
const a = Array.isArray(input) ? input : [];
|
|
1707
|
+
return a.length >= 2 ? Number(a[0]) % Number(a[1]) : 0;
|
|
1708
|
+
};
|
|
1709
|
+
_fns.gt = (input) => {
|
|
1710
|
+
const a = Array.isArray(input) ? input : [];
|
|
1711
|
+
return a.length >= 2 && Number(a[0]) > Number(a[1]);
|
|
1712
|
+
};
|
|
1713
|
+
_fns.gte = (input) => {
|
|
1714
|
+
const a = Array.isArray(input) ? input : [];
|
|
1715
|
+
return a.length >= 2 && Number(a[0]) >= Number(a[1]);
|
|
1716
|
+
};
|
|
1717
|
+
_fns.lt = (input) => {
|
|
1718
|
+
const a = Array.isArray(input) ? input : [];
|
|
1719
|
+
return a.length >= 2 && Number(a[0]) < Number(a[1]);
|
|
1720
|
+
};
|
|
1721
|
+
_fns.lte = (input) => {
|
|
1722
|
+
const a = Array.isArray(input) ? input : [];
|
|
1723
|
+
return a.length >= 2 && Number(a[0]) <= Number(a[1]);
|
|
1724
|
+
};
|
|
1725
|
+
_fns.eq = (input) => {
|
|
1726
|
+
const a = Array.isArray(input) ? input : [];
|
|
1727
|
+
return a.length >= 2 && a[0] === a[1];
|
|
1728
|
+
};
|
|
1729
|
+
_fns.neq = (input) => {
|
|
1730
|
+
const a = Array.isArray(input) ? input : [];
|
|
1731
|
+
return a.length >= 2 && a[0] !== a[1];
|
|
1732
|
+
};
|
|
1733
|
+
_fns.and = (input) => {
|
|
1734
|
+
const a = Array.isArray(input) ? input : [];
|
|
1735
|
+
return a.every(Boolean);
|
|
1736
|
+
};
|
|
1737
|
+
_fns.or = (input) => {
|
|
1738
|
+
const a = Array.isArray(input) ? input : [];
|
|
1739
|
+
return a.some(Boolean);
|
|
1740
|
+
};
|
|
1741
|
+
_fns.not = (input) => !input;
|
|
1742
|
+
_fns.concat = (input) => {
|
|
1743
|
+
const a = Array.isArray(input) ? input : [];
|
|
1744
|
+
return a.map((v) => v != null ? String(v) : "").join("");
|
|
1745
|
+
};
|
|
1746
|
+
_fns.upper = (input) => String(input || "").toUpperCase();
|
|
1747
|
+
_fns.lower = (input) => String(input || "").toLowerCase();
|
|
1748
|
+
_fns.template = (input, _e, opts) => {
|
|
1749
|
+
let t = String(opts.format || "");
|
|
1750
|
+
if (input && typeof input === "object" && !Array.isArray(input)) {
|
|
1751
|
+
for (const k of Object.keys(input)) {
|
|
1752
|
+
const v = input[k];
|
|
1753
|
+
t = t.split("{{" + k + "}}").join(v != null ? String(v) : "");
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
return t;
|
|
1757
|
+
};
|
|
1758
|
+
_fns.join = (input, _e, opts) => {
|
|
1759
|
+
const a = Array.isArray(input) ? input : [];
|
|
1760
|
+
const sep = opts.separator != null ? String(opts.separator) : ", ";
|
|
1761
|
+
return a.map((v) => v != null ? String(v) : "").join(sep);
|
|
1762
|
+
};
|
|
1763
|
+
_fns.split = (input, _e, opts) => {
|
|
1764
|
+
const sep = opts.separator != null ? String(opts.separator) : ",";
|
|
1765
|
+
return String(input || "").split(sep).map((s) => s.trim());
|
|
1766
|
+
};
|
|
1767
|
+
_fns.trim = (input) => String(input || "").trim();
|
|
1768
|
+
_fns.pluck = (input, _e, opts) => Array.isArray(input) ? input.map((r) => r[opts.field]) : [];
|
|
1769
|
+
_fns.filter = (input, _e, opts) => {
|
|
1770
|
+
if (!Array.isArray(input)) return [];
|
|
1771
|
+
if (opts.field) return input.filter((r) => !!r[opts.field]);
|
|
1772
|
+
return input.filter(Boolean);
|
|
1773
|
+
};
|
|
1774
|
+
_fns.map = (input) => Array.isArray(input) ? input.slice() : [];
|
|
1775
|
+
_fns.sort = (input, _e, opts) => {
|
|
1776
|
+
const a = Array.isArray(input) ? input.slice() : [];
|
|
1777
|
+
const f = opts.field;
|
|
1778
|
+
const dir = opts.direction === "desc" ? -1 : 1;
|
|
1779
|
+
if (f) return a.sort((x, y) => x[f] > y[f] ? dir : x[f] < y[f] ? -dir : 0);
|
|
1780
|
+
return a.sort((x, y) => x > y ? dir : x < y ? -dir : 0);
|
|
1781
|
+
};
|
|
1782
|
+
_fns.slice = (input, _e, opts) => Array.isArray(input) ? input.slice(opts.start || 0, opts.end) : input;
|
|
1783
|
+
_fns.flat = (input, _e, opts) => {
|
|
1784
|
+
const depth = opts.depth != null ? opts.depth : 1;
|
|
1785
|
+
return Array.isArray(input) ? input.flat(depth) : [input];
|
|
1786
|
+
};
|
|
1787
|
+
_fns.unique = (input) => {
|
|
1788
|
+
if (!Array.isArray(input)) return [input];
|
|
1789
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1790
|
+
return input.filter((v) => {
|
|
1791
|
+
const key = typeof v === "object" ? JSON.stringify(v) : v;
|
|
1792
|
+
if (seen.has(key)) return false;
|
|
1793
|
+
seen.add(key);
|
|
1794
|
+
return true;
|
|
1795
|
+
});
|
|
1796
|
+
};
|
|
1797
|
+
_fns.group = (input, _e, opts) => {
|
|
1798
|
+
const a = Array.isArray(input) ? input : [];
|
|
1799
|
+
const g = {};
|
|
1800
|
+
a.forEach((r) => {
|
|
1801
|
+
const k = String(r[opts.field] || "");
|
|
1802
|
+
if (!g[k]) g[k] = [];
|
|
1803
|
+
g[k].push(r);
|
|
1804
|
+
});
|
|
1805
|
+
return g;
|
|
1806
|
+
};
|
|
1807
|
+
_fns.flatten_keys = (input) => {
|
|
1808
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) return [];
|
|
1809
|
+
const result = [];
|
|
1810
|
+
for (const k of Object.keys(input)) {
|
|
1811
|
+
const vals = Array.isArray(input[k]) ? input[k] : [input[k]];
|
|
1812
|
+
vals.forEach((v) => result.push({ key: k, value: v }));
|
|
1813
|
+
}
|
|
1814
|
+
return result;
|
|
1815
|
+
};
|
|
1816
|
+
_fns.entries = (input) => {
|
|
1817
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) return [];
|
|
1818
|
+
return Object.keys(input).map((k) => ({ key: k, value: input[k] }));
|
|
1819
|
+
};
|
|
1820
|
+
_fns.from_entries = (input) => {
|
|
1821
|
+
if (!Array.isArray(input)) return {};
|
|
1822
|
+
const obj = {};
|
|
1823
|
+
input.forEach((item) => {
|
|
1824
|
+
if (item.key != null) obj[item.key] = item.value;
|
|
1825
|
+
});
|
|
1826
|
+
return obj;
|
|
1827
|
+
};
|
|
1828
|
+
_fns.length = (input) => {
|
|
1829
|
+
if (Array.isArray(input)) return input.length;
|
|
1830
|
+
if (typeof input === "string") return input.length;
|
|
1831
|
+
if (input && typeof input === "object") return Object.keys(input).length;
|
|
1832
|
+
return 0;
|
|
1833
|
+
};
|
|
1834
|
+
_fns.get = (input, _e, opts) => deepGet(input, opts.field || opts.path || "");
|
|
1835
|
+
_fns.default = (input, _e, opts) => input != null ? input : opts.value;
|
|
1836
|
+
_fns.coalesce = (input) => {
|
|
1837
|
+
const a = Array.isArray(input) ? input : [];
|
|
1838
|
+
for (let i = 0; i < a.length; i++) {
|
|
1839
|
+
if (a[i] != null) return a[i];
|
|
1840
|
+
}
|
|
1841
|
+
return null;
|
|
1842
|
+
};
|
|
1843
|
+
_fns.now = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
1844
|
+
_fns.diff_days = (input) => {
|
|
1845
|
+
const a = Array.isArray(input) ? input : [];
|
|
1846
|
+
return a.length >= 2 ? Math.floor((new Date(a[0]).getTime() - new Date(a[1]).getTime()) / 864e5) : 0;
|
|
1847
|
+
};
|
|
1848
|
+
_fns.format_date = (input, _e, opts) => {
|
|
1849
|
+
try {
|
|
1850
|
+
const d = new Date(input);
|
|
1851
|
+
if (opts.format === "iso") return d.toISOString();
|
|
1852
|
+
if (opts.format === "date") return d.toLocaleDateString();
|
|
1853
|
+
if (opts.format === "time") return d.toLocaleTimeString();
|
|
1854
|
+
return d.toLocaleDateString();
|
|
1855
|
+
} catch {
|
|
1856
|
+
return String(input);
|
|
1857
|
+
}
|
|
1858
|
+
};
|
|
1859
|
+
_fns.parse_date = (input) => {
|
|
1860
|
+
try {
|
|
1861
|
+
return new Date(input).toISOString();
|
|
1862
|
+
} catch {
|
|
1863
|
+
return null;
|
|
1864
|
+
}
|
|
1865
|
+
};
|
|
1866
|
+
_fns.to_number = (input) => Number(input) || 0;
|
|
1867
|
+
_fns.to_string = (input) => input != null ? String(input) : "";
|
|
1868
|
+
_fns.to_bool = (input) => !!input;
|
|
1869
|
+
_fns.type_of = (input) => Array.isArray(input) ? "array" : typeof input;
|
|
1870
|
+
_fns.is_null = (input) => input == null;
|
|
1871
|
+
_fns.is_empty = (input) => {
|
|
1872
|
+
if (input == null) return true;
|
|
1873
|
+
if (Array.isArray(input)) return input.length === 0;
|
|
1874
|
+
if (typeof input === "string") return input.length === 0;
|
|
1875
|
+
if (typeof input === "object") return Object.keys(input).length === 0;
|
|
1876
|
+
return false;
|
|
1877
|
+
};
|
|
1878
|
+
var _customFns = {};
|
|
1879
|
+
function evalExpr(expr, node) {
|
|
1880
|
+
if (expr == null) return expr;
|
|
1881
|
+
if (typeof expr !== "object" || Array.isArray(expr)) return expr;
|
|
1882
|
+
const e = expr;
|
|
1883
|
+
if (!e.fn) return expr;
|
|
1884
|
+
let input = e.input;
|
|
1885
|
+
if (typeof input === "string" && input.startsWith("state.")) {
|
|
1886
|
+
input = deepGet(node, input);
|
|
1887
|
+
} else if (Array.isArray(input)) {
|
|
1888
|
+
input = input.map((v) => {
|
|
1889
|
+
if (typeof v === "string" && v.startsWith("state.")) return deepGet(node, v);
|
|
1890
|
+
if (v && typeof v === "object" && v.fn) return evalExpr(v, node);
|
|
1891
|
+
return v;
|
|
1892
|
+
});
|
|
1893
|
+
} else if (input && typeof input === "object" && input.fn) {
|
|
1894
|
+
input = evalExpr(input, node);
|
|
1895
|
+
}
|
|
1896
|
+
if (e.fn === "if") {
|
|
1897
|
+
const cond = evalExpr(e.cond, node);
|
|
1898
|
+
if (cond) {
|
|
1899
|
+
return e.then && typeof e.then === "object" && e.then.fn ? evalExpr(e.then, node) : e.then;
|
|
1900
|
+
} else {
|
|
1901
|
+
return e.else && typeof e.else === "object" && e.else.fn ? evalExpr(e.else, node) : e.else;
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
if (e.fn === "filter" && Array.isArray(input) && e.where) {
|
|
1905
|
+
return input.filter((item) => {
|
|
1906
|
+
const tmp = { state: { ...node.state, $: item } };
|
|
1907
|
+
return evalExpr(e.where, tmp);
|
|
1908
|
+
});
|
|
1909
|
+
}
|
|
1910
|
+
if (e.fn === "map" && Array.isArray(input) && e.apply) {
|
|
1911
|
+
return input.map((item) => {
|
|
1912
|
+
const tmp = { state: { ...node.state, $: item } };
|
|
1913
|
+
return evalExpr(e.apply, tmp);
|
|
1914
|
+
});
|
|
1915
|
+
}
|
|
1916
|
+
const fn = _customFns[e.fn] || _fns[e.fn];
|
|
1917
|
+
if (!fn) {
|
|
1918
|
+
console.warn('CardCompute: unknown function "' + e.fn + '"');
|
|
1919
|
+
return void 0;
|
|
1920
|
+
}
|
|
1921
|
+
return fn(input, evalExpr, e);
|
|
1922
|
+
}
|
|
1923
|
+
function run(node) {
|
|
1924
|
+
if (!node || !node.compute) return node;
|
|
1925
|
+
if (!node.state) node.state = {};
|
|
1926
|
+
for (const key of Object.keys(node.compute)) {
|
|
1927
|
+
try {
|
|
1928
|
+
const val = evalExpr(node.compute[key], node);
|
|
1929
|
+
deepSet(node.state, key, val);
|
|
1930
|
+
} catch (err) {
|
|
1931
|
+
console.error(`CardCompute.run error on "${node.id || "?"}.${key}":`, err);
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
return node;
|
|
1935
|
+
}
|
|
1936
|
+
function resolve(node, path) {
|
|
1937
|
+
return deepGet(node, path);
|
|
1938
|
+
}
|
|
1939
|
+
function registerFunction(name, fn) {
|
|
1940
|
+
_customFns[name] = fn;
|
|
1941
|
+
}
|
|
1942
|
+
var VALID_ELEMENT_KINDS = /* @__PURE__ */ new Set([
|
|
1943
|
+
"metric",
|
|
1944
|
+
"table",
|
|
1945
|
+
"chart",
|
|
1946
|
+
"form",
|
|
1947
|
+
"filter",
|
|
1948
|
+
"list",
|
|
1949
|
+
"notes",
|
|
1950
|
+
"todo",
|
|
1951
|
+
"alert",
|
|
1952
|
+
"narrative",
|
|
1953
|
+
"badge",
|
|
1954
|
+
"text",
|
|
1955
|
+
"markdown",
|
|
1956
|
+
"custom"
|
|
1957
|
+
]);
|
|
1958
|
+
var VALID_SOURCE_KINDS = /* @__PURE__ */ new Set(["api", "websocket", "static", "llm"]);
|
|
1959
|
+
var VALID_STATUSES = /* @__PURE__ */ new Set(["fresh", "stale", "loading", "error"]);
|
|
1960
|
+
var CARD_ALLOWED_KEYS = /* @__PURE__ */ new Set(["id", "type", "meta", "data", "view", "state", "compute"]);
|
|
1961
|
+
var SOURCE_ALLOWED_KEYS = /* @__PURE__ */ new Set(["id", "type", "meta", "data", "source", "state", "compute"]);
|
|
1962
|
+
function validateNode(node) {
|
|
1963
|
+
const errors = [];
|
|
1964
|
+
if (!node || typeof node !== "object" || Array.isArray(node)) {
|
|
1965
|
+
return { ok: false, errors: ["Node must be a non-null object"] };
|
|
1966
|
+
}
|
|
1967
|
+
const n = node;
|
|
1968
|
+
if (typeof n.id !== "string" || !n.id) {
|
|
1969
|
+
errors.push("id: required, must be a non-empty string");
|
|
1970
|
+
}
|
|
1971
|
+
if (n.type !== "card" && n.type !== "source") {
|
|
1972
|
+
errors.push('type: must be "card" or "source"');
|
|
1973
|
+
return { ok: false, errors };
|
|
1974
|
+
}
|
|
1975
|
+
const allowed = n.type === "card" ? CARD_ALLOWED_KEYS : SOURCE_ALLOWED_KEYS;
|
|
1976
|
+
for (const key of Object.keys(n)) {
|
|
1977
|
+
if (!allowed.has(key)) errors.push(`Unknown top-level key: "${key}"`);
|
|
1978
|
+
}
|
|
1979
|
+
if (n.state == null || typeof n.state !== "object" || Array.isArray(n.state)) {
|
|
1980
|
+
errors.push("state: required, must be an object");
|
|
1981
|
+
} else {
|
|
1982
|
+
const state = n.state;
|
|
1983
|
+
if (state.status != null && !VALID_STATUSES.has(state.status)) {
|
|
1984
|
+
errors.push(`state.status: must be one of: ${[...VALID_STATUSES].join(", ")}`);
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
if (n.meta != null) {
|
|
1988
|
+
if (typeof n.meta !== "object" || Array.isArray(n.meta)) {
|
|
1989
|
+
errors.push("meta: must be an object");
|
|
1990
|
+
} else {
|
|
1991
|
+
const meta = n.meta;
|
|
1992
|
+
if (meta.title != null && typeof meta.title !== "string") errors.push("meta.title: must be a string");
|
|
1993
|
+
if (meta.tags != null && !Array.isArray(meta.tags)) errors.push("meta.tags: must be an array");
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
if (n.data != null) {
|
|
1997
|
+
if (typeof n.data !== "object" || Array.isArray(n.data)) {
|
|
1998
|
+
errors.push("data: must be an object");
|
|
1999
|
+
} else {
|
|
2000
|
+
const data = n.data;
|
|
2001
|
+
if (data.requires != null && !Array.isArray(data.requires)) errors.push("data.requires: must be an array of strings");
|
|
2002
|
+
if (data.provides != null && (typeof data.provides !== "object" || Array.isArray(data.provides))) errors.push("data.provides: must be an object");
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
if (n.compute != null) {
|
|
2006
|
+
if (typeof n.compute !== "object" || Array.isArray(n.compute)) {
|
|
2007
|
+
errors.push("compute: must be an object");
|
|
2008
|
+
} else {
|
|
2009
|
+
for (const [key, expr] of Object.entries(n.compute)) {
|
|
2010
|
+
if (!expr || typeof expr !== "object" || Array.isArray(expr)) {
|
|
2011
|
+
errors.push(`compute.${key}: must be a compute expression object`);
|
|
2012
|
+
} else if (!expr.fn) {
|
|
2013
|
+
errors.push(`compute.${key}: missing required "fn" property`);
|
|
2014
|
+
} else {
|
|
2015
|
+
const fn = expr.fn;
|
|
2016
|
+
if (!_fns[fn] && !_customFns[fn]) {
|
|
2017
|
+
errors.push(`compute.${key}: unknown function "${fn}"`);
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
if (n.type === "card") {
|
|
2024
|
+
if (n.source != null) errors.push('Card nodes must not have "source" \u2014 use type "source" instead');
|
|
2025
|
+
if (n.view == null || typeof n.view !== "object" || Array.isArray(n.view)) {
|
|
2026
|
+
errors.push("view: required for card nodes, must be an object");
|
|
2027
|
+
} else {
|
|
2028
|
+
const view = n.view;
|
|
2029
|
+
if (!Array.isArray(view.elements) || view.elements.length === 0) {
|
|
2030
|
+
errors.push("view.elements: required, must be a non-empty array");
|
|
2031
|
+
} else {
|
|
2032
|
+
view.elements.forEach((elem, i) => {
|
|
2033
|
+
if (!elem || typeof elem !== "object") {
|
|
2034
|
+
errors.push(`view.elements[${i}]: must be an object`);
|
|
2035
|
+
return;
|
|
2036
|
+
}
|
|
2037
|
+
if (!elem.kind || typeof elem.kind !== "string") {
|
|
2038
|
+
errors.push(`view.elements[${i}].kind: required, must be a string`);
|
|
2039
|
+
} else if (!VALID_ELEMENT_KINDS.has(elem.kind)) {
|
|
2040
|
+
errors.push(`view.elements[${i}].kind: unknown kind "${elem.kind}". Valid: ${[...VALID_ELEMENT_KINDS].join(", ")}`);
|
|
2041
|
+
}
|
|
2042
|
+
if (elem.data != null && (typeof elem.data !== "object" || Array.isArray(elem.data))) {
|
|
2043
|
+
errors.push(`view.elements[${i}].data: must be an object`);
|
|
2044
|
+
}
|
|
2045
|
+
});
|
|
2046
|
+
}
|
|
2047
|
+
if (view.layout != null && (typeof view.layout !== "object" || Array.isArray(view.layout))) {
|
|
2048
|
+
errors.push("view.layout: must be an object");
|
|
2049
|
+
}
|
|
2050
|
+
if (view.features != null && (typeof view.features !== "object" || Array.isArray(view.features))) {
|
|
2051
|
+
errors.push("view.features: must be an object");
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
if (n.type === "source") {
|
|
2056
|
+
if (n.view != null) errors.push('Source nodes must not have "view" \u2014 use type "card" instead');
|
|
2057
|
+
if (n.source == null || typeof n.source !== "object" || Array.isArray(n.source)) {
|
|
2058
|
+
errors.push("source: required for source nodes, must be an object");
|
|
2059
|
+
} else {
|
|
2060
|
+
const src = n.source;
|
|
2061
|
+
if (!src.kind || !VALID_SOURCE_KINDS.has(src.kind)) {
|
|
2062
|
+
errors.push(`source.kind: required, must be one of: ${[...VALID_SOURCE_KINDS].join(", ")}`);
|
|
2063
|
+
}
|
|
2064
|
+
if (typeof src.bindTo !== "string" || !src.bindTo) {
|
|
2065
|
+
errors.push("source.bindTo: required, must be a state path string");
|
|
2066
|
+
} else if (!src.bindTo.startsWith("state.")) {
|
|
2067
|
+
errors.push('source.bindTo: must start with "state."');
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
return { ok: errors.length === 0, errors };
|
|
2072
|
+
}
|
|
2073
|
+
var CardCompute = {
|
|
2074
|
+
run,
|
|
2075
|
+
eval: evalExpr,
|
|
2076
|
+
resolve,
|
|
2077
|
+
validate: validateNode,
|
|
2078
|
+
registerFunction,
|
|
2079
|
+
get functions() {
|
|
2080
|
+
const all = {};
|
|
2081
|
+
for (const k of Object.keys(_fns)) all[k] = _fns[k];
|
|
2082
|
+
for (const k of Object.keys(_customFns)) all[k] = _customFns[k];
|
|
2083
|
+
return all;
|
|
2084
|
+
}
|
|
2085
|
+
};
|
|
2086
|
+
|
|
2087
|
+
// src/continuous-event-graph/live-cards-bridge.ts
|
|
2088
|
+
function liveCardsToReactiveGraph(input, options = {}) {
|
|
2089
|
+
let cards;
|
|
2090
|
+
let boardSettings = {};
|
|
2091
|
+
let boardId;
|
|
2092
|
+
if (!Array.isArray(input) && "nodes" in input) {
|
|
2093
|
+
const board = input;
|
|
2094
|
+
cards = board.nodes;
|
|
2095
|
+
boardId = board.id;
|
|
2096
|
+
boardSettings = board.settings ?? {};
|
|
2097
|
+
} else {
|
|
2098
|
+
cards = input;
|
|
2099
|
+
}
|
|
2100
|
+
const {
|
|
2101
|
+
sourceHandlers = {},
|
|
2102
|
+
defaultSourceHandler,
|
|
2103
|
+
cardHandlers = {},
|
|
2104
|
+
reactiveOptions = {},
|
|
2105
|
+
graphSettings = {},
|
|
2106
|
+
executionId
|
|
2107
|
+
} = options;
|
|
2108
|
+
const cardMap = /* @__PURE__ */ new Map();
|
|
2109
|
+
for (const card of cards) {
|
|
2110
|
+
if (cardMap.has(card.id)) {
|
|
2111
|
+
throw new Error(`Duplicate card ID: "${card.id}"`);
|
|
2112
|
+
}
|
|
2113
|
+
cardMap.set(card.id, card);
|
|
2114
|
+
}
|
|
2115
|
+
const sharedState = options.sharedState ?? /* @__PURE__ */ new Map();
|
|
2116
|
+
const tasks = {};
|
|
2117
|
+
for (const card of cards) {
|
|
2118
|
+
const requires = card.data?.requires ?? [];
|
|
2119
|
+
for (const req of requires) {
|
|
2120
|
+
if (!cardMap.has(req)) {
|
|
2121
|
+
throw new Error(`Card "${card.id}" requires "${req}" but no card with that ID exists`);
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
tasks[card.id] = {
|
|
2125
|
+
requires: requires.length > 0 ? requires : void 0,
|
|
2126
|
+
provides: [card.id],
|
|
2127
|
+
taskHandlers: [card.id],
|
|
2128
|
+
// each card has a named handler matching its ID
|
|
2129
|
+
description: card.meta?.title ?? `${card.type}: ${card.id}`
|
|
2130
|
+
};
|
|
2131
|
+
}
|
|
2132
|
+
const config = {
|
|
2133
|
+
id: boardId ?? `live-cards-${Date.now()}`,
|
|
2134
|
+
settings: {
|
|
2135
|
+
completion: "manual",
|
|
2136
|
+
execution_mode: "eligibility-mode",
|
|
2137
|
+
...boardSettings,
|
|
2138
|
+
...graphSettings
|
|
2139
|
+
},
|
|
2140
|
+
tasks
|
|
2141
|
+
};
|
|
2142
|
+
const handlers = {};
|
|
2143
|
+
let graphRef = null;
|
|
2144
|
+
const getResolve = () => (token, data, errors) => {
|
|
2145
|
+
graphRef.resolveCallback(token, data, errors);
|
|
2146
|
+
};
|
|
2147
|
+
for (const card of cards) {
|
|
2148
|
+
if (card.type === "source") {
|
|
2149
|
+
handlers[card.id] = buildSourceHandler(card, sourceHandlers, defaultSourceHandler, sharedState, getResolve);
|
|
2150
|
+
} else {
|
|
2151
|
+
handlers[card.id] = buildCardHandler(card, cardHandlers, sharedState, cardMap, getResolve);
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
const graph = createReactiveGraph(
|
|
2155
|
+
config,
|
|
2156
|
+
{
|
|
2157
|
+
...reactiveOptions,
|
|
2158
|
+
handlers
|
|
2159
|
+
},
|
|
2160
|
+
executionId
|
|
2161
|
+
);
|
|
2162
|
+
graphRef = graph;
|
|
2163
|
+
return { graph, config, handlers, cards: cardMap, sharedState };
|
|
2164
|
+
}
|
|
2165
|
+
function buildSourceHandler(card, sourceHandlers, defaultSourceHandler, sharedState, getResolve) {
|
|
2166
|
+
if (sourceHandlers[card.id]) {
|
|
2167
|
+
const userHandler = sourceHandlers[card.id];
|
|
2168
|
+
return async (input) => {
|
|
2169
|
+
return userHandler(input);
|
|
2170
|
+
};
|
|
2171
|
+
}
|
|
2172
|
+
if (defaultSourceHandler) {
|
|
2173
|
+
const factoryHandler = defaultSourceHandler(card);
|
|
2174
|
+
return async (input) => {
|
|
2175
|
+
return factoryHandler(input);
|
|
2176
|
+
};
|
|
2177
|
+
}
|
|
2178
|
+
return async (input) => {
|
|
2179
|
+
const state = { ...card.state };
|
|
2180
|
+
sharedState.set(card.id, state);
|
|
2181
|
+
getResolve()(input.callbackToken, state);
|
|
2182
|
+
return "task-initiated";
|
|
2183
|
+
};
|
|
2184
|
+
}
|
|
2185
|
+
function buildCardHandler(card, cardHandlers, sharedState, cardMap, getResolve) {
|
|
2186
|
+
if (cardHandlers[card.id]) {
|
|
2187
|
+
const userHandler = cardHandlers[card.id];
|
|
2188
|
+
return async (input) => {
|
|
2189
|
+
return userHandler(input);
|
|
2190
|
+
};
|
|
2191
|
+
}
|
|
2192
|
+
return async (input) => {
|
|
2193
|
+
const computeNode = {
|
|
2194
|
+
id: card.id,
|
|
2195
|
+
state: { ...card.state },
|
|
2196
|
+
compute: card.compute
|
|
2197
|
+
};
|
|
2198
|
+
const requires = card.data?.requires ?? [];
|
|
2199
|
+
for (const upstreamId of requires) {
|
|
2200
|
+
const upstreamState = sharedState.get(upstreamId);
|
|
2201
|
+
if (upstreamState) {
|
|
2202
|
+
computeNode.state[upstreamId] = upstreamState;
|
|
2203
|
+
}
|
|
2204
|
+
const upstreamCard = cardMap.get(upstreamId);
|
|
2205
|
+
if (upstreamCard?.data?.provides && upstreamState) {
|
|
2206
|
+
for (const [key, bindRef] of Object.entries(upstreamCard.data.provides)) {
|
|
2207
|
+
if (typeof bindRef === "string" && bindRef.startsWith("state.")) {
|
|
2208
|
+
const path = bindRef.slice(6);
|
|
2209
|
+
const value = deepGet2(upstreamState, path);
|
|
2210
|
+
if (value !== void 0) {
|
|
2211
|
+
computeNode.state[key] = value;
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
CardCompute.run(computeNode);
|
|
2218
|
+
const resultState = { ...computeNode.state };
|
|
2219
|
+
sharedState.set(card.id, resultState);
|
|
2220
|
+
getResolve()(input.callbackToken, resultState);
|
|
2221
|
+
return "task-initiated";
|
|
2222
|
+
};
|
|
2223
|
+
}
|
|
2224
|
+
function deepGet2(obj, path) {
|
|
2225
|
+
if (!path || !obj) return void 0;
|
|
2226
|
+
const parts = path.split(".");
|
|
2227
|
+
let cur = obj;
|
|
2228
|
+
for (const part of parts) {
|
|
2229
|
+
if (cur == null) return void 0;
|
|
2230
|
+
cur = cur[part];
|
|
2231
|
+
}
|
|
2232
|
+
return cur;
|
|
2233
|
+
}
|
|
913
2234
|
|
|
914
|
-
export { addNode, addProvides, addRequires, applyEvent, createLiveGraph, disableNode, drainTokens, enableNode, getDownstream, getNode, getUnreachableNodes, getUnreachableTokens, getUpstream, injectTokens, inspect, removeNode, removeProvides, removeRequires, resetNode, restore, schedule, snapshot };
|
|
2235
|
+
export { FileJournal, MemoryJournal, addNode, addProvides, addRequires, applyEvent, applyEvents, computeDataHash, createCallbackHandler, createFireAndForgetHandler, createLiveGraph, createNoopHandler, createReactiveGraph, createScriptHandler, createShellHandler, createWebhookHandler, disableNode, drainTokens, enableNode, getDownstream, getNode, getUnreachableNodes, getUnreachableTokens, getUpstream, injectTokens, inspect, liveCardsToReactiveGraph, mutateGraph, removeNode, removeProvides, removeRequires, resetNode, restore, schedule, snapshot, validateLiveGraph, validateReactiveGraph };
|
|
915
2236
|
//# sourceMappingURL=index.js.map
|
|
916
2237
|
//# sourceMappingURL=index.js.map
|