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.
Files changed (36) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +110 -24
  3. package/dist/runtime/adapters/factory.js +8 -7
  4. package/dist/runtime/adapters/interfaces/queue.d.ts +5 -0
  5. package/dist/runtime/config/index.js +14 -1
  6. package/dist/runtime/config/types.d.ts +42 -0
  7. package/dist/runtime/events/types.d.ts +0 -1
  8. package/dist/runtime/events/utils/stallDetector.d.ts +13 -77
  9. package/dist/runtime/events/utils/stallDetector.js +8 -192
  10. package/dist/runtime/events/wiring/flowWiring.js +311 -107
  11. package/dist/runtime/events/wiring/triggerWiring.js +3 -1
  12. package/dist/runtime/nitro/plugins/02.workers.js +31 -2
  13. package/dist/runtime/nitro/routes/webhook.await.js +28 -6
  14. package/dist/runtime/nitro/utils/awaitPatterns/event.js +58 -50
  15. package/dist/runtime/nitro/utils/awaitPatterns/schedule.js +6 -1
  16. package/dist/runtime/nitro/utils/awaitPatterns/time.d.ts +1 -1
  17. package/dist/runtime/nitro/utils/awaitPatterns/time.js +6 -2
  18. package/dist/runtime/nitro/utils/awaitPatterns/webhook.js +53 -45
  19. package/dist/runtime/nitro/utils/defineFunction.d.ts +2 -9
  20. package/dist/runtime/nitro/utils/defineFunction.js +1 -14
  21. package/dist/runtime/nitro/utils/defineFunctionConfig.d.ts +84 -16
  22. package/dist/runtime/nitro/utils/defineHooks.d.ts +64 -10
  23. package/dist/runtime/nitro/utils/defineHooks.js +3 -0
  24. package/dist/runtime/nitro/utils/useAwait.d.ts +12 -0
  25. package/dist/runtime/nitro/utils/useAwait.js +34 -4
  26. package/dist/runtime/nitro/utils/useFlow.js +13 -2
  27. package/dist/runtime/nitro/utils/useHookRegistry.d.ts +10 -4
  28. package/dist/runtime/scheduler/scheduler.d.ts +19 -0
  29. package/dist/runtime/scheduler/scheduler.js +184 -8
  30. package/dist/runtime/scheduler/types.d.ts +6 -0
  31. package/dist/runtime/worker/node/runner.js +32 -91
  32. package/dist/runtime/worker/system/awaitHandlers.d.ts +27 -0
  33. package/dist/runtime/worker/system/awaitHandlers.js +230 -0
  34. package/dist/runtime/worker/system/index.d.ts +24 -0
  35. package/dist/runtime/worker/system/index.js +39 -0
  36. package/package.json +1 -1
package/dist/module.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nvent",
3
- "version": "0.4.1",
3
+ "version": "0.5.5",
4
4
  "configKey": "nvent",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
package/dist/module.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { dirname, join, extname, relative } from 'node:path';
2
2
  import { useLogger, defineNuxtModule, createResolver, addTemplate, addTypeTemplate, addServerPlugin, addServerHandler, addServerImports, updateTemplates } from '@nuxt/kit';
3
3
  import defu from 'defu';
4
- import { existsSync, realpathSync } from 'node:fs';
4
+ import { existsSync, realpathSync, readFileSync } from 'node:fs';
5
5
  import { globby } from 'globby';
6
6
  import { pathToFileURL, fileURLToPath } from 'node:url';
7
7
  import { readFile } from 'node:fs/promises';
@@ -32,6 +32,7 @@ async function loadJsConfig(absPath) {
32
32
  emits: flowCfg.emits,
33
33
  subscribes,
34
34
  triggers: flowCfg.triggers,
35
+ stepTimeout: flowCfg.stepTimeout,
35
36
  awaitBefore: flowCfg.awaitBefore,
36
37
  awaitAfter: flowCfg.awaitAfter
37
38
  };
@@ -45,7 +46,7 @@ async function loadJsConfig(absPath) {
45
46
  } : void 0;
46
47
  const workerCfg = cfg?.worker && typeof cfg.worker === "object" ? { ...cfg.worker } : void 0;
47
48
  const hasDefaultExport = !!(mod && mod.default);
