@adonis-agora/durable 1.0.0-rc.2 → 1.0.0-rc.21
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/CHANGELOG.md +204 -0
- package/dist/assets/spa/assets/index-BgoNV3TR.css +1 -0
- package/dist/assets/spa/assets/index-DzXygyDm.js +57 -0
- package/dist/assets/spa/assets/index-DzXygyDm.js.map +1 -0
- package/dist/assets/spa/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/dist/assets/spa/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/dist/assets/spa/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
- package/dist/assets/spa/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
- package/dist/assets/spa/assets/jetbrains-mono-latin-600-normal-BfsvjouI.woff +0 -0
- package/dist/assets/spa/assets/jetbrains-mono-latin-600-normal-C8RAYTDA.woff2 +0 -0
- package/dist/assets/spa/assets/sora-latin-400-normal-CRt88UEn.woff2 +0 -0
- package/dist/assets/spa/assets/sora-latin-400-normal-OW7qkl5a.woff +0 -0
- package/dist/assets/spa/assets/sora-latin-500-normal-01eiPEn0.woff2 +0 -0
- package/dist/assets/spa/assets/sora-latin-500-normal-w58xtEt9.woff +0 -0
- package/dist/assets/spa/assets/sora-latin-600-normal-1_7fyUAY.woff +0 -0
- package/dist/assets/spa/assets/sora-latin-600-normal-Cdg4DaK0.woff2 +0 -0
- package/dist/assets/spa/assets/sora-latin-700-normal-9waGdLWo.woff2 +0 -0
- package/dist/assets/spa/assets/sora-latin-700-normal-BKPfQAnC.woff +0 -0
- package/dist/assets/spa/index.html +2 -8
- package/dist/commands/agora_doctor.d.ts +23 -0
- package/dist/commands/agora_doctor.d.ts.map +1 -0
- package/dist/commands/agora_doctor.js +110 -0
- package/dist/commands/agora_doctor.js.map +1 -0
- package/dist/commands/durable_doctor.d.ts +8 -0
- package/dist/commands/durable_doctor.d.ts.map +1 -0
- package/dist/commands/durable_doctor.js +8 -0
- package/dist/commands/durable_doctor.js.map +1 -0
- package/dist/commands/durable_worker.d.ts +3 -3
- package/dist/commands/durable_worker.js +7 -7
- package/dist/commands/durable_worker.js.map +1 -1
- package/dist/commands/health.d.ts +12 -0
- package/dist/commands/health.d.ts.map +1 -0
- package/dist/commands/health.js +34 -0
- package/dist/commands/health.js.map +1 -0
- package/dist/commands/lifecycle_republish.d.ts +8 -9
- package/dist/commands/lifecycle_republish.d.ts.map +1 -1
- package/dist/commands/lifecycle_republish.js +29 -16
- package/dist/commands/lifecycle_republish.js.map +1 -1
- package/dist/commands/main.d.ts +25 -5
- package/dist/commands/main.d.ts.map +1 -1
- package/dist/commands/main.js +54 -7
- package/dist/commands/main.js.map +1 -1
- package/dist/commands/runs.d.ts +9 -16
- package/dist/commands/runs.d.ts.map +1 -1
- package/dist/commands/runs.js +60 -47
- package/dist/commands/runs.js.map +1 -1
- package/dist/commands/runs_bulk.d.ts +17 -0
- package/dist/commands/runs_bulk.d.ts.map +1 -0
- package/dist/commands/runs_bulk.js +76 -0
- package/dist/commands/runs_bulk.js.map +1 -0
- package/dist/commands/runs_cancel.d.ts +15 -0
- package/dist/commands/runs_cancel.d.ts.map +1 -0
- package/dist/commands/runs_cancel.js +39 -0
- package/dist/commands/runs_cancel.js.map +1 -0
- package/dist/commands/runs_continue.d.ts +12 -0
- package/dist/commands/runs_continue.d.ts.map +1 -0
- package/dist/commands/runs_continue.js +28 -0
- package/dist/commands/runs_continue.js.map +1 -0
- package/dist/commands/runs_event.d.ts +13 -0
- package/dist/commands/runs_event.d.ts.map +1 -0
- package/dist/commands/runs_event.js +28 -0
- package/dist/commands/runs_event.js.map +1 -0
- package/dist/commands/runs_fan.d.ts +16 -0
- package/dist/commands/runs_fan.d.ts.map +1 -0
- package/dist/commands/runs_fan.js +58 -0
- package/dist/commands/runs_fan.js.map +1 -0
- package/dist/commands/runs_redispatch.d.ts +14 -0
- package/dist/commands/runs_redispatch.d.ts.map +1 -0
- package/dist/commands/runs_redispatch.js +35 -0
- package/dist/commands/runs_redispatch.js.map +1 -0
- package/dist/commands/runs_replay.d.ts +14 -0
- package/dist/commands/runs_replay.d.ts.map +1 -0
- package/dist/commands/runs_replay.js +38 -0
- package/dist/commands/runs_replay.js.map +1 -0
- package/dist/commands/runs_retry.d.ts +13 -0
- package/dist/commands/runs_retry.d.ts.map +1 -0
- package/dist/commands/runs_retry.js +29 -0
- package/dist/commands/runs_retry.js.map +1 -0
- package/dist/commands/runs_show.d.ts +14 -0
- package/dist/commands/runs_show.d.ts.map +1 -0
- package/dist/commands/runs_show.js +96 -0
- package/dist/commands/runs_show.js.map +1 -0
- package/dist/commands/runs_signal.d.ts +17 -0
- package/dist/commands/runs_signal.d.ts.map +1 -0
- package/dist/commands/runs_signal.js +49 -0
- package/dist/commands/runs_signal.js.map +1 -0
- package/dist/commands/runs_update.d.ts +15 -0
- package/dist/commands/runs_update.d.ts.map +1 -0
- package/dist/commands/runs_update.js +39 -0
- package/dist/commands/runs_update.js.map +1 -0
- package/dist/commands/runs_values.d.ts +15 -0
- package/dist/commands/runs_values.d.ts.map +1 -0
- package/dist/commands/runs_values.js +45 -0
- package/dist/commands/runs_values.js.map +1 -0
- package/dist/commands/schedules.d.ts +18 -0
- package/dist/commands/schedules.d.ts.map +1 -0
- package/dist/commands/schedules.js +70 -0
- package/dist/commands/schedules.js.map +1 -0
- package/dist/commands/schedules_pause.d.ts +16 -0
- package/dist/commands/schedules_pause.d.ts.map +1 -0
- package/dist/commands/schedules_pause.js +40 -0
- package/dist/commands/schedules_pause.js.map +1 -0
- package/dist/commands/schedules_resume.d.ts +8 -0
- package/dist/commands/schedules_resume.d.ts.map +1 -0
- package/dist/commands/schedules_resume.js +8 -0
- package/dist/commands/schedules_resume.js.map +1 -0
- package/dist/commands/schedules_trigger.d.ts +13 -0
- package/dist/commands/schedules_trigger.d.ts.map +1 -0
- package/dist/commands/schedules_trigger.js +32 -0
- package/dist/commands/schedules_trigger.js.map +1 -0
- package/dist/commands/tasks_complete.d.ts +15 -0
- package/dist/commands/tasks_complete.d.ts.map +1 -0
- package/dist/commands/tasks_complete.js +41 -0
- package/dist/commands/tasks_complete.js.map +1 -0
- package/dist/commands/tasks_fail.d.ts +16 -0
- package/dist/commands/tasks_fail.d.ts.map +1 -0
- package/dist/commands/tasks_fail.js +47 -0
- package/dist/commands/tasks_fail.js.map +1 -0
- package/dist/commands/workers.d.ts +11 -0
- package/dist/commands/workers.d.ts.map +1 -0
- package/dist/commands/workers.js +35 -0
- package/dist/commands/workers.js.map +1 -0
- package/dist/commands/workflows_lifecycle.d.ts +12 -0
- package/dist/commands/workflows_lifecycle.d.ts.map +1 -0
- package/dist/commands/workflows_lifecycle.js +37 -0
- package/dist/commands/workflows_lifecycle.js.map +1 -0
- package/dist/configure.d.ts +2 -2
- package/dist/configure.js +2 -2
- package/dist/providers/dashboard_provider.d.ts +57 -54
- package/dist/providers/dashboard_provider.d.ts.map +1 -1
- package/dist/providers/dashboard_provider.js +399 -288
- package/dist/providers/dashboard_provider.js.map +1 -1
- package/dist/providers/durable_provider.d.ts.map +1 -1
- package/dist/providers/durable_provider.js +67 -37
- package/dist/providers/durable_provider.js.map +1 -1
- package/dist/src/commands/console_command.d.ts +82 -0
- package/dist/src/commands/console_command.d.ts.map +1 -0
- package/dist/src/commands/console_command.js +238 -0
- package/dist/src/commands/console_command.js.map +1 -0
- package/dist/src/commands/runs.js +2 -2
- package/dist/src/commands/runs.js.map +1 -1
- package/dist/src/concurrency.d.ts.map +1 -1
- package/dist/src/concurrency.js +1 -0
- package/dist/src/concurrency.js.map +1 -1
- package/dist/src/config_types.d.ts +79 -26
- package/dist/src/config_types.d.ts.map +1 -1
- package/dist/src/dashboard/access_denied_page.d.ts +5 -7
- package/dist/src/dashboard/access_denied_page.d.ts.map +1 -1
- package/dist/src/dashboard/access_denied_page.js +2 -6
- package/dist/src/dashboard/access_denied_page.js.map +1 -1
- package/dist/src/dashboard/audit.d.ts +14 -0
- package/dist/src/dashboard/audit.d.ts.map +1 -0
- package/dist/src/dashboard/audit.js +36 -0
- package/dist/src/dashboard/audit.js.map +1 -0
- package/dist/src/dashboard/console_authorize.d.ts +98 -0
- package/dist/src/dashboard/console_authorize.d.ts.map +1 -0
- package/dist/src/dashboard/console_authorize.js +163 -0
- package/dist/src/dashboard/console_authorize.js.map +1 -0
- package/dist/src/dashboard/console_shell.d.ts +45 -0
- package/dist/src/dashboard/console_shell.d.ts.map +1 -0
- package/dist/src/dashboard/console_shell.js +125 -0
- package/dist/src/dashboard/console_shell.js.map +1 -0
- package/dist/src/dashboard/console_switcher.d.ts +132 -0
- package/dist/src/dashboard/console_switcher.d.ts.map +1 -0
- package/dist/src/dashboard/console_switcher.js +201 -0
- package/dist/src/dashboard/console_switcher.js.map +1 -0
- package/dist/src/dashboard/define_config.d.ts +68 -81
- package/dist/src/dashboard/define_config.d.ts.map +1 -1
- package/dist/src/dashboard/define_config.js +18 -112
- package/dist/src/dashboard/define_config.js.map +1 -1
- package/dist/src/dashboard/dev_tools.d.ts +102 -0
- package/dist/src/dashboard/dev_tools.d.ts.map +1 -0
- package/dist/src/dashboard/dev_tools.js +502 -0
- package/dist/src/dashboard/dev_tools.js.map +1 -0
- package/dist/src/dashboard/diagnostics-recorder.d.ts +1 -1
- package/dist/src/dashboard/diagnostics-recorder.js +1 -1
- package/dist/src/dashboard/gateway-adapter.d.ts +6 -6
- package/dist/src/dashboard/gateway-adapter.d.ts.map +1 -1
- package/dist/src/dashboard/gateway-adapter.js +5 -5
- package/dist/src/dashboard/gateway-adapter.js.map +1 -1
- package/dist/src/dashboard/handlers.d.ts +48 -39
- package/dist/src/dashboard/handlers.d.ts.map +1 -1
- package/dist/src/dashboard/handlers.js +181 -83
- package/dist/src/dashboard/handlers.js.map +1 -1
- package/dist/src/dashboard/index.d.ts +12 -9
- package/dist/src/dashboard/index.d.ts.map +1 -1
- package/dist/src/dashboard/index.js +10 -7
- package/dist/src/dashboard/index.js.map +1 -1
- package/dist/src/dashboard/list-filters.d.ts +27 -0
- package/dist/src/dashboard/list-filters.d.ts.map +1 -0
- package/dist/src/dashboard/list-filters.js +69 -0
- package/dist/src/dashboard/list-filters.js.map +1 -0
- package/dist/src/dashboard/openapi.d.ts.map +1 -1
- package/dist/src/dashboard/openapi.js +127 -52
- package/dist/src/dashboard/openapi.js.map +1 -1
- package/dist/src/dashboard/run-filter.d.ts +18 -15
- package/dist/src/dashboard/run-filter.d.ts.map +1 -1
- package/dist/src/dashboard/run-filter.js +30 -22
- package/dist/src/dashboard/run-filter.js.map +1 -1
- package/dist/src/dashboard/spa.d.ts +26 -1
- package/dist/src/dashboard/spa.d.ts.map +1 -1
- package/dist/src/dashboard/spa.js +9 -2
- package/dist/src/dashboard/spa.js.map +1 -1
- package/dist/src/dashboard/tenant_scope.d.ts +38 -0
- package/dist/src/dashboard/tenant_scope.d.ts.map +1 -0
- package/dist/src/dashboard/tenant_scope.js +166 -0
- package/dist/src/dashboard/tenant_scope.js.map +1 -0
- package/dist/src/define_config.d.ts +27 -12
- package/dist/src/define_config.d.ts.map +1 -1
- package/dist/src/define_config.js +9 -4
- package/dist/src/define_config.js.map +1 -1
- package/dist/src/diagnostics/index.d.ts +11 -0
- package/dist/src/diagnostics/index.d.ts.map +1 -0
- package/dist/src/diagnostics/index.js +6 -0
- package/dist/src/diagnostics/index.js.map +1 -0
- package/dist/src/diagnostics/transport.d.ts +71 -0
- package/dist/src/diagnostics/transport.d.ts.map +1 -0
- package/dist/src/diagnostics/transport.js +188 -0
- package/dist/src/diagnostics/transport.js.map +1 -0
- package/dist/src/diagnostics-bridge.d.ts +2 -1
- package/dist/src/diagnostics-bridge.d.ts.map +1 -1
- package/dist/src/diagnostics-bridge.js +7 -1
- package/dist/src/diagnostics-bridge.js.map +1 -1
- package/dist/src/doctor/agora_doctor.d.ts +116 -0
- package/dist/src/doctor/agora_doctor.d.ts.map +1 -0
- package/dist/src/doctor/agora_doctor.js +685 -0
- package/dist/src/doctor/agora_doctor.js.map +1 -0
- package/dist/src/doctor/checks.d.ts +28 -0
- package/dist/src/doctor/checks.d.ts.map +1 -0
- package/dist/src/doctor/checks.js +288 -0
- package/dist/src/doctor/checks.js.map +1 -0
- package/dist/src/doctor/index.d.ts +9 -0
- package/dist/src/doctor/index.d.ts.map +1 -0
- package/dist/src/doctor/index.js +16 -0
- package/dist/src/doctor/index.js.map +1 -0
- package/dist/src/engine.d.ts +55 -9
- package/dist/src/engine.d.ts.map +1 -1
- package/dist/src/engine.js +333 -84
- package/dist/src/engine.js.map +1 -1
- package/dist/src/errors.d.ts +9 -0
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/errors.js +13 -0
- package/dist/src/errors.js.map +1 -1
- package/dist/src/event-trigger-bridge.d.ts +5 -14
- package/dist/src/event-trigger-bridge.d.ts.map +1 -1
- package/dist/src/event-trigger-bridge.js +11 -99
- package/dist/src/event-trigger-bridge.js.map +1 -1
- package/dist/src/index.d.ts +8 -5
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +6 -4
- package/dist/src/index.js.map +1 -1
- package/dist/src/interfaces.d.ts +53 -9
- package/dist/src/interfaces.d.ts.map +1 -1
- package/dist/src/interfaces.js +8 -0
- package/dist/src/interfaces.js.map +1 -1
- package/dist/src/origin.js +3 -3
- package/dist/src/role_bindings.d.ts +8 -8
- package/dist/src/role_bindings.js +8 -8
- package/dist/src/run-gateway/interface.d.ts +4 -3
- package/dist/src/run-gateway/interface.d.ts.map +1 -1
- package/dist/src/run-gateway/proxy-run-gateway.d.ts +4 -2
- package/dist/src/run-gateway/proxy-run-gateway.d.ts.map +1 -1
- package/dist/src/run-gateway/proxy-run-gateway.js +21 -6
- package/dist/src/run-gateway/proxy-run-gateway.js.map +1 -1
- package/dist/src/run-gateway/run-request-responder.d.ts +6 -1
- package/dist/src/run-gateway/run-request-responder.d.ts.map +1 -1
- package/dist/src/run-gateway/run-request-responder.js +30 -3
- package/dist/src/run-gateway/run-request-responder.js.map +1 -1
- package/dist/src/run-gateway/store-run-gateway.d.ts +4 -4
- package/dist/src/run-gateway/store-run-gateway.d.ts.map +1 -1
- package/dist/src/run-gateway/store-run-gateway.js +4 -4
- package/dist/src/run-gateway/store-run-gateway.js.map +1 -1
- package/dist/src/run-gateway/tenant-auth.d.ts +19 -1
- package/dist/src/run-gateway/tenant-auth.d.ts.map +1 -1
- package/dist/src/run-gateway/tenant-auth.js +133 -0
- package/dist/src/run-gateway/tenant-auth.js.map +1 -1
- package/dist/src/run-gateway/tenant-scoped-gateway.d.ts +10 -0
- package/dist/src/run-gateway/tenant-scoped-gateway.d.ts.map +1 -0
- package/dist/src/run-gateway/tenant-scoped-gateway.js +107 -0
- package/dist/src/run-gateway/tenant-scoped-gateway.js.map +1 -0
- package/dist/src/run-value-facets.d.ts +1 -1
- package/dist/src/run-value-facets.d.ts.map +1 -1
- package/dist/src/run-value-facets.js +5 -1
- package/dist/src/run-value-facets.js.map +1 -1
- package/dist/src/scheduler.d.ts.map +1 -1
- package/dist/src/scheduler.js +2 -1
- package/dist/src/scheduler.js.map +1 -1
- package/dist/src/schedules.d.ts +16 -1
- package/dist/src/schedules.d.ts.map +1 -1
- package/dist/src/schedules.js +29 -4
- package/dist/src/schedules.js.map +1 -1
- package/dist/src/services/main.d.ts +3 -3
- package/dist/src/services/main.js +6 -6
- package/dist/src/services/main.js.map +1 -1
- package/dist/src/singleton-admission.d.ts.map +1 -1
- package/dist/src/singleton-admission.js +2 -0
- package/dist/src/singleton-admission.js.map +1 -1
- package/dist/src/singleton-gate.d.ts +1 -1
- package/dist/src/singleton-gate.d.ts.map +1 -1
- package/dist/src/singleton-gate.js +4 -3
- package/dist/src/singleton-gate.js.map +1 -1
- package/dist/src/sql-schema.d.ts +54 -0
- package/dist/src/sql-schema.d.ts.map +1 -0
- package/dist/src/sql-schema.js +145 -0
- package/dist/src/sql-schema.js.map +1 -0
- package/dist/src/stores/factory.d.ts +6 -0
- package/dist/src/stores/factory.d.ts.map +1 -1
- package/dist/src/stores/factory.js +3 -0
- package/dist/src/stores/factory.js.map +1 -1
- package/dist/src/stores/lucid-mappers.d.ts +3 -0
- package/dist/src/stores/lucid-mappers.d.ts.map +1 -1
- package/dist/src/stores/lucid-mappers.js +12 -0
- package/dist/src/stores/lucid-mappers.js.map +1 -1
- package/dist/src/stores/lucid-pg-hot-path.d.ts +6 -2
- package/dist/src/stores/lucid-pg-hot-path.d.ts.map +1 -1
- package/dist/src/stores/lucid-pg-hot-path.js +7 -3
- package/dist/src/stores/lucid-pg-hot-path.js.map +1 -1
- package/dist/src/stores/lucid-schema.d.ts +18 -4
- package/dist/src/stores/lucid-schema.d.ts.map +1 -1
- package/dist/src/stores/lucid-schema.js +106 -61
- package/dist/src/stores/lucid-schema.js.map +1 -1
- package/dist/src/stores/lucid.d.ts +42 -2
- package/dist/src/stores/lucid.d.ts.map +1 -1
- package/dist/src/stores/lucid.js +450 -231
- package/dist/src/stores/lucid.js.map +1 -1
- package/dist/src/tenancy.d.ts +104 -0
- package/dist/src/tenancy.d.ts.map +1 -0
- package/dist/src/tenancy.js +91 -0
- package/dist/src/tenancy.js.map +1 -0
- package/dist/src/testing/in-memory-state-store.d.ts.map +1 -1
- package/dist/src/testing/in-memory-state-store.js +9 -3
- package/dist/src/testing/in-memory-state-store.js.map +1 -1
- package/dist/src/testing-kit/state-store-conformance.d.ts.map +1 -1
- package/dist/src/testing-kit/state-store-conformance.js +59 -0
- package/dist/src/testing-kit/state-store-conformance.js.map +1 -1
- package/dist/src/testing-kit/transport-conformance.d.ts.map +1 -1
- package/dist/src/testing-kit/transport-conformance.js +5 -1
- package/dist/src/testing-kit/transport-conformance.js.map +1 -1
- package/dist/src/transports/bullmq/bullmq-transport.d.ts +1 -1
- package/dist/src/transports/bullmq/bullmq-transport.d.ts.map +1 -1
- package/dist/src/transports/bullmq/bullmq-transport.js +14 -3
- package/dist/src/transports/bullmq/bullmq-transport.js.map +1 -1
- package/dist/src/transports/db-schema.d.ts +13 -5
- package/dist/src/transports/db-schema.d.ts.map +1 -1
- package/dist/src/transports/db-schema.js +52 -27
- package/dist/src/transports/db-schema.js.map +1 -1
- package/dist/src/transports/db.d.ts +6 -0
- package/dist/src/transports/db.d.ts.map +1 -1
- package/dist/src/transports/db.js +91 -42
- package/dist/src/transports/db.js.map +1 -1
- package/dist/src/transports/factory.d.ts +16 -0
- package/dist/src/transports/factory.d.ts.map +1 -1
- package/dist/src/transports/factory.js +6 -1
- package/dist/src/transports/factory.js.map +1 -1
- package/dist/src/transports/queue.d.ts +2 -2
- package/dist/src/transports/queue.d.ts.map +1 -1
- package/dist/src/transports/queue.js +49 -8
- package/dist/src/transports/queue.js.map +1 -1
- package/dist/src/transports/worker-keys-db.d.ts +5 -0
- package/dist/src/transports/worker-keys-db.d.ts.map +1 -1
- package/dist/src/transports/worker-keys-db.js +10 -8
- package/dist/src/transports/worker-keys-db.js.map +1 -1
- package/dist/src/workflow-discovery.d.ts +1 -1
- package/dist/src/workflow-discovery.d.ts.map +1 -1
- package/dist/src/workflow-discovery.js +4 -7
- package/dist/src/workflow-discovery.js.map +1 -1
- package/dist/src/workflow-events.d.ts +20 -51
- package/dist/src/workflow-events.d.ts.map +1 -1
- package/dist/src/workflow-events.js +12 -30
- package/dist/src/workflow-events.js.map +1 -1
- package/dist/src/workflow-ref.d.ts +1 -1
- package/dist/stubs/config/durable_dashboard.stub +9 -29
- package/dist/stubs/database/migrations/create_durable_tables.stub +12 -2
- package/dist/stubs/database/migrations/create_durable_transport_messages.stub +34 -5
- package/dist/stubs/database/migrations/create_durable_transport_tables.stub +30 -8
- package/dist/stubs/database/migrations/create_durable_transport_worker_keys.stub +24 -2
- package/package.json +25 -7
- package/skills/durable-cluster/SKILL.md +43 -23
- package/skills/durable-reliability/SKILL.md +13 -7
- package/dist/assets/spa/assets/index-70hGzOnT.css +0 -1
- package/dist/assets/spa/assets/index-CJjMAUau.js +0 -16
- package/dist/assets/spa/assets/index-CJjMAUau.js.map +0 -1
- package/dist/commands/retry.d.ts +0 -16
- package/dist/commands/retry.d.ts.map +0 -1
- package/dist/commands/retry.js +0 -34
- package/dist/commands/retry.js.map +0 -1
- package/dist/src/dashboard/auth.d.ts +0 -134
- package/dist/src/dashboard/auth.d.ts.map +0 -1
- package/dist/src/dashboard/auth.js +0 -156
- package/dist/src/dashboard/auth.js.map +0 -1
- package/dist/src/dashboard/login_page.d.ts +0 -36
- package/dist/src/dashboard/login_page.d.ts.map +0 -1
- package/dist/src/dashboard/login_page.js +0 -158
- package/dist/src/dashboard/login_page.js.map +0 -1
- package/dist/src/dashboard/session_cookie.d.ts +0 -45
- package/dist/src/dashboard/session_cookie.d.ts.map +0 -1
- package/dist/src/dashboard/session_cookie.js +0 -97
- package/dist/src/dashboard/session_cookie.js.map +0 -1
package/dist/src/engine.js
CHANGED
|
@@ -36,6 +36,7 @@ import { SINGLETON_RESERVED_TAGS, withoutAdmission, withSingletonTracked, } from
|
|
|
36
36
|
import { SingletonGate } from './singleton-gate.js';
|
|
37
37
|
import { validateStandard, validateStandardSync, } from './standard-schema.js';
|
|
38
38
|
import { StickyCache, StickyContinuation } from './sticky-continuation.js';
|
|
39
|
+
import { intersectTenantIds, runTenantOf, runVisible, tenantScopeIds, } from './tenancy.js';
|
|
39
40
|
import { sanitizeQueueToken, tenantGroup } from './tenant-group.js';
|
|
40
41
|
import { TransportPool } from './transport-pool.js';
|
|
41
42
|
import { workflowAls } from './workflow-als.js';
|
|
@@ -72,6 +73,13 @@ const isBreakpoint = (cp) => cp.status === 'pending' && cp.name.startsWith(BREAK
|
|
|
72
73
|
* through some other path (an operator's cancel, a timeout sweep, a dead-letter) while this turn's
|
|
73
74
|
* outcome was being computed from a possibly-stale snapshot.
|
|
74
75
|
*/
|
|
76
|
+
/** Statuses a wake refused by a held lease may stamp a request for another turn on. */
|
|
77
|
+
const WAKEABLE_RUN_STATUSES = ['running', 'suspended', 'blocked', 'cancelling'];
|
|
78
|
+
/**
|
|
79
|
+
* Statuses a wake that finds the lease free re-drives: parked runs, and a `cancelling` run whose
|
|
80
|
+
* undo is not executing (its turn unwinds the saga).
|
|
81
|
+
*/
|
|
82
|
+
const PARKED_RUN_STATUSES = ['suspended', 'blocked', 'cancelling'];
|
|
75
83
|
const SETTLE_ELIGIBLE_STATUSES = [
|
|
76
84
|
'pending',
|
|
77
85
|
'running',
|
|
@@ -247,7 +255,13 @@ function isNewerVersion(a, b) {
|
|
|
247
255
|
return a.localeCompare(b, undefined, { numeric: true }) > 0;
|
|
248
256
|
}
|
|
249
257
|
/** The statuses a run can be skipped in for an unregistered workflow — every non-terminal one. */
|
|
250
|
-
const UNREGISTERED_DEAD_LETTER_FROM = [
|
|
258
|
+
const UNREGISTERED_DEAD_LETTER_FROM = [
|
|
259
|
+
'pending',
|
|
260
|
+
'running',
|
|
261
|
+
'suspended',
|
|
262
|
+
'blocked',
|
|
263
|
+
'cancelling',
|
|
264
|
+
];
|
|
251
265
|
/** Thrown by {@link WorkflowEngine.resume} when the run belongs to a different namespace. */
|
|
252
266
|
export class NamespaceMismatch extends Error {
|
|
253
267
|
constructor() {
|
|
@@ -325,6 +339,7 @@ export class WorkflowEngine {
|
|
|
325
339
|
context;
|
|
326
340
|
/** Establish the originating context ambiently around a local step body (see {@link WorkflowEngineDeps.rehydrate}). Default passthrough. */
|
|
327
341
|
rehydrate;
|
|
342
|
+
tenancy;
|
|
328
343
|
compensationRetries;
|
|
329
344
|
compensationTimeoutMs;
|
|
330
345
|
retention;
|
|
@@ -402,6 +417,8 @@ export class WorkflowEngine {
|
|
|
402
417
|
#allSchedules = [];
|
|
403
418
|
/** Runtime pause overrides by schedule key (console-issued; win over the config's `paused`). */
|
|
404
419
|
#schedulePauseOverrides = new Map();
|
|
420
|
+
/** Schedule-pause broadcasts still being handed to the control plane (see {@link flushControl}). */
|
|
421
|
+
#controlInFlight = new Set();
|
|
405
422
|
/** Flow-control admission backend for remote steps (see {@link registerQueue}). */
|
|
406
423
|
admission;
|
|
407
424
|
/** Runs on THIS instance blocked on admission, by queue — woken early on a freed-slot signal. */
|
|
@@ -602,6 +619,7 @@ export class WorkflowEngine {
|
|
|
602
619
|
this.context = deps.context;
|
|
603
620
|
// Default passthrough: with no bridge supplied, a local step body runs exactly as before.
|
|
604
621
|
this.rehydrate = deps.rehydrate ?? ((_carrier, fn) => fn());
|
|
622
|
+
this.tenancy = deps.tenancy;
|
|
605
623
|
this.compensationRetries = Math.max(1, deps.compensationRetries ?? 1);
|
|
606
624
|
this.compensationTimeoutMs = Math.max(1_000, deps.compensationTimeoutMs ?? 300_000);
|
|
607
625
|
this.retention = deps.retention;
|
|
@@ -613,6 +631,8 @@ export class WorkflowEngine {
|
|
|
613
631
|
start: (workflow, input, runId, opts) => this.start(workflow, input, runId, opts),
|
|
614
632
|
getRun: (runId) => this.store.getRun(runId),
|
|
615
633
|
isDraining: () => this.draining,
|
|
634
|
+
tenant: () => this.startTenant(undefined),
|
|
635
|
+
visible: (record) => this.runVisibleNow({ tenantId: record.tenantId }),
|
|
616
636
|
}, { namespace: this.namespace, clock: this.clock });
|
|
617
637
|
this.stalledAfterMs = Math.max(60_000, deps.stalledAfterMs ?? 900_000);
|
|
618
638
|
this.trackStepStart = deps.trackStepStart ?? true;
|
|
@@ -1007,12 +1027,23 @@ export class WorkflowEngine {
|
|
|
1007
1027
|
return false;
|
|
1008
1028
|
this.#schedulePauseOverrides.set(key, paused);
|
|
1009
1029
|
if (this.controlPlane) {
|
|
1010
|
-
|
|
1030
|
+
const sent = this.controlPlane
|
|
1011
1031
|
.publishControl({ kind: 'schedulePause', key, paused, from: this.instanceId })
|
|
1012
|
-
.catch(() => undefined)
|
|
1032
|
+
.catch(() => undefined)
|
|
1033
|
+
.finally(() => this.#controlInFlight.delete(sent));
|
|
1034
|
+
this.#controlInFlight.add(sent);
|
|
1013
1035
|
}
|
|
1014
1036
|
return true;
|
|
1015
1037
|
}
|
|
1038
|
+
/**
|
|
1039
|
+
* Wait until the schedule pauses this instance broadcast have been handed to the control plane. A
|
|
1040
|
+
* short-lived process (an ace command) calls it before it exits. Resolves `false` when the engine
|
|
1041
|
+
* has no control plane, so a pause reached this process only.
|
|
1042
|
+
*/
|
|
1043
|
+
async flushControl() {
|
|
1044
|
+
await Promise.allSettled([...this.#controlInFlight]);
|
|
1045
|
+
return this.controlPlane !== undefined;
|
|
1046
|
+
}
|
|
1016
1047
|
/**
|
|
1017
1048
|
* Fire a schedule's CURRENT window now (console "run now"). Idempotent by the window's
|
|
1018
1049
|
* deterministic run id — triggering a window that already fired returns the existing run instead
|
|
@@ -1452,6 +1483,8 @@ export class WorkflowEngine {
|
|
|
1452
1483
|
};
|
|
1453
1484
|
this.trackEffect(this.start(target, input, `dlq:${run.id}`, {
|
|
1454
1485
|
...(run.namespace !== undefined ? { namespace: run.namespace } : {}),
|
|
1486
|
+
tenantId: run.tenantId ?? null,
|
|
1487
|
+
...(run.tenantKind ? { tenantKind: run.tenantKind } : {}),
|
|
1455
1488
|
}).catch((err) => {
|
|
1456
1489
|
console.warn(`[adonis-durable] could not start dead-letter workflow ${target} for run ${run.id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1457
1490
|
}));
|
|
@@ -2535,6 +2568,11 @@ export class WorkflowEngine {
|
|
|
2535
2568
|
*/
|
|
2536
2569
|
async republishLifecycle(target) {
|
|
2537
2570
|
const result = { runs: 0, started: 0, failed: [] };
|
|
2571
|
+
if ('runId' in target &&
|
|
2572
|
+
typeof target.runId === 'string' &&
|
|
2573
|
+
(await this.runHidden(target.runId))) {
|
|
2574
|
+
return result;
|
|
2575
|
+
}
|
|
2538
2576
|
// A backfill usually follows a deploy: read the fleet's subscriptions now, not from the cache.
|
|
2539
2577
|
await this.refreshAnnouncedSubscriptions();
|
|
2540
2578
|
const publish = async (run) => {
|
|
@@ -2779,6 +2817,9 @@ export class WorkflowEngine {
|
|
|
2779
2817
|
// Idempotent by run id: a redelivered trigger (at-least-once queues) or a scheduler re-tick for
|
|
2780
2818
|
// the same id is a no-op, returning the existing run's state instead of starting a duplicate.
|
|
2781
2819
|
const existing = async (prior) => {
|
|
2820
|
+
// Another tenant's run id: answer like a clash, never with that run's state.
|
|
2821
|
+
if (!this.runVisibleNow(prior))
|
|
2822
|
+
throw new Error(`run ${runId} already exists`);
|
|
2782
2823
|
// A PENDING prior still needs an executor: re-nudge the dispatcher so a redelivered trigger —
|
|
2783
2824
|
// or a continuation persisted pre-settle (see the ContinueAsNew handoff) picked up by a custom
|
|
2784
2825
|
// dispatcher — doesn't sit `pending` until the next poll. Idempotent: execution is leased, so
|
|
@@ -2827,6 +2868,8 @@ export class WorkflowEngine {
|
|
|
2827
2868
|
// Compared against the ENGINE clock (injectable), not wall time — the same clock the timer
|
|
2828
2869
|
// poller will use to decide the run is due.
|
|
2829
2870
|
const delayed = startAtMs !== undefined && startAtMs > this.clock();
|
|
2871
|
+
const tenant = this.startTenant(opts);
|
|
2872
|
+
const tenantNamespace = tenant && opts?.namespace === undefined ? this.tenancy?.namespace?.(tenant) : undefined;
|
|
2830
2873
|
const run = {
|
|
2831
2874
|
id: runId,
|
|
2832
2875
|
workflow: name,
|
|
@@ -2834,7 +2877,10 @@ export class WorkflowEngine {
|
|
|
2834
2877
|
status: delayed ? 'suspended' : 'pending',
|
|
2835
2878
|
...(delayed ? { wakeAt: startAtMs } : {}),
|
|
2836
2879
|
origin: opts?.origin ?? registered.origin,
|
|
2837
|
-
namespace: opts?.namespace ?? this.namespace,
|
|
2880
|
+
namespace: opts?.namespace ?? tenantNamespace ?? this.namespace,
|
|
2881
|
+
...(tenant
|
|
2882
|
+
? { tenantId: tenant.id, ...(tenant.kind ? { tenantKind: tenant.kind } : {}) }
|
|
2883
|
+
: {}),
|
|
2838
2884
|
input,
|
|
2839
2885
|
tags,
|
|
2840
2886
|
searchAttributes: opts?.searchAttributes,
|
|
@@ -2897,6 +2943,16 @@ export class WorkflowEngine {
|
|
|
2897
2943
|
* `limiter` bounds how many of these run at once (a fan's children wait in it as ids).
|
|
2898
2944
|
*/
|
|
2899
2945
|
handoffRun(workflow, input, runId, opts, onStartError, onAlreadySettled, limiter) {
|
|
2946
|
+
// The tenant is read NOW, inside the parent's turn: a bounded handoff may run later, elsewhere.
|
|
2947
|
+
if (opts?.tenantId === undefined) {
|
|
2948
|
+
const tenant = this.startTenant(opts);
|
|
2949
|
+
if (tenant)
|
|
2950
|
+
opts = {
|
|
2951
|
+
...opts,
|
|
2952
|
+
tenantId: tenant.id,
|
|
2953
|
+
...(tenant.kind ? { tenantKind: tenant.kind } : {}),
|
|
2954
|
+
};
|
|
2955
|
+
}
|
|
2900
2956
|
const handoff = async () => {
|
|
2901
2957
|
// Defer one microtask: never reentrantly resume the parent that is still suspending/settling.
|
|
2902
2958
|
await Promise.resolve();
|
|
@@ -2936,8 +2992,54 @@ export class WorkflowEngine {
|
|
|
2936
2992
|
this.trackEffect(limiter ? limiter.submit(runId, handoff) : handoff());
|
|
2937
2993
|
}
|
|
2938
2994
|
/** Read a run's current persisted state (or null if unknown). A thin pass-through to the store. */
|
|
2939
|
-
getRun(runId) {
|
|
2940
|
-
|
|
2995
|
+
async getRun(runId) {
|
|
2996
|
+
const run = await this.store.getRun(runId);
|
|
2997
|
+
return run && this.runVisibleNow(run) ? run : null;
|
|
2998
|
+
}
|
|
2999
|
+
/** The tenant a new run gets: the explicit option, else the active tenant (tenancy on), else none. */
|
|
3000
|
+
startTenant(opts) {
|
|
3001
|
+
if (opts?.tenantId === null)
|
|
3002
|
+
return undefined;
|
|
3003
|
+
if (typeof opts?.tenantId === 'string' && opts.tenantId !== '') {
|
|
3004
|
+
return opts.tenantKind ? { id: opts.tenantId, kind: opts.tenantKind } : { id: opts.tenantId };
|
|
3005
|
+
}
|
|
3006
|
+
const filter = this.tenantFilterNow();
|
|
3007
|
+
return filter.mode === 'tenant' ? filter.tenant : undefined;
|
|
3008
|
+
}
|
|
3009
|
+
/** How the current app-facing call is scoped (`system` inside the engine's own work). */
|
|
3010
|
+
tenantFilterNow() {
|
|
3011
|
+
if (!this.tenancy || engineInternal.getStore() === true)
|
|
3012
|
+
return { mode: 'system' };
|
|
3013
|
+
return this.tenancy.filter();
|
|
3014
|
+
}
|
|
3015
|
+
/** Whether the current call may see `run` (always, with tenancy off). */
|
|
3016
|
+
runVisibleNow(run) {
|
|
3017
|
+
if (!this.tenancy)
|
|
3018
|
+
return true;
|
|
3019
|
+
return runVisible(this.tenantFilterNow(), run, this.tenancy.nullRows);
|
|
3020
|
+
}
|
|
3021
|
+
/** Whether `runId` exists but the current call may not see it (an app-facing guard: answer not-found). */
|
|
3022
|
+
async runHidden(runId) {
|
|
3023
|
+
if (!this.tenancy)
|
|
3024
|
+
return false;
|
|
3025
|
+
const filter = this.tenantFilterNow();
|
|
3026
|
+
if (filter.mode === 'system' || filter.mode === 'all')
|
|
3027
|
+
return false;
|
|
3028
|
+
const run = await this.store.getRun(runId);
|
|
3029
|
+
return run !== null && !runVisible(filter, run, this.tenancy.nullRows);
|
|
3030
|
+
}
|
|
3031
|
+
/** `query` confined to the runs the current call may see. */
|
|
3032
|
+
tenantScoped(query) {
|
|
3033
|
+
if (!this.tenancy)
|
|
3034
|
+
return query;
|
|
3035
|
+
const ids = tenantScopeIds(this.tenantFilterNow(), this.tenancy.nullRows);
|
|
3036
|
+
if (ids === undefined)
|
|
3037
|
+
return query;
|
|
3038
|
+
return { ...query, tenantIds: intersectTenantIds(query.tenantIds, ids) };
|
|
3039
|
+
}
|
|
3040
|
+
/** Run `fn` as the engine's own work: app-facing tenant guards do not apply inside it. */
|
|
3041
|
+
asEngine(fn) {
|
|
3042
|
+
return this.tenancy ? engineInternal.run(true, fn) : fn();
|
|
2941
3043
|
}
|
|
2942
3044
|
/**
|
|
2943
3045
|
* List persisted runs matching `query` (newest activity first, per the store's own ordering). The
|
|
@@ -2945,7 +3047,7 @@ export class WorkflowEngine {
|
|
|
2945
3047
|
* through the engine rather than reaching for its private store.
|
|
2946
3048
|
*/
|
|
2947
3049
|
async listRuns(query) {
|
|
2948
|
-
const scoped = withoutBookkeepingRuns(query);
|
|
3050
|
+
const scoped = withoutBookkeepingRuns(this.tenantScoped(query));
|
|
2949
3051
|
const runs = await this.store.listRuns(scoped);
|
|
2950
3052
|
const hidden = scoped.excludeWorkflows;
|
|
2951
3053
|
return hidden?.length ? runs.filter((run) => !hidden.includes(run.workflow)) : runs;
|
|
@@ -2957,14 +3059,16 @@ export class WorkflowEngine {
|
|
|
2957
3059
|
* {@link listRuns}.
|
|
2958
3060
|
*/
|
|
2959
3061
|
runValueFacets(axis, query, opts) {
|
|
2960
|
-
const scoped = withoutBookkeepingRuns(query);
|
|
3062
|
+
const scoped = withoutBookkeepingRuns(this.tenantScoped(query));
|
|
2961
3063
|
if (this.store.runValueFacets) {
|
|
2962
3064
|
return this.store.runValueFacets(axis, scoped, opts);
|
|
2963
3065
|
}
|
|
2964
3066
|
return scanRunValueFacets(this.store, axis, scoped, opts);
|
|
2965
3067
|
}
|
|
2966
3068
|
/** List a run's step checkpoints (its timeline). Part of the engine's read API; see {@link listRuns}. */
|
|
2967
|
-
listCheckpoints(runId) {
|
|
3069
|
+
async listCheckpoints(runId) {
|
|
3070
|
+
if (await this.runHidden(runId))
|
|
3071
|
+
return [];
|
|
2968
3072
|
return this.store.listCheckpoints(runId);
|
|
2969
3073
|
}
|
|
2970
3074
|
/**
|
|
@@ -2972,7 +3076,7 @@ export class WorkflowEngine {
|
|
|
2972
3076
|
* has {@link StateStore.runFacets} (else counted over a listing). Part of the engine's read API.
|
|
2973
3077
|
*/
|
|
2974
3078
|
runFacets(query) {
|
|
2975
|
-
return runFacetsOf(this.store, withoutBookkeepingRuns(query));
|
|
3079
|
+
return runFacetsOf(this.store, withoutBookkeepingRuns(this.tenantScoped(query)));
|
|
2976
3080
|
}
|
|
2977
3081
|
/**
|
|
2978
3082
|
* A run's detail (see `loadRunDetail`), built from the store's checkpoint aggregates when it has
|
|
@@ -2980,6 +3084,8 @@ export class WorkflowEngine {
|
|
|
2980
3084
|
* instead of the whole history, and carries an `etag` that `options.ifNoneMatch` can match.
|
|
2981
3085
|
*/
|
|
2982
3086
|
async getRunDetail(runId, options) {
|
|
3087
|
+
if (await this.runHidden(runId))
|
|
3088
|
+
return null;
|
|
2983
3089
|
const source = this.detailSource();
|
|
2984
3090
|
const withExtras = async (detail) => {
|
|
2985
3091
|
const extras = await this.lifecycleDetail(detail.run);
|
|
@@ -3117,7 +3223,9 @@ export class WorkflowEngine {
|
|
|
3117
3223
|
return out;
|
|
3118
3224
|
}
|
|
3119
3225
|
/** One page of a summarized fan's members (see `loadFanMembers`), versioned like {@link getRunDetail}. */
|
|
3120
|
-
getFanMembers(runId, group, query) {
|
|
3226
|
+
async getFanMembers(runId, group, query) {
|
|
3227
|
+
if (await this.runHidden(runId))
|
|
3228
|
+
return null;
|
|
3121
3229
|
return loadFanMembers(this.detailSource(), runId, group, query);
|
|
3122
3230
|
}
|
|
3123
3231
|
detailSource() {
|
|
@@ -3225,7 +3333,7 @@ export class WorkflowEngine {
|
|
|
3225
3333
|
return [];
|
|
3226
3334
|
this.#lastStalledSweepAt = now;
|
|
3227
3335
|
const candidates = await this.store.listRuns({
|
|
3228
|
-
statuses: ['suspended', 'running'],
|
|
3336
|
+
statuses: ['suspended', 'running', 'cancelling'],
|
|
3229
3337
|
namespace: this.namespace,
|
|
3230
3338
|
updatedBefore: now - this.stalledAfterMs,
|
|
3231
3339
|
size: 100,
|
|
@@ -3455,7 +3563,7 @@ export class WorkflowEngine {
|
|
|
3455
3563
|
}
|
|
3456
3564
|
async deleteRun(runId) {
|
|
3457
3565
|
const run = await this.store.getRun(runId);
|
|
3458
|
-
if (!run)
|
|
3566
|
+
if (!run || !this.runVisibleNow(run))
|
|
3459
3567
|
return 0;
|
|
3460
3568
|
// Collect children BEFORE deleting this run's checkpoints (getRunChildren reads them).
|
|
3461
3569
|
let deleted = 0;
|
|
@@ -3469,11 +3577,10 @@ export class WorkflowEngine {
|
|
|
3469
3577
|
/**
|
|
3470
3578
|
* Sweep a deleted run's token-keyed buffered signals — they are not run-scoped rows, so the store's
|
|
3471
3579
|
* deleteRun can't cascade them. Without this every fire-and-forget child that was never joined leaks
|
|
3472
|
-
* its buffered `child:<id>` completion FOREVER (one row per spawn)
|
|
3473
|
-
* can leave a `cancel:<id>` marker behind.
|
|
3580
|
+
* its buffered `child:<id>` completion FOREVER (one row per spawn).
|
|
3474
3581
|
*/
|
|
3475
3582
|
async sweepRunBufferedSignals(runId) {
|
|
3476
|
-
for (const token of [`child:${runId}
|
|
3583
|
+
for (const token of [`child:${runId}`]) {
|
|
3477
3584
|
for (;;) {
|
|
3478
3585
|
const buffered = await this.store.takeBufferedSignal(token).catch(() => null);
|
|
3479
3586
|
if (!buffered)
|
|
@@ -3541,7 +3648,7 @@ export class WorkflowEngine {
|
|
|
3541
3648
|
if (!TERMINAL_RUN_STATUSES.includes(status)) {
|
|
3542
3649
|
if (!cancelLive)
|
|
3543
3650
|
continue;
|
|
3544
|
-
await this.cancel(id).catch(() => undefined);
|
|
3651
|
+
await this.asEngine(() => this.cancel(id)).catch(() => undefined);
|
|
3545
3652
|
}
|
|
3546
3653
|
doomed.push(id);
|
|
3547
3654
|
}
|
|
@@ -3706,7 +3813,7 @@ export class WorkflowEngine {
|
|
|
3706
3813
|
// Piggybacked on the existing store.getRun above — no extra async step on the happy path. An
|
|
3707
3814
|
// undefined namespace (a store row created before the field existed) is treated as "belongs to
|
|
3708
3815
|
// everyone" for back-compat: don't skip it. An operator (this.namespace undefined) belongs to
|
|
3709
|
-
// every namespace too, so the guard never applies to it — that is the whole point of the
|
|
3816
|
+
// every namespace too, so the guard never applies to it — that is the whole point of the topology.
|
|
3710
3817
|
if (this.namespace !== undefined &&
|
|
3711
3818
|
run.namespace !== undefined &&
|
|
3712
3819
|
run.namespace !== this.namespace) {
|
|
@@ -3725,7 +3832,7 @@ export class WorkflowEngine {
|
|
|
3725
3832
|
}
|
|
3726
3833
|
if (!registered)
|
|
3727
3834
|
throw new WorkflowNotRegistered(run.workflow, run.workflowVersion);
|
|
3728
|
-
return this.track(runId, this.execute(run, registered.fn));
|
|
3835
|
+
return this.track(runId, this.inRunTenant(run, () => this.execute(run, registered.fn)));
|
|
3729
3836
|
}
|
|
3730
3837
|
/**
|
|
3731
3838
|
* Deferred re-drive after a suspended settle whose waited checkpoint(s) already settled — the
|
|
@@ -4069,6 +4176,27 @@ export class WorkflowEngine {
|
|
|
4069
4176
|
// finished run) and re-enqueues it for a second execution. Only a run that still reads
|
|
4070
4177
|
// `running` under the lease is an orphan; release the lease taken just to look at anything else.
|
|
4071
4178
|
const run = await this.store.getRun(listed.id);
|
|
4179
|
+
// A compensating cancel whose undo died with its pod (or never started: the pod that took the
|
|
4180
|
+
// cancel crashed before its resume ran). Re-drive it rather than re-pend it, which would drop
|
|
4181
|
+
// the cancel: any turn of a `cancelling` run unwinds the saga and settles it `cancelled`.
|
|
4182
|
+
if (run?.status === 'cancelling') {
|
|
4183
|
+
// Only a pod that has the workflow can run its compensations; past the grace, one that is
|
|
4184
|
+
// registered nowhere is dead-lettered like any orphan.
|
|
4185
|
+
if (!this.isRegisteredHere(run) && !(await this.canResolveWorkflow(run))) {
|
|
4186
|
+
const settled = await this.settleUnregistered(run, nowMs);
|
|
4187
|
+
if (settled)
|
|
4188
|
+
results.push(settled);
|
|
4189
|
+
else if (settled === undefined)
|
|
4190
|
+
this.logUnregisteredSkip(run);
|
|
4191
|
+
continue;
|
|
4192
|
+
}
|
|
4193
|
+
await this.store.releaseRunLock(listed.id, this.instanceId);
|
|
4194
|
+
this.trackEffect(Promise.resolve(this.wake(run.id)).catch((error) => {
|
|
4195
|
+
console.error(`[adonis-durable] could not re-drive cancelling run ${run.id}; retrying on a later tick:`, error);
|
|
4196
|
+
}));
|
|
4197
|
+
results.push({ runId: run.id, status: 'cancelling' });
|
|
4198
|
+
continue;
|
|
4199
|
+
}
|
|
4072
4200
|
if (run?.status !== 'running') {
|
|
4073
4201
|
await this.store.releaseRunLock(listed.id, this.instanceId);
|
|
4074
4202
|
continue;
|
|
@@ -4547,10 +4675,16 @@ export class WorkflowEngine {
|
|
|
4547
4675
|
const seen = _seen ?? new Set();
|
|
4548
4676
|
if (seen.has(runId))
|
|
4549
4677
|
return null;
|
|
4678
|
+
if (_seen === undefined && (await this.runHidden(runId)))
|
|
4679
|
+
return null;
|
|
4550
4680
|
seen.add(runId);
|
|
4551
4681
|
const run = await this.store.getRun(runId);
|
|
4552
4682
|
if (!run)
|
|
4553
4683
|
return null;
|
|
4684
|
+
// A run undoing its saga for a compensating cancel is not retried over: re-pending it would drop
|
|
4685
|
+
// the cancel. It ends `cancelled`, and can be requeued from there.
|
|
4686
|
+
if (run.status === 'cancelling')
|
|
4687
|
+
return { runId, status: 'cancelling' };
|
|
4554
4688
|
// A FAILED/DEAD run's replay deterministically re-throws its recorded failures — an exhausted
|
|
4555
4689
|
// failed checkpoint rethrows, and an awaited child's failure completion rethrows through
|
|
4556
4690
|
// unwrapCompletion — so "retry" without resetting that state is a no-op that re-fails in
|
|
@@ -4691,7 +4825,7 @@ export class WorkflowEngine {
|
|
|
4691
4825
|
*/
|
|
4692
4826
|
async redispatchPending(runId) {
|
|
4693
4827
|
const run = await this.store.getRun(runId);
|
|
4694
|
-
if (!run)
|
|
4828
|
+
if (!run || !this.runVisibleNow(run))
|
|
4695
4829
|
return null;
|
|
4696
4830
|
const pending = (await this.store.listCheckpoints(runId)).filter((cp) => cp.kind === 'remote' && cp.status === 'pending');
|
|
4697
4831
|
let redispatched = 0;
|
|
@@ -4727,12 +4861,18 @@ export class WorkflowEngine {
|
|
|
4727
4861
|
*/
|
|
4728
4862
|
async retryWithInput(runId, input, newRunId) {
|
|
4729
4863
|
const run = await this.store.getRun(runId);
|
|
4730
|
-
if (!run)
|
|
4864
|
+
if (!run || !this.runVisibleNow(run))
|
|
4731
4865
|
return null;
|
|
4732
4866
|
const id = newRunId ?? `${runId}~retry~${globalThis.crypto.randomUUID().slice(0, 8)}`;
|
|
4733
4867
|
// Inherit the original run's namespace so a retry lands in the SAME worker-pool partition (an
|
|
4734
|
-
// operator-routed run keeps its
|
|
4735
|
-
|
|
4868
|
+
// operator-routed run keeps its pool on retry, rather than falling back to the engine's own),
|
|
4869
|
+
// and its tenant (or none).
|
|
4870
|
+
await this.start(run.workflow, input, id, {
|
|
4871
|
+
tags: run.tags,
|
|
4872
|
+
namespace: run.namespace,
|
|
4873
|
+
tenantId: run.tenantId ?? null,
|
|
4874
|
+
...(run.tenantKind ? { tenantKind: run.tenantKind } : {}),
|
|
4875
|
+
});
|
|
4736
4876
|
return { runId: id };
|
|
4737
4877
|
}
|
|
4738
4878
|
/**
|
|
@@ -4821,8 +4961,10 @@ export class WorkflowEngine {
|
|
|
4821
4961
|
// (`awaitingDecisionTaskId` set) is mid-turn, not settled: that suspend is the engine's re-drive
|
|
4822
4962
|
// marker, not a sleep/signal the body asked for. Keep waiting until the decision lands. A `blocked`
|
|
4823
4963
|
// run is parked on the fleet, not on anything the body asked for, and is re-driven on its own.
|
|
4824
|
-
|
|
4825
|
-
|
|
4964
|
+
// A `cancelling` run is still undoing its saga: it settles once it is `cancelled`.
|
|
4965
|
+
const isSettled = (run) => run.status !== 'cancelling' &&
|
|
4966
|
+
(isTerminal(run.status) ||
|
|
4967
|
+
(run.status === 'suspended' && run.awaitingDecisionTaskId === undefined));
|
|
4826
4968
|
const shouldResolve = (run) => opts?.terminal ? isTerminal(run.status) : isSettled(run);
|
|
4827
4969
|
const toResult = (run) => ({
|
|
4828
4970
|
runId,
|
|
@@ -4997,7 +5139,10 @@ export class WorkflowEngine {
|
|
|
4997
5139
|
const waiters = await this.store.listSignalWaiters(eventPrefix(name));
|
|
4998
5140
|
for (const w of waiters) {
|
|
4999
5141
|
if (eventMatches(payload, eventMatchOf(w.token))) {
|
|
5000
|
-
|
|
5142
|
+
// With tenancy on, a tenant's event reaches its own runs only.
|
|
5143
|
+
if (await this.runHidden(w.runId))
|
|
5144
|
+
continue;
|
|
5145
|
+
await this.asEngine(() => this.signal(w.token, payload));
|
|
5001
5146
|
touched += 1;
|
|
5002
5147
|
}
|
|
5003
5148
|
}
|
|
@@ -5015,6 +5160,13 @@ export class WorkflowEngine {
|
|
|
5015
5160
|
// unless the caller opted out. `touched > 0` covers BOTH the waiter fan-out and the subscriber loop.
|
|
5016
5161
|
if (touched > 0 || opts?.buffer === false)
|
|
5017
5162
|
return touched;
|
|
5163
|
+
// A buffered copy carries no tenant and would reach whichever run waits next, so a publisher
|
|
5164
|
+
// scoped to one tenant (or to none) does not buffer: its event reaches live waiters only.
|
|
5165
|
+
if (this.tenancy) {
|
|
5166
|
+
const mode = this.tenantFilterNow().mode;
|
|
5167
|
+
if (mode === 'tenant' || mode === 'tenantless')
|
|
5168
|
+
return touched;
|
|
5169
|
+
}
|
|
5018
5170
|
const bufferedId = globalThis.crypto.randomUUID();
|
|
5019
5171
|
await this.store.bufferEvent({ name, payload, id: bufferedId, publishedAt: this.clock() });
|
|
5020
5172
|
// Re-check: a waiter may have registered in the window between the miss above and this buffer
|
|
@@ -5125,6 +5277,8 @@ export class WorkflowEngine {
|
|
|
5125
5277
|
* side's write, and the destructive `take*` ops arbitrate so a signal is never delivered twice.
|
|
5126
5278
|
*/
|
|
5127
5279
|
async signal(token, payload) {
|
|
5280
|
+
if (await this.signalHidden(token))
|
|
5281
|
+
return null;
|
|
5128
5282
|
const waiter = await this.store.takeSignalWaiter(token);
|
|
5129
5283
|
if (waiter)
|
|
5130
5284
|
return this.deliverSignal(waiter, payload);
|
|
@@ -5146,6 +5300,23 @@ export class WorkflowEngine {
|
|
|
5146
5300
|
}
|
|
5147
5301
|
return this.deliverSignal(lateWaiter, reclaimed.payload);
|
|
5148
5302
|
}
|
|
5303
|
+
/** Whether a run another tenant owns is the one waiting on `token` (an app-facing signal is dropped). */
|
|
5304
|
+
async signalHidden(token) {
|
|
5305
|
+
if (!this.tenancy)
|
|
5306
|
+
return false;
|
|
5307
|
+
const filter = this.tenantFilterNow();
|
|
5308
|
+
if (filter.mode === 'system' || filter.mode === 'all')
|
|
5309
|
+
return false;
|
|
5310
|
+
const waiters = await this.store.listSignalWaiters(token);
|
|
5311
|
+
for (const waiter of waiters) {
|
|
5312
|
+
if (waiter.token !== token)
|
|
5313
|
+
continue;
|
|
5314
|
+
const run = await this.store.getRun(waiter.runId);
|
|
5315
|
+
if (run && !runVisible(filter, run, this.tenancy.nullRows))
|
|
5316
|
+
return true;
|
|
5317
|
+
}
|
|
5318
|
+
return false;
|
|
5319
|
+
}
|
|
5149
5320
|
/**
|
|
5150
5321
|
* Wake the run parked on `token` with `payload`, if there is one; otherwise do nothing — unlike
|
|
5151
5322
|
* {@link signal}, it never buffers. For a wake-up whose real payload is ALREADY buffered on the
|
|
@@ -5177,14 +5348,18 @@ export class WorkflowEngine {
|
|
|
5177
5348
|
* null if no run is waiting on the task (e.g. a duplicate/late delivery) — a safe no-op.
|
|
5178
5349
|
*/
|
|
5179
5350
|
async completeTask(runId, name, result) {
|
|
5180
|
-
|
|
5351
|
+
if (await this.runHidden(runId))
|
|
5352
|
+
return null;
|
|
5353
|
+
return this.asEngine(() => this.signal(`task:${runId}:${name}`, {
|
|
5181
5354
|
ok: true,
|
|
5182
5355
|
value: result,
|
|
5183
|
-
});
|
|
5356
|
+
}));
|
|
5184
5357
|
}
|
|
5185
5358
|
/** Report that a `ctx.task` failed — the run resumes and throws a FatalError at the task. */
|
|
5186
5359
|
async failTask(runId, name, error) {
|
|
5187
|
-
|
|
5360
|
+
if (await this.runHidden(runId))
|
|
5361
|
+
return null;
|
|
5362
|
+
return this.asEngine(() => this.signal(`task:${runId}:${name}`, { ok: false, error }));
|
|
5188
5363
|
}
|
|
5189
5364
|
/**
|
|
5190
5365
|
* Notify a parent that's waiting on `runId` as a child of its terminal outcome (the `ctx.child`
|
|
@@ -5237,7 +5412,7 @@ export class WorkflowEngine {
|
|
|
5237
5412
|
if (!(await childCompletionNeedsBuffer(this.store, token.slice(CHILD_TOKEN_PREFIX.length), completion))) {
|
|
5238
5413
|
return;
|
|
5239
5414
|
}
|
|
5240
|
-
const resumed = await this.signal(token, completion);
|
|
5415
|
+
const resumed = await this.asEngine(() => this.signal(token, completion));
|
|
5241
5416
|
// The parent may settle the child from its run row between the look above and the buffer
|
|
5242
5417
|
// write (a join that registers its waiter only now): it then stamped the child first, and the
|
|
5243
5418
|
// copy just buffered would never be read.
|
|
@@ -5302,7 +5477,7 @@ export class WorkflowEngine {
|
|
|
5302
5477
|
: settled.status === 'completed'
|
|
5303
5478
|
? { ok: true, value: settled.output }
|
|
5304
5479
|
: failedCompletion(settled.error ?? { message: settled.status });
|
|
5305
|
-
await this.signal(token, completion);
|
|
5480
|
+
await this.asEngine(() => this.signal(token, completion));
|
|
5306
5481
|
}
|
|
5307
5482
|
/**
|
|
5308
5483
|
* Cancel a run (e.g. from the dashboard). Returns null if the run does not exist. Pass
|
|
@@ -5311,6 +5486,11 @@ export class WorkflowEngine {
|
|
|
5311
5486
|
* cancelled. Without it, cancellation is immediate (no undo).
|
|
5312
5487
|
*/
|
|
5313
5488
|
async cancel(runId, opts) {
|
|
5489
|
+
if (await this.runHidden(runId))
|
|
5490
|
+
return null;
|
|
5491
|
+
return this.asEngine(() => this.cancelOnce(runId, opts, true));
|
|
5492
|
+
}
|
|
5493
|
+
async cancelOnce(runId, opts, mayRetry) {
|
|
5314
5494
|
const run = await this.store.getRun(runId);
|
|
5315
5495
|
if (!run)
|
|
5316
5496
|
return null;
|
|
@@ -5323,18 +5503,35 @@ export class WorkflowEngine {
|
|
|
5323
5503
|
if (TERMINAL_RUN_STATUSES.includes(run.status)) {
|
|
5324
5504
|
return { runId, status: run.status, output: run.output, error: run.error };
|
|
5325
5505
|
}
|
|
5506
|
+
// A compensating cancel is already undoing the saga: a repeat cancel, with or without
|
|
5507
|
+
// `compensate`, echoes it. Taking the instant path below would mark it `cancelled` and skip the
|
|
5508
|
+
// undo still in flight. Recovery re-drives a `cancelling` run whose undo died.
|
|
5509
|
+
if (run.status === 'cancelling')
|
|
5510
|
+
return { runId, status: 'cancelling' };
|
|
5326
5511
|
// Compensating cancel: resume the run with a cancellation pending — replay re-registers the saga,
|
|
5327
5512
|
// and at the suspension point execute() runs the undo and marks it cancelled. Run that resume in
|
|
5328
5513
|
// the BACKGROUND so the caller (e.g. an HTTP request) never blocks on replaying the workflow +
|
|
5329
5514
|
// compensations. `execute` holds the run's lease, so this can't double-run one a live worker owns
|
|
5330
5515
|
// (its lease acquire fails and it no-ops); the broadcast tells that worker to abort cooperatively.
|
|
5331
5516
|
if (opts?.compensate && (run.status === 'suspended' || run.status === 'running')) {
|
|
5517
|
+
// DURABLE cancel intent: the persisted `cancelling` status. It survives a crash or a pod
|
|
5518
|
+
// handoff (any turn of a `cancelling` run unwinds it, a turn already executing on another pod
|
|
5519
|
+
// finds it when its settle loses the status race, and crash recovery re-drives one whose undo
|
|
5520
|
+
// died), and shows the dashboard/API that an undo is in progress. Conditional: a run that
|
|
5521
|
+
// moved meanwhile (settled, parked) is re-read and cancelled for what it is now.
|
|
5522
|
+
const marked = await this.store.updateRunIf(runId, ['suspended', 'running'], {
|
|
5523
|
+
status: 'cancelling',
|
|
5524
|
+
updatedAt: new Date(),
|
|
5525
|
+
});
|
|
5526
|
+
if (!marked) {
|
|
5527
|
+
if (mayRetry)
|
|
5528
|
+
return this.cancelOnce(runId, opts, false);
|
|
5529
|
+
const current = await this.store.getRun(runId);
|
|
5530
|
+
return current
|
|
5531
|
+
? { runId, status: current.status, output: current.output, error: current.error }
|
|
5532
|
+
: null;
|
|
5533
|
+
}
|
|
5332
5534
|
this.cancelRequested.add(runId);
|
|
5333
|
-
// DURABLE cancel intent: the in-memory flag above only survives on this pod, this boot. The
|
|
5334
|
-
// buffered `cancel:<id>` marker is what lets the compensating unwind survive a crash or be
|
|
5335
|
-
// picked up by ANOTHER pod (its resume replays into the WorkflowSuspended catch, which checks
|
|
5336
|
-
// the marker when the flag is absent). Consumed there once `cancelled` is durably recorded.
|
|
5337
|
-
await this.store.bufferSignal(`cancel:${runId}`, true).catch(() => undefined);
|
|
5338
5535
|
if (this.controlPlane) {
|
|
5339
5536
|
void this.controlPlane
|
|
5340
5537
|
.publishControl({ kind: 'cancel', runId, from: this.instanceId }, runTarget(run.namespace))
|
|
@@ -5352,7 +5549,7 @@ export class WorkflowEngine {
|
|
|
5352
5549
|
// needed covering. The one-microtask defer is preserved; the promise is registered synchronously.
|
|
5353
5550
|
this.trackEffect(Promise.resolve().then(() => this.resumeImplicit(runId).then(() => this.notifyCancelled(runId))));
|
|
5354
5551
|
await this.cancelChildren(runId, opts);
|
|
5355
|
-
return { runId, status:
|
|
5552
|
+
return { runId, status: 'cancelling' };
|
|
5356
5553
|
}
|
|
5357
5554
|
const error = { message: 'cancelled' };
|
|
5358
5555
|
// Clears a remote turn's decision marker too: a cancelled run awaits no decision.
|
|
@@ -5412,7 +5609,7 @@ export class WorkflowEngine {
|
|
|
5412
5609
|
*/
|
|
5413
5610
|
async cancelChildren(parentRunId, opts) {
|
|
5414
5611
|
for (const id of await this.getRunChildren(parentRunId)) {
|
|
5415
|
-
await this.cancel(id, opts).catch(() => undefined);
|
|
5612
|
+
await this.asEngine(() => this.cancel(id, opts)).catch(() => undefined);
|
|
5416
5613
|
}
|
|
5417
5614
|
}
|
|
5418
5615
|
/**
|
|
@@ -5468,7 +5665,7 @@ export class WorkflowEngine {
|
|
|
5468
5665
|
* the full listing for a store without one.
|
|
5469
5666
|
*/
|
|
5470
5667
|
countRuns(query) {
|
|
5471
|
-
return countRuns(this.store, withoutBookkeepingRuns(query));
|
|
5668
|
+
return countRuns(this.store, withoutBookkeepingRuns(this.tenantScoped(query)));
|
|
5472
5669
|
}
|
|
5473
5670
|
/**
|
|
5474
5671
|
* Every workflow the live fleet can execute in this engine's partition: what workers DECLARE in
|
|
@@ -5689,7 +5886,7 @@ export class WorkflowEngine {
|
|
|
5689
5886
|
publishedAt: this.clock(),
|
|
5690
5887
|
})
|
|
5691
5888
|
.catch(() => undefined);
|
|
5692
|
-
const marked = await this.store.updateRunIf(runId,
|
|
5889
|
+
const marked = await this.store.updateRunIf(runId, WAKEABLE_RUN_STATUSES, {
|
|
5693
5890
|
wakeRequestedAt: this.clock(),
|
|
5694
5891
|
});
|
|
5695
5892
|
return marked ? 'stamped' : 'gone';
|
|
@@ -5728,7 +5925,7 @@ export class WorkflowEngine {
|
|
|
5728
5925
|
if (!(await this.store.tryLockRun(runId, this.instanceId, nowMs + this.leaseMs, nowMs)))
|
|
5729
5926
|
return;
|
|
5730
5927
|
const fresh = await this.store.getRun(runId);
|
|
5731
|
-
if (fresh
|
|
5928
|
+
if (!fresh || !PARKED_RUN_STATUSES.includes(fresh.status)) {
|
|
5732
5929
|
await this.store.releaseRunLock(runId, this.instanceId);
|
|
5733
5930
|
return;
|
|
5734
5931
|
}
|
|
@@ -5828,7 +6025,7 @@ export class WorkflowEngine {
|
|
|
5828
6025
|
}
|
|
5829
6026
|
// Free, and `pending` (its dispatch or the pending-run poll runs it) or `running` with no lease
|
|
5830
6027
|
// (a holder that died mid-turn: crash recovery takes it): nothing for a wake to do.
|
|
5831
|
-
if (
|
|
6028
|
+
if (!PARKED_RUN_STATUSES.includes(run.status))
|
|
5832
6029
|
return;
|
|
5833
6030
|
if (this.runQueue) {
|
|
5834
6031
|
this.enqueueRun({
|
|
@@ -5841,7 +6038,7 @@ export class WorkflowEngine {
|
|
|
5841
6038
|
if (!(await this.store.tryLockRun(runId, this.instanceId, nowMs + this.leaseMs, nowMs)))
|
|
5842
6039
|
return;
|
|
5843
6040
|
const fresh = await this.store.getRun(runId);
|
|
5844
|
-
if (fresh
|
|
6041
|
+
if (!fresh || !PARKED_RUN_STATUSES.includes(fresh.status)) {
|
|
5845
6042
|
await this.store.releaseRunLock(runId, this.instanceId);
|
|
5846
6043
|
return;
|
|
5847
6044
|
}
|
|
@@ -5850,6 +6047,18 @@ export class WorkflowEngine {
|
|
|
5850
6047
|
throw err;
|
|
5851
6048
|
});
|
|
5852
6049
|
}
|
|
6050
|
+
/**
|
|
6051
|
+
* Run a turn with the run's own tenant active, so a resume driven by a timer, a signal or recovery
|
|
6052
|
+
* (no request around it) sees the tenant the run was started under, and what the turn starts or
|
|
6053
|
+
* dispatches carries it. A run with no tenant runs as before.
|
|
6054
|
+
*/
|
|
6055
|
+
inRunTenant(run, fn) {
|
|
6056
|
+
const tenant = runTenantOf(run);
|
|
6057
|
+
if (!this.tenancy || !tenant)
|
|
6058
|
+
return fn();
|
|
6059
|
+
const tenancy = this.tenancy;
|
|
6060
|
+
return engineInternal.run(false, () => tenancy.run(tenant, fn));
|
|
6061
|
+
}
|
|
5853
6062
|
async execute(run, fn) {
|
|
5854
6063
|
const registered = this.workflows.get(versionKey(run.workflow, run.workflowVersion));
|
|
5855
6064
|
// Hold the lease for the WHOLE execution — whatever path got us here (leased sweep, a signal, a
|
|
@@ -5916,6 +6125,11 @@ export class WorkflowEngine {
|
|
|
5916
6125
|
// A remote (e.g. Python) workflow is advanced by dispatching workflow tasks, not by running an
|
|
5917
6126
|
// in-process body — but everything around it (lease, recovery, timers, the resume that lands us
|
|
5918
6127
|
// here on a step result) is identical, so it branches here under the same lease.
|
|
6128
|
+
// A `cancelling` run is a compensating cancel in flight, maybe re-driven by crash recovery or
|
|
6129
|
+
// by another pod that never saw the in-memory flag: re-derive the intent from the status, so
|
|
6130
|
+
// this turn unwinds the saga and settles `cancelled` at whichever exit it reaches.
|
|
6131
|
+
if (run.status === 'cancelling')
|
|
6132
|
+
this.cancelRequested.add(run.id);
|
|
5919
6133
|
const result = registered?.remote
|
|
5920
6134
|
? await this.runRemoteExecution(run, registered)
|
|
5921
6135
|
: await this.runExecution(run, fn, registered);
|
|
@@ -6306,7 +6520,7 @@ export class WorkflowEngine {
|
|
|
6306
6520
|
// settle — and it must happen even while a dispatched turn is still out (the hold below would
|
|
6307
6521
|
// otherwise swallow the cancel's resume, and the in-flight turn's decision would carry the run on
|
|
6308
6522
|
// as if it was never cancelled). The late decision then finds the run cancelled and is dropped.
|
|
6309
|
-
if (
|
|
6523
|
+
if (this.takeCancelIntent(run.id))
|
|
6310
6524
|
return this.compensateAndCancel(run, []);
|
|
6311
6525
|
// A dispatched turn is still out and within its liveness window: whatever woke the run (a step
|
|
6312
6526
|
// result, a signal, a wait recheck) must not dispatch a second turn over it — that would supersede
|
|
@@ -6399,13 +6613,17 @@ export class WorkflowEngine {
|
|
|
6399
6613
|
// with its lease released, so crash recovery re-drives it (replaying completed steps; bounded by
|
|
6400
6614
|
// `maxRecoveryAttempts`).
|
|
6401
6615
|
if (err instanceof RemoteWorkflowTimeout) {
|
|
6402
|
-
await this.store
|
|
6616
|
+
const reset = await this.store
|
|
6403
6617
|
.updateRunIf(run.id, ['running', 'suspended'], {
|
|
6404
6618
|
status: 'running',
|
|
6405
6619
|
updatedAt: new Date(),
|
|
6406
6620
|
})
|
|
6407
6621
|
.catch(() => false);
|
|
6408
6622
|
await this.releaseTurnLease(run.id);
|
|
6623
|
+
// Not reset: the run moved on meanwhile (a compensating cancel made it `cancelling`, or it
|
|
6624
|
+
// settled). Report what it is now, not a `running` it no longer reads.
|
|
6625
|
+
if (!reset)
|
|
6626
|
+
return this.echoCurrentStatus(run.id);
|
|
6409
6627
|
return { runId: run.id, status: 'running' };
|
|
6410
6628
|
}
|
|
6411
6629
|
const error = {
|
|
@@ -7317,21 +7535,25 @@ export class WorkflowEngine {
|
|
|
7317
7535
|
return wakeAt;
|
|
7318
7536
|
}
|
|
7319
7537
|
/**
|
|
7320
|
-
* Whether a compensating cancel is pending for this run, consuming the in-memory flag. The
|
|
7321
|
-
*
|
|
7322
|
-
*
|
|
7323
|
-
*
|
|
7324
|
-
*
|
|
7325
|
-
* `cancelled` is durably recorded.
|
|
7538
|
+
* Whether a compensating cancel is pending for this run, consuming the in-memory flag. The flag is
|
|
7539
|
+
* set by `cancel()` on this pod, and at turn start for a run that reads `cancelling` (the durable
|
|
7540
|
+
* intent: a cancel from another pod, or one re-driven by recovery). A cancel another pod persists
|
|
7541
|
+
* while this turn runs is found at the settle instead: the run no longer reads a settle-eligible
|
|
7542
|
+
* status, and {@link settleOrUnwind} unwinds it.
|
|
7326
7543
|
*/
|
|
7327
|
-
|
|
7328
|
-
|
|
7329
|
-
|
|
7330
|
-
|
|
7331
|
-
|
|
7332
|
-
|
|
7333
|
-
|
|
7334
|
-
|
|
7544
|
+
takeCancelIntent(runId) {
|
|
7545
|
+
return this.cancelRequested.delete(runId);
|
|
7546
|
+
}
|
|
7547
|
+
/**
|
|
7548
|
+
* Settle this turn's `outcome` — unless a compensating cancel was persisted on another pod while the
|
|
7549
|
+
* turn ran: the settle's status check then reads `cancelling`, and this turn (which still holds the
|
|
7550
|
+
* lease and the saga it registered) undoes it and settles `cancelled` itself.
|
|
7551
|
+
*/
|
|
7552
|
+
async settleOrUnwind(run, outcome, compensations) {
|
|
7553
|
+
const settled = await this.settleRun(run, outcome);
|
|
7554
|
+
if (settled.status !== 'cancelling' || this.leaseLostRuns.has(run.id))
|
|
7555
|
+
return settled;
|
|
7556
|
+
return this.compensateAndCancel(run, compensations);
|
|
7335
7557
|
}
|
|
7336
7558
|
/**
|
|
7337
7559
|
* Undo the registered saga compensations in reverse and settle the run `cancelled`. Shared by EVERY
|
|
@@ -7341,8 +7563,8 @@ export class WorkflowEngine {
|
|
|
7341
7563
|
*/
|
|
7342
7564
|
async compensateAndCancel(run, compensations) {
|
|
7343
7565
|
this.cancelRequested.delete(run.id);
|
|
7344
|
-
// Zombie fence (as in settleRun): another instance owns the run now; the
|
|
7345
|
-
// there for it, so this stale turn must neither undo nor write.
|
|
7566
|
+
// Zombie fence (as in settleRun): another instance owns the run now; the persisted `cancelling`
|
|
7567
|
+
// status is still there for it, so this stale turn must neither undo nor write.
|
|
7346
7568
|
if (this.leaseLostRuns.has(run.id))
|
|
7347
7569
|
return this.echoCurrentStatus(run.id);
|
|
7348
7570
|
for (let i = compensations.length - 1; i >= 0; i -= 1) {
|
|
@@ -7357,13 +7579,6 @@ export class WorkflowEngine {
|
|
|
7357
7579
|
awaitingDecisionTaskId: undefined,
|
|
7358
7580
|
updatedAt: new Date(),
|
|
7359
7581
|
}));
|
|
7360
|
-
// The cancellation is durably recorded — consume the marker(s) so a later retry of this run id
|
|
7361
|
-
// doesn't get spuriously re-cancelled.
|
|
7362
|
-
for (;;) {
|
|
7363
|
-
const m = await this.store.takeBufferedSignal(`cancel:${run.id}`).catch(() => null);
|
|
7364
|
-
if (!m)
|
|
7365
|
-
break;
|
|
7366
|
-
}
|
|
7367
7582
|
this.emit({
|
|
7368
7583
|
type: 'run.failed',
|
|
7369
7584
|
runId: run.id,
|
|
@@ -7408,7 +7623,11 @@ export class WorkflowEngine {
|
|
|
7408
7623
|
}
|
|
7409
7624
|
// Singleton admission gate: if this run shares its key with `limit` older in-flight runs, wait
|
|
7410
7625
|
// (suspend on a short timer) until a slot frees instead of running now. Re-checked on each resume.
|
|
7411
|
-
|
|
7626
|
+
// A `cancelling` run skips it: it is tearing down, not competing for a slot, and parking it here
|
|
7627
|
+
// would overwrite `cancelling` with `suspended`.
|
|
7628
|
+
if (run.status !== 'cancelling' &&
|
|
7629
|
+
registered?.singleton &&
|
|
7630
|
+
!(await this.singletons.admit(run, registered.singleton))) {
|
|
7412
7631
|
return this.singletonWaitResult(run);
|
|
7413
7632
|
}
|
|
7414
7633
|
// A body this instance parked on dispatched steps in an earlier turn continues from there (see
|
|
@@ -7487,10 +7706,10 @@ export class WorkflowEngine {
|
|
|
7487
7706
|
}
|
|
7488
7707
|
// A compensating cancel requested while this turn ran (or one whose resume replays straight to
|
|
7489
7708
|
// `return`, with no suspension point left) still wins: undo + settle cancelled, never completed.
|
|
7490
|
-
if (
|
|
7709
|
+
if (this.takeCancelIntent(run.id)) {
|
|
7491
7710
|
return await this.compensateAndCancel(run, compensations);
|
|
7492
7711
|
}
|
|
7493
|
-
return await this.
|
|
7712
|
+
return await this.settleOrUnwind(run, { kind: 'completed', output }, compensations);
|
|
7494
7713
|
}
|
|
7495
7714
|
catch (err) {
|
|
7496
7715
|
if (err instanceof ContinueAsNew) {
|
|
@@ -7499,7 +7718,11 @@ export class WorkflowEngine {
|
|
|
7499
7718
|
if (this.leaseLostRuns.has(run.id))
|
|
7500
7719
|
return await this.echoCurrentStatus(run.id);
|
|
7501
7720
|
// A cancel in flight wins over continue-as-new: undo + cancel instead of forking the next run.
|
|
7502
|
-
|
|
7721
|
+
// The run is re-read here (continue-as-new is rare) so a cancel another pod persisted during
|
|
7722
|
+
// this turn usually stops the fork; one that lands after the read loses the conditional
|
|
7723
|
+
// completion below instead.
|
|
7724
|
+
if (this.takeCancelIntent(run.id) ||
|
|
7725
|
+
(await this.store.getRun(run.id))?.status === 'cancelling') {
|
|
7503
7726
|
return await this.compensateAndCancel(run, compensations);
|
|
7504
7727
|
}
|
|
7505
7728
|
const nextId = nextContinuationId(run.id);
|
|
@@ -7513,13 +7736,28 @@ export class WorkflowEngine {
|
|
|
7513
7736
|
// converges on the already-persisted continuation (idempotent by the continuation id).
|
|
7514
7737
|
// `dispatch: false`: the tracked handoff below owns the pickup. The continuation inherits
|
|
7515
7738
|
// the parent RUN's namespace so a tenant's chain stays in its worker-pool partition.
|
|
7516
|
-
await this.startCore(run.workflow, err.input, nextId, {
|
|
7517
|
-
|
|
7739
|
+
await this.startCore(run.workflow, err.input, nextId, {
|
|
7740
|
+
namespace: run.namespace,
|
|
7741
|
+
tenantId: run.tenantId ?? null,
|
|
7742
|
+
...(run.tenantKind ? { tenantKind: run.tenantKind } : {}),
|
|
7743
|
+
}, false);
|
|
7744
|
+
// Conditional, like every settle: a cancel persisted after the re-read above (`cancelling`, or a
|
|
7745
|
+
// plain `cancelled`) must win over this completion. The continuation already exists then, and
|
|
7746
|
+
// nothing has handed it off: cancel it, and unwind this run if the cancel compensates.
|
|
7747
|
+
const completed = await this.store.updateRunIf(run.id, SETTLE_ELIGIBLE_STATUSES, {
|
|
7518
7748
|
status: 'completed',
|
|
7519
7749
|
output: undefined,
|
|
7520
7750
|
error: undefined,
|
|
7521
7751
|
updatedAt: new Date(),
|
|
7522
7752
|
});
|
|
7753
|
+
if (!completed) {
|
|
7754
|
+
await this.asEngine(() => this.cancel(nextId));
|
|
7755
|
+
const current = await this.store.getRun(run.id);
|
|
7756
|
+
if (current?.status === 'cancelling' && !this.leaseLostRuns.has(run.id)) {
|
|
7757
|
+
return await this.compensateAndCancel(run, compensations);
|
|
7758
|
+
}
|
|
7759
|
+
return await this.echoCurrentStatus(run.id);
|
|
7760
|
+
}
|
|
7523
7761
|
this.emit({
|
|
7524
7762
|
type: 'run.completed',
|
|
7525
7763
|
runId: run.id,
|
|
@@ -7531,7 +7769,11 @@ export class WorkflowEngine {
|
|
|
7531
7769
|
// continuation has entered `inflight` and settled, so `drain()` can't return before the next
|
|
7532
7770
|
// run's store writes land. Still deferred; its startCore call converges on the run persisted
|
|
7533
7771
|
// above (prior-run branch) and drives the pickup.
|
|
7534
|
-
this.handoffRun(run.workflow, err.input, nextId, {
|
|
7772
|
+
this.handoffRun(run.workflow, err.input, nextId, {
|
|
7773
|
+
namespace: run.namespace,
|
|
7774
|
+
tenantId: run.tenantId ?? null,
|
|
7775
|
+
...(run.tenantKind ? { tenantKind: run.tenantKind } : {}),
|
|
7776
|
+
});
|
|
7535
7777
|
return { runId: run.id, status: 'completed' };
|
|
7536
7778
|
}
|
|
7537
7779
|
if (err instanceof WorkflowBlocked) {
|
|
@@ -7543,10 +7785,10 @@ export class WorkflowEngine {
|
|
|
7543
7785
|
if (err instanceof WorkflowSuspended) {
|
|
7544
7786
|
// A compensating cancel resumed this run to reach here: the replay re-registered the saga,
|
|
7545
7787
|
// so undo the completed steps in reverse and mark it cancelled instead of re-suspending.
|
|
7546
|
-
if (
|
|
7788
|
+
if (this.takeCancelIntent(run.id)) {
|
|
7547
7789
|
return await this.compensateAndCancel(run, compensations);
|
|
7548
7790
|
}
|
|
7549
|
-
const settled = await this.
|
|
7791
|
+
const settled = await this.settleOrUnwind(run, { kind: 'suspended', wakeAt: err.wakeAt }, compensations);
|
|
7550
7792
|
const stamp = this.suspendStamps.get(run.id);
|
|
7551
7793
|
this.suspendStamps.delete(run.id);
|
|
7552
7794
|
if (parkedTurn && cont && settled.status === 'suspended' && stamp !== undefined) {
|
|
@@ -7571,7 +7813,7 @@ export class WorkflowEngine {
|
|
|
7571
7813
|
}
|
|
7572
7814
|
// A cancel in flight that surfaced as a thrown error settles `cancelled`, not `failed`: the saga
|
|
7573
7815
|
// undo runs either way; this keeps the terminal status faithful to the cancel request.
|
|
7574
|
-
if (
|
|
7816
|
+
if (this.takeCancelIntent(run.id)) {
|
|
7575
7817
|
return await this.compensateAndCancel(run, compensations);
|
|
7576
7818
|
}
|
|
7577
7819
|
const error = {
|
|
@@ -7589,7 +7831,8 @@ export class WorkflowEngine {
|
|
|
7589
7831
|
continue;
|
|
7590
7832
|
await this.runCompensation(run, comp, i);
|
|
7591
7833
|
}
|
|
7592
|
-
|
|
7834
|
+
// The saga is undone already: a cancel persisted meanwhile only needs the `cancelled` settle.
|
|
7835
|
+
return await this.settleOrUnwind(run, { kind: 'failed', error }, []);
|
|
7593
7836
|
}
|
|
7594
7837
|
finally {
|
|
7595
7838
|
// Release the recovery lease once the run reaches a terminal/suspended state, so the
|
|
@@ -7903,7 +8146,7 @@ export class WorkflowEngine {
|
|
|
7903
8146
|
// escape `drain()` onto a torn-down connection ("Transaction query already complete"). The
|
|
7904
8147
|
// one-microtask defer is preserved; the promise is registered synchronously.
|
|
7905
8148
|
cancelChild: (childId) => {
|
|
7906
|
-
this.trackEffect(Promise.resolve().then(() => this.cancel(childId)));
|
|
8149
|
+
this.trackEffect(Promise.resolve().then(() => this.asEngine(() => this.cancel(childId))));
|
|
7907
8150
|
},
|
|
7908
8151
|
// Shallow-merge into the run's searchAttributes (the ctx primitive makes this exactly-once).
|
|
7909
8152
|
upsertSearchAttributes: async (runId, attrs) => {
|
|
@@ -7951,11 +8194,13 @@ export class WorkflowEngine {
|
|
|
7951
8194
|
* paused at a breakpoint.
|
|
7952
8195
|
*/
|
|
7953
8196
|
async continue(runId) {
|
|
8197
|
+
if (await this.runHidden(runId))
|
|
8198
|
+
return null;
|
|
7954
8199
|
const checkpoints = await this.store.listCheckpoints(runId);
|
|
7955
8200
|
const bp = checkpoints.find(isBreakpoint);
|
|
7956
8201
|
if (!bp)
|
|
7957
8202
|
return null;
|
|
7958
|
-
return this.signal(breakpointToken(runId, bp.seq), undefined);
|
|
8203
|
+
return this.asEngine(() => this.signal(breakpointToken(runId, bp.seq), undefined));
|
|
7959
8204
|
}
|
|
7960
8205
|
/**
|
|
7961
8206
|
* Read the latest value a run published for `key` via {@link WorkflowCtx.setEvent} — a
|
|
@@ -7963,6 +8208,8 @@ export class WorkflowEngine {
|
|
|
7963
8208
|
* never published that key. The suspend-model counterpart of a Temporal query.
|
|
7964
8209
|
*/
|
|
7965
8210
|
async getEvent(runId, key) {
|
|
8211
|
+
if (await this.runHidden(runId))
|
|
8212
|
+
return undefined;
|
|
7966
8213
|
const name = `event:${key}`;
|
|
7967
8214
|
// Targeted read: the highest-seq checkpoint for this name is the most recent value (a re-published
|
|
7968
8215
|
// key overwrites at a higher seq), matching the old "last in seq order wins" scan. Falls back to a
|
|
@@ -7994,7 +8241,7 @@ export class WorkflowEngine {
|
|
|
7994
8241
|
*/
|
|
7995
8242
|
async update(runId, name, arg) {
|
|
7996
8243
|
const run = await this.store.getRun(runId);
|
|
7997
|
-
if (!run)
|
|
8244
|
+
if (!run || !this.runVisibleNow(run))
|
|
7998
8245
|
return { accepted: false, reason: `run ${runId} not found` };
|
|
7999
8246
|
const validate = this.updateValidators.get(`${run.workflow}:${name}`);
|
|
8000
8247
|
if (validate) {
|
|
@@ -8007,7 +8254,7 @@ export class WorkflowEngine {
|
|
|
8007
8254
|
return { accepted: false, reason: err instanceof Error ? err.message : String(err) };
|
|
8008
8255
|
}
|
|
8009
8256
|
}
|
|
8010
|
-
const result = await this.signal(`update:${runId}:${name}`, arg);
|
|
8257
|
+
const result = await this.asEngine(() => this.signal(`update:${runId}:${name}`, arg));
|
|
8011
8258
|
return { accepted: true, run: result };
|
|
8012
8259
|
}
|
|
8013
8260
|
// ---------------------------------------------------------------------------
|
|
@@ -9365,7 +9612,7 @@ export class WorkflowEngine {
|
|
|
9365
9612
|
// batch result resurrected a `failed` run into `suspended`, which then continued executing as if
|
|
9366
9613
|
// the failure never happened, racing whatever the operator did about it, and after any saga
|
|
9367
9614
|
// compensations already ran). Terminal is terminal — recovery of a failed run belongs to an
|
|
9368
|
-
// explicit `requeue`/`durable:retry`, never to a stray result.
|
|
9615
|
+
// explicit `requeue`/`durable:runs:retry`, never to a stray result.
|
|
9369
9616
|
if (cp.status !== 'pending') {
|
|
9370
9617
|
if (!terminal)
|
|
9371
9618
|
return { cp, run };
|
|
@@ -9731,6 +9978,8 @@ function turnBeatKey(runId) {
|
|
|
9731
9978
|
/** The run whose remote turn is being enqueued in the current async context — lets the decision
|
|
9732
9979
|
* handler spot a transport delivering the decision from inside its own `dispatchWorkflowTask`. */
|
|
9733
9980
|
const turnDispatchAls = new AsyncLocalStorage();
|
|
9981
|
+
/** `true` while the engine does its own work: app-facing tenant guards do not apply inside it. */
|
|
9982
|
+
const engineInternal = new AsyncLocalStorage();
|
|
9734
9983
|
/** Tolerance (ms) between the worker's clock, which stamps a persisted heartbeat, and this engine's,
|
|
9735
9984
|
* when deciding whether a beat belongs to the current attempt (see `awaitWithHeartbeat`). */
|
|
9736
9985
|
const HEARTBEAT_CLOCK_SKEW_MS = 1_000;
|