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