48
- const hasHooks = !!(mod && typeof mod.onAwaitRegister === "function" || mod && typeof mod.onAwaitResolve === "function");
49
+ const hasHooks = !!(mod && typeof mod.onAwaitRegister === "function" || mod && typeof mod.onAwaitResolve === "function" || mod && typeof mod.onAwaitTimeout === "function");
49
50
  return { queueName, flow, runtype, queue: queueCfg, worker: workerCfg, hasDefaultExport, hasHooks };
50
51
  }
51
52
 
@@ -75,6 +76,7 @@ async function loadTsConfig(absPath) {
75
76
  emits: flowCfg.emits,
76
77
  subscribes,
77
78
  triggers: flowCfg.triggers,
79
+ stepTimeout: flowCfg.stepTimeout,
78
80
  awaitBefore: flowCfg.awaitBefore,
79
81
  awaitAfter: flowCfg.awaitAfter
80
82
  };
@@ -87,7 +89,7 @@ async function loadTsConfig(absPath) {
87
89
  limiter: cfg.queue.limiter
88
90
  } : void 0;
89
91
  const workerCfg = cfg?.worker && typeof cfg.worker === "object" ? { ...cfg.worker } : void 0;
90
- const hasHooks = !!(mod.exports.onAwaitRegister || mod.exports.onAwaitResolve);
92
+ const hasHooks = !!(mod.exports.onAwaitRegister || mod.exports.onAwaitResolve || mod.exports.onAwaitTimeout);
91
93
  return { queueName, flow, runtype, queue: queueCfg, worker: workerCfg, hasDefaultExport, hasHooks };
92
94
  } catch (error) {
93
95
  throw new Error(`Failed to parse config from ${absPath}: ${error}`);
@@ -185,6 +187,7 @@ async function loadPyConfig(absPath, logger) {
185
187
  emits: flowCfg.emits,
186
188
  subscribes,
187
189
  triggers: flowCfg.triggers,
190
+ stepTimeout: flowCfg.stepTimeout,
188
191
  awaitBefore: flowCfg.awaitBefore,
189
192
  awaitAfter: flowCfg.awaitAfter
190
193
  };
@@ -334,6 +337,7 @@ function buildFlows(flowSources) {
334
337
  queue,
335
338
  workerId: id,
336
339
  emits: f.emits,
340
+ stepTimeout: f.stepTimeout,
337
341
  awaitBefore: f.awaitBefore,
338
342
  awaitAfter: f.awaitAfter
339
343
  };
@@ -347,6 +351,7 @@ function buildFlows(flowSources) {
347
351
  workerId: id,
348
352
  subscribes: f.subscribes,
349
353
  emits: f.emits,
354
+ stepTimeout: f.stepTimeout,
350
355
  awaitBefore: f.awaitBefore,
351
356
  awaitAfter: f.awaitAfter
352
357
  };
@@ -361,6 +366,7 @@ function buildFlows(flowSources) {
361
366
  workerId: id,
362
367
  subscribes: f.subscribes,
363
368
  emits: f.emits,
369
+ stepTimeout: f.stepTimeout,
364
370
  awaitBefore: f.awaitBefore,
365
371
  awaitAfter: f.awaitAfter
366
372
  };
@@ -407,10 +413,10 @@ function parseSubscription(token) {
407
413
  }
408
414
  return { type: "implicit", value: token };
409
415
  }
