nvent 0.4.4 → 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.
- package/dist/module.d.mts +4 -185
- package/dist/module.json +3 -3
- package/dist/module.mjs +451 -257
- package/dist/runtime/adapters/base/index.d.ts +6 -0
- package/dist/runtime/adapters/base/index.js +1 -0
- package/dist/runtime/adapters/base/store-validator.d.ts +48 -0
- package/dist/runtime/adapters/base/store-validator.js +147 -0
- package/dist/runtime/adapters/builtin/file-queue.d.ts +67 -0
- package/dist/runtime/adapters/builtin/file-queue.js +499 -0
- package/dist/runtime/adapters/builtin/file-store.d.ts +32 -0
- package/dist/runtime/adapters/builtin/file-store.js +206 -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 +243 -0
- package/dist/runtime/adapters/builtin/memory-store.d.ts +68 -0
- package/dist/runtime/adapters/builtin/memory-store.js +333 -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 +134 -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 +297 -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 +175 -0
- package/dist/runtime/config/types.d.ts +397 -0
- package/dist/runtime/config/types.js +0 -0
- package/dist/runtime/{server-utils/events → events}/eventBus.d.ts +1 -1
- package/dist/runtime/events/types.d.ts +145 -0
- package/dist/runtime/events/types.js +0 -0
- package/dist/runtime/events/utils/scheduleTrigger.d.ts +8 -0
- package/dist/runtime/events/utils/scheduleTrigger.js +69 -0
- package/dist/runtime/events/utils/stallDetector.d.ts +140 -0
- package/dist/runtime/events/utils/stallDetector.js +436 -0
- package/dist/runtime/events/utils/triggerRuntime.d.ts +58 -0
- package/dist/runtime/events/utils/triggerRuntime.js +212 -0
- package/dist/runtime/{server-utils/events → events}/wiring/flowWiring.d.ts +12 -11
- package/dist/runtime/events/wiring/flowWiring.js +1020 -0
- package/dist/runtime/events/wiring/registry.d.ts +19 -0
- package/dist/runtime/events/wiring/registry.js +35 -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 +36 -0
- package/dist/runtime/events/wiring/streamWiring.js +156 -0
- package/dist/runtime/events/wiring/triggerWiring.d.ts +21 -0
- package/dist/runtime/events/wiring/triggerWiring.js +412 -0
- package/dist/runtime/nitro/plugins/00.adapters.d.ts +14 -0
- package/dist/runtime/nitro/plugins/00.adapters.js +73 -0
- package/dist/runtime/nitro/plugins/02.workers.js +63 -0
- package/dist/runtime/nitro/plugins/03.triggers.d.ts +12 -0
- package/dist/runtime/nitro/plugins/03.triggers.js +55 -0
- package/dist/runtime/nitro/routes/webhook.await.d.ts +23 -0
- package/dist/runtime/nitro/routes/webhook.await.js +90 -0
- package/dist/runtime/nitro/routes/webhook.trigger.d.ts +69 -0
- package/dist/runtime/nitro/routes/webhook.trigger.js +64 -0
- package/dist/runtime/nitro/utils/adapters.d.ts +66 -0
- package/dist/runtime/nitro/utils/adapters.js +51 -0
- package/dist/runtime/nitro/utils/awaitPatterns/event.d.ts +15 -0
- package/dist/runtime/nitro/utils/awaitPatterns/event.js +120 -0
- package/dist/runtime/nitro/utils/awaitPatterns/index.d.ts +28 -0
- package/dist/runtime/nitro/utils/awaitPatterns/index.js +55 -0
- package/dist/runtime/nitro/utils/awaitPatterns/schedule.d.ts +16 -0
- package/dist/runtime/nitro/utils/awaitPatterns/schedule.js +78 -0
- package/dist/runtime/nitro/utils/awaitPatterns/time.d.ts +15 -0
- package/dist/runtime/nitro/utils/awaitPatterns/time.js +67 -0
- package/dist/runtime/nitro/utils/awaitPatterns/webhook.d.ts +15 -0
- package/dist/runtime/nitro/utils/awaitPatterns/webhook.js +120 -0
- package/dist/runtime/nitro/utils/defineFunction.d.ts +10 -0
- package/dist/runtime/nitro/utils/defineFunction.js +17 -0
- package/dist/runtime/nitro/utils/defineFunctionConfig.d.ts +310 -0
- package/dist/runtime/nitro/utils/defineFunctionConfig.js +3 -0
- package/dist/runtime/nitro/utils/defineHooks.d.ts +41 -0
- package/dist/runtime/nitro/utils/defineHooks.js +6 -0
- package/dist/runtime/nitro/utils/registerAdapter.d.ts +59 -0
- package/dist/runtime/nitro/utils/registerAdapter.js +13 -0
- package/dist/runtime/nitro/utils/useAwait.d.ts +71 -0
- package/dist/runtime/nitro/utils/useAwait.js +139 -0
- package/dist/runtime/{server-utils → nitro}/utils/useEventManager.d.ts +2 -2
- package/dist/runtime/{server-utils → nitro}/utils/useEventManager.js +1 -1
- package/dist/runtime/nitro/utils/useFlow.d.ts +68 -0
- package/dist/runtime/nitro/utils/useFlow.js +226 -0
- package/dist/runtime/nitro/utils/useHookRegistry.d.ts +34 -0
- package/dist/runtime/nitro/utils/useHookRegistry.js +25 -0
- package/dist/runtime/{server-utils → nitro}/utils/useNventLogger.js +2 -2
- package/dist/runtime/nitro/utils/useRunContext.d.ts +6 -0
- package/dist/runtime/nitro/utils/useRunContext.js +102 -0
- package/dist/runtime/nitro/utils/useStreamTopics.d.ts +83 -0
- package/dist/runtime/nitro/utils/useStreamTopics.js +94 -0
- package/dist/runtime/nitro/utils/useTrigger.d.ts +150 -0
- package/dist/runtime/nitro/utils/useTrigger.js +320 -0
- package/dist/runtime/scheduler/index.d.ts +33 -0
- package/dist/runtime/scheduler/index.js +38 -0
- package/dist/runtime/scheduler/scheduler.d.ts +113 -0
- package/dist/runtime/scheduler/scheduler.js +623 -0
- package/dist/runtime/scheduler/types.d.ts +116 -0
- package/dist/runtime/scheduler/types.js +0 -0
- package/dist/runtime/tsconfig.json +8 -0
- package/dist/runtime/worker/node/runner.d.ts +53 -0
- package/dist/runtime/worker/node/runner.js +327 -0
- package/dist/types.d.mts +2 -2
- package/package.json +16 -46
- 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 -80
- package/dist/runtime/app/composables/useFlowRunTimeline.js +0 -68
- 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 -26
- 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 -45
- 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/api/_flows/[name]/clear-history.delete.d.ts +0 -10
- package/dist/runtime/server/api/_flows/[name]/clear-history.delete.js +0 -44
- package/dist/runtime/server/api/_flows/[name]/runs.get.d.ts +0 -7
- package/dist/runtime/server/api/_flows/[name]/runs.get.js +0 -53
- package/dist/runtime/server/api/_flows/[name]/schedule.post.js +0 -57
- package/dist/runtime/server/api/_flows/[name]/schedules/[id].delete.d.ts +0 -2
- package/dist/runtime/server/api/_flows/[name]/schedules/[id].delete.js +0 -42
- package/dist/runtime/server/api/_flows/[name]/schedules.get.d.ts +0 -2
- package/dist/runtime/server/api/_flows/[name]/schedules.get.js +0 -48
- package/dist/runtime/server/api/_flows/[name]/start.post.d.ts +0 -2
- package/dist/runtime/server/api/_flows/[name]/start.post.js +0 -9
- package/dist/runtime/server/api/_flows/index.get.d.ts +0 -6
- package/dist/runtime/server/api/_flows/index.get.js +0 -5
- package/dist/runtime/server/api/_flows/ws.d.ts +0 -60
- package/dist/runtime/server/api/_flows/ws.js +0 -188
- package/dist/runtime/server/api/_queues/[name]/job/[id].get.d.ts +0 -2
- package/dist/runtime/server/api/_queues/[name]/job/[id].get.js +0 -9
- package/dist/runtime/server/api/_queues/[name]/job/index.get.d.ts +0 -2
- package/dist/runtime/server/api/_queues/[name]/job/index.get.js +0 -18
- package/dist/runtime/server/api/_queues/index.get.d.ts +0 -2
- package/dist/runtime/server/api/_queues/index.get.js +0 -63
- package/dist/runtime/server/api/_queues/ws.d.ts +0 -48
- package/dist/runtime/server/api/_queues/ws.js +0 -205
- 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.d.ts +0 -2
- 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.d.ts +0 -2
- 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/flowWiring.js +0 -409
- 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.d.ts +0 -154
- 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/defineQueueWorker.js +0 -17
- 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/server-utils/worker/runner/node.d.ts +0 -27
- package/dist/runtime/server-utils/worker/runner/node.js +0 -196
- 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.js +0 -0
- /package/dist/runtime/{server/plugins/00.ws-lifecycle.d.ts → nitro/plugins/01.ws-lifecycle.d.ts} +0 -0
- /package/dist/runtime/{server/plugins/00.ws-lifecycle.js → nitro/plugins/01.ws-lifecycle.js} +0 -0
- /package/dist/runtime/{server/api/_flows/[name]/schedule.post.d.ts → nitro/plugins/02.workers.d.ts} +0 -0
- /package/dist/runtime/{server-utils → nitro}/utils/useNventLogger.d.ts +0 -0
- /package/dist/runtime/{server-utils → nitro}/utils/wsPeerManager.d.ts +0 -0
- /package/dist/runtime/{server-utils → nitro}/utils/wsPeerManager.js +0 -0
- /package/dist/runtime/{python → worker/python}/get_config.py +0 -0
|
@@ -0,0 +1,623 @@
|
|
|
1
|
+
import { CronJob } from "cron";
|
|
2
|
+
import { getEventBus } from "../events/eventBus.js";
|
|
3
|
+
import { resolveTimeAwait } from "../nitro/utils/awaitPatterns/time.js";
|
|
4
|
+
import { resolveScheduleAwait } from "../nitro/utils/awaitPatterns/schedule.js";
|
|
5
|
+
import { useNventLogger } from "#imports";
|
|
6
|
+
export class Scheduler {
|
|
7
|
+
store;
|
|
8
|
+
keyPrefix;
|
|
9
|
+
lockTTL;
|
|
10
|
+
instanceId;
|
|
11
|
+
useIndexLocking;
|
|
12
|
+
jobs = /* @__PURE__ */ new Map();
|
|
13
|
+
jobConfigs = /* @__PURE__ */ new Map();
|
|
14
|
+
lockRenewalTimers = /* @__PURE__ */ new Map();
|
|
15
|
+
started = false;
|
|
16
|
+
logger = useNventLogger("scheduler");
|
|
17
|
+
constructor(options) {
|
|
18
|
+
this.store = options.store;
|
|
19
|
+
this.keyPrefix = options.keyPrefix || "nvent:scheduler";
|
|
20
|
+
this.lockTTL = options.lockTTL || 3e5;
|
|
21
|
+
this.instanceId = options.instanceId || `instance-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
22
|
+
this.useIndexLocking = options.useIndexLocking !== false;
|
|
23
|
+
}
|
|
24
|
+
async schedule(job) {
|
|
25
|
+
this.jobConfigs.set(job.id, job);
|
|
26
|
+
await this.persistJob(job);
|
|
27
|
+
if (job.type === "cron" && job.cron) {
|
|
28
|
+
const cronJob = new CronJob(
|
|
29
|
+
job.cron,
|
|
30
|
+
async () => {
|
|
31
|
+
await this.executeWithLock(job);
|
|
32
|
+
},
|
|
33
|
+
null,
|
|
34
|
+
job.enabled !== false && this.started,
|
|
35
|
+
job.timezone || "UTC"
|
|
36
|
+
);
|
|
37
|
+
this.jobs.set(job.id, cronJob);
|
|
38
|
+
const nextDate = cronJob.nextDate();
|
|
39
|
+
if (nextDate) {
|
|
40
|
+
job.nextRun = nextDate.toMillis();
|
|
41
|
+
await this.updateJobStats(job.id, { nextRun: job.nextRun });
|
|
42
|
+
}
|
|
43
|
+
return job.id;
|
|
44
|
+
}
|
|
45
|
+
if (job.type === "interval" && job.interval) {
|
|
46
|
+
if (job.enabled !== false && this.started) {
|
|
47
|
+
const intervalId = setInterval(
|
|
48
|
+
async () => {
|
|
49
|
+
await this.executeWithLock(job);
|
|
50
|
+
},
|
|
51
|
+
job.interval
|
|
52
|
+
);
|
|
53
|
+
this.jobs.set(job.id, intervalId);
|
|
54
|
+
}
|
|
55
|
+
job.nextRun = Date.now() + job.interval;
|
|
56
|
+
await this.updateJobStats(job.id, { nextRun: job.nextRun });
|
|
57
|
+
return job.id;
|
|
58
|
+
}
|
|
59
|
+
if (job.type === "one-time" && job.executeAt) {
|
|
60
|
+
const delay = job.executeAt - Date.now();
|
|
61
|
+
if (delay > 0) {
|
|
62
|
+
const timeoutId = setTimeout(
|
|
63
|
+
async () => {
|
|
64
|
+
await this.executeWithLock(job);
|
|
65
|
+
await this.unschedule(job.id);
|
|
66
|
+
},
|
|
67
|
+
delay
|
|
68
|
+
);
|
|
69
|
+
this.jobs.set(job.id, timeoutId);
|
|
70
|
+
job.nextRun = job.executeAt;
|
|
71
|
+
await this.updateJobStats(job.id, { nextRun: job.nextRun });
|
|
72
|
+
} else {
|
|
73
|
+
await this.executeWithLock(job);
|
|
74
|
+
}
|
|
75
|
+
return job.id;
|
|
76
|
+
}
|
|
77
|
+
throw new Error(`Invalid job configuration: ${job.id}`);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Execute job with distributed lock
|
|
81
|
+
* Prevents multiple instances from running the same job
|
|
82
|
+
*/
|
|
83
|
+
async executeWithLock(job) {
|
|
84
|
+
const lockAcquired = await this.acquireLock(job.id);
|
|
85
|
+
if (!lockAcquired) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
this.startLockRenewal(job.id);
|
|
90
|
+
await this.executeJob(job);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
this.logger.error("Job failed", { jobId: job.id, error: error.message });
|
|
93
|
+
} finally {
|
|
94
|
+
this.stopLockRenewal(job.id);
|
|
95
|
+
await this.releaseLock(job.id).catch((err) => {
|
|
96
|
+
this.logger.error("Failed to release lock", { jobId: job.id, error: err.message });
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async executeJob(job) {
|
|
101
|
+
const config = this.jobConfigs.get(job.id);
|
|
102
|
+
if (!config) return;
|
|
103
|
+
try {
|
|
104
|
+
const now = Date.now();
|
|
105
|
+
config.lastRun = now;
|
|
106
|
+
config.runCount = (config.runCount || 0) + 1;
|
|
107
|
+
await job.handler();
|
|
108
|
+
if (job.type === "cron" && job.cron) {
|
|
109
|
+
const cronJob = this.jobs.get(job.id);
|
|
110
|
+
if (cronJob instanceof CronJob) {
|
|
111
|
+
const nextDate = cronJob.nextDate();
|
|
112
|
+
if (nextDate) {
|
|
113
|
+
config.nextRun = nextDate.toMillis();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
} else if (job.type === "interval" && job.interval) {
|
|
117
|
+
config.nextRun = Date.now() + job.interval;
|
|
118
|
+
}
|
|
119
|
+
await this.updateJobStats(job.id, {
|
|
120
|
+
lastRun: now,
|
|
121
|
+
nextRun: config.nextRun,
|
|
122
|
+
runCount: config.runCount
|
|
123
|
+
});
|
|
124
|
+
} catch (error) {
|
|
125
|
+
config.failCount = (config.failCount || 0) + 1;
|
|
126
|
+
await this.updateJobStats(job.id, {
|
|
127
|
+
failCount: config.failCount,
|
|
128
|
+
lastError: error instanceof Error ? error.message : String(error)
|
|
129
|
+
});
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Acquire distributed lock using store adapter
|
|
135
|
+
*/
|
|
136
|
+
async acquireLock(jobId) {
|
|
137
|
+
const lockKey = `${this.keyPrefix}:lock:${jobId}`;
|
|
138
|
+
const now = Date.now();
|
|
139
|
+
const expiresAt = now + this.lockTTL;
|
|
140
|
+
if (this.useIndexLocking && this.store.index.add) {
|
|
141
|
+
try {
|
|
142
|
+
const lockIndex = `${this.keyPrefix}:locks`;
|
|
143
|
+
await this.store.index.add(lockIndex, jobId, expiresAt, {
|
|
144
|
+
instanceId: this.instanceId,
|
|
145
|
+
acquiredAt: now,
|
|
146
|
+
expiresAt
|
|
147
|
+
});
|
|
148
|
+
return true;
|
|
149
|
+
} catch {
|
|
150
|
+
if (this.store.index.get) {
|
|
151
|
+
const existing = await this.store.index.get(`${this.keyPrefix}:locks`, jobId);
|
|
152
|
+
if (existing && existing.score < now) {
|
|
153
|
+
if (this.store.index.delete) {
|
|
154
|
+
await this.store.index.delete(`${this.keyPrefix}:locks`, jobId);
|
|
155
|
+
return this.acquireLock(jobId);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
try {
|
|
163
|
+
const existingLock = await this.store.kv.get(lockKey);
|
|
164
|
+
if (existingLock) {
|
|
165
|
+
if (existingLock.expiresAt < now) {
|
|
166
|
+
await this.store.kv.delete(lockKey);
|
|
167
|
+
return this.acquireLock(jobId);
|
|
168
|
+
}
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
const lock = {
|
|
172
|
+
jobId,
|
|
173
|
+
instanceId: this.instanceId,
|
|
174
|
+
acquiredAt: now,
|
|
175
|
+
expiresAt
|
|
176
|
+
};
|
|
177
|
+
await this.store.kv.set(lockKey, lock, Math.ceil(this.lockTTL / 1e3));
|
|
178
|
+
return true;
|
|
179
|
+
} catch (error) {
|
|
180
|
+
this.logger.error("Error acquiring lock", { jobId, error: error.message });
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Release distributed lock
|
|
187
|
+
*/
|
|
188
|
+
async releaseLock(jobId) {
|
|
189
|
+
if (this.useIndexLocking && this.store.index.delete) {
|
|
190
|
+
const lockIndex = `${this.keyPrefix}:locks`;
|
|
191
|
+
await this.store.index.delete(lockIndex, jobId);
|
|
192
|
+
} else {
|
|
193
|
+
const lockKey = `${this.keyPrefix}:lock:${jobId}`;
|
|
194
|
+
await this.store.kv.delete(lockKey);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Start periodic lock renewal (for long-running jobs)
|
|
199
|
+
*/
|
|
200
|
+
startLockRenewal(jobId) {
|
|
201
|
+
const renewalInterval = this.lockTTL / 2;
|
|
202
|
+
const timer = setInterval(async () => {
|
|
203
|
+
try {
|
|
204
|
+
if (this.useIndexLocking && this.store.index.update) {
|
|
205
|
+
const lockIndex = `${this.keyPrefix}:locks`;
|
|
206
|
+
const expiresAt = Date.now() + this.lockTTL;
|
|
207
|
+
await this.store.index.update(lockIndex, jobId, {
|
|
208
|
+
expiresAt
|
|
209
|
+
});
|
|
210
|
+
} else {
|
|
211
|
+
const lockKey = `${this.keyPrefix}:lock:${jobId}`;
|
|
212
|
+
const lock = await this.store.kv.get(lockKey);
|
|
213
|
+
if (lock && lock.instanceId === this.instanceId) {
|
|
214
|
+
lock.expiresAt = Date.now() + this.lockTTL;
|
|
215
|
+
await this.store.kv.set(lockKey, lock, Math.ceil(this.lockTTL / 1e3));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
} catch (error) {
|
|
219
|
+
this.logger.error("Error renewing lock", { jobId, error: error.message });
|
|
220
|
+
}
|
|
221
|
+
}, renewalInterval);
|
|
222
|
+
this.lockRenewalTimers.set(jobId, timer);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Stop lock renewal
|
|
226
|
+
*/
|
|
227
|
+
stopLockRenewal(jobId) {
|
|
228
|
+
const timer = this.lockRenewalTimers.get(jobId);
|
|
229
|
+
if (timer) {
|
|
230
|
+
clearInterval(timer);
|
|
231
|
+
this.lockRenewalTimers.delete(jobId);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Persist job configuration
|
|
236
|
+
*/
|
|
237
|
+
async persistJob(job) {
|
|
238
|
+
const jobKey = `${this.keyPrefix}:jobs:${job.id}`;
|
|
239
|
+
const now = Date.now();
|
|
240
|
+
const jobData = {
|
|
241
|
+
id: job.id,
|
|
242
|
+
name: job.name,
|
|
243
|
+
type: job.type,
|
|
244
|
+
cron: job.cron,
|
|
245
|
+
interval: job.interval,
|
|
246
|
+
executeAt: job.executeAt,
|
|
247
|
+
timezone: job.timezone || "UTC",
|
|
248
|
+
enabled: job.enabled !== false,
|
|
249
|
+
metadata: job.metadata,
|
|
250
|
+
persistedAt: now
|
|
251
|
+
};
|
|
252
|
+
await this.store.kv.set(jobKey, jobData);
|
|
253
|
+
if (this.store.index.add) {
|
|
254
|
+
try {
|
|
255
|
+
const jobIndex = `${this.keyPrefix}:jobs`;
|
|
256
|
+
await this.store.index.add(jobIndex, job.id, now, jobData);
|
|
257
|
+
} catch {
|
|
258
|
+
if (this.store.index.update) {
|
|
259
|
+
try {
|
|
260
|
+
const jobIndex = `${this.keyPrefix}:jobs`;
|
|
261
|
+
await this.store.index.update(jobIndex, job.id, jobData);
|
|
262
|
+
} catch (updateError) {
|
|
263
|
+
this.logger.error("Failed to persist job to index", { jobId: job.id, error: updateError.message });
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Update job statistics
|
|
271
|
+
*/
|
|
272
|
+
async updateJobStats(jobId, stats) {
|
|
273
|
+
const statsKey = `${this.keyPrefix}:stats:${jobId}`;
|
|
274
|
+
try {
|
|
275
|
+
const existing = await this.store.kv.get(statsKey) || {};
|
|
276
|
+
await this.store.kv.set(statsKey, {
|
|
277
|
+
...existing,
|
|
278
|
+
...stats
|
|
279
|
+
});
|
|
280
|
+
} catch (error) {
|
|
281
|
+
this.logger.error("Error updating stats", {
|
|
282
|
+
jobId,
|
|
283
|
+
error: error.message
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
async unschedule(jobId) {
|
|
288
|
+
const job = this.jobs.get(jobId);
|
|
289
|
+
if (job instanceof CronJob) {
|
|
290
|
+
job.stop();
|
|
291
|
+
} else if (job) {
|
|
292
|
+
clearTimeout(job);
|
|
293
|
+
clearInterval(job);
|
|
294
|
+
}
|
|
295
|
+
this.jobs.delete(jobId);
|
|
296
|
+
this.jobConfigs.delete(jobId);
|
|
297
|
+
await this.store.kv.delete(`${this.keyPrefix}:jobs:${jobId}`);
|
|
298
|
+
await this.store.kv.delete(`${this.keyPrefix}:stats:${jobId}`);
|
|
299
|
+
if (this.store.index.delete) {
|
|
300
|
+
try {
|
|
301
|
+
await this.store.index.delete(`${this.keyPrefix}:jobs`, jobId);
|
|
302
|
+
} catch {
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
await this.releaseLock(jobId);
|
|
306
|
+
return !!job;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Recover jobs from store on startup
|
|
310
|
+
* Critical for:
|
|
311
|
+
* - Restarts: Re-create in-memory schedulers
|
|
312
|
+
* - Horizontal scaling: New instances pick up existing jobs
|
|
313
|
+
* - Orphaned awaits: Resume flows waiting on schedules
|
|
314
|
+
*/
|
|
315
|
+
async recoverJobs() {
|
|
316
|
+
this.logger.info("Recovering jobs from store");
|
|
317
|
+
try {
|
|
318
|
+
if (this.store.index.read) {
|
|
319
|
+
const jobIndex = `${this.keyPrefix}:jobs`;
|
|
320
|
+
const jobEntries = await this.store.index.read(jobIndex, { limit: 1e4 });
|
|
321
|
+
this.logger.info("Found jobs in index", { count: jobEntries.length });
|
|
322
|
+
for (const entry of jobEntries) {
|
|
323
|
+
const jobData = entry.metadata;
|
|
324
|
+
if (jobData) {
|
|
325
|
+
await this.recoverJob(jobData);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
} else {
|
|
329
|
+
this.logger.warn("Store does not support index read. Job recovery limited.");
|
|
330
|
+
this.logger.warn("For full job recovery, use Redis or Postgres store adapter.");
|
|
331
|
+
await this.recoverWellKnownJobs();
|
|
332
|
+
}
|
|
333
|
+
} catch (error) {
|
|
334
|
+
this.logger.error("Error during job recovery", { error: error.message });
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Recover a single job from persisted data
|
|
339
|
+
*
|
|
340
|
+
* IMPORTANT: This method reconstructs in-memory schedulers (CronJob/setInterval)
|
|
341
|
+
* WITHOUT calling schedule() to avoid re-persisting handler functions (which
|
|
342
|
+
* cannot be serialized). Handlers are reconstructed from metadata where possible.
|
|
343
|
+
*/
|
|
344
|
+
async recoverJob(jobData) {
|
|
345
|
+
try {
|
|
346
|
+
if (this.jobs.has(jobData.id)) {
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
if (jobData.enabled === false) {
|
|
350
|
+
this.logger.debug("Skipping disabled job", { jobId: jobData.id });
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
if (jobData.metadata?.type === "schedule-trigger" && jobData.metadata?.triggerName) {
|
|
354
|
+
jobData.handler = async () => {
|
|
355
|
+
const logger = useNventLogger("scheduler");
|
|
356
|
+
const eventBus = getEventBus();
|
|
357
|
+
const triggerName = jobData.metadata?.triggerName;
|
|
358
|
+
const scheduleConfig = jobData.metadata?.scheduleConfig;
|
|
359
|
+
logger.debug("Schedule trigger fired", { trigger: triggerName });
|
|
360
|
+
await eventBus.publish({
|
|
361
|
+
type: "trigger.fired",
|
|
362
|
+
triggerName,
|
|
363
|
+
data: {
|
|
364
|
+
scheduledAt: Date.now(),
|
|
365
|
+
timezone: scheduleConfig?.timezone || "UTC"
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
};
|
|
369
|
+
} else if (jobData.metadata?.component === "await-pattern") {
|
|
370
|
+
const { awaitType, runId, stepName, flowName, position } = jobData.metadata;
|
|
371
|
+
if (awaitType === "time") {
|
|
372
|
+
jobData.handler = async () => {
|
|
373
|
+
await resolveTimeAwait(runId, stepName, flowName, position, { delayCompleted: true });
|
|
374
|
+
};
|
|
375
|
+
} else if (awaitType === "schedule") {
|
|
376
|
+
jobData.handler = async () => {
|
|
377
|
+
await resolveScheduleAwait(runId, stepName, flowName, position, { scheduledAt: Date.now() });
|
|
378
|
+
};
|
|
379
|
+
} else if (awaitType === "webhook") {
|
|
380
|
+
jobData.handler = async () => {
|
|
381
|
+
const eventBus = getEventBus();
|
|
382
|
+
const timeout = jobData.metadata?.timeout;
|
|
383
|
+
const timeoutAction = jobData.metadata?.timeoutAction || "fail";
|
|
384
|
+
this.logger.warn("Webhook await timeout", {
|
|
385
|
+
runId,
|
|
386
|
+
stepName,
|
|
387
|
+
flowName,
|
|
388
|
+
timeout,
|
|
389
|
+
timeoutAction
|
|
390
|
+
});
|
|
391
|
+
eventBus.publish({
|
|
392
|
+
type: "await.timeout",
|
|
393
|
+
flowName,
|
|
394
|
+
runId,
|
|
395
|
+
stepName,
|
|
396
|
+
position,
|
|
397
|
+
awaitType: "webhook",
|
|
398
|
+
timeoutAction,
|
|
399
|
+
data: {
|
|
400
|
+
timeout,
|
|
401
|
+
registeredAt: Date.now() - (timeout || 0),
|
|
402
|
+
timedOutAt: Date.now()
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
};
|
|
406
|
+
} else if (awaitType === "event") {
|
|
407
|
+
jobData.handler = async () => {
|
|
408
|
+
const eventBus = getEventBus();
|
|
409
|
+
const timeout = jobData.metadata?.timeout;
|
|
410
|
+
const timeoutAction = jobData.metadata?.timeoutAction || "fail";
|
|
411
|
+
const eventName = jobData.metadata?.eventName;
|
|
412
|
+
this.logger.warn("Event await timeout", {
|
|
413
|
+
runId,
|
|
414
|
+
stepName,
|
|
415
|
+
flowName,
|
|
416
|
+
eventName,
|
|
417
|
+
timeout,
|
|
418
|
+
timeoutAction
|
|
419
|
+
});
|
|
420
|
+
eventBus.publish({
|
|
421
|
+
type: "await.timeout",
|
|
422
|
+
flowName,
|
|
423
|
+
runId,
|
|
424
|
+
stepName,
|
|
425
|
+
position,
|
|
426
|
+
awaitType: "event",
|
|
427
|
+
timeoutAction,
|
|
428
|
+
data: {
|
|
429
|
+
eventName,
|
|
430
|
+
timeout,
|
|
431
|
+
registeredAt: Date.now() - (timeout || 0),
|
|
432
|
+
timedOutAt: Date.now()
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
};
|
|
436
|
+
} else {
|
|
437
|
+
this.logger.warn("Cannot reconstruct await pattern", { awaitType, jobId: jobData.id });
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
this.logger.info("Reconstructed await pattern handler", {
|
|
441
|
+
awaitType,
|
|
442
|
+
flowName,
|
|
443
|
+
runId
|
|
444
|
+
});
|
|
445
|
+
} else if (!jobData.handler) {
|
|
446
|
+
this.logger.debug("Skipping job - no handler available, waiting for re-registration", {
|
|
447
|
+
jobId: jobData.id
|
|
448
|
+
});
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
this.jobConfigs.set(jobData.id, jobData);
|
|
452
|
+
if (jobData.type === "cron" && jobData.cron) {
|
|
453
|
+
const cronJob = new CronJob(
|
|
454
|
+
jobData.cron,
|
|
455
|
+
async () => {
|
|
456
|
+
await this.executeWithLock(jobData);
|
|
457
|
+
},
|
|
458
|
+
null,
|
|
459
|
+
true,
|
|
460
|
+
// Start immediately since scheduler.started is already true
|
|
461
|
+
jobData.timezone || "UTC"
|
|
462
|
+
);
|
|
463
|
+
this.jobs.set(jobData.id, cronJob);
|
|
464
|
+
this.logger.info("Recovered cron job", { jobId: jobData.id });
|
|
465
|
+
} else if (jobData.type === "interval" && jobData.interval) {
|
|
466
|
+
const intervalId = setInterval(
|
|
467
|
+
async () => {
|
|
468
|
+
await this.executeWithLock(jobData);
|
|
469
|
+
},
|
|
470
|
+
jobData.interval
|
|
471
|
+
);
|
|
472
|
+
this.jobs.set(jobData.id, intervalId);
|
|
473
|
+
this.logger.info("Recovered interval job", { jobId: jobData.id });
|
|
474
|
+
} else if (jobData.type === "one-time" && jobData.executeAt) {
|
|
475
|
+
const delay = jobData.executeAt - Date.now();
|
|
476
|
+
const isAwaitPattern = jobData.metadata?.component === "await-pattern";
|
|
477
|
+
if (delay > 0) {
|
|
478
|
+
const timeoutId = setTimeout(
|
|
479
|
+
async () => {
|
|
480
|
+
await this.executeWithLock(jobData);
|
|
481
|
+
await this.unschedule(jobData.id);
|
|
482
|
+
},
|
|
483
|
+
delay
|
|
484
|
+
);
|
|
485
|
+
this.jobs.set(jobData.id, timeoutId);
|
|
486
|
+
this.logger.info("Recovered one-time job", { jobId: jobData.id });
|
|
487
|
+
} else if (isAwaitPattern) {
|
|
488
|
+
this.logger.info("Executing overdue await pattern immediately", {
|
|
489
|
+
jobId: jobData.id,
|
|
490
|
+
awaitType: jobData.metadata?.awaitType,
|
|
491
|
+
flowName: jobData.metadata?.flowName
|
|
492
|
+
});
|
|
493
|
+
setImmediate(async () => {
|
|
494
|
+
try {
|
|
495
|
+
await jobData.handler();
|
|
496
|
+
await this.unschedule(jobData.id);
|
|
497
|
+
} catch (error) {
|
|
498
|
+
this.logger.error("Failed to execute overdue await pattern", {
|
|
499
|
+
jobId: jobData.id,
|
|
500
|
+
error: error.message
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
} else {
|
|
505
|
+
this.logger.debug("Skipping expired one-time job", { jobId: jobData.id });
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
} catch (error) {
|
|
509
|
+
this.logger.error("Failed to recover job", {
|
|
510
|
+
jobId: jobData.id,
|
|
511
|
+
error: error.message
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Recover well-known job patterns when index scan is not available
|
|
517
|
+
* This includes:
|
|
518
|
+
* - stall-detection (always exists)
|
|
519
|
+
* - await-time-* (flows waiting on time delays)
|
|
520
|
+
* - await-schedule-* (flows waiting on cron schedules)
|
|
521
|
+
*/
|
|
522
|
+
async recoverWellKnownJobs() {
|
|
523
|
+
const knownPatterns = [
|
|
524
|
+
"stall-detection"
|
|
525
|
+
// Note: await-* jobs have dynamic IDs, we can't easily recover them without scanning
|
|
526
|
+
// This is a limitation of KV-only stores
|
|
527
|
+
];
|
|
528
|
+
for (const jobId of knownPatterns) {
|
|
529
|
+
try {
|
|
530
|
+
const jobKey = `${this.keyPrefix}:jobs:${jobId}`;
|
|
531
|
+
const jobData = await this.store.kv.get(jobKey);
|
|
532
|
+
if (jobData) {
|
|
533
|
+
await this.recoverJob(jobData);
|
|
534
|
+
}
|
|
535
|
+
} catch {
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
this.logger.info("Recovered well-known jobs. Await patterns may need manual recovery.");
|
|
539
|
+
}
|
|
540
|
+
async start() {
|
|
541
|
+
if (this.started) return;
|
|
542
|
+
this.started = true;
|
|
543
|
+
await this.recoverJobs();
|
|
544
|
+
this.logger.info("Started with active jobs", { count: this.jobs.size });
|
|
545
|
+
}
|
|
546
|
+
async stop() {
|
|
547
|
+
this.started = false;
|
|
548
|
+
for (const job of this.jobs.values()) {
|
|
549
|
+
if (job instanceof CronJob) {
|
|
550
|
+
job.stop();
|
|
551
|
+
} else {
|
|
552
|
+
clearTimeout(job);
|
|
553
|
+
clearInterval(job);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
for (const timer of this.lockRenewalTimers.values()) {
|
|
557
|
+
clearInterval(timer);
|
|
558
|
+
}
|
|
559
|
+
this.lockRenewalTimers.clear();
|
|
560
|
+
if (this.useIndexLocking && this.store.index.read) {
|
|
561
|
+
const lockIndex = `${this.keyPrefix}:locks`;
|
|
562
|
+
const locks = await this.store.index.read(lockIndex, { limit: 1e3 });
|
|
563
|
+
for (const lock of locks) {
|
|
564
|
+
if (lock.metadata?.instanceId === this.instanceId) {
|
|
565
|
+
await this.releaseLock(lock.id);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
this.jobs.clear();
|
|
570
|
+
this.jobConfigs.clear();
|
|
571
|
+
}
|
|
572
|
+
async getScheduledJobs() {
|
|
573
|
+
return Array.from(this.jobConfigs.values());
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Get all persisted jobs from store (for debugging/monitoring)
|
|
577
|
+
* This shows ALL jobs across ALL instances with their runtime stats
|
|
578
|
+
*/
|
|
579
|
+
async getAllPersistedJobs() {
|
|
580
|
+
const jobs = [];
|
|
581
|
+
try {
|
|
582
|
+
if (this.store.index.read) {
|
|
583
|
+
const jobIndex = `${this.keyPrefix}:jobs`;
|
|
584
|
+
const entries = await this.store.index.read(jobIndex, { limit: 1e4 });
|
|
585
|
+
for (const entry of entries) {
|
|
586
|
+
if (entry.metadata) {
|
|
587
|
+
const jobConfig = entry.metadata;
|
|
588
|
+
const statsKey = `${this.keyPrefix}:stats:${jobConfig.id}`;
|
|
589
|
+
try {
|
|
590
|
+
const stats = await this.store.kv.get(statsKey);
|
|
591
|
+
if (stats) {
|
|
592
|
+
jobs.push({
|
|
593
|
+
...jobConfig,
|
|
594
|
+
lastRun: stats.lastRun,
|
|
595
|
+
nextRun: stats.nextRun,
|
|
596
|
+
runCount: stats.runCount,
|
|
597
|
+
failCount: stats.failCount
|
|
598
|
+
});
|
|
599
|
+
} else {
|
|
600
|
+
jobs.push(jobConfig);
|
|
601
|
+
}
|
|
602
|
+
} catch {
|
|
603
|
+
jobs.push(jobConfig);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
} else {
|
|
608
|
+
this.logger.warn("Cannot list all persisted jobs without index support");
|
|
609
|
+
}
|
|
610
|
+
} catch (error) {
|
|
611
|
+
this.logger.error("Error getting persisted jobs", { error: error.message });
|
|
612
|
+
}
|
|
613
|
+
return jobs;
|
|
614
|
+
}
|
|
615
|
+
async isHealthy() {
|
|
616
|
+
try {
|
|
617
|
+
await this.store.kv.get("__health_check__");
|
|
618
|
+
return true;
|
|
619
|
+
} catch {
|
|
620
|
+
return false;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|