yaml-flow 3.0.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.
Files changed (34) 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/package.json +1 -1
@@ -1,4 +1,7 @@
1
1
  import { existsSync, writeFileSync, appendFileSync, readFileSync } from 'fs';
2
+ import { createHash } from 'crypto';
3
+ import { exec } from 'child_process';
4
+ import 'ajv-formats';
2
5
 
3
6
  // src/event-graph/constants.ts
4
7
  var TASK_STATUS = {
@@ -64,7 +67,7 @@ function groupTasksByProvides(candidateTaskNames, tasks) {
64
67
 
65
68
  // src/event-graph/task-transitions.ts
66
69
  function applyTaskStart(state, taskName) {
67
- const existingTask = state.tasks[taskName] ?? createDefaultTaskState();
70
+ const existingTask = state.tasks[taskName] ?? createDefaultGraphEngineStore();
68
71
  const updatedTask = {
69
72
  ...existingTask,
70
73
  status: "running",
@@ -79,8 +82,8 @@ function applyTaskStart(state, taskName) {
79
82
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
80
83
  };
81
84
  }
82
- function applyTaskCompletion(state, graph, taskName, result, dataHash) {
83
- const existingTask = state.tasks[taskName] ?? createDefaultTaskState();
85
+ function applyTaskCompletion(state, graph, taskName, result, dataHash, data) {
86
+ const existingTask = state.tasks[taskName] ?? createDefaultGraphEngineStore();
84
87
  const taskConfig = graph.tasks[taskName];
85
88
  if (!taskConfig) {
86
89
  throw new Error(`Task "${taskName}" not found in graph`);
@@ -112,6 +115,7 @@ function applyTaskCompletion(state, graph, taskName, result, dataHash) {
112
115
  executionCount: existingTask.executionCount + 1,
113
116
  lastEpoch: existingTask.executionCount + 1,
114
117
  lastDataHash: dataHash,
118
+ data,
115
119
  lastConsumedHashes,
116
120
  error: void 0
117
121
  };
@@ -124,7 +128,7 @@ function applyTaskCompletion(state, graph, taskName, result, dataHash) {
124
128
  };
125
129
  }
126
130
  function applyTaskFailure(state, graph, taskName, error) {
127
- const existingTask = state.tasks[taskName] ?? createDefaultTaskState();
131
+ const existingTask = state.tasks[taskName] ?? createDefaultGraphEngineStore();
128
132
  const taskConfig = graph.tasks[taskName];
129
133
  if (taskConfig?.retry) {
130
134
  const retryCount = existingTask.retryCount + 1;
@@ -167,7 +171,7 @@ function applyTaskFailure(state, graph, taskName, error) {
167
171
  };
168
172
  }
169
173
  function applyTaskProgress(state, taskName, message, progress) {
170
- const existingTask = state.tasks[taskName] ?? createDefaultTaskState();
174
+ const existingTask = state.tasks[taskName] ?? createDefaultGraphEngineStore();
171
175
  const updatedTask = {
172
176
  ...existingTask,
173
177
  progress: typeof progress === "number" ? progress : existingTask.progress,
@@ -183,7 +187,27 @@ function applyTaskProgress(state, taskName, message, progress) {
183
187
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
184
188
  };
185
189
  }
186
- function createDefaultTaskState() {
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() {
187
211
  return {
188
212
  status: "not-started",
189
213
  executionCount: 0,
@@ -199,7 +223,7 @@ function createLiveGraph(config, executionId) {
199
223
  const id = executionId ?? `live-${Date.now()}`;
200
224
  const tasks = {};
201
225
  for (const taskName of Object.keys(config.tasks)) {
202
- tasks[taskName] = createDefaultTaskState2();
226
+ tasks[taskName] = createDefaultGraphEngineStore2();
203
227
  }
204
228
  const state = {
205
229
  status: "running",
@@ -227,7 +251,7 @@ function applyEvent(live, event) {
227
251
  newState = applyTaskStart(state, event.taskName);
228
252
  break;
229
253
  case "task-completed":
230
- newState = applyTaskCompletion(state, config, event.taskName, event.result, event.dataHash);
254
+ newState = applyTaskCompletion(state, config, event.taskName, event.result, event.dataHash, event.data);
231
255
  break;
232
256
  case "task-failed":
233
257
  newState = applyTaskFailure(state, config, event.taskName, event.error);
@@ -235,6 +259,9 @@ function applyEvent(live, event) {
235
259
  case "task-progress":
236
260
  newState = applyTaskProgress(state, event.taskName, event.message, event.progress);
237
261
  break;
262
+ case "task-restart":
263
+ newState = applyTaskRestart(state, event.taskName);
264
+ break;
238
265
  case "inject-tokens":
239
266
  newState = {
240
267
  ...state,
@@ -262,7 +289,7 @@ function addNode(live, name, taskConfig) {
262
289
  },
263
290
  state: {
264
291
  ...live.state,
265
- tasks: { ...live.state.tasks, [name]: createDefaultTaskState2() },
292
+ tasks: { ...live.state.tasks, [name]: createDefaultGraphEngineStore2() },
266
293
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
267
294
  }
268
295
  };
@@ -379,7 +406,7 @@ function resetNode(live, name) {
379
406
  ...live.state,
380
407
  tasks: {
381
408
  ...live.state.tasks,
382
- [name]: createDefaultTaskState2()
409
+ [name]: createDefaultGraphEngineStore2()
383
410
  },
384
411
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
385
412
  }
@@ -418,7 +445,7 @@ function enableNode(live, name) {
418
445
  function getNode(live, name) {
419
446
  const config = live.config.tasks[name];
420
447
  if (!config) return void 0;
421
- const state = live.state.tasks[name] ?? createDefaultTaskState2();
448
+ const state = live.state.tasks[name] ?? createDefaultGraphEngineStore2();
422
449
  return { name, config, state };
423
450
  }
424
451
  function snapshot(live) {
@@ -456,7 +483,7 @@ function restore(data) {
456
483
  }
457
484
  return { config, state };
458
485
  }
459
- function createDefaultTaskState2() {
486
+ function createDefaultGraphEngineStore2() {
460
487
  return {
461
488
  status: "not-started",
462
489
  executionCount: 0,
@@ -1013,23 +1040,43 @@ var FileJournal = class {
1013
1040
  }
1014
1041
  }
1015
1042
  };
1016
-
1017
- // src/continuous-event-graph/reactive.ts
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
+ }
1018
1071
  function createReactiveGraph(config, options, executionId) {
1019
1072
  const {
1020
1073
  handlers: initialHandlers,
1021
- maxDispatchRetries = 3,
1022
- defaultTimeoutMs = 3e4,
1023
1074
  journal = new MemoryJournal(),
1024
- onDispatchFailed,
1025
- onAbandoned,
1026
1075
  onDrain
1027
1076
  } = options;
1028
1077
  let live = createLiveGraph(config, executionId);
1029
1078
  let disposed = false;
1030
1079
  const handlers = new Map(Object.entries(initialHandlers));
1031
- const dispatched = /* @__PURE__ */ new Map();
1032
- const timeoutTimers = /* @__PURE__ */ new Map();
1033
1080
  let draining = false;
1034
1081
  let drainQueued = false;
1035
1082
  function drain() {
@@ -1049,201 +1096,183 @@ function createReactiveGraph(config, options, executionId) {
1049
1096
  }
1050
1097
  }
1051
1098
  function drainOnce() {
1052
- sweepTimeouts();
1053
1099
  const events = journal.drain();
1054
- for (const event of events) {
1055
- if (event.type === "task-completed" || event.type === "task-failed") {
1056
- const taskName = event.taskName;
1057
- dispatched.delete(taskName);
1058
- clearTimeout(timeoutTimers.get(taskName));
1059
- timeoutTimers.delete(taskName);
1060
- }
1061
- }
1062
1100
  if (events.length > 0) {
1063
1101
  live = applyEvents(live, events);
1064
1102
  }
1065
1103
  const result = schedule(live);
1066
- if (onDrain && events.length > 0) {
1067
- onDrain(events, live, result);
1104
+ if (events.length > 0) {
1105
+ onDrain?.(events, live, result);
1068
1106
  }
1069
1107
  for (const taskName of result.eligible) {
1070
- if (dispatched.has(taskName)) continue;
1071
1108
  dispatchTask(taskName);
1072
1109
  }
1073
- for (const [taskName, entry] of dispatched) {
1074
- if (entry.status === "retry-queued") {
1075
- dispatchTask(taskName);
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;
1076
1127
  }
1077
1128
  }
1129
+ return state;
1078
1130
  }
1079
- function dispatchTask(taskName) {
1080
- const handler = handlers.get(taskName);
1081
- if (!handler) {
1082
- journal.append({
1083
- type: "task-failed",
1084
- taskName,
1085
- error: `No handler registered for task "${taskName}"`,
1086
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
1087
- });
1088
- drainQueued = true;
1089
- return;
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
+ }
1090
1151
  }
1091
- const existing = dispatched.get(taskName);
1092
- const attempt = existing ? existing.dispatchAttempts + 1 : 1;
1093
- if (attempt > maxDispatchRetries) {
1094
- dispatched.set(taskName, {
1095
- status: "abandoned",
1096
- dispatchedAt: existing?.dispatchedAt ?? Date.now(),
1097
- dispatchAttempts: attempt - 1,
1098
- lastError: existing?.lastError
1099
- });
1100
- onAbandoned?.(taskName);
1101
- journal.append({
1102
- type: "task-failed",
1103
- taskName,
1104
- error: `dispatch-abandoned: handler unreachable after ${attempt - 1} attempts${existing?.lastError ? ` (${existing.lastError})` : ""}`,
1105
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
1106
- });
1107
- drainQueued = true;
1152
+ }
1153
+ function dispatchTask(taskName) {
1154
+ const taskConfig = live.config.tasks[taskName];
1155
+ const handlerNames = taskConfig?.taskHandlers;
1156
+ if (!handlerNames || handlerNames.length === 0) {
1108
1157
  return;
1109
1158
  }
1110
- dispatched.set(taskName, {
1111
- status: "initiated",
1112
- dispatchedAt: Date.now(),
1113
- dispatchAttempts: attempt
1114
- });
1115
1159
  journal.append({
1116
1160
  type: "task-started",
1117
1161
  taskName,
1118
1162
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1119
1163
  });
1120
- if (defaultTimeoutMs > 0) {
1121
- const timer = setTimeout(() => {
1122
- if (disposed) return;
1123
- const entry = dispatched.get(taskName);
1124
- if (entry?.status === "initiated") {
1125
- dispatched.set(taskName, {
1126
- ...entry,
1127
- status: "timed-out"
1128
- });
1129
- dispatched.set(taskName, {
1130
- ...entry,
1131
- status: entry.dispatchAttempts >= maxDispatchRetries ? "abandoned" : "retry-queued"
1132
- });
1133
- if (entry.dispatchAttempts >= maxDispatchRetries) {
1134
- onAbandoned?.(taskName);
1135
- journal.append({
1136
- type: "task-failed",
1137
- taskName,
1138
- error: `dispatch-timeout: no callback after ${defaultTimeoutMs}ms (${entry.dispatchAttempts} attempts)`,
1139
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
1140
- });
1141
- }
1142
- drain();
1143
- }
1144
- }, defaultTimeoutMs);
1145
- timeoutTimers.set(taskName, timer);
1146
- }
1147
- const ctx = {
1148
- taskName,
1149
- live,
1150
- config: live.config.tasks[taskName]
1151
- };
1152
- try {
1153
- const promise = handler(ctx);
1154
- promise.then(
1155
- (handlerResult) => {
1156
- if (disposed) return;
1157
- clearTimeout(timeoutTimers.get(taskName));
1158
- timeoutTimers.delete(taskName);
1159
- journal.append({
1160
- type: "task-completed",
1161
- taskName,
1162
- result: handlerResult.result,
1163
- data: handlerResult.data,
1164
- dataHash: handlerResult.dataHash,
1165
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
1166
- });
1167
- drain();
1168
- },
1169
- (error) => {
1170
- if (disposed) return;
1171
- clearTimeout(timeoutTimers.get(taskName));
1172
- timeoutTimers.delete(taskName);
1173
- journal.append({
1174
- type: "task-failed",
1175
- taskName,
1176
- error: error.message ?? String(error),
1177
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
1178
- });
1179
- drain();
1180
- }
1181
- );
1182
- } catch (syncError) {
1183
- const err = syncError instanceof Error ? syncError : new Error(String(syncError));
1184
- dispatched.set(taskName, {
1185
- status: "dispatch-failed",
1186
- dispatchedAt: Date.now(),
1187
- dispatchAttempts: attempt,
1188
- lastError: err.message
1189
- });
1190
- onDispatchFailed?.(taskName, err, attempt);
1191
- dispatched.set(taskName, {
1192
- ...dispatched.get(taskName),
1193
- status: "retry-queued"
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()
1194
1172
  });
1195
- drainQueued = true;
1196
- }
1197
- }
1198
- function sweepTimeouts() {
1199
- const now = Date.now();
1200
- for (const [taskName, entry] of dispatched) {
1201
- if (entry.status !== "initiated") continue;
1202
- if (defaultTimeoutMs <= 0) continue;
1203
- if (now - entry.dispatchedAt >= defaultTimeoutMs) {
1204
- dispatched.set(taskName, {
1205
- ...entry,
1206
- status: entry.dispatchAttempts >= maxDispatchRetries ? "abandoned" : "retry-queued"
1207
- });
1208
- if (entry.dispatchAttempts >= maxDispatchRetries) {
1209
- onAbandoned?.(taskName);
1210
- journal.append({
1211
- type: "task-failed",
1212
- taskName,
1213
- error: `dispatch-timeout: no callback after ${defaultTimeoutMs}ms (${entry.dispatchAttempts} attempts)`,
1214
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
1215
- });
1216
- }
1217
- clearTimeout(timeoutTimers.get(taskName));
1218
- timeoutTimers.delete(taskName);
1219
- }
1220
- }
1173
+ drain();
1174
+ });
1221
1175
  }
1222
1176
  return {
1223
1177
  push(event) {
1224
1178
  if (disposed) return;
1225
- live = applyEvent(live, event);
1179
+ if (event.type === "task-completed" && event.data && !event.dataHash) {
1180
+ event = { ...event, dataHash: computeDataHash(event.data) };
1181
+ }
1182
+ journal.append(event);
1226
1183
  drain();
1227
1184
  },
1228
1185
  pushAll(events) {
1229
1186
  if (disposed) return;
1230
- if (events.length === 0) return;
1231
- live = applyEvents(live, events);
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
+ }
1232
1194
  drain();
1233
1195
  },
1234
- addNode(name, taskConfig, handler) {
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) {
1235
1222
  if (disposed) return;
1236
1223
  live = addNode(live, name, taskConfig);
1237
- handlers.set(name, handler);
1238
1224
  drain();
1239
1225
  },
1240
1226
  removeNode(name) {
1241
1227
  if (disposed) return;
1242
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) {
1243
1253
  handlers.delete(name);
1244
- dispatched.delete(name);
1245
- clearTimeout(timeoutTimers.get(name));
1246
- timeoutTimers.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();
1247
1276
  },
1248
1277
  getState() {
1249
1278
  return live;
@@ -1251,19 +1280,958 @@ function createReactiveGraph(config, options, executionId) {
1251
1280
  getSchedule() {
1252
1281
  return schedule(live);
1253
1282
  },
1254
- getDispatchState() {
1255
- return dispatched;
1256
- },
1257
1283
  dispose() {
1258
1284
  disposed = true;
1259
- for (const timer of timeoutTimers.values()) {
1260
- clearTimeout(timer);
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
+ });
1261
1350
  }
1262
- timeoutTimers.clear();
1263
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
1264
1470
  };
1265
1471
  }
1266
1472
 
1267
- export { FileJournal, MemoryJournal, addNode, addProvides, addRequires, applyEvent, applyEvents, createLiveGraph, createReactiveGraph, disableNode, drainTokens, enableNode, getDownstream, getNode, getUnreachableNodes, getUnreachableTokens, getUpstream, injectTokens, inspect, removeNode, removeProvides, removeRequires, resetNode, restore, schedule, snapshot };
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
+ }
2234
+
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 };
1268
2236
  //# sourceMappingURL=index.js.map
1269
2237
  //# sourceMappingURL=index.js.map