nvent 0.4.3 → 0.4.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 (234) hide show
  1. package/dist/module.d.mts +3 -184
  2. package/dist/module.json +3 -3
  3. package/dist/module.mjs +133 -197
  4. package/dist/runtime/adapters/builtin/file-queue.d.ts +53 -0
  5. package/dist/runtime/adapters/builtin/file-queue.js +435 -0
  6. package/dist/runtime/adapters/builtin/file-store.d.ts +46 -0
  7. package/dist/runtime/adapters/builtin/file-store.js +225 -0
  8. package/dist/runtime/adapters/builtin/file-stream.d.ts +39 -0
  9. package/dist/runtime/adapters/builtin/file-stream.js +56 -0
  10. package/dist/runtime/adapters/builtin/index.d.ts +10 -0
  11. package/dist/runtime/adapters/builtin/index.js +5 -0
  12. package/dist/runtime/adapters/builtin/memory-queue.d.ts +52 -0
  13. package/dist/runtime/adapters/builtin/memory-queue.js +239 -0
  14. package/dist/runtime/adapters/builtin/memory-store.d.ts +57 -0
  15. package/dist/runtime/adapters/builtin/memory-store.js +263 -0
  16. package/dist/runtime/adapters/builtin/memory-stream.d.ts +21 -0
  17. package/dist/runtime/adapters/builtin/memory-stream.js +56 -0
  18. package/dist/runtime/adapters/factory.d.ts +31 -0
  19. package/dist/runtime/adapters/factory.js +100 -0
  20. package/dist/runtime/adapters/index.d.ts +8 -0
  21. package/dist/runtime/adapters/index.js +3 -0
  22. package/dist/runtime/adapters/interfaces/index.d.ts +11 -0
  23. package/dist/runtime/adapters/interfaces/index.js +3 -0
  24. package/dist/runtime/adapters/interfaces/queue.d.ts +150 -0
  25. package/dist/runtime/adapters/interfaces/store.d.ts +233 -0
  26. package/dist/runtime/adapters/interfaces/stream.d.ts +62 -0
  27. package/dist/runtime/adapters/registry.d.ts +85 -0
  28. package/dist/runtime/adapters/registry.js +161 -0
  29. package/dist/runtime/config/index.d.ts +29 -0
  30. package/dist/runtime/config/index.js +167 -0
  31. package/dist/runtime/config/types.d.ts +367 -0
  32. package/dist/runtime/config/types.js +0 -0
  33. package/dist/runtime/events/types.d.ts +116 -0
  34. package/dist/runtime/events/types.js +0 -0
  35. package/dist/runtime/events/utils/stallDetector.d.ts +99 -0
  36. package/dist/runtime/events/utils/stallDetector.js +237 -0
  37. package/dist/runtime/{server-utils/events → events}/wiring/flowWiring.d.ts +3 -8
  38. package/dist/runtime/{server-utils/events → events}/wiring/flowWiring.js +119 -36
  39. package/dist/runtime/events/wiring/registry.d.ts +19 -0
  40. package/dist/runtime/events/wiring/registry.js +33 -0
  41. package/dist/runtime/events/wiring/stateWiring.d.ts +37 -0
  42. package/dist/runtime/events/wiring/stateWiring.js +92 -0
  43. package/dist/runtime/events/wiring/streamWiring.d.ts +32 -0
  44. package/dist/runtime/events/wiring/streamWiring.js +79 -0
  45. package/dist/runtime/server/api/_flows/[name]/clear-history.delete.js +16 -5
  46. package/dist/runtime/server/api/_flows/[name]/runs/[runId]/cancel.post.js +21 -0
  47. package/dist/runtime/server/api/_flows/[name]/runs.get.d.ts +12 -2
  48. package/dist/runtime/server/api/_flows/[name]/runs.get.js +15 -4
  49. package/dist/runtime/server/api/_flows/[name]/schedule.post.js +11 -2
  50. package/dist/runtime/server/api/_flows/[name]/schedules/[id].delete.js +21 -16
  51. package/dist/runtime/server/api/_flows/[name]/schedules.get.js +21 -19
  52. package/dist/runtime/server/api/_flows/ws.js +43 -22
  53. package/dist/runtime/server/api/_queues/[name]/job/[id].get.js +8 -3
  54. package/dist/runtime/server/api/_queues/[name]/job/index.get.js +12 -3
  55. package/dist/runtime/server/api/_queues/index.get.js +66 -23
  56. package/dist/runtime/server/api/_queues/ws.js +14 -4
  57. package/dist/runtime/server/plugins/00.adapters.d.ts +14 -0
  58. package/dist/runtime/server/plugins/00.adapters.js +69 -0
  59. package/dist/runtime/server/plugins/02.workers.js +45 -0
  60. package/dist/runtime/tsconfig.json +8 -0
  61. package/dist/runtime/utils/adapters.d.ts +66 -0
  62. package/dist/runtime/utils/adapters.js +51 -0
  63. package/dist/runtime/utils/defineFunction.d.ts +10 -0
  64. package/dist/runtime/{server-utils/utils/defineQueueWorker.js → utils/defineFunction.js} +4 -4
  65. package/dist/runtime/{server-utils/utils/defineQueueConfig.d.ts → utils/defineFunctionConfig.d.ts} +3 -3
  66. package/dist/runtime/utils/defineFunctionConfig.js +2 -0
  67. package/dist/runtime/utils/registerAdapter.d.ts +59 -0
  68. package/dist/runtime/utils/registerAdapter.js +13 -0
  69. package/dist/runtime/utils/useFlowEngine.d.ts +19 -0
  70. package/dist/runtime/utils/useFlowEngine.js +108 -0
  71. package/dist/runtime/{server-utils/utils → utils}/useNventLogger.js +2 -2
  72. package/dist/runtime/utils/useStreamTopics.d.ts +72 -0
  73. package/dist/runtime/utils/useStreamTopics.js +47 -0
  74. package/dist/runtime/{server-utils/worker/runner/node.d.ts → worker/node/runner.d.ts} +18 -2
  75. package/dist/runtime/{server-utils/worker/runner/node.js → worker/node/runner.js} +44 -17
  76. package/dist/types.d.mts +2 -2
  77. package/package.json +14 -44
  78. package/LICENSE +0 -21
  79. package/README.md +0 -389
  80. package/dist/runtime/app/assets/vueflow.css +0 -1
  81. package/dist/runtime/app/components/ConfirmDialog.d.vue.ts +0 -33
  82. package/dist/runtime/app/components/ConfirmDialog.vue +0 -121
  83. package/dist/runtime/app/components/ConfirmDialog.vue.d.ts +0 -33
  84. package/dist/runtime/app/components/FlowDiagram.d.vue.ts +0 -64
  85. package/dist/runtime/app/components/FlowDiagram.vue +0 -338
  86. package/dist/runtime/app/components/FlowDiagram.vue.d.ts +0 -64
  87. package/dist/runtime/app/components/FlowNodeCard.d.vue.ts +0 -29
  88. package/dist/runtime/app/components/FlowNodeCard.vue +0 -156
  89. package/dist/runtime/app/components/FlowNodeCard.vue.d.ts +0 -29
  90. package/dist/runtime/app/components/FlowRunOverview.d.vue.ts +0 -9
  91. package/dist/runtime/app/components/FlowRunOverview.vue +0 -291
  92. package/dist/runtime/app/components/FlowRunOverview.vue.d.ts +0 -9
  93. package/dist/runtime/app/components/FlowRunStatusBadge.d.vue.ts +0 -14
  94. package/dist/runtime/app/components/FlowRunStatusBadge.vue +0 -60
  95. package/dist/runtime/app/components/FlowRunStatusBadge.vue.d.ts +0 -14
  96. package/dist/runtime/app/components/FlowRunTimeline.d.vue.ts +0 -12
  97. package/dist/runtime/app/components/FlowRunTimeline.vue +0 -127
  98. package/dist/runtime/app/components/FlowRunTimeline.vue.d.ts +0 -12
  99. package/dist/runtime/app/components/FlowScheduleDialog.d.vue.ts +0 -16
  100. package/dist/runtime/app/components/FlowScheduleDialog.vue +0 -226
  101. package/dist/runtime/app/components/FlowScheduleDialog.vue.d.ts +0 -16
  102. package/dist/runtime/app/components/FlowSchedulesList.d.vue.ts +0 -12
  103. package/dist/runtime/app/components/FlowSchedulesList.vue +0 -99
  104. package/dist/runtime/app/components/FlowSchedulesList.vue.d.ts +0 -12
  105. package/dist/runtime/app/components/JobScheduling.d.vue.ts +0 -6
  106. package/dist/runtime/app/components/JobScheduling.vue +0 -203
  107. package/dist/runtime/app/components/JobScheduling.vue.d.ts +0 -6
  108. package/dist/runtime/app/components/ListItem.d.vue.ts +0 -23
  109. package/dist/runtime/app/components/ListItem.vue +0 -70
  110. package/dist/runtime/app/components/ListItem.vue.d.ts +0 -23
  111. package/dist/runtime/app/components/QueueConfigDetails.d.vue.ts +0 -45
  112. package/dist/runtime/app/components/QueueConfigDetails.vue +0 -412
  113. package/dist/runtime/app/components/QueueConfigDetails.vue.d.ts +0 -45
  114. package/dist/runtime/app/components/StatCounter.d.vue.ts +0 -9
  115. package/dist/runtime/app/components/StatCounter.vue +0 -25
  116. package/dist/runtime/app/components/StatCounter.vue.d.ts +0 -9
  117. package/dist/runtime/app/components/TimelineList.d.vue.ts +0 -7
  118. package/dist/runtime/app/components/TimelineList.vue +0 -210
  119. package/dist/runtime/app/components/TimelineList.vue.d.ts +0 -7
  120. package/dist/runtime/app/components/nhealth/component-router.d.vue.ts +0 -46
  121. package/dist/runtime/app/components/nhealth/component-router.vue +0 -26
  122. package/dist/runtime/app/components/nhealth/component-router.vue.d.ts +0 -46
  123. package/dist/runtime/app/components/nhealth/component-shell.d.vue.ts +0 -24
  124. package/dist/runtime/app/components/nhealth/component-shell.vue +0 -89
  125. package/dist/runtime/app/components/nhealth/component-shell.vue.d.ts +0 -24
  126. package/dist/runtime/app/composables/useAnalyzedFlows.d.ts +0 -14
  127. package/dist/runtime/app/composables/useAnalyzedFlows.js +0 -8
  128. package/dist/runtime/app/composables/useComponentRouter.d.ts +0 -38
  129. package/dist/runtime/app/composables/useComponentRouter.js +0 -240
  130. package/dist/runtime/app/composables/useFlowRunTimeline.d.ts +0 -15
  131. package/dist/runtime/app/composables/useFlowRunTimeline.js +0 -66
  132. package/dist/runtime/app/composables/useFlowRuns.d.ts +0 -18
  133. package/dist/runtime/app/composables/useFlowRuns.js +0 -32
  134. package/dist/runtime/app/composables/useFlowRunsInfinite.d.ts +0 -24
  135. package/dist/runtime/app/composables/useFlowRunsInfinite.js +0 -123
  136. package/dist/runtime/app/composables/useFlowRunsPolling.d.ts +0 -9
  137. package/dist/runtime/app/composables/useFlowRunsPolling.js +0 -33
  138. package/dist/runtime/app/composables/useFlowState.d.ts +0 -125
  139. package/dist/runtime/app/composables/useFlowState.js +0 -211
  140. package/dist/runtime/app/composables/useFlowWebSocket.d.ts +0 -27
  141. package/dist/runtime/app/composables/useFlowWebSocket.js +0 -205
  142. package/dist/runtime/app/composables/useFlowsNavigation.d.ts +0 -10
  143. package/dist/runtime/app/composables/useFlowsNavigation.js +0 -58
  144. package/dist/runtime/app/composables/useQueueJobs.d.ts +0 -20
  145. package/dist/runtime/app/composables/useQueueJobs.js +0 -20
  146. package/dist/runtime/app/composables/useQueueUpdates.d.ts +0 -26
  147. package/dist/runtime/app/composables/useQueueUpdates.js +0 -122
  148. package/dist/runtime/app/composables/useQueues.d.ts +0 -44
  149. package/dist/runtime/app/composables/useQueues.js +0 -26
  150. package/dist/runtime/app/composables/useQueuesLive.d.ts +0 -19
  151. package/dist/runtime/app/composables/useQueuesLive.js +0 -143
  152. package/dist/runtime/app/pages/flows/index.d.vue.ts +0 -3
  153. package/dist/runtime/app/pages/flows/index.vue +0 -645
  154. package/dist/runtime/app/pages/flows/index.vue.d.ts +0 -3
  155. package/dist/runtime/app/pages/index.d.vue.ts +0 -3
  156. package/dist/runtime/app/pages/index.vue +0 -34
  157. package/dist/runtime/app/pages/index.vue.d.ts +0 -3
  158. package/dist/runtime/app/pages/queues/index.d.vue.ts +0 -3
  159. package/dist/runtime/app/pages/queues/index.vue +0 -229
  160. package/dist/runtime/app/pages/queues/index.vue.d.ts +0 -3
  161. package/dist/runtime/app/pages/queues/job.d.vue.ts +0 -3
  162. package/dist/runtime/app/pages/queues/job.vue +0 -262
  163. package/dist/runtime/app/pages/queues/job.vue.d.ts +0 -3
  164. package/dist/runtime/app/pages/queues/jobs.d.vue.ts +0 -3
  165. package/dist/runtime/app/pages/queues/jobs.vue +0 -291
  166. package/dist/runtime/app/pages/queues/jobs.vue.d.ts +0 -3
  167. package/dist/runtime/app/plugins/vueflow.client.d.ts +0 -2
  168. package/dist/runtime/app/plugins/vueflow.client.js +0 -11
  169. package/dist/runtime/constants.d.ts +0 -11
  170. package/dist/runtime/constants.js +0 -11
  171. package/dist/runtime/schema.d.ts +0 -37
  172. package/dist/runtime/schema.js +0 -20
  173. package/dist/runtime/server/plugins/00.event-store.d.ts +0 -13
  174. package/dist/runtime/server/plugins/00.event-store.js +0 -16
  175. package/dist/runtime/server/plugins/flow-management.d.ts +0 -13
  176. package/dist/runtime/server/plugins/flow-management.js +0 -65
  177. package/dist/runtime/server/plugins/queue-management.js +0 -27
  178. package/dist/runtime/server/plugins/state-cleanup.d.ts +0 -11
  179. package/dist/runtime/server/plugins/state-cleanup.js +0 -93
  180. package/dist/runtime/server/plugins/worker-management.js +0 -33
  181. package/dist/runtime/server/tsconfig.json +0 -3
  182. package/dist/runtime/server-utils/events/adapters/fileAdapter.d.ts +0 -2
  183. package/dist/runtime/server-utils/events/adapters/fileAdapter.js +0 -382
  184. package/dist/runtime/server-utils/events/adapters/memoryAdapter.d.ts +0 -2
  185. package/dist/runtime/server-utils/events/adapters/memoryAdapter.js +0 -171
  186. package/dist/runtime/server-utils/events/adapters/redis/redisAdapter.d.ts +0 -2
  187. package/dist/runtime/server-utils/events/adapters/redis/redisAdapter.js +0 -348
  188. package/dist/runtime/server-utils/events/adapters/redis/redisPubSubGateway.d.ts +0 -30
  189. package/dist/runtime/server-utils/events/adapters/redis/redisPubSubGateway.js +0 -82
  190. package/dist/runtime/server-utils/events/eventStoreFactory.d.ts +0 -19
  191. package/dist/runtime/server-utils/events/eventStoreFactory.js +0 -44
  192. package/dist/runtime/server-utils/events/streamNames.d.ts +0 -17
  193. package/dist/runtime/server-utils/events/streamNames.js +0 -17
  194. package/dist/runtime/server-utils/events/types.d.ts +0 -63
  195. package/dist/runtime/server-utils/events/wiring/registry.d.ts +0 -10
  196. package/dist/runtime/server-utils/events/wiring/registry.js +0 -24
  197. package/dist/runtime/server-utils/queue/adapters/bullmq.d.ts +0 -18
  198. package/dist/runtime/server-utils/queue/adapters/bullmq.js +0 -164
  199. package/dist/runtime/server-utils/queue/queueFactory.d.ts +0 -3
  200. package/dist/runtime/server-utils/queue/queueFactory.js +0 -10
  201. package/dist/runtime/server-utils/queue/types.d.ts +0 -47
  202. package/dist/runtime/server-utils/state/adapters/redis.d.ts +0 -2
  203. package/dist/runtime/server-utils/state/adapters/redis.js +0 -42
  204. package/dist/runtime/server-utils/state/stateFactory.d.ts +0 -3
  205. package/dist/runtime/server-utils/state/stateFactory.js +0 -17
  206. package/dist/runtime/server-utils/state/types.d.ts +0 -23
  207. package/dist/runtime/server-utils/utils/defineQueueConfig.js +0 -2
  208. package/dist/runtime/server-utils/utils/defineQueueWorker.d.ts +0 -10
  209. package/dist/runtime/server-utils/utils/useEventStore.d.ts +0 -20
  210. package/dist/runtime/server-utils/utils/useEventStore.js +0 -119
  211. package/dist/runtime/server-utils/utils/useFlowEngine.d.ts +0 -9
  212. package/dist/runtime/server-utils/utils/useFlowEngine.js +0 -44
  213. package/dist/runtime/server-utils/utils/useLogs.d.ts +0 -41
  214. package/dist/runtime/server-utils/utils/useLogs.js +0 -74
  215. package/dist/runtime/server-utils/utils/useQueue.d.ts +0 -31
  216. package/dist/runtime/server-utils/utils/useQueue.js +0 -24
  217. package/dist/runtime/server-utils/worker/adapter.d.ts +0 -4
  218. package/dist/runtime/server-utils/worker/adapter.js +0 -66
  219. package/dist/runtime/types.d.ts +0 -132
  220. /package/dist/runtime/{server-utils/events/types.js → adapters/interfaces/queue.js} +0 -0
  221. /package/dist/runtime/{server-utils/queue/types.js → adapters/interfaces/store.js} +0 -0
  222. /package/dist/runtime/{server-utils/state/types.js → adapters/interfaces/stream.js} +0 -0
  223. /package/dist/runtime/{server-utils/events → events}/eventBus.d.ts +0 -0
  224. /package/dist/runtime/{server-utils/events → events}/eventBus.js +0 -0
  225. /package/dist/runtime/server/{plugins/queue-management.d.ts → api/_flows/[name]/runs/[runId]/cancel.post.d.ts} +0 -0
  226. /package/dist/runtime/server/plugins/{00.ws-lifecycle.d.ts → 01.ws-lifecycle.d.ts} +0 -0
  227. /package/dist/runtime/server/plugins/{00.ws-lifecycle.js → 01.ws-lifecycle.js} +0 -0
  228. /package/dist/runtime/server/plugins/{worker-management.d.ts → 02.workers.d.ts} +0 -0
  229. /package/dist/runtime/{server-utils/utils → utils}/useEventManager.d.ts +0 -0
  230. /package/dist/runtime/{server-utils/utils → utils}/useEventManager.js +0 -0
  231. /package/dist/runtime/{server-utils/utils → utils}/useNventLogger.d.ts +0 -0
  232. /package/dist/runtime/{server-utils/utils → utils}/wsPeerManager.d.ts +0 -0
  233. /package/dist/runtime/{server-utils/utils → utils}/wsPeerManager.js +0 -0
  234. /package/dist/runtime/{python → worker/python}/get_config.py +0 -0
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Flow Stall Detection System
3
+ *
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)
8
+ *
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
13
+ */
14
+ import type { StoreAdapter } from '../../adapters/interfaces/store.js';
15
+ export interface StallDetectorConfig {
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
29
+ * @default true
30
+ */
31
+ enablePeriodicCheck?: boolean;
32
+ }
33
+ 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
+ export declare class FlowStallDetector {
43
+ private store;
44
+ private config;
45
+ private logger;
46
+ private intervalId?;
47
+ private started;
48
+ constructor(store: StoreAdapter, config?: StallDetectorConfig);
49
+ /**
50
+ * Start the periodic stall detector
51
+ * Should be called once per instance after adapters are initialized
52
+ */
53
+ start(): void;
54
+ /**
55
+ * Stop the periodic stall detector
56
+ */
57
+ stop(): void;
58
+ /**
59
+ * Update activity timestamp for a flow
60
+ * Should be called on every step event (started, completed, failed, retry)
61
+ */
62
+ updateActivity(flowName: string, runId: string): Promise<void>;
63
+ /**
64
+ * Check if a specific flow is stalled (lazy detection)
65
+ * Returns true if the flow should be marked as stalled
66
+ */
67
+ isStalled(flowName: string, runId: string): Promise<boolean>;
68
+ /**
69
+ * Mark a flow as stalled
70
+ * Emits a flow.stalled event and updates the flow status
71
+ */
72
+ markAsStalled(flowName: string, runId: string, reason?: string): Promise<void>;
73
+ /**
74
+ * Check all running flows and mark stalled ones
75
+ * This is called by the periodic background job
76
+ *
77
+ * Note: This method requires knowledge of which flows exist.
78
+ * For now, we'll need to pass flow names to check, or iterate known flows from registry.
79
+ */
80
+ checkFlowsForStalls(flowNames: string[]): Promise<void>;
81
+ /**
82
+ * Internal method for periodic checks
83
+ * Gets flow names from registry and checks them
84
+ */
85
+ private checkAllRunningFlows;
86
+ /**
87
+ * Get stall detector statistics
88
+ */
89
+ getStats(): {
90
+ enabled: boolean;
91
+ periodicCheckEnabled: boolean;
92
+ stallTimeout: number;
93
+ checkInterval: number;
94
+ };
95
+ }
96
+ /**
97
+ * Create and configure a flow stall detector
98
+ */
99
+ export declare function createStallDetector(store: StoreAdapter, config?: StallDetectorConfig): FlowStallDetector;
@@ -0,0 +1,237 @@
1
+ import { useNventLogger, useStreamTopics } from "#imports";
2
+ const DEFAULT_STALL_TIMEOUT = 30 * 60 * 1e3;
3
+ const DEFAULT_CHECK_INTERVAL = 15 * 60 * 1e3;
4
+ export class FlowStallDetector {
5
+ store;
6
+ config;
7
+ logger = useNventLogger("stall-detector");
8
+ intervalId;
9
+ started = false;
10
+ constructor(store, config = {}) {
11
+ this.store = store;
12
+ this.config = {
13
+ stallTimeout: config.stallTimeout ?? DEFAULT_STALL_TIMEOUT,
14
+ checkInterval: config.checkInterval ?? DEFAULT_CHECK_INTERVAL,
15
+ enablePeriodicCheck: config.enablePeriodicCheck ?? true
16
+ };
17
+ }
18
+ /**
19
+ * Start the periodic stall detector
20
+ * Should be called once per instance after adapters are initialized
21
+ */
22
+ start() {
23
+ if (this.started) {
24
+ this.logger.warn("Stall detector already started");
25
+ return;
26
+ }
27
+ this.started = true;
28
+ if (this.config.enablePeriodicCheck) {
29
+ this.logger.info("Starting periodic stall detector", {
30
+ stallTimeout: `${this.config.stallTimeout / 1e3}s`,
31
+ checkInterval: `${this.config.checkInterval / 1e3}s`
32
+ });
33
+ setTimeout(() => {
34
+ this.checkAllRunningFlows();
35
+ }, 60 * 1e3);
36
+ this.intervalId = setInterval(() => {
37
+ this.checkAllRunningFlows();
38
+ }, this.config.checkInterval);
39
+ } else {
40
+ this.logger.info("Periodic stall detector disabled, using lazy detection only");
41
+ }
42
+ }
43
+ /**
44
+ * Stop the periodic stall detector
45
+ */
46
+ stop() {
47
+ if (this.intervalId) {
48
+ clearInterval(this.intervalId);
49
+ this.intervalId = void 0;
50
+ this.logger.info("Stopped periodic stall detector");
51
+ }
52
+ this.started = false;
53
+ }
54
+ /**
55
+ * Update activity timestamp for a flow
56
+ * Should be called on every step event (started, completed, failed, retry)
57
+ */
58
+ async updateActivity(flowName, runId) {
59
+ const { SubjectPatterns } = useStreamTopics();
60
+ const indexKey = SubjectPatterns.flowRunIndex(flowName);
61
+ try {
62
+ if (!this.store.indexUpdate) {
63
+ this.logger.warn("Store does not support indexUpdate, cannot update activity");
64
+ return;
65
+ }
66
+ await this.store.indexUpdate(indexKey, runId, {
67
+ lastActivityAt: Date.now()
68
+ });
69
+ } catch (error) {
70
+ this.logger.warn("Failed to update flow activity", {
71
+ flowName,
72
+ runId,
73
+ error: error.message
74
+ });
75
+ }
76
+ }
77
+ /**
78
+ * Check if a specific flow is stalled (lazy detection)
79
+ * Returns true if the flow should be marked as stalled
80
+ */
81
+ async isStalled(flowName, runId) {
82
+ const { SubjectPatterns } = useStreamTopics();
83
+ const indexKey = SubjectPatterns.flowRunIndex(flowName);
84
+ try {
85
+ if (!this.store.indexGet) return false;
86
+ const flowEntry = await this.store.indexGet(indexKey, runId);
87
+ if (!flowEntry?.metadata) return false;
88
+ if (flowEntry.metadata.status !== "running") return false;
89
+ const lastActivity = flowEntry.metadata.lastActivityAt || flowEntry.metadata.startedAt || 0;
90
+ const timeSinceActivity = Date.now() - lastActivity;
91
+ if (timeSinceActivity > this.config.stallTimeout) {
92
+ this.logger.info("Flow detected as stalled (lazy check)", {
93
+ flowName,
94
+ runId,
95
+ timeSinceActivity: `${Math.round(timeSinceActivity / 1e3)}s`,
96
+ stallTimeout: `${this.config.stallTimeout / 1e3}s`
97
+ });
98
+ return true;
99
+ }
100
+ return false;
101
+ } catch (error) {
102
+ this.logger.warn("Failed to check if flow is stalled", {
103
+ flowName,
104
+ runId,
105
+ error: error.message
106
+ });
107
+ return false;
108
+ }
109
+ }
110
+ /**
111
+ * Mark a flow as stalled
112
+ * Emits a flow.stalled event and updates the flow status
113
+ */
114
+ async markAsStalled(flowName, runId, reason = "No activity timeout") {
115
+ const { SubjectPatterns } = useStreamTopics();
116
+ const indexKey = SubjectPatterns.flowRunIndex(flowName);
117
+ try {
118
+ if (!this.store.indexGet) {
119
+ this.logger.warn("Store does not support indexGet, cannot mark as stalled");
120
+ return;
121
+ }
122
+ const flowEntry = await this.store.indexGet(indexKey, runId);
123
+ if (!flowEntry?.metadata) return;
124
+ if (flowEntry.metadata.status !== "running") return;
125
+ if (this.store.indexUpdate) {
126
+ await this.store.indexUpdate(indexKey, runId, {
127
+ status: "stalled",
128
+ stalledAt: Date.now(),
129
+ stallReason: reason
130
+ });
131
+ }
132
+ const streamName = SubjectPatterns.flowRun(runId);
133
+ await this.store.append(streamName, {
134
+ type: "flow.stalled",
135
+ runId,
136
+ flowName,
137
+ data: {
138
+ reason
139
+ }
140
+ });
141
+ this.logger.info("Marked flow as stalled", {
142
+ flowName,
143
+ runId,
144
+ reason
145
+ });
146
+ } catch (error) {
147
+ this.logger.error("Failed to mark flow as stalled", {
148
+ flowName,
149
+ runId,
150
+ error: error.message
151
+ });
152
+ }
153
+ }
154
+ /**
155
+ * Check all running flows and mark stalled ones
156
+ * This is called by the periodic background job
157
+ *
158
+ * Note: This method requires knowledge of which flows exist.
159
+ * For now, we'll need to pass flow names to check, or iterate known flows from registry.
160
+ */
161
+ async checkFlowsForStalls(flowNames) {
162
+ this.logger.debug("Running periodic stall check", { flows: flowNames.length });
163
+ try {
164
+ if (!this.store.indexGet || !this.store.indexRead) {
165
+ this.logger.warn("Store does not support required index operations");
166
+ return;
167
+ }
168
+ const { SubjectPatterns } = useStreamTopics();
169
+ let checkedCount = 0;
170
+ let stalledCount = 0;
171
+ for (const flowName of flowNames) {
172
+ const indexKey = SubjectPatterns.flowRunIndex(flowName);
173
+ const entries = await this.store.indexRead(indexKey, { limit: 1e3 });
174
+ for (const entry of entries) {
175
+ if (!entry.metadata) continue;
176
+ checkedCount++;
177
+ if (entry.metadata.status !== "running") continue;
178
+ const lastActivity = entry.metadata.lastActivityAt || entry.metadata.startedAt || 0;
179
+ const timeSinceActivity = Date.now() - lastActivity;
180
+ if (timeSinceActivity > this.config.stallTimeout) {
181
+ await this.markAsStalled(flowName, entry.id, "Periodic check detected no activity");
182
+ stalledCount++;
183
+ }
184
+ }
185
+ }
186
+ if (stalledCount > 0) {
187
+ this.logger.info("Periodic stall check completed", {
188
+ checked: checkedCount,
189
+ stalled: stalledCount
190
+ });
191
+ } else {
192
+ this.logger.debug("Periodic stall check completed", {
193
+ checked: checkedCount,
194
+ stalled: 0
195
+ });
196
+ }
197
+ } catch (error) {
198
+ this.logger.error("Failed to run periodic stall check", {
199
+ error: error.message
200
+ });
201
+ }
202
+ }
203
+ /**
204
+ * Internal method for periodic checks
205
+ * Gets flow names from registry and checks them
206
+ */
207
+ async checkAllRunningFlows() {
208
+ try {
209
+ const { $useAnalyzedFlows } = await import("#imports");
210
+ const analyzedFlows = $useAnalyzedFlows();
211
+ const flowNames = analyzedFlows.map((f) => f.id).filter(Boolean);
212
+ if (flowNames.length === 0) {
213
+ this.logger.debug("No flows registered, skipping stall check");
214
+ return;
215
+ }
216
+ await this.checkFlowsForStalls(flowNames);
217
+ } catch (error) {
218
+ this.logger.error("Failed to run periodic stall check", {
219
+ error: error.message
220
+ });
221
+ }
222
+ }
223
+ /**
224
+ * Get stall detector statistics
225
+ */
226
+ getStats() {
227
+ return {
228
+ enabled: this.started,
229
+ periodicCheckEnabled: this.config.enablePeriodicCheck,
230
+ stallTimeout: this.config.stallTimeout,
231
+ checkInterval: this.config.checkInterval
232
+ };
233
+ }
234
+ }
235
+ export function createStallDetector(store, config) {
236
+ return new FlowStallDetector(store, config);
237
+ }
@@ -1,8 +1,3 @@
1
- import type { EventStoreAdapter } from '../types.js';
2
- import type { EventRecord } from '../../types.js';
3
- export interface FlowWiringDeps {
4
- adapter: EventStoreAdapter;
5
- }
6
1
  /**
7
2
  * Check if all dependencies for a step are met
8
3
  * Returns true if all subscriptions have been emitted or completed
@@ -12,8 +7,8 @@ export declare function checkPendingStepTriggers(step: any, emittedEvents: Set<s
12
7
  * Analyze flow completion status from events
13
8
  * Returns status, step counts, and timestamps
14
9
  */
15
- export declare function analyzeFlowCompletion(flowSteps: Record<string, any>, entryStep: string | undefined, events: EventRecord[]): {
16
- status: 'running' | 'completed' | 'failed';
10
+ export declare function analyzeFlowCompletion(flowSteps: Record<string, any>, entryStep: string | undefined, events: any[]): {
11
+ status: 'running' | 'completed' | 'failed' | 'canceled';
17
12
  totalSteps: number;
18
13
  completedSteps: number;
19
14
  startedAt: number;
@@ -27,7 +22,7 @@ export declare function analyzeFlowCompletion(flowSteps: Record<string, any>, en
27
22
  *
28
23
  * Events arrive as "ingress" (no id/ts) and are persisted to `nq:flow:{runId}` streams.
29
24
  */
30
- export declare function createFlowWiring(deps: FlowWiringDeps): {
25
+ export declare function createFlowWiring(): {
31
26
  start: () => void;
32
27
  stop: () => void;
33
28
  };
@@ -1,5 +1,6 @@
1
1
  import { getEventBus } from "../eventBus.js";
2
- import { useEventStore, $useAnalyzedFlows, $useQueueRegistry, useQueue, useNventLogger } from "#imports";
2
+ import { useNventLogger, useStoreAdapter, useQueueAdapter, $useAnalyzedFlows, $useQueueRegistry, useStreamTopics, useRuntimeConfig } from "#imports";
3
+ import { createStallDetector } from "../utils/stallDetector.js";
3
4
  export function checkPendingStepTriggers(step, emittedEvents, completedSteps) {
4
5
  if (!step.subscribes || step.subscribes.length === 0) {
5
6
  return true;
@@ -18,15 +19,22 @@ async function checkAndTriggerPendingSteps(flowName, runId, store) {
18
19
  try {
19
20
  const analyzedFlows = $useAnalyzedFlows();
20
21
  const registry = $useQueueRegistry();
21
- const { enqueue } = useQueue();
22
+ const queue = useQueueAdapter();
23
+ const { SubjectPatterns } = useStreamTopics();
22
24
  const flowDef = analyzedFlows.find((f) => f.id === flowName);
23
25
  if (!flowDef?.steps) return;
24
- const indexKey = store.names().flowIndex(flowName);
26
+ const indexKey = SubjectPatterns.flowRunIndex(flowName);
27
+ if (!store.indexGet) return;
25
28
  const flowEntry = await store.indexGet(indexKey, runId);
26
29
  if (!flowEntry?.metadata) return;
27
- const emittedEvents = new Set(flowEntry.metadata.emittedEvents || []);
28
- const streamName = store.names().flow(runId);
30
+ const streamName = SubjectPatterns.flowRun(runId);
29
31
  const allEvents = await store.read(streamName);
32
+ const isCanceled = allEvents.some((event) => event.type === "flow.cancel");
33
+ if (isCanceled) {
34
+ logger.debug("Flow is canceled, skipping pending step triggers", { flowName, runId });
35
+ return;
36
+ }
37
+ const emittedEvents = new Set(flowEntry.metadata.emittedEvents || []);
30
38
  const completedSteps = /* @__PURE__ */ new Set();
31
39
  for (const event of allEvents) {
32
40
  if (event.type === "step.completed" && "stepName" in event) {
@@ -58,8 +66,13 @@ async function checkAndTriggerPendingSteps(flowName, runId, store) {
58
66
  // Keyed by event name
59
67
  };
60
68
  const jobId = `${runId}__${stepName}`;
69
+ const worker = registry?.workers?.find(
70
+ (w) => w?.flow?.step === stepName && w?.queue?.name === stepMeta.queue
71
+ );
72
+ const defaultOpts = worker?.queue?.defaultJobOptions || {};
73
+ const opts = { ...defaultOpts, jobId };
61
74
  try {
62
- await enqueue(stepMeta.queue, { name: stepName, data: payload, opts: { jobId } });
75
+ await queue.enqueue(stepMeta.queue, { name: stepName, data: payload, opts });
63
76
  logger.debug("Triggered pending step", {
64
77
  flowName,
65
78
  runId,
@@ -80,6 +93,7 @@ async function checkAndTriggerPendingSteps(flowName, runId, store) {
80
93
  }
81
94
  }
82
95
  export function analyzeFlowCompletion(flowSteps, entryStep, events) {
96
+ const isCanceled = events.some((event) => event.type === "flow.cancel");
83
97
  const allSteps = entryStep ? [entryStep, ...Object.keys(flowSteps)] : Object.keys(flowSteps);
84
98
  const completedSteps = /* @__PURE__ */ new Set();
85
99
  const failedSteps = /* @__PURE__ */ new Set();
@@ -90,6 +104,9 @@ export function analyzeFlowCompletion(flowSteps, entryStep, events) {
90
104
  if (event.type === "flow.start") {
91
105
  startedAt = typeof event.ts === "string" ? new Date(event.ts).getTime() : 0;
92
106
  }
107
+ if (event.type === "flow.cancel") {
108
+ completedAt = typeof event.ts === "string" ? new Date(event.ts).getTime() : Date.now();
109
+ }
93
110
  if (event.type === "step.completed" && "stepName" in event) {
94
111
  completedSteps.add(event.stepName);
95
112
  }
@@ -102,7 +119,7 @@ export function analyzeFlowCompletion(flowSteps, entryStep, events) {
102
119
  }
103
120
  }
104
121
  const finalFailedSteps = /* @__PURE__ */ new Set();
105
- for (const stepName of failedSteps) {
122
+ for (const stepName of Array.from(failedSteps)) {
106
123
  let lastRetryIndex = -1;
107
124
  let lastFailedIndex = -1;
108
125
  for (let i = 0; i < events.length; i++) {
@@ -124,7 +141,7 @@ export function analyzeFlowCompletion(flowSteps, entryStep, events) {
124
141
  const hasFinalFailures = finalFailedSteps.size > 0;
125
142
  let hasBlockingFailure = false;
126
143
  if (hasFinalFailures) {
127
- for (const failedStepName of finalFailedSteps) {
144
+ for (const failedStepName of Array.from(finalFailedSteps)) {
128
145
  const failedStepDef = flowSteps[failedStepName];
129
146
  if (failedStepDef?.emits && failedStepDef.emits.length > 0) {
130
147
  for (const [stepName, stepDef] of Object.entries(flowSteps)) {
@@ -146,6 +163,15 @@ export function analyzeFlowCompletion(flowSteps, entryStep, events) {
146
163
  if (hasBlockingFailure) break;
147
164
  }
148
165
  }
166
+ if (isCanceled) {
167
+ return {
168
+ status: "canceled",
169
+ totalSteps,
170
+ completedSteps: completedSteps.size,
171
+ startedAt,
172
+ completedAt
173
+ };
174
+ }
149
175
  if (hasBlockingFailure) {
150
176
  return {
151
177
  status: "failed",
@@ -171,17 +197,20 @@ export function analyzeFlowCompletion(flowSteps, entryStep, events) {
171
197
  completedAt
172
198
  };
173
199
  }
174
- export function createFlowWiring(deps) {
175
- const { adapter } = deps;
200
+ export function createFlowWiring() {
176
201
  const bus = getEventBus();
177
202
  const unsubs = [];
178
203
  let wired = false;
204
+ let stallDetector;
179
205
  const indexFlowRun = async (flowName, flowId, timestamp, metadata) => {
180
206
  const logger = useNventLogger("flow-wiring");
181
207
  try {
182
- const store = useEventStore();
183
- const names = store.names();
184
- const indexKey = names.flowIndex(flowName);
208
+ const store = useStoreAdapter();
209
+ const { SubjectPatterns } = useStreamTopics();
210
+ const indexKey = SubjectPatterns.flowRunIndex(flowName);
211
+ if (!store.indexAdd) {
212
+ throw new Error("StoreAdapter does not support indexAdd");
213
+ }
185
214
  await store.indexAdd(indexKey, flowId, timestamp, metadata);
186
215
  logger.debug("Indexed run", { flowName, flowId, indexKey, timestamp, metadata });
187
216
  } catch (err) {
@@ -192,8 +221,15 @@ export function createFlowWiring(deps) {
192
221
  if (wired) return;
193
222
  wired = true;
194
223
  const logger = useNventLogger("flow-wiring");
195
- const store = useEventStore();
196
- const names = store.names();
224
+ const { SubjectPatterns } = useStreamTopics();
225
+ const store = useStoreAdapter();
226
+ if (!store || !store.append) {
227
+ logger.error("StoreAdapter not properly initialized or missing append method", {
228
+ hasStore: !!store,
229
+ hasAppend: !!(store && store.append)
230
+ });
231
+ throw new Error("StoreAdapter not initialized");
232
+ }
197
233
  const handlePersistence = async (e) => {
198
234
  try {
199
235
  if (e.id && e.ts) {
@@ -207,7 +243,11 @@ export function createFlowWiring(deps) {
207
243
  if (!flowName) {
208
244
  return;
209
245
  }
210
- const streamName = names.flow(runId);
246
+ const streamName = SubjectPatterns.flowRun(runId);
247
+ if (!e.type) {
248
+ logger.error("Event missing type field", { event: e });
249
+ return;
250
+ }
211
251
  const eventData = {
212
252
  type: e.type,
213
253
  runId: e.runId,
@@ -217,19 +257,22 @@ export function createFlowWiring(deps) {
217
257
  if ("stepName" in e && e.stepName) eventData.stepName = e.stepName;
218
258
  if ("stepId" in e && e.stepId) eventData.stepId = e.stepId;
219
259
  if ("attempt" in e && e.attempt) eventData.attempt = e.attempt;
220
- await adapter.append(streamName, eventData);
260
+ const persistedEvent = await store.append(streamName, eventData);
261
+ await bus.publish(persistedEvent);
221
262
  if (e.type === "flow.completed" || e.type === "flow.failed") {
222
- logger.info("Stored terminal event to stream", {
263
+ logger.info("Stored terminal event", {
223
264
  type: e.type,
224
265
  flowName,
225
- runId
266
+ runId,
267
+ id: persistedEvent.id
226
268
  });
227
269
  } else {
228
- logger.debug("Stored event to stream", {
270
+ logger.debug("Stored event", {
229
271
  type: e.type,
230
272
  flowName,
231
273
  runId,
232
- stepName: "stepName" in e ? e.stepName : void 0
274
+ stepName: "stepName" in e ? e.stepName : void 0,
275
+ id: persistedEvent.id
233
276
  });
234
277
  }
235
278
  } catch (err) {
@@ -253,27 +296,53 @@ export function createFlowWiring(deps) {
253
296
  if (!runId) return;
254
297
  const flowName = e.flowName;
255
298
  if (!flowName) return;
256
- const streamName = names.flow(runId);
257
- const indexKey = names.flowIndex(flowName);
299
+ const streamName = SubjectPatterns.flowRun(runId);
300
+ const indexKey = SubjectPatterns.flowRunIndex(flowName);
258
301
  if (e.type === "flow.start") {
259
302
  const timestamp = Date.now();
260
303
  await indexFlowRun(flowName, runId, timestamp, {
261
304
  status: "running",
262
305
  startedAt: timestamp,
306
+ lastActivityAt: timestamp,
307
+ // Initialize for stall detection
263
308
  stepCount: 0,
264
309
  completedSteps: 0,
265
310
  emittedEvents: []
266
311
  });
267
312
  }
268
- if (e.type === "step.completed") {
313
+ if (e.type === "flow.cancel") {
269
314
  try {
270
- const newCount = await store.indexIncrement(indexKey, runId, "completedSteps", 1);
271
- logger.debug("Incremented completedSteps", {
315
+ if (store.indexUpdateWithRetry) {
316
+ await store.indexUpdateWithRetry(indexKey, runId, {
317
+ status: "canceled",
318
+ completedAt: Date.now()
319
+ });
320
+ logger.info("Marked flow as canceled", { flowName, runId });
321
+ }
322
+ } catch (err) {
323
+ logger.warn("Failed to update canceled status", {
272
324
  flowName,
273
325
  runId,
274
- stepName: "stepName" in e ? e.stepName : "unknown",
275
- newCount
326
+ error: err?.message
276
327
  });
328
+ }
329
+ }
330
+ if (e.type === "step.started" || e.type === "step.completed" || e.type === "step.failed" || e.type === "step.retry") {
331
+ if (stallDetector) {
332
+ await stallDetector.updateActivity(flowName, runId);
333
+ }
334
+ }
335
+ if (e.type === "step.completed") {
336
+ try {
337
+ if (store.indexIncrement) {
338
+ const newCount = await store.indexIncrement(indexKey, runId, "completedSteps", 1);
339
+ logger.debug("Incremented completedSteps", {
340
+ flowName,
341
+ runId,
342
+ stepName: "stepName" in e ? e.stepName : "unknown",
343
+ newCount
344
+ });
345
+ }
277
346
  } catch (err) {
278
347
  logger.warn("Failed to update completedSteps", {
279
348
  flowName,
@@ -283,12 +352,15 @@ export function createFlowWiring(deps) {
283
352
  }
284
353
  }
285
354
  if (e.type === "emit") {
286
- const emitEvent = e;
287
- const eventName = emitEvent.data?.name || emitEvent.data?.topic;
355
+ const eventName = e.data?.name || e.data?.topic;
288
356
  if (!eventName) {
289
- logger.warn("Emit event missing name/topic", { flowName, runId, data: emitEvent.data });
357
+ logger.warn("Emit event missing name/topic", { flowName, runId, data: e.data });
290
358
  } else {
291
359
  try {
360
+ if (!store.indexGet || !store.indexUpdateWithRetry) {
361
+ logger.warn("StoreAdapter does not support indexGet or indexUpdateWithRetry");
362
+ return;
363
+ }
292
364
  const currentEntry = await store.indexGet(indexKey, runId);
293
365
  const emittedEvents = (currentEntry?.metadata?.emittedEvents || []).filter((item) => item != null && typeof item === "string");
294
366
  if (!emittedEvents.includes(eventName)) {
@@ -310,7 +382,6 @@ export function createFlowWiring(deps) {
310
382
  error: err?.message
311
383
  });
312
384
  }
313
- await checkAndTriggerPendingSteps(flowName, runId, store);
314
385
  }
315
386
  }
316
387
  if (e.type === "step.completed" || e.type === "step.failed") {
@@ -330,12 +401,12 @@ export function createFlowWiring(deps) {
330
401
  if (analysis.status !== "running" && analysis.completedAt) {
331
402
  updateMetadata.completedAt = analysis.completedAt;
332
403
  }
333
- await store.indexUpdateWithRetry(indexKey, runId, updateMetadata);
404
+ if (store.indexUpdateWithRetry) {
405
+ await store.indexUpdateWithRetry(indexKey, runId, updateMetadata);
406
+ }
334
407
  if (analysis.status === "completed" || analysis.status === "failed") {
335
408
  const eventType = analysis.status === "completed" ? "flow.completed" : "flow.failed";
336
- const terminalEventExists = allEvents.some(
337
- (evt) => evt.type === "flow.completed" || evt.type === "flow.failed"
338
- );
409
+ const terminalEventExists = allEvents.some((evt) => evt.type === "flow.completed" || evt.type === "flow.failed");
339
410
  if (terminalEventExists) {
340
411
  logger.debug("Terminal event already exists, skipping publish", {
341
412
  flowName,
@@ -379,6 +450,7 @@ export function createFlowWiring(deps) {
379
450
  "flow.start",
380
451
  "flow.completed",
381
452
  "flow.failed",
453
+ "flow.cancel",
382
454
  "step.started",
383
455
  "step.completed",
384
456
  "step.failed",
@@ -393,9 +465,20 @@ export function createFlowWiring(deps) {
393
465
  for (const type of eventTypes) {
394
466
  unsubs.push(bus.onType(type, handleOrchestration));
395
467
  }
468
+ const config = useRuntimeConfig();
469
+ const flowConfig = config.nvent.flows;
470
+ stallDetector = createStallDetector(store, flowConfig.stallDetection);
471
+ if (flowConfig.stallDetection.enabled) {
472
+ stallDetector.start();
473
+ logger.info("Stall detector started");
474
+ }
396
475
  }
397
476
  function stop() {
398
477
  const logger = useNventLogger("flow-wiring");
478
+ if (stallDetector) {
479
+ stallDetector.stop();
480
+ stallDetector = void 0;
481
+ }
399
482
  for (const u of unsubs.splice(0)) {
400
483
  try {
401
484
  u();