nvent 0.4.5 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/dist/module.d.mts +1 -1
  2. package/dist/module.mjs +433 -175
  3. package/dist/runtime/adapters/base/index.d.ts +6 -0
  4. package/dist/runtime/adapters/base/index.js +1 -0
  5. package/dist/runtime/adapters/base/store-validator.d.ts +48 -0
  6. package/dist/runtime/adapters/base/store-validator.js +147 -0
  7. package/dist/runtime/adapters/builtin/file-queue.d.ts +15 -1
  8. package/dist/runtime/adapters/builtin/file-queue.js +70 -6
  9. package/dist/runtime/adapters/builtin/file-store.d.ts +4 -18
  10. package/dist/runtime/adapters/builtin/file-store.js +90 -109
  11. package/dist/runtime/adapters/builtin/memory-queue.js +4 -0
  12. package/dist/runtime/adapters/builtin/memory-store.d.ts +42 -31
  13. package/dist/runtime/adapters/builtin/memory-store.js +253 -183
  14. package/dist/runtime/adapters/factory.d.ts +2 -2
  15. package/dist/runtime/adapters/factory.js +54 -20
  16. package/dist/runtime/adapters/interfaces/store.d.ts +177 -113
  17. package/dist/runtime/config/index.d.ts +2 -2
  18. package/dist/runtime/config/index.js +14 -6
  19. package/dist/runtime/config/types.d.ts +32 -2
  20. package/dist/runtime/events/eventBus.d.ts +1 -1
  21. package/dist/runtime/events/types.d.ts +31 -2
  22. package/dist/runtime/events/utils/scheduleTrigger.d.ts +8 -0
  23. package/dist/runtime/events/utils/scheduleTrigger.js +69 -0
  24. package/dist/runtime/events/utils/stallDetector.d.ts +44 -3
  25. package/dist/runtime/events/utils/stallDetector.js +288 -89
  26. package/dist/runtime/events/utils/triggerRuntime.d.ts +58 -0
  27. package/dist/runtime/events/utils/triggerRuntime.js +212 -0
  28. package/dist/runtime/events/wiring/flowWiring.d.ts +11 -5
  29. package/dist/runtime/events/wiring/flowWiring.js +620 -92
  30. package/dist/runtime/events/wiring/registry.d.ts +2 -2
  31. package/dist/runtime/events/wiring/registry.js +8 -6
  32. package/dist/runtime/events/wiring/streamWiring.d.ts +15 -11
  33. package/dist/runtime/events/wiring/streamWiring.js +88 -11
  34. package/dist/runtime/events/wiring/triggerWiring.d.ts +21 -0
  35. package/dist/runtime/events/wiring/triggerWiring.js +412 -0
  36. package/dist/runtime/{server → nitro}/plugins/00.adapters.js +8 -4
  37. package/dist/runtime/{server → nitro}/plugins/02.workers.js +21 -3
  38. package/dist/runtime/nitro/plugins/03.triggers.d.ts +12 -0
  39. package/dist/runtime/nitro/plugins/03.triggers.js +55 -0
  40. package/dist/runtime/nitro/routes/webhook.await.d.ts +23 -0
  41. package/dist/runtime/nitro/routes/webhook.await.js +90 -0
  42. package/dist/runtime/nitro/routes/webhook.trigger.d.ts +69 -0
  43. package/dist/runtime/nitro/routes/webhook.trigger.js +64 -0
  44. package/dist/runtime/{utils → nitro/utils}/adapters.d.ts +6 -6
  45. package/dist/runtime/nitro/utils/awaitPatterns/event.d.ts +15 -0
  46. package/dist/runtime/nitro/utils/awaitPatterns/event.js +120 -0
  47. package/dist/runtime/nitro/utils/awaitPatterns/index.d.ts +28 -0
  48. package/dist/runtime/nitro/utils/awaitPatterns/index.js +55 -0
  49. package/dist/runtime/nitro/utils/awaitPatterns/schedule.d.ts +16 -0
  50. package/dist/runtime/nitro/utils/awaitPatterns/schedule.js +78 -0
  51. package/dist/runtime/nitro/utils/awaitPatterns/time.d.ts +15 -0
  52. package/dist/runtime/nitro/utils/awaitPatterns/time.js +67 -0
  53. package/dist/runtime/nitro/utils/awaitPatterns/webhook.d.ts +15 -0
  54. package/dist/runtime/nitro/utils/awaitPatterns/webhook.js +120 -0
  55. package/dist/runtime/{utils → nitro/utils}/defineFunction.d.ts +2 -2
  56. package/dist/runtime/{utils → nitro/utils}/defineFunction.js +3 -3
  57. package/dist/runtime/{utils → nitro/utils}/defineFunctionConfig.d.ts +156 -0
  58. package/dist/runtime/{utils → nitro/utils}/defineFunctionConfig.js +1 -0
  59. package/dist/runtime/nitro/utils/defineHooks.d.ts +41 -0
  60. package/dist/runtime/nitro/utils/defineHooks.js +6 -0
  61. package/dist/runtime/{utils → nitro/utils}/registerAdapter.d.ts +3 -3
  62. package/dist/runtime/{utils → nitro/utils}/registerAdapter.js +1 -1
  63. package/dist/runtime/nitro/utils/useAwait.d.ts +71 -0
  64. package/dist/runtime/nitro/utils/useAwait.js +139 -0
  65. package/dist/runtime/{utils → nitro/utils}/useEventManager.d.ts +2 -2
  66. package/dist/runtime/{utils → nitro/utils}/useEventManager.js +1 -1
  67. package/dist/runtime/nitro/utils/useFlow.d.ts +68 -0
  68. package/dist/runtime/nitro/utils/useFlow.js +226 -0
  69. package/dist/runtime/nitro/utils/useHookRegistry.d.ts +34 -0
  70. package/dist/runtime/nitro/utils/useHookRegistry.js +25 -0
  71. package/dist/runtime/nitro/utils/useRunContext.d.ts +6 -0
  72. package/dist/runtime/nitro/utils/useRunContext.js +102 -0
  73. package/dist/runtime/nitro/utils/useStreamTopics.d.ts +83 -0
  74. package/dist/runtime/nitro/utils/useStreamTopics.js +94 -0
  75. package/dist/runtime/nitro/utils/useTrigger.d.ts +150 -0
  76. package/dist/runtime/nitro/utils/useTrigger.js +320 -0
  77. package/dist/runtime/scheduler/index.d.ts +33 -0
  78. package/dist/runtime/scheduler/index.js +38 -0
  79. package/dist/runtime/scheduler/scheduler.d.ts +113 -0
  80. package/dist/runtime/scheduler/scheduler.js +623 -0
  81. package/dist/runtime/scheduler/types.d.ts +116 -0
  82. package/dist/runtime/scheduler/types.js +0 -0
  83. package/dist/runtime/worker/node/runner.d.ts +12 -2
  84. package/dist/runtime/worker/node/runner.js +141 -37
  85. package/package.json +6 -6
  86. package/dist/runtime/server/api/_flows/[name]/clear-history.delete.d.ts +0 -10
  87. package/dist/runtime/server/api/_flows/[name]/clear-history.delete.js +0 -55
  88. package/dist/runtime/server/api/_flows/[name]/runs/[runId]/cancel.post.d.ts +0 -2
  89. package/dist/runtime/server/api/_flows/[name]/runs/[runId]/cancel.post.js +0 -21
  90. package/dist/runtime/server/api/_flows/[name]/runs.get.d.ts +0 -17
  91. package/dist/runtime/server/api/_flows/[name]/runs.get.js +0 -64
  92. package/dist/runtime/server/api/_flows/[name]/schedule.post.d.ts +0 -2
  93. package/dist/runtime/server/api/_flows/[name]/schedule.post.js +0 -66
  94. package/dist/runtime/server/api/_flows/[name]/schedules/[id].delete.d.ts +0 -2
  95. package/dist/runtime/server/api/_flows/[name]/schedules/[id].delete.js +0 -47
  96. package/dist/runtime/server/api/_flows/[name]/schedules.get.d.ts +0 -2
  97. package/dist/runtime/server/api/_flows/[name]/schedules.get.js +0 -50
  98. package/dist/runtime/server/api/_flows/[name]/start.post.d.ts +0 -2
  99. package/dist/runtime/server/api/_flows/[name]/start.post.js +0 -9
  100. package/dist/runtime/server/api/_flows/index.get.d.ts +0 -6
  101. package/dist/runtime/server/api/_flows/index.get.js +0 -5
  102. package/dist/runtime/server/api/_flows/ws.d.ts +0 -60
  103. package/dist/runtime/server/api/_flows/ws.js +0 -209
  104. package/dist/runtime/server/api/_queues/[name]/job/[id].get.d.ts +0 -2
  105. package/dist/runtime/server/api/_queues/[name]/job/[id].get.js +0 -14
  106. package/dist/runtime/server/api/_queues/[name]/job/index.get.d.ts +0 -2
  107. package/dist/runtime/server/api/_queues/[name]/job/index.get.js +0 -27
  108. package/dist/runtime/server/api/_queues/index.get.d.ts +0 -2
  109. package/dist/runtime/server/api/_queues/index.get.js +0 -106
  110. package/dist/runtime/server/api/_queues/ws.d.ts +0 -48
  111. package/dist/runtime/server/api/_queues/ws.js +0 -215
  112. package/dist/runtime/utils/useFlowEngine.d.ts +0 -19
  113. package/dist/runtime/utils/useFlowEngine.js +0 -108
  114. package/dist/runtime/utils/useStreamTopics.d.ts +0 -72
  115. package/dist/runtime/utils/useStreamTopics.js +0 -47
  116. /package/dist/runtime/{server → nitro}/plugins/00.adapters.d.ts +0 -0
  117. /package/dist/runtime/{server → nitro}/plugins/01.ws-lifecycle.d.ts +0 -0
  118. /package/dist/runtime/{server → nitro}/plugins/01.ws-lifecycle.js +0 -0
  119. /package/dist/runtime/{server → nitro}/plugins/02.workers.d.ts +0 -0
  120. /package/dist/runtime/{utils → nitro/utils}/adapters.js +0 -0
  121. /package/dist/runtime/{utils → nitro/utils}/useNventLogger.d.ts +0 -0
  122. /package/dist/runtime/{utils → nitro/utils}/useNventLogger.js +0 -0
  123. /package/dist/runtime/{utils → nitro/utils}/wsPeerManager.d.ts +0 -0
  124. /package/dist/runtime/{utils → nitro/utils}/wsPeerManager.js +0 -0
@@ -1,5 +1,5 @@
1
1
  import { getEventBus } from "../eventBus.js";
2
- import { useNventLogger, useStoreAdapter, useQueueAdapter, $useAnalyzedFlows, $useQueueRegistry, useStreamTopics, useRuntimeConfig } from "#imports";
2
+ import { useNventLogger, useStoreAdapter, useQueueAdapter, $useAnalyzedFlows, $useFunctionRegistry, useStreamTopics, useRuntimeConfig, useScheduler } from "#imports";
3
3
  import { createStallDetector } from "../utils/stallDetector.js";
4
4
  export function checkPendingStepTriggers(step, emittedEvents, completedSteps) {
5
5
  if (!step.subscribes || step.subscribes.length === 0) {
@@ -14,36 +14,132 @@ export function checkPendingStepTriggers(step, emittedEvents, completedSteps) {
14
14
  return false;
15
15
  });
16
16
  }
17
- async function checkAndTriggerPendingSteps(flowName, runId, store) {
17
+ export async function checkAndTriggerPendingSteps(flowName, runId, store) {
18
18
  const logger = useNventLogger("flow-wiring");
19
19
  try {
20
20
  const analyzedFlows = $useAnalyzedFlows();
21
- const registry = $useQueueRegistry();
21
+ const registry = $useFunctionRegistry();
22
22
  const queue = useQueueAdapter();
23
- const { SubjectPatterns } = useStreamTopics();
23
+ const { StoreSubjects } = useStreamTopics();
24
24
  const flowDef = analyzedFlows.find((f) => f.id === flowName);
25
- if (!flowDef?.steps) return;
26
- const indexKey = SubjectPatterns.flowRunIndex(flowName);
27
- if (!store.indexGet) return;
28
- const flowEntry = await store.indexGet(indexKey, runId);
29
- if (!flowEntry?.metadata) return;
30
- const streamName = SubjectPatterns.flowRun(runId);
31
- const allEvents = await store.read(streamName);
25
+ if (!flowDef?.steps) {
26
+ logger.info("No flow definition or steps found", { flowName });
27
+ return;
28
+ }
29
+ const indexKey = StoreSubjects.flowRunIndex(flowName);
30
+ if (!store.index.get) {
31
+ logger.info("No indexGet method on store", { flowName });
32
+ return;
33
+ }
34
+ const flowEntry = await store.index.get(indexKey, runId);
35
+ if (!flowEntry?.metadata) {
36
+ logger.info("No flow entry or metadata found", { flowName, runId });
37
+ return;
38
+ }
39
+ const streamName = StoreSubjects.flowRun(runId);
40
+ const allEvents = await store.stream.read(streamName);
32
41
  const isCanceled = allEvents.some((event) => event.type === "flow.cancel");
33
42
  if (isCanceled) {
34
43
  logger.debug("Flow is canceled, skipping pending step triggers", { flowName, runId });
35
44
  return;
36
45
  }
37
- const emittedEvents = new Set(flowEntry.metadata.emittedEvents || []);
46
+ const emittedEventsObj = flowEntry.metadata.emittedEvents || {};
47
+ const flattenEmittedEvents = (obj, prefix = "") => {
48
+ const result = [];
49
+ for (const [key, value] of Object.entries(obj)) {
50
+ if (key === "undefined" || key === "null") continue;
51
+ const fullKey = prefix ? `${prefix}.${key}` : key;
52
+ if (value && typeof value === "object" && !Array.isArray(value)) {
53
+ result.push(...flattenEmittedEvents(value, fullKey));
54
+ } else {
55
+ result.push(fullKey);
56
+ }
57
+ }
58
+ return result;
59
+ };
60
+ const emittedEvents = new Set(
61
+ typeof emittedEventsObj === "object" && !Array.isArray(emittedEventsObj) ? flattenEmittedEvents(emittedEventsObj) : Array.isArray(emittedEventsObj) ? emittedEventsObj : []
62
+ );
38
63
  const completedSteps = /* @__PURE__ */ new Set();
39
64
  for (const event of allEvents) {
40
65
  if (event.type === "step.completed" && "stepName" in event) {
41
66
  completedSteps.add(event.stepName);
42
67
  }
43
68
  }
69
+ const awaitingSteps = flowEntry?.metadata?.awaitingSteps || {};
44
70
  for (const [stepName, stepDef] of Object.entries(flowDef.steps)) {
45
71
  const step = stepDef;
46
72
  if (!step.subscribes || completedSteps.has(stepName)) continue;
73
+ const awaitState = flowEntry?.metadata?.awaitingSteps?.[stepName];
74
+ if (awaitState && awaitState.status === "awaiting") {
75
+ logger.debug("Step is awaiting, skipping trigger", {
76
+ flowName,
77
+ runId,
78
+ stepName,
79
+ awaitType: awaitState.awaitType,
80
+ position: awaitState.position,
81
+ status: awaitState.status
82
+ });
83
+ continue;
84
+ }
85
+ if (awaitState && awaitState.status === "timeout") {
86
+ logger.debug("Step await timed out, skipping trigger", {
87
+ flowName,
88
+ runId,
89
+ stepName,
90
+ awaitType: awaitState.awaitType
91
+ });
92
+ continue;
93
+ }
94
+ if (awaitState?.status === "resolved") {
95
+ logger.debug("Step await is resolved, will proceed", {
96
+ flowName,
97
+ runId,
98
+ stepName,
99
+ awaitType: awaitState.awaitType,
100
+ position: awaitState.position
101
+ });
102
+ }
103
+ const isDependencyAwaiting = step.subscribes.some((sub) => {
104
+ const emitEvent = allEvents.find(
105
+ (evt) => evt.type === "emit" && evt.data?.name === sub
106
+ );
107
+ if (!emitEvent) {
108
+ return false;
109
+ }
110
+ const emitStepName = emitEvent.stepName;
111
+ if (!emitStepName) {
112
+ return false;
113
+ }
114
+ const awaitState2 = awaitingSteps[emitStepName];
115
+ if (awaitState2?.position === "after" && awaitState2?.status === "awaiting") {
116
+ return true;
117
+ }
118
+ if (awaitState2?.status === "resolved") {
119
+ return false;
120
+ }
121
+ let emittingStepMeta = flowDef.steps[emitStepName];
122
+ if (!emittingStepMeta && emitStepName === flowDef.entry?.step) {
123
+ emittingStepMeta = flowDef.entry;
124
+ }
125
+ if (emittingStepMeta?.awaitAfter) {
126
+ const stepCompleted = allEvents.some(
127
+ (evt) => evt.type === "step.completed" && evt.stepName === emitStepName
128
+ );
129
+ if (stepCompleted) {
130
+ const awaitResolved = allEvents.some(
131
+ (evt) => evt.type === "await.resolved" && evt.stepName === emitStepName
132
+ );
133
+ if (!awaitResolved) {
134
+ return true;
135
+ }
136
+ }
137
+ }
138
+ return false;
139
+ });
140
+ if (isDependencyAwaiting) {
141
+ continue;
142
+ }
47
143
  const canTrigger = checkPendingStepTriggers(step, emittedEvents, completedSteps);
48
144
  if (canTrigger) {
49
145
  const flowRegistry = (registry?.flows || {})[flowName];
@@ -65,6 +161,10 @@ async function checkAndTriggerPendingSteps(flowName, runId, store) {
65
161
  input: emitData
66
162
  // Keyed by event name
67
163
  };
164
+ if (awaitState?.status === "resolved" && awaitState?.position === "before") {
165
+ payload.awaitResolved = true;
166
+ payload.awaitData = awaitState.triggerData;
167
+ }
68
168
  const jobId = `${runId}__${stepName}`;
69
169
  const worker = registry?.workers?.find(
70
170
  (w) => w?.flow?.step === stepName && w?.queue?.name === stepMeta.queue
@@ -73,12 +173,6 @@ async function checkAndTriggerPendingSteps(flowName, runId, store) {
73
173
  const opts = { ...defaultOpts, jobId };
74
174
  try {
75
175
  await queue.enqueue(stepMeta.queue, { name: stepName, data: payload, opts });
76
- logger.debug("Triggered pending step", {
77
- flowName,
78
- runId,
79
- step: stepName,
80
- subscribes: step.subscribes
81
- });
82
176
  } catch {
83
177
  }
84
178
  }
@@ -92,7 +186,7 @@ async function checkAndTriggerPendingSteps(flowName, runId, store) {
92
186
  });
93
187
  }
94
188
  }
95
- export function analyzeFlowCompletion(flowSteps, entryStep, events) {
189
+ export function analyzeFlowCompletion(flowSteps, entryStep, events, entryStepDef) {
96
190
  const isCanceled = events.some((event) => event.type === "flow.cancel");
97
191
  const allSteps = entryStep ? [entryStep, ...Object.keys(flowSteps)] : Object.keys(flowSteps);
98
192
  const completedSteps = /* @__PURE__ */ new Set();
@@ -139,29 +233,74 @@ export function analyzeFlowCompletion(flowSteps, entryStep, events) {
139
233
  }
140
234
  const totalSteps = allSteps.length;
141
235
  const hasFinalFailures = finalFailedSteps.size > 0;
236
+ const stepDependencies = /* @__PURE__ */ new Map();
237
+ const stepDependents = /* @__PURE__ */ new Map();
238
+ for (const stepName of allSteps) {
239
+ stepDependencies.set(stepName, /* @__PURE__ */ new Set());
240
+ stepDependents.set(stepName, /* @__PURE__ */ new Set());
241
+ }
242
+ for (const [stepName, stepDef] of Object.entries(flowSteps)) {
243
+ const step = stepDef;
244
+ const subscribes = step.subscribes || [];
245
+ for (const sub of subscribes) {
246
+ for (const [emitStepName, emitStepDef] of Object.entries(flowSteps)) {
247
+ const emitStep = emitStepDef;
248
+ const emits = emitStep.emits || [];
249
+ if (emits.some((emit) => sub === `${emitStepName}.${emit}` || sub === emit)) {
250
+ stepDependencies.get(stepName)?.add(emitStepName);
251
+ stepDependents.get(emitStepName)?.add(stepName);
252
+ }
253
+ }
254
+ if (entryStep && entryStepDef?.emits) {
255
+ const entryEmits = entryStepDef.emits || [];
256
+ if (entryEmits.some((emit) => sub === `${entryStep}.${emit}` || sub === emit)) {
257
+ stepDependencies.get(stepName)?.add(entryStep);
258
+ stepDependents.get(entryStep)?.add(stepName);
259
+ }
260
+ }
261
+ }
262
+ }
142
263
  let hasBlockingFailure = false;
264
+ let hasCriticalLayerFailure = false;
143
265
  if (hasFinalFailures) {
144
266
  for (const failedStepName of Array.from(finalFailedSteps)) {
145
- const failedStepDef = flowSteps[failedStepName];
146
- if (failedStepDef?.emits && failedStepDef.emits.length > 0) {
147
- for (const [stepName, stepDef] of Object.entries(flowSteps)) {
148
- const step = stepDef;
149
- if (stepName === failedStepName) continue;
150
- if (step.subscribes && step.subscribes.length > 0) {
151
- const dependsOnFailedStep = step.subscribes.some((sub) => {
152
- return failedStepDef.emits.some(
153
- (emit) => sub === `${failedStepName}.${emit}` || sub === emit
154
- );
155
- });
156
- if (dependsOnFailedStep && !completedSteps.has(stepName)) {
157
- hasBlockingFailure = true;
158
- break;
159
- }
267
+ const dependents = stepDependents.get(failedStepName);
268
+ if (dependents && dependents.size > 0) {
269
+ for (const dependentName of Array.from(dependents)) {
270
+ if (!completedSteps.has(dependentName)) {
271
+ hasBlockingFailure = true;
272
+ break;
160
273
  }
161
274
  }
162
275
  }
163
276
  if (hasBlockingFailure) break;
164
277
  }
278
+ const layerGroups = /* @__PURE__ */ new Map();
279
+ for (const stepName of allSteps) {
280
+ const deps = stepDependencies.get(stepName);
281
+ const depsKey = Array.from(deps || []).sort().join(",");
282
+ if (!layerGroups.has(depsKey)) {
283
+ layerGroups.set(depsKey, /* @__PURE__ */ new Set());
284
+ }
285
+ layerGroups.get(depsKey)?.add(stepName);
286
+ }
287
+ for (const [_depsKey, layerSteps] of Array.from(layerGroups)) {
288
+ const layerHasFailures = Array.from(layerSteps).some((s) => finalFailedSteps.has(s));
289
+ if (!layerHasFailures) continue;
290
+ const allLayerStepsFailed = Array.from(layerSteps).every(
291
+ (s) => finalFailedSteps.has(s)
292
+ );
293
+ if (allLayerStepsFailed) {
294
+ const hasLeafNode = Array.from(layerSteps).some((s) => {
295
+ const deps = stepDependents.get(s);
296
+ return !deps || deps.size === 0;
297
+ });
298
+ if (hasLeafNode) {
299
+ hasCriticalLayerFailure = true;
300
+ break;
301
+ }
302
+ }
303
+ }
165
304
  }
166
305
  if (isCanceled) {
167
306
  return {
@@ -172,7 +311,7 @@ export function analyzeFlowCompletion(flowSteps, entryStep, events) {
172
311
  completedAt
173
312
  };
174
313
  }
175
- if (hasBlockingFailure) {
314
+ if (hasBlockingFailure || hasCriticalLayerFailure) {
176
315
  return {
177
316
  status: "failed",
178
317
  totalSteps,
@@ -181,11 +320,11 @@ export function analyzeFlowCompletion(flowSteps, entryStep, events) {
181
320
  completedAt: Date.now()
182
321
  };
183
322
  }
184
- const allCompleted = allSteps.every(
323
+ const allStepsTerminal = allSteps.every(
185
324
  (step) => completedSteps.has(step) || finalFailedSteps.has(step)
186
325
  );
187
326
  let status = "running";
188
- if (allCompleted) {
327
+ if (allStepsTerminal) {
189
328
  status = "completed";
190
329
  completedAt = Date.now();
191
330
  }
@@ -202,31 +341,34 @@ export function createFlowWiring() {
202
341
  const unsubs = [];
203
342
  let wired = false;
204
343
  let stallDetector;
344
+ const publishingTerminalEvents = /* @__PURE__ */ new Set();
205
345
  const indexFlowRun = async (flowName, flowId, timestamp, metadata) => {
206
346
  const logger = useNventLogger("flow-wiring");
207
347
  try {
208
348
  const store = useStoreAdapter();
209
- const { SubjectPatterns } = useStreamTopics();
210
- const indexKey = SubjectPatterns.flowRunIndex(flowName);
211
- if (!store.indexAdd) {
349
+ const { StoreSubjects } = useStreamTopics();
350
+ const indexKey = StoreSubjects.flowRunIndex(flowName);
351
+ if (!store.index.add) {
212
352
  throw new Error("StoreAdapter does not support indexAdd");
213
353
  }
214
- await store.indexAdd(indexKey, flowId, timestamp, metadata);
215
- logger.debug("Indexed run", { flowName, flowId, indexKey, timestamp, metadata });
354
+ await store.index.add(indexKey, flowId, timestamp, metadata);
216
355
  } catch (err) {
217
356
  logger.error("Failed to index run", { error: err });
218
357
  }
219
358
  };
220
- function start() {
359
+ const flowProcessingChain = /* @__PURE__ */ new Map();
360
+ const cleanupTimers = /* @__PURE__ */ new Map();
361
+ async function start() {
221
362
  if (wired) return;
222
363
  wired = true;
223
364
  const logger = useNventLogger("flow-wiring");
224
- const { SubjectPatterns } = useStreamTopics();
365
+ const { StoreSubjects } = useStreamTopics();
366
+ logger.info("Flow wiring starting");
225
367
  const store = useStoreAdapter();
226
- if (!store || !store.append) {
368
+ if (!store || !store.stream.append) {
227
369
  logger.error("StoreAdapter not properly initialized or missing append method", {
228
370
  hasStore: !!store,
229
- hasAppend: !!(store && store.append)
371
+ hasAppend: !!(store && store.stream.append)
230
372
  });
231
373
  throw new Error("StoreAdapter not initialized");
232
374
  }
@@ -243,7 +385,7 @@ export function createFlowWiring() {
243
385
  if (!flowName) {
244
386
  return;
245
387
  }
246
- const streamName = SubjectPatterns.flowRun(runId);
388
+ const streamName = StoreSubjects.flowRun(runId);
247
389
  if (!e.type) {
248
390
  logger.error("Event missing type field", { event: e });
249
391
  return;
@@ -257,9 +399,17 @@ export function createFlowWiring() {
257
399
  if ("stepName" in e && e.stepName) eventData.stepName = e.stepName;
258
400
  if ("stepId" in e && e.stepId) eventData.stepId = e.stepId;
259
401
  if ("attempt" in e && e.attempt) eventData.attempt = e.attempt;
260
- const persistedEvent = await store.append(streamName, eventData);
402
+ const persistedEvent = await store.stream.append(streamName, eventData);
261
403
  await bus.publish(persistedEvent);
262
404
  if (e.type === "flow.completed" || e.type === "flow.failed") {
405
+ const publishKey = `${runId}:terminal`;
406
+ setTimeout(() => {
407
+ try {
408
+ publishingTerminalEvents.delete(publishKey);
409
+ } catch (err) {
410
+ logger.debug("Error cleaning up terminal event tracker", { publishKey, error: err?.message });
411
+ }
412
+ }, 200);
263
413
  logger.info("Stored terminal event", {
264
414
  type: e.type,
265
415
  flowName,
@@ -284,6 +434,107 @@ export function createFlowWiring() {
284
434
  });
285
435
  }
286
436
  };
437
+ const handleFlowStats = async (e) => {
438
+ try {
439
+ if (!e.id || !e.ts) {
440
+ return;
441
+ }
442
+ const flowName = e.flowName;
443
+ if (!flowName) return;
444
+ const flowIndexKey = StoreSubjects.flowIndex();
445
+ if (e.type === "flow.start") {
446
+ if (store.index.increment) {
447
+ await store.index.increment(flowIndexKey, flowName, "stats.total", 1);
448
+ await store.index.increment(flowIndexKey, flowName, "stats.running", 1);
449
+ }
450
+ if (store.index.updateWithRetry) {
451
+ await store.index.updateWithRetry(flowIndexKey, flowName, {
452
+ lastRunAt: (/* @__PURE__ */ new Date()).toISOString()
453
+ });
454
+ }
455
+ logger.debug("Updated flow stats for start", { flowName });
456
+ } else if (e.type === "flow.completed") {
457
+ if (store.index.increment) {
458
+ await store.index.increment(flowIndexKey, flowName, "stats.running", -1);
459
+ await store.index.increment(flowIndexKey, flowName, "stats.success", 1);
460
+ }
461
+ if (store.index.updateWithRetry) {
462
+ await store.index.updateWithRetry(flowIndexKey, flowName, {
463
+ lastCompletedAt: (/* @__PURE__ */ new Date()).toISOString()
464
+ });
465
+ }
466
+ logger.debug("Updated flow stats for completion", { flowName });
467
+ } else if (e.type === "flow.failed") {
468
+ if (store.index.increment) {
469
+ await store.index.increment(flowIndexKey, flowName, "stats.running", -1);
470
+ await store.index.increment(flowIndexKey, flowName, "stats.failure", 1);
471
+ }
472
+ if (store.index.updateWithRetry) {
473
+ await store.index.updateWithRetry(flowIndexKey, flowName, {
474
+ lastCompletedAt: (/* @__PURE__ */ new Date()).toISOString()
475
+ });
476
+ }
477
+ logger.debug("Updated flow stats for failure", { flowName });
478
+ } else if (e.type === "flow.cancel") {
479
+ if (store.index.increment) {
480
+ await store.index.increment(flowIndexKey, flowName, "stats.running", -1);
481
+ await store.index.increment(flowIndexKey, flowName, "stats.cancel", 1);
482
+ }
483
+ if (store.index.updateWithRetry) {
484
+ await store.index.updateWithRetry(flowIndexKey, flowName, {
485
+ lastCompletedAt: (/* @__PURE__ */ new Date()).toISOString()
486
+ });
487
+ }
488
+ logger.debug("Updated flow stats for cancellation", { flowName });
489
+ } else if (e.type === "flow.stalled") {
490
+ if (store.index.increment && e.data?.previousStatus) {
491
+ if (e.data.previousStatus === "awaiting") {
492
+ await store.index.increment(flowIndexKey, flowName, "stats.awaiting", -1);
493
+ logger.debug("Updated flow stats for stalled detection (was awaiting)", { flowName });
494
+ } else if (e.data.previousStatus === "running") {
495
+ await store.index.increment(flowIndexKey, flowName, "stats.running", -1);
496
+ logger.debug("Updated flow stats for stalled detection (was running)", { flowName });
497
+ }
498
+ }
499
+ } else if (e.type === "await.registered") {
500
+ if (store.index.increment) {
501
+ await store.index.increment(flowIndexKey, flowName, "stats.running", -1);
502
+ await store.index.increment(flowIndexKey, flowName, "stats.awaiting", 1);
503
+ }
504
+ logger.debug("Updated flow stats for await registered", { flowName });
505
+ } else if (e.type === "await.resolved" || e.type === "await.timeout") {
506
+ if (store.index.increment) {
507
+ await store.index.increment(flowIndexKey, flowName, "stats.awaiting", -1);
508
+ await store.index.increment(flowIndexKey, flowName, "stats.running", 1);
509
+ }
510
+ logger.debug("Updated flow stats for await resolved/timeout", { flowName, type: e.type });
511
+ }
512
+ try {
513
+ const indexEntry = await store.index.get(flowIndexKey, flowName);
514
+ if (indexEntry) {
515
+ await bus.publish({
516
+ type: "flow.stats.updated",
517
+ flowName,
518
+ id: indexEntry.id,
519
+ metadata: indexEntry.metadata,
520
+ ts: Date.now()
521
+ });
522
+ logger.debug("Published flow stats update event to bus", { flowName });
523
+ }
524
+ } catch (err) {
525
+ logger.warn("Failed to publish flow stats update event", {
526
+ flowName,
527
+ error: err?.message
528
+ });
529
+ }
530
+ } catch (err) {
531
+ logger.warn("Failed to update flow stats", {
532
+ type: e.type,
533
+ flowName: e.flowName,
534
+ error: err?.message
535
+ });
536
+ }
537
+ };
287
538
  const handleOrchestration = async (e) => {
288
539
  try {
289
540
  if (e.id && e.ts) {
@@ -296,8 +547,8 @@ export function createFlowWiring() {
296
547
  if (!runId) return;
297
548
  const flowName = e.flowName;
298
549
  if (!flowName) return;
299
- const streamName = SubjectPatterns.flowRun(runId);
300
- const indexKey = SubjectPatterns.flowRunIndex(flowName);
550
+ const streamName = StoreSubjects.flowRun(runId);
551
+ const indexKey = StoreSubjects.flowRunIndex(flowName);
301
552
  if (e.type === "flow.start") {
302
553
  const timestamp = Date.now();
303
554
  await indexFlowRun(flowName, runId, timestamp, {
@@ -307,13 +558,14 @@ export function createFlowWiring() {
307
558
  // Initialize for stall detection
308
559
  stepCount: 0,
309
560
  completedSteps: 0,
310
- emittedEvents: []
561
+ emittedEvents: {}
562
+ // Object for atomic updates
311
563
  });
312
564
  }
313
565
  if (e.type === "flow.cancel") {
314
566
  try {
315
- if (store.indexUpdateWithRetry) {
316
- await store.indexUpdateWithRetry(indexKey, runId, {
567
+ if (store.index.updateWithRetry) {
568
+ await store.index.updateWithRetry(indexKey, runId, {
317
569
  status: "canceled",
318
570
  completedAt: Date.now()
319
571
  });
@@ -334,8 +586,8 @@ export function createFlowWiring() {
334
586
  }
335
587
  if (e.type === "step.completed") {
336
588
  try {
337
- if (store.indexIncrement) {
338
- const newCount = await store.indexIncrement(indexKey, runId, "completedSteps", 1);
589
+ if (store.index.increment) {
590
+ const newCount = await store.index.increment(indexKey, runId, "completedSteps", 1);
339
591
  logger.debug("Incremented completedSteps", {
340
592
  flowName,
341
593
  runId,
@@ -351,29 +603,161 @@ export function createFlowWiring() {
351
603
  });
352
604
  }
353
605
  }
606
+ if (e.type === "await.registered") {
607
+ const awaitEvent = e;
608
+ const { stepName, awaitType, position, config: config2 } = awaitEvent;
609
+ try {
610
+ if (store.index.updateWithRetry) {
611
+ const now = Date.now();
612
+ let timeoutAt;
613
+ if (awaitType === "time" && config2.delay) {
614
+ timeoutAt = now + config2.delay;
615
+ } else if (awaitType === "schedule" && config2.nextOccurrence) {
616
+ timeoutAt = config2.nextOccurrence;
617
+ } else if (config2.timeout) {
618
+ timeoutAt = now + config2.timeout;
619
+ }
620
+ if (!timeoutAt) {
621
+ timeoutAt = now + 24 * 60 * 60 * 1e3;
622
+ }
623
+ const updatePayload = {
624
+ awaitingSteps: {
625
+ [stepName]: {
626
+ status: "awaiting",
627
+ awaitType,
628
+ position,
629
+ config: config2,
630
+ registeredAt: now,
631
+ timeoutAt
632
+ }
633
+ }
634
+ };
635
+ await store.index.updateWithRetry(indexKey, runId, updatePayload);
636
+ logger.info("Await registered in index", {
637
+ runId,
638
+ stepName,
639
+ awaitType,
640
+ position,
641
+ timeoutAt: new Date(timeoutAt).toISOString()
642
+ });
643
+ }
644
+ } catch (err) {
645
+ logger.error("Error updating await status", {
646
+ runId,
647
+ stepName,
648
+ error: err?.message
649
+ });
650
+ }
651
+ }
652
+ if (e.type === "await.resolved") {
653
+ const awaitEvent = e;
654
+ const { stepName, triggerData } = awaitEvent;
655
+ try {
656
+ if (store.index.updateWithRetry) {
657
+ await store.index.updateWithRetry(indexKey, runId, {
658
+ awaitingSteps: {
659
+ [stepName]: {
660
+ status: "resolved",
661
+ triggerData
662
+ }
663
+ }
664
+ });
665
+ }
666
+ await checkAndTriggerPendingSteps(flowName, runId, store);
667
+ } catch (err) {
668
+ logger.error("Error handling await resolution", {
669
+ runId,
670
+ stepName,
671
+ error: err?.message
672
+ });
673
+ }
674
+ }
675
+ if (e.type === "await.timeout") {
676
+ const timeoutEvent = e;
677
+ const { stepName, timeoutAction, position, awaitType } = timeoutEvent;
678
+ const action = timeoutAction || "fail";
679
+ logger.warn("Await timeout occurred", {
680
+ runId,
681
+ stepName,
682
+ awaitType,
683
+ position,
684
+ action
685
+ });
686
+ try {
687
+ if (action === "fail") {
688
+ if (store.index.updateWithRetry) {
689
+ await store.index.updateWithRetry(indexKey, runId, {
690
+ awaitingSteps: {
691
+ [stepName]: {
692
+ status: "timeout",
693
+ timedOutAt: Date.now()
694
+ }
695
+ }
696
+ });
697
+ }
698
+ bus.publish({
699
+ type: "step.failed",
700
+ runId,
701
+ flowName,
702
+ stepName,
703
+ stepId: `${runId}__${stepName}__timeout`,
704
+ attempt: 1,
705
+ data: {
706
+ error: `Await timeout: ${awaitType} await exceeded timeout`
707
+ }
708
+ });
709
+ } else if (action === "continue") {
710
+ if (store.index.updateWithRetry) {
711
+ await store.index.updateWithRetry(indexKey, runId, {
712
+ awaitingSteps: {
713
+ [stepName]: {
714
+ status: "resolved",
715
+ triggerData: null,
716
+ timedOutAt: Date.now()
717
+ }
718
+ }
719
+ });
720
+ }
721
+ logger.info("Await timeout - continuing with null data", { runId, stepName });
722
+ await checkAndTriggerPendingSteps(flowName, runId, store);
723
+ }
724
+ } catch (err) {
725
+ logger.error("Error handling await timeout", {
726
+ runId,
727
+ stepName,
728
+ error: err?.message
729
+ });
730
+ }
731
+ }
354
732
  if (e.type === "emit") {
355
733
  const eventName = e.data?.name || e.data?.topic;
356
734
  if (!eventName) {
357
735
  logger.warn("Emit event missing name/topic", { flowName, runId, data: e.data });
358
736
  } else {
359
737
  try {
360
- if (!store.indexGet || !store.indexUpdateWithRetry) {
361
- logger.warn("StoreAdapter does not support indexGet or indexUpdateWithRetry");
738
+ if (!store.index.updateWithRetry) {
739
+ logger.warn("StoreAdapter does not support indexUpdateWithRetry");
362
740
  return;
363
741
  }
364
- const currentEntry = await store.indexGet(indexKey, runId);
365
- const emittedEvents = (currentEntry?.metadata?.emittedEvents || []).filter((item) => item != null && typeof item === "string");
366
- if (!emittedEvents.includes(eventName)) {
367
- await store.indexUpdateWithRetry(indexKey, runId, {
368
- emittedEvents: [...emittedEvents, eventName]
369
- });
370
- logger.debug("Tracked emit event", {
371
- flowName,
372
- runId,
373
- name: eventName,
374
- allEmitted: [...emittedEvents, eventName]
375
- });
742
+ const timestamp = Date.now();
743
+ const eventParts = eventName.split(".");
744
+ const emittedEventsUpdate = {};
745
+ let current = emittedEventsUpdate;
746
+ for (let i = 0; i < eventParts.length - 1; i++) {
747
+ current[eventParts[i]] = {};
748
+ current = current[eventParts[i]];
376
749
  }
750
+ current[eventParts[eventParts.length - 1]] = timestamp;
751
+ const updatePayload = {
752
+ emittedEvents: emittedEventsUpdate
753
+ };
754
+ await store.index.updateWithRetry(indexKey, runId, updatePayload);
755
+ logger.debug("Tracked emit event", {
756
+ flowName,
757
+ runId,
758
+ name: eventName,
759
+ timestamp
760
+ });
377
761
  } catch (err) {
378
762
  logger.warn("Failed to track emitted event", {
379
763
  flowName,
@@ -384,16 +768,27 @@ export function createFlowWiring() {
384
768
  }
385
769
  }
386
770
  }
771
+ if (e.type === "step.completed") {
772
+ try {
773
+ await checkAndTriggerPendingSteps(flowName, runId, store);
774
+ } catch (err) {
775
+ logger.error("Error checking pending steps", {
776
+ flowName,
777
+ runId,
778
+ error: err.message
779
+ });
780
+ throw err;
781
+ }
782
+ }
387
783
  if (e.type === "step.completed" || e.type === "step.failed") {
388
- await checkAndTriggerPendingSteps(flowName, runId, store);
389
- await new Promise((resolve) => setTimeout(resolve, 50));
390
784
  try {
391
- const allEvents = await store.read(streamName);
785
+ const allEvents = await store.stream.read(streamName);
392
786
  const analyzedFlows = $useAnalyzedFlows();
393
787
  const flowDef = analyzedFlows.find((f) => f.id === flowName);
394
788
  if (flowDef?.steps) {
395
789
  const entryStepName = flowDef.entry?.step;
396
- const analysis = analyzeFlowCompletion(flowDef.steps, entryStepName, allEvents);
790
+ const entryStepDef = flowDef.entry;
791
+ const analysis = analyzeFlowCompletion(flowDef.steps, entryStepName, allEvents, entryStepDef);
397
792
  const updateMetadata = {
398
793
  status: analysis.status,
399
794
  stepCount: analysis.totalSteps
@@ -401,19 +796,57 @@ export function createFlowWiring() {
401
796
  if (analysis.status !== "running" && analysis.completedAt) {
402
797
  updateMetadata.completedAt = analysis.completedAt;
403
798
  }
404
- if (store.indexUpdateWithRetry) {
405
- await store.indexUpdateWithRetry(indexKey, runId, updateMetadata);
799
+ if (store.index.get) {
800
+ const currentEntry = await store.index.get(indexKey, runId);
801
+ const awaitingStepsObj = currentEntry?.metadata?.awaitingSteps || {};
802
+ let hasActiveAwaits = false;
803
+ let hasTimedOutAwaits = false;
804
+ for (const [_stepName, awaitState] of Object.entries(awaitingStepsObj)) {
805
+ if (awaitState?.status === "awaiting") {
806
+ hasActiveAwaits = true;
807
+ } else if (awaitState?.status === "timeout") {
808
+ hasTimedOutAwaits = true;
809
+ }
810
+ }
811
+ if (hasActiveAwaits) {
812
+ updateMetadata.status = "awaiting";
813
+ }
814
+ if (hasTimedOutAwaits) {
815
+ updateMetadata.status = "failed";
816
+ if (!updateMetadata.completedAt) {
817
+ updateMetadata.completedAt = Date.now();
818
+ }
819
+ }
820
+ }
821
+ if (store.index.updateWithRetry) {
822
+ await store.index.updateWithRetry(indexKey, runId, updateMetadata);
406
823
  }
407
- if (analysis.status === "completed" || analysis.status === "failed") {
408
- const eventType = analysis.status === "completed" ? "flow.completed" : "flow.failed";
824
+ const finalStatus = updateMetadata.status || analysis.status;
825
+ if (finalStatus === "completed" || finalStatus === "failed") {
826
+ const eventType = finalStatus === "completed" ? "flow.completed" : "flow.failed";
827
+ let currentStatus = null;
828
+ if (store.index.get) {
829
+ const currentEntry = await store.index.get(indexKey, runId);
830
+ currentStatus = currentEntry?.metadata?.status;
831
+ }
409
832
  const terminalEventExists = allEvents.some((evt) => evt.type === "flow.completed" || evt.type === "flow.failed");
833
+ const publishKey = `${runId}:terminal`;
834
+ const alreadyPublishing = publishingTerminalEvents.has(publishKey);
410
835
  if (terminalEventExists) {
411
- logger.debug("Terminal event already exists, skipping publish", {
836
+ logger.debug("Flow terminal event already exists in stream, skipping publish", {
837
+ flowName,
838
+ runId,
839
+ currentStatus,
840
+ eventType
841
+ });
842
+ } else if (alreadyPublishing) {
843
+ logger.debug("Flow terminal event already being published, skipping duplicate", {
412
844
  flowName,
413
845
  runId,
414
846
  eventType
415
847
  });
416
848
  } else {
849
+ publishingTerminalEvents.add(publishKey);
417
850
  logger.info("Publishing terminal event to bus", {
418
851
  flowName,
419
852
  runId,
@@ -441,11 +874,48 @@ export function createFlowWiring() {
441
874
  type: e.type,
442
875
  runId: e.runId,
443
876
  flowName: e.flowName,
444
- error: err?.message,
445
- stack: err?.stack
877
+ error: err?.message
446
878
  });
447
879
  }
448
880
  };
881
+ const processEventSequentially = async (event) => {
882
+ const runId = event.runId;
883
+ if (!runId) {
884
+ await handlePersistence(event);
885
+ await handleOrchestration(event);
886
+ await handleFlowStats(event);
887
+ return;
888
+ }
889
+ const previousProcessing = flowProcessingChain.get(runId) || Promise.resolve();
890
+ const currentProcessing = previousProcessing.then(async () => {
891
+ try {
892
+ await handlePersistence(event);
893
+ await handleOrchestration(event);
894
+ await handleFlowStats(event);
895
+ } catch (err) {
896
+ logger.error("Error in sequential event processing", {
897
+ runId,
898
+ type: event.type,
899
+ error: err.message,
900
+ stack: err.stack
901
+ });
902
+ } finally {
903
+ const existingTimer = cleanupTimers.get(runId);
904
+ if (existingTimer) {
905
+ clearTimeout(existingTimer);
906
+ }
907
+ const timer = setTimeout(() => {
908
+ if (flowProcessingChain.get(runId) === currentProcessing) {
909
+ flowProcessingChain.delete(runId);
910
+ cleanupTimers.delete(runId);
911
+ }
912
+ }, 6e4);
913
+ cleanupTimers.set(runId, timer);
914
+ }
915
+ });
916
+ flowProcessingChain.set(runId, currentProcessing);
917
+ return currentProcessing;
918
+ };
449
919
  const eventTypes = [
450
920
  "flow.start",
451
921
  "flow.completed",
@@ -455,29 +925,82 @@ export function createFlowWiring() {
455
925
  "step.completed",
456
926
  "step.failed",
457
927
  "step.retry",
928
+ "await.registered",
929
+ "await.resolved",
930
+ "await.timeout",
458
931
  "log",
459
932
  "emit",
460
933
  "state"
461
934
  ];
462
935
  for (const type of eventTypes) {
463
- unsubs.push(bus.onType(type, handlePersistence));
464
- }
465
- for (const type of eventTypes) {
466
- unsubs.push(bus.onType(type, handleOrchestration));
936
+ unsubs.push(bus.onType(type, processEventSequentially));
467
937
  }
468
938
  const config = useRuntimeConfig();
469
- const flowConfig = config.nvent.flows;
939
+ const flowConfig = config.nvent.flow || {};
470
940
  stallDetector = createStallDetector(store, flowConfig.stallDetection);
471
- if (flowConfig.stallDetection.enabled) {
472
- stallDetector.start();
473
- logger.info("Stall detector started");
941
+ if (flowConfig.stallDetection?.enabled) {
942
+ await stallDetector.start();
943
+ const scheduleConfig = stallDetector.getScheduleConfig();
944
+ if (scheduleConfig.enabled) {
945
+ try {
946
+ const scheduler = useScheduler();
947
+ logger.info("Scheduling periodic stall detector from flowWiring", {
948
+ checkInterval: `${scheduleConfig.interval / 1e3}s`
949
+ });
950
+ const jobId = await scheduler.schedule({
951
+ id: "stall-detection",
952
+ name: "Flow Stall Detection",
953
+ type: "interval",
954
+ interval: scheduleConfig.interval,
955
+ handler: async () => {
956
+ if (!stallDetector || !wired) {
957
+ logger.debug("Stall detector handler called but wiring stopped");
958
+ return;
959
+ }
960
+ try {
961
+ logger.info("Stall detector running periodic check");
962
+ const analyzedFlows = $useAnalyzedFlows();
963
+ const flowNames = analyzedFlows.map((f) => f.id).filter(Boolean);
964
+ if (flowNames.length > 0) {
965
+ await stallDetector.checkFlowsForStalls(flowNames);
966
+ }
967
+ } catch (error) {
968
+ logger.error("Stall detector periodic check failed", {
969
+ error: error.message,
970
+ stack: error.stack
971
+ });
972
+ }
973
+ },
974
+ metadata: {
975
+ component: "stall-detector",
976
+ stallTimeout: scheduleConfig.stallTimeout,
977
+ checkInterval: scheduleConfig.interval
978
+ }
979
+ });
980
+ stallDetector.setSchedulerJobId(jobId);
981
+ logger.info("Stall detector started and scheduled", { jobId });
982
+ } catch (error) {
983
+ logger.error("Failed to schedule stall detector - periodic checks disabled", {
984
+ error: error.message,
985
+ stack: error.stack
986
+ });
987
+ }
988
+ } else {
989
+ logger.info("Stall detector started (periodic check disabled)");
990
+ }
474
991
  }
475
992
  }
476
- function stop() {
993
+ async function stop() {
477
994
  const logger = useNventLogger("flow-wiring");
478
995
  if (stallDetector) {
479
- stallDetector.stop();
480
- stallDetector = void 0;
996
+ try {
997
+ await stallDetector.stop();
998
+ stallDetector = void 0;
999
+ } catch (error) {
1000
+ logger.error("Error stopping stall detector", {
1001
+ error: error.message
1002
+ });
1003
+ }
481
1004
  }
482
1005
  for (const u of unsubs.splice(0)) {
483
1006
  try {
@@ -485,8 +1008,13 @@ export function createFlowWiring() {
485
1008
  } catch {
486
1009
  }
487
1010
  }
1011
+ publishingTerminalEvents.clear();
1012
+ for (const timer of cleanupTimers.values()) {
1013
+ clearTimeout(timer);
1014
+ }
1015
+ cleanupTimers.clear();
1016
+ flowProcessingChain.clear();
488
1017
  wired = false;
489
- logger.debug("Flow wiring stopped");
490
1018
  }
491
1019
  return { start, stop };
492
1020
  }