bunqueue 2.8.59 → 2.9.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/README.md +109 -77
- package/dist/application/background/dlq.js +2 -0
- package/dist/application/contextFactory.js +5 -0
- package/dist/application/dlqManager.d.ts +11 -1
- package/dist/application/dlqManager.js +48 -10
- package/dist/application/dlqRetry.js +59 -38
- package/dist/application/eventsManager.d.ts +1 -1
- package/dist/application/eventsManager.js +30 -26
- package/dist/application/lockManager.js +1 -1
- package/dist/application/operations/jobManagement.d.ts +4 -0
- package/dist/application/operations/jobMoveOperations.js +9 -4
- package/dist/application/postgres-queue-manager/batchSnapshot.d.ts +16 -0
- package/dist/application/postgres-queue-manager/batchSnapshot.js +17 -0
- package/dist/application/postgres-queue-manager/cloud.d.ts +30 -0
- package/dist/application/postgres-queue-manager/cloud.js +55 -0
- package/dist/application/postgres-queue-manager/config.d.ts +5 -0
- package/dist/application/postgres-queue-manager/config.js +1 -0
- package/dist/application/postgres-queue-manager/control.d.ts +26 -0
- package/dist/application/postgres-queue-manager/control.js +186 -0
- package/dist/application/postgres-queue-manager/deferredWrites.d.ts +13 -0
- package/dist/application/postgres-queue-manager/deferredWrites.js +60 -0
- package/dist/application/postgres-queue-manager/delivery.d.ts +19 -0
- package/dist/application/postgres-queue-manager/delivery.js +80 -0
- package/dist/application/postgres-queue-manager/lease.d.ts +13 -0
- package/dist/application/postgres-queue-manager/lease.js +88 -0
- package/dist/application/postgres-queue-manager/lifetimeMetrics.d.ts +32 -0
- package/dist/application/postgres-queue-manager/lifetimeMetrics.js +53 -0
- package/dist/application/postgres-queue-manager/maintenance.d.ts +14 -0
- package/dist/application/postgres-queue-manager/maintenance.js +100 -0
- package/dist/application/postgres-queue-manager/operationGate.d.ts +26 -0
- package/dist/application/postgres-queue-manager/operationGate.js +96 -0
- package/dist/application/postgres-queue-manager/operations.d.ts +25 -0
- package/dist/application/postgres-queue-manager/operations.js +194 -0
- package/dist/application/postgres-queue-manager/projectionRefreshes.d.ts +43 -0
- package/dist/application/postgres-queue-manager/projectionRefreshes.js +170 -0
- package/dist/application/postgres-queue-manager/queries.d.ts +142 -0
- package/dist/application/postgres-queue-manager/queries.js +231 -0
- package/dist/application/postgres-queue-manager/relationships.d.ts +10 -0
- package/dist/application/postgres-queue-manager/relationships.js +45 -0
- package/dist/application/postgres-queue-manager/services.d.ts +31 -0
- package/dist/application/postgres-queue-manager/services.js +149 -0
- package/dist/application/postgres-queue-manager/snapshot.d.ts +51 -0
- package/dist/application/postgres-queue-manager/snapshot.js +236 -0
- package/dist/application/postgres-queue-manager/snapshotViews.d.ts +15 -0
- package/dist/application/postgres-queue-manager/snapshotViews.js +100 -0
- package/dist/application/postgres-queue-manager/startupEventBuffer.d.ts +12 -0
- package/dist/application/postgres-queue-manager/startupEventBuffer.js +36 -0
- package/dist/application/postgres-queue-manager/state.d.ts +47 -0
- package/dist/application/postgres-queue-manager/state.js +273 -0
- package/dist/application/postgres-queue-manager/terminalDelivery.d.ts +20 -0
- package/dist/application/postgres-queue-manager/terminalDelivery.js +100 -0
- package/dist/application/postgresQueueManager.d.ts +2 -0
- package/dist/application/postgresQueueManager.js +1 -0
- package/dist/application/queue-manager/ack.js +1 -1
- package/dist/application/queue-manager/delivery.js +1 -1
- package/dist/application/queue-manager/flow-failures.js +4 -0
- package/dist/application/queue-manager/limits.d.ts +2 -0
- package/dist/application/queue-manager/limits.js +4 -0
- package/dist/application/queue-manager/observability.js +15 -3
- package/dist/application/queue-manager/queries.d.ts +7 -0
- package/dist/application/queue-manager/queries.js +9 -0
- package/dist/application/queue-manager/services.d.ts +1 -1
- package/dist/application/queue-manager/services.js +4 -2
- package/dist/application/queue-manager/state.js +16 -1
- package/dist/application/statsManager.d.ts +1 -1
- package/dist/application/statsManager.js +1 -1
- package/dist/cli/commands/server.js +1 -1
- package/dist/client/events.js +2 -2
- package/dist/client/flow.js +0 -1
- package/dist/client/forwarder.d.ts +6 -4
- package/dist/client/forwarder.js +5 -3
- package/dist/client/queue/dlq.d.ts +2 -0
- package/dist/client/queue/dlq.js +14 -0
- package/dist/client/queue/dlqOps.d.ts +2 -0
- package/dist/client/queue/dlqOps.js +4 -0
- package/dist/client/queue/helpers.js +2 -0
- package/dist/client/queue/operations/add/single.js +1 -1
- package/dist/client/queue/runtime/configuration.d.ts +4 -0
- package/dist/client/queue/runtime/configuration.js +8 -0
- package/dist/client/sandboxed/runtime/state.js +2 -2
- package/dist/client/tcp/connection.d.ts +1 -1
- package/dist/client/tcp/connection.js +1 -1
- package/dist/client/tcp/runtime/state.js +2 -2
- package/dist/client/types/metrics.d.ts +1 -1
- package/dist/client/worker/runtime/control.js +0 -1
- package/dist/client/worker/runtime/execution.js +3 -8
- package/dist/client/worker/runtime/lifecycle.js +0 -1
- package/dist/client/worker/runtime/polling.d.ts +1 -0
- package/dist/client/worker/runtime/polling.js +9 -0
- package/dist/client/worker/runtime/state.js +3 -3
- package/dist/client/workflow/compensationChild.d.ts +2 -0
- package/dist/client/workflow/compensationChild.js +3 -1
- package/dist/client/workflow/compensationPass.d.ts +1 -0
- package/dist/client/workflow/compensationPass.js +18 -2
- package/dist/client/workflow/compensator.d.ts +2 -1
- package/dist/client/workflow/compensator.js +11 -3
- package/dist/client/workflow/engine.js +1 -1
- package/dist/client/workflow/executionFence.d.ts +15 -0
- package/dist/client/workflow/executionFence.js +29 -0
- package/dist/client/workflow/executor.d.ts +5 -11
- package/dist/client/workflow/executor.js +48 -47
- package/dist/client/workflow/executorLifecycle.d.ts +1 -0
- package/dist/client/workflow/executorLifecycle.js +41 -10
- package/dist/client/workflow/executorNodes.d.ts +1 -0
- package/dist/client/workflow/executorNodes.js +45 -16
- package/dist/client/workflow/executorQueue.d.ts +9 -0
- package/dist/client/workflow/executorQueue.js +15 -0
- package/dist/client/workflow/forEachRunner.d.ts +4 -0
- package/dist/client/workflow/forEachRunner.js +70 -0
- package/dist/client/workflow/loops.d.ts +4 -5
- package/dist/client/workflow/loops.js +27 -93
- package/dist/client/workflow/mapRunner.d.ts +1 -1
- package/dist/client/workflow/mapRunner.js +11 -2
- package/dist/client/workflow/recovery.d.ts +1 -0
- package/dist/client/workflow/recovery.js +24 -18
- package/dist/client/workflow/rollbackControl.d.ts +1 -0
- package/dist/client/workflow/rollbackControl.js +5 -2
- package/dist/client/workflow/runner.d.ts +3 -1
- package/dist/client/workflow/runner.js +26 -3
- package/dist/client/workflow/stepTypes.js +1 -1
- package/dist/client/workflow/subWorkflowRunner.d.ts +1 -0
- package/dist/client/workflow/subWorkflowRunner.js +7 -0
- package/dist/client/workflow/waitFor.d.ts +4 -0
- package/dist/client/workflow/waitFor.js +23 -11
- package/dist/client/workflow/workflowDecisions.d.ts +1 -1
- package/dist/client/workflow/workflowDecisions.js +3 -1
- package/dist/config/resolve.d.ts +14 -0
- package/dist/config/resolve.js +50 -5
- package/dist/config/types.d.ts +18 -0
- package/dist/domain/queue/dlqShard.d.ts +2 -0
- package/dist/domain/queue/dlqShard.js +16 -0
- package/dist/domain/queue/shard/dlq.d.ts +1 -0
- package/dist/domain/queue/shard/dlq.js +3 -0
- package/dist/domain/types/commands/dlq.d.ts +5 -0
- package/dist/domain/types/commands/union.d.ts +1 -1
- package/dist/domain/types/cron.d.ts +5 -0
- package/dist/domain/types/cron.js +14 -3
- package/dist/domain/types/dlq.d.ts +1 -1
- package/dist/domain/types/dlq.js +1 -2
- package/dist/domain/types/jobs/model.d.ts +1 -1
- package/dist/infrastructure/cloud/commandHandler.js +5 -3
- package/dist/infrastructure/cloud/commands/integrations.js +11 -11
- package/dist/infrastructure/cloud/commands/jobs.js +48 -40
- package/dist/infrastructure/cloud/commands/queues.js +61 -75
- package/dist/infrastructure/cloud/queueAdapter/local.d.ts +41 -0
- package/dist/infrastructure/cloud/queueAdapter/local.js +187 -0
- package/dist/infrastructure/cloud/queueAdapter/postgres.d.ts +42 -0
- package/dist/infrastructure/cloud/queueAdapter/postgres.js +188 -0
- package/dist/infrastructure/cloud/queueAdapter/registry.d.ts +4 -0
- package/dist/infrastructure/cloud/queueAdapter/registry.js +15 -0
- package/dist/infrastructure/cloud/queueAdapter/types.d.ts +87 -0
- package/dist/infrastructure/cloud/queueAdapter/types.js +1 -0
- package/dist/infrastructure/cloud/snapshot/analytics.d.ts +2 -0
- package/dist/infrastructure/cloud/snapshot/analytics.js +7 -0
- package/dist/infrastructure/cloud/snapshot/collector.d.ts +4 -0
- package/dist/infrastructure/cloud/snapshot/collector.js +39 -1
- package/dist/infrastructure/cloud/snapshot/config.d.ts +3 -0
- package/dist/infrastructure/cloud/snapshot/config.js +43 -0
- package/dist/infrastructure/cloud/snapshot/jobs.d.ts +3 -0
- package/dist/infrastructure/cloud/snapshot/jobs.js +32 -1
- package/dist/infrastructure/cloud/snapshotCollector.js +20 -35
- package/dist/infrastructure/cloud/snapshotHelpers.d.ts +3 -3
- package/dist/infrastructure/cloud/snapshotHelpers.js +3 -3
- package/dist/infrastructure/cloud/statsRefresh.js +2 -1
- package/dist/infrastructure/cloud/types/command.d.ts +2 -2
- package/dist/infrastructure/persistence/postgres/admission.d.ts +18 -0
- package/dist/infrastructure/persistence/postgres/admission.js +193 -0
- package/dist/infrastructure/persistence/postgres/admissionParent.d.ts +5 -0
- package/dist/infrastructure/persistence/postgres/admissionParent.js +35 -0
- package/dist/infrastructure/persistence/postgres/admissionResult.d.ts +9 -0
- package/dist/infrastructure/persistence/postgres/admissionResult.js +1 -0
- package/dist/infrastructure/persistence/postgres/admissionStore.d.ts +12 -0
- package/dist/infrastructure/persistence/postgres/admissionStore.js +101 -0
- package/dist/infrastructure/persistence/postgres/advisoryLocks.d.ts +2 -0
- package/dist/infrastructure/persistence/postgres/advisoryLocks.js +13 -0
- package/dist/infrastructure/persistence/postgres/batchAdmission.d.ts +11 -0
- package/dist/infrastructure/persistence/postgres/batchAdmission.js +110 -0
- package/dist/infrastructure/persistence/postgres/batchCompletion.d.ts +4 -0
- package/dist/infrastructure/persistence/postgres/batchCompletion.js +174 -0
- package/dist/infrastructure/persistence/postgres/batchEvents.d.ts +14 -0
- package/dist/infrastructure/persistence/postgres/batchEvents.js +72 -0
- package/dist/infrastructure/persistence/postgres/brokerSessions.d.ts +14 -0
- package/dist/infrastructure/persistence/postgres/brokerSessions.js +94 -0
- package/dist/infrastructure/persistence/postgres/claimBatch.d.ts +11 -0
- package/dist/infrastructure/persistence/postgres/claimBatch.js +56 -0
- package/dist/infrastructure/persistence/postgres/claimSelection.d.ts +4 -0
- package/dist/infrastructure/persistence/postgres/claimSelection.js +198 -0
- package/dist/infrastructure/persistence/postgres/claims.d.ts +3 -0
- package/dist/infrastructure/persistence/postgres/claims.js +137 -0
- package/dist/infrastructure/persistence/postgres/cloudReadModel.d.ts +26 -0
- package/dist/infrastructure/persistence/postgres/cloudReadModel.js +123 -0
- package/dist/infrastructure/persistence/postgres/codec.d.ts +30 -0
- package/dist/infrastructure/persistence/postgres/codec.js +82 -0
- package/dist/infrastructure/persistence/postgres/completionEvents.d.ts +11 -0
- package/dist/infrastructure/persistence/postgres/completionEvents.js +21 -0
- package/dist/infrastructure/persistence/postgres/completionLifecycle.d.ts +11 -0
- package/dist/infrastructure/persistence/postgres/completionLifecycle.js +145 -0
- package/dist/infrastructure/persistence/postgres/completionOutcome.d.ts +4 -0
- package/dist/infrastructure/persistence/postgres/completionOutcome.js +112 -0
- package/dist/infrastructure/persistence/postgres/completionQueries.d.ts +10 -0
- package/dist/infrastructure/persistence/postgres/completionQueries.js +87 -0
- package/dist/infrastructure/persistence/postgres/connection.d.ts +3 -0
- package/dist/infrastructure/persistence/postgres/connection.js +15 -0
- package/dist/infrastructure/persistence/postgres/context.d.ts +28 -0
- package/dist/infrastructure/persistence/postgres/context.js +74 -0
- package/dist/infrastructure/persistence/postgres/control.d.ts +23 -0
- package/dist/infrastructure/persistence/postgres/control.js +105 -0
- package/dist/infrastructure/persistence/postgres/crons.d.ts +18 -0
- package/dist/infrastructure/persistence/postgres/crons.js +205 -0
- package/dist/infrastructure/persistence/postgres/deduplication.d.ts +9 -0
- package/dist/infrastructure/persistence/postgres/deduplication.js +47 -0
- package/dist/infrastructure/persistence/postgres/dependencyDestruction.d.ts +14 -0
- package/dist/infrastructure/persistence/postgres/dependencyDestruction.js +74 -0
- package/dist/infrastructure/persistence/postgres/dependencyPromotion.d.ts +29 -0
- package/dist/infrastructure/persistence/postgres/dependencyPromotion.js +212 -0
- package/dist/infrastructure/persistence/postgres/destructiveMutations.d.ts +4 -0
- package/dist/infrastructure/persistence/postgres/destructiveMutations.js +111 -0
- package/dist/infrastructure/persistence/postgres/dlqLifecycle.d.ts +13 -0
- package/dist/infrastructure/persistence/postgres/dlqLifecycle.js +184 -0
- package/dist/infrastructure/persistence/postgres/dlqMaintenance.d.ts +6 -0
- package/dist/infrastructure/persistence/postgres/dlqMaintenance.js +36 -0
- package/dist/infrastructure/persistence/postgres/dlqRetryPlan.d.ts +21 -0
- package/dist/infrastructure/persistence/postgres/dlqRetryPlan.js +92 -0
- package/dist/infrastructure/persistence/postgres/eventCommitGc.d.ts +27 -0
- package/dist/infrastructure/persistence/postgres/eventCommitGc.js +62 -0
- package/dist/infrastructure/persistence/postgres/eventJournal.d.ts +18 -0
- package/dist/infrastructure/persistence/postgres/eventJournal.js +76 -0
- package/dist/infrastructure/persistence/postgres/eventJournalSchema.d.ts +5 -0
- package/dist/infrastructure/persistence/postgres/eventJournalSchema.js +253 -0
- package/dist/infrastructure/persistence/postgres/eventPruneWatermarks.d.ts +24 -0
- package/dist/infrastructure/persistence/postgres/eventPruneWatermarks.js +111 -0
- package/dist/infrastructure/persistence/postgres/eventRetention.d.ts +18 -0
- package/dist/infrastructure/persistence/postgres/eventRetention.js +76 -0
- package/dist/infrastructure/persistence/postgres/events.d.ts +45 -0
- package/dist/infrastructure/persistence/postgres/events.js +249 -0
- package/dist/infrastructure/persistence/postgres/expiry.d.ts +3 -0
- package/dist/infrastructure/persistence/postgres/expiry.js +74 -0
- package/dist/infrastructure/persistence/postgres/flowFailures.d.ts +17 -0
- package/dist/infrastructure/persistence/postgres/flowFailures.js +199 -0
- package/dist/infrastructure/persistence/postgres/jobData.d.ts +3 -0
- package/dist/infrastructure/persistence/postgres/jobData.js +32 -0
- package/dist/infrastructure/persistence/postgres/leaseRelease.d.ts +9 -0
- package/dist/infrastructure/persistence/postgres/leaseRelease.js +120 -0
- package/dist/infrastructure/persistence/postgres/leaseRenewal.d.ts +4 -0
- package/dist/infrastructure/persistence/postgres/leaseRenewal.js +35 -0
- package/dist/infrastructure/persistence/postgres/lifetimeMetrics.d.ts +15 -0
- package/dist/infrastructure/persistence/postgres/lifetimeMetrics.js +46 -0
- package/dist/infrastructure/persistence/postgres/lifetimeMetricsFinalizer.d.ts +4 -0
- package/dist/infrastructure/persistence/postgres/lifetimeMetricsFinalizer.js +13 -0
- package/dist/infrastructure/persistence/postgres/logs.d.ts +6 -0
- package/dist/infrastructure/persistence/postgres/logs.js +70 -0
- package/dist/infrastructure/persistence/postgres/maintenance.d.ts +6 -0
- package/dist/infrastructure/persistence/postgres/maintenance.js +138 -0
- package/dist/infrastructure/persistence/postgres/maintenanceFlights.d.ts +14 -0
- package/dist/infrastructure/persistence/postgres/maintenanceFlights.js +66 -0
- package/dist/infrastructure/persistence/postgres/metricWrites.d.ts +9 -0
- package/dist/infrastructure/persistence/postgres/metricWrites.js +94 -0
- package/dist/infrastructure/persistence/postgres/mutations.d.ts +9 -0
- package/dist/infrastructure/persistence/postgres/mutations.js +171 -0
- package/dist/infrastructure/persistence/postgres/outcomes.d.ts +4 -0
- package/dist/infrastructure/persistence/postgres/outcomes.js +194 -0
- package/dist/infrastructure/persistence/postgres/policies.d.ts +11 -0
- package/dist/infrastructure/persistence/postgres/policies.js +18 -0
- package/dist/infrastructure/persistence/postgres/postCommitMaintenance.d.ts +17 -0
- package/dist/infrastructure/persistence/postgres/postCommitMaintenance.js +117 -0
- package/dist/infrastructure/persistence/postgres/promotion.d.ts +3 -0
- package/dist/infrastructure/persistence/postgres/promotion.js +43 -0
- package/dist/infrastructure/persistence/postgres/queries.d.ts +22 -0
- package/dist/infrastructure/persistence/postgres/queries.js +152 -0
- package/dist/infrastructure/persistence/postgres/queueDestruction.d.ts +3 -0
- package/dist/infrastructure/persistence/postgres/queueDestruction.js +154 -0
- package/dist/infrastructure/persistence/postgres/queueLifecycle.d.ts +10 -0
- package/dist/infrastructure/persistence/postgres/queueLifecycle.js +60 -0
- package/dist/infrastructure/persistence/postgres/rateLimit.d.ts +6 -0
- package/dist/infrastructure/persistence/postgres/rateLimit.js +10 -0
- package/dist/infrastructure/persistence/postgres/readModels.d.ts +32 -0
- package/dist/infrastructure/persistence/postgres/readModels.js +72 -0
- package/dist/infrastructure/persistence/postgres/recovery.d.ts +3 -0
- package/dist/infrastructure/persistence/postgres/recovery.js +178 -0
- package/dist/infrastructure/persistence/postgres/relationships.d.ts +9 -0
- package/dist/infrastructure/persistence/postgres/relationships.js +201 -0
- package/dist/infrastructure/persistence/postgres/removeUnprocessedChildren.d.ts +4 -0
- package/dist/infrastructure/persistence/postgres/removeUnprocessedChildren.js +241 -0
- package/dist/infrastructure/persistence/postgres/repeatMutations.d.ts +5 -0
- package/dist/infrastructure/persistence/postgres/repeatMutations.js +37 -0
- package/dist/infrastructure/persistence/postgres/runtime.d.ts +46 -0
- package/dist/infrastructure/persistence/postgres/runtime.js +236 -0
- package/dist/infrastructure/persistence/postgres/runtimeConfig.d.ts +21 -0
- package/dist/infrastructure/persistence/postgres/runtimeConfig.js +39 -0
- package/dist/infrastructure/persistence/postgres/schema.d.ts +4 -0
- package/dist/infrastructure/persistence/postgres/schema.js +257 -0
- package/dist/infrastructure/persistence/postgres/schemaInitialization.d.ts +3 -0
- package/dist/infrastructure/persistence/postgres/schemaInitialization.js +221 -0
- package/dist/infrastructure/persistence/postgres/serialAdmission.d.ts +6 -0
- package/dist/infrastructure/persistence/postgres/serialAdmission.js +96 -0
- package/dist/infrastructure/persistence/postgres/snapshotBudget.d.ts +3 -0
- package/dist/infrastructure/persistence/postgres/snapshotBudget.js +83 -0
- package/dist/infrastructure/persistence/postgres/storageHealth.d.ts +10 -0
- package/dist/infrastructure/persistence/postgres/storageHealth.js +29 -0
- package/dist/infrastructure/persistence/postgres/store.d.ts +90 -0
- package/dist/infrastructure/persistence/postgres/store.js +136 -0
- package/dist/infrastructure/persistence/postgres/telemetry.d.ts +6 -0
- package/dist/infrastructure/persistence/postgres/telemetry.js +88 -0
- package/dist/infrastructure/persistence/postgres/terminal.d.ts +8 -0
- package/dist/infrastructure/persistence/postgres/terminal.js +137 -0
- package/dist/infrastructure/persistence/postgres/transactionRetry.d.ts +6 -0
- package/dist/infrastructure/persistence/postgres/transactionRetry.js +42 -0
- package/dist/infrastructure/persistence/postgres/types.d.ts +175 -0
- package/dist/infrastructure/persistence/postgres/types.js +1 -0
- package/dist/infrastructure/persistence/postgres/workers.d.ts +15 -0
- package/dist/infrastructure/persistence/postgres/workers.js +126 -0
- package/dist/infrastructure/persistence/postgres.d.ts +2 -0
- package/dist/infrastructure/persistence/postgres.js +1 -0
- package/dist/infrastructure/persistence/sqlite/jobs.js +2 -0
- package/dist/infrastructure/persistence/sqlite.d.ts +1 -1
- package/dist/infrastructure/scheduler/cron/persisted.d.ts +5 -0
- package/dist/infrastructure/scheduler/cron/persisted.js +27 -0
- package/dist/infrastructure/scheduler/cron/runtime.js +5 -10
- package/dist/infrastructure/scheduler/cron/validation.d.ts +3 -0
- package/dist/infrastructure/scheduler/cron/validation.js +11 -0
- package/dist/infrastructure/scheduler/cronParser.d.ts +1 -1
- package/dist/infrastructure/scheduler/cronParser.js +144 -11
- package/dist/infrastructure/server/bootstrap.d.ts +2 -3
- package/dist/infrastructure/server/bootstrap.js +33 -59
- package/dist/infrastructure/server/errors.d.ts +4 -0
- package/dist/infrastructure/server/errors.js +54 -0
- package/dist/infrastructure/server/handler-routes/control.d.ts +5 -5
- package/dist/infrastructure/server/handler-routes/control.js +3 -1
- package/dist/infrastructure/server/handler-routes/jobs.js +0 -3
- package/dist/infrastructure/server/handler-routes/monitoring.d.ts +2 -2
- package/dist/infrastructure/server/handler.js +9 -11
- package/dist/infrastructure/server/handlerRoutes.d.ts +2 -2
- package/dist/infrastructure/server/handlerRoutes.js +2 -2
- package/dist/infrastructure/server/handlers/advanced/dependencies.js +7 -6
- package/dist/infrastructure/server/handlers/advanced/jobs.js +19 -6
- package/dist/infrastructure/server/handlers/advanced/queue.d.ts +9 -9
- package/dist/infrastructure/server/handlers/advanced/queue.js +91 -27
- package/dist/infrastructure/server/handlers/core.js +32 -31
- package/dist/infrastructure/server/handlers/cron.d.ts +4 -4
- package/dist/infrastructure/server/handlers/cron.js +60 -59
- package/dist/infrastructure/server/handlers/dashboard.d.ts +1 -1
- package/dist/infrastructure/server/handlers/dashboard.js +21 -8
- package/dist/infrastructure/server/handlers/dlq.d.ts +6 -3
- package/dist/infrastructure/server/handlers/dlq.js +35 -9
- package/dist/infrastructure/server/handlers/flow.js +2 -1
- package/dist/infrastructure/server/handlers/management.d.ts +5 -5
- package/dist/infrastructure/server/handlers/management.js +36 -9
- package/dist/infrastructure/server/handlers/monitoring/health.d.ts +5 -5
- package/dist/infrastructure/server/handlers/monitoring/health.js +45 -16
- package/dist/infrastructure/server/handlers/monitoring/operations.d.ts +1 -1
- package/dist/infrastructure/server/handlers/monitoring/operations.js +8 -1
- package/dist/infrastructure/server/handlers/monitoring/workers.d.ts +3 -3
- package/dist/infrastructure/server/handlers/monitoring/workers.js +31 -7
- package/dist/infrastructure/server/handlers/pushBatchValidation.d.ts +3 -1
- package/dist/infrastructure/server/handlers/pushBatchValidation.js +52 -7
- package/dist/infrastructure/server/handlers/query.d.ts +1 -1
- package/dist/infrastructure/server/handlers/query.js +5 -4
- package/dist/infrastructure/server/http.js +2 -2
- package/dist/infrastructure/server/httpDashboardEndpoints.d.ts +1 -1
- package/dist/infrastructure/server/httpDashboardEndpoints.js +19 -6
- package/dist/infrastructure/server/httpEndpoints.js +20 -7
- package/dist/infrastructure/server/httpRouteQueues.js +11 -1
- package/dist/infrastructure/server/shutdownCoordinator.d.ts +24 -0
- package/dist/infrastructure/server/shutdownCoordinator.js +106 -0
- package/dist/infrastructure/server/sseHandler.js +8 -3
- package/dist/infrastructure/server/storageAdapter.d.ts +29 -0
- package/dist/infrastructure/server/storageAdapter.js +61 -0
- package/dist/infrastructure/server/storageManager.d.ts +12 -0
- package/dist/infrastructure/server/storageManager.js +102 -0
- package/dist/infrastructure/server/tcp.js +2 -3
- package/dist/infrastructure/server/ws/snapshots.d.ts +1 -1
- package/dist/infrastructure/server/ws/snapshots.js +19 -4
- package/dist/infrastructure/server/wsHandler.js +10 -5
- package/dist/main.js +1 -1
- package/dist/mcp/backend/embedded/services.js +2 -1
- package/dist/mcp/index.d.ts +2 -2
- package/dist/mcp/index.js +2 -2
- package/dist/shared/storageHealth.d.ts +9 -0
- package/dist/shared/storageHealth.js +12 -0
- package/package.json +85 -74
|
@@ -8,19 +8,23 @@ import { claimKey, decideAdmission } from './admission';
|
|
|
8
8
|
import { signalExecution, startExecution } from './executorLifecycle';
|
|
9
9
|
import { executeWorkflowNode } from './executorNodes';
|
|
10
10
|
import { bindExecutionDefinition, WorkflowDefinitionMismatchError } from './definitionGuard';
|
|
11
|
+
import { isWorkflowExecutionClosed, WorkflowExecutionFence } from './executionFence';
|
|
12
|
+
import { enqueueWorkflowStep } from './executorQueue';
|
|
11
13
|
export class WorkflowExecutor {
|
|
12
14
|
store;
|
|
13
15
|
queue;
|
|
14
16
|
emitter;
|
|
15
17
|
workflows = new Map();
|
|
16
18
|
timeoutTimers = new Map();
|
|
19
|
+
fence = new WorkflowExecutionFence();
|
|
17
20
|
/**
|
|
18
21
|
* Release every armed waitFor timer. The engine owns the executor's lifetime, so
|
|
19
22
|
* `Engine.close()` must call this: an armed timer would otherwise fire into a
|
|
20
23
|
* closing queue, and a caller that keeps the process alive after closing one engine
|
|
21
24
|
* has no other handle on them.
|
|
22
25
|
*/
|
|
23
|
-
close() {
|
|
26
|
+
close(force = false) {
|
|
27
|
+
this.fence.close(force);
|
|
24
28
|
clearTimers(this.timeoutTimers);
|
|
25
29
|
}
|
|
26
30
|
updateFn;
|
|
@@ -29,10 +33,12 @@ export class WorkflowExecutor {
|
|
|
29
33
|
this.queue = queue;
|
|
30
34
|
this.emitter = emitter;
|
|
31
35
|
this.updateFn = (e) => {
|
|
36
|
+
this.fence.assertActive();
|
|
32
37
|
this.store.update(e);
|
|
33
38
|
};
|
|
34
39
|
}
|
|
35
40
|
register(workflow) {
|
|
41
|
+
this.fence.assertActive();
|
|
36
42
|
if (this.workflows.has(workflow.name)) {
|
|
37
43
|
throw new Error(`Workflow "${workflow.name}" is already registered`);
|
|
38
44
|
}
|
|
@@ -58,31 +64,21 @@ export class WorkflowExecutor {
|
|
|
58
64
|
this.workflows.set(workflow.name, workflow);
|
|
59
65
|
}
|
|
60
66
|
async start(workflowName, input, parentExecutionId) {
|
|
67
|
+
this.fence.assertActive();
|
|
61
68
|
return await startExecution(this.lifecycleDeps, workflowName, input, parentExecutionId);
|
|
62
69
|
}
|
|
63
70
|
/**
|
|
64
71
|
* Nodes this process is currently executing, keyed `<execution>:<nodeIndex>`.
|
|
65
72
|
*
|
|
66
|
-
* The cursor
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* `running` execution and is documented as callable on a live engine. Without this
|
|
70
|
-
* claim the node runs twice and each copy advances the run independently, doubling
|
|
71
|
-
* every side effect after it while the run still ends `completed`.
|
|
72
|
-
*
|
|
73
|
-
* A claim, not a queue-level dedup: a deterministic `jobId` was tried and could
|
|
74
|
-
* swallow a LEGITIMATE later re-enqueue of the same node, wedging the run forever.
|
|
75
|
-
* This drops only a duplicate that overlaps in time.
|
|
73
|
+
* The cursor rejects stale jobs but not two overlapping deliveries for the current
|
|
74
|
+
* node. This process-local claim drops that overlap. Queue-level dedup is unsuitable:
|
|
75
|
+
* a retained ID can swallow a legitimate recovery enqueue and wedge the run.
|
|
76
76
|
*/
|
|
77
77
|
nodesInFlight = new Set();
|
|
78
78
|
async processStep(data) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
//
|
|
82
|
-
// the node and every node after it: two advance chains on one execution, doubled
|
|
83
|
-
// side effects, and a final `completed` that hid it. That took a long model
|
|
84
|
-
// campaign to find because the reasoning was buried in a method that also read
|
|
85
|
-
// SQLite and dispatched work.
|
|
79
|
+
if (!this.fence.isActive())
|
|
80
|
+
return null;
|
|
81
|
+
// Admission is pure; this method only owns the in-flight claim and dispatch.
|
|
86
82
|
const exec = this.store.get(data.executionId);
|
|
87
83
|
const admission = decideAdmission(exec, data.nodeIndex, this.nodesInFlight);
|
|
88
84
|
if (admission.kind === 'reject' || !exec)
|
|
@@ -106,8 +102,9 @@ export class WorkflowExecutor {
|
|
|
106
102
|
exec.state = 'running';
|
|
107
103
|
const node = wf.nodes[data.nodeIndex];
|
|
108
104
|
if (!node) {
|
|
105
|
+
this.fence.assertActive();
|
|
109
106
|
exec.state = 'completed';
|
|
110
|
-
this.
|
|
107
|
+
this.updateFn(exec);
|
|
111
108
|
this.emitter?.emitWorkflow('workflow:completed', exec.id, exec.workflowName, 'completed');
|
|
112
109
|
return null;
|
|
113
110
|
}
|
|
@@ -115,26 +112,27 @@ export class WorkflowExecutor {
|
|
|
115
112
|
await executeWorkflowNode(this.nodeDeps, exec, node, data.nodeIndex, wf);
|
|
116
113
|
}
|
|
117
114
|
catch (err) {
|
|
115
|
+
if (!this.fence.isActive() || isWorkflowExecutionClosed(err))
|
|
116
|
+
return null;
|
|
118
117
|
if (err instanceof WaitForSignalError)
|
|
119
118
|
return null;
|
|
119
|
+
this.fence.assertActive();
|
|
120
120
|
exec.state = 'failed';
|
|
121
121
|
// Why the run failed — kept distinct from what the rollback then did.
|
|
122
122
|
exec.failureReason = describeError(err);
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
// next `recover()` re-drives this node and the unwind happens then. Swallowing it
|
|
127
|
-
// would instead leave a run that looks failed and was never rolled back, with
|
|
128
|
-
// nothing scheduled to notice. Guard this only alongside a durable signal that the
|
|
129
|
-
// rollback is still owed.
|
|
130
|
-
this.store.update(exec);
|
|
123
|
+
// A failed transition must persist before compensation starts. If the guarded
|
|
124
|
+
// write fails, recovery sees the old running row and re-drives this node.
|
|
125
|
+
this.updateFn(exec);
|
|
131
126
|
this.emitter?.emitWorkflow('workflow:failed', exec.id, exec.workflowName, 'failed');
|
|
127
|
+
if (!this.fence.isActive())
|
|
128
|
+
return null;
|
|
132
129
|
await this.compensate(exec, wf);
|
|
133
130
|
throw err;
|
|
134
131
|
}
|
|
135
132
|
return null;
|
|
136
133
|
}
|
|
137
134
|
async signal(executionId, event, payload) {
|
|
135
|
+
this.fence.assertActive();
|
|
138
136
|
await signalExecution(this.lifecycleDeps, executionId, event, payload);
|
|
139
137
|
}
|
|
140
138
|
get lifecycleDeps() {
|
|
@@ -145,14 +143,17 @@ export class WorkflowExecutor {
|
|
|
145
143
|
emitter: this.emitter,
|
|
146
144
|
timers: this.timeoutTimers,
|
|
147
145
|
enqueue: (exec) => this.enqueue(exec),
|
|
146
|
+
assertActive: this.fence.assertActive,
|
|
148
147
|
};
|
|
149
148
|
}
|
|
150
149
|
/** Retry the compensation that parked the run, then finish the unwind. */
|
|
151
150
|
async resumeCompensation(executionId) {
|
|
151
|
+
this.fence.assertActive();
|
|
152
152
|
await resumeCompensation(this.rollbackDeps, executionId);
|
|
153
153
|
}
|
|
154
154
|
/** Give up on a parked unwind, recording the outstanding steps as skipped. */
|
|
155
155
|
abandonCompensation(executionId) {
|
|
156
|
+
this.fence.assertActive();
|
|
156
157
|
abandonParkedCompensation(this.rollbackDeps, executionId);
|
|
157
158
|
}
|
|
158
159
|
get rollbackDeps() {
|
|
@@ -160,6 +161,7 @@ export class WorkflowExecutor {
|
|
|
160
161
|
store: this.store,
|
|
161
162
|
emitter: this.emitter,
|
|
162
163
|
workflows: this.workflows,
|
|
164
|
+
assertActive: this.fence.assertActive,
|
|
163
165
|
};
|
|
164
166
|
}
|
|
165
167
|
getExecution(id) {
|
|
@@ -177,14 +179,16 @@ export class WorkflowExecutor {
|
|
|
177
179
|
start: (name, input, parentId) => this.start(name, input, parentId),
|
|
178
180
|
enqueue: (exec) => this.enqueue(exec),
|
|
179
181
|
waitFor: this.waitForDeps,
|
|
182
|
+
assertActive: this.fence.assertActive,
|
|
180
183
|
};
|
|
181
184
|
}
|
|
182
185
|
async advance(exec, nextIdx, wf) {
|
|
186
|
+
this.fence.assertActive();
|
|
183
187
|
exec.currentNodeIndex = nextIdx;
|
|
184
|
-
this.
|
|
188
|
+
this.updateFn(exec);
|
|
185
189
|
if (nextIdx >= wf.nodes.length) {
|
|
186
190
|
exec.state = 'completed';
|
|
187
|
-
this.
|
|
191
|
+
this.updateFn(exec);
|
|
188
192
|
this.emitter?.emitWorkflow('workflow:completed', exec.id, exec.workflowName, 'completed');
|
|
189
193
|
}
|
|
190
194
|
else {
|
|
@@ -192,22 +196,7 @@ export class WorkflowExecutor {
|
|
|
192
196
|
}
|
|
193
197
|
}
|
|
194
198
|
async enqueue(exec) {
|
|
195
|
-
|
|
196
|
-
executionId: exec.id,
|
|
197
|
-
workflowName: exec.workflowName,
|
|
198
|
-
nodeIndex: exec.currentNodeIndex,
|
|
199
|
-
};
|
|
200
|
-
// Deliberately NO deterministic jobId here.
|
|
201
|
-
//
|
|
202
|
-
// `<execution>:<nodeIndex>` was tried, to let the queue's custom-id dedup collapse
|
|
203
|
-
// a duplicate enqueue. It buys nothing the cursor guard in processStep does not
|
|
204
|
-
// already provide (a duplicate job is ignored there), and it introduces a liveness
|
|
205
|
-
// risk in exchange: if the custom-id entry outlives the job it names, a legitimate
|
|
206
|
-
// re-enqueue of the same node is swallowed and the run wedges permanently. A
|
|
207
|
-
// generated model campaign produced exactly one unexplained `execution wedged in
|
|
208
|
-
// "running"` with it enabled and none without. A duplicate job that is ignored is
|
|
209
|
-
// strictly safer than a missing job that never arrives.
|
|
210
|
-
await this.queue.add('wf:step', jobData);
|
|
199
|
+
await enqueueWorkflowStep(this.queue, exec, this.fence.assertActive);
|
|
211
200
|
}
|
|
212
201
|
get waitForDeps() {
|
|
213
202
|
return {
|
|
@@ -215,19 +204,30 @@ export class WorkflowExecutor {
|
|
|
215
204
|
emitter: this.emitter,
|
|
216
205
|
advance: (e, next, w) => this.advance(e, next, w),
|
|
217
206
|
compensate: (e, w) => this.compensate(e, w),
|
|
207
|
+
updateFn: this.updateFn,
|
|
208
|
+
assertActive: this.fence.assertActive,
|
|
218
209
|
scheduleTimeoutCheck: (id, name, i, ms) => {
|
|
219
210
|
this.scheduleTimeoutCheck(id, name, i, ms);
|
|
220
211
|
},
|
|
221
212
|
};
|
|
222
213
|
}
|
|
223
214
|
scheduleTimeoutCheck(execId, workflowName, nodeIdx, ms) {
|
|
224
|
-
scheduleTimeoutCheck({
|
|
215
|
+
scheduleTimeoutCheck({
|
|
216
|
+
queue: this.queue,
|
|
217
|
+
timers: this.timeoutTimers,
|
|
218
|
+
assertActive: this.fence.assertActive,
|
|
219
|
+
isActive: () => this.fence.isActive(),
|
|
220
|
+
}, execId, workflowName, nodeIdx, ms);
|
|
225
221
|
}
|
|
226
222
|
async compensate(exec, wf) {
|
|
227
|
-
|
|
223
|
+
this.fence.assertActive();
|
|
224
|
+
await runCompensation(exec, wf, this.store, this.emitter, this.workflows, {
|
|
225
|
+
assertActive: this.fence.assertActive,
|
|
226
|
+
});
|
|
228
227
|
}
|
|
229
228
|
/** Recover orphaned executions after a crash/restart */
|
|
230
229
|
async recover() {
|
|
230
|
+
this.fence.assertActive();
|
|
231
231
|
return await recoverExecutions({
|
|
232
232
|
store: this.store,
|
|
233
233
|
queue: this.queue,
|
|
@@ -235,6 +235,7 @@ export class WorkflowExecutor {
|
|
|
235
235
|
emitter: this.emitter,
|
|
236
236
|
timeoutTimers: this.timeoutTimers,
|
|
237
237
|
nodesInFlight: this.nodesInFlight,
|
|
238
|
+
assertActive: this.fence.assertActive,
|
|
238
239
|
scheduleTimeoutCheck: (id, name, idx, ms) => {
|
|
239
240
|
this.scheduleTimeoutCheck(id, name, idx, ms);
|
|
240
241
|
},
|
|
@@ -12,6 +12,7 @@ interface LifecycleDeps {
|
|
|
12
12
|
emitter: WorkflowEmitter | null;
|
|
13
13
|
timers: Map<string, TimerHandle>;
|
|
14
14
|
enqueue: (exec: Execution) => Promise<void>;
|
|
15
|
+
assertActive: () => void;
|
|
15
16
|
}
|
|
16
17
|
export declare function startExecution(deps: LifecycleDeps, workflowName: string, input: unknown, parentExecutionId?: string): Promise<RunHandle>;
|
|
17
18
|
export declare function signalExecution(deps: LifecycleDeps, executionId: string, event: string, payload: unknown): Promise<void>;
|
|
@@ -3,6 +3,7 @@ import { clock } from './clock';
|
|
|
3
3
|
import { unusableEventName } from './workflow';
|
|
4
4
|
import { newExecutionId } from './identity';
|
|
5
5
|
export async function startExecution(deps, workflowName, input, parentExecutionId) {
|
|
6
|
+
deps.assertActive();
|
|
6
7
|
const wf = deps.workflows.get(workflowName);
|
|
7
8
|
if (!wf)
|
|
8
9
|
throw new Error(`Workflow "${workflowName}" not registered`);
|
|
@@ -23,20 +24,36 @@ export async function startExecution(deps, workflowName, input, parentExecutionI
|
|
|
23
24
|
createdAt: now,
|
|
24
25
|
updatedAt: now,
|
|
25
26
|
};
|
|
27
|
+
deps.assertActive();
|
|
26
28
|
deps.store.save(exec);
|
|
27
|
-
deps.emitter?.emitWorkflow('workflow:started', id, workflowName, 'running', { input });
|
|
28
29
|
try {
|
|
30
|
+
deps.emitter?.emitWorkflow('workflow:started', id, workflowName, 'running', { input });
|
|
31
|
+
deps.assertActive();
|
|
29
32
|
await deps.enqueue(exec);
|
|
33
|
+
deps.assertActive();
|
|
30
34
|
}
|
|
31
35
|
catch (error) {
|
|
32
36
|
// start() cannot return an id when publication fails. Removing the row keeps both
|
|
33
37
|
// top-level starts and sub-workflow starts from creating an unreachable orphan.
|
|
34
|
-
|
|
35
|
-
|
|
38
|
+
let failure = error;
|
|
39
|
+
try {
|
|
40
|
+
deps.assertActive();
|
|
41
|
+
}
|
|
42
|
+
catch (closedError) {
|
|
43
|
+
failure = closedError;
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
deps.store.remove(id);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// If shutdown already closed SQLite, recovery owns the still-running row.
|
|
50
|
+
}
|
|
51
|
+
throw failure;
|
|
36
52
|
}
|
|
37
53
|
return { id, workflowName };
|
|
38
54
|
}
|
|
39
55
|
export async function signalExecution(deps, executionId, event, payload) {
|
|
56
|
+
deps.assertActive();
|
|
40
57
|
const bad = unusableEventName(event);
|
|
41
58
|
if (bad)
|
|
42
59
|
throw new Error(`Cannot signal an event ${bad}`);
|
|
@@ -48,22 +65,36 @@ export async function signalExecution(deps, executionId, event, payload) {
|
|
|
48
65
|
deps.emitter?.emitSignal('signal:received', executionId, outcome.workflowName, event, payload);
|
|
49
66
|
if (!outcome.resumed)
|
|
50
67
|
return;
|
|
51
|
-
const timer = deps.timers.get(executionId);
|
|
52
|
-
if (timer) {
|
|
53
|
-
clock().clearTimeout(timer);
|
|
54
|
-
deps.timers.delete(executionId);
|
|
55
|
-
}
|
|
56
68
|
try {
|
|
69
|
+
deps.assertActive();
|
|
70
|
+
const timer = deps.timers.get(executionId);
|
|
71
|
+
if (timer) {
|
|
72
|
+
clock().clearTimeout(timer);
|
|
73
|
+
deps.timers.delete(executionId);
|
|
74
|
+
}
|
|
57
75
|
await deps.queue.add('wf:step', {
|
|
58
76
|
executionId,
|
|
59
77
|
workflowName: outcome.workflowName,
|
|
60
78
|
nodeIndex: outcome.currentNodeIndex,
|
|
61
79
|
});
|
|
80
|
+
deps.assertActive();
|
|
62
81
|
}
|
|
63
82
|
catch (error) {
|
|
64
83
|
// Preserve the accepted payload but release the publication claim. Recovery sees
|
|
65
84
|
// a waiting execution with its signal present and republishes the same node.
|
|
66
|
-
|
|
67
|
-
|
|
85
|
+
let failure = error;
|
|
86
|
+
try {
|
|
87
|
+
deps.assertActive();
|
|
88
|
+
}
|
|
89
|
+
catch (closedError) {
|
|
90
|
+
failure = closedError;
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
deps.store.restoreSignalWait(executionId, event, outcome.currentNodeIndex);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// A closed store leaves a recoverable running row with its signal preserved.
|
|
97
|
+
}
|
|
98
|
+
throw failure;
|
|
68
99
|
}
|
|
69
100
|
}
|
|
@@ -12,6 +12,7 @@ interface NodeExecutionDeps {
|
|
|
12
12
|
start: (name: string, input: unknown, parentId: string) => Promise<RunHandle>;
|
|
13
13
|
enqueue: (exec: Execution) => Promise<void>;
|
|
14
14
|
waitFor: WaitForDeps;
|
|
15
|
+
assertActive: () => void;
|
|
15
16
|
}
|
|
16
17
|
export declare function executeWorkflowNode(deps: NodeExecutionDeps, exec: Execution, node: WorkflowNode, idx: number, wf: Workflow): Promise<void>;
|
|
17
18
|
export {};
|
|
@@ -1,51 +1,61 @@
|
|
|
1
1
|
/** Workflow node dispatch, split from the executor's lifecycle orchestration. */
|
|
2
2
|
import { clock } from './clock';
|
|
3
|
+
import { isWorkflowExecutionClosed } from './executionFence';
|
|
3
4
|
import { describeError } from './identity';
|
|
4
5
|
import { executeDoUntil, executeDoWhile, executeForEach, executeMap } from './loops';
|
|
5
6
|
import { buildContext, executeParallelSteps, executeStepWithRetry, executeSubWorkflow, } from './runner';
|
|
6
7
|
import { runWaitFor } from './waitFor';
|
|
7
8
|
import { branchDecisionKey, resolveDecision, subWorkflowInputDecisionKey, } from './workflowDecisions';
|
|
8
9
|
export async function executeWorkflowNode(deps, exec, node, idx, wf) {
|
|
10
|
+
deps.assertActive();
|
|
9
11
|
if (node.type === 'step') {
|
|
10
12
|
await executeStepWithRetry(node.def, buildContext(exec), exec, {
|
|
11
13
|
emitter: deps.emitter,
|
|
12
14
|
updateFn: deps.updateFn,
|
|
15
|
+
assertActive: deps.assertActive,
|
|
13
16
|
});
|
|
14
17
|
}
|
|
15
18
|
else if (node.type === 'branch') {
|
|
16
19
|
await runBranch(deps, exec, node, idx);
|
|
17
20
|
}
|
|
18
21
|
else if (node.type === 'parallel') {
|
|
19
|
-
await executeParallelSteps(node.def.steps, buildContext(exec), exec,
|
|
22
|
+
await executeParallelSteps(node.def.steps, buildContext(exec), exec, {
|
|
23
|
+
emitter: deps.emitter,
|
|
24
|
+
updateFn: deps.updateFn,
|
|
25
|
+
assertActive: deps.assertActive,
|
|
26
|
+
});
|
|
20
27
|
}
|
|
21
28
|
else if (node.type === 'subWorkflow') {
|
|
22
29
|
await runSubWorkflow(deps, exec, node, idx);
|
|
23
30
|
}
|
|
24
31
|
else if (node.type === 'doUntil') {
|
|
25
|
-
await executeDoUntil(node.def, exec, deps.emitter, deps.updateFn);
|
|
32
|
+
await executeDoUntil(node.def, exec, deps.emitter, deps.updateFn, deps.assertActive);
|
|
26
33
|
}
|
|
27
34
|
else if (node.type === 'doWhile') {
|
|
28
|
-
await executeDoWhile(node.def, exec, deps.emitter, deps.updateFn);
|
|
35
|
+
await executeDoWhile(node.def, exec, deps.emitter, deps.updateFn, deps.assertActive);
|
|
29
36
|
}
|
|
30
37
|
else if (node.type === 'forEach') {
|
|
31
|
-
await executeForEach(node.def, exec, deps.emitter, deps.updateFn);
|
|
38
|
+
await executeForEach(node.def, exec, deps.emitter, deps.updateFn, deps.assertActive);
|
|
32
39
|
}
|
|
33
40
|
else if (node.type === 'map') {
|
|
34
|
-
await executeMap(node.def, exec, deps.emitter, deps.updateFn);
|
|
41
|
+
await executeMap(node.def, exec, deps.emitter, deps.updateFn, deps.assertActive);
|
|
35
42
|
}
|
|
36
43
|
else if (node.type === 'pivot') {
|
|
37
44
|
// A pivot commits the saga before the cursor moves beyond it.
|
|
38
45
|
exec.committedAt = idx;
|
|
39
|
-
deps.
|
|
46
|
+
deps.updateFn(exec);
|
|
40
47
|
}
|
|
41
48
|
else {
|
|
42
49
|
await runWaitFor(deps.waitFor, exec, node, idx, wf);
|
|
43
50
|
return;
|
|
44
51
|
}
|
|
52
|
+
deps.assertActive();
|
|
45
53
|
await deps.advance(exec, idx + 1, wf);
|
|
46
54
|
}
|
|
47
55
|
async function runBranch(deps, exec, node, idx) {
|
|
48
|
-
|
|
56
|
+
deps.assertActive();
|
|
57
|
+
const pathName = await resolveDecision(exec, branchDecisionKey(idx), () => node.def.condition(buildContext(exec)), deps.updateFn, deps.assertActive);
|
|
58
|
+
deps.assertActive();
|
|
49
59
|
if (typeof pathName !== 'string') {
|
|
50
60
|
throw new Error(`Branch at node ${idx} returned a non-string path`);
|
|
51
61
|
}
|
|
@@ -66,26 +76,37 @@ async function runBranch(deps, exec, node, idx) {
|
|
|
66
76
|
await executeStepWithRetry(step, buildContext(exec), exec, {
|
|
67
77
|
emitter: deps.emitter,
|
|
68
78
|
updateFn: deps.updateFn,
|
|
79
|
+
assertActive: deps.assertActive,
|
|
69
80
|
});
|
|
81
|
+
deps.assertActive();
|
|
70
82
|
}
|
|
71
83
|
}
|
|
72
84
|
async function runSubWorkflow(deps, exec, node, idx) {
|
|
73
|
-
|
|
85
|
+
deps.assertActive();
|
|
86
|
+
const subInput = await resolveDecision(exec, subWorkflowInputDecisionKey(idx), () => structuredClone(node.inputMapper(buildContext(exec))), deps.updateFn, deps.assertActive);
|
|
87
|
+
deps.assertActive();
|
|
74
88
|
const recordKey = `sub:${node.name}`;
|
|
75
89
|
const childExecutionId = await adoptExistingChild(deps, exec, node.name, recordKey);
|
|
90
|
+
deps.assertActive();
|
|
76
91
|
try {
|
|
77
92
|
const { results, executionId } = await executeSubWorkflow(node.name, subInput, async (name, input) => {
|
|
78
93
|
const handle = await deps.start(name, input, exec.id);
|
|
94
|
+
deps.assertActive();
|
|
79
95
|
// Claim the child before polling it. A restart can then resume the existing
|
|
80
96
|
// child instead of starting another one.
|
|
81
97
|
exec.steps[recordKey] = { status: 'running', childExecutionId: handle.id };
|
|
82
|
-
deps.
|
|
98
|
+
deps.updateFn(exec);
|
|
83
99
|
return handle;
|
|
84
|
-
}, (id) =>
|
|
100
|
+
}, (id) => {
|
|
101
|
+
deps.assertActive();
|
|
102
|
+
return deps.store.get(id);
|
|
103
|
+
}, {
|
|
85
104
|
pollIntervalMs: node.pollInterval,
|
|
86
105
|
maxWaitMs: node.timeout,
|
|
87
106
|
existingChildId: childExecutionId,
|
|
107
|
+
assertActive: deps.assertActive,
|
|
88
108
|
});
|
|
109
|
+
deps.assertActive();
|
|
89
110
|
exec.steps[recordKey] = {
|
|
90
111
|
status: 'completed',
|
|
91
112
|
result: results,
|
|
@@ -94,11 +115,15 @@ async function runSubWorkflow(deps, exec, node, idx) {
|
|
|
94
115
|
};
|
|
95
116
|
}
|
|
96
117
|
catch (error) {
|
|
97
|
-
|
|
118
|
+
if (isWorkflowExecutionClosed(error))
|
|
119
|
+
throw error;
|
|
120
|
+
deps.assertActive();
|
|
121
|
+
settleFailedChild(deps.updateFn, exec, recordKey, error);
|
|
98
122
|
throw error;
|
|
99
123
|
}
|
|
100
124
|
}
|
|
101
125
|
async function adoptExistingChild(deps, exec, workflowName, recordKey) {
|
|
126
|
+
deps.assertActive();
|
|
102
127
|
const claimedId = exec.steps[recordKey]?.childExecutionId;
|
|
103
128
|
const child = claimedId ? deps.store.get(claimedId) : deps.store.findChild(exec.id, workflowName);
|
|
104
129
|
if (!child)
|
|
@@ -109,16 +134,18 @@ async function adoptExistingChild(deps, exec, workflowName, recordKey) {
|
|
|
109
134
|
status: 'running',
|
|
110
135
|
childExecutionId: child.id,
|
|
111
136
|
};
|
|
112
|
-
deps.
|
|
137
|
+
deps.updateFn(exec);
|
|
113
138
|
}
|
|
114
139
|
// The child row is durable before its initial queue publication. A crash in that
|
|
115
140
|
// gap leaves it running but undelivered; duplicate publication is safe because node
|
|
116
141
|
// admission and persisted outcomes make it idempotent.
|
|
117
|
-
if (child.state === 'running')
|
|
142
|
+
if (child.state === 'running') {
|
|
118
143
|
await deps.enqueue(child);
|
|
144
|
+
deps.assertActive();
|
|
145
|
+
}
|
|
119
146
|
return child.id;
|
|
120
147
|
}
|
|
121
|
-
function settleFailedChild(
|
|
148
|
+
function settleFailedChild(updateFn, exec, recordKey, error) {
|
|
122
149
|
const claimed = exec.steps[recordKey];
|
|
123
150
|
if (!claimed)
|
|
124
151
|
return;
|
|
@@ -129,9 +156,11 @@ function settleFailedChild(store, exec, recordKey, error) {
|
|
|
129
156
|
completedAt: clock().now(),
|
|
130
157
|
};
|
|
131
158
|
try {
|
|
132
|
-
|
|
159
|
+
updateFn(exec);
|
|
133
160
|
}
|
|
134
|
-
catch {
|
|
161
|
+
catch (writeError) {
|
|
162
|
+
if (isWorkflowExecutionClosed(writeError))
|
|
163
|
+
throw writeError;
|
|
135
164
|
// The generic failure path persists this in-memory record. Do not replace the
|
|
136
165
|
// child's diagnostic with an incidental write error.
|
|
137
166
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Durable workflow-node publication shared by execution and recovery. */
|
|
2
|
+
import type { Queue } from '../queue/queue';
|
|
3
|
+
import type { Execution } from './types';
|
|
4
|
+
/**
|
|
5
|
+
* Publish one node without a deterministic job ID. The cursor and in-flight guards
|
|
6
|
+
* discard overlapping duplicates, while queue-level dedup could suppress a legitimate
|
|
7
|
+
* re-enqueue after recovery and wedge the execution permanently.
|
|
8
|
+
*/
|
|
9
|
+
export declare function enqueueWorkflowStep(queue: Queue, exec: Execution, assertActive: () => void): Promise<void>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Durable workflow-node publication shared by execution and recovery. */
|
|
2
|
+
/**
|
|
3
|
+
* Publish one node without a deterministic job ID. The cursor and in-flight guards
|
|
4
|
+
* discard overlapping duplicates, while queue-level dedup could suppress a legitimate
|
|
5
|
+
* re-enqueue after recovery and wedge the execution permanently.
|
|
6
|
+
*/
|
|
7
|
+
export async function enqueueWorkflowStep(queue, exec, assertActive) {
|
|
8
|
+
assertActive();
|
|
9
|
+
const jobData = {
|
|
10
|
+
executionId: exec.id,
|
|
11
|
+
workflowName: exec.workflowName,
|
|
12
|
+
nodeIndex: exec.currentNodeIndex,
|
|
13
|
+
};
|
|
14
|
+
await queue.add('wf:step', jobData);
|
|
15
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Durable forEach execution, split from conditional loop runners. */
|
|
2
|
+
import type { Execution, ForEachDefinition } from './types';
|
|
3
|
+
import type { WorkflowEmitter } from './emitter';
|
|
4
|
+
export declare function executeForEach(def: ForEachDefinition, exec: Execution, emitter: WorkflowEmitter | null, updateFn: (exec: Execution) => void, assertActive?: () => void): Promise<void>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/** Durable forEach execution, split from conditional loop runners. */
|
|
2
|
+
import { assertWorkflowActive, isWorkflowExecutionClosed } from './executionFence';
|
|
3
|
+
import { buildContext, executeStepWithRetry } from './runner';
|
|
4
|
+
import { forEachItemsDecisionKey, resolveDecision } from './workflowDecisions';
|
|
5
|
+
export async function executeForEach(def, exec, emitter, updateFn, assertActive = assertWorkflowActive) {
|
|
6
|
+
assertActive();
|
|
7
|
+
const items = await resolveDecision(exec, forEachItemsDecisionKey(def.step.name), () => structuredClone(def.items(buildContext(exec))), updateFn, assertActive);
|
|
8
|
+
assertActive();
|
|
9
|
+
// Array-likes used to report success while doing the wrong amount of work: a number
|
|
10
|
+
// ran zero items and a string ran one item per character.
|
|
11
|
+
if (!Array.isArray(items)) {
|
|
12
|
+
throw new Error(`forEach items must be an array, got ${items === null ? 'null' : typeof items}`);
|
|
13
|
+
}
|
|
14
|
+
if (items.length > def.maxIterations) {
|
|
15
|
+
throw new Error(`forEach items (${items.length}) exceeds maxIterations (${def.maxIterations})`);
|
|
16
|
+
}
|
|
17
|
+
for (let i = 0; i < items.length; i++) {
|
|
18
|
+
assertActive();
|
|
19
|
+
const item = structuredClone(items[i]);
|
|
20
|
+
const indexedName = `${def.step.name}:${i}`;
|
|
21
|
+
const indexedStep = {
|
|
22
|
+
...def.step,
|
|
23
|
+
name: indexedName,
|
|
24
|
+
handler: (stepCtx) => def.step.handler({
|
|
25
|
+
...stepCtx,
|
|
26
|
+
steps: { ...stepCtx.steps, __item: item, __index: i },
|
|
27
|
+
}),
|
|
28
|
+
};
|
|
29
|
+
// A completed item is memoised; the bare name remains the aggregate view of the
|
|
30
|
+
// last iteration for loop conditions and downstream steps.
|
|
31
|
+
const completed = exec.steps[indexedName];
|
|
32
|
+
if (completed?.status === 'completed') {
|
|
33
|
+
exec.steps[def.step.name] = { ...completed };
|
|
34
|
+
updateFn(exec);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
let thrown;
|
|
38
|
+
let threw = false;
|
|
39
|
+
try {
|
|
40
|
+
await executeStepWithRetry(indexedStep, buildContext(exec), exec, { emitter, updateFn, assertActive }, i);
|
|
41
|
+
assertActive();
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
if (isWorkflowExecutionClosed(error))
|
|
45
|
+
throw error;
|
|
46
|
+
assertActive();
|
|
47
|
+
thrown = error;
|
|
48
|
+
threw = true;
|
|
49
|
+
}
|
|
50
|
+
// Persist the item identity on both success and failure. A failed provider call
|
|
51
|
+
// can still need compensation, and its reversal needs the original item/index.
|
|
52
|
+
const record = exec.steps[indexedName];
|
|
53
|
+
if (record) {
|
|
54
|
+
record.loopItem = item;
|
|
55
|
+
record.loopIndex = i;
|
|
56
|
+
exec.steps[def.step.name] = { ...record };
|
|
57
|
+
try {
|
|
58
|
+
updateFn(exec);
|
|
59
|
+
}
|
|
60
|
+
catch (writeError) {
|
|
61
|
+
if (isWorkflowExecutionClosed(writeError))
|
|
62
|
+
throw writeError;
|
|
63
|
+
if (!threw)
|
|
64
|
+
throw writeError;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (threw)
|
|
68
|
+
throw thrown;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Loop execution logic for the Workflow Engine.
|
|
3
3
|
*/
|
|
4
|
-
import type { Execution, LoopDefinition
|
|
4
|
+
import type { Execution, LoopDefinition } from './types';
|
|
5
5
|
import type { WorkflowEmitter } from './emitter';
|
|
6
6
|
export { executeMap } from './mapRunner';
|
|
7
|
+
export { executeForEach } from './forEachRunner';
|
|
7
8
|
/** Execute a doUntil loop: run steps, then check condition. Repeat until condition returns true. */
|
|
8
|
-
export declare function executeDoUntil(def: LoopDefinition, exec: Execution, emitter: WorkflowEmitter | null, updateFn: (exec: Execution) => void): Promise<void>;
|
|
9
|
+
export declare function executeDoUntil(def: LoopDefinition, exec: Execution, emitter: WorkflowEmitter | null, updateFn: (exec: Execution) => void, assertActive?: () => void): Promise<void>;
|
|
9
10
|
/** Execute a doWhile loop: check condition first, then run steps. Repeat while condition is true. */
|
|
10
|
-
export declare function executeDoWhile(def: LoopDefinition, exec: Execution, emitter: WorkflowEmitter | null, updateFn: (exec: Execution) => void): Promise<void>;
|
|
11
|
-
/** Execute a forEach loop: iterate over items, executing the step for each */
|
|
12
|
-
export declare function executeForEach(def: ForEachDefinition, exec: Execution, emitter: WorkflowEmitter | null, updateFn: (exec: Execution) => void): Promise<void>;
|
|
11
|
+
export declare function executeDoWhile(def: LoopDefinition, exec: Execution, emitter: WorkflowEmitter | null, updateFn: (exec: Execution) => void, assertActive?: () => void): Promise<void>;
|