@lostgradient/weft 0.2.1 → 0.4.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 +123 -43
- package/dist/cli/conformance.js +7 -1
- package/dist/cli/generated/operation-client.generated.d.ts +55 -13
- package/dist/cli/generated/operation-client.generated.js +3 -0
- package/dist/cli/help-text.d.ts +1 -1
- package/dist/cli/help-text.js +1 -0
- package/dist/cli/operation-catalog-snapshot.d.ts +8 -0
- package/dist/cli/operation-catalog-snapshot.js +11 -0
- package/dist/cli/parse-schedule-arguments.js +3 -1
- package/dist/cli/schedule.js +2 -1
- package/dist/cli/types.d.ts +1 -0
- package/dist/cli-main.js +80 -79
- package/dist/client/handle-delegation.d.ts +7 -2
- package/dist/client/handle-delegation.js +9 -1
- package/dist/client/http-client-requests.d.ts +4 -1
- package/dist/client/http-client-requests.js +10 -0
- package/dist/client/http-client.d.ts +10 -4
- package/dist/client/http-client.js +18 -1
- package/dist/client/http-request.d.ts +11 -1
- package/dist/client/http-request.js +17 -7
- package/dist/client/index.d.ts +1 -1
- package/dist/client/interface.d.ts +100 -4
- package/dist/client/local.d.ts +7 -1
- package/dist/client/local.js +17 -2
- package/dist/client/start-body.d.ts +9 -2
- package/dist/client/start-body.js +13 -4
- package/dist/core/byte-arrays.d.ts +5 -0
- package/dist/core/byte-arrays.js +5 -0
- package/dist/core/checkpoint/lifecycle.d.ts +3 -0
- package/dist/core/checkpoint/lifecycle.js +9 -4
- package/dist/core/checkpoint/serialization.js +12 -5
- package/dist/core/codec/extension-codec.d.ts +16 -0
- package/dist/core/codec/extension-codec.js +104 -8
- package/dist/core/codec/index.d.ts +1 -0
- package/dist/core/codec/index.js +1 -0
- package/dist/core/codec/serializer-registry.d.ts +122 -0
- package/dist/core/codec/serializer-registry.js +51 -0
- package/dist/core/context/activity-retry-state.d.ts +38 -0
- package/dist/core/context/activity-retry-state.js +89 -0
- package/dist/core/context/activity-schedule-to-close.d.ts +159 -0
- package/dist/core/context/activity-schedule-to-close.js +69 -0
- package/dist/core/context/child-workflow-pipe.d.ts +5 -5
- package/dist/core/context/child-workflow-pipe.js +15 -2
- package/dist/core/context/context-presence.d.ts +19 -0
- package/dist/core/context/context-presence.js +13 -0
- package/dist/core/context/durable-operations.d.ts +25 -0
- package/dist/core/context/durable-operations.js +64 -0
- package/dist/core/context/index.d.ts +18 -5
- package/dist/core/context/index.js +17 -12
- package/dist/core/context/internals.d.ts +29 -0
- package/dist/core/context/internals.js +6 -1
- package/dist/core/context/operation-request.d.ts +35 -0
- package/dist/core/context/parallel-cache-entry.d.ts +14 -5
- package/dist/core/context/run-operation.d.ts +21 -3
- package/dist/core/context/run-operation.js +56 -93
- package/dist/core/context/session-state.js +7 -8
- package/dist/core/context/speculative-child.js +2 -0
- package/dist/core/context/types.d.ts +15 -0
- package/dist/core/context/version-patching.d.ts +8 -0
- package/dist/core/context/version-patching.js +40 -0
- package/dist/core/context/workflow-logger.d.ts +98 -0
- package/dist/core/context/workflow-logger.js +68 -0
- package/dist/core/engine/activity-heartbeat-tracking.d.ts +64 -0
- package/dist/core/engine/activity-heartbeat-tracking.js +42 -0
- package/dist/core/engine/activity-per-attempt-timeout.d.ts +41 -0
- package/dist/core/engine/activity-per-attempt-timeout.js +36 -0
- package/dist/core/engine/activity-reconciliation.d.ts +1 -0
- package/dist/core/engine/activity-reconciliation.js +12 -1
- package/dist/core/engine/activity-resolution.d.ts +23 -0
- package/dist/core/engine/activity-resolution.js +37 -0
- package/dist/core/engine/aggregate.js +66 -30
- package/dist/core/engine/async-activity-completion.d.ts +23 -6
- package/dist/core/engine/async-activity-completion.js +53 -12
- package/dist/core/engine/attributes-tags.js +9 -4
- package/dist/core/engine/bulk-operations-purge.d.ts +26 -0
- package/dist/core/engine/bulk-operations-purge.js +38 -3
- package/dist/core/engine/bulk-operations-shared.d.ts +2 -0
- package/dist/core/engine/bulk-operations-shared.js +8 -2
- package/dist/core/engine/bulk-operations.d.ts +4 -1
- package/dist/core/engine/bulk-operations.js +235 -24
- package/dist/core/engine/callback-creators-bundles.d.ts +9 -0
- package/dist/core/engine/callback-creators-bundles.js +30 -2
- package/dist/core/engine/callback-creators-core.js +6 -4
- package/dist/core/engine/callback-creators-router.js +10 -0
- package/dist/core/engine/callback-creators-schedule.d.ts +2 -2
- package/dist/core/engine/callback-creators-schedule.js +12 -8
- package/dist/core/engine/candidate-read-batching.d.ts +6 -0
- package/dist/core/engine/candidate-read-batching.js +1 -0
- package/dist/core/engine/checkpoint-io.js +61 -25
- package/dist/core/engine/checkpoint-reads.js +3 -2
- package/dist/core/engine/checkpoint-replay.d.ts +20 -0
- package/dist/core/engine/checkpoint-replay.js +242 -0
- package/dist/core/engine/checkpoint-side-effects.d.ts +23 -0
- package/dist/core/engine/checkpoint-side-effects.js +35 -0
- package/dist/core/engine/child-workflow-cancellation.d.ts +10 -0
- package/dist/core/engine/child-workflow-cancellation.js +40 -0
- package/dist/core/engine/child-workflow.js +27 -4
- package/dist/core/engine/condition-waiters.d.ts +14 -0
- package/dist/core/engine/condition-waiters.js +5 -0
- package/dist/core/engine/construction.d.ts +3 -1
- package/dist/core/engine/construction.js +30 -8
- package/dist/core/engine/coordination-branch-executors.d.ts +70 -0
- package/dist/core/engine/coordination-branch-executors.js +107 -0
- package/dist/core/engine/deferred-consume-envelope.d.ts +59 -0
- package/dist/core/engine/deferred-consume-envelope.js +14 -0
- package/dist/core/engine/disposal.js +25 -0
- package/dist/core/engine/engine-create-types.d.ts +14 -18
- package/dist/core/engine/engine-internal-types.d.ts +38 -3
- package/dist/core/engine/engine-leak-warnings.d.ts +6 -0
- package/dist/core/engine/engine-leak-warnings.js +4 -0
- package/dist/core/engine/engine-runtime-helpers.d.ts +17 -0
- package/dist/core/engine/engine-runtime-helpers.js +26 -5
- package/dist/core/engine/errors.d.ts +121 -0
- package/dist/core/engine/errors.js +45 -1
- package/dist/core/engine/event-log-compaction.d.ts +8 -0
- package/dist/core/engine/event-log-compaction.js +20 -2
- package/dist/core/engine/fenced-write.d.ts +65 -0
- package/dist/core/engine/fenced-write.js +71 -0
- package/dist/core/engine/finalizer-state.d.ts +28 -0
- package/dist/core/engine/finalizer-state.js +16 -0
- package/dist/core/engine/handle-result.js +1 -1
- package/dist/core/engine/handles.d.ts +97 -40
- package/dist/core/engine/handles.js +25 -27
- package/dist/core/engine/index.d.ts +125 -15
- package/dist/core/engine/index.js +207 -11
- package/dist/core/engine/inline-launch-queue.d.ts +14 -0
- package/dist/core/engine/inline-launch-queue.js +32 -7
- package/dist/core/engine/inline-parking.js +3 -2
- package/dist/core/engine/internals.d.ts +125 -13
- package/dist/core/engine/lease-codec.d.ts +35 -0
- package/dist/core/engine/lease-codec.js +32 -0
- package/dist/core/engine/lease-deposition.d.ts +66 -0
- package/dist/core/engine/lease-deposition.js +10 -0
- package/dist/core/engine/lease-errors.d.ts +99 -0
- package/dist/core/engine/lease-errors.js +30 -0
- package/dist/core/engine/lease-manager.d.ts +114 -0
- package/dist/core/engine/lease-manager.js +135 -0
- package/dist/core/engine/lifecycle/fork-helpers.js +1 -7
- package/dist/core/engine/lifecycle/persist.js +15 -52
- package/dist/core/engine/lifecycle/recovered-services.d.ts +8 -6
- package/dist/core/engine/lifecycle/recovered-services.js +18 -3
- package/dist/core/engine/lifecycle/resume.js +39 -11
- package/dist/core/engine/lifecycle/shared.d.ts +8 -1
- package/dist/core/engine/lifecycle/start-batch.d.ts +1 -1
- package/dist/core/engine/lifecycle/start-batch.js +2 -1
- package/dist/core/engine/lifecycle/start-commit.d.ts +56 -0
- package/dist/core/engine/lifecycle/start-commit.js +69 -0
- package/dist/core/engine/lifecycle/start-exec.d.ts +30 -2
- package/dist/core/engine/lifecycle/start-exec.js +38 -0
- package/dist/core/engine/lifecycle/start-or-signal-create.d.ts +58 -0
- package/dist/core/engine/lifecycle/start-or-signal-create.js +95 -0
- package/dist/core/engine/lifecycle/start-or-signal-resolution.d.ts +79 -0
- package/dist/core/engine/lifecycle/start-or-signal-resolution.js +60 -0
- package/dist/core/engine/lifecycle/start-or-signal.d.ts +46 -0
- package/dist/core/engine/lifecycle/start-or-signal.js +71 -0
- package/dist/core/engine/lifecycle/start-terminal-conflict-purge.d.ts +39 -0
- package/dist/core/engine/lifecycle/start-terminal-conflict-purge.js +28 -0
- package/dist/core/engine/lifecycle/start.d.ts +6 -6
- package/dist/core/engine/lifecycle/start.js +47 -45
- package/dist/core/engine/lifecycle/transition.js +17 -5
- package/dist/core/engine/lifecycle.d.ts +3 -2
- package/dist/core/engine/lifecycle.js +9 -2
- package/dist/core/engine/list-candidate-resolution.js +2 -2
- package/dist/core/engine/listing.js +10 -6
- package/dist/core/engine/operations-activity.d.ts +4 -11
- package/dist/core/engine/operations-activity.js +22 -49
- package/dist/core/engine/operations-coordination.d.ts +10 -0
- package/dist/core/engine/operations-coordination.js +43 -8
- package/dist/core/engine/operations-router.d.ts +6 -0
- package/dist/core/engine/operations-router.js +2 -0
- package/dist/core/engine/operations-speculate.js +6 -2
- package/dist/core/engine/operations-stream.js +12 -2
- package/dist/core/engine/operations-time.d.ts +4 -2
- package/dist/core/engine/operations-time.js +14 -2
- package/dist/core/engine/operations-wait-condition.d.ts +23 -0
- package/dist/core/engine/operations-wait-condition.js +62 -0
- package/dist/core/engine/ownership-options.d.ts +23 -0
- package/dist/core/engine/ownership-options.js +26 -0
- package/dist/core/engine/payload-size-policy.d.ts +3 -0
- package/dist/core/engine/payload-size-policy.js +4 -0
- package/dist/core/engine/pending-updates.d.ts +17 -0
- package/dist/core/engine/pending-updates.js +33 -11
- package/dist/core/engine/persisted-data-version.d.ts +5 -9
- package/dist/core/engine/persisted-data-version.js +4 -5
- package/dist/core/engine/queries.js +1 -1
- package/dist/core/engine/registration.d.ts +1 -0
- package/dist/core/engine/registration.js +18 -3
- package/dist/core/engine/schedule-handle.d.ts +45 -0
- package/dist/core/engine/schedule-handle.js +26 -0
- package/dist/core/engine/schedule-jitter.d.ts +5 -0
- package/dist/core/engine/schedule-jitter.js +10 -0
- package/dist/core/engine/schedule-run.d.ts +17 -0
- package/dist/core/engine/schedule-run.js +38 -0
- package/dist/core/engine/schedule-timer.js +81 -17
- package/dist/core/engine/schedules.d.ts +8 -5
- package/dist/core/engine/schedules.js +44 -19
- package/dist/core/engine/search-attribute-records.d.ts +4 -0
- package/dist/core/engine/search-attribute-records.js +17 -0
- package/dist/core/engine/second-instance-detector.d.ts +96 -0
- package/dist/core/engine/second-instance-detector.js +108 -0
- package/dist/core/engine/signals.d.ts +38 -0
- package/dist/core/engine/signals.js +55 -19
- package/dist/core/engine/state-utilities.d.ts +49 -2
- package/dist/core/engine/state-utilities.js +114 -3
- package/dist/core/engine/storage-io.d.ts +16 -2
- package/dist/core/engine/storage-io.js +26 -5
- package/dist/core/engine/stream-chunk-loading.d.ts +2 -0
- package/dist/core/engine/stream-chunk-loading.js +24 -1
- package/dist/core/engine/sub-operation.js +22 -3
- package/dist/core/engine/termination/cleanup.d.ts +28 -1
- package/dist/core/engine/termination/cleanup.js +44 -2
- package/dist/core/engine/termination/complete.js +48 -9
- package/dist/core/engine/termination/finalizer-activity.d.ts +53 -0
- package/dist/core/engine/termination/finalizer-activity.js +30 -0
- package/dist/core/engine/termination/finalizer-claim.d.ts +110 -0
- package/dist/core/engine/termination/finalizer-claim.js +80 -0
- package/dist/core/engine/termination/finalizer.d.ts +48 -0
- package/dist/core/engine/termination/finalizer.js +159 -0
- package/dist/core/engine/termination/suspend.d.ts +70 -0
- package/dist/core/engine/termination/suspend.js +41 -0
- package/dist/core/engine/termination.d.ts +5 -2
- package/dist/core/engine/termination.js +6 -0
- package/dist/core/engine/updates.js +5 -25
- package/dist/core/engine/validation/schedule.d.ts +2 -1
- package/dist/core/engine/validation/schedule.js +51 -2
- package/dist/core/engine/validation.js +54 -3
- package/dist/core/engine/waiting-update-response.d.ts +8 -0
- package/dist/core/engine/waiting-update-response.js +22 -0
- package/dist/core/engine/workflow-concurrency.d.ts +10 -0
- package/dist/core/engine/workflow-concurrency.js +136 -0
- package/dist/core/engine/workflow-feed.d.ts +9 -8
- package/dist/core/engine/workflow-feed.js +4 -1
- package/dist/core/engine/workflow-indexes.d.ts +9 -0
- package/dist/core/engine/workflow-indexes.js +10 -0
- package/dist/core/engine/workflow-state-stream.js +33 -13
- package/dist/core/engine/workflow-visibility-queries.d.ts +2 -2
- package/dist/core/engine-helpers.js +1 -0
- package/dist/core/events/activity-events.d.ts +43 -12
- package/dist/core/events/activity-events.js +28 -0
- package/dist/core/events/attribute-events.d.ts +2 -3
- package/dist/core/events/event-map.d.ts +19 -12
- package/dist/core/events/index.d.ts +1 -0
- package/dist/core/events/index.js +1 -0
- package/dist/core/events/schedule-events.d.ts +71 -0
- package/dist/core/events/schedule-events.js +29 -0
- package/dist/core/events/signal-events.d.ts +4 -6
- package/dist/core/events/system-events.d.ts +29 -19
- package/dist/core/events/system-events.js +26 -0
- package/dist/core/events/update-events.d.ts +6 -8
- package/dist/core/events/workflow-events.d.ts +108 -20
- package/dist/core/events/workflow-events.js +35 -0
- package/dist/core/failure-categories.js +5 -17
- package/dist/core/fault-code.d.ts +9 -3
- package/dist/core/fault-code.js +1 -0
- package/dist/core/inline-execution-strategy.context-options.d.ts +69 -0
- package/dist/core/inline-execution-strategy.context-options.js +56 -0
- package/dist/core/inline-execution-strategy.d.ts +31 -36
- package/dist/core/inline-execution-strategy.js +16 -27
- package/dist/core/list-filter-validation.d.ts +1 -1
- package/dist/core/list-filter-validation.js +2 -1
- package/dist/core/payload-size.d.ts +1 -1
- package/dist/core/persisted-data-incompatible-error.d.ts +9 -7
- package/dist/core/review/events.d.ts +6 -8
- package/dist/core/scheduler/scheduler-class.d.ts +10 -1
- package/dist/core/scheduler/scheduler-class.js +38 -11
- package/dist/core/scheduler/timer-batch.js +9 -1
- package/dist/core/scheduler/timer-sources.d.ts +1 -0
- package/dist/core/scheduler/timer-sources.js +11 -5
- package/dist/core/session-state.d.ts +0 -1
- package/dist/core/session-state.js +2 -2
- package/dist/core/start-workflow-validation.d.ts +56 -0
- package/dist/core/start-workflow-validation.js +30 -1
- package/dist/core/step-context.d.ts +10 -6
- package/dist/core/step-context.js +7 -15
- package/dist/core/types/activity-verification.d.ts +67 -0
- package/dist/core/types/activity-verification.js +0 -0
- package/dist/core/types/activity.d.ts +86 -64
- package/dist/core/types/bulk.d.ts +50 -9
- package/dist/core/types/checkpoint.d.ts +44 -8
- package/dist/core/types/identity.d.ts +8 -1
- package/dist/core/types/launch-metadata.d.ts +33 -0
- package/dist/core/types/launch-metadata.js +0 -0
- package/dist/core/types/list-options.d.ts +146 -0
- package/dist/core/types/list-options.js +0 -0
- package/dist/core/types/message-handles.d.ts +25 -0
- package/dist/core/types/options.d.ts +165 -215
- package/dist/core/types/reviews.d.ts +2 -1
- package/dist/core/types/schedules.d.ts +39 -3
- package/dist/core/types/services-resolution.d.ts +47 -0
- package/dist/core/types/services-resolution.js +0 -0
- package/dist/core/types/state.d.ts +11 -11
- package/dist/core/types/workflow-builder-runtime.d.ts +12 -3
- package/dist/core/types/workflow-builder-runtime.js +11 -2
- package/dist/core/types/workflow-builder.d.ts +47 -15
- package/dist/core/types/workflow-concurrency.d.ts +39 -0
- package/dist/core/types/workflow-concurrency.js +0 -0
- package/dist/core/types/workflow-context.d.ts +157 -14
- package/dist/core/types/workflow-definition.d.ts +53 -10
- package/dist/core/types/workflow-function.d.ts +101 -9
- package/dist/core/types/workflow-log.d.ts +98 -0
- package/dist/core/types/workflow-log.js +0 -0
- package/dist/core/types/workflow-registries.d.ts +14 -3
- package/dist/core/types/workflow-registry.d.ts +3 -0
- package/dist/core/types/workflow-snapshot.d.ts +29 -0
- package/dist/core/types/workflow-snapshot.js +0 -0
- package/dist/core/types.d.ts +7 -0
- package/dist/core/types.js +7 -0
- package/dist/core/versioning.d.ts +9 -39
- package/dist/core/versioning.js +2 -12
- package/dist/core/weft-error.d.ts +44 -1
- package/dist/core/weft-error.js +19 -1
- package/dist/core/worker-execution-strategy-options.d.ts +26 -0
- package/dist/core/worker-execution-strategy.js +40 -36
- package/dist/core/worker-inbound-message.d.ts +38 -0
- package/dist/core/worker-inbound-message.js +37 -0
- package/dist/core/worker-log-abuse-counter.d.ts +175 -0
- package/dist/core/worker-log-abuse-counter.js +107 -0
- package/dist/core/worker-message-helpers.d.ts +47 -0
- package/dist/core/worker-message-helpers.js +26 -0
- package/dist/core/worker-protocol-guard.js +1 -1
- package/dist/core/worker-protocol-log.d.ts +56 -0
- package/dist/core/worker-protocol-log.js +18 -0
- package/dist/core/worker-protocol.d.ts +23 -0
- package/dist/core/worker-protocol.js +12 -2
- package/dist/core/workflow-version-tuple.d.ts +3 -9
- package/dist/core/workflow-version-tuple.js +2 -3
- package/dist/diagnostics/doctor.js +6 -3
- package/dist/diagnostics/format.js +3 -8
- package/dist/diagnostics/types.d.ts +5 -5
- package/dist/diagnostics/validate.d.ts +0 -1
- package/dist/diagnostics/version-check.d.ts +1 -1
- package/dist/diagnostics/version-check.js +10 -13
- package/dist/index.d.ts +13 -11
- package/dist/index.js +39 -4
- package/dist/json-schema.js +3 -3
- package/dist/mcp/cli.js +35 -35
- package/dist/mcp/dispatcher.js +1 -1
- package/dist/mcp/http.js +16 -6
- package/dist/mcp/list-filter.js +2 -1
- package/dist/mcp/protocol.d.ts +2 -0
- package/dist/mcp/protocol.js +1 -1
- package/dist/mcp/session.d.ts +16 -0
- package/dist/mcp/session.js +60 -6
- package/dist/mcp/tools.js +73 -6
- package/dist/observability/index.js +2 -2
- package/dist/server/asyncapi-channels.js +5 -2
- package/dist/server/attribute-filters.d.ts +8 -3
- package/dist/server/attribute-filters.js +6 -1
- package/dist/server/authentication/api-key.d.ts +2 -1
- package/dist/server/authentication/api-key.js +5 -2
- package/dist/server/authentication/constant-time-api-key.d.ts +15 -0
- package/dist/server/authentication/constant-time-api-key.js +24 -0
- package/dist/server/authentication/index.d.ts +1 -1
- package/dist/server/authentication/index.js +7 -4
- package/dist/server/authentication/rotating-api-key-store.d.ts +5 -3
- package/dist/server/authentication/rotating-api-key-store.js +19 -11
- package/dist/server/engine-event-feed-backend.d.ts +4 -5
- package/dist/server/fault-to-http.js +8 -1
- package/dist/server/fault-to-json-rpc.js +1 -0
- package/dist/server/fleet-event-feed.d.ts +31 -0
- package/dist/server/fleet-event-feed.js +157 -0
- package/dist/server/handler/index.js +1 -1
- package/dist/server/handler/response-helpers.js +2 -1
- package/dist/server/handler/route-dispatch.d.ts +6 -3
- package/dist/server/handler/route-dispatch.js +2 -2
- package/dist/server/handler.js +31 -31
- package/dist/server/index.d.ts +32 -2
- package/dist/server/index.js +35 -35
- package/dist/server/interactive-operations.js +1 -0
- package/dist/server/json-rpc-transport-helpers.d.ts +1 -0
- package/dist/server/json-rpc-transport-helpers.js +2 -1
- package/dist/server/json-rpc-websocket-runtime.d.ts +11 -2
- package/dist/server/json-rpc-websocket-runtime.js +1 -0
- package/dist/server/json-rpc-websocket-subscriptions.d.ts +10 -0
- package/dist/server/json-rpc-websocket-subscriptions.js +29 -0
- package/dist/server/json-rpc-websocket-types.d.ts +22 -0
- package/dist/server/json-rpc-websocket-types.js +0 -0
- package/dist/server/json-rpc-websocket-validation.js +20 -4
- package/dist/server/json-rpc-websocket.d.ts +2 -43
- package/dist/server/json-rpc-websocket.js +57 -74
- package/dist/server/openapi.d.ts +2 -1
- package/dist/server/openapi.js +1 -3
- package/dist/server/openrpc-document-schema.d.ts +78 -0
- package/dist/server/openrpc-document-schema.js +27 -1
- package/dist/server/openrpc-errors.d.ts +3 -0
- package/dist/server/openrpc-errors.js +2 -1
- package/dist/server/openrpc.js +32 -0
- package/dist/server/operation-catalog/index.d.ts +1 -1
- package/dist/server/operation-catalog/pipeline-helpers.js +1 -0
- package/dist/server/operation-catalog/pipeline-stages.js +26 -12
- package/dist/server/operation-catalog/pipeline.js +8 -1
- package/dist/server/operation-catalog/registry.js +13 -0
- package/dist/server/operation-catalog/types.d.ts +20 -9
- package/dist/server/operation-fault.d.ts +10 -0
- package/dist/server/operation-fault.js +2 -0
- package/dist/server/operation-registry.d.ts +12 -11
- package/dist/server/operation-registry.js +12 -3
- package/dist/server/operations/aggregate-workflows.d.ts +15 -15
- package/dist/server/operations/async-activity.d.ts +2 -2
- package/dist/server/operations/async-activity.js +12 -7
- package/dist/server/operations/bulk-cancel-workflows.d.ts +1 -1
- package/dist/server/operations/bulk-cancel-workflows.js +2 -2
- package/dist/server/operations/bulk-delete-workflows.d.ts +1 -1
- package/dist/server/operations/bulk-delete-workflows.js +2 -2
- package/dist/server/operations/bulk-filter-helpers.d.ts +8 -6
- package/dist/server/operations/bulk-filter-helpers.js +54 -24
- package/dist/server/operations/bulk-mutate-workflow-tags.d.ts +11 -9
- package/dist/server/operations/bulk-mutate-workflow-tags.js +2 -2
- package/dist/server/operations/bulk-operation-helpers.d.ts +2 -2
- package/dist/server/operations/bulk-retry-failed-workflows.d.ts +7 -0
- package/dist/server/operations/bulk-retry-failed-workflows.js +64 -0
- package/dist/server/operations/bulk-signal-workflows.d.ts +11 -9
- package/dist/server/operations/bulk-signal-workflows.js +2 -2
- package/dist/server/operations/cancel-schedule.d.ts +1 -1
- package/dist/server/operations/create-schedule.d.ts +3 -1
- package/dist/server/operations/create-schedule.js +29 -11
- package/dist/server/operations/fleet-events-subscription.d.ts +54 -0
- package/dist/server/operations/fleet-events-subscription.js +99 -0
- package/dist/server/operations/fork-workflow.d.ts +1 -1
- package/dist/server/operations/fork-workflow.js +3 -2
- package/dist/server/operations/get-checkpoint-at.d.ts +1 -1
- package/dist/server/operations/get-registry.d.ts +1 -1
- package/dist/server/operations/get-retention-overview.d.ts +1 -1
- package/dist/server/operations/get-review.d.ts +1 -1
- package/dist/server/operations/get-schedule.d.ts +1 -1
- package/dist/server/operations/get-stream-chunks.d.ts +1 -1
- package/dist/server/operations/get-system-metrics.d.ts +2 -2
- package/dist/server/operations/get-task-diagnostics.d.ts +45 -6
- package/dist/server/operations/get-task-diagnostics.js +101 -7
- package/dist/server/operations/get-update-result.d.ts +1 -1
- package/dist/server/operations/get-workflow-attributes.d.ts +1 -1
- package/dist/server/operations/get-workflow-events.d.ts +1 -1
- package/dist/server/operations/get-workflow-result.d.ts +1 -1
- package/dist/server/operations/get-workflow-timeline.d.ts +1 -1
- package/dist/server/operations/get-workflow.d.ts +1 -1
- package/dist/server/operations/list-checkpoints.d.ts +1 -1
- package/dist/server/operations/list-reviews.d.ts +1 -1
- package/dist/server/operations/list-schedules.d.ts +1 -1
- package/dist/server/operations/list-task-queues.d.ts +2 -2
- package/dist/server/operations/list-workers.d.ts +2 -2
- package/dist/server/operations/list-workflows.d.ts +1 -1
- package/dist/server/operations/operation-helpers.d.ts +16 -1
- package/dist/server/operations/operation-helpers.js +15 -3
- package/dist/server/operations/pause-schedule.d.ts +1 -1
- package/dist/server/operations/purge-workflows.d.ts +5 -5
- package/dist/server/operations/purge-workflows.js +2 -2
- package/dist/server/operations/query-workflow.d.ts +1 -1
- package/dist/server/operations/query-workflow.js +3 -2
- package/dist/server/operations/recover-all.d.ts +1 -1
- package/dist/server/operations/replay-workflow.d.ts +1 -1
- package/dist/server/operations/resume-schedule.d.ts +1 -1
- package/dist/server/operations/resume-workflow.js +2 -2
- package/dist/server/operations/set-workflow-attributes.d.ts +1 -1
- package/dist/server/operations/set-workflow-attributes.js +6 -3
- package/dist/server/operations/signal-workflow.js +8 -3
- package/dist/server/operations/single-workflow-tag-mutation.js +13 -3
- package/dist/server/operations/start-or-signal-workflow.d.ts +44 -0
- package/dist/server/operations/start-or-signal-workflow.js +130 -0
- package/dist/server/operations/start-workflow-options.d.ts +38 -0
- package/dist/server/operations/start-workflow-options.js +63 -0
- package/dist/server/operations/start-workflow-rest-input.d.ts +14 -0
- package/dist/server/operations/start-workflow-rest-input.js +31 -0
- package/dist/server/operations/start-workflow.d.ts +1 -1
- package/dist/server/operations/start-workflow.js +15 -92
- package/dist/server/operations/storage.d.ts +6 -6
- package/dist/server/operations/storage.js +24 -14
- package/dist/server/operations/stream-workflow-sse.d.ts +1 -1
- package/dist/server/operations/submit-review-decision.d.ts +1 -1
- package/dist/server/operations/submit-review-decision.js +6 -3
- package/dist/server/operations/suspend-workflow.d.ts +13 -0
- package/dist/server/operations/suspend-workflow.js +36 -0
- package/dist/server/operations/update-schedule.d.ts +1 -1
- package/dist/server/operations/update-schedule.js +7 -4
- package/dist/server/operations/update-workflow.d.ts +1 -1
- package/dist/server/operations/update-workflow.js +7 -3
- package/dist/server/operations/worker-drain.d.ts +8 -8
- package/dist/server/operations/worker-drain.js +11 -8
- package/dist/server/operations/workflow-events-subscription.d.ts +7 -8
- package/dist/server/operations/workflow-events-subscription.js +43 -8
- package/dist/server/rest-binding.d.ts +21 -8
- package/dist/server/rest-bindings.js +24 -0
- package/dist/server/rest-body.d.ts +10 -0
- package/dist/server/rest-body.js +80 -0
- package/dist/server/runtime/authentication-bridge.d.ts +1 -0
- package/dist/server/runtime/authentication-bridge.js +33 -6
- package/dist/server/runtime/client-visible-events.d.ts +3 -0
- package/dist/server/runtime/client-visible-events.js +65 -0
- package/dist/server/runtime/context.d.ts +7 -0
- package/dist/server/runtime/event-broadcasting.d.ts +6 -0
- package/dist/server/runtime/event-broadcasting.js +72 -38
- package/dist/server/runtime/retry.d.ts +1 -0
- package/dist/server/runtime/retry.js +14 -0
- package/dist/server/runtime/shutdown.d.ts +7 -6
- package/dist/server/runtime/shutdown.js +6 -1
- package/dist/server/runtime/task-dispatch.js +5 -3
- package/dist/server/runtime/task-polling.d.ts +16 -2
- package/dist/server/runtime/task-polling.js +80 -24
- package/dist/server/runtime/task-reconciliation.js +9 -1
- package/dist/server/runtime/task-result-resolution.d.ts +15 -0
- package/dist/server/runtime/task-result-resolution.js +81 -0
- package/dist/server/runtime/websocket-stream.d.ts +20 -1
- package/dist/server/runtime/websocket-stream.js +173 -7
- package/dist/server/runtime/websocket-upgrade.js +22 -3
- package/dist/server/runtime/websocket-worker.d.ts +1 -1
- package/dist/server/runtime/websocket-worker.js +46 -30
- package/dist/server/serve-internals.d.ts +11 -1
- package/dist/server/serve-internals.js +20 -2
- package/dist/server/task-state.d.ts +38 -0
- package/dist/server/task-state.js +28 -0
- package/dist/server/workflow-event-feed.d.ts +36 -115
- package/dist/server/workflow-event-feed.js +92 -27
- package/dist/service-worker/index.js +31 -31
- package/dist/storage/auto.js +1 -1
- package/dist/storage/bun-sql.js +24 -0
- package/dist/storage/capabilities.d.ts +10 -2
- package/dist/storage/capabilities.js +2 -2
- package/dist/storage/compressed-storage.js +1 -1
- package/dist/storage/default-scope.d.ts +7 -4
- package/dist/storage/http.js +2 -2
- package/dist/storage/index.d.ts +81 -4
- package/dist/storage/indexeddb.js +1 -1
- package/dist/storage/interface.d.ts +189 -0
- package/dist/storage/interface.js +1 -1
- package/dist/storage/key-prefixes.d.ts +1 -1
- package/dist/storage/key-prefixes.js +12 -0
- package/dist/storage/lmdb.d.ts +4 -3
- package/dist/storage/lmdb.js +1 -1
- package/dist/storage/memory.js +1 -1
- package/dist/storage/neon-batch.d.ts +30 -0
- package/dist/storage/neon-batch.js +30 -0
- package/dist/storage/neon-value-mapping.d.ts +74 -0
- package/dist/storage/neon-value-mapping.js +23 -0
- package/dist/storage/neon.d.ts +124 -0
- package/dist/storage/neon.js +15 -0
- package/dist/storage/node-sqlite-loader.d.ts +71 -0
- package/dist/storage/node-sqlite-loader.js +41 -0
- package/dist/storage/node-sqlite.d.ts +1 -19
- package/dist/storage/node-sqlite.js +61 -32
- package/dist/storage/postgres-key-value-queries.d.ts +104 -0
- package/dist/storage/postgres-key-value-queries.js +101 -0
- package/dist/storage/resolve.d.ts +2 -165
- package/dist/storage/resolve.js +1 -1
- package/dist/storage/scoped-storage.js +1 -1
- package/dist/storage/storage-configuration.d.ts +209 -0
- package/dist/storage/storage-configuration.js +0 -0
- package/dist/storage/testing.d.ts +168 -0
- package/dist/storage/testing.js +2 -0
- package/dist/storage/text-value-store.d.ts +9 -9
- package/dist/storage/turso.d.ts +8 -0
- package/dist/storage/turso.js +2 -2
- package/dist/storage/typed-storage.js +1 -1
- package/dist/storage/web-extension.js +1 -1
- package/dist/testing/index.js +35 -35
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker/index.js +9 -5
- package/dist/worker/long-poll.js +4 -0
- package/dist/worker/protocol-messages.d.ts +20 -0
- package/dist/worker/protocol-schemas.d.ts +32 -0
- package/dist/worker/protocol-schemas.js +8 -4
- package/dist/worker/protocol-task-result.d.ts +28 -0
- package/dist/worker/protocol-task-result.js +76 -0
- package/dist/worker/protocol.d.ts +4 -15
- package/dist/worker/protocol.js +1 -1
- package/dist/worker/registry/fair-share.d.ts +29 -0
- package/dist/worker/registry/fair-share.js +30 -0
- package/dist/worker/registry/routing.d.ts +18 -0
- package/dist/worker/registry/routing.js +14 -0
- package/dist/worker/registry/types.d.ts +7 -0
- package/dist/worker/registry.d.ts +16 -1
- package/dist/worker/registry.js +24 -36
- package/dist/workers/worker-replay-state.d.ts +32 -0
- package/dist/workers/worker-replay-state.js +35 -0
- package/dist/workers/worker-state-namespace.d.ts +21 -0
- package/dist/workers/worker-state-namespace.js +13 -0
- package/dist/workers/workflow-runner.d.ts +19 -15
- package/dist/workers/workflow-runner.js +72 -60
- package/dist/workers/workflow-worker-entry.js +5 -2
- package/package.json +21 -4
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { KEYS } from "../../storage/interface.js";
|
|
2
|
+
import { encode } from "../codec.js";
|
|
3
|
+
import { ScheduleFiredEvent } from "../events.js";
|
|
4
|
+
import { unavailableServicesError } from "./lifecycle/recovered-services.js";
|
|
5
|
+
import { EMPTY_STORAGE_VALUE } from "./lifecycle/shared.js";
|
|
6
|
+
export async function startScheduledRun(internals, state, callbacks, occurrence) {
|
|
7
|
+
const workflowId = crypto.randomUUID(), scheduleRunOperations = state.overlap === "allow" ? [] : [{ type: "put", key: KEYS.scheduleRun(workflowId), value: encode(state.id) }], resolution = await resolveScheduledRunServices(internals, workflowId, state);
|
|
8
|
+
if (resolution !== null) {
|
|
9
|
+
scheduleRunOperations.push({ type: "put", key: KEYS.workflowHasServices(workflowId), value: EMPTY_STORAGE_VALUE }, { type: "put", key: KEYS.terminalCleanupNeeded(workflowId), value: EMPTY_STORAGE_VALUE });
|
|
10
|
+
internals.workflowsNeedingTerminalCleanup.add(workflowId);
|
|
11
|
+
if (resolution.status === "available")
|
|
12
|
+
internals.workflowServices.set(workflowId, resolution.services);
|
|
13
|
+
}
|
|
14
|
+
await callbacks.startWorkflow(state.workflowType, state.input, { id: workflowId }, scheduleRunOperations);
|
|
15
|
+
internals.engine.dispatchEvent(new ScheduleFiredEvent(state.id, workflowId, internals.options.getNow(), occurrence));
|
|
16
|
+
if (resolution !== null && resolution.status === "unavailable") {
|
|
17
|
+
const unavailableError = unavailableServicesError(workflowId, resolution.reason);
|
|
18
|
+
try {
|
|
19
|
+
await callbacks.failWorkflow(workflowId, unavailableError);
|
|
20
|
+
} catch (error) {
|
|
21
|
+
callbacks.handleCleanupError("startScheduledRun", error, workflowId);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return workflowId;
|
|
25
|
+
}
|
|
26
|
+
async function resolveScheduledRunServices(internals, workflowId, state) {
|
|
27
|
+
const resolver = internals.options.resolveWorkflowServices;
|
|
28
|
+
if (internals.inlineStrategy === null || !resolver)
|
|
29
|
+
return null;
|
|
30
|
+
try {
|
|
31
|
+
return await resolver({ workflowId, workflowType: state.workflowType, input: state.input });
|
|
32
|
+
} catch (error) {
|
|
33
|
+
return {
|
|
34
|
+
status: "unavailable",
|
|
35
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -1,6 +1,16 @@
|
|
|
1
|
+
import { ScheduleMissedFireEvent } from "../events/schedule-events.js";
|
|
2
|
+
import { resolveEffectiveScheduleFireAt } from "./schedule-jitter.js";
|
|
1
3
|
import { collectDueScheduleOccurrences, getNextScheduleOccurrence } from "./schedule-occurrence.js";
|
|
2
4
|
import { loadScheduleState, writeScheduleState } from "./storage-io.js";
|
|
3
|
-
const SCHEDULE_LATE_GRACE_MILLISECONDS = 1000, MAX_SCHEDULE_BACKFILL_OCCURRENCES_PER_TICK = 256;
|
|
5
|
+
const SCHEDULE_LATE_GRACE_MILLISECONDS = 1000, MAX_SCHEDULE_BACKFILL_OCCURRENCES_PER_TICK = 256, MAX_MISSED_FIRE_COUNT_PER_TICK = MAX_SCHEDULE_BACKFILL_OCCURRENCES_PER_TICK;
|
|
6
|
+
|
|
7
|
+
class ScheduleStatePersistenceError extends Error {
|
|
8
|
+
scheduleId;
|
|
9
|
+
constructor(scheduleId, cause) {
|
|
10
|
+
super(`Failed to persist schedule "${scheduleId}" while processing its timer`, { cause });
|
|
11
|
+
this.scheduleId = scheduleId;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
4
14
|
export async function handleScheduleTimer(internals, entry, callbacks) {
|
|
5
15
|
const state = await loadScheduleState(internals, entry.workflowId);
|
|
6
16
|
if (!isCurrentActiveScheduleTimer(state, entry))
|
|
@@ -10,28 +20,43 @@ export async function handleScheduleTimer(internals, entry, callbacks) {
|
|
|
10
20
|
const now = internals.options.getNow(), work = planScheduleTimerWork(state, entry, now);
|
|
11
21
|
if (!work)
|
|
12
22
|
return;
|
|
13
|
-
|
|
14
|
-
nextState =
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
23
|
+
if (work.occurrencesToProcess.length === 0) {
|
|
24
|
+
nextState = {
|
|
25
|
+
...nextState,
|
|
26
|
+
updatedAt: now,
|
|
27
|
+
nextFireAt: resolveNextScheduleFireAt(nextState, work, now),
|
|
28
|
+
...work.missedOccurrences && {
|
|
29
|
+
lastMissedFireAt: work.missedOccurrences.lastMissedFireAt,
|
|
30
|
+
missedFireCount: nextState.missedFireCount + work.missedOccurrences.count
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
await persistScheduleTimerState(internals, nextState);
|
|
34
|
+
if (work.missedOccurrences)
|
|
35
|
+
internals.engine.dispatchEvent(new ScheduleMissedFireEvent(nextState.id, work.missedOccurrences.count, work.missedOccurrences.windowStart, work.missedOccurrences.windowEnd));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
nextState = await processScheduleTimerOccurrences(internals, state, work, now, callbacks, {
|
|
39
|
+
onProcessedState: (processedState) => {
|
|
40
|
+
nextState = processedState;
|
|
41
|
+
}
|
|
20
42
|
});
|
|
21
43
|
} catch (error) {
|
|
44
|
+
if (error instanceof ScheduleStatePersistenceError)
|
|
45
|
+
throw error;
|
|
22
46
|
await pauseScheduleAfterTimerFailure(internals, nextState, error);
|
|
23
47
|
}
|
|
24
48
|
}
|
|
25
49
|
function isCurrentActiveScheduleTimer(state, entry) {
|
|
26
|
-
return state?.status === "active" && state.nextFireAt === entry.fireAt;
|
|
50
|
+
return state?.status === "active" && state.nextFireAt !== null && resolveEffectiveScheduleFireAt(state, state.nextFireAt) === entry.fireAt;
|
|
27
51
|
}
|
|
28
52
|
function planScheduleTimerWork(state, entry, now) {
|
|
29
53
|
const dueOccurrences = collectDueScheduleOccurrences(state, state.nextFireAt, Math.max(now, entry.fireAt), state.backfill ? MAX_SCHEDULE_BACKFILL_OCCURRENCES_PER_TICK : 2);
|
|
30
54
|
if (dueOccurrences.length === 0)
|
|
31
55
|
return null;
|
|
32
|
-
if (!state.backfill && now -
|
|
56
|
+
if (!state.backfill && now - entry.fireAt > SCHEDULE_LATE_GRACE_MILLISECONDS)
|
|
33
57
|
return {
|
|
34
58
|
dueOccurrences,
|
|
59
|
+
missedOccurrences: countMissedScheduleOccurrences(state, state.nextFireAt, now),
|
|
35
60
|
occurrencesToProcess: [],
|
|
36
61
|
skipMissedOccurrences: !0
|
|
37
62
|
};
|
|
@@ -42,29 +67,68 @@ function planScheduleTimerWork(state, entry, now) {
|
|
|
42
67
|
skipMissedOccurrences: !1
|
|
43
68
|
};
|
|
44
69
|
}
|
|
70
|
+
function countMissedScheduleOccurrences(state, firstDueAt, throughTimestamp) {
|
|
71
|
+
let count = 0, occurrence = firstDueAt, lastMissedFireAt = firstDueAt;
|
|
72
|
+
while (occurrence <= throughTimestamp && count < MAX_MISSED_FIRE_COUNT_PER_TICK) {
|
|
73
|
+
count += 1;
|
|
74
|
+
lastMissedFireAt = occurrence;
|
|
75
|
+
const nextOccurrence = getNextScheduleOccurrence(state, occurrence);
|
|
76
|
+
if (nextOccurrence <= occurrence)
|
|
77
|
+
throw Error(`Schedule "${state.id}" produced a non-advancing occurrence`);
|
|
78
|
+
occurrence = nextOccurrence;
|
|
79
|
+
}
|
|
80
|
+
const cappedBeforeThroughTimestamp = occurrence <= throughTimestamp;
|
|
81
|
+
return {
|
|
82
|
+
count,
|
|
83
|
+
lastMissedFireAt,
|
|
84
|
+
windowStart: firstDueAt,
|
|
85
|
+
windowEnd: cappedBeforeThroughTimestamp ? lastMissedFireAt : throughTimestamp
|
|
86
|
+
};
|
|
87
|
+
}
|
|
45
88
|
function resolveNextScheduleFireAt(state, work, now) {
|
|
46
89
|
if (work.skipMissedOccurrences)
|
|
47
|
-
return getNextScheduleOccurrence(state, now);
|
|
90
|
+
return getNextScheduleOccurrence(state, work.missedOccurrences?.lastMissedFireAt ?? now);
|
|
48
91
|
const anchorOccurrence = work.occurrencesToProcess.at(-1) ?? work.dueOccurrences.at(-1);
|
|
49
92
|
return getNextScheduleOccurrence(state, anchorOccurrence);
|
|
50
93
|
}
|
|
51
|
-
async function processScheduleTimerOccurrences(internals, state,
|
|
94
|
+
async function processScheduleTimerOccurrences(internals, state, work, now, callbacks, options) {
|
|
52
95
|
let nextState = state;
|
|
53
|
-
for (const occurrence of occurrencesToProcess) {
|
|
96
|
+
for (const [index, occurrence] of work.occurrencesToProcess.entries()) {
|
|
97
|
+
const finalOccurrence = index === work.occurrencesToProcess.length - 1;
|
|
54
98
|
nextState = {
|
|
55
|
-
...await callbacks.applyScheduleOccurrence(nextState),
|
|
99
|
+
...await callbacks.applyScheduleOccurrence(nextState, occurrence),
|
|
56
100
|
lastFireAt: occurrence,
|
|
57
101
|
updatedAt: now
|
|
58
102
|
};
|
|
59
|
-
|
|
60
|
-
|
|
103
|
+
if (finalOccurrence)
|
|
104
|
+
nextState = {
|
|
105
|
+
...nextState,
|
|
106
|
+
nextFireAt: resolveNextScheduleFireAt(nextState, work, now)
|
|
107
|
+
};
|
|
108
|
+
options.onProcessedState(nextState);
|
|
109
|
+
await persistScheduleTimerState(internals, nextState, { includeTimer: finalOccurrence });
|
|
61
110
|
if (state.backfill && nextState.currentWorkflowId !== void 0) {
|
|
62
111
|
nextState = await callbacks.settleBackfillScheduleState(nextState);
|
|
63
|
-
|
|
112
|
+
if (finalOccurrence) {
|
|
113
|
+
nextState = {
|
|
114
|
+
...nextState,
|
|
115
|
+
updatedAt: now,
|
|
116
|
+
nextFireAt: resolveNextScheduleFireAt(nextState, work, now)
|
|
117
|
+
};
|
|
118
|
+
await persistScheduleTimerState(internals, nextState);
|
|
119
|
+
}
|
|
120
|
+
options.onProcessedState(nextState);
|
|
64
121
|
}
|
|
65
122
|
}
|
|
66
123
|
return nextState;
|
|
67
124
|
}
|
|
125
|
+
async function persistScheduleTimerState(internals, state, options) {
|
|
126
|
+
try {
|
|
127
|
+
await writeScheduleState(internals, state, options);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
throw new ScheduleStatePersistenceError(state.id, error);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
68
132
|
async function pauseScheduleAfterTimerFailure(internals, state, error) {
|
|
69
133
|
const errorNow = internals.options.getNow(), pausedState = {
|
|
70
134
|
...state,
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { BatchOperation } from '../../storage/interface.ts';
|
|
2
2
|
import type { PaginatedResult, ScheduleFilter, ScheduleOptions, ScheduleSpec, ScheduleState, ScheduleSummary, WorkflowState } from '../types.ts';
|
|
3
|
-
import { ScheduleHandle } from './handles.ts';
|
|
4
3
|
import type { EngineInternals } from './internals.ts';
|
|
4
|
+
import { ScheduleHandle } from './schedule-handle.ts';
|
|
5
|
+
export { startScheduledRun } from './schedule-run.ts';
|
|
5
6
|
export { handleScheduleTimer } from './schedule-timer.ts';
|
|
6
7
|
export type RefreshedScheduleState = {
|
|
7
8
|
state: ScheduleState;
|
|
@@ -15,20 +16,22 @@ export type ScheduleCallbacks = {
|
|
|
15
16
|
cancelWorkflow: (workflowId: string) => Promise<void>;
|
|
16
17
|
getWorkflowResult: (workflowId: string) => Promise<unknown>;
|
|
17
18
|
refreshScheduledWorkflowState: (state: ScheduleState) => Promise<RefreshedScheduleState>;
|
|
18
|
-
startScheduledRun: (state: ScheduleState) => Promise<string>;
|
|
19
|
-
applyScheduleOccurrence: (state: ScheduleState) => Promise<ScheduleState>;
|
|
19
|
+
startScheduledRun: (state: ScheduleState, occurrence?: number) => Promise<string>;
|
|
20
|
+
applyScheduleOccurrence: (state: ScheduleState, occurrence?: number) => Promise<ScheduleState>;
|
|
20
21
|
settleBackfillScheduleState: (state: ScheduleState) => Promise<ScheduleState>;
|
|
21
22
|
flushQueuedInlineWorkflowStartsDirectly: () => Promise<void>;
|
|
23
|
+
failWorkflow: (workflowId: string, error: Error) => Promise<void>;
|
|
24
|
+
handleCleanupError: (source: string, error: unknown, workflowId: string) => void;
|
|
22
25
|
};
|
|
23
26
|
export declare function schedule(internals: EngineInternals, type: string, input: unknown, spec: string | ScheduleSpec, options?: ScheduleOptions): Promise<ScheduleHandle>;
|
|
24
27
|
export declare function listSchedules(internals: EngineInternals, filter?: ScheduleFilter): Promise<PaginatedResult<ScheduleSummary>>;
|
|
28
|
+
export declare function recoverOrphanedScheduleTimers(internals: EngineInternals): Promise<void>;
|
|
25
29
|
export declare function toScheduleSummary(state: ScheduleState): ScheduleSummary;
|
|
26
30
|
export declare function pauseSchedule(internals: EngineInternals, scheduleId: string): Promise<void>;
|
|
27
31
|
export declare function resumeSchedule(internals: EngineInternals, scheduleId: string): Promise<void>;
|
|
28
32
|
export declare function cancelSchedule(internals: EngineInternals, scheduleId: string): Promise<void>;
|
|
29
33
|
export declare function updateSchedule(internals: EngineInternals, scheduleId: string, newSpec: string | ScheduleSpec): Promise<void>;
|
|
30
34
|
export declare function refreshScheduledWorkflowState(internals: EngineInternals, state: ScheduleState, callbacks: Pick<ScheduleCallbacks, 'loadWorkflowState'>): Promise<RefreshedScheduleState>;
|
|
31
|
-
export declare function
|
|
32
|
-
export declare function applyScheduleOccurrence(_internals: EngineInternals, state: ScheduleState, callbacks: ScheduleCallbacks): Promise<ScheduleState>;
|
|
35
|
+
export declare function applyScheduleOccurrence(_internals: EngineInternals, state: ScheduleState, callbacks: ScheduleCallbacks, occurrence?: number): Promise<ScheduleState>;
|
|
33
36
|
export declare function settleBackfillScheduleState(internals: EngineInternals, state: ScheduleState, callbacks: Pick<ScheduleCallbacks, 'flushQueuedInlineWorkflowStartsDirectly' | 'refreshScheduledWorkflowState'>): Promise<ScheduleState>;
|
|
34
37
|
export declare function handleScheduledWorkflowTerminal(internals: EngineInternals, workflowId: string, callbacks: ScheduleCallbacks): Promise<void>;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { KEYS } from "../../storage/interface.js";
|
|
2
|
-
import { decode
|
|
2
|
+
import { decode } from "../codec.js";
|
|
3
3
|
import { WorkflowNotRegisteredError } from "./errors.js";
|
|
4
|
-
import { ScheduleHandle } from "./
|
|
4
|
+
import { ScheduleHandle } from "./schedule-handle.js";
|
|
5
|
+
import { resolveEffectiveScheduleFireAt } from "./schedule-jitter.js";
|
|
5
6
|
import { getNextScheduleOccurrence } from "./schedule-occurrence.js";
|
|
6
7
|
import {
|
|
7
8
|
clearScheduleCurrentWorkflow,
|
|
@@ -18,6 +19,7 @@ import {
|
|
|
18
19
|
normalizeScheduleOptions,
|
|
19
20
|
normalizeScheduleSpec
|
|
20
21
|
} from "./validation/schedule.js";
|
|
22
|
+
export { startScheduledRun } from "./schedule-run.js";
|
|
21
23
|
export { handleScheduleTimer } from "./schedule-timer.js";
|
|
22
24
|
export async function schedule(internals, type, input, spec, options) {
|
|
23
25
|
if (!internals.registrations.has(type))
|
|
@@ -37,9 +39,11 @@ export async function schedule(internals, type, input, spec, options) {
|
|
|
37
39
|
status: "active",
|
|
38
40
|
overlap: normalizedOptions.overlap,
|
|
39
41
|
backfill: normalizedOptions.backfill,
|
|
42
|
+
...normalizedOptions.jitterMs !== void 0 && { jitterMs: normalizedOptions.jitterMs },
|
|
40
43
|
createdAt: now,
|
|
41
44
|
updatedAt: now,
|
|
42
45
|
nextFireAt: getNextScheduleOccurrence({ ...cadenceFields, createdAt: now }, now),
|
|
46
|
+
missedFireCount: 0,
|
|
43
47
|
queuedRuns: 0
|
|
44
48
|
};
|
|
45
49
|
await writeScheduleState(internals, state);
|
|
@@ -61,6 +65,28 @@ export async function listSchedules(internals, filter) {
|
|
|
61
65
|
}
|
|
62
66
|
return paginateScheduleSummaries(items, normalizedFilter);
|
|
63
67
|
}
|
|
68
|
+
export async function recoverOrphanedScheduleTimers(internals) {
|
|
69
|
+
for await (const [key, value] of internals.storage.scan("schedule:")) {
|
|
70
|
+
if (key.slice(9).includes(":"))
|
|
71
|
+
continue;
|
|
72
|
+
const state = decodeScheduleState(value);
|
|
73
|
+
if (!isActiveScheduleWithTimer(state))
|
|
74
|
+
continue;
|
|
75
|
+
if (await hasCurrentScheduleTimer(internals, state))
|
|
76
|
+
continue;
|
|
77
|
+
await writeScheduleState(internals, state);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function isActiveScheduleWithTimer(state) {
|
|
81
|
+
return state?.status === "active" && state.nextFireAt !== null;
|
|
82
|
+
}
|
|
83
|
+
async function hasCurrentScheduleTimer(internals, state) {
|
|
84
|
+
const timerKey = KEYS.scheduleTick(resolveEffectiveScheduleFireAt(state, state.nextFireAt), state.id);
|
|
85
|
+
if (await internals.storage.get(timerKey) === null)
|
|
86
|
+
return !1;
|
|
87
|
+
const timerIndexBytes = await internals.storage.get(`timer-idx:${createScheduleTimerId(state.id)}`);
|
|
88
|
+
return timerIndexBytes !== null && decode(timerIndexBytes) === timerKey;
|
|
89
|
+
}
|
|
64
90
|
export function toScheduleSummary(state) {
|
|
65
91
|
const { input: _input, ...summary } = state;
|
|
66
92
|
return summary;
|
|
@@ -107,10 +133,7 @@ export async function cancelSchedule(internals, scheduleId) {
|
|
|
107
133
|
await writeScheduleState(internals, updatedState, { includeTimer: !1 });
|
|
108
134
|
}
|
|
109
135
|
export async function updateSchedule(internals, scheduleId, newSpec) {
|
|
110
|
-
const normalizedScheduleId = coerceScheduleId(scheduleId, "scheduleId"), normalizedSpec = normalizeScheduleSpec(newSpec), state = await requireScheduleState(internals, normalizedScheduleId)
|
|
111
|
-
if (state.status === "active")
|
|
112
|
-
await internals.scheduler.cancel(createScheduleTimerId(normalizedScheduleId), normalizedScheduleId);
|
|
113
|
-
const now = internals.options.getNow(), {
|
|
136
|
+
const normalizedScheduleId = coerceScheduleId(scheduleId, "scheduleId"), normalizedSpec = normalizeScheduleSpec(newSpec), state = await requireScheduleState(internals, normalizedScheduleId), now = internals.options.getNow(), {
|
|
114
137
|
cronExpression: _droppedCron,
|
|
115
138
|
intervalMs: _droppedInterval,
|
|
116
139
|
...stateWithoutCadence
|
|
@@ -123,11 +146,15 @@ export async function updateSchedule(internals, scheduleId, newSpec) {
|
|
|
123
146
|
};
|
|
124
147
|
await writeScheduleState(internals, updatedState, { includeTimer: state.status === "active" });
|
|
125
148
|
}
|
|
149
|
+
function scheduledRunOccupiesSlot(currentWorkflowState) {
|
|
150
|
+
const status = currentWorkflowState?.status;
|
|
151
|
+
return status === "running" || status === "pending" || status === "suspended";
|
|
152
|
+
}
|
|
126
153
|
export async function refreshScheduledWorkflowState(internals, state, callbacks) {
|
|
127
154
|
if (!state.currentWorkflowId)
|
|
128
155
|
return { state, currentWorkflowState: null };
|
|
129
156
|
const currentWorkflowState = await callbacks.loadWorkflowState(state.currentWorkflowId);
|
|
130
|
-
if (currentWorkflowState
|
|
157
|
+
if (scheduledRunOccupiesSlot(currentWorkflowState))
|
|
131
158
|
return { state, currentWorkflowState };
|
|
132
159
|
await internals.storage.delete(KEYS.scheduleRun(state.currentWorkflowId));
|
|
133
160
|
return {
|
|
@@ -135,36 +162,34 @@ export async function refreshScheduledWorkflowState(internals, state, callbacks)
|
|
|
135
162
|
currentWorkflowState: currentWorkflowState ?? null
|
|
136
163
|
};
|
|
137
164
|
}
|
|
138
|
-
export async function startScheduledRun(_internals, state, callbacks) {
|
|
139
|
-
const workflowId = crypto.randomUUID(), scheduleRunOperations = state.overlap === "allow" ? void 0 : [{ type: "put", key: KEYS.scheduleRun(workflowId), value: encode(state.id) }];
|
|
140
|
-
await callbacks.startWorkflow(state.workflowType, state.input, { id: workflowId }, scheduleRunOperations);
|
|
141
|
-
return workflowId;
|
|
142
|
-
}
|
|
143
165
|
function hasActiveScheduledWorkflow(currentWorkflowState) {
|
|
144
|
-
return currentWorkflowState
|
|
166
|
+
return scheduledRunOccupiesSlot(currentWorkflowState);
|
|
145
167
|
}
|
|
146
|
-
async function applyBlockedScheduleOccurrence(state, hasActiveWorkflow, callbacks) {
|
|
168
|
+
async function applyBlockedScheduleOccurrence(state, hasActiveWorkflow, callbacks, occurrence) {
|
|
147
169
|
if (!hasActiveWorkflow)
|
|
148
|
-
return { ...state, currentWorkflowId: await callbacks.startScheduledRun(state) };
|
|
170
|
+
return { ...state, currentWorkflowId: await callbacks.startScheduledRun(state, occurrence) };
|
|
149
171
|
if (state.overlap === "cancel-running") {
|
|
150
172
|
if (state.currentWorkflowId) {
|
|
151
173
|
callbacks.getWorkflowResult(state.currentWorkflowId).catch(() => {});
|
|
152
174
|
await callbacks.cancelWorkflow(state.currentWorkflowId);
|
|
153
175
|
}
|
|
154
176
|
const stateForStart = clearScheduleCurrentWorkflow(state);
|
|
155
|
-
return {
|
|
177
|
+
return {
|
|
178
|
+
...state,
|
|
179
|
+
currentWorkflowId: await callbacks.startScheduledRun(stateForStart, occurrence)
|
|
180
|
+
};
|
|
156
181
|
}
|
|
157
182
|
if (state.overlap === "queue")
|
|
158
183
|
return { ...state, queuedRuns: state.queuedRuns + 1 };
|
|
159
184
|
return state;
|
|
160
185
|
}
|
|
161
|
-
export async function applyScheduleOccurrence(_internals, state, callbacks) {
|
|
186
|
+
export async function applyScheduleOccurrence(_internals, state, callbacks, occurrence) {
|
|
162
187
|
const { state: refreshedState, currentWorkflowState } = await callbacks.refreshScheduledWorkflowState(state), hasActiveWorkflow = hasActiveScheduledWorkflow(currentWorkflowState);
|
|
163
188
|
if (refreshedState.overlap === "allow") {
|
|
164
|
-
await callbacks.startScheduledRun(refreshedState);
|
|
189
|
+
await callbacks.startScheduledRun(refreshedState, occurrence);
|
|
165
190
|
return refreshedState;
|
|
166
191
|
}
|
|
167
|
-
return applyBlockedScheduleOccurrence(refreshedState, hasActiveWorkflow, callbacks);
|
|
192
|
+
return applyBlockedScheduleOccurrence(refreshedState, hasActiveWorkflow, callbacks, occurrence);
|
|
168
193
|
}
|
|
169
194
|
export async function settleBackfillScheduleState(internals, state, callbacks) {
|
|
170
195
|
if (!state.currentWorkflowId)
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ListFilter, SearchAttributeValue } from '../types.ts';
|
|
2
|
+
import type { EngineInternals } from './internals.ts';
|
|
3
|
+
export declare function readSearchAttributesForStates(internals: EngineInternals, stateBytesList: readonly (Uint8Array | null)[], filter: ListFilter | undefined): Promise<Map<string, Record<string, SearchAttributeValue> | null>>;
|
|
4
|
+
export declare function readSearchAttributesForFilter(internals: EngineInternals, workflowId: string, filter: ListFilter | undefined): Promise<Record<string, SearchAttributeValue> | null>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { KEYS } from "../../storage/interface.js";
|
|
2
|
+
import { decodeSearchAttributeRecord, listFilterHasAttributeFilters } from "./state-utilities.js";
|
|
3
|
+
import { decodeWorkflowState } from "./validation.js";
|
|
4
|
+
export async function readSearchAttributesForStates(internals, stateBytesList, filter) {
|
|
5
|
+
const searchAttributesByWorkflowId = new Map;
|
|
6
|
+
if (!listFilterHasAttributeFilters(filter))
|
|
7
|
+
return searchAttributesByWorkflowId;
|
|
8
|
+
const states = stateBytesList.flatMap((stateBytes) => stateBytes === null ? [] : [decodeWorkflowState(stateBytes)]), attributeBytesList = await Promise.all(states.map((state) => internals.storage.get(KEYS.attribute(state.id))));
|
|
9
|
+
for (let index = 0;index < states.length; index += 1)
|
|
10
|
+
searchAttributesByWorkflowId.set(states[index].id, decodeSearchAttributeRecord(attributeBytesList[index] ?? null));
|
|
11
|
+
return searchAttributesByWorkflowId;
|
|
12
|
+
}
|
|
13
|
+
export async function readSearchAttributesForFilter(internals, workflowId, filter) {
|
|
14
|
+
if (!listFilterHasAttributeFilters(filter))
|
|
15
|
+
return null;
|
|
16
|
+
return decodeSearchAttributeRecord(await internals.storage.get(KEYS.attribute(workflowId)));
|
|
17
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Best-effort, warn-only detection of a SECOND engine instance writing to the
|
|
3
|
+
* same durable store — a smoke alarm for singleton-deployment misconfiguration
|
|
4
|
+
* (an autoscaler accidentally set above one replica, or overlapping rolling
|
|
5
|
+
* deploys), NOT a correctness mechanism.
|
|
6
|
+
*
|
|
7
|
+
* **This is liveness, not fencing.** Weft's supported model is one engine process
|
|
8
|
+
* per durable store (see the recovery-and-deploys guide); fenced ownership is a
|
|
9
|
+
* future `MultiEngine` capability that does not exist yet. This detector never
|
|
10
|
+
* blocks boot, gates recovery, refuses a write, or claims ownership. It only
|
|
11
|
+
* observes whether another instance's heartbeat is *advancing while this instance
|
|
12
|
+
* is also running* and emits a warning if so. It does not prevent duplicate
|
|
13
|
+
* execution — infrastructure-level enforcement (`replicas: 1` + a `Recreate`
|
|
14
|
+
* deploy strategy, or a single systemd unit) is the real control.
|
|
15
|
+
*
|
|
16
|
+
* **Why liveness, not a boot check.** A boot-time check cannot distinguish a
|
|
17
|
+
* rolling-deploy handoff from a genuine second instance: both leave a recent
|
|
18
|
+
* heartbeat record in the store. Only observing a *foreign* heartbeat advance
|
|
19
|
+
* across several of our own ticks separates a live peer (autoscaling=2 → both
|
|
20
|
+
* heartbeats advance forever → both warn) from a dead-but-recent predecessor (a
|
|
21
|
+
* clean `Recreate` deploy → old heartbeat never advances after handoff → quiet).
|
|
22
|
+
*
|
|
23
|
+
* **Advance is measured by sequence, not wall clock.** Each heartbeat carries a
|
|
24
|
+
* per-instance monotonic `sequence`; a peer counts as advancing only when its
|
|
25
|
+
* `sequence` grows between two of *our* ticks. A peer's sequence cannot increase
|
|
26
|
+
* unless it is alive and ticking in our own time frame, so detection never
|
|
27
|
+
* compares clocks across hosts — it stays correct even if a peer's clock is
|
|
28
|
+
* frozen, skewed, or stepped backward. `heartbeatAt` exists only for the boot
|
|
29
|
+
* staleness sweep that garbage-collects long-dead instances' keys.
|
|
30
|
+
*
|
|
31
|
+
* Each engine writes its own heartbeat under `liveness:<instanceId>` and scans
|
|
32
|
+
* the `liveness:` prefix to observe peers. Per-instance keys (not one shared,
|
|
33
|
+
* clobbered key) keep every heartbeat independently observable and the sequence
|
|
34
|
+
* monotonic per writer.
|
|
35
|
+
*
|
|
36
|
+
* @module core/engine/second-instance-detector
|
|
37
|
+
*/
|
|
38
|
+
import { type Storage } from '../../storage/interface.ts';
|
|
39
|
+
import type { EngineCleanupIntervalDisposalTracker } from './engine-leak-warnings.ts';
|
|
40
|
+
/** Options for {@link createSecondInstanceDetector}. */
|
|
41
|
+
export type SecondInstanceDetectorOptions = {
|
|
42
|
+
storage: Storage;
|
|
43
|
+
/** This engine's unique instance id. */
|
|
44
|
+
instanceId: string;
|
|
45
|
+
/** Wall-clock source (ms), injected so tests can advance time deterministically. */
|
|
46
|
+
getNow: () => number;
|
|
47
|
+
/**
|
|
48
|
+
* Heartbeat interval in ms. The staleness window is derived from this, so the
|
|
49
|
+
* interval also sets how long a deploy overlap must last before it warns.
|
|
50
|
+
*/
|
|
51
|
+
intervalMs: number;
|
|
52
|
+
/**
|
|
53
|
+
* Emit a warning. Defaults to `process.emitWarning(message, WARNING_NAME)`,
|
|
54
|
+
* so the emitted `Warning.name` is `WeftSecondInstanceWarning` and consumers
|
|
55
|
+
* can filter on `warning.name` rather than scraping the message. Injected for
|
|
56
|
+
* tests; the seam is message-only because the name is a fixed constant.
|
|
57
|
+
*/
|
|
58
|
+
warn?: (message: string) => void;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* The `name` of the emitted warning. A stable, filterable identifier on the
|
|
62
|
+
* `Warning` object — consumers subscribe to the process `warning` event and
|
|
63
|
+
* match `warning.name === WARNING_NAME` (see the singleton-deployment guide).
|
|
64
|
+
*/
|
|
65
|
+
export declare const SECOND_INSTANCE_WARNING_NAME = "WeftSecondInstanceWarning";
|
|
66
|
+
/**
|
|
67
|
+
* A running detector. `tick()` runs one heartbeat round (exposed for tests and
|
|
68
|
+
* driven by an interval in production); `stop()` clears the interval and
|
|
69
|
+
* best-effort removes this instance's heartbeat so the next boot starts quiet.
|
|
70
|
+
*/
|
|
71
|
+
export type SecondInstanceDetector = {
|
|
72
|
+
/** Run one heartbeat round: observe peers, then write our own heartbeat. */
|
|
73
|
+
tick(): Promise<void>;
|
|
74
|
+
/** Stop the interval and best-effort delete this instance's heartbeat key. */
|
|
75
|
+
stop(): Promise<void>;
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Create a best-effort second-instance detector. Does not start an interval
|
|
79
|
+
* itself — the engine owns timer lifecycle so disposal can clear it through the
|
|
80
|
+
* same path as its other intervals. Call {@link SecondInstanceDetector.tick} on
|
|
81
|
+
* an interval and {@link SecondInstanceDetector.stop} on dispose.
|
|
82
|
+
*/
|
|
83
|
+
export declare function createSecondInstanceDetector(options: SecondInstanceDetectorOptions): SecondInstanceDetector;
|
|
84
|
+
/**
|
|
85
|
+
* Build the `setInterval` callback that drives a detector tick. `resolveDetector`
|
|
86
|
+
* returns the live detector, or `null` when the engine has been garbage-collected
|
|
87
|
+
* or disposed. When it returns `null` the tick SELF-CLEARS its own interval (via
|
|
88
|
+
* `tracker.secondInstanceDetectionInterval`) and returns — mirroring
|
|
89
|
+
* {@link createCleanupIntervalTick}. This is the prompt cleanup path: a leaked
|
|
90
|
+
* engine's first post-GC tick clears the timer immediately, rather than relying
|
|
91
|
+
* on the `FinalizationRegistry` backstop, whose callbacks are not guaranteed to
|
|
92
|
+
* run promptly (or at all). Extracted so the skip/clear guard is directly
|
|
93
|
+
* testable without a timer. A tick failure is swallowed: the detector is a smoke
|
|
94
|
+
* alarm, never a correctness path, so it must not surface as an unhandled rejection.
|
|
95
|
+
*/
|
|
96
|
+
export declare function createSecondInstanceDetectionTick(resolveDetector: () => SecondInstanceDetector | null, tracker: EngineCleanupIntervalDisposalTracker): () => void;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { KEYS } from "../../storage/interface.js";
|
|
2
|
+
const textEncoder = new TextEncoder, textDecoder = new TextDecoder;
|
|
3
|
+
function encodeHeartbeat(heartbeat) {
|
|
4
|
+
return textEncoder.encode(JSON.stringify(heartbeat));
|
|
5
|
+
}
|
|
6
|
+
async function bestEffort(operation) {
|
|
7
|
+
try {
|
|
8
|
+
await operation();
|
|
9
|
+
} catch {}
|
|
10
|
+
}
|
|
11
|
+
function isUsableHeartbeat(candidate) {
|
|
12
|
+
const { instanceId, heartbeatAt, sequence } = candidate;
|
|
13
|
+
return typeof instanceId === "string" && instanceId.length > 0 && typeof heartbeatAt === "number" && Number.isFinite(heartbeatAt) && typeof sequence === "number" && Number.isInteger(sequence) && sequence >= 0;
|
|
14
|
+
}
|
|
15
|
+
function decodeHeartbeat(raw) {
|
|
16
|
+
let parsed;
|
|
17
|
+
try {
|
|
18
|
+
parsed = JSON.parse(textDecoder.decode(raw));
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
23
|
+
return null;
|
|
24
|
+
const candidate = parsed;
|
|
25
|
+
return isUsableHeartbeat(candidate) ? candidate : null;
|
|
26
|
+
}
|
|
27
|
+
export const SECOND_INSTANCE_WARNING_NAME = "WeftSecondInstanceWarning";
|
|
28
|
+
const STALE_SWEEP_WINDOW_MULTIPLE = 10, ADVANCE_TICKS_BEFORE_WARN = 2;
|
|
29
|
+
export function createSecondInstanceDetector(options) {
|
|
30
|
+
const { storage, instanceId, getNow, intervalMs } = options, warn = options.warn ?? ((message) => process.emitWarning(message, SECOND_INSTANCE_WARNING_NAME)), stalenessWindowMs = intervalMs * (ADVANCE_TICKS_BEFORE_WARN + 1);
|
|
31
|
+
let sequence = 0, swept = !1, stopped = !1;
|
|
32
|
+
const observed = new Map, warnedInstanceIds = new Set;
|
|
33
|
+
async function readPeers() {
|
|
34
|
+
const peers = [];
|
|
35
|
+
for await (const [key, value] of storage.scan(KEYS.livenessPrefix())) {
|
|
36
|
+
const heartbeat = decodeHeartbeat(value);
|
|
37
|
+
if (heartbeat !== null && heartbeat.instanceId !== instanceId)
|
|
38
|
+
peers.push({ key, heartbeat });
|
|
39
|
+
}
|
|
40
|
+
return peers;
|
|
41
|
+
}
|
|
42
|
+
async function sweepStaleHeartbeats(peers, now) {
|
|
43
|
+
const staleBefore = now - stalenessWindowMs * STALE_SWEEP_WINDOW_MULTIPLE;
|
|
44
|
+
for (const peer of peers)
|
|
45
|
+
if (peer.heartbeat.heartbeatAt < staleBefore)
|
|
46
|
+
await bestEffort(() => storage.delete(peer.key));
|
|
47
|
+
}
|
|
48
|
+
function evaluatePeers(peers) {
|
|
49
|
+
const seenThisTick = new Set;
|
|
50
|
+
for (const { key, heartbeat } of peers) {
|
|
51
|
+
if (KEYS.liveness(heartbeat.instanceId) !== key)
|
|
52
|
+
continue;
|
|
53
|
+
seenThisTick.add(heartbeat.instanceId);
|
|
54
|
+
const previous = observed.get(heartbeat.instanceId), advances = previous !== void 0 && heartbeat.sequence > previous.sequence ? previous.advances + 1 : 0;
|
|
55
|
+
observed.set(heartbeat.instanceId, { sequence: heartbeat.sequence, advances });
|
|
56
|
+
if (advances >= ADVANCE_TICKS_BEFORE_WARN && !warnedInstanceIds.has(heartbeat.instanceId)) {
|
|
57
|
+
warnedInstanceIds.add(heartbeat.instanceId);
|
|
58
|
+
warn(`another engine instance (${heartbeat.instanceId}) is writing to this durable store. Weft supports one engine process per store; running two can cause duplicate workflow execution. Enforce a single instance at the infrastructure layer (for example, one replica with a Recreate deploy strategy).`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
for (const knownInstanceId of Array.from(observed.keys()))
|
|
62
|
+
if (!seenThisTick.has(knownInstanceId))
|
|
63
|
+
observed.delete(knownInstanceId);
|
|
64
|
+
}
|
|
65
|
+
let tickInFlight = !1;
|
|
66
|
+
async function tick() {
|
|
67
|
+
if (stopped || tickInFlight)
|
|
68
|
+
return;
|
|
69
|
+
tickInFlight = !0;
|
|
70
|
+
let wroteHeartbeat = !1;
|
|
71
|
+
try {
|
|
72
|
+
const now = getNow(), peers = await readPeers();
|
|
73
|
+
if (!swept) {
|
|
74
|
+
swept = !0;
|
|
75
|
+
await sweepStaleHeartbeats(peers, now);
|
|
76
|
+
}
|
|
77
|
+
evaluatePeers(peers);
|
|
78
|
+
if (stopped)
|
|
79
|
+
return;
|
|
80
|
+
sequence += 1;
|
|
81
|
+
const heartbeat = { instanceId, heartbeatAt: now, sequence };
|
|
82
|
+
wroteHeartbeat = !0;
|
|
83
|
+
await bestEffort(() => storage.put(KEYS.liveness(instanceId), encodeHeartbeat(heartbeat)));
|
|
84
|
+
} finally {
|
|
85
|
+
tickInFlight = !1;
|
|
86
|
+
if (wroteHeartbeat && stopped)
|
|
87
|
+
await bestEffort(() => storage.delete(KEYS.liveness(instanceId)));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async function stop() {
|
|
91
|
+
stopped = !0;
|
|
92
|
+
await bestEffort(() => storage.delete(KEYS.liveness(instanceId)));
|
|
93
|
+
}
|
|
94
|
+
return { tick, stop };
|
|
95
|
+
}
|
|
96
|
+
export function createSecondInstanceDetectionTick(resolveDetector, tracker) {
|
|
97
|
+
return function secondInstanceDetectionTick() {
|
|
98
|
+
const detector = resolveDetector();
|
|
99
|
+
if (detector === null) {
|
|
100
|
+
if (tracker.secondInstanceDetectionInterval !== null) {
|
|
101
|
+
clearInterval(tracker.secondInstanceDetectionInterval);
|
|
102
|
+
tracker.secondInstanceDetectionInterval = null;
|
|
103
|
+
}
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
detector.tick().catch(() => {});
|
|
107
|
+
};
|
|
108
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { BatchOperation } from '../../storage/interface.ts';
|
|
1
2
|
import type { ComposedWorkflowInterceptor } from '../interceptor.ts';
|
|
2
3
|
import type { SignalDeliveryOptions, WorkflowState } from '../types.ts';
|
|
3
4
|
import type { EngineInternals } from './internals.ts';
|
|
@@ -31,8 +32,45 @@ export type ConsumedSignalResult = {
|
|
|
31
32
|
export declare function signal(internals: EngineInternals, workflowId: string, name: string, payload: unknown, callbacks: SignalCallbacks, options?: SignalDeliveryOptions): Promise<void>;
|
|
32
33
|
export declare function releaseSignalWaiter(internals: EngineInternals, workflowId: string, waiterKey: string, expectedResolve?: () => void): void;
|
|
33
34
|
export declare function bufferSignalPayloads(internals: EngineInternals, workflowId: string, deliveries: BufferedSignalDelivery[], callbacks: SignalCallbacks, defaultOptions?: SignalDeliveryOptions): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Build the durable operations for a single keyed signal so it can be folded
|
|
37
|
+
* into a workflow's create batch by {@link startOrSignal}. Writes the same pair
|
|
38
|
+
* the live signal path writes — the `sig:` payload (consumed on first drive by
|
|
39
|
+
* `processWaitSignalOperation`) and the `sigres:` accepted-response marker — so a
|
|
40
|
+
* concurrent caller that falls back to the standard signal path dedups against
|
|
41
|
+
* the SAME `signalId`. The accepted-response marker is consumption-independent:
|
|
42
|
+
* even after the winning run consumes the `sig:` payload, a late loser finds the
|
|
43
|
+
* `sigres:` key and short-circuits instead of re-delivering, which is what
|
|
44
|
+
* guarantees "one signal per signalId" across the create and signal paths.
|
|
45
|
+
*
|
|
46
|
+
* Returns both the put operations and the CAS condition. The condition gates on
|
|
47
|
+
* the `sigres:` accepted-response marker (the dedup identity, keyed by signalId
|
|
48
|
+
* alone), NOT the `sig:` payload key: the FIFO sort-class (#458) makes the
|
|
49
|
+
* start-signal's `sig:` key (class `0`) differ from the live signal path's (class
|
|
50
|
+
* `1`), so a `sig:`-keyed CAS would no longer collide with a pre-buffered signal
|
|
51
|
+
* of the same signalId and would buffer a second copy. The class-independent
|
|
52
|
+
* `sigres:` marker is what both paths share, so gating on it preserves "one
|
|
53
|
+
* signal per signalId" across the create and signal paths.
|
|
54
|
+
*/
|
|
55
|
+
export declare function buildCreateBatchSignalOperations(internals: EngineInternals, workflowId: string, signalName: string, payload: unknown, signalId: string): {
|
|
56
|
+
operations: BatchOperation[];
|
|
57
|
+
condition: {
|
|
58
|
+
key: string;
|
|
59
|
+
expectedValue: null;
|
|
60
|
+
};
|
|
61
|
+
};
|
|
34
62
|
export declare function hasBufferedSignal(internals: EngineInternals, workflowId: string, signalName: string): Promise<boolean>;
|
|
35
63
|
export declare function consumeSignal(internals: EngineInternals, workflowId: string, signalName: string): Promise<ConsumedSignalResult>;
|
|
64
|
+
export declare function consumeSignalWithAtomicWorkflowCommit(internals: EngineInternals, workflowId: string, signalName: string): Promise<ConsumedSignalResult>;
|
|
65
|
+
/**
|
|
66
|
+
* Read the first buffered signal payload WITHOUT deleting it. Use when a caller
|
|
67
|
+
* must check for a buffered signal but might not end up consuming it — e.g. a
|
|
68
|
+
* `ctx.race` / `ctx.all` wait-signal branch that could lose, where a destructive
|
|
69
|
+
* {@link consumeSignal} on the losing path would silently drop the signal. The
|
|
70
|
+
* winner calls {@link consumeSignalWithAtomicWorkflowCommit} to stage the
|
|
71
|
+
* durable delete into the checkpoint batch.
|
|
72
|
+
*/
|
|
73
|
+
export declare function peekSignal(internals: EngineInternals, workflowId: string, signalName: string): Promise<ConsumedSignalResult>;
|
|
36
74
|
/** Register a waiter key in a workflow-keyed reverse index. */
|
|
37
75
|
export declare function trackWaiterKey(reverseIndex: Map<string, TrackedWaiterKeys>, workflowId: string, waiterKey: string): void;
|
|
38
76
|
/** Remove a waiter key from a workflow-keyed reverse index. */
|