nvent 0.5.15 → 1.0.0-alpha.2
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/dist/module.d.mts +181 -8
- package/dist/module.json +1 -1
- package/dist/module.mjs +1257 -1196
- package/dist/runtime/app/composables/useFunctionCall.d.ts +17 -0
- package/dist/runtime/app/composables/useFunctionCall.js +25 -0
- package/dist/runtime/app/composables/useNventStream.d.ts +23 -0
- package/dist/runtime/app/composables/useNventStream.js +58 -0
- package/dist/runtime/nitro/plugins/00.iii-lifecycle.d.ts +19 -0
- package/dist/runtime/nitro/plugins/00.iii-lifecycle.js +62 -0
- package/dist/runtime/nitro/plugins/01.iii-worker.d.ts +15 -0
- package/dist/runtime/nitro/plugins/01.iii-worker.js +91 -0
- package/dist/runtime/nitro/routes/stream-proxy.d.ts +17 -0
- package/dist/runtime/nitro/routes/stream-proxy.js +45 -0
- package/dist/runtime/nitro/utils/console.d.ts +32 -0
- package/dist/runtime/nitro/utils/console.js +95 -0
- package/dist/runtime/nitro/utils/defineFunction.d.ts +378 -3
- package/dist/runtime/nitro/utils/defineFunction.js +224 -4
- package/dist/runtime/nitro/utils/engine.d.ts +25 -0
- package/dist/runtime/nitro/utils/engine.js +130 -0
- package/dist/runtime/nitro/utils/nventDir.d.ts +1 -0
- package/dist/runtime/nitro/utils/nventDir.js +20 -0
- package/dist/runtime/nitro/utils/useIii.d.ts +23 -0
- package/dist/runtime/nitro/utils/useIii.js +14 -0
- package/dist/runtime/nitro/utils/workers/node.d.ts +24 -0
- package/dist/runtime/nitro/utils/workers/node.js +57 -0
- package/dist/runtime/nitro/utils/workers/python.d.ts +67 -0
- package/dist/runtime/nitro/utils/workers/python.js +220 -0
- package/dist/runtime/python/nvent.py +208 -0
- package/dist/runtime/python/worker_runtime.py +659 -0
- package/dist/runtime/virtual.d.ts +17 -0
- package/dist/types.d.mts +5 -1
- package/package.json +11 -14
- package/dist/runtime/adapters/base/index.d.ts +0 -6
- package/dist/runtime/adapters/base/index.js +0 -1
- package/dist/runtime/adapters/base/store-validator.d.ts +0 -48
- package/dist/runtime/adapters/base/store-validator.js +0 -147
- package/dist/runtime/adapters/builtin/file-queue.d.ts +0 -67
- package/dist/runtime/adapters/builtin/file-queue.js +0 -499
- package/dist/runtime/adapters/builtin/file-store.d.ts +0 -32
- package/dist/runtime/adapters/builtin/file-store.js +0 -206
- package/dist/runtime/adapters/builtin/file-stream.d.ts +0 -39
- package/dist/runtime/adapters/builtin/file-stream.js +0 -56
- package/dist/runtime/adapters/builtin/index.d.ts +0 -10
- package/dist/runtime/adapters/builtin/index.js +0 -5
- package/dist/runtime/adapters/builtin/memory-queue.d.ts +0 -52
- package/dist/runtime/adapters/builtin/memory-queue.js +0 -243
- package/dist/runtime/adapters/builtin/memory-store.d.ts +0 -69
- package/dist/runtime/adapters/builtin/memory-store.js +0 -357
- package/dist/runtime/adapters/builtin/memory-stream.d.ts +0 -21
- package/dist/runtime/adapters/builtin/memory-stream.js +0 -56
- package/dist/runtime/adapters/factory.d.ts +0 -31
- package/dist/runtime/adapters/factory.js +0 -135
- package/dist/runtime/adapters/index.d.ts +0 -8
- package/dist/runtime/adapters/index.js +0 -3
- package/dist/runtime/adapters/interfaces/index.d.ts +0 -11
- package/dist/runtime/adapters/interfaces/index.js +0 -3
- package/dist/runtime/adapters/interfaces/queue.d.ts +0 -155
- package/dist/runtime/adapters/interfaces/queue.js +0 -0
- package/dist/runtime/adapters/interfaces/store.d.ts +0 -299
- package/dist/runtime/adapters/interfaces/store.js +0 -0
- package/dist/runtime/adapters/interfaces/stream.d.ts +0 -62
- package/dist/runtime/adapters/interfaces/stream.js +0 -0
- package/dist/runtime/adapters/registry.d.ts +0 -85
- package/dist/runtime/adapters/registry.js +0 -161
- package/dist/runtime/config/index.d.ts +0 -23
- package/dist/runtime/config/index.js +0 -200
- package/dist/runtime/config/types.d.ts +0 -439
- package/dist/runtime/config/types.js +0 -0
- package/dist/runtime/events/eventBus.d.ts +0 -20
- package/dist/runtime/events/eventBus.js +0 -35
- package/dist/runtime/events/types.d.ts +0 -144
- package/dist/runtime/events/types.js +0 -0
- package/dist/runtime/events/utils/scheduleTrigger.d.ts +0 -8
- package/dist/runtime/events/utils/scheduleTrigger.js +0 -71
- package/dist/runtime/events/utils/stallDetector.d.ts +0 -83
- package/dist/runtime/events/utils/stallDetector.js +0 -318
- package/dist/runtime/events/utils/triggerRuntime.d.ts +0 -58
- package/dist/runtime/events/utils/triggerRuntime.js +0 -212
- package/dist/runtime/events/wiring/flowWiring.d.ts +0 -34
- package/dist/runtime/events/wiring/flowWiring.js +0 -1258
- package/dist/runtime/events/wiring/registry.d.ts +0 -19
- package/dist/runtime/events/wiring/registry.js +0 -43
- package/dist/runtime/events/wiring/stateWiring.d.ts +0 -37
- package/dist/runtime/events/wiring/stateWiring.js +0 -92
- package/dist/runtime/events/wiring/streamWiring.d.ts +0 -36
- package/dist/runtime/events/wiring/streamWiring.js +0 -156
- package/dist/runtime/events/wiring/triggerWiring.d.ts +0 -21
- package/dist/runtime/events/wiring/triggerWiring.js +0 -422
- package/dist/runtime/nitro/plugins/00.adapters.d.ts +0 -14
- package/dist/runtime/nitro/plugins/00.adapters.js +0 -73
- package/dist/runtime/nitro/plugins/01.ws-lifecycle.d.ts +0 -5
- package/dist/runtime/nitro/plugins/01.ws-lifecycle.js +0 -69
- package/dist/runtime/nitro/plugins/02.workers.d.ts +0 -2
- package/dist/runtime/nitro/plugins/02.workers.js +0 -92
- package/dist/runtime/nitro/plugins/03.triggers.d.ts +0 -12
- package/dist/runtime/nitro/plugins/03.triggers.js +0 -55
- package/dist/runtime/nitro/routes/webhook.await.d.ts +0 -23
- package/dist/runtime/nitro/routes/webhook.await.js +0 -112
- package/dist/runtime/nitro/routes/webhook.trigger.d.ts +0 -86
- package/dist/runtime/nitro/routes/webhook.trigger.js +0 -73
- package/dist/runtime/nitro/utils/adapters.d.ts +0 -66
- package/dist/runtime/nitro/utils/adapters.js +0 -51
- package/dist/runtime/nitro/utils/awaitPatterns/event.d.ts +0 -15
- package/dist/runtime/nitro/utils/awaitPatterns/event.js +0 -128
- package/dist/runtime/nitro/utils/awaitPatterns/index.d.ts +0 -28
- package/dist/runtime/nitro/utils/awaitPatterns/index.js +0 -55
- package/dist/runtime/nitro/utils/awaitPatterns/schedule.d.ts +0 -16
- package/dist/runtime/nitro/utils/awaitPatterns/schedule.js +0 -83
- package/dist/runtime/nitro/utils/awaitPatterns/time.d.ts +0 -15
- package/dist/runtime/nitro/utils/awaitPatterns/time.js +0 -71
- package/dist/runtime/nitro/utils/awaitPatterns/webhook.d.ts +0 -15
- package/dist/runtime/nitro/utils/awaitPatterns/webhook.js +0 -128
- package/dist/runtime/nitro/utils/defineFunctionConfig.d.ts +0 -378
- package/dist/runtime/nitro/utils/defineFunctionConfig.js +0 -3
- package/dist/runtime/nitro/utils/defineHooks.d.ts +0 -95
- package/dist/runtime/nitro/utils/defineHooks.js +0 -9
- package/dist/runtime/nitro/utils/registerAdapter.d.ts +0 -59
- package/dist/runtime/nitro/utils/registerAdapter.js +0 -13
- package/dist/runtime/nitro/utils/useAwait.d.ts +0 -83
- package/dist/runtime/nitro/utils/useAwait.js +0 -169
- package/dist/runtime/nitro/utils/useEventManager.d.ts +0 -15
- package/dist/runtime/nitro/utils/useEventManager.js +0 -26
- package/dist/runtime/nitro/utils/useFlow.d.ts +0 -66
- package/dist/runtime/nitro/utils/useFlow.js +0 -310
- package/dist/runtime/nitro/utils/useHookRegistry.d.ts +0 -40
- package/dist/runtime/nitro/utils/useHookRegistry.js +0 -25
- package/dist/runtime/nitro/utils/useNventLogger.d.ts +0 -42
- package/dist/runtime/nitro/utils/useNventLogger.js +0 -54
- package/dist/runtime/nitro/utils/useRunContext.d.ts +0 -6
- package/dist/runtime/nitro/utils/useRunContext.js +0 -102
- package/dist/runtime/nitro/utils/useStreamTopics.d.ts +0 -83
- package/dist/runtime/nitro/utils/useStreamTopics.js +0 -94
- package/dist/runtime/nitro/utils/useTrigger.d.ts +0 -150
- package/dist/runtime/nitro/utils/useTrigger.js +0 -311
- package/dist/runtime/nitro/utils/wsPeerManager.d.ts +0 -44
- package/dist/runtime/nitro/utils/wsPeerManager.js +0 -32
- package/dist/runtime/scheduler/index.d.ts +0 -33
- package/dist/runtime/scheduler/index.js +0 -42
- package/dist/runtime/scheduler/scheduler.d.ts +0 -132
- package/dist/runtime/scheduler/scheduler.js +0 -799
- package/dist/runtime/scheduler/types.d.ts +0 -122
- package/dist/runtime/scheduler/types.js +0 -0
- package/dist/runtime/worker/node/runner.d.ts +0 -95
- package/dist/runtime/worker/node/runner.js +0 -272
- package/dist/runtime/worker/python/get_config.py +0 -64
- package/dist/runtime/worker/system/awaitHandlers.d.ts +0 -27
- package/dist/runtime/worker/system/awaitHandlers.js +0 -230
- package/dist/runtime/worker/system/index.d.ts +0 -24
- package/dist/runtime/worker/system/index.js +0 -39
|
@@ -1,1258 +0,0 @@
|
|
|
1
|
-
import { getEventBus } from "../eventBus.js";
|
|
2
|
-
import { useNventLogger, useStoreAdapter, useQueueAdapter, $useAnalyzedFlows, $useFunctionRegistry, useStreamTopics, useRuntimeConfig, useScheduler } from "#imports";
|
|
3
|
-
import { createStallDetector } from "../utils/stallDetector.js";
|
|
4
|
-
import { SYSTEM_HANDLERS } from "../../worker/system/index.js";
|
|
5
|
-
export function checkPendingStepTriggers(step, emittedEvents, completedSteps) {
|
|
6
|
-
if (!step.subscribes || step.subscribes.length === 0) {
|
|
7
|
-
return true;
|
|
8
|
-
}
|
|
9
|
-
return step.subscribes.every((sub) => {
|
|
10
|
-
if (emittedEvents.has(sub)) return true;
|
|
11
|
-
const [prefix, value] = sub.split(":");
|
|
12
|
-
if (prefix === "step" && value) {
|
|
13
|
-
return completedSteps.has(value);
|
|
14
|
-
}
|
|
15
|
-
return false;
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
export async function checkAndTriggerPendingSteps(flowName, runId, store) {
|
|
19
|
-
const logger = useNventLogger("flow-wiring");
|
|
20
|
-
try {
|
|
21
|
-
const analyzedFlows = $useAnalyzedFlows();
|
|
22
|
-
const registry = $useFunctionRegistry();
|
|
23
|
-
const queue = useQueueAdapter();
|
|
24
|
-
const { StoreSubjects } = useStreamTopics();
|
|
25
|
-
const flowDef = analyzedFlows.find((f) => f.id === flowName);
|
|
26
|
-
if (!flowDef?.steps) {
|
|
27
|
-
logger.info("No flow definition or steps found", { flowName });
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
const indexKey = StoreSubjects.flowRunIndex(flowName);
|
|
31
|
-
if (!store.index.get) {
|
|
32
|
-
logger.info("No indexGet method on store", { flowName });
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
const flowEntry = await store.index.get(indexKey, runId);
|
|
36
|
-
if (!flowEntry?.metadata) {
|
|
37
|
-
logger.info("No flow entry or metadata found", { flowName, runId });
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
const streamName = StoreSubjects.flowRun(runId);
|
|
41
|
-
const allEvents = await store.stream.read(streamName);
|
|
42
|
-
const isCanceled = allEvents.some((event) => event.type === "flow.cancel");
|
|
43
|
-
if (isCanceled) {
|
|
44
|
-
logger.debug("Flow is canceled, skipping pending step triggers", { flowName, runId });
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
const emittedEventsObj = flowEntry.metadata.emittedEvents || {};
|
|
48
|
-
const flattenEmittedEvents = (obj, prefix = "") => {
|
|
49
|
-
const result = [];
|
|
50
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
51
|
-
if (key === "undefined" || key === "null") continue;
|
|
52
|
-
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
53
|
-
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
54
|
-
result.push(...flattenEmittedEvents(value, fullKey));
|
|
55
|
-
} else {
|
|
56
|
-
result.push(fullKey);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return result;
|
|
60
|
-
};
|
|
61
|
-
const emittedEvents = new Set(
|
|
62
|
-
typeof emittedEventsObj === "object" && !Array.isArray(emittedEventsObj) ? flattenEmittedEvents(emittedEventsObj) : Array.isArray(emittedEventsObj) ? emittedEventsObj : []
|
|
63
|
-
);
|
|
64
|
-
const completedSteps = /* @__PURE__ */ new Set();
|
|
65
|
-
for (const event of allEvents) {
|
|
66
|
-
if (event.type === "step.completed" && "stepName" in event) {
|
|
67
|
-
completedSteps.add(event.stepName);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
const awaitingSteps = flowEntry?.metadata?.awaitingSteps || {};
|
|
71
|
-
for (const [stepName, stepDef] of Object.entries(flowDef.steps)) {
|
|
72
|
-
const step = stepDef;
|
|
73
|
-
if (!step.subscribes || completedSteps.has(stepName)) continue;
|
|
74
|
-
const awaitBeforeKey = `${stepName}:before`;
|
|
75
|
-
const awaitState = flowEntry?.metadata?.awaitingSteps?.[awaitBeforeKey];
|
|
76
|
-
if (awaitState && awaitState.status === "awaiting") continue;
|
|
77
|
-
if (awaitState && awaitState.status === "timeout") continue;
|
|
78
|
-
const isDependencyAwaiting = step.subscribes.some((sub) => {
|
|
79
|
-
const emitEvent = allEvents.find(
|
|
80
|
-
(evt) => evt.type === "emit" && evt.data?.name === sub
|
|
81
|
-
);
|
|
82
|
-
if (!emitEvent) return false;
|
|
83
|
-
const emitStepName = emitEvent.stepName;
|
|
84
|
-
if (!emitStepName) {
|
|
85
|
-
return false;
|
|
86
|
-
}
|
|
87
|
-
const awaitAfterKey = `${emitStepName}:after`;
|
|
88
|
-
const awaitState2 = awaitingSteps[awaitAfterKey];
|
|
89
|
-
if (awaitState2?.status === "awaiting") {
|
|
90
|
-
return true;
|
|
91
|
-
}
|
|
92
|
-
if (awaitState2?.status === "resolved") {
|
|
93
|
-
return false;
|
|
94
|
-
}
|
|
95
|
-
let emittingStepMeta = flowDef.steps[emitStepName];
|
|
96
|
-
if (!emittingStepMeta && emitStepName === flowDef.entry?.step) {
|
|
97
|
-
emittingStepMeta = flowDef.entry;
|
|
98
|
-
}
|
|
99
|
-
if (emittingStepMeta?.awaitAfter) {
|
|
100
|
-
const stepCompleted = allEvents.some(
|
|
101
|
-
(evt) => evt.type === "step.completed" && evt.stepName === emitStepName
|
|
102
|
-
);
|
|
103
|
-
if (stepCompleted) {
|
|
104
|
-
const awaitResolved = allEvents.some(
|
|
105
|
-
(evt) => evt.type === "await.resolved" && evt.stepName === emitStepName && evt.position === "after"
|
|
106
|
-
);
|
|
107
|
-
if (!awaitResolved) return true;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
return false;
|
|
111
|
-
});
|
|
112
|
-
if (isDependencyAwaiting) continue;
|
|
113
|
-
const canTrigger = checkPendingStepTriggers(step, emittedEvents, completedSteps);
|
|
114
|
-
if (canTrigger && step.awaitBefore) {
|
|
115
|
-
if (awaitState?.status === "awaiting") continue;
|
|
116
|
-
if (awaitState?.status !== "resolved") {
|
|
117
|
-
try {
|
|
118
|
-
const emitData = {};
|
|
119
|
-
const subscribes = step.subscribes || [];
|
|
120
|
-
for (const sub of subscribes) {
|
|
121
|
-
const emitEvent = allEvents.find(
|
|
122
|
-
(evt) => evt.type === "emit" && evt.data?.name === sub
|
|
123
|
-
);
|
|
124
|
-
if (emitEvent && emitEvent.data?.payload !== void 0) {
|
|
125
|
-
emitData[sub] = emitEvent.data.payload;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
const payload = {
|
|
129
|
-
flowId: runId,
|
|
130
|
-
flowName,
|
|
131
|
-
stepName,
|
|
132
|
-
position: "before",
|
|
133
|
-
awaitConfig: step.awaitBefore,
|
|
134
|
-
input: emitData
|
|
135
|
-
};
|
|
136
|
-
const jobId = `${runId}__${stepName}__await-register-before`;
|
|
137
|
-
const flowRegistry = (registry?.flows || {})[flowName];
|
|
138
|
-
const stepMeta = flowRegistry?.steps?.[stepName];
|
|
139
|
-
let stepQueue = stepMeta?.queue;
|
|
140
|
-
if (!stepQueue && flowRegistry?.entry?.step === stepName) {
|
|
141
|
-
stepQueue = flowRegistry.entry.queue;
|
|
142
|
-
}
|
|
143
|
-
if (!stepQueue && registry?.workers) {
|
|
144
|
-
const worker = registry.workers.find((w) => {
|
|
145
|
-
const flowNames = w?.flow?.names || (w?.flow?.name ? [w?.flow?.name] : []);
|
|
146
|
-
const stepMatch = w?.flow?.step === stepName || Array.isArray(w?.flow?.step) && w?.flow?.step.includes(stepName);
|
|
147
|
-
return flowNames.includes(flowName) && stepMatch;
|
|
148
|
-
});
|
|
149
|
-
stepQueue = worker?.queue?.name;
|
|
150
|
-
}
|
|
151
|
-
if (!stepQueue) {
|
|
152
|
-
logger.error("Cannot find queue for step", {
|
|
153
|
-
stepName,
|
|
154
|
-
flowName,
|
|
155
|
-
availableSteps: Object.keys(flowRegistry?.steps || {}),
|
|
156
|
-
entryStep: flowRegistry?.entry?.step
|
|
157
|
-
});
|
|
158
|
-
throw new Error(`Cannot register await: queue not found for step ${stepName} in flow ${flowName}`);
|
|
159
|
-
}
|
|
160
|
-
const analyzedAwaitStep = (flowDef.analyzed?.steps || {})[stepName];
|
|
161
|
-
const awaitStepTimeout = analyzedAwaitStep?.stepTimeout;
|
|
162
|
-
await queue.enqueue(stepQueue, {
|
|
163
|
-
name: SYSTEM_HANDLERS.AWAIT_REGISTER,
|
|
164
|
-
data: payload,
|
|
165
|
-
opts: { jobId, timeout: awaitStepTimeout }
|
|
166
|
-
});
|
|
167
|
-
} catch (err) {
|
|
168
|
-
logger.error("Failed to register awaitBefore pattern", {
|
|
169
|
-
flowName,
|
|
170
|
-
stepName,
|
|
171
|
-
error: err.message
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
continue;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
const shouldEnqueueStep = canTrigger && (!step.awaitBefore || awaitState?.status === "resolved");
|
|
178
|
-
if (shouldEnqueueStep) {
|
|
179
|
-
const flowRegistry = (registry?.flows || {})[flowName];
|
|
180
|
-
const stepMeta = flowRegistry?.steps?.[stepName];
|
|
181
|
-
if (stepMeta?.queue) {
|
|
182
|
-
const emitData = {};
|
|
183
|
-
const subscribes = step.subscribes || [];
|
|
184
|
-
for (const sub of subscribes) {
|
|
185
|
-
const emitEvent = allEvents.find(
|
|
186
|
-
(evt) => evt.type === "emit" && evt.data?.name === sub
|
|
187
|
-
);
|
|
188
|
-
if (emitEvent && emitEvent.data?.payload !== void 0) {
|
|
189
|
-
emitData[sub] = emitEvent.data.payload;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
const payload = {
|
|
193
|
-
flowId: runId,
|
|
194
|
-
flowName,
|
|
195
|
-
input: emitData
|
|
196
|
-
// Keyed by event name
|
|
197
|
-
};
|
|
198
|
-
const isAwaitResuming = awaitState?.status === "resolved" && awaitState?.position === "before";
|
|
199
|
-
if (isAwaitResuming) {
|
|
200
|
-
payload.awaitResolved = true;
|
|
201
|
-
payload.awaitData = awaitState.triggerData;
|
|
202
|
-
payload.awaitPosition = "before";
|
|
203
|
-
}
|
|
204
|
-
const jobId = isAwaitResuming ? `${runId}__${stepName}__resumed` : `${runId}__${stepName}`;
|
|
205
|
-
const worker = registry?.workers?.find(
|
|
206
|
-
(w) => w?.flow?.step === stepName && w?.queue?.name === stepMeta.queue
|
|
207
|
-
);
|
|
208
|
-
const defaultOpts = worker?.queue?.defaultJobOptions || {};
|
|
209
|
-
const analyzedStep = (flowDef.analyzed?.steps || {})[stepName];
|
|
210
|
-
const stepTimeout = analyzedStep?.stepTimeout;
|
|
211
|
-
const opts = { ...defaultOpts, jobId, timeout: stepTimeout };
|
|
212
|
-
try {
|
|
213
|
-
await queue.enqueue(stepMeta.queue, { name: stepName, data: payload, opts });
|
|
214
|
-
} catch {
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
} catch (err) {
|
|
220
|
-
logger.warn("Failed to check pending steps", {
|
|
221
|
-
flowName,
|
|
222
|
-
runId,
|
|
223
|
-
error: err?.message
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
export function analyzeFlowCompletion(flowSteps, entryStep, events, entryStepDef) {
|
|
228
|
-
const isCanceled = events.some((event) => event.type === "flow.cancel");
|
|
229
|
-
const allSteps = entryStep ? [entryStep, ...Object.keys(flowSteps)] : Object.keys(flowSteps);
|
|
230
|
-
const completedSteps = /* @__PURE__ */ new Set();
|
|
231
|
-
const failedSteps = /* @__PURE__ */ new Set();
|
|
232
|
-
const retriedSteps = /* @__PURE__ */ new Map();
|
|
233
|
-
let startedAt = 0;
|
|
234
|
-
let completedAt = 0;
|
|
235
|
-
for (const event of events) {
|
|
236
|
-
if (event.type === "flow.start") {
|
|
237
|
-
startedAt = typeof event.ts === "string" ? new Date(event.ts).getTime() : 0;
|
|
238
|
-
}
|
|
239
|
-
if (event.type === "flow.cancel") {
|
|
240
|
-
completedAt = typeof event.ts === "string" ? new Date(event.ts).getTime() : Date.now();
|
|
241
|
-
}
|
|
242
|
-
if (event.type === "step.completed" && "stepName" in event) {
|
|
243
|
-
completedSteps.add(event.stepName);
|
|
244
|
-
}
|
|
245
|
-
if (event.type === "step.retry" && "stepName" in event) {
|
|
246
|
-
const currentAttempts = retriedSteps.get(event.stepName) || 0;
|
|
247
|
-
retriedSteps.set(event.stepName, currentAttempts + 1);
|
|
248
|
-
}
|
|
249
|
-
if (event.type === "step.failed" && "stepName" in event) {
|
|
250
|
-
failedSteps.add(event.stepName);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
const finalFailedSteps = /* @__PURE__ */ new Set();
|
|
254
|
-
for (const stepName of Array.from(failedSteps)) {
|
|
255
|
-
let lastRetryIndex = -1;
|
|
256
|
-
let lastFailedIndex = -1;
|
|
257
|
-
for (let i = 0; i < events.length; i++) {
|
|
258
|
-
const event = events[i];
|
|
259
|
-
if ("stepName" in event && event.stepName === stepName) {
|
|
260
|
-
if (event.type === "step.retry") {
|
|
261
|
-
lastRetryIndex = i;
|
|
262
|
-
}
|
|
263
|
-
if (event.type === "step.failed") {
|
|
264
|
-
lastFailedIndex = i;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
if (lastFailedIndex > lastRetryIndex) {
|
|
269
|
-
finalFailedSteps.add(stepName);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
const totalSteps = allSteps.length;
|
|
273
|
-
const hasFinalFailures = finalFailedSteps.size > 0;
|
|
274
|
-
const stepDependencies = /* @__PURE__ */ new Map();
|
|
275
|
-
const stepDependents = /* @__PURE__ */ new Map();
|
|
276
|
-
for (const stepName of allSteps) {
|
|
277
|
-
stepDependencies.set(stepName, /* @__PURE__ */ new Set());
|
|
278
|
-
stepDependents.set(stepName, /* @__PURE__ */ new Set());
|
|
279
|
-
}
|
|
280
|
-
for (const [stepName, stepDef] of Object.entries(flowSteps)) {
|
|
281
|
-
const step = stepDef;
|
|
282
|
-
const subscribes = step.subscribes || [];
|
|
283
|
-
for (const sub of subscribes) {
|
|
284
|
-
for (const [emitStepName, emitStepDef] of Object.entries(flowSteps)) {
|
|
285
|
-
const emitStep = emitStepDef;
|
|
286
|
-
const emits = emitStep.emits || [];
|
|
287
|
-
if (emits.some((emit) => sub === `${emitStepName}.${emit}` || sub === emit)) {
|
|
288
|
-
stepDependencies.get(stepName)?.add(emitStepName);
|
|
289
|
-
stepDependents.get(emitStepName)?.add(stepName);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
if (entryStep && entryStepDef?.emits) {
|
|
293
|
-
const entryEmits = entryStepDef.emits || [];
|
|
294
|
-
if (entryEmits.some((emit) => sub === `${entryStep}.${emit}` || sub === emit)) {
|
|
295
|
-
stepDependencies.get(stepName)?.add(entryStep);
|
|
296
|
-
stepDependents.get(entryStep)?.add(stepName);
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
let hasBlockingFailure = false;
|
|
302
|
-
let hasCriticalLayerFailure = false;
|
|
303
|
-
if (hasFinalFailures) {
|
|
304
|
-
for (const failedStepName of Array.from(finalFailedSteps)) {
|
|
305
|
-
const dependents = stepDependents.get(failedStepName);
|
|
306
|
-
if (dependents && dependents.size > 0) {
|
|
307
|
-
for (const dependentName of Array.from(dependents)) {
|
|
308
|
-
if (!completedSteps.has(dependentName)) {
|
|
309
|
-
hasBlockingFailure = true;
|
|
310
|
-
break;
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
if (hasBlockingFailure) break;
|
|
315
|
-
}
|
|
316
|
-
const layerGroups = /* @__PURE__ */ new Map();
|
|
317
|
-
for (const stepName of allSteps) {
|
|
318
|
-
const deps = stepDependencies.get(stepName);
|
|
319
|
-
const depsKey = Array.from(deps || []).sort().join(",");
|
|
320
|
-
if (!layerGroups.has(depsKey)) {
|
|
321
|
-
layerGroups.set(depsKey, /* @__PURE__ */ new Set());
|
|
322
|
-
}
|
|
323
|
-
layerGroups.get(depsKey)?.add(stepName);
|
|
324
|
-
}
|
|
325
|
-
for (const [_depsKey, layerSteps] of Array.from(layerGroups)) {
|
|
326
|
-
const layerHasFailures = Array.from(layerSteps).some((s) => finalFailedSteps.has(s));
|
|
327
|
-
if (!layerHasFailures) continue;
|
|
328
|
-
const allLayerStepsFailed = Array.from(layerSteps).every(
|
|
329
|
-
(s) => finalFailedSteps.has(s)
|
|
330
|
-
);
|
|
331
|
-
if (allLayerStepsFailed) {
|
|
332
|
-
const hasLeafNode = Array.from(layerSteps).some((s) => {
|
|
333
|
-
const deps = stepDependents.get(s);
|
|
334
|
-
return !deps || deps.size === 0;
|
|
335
|
-
});
|
|
336
|
-
if (hasLeafNode) {
|
|
337
|
-
hasCriticalLayerFailure = true;
|
|
338
|
-
break;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
if (isCanceled) {
|
|
344
|
-
return {
|
|
345
|
-
status: "canceled",
|
|
346
|
-
totalSteps,
|
|
347
|
-
completedSteps: completedSteps.size,
|
|
348
|
-
startedAt,
|
|
349
|
-
completedAt
|
|
350
|
-
};
|
|
351
|
-
}
|
|
352
|
-
if (hasBlockingFailure || hasCriticalLayerFailure) {
|
|
353
|
-
return {
|
|
354
|
-
status: "failed",
|
|
355
|
-
totalSteps,
|
|
356
|
-
completedSteps: completedSteps.size,
|
|
357
|
-
startedAt,
|
|
358
|
-
completedAt: Date.now()
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
|
-
const allStepsTerminal = allSteps.every(
|
|
362
|
-
(step) => completedSteps.has(step) || finalFailedSteps.has(step)
|
|
363
|
-
);
|
|
364
|
-
let status = "running";
|
|
365
|
-
if (allStepsTerminal) {
|
|
366
|
-
status = "completed";
|
|
367
|
-
completedAt = Date.now();
|
|
368
|
-
}
|
|
369
|
-
return {
|
|
370
|
-
status,
|
|
371
|
-
totalSteps,
|
|
372
|
-
completedSteps: completedSteps.size,
|
|
373
|
-
startedAt,
|
|
374
|
-
completedAt
|
|
375
|
-
};
|
|
376
|
-
}
|
|
377
|
-
export function createFlowWiring() {
|
|
378
|
-
const bus = getEventBus();
|
|
379
|
-
const unsubs = [];
|
|
380
|
-
let wired = false;
|
|
381
|
-
let stallDetector;
|
|
382
|
-
const publishingTerminalEvents = /* @__PURE__ */ new Set();
|
|
383
|
-
const indexFlowRun = async (flowName, flowId, timestamp, metadata) => {
|
|
384
|
-
const logger = useNventLogger("flow-wiring");
|
|
385
|
-
try {
|
|
386
|
-
const store = useStoreAdapter();
|
|
387
|
-
const { StoreSubjects } = useStreamTopics();
|
|
388
|
-
const indexKey = StoreSubjects.flowRunIndex(flowName);
|
|
389
|
-
if (!store.index.add) {
|
|
390
|
-
throw new Error("StoreAdapter does not support indexAdd");
|
|
391
|
-
}
|
|
392
|
-
await store.index.add(indexKey, flowId, timestamp, metadata);
|
|
393
|
-
} catch (err) {
|
|
394
|
-
logger.error("Failed to index run", { error: err });
|
|
395
|
-
}
|
|
396
|
-
};
|
|
397
|
-
const flowProcessingChain = /* @__PURE__ */ new Map();
|
|
398
|
-
const cleanupTimers = /* @__PURE__ */ new Map();
|
|
399
|
-
async function start() {
|
|
400
|
-
if (wired) return;
|
|
401
|
-
wired = true;
|
|
402
|
-
const logger = useNventLogger("flow-wiring");
|
|
403
|
-
const { StoreSubjects } = useStreamTopics();
|
|
404
|
-
logger.info("Flow wiring starting");
|
|
405
|
-
const store = useStoreAdapter();
|
|
406
|
-
if (!store || !store.stream.append) {
|
|
407
|
-
logger.error("StoreAdapter not properly initialized or missing append method", {
|
|
408
|
-
hasStore: !!store,
|
|
409
|
-
hasAppend: !!(store && store.stream.append)
|
|
410
|
-
});
|
|
411
|
-
throw new Error("StoreAdapter not initialized");
|
|
412
|
-
}
|
|
413
|
-
const handlePersistence = async (e) => {
|
|
414
|
-
try {
|
|
415
|
-
if (e.id && e.ts) {
|
|
416
|
-
return;
|
|
417
|
-
}
|
|
418
|
-
const runId = e.runId;
|
|
419
|
-
if (!runId) {
|
|
420
|
-
return;
|
|
421
|
-
}
|
|
422
|
-
const flowName = e.flowName;
|
|
423
|
-
if (!flowName) {
|
|
424
|
-
return;
|
|
425
|
-
}
|
|
426
|
-
const streamName = StoreSubjects.flowRun(runId);
|
|
427
|
-
if (!e.type) {
|
|
428
|
-
logger.error("Event missing type field", { event: e });
|
|
429
|
-
return;
|
|
430
|
-
}
|
|
431
|
-
const eventData = {
|
|
432
|
-
type: e.type,
|
|
433
|
-
runId: e.runId,
|
|
434
|
-
flowName: e.flowName,
|
|
435
|
-
data: e.data
|
|
436
|
-
};
|
|
437
|
-
if ("stepName" in e && e.stepName) eventData.stepName = e.stepName;
|
|
438
|
-
if ("stepId" in e && e.stepId) eventData.stepId = e.stepId;
|
|
439
|
-
if ("attempt" in e && e.attempt) eventData.attempt = e.attempt;
|
|
440
|
-
const persistedEvent = await store.stream.append(streamName, eventData);
|
|
441
|
-
await bus.publish(persistedEvent);
|
|
442
|
-
if (e.type === "flow.completed" || e.type === "flow.failed") {
|
|
443
|
-
try {
|
|
444
|
-
const scheduler = useScheduler();
|
|
445
|
-
const flowJobs = await scheduler.getJobsByPattern(runId);
|
|
446
|
-
for (const job of flowJobs) {
|
|
447
|
-
await scheduler.unschedule(job.id);
|
|
448
|
-
logger.debug(`Unscheduled job for ${e.type} flow: ${job.id}`);
|
|
449
|
-
}
|
|
450
|
-
logger.debug(`Unscheduled ${flowJobs.length} scheduled jobs for ${e.type} flow runId '${runId}'`, {
|
|
451
|
-
jobs: flowJobs.map((j) => j.id)
|
|
452
|
-
});
|
|
453
|
-
} catch (error) {
|
|
454
|
-
logger.debug(`Could not unschedule jobs for runId '${runId}'`, {
|
|
455
|
-
error: error.message
|
|
456
|
-
});
|
|
457
|
-
}
|
|
458
|
-
const publishKey = `${runId}:terminal`;
|
|
459
|
-
setTimeout(() => {
|
|
460
|
-
try {
|
|
461
|
-
publishingTerminalEvents.delete(publishKey);
|
|
462
|
-
} catch (err) {
|
|
463
|
-
logger.debug("Error cleaning up terminal event tracker", { publishKey, error: err?.message });
|
|
464
|
-
}
|
|
465
|
-
}, 200);
|
|
466
|
-
logger.info("Stored terminal event", {
|
|
467
|
-
type: e.type,
|
|
468
|
-
flowName,
|
|
469
|
-
runId,
|
|
470
|
-
id: persistedEvent.id
|
|
471
|
-
});
|
|
472
|
-
} else {
|
|
473
|
-
logger.debug("Stored event", {
|
|
474
|
-
type: e.type,
|
|
475
|
-
flowName,
|
|
476
|
-
runId,
|
|
477
|
-
stepName: "stepName" in e ? e.stepName : void 0,
|
|
478
|
-
id: persistedEvent.id
|
|
479
|
-
});
|
|
480
|
-
}
|
|
481
|
-
} catch (err) {
|
|
482
|
-
logger.error("ERROR persisting event", {
|
|
483
|
-
type: e.type,
|
|
484
|
-
runId: e.runId,
|
|
485
|
-
flowName: e.flowName,
|
|
486
|
-
error: err?.message
|
|
487
|
-
});
|
|
488
|
-
}
|
|
489
|
-
};
|
|
490
|
-
const handleFlowStats = async (e) => {
|
|
491
|
-
try {
|
|
492
|
-
if (!e.id || !e.ts) {
|
|
493
|
-
return;
|
|
494
|
-
}
|
|
495
|
-
const flowName = e.flowName;
|
|
496
|
-
if (!flowName) return;
|
|
497
|
-
const flowIndexKey = StoreSubjects.flowIndex();
|
|
498
|
-
if (e.type === "flow.start") {
|
|
499
|
-
if (store.index.increment) {
|
|
500
|
-
await store.index.increment(flowIndexKey, flowName, "stats.total", 1);
|
|
501
|
-
await store.index.increment(flowIndexKey, flowName, "stats.running", 1);
|
|
502
|
-
}
|
|
503
|
-
if (store.index.updateWithRetry) {
|
|
504
|
-
await store.index.updateWithRetry(flowIndexKey, flowName, {
|
|
505
|
-
lastRunAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
506
|
-
});
|
|
507
|
-
}
|
|
508
|
-
logger.debug("Updated flow stats for start", { flowName });
|
|
509
|
-
} else if (e.type === "flow.completed") {
|
|
510
|
-
if (store.index.increment) {
|
|
511
|
-
await store.index.increment(flowIndexKey, flowName, "stats.running", -1);
|
|
512
|
-
await store.index.increment(flowIndexKey, flowName, "stats.success", 1);
|
|
513
|
-
}
|
|
514
|
-
if (store.index.updateWithRetry) {
|
|
515
|
-
await store.index.updateWithRetry(flowIndexKey, flowName, {
|
|
516
|
-
lastCompletedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
517
|
-
});
|
|
518
|
-
}
|
|
519
|
-
logger.debug("Updated flow stats for completion", { flowName });
|
|
520
|
-
} else if (e.type === "flow.failed") {
|
|
521
|
-
if (store.index.increment) {
|
|
522
|
-
await store.index.increment(flowIndexKey, flowName, "stats.running", -1);
|
|
523
|
-
await store.index.increment(flowIndexKey, flowName, "stats.failure", 1);
|
|
524
|
-
}
|
|
525
|
-
if (store.index.updateWithRetry) {
|
|
526
|
-
await store.index.updateWithRetry(flowIndexKey, flowName, {
|
|
527
|
-
lastCompletedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
528
|
-
});
|
|
529
|
-
}
|
|
530
|
-
logger.debug("Updated flow stats for failure", { flowName });
|
|
531
|
-
} else if (e.type === "flow.cancel") {
|
|
532
|
-
if (store.index.increment) {
|
|
533
|
-
const previousStatus = e.data?.previousStatus;
|
|
534
|
-
if (previousStatus === "awaiting") {
|
|
535
|
-
await store.index.increment(flowIndexKey, flowName, "stats.awaiting", -1);
|
|
536
|
-
} else {
|
|
537
|
-
await store.index.increment(flowIndexKey, flowName, "stats.running", -1);
|
|
538
|
-
}
|
|
539
|
-
await store.index.increment(flowIndexKey, flowName, "stats.cancel", 1);
|
|
540
|
-
}
|
|
541
|
-
if (store.index.updateWithRetry) {
|
|
542
|
-
await store.index.updateWithRetry(flowIndexKey, flowName, {
|
|
543
|
-
lastCompletedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
544
|
-
});
|
|
545
|
-
}
|
|
546
|
-
logger.debug("Updated flow stats for cancellation", { flowName, previousStatus: e.data?.previousStatus });
|
|
547
|
-
} else if (e.type === "flow.stalled") {
|
|
548
|
-
if (store.index.increment && e.data?.previousStatus) {
|
|
549
|
-
if (e.data.previousStatus === "awaiting") {
|
|
550
|
-
await store.index.increment(flowIndexKey, flowName, "stats.awaiting", -1);
|
|
551
|
-
logger.debug("Updated flow stats for stalled detection (was awaiting)", { flowName });
|
|
552
|
-
} else if (e.data.previousStatus === "running") {
|
|
553
|
-
await store.index.increment(flowIndexKey, flowName, "stats.running", -1);
|
|
554
|
-
logger.debug("Updated flow stats for stalled detection (was running)", { flowName });
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
} else if (e.type === "await.registered") {
|
|
558
|
-
if (store.index.increment) {
|
|
559
|
-
await store.index.increment(flowIndexKey, flowName, "stats.running", -1);
|
|
560
|
-
await store.index.increment(flowIndexKey, flowName, "stats.awaiting", 1);
|
|
561
|
-
}
|
|
562
|
-
logger.debug("Updated flow stats for await registered", { flowName });
|
|
563
|
-
} else if (e.type === "await.resolved" || e.type === "await.timeout") {
|
|
564
|
-
if (store.index.increment) {
|
|
565
|
-
await store.index.increment(flowIndexKey, flowName, "stats.awaiting", -1);
|
|
566
|
-
await store.index.increment(flowIndexKey, flowName, "stats.running", 1);
|
|
567
|
-
}
|
|
568
|
-
logger.debug("Updated flow stats for await resolved/timeout", { flowName, type: e.type });
|
|
569
|
-
}
|
|
570
|
-
try {
|
|
571
|
-
const indexEntry = await store.index.get(flowIndexKey, flowName);
|
|
572
|
-
if (indexEntry) {
|
|
573
|
-
await bus.publish({
|
|
574
|
-
type: "flow.stats.updated",
|
|
575
|
-
flowName,
|
|
576
|
-
id: indexEntry.id,
|
|
577
|
-
metadata: indexEntry.metadata,
|
|
578
|
-
ts: Date.now()
|
|
579
|
-
});
|
|
580
|
-
logger.debug("Published flow stats update event to bus", { flowName });
|
|
581
|
-
}
|
|
582
|
-
} catch (err) {
|
|
583
|
-
logger.warn("Failed to publish flow stats update event", {
|
|
584
|
-
flowName,
|
|
585
|
-
error: err?.message
|
|
586
|
-
});
|
|
587
|
-
}
|
|
588
|
-
} catch (err) {
|
|
589
|
-
const errorMsg = err?.message || "";
|
|
590
|
-
if (!errorMsg.includes("Entry not found")) {
|
|
591
|
-
logger.warn("Failed to update flow stats", {
|
|
592
|
-
type: e.type,
|
|
593
|
-
flowName: e.flowName,
|
|
594
|
-
error: errorMsg
|
|
595
|
-
});
|
|
596
|
-
} else {
|
|
597
|
-
logger.debug("Flow entry not found (will be created)", {
|
|
598
|
-
type: e.type,
|
|
599
|
-
flowName: e.flowName
|
|
600
|
-
});
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
};
|
|
604
|
-
const handleOrchestration = async (e) => {
|
|
605
|
-
try {
|
|
606
|
-
if (e.id && e.ts) {
|
|
607
|
-
return;
|
|
608
|
-
}
|
|
609
|
-
if (e.type === "flow.completed" || e.type === "flow.failed") {
|
|
610
|
-
return;
|
|
611
|
-
}
|
|
612
|
-
const runId = e.runId;
|
|
613
|
-
if (!runId) return;
|
|
614
|
-
const flowName = e.flowName;
|
|
615
|
-
if (!flowName) return;
|
|
616
|
-
const streamName = StoreSubjects.flowRun(runId);
|
|
617
|
-
const indexKey = StoreSubjects.flowRunIndex(flowName);
|
|
618
|
-
if (e.type === "flow.start") {
|
|
619
|
-
const timestamp = Date.now();
|
|
620
|
-
await indexFlowRun(flowName, runId, timestamp, {
|
|
621
|
-
status: "running",
|
|
622
|
-
startedAt: timestamp,
|
|
623
|
-
lastActivityAt: timestamp,
|
|
624
|
-
// Initialize for stall detection
|
|
625
|
-
stepCount: 0,
|
|
626
|
-
completedSteps: 0,
|
|
627
|
-
emittedEvents: {}
|
|
628
|
-
// Object for atomic updates
|
|
629
|
-
});
|
|
630
|
-
try {
|
|
631
|
-
const analyzedFlows = $useAnalyzedFlows();
|
|
632
|
-
const flowMeta = analyzedFlows.find((f) => f.id === flowName);
|
|
633
|
-
const stallTimeout = flowMeta?.analyzed?.stallTimeout || 30 * 60 * 1e3;
|
|
634
|
-
const scheduler = useScheduler();
|
|
635
|
-
const stallJobId = `stall-timeout:${runId}`;
|
|
636
|
-
await scheduler.schedule({
|
|
637
|
-
id: stallJobId,
|
|
638
|
-
name: `Stall Timeout - ${flowName}`,
|
|
639
|
-
type: "one-time",
|
|
640
|
-
executeAt: timestamp + stallTimeout,
|
|
641
|
-
handler: async () => {
|
|
642
|
-
if (stallDetector) {
|
|
643
|
-
logger.info(`Per-flow stall timeout fired for '${flowName}' runId '${runId}'`);
|
|
644
|
-
await stallDetector.markAsStalled(flowName, runId, "Stall timeout reached");
|
|
645
|
-
}
|
|
646
|
-
},
|
|
647
|
-
metadata: {
|
|
648
|
-
component: "stall-detector",
|
|
649
|
-
flowName,
|
|
650
|
-
runId
|
|
651
|
-
}
|
|
652
|
-
});
|
|
653
|
-
logger.debug(`Scheduled stall timeout for flow '${flowName}' runId '${runId}' in ${stallTimeout / 1e3}s`, { jobId: stallJobId });
|
|
654
|
-
} catch (error) {
|
|
655
|
-
logger.warn(`Failed to schedule stall timeout for flow '${flowName}' runId '${runId}'`, {
|
|
656
|
-
error: error.message
|
|
657
|
-
});
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
if (e.type === "flow.cancel") {
|
|
661
|
-
try {
|
|
662
|
-
if (store.index.updateWithRetry) {
|
|
663
|
-
await store.index.updateWithRetry(indexKey, runId, {
|
|
664
|
-
status: "canceled",
|
|
665
|
-
completedAt: Date.now()
|
|
666
|
-
});
|
|
667
|
-
logger.info("Marked flow as canceled", { flowName, runId });
|
|
668
|
-
}
|
|
669
|
-
const scheduler = useScheduler();
|
|
670
|
-
const flowJobs = await scheduler.getJobsByPattern(runId);
|
|
671
|
-
for (const job of flowJobs) {
|
|
672
|
-
await scheduler.unschedule(job.id);
|
|
673
|
-
logger.debug(`Unscheduled job for canceled flow: ${job.id}`);
|
|
674
|
-
}
|
|
675
|
-
logger.debug(`Unscheduled ${flowJobs.length} scheduled jobs for canceled flow runId '${runId}'`, {
|
|
676
|
-
jobs: flowJobs.map((j) => j.id)
|
|
677
|
-
});
|
|
678
|
-
} catch (err) {
|
|
679
|
-
logger.warn("Failed to update canceled status or unschedule flow jobs", {
|
|
680
|
-
flowName,
|
|
681
|
-
runId,
|
|
682
|
-
error: err?.message
|
|
683
|
-
});
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
if (e.type === "step.started" || e.type === "step.completed" || e.type === "step.failed" || e.type === "step.retry") {
|
|
687
|
-
try {
|
|
688
|
-
const scheduler = useScheduler();
|
|
689
|
-
const stallJobId = `stall-timeout:${runId}`;
|
|
690
|
-
const analyzedFlows = $useAnalyzedFlows();
|
|
691
|
-
const flowMeta = analyzedFlows.find((f) => f.id === flowName);
|
|
692
|
-
const stallTimeout = flowMeta?.analyzed?.stallTimeout || 30 * 60 * 1e3;
|
|
693
|
-
await scheduler.unschedule(stallJobId);
|
|
694
|
-
await scheduler.schedule({
|
|
695
|
-
id: stallJobId,
|
|
696
|
-
name: `Stall Timeout - ${flowName}`,
|
|
697
|
-
type: "one-time",
|
|
698
|
-
executeAt: Date.now() + stallTimeout,
|
|
699
|
-
handler: async () => {
|
|
700
|
-
if (stallDetector) {
|
|
701
|
-
logger.info(`Per-flow stall timeout fired for '${flowName}' runId '${runId}'`);
|
|
702
|
-
await stallDetector.markAsStalled(flowName, runId, "Stall timeout reached");
|
|
703
|
-
}
|
|
704
|
-
},
|
|
705
|
-
metadata: {
|
|
706
|
-
component: "stall-detector",
|
|
707
|
-
flowName,
|
|
708
|
-
runId,
|
|
709
|
-
stallTimeout
|
|
710
|
-
}
|
|
711
|
-
});
|
|
712
|
-
logger.debug(`Rescheduled stall timeout for flow '${flowName}' runId '${runId}' (activity: ${e.type})`);
|
|
713
|
-
} catch (error) {
|
|
714
|
-
logger.debug(`Could not reschedule stall timeout for flow '${flowName}' runId '${runId}'`, {
|
|
715
|
-
error: error.message
|
|
716
|
-
});
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
if (e.type === "step.completed") {
|
|
720
|
-
try {
|
|
721
|
-
if (store.index.increment) {
|
|
722
|
-
const newCount = await store.index.increment(indexKey, runId, "completedSteps", 1);
|
|
723
|
-
logger.debug("Incremented completedSteps", {
|
|
724
|
-
flowName,
|
|
725
|
-
runId,
|
|
726
|
-
stepName: "stepName" in e ? e.stepName : "unknown",
|
|
727
|
-
newCount
|
|
728
|
-
});
|
|
729
|
-
}
|
|
730
|
-
} catch (err) {
|
|
731
|
-
logger.warn("Failed to update completedSteps", {
|
|
732
|
-
flowName,
|
|
733
|
-
runId,
|
|
734
|
-
error: err?.message
|
|
735
|
-
});
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
if (e.type === "await.registered") {
|
|
739
|
-
const awaitEvent = e;
|
|
740
|
-
const { stepName, awaitType, position, config: config2 } = awaitEvent;
|
|
741
|
-
try {
|
|
742
|
-
if (store.index.updateWithRetry) {
|
|
743
|
-
if (store.index.get) {
|
|
744
|
-
const currentEntry = await store.index.get(indexKey, runId);
|
|
745
|
-
const currentStatus = currentEntry?.metadata?.status;
|
|
746
|
-
if (currentStatus === "canceled") {
|
|
747
|
-
logger.debug("Flow already canceled, skipping await registration", { flowName, runId, stepName });
|
|
748
|
-
return;
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
const now = Date.now();
|
|
752
|
-
let timeoutAt;
|
|
753
|
-
if (awaitType === "time" && config2.delay) {
|
|
754
|
-
timeoutAt = now + config2.delay;
|
|
755
|
-
} else if (awaitType === "schedule" && config2.nextOccurrence) {
|
|
756
|
-
timeoutAt = config2.nextOccurrence;
|
|
757
|
-
} else if (config2.timeout) {
|
|
758
|
-
timeoutAt = now + config2.timeout;
|
|
759
|
-
}
|
|
760
|
-
if (!timeoutAt) {
|
|
761
|
-
timeoutAt = now + 24 * 60 * 60 * 1e3;
|
|
762
|
-
}
|
|
763
|
-
const awaitKey = `${stepName}:${position}`;
|
|
764
|
-
const updatePayload = {
|
|
765
|
-
status: "awaiting",
|
|
766
|
-
// Set flow status to awaiting
|
|
767
|
-
awaitingSteps: {
|
|
768
|
-
[awaitKey]: {
|
|
769
|
-
status: "awaiting",
|
|
770
|
-
stepName,
|
|
771
|
-
// Keep stepName for queries
|
|
772
|
-
awaitType,
|
|
773
|
-
position,
|
|
774
|
-
config: config2,
|
|
775
|
-
registeredAt: now,
|
|
776
|
-
timeoutAt
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
};
|
|
780
|
-
await store.index.updateWithRetry(indexKey, runId, updatePayload);
|
|
781
|
-
logger.info("Await registered in index", {
|
|
782
|
-
runId,
|
|
783
|
-
stepName,
|
|
784
|
-
awaitType,
|
|
785
|
-
position,
|
|
786
|
-
timeoutAt: new Date(timeoutAt).toISOString()
|
|
787
|
-
});
|
|
788
|
-
}
|
|
789
|
-
} catch (err) {
|
|
790
|
-
logger.error("Error updating await status", {
|
|
791
|
-
runId,
|
|
792
|
-
stepName,
|
|
793
|
-
error: err?.message
|
|
794
|
-
});
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
if (e.type === "await.resolved") {
|
|
798
|
-
const awaitEvent = e;
|
|
799
|
-
const { stepName, triggerData, position } = awaitEvent;
|
|
800
|
-
try {
|
|
801
|
-
if (store.index.get) {
|
|
802
|
-
const currentEntry = await store.index.get(indexKey, runId);
|
|
803
|
-
const currentStatus = currentEntry?.metadata?.status;
|
|
804
|
-
if (currentStatus === "canceled") {
|
|
805
|
-
logger.debug("Flow already canceled, skipping await resolution", { flowName, runId, stepName });
|
|
806
|
-
return;
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
if (store.index.updateWithRetry) {
|
|
810
|
-
const awaitKey = `${stepName}:${position}`;
|
|
811
|
-
await store.index.updateWithRetry(indexKey, runId, {
|
|
812
|
-
awaitingSteps: {
|
|
813
|
-
[awaitKey]: {
|
|
814
|
-
status: "resolved",
|
|
815
|
-
stepName,
|
|
816
|
-
// Keep stepName for queries
|
|
817
|
-
triggerData,
|
|
818
|
-
position
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
});
|
|
822
|
-
}
|
|
823
|
-
const queue = useQueueAdapter();
|
|
824
|
-
const { StoreSubjects: StoreSubjects2 } = useStreamTopics();
|
|
825
|
-
const streamName2 = StoreSubjects2.flowRun(runId);
|
|
826
|
-
const inputData = {};
|
|
827
|
-
if (store.stream.read) {
|
|
828
|
-
const events = await store.stream.read(streamName2, { limit: 100 });
|
|
829
|
-
const registry2 = $useFunctionRegistry();
|
|
830
|
-
const flowRegistry2 = (registry2?.flows || {})[flowName];
|
|
831
|
-
const stepMeta2 = flowRegistry2?.steps?.[stepName];
|
|
832
|
-
const subscribes = stepMeta2?.subscribes || [];
|
|
833
|
-
for (const sub of subscribes) {
|
|
834
|
-
const emitEvent = events.find(
|
|
835
|
-
(evt) => evt.type === "emit" && evt.data?.name === sub
|
|
836
|
-
);
|
|
837
|
-
if (emitEvent && emitEvent.data?.payload !== void 0) {
|
|
838
|
-
inputData[sub] = emitEvent.data.payload;
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
const { SYSTEM_HANDLERS: SYSTEM_HANDLERS2 } = await import("../../worker/system/index.js");
|
|
843
|
-
const payload = {
|
|
844
|
-
flowId: runId,
|
|
845
|
-
flowName,
|
|
846
|
-
stepName,
|
|
847
|
-
position,
|
|
848
|
-
triggerData,
|
|
849
|
-
input: inputData
|
|
850
|
-
};
|
|
851
|
-
const jobId = `${runId}__${stepName}__await-resolve`;
|
|
852
|
-
const registry = $useFunctionRegistry();
|
|
853
|
-
const flowRegistry = (registry?.flows || {})[flowName];
|
|
854
|
-
const stepMeta = flowRegistry?.steps?.[stepName];
|
|
855
|
-
let stepQueue = stepMeta?.queue;
|
|
856
|
-
if (!stepQueue && flowRegistry?.entry?.step === stepName) {
|
|
857
|
-
stepQueue = flowRegistry.entry.queue;
|
|
858
|
-
}
|
|
859
|
-
if (!stepQueue && registry?.workers) {
|
|
860
|
-
const worker = registry.workers.find((w) => {
|
|
861
|
-
const flowNames = w?.flow?.names || (w?.flow?.name ? [w?.flow?.name] : []);
|
|
862
|
-
const stepMatch = w?.flow?.step === stepName || Array.isArray(w?.flow?.step) && w?.flow?.step.includes(stepName);
|
|
863
|
-
return flowNames.includes(flowName) && stepMatch;
|
|
864
|
-
});
|
|
865
|
-
stepQueue = worker?.queue?.name;
|
|
866
|
-
}
|
|
867
|
-
if (!stepQueue) {
|
|
868
|
-
logger.error("Cannot find queue for step", {
|
|
869
|
-
stepName,
|
|
870
|
-
flowName,
|
|
871
|
-
availableSteps: Object.keys(flowRegistry?.steps || {}),
|
|
872
|
-
entryStep: flowRegistry?.entry?.step
|
|
873
|
-
});
|
|
874
|
-
throw new Error(`Cannot resolve await: queue not found for step ${stepName} in flow ${flowName}`);
|
|
875
|
-
}
|
|
876
|
-
await queue.enqueue(stepQueue, {
|
|
877
|
-
name: SYSTEM_HANDLERS2.AWAIT_RESOLVE,
|
|
878
|
-
data: payload,
|
|
879
|
-
opts: { jobId }
|
|
880
|
-
});
|
|
881
|
-
await checkAndTriggerPendingSteps(flowName, runId, store);
|
|
882
|
-
} catch (err) {
|
|
883
|
-
logger.error("Error handling await resolution", {
|
|
884
|
-
runId,
|
|
885
|
-
stepName,
|
|
886
|
-
error: err?.message
|
|
887
|
-
});
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
if (e.type === "await.timeout") {
|
|
891
|
-
const timeoutEvent = e;
|
|
892
|
-
const { stepName, timeoutAction, position, awaitType } = timeoutEvent;
|
|
893
|
-
const action = timeoutAction || "fail";
|
|
894
|
-
if (store.index.get) {
|
|
895
|
-
const currentEntry = await store.index.get(indexKey, runId);
|
|
896
|
-
const currentStatus = currentEntry?.metadata?.status;
|
|
897
|
-
if (currentStatus === "canceled") {
|
|
898
|
-
logger.debug("Flow already canceled, skipping await timeout handling", { flowName, runId, stepName });
|
|
899
|
-
return;
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
logger.warn("Await timeout occurred", {
|
|
903
|
-
runId,
|
|
904
|
-
stepName,
|
|
905
|
-
awaitType,
|
|
906
|
-
position,
|
|
907
|
-
action
|
|
908
|
-
});
|
|
909
|
-
try {
|
|
910
|
-
const queue = useQueueAdapter();
|
|
911
|
-
const { StoreSubjects: StoreSubjects2 } = useStreamTopics();
|
|
912
|
-
const streamName2 = StoreSubjects2.flowRun(runId);
|
|
913
|
-
const inputData = {};
|
|
914
|
-
if (store.stream.read) {
|
|
915
|
-
const events = await store.stream.read(streamName2, { limit: 100 });
|
|
916
|
-
const registry2 = $useFunctionRegistry();
|
|
917
|
-
const flowRegistry2 = (registry2?.flows || {})[flowName];
|
|
918
|
-
const stepMeta2 = flowRegistry2?.steps?.[stepName];
|
|
919
|
-
const subscribes = stepMeta2?.subscribes || [];
|
|
920
|
-
for (const sub of subscribes) {
|
|
921
|
-
const emitEvent = events.find(
|
|
922
|
-
(evt) => evt.type === "emit" && evt.data?.name === sub
|
|
923
|
-
);
|
|
924
|
-
if (emitEvent && emitEvent.data?.payload !== void 0) {
|
|
925
|
-
inputData[sub] = emitEvent.data.payload;
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
|
-
const payload = {
|
|
930
|
-
flowId: runId,
|
|
931
|
-
flowName,
|
|
932
|
-
stepName,
|
|
933
|
-
position,
|
|
934
|
-
timeoutAction: action,
|
|
935
|
-
input: inputData
|
|
936
|
-
};
|
|
937
|
-
const jobId = `${runId}__${stepName}__await-timeout`;
|
|
938
|
-
const registry = $useFunctionRegistry();
|
|
939
|
-
const flowRegistry = (registry?.flows || {})[flowName];
|
|
940
|
-
const stepMeta = flowRegistry?.steps?.[stepName];
|
|
941
|
-
let stepQueue = stepMeta?.queue;
|
|
942
|
-
if (!stepQueue && flowRegistry?.entry?.step === stepName) {
|
|
943
|
-
stepQueue = flowRegistry.entry.queue;
|
|
944
|
-
}
|
|
945
|
-
if (!stepQueue && registry?.workers) {
|
|
946
|
-
const worker = registry.workers.find((w) => {
|
|
947
|
-
const flowNames = w?.flow?.names || (w?.flow?.name ? [w?.flow?.name] : []);
|
|
948
|
-
const stepMatch = w?.flow?.step === stepName || Array.isArray(w?.flow?.step) && w?.flow?.step.includes(stepName);
|
|
949
|
-
return flowNames.includes(flowName) && stepMatch;
|
|
950
|
-
});
|
|
951
|
-
stepQueue = worker?.queue?.name;
|
|
952
|
-
}
|
|
953
|
-
if (!stepQueue) {
|
|
954
|
-
logger.error("Cannot find queue for step", {
|
|
955
|
-
stepName,
|
|
956
|
-
flowName,
|
|
957
|
-
availableSteps: Object.keys(flowRegistry?.steps || {}),
|
|
958
|
-
entryStep: flowRegistry?.entry?.step
|
|
959
|
-
});
|
|
960
|
-
throw new Error(`Cannot handle await timeout: queue not found for step ${stepName} in flow ${flowName}`);
|
|
961
|
-
}
|
|
962
|
-
await queue.enqueue(stepQueue, {
|
|
963
|
-
name: SYSTEM_HANDLERS.AWAIT_TIMEOUT,
|
|
964
|
-
data: payload,
|
|
965
|
-
opts: { jobId }
|
|
966
|
-
});
|
|
967
|
-
if (action === "fail") {
|
|
968
|
-
if (store.index.updateWithRetry) {
|
|
969
|
-
await store.index.updateWithRetry(indexKey, runId, {
|
|
970
|
-
awaitingSteps: {
|
|
971
|
-
[stepName]: {
|
|
972
|
-
status: "timeout",
|
|
973
|
-
timedOutAt: Date.now()
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
});
|
|
977
|
-
}
|
|
978
|
-
bus.publish({
|
|
979
|
-
type: "step.failed",
|
|
980
|
-
runId,
|
|
981
|
-
flowName,
|
|
982
|
-
stepName,
|
|
983
|
-
stepId: `${runId}__${stepName}__timeout`,
|
|
984
|
-
attempt: 1,
|
|
985
|
-
data: {
|
|
986
|
-
error: `Await timeout: ${awaitType} await exceeded timeout`
|
|
987
|
-
}
|
|
988
|
-
});
|
|
989
|
-
} else if (action === "continue") {
|
|
990
|
-
if (store.index.updateWithRetry) {
|
|
991
|
-
await store.index.updateWithRetry(indexKey, runId, {
|
|
992
|
-
awaitingSteps: {
|
|
993
|
-
[stepName]: {
|
|
994
|
-
status: "resolved",
|
|
995
|
-
triggerData: null,
|
|
996
|
-
timedOutAt: Date.now()
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
});
|
|
1000
|
-
}
|
|
1001
|
-
logger.info("Await timeout - continuing with null data", { runId, stepName });
|
|
1002
|
-
await checkAndTriggerPendingSteps(flowName, runId, store);
|
|
1003
|
-
}
|
|
1004
|
-
} catch (err) {
|
|
1005
|
-
logger.error("Error handling await timeout", {
|
|
1006
|
-
runId,
|
|
1007
|
-
stepName,
|
|
1008
|
-
error: err?.message
|
|
1009
|
-
});
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
if (e.type === "emit") {
|
|
1013
|
-
const eventName = e.data?.name || e.data?.topic;
|
|
1014
|
-
if (!eventName) {
|
|
1015
|
-
logger.warn("Emit event missing name/topic", { flowName, runId, data: e.data });
|
|
1016
|
-
} else {
|
|
1017
|
-
try {
|
|
1018
|
-
if (!store.index.updateWithRetry) {
|
|
1019
|
-
logger.warn("StoreAdapter does not support indexUpdateWithRetry");
|
|
1020
|
-
return;
|
|
1021
|
-
}
|
|
1022
|
-
const timestamp = Date.now();
|
|
1023
|
-
const eventParts = eventName.split(".");
|
|
1024
|
-
const emittedEventsUpdate = {};
|
|
1025
|
-
let current = emittedEventsUpdate;
|
|
1026
|
-
for (let i = 0; i < eventParts.length - 1; i++) {
|
|
1027
|
-
current[eventParts[i]] = {};
|
|
1028
|
-
current = current[eventParts[i]];
|
|
1029
|
-
}
|
|
1030
|
-
current[eventParts[eventParts.length - 1]] = timestamp;
|
|
1031
|
-
const updatePayload = {
|
|
1032
|
-
emittedEvents: emittedEventsUpdate
|
|
1033
|
-
};
|
|
1034
|
-
await store.index.updateWithRetry(indexKey, runId, updatePayload);
|
|
1035
|
-
logger.debug("Tracked emit event", {
|
|
1036
|
-
flowName,
|
|
1037
|
-
runId,
|
|
1038
|
-
name: eventName,
|
|
1039
|
-
timestamp
|
|
1040
|
-
});
|
|
1041
|
-
} catch (err) {
|
|
1042
|
-
logger.warn("Failed to track emitted event", {
|
|
1043
|
-
flowName,
|
|
1044
|
-
runId,
|
|
1045
|
-
event: eventName,
|
|
1046
|
-
error: err?.message
|
|
1047
|
-
});
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
}
|
|
1051
|
-
if (e.type === "step.completed") {
|
|
1052
|
-
try {
|
|
1053
|
-
await checkAndTriggerPendingSteps(flowName, runId, store);
|
|
1054
|
-
} catch (err) {
|
|
1055
|
-
logger.error("Error checking pending steps", {
|
|
1056
|
-
flowName,
|
|
1057
|
-
runId,
|
|
1058
|
-
error: err.message
|
|
1059
|
-
});
|
|
1060
|
-
throw err;
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
|
-
if (e.type === "step.completed" || e.type === "step.failed") {
|
|
1064
|
-
try {
|
|
1065
|
-
const allEvents = await store.stream.read(streamName);
|
|
1066
|
-
const analyzedFlows = $useAnalyzedFlows();
|
|
1067
|
-
const flowDef = analyzedFlows.find((f) => f.id === flowName);
|
|
1068
|
-
if (flowDef?.steps) {
|
|
1069
|
-
const entryStepName = flowDef.entry?.step;
|
|
1070
|
-
const entryStepDef = flowDef.entry;
|
|
1071
|
-
const analysis = analyzeFlowCompletion(flowDef.steps, entryStepName, allEvents, entryStepDef);
|
|
1072
|
-
const updateMetadata = {
|
|
1073
|
-
status: analysis.status,
|
|
1074
|
-
stepCount: analysis.totalSteps
|
|
1075
|
-
};
|
|
1076
|
-
if (analysis.status !== "running" && analysis.completedAt) {
|
|
1077
|
-
updateMetadata.completedAt = analysis.completedAt;
|
|
1078
|
-
}
|
|
1079
|
-
if (store.index.get) {
|
|
1080
|
-
const currentEntry = await store.index.get(indexKey, runId);
|
|
1081
|
-
const currentStatus = currentEntry?.metadata?.status;
|
|
1082
|
-
if (currentStatus === "canceled") {
|
|
1083
|
-
logger.debug("Flow already canceled, skipping status update", { flowName, runId });
|
|
1084
|
-
return;
|
|
1085
|
-
}
|
|
1086
|
-
const awaitingStepsObj = currentEntry?.metadata?.awaitingSteps || {};
|
|
1087
|
-
let hasActiveAwaits = false;
|
|
1088
|
-
let hasTimedOutAwaits = false;
|
|
1089
|
-
for (const [_stepName, awaitState] of Object.entries(awaitingStepsObj)) {
|
|
1090
|
-
if (awaitState?.status === "awaiting") {
|
|
1091
|
-
hasActiveAwaits = true;
|
|
1092
|
-
} else if (awaitState?.status === "timeout") {
|
|
1093
|
-
hasTimedOutAwaits = true;
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
if (hasActiveAwaits) {
|
|
1097
|
-
updateMetadata.status = "awaiting";
|
|
1098
|
-
}
|
|
1099
|
-
if (hasTimedOutAwaits) {
|
|
1100
|
-
updateMetadata.status = "failed";
|
|
1101
|
-
if (!updateMetadata.completedAt) {
|
|
1102
|
-
updateMetadata.completedAt = Date.now();
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
if (store.index.updateWithRetry) {
|
|
1107
|
-
await store.index.updateWithRetry(indexKey, runId, updateMetadata);
|
|
1108
|
-
}
|
|
1109
|
-
const finalStatus = updateMetadata.status || analysis.status;
|
|
1110
|
-
if (finalStatus === "completed" || finalStatus === "failed") {
|
|
1111
|
-
const eventType = finalStatus === "completed" ? "flow.completed" : "flow.failed";
|
|
1112
|
-
let currentStatus = null;
|
|
1113
|
-
if (store.index.get) {
|
|
1114
|
-
const currentEntry = await store.index.get(indexKey, runId);
|
|
1115
|
-
currentStatus = currentEntry?.metadata?.status;
|
|
1116
|
-
}
|
|
1117
|
-
const terminalEventExists = allEvents.some((evt) => evt.type === "flow.completed" || evt.type === "flow.failed");
|
|
1118
|
-
const publishKey = `${runId}:terminal`;
|
|
1119
|
-
const alreadyPublishing = publishingTerminalEvents.has(publishKey);
|
|
1120
|
-
if (terminalEventExists) {
|
|
1121
|
-
logger.debug("Flow terminal event already exists in stream, skipping publish", {
|
|
1122
|
-
flowName,
|
|
1123
|
-
runId,
|
|
1124
|
-
currentStatus,
|
|
1125
|
-
eventType
|
|
1126
|
-
});
|
|
1127
|
-
} else if (alreadyPublishing) {
|
|
1128
|
-
logger.debug("Flow terminal event already being published, skipping duplicate", {
|
|
1129
|
-
flowName,
|
|
1130
|
-
runId,
|
|
1131
|
-
eventType
|
|
1132
|
-
});
|
|
1133
|
-
} else {
|
|
1134
|
-
publishingTerminalEvents.add(publishKey);
|
|
1135
|
-
logger.info("Publishing terminal event to bus", {
|
|
1136
|
-
flowName,
|
|
1137
|
-
runId,
|
|
1138
|
-
eventType
|
|
1139
|
-
});
|
|
1140
|
-
await bus.publish({
|
|
1141
|
-
type: eventType,
|
|
1142
|
-
runId,
|
|
1143
|
-
flowName,
|
|
1144
|
-
data: {}
|
|
1145
|
-
});
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
|
-
}
|
|
1149
|
-
} catch (err) {
|
|
1150
|
-
logger.warn("Failed to analyze flow completion", {
|
|
1151
|
-
flowName,
|
|
1152
|
-
runId,
|
|
1153
|
-
error: err?.message
|
|
1154
|
-
});
|
|
1155
|
-
}
|
|
1156
|
-
}
|
|
1157
|
-
} catch (err) {
|
|
1158
|
-
logger.error("ERROR handling event", {
|
|
1159
|
-
type: e.type,
|
|
1160
|
-
runId: e.runId,
|
|
1161
|
-
flowName: e.flowName,
|
|
1162
|
-
error: err?.message
|
|
1163
|
-
});
|
|
1164
|
-
}
|
|
1165
|
-
};
|
|
1166
|
-
const processEventSequentially = async (event) => {
|
|
1167
|
-
const runId = event.runId;
|
|
1168
|
-
if (!runId) {
|
|
1169
|
-
await handlePersistence(event);
|
|
1170
|
-
await handleOrchestration(event);
|
|
1171
|
-
await handleFlowStats(event);
|
|
1172
|
-
return;
|
|
1173
|
-
}
|
|
1174
|
-
const previousProcessing = flowProcessingChain.get(runId) || Promise.resolve();
|
|
1175
|
-
const currentProcessing = previousProcessing.then(async () => {
|
|
1176
|
-
try {
|
|
1177
|
-
await handlePersistence(event);
|
|
1178
|
-
await handleOrchestration(event);
|
|
1179
|
-
await handleFlowStats(event);
|
|
1180
|
-
} catch (err) {
|
|
1181
|
-
logger.error("Error in sequential event processing", {
|
|
1182
|
-
runId,
|
|
1183
|
-
type: event.type,
|
|
1184
|
-
error: err.message,
|
|
1185
|
-
stack: err.stack
|
|
1186
|
-
});
|
|
1187
|
-
} finally {
|
|
1188
|
-
const existingTimer = cleanupTimers.get(runId);
|
|
1189
|
-
if (existingTimer) {
|
|
1190
|
-
clearTimeout(existingTimer);
|
|
1191
|
-
}
|
|
1192
|
-
const timer = setTimeout(() => {
|
|
1193
|
-
if (flowProcessingChain.get(runId) === currentProcessing) {
|
|
1194
|
-
flowProcessingChain.delete(runId);
|
|
1195
|
-
cleanupTimers.delete(runId);
|
|
1196
|
-
}
|
|
1197
|
-
}, 6e4);
|
|
1198
|
-
cleanupTimers.set(runId, timer);
|
|
1199
|
-
}
|
|
1200
|
-
});
|
|
1201
|
-
flowProcessingChain.set(runId, currentProcessing);
|
|
1202
|
-
return currentProcessing;
|
|
1203
|
-
};
|
|
1204
|
-
const eventTypes = [
|
|
1205
|
-
"flow.start",
|
|
1206
|
-
"flow.completed",
|
|
1207
|
-
"flow.failed",
|
|
1208
|
-
"flow.cancel",
|
|
1209
|
-
"step.started",
|
|
1210
|
-
"step.completed",
|
|
1211
|
-
"step.failed",
|
|
1212
|
-
"step.retry",
|
|
1213
|
-
"await.registered",
|
|
1214
|
-
"await.resolved",
|
|
1215
|
-
"await.timeout",
|
|
1216
|
-
"log",
|
|
1217
|
-
"emit",
|
|
1218
|
-
"state"
|
|
1219
|
-
];
|
|
1220
|
-
for (const type of eventTypes) {
|
|
1221
|
-
unsubs.push(bus.onType(type, processEventSequentially));
|
|
1222
|
-
}
|
|
1223
|
-
const config = useRuntimeConfig();
|
|
1224
|
-
const flowConfig = config.nvent.flow || {};
|
|
1225
|
-
stallDetector = createStallDetector(store, flowConfig.stallDetection);
|
|
1226
|
-
if (flowConfig.stallDetection?.enabled !== false) {
|
|
1227
|
-
await stallDetector.start();
|
|
1228
|
-
logger.info("Stall detector initialized - using per-flow scheduler jobs");
|
|
1229
|
-
}
|
|
1230
|
-
}
|
|
1231
|
-
async function stop() {
|
|
1232
|
-
const logger = useNventLogger("flow-wiring");
|
|
1233
|
-
if (stallDetector) {
|
|
1234
|
-
try {
|
|
1235
|
-
await stallDetector.stop();
|
|
1236
|
-
stallDetector = void 0;
|
|
1237
|
-
} catch (error) {
|
|
1238
|
-
logger.error("Error stopping stall detector", {
|
|
1239
|
-
error: error.message
|
|
1240
|
-
});
|
|
1241
|
-
}
|
|
1242
|
-
}
|
|
1243
|
-
for (const u of unsubs.splice(0)) {
|
|
1244
|
-
try {
|
|
1245
|
-
u();
|
|
1246
|
-
} catch {
|
|
1247
|
-
}
|
|
1248
|
-
}
|
|
1249
|
-
publishingTerminalEvents.clear();
|
|
1250
|
-
for (const timer of cleanupTimers.values()) {
|
|
1251
|
-
clearTimeout(timer);
|
|
1252
|
-
}
|
|
1253
|
-
cleanupTimers.clear();
|
|
1254
|
-
flowProcessingChain.clear();
|
|
1255
|
-
wired = false;
|
|
1256
|
-
}
|
|
1257
|
-
return { start, stop };
|
|
1258
|
-
}
|