410
- function findEmitter(token, entryStep, steps) {
416
+ function findEmitter(token, entryStep, steps, entry) {
411
417
  const { type, value } = parseSubscription(token);
412
- if (entryStep) {
413
- const entryEmits = steps[entryStep]?.emits || [];
418
+ if (entryStep && entry) {
419
+ const entryEmits = entry.emits || [];
414
420
  if (entryEmits.includes(token) || entryEmits.includes(value)) {
415
421
  return entryStep;
416
422
  }
@@ -439,13 +445,13 @@ function findEmitter(token, entryStep, steps) {
439
445
  }
440
446
  return null;
441
447
  }
442
- function buildDependencyGraph(entryStep, steps) {
448
+ function buildDependencyGraph(entryStep, steps, entry) {
443
449
  const dependencies = {};
444
450
  for (const [stepName, step] of Object.entries(steps)) {
445
451
  const deps = /* @__PURE__ */ new Set();
446
452
  const subscribes = step.subscribes || [];
447
453
  for (const token of subscribes) {
448
- const emitter = findEmitter(token, entryStep, steps);
454
+ const emitter = findEmitter(token, entryStep, steps, entry);
449
455
  if (emitter && emitter !== stepName) {
450
456
  deps.add(emitter);
451
457
  }
@@ -520,27 +526,67 @@ function findTriggeredSteps(stepName, step, allSteps) {
520
526
  }
521
527
  return Array.from(triggered);
522
528
  }
529
+ function getAwaitDefaultTimeout(awaitConfig) {
530
+ if (!awaitConfig) return 0;
531
+ if (awaitConfig.timeout && awaitConfig.timeout > 0) {
532
+ return awaitConfig.timeout;
533
+ }
534
+ const type = awaitConfig.type;
535
+ switch (type) {
536
+ case "webhook":
537
+ return 24 * 60 * 60 * 1e3;
538
+ // 24 hours (matches flow.awaitDefaults.webhookTimeout)
539
+ case "event":
540
+ return 24 * 60 * 60 * 1e3;
541
+ // 24 hours (matches flow.awaitDefaults.eventTimeout)
542
+ case "time":
543
+ return 0;
544
+ // No timeout for time awaits by default
545
+ case "schedule":
546
+ return 0;
547
+ // No timeout for schedule awaits by default
548
+ default:
549
+ return 0;
550
+ }
551
+ }
523
552
  function getStepAwaitTimeout(step) {
524
553
  let timeout = 0;
525
- if (step.awaitBefore?.timeout) timeout += step.awaitBefore.timeout;
526
- if (step.awaitAfter?.timeout) timeout += step.awaitAfter.timeout;
554
+ if (step.awaitBefore) {
555
+ timeout += getAwaitDefaultTimeout(step.awaitBefore);
556
+ }
557
+ if (step.awaitAfter) {
558
+ timeout += getAwaitDefaultTimeout(step.awaitAfter);
559
+ }
527
560
  return timeout;
528
561
  }
562
+ function getStepExecutionTimeout(step, config) {
563
+ if (step.stepTimeout !== void 0) {
564
+ return step.stepTimeout;
565
+ }
566
+ if (config?.flow?.stepTimeout !== void 0) {
567
+ return config.flow.stepTimeout;
568
+ }
569
+ if (config?.queue?.defaultJobOptions?.timeout !== void 0) {
570
+ return config.queue.defaultJobOptions.timeout;
571
+ }
572
+ return void 0;
573
+ }
529
574
  function calculateFlowStallTimeout(steps, levels) {
530
575
  const DEFAULT_STALL_TIMEOUT = 30 * 60 * 1e3;
531
576
  const MIN_BUFFER = 5 * 60 * 1e3;
532
577
  const BUFFER_PERCENTAGE = 0.1;
578
+ const DEFAULT_STEP_TIMEOUT = 5 * 60 * 1e3;
533
579
  const levelTimeouts = [];
534
- let awaitCount = 0;
535
580
  for (const levelSteps of levels) {
536
581
  let maxLevelTimeout = 0;
537
582
  for (const stepName of levelSteps) {
538
583
  const step = steps[stepName];
539
584
  if (!step) continue;
540
- const stepTimeout = getStepAwaitTimeout(step);
541
- if (stepTimeout > 0) {
542
- awaitCount++;
543
- maxLevelTimeout = Math.max(maxLevelTimeout, stepTimeout);
585
+ const stepExecTimeout = step.stepTimeout ?? DEFAULT_STEP_TIMEOUT;
586
+ const awaitTimeout = getStepAwaitTimeout(step);
587
+ const totalStepTimeout = stepExecTimeout + awaitTimeout;
588
+ if (totalStepTimeout > 0) {
589
+ maxLevelTimeout = Math.max(maxLevelTimeout, totalStepTimeout);
544
590
  }
545
591
  }
546
592
  if (maxLevelTimeout > 0) {
@@ -555,31 +601,58 @@ function calculateFlowStallTimeout(steps, levels) {
555
601
  const calculatedTimeout = totalAwaitTimeout + buffer;
556
602
  if (calculatedTimeout > DEFAULT_STALL_TIMEOUT * 2) {
557
603
  console.log(
558
- `[flow-analyzer] Flow has ${awaitCount} await patterns across ${levelTimeouts.length} levels, calculated stall timeout: ${calculatedTimeout / 1e3}s (total await time: ${totalAwaitTimeout / 1e3}s, level timeouts: [${levelTimeouts.map((t) => `${t / 1e3}s`).join(", ")}])`
604
+ `[flow-analyzer] Flow stall timeout calculated across ${levelTimeouts.length} levels: ${calculatedTimeout / 1e3}s (total time: ${totalAwaitTimeout / 1e3}s, level timeouts: [${levelTimeouts.map((t) => `${t / 1e3}s`).join(", ")}])`
559
605
  );
560
606
  }
561
607
  return calculatedTimeout;
562
608
  }
563
- function analyzeFlow(flow) {
609
+ function analyzeFlow(flow, config) {
564
610
  const entryStepName = flow.entry?.step;
565
611
  const steps = flow.steps || {};
566
- const dependencies = buildDependencyGraph(entryStepName, steps);
612
+ const dependencies = buildDependencyGraph(entryStepName, steps, flow.entry);
567
613
  const levels = calculateLevels(entryStepName, dependencies);
568
614
  const analyzedSteps = {};
615
+ if (flow.entry && entryStepName) {
616
+ const hasAwaitPattern = !!(flow.entry.awaitBefore || flow.entry.awaitAfter);
617
+ const entryStep = {
618
+ queue: flow.entry.queue,
619
+ workerId: flow.entry.workerId,
620
+ emits: flow.entry.emits,
621
+ stepTimeout: flow.entry.stepTimeout,
622
+ // Include stepTimeout from flow metadata
623
+ awaitBefore: flow.entry.awaitBefore,
624
+ awaitAfter: flow.entry.awaitAfter,
625
+ name: entryStepName,
626
+ dependsOn: [],
627
+ triggers: findTriggeredSteps(entryStepName, flow.entry, steps),
628
+ level: 0,
629
+ hasAwaitPattern
630
+ };
631
+ entryStep.stepTimeout = getStepExecutionTimeout(entryStep, config);
632
+ analyzedSteps[entryStepName] = entryStep;
633
+ }
569
634
  for (const [stepName, step] of Object.entries(steps)) {
570
635
  const hasAwaitPattern = !!(step.awaitBefore || step.awaitAfter);
571
- analyzedSteps[stepName] = {
636
+ const analyzedStep = {
572
637
  ...step,
573
638
  name: stepName,
574
639
  dependsOn: dependencies[stepName] || [],
575
640
  triggers: findTriggeredSteps(stepName, step, steps),
576
641
  level: levels[stepName] ?? 1,
577
- hasAwaitPattern
642
+ hasAwaitPattern,
643
+ stepTimeout: void 0
644
+ // Step execution timeout from config
578
645
  };
646
+ analyzedStep.stepTimeout = getStepExecutionTimeout(analyzedStep, config);
647
+ analyzedSteps[stepName] = analyzedStep;
579
648
  }
580
649
  const maxLevel = Math.max(0, ...Object.values(levels));
581
650
  const levelGroups = Array.from({ length: maxLevel + 1 }, () => []);
651
+ if (entryStepName && flow.entry) {
652
+ levelGroups[0]?.push(entryStepName);
653
+ }
582
654
  for (const [stepName, level] of Object.entries(levels)) {
655
+ if (stepName === entryStepName) continue;
583
656
  const levelArray = levelGroups[level];
584
657
  if (levelArray) {
585
658
  levelArray.push(stepName);
@@ -822,7 +895,7 @@ function generateAnalyzedFlowsTemplate(registry) {
822
895
  entry,
823
896
  steps
824
897
  };
825
- const analyzed = analyzeFlow(flowMeta);
898
+ const analyzed = analyzeFlow(flowMeta, registry.config);
826
899
  return {
827
900
  ...flowMeta,
828
901
  analyzed: {
@@ -998,6 +1071,10 @@ function getServerImports(resolverFn, buildDir) {
998
1071
  name: "defineAwaitResolveHook",
999
1072
  from: resolverFn("./runtime/nitro/utils/defineHooks")
1000
1073
  },
1074
+ {
1075
+ name: "defineAwaitTimeoutHook",
1076
+ from: resolverFn("./runtime/nitro/utils/defineHooks")
1077
+ },
1001
1078
  // Adapter composables
1002
1079
  {
1003
1080
  name: "useQueueAdapter",
@@ -1070,9 +1147,10 @@ function getServerImports(resolverFn, buildDir) {
1070
1147
  ];
1071
1148
  }
1072
1149
 
1150
+ const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
1073
1151
  const meta = {
1074
1152
  name: "nvent",
1075
- version: "0.4.1",
1153
+ version: packageJson.version,
1076
1154
  configKey: "nvent"
1077
1155
  };
1078
1156
  const module$1 = defineNuxtModule().with({
@@ -1117,7 +1195,11 @@ const module$1 = defineNuxtModule().with({
1117
1195
  logger: { name: "console", level: "info" },
1118
1196
  runner: { ts: { isolate: "inprocess" }, py: { enabled: false, cmd: "python3", importMode: "file" } },
1119
1197
  flows: {},
1120
- eventIndex: {}
1198
+ eventIndex: {},
1199
+ config: {
1200
+ flow: config.flow,
1201
+ queue: config.queue
1202
+ }
1121
1203
  });
1122
1204
  const compiledSnapshot = JSON.parse(JSON.stringify(compiledWithMeta));
1123
1205
  let lastCompiledRegistry = compiledSnapshot;
@@ -1177,7 +1259,11 @@ const module$1 = defineNuxtModule().with({
1177
1259
  logger: { name: "console", level: "info" },
1178
1260
  runner: { ts: { isolate: "inprocess" }, py: { enabled: false, cmd: "python3", importMode: "file" } },
1179
1261
  flows: {},
1180
- eventIndex: {}
1262
+ eventIndex: {},
1263
+ config: {
1264
+ flow: config.flow,
1265
+ queue: config.queue
1266
+ }
1181
1267
  })));
1182
1268
  console.log(`[nvent] registry refreshed (${reason})`, changedPath || "");
1183
1269
  console.log(`[nvent] new registry has ${lastCompiledRegistry.workers?.length || 0} workers`);
@@ -69,13 +69,14 @@ async function createStoreAdapter(config) {
69
69
  try {
70
70
  const { StoreSubjects } = useStreamTopics();
71
71
  const flowIndexKey = StoreSubjects.flowIndex();
72
- const existingFlows = await adapter.index.read(flowIndexKey, { limit: 1 });
73
- if (existingFlows.length === 0) {
74
- const analyzedFlows = $useAnalyzedFlows();
75
- if (analyzedFlows && analyzedFlows.length > 0) {
76
- const now = (/* @__PURE__ */ new Date()).toISOString();
77
- for (const flow of analyzedFlows) {
78
- await adapter.index.add(flowIndexKey, flow.id, Date.now(), {
72
+ const analyzedFlows = $useAnalyzedFlows();
73
+ if (analyzedFlows && analyzedFlows.length > 0) {
74
+ const existingFlows = await adapter.index.read(flowIndexKey, { limit: 100 });
75
+ const existingFlowIds = new Set(existingFlows.map((f) => f.id));
76
+ const now = Date.now();
77
+ for (const flow of analyzedFlows) {
78
+ if (!existingFlowIds.has(flow.id)) {
79
+ await adapter.index.add(flowIndexKey, flow.id, now, {
79
80
  name: flow.id,
80
81
  displayName: flow.id,
81
82
  registeredAt: now,
@@ -49,6 +49,11 @@ export interface QueueAdapter {
49
49
  * @returns true if removed, false if not found
50
50
  */
51
51
  removeScheduledJob?(scheduleId: string): Promise<boolean>;
52
+ /**
53
+ * Remove/cancel a job by ID
54
+ * @returns true if removed, false if not found
55
+ */
56
+ removeJob?(queueName: string, jobId: string): Promise<boolean>;
52
57
  /**
53
58
  * Pause the queue
54
59
  */
@@ -45,7 +45,20 @@ export function normalizeModuleOptions(options) {
45
45
  checkInterval: 15 * 60 * 1e3,
46
46
  // 15 minutes
47
47
  enablePeriodicCheck: true
48
- }
48
+ },
49
+ awaitDefaults: {
50
+ webhookTimeout: 24 * 60 * 60 * 1e3,
51
+ // 24 hours
52
+ eventTimeout: 24 * 60 * 60 * 1e3,
53
+ // 24 hours
54
+ timeTimeout: void 0,
55
+ // No default timeout for time awaits
56
+ scheduleTimeout: void 0,
57
+ // No default timeout for schedule awaits
58
+ timeoutAction: "fail"
59
+ },
60
+ stepTimeout: 5 * 60 * 1e3
61
+ // 5 minutes default step execution timeout
49
62
  },
50
63
  webhooks: {
51
64
  // baseUrl will be determined at runtime from Nitro context
@@ -238,6 +238,48 @@ export interface FlowConfig {
238
238
  */
239
239
  enablePeriodicCheck?: boolean;
240
240
  };
241
+ /**
242
+ * Default timeout values for await patterns
243
+ * These are used when developers don't specify explicit timeouts
244
+ * @since v0.5.0
245
+ */
246
+ awaitDefaults?: {
247
+ /**
248
+ * Default timeout for webhook await patterns in milliseconds
249
+ * @default 86400000 (24 hours)
250
+ */
251
+ webhookTimeout?: number;
252
+ /**
253
+ * Default timeout for event await patterns in milliseconds
254
+ * @default 86400000 (24 hours)
255
+ */
256
+ eventTimeout?: number;
257
+ /**
258
+ * Default timeout for time await patterns in milliseconds
259
+ * Time awaits typically don't need a timeout since they resolve based on delay
260
+ * @default undefined (no timeout)
261
+ */
262
+ timeTimeout?: number;
263
+ /**
264
+ * Default timeout for schedule await patterns in milliseconds
265
+ * Schedule awaits typically don't need a timeout since they resolve based on cron
266
+ * @default undefined (no timeout)
267
+ */
268
+ scheduleTimeout?: number;
269
+ /**
270
+ * Default timeout action when await times out
271
+ * @default 'fail'
272
+ */
273
+ timeoutAction?: 'fail' | 'continue' | 'retry';
274
+ };
275
+ /**
276
+ * Default step execution timeout in milliseconds
277
+ * This is the maximum time a step function can run before timing out
278
+ * Priority: defineFunctionConfig > flow.stepTimeout > queue.defaultJobOptions.timeout
279
+ * @default 300000 (5 minutes)
280
+ * @since v0.5.0
281
+ */
282
+ stepTimeout?: number;
241
283
  }
242
284
  /**
243
285
  * State management configuration
@@ -57,7 +57,6 @@ export interface FlowStalledEvent extends BaseEvent {
57
57
  type: 'flow.stalled';
58
58
  data?: {
59
59
  lastActivityAt?: number;
60
- stallTimeout?: number;
61
60
  };
62
61
  }
63
62
  export interface StepStartedEvent extends StepEvent {
@@ -2,102 +2,45 @@
2
2
  * Flow Stall Detection System
3
3
  *
4
4
  * Detects and marks flows that have been in "running" state for too long without activity.
5
- * Uses a hybrid approach:
6
- * 1. Lazy detection: Check stall status when flows are queried (zero overhead)
7
- * 2. Periodic cleanup: Background job that checks all running flows periodically (safety net)
5
+ * Uses per-flow scheduler jobs for precise timeout tracking:
8
6
  *
9
- * A flow is considered "stalled" when:
10
- * - Status is "running"
11
- * - No activity (step events) for longer than STALL_TIMEOUT
12
- * - lastActivityAt timestamp is older than threshold
7
+ * - On flow.start: Schedule timeout job with flow-specific deadline
8
+ * - On step events: Reschedule to extend timeout from current time
9
+ * - On flow end: Unschedule the timeout job
10
+ * - When timeout fires: Mark flow as stalled
11
+ *
12
+ * Startup recovery handles flows left running from previous server instance.
13
13
  */
14
14
  import type { StoreAdapter } from '../../adapters/interfaces/store.js';
15
15
  export interface StallDetectorConfig {
16
16
  /**
17
- * Time in milliseconds after which a running flow without activity is considered stalled
18
- * @default 1800000 (30 minutes)
19
- */
20
- stallTimeout?: number;
21
- /**
22
- * Interval in milliseconds for periodic stall checks
23
- * @default 900000 (15 minutes)
24
- */
25
- checkInterval?: number;
26
- /**
27
- * Enable periodic background checks
28
- * Set to false to use only lazy detection
17
+ * Enable stall detection system
29
18
  * @default true
30
19
  */
31
- enablePeriodicCheck?: boolean;
20
+ enabled?: boolean;
32
21
  }
33
22
  export type FlowStatus = 'running' | 'completed' | 'failed' | 'canceled' | 'stalled';
34
- export interface FlowActivity {
35
- runId: string;
36
- flowName: string;
37
- status: FlowStatus;
38
- startedAt: number;
39
- lastActivityAt: number;
40
- metadata?: any;
41
- }
42
23
  export declare class FlowStallDetector {
43
24
  private store;
44
25
  private config;
45
26
  private logger;
46
- private schedulerJobId?;
47
27
  private started;
48
28
  constructor(store: StoreAdapter, config?: StallDetectorConfig);
49
29
  /**
50
- * Start the periodic stall detector
51
- * Should be called once per instance after adapters are initialized
30
+ * Start the stall detector
52
31
  * Runs startup recovery to clean up flows from previous server instances
32
+ * Note: Periodic checking removed - now uses per-flow scheduler jobs
53
33
  */
54
34
  start(): Promise<void>;
55
35
  /**
56
- * Get the configuration for scheduling
57
- * Returns config needed by flowWiring to register the scheduler job
58
- */
59
- getScheduleConfig(): {
60
- enabled: boolean;
61
- interval: number;
62
- stallTimeout: number;
63
- };
64
- /**
65
- * Set the scheduler job ID (called from flowWiring after scheduling)
66
- */
67
- setSchedulerJobId(jobId: string): void;
68
- /**
69
- * Stop the periodic stall detector
36
+ * Stop the stall detector
70
37
  */
71
38
  stop(): Promise<void>;
72
- /**
73
- * Get stall timeout for a specific flow
74
- * Uses flow-specific timeout from analyzed metadata, falls back to global config
75
- */
76
- private getFlowStallTimeout;
77
- /**
78
- * Update activity timestamp for a flow
79
- * Should be called on every step event (started, completed, failed, retry)
80
- */
81
- updateActivity(flowName: string, runId: string): Promise<void>;
82
- /**
83
- * Check if a specific flow is stalled (lazy detection)
84
- * Returns true if the flow should be marked as stalled
85
- * v0.5: Await-aware - uses flow-specific timeout and skips awaiting flows
86
- */
87
- isStalled(flowName: string, runId: string): Promise<boolean>;
88
39
  /**
89
40
  * Mark a flow as stalled
90
41
  * Emits a flow.stalled event and updates the flow status
91
42
  */
92
43
  markAsStalled(flowName: string, runId: string, reason?: string): Promise<void>;
93
- /**
94
- * Check all running flows and mark stalled ones
95
- * This is called by the periodic background job
96
- *
97
- * Note: This method requires knowledge of which flows exist.
98
- * For now, we'll need to pass flow names to check, or iterate known flows from registry.
99
- */
100
- checkFlowsForStalls(flowNames: string[]): Promise<void>;
101
44
  /**
102
45
  * Run startup recovery to clean up flows left in running state from previous server instance
103
46
  * This marks all running flows as stalled since their in-memory state is lost
@@ -119,19 +62,12 @@ export declare class FlowStallDetector {
119
62
  * - Minor discrepancies don't affect runtime behavior
120
63
  */
121
64
  private validateFlowStats;
122
- /**
123
- * Internal method for periodic checks
124
- * Gets flow names from registry and checks them
125
- */
126
- private checkAllRunningFlows;
127
65
  /**
128
66
  * Get stall detector statistics
129
67
  */
130
68
  getStats(): {
131
69
  enabled: boolean;
132
- periodicCheckEnabled: boolean;
133
- stallTimeout: number;
134
- checkInterval: number;
70
+ mode: string;
135
71
  };
136
72
  }
137
73
  /**