yaml-flow 2.7.0 → 3.0.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 (49) hide show
  1. package/README.md +168 -3
  2. package/browser/ingest-board.js +296 -0
  3. package/browser/live-cards.js +303 -0
  4. package/browser/live-cards.schema.json +22 -2
  5. package/dist/card-compute/index.cjs +6751 -0
  6. package/dist/card-compute/index.cjs.map +1 -1
  7. package/dist/card-compute/index.d.cts +24 -1
  8. package/dist/card-compute/index.d.ts +24 -1
  9. package/dist/card-compute/index.js +6747 -1
  10. package/dist/card-compute/index.js.map +1 -1
  11. package/dist/{constants-BEbO2_OK.d.ts → constants-B_ftYTTE.d.ts} +36 -6
  12. package/dist/{constants-BNjeIlZ8.d.cts → constants-CiyHX8L-.d.cts} +36 -6
  13. package/dist/continuous-event-graph/index.cjs +399 -42
  14. package/dist/continuous-event-graph/index.cjs.map +1 -1
  15. package/dist/continuous-event-graph/index.d.cts +124 -5
  16. package/dist/continuous-event-graph/index.d.ts +124 -5
  17. package/dist/continuous-event-graph/index.js +396 -43
  18. package/dist/continuous-event-graph/index.js.map +1 -1
  19. package/dist/event-graph/index.cjs +6784 -44
  20. package/dist/event-graph/index.cjs.map +1 -1
  21. package/dist/event-graph/index.d.cts +5 -5
  22. package/dist/event-graph/index.d.ts +5 -5
  23. package/dist/event-graph/index.js +6777 -43
  24. package/dist/event-graph/index.js.map +1 -1
  25. package/dist/index.cjs +7678 -73
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.d.cts +6 -6
  28. package/dist/index.d.ts +6 -6
  29. package/dist/index.js +7665 -73
  30. package/dist/index.js.map +1 -1
  31. package/dist/inference/index.cjs +17 -8
  32. package/dist/inference/index.cjs.map +1 -1
  33. package/dist/inference/index.d.cts +2 -2
  34. package/dist/inference/index.d.ts +2 -2
  35. package/dist/inference/index.js +17 -8
  36. package/dist/inference/index.js.map +1 -1
  37. package/dist/step-machine/index.cjs +6600 -0
  38. package/dist/step-machine/index.cjs.map +1 -1
  39. package/dist/step-machine/index.d.cts +26 -1
  40. package/dist/step-machine/index.d.ts +26 -1
  41. package/dist/step-machine/index.js +6596 -1
  42. package/dist/step-machine/index.js.map +1 -1
  43. package/dist/{types-DAI_a2as.d.ts → types-BpWrH1sf.d.cts} +16 -7
  44. package/dist/{types-DAI_a2as.d.cts → types-BpWrH1sf.d.ts} +16 -7
  45. package/dist/{types-mS_pPftm.d.ts → types-BuEo3wVG.d.ts} +1 -1
  46. package/dist/{types-C2lOwquM.d.cts → types-CxJg9Jrt.d.cts} +1 -1
  47. package/package.json +3 -2
  48. package/schema/event-graph.schema.json +254 -0
  49. package/schema/live-cards.schema.json +22 -2
@@ -1,3 +1,5 @@
1
+ import { existsSync, writeFileSync, appendFileSync, readFileSync } from 'fs';
2
+
1
3
  // src/event-graph/constants.ts
2
4
  var TASK_STATUS = {
3
5
  NOT_STARTED: "not-started",
@@ -25,15 +27,11 @@ function isNonActiveTask(taskState) {
25
27
  if (!taskState) return false;
26
28
  return taskState.status === TASK_STATUS.FAILED || taskState.status === TASK_STATUS.INACTIVATED;
27
29
  }
28
- function isRepeatableTask(taskConfig) {
29
- return taskConfig.repeatable === true || typeof taskConfig.repeatable === "object" && taskConfig.repeatable !== null;
30
+ function getRefreshStrategy(taskConfig, graphSettings) {
31
+ return taskConfig.refreshStrategy ?? graphSettings?.refreshStrategy ?? "data-changed";
30
32
  }
31
- function getRepeatableMax(taskConfig) {
32
- if (taskConfig.repeatable === true) return void 0;
33
- if (typeof taskConfig.repeatable === "object" && taskConfig.repeatable !== null) {
34
- return taskConfig.repeatable.max;
35
- }
36
- return void 0;
33
+ function getMaxExecutions(taskConfig) {
34
+ return taskConfig.maxExecutions;
37
35
  }
38
36
  function computeAvailableOutputs(graph, taskStates) {
39
37
  const outputs = /* @__PURE__ */ new Set();
@@ -81,7 +79,7 @@ function applyTaskStart(state, taskName) {
81
79
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
82
80
  };
83
81
  }
84
- function applyTaskCompletion(state, graph, taskName, result) {
82
+ function applyTaskCompletion(state, graph, taskName, result, dataHash) {
85
83
  const existingTask = state.tasks[taskName] ?? createDefaultTaskState();
86
84
  const taskConfig = graph.tasks[taskName];
87
85
  if (!taskConfig) {
@@ -93,6 +91,19 @@ function applyTaskCompletion(state, graph, taskName, result) {
93
91
  } else {
94
92
  outputTokens = getProvides(taskConfig);
95
93
  }
94
+ const lastConsumedHashes = { ...existingTask.lastConsumedHashes };
95
+ const requires = taskConfig.requires ?? [];
96
+ for (const token of requires) {
97
+ for (const [otherName, otherConfig] of Object.entries(graph.tasks)) {
98
+ if (getProvides(otherConfig).includes(token)) {
99
+ const otherState = state.tasks[otherName];
100
+ if (otherState?.lastDataHash) {
101
+ lastConsumedHashes[token] = otherState.lastDataHash;
102
+ }
103
+ break;
104
+ }
105
+ }
106
+ }
96
107
  const updatedTask = {
97
108
  ...existingTask,
98
109
  status: "completed",
@@ -100,11 +111,10 @@ function applyTaskCompletion(state, graph, taskName, result) {
100
111
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
101
112
  executionCount: existingTask.executionCount + 1,
102
113
  lastEpoch: existingTask.executionCount + 1,
114
+ lastDataHash: dataHash,
115
+ lastConsumedHashes,
103
116
  error: void 0
104
117
  };
105
- if (isRepeatableTask(taskConfig)) {
106
- updatedTask.status = "not-started";
107
- }
108
118
  const newOutputs = [.../* @__PURE__ */ new Set([...state.availableOutputs, ...outputTokens])];
109
119
  return {
110
120
  ...state,
@@ -217,7 +227,7 @@ function applyEvent(live, event) {
217
227
  newState = applyTaskStart(state, event.taskName);
218
228
  break;
219
229
  case "task-completed":
220
- newState = applyTaskCompletion(state, config, event.taskName, event.result);
230
+ newState = applyTaskCompletion(state, config, event.taskName, event.result, event.dataHash);
221
231
  break;
222
232
  case "task-failed":
223
233
  newState = applyTaskFailure(state, config, event.taskName, event.error);
@@ -240,6 +250,9 @@ function applyEvent(live, event) {
240
250
  }
241
251
  return { config, state: newState };
242
252
  }
253
+ function applyEvents(live, events) {
254
+ return events.reduce((current, event) => applyEvent(current, event), live);
255
+ }
243
256
  function addNode(live, name, taskConfig) {
244
257
  if (live.config.tasks[name]) return live;
245
258
  return {
@@ -484,38 +497,84 @@ function schedule(live) {
484
497
  const blocked = [];
485
498
  for (const [taskName, taskConfig] of Object.entries(graphTasks)) {
486
499
  const taskState = state.tasks[taskName];
487
- if (!isRepeatableTask(taskConfig)) {
488
- if (taskState?.status === TASK_STATUS.COMPLETED || taskState?.status === TASK_STATUS.RUNNING || isNonActiveTask(taskState)) {
489
- continue;
490
- }
491
- } else {
492
- if (taskState?.status === TASK_STATUS.RUNNING || isNonActiveTask(taskState)) {
493
- continue;
494
- }
495
- const maxExec = getRepeatableMax(taskConfig);
496
- if (maxExec !== void 0 && taskState && taskState.executionCount >= maxExec) {
497
- continue;
498
- }
499
- if (taskConfig.circuit_breaker && taskState && taskState.executionCount >= taskConfig.circuit_breaker.max_executions) {
500
- continue;
501
- }
502
- if (taskState?.status === TASK_STATUS.COMPLETED) {
503
- const requires2 = getRequires(taskConfig);
504
- if (requires2.length > 0) {
505
- const hasRefreshed = requires2.some((req) => {
506
- for (const [otherName, otherConfig] of Object.entries(graphTasks)) {
507
- if (getProvides(otherConfig).includes(req)) {
508
- const otherState = state.tasks[otherName];
509
- if (otherState && otherState.executionCount > taskState.lastEpoch) return true;
500
+ const strategy = getRefreshStrategy(taskConfig, config.settings);
501
+ const rerunnable = strategy !== "once";
502
+ if (taskState?.status === TASK_STATUS.RUNNING || isNonActiveTask(taskState)) {
503
+ continue;
504
+ }
505
+ const maxExec = getMaxExecutions(taskConfig);
506
+ if (maxExec !== void 0 && taskState && taskState.executionCount >= maxExec) {
507
+ continue;
508
+ }
509
+ if (taskConfig.circuit_breaker && taskState && taskState.executionCount >= taskConfig.circuit_breaker.max_executions) {
510
+ continue;
511
+ }
512
+ if (!rerunnable && taskState?.status === TASK_STATUS.COMPLETED) {
513
+ continue;
514
+ }
515
+ if (rerunnable && taskState?.status === TASK_STATUS.COMPLETED) {
516
+ const requires2 = getRequires(taskConfig);
517
+ let shouldSkip = false;
518
+ switch (strategy) {
519
+ case "data-changed": {
520
+ if (requires2.length > 0) {
521
+ const hasChangedData = requires2.some((req) => {
522
+ for (const [otherName, otherConfig] of Object.entries(graphTasks)) {
523
+ if (getProvides(otherConfig).includes(req)) {
524
+ const otherState = state.tasks[otherName];
525
+ if (!otherState) continue;
526
+ const consumed = taskState.lastConsumedHashes?.[req];
527
+ if (otherState.lastDataHash == null) {
528
+ return otherState.executionCount > taskState.lastEpoch;
529
+ }
530
+ return otherState.lastDataHash !== consumed;
531
+ }
510
532
  }
511
- }
512
- return false;
513
- });
514
- if (!hasRefreshed) continue;
515
- } else {
516
- continue;
533
+ return false;
534
+ });
535
+ if (!hasChangedData) shouldSkip = true;
536
+ } else {
537
+ shouldSkip = true;
538
+ }
539
+ break;
540
+ }
541
+ case "epoch-changed": {
542
+ if (requires2.length > 0) {
543
+ const hasRefreshed = requires2.some((req) => {
544
+ for (const [otherName, otherConfig] of Object.entries(graphTasks)) {
545
+ if (getProvides(otherConfig).includes(req)) {
546
+ const otherState = state.tasks[otherName];
547
+ if (otherState && otherState.executionCount > taskState.lastEpoch) return true;
548
+ }
549
+ }
550
+ return false;
551
+ });
552
+ if (!hasRefreshed) shouldSkip = true;
553
+ } else {
554
+ shouldSkip = true;
555
+ }
556
+ break;
557
+ }
558
+ case "time-based": {
559
+ const interval = taskConfig.refreshInterval ?? 0;
560
+ if (interval <= 0) {
561
+ shouldSkip = true;
562
+ break;
563
+ }
564
+ const completedAt = taskState.completedAt;
565
+ if (!completedAt) {
566
+ shouldSkip = true;
567
+ break;
568
+ }
569
+ const elapsedSec = (Date.now() - Date.parse(completedAt)) / 1e3;
570
+ if (elapsedSec < interval) shouldSkip = true;
571
+ break;
517
572
  }
573
+ case "manual":
574
+ shouldSkip = true;
575
+ break;
518
576
  }
577
+ if (shouldSkip) continue;
519
578
  }
520
579
  const requires = getRequires(taskConfig);
521
580
  if (requires.length === 0) {
@@ -910,7 +969,301 @@ function getDownstream(live, nodeName) {
910
969
  }));
911
970
  return { nodeName, nodes, tokens: [...tokenSet] };
912
971
  }
972
+ var MemoryJournal = class {
973
+ buffer = [];
974
+ append(event) {
975
+ this.buffer.push(event);
976
+ }
977
+ drain() {
978
+ const events = this.buffer;
979
+ this.buffer = [];
980
+ return events;
981
+ }
982
+ get size() {
983
+ return this.buffer.length;
984
+ }
985
+ };
986
+ var FileJournal = class {
987
+ constructor(path) {
988
+ this.path = path;
989
+ if (!existsSync(path)) {
990
+ writeFileSync(path, "", "utf-8");
991
+ }
992
+ }
993
+ path;
994
+ pending = 0;
995
+ append(event) {
996
+ appendFileSync(this.path, JSON.stringify(event) + "\n", "utf-8");
997
+ this.pending++;
998
+ }
999
+ drain() {
1000
+ const content = readFileSync(this.path, "utf-8").trim();
1001
+ writeFileSync(this.path, "", "utf-8");
1002
+ this.pending = 0;
1003
+ if (!content) return [];
1004
+ return content.split("\n").map((line) => JSON.parse(line));
1005
+ }
1006
+ get size() {
1007
+ try {
1008
+ const content = readFileSync(this.path, "utf-8").trim();
1009
+ if (!content) return 0;
1010
+ return content.split("\n").length;
1011
+ } catch {
1012
+ return this.pending;
1013
+ }
1014
+ }
1015
+ };
1016
+
1017
+ // src/continuous-event-graph/reactive.ts
1018
+ function createReactiveGraph(config, options, executionId) {
1019
+ const {
1020
+ handlers: initialHandlers,
1021
+ maxDispatchRetries = 3,
1022
+ defaultTimeoutMs = 3e4,
1023
+ journal = new MemoryJournal(),
1024
+ onDispatchFailed,
1025
+ onAbandoned,
1026
+ onDrain
1027
+ } = options;
1028
+ let live = createLiveGraph(config, executionId);
1029
+ let disposed = false;
1030
+ const handlers = new Map(Object.entries(initialHandlers));
1031
+ const dispatched = /* @__PURE__ */ new Map();
1032
+ const timeoutTimers = /* @__PURE__ */ new Map();
1033
+ let draining = false;
1034
+ let drainQueued = false;
1035
+ function drain() {
1036
+ if (disposed) return;
1037
+ if (draining) {
1038
+ drainQueued = true;
1039
+ return;
1040
+ }
1041
+ draining = true;
1042
+ try {
1043
+ do {
1044
+ drainQueued = false;
1045
+ drainOnce();
1046
+ } while (drainQueued);
1047
+ } finally {
1048
+ draining = false;
1049
+ }
1050
+ }
1051
+ function drainOnce() {
1052
+ sweepTimeouts();
1053
+ 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
+ if (events.length > 0) {
1063
+ live = applyEvents(live, events);
1064
+ }
1065
+ const result = schedule(live);
1066
+ if (onDrain && events.length > 0) {
1067
+ onDrain(events, live, result);
1068
+ }
1069
+ for (const taskName of result.eligible) {
1070
+ if (dispatched.has(taskName)) continue;
1071
+ dispatchTask(taskName);
1072
+ }
1073
+ for (const [taskName, entry] of dispatched) {
1074
+ if (entry.status === "retry-queued") {
1075
+ dispatchTask(taskName);
1076
+ }
1077
+ }
1078
+ }
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;
1090
+ }
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;
1108
+ return;
1109
+ }
1110
+ dispatched.set(taskName, {
1111
+ status: "initiated",
1112
+ dispatchedAt: Date.now(),
1113
+ dispatchAttempts: attempt
1114
+ });
1115
+ journal.append({
1116
+ type: "task-started",
1117
+ taskName,
1118
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1119
+ });
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"
1194
+ });
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
+ }
1221
+ }
1222
+ return {
1223
+ push(event) {
1224
+ if (disposed) return;
1225
+ live = applyEvent(live, event);
1226
+ drain();
1227
+ },
1228
+ pushAll(events) {
1229
+ if (disposed) return;
1230
+ if (events.length === 0) return;
1231
+ live = applyEvents(live, events);
1232
+ drain();
1233
+ },
1234
+ addNode(name, taskConfig, handler) {
1235
+ if (disposed) return;
1236
+ live = addNode(live, name, taskConfig);
1237
+ handlers.set(name, handler);
1238
+ drain();
1239
+ },
1240
+ removeNode(name) {
1241
+ if (disposed) return;
1242
+ live = removeNode(live, name);
1243
+ handlers.delete(name);
1244
+ dispatched.delete(name);
1245
+ clearTimeout(timeoutTimers.get(name));
1246
+ timeoutTimers.delete(name);
1247
+ },
1248
+ getState() {
1249
+ return live;
1250
+ },
1251
+ getSchedule() {
1252
+ return schedule(live);
1253
+ },
1254
+ getDispatchState() {
1255
+ return dispatched;
1256
+ },
1257
+ dispose() {
1258
+ disposed = true;
1259
+ for (const timer of timeoutTimers.values()) {
1260
+ clearTimeout(timer);
1261
+ }
1262
+ timeoutTimers.clear();
1263
+ }
1264
+ };
1265
+ }
913
1266
 
914
- export { addNode, addProvides, addRequires, applyEvent, createLiveGraph, disableNode, drainTokens, enableNode, getDownstream, getNode, getUnreachableNodes, getUnreachableTokens, getUpstream, injectTokens, inspect, removeNode, removeProvides, removeRequires, resetNode, restore, schedule, snapshot };
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 };
915
1268
  //# sourceMappingURL=index.js.map
916
1269
  //# sourceMappingURL=index.js.map