yaml-flow 3.0.0 → 3.1.1

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