nvent 0.5.3 → 0.5.5
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.json +1 -1
- package/dist/module.mjs +110 -24
- package/dist/runtime/adapters/factory.js +8 -7
- package/dist/runtime/adapters/interfaces/queue.d.ts +5 -0
- package/dist/runtime/config/index.js +14 -1
- package/dist/runtime/config/types.d.ts +42 -0
- package/dist/runtime/events/types.d.ts +0 -1
- package/dist/runtime/events/utils/stallDetector.d.ts +13 -77
- package/dist/runtime/events/utils/stallDetector.js +8 -192
- package/dist/runtime/events/wiring/flowWiring.js +311 -107
- package/dist/runtime/events/wiring/triggerWiring.js +3 -1
- package/dist/runtime/nitro/plugins/02.workers.js +31 -2
- package/dist/runtime/nitro/routes/webhook.await.js +28 -6
- package/dist/runtime/nitro/utils/awaitPatterns/event.js +58 -50
- package/dist/runtime/nitro/utils/awaitPatterns/schedule.js +6 -1
- package/dist/runtime/nitro/utils/awaitPatterns/time.d.ts +1 -1
- package/dist/runtime/nitro/utils/awaitPatterns/time.js +6 -2
- package/dist/runtime/nitro/utils/awaitPatterns/webhook.js +53 -45
- package/dist/runtime/nitro/utils/defineFunction.d.ts +2 -9
- package/dist/runtime/nitro/utils/defineFunction.js +1 -14
- package/dist/runtime/nitro/utils/defineFunctionConfig.d.ts +84 -16
- package/dist/runtime/nitro/utils/defineHooks.d.ts +64 -10
- package/dist/runtime/nitro/utils/defineHooks.js +3 -0
- package/dist/runtime/nitro/utils/useAwait.d.ts +12 -0
- package/dist/runtime/nitro/utils/useAwait.js +34 -4
- package/dist/runtime/nitro/utils/useFlow.js +13 -2
- package/dist/runtime/nitro/utils/useHookRegistry.d.ts +10 -4
- package/dist/runtime/scheduler/scheduler.d.ts +19 -0
- package/dist/runtime/scheduler/scheduler.js +184 -8
- package/dist/runtime/scheduler/types.d.ts +6 -0
- package/dist/runtime/worker/node/runner.js +32 -91
- package/dist/runtime/worker/system/awaitHandlers.d.ts +27 -0
- package/dist/runtime/worker/system/awaitHandlers.js +230 -0
- package/dist/runtime/worker/system/index.d.ts +24 -0
- package/dist/runtime/worker/system/index.js +39 -0
- package/package.json +1 -1
|
@@ -1,24 +1,19 @@
|
|
|
1
|
-
import { useNventLogger, useStreamTopics, $useAnalyzedFlows
|
|
2
|
-
const DEFAULT_STALL_TIMEOUT = 30 * 60 * 1e3;
|
|
3
|
-
const DEFAULT_CHECK_INTERVAL = 15 * 60 * 1e3;
|
|
1
|
+
import { useNventLogger, useStreamTopics, $useAnalyzedFlows } from "#imports";
|
|
4
2
|
export class FlowStallDetector {
|
|
5
3
|
store;
|
|
6
4
|
config;
|
|
7
5
|
logger = useNventLogger("stall-detector");
|
|
8
|
-
schedulerJobId;
|
|
9
6
|
started = false;
|
|
10
7
|
constructor(store, config = {}) {
|
|
11
8
|
this.store = store;
|
|
12
9
|
this.config = {
|
|
13
|
-
|
|
14
|
-
checkInterval: config.checkInterval ?? DEFAULT_CHECK_INTERVAL,
|
|
15
|
-
enablePeriodicCheck: config.enablePeriodicCheck ?? true
|
|
10
|
+
enabled: config.enabled ?? true
|
|
16
11
|
};
|
|
17
12
|
}
|
|
18
13
|
/**
|
|
19
|
-
* Start the
|
|
20
|
-
* Should be called once per instance after adapters are initialized
|
|
14
|
+
* Start the stall detector
|
|
21
15
|
* Runs startup recovery to clean up flows from previous server instances
|
|
16
|
+
* Note: Periodic checking removed - now uses per-flow scheduler jobs
|
|
22
17
|
*/
|
|
23
18
|
async start() {
|
|
24
19
|
if (this.started) {
|
|
@@ -27,108 +22,14 @@ export class FlowStallDetector {
|
|
|
27
22
|
}
|
|
28
23
|
this.started = true;
|
|
29
24
|
await this.runStartupRecovery();
|
|
30
|
-
this.logger.info(
|
|
25
|
+
this.logger.info("Stall detector started - using per-flow scheduler jobs for stall timeouts");
|
|
31
26
|
}
|
|
32
27
|
/**
|
|
33
|
-
*
|
|
34
|
-
* Returns config needed by flowWiring to register the scheduler job
|
|
35
|
-
*/
|
|
36
|
-
getScheduleConfig() {
|
|
37
|
-
return {
|
|
38
|
-
enabled: this.config.enablePeriodicCheck,
|
|
39
|
-
interval: this.config.checkInterval,
|
|
40
|
-
stallTimeout: this.config.stallTimeout
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Set the scheduler job ID (called from flowWiring after scheduling)
|
|
45
|
-
*/
|
|
46
|
-
setSchedulerJobId(jobId) {
|
|
47
|
-
this.schedulerJobId = jobId;
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Stop the periodic stall detector
|
|
28
|
+
* Stop the stall detector
|
|
51
29
|
*/
|
|
52
30
|
async stop() {
|
|
53
|
-
if (this.schedulerJobId) {
|
|
54
|
-
try {
|
|
55
|
-
const scheduler = useScheduler();
|
|
56
|
-
await scheduler.unschedule(this.schedulerJobId);
|
|
57
|
-
this.schedulerJobId = void 0;
|
|
58
|
-
this.logger.info("Stopped periodic stall detector");
|
|
59
|
-
} catch (error) {
|
|
60
|
-
this.logger.error(`Failed to stop stall detector: ${error.message}`);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
31
|
this.started = false;
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Get stall timeout for a specific flow
|
|
67
|
-
* Uses flow-specific timeout from analyzed metadata, falls back to global config
|
|
68
|
-
*/
|
|
69
|
-
async getFlowStallTimeout(flowName) {
|
|
70
|
-
try {
|
|
71
|
-
const analyzedFlows = $useAnalyzedFlows();
|
|
72
|
-
const flowMeta = analyzedFlows.find((f) => f.id === flowName);
|
|
73
|
-
if (flowMeta?.stallTimeout) {
|
|
74
|
-
this.logger.debug(`Using flow-specific stall timeout for '${flowName}': ${flowMeta.stallTimeout / 1e3}s`);
|
|
75
|
-
return flowMeta.stallTimeout;
|
|
76
|
-
}
|
|
77
|
-
} catch (error) {
|
|
78
|
-
this.logger.warn(`Failed to get flow-specific stall timeout for '${flowName}': ${error.message}`);
|
|
79
|
-
}
|
|
80
|
-
return this.config.stallTimeout;
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Update activity timestamp for a flow
|
|
84
|
-
* Should be called on every step event (started, completed, failed, retry)
|
|
85
|
-
*/
|
|
86
|
-
async updateActivity(flowName, runId) {
|
|
87
|
-
const { StoreSubjects } = useStreamTopics();
|
|
88
|
-
const indexKey = StoreSubjects.flowRunIndex(flowName);
|
|
89
|
-
try {
|
|
90
|
-
if (!this.store.index.update) {
|
|
91
|
-
this.logger.warn("Store does not support indexUpdate, cannot update activity");
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
await this.store.index.update(indexKey, runId, {
|
|
95
|
-
lastActivityAt: Date.now()
|
|
96
|
-
});
|
|
97
|
-
} catch (error) {
|
|
98
|
-
this.logger.warn(`Failed to update flow activity for '${flowName}' runId '${runId}': ${error.message}`);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Check if a specific flow is stalled (lazy detection)
|
|
103
|
-
* Returns true if the flow should be marked as stalled
|
|
104
|
-
* v0.5: Await-aware - uses flow-specific timeout and skips awaiting flows
|
|
105
|
-
*/
|
|
106
|
-
async isStalled(flowName, runId) {
|
|
107
|
-
const { StoreSubjects } = useStreamTopics();
|
|
108
|
-
const indexKey = StoreSubjects.flowRunIndex(flowName);
|
|
109
|
-
try {
|
|
110
|
-
if (!this.store.index.get) return false;
|
|
111
|
-
const flowEntry = await this.store.index.get(indexKey, runId);
|
|
112
|
-
if (!flowEntry?.metadata) return false;
|
|
113
|
-
if (flowEntry.metadata.status !== "running") return false;
|
|
114
|
-
const awaitingSteps = flowEntry.metadata.awaitingSteps || {};
|
|
115
|
-
const hasActiveAwaits = Object.keys(awaitingSteps).length > 0;
|
|
116
|
-
if (hasActiveAwaits) {
|
|
117
|
-
this.logger.debug(`Flow '${flowName}' runId '${runId}' has active awaits [${Object.keys(awaitingSteps).join(", ")}], skipping stall check`);
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
const stallTimeout = await this.getFlowStallTimeout(flowName);
|
|
121
|
-
const lastActivity = flowEntry.metadata.lastActivityAt || flowEntry.metadata.startedAt || 0;
|
|
122
|
-
const timeSinceActivity = Date.now() - lastActivity;
|
|
123
|
-
if (timeSinceActivity > stallTimeout) {
|
|
124
|
-
this.logger.info(`Flow detected as stalled (lazy check) - '${flowName}' runId '${runId}': ${Math.round(timeSinceActivity / 1e3)}s since activity (timeout: ${stallTimeout / 1e3}s)`);
|
|
125
|
-
return true;
|
|
126
|
-
}
|
|
127
|
-
return false;
|
|
128
|
-
} catch (error) {
|
|
129
|
-
this.logger.warn(`Failed to check if flow is stalled for '${flowName}' runId '${runId}': ${error.message}`);
|
|
130
|
-
return false;
|
|
131
|
-
}
|
|
32
|
+
this.logger.info("Stall detector stopped");
|
|
132
33
|
}
|
|
133
34
|
/**
|
|
134
35
|
* Mark a flow as stalled
|
|
@@ -171,72 +72,6 @@ export class FlowStallDetector {
|
|
|
171
72
|
this.logger.error(`Failed to mark flow as stalled for '${flowName}' runId '${runId}': ${error.message}`);
|
|
172
73
|
}
|
|
173
74
|
}
|
|
174
|
-
/**
|
|
175
|
-
* Check all running flows and mark stalled ones
|
|
176
|
-
* This is called by the periodic background job
|
|
177
|
-
*
|
|
178
|
-
* Note: This method requires knowledge of which flows exist.
|
|
179
|
-
* For now, we'll need to pass flow names to check, or iterate known flows from registry.
|
|
180
|
-
*/
|
|
181
|
-
async checkFlowsForStalls(flowNames) {
|
|
182
|
-
this.logger.info(`Running periodic stall check for ${flowNames.length} flows`);
|
|
183
|
-
try {
|
|
184
|
-
if (!this.store.index.get || !this.store.index.read) {
|
|
185
|
-
this.logger.warn("Store does not support required index operations");
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
const { StoreSubjects } = useStreamTopics();
|
|
189
|
-
let checkedCount = 0;
|
|
190
|
-
let stalledCount = 0;
|
|
191
|
-
for (const flowName of flowNames) {
|
|
192
|
-
const indexKey = StoreSubjects.flowRunIndex(flowName);
|
|
193
|
-
const flowStallTimeout = await this.getFlowStallTimeout(flowName);
|
|
194
|
-
const entries = await this.store.index.read(indexKey, { limit: 1e3 });
|
|
195
|
-
for (const entry of entries) {
|
|
196
|
-
if (!entry.metadata) continue;
|
|
197
|
-
checkedCount++;
|
|
198
|
-
if (entry.metadata.status !== "running" && entry.metadata.status !== "awaiting") continue;
|
|
199
|
-
const awaitingSteps = entry.metadata.awaitingSteps || {};
|
|
200
|
-
const awaitingStepNames = Object.keys(awaitingSteps);
|
|
201
|
-
if (awaitingStepNames.length > 0) {
|
|
202
|
-
let hasOverdueAwaits = false;
|
|
203
|
-
let hasLegacyAwait = false;
|
|
204
|
-
for (const stepName of awaitingStepNames) {
|
|
205
|
-
const awaitState = awaitingSteps[stepName];
|
|
206
|
-
if (awaitState?.status === "awaiting") {
|
|
207
|
-
if (!awaitState.timeoutAt) {
|
|
208
|
-
hasLegacyAwait = true;
|
|
209
|
-
} else if (Date.now() > awaitState.timeoutAt) {
|
|
210
|
-
hasOverdueAwaits = true;
|
|
211
|
-
break;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
if (hasOverdueAwaits) {
|
|
216
|
-
await this.markAsStalled(flowName, entry.id, "Await pattern timed out");
|
|
217
|
-
stalledCount++;
|
|
218
|
-
continue;
|
|
219
|
-
}
|
|
220
|
-
if (hasLegacyAwait) {
|
|
221
|
-
this.logger.debug(`Skipping flow with legacy await (no timeout) - '${flowName}' runId '${entry.id}'`);
|
|
222
|
-
}
|
|
223
|
-
continue;
|
|
224
|
-
}
|
|
225
|
-
const lastActivity = entry.metadata.lastActivityAt || entry.metadata.startedAt || 0;
|
|
226
|
-
const timeSinceActivity = Date.now() - lastActivity;
|
|
227
|
-
if (timeSinceActivity > flowStallTimeout) {
|
|
228
|
-
await this.markAsStalled(flowName, entry.id, "Periodic check detected no activity");
|
|
229
|
-
stalledCount++;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
this.logger.info(`Periodic stall check completed - checked: ${checkedCount}, stalled: ${stalledCount}`);
|
|
234
|
-
} catch (error) {
|
|
235
|
-
this.logger.error("Failed to run periodic stall check", {
|
|
236
|
-
error: error.message
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
75
|
/**
|
|
241
76
|
* Run startup recovery to clean up flows left in running state from previous server instance
|
|
242
77
|
* This marks all running flows as stalled since their in-memory state is lost
|
|
@@ -402,32 +237,13 @@ export class FlowStallDetector {
|
|
|
402
237
|
this.logger.error(`Failed to validate flow stats: ${error.message}`);
|
|
403
238
|
}
|
|
404
239
|
}
|
|
405
|
-
/**
|
|
406
|
-
* Internal method for periodic checks
|
|
407
|
-
* Gets flow names from registry and checks them
|
|
408
|
-
*/
|
|
409
|
-
async checkAllRunningFlows() {
|
|
410
|
-
try {
|
|
411
|
-
const analyzedFlows = $useAnalyzedFlows();
|
|
412
|
-
const flowNames = analyzedFlows.map((f) => f.id).filter(Boolean);
|
|
413
|
-
if (flowNames.length === 0) {
|
|
414
|
-
this.logger.debug("No flows registered, skipping stall check");
|
|
415
|
-
return;
|
|
416
|
-
}
|
|
417
|
-
await this.checkFlowsForStalls(flowNames);
|
|
418
|
-
} catch (error) {
|
|
419
|
-
this.logger.error(`Failed to run periodic stall check: ${error.message}`);
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
240
|
/**
|
|
423
241
|
* Get stall detector statistics
|
|
424
242
|
*/
|
|
425
243
|
getStats() {
|
|
426
244
|
return {
|
|
427
245
|
enabled: this.started,
|
|
428
|
-
|
|
429
|
-
stallTimeout: this.config.stallTimeout,
|
|
430
|
-
checkInterval: this.config.checkInterval
|
|
246
|
+
mode: "per-flow-scheduler"
|
|
431
247
|
};
|
|
432
248
|
}
|
|
433
249
|
}
|