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.
- package/dist/module.d.mts +3 -184
- package/dist/module.json +3 -3
- package/dist/module.mjs +133 -197
- package/dist/runtime/adapters/builtin/file-queue.d.ts +53 -0
- package/dist/runtime/adapters/builtin/file-queue.js +435 -0
- package/dist/runtime/adapters/builtin/file-store.d.ts +46 -0
- package/dist/runtime/adapters/builtin/file-store.js +225 -0
- package/dist/runtime/adapters/builtin/file-stream.d.ts +39 -0
- package/dist/runtime/adapters/builtin/file-stream.js +56 -0
- package/dist/runtime/adapters/builtin/index.d.ts +10 -0
- package/dist/runtime/adapters/builtin/index.js +5 -0
- package/dist/runtime/adapters/builtin/memory-queue.d.ts +52 -0
- package/dist/runtime/adapters/builtin/memory-queue.js +239 -0
- package/dist/runtime/adapters/builtin/memory-store.d.ts +57 -0
- package/dist/runtime/adapters/builtin/memory-store.js +263 -0
- package/dist/runtime/adapters/builtin/memory-stream.d.ts +21 -0
- package/dist/runtime/adapters/builtin/memory-stream.js +56 -0
- package/dist/runtime/adapters/factory.d.ts +31 -0
- package/dist/runtime/adapters/factory.js +100 -0
- package/dist/runtime/adapters/index.d.ts +8 -0
- package/dist/runtime/adapters/index.js +3 -0
- package/dist/runtime/adapters/interfaces/index.d.ts +11 -0
- package/dist/runtime/adapters/interfaces/index.js +3 -0
- package/dist/runtime/adapters/interfaces/queue.d.ts +150 -0
- package/dist/runtime/adapters/interfaces/store.d.ts +233 -0
- package/dist/runtime/adapters/interfaces/stream.d.ts +62 -0
- package/dist/runtime/adapters/registry.d.ts +85 -0
- package/dist/runtime/adapters/registry.js +161 -0
- package/dist/runtime/config/index.d.ts +29 -0
- package/dist/runtime/config/index.js +167 -0
- package/dist/runtime/config/types.d.ts +367 -0
- package/dist/runtime/config/types.js +0 -0
- package/dist/runtime/events/types.d.ts +116 -0
- package/dist/runtime/events/types.js +0 -0
- package/dist/runtime/events/utils/stallDetector.d.ts +99 -0
- package/dist/runtime/events/utils/stallDetector.js +237 -0
- package/dist/runtime/{server-utils/events → events}/wiring/flowWiring.d.ts +3 -8
- package/dist/runtime/{server-utils/events → events}/wiring/flowWiring.js +119 -36
- package/dist/runtime/events/wiring/registry.d.ts +19 -0
- package/dist/runtime/events/wiring/registry.js +33 -0
- package/dist/runtime/events/wiring/stateWiring.d.ts +37 -0
- package/dist/runtime/events/wiring/stateWiring.js +92 -0
- package/dist/runtime/events/wiring/streamWiring.d.ts +32 -0
- package/dist/runtime/events/wiring/streamWiring.js +79 -0
- package/dist/runtime/server/api/_flows/[name]/clear-history.delete.js +16 -5
- package/dist/runtime/server/api/_flows/[name]/runs/[runId]/cancel.post.js +21 -0
- package/dist/runtime/server/api/_flows/[name]/runs.get.d.ts +12 -2
- package/dist/runtime/server/api/_flows/[name]/runs.get.js +15 -4
- package/dist/runtime/server/api/_flows/[name]/schedule.post.js +11 -2
- package/dist/runtime/server/api/_flows/[name]/schedules/[id].delete.js +21 -16
- package/dist/runtime/server/api/_flows/[name]/schedules.get.js +21 -19
- package/dist/runtime/server/api/_flows/ws.js +43 -22
- package/dist/runtime/server/api/_queues/[name]/job/[id].get.js +8 -3
- package/dist/runtime/server/api/_queues/[name]/job/index.get.js +12 -3
- package/dist/runtime/server/api/_queues/index.get.js +66 -23
- package/dist/runtime/server/api/_queues/ws.js +14 -4
- package/dist/runtime/server/plugins/00.adapters.d.ts +14 -0
- package/dist/runtime/server/plugins/00.adapters.js +69 -0
- package/dist/runtime/server/plugins/02.workers.js +45 -0
- package/dist/runtime/tsconfig.json +8 -0
- package/dist/runtime/utils/adapters.d.ts +66 -0
- package/dist/runtime/utils/adapters.js +51 -0
- package/dist/runtime/utils/defineFunction.d.ts +10 -0
- package/dist/runtime/{server-utils/utils/defineQueueWorker.js → utils/defineFunction.js} +4 -4
- package/dist/runtime/{server-utils/utils/defineQueueConfig.d.ts → utils/defineFunctionConfig.d.ts} +3 -3
- package/dist/runtime/utils/defineFunctionConfig.js +2 -0
- package/dist/runtime/utils/registerAdapter.d.ts +59 -0
- package/dist/runtime/utils/registerAdapter.js +13 -0
- package/dist/runtime/utils/useFlowEngine.d.ts +19 -0
- package/dist/runtime/utils/useFlowEngine.js +108 -0
- package/dist/runtime/{server-utils/utils → utils}/useNventLogger.js +2 -2
- package/dist/runtime/utils/useStreamTopics.d.ts +72 -0
- package/dist/runtime/utils/useStreamTopics.js +47 -0
- package/dist/runtime/{server-utils/worker/runner/node.d.ts → worker/node/runner.d.ts} +18 -2
- package/dist/runtime/{server-utils/worker/runner/node.js → worker/node/runner.js} +44 -17
- package/dist/types.d.mts +2 -2
- package/package.json +14 -44
- package/LICENSE +0 -21
- package/README.md +0 -389
- package/dist/runtime/app/assets/vueflow.css +0 -1
- package/dist/runtime/app/components/ConfirmDialog.d.vue.ts +0 -33
- package/dist/runtime/app/components/ConfirmDialog.vue +0 -121
- package/dist/runtime/app/components/ConfirmDialog.vue.d.ts +0 -33
- package/dist/runtime/app/components/FlowDiagram.d.vue.ts +0 -64
- package/dist/runtime/app/components/FlowDiagram.vue +0 -338
- package/dist/runtime/app/components/FlowDiagram.vue.d.ts +0 -64
- package/dist/runtime/app/components/FlowNodeCard.d.vue.ts +0 -29
- package/dist/runtime/app/components/FlowNodeCard.vue +0 -156
- package/dist/runtime/app/components/FlowNodeCard.vue.d.ts +0 -29
- package/dist/runtime/app/components/FlowRunOverview.d.vue.ts +0 -9
- package/dist/runtime/app/components/FlowRunOverview.vue +0 -291
- package/dist/runtime/app/components/FlowRunOverview.vue.d.ts +0 -9
- package/dist/runtime/app/components/FlowRunStatusBadge.d.vue.ts +0 -14
- package/dist/runtime/app/components/FlowRunStatusBadge.vue +0 -60
- package/dist/runtime/app/components/FlowRunStatusBadge.vue.d.ts +0 -14
- package/dist/runtime/app/components/FlowRunTimeline.d.vue.ts +0 -12
- package/dist/runtime/app/components/FlowRunTimeline.vue +0 -127
- package/dist/runtime/app/components/FlowRunTimeline.vue.d.ts +0 -12
- package/dist/runtime/app/components/FlowScheduleDialog.d.vue.ts +0 -16
- package/dist/runtime/app/components/FlowScheduleDialog.vue +0 -226
- package/dist/runtime/app/components/FlowScheduleDialog.vue.d.ts +0 -16
- package/dist/runtime/app/components/FlowSchedulesList.d.vue.ts +0 -12
- package/dist/runtime/app/components/FlowSchedulesList.vue +0 -99
- package/dist/runtime/app/components/FlowSchedulesList.vue.d.ts +0 -12
- package/dist/runtime/app/components/JobScheduling.d.vue.ts +0 -6
- package/dist/runtime/app/components/JobScheduling.vue +0 -203
- package/dist/runtime/app/components/JobScheduling.vue.d.ts +0 -6
- package/dist/runtime/app/components/ListItem.d.vue.ts +0 -23
- package/dist/runtime/app/components/ListItem.vue +0 -70
- package/dist/runtime/app/components/ListItem.vue.d.ts +0 -23
- package/dist/runtime/app/components/QueueConfigDetails.d.vue.ts +0 -45
- package/dist/runtime/app/components/QueueConfigDetails.vue +0 -412
- package/dist/runtime/app/components/QueueConfigDetails.vue.d.ts +0 -45
- package/dist/runtime/app/components/StatCounter.d.vue.ts +0 -9
- package/dist/runtime/app/components/StatCounter.vue +0 -25
- package/dist/runtime/app/components/StatCounter.vue.d.ts +0 -9
- package/dist/runtime/app/components/TimelineList.d.vue.ts +0 -7
- package/dist/runtime/app/components/TimelineList.vue +0 -210
- package/dist/runtime/app/components/TimelineList.vue.d.ts +0 -7
- package/dist/runtime/app/components/nhealth/component-router.d.vue.ts +0 -46
- package/dist/runtime/app/components/nhealth/component-router.vue +0 -26
- package/dist/runtime/app/components/nhealth/component-router.vue.d.ts +0 -46
- package/dist/runtime/app/components/nhealth/component-shell.d.vue.ts +0 -24
- package/dist/runtime/app/components/nhealth/component-shell.vue +0 -89
- package/dist/runtime/app/components/nhealth/component-shell.vue.d.ts +0 -24
- package/dist/runtime/app/composables/useAnalyzedFlows.d.ts +0 -14
- package/dist/runtime/app/composables/useAnalyzedFlows.js +0 -8
- package/dist/runtime/app/composables/useComponentRouter.d.ts +0 -38
- package/dist/runtime/app/composables/useComponentRouter.js +0 -240
- package/dist/runtime/app/composables/useFlowRunTimeline.d.ts +0 -15
- package/dist/runtime/app/composables/useFlowRunTimeline.js +0 -66
- package/dist/runtime/app/composables/useFlowRuns.d.ts +0 -18
- package/dist/runtime/app/composables/useFlowRuns.js +0 -32
- package/dist/runtime/app/composables/useFlowRunsInfinite.d.ts +0 -24
- package/dist/runtime/app/composables/useFlowRunsInfinite.js +0 -123
- package/dist/runtime/app/composables/useFlowRunsPolling.d.ts +0 -9
- package/dist/runtime/app/composables/useFlowRunsPolling.js +0 -33
- package/dist/runtime/app/composables/useFlowState.d.ts +0 -125
- package/dist/runtime/app/composables/useFlowState.js +0 -211
- package/dist/runtime/app/composables/useFlowWebSocket.d.ts +0 -27
- package/dist/runtime/app/composables/useFlowWebSocket.js +0 -205
- package/dist/runtime/app/composables/useFlowsNavigation.d.ts +0 -10
- package/dist/runtime/app/composables/useFlowsNavigation.js +0 -58
- package/dist/runtime/app/composables/useQueueJobs.d.ts +0 -20
- package/dist/runtime/app/composables/useQueueJobs.js +0 -20
- package/dist/runtime/app/composables/useQueueUpdates.d.ts +0 -26
- package/dist/runtime/app/composables/useQueueUpdates.js +0 -122
- package/dist/runtime/app/composables/useQueues.d.ts +0 -44
- package/dist/runtime/app/composables/useQueues.js +0 -26
- package/dist/runtime/app/composables/useQueuesLive.d.ts +0 -19
- package/dist/runtime/app/composables/useQueuesLive.js +0 -143
- package/dist/runtime/app/pages/flows/index.d.vue.ts +0 -3
- package/dist/runtime/app/pages/flows/index.vue +0 -645
- package/dist/runtime/app/pages/flows/index.vue.d.ts +0 -3
- package/dist/runtime/app/pages/index.d.vue.ts +0 -3
- package/dist/runtime/app/pages/index.vue +0 -34
- package/dist/runtime/app/pages/index.vue.d.ts +0 -3
- package/dist/runtime/app/pages/queues/index.d.vue.ts +0 -3
- package/dist/runtime/app/pages/queues/index.vue +0 -229
- package/dist/runtime/app/pages/queues/index.vue.d.ts +0 -3
- package/dist/runtime/app/pages/queues/job.d.vue.ts +0 -3
- package/dist/runtime/app/pages/queues/job.vue +0 -262
- package/dist/runtime/app/pages/queues/job.vue.d.ts +0 -3
- package/dist/runtime/app/pages/queues/jobs.d.vue.ts +0 -3
- package/dist/runtime/app/pages/queues/jobs.vue +0 -291
- package/dist/runtime/app/pages/queues/jobs.vue.d.ts +0 -3
- package/dist/runtime/app/plugins/vueflow.client.d.ts +0 -2
- package/dist/runtime/app/plugins/vueflow.client.js +0 -11
- package/dist/runtime/constants.d.ts +0 -11
- package/dist/runtime/constants.js +0 -11
- package/dist/runtime/schema.d.ts +0 -37
- package/dist/runtime/schema.js +0 -20
- package/dist/runtime/server/plugins/00.event-store.d.ts +0 -13
- package/dist/runtime/server/plugins/00.event-store.js +0 -16
- package/dist/runtime/server/plugins/flow-management.d.ts +0 -13
- package/dist/runtime/server/plugins/flow-management.js +0 -65
- package/dist/runtime/server/plugins/queue-management.js +0 -27
- package/dist/runtime/server/plugins/state-cleanup.d.ts +0 -11
- package/dist/runtime/server/plugins/state-cleanup.js +0 -93
- package/dist/runtime/server/plugins/worker-management.js +0 -33
- package/dist/runtime/server/tsconfig.json +0 -3
- package/dist/runtime/server-utils/events/adapters/fileAdapter.d.ts +0 -2
- package/dist/runtime/server-utils/events/adapters/fileAdapter.js +0 -382
- package/dist/runtime/server-utils/events/adapters/memoryAdapter.d.ts +0 -2
- package/dist/runtime/server-utils/events/adapters/memoryAdapter.js +0 -171
- package/dist/runtime/server-utils/events/adapters/redis/redisAdapter.d.ts +0 -2
- package/dist/runtime/server-utils/events/adapters/redis/redisAdapter.js +0 -348
- package/dist/runtime/server-utils/events/adapters/redis/redisPubSubGateway.d.ts +0 -30
- package/dist/runtime/server-utils/events/adapters/redis/redisPubSubGateway.js +0 -82
- package/dist/runtime/server-utils/events/eventStoreFactory.d.ts +0 -19
- package/dist/runtime/server-utils/events/eventStoreFactory.js +0 -44
- package/dist/runtime/server-utils/events/streamNames.d.ts +0 -17
- package/dist/runtime/server-utils/events/streamNames.js +0 -17
- package/dist/runtime/server-utils/events/types.d.ts +0 -63
- package/dist/runtime/server-utils/events/wiring/registry.d.ts +0 -10
- package/dist/runtime/server-utils/events/wiring/registry.js +0 -24
- package/dist/runtime/server-utils/queue/adapters/bullmq.d.ts +0 -18
- package/dist/runtime/server-utils/queue/adapters/bullmq.js +0 -164
- package/dist/runtime/server-utils/queue/queueFactory.d.ts +0 -3
- package/dist/runtime/server-utils/queue/queueFactory.js +0 -10
- package/dist/runtime/server-utils/queue/types.d.ts +0 -47
- package/dist/runtime/server-utils/state/adapters/redis.d.ts +0 -2
- package/dist/runtime/server-utils/state/adapters/redis.js +0 -42
- package/dist/runtime/server-utils/state/stateFactory.d.ts +0 -3
- package/dist/runtime/server-utils/state/stateFactory.js +0 -17
- package/dist/runtime/server-utils/state/types.d.ts +0 -23
- package/dist/runtime/server-utils/utils/defineQueueConfig.js +0 -2
- package/dist/runtime/server-utils/utils/defineQueueWorker.d.ts +0 -10
- package/dist/runtime/server-utils/utils/useEventStore.d.ts +0 -20
- package/dist/runtime/server-utils/utils/useEventStore.js +0 -119
- package/dist/runtime/server-utils/utils/useFlowEngine.d.ts +0 -9
- package/dist/runtime/server-utils/utils/useFlowEngine.js +0 -44
- package/dist/runtime/server-utils/utils/useLogs.d.ts +0 -41
- package/dist/runtime/server-utils/utils/useLogs.js +0 -74
- package/dist/runtime/server-utils/utils/useQueue.d.ts +0 -31
- package/dist/runtime/server-utils/utils/useQueue.js +0 -24
- package/dist/runtime/server-utils/worker/adapter.d.ts +0 -4
- package/dist/runtime/server-utils/worker/adapter.js +0 -66
- package/dist/runtime/types.d.ts +0 -132
- /package/dist/runtime/{server-utils/events/types.js → adapters/interfaces/queue.js} +0 -0
- /package/dist/runtime/{server-utils/queue/types.js → adapters/interfaces/store.js} +0 -0
- /package/dist/runtime/{server-utils/state/types.js → adapters/interfaces/stream.js} +0 -0
- /package/dist/runtime/{server-utils/events → events}/eventBus.d.ts +0 -0
- /package/dist/runtime/{server-utils/events → events}/eventBus.js +0 -0
- /package/dist/runtime/server/{plugins/queue-management.d.ts → api/_flows/[name]/runs/[runId]/cancel.post.d.ts} +0 -0
- /package/dist/runtime/server/plugins/{00.ws-lifecycle.d.ts → 01.ws-lifecycle.d.ts} +0 -0
- /package/dist/runtime/server/plugins/{00.ws-lifecycle.js → 01.ws-lifecycle.js} +0 -0
- /package/dist/runtime/server/plugins/{worker-management.d.ts → 02.workers.d.ts} +0 -0
- /package/dist/runtime/{server-utils/utils → utils}/useEventManager.d.ts +0 -0
- /package/dist/runtime/{server-utils/utils → utils}/useEventManager.js +0 -0
- /package/dist/runtime/{server-utils/utils → utils}/useNventLogger.d.ts +0 -0
- /package/dist/runtime/{server-utils/utils → utils}/wsPeerManager.d.ts +0 -0
- /package/dist/runtime/{server-utils/utils → utils}/wsPeerManager.js +0 -0
- /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:
|
|
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(
|
|
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 {
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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(
|
|
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 =
|
|
183
|
-
const
|
|
184
|
-
const indexKey =
|
|
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
|
|
196
|
-
const
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
257
|
-
const indexKey =
|
|
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 === "
|
|
313
|
+
if (e.type === "flow.cancel") {
|
|
269
314
|
try {
|
|
270
|
-
|
|
271
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
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();
|