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.
- package/README.md +168 -3
- package/browser/ingest-board.js +296 -0
- package/browser/live-cards.js +303 -0
- package/browser/live-cards.schema.json +22 -2
- package/dist/card-compute/index.cjs +6751 -0
- package/dist/card-compute/index.cjs.map +1 -1
- package/dist/card-compute/index.d.cts +24 -1
- package/dist/card-compute/index.d.ts +24 -1
- package/dist/card-compute/index.js +6747 -1
- package/dist/card-compute/index.js.map +1 -1
- package/dist/{constants-BEbO2_OK.d.ts → constants-B_ftYTTE.d.ts} +36 -6
- package/dist/{constants-BNjeIlZ8.d.cts → constants-CiyHX8L-.d.cts} +36 -6
- package/dist/continuous-event-graph/index.cjs +399 -42
- package/dist/continuous-event-graph/index.cjs.map +1 -1
- package/dist/continuous-event-graph/index.d.cts +124 -5
- package/dist/continuous-event-graph/index.d.ts +124 -5
- package/dist/continuous-event-graph/index.js +396 -43
- package/dist/continuous-event-graph/index.js.map +1 -1
- package/dist/event-graph/index.cjs +6784 -44
- package/dist/event-graph/index.cjs.map +1 -1
- package/dist/event-graph/index.d.cts +5 -5
- package/dist/event-graph/index.d.ts +5 -5
- package/dist/event-graph/index.js +6777 -43
- package/dist/event-graph/index.js.map +1 -1
- package/dist/index.cjs +7678 -73
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +7665 -73
- package/dist/index.js.map +1 -1
- package/dist/inference/index.cjs +17 -8
- package/dist/inference/index.cjs.map +1 -1
- package/dist/inference/index.d.cts +2 -2
- package/dist/inference/index.d.ts +2 -2
- package/dist/inference/index.js +17 -8
- package/dist/inference/index.js.map +1 -1
- package/dist/step-machine/index.cjs +6600 -0
- package/dist/step-machine/index.cjs.map +1 -1
- package/dist/step-machine/index.d.cts +26 -1
- package/dist/step-machine/index.d.ts +26 -1
- package/dist/step-machine/index.js +6596 -1
- package/dist/step-machine/index.js.map +1 -1
- package/dist/{types-DAI_a2as.d.ts → types-BpWrH1sf.d.cts} +16 -7
- package/dist/{types-DAI_a2as.d.cts → types-BpWrH1sf.d.ts} +16 -7
- package/dist/{types-mS_pPftm.d.ts → types-BuEo3wVG.d.ts} +1 -1
- package/dist/{types-C2lOwquM.d.cts → types-CxJg9Jrt.d.cts} +1 -1
- package/package.json +3 -2
- package/schema/event-graph.schema.json +254 -0
- package/schema/live-cards.schema.json +22 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { G as GraphConfig, c as ExecutionState, S as SchedulerResult, e as GraphEvent, T as TaskConfig, l as TaskState, g as StuckDetection, C as CompletionStrategy, a as ConflictStrategy, b as ExecutionMode, d as ExecutionStatus, m as TaskStatus } from './types-
|
|
1
|
+
import { G as GraphConfig, c as ExecutionState, S as SchedulerResult, e as GraphEvent, T as TaskConfig, l as TaskState, R as RefreshStrategy, g as StuckDetection, C as CompletionStrategy, a as ConflictStrategy, b as ExecutionMode, d as ExecutionStatus, m as TaskStatus } from './types-BpWrH1sf.js';
|
|
2
2
|
import { e as StepFlowConfig } from './types-FZ_eyErS.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -15,7 +15,7 @@ import { e as StepFlowConfig } from './types-FZ_eyErS.js';
|
|
|
15
15
|
declare function next(graph: GraphConfig, state: ExecutionState): SchedulerResult;
|
|
16
16
|
/**
|
|
17
17
|
* Get candidate tasks whose dependencies are all met.
|
|
18
|
-
*
|
|
18
|
+
* Uses refreshStrategy to determine re-execution eligibility.
|
|
19
19
|
* Pure function.
|
|
20
20
|
*/
|
|
21
21
|
declare function getCandidateTasks(graph: GraphConfig, state: ExecutionState): string[];
|
|
@@ -57,11 +57,16 @@ declare function hasTask(graph: GraphConfig, taskName: string): boolean;
|
|
|
57
57
|
declare function isNonActiveTask(taskState: TaskState | undefined): boolean;
|
|
58
58
|
declare function isTaskCompleted(taskState: TaskState | undefined): boolean;
|
|
59
59
|
declare function isTaskRunning(taskState: TaskState | undefined): boolean;
|
|
60
|
-
declare function
|
|
61
|
-
|
|
60
|
+
declare function getRefreshStrategy(taskConfig: TaskConfig, graphSettings?: {
|
|
61
|
+
refreshStrategy?: RefreshStrategy;
|
|
62
|
+
}): RefreshStrategy;
|
|
63
|
+
declare function isRerunnable(taskConfig: TaskConfig, graphSettings?: {
|
|
64
|
+
refreshStrategy?: RefreshStrategy;
|
|
65
|
+
}): boolean;
|
|
66
|
+
declare function getMaxExecutions(taskConfig: TaskConfig): number | undefined;
|
|
62
67
|
/**
|
|
63
68
|
* Dynamically compute available outputs from all completed tasks.
|
|
64
|
-
*
|
|
69
|
+
* Tasks with strategies other than 'once' may have completed and reset.
|
|
65
70
|
* Pure function.
|
|
66
71
|
*/
|
|
67
72
|
declare function computeAvailableOutputs(graph: GraphConfig, taskStates: Record<string, TaskState>): string[];
|
|
@@ -303,6 +308,31 @@ interface GraphValidationResult {
|
|
|
303
308
|
*/
|
|
304
309
|
declare function validateGraph(graph: GraphConfig): GraphValidationResult;
|
|
305
310
|
|
|
311
|
+
/**
|
|
312
|
+
* schema-validator — Full JSON Schema validation for EventGraph configs.
|
|
313
|
+
*
|
|
314
|
+
* Uses AJV to validate against the published event-graph.schema.json.
|
|
315
|
+
* For a lightweight sync check without AJV, use `validateGraphConfig()` instead.
|
|
316
|
+
*
|
|
317
|
+
* @example
|
|
318
|
+
* ```typescript
|
|
319
|
+
* import { validateGraphSchema } from 'yaml-flow/event-graph';
|
|
320
|
+
*
|
|
321
|
+
* const result = validateGraphSchema(config);
|
|
322
|
+
* if (!result.ok) console.error(result.errors);
|
|
323
|
+
* ```
|
|
324
|
+
*/
|
|
325
|
+
interface SchemaValidationResult {
|
|
326
|
+
ok: boolean;
|
|
327
|
+
errors: string[];
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Validate an event-graph config against the full event-graph.schema.json (draft-07).
|
|
331
|
+
*
|
|
332
|
+
* Requires `ajv` and `ajv-formats` to be installed.
|
|
333
|
+
*/
|
|
334
|
+
declare function validateGraphSchema(config: unknown): SchemaValidationResult;
|
|
335
|
+
|
|
306
336
|
/**
|
|
307
337
|
* Event Graph — Constants
|
|
308
338
|
*/
|
|
@@ -319,4 +349,4 @@ declare const DEFAULTS: {
|
|
|
319
349
|
readonly MAX_ITERATIONS: 1000;
|
|
320
350
|
};
|
|
321
351
|
|
|
322
|
-
export {
|
|
352
|
+
export { isNonActiveTask as A, isRerunnable as B, COMPLETION_STRATEGIES as C, DEFAULTS as D, EXECUTION_MODES as E, isTaskCompleted as F, type GraphIssue as G, isTaskRunning as H, type IssueSeverity as I, loadGraphConfig as J, next as K, planExecution as L, type MermaidOptions as M, validateGraph as N, validateGraphConfig as O, validateGraphSchema as P, addKeyToProvides as Q, addKeyToRequires as R, groupTasksByProvides as S, TASK_STATUS as T, hasOutputConflict as U, removeKeyFromProvides as V, removeKeyFromRequires as W, CONFLICT_STRATEGIES as a, type CompletionResult as b, EXECUTION_STATUS as c, type ExecutionPlan as d, type ExportOptions as e, type GraphValidationResult as f, addDynamicTask as g, apply as h, applyAll as i, computeAvailableOutputs as j, createDefaultTaskState as k, createInitialExecutionState as l, detectStuckState as m, exportGraphConfig as n, exportGraphConfigToFile as o, flowToMermaid as p, getAllTasks as q, getCandidateTasks as r, getMaxExecutions as s, getProvides as t, getRefreshStrategy as u, getRequires as v, getTask as w, graphToMermaid as x, hasTask as y, isExecutionComplete as z };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { G as GraphConfig, c as ExecutionState, S as SchedulerResult, e as GraphEvent, T as TaskConfig, l as TaskState, g as StuckDetection, C as CompletionStrategy, a as ConflictStrategy, b as ExecutionMode, d as ExecutionStatus, m as TaskStatus } from './types-
|
|
1
|
+
import { G as GraphConfig, c as ExecutionState, S as SchedulerResult, e as GraphEvent, T as TaskConfig, l as TaskState, R as RefreshStrategy, g as StuckDetection, C as CompletionStrategy, a as ConflictStrategy, b as ExecutionMode, d as ExecutionStatus, m as TaskStatus } from './types-BpWrH1sf.cjs';
|
|
2
2
|
import { e as StepFlowConfig } from './types-FZ_eyErS.cjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -15,7 +15,7 @@ import { e as StepFlowConfig } from './types-FZ_eyErS.cjs';
|
|
|
15
15
|
declare function next(graph: GraphConfig, state: ExecutionState): SchedulerResult;
|
|
16
16
|
/**
|
|
17
17
|
* Get candidate tasks whose dependencies are all met.
|
|
18
|
-
*
|
|
18
|
+
* Uses refreshStrategy to determine re-execution eligibility.
|
|
19
19
|
* Pure function.
|
|
20
20
|
*/
|
|
21
21
|
declare function getCandidateTasks(graph: GraphConfig, state: ExecutionState): string[];
|
|
@@ -57,11 +57,16 @@ declare function hasTask(graph: GraphConfig, taskName: string): boolean;
|
|
|
57
57
|
declare function isNonActiveTask(taskState: TaskState | undefined): boolean;
|
|
58
58
|
declare function isTaskCompleted(taskState: TaskState | undefined): boolean;
|
|
59
59
|
declare function isTaskRunning(taskState: TaskState | undefined): boolean;
|
|
60
|
-
declare function
|
|
61
|
-
|
|
60
|
+
declare function getRefreshStrategy(taskConfig: TaskConfig, graphSettings?: {
|
|
61
|
+
refreshStrategy?: RefreshStrategy;
|
|
62
|
+
}): RefreshStrategy;
|
|
63
|
+
declare function isRerunnable(taskConfig: TaskConfig, graphSettings?: {
|
|
64
|
+
refreshStrategy?: RefreshStrategy;
|
|
65
|
+
}): boolean;
|
|
66
|
+
declare function getMaxExecutions(taskConfig: TaskConfig): number | undefined;
|
|
62
67
|
/**
|
|
63
68
|
* Dynamically compute available outputs from all completed tasks.
|
|
64
|
-
*
|
|
69
|
+
* Tasks with strategies other than 'once' may have completed and reset.
|
|
65
70
|
* Pure function.
|
|
66
71
|
*/
|
|
67
72
|
declare function computeAvailableOutputs(graph: GraphConfig, taskStates: Record<string, TaskState>): string[];
|
|
@@ -303,6 +308,31 @@ interface GraphValidationResult {
|
|
|
303
308
|
*/
|
|
304
309
|
declare function validateGraph(graph: GraphConfig): GraphValidationResult;
|
|
305
310
|
|
|
311
|
+
/**
|
|
312
|
+
* schema-validator — Full JSON Schema validation for EventGraph configs.
|
|
313
|
+
*
|
|
314
|
+
* Uses AJV to validate against the published event-graph.schema.json.
|
|
315
|
+
* For a lightweight sync check without AJV, use `validateGraphConfig()` instead.
|
|
316
|
+
*
|
|
317
|
+
* @example
|
|
318
|
+
* ```typescript
|
|
319
|
+
* import { validateGraphSchema } from 'yaml-flow/event-graph';
|
|
320
|
+
*
|
|
321
|
+
* const result = validateGraphSchema(config);
|
|
322
|
+
* if (!result.ok) console.error(result.errors);
|
|
323
|
+
* ```
|
|
324
|
+
*/
|
|
325
|
+
interface SchemaValidationResult {
|
|
326
|
+
ok: boolean;
|
|
327
|
+
errors: string[];
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Validate an event-graph config against the full event-graph.schema.json (draft-07).
|
|
331
|
+
*
|
|
332
|
+
* Requires `ajv` and `ajv-formats` to be installed.
|
|
333
|
+
*/
|
|
334
|
+
declare function validateGraphSchema(config: unknown): SchemaValidationResult;
|
|
335
|
+
|
|
306
336
|
/**
|
|
307
337
|
* Event Graph — Constants
|
|
308
338
|
*/
|
|
@@ -319,4 +349,4 @@ declare const DEFAULTS: {
|
|
|
319
349
|
readonly MAX_ITERATIONS: 1000;
|
|
320
350
|
};
|
|
321
351
|
|
|
322
|
-
export {
|
|
352
|
+
export { isNonActiveTask as A, isRerunnable as B, COMPLETION_STRATEGIES as C, DEFAULTS as D, EXECUTION_MODES as E, isTaskCompleted as F, type GraphIssue as G, isTaskRunning as H, type IssueSeverity as I, loadGraphConfig as J, next as K, planExecution as L, type MermaidOptions as M, validateGraph as N, validateGraphConfig as O, validateGraphSchema as P, addKeyToProvides as Q, addKeyToRequires as R, groupTasksByProvides as S, TASK_STATUS as T, hasOutputConflict as U, removeKeyFromProvides as V, removeKeyFromRequires as W, CONFLICT_STRATEGIES as a, type CompletionResult as b, EXECUTION_STATUS as c, type ExecutionPlan as d, type ExportOptions as e, type GraphValidationResult as f, addDynamicTask as g, apply as h, applyAll as i, computeAvailableOutputs as j, createDefaultTaskState as k, createInitialExecutionState as l, detectStuckState as m, exportGraphConfig as n, exportGraphConfigToFile as o, flowToMermaid as p, getAllTasks as q, getCandidateTasks as r, getMaxExecutions as s, getProvides as t, getRefreshStrategy as u, getRequires as v, getTask as w, graphToMermaid as x, hasTask as y, isExecutionComplete as z };
|
|
@@ -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
|
|
31
|
-
return taskConfig.
|
|
32
|
+
function getRefreshStrategy(taskConfig, graphSettings) {
|
|
33
|
+
return taskConfig.refreshStrategy ?? graphSettings?.refreshStrategy ?? "data-changed";
|
|
32
34
|
}
|
|
33
|
-
function
|
|
34
|
-
|
|
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
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
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
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
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;
|