@adonis-agora/durable 0.49.0 → 0.50.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.
Files changed (324) hide show
  1. package/CHANGELOG.md +261 -0
  2. package/README.md +53 -22
  3. package/dist/assets/spa/assets/index-DupjrK7N.js +16 -0
  4. package/dist/assets/spa/assets/index-DupjrK7N.js.map +1 -0
  5. package/dist/assets/spa/index.html +1 -1
  6. package/dist/commands/durable_worker.d.ts.map +1 -1
  7. package/dist/commands/durable_worker.js +9 -8
  8. package/dist/commands/durable_worker.js.map +1 -1
  9. package/dist/configure.d.ts.map +1 -1
  10. package/dist/configure.js +1 -0
  11. package/dist/configure.js.map +1 -1
  12. package/dist/providers/dashboard_provider.d.ts +2 -0
  13. package/dist/providers/dashboard_provider.d.ts.map +1 -1
  14. package/dist/providers/dashboard_provider.js +50 -6
  15. package/dist/providers/dashboard_provider.js.map +1 -1
  16. package/dist/providers/durable_provider.d.ts.map +1 -1
  17. package/dist/providers/durable_provider.js +143 -34
  18. package/dist/providers/durable_provider.js.map +1 -1
  19. package/dist/src/admission-redis/redis-admission-backend.d.ts.map +1 -1
  20. package/dist/src/admission-redis/redis-admission-backend.js +3 -1
  21. package/dist/src/admission-redis/redis-admission-backend.js.map +1 -1
  22. package/dist/src/ambient-step.d.ts +24 -0
  23. package/dist/src/ambient-step.d.ts.map +1 -0
  24. package/dist/src/ambient-step.js +42 -0
  25. package/dist/src/ambient-step.js.map +1 -0
  26. package/dist/src/base-workflow.d.ts +10 -1
  27. package/dist/src/base-workflow.d.ts.map +1 -1
  28. package/dist/src/base-workflow.js.map +1 -1
  29. package/dist/src/codec-state-store.d.ts +28 -2
  30. package/dist/src/codec-state-store.d.ts.map +1 -1
  31. package/dist/src/codec-state-store.js +61 -3
  32. package/dist/src/codec-state-store.js.map +1 -1
  33. package/dist/src/commands/worker.d.ts +4 -1
  34. package/dist/src/commands/worker.d.ts.map +1 -1
  35. package/dist/src/commands/worker.js +27 -3
  36. package/dist/src/commands/worker.js.map +1 -1
  37. package/dist/src/config_types.d.ts +6 -0
  38. package/dist/src/config_types.d.ts.map +1 -1
  39. package/dist/src/control-plane-redis/redis-control-plane.d.ts.map +1 -1
  40. package/dist/src/control-plane-redis/redis-control-plane.js +22 -6
  41. package/dist/src/control-plane-redis/redis-control-plane.js.map +1 -1
  42. package/dist/src/control-planes/factory.d.ts +6 -0
  43. package/dist/src/control-planes/factory.d.ts.map +1 -1
  44. package/dist/src/control-planes/factory.js +1 -0
  45. package/dist/src/control-planes/factory.js.map +1 -1
  46. package/dist/src/dashboard/gateway-adapter.d.ts +15 -5
  47. package/dist/src/dashboard/gateway-adapter.d.ts.map +1 -1
  48. package/dist/src/dashboard/gateway-adapter.js +71 -7
  49. package/dist/src/dashboard/gateway-adapter.js.map +1 -1
  50. package/dist/src/dashboard/handlers.d.ts +53 -1
  51. package/dist/src/dashboard/handlers.d.ts.map +1 -1
  52. package/dist/src/dashboard/handlers.js +200 -20
  53. package/dist/src/dashboard/handlers.js.map +1 -1
  54. package/dist/src/dashboard/index.d.ts +2 -2
  55. package/dist/src/dashboard/index.d.ts.map +1 -1
  56. package/dist/src/dashboard/index.js +2 -2
  57. package/dist/src/dashboard/index.js.map +1 -1
  58. package/dist/src/dashboard/openapi.d.ts.map +1 -1
  59. package/dist/src/dashboard/openapi.js +71 -1
  60. package/dist/src/dashboard/openapi.js.map +1 -1
  61. package/dist/src/define_config.d.ts +122 -8
  62. package/dist/src/define_config.d.ts.map +1 -1
  63. package/dist/src/define_config.js.map +1 -1
  64. package/dist/src/diagnostics-bridge.d.ts +16 -3
  65. package/dist/src/diagnostics-bridge.d.ts.map +1 -1
  66. package/dist/src/diagnostics-bridge.js +9 -3
  67. package/dist/src/diagnostics-bridge.js.map +1 -1
  68. package/dist/src/discovery-roots.d.ts +92 -0
  69. package/dist/src/discovery-roots.d.ts.map +1 -0
  70. package/dist/src/discovery-roots.js +150 -0
  71. package/dist/src/discovery-roots.js.map +1 -0
  72. package/dist/src/engine.d.ts +418 -20
  73. package/dist/src/engine.d.ts.map +1 -1
  74. package/dist/src/engine.js +1523 -159
  75. package/dist/src/engine.js.map +1 -1
  76. package/dist/src/entities.d.ts +36 -1
  77. package/dist/src/entities.d.ts.map +1 -1
  78. package/dist/src/entities.js +65 -4
  79. package/dist/src/entities.js.map +1 -1
  80. package/dist/src/errors.d.ts +36 -0
  81. package/dist/src/errors.d.ts.map +1 -1
  82. package/dist/src/errors.js +44 -0
  83. package/dist/src/errors.js.map +1 -1
  84. package/dist/src/event-trigger-bridge.d.ts +8 -0
  85. package/dist/src/event-trigger-bridge.d.ts.map +1 -1
  86. package/dist/src/event-trigger-bridge.js +24 -3
  87. package/dist/src/event-trigger-bridge.js.map +1 -1
  88. package/dist/src/handshake/announced.d.ts +27 -0
  89. package/dist/src/handshake/announced.d.ts.map +1 -1
  90. package/dist/src/handshake/announced.js +62 -0
  91. package/dist/src/handshake/announced.js.map +1 -1
  92. package/dist/src/handshake/directory.d.ts +92 -0
  93. package/dist/src/handshake/directory.d.ts.map +1 -0
  94. package/dist/src/handshake/directory.js +139 -0
  95. package/dist/src/handshake/directory.js.map +1 -0
  96. package/dist/src/hooks/barrel-sources.d.ts +28 -0
  97. package/dist/src/hooks/barrel-sources.d.ts.map +1 -0
  98. package/dist/src/hooks/barrel-sources.js +92 -0
  99. package/dist/src/hooks/barrel-sources.js.map +1 -0
  100. package/dist/src/hooks/steps.d.ts +12 -3
  101. package/dist/src/hooks/steps.d.ts.map +1 -1
  102. package/dist/src/hooks/steps.js +7 -11
  103. package/dist/src/hooks/steps.js.map +1 -1
  104. package/dist/src/hooks/workflows.d.ts +12 -3
  105. package/dist/src/hooks/workflows.d.ts.map +1 -1
  106. package/dist/src/hooks/workflows.js +7 -13
  107. package/dist/src/hooks/workflows.js.map +1 -1
  108. package/dist/src/index.d.ts +12 -3
  109. package/dist/src/index.d.ts.map +1 -1
  110. package/dist/src/index.js +12 -3
  111. package/dist/src/index.js.map +1 -1
  112. package/dist/src/interfaces.d.ts +309 -21
  113. package/dist/src/interfaces.d.ts.map +1 -1
  114. package/dist/src/interfaces.js +10 -0
  115. package/dist/src/interfaces.js.map +1 -1
  116. package/dist/src/metrics.d.ts.map +1 -1
  117. package/dist/src/metrics.js +5 -0
  118. package/dist/src/metrics.js.map +1 -1
  119. package/dist/src/namespaces.d.ts +17 -0
  120. package/dist/src/namespaces.d.ts.map +1 -0
  121. package/dist/src/namespaces.js +24 -0
  122. package/dist/src/namespaces.js.map +1 -0
  123. package/dist/src/otel/durable-metrics.d.ts.map +1 -1
  124. package/dist/src/otel/durable-metrics.js +5 -0
  125. package/dist/src/otel/durable-metrics.js.map +1 -1
  126. package/dist/src/otel/durable-otel.d.ts.map +1 -1
  127. package/dist/src/otel/durable-otel.js +5 -0
  128. package/dist/src/otel/durable-otel.js.map +1 -1
  129. package/dist/src/pollers.d.ts +10 -1
  130. package/dist/src/pollers.d.ts.map +1 -1
  131. package/dist/src/pollers.js +15 -5
  132. package/dist/src/pollers.js.map +1 -1
  133. package/dist/src/protocol.d.ts +12 -0
  134. package/dist/src/protocol.d.ts.map +1 -1
  135. package/dist/src/protocol.js +27 -1
  136. package/dist/src/protocol.js.map +1 -1
  137. package/dist/src/remote-event.d.ts +6 -0
  138. package/dist/src/remote-event.d.ts.map +1 -0
  139. package/dist/src/remote-event.js +19 -0
  140. package/dist/src/remote-event.js.map +1 -0
  141. package/dist/src/remote-workflow-executor.d.ts +34 -12
  142. package/dist/src/remote-workflow-executor.d.ts.map +1 -1
  143. package/dist/src/remote-workflow-executor.js +42 -40
  144. package/dist/src/remote-workflow-executor.js.map +1 -1
  145. package/dist/src/run-detail-summary.d.ts +90 -3
  146. package/dist/src/run-detail-summary.d.ts.map +1 -1
  147. package/dist/src/run-detail-summary.js +360 -2
  148. package/dist/src/run-detail-summary.js.map +1 -1
  149. package/dist/src/run-facets.d.ts +48 -0
  150. package/dist/src/run-facets.d.ts.map +1 -0
  151. package/dist/src/run-facets.js +100 -0
  152. package/dist/src/run-facets.js.map +1 -0
  153. package/dist/src/run-gateway/interface.d.ts +27 -1
  154. package/dist/src/run-gateway/interface.d.ts.map +1 -1
  155. package/dist/src/run-gateway/proxy-run-gateway.d.ts +32 -4
  156. package/dist/src/run-gateway/proxy-run-gateway.d.ts.map +1 -1
  157. package/dist/src/run-gateway/proxy-run-gateway.js +101 -5
  158. package/dist/src/run-gateway/proxy-run-gateway.js.map +1 -1
  159. package/dist/src/run-gateway/run-request-responder.d.ts +18 -5
  160. package/dist/src/run-gateway/run-request-responder.d.ts.map +1 -1
  161. package/dist/src/run-gateway/run-request-responder.js +155 -32
  162. package/dist/src/run-gateway/run-request-responder.js.map +1 -1
  163. package/dist/src/run-gateway/store-run-gateway.d.ts +23 -1
  164. package/dist/src/run-gateway/store-run-gateway.d.ts.map +1 -1
  165. package/dist/src/run-gateway/store-run-gateway.js +38 -0
  166. package/dist/src/run-gateway/store-run-gateway.js.map +1 -1
  167. package/dist/src/search-attributes-schema.d.ts +44 -0
  168. package/dist/src/search-attributes-schema.d.ts.map +1 -0
  169. package/dist/src/search-attributes-schema.js +51 -0
  170. package/dist/src/search-attributes-schema.js.map +1 -0
  171. package/dist/src/singleton-admission.d.ts +75 -0
  172. package/dist/src/singleton-admission.d.ts.map +1 -0
  173. package/dist/src/singleton-admission.js +156 -0
  174. package/dist/src/singleton-admission.js.map +1 -0
  175. package/dist/src/singleton-gate.d.ts +23 -8
  176. package/dist/src/singleton-gate.d.ts.map +1 -1
  177. package/dist/src/singleton-gate.js +59 -12
  178. package/dist/src/singleton-gate.js.map +1 -1
  179. package/dist/src/standard-schema.d.ts +83 -0
  180. package/dist/src/standard-schema.d.ts.map +1 -0
  181. package/dist/src/standard-schema.js +31 -0
  182. package/dist/src/standard-schema.js.map +1 -0
  183. package/dist/src/step-discovery.d.ts +21 -11
  184. package/dist/src/step-discovery.d.ts.map +1 -1
  185. package/dist/src/step-discovery.js +86 -45
  186. package/dist/src/step-discovery.js.map +1 -1
  187. package/dist/src/step-ref.d.ts +38 -12
  188. package/dist/src/step-ref.d.ts.map +1 -1
  189. package/dist/src/step-ref.js +5 -13
  190. package/dist/src/step-ref.js.map +1 -1
  191. package/dist/src/stores/factory.d.ts +6 -0
  192. package/dist/src/stores/factory.d.ts.map +1 -1
  193. package/dist/src/stores/factory.js +4 -1
  194. package/dist/src/stores/factory.js.map +1 -1
  195. package/dist/src/stores/lucid-helpers.d.ts +2 -0
  196. package/dist/src/stores/lucid-helpers.d.ts.map +1 -1
  197. package/dist/src/stores/lucid-helpers.js +7 -0
  198. package/dist/src/stores/lucid-helpers.js.map +1 -1
  199. package/dist/src/stores/lucid-mappers.d.ts +25 -0
  200. package/dist/src/stores/lucid-mappers.d.ts.map +1 -1
  201. package/dist/src/stores/lucid-mappers.js +81 -18
  202. package/dist/src/stores/lucid-mappers.js.map +1 -1
  203. package/dist/src/stores/lucid-schema.d.ts +59 -1
  204. package/dist/src/stores/lucid-schema.d.ts.map +1 -1
  205. package/dist/src/stores/lucid-schema.js +271 -25
  206. package/dist/src/stores/lucid-schema.js.map +1 -1
  207. package/dist/src/stores/lucid.d.ts +61 -2
  208. package/dist/src/stores/lucid.d.ts.map +1 -1
  209. package/dist/src/stores/lucid.js +346 -41
  210. package/dist/src/stores/lucid.js.map +1 -1
  211. package/dist/src/telescope/dashboard.d.ts.map +1 -1
  212. package/dist/src/telescope/dashboard.js +30 -1
  213. package/dist/src/telescope/dashboard.js.map +1 -1
  214. package/dist/src/telescope/data-providers.d.ts +19 -1
  215. package/dist/src/telescope/data-providers.d.ts.map +1 -1
  216. package/dist/src/telescope/data-providers.js +119 -3
  217. package/dist/src/telescope/data-providers.js.map +1 -1
  218. package/dist/src/telescope/extension.d.ts.map +1 -1
  219. package/dist/src/telescope/extension.js +2 -1
  220. package/dist/src/telescope/extension.js.map +1 -1
  221. package/dist/src/telescope/index.d.ts +1 -1
  222. package/dist/src/telescope/index.d.ts.map +1 -1
  223. package/dist/src/telescope/index.js +1 -1
  224. package/dist/src/telescope/index.js.map +1 -1
  225. package/dist/src/testing/in-memory-state-store.d.ts +44 -15
  226. package/dist/src/testing/in-memory-state-store.d.ts.map +1 -1
  227. package/dist/src/testing/in-memory-state-store.js +204 -41
  228. package/dist/src/testing/in-memory-state-store.js.map +1 -1
  229. package/dist/src/testing-kit/checkpoint-aggregation-contract.d.ts +16 -0
  230. package/dist/src/testing-kit/checkpoint-aggregation-contract.d.ts.map +1 -0
  231. package/dist/src/testing-kit/checkpoint-aggregation-contract.js +228 -0
  232. package/dist/src/testing-kit/checkpoint-aggregation-contract.js.map +1 -0
  233. package/dist/src/testing-kit/state-store-conformance.d.ts.map +1 -1
  234. package/dist/src/testing-kit/state-store-conformance.js +355 -3
  235. package/dist/src/testing-kit/state-store-conformance.js.map +1 -1
  236. package/dist/src/transport-pool.d.ts +15 -1
  237. package/dist/src/transport-pool.d.ts.map +1 -1
  238. package/dist/src/transport-pool.js +50 -4
  239. package/dist/src/transport-pool.js.map +1 -1
  240. package/dist/src/transports/adaptive-concurrency.d.ts +168 -0
  241. package/dist/src/transports/adaptive-concurrency.d.ts.map +1 -0
  242. package/dist/src/transports/adaptive-concurrency.js +353 -0
  243. package/dist/src/transports/adaptive-concurrency.js.map +1 -0
  244. package/dist/src/transports/bullmq/adaptive-concurrency.d.ts +1 -143
  245. package/dist/src/transports/bullmq/adaptive-concurrency.d.ts.map +1 -1
  246. package/dist/src/transports/bullmq/adaptive-concurrency.js +3 -311
  247. package/dist/src/transports/bullmq/adaptive-concurrency.js.map +1 -1
  248. package/dist/src/transports/bullmq/bullmq-transport.d.ts +49 -1
  249. package/dist/src/transports/bullmq/bullmq-transport.d.ts.map +1 -1
  250. package/dist/src/transports/bullmq/bullmq-transport.js +330 -38
  251. package/dist/src/transports/bullmq/bullmq-transport.js.map +1 -1
  252. package/dist/src/transports/bullmq/deps.d.ts +18 -2
  253. package/dist/src/transports/bullmq/deps.d.ts.map +1 -1
  254. package/dist/src/transports/bullmq/deps.js +44 -2
  255. package/dist/src/transports/bullmq/deps.js.map +1 -1
  256. package/dist/src/transports/db-helpers.d.ts +39 -0
  257. package/dist/src/transports/db-helpers.d.ts.map +1 -1
  258. package/dist/src/transports/db-helpers.js +73 -0
  259. package/dist/src/transports/db-helpers.js.map +1 -1
  260. package/dist/src/transports/db-remote.d.ts +53 -0
  261. package/dist/src/transports/db-remote.d.ts.map +1 -0
  262. package/dist/src/transports/db-remote.js +147 -0
  263. package/dist/src/transports/db-remote.js.map +1 -0
  264. package/dist/src/transports/db-schema.d.ts +44 -1
  265. package/dist/src/transports/db-schema.d.ts.map +1 -1
  266. package/dist/src/transports/db-schema.js +227 -88
  267. package/dist/src/transports/db-schema.js.map +1 -1
  268. package/dist/src/transports/db.d.ts +189 -6
  269. package/dist/src/transports/db.d.ts.map +1 -1
  270. package/dist/src/transports/db.js +1291 -137
  271. package/dist/src/transports/db.js.map +1 -1
  272. package/dist/src/transports/event-emitter.d.ts +8 -0
  273. package/dist/src/transports/event-emitter.d.ts.map +1 -1
  274. package/dist/src/transports/event-emitter.js +36 -3
  275. package/dist/src/transports/event-emitter.js.map +1 -1
  276. package/dist/src/transports/factory.d.ts +110 -5
  277. package/dist/src/transports/factory.d.ts.map +1 -1
  278. package/dist/src/transports/factory.js +56 -9
  279. package/dist/src/transports/factory.js.map +1 -1
  280. package/dist/src/transports/queue-mock-adapter.d.ts +7 -2
  281. package/dist/src/transports/queue-mock-adapter.d.ts.map +1 -1
  282. package/dist/src/transports/queue-mock-adapter.js +18 -2
  283. package/dist/src/transports/queue-mock-adapter.js.map +1 -1
  284. package/dist/src/transports/queue-remote.d.ts +146 -0
  285. package/dist/src/transports/queue-remote.d.ts.map +1 -0
  286. package/dist/src/transports/queue-remote.js +398 -0
  287. package/dist/src/transports/queue-remote.js.map +1 -0
  288. package/dist/src/transports/queue.d.ts +159 -6
  289. package/dist/src/transports/queue.d.ts.map +1 -1
  290. package/dist/src/transports/queue.js +747 -37
  291. package/dist/src/transports/queue.js.map +1 -1
  292. package/dist/src/transports/task-failure.d.ts +31 -0
  293. package/dist/src/transports/task-failure.d.ts.map +1 -0
  294. package/dist/src/transports/task-failure.js +50 -0
  295. package/dist/src/transports/task-failure.js.map +1 -0
  296. package/dist/src/workflow-als.d.ts +1 -1
  297. package/dist/src/workflow-als.d.ts.map +1 -1
  298. package/dist/src/workflow-ctx.d.ts +10 -0
  299. package/dist/src/workflow-ctx.d.ts.map +1 -1
  300. package/dist/src/workflow-ctx.js +97 -15
  301. package/dist/src/workflow-ctx.js.map +1 -1
  302. package/dist/src/workflow-discovery.d.ts +32 -41
  303. package/dist/src/workflow-discovery.d.ts.map +1 -1
  304. package/dist/src/workflow-discovery.js +65 -88
  305. package/dist/src/workflow-discovery.js.map +1 -1
  306. package/dist/src/workflow-ref.d.ts +24 -0
  307. package/dist/src/workflow-ref.d.ts.map +1 -1
  308. package/dist/src/workflow-ref.js.map +1 -1
  309. package/dist/stubs/config/durable.stub +6 -0
  310. package/dist/stubs/config/durable_dashboard.stub +7 -3
  311. package/dist/stubs/database/migrations/create_durable_tables.stub +2 -1
  312. package/dist/stubs/database/migrations/create_durable_transport_messages.stub +43 -0
  313. package/dist/stubs/database/migrations/create_durable_transport_tables.stub +12 -9
  314. package/dist/stubs/database/migrations/create_durable_transport_worker_keys.stub +1 -1
  315. package/dist/stubs/make/workflow/main.stub +1 -1
  316. package/package.json +6 -4
  317. package/skills/durable-cluster/SKILL.md +80 -11
  318. package/skills/durable-determinism/SKILL.md +11 -5
  319. package/skills/durable-reliability/SKILL.md +77 -37
  320. package/skills/durable-setup/SKILL.md +51 -22
  321. package/skills/durable-transports-stores/SKILL.md +130 -19
  322. package/skills/durable-workflows/SKILL.md +71 -18
  323. package/dist/assets/spa/assets/index-DdB9zxuX.js +0 -16
  324. package/dist/assets/spa/assets/index-DdB9zxuX.js.map +0 -1
@@ -2,16 +2,31 @@ import { type AdmissionBackend } from './admission.js';
2
2
  import { type ConcurrencyConfig, type ConcurrencyQuota } from './concurrency.js';
3
3
  import { type EntityConfig } from './entities.js';
4
4
  import { type EventBatchConfig } from './event-accumulators.js';
5
- import type { ControlPlane, EngineListener, GroupHealth, NamedTransport, RunDispatcher, RunFacetQuery, RunQuery, RunResult, RunScope, RunStatus, RunValueAxis, RunValueFacetOptions, RunValueFacetRow, ScopedRetentionPolicy, SearchAttributes, SignalWaiter, StateStore, StepCheckpoint, StepError, StepInterceptor, Transport, UpdateResult, UpdateValidator, WorkflowCtx, WorkflowExecutor, WorkflowRun } from './interfaces.js';
5
+ import { type AnnouncedWorkflow } from './handshake/announced.js';
6
+ import { type WorkflowDirectory } from './handshake/directory.js';
7
+ import type { ControlPlane, EngineListener, GroupHealth, NamedTransport, RunDispatcher, RunFacetQuery, RunFacetRow, RunQuery, RunResult, RunScope, RunStatus, RunValueAxis, RunValueFacetOptions, RunValueFacetRow, ScopedRetentionPolicy, SearchAttributes, SearchAttributesSchema, SignalWaiter, StateStore, StepCheckpoint, StepError, StepInterceptor, Transport, UpdateResult, UpdateValidator, WorkflowCtx, WorkflowExecutor, WorkflowRun } from './interfaces.js';
6
8
  import { type OnErrorRoute } from './on-error.js';
9
+ import { type StepHandler } from './protocol.js';
7
10
  import type { QueueConfig } from './queue.js';
11
+ import { type FanPage, type FanPageQuery, type RunDetail, type RunDetailOptions } from './run-detail-summary.js';
8
12
  import { type ScheduledWorkflow } from './scheduler.js';
9
13
  import { ScheduleClient } from './schedules.js';
14
+ import { type StandardSchemaV1 } from './standard-schema.js';
10
15
  import type { NormalizedEventTrigger } from './workflow-events.js';
11
16
  import { type WorkflowClass, type WorkflowInputOf, type WorkflowRef } from './workflow-ref.js';
12
17
  type WorkflowFn = (ctx: WorkflowCtx, input: unknown) => Promise<unknown>;
13
18
  /** Options for {@link WorkflowEngine.start}. */
14
19
  export interface StartOptions {
20
+ /**
21
+ * Pin the run to this registered version of the workflow instead of the newest one. Resolved
22
+ * against the name@version registry ({@link WorkflowEngine.register}, `registerRemote`,
23
+ * {@link WorkflowEngine.remote}); a version that is not registered throws before any run row is
24
+ * created and never falls back to the newest (a caller that asked for `name@3` and silently got
25
+ * `name@4` could not tell). Not honoured by convention routing (an unregistered name routed to a
26
+ * live worker group), which invents its version: a pinned start there throws instead. Absent →
27
+ * the newest registered version, as before.
28
+ */
29
+ version?: string | undefined;
15
30
  /** Run-scoped tags, merged with the workflow's static `workflow` config `tags` onto the run. */
16
31
  tags?: string[] | undefined;
17
32
  /** Typed, queryable run data stamped on the run (e.g. `{ amount: 200, tier: 'pro' }`). */
@@ -90,8 +105,9 @@ export interface StalledRunInfo {
90
105
  /**
91
106
  * Serialize runs of a workflow that share a key — a durable, FIFO mutex (e.g. one pipeline per base).
92
107
  * Excess runs are admitted in creation order, `limit` at a time; the rest wait (suspended) and retry
93
- * admission on a timer until a slot frees. Race-free on a consistent store (admission order is the
94
- * same `(createdAt, id)` view for every instance).
108
+ * admission on a timer until a slot frees. The store claims slots atomically
109
+ * ({@link StateStore.tryAdmitSingleton}) and records them durably on the run, so concurrent
110
+ * replicas can't over-admit and a later (or same-millisecond) start can't displace a holder.
95
111
  */
96
112
  export interface SingletonConfig {
97
113
  /** Derive the serialization key from the workflow input. */
@@ -102,7 +118,7 @@ export interface SingletonConfig {
102
118
  * Max GATED (waiting-for-admission) runs allowed to queue behind the `limit` in-flight ones. When
103
119
  * set, `start` rejects with {@link SingletonQueueFullError} once in-flight + gated reaches
104
120
  * `limit + maxQueueDepth` — back-pressure against an unbounded same-key backlog. Omit for the
105
- * default unbounded queue. Counts `pending`/`running`/`suspended` runs sharing the key.
121
+ * default unbounded queue. Counts `pending`/`running`/`suspended`/`blocked` runs sharing the key.
106
122
  */
107
123
  maxQueueDepth?: number;
108
124
  }
@@ -135,12 +151,17 @@ export interface WorkflowEngineDeps {
135
151
  * (`runPending` / `recoverIncomplete` / `resumeDueTimers` / `sweepTimeouts`) only act on runs in
136
152
  * this namespace, and `resume` of a foreign run throws {@link NamespaceMismatch}.
137
153
  *
138
- * **Leaving it UNSET makes this engine an operator**: it drives, recovers and resumes runs of EVERY
154
+ * **`undefined` here makes this engine an operator**: it drives, recovers and resumes runs of EVERY
139
155
  * namespace (the store's poll filters no-op on `undefined`) and leaves its transports on their bare
140
156
  * prefix. That is what lets one control plane serve many tenant pools — the queue a step lands on
141
157
  * is derived from the RUN's namespace, not this engine's. Set a value to scope the instance to one
142
158
  * pool instead, so ONE state store can be shared across non-interchangeable pools (e.g. local dev
143
159
  * vs a cluster). `'default'` and `''` are the bare pool, byte-identical to a single-pool deployment.
160
+ *
161
+ * The AdonisJS provider maps `config/durable.ts` onto this: an absent `namespace` is `'default'` on
162
+ * a `standalone` engine (it drives only the default pool, legacy rows included), and stays
163
+ * `undefined` — the operator — only on a `role: 'control-plane'` engine. Constructing the engine by
164
+ * hand keeps the raw meaning above.
144
165
  */
145
166
  namespace?: string | undefined;
146
167
  /** Recovery lease duration in ms — how long this instance owns a run it picked up. Default 30s. */
@@ -168,6 +189,7 @@ export interface WorkflowEngineDeps {
168
189
  * advances), so it's a safe self-heal, not a retry that can double-dispatch. The primary event path
169
190
  * still wins first for a healthy run (it re-drives long before this fires), so this only ever fires
170
191
  * for a genuinely-orphaned run. Default 5min; set `0` to disable (the prior wake-forever behavior).
192
+ * A non-finite or negative value falls back to the default.
171
193
  */
172
194
  reconcileMs?: number | undefined;
173
195
  /**
@@ -178,6 +200,14 @@ export interface WorkflowEngineDeps {
178
200
  * falls back to the default. Default 500.
179
201
  */
180
202
  timerBatchSize?: number | undefined;
203
+ /**
204
+ * How long (ms) a BUFFERED event that no `waitForEvent` claimed is kept (see
205
+ * {@link WorkflowEngine.publishEvent}'s buffering). Default `undefined`: kept until consumed, like a
206
+ * buffered signal. When set, a `waitForEvent` skips (and removes) a candidate older than this, and
207
+ * the worker tick's retention phase deletes expired rows across every name in bounded batches
208
+ * (through {@link StateStore.pruneBufferedEvents}, when the store has it).
209
+ */
210
+ eventBufferTtlMs?: number | undefined;
181
211
  /**
182
212
  * Opt-in self-heal window (ms) for a remote step with NO `timeoutMs` whose dispatched job was LOST —
183
213
  * the worker crashed mid-step leaving no result, or the transport dropped the job (a Redis
@@ -193,12 +223,33 @@ export interface WorkflowEngineDeps {
193
223
  * survive a lost dispatch. {@link WorkflowEngine.redispatchPending} is the manual counterpart.
194
224
  */
195
225
  remoteRedispatchMs?: number | undefined;
226
+ /**
227
+ * Minimum gap (ms) between two persisted heartbeats of one step (see `persistHeartbeat`). Default
228
+ * 10s: beats can arrive every few seconds from a hot loop, and liveness observability must not
229
+ * become a per-item UPDATE storm. It is also how stale a beat persisted by ANOTHER instance can be
230
+ * when a step's dispatcher reads it, so the dispatcher allows this much extra silence once it sees
231
+ * beats landing elsewhere. Mostly a testing knob.
232
+ */
233
+ heartbeatPersistThrottleMs?: number | undefined;
196
234
  /**
197
235
  * Max times {@link remoteRedispatchMs} re-dispatches one lost remote step before giving up and
198
236
  * failing it as a {@link RemoteStepError} (`code: 'remote_step_lost'`), so the run fails / dead-letters
199
237
  * instead of re-dispatching forever. Default 10. Ignored when `remoteRedispatchMs` is unset.
200
238
  */
201
239
  remoteRedispatchMax?: number | undefined;
240
+ /**
241
+ * Opt-in liveness window (ms) for a remote workflow TURN. A dispatched turn whose decision has not
242
+ * landed, and whose worker has sent no run-scoped {@link Heartbeat}, within this window is presumed
243
+ * lost: the timer poller re-drives the run by dispatching a fresh turn (new `taskId`, same history —
244
+ * the worker's replay is deterministic), and a late decision for the old turn is dropped. Each
245
+ * run-scoped beat pushes the deadline forward (throttled to one write per half window), so a worker
246
+ * still replaying a long turn is never re-driven. For an INLINE executor (one that returns its
247
+ * decision in-process) a lapsed window releases the run lease and leaves the run to crash recovery
248
+ * instead of failing it. A `RemoteWorkflowExecutor`'s own `timeoutMs` is the per-workflow version of
249
+ * this window; when both are set the shorter wins. Unset (the default) waits for the decision with
250
+ * no deadline.
251
+ */
252
+ remoteAdvanceSilenceMs?: number | undefined;
202
253
  /**
203
254
  * Build the public callback URL for a `ctx.webhook()` token (e.g.
204
255
  * ``(t) => `https://api.example.com/durable/webhooks/${t}` ``). Populates
@@ -264,6 +315,14 @@ export interface WorkflowEngineDeps {
264
315
  * rule matching a run wins, since every rule is applied.
265
316
  */
266
317
  retentionPolicies?: ScopedRetentionPolicy[] | undefined;
318
+ /**
319
+ * The fleet-default dead-letter workflow: a run that goes `dead` (past `maxRecoveryAttempts`) is
320
+ * handed to it — started with a {@link DeadLetter} input under the run id `dlq:<deadRunId>`, in the
321
+ * dead run's namespace. A workflow's own `deadLetterWorkflow` (register option / static config)
322
+ * wins. Idempotent by that run id, so a run dead-lettered twice is handled once. A handler run
323
+ * (`dlq:…`) that itself goes dead is not routed again.
324
+ */
325
+ deadLetterWorkflow?: WorkflowRef | undefined;
267
326
  /**
268
327
  * How long an in-flight run must sit untouched — with the stranded signature (a wake-forever
269
328
  * suspension, or an old pending remote step whose worker heartbeat is silent) — before the
@@ -307,7 +366,7 @@ export interface WorkflowEngineDeps {
307
366
  * How long (ms) a run parked `blocked` (no capable/compatible worker, design §7.5) waits before the
308
367
  * blocked-recovery poll re-drives it to re-check the live fleet. Default 5000. The run proceeds the
309
368
  * moment a capable+compatible worker appears on the next poll; a lower value trades broker SCANs for
310
- * faster pickup.
369
+ * faster pickup. A non-finite or sub-1 value falls back to the default.
311
370
  */
312
371
  blockedPollMs?: number | undefined;
313
372
  /**
@@ -350,11 +409,21 @@ export declare class WorkflowEngine {
350
409
  /** Whether the worker tick fires {@link schedules} (the `persistedSchedules` option). */
351
410
  readonly persistedSchedulesEnabled: boolean;
352
411
  private readonly leaseMs;
412
+ /**
413
+ * The engine clock when this instance was constructed. Every lease THIS incarnation writes ends at
414
+ * or after `bootedAt + leaseMs`; a lease held under {@link instanceId} that ends before that was
415
+ * written by a previous process that ran under the same id (a pod restarted after a crash) — see
416
+ * {@link selfReclaimable}.
417
+ */
418
+ private readonly bootedAt;
353
419
  private readonly maxRecoveryAttempts?;
354
420
  private readonly reconcileMs;
421
+ private readonly heartbeatPersistThrottleMs;
355
422
  private readonly timerBatchSize;
423
+ private readonly eventBufferTtlMs;
356
424
  private readonly remoteRedispatchMs;
357
425
  private readonly remoteRedispatchMax;
426
+ private readonly remoteAdvanceSilenceMs;
358
427
  private readonly webhookUrl?;
359
428
  private readonly traceparent?;
360
429
  private readonly context?;
@@ -400,7 +469,10 @@ export declare class WorkflowEngine {
400
469
  private readonly accumulators;
401
470
  /** In-flight remote steps awaiting a worker result, keyed by stepId. */
402
471
  private readonly pending;
403
- /** Per-step "reset the liveness timer" callbacks, called when a heartbeat arrives. */
472
+ /** Each run's step results waiting to be settled on this instance — see {@link completeRemoteResult}. */
473
+ private readonly resultQueues;
474
+ /** Per-step "reset the liveness timer" callbacks, called with the beat's worker timestamp when a
475
+ * heartbeat arrives. */
404
476
  private readonly heartbeatResets;
405
477
  private readonly listeners;
406
478
  /** Step interceptors (onion middleware around real local-step execution), first = outermost. */
@@ -411,6 +483,8 @@ export declare class WorkflowEngine {
411
483
  private readonly enqueuedListeners;
412
484
  /** Notified when a run is dead-lettered (moved to `dead`) — a hook for a DLQ handler. */
413
485
  private readonly deadListeners;
486
+ /** The fleet-default dead-letter workflow's name — see {@link WorkflowEngineDeps.deadLetterWorkflow}. */
487
+ private readonly deadLetterWorkflow;
414
488
  /** Validators gating `engine.update`, keyed by `<workflow>:<updateName>`. */
415
489
  private readonly updateValidators;
416
490
  /** Runs being cancelled WITH saga compensation — see `cancel({ compensate: true })`. */
@@ -433,6 +507,20 @@ export declare class WorkflowEngine {
433
507
  * writes. `settleRun` checks this and degrades to an echo; `execute`'s finally clears the mark.
434
508
  */
435
509
  private readonly leaseLostRuns;
510
+ /**
511
+ * The lease handle of each execution in flight in this process, by run id. A path that gives the run
512
+ * lease away mid-execution on purpose (a remote turn suspending on its dispatched decision, see
513
+ * {@link releaseTurnLease}) flags it, so the execution's renew loop stops renewing and never reads its
514
+ * own release as a takeover: a late `false` renew would otherwise fence whoever applies the decision.
515
+ */
516
+ private readonly executionLeases;
517
+ /** Stable decision handler subscribed on every remote executor's transport (see registerRemote). */
518
+ private readonly decisionHandler;
519
+ /** Earliest clock time an awaited turn's deadline is worth reading again (`runId` → clock ms), the
520
+ * read throttle for {@link rearmDecisionDeadline}. */
521
+ private readonly decisionDeadlineRearms;
522
+ /** Whether any registered remote executor carries its own turn window (`decisionTimeoutMs`). */
523
+ private anyDecisionTimeout;
436
524
  /**
437
525
  * The in-process execution gate, by run id: the execution of the run currently in flight in THIS
438
526
  * process (`tail`), and at most one resume queued behind it (`queued`). See {@link resumeRun}.
@@ -456,6 +544,10 @@ export declare class WorkflowEngine {
456
544
  */
457
545
  private readonly postSettle;
458
546
  private draining;
547
+ /** Shutdown wait already spent before/alongside {@link drain} (see {@link beginDrain}). */
548
+ private drainSpentMs;
549
+ /** Re-arms the deadline of every {@link drain} currently waiting, when more wait is reported. */
550
+ private readonly drainRearms;
459
551
  constructor(deps: WorkflowEngineDeps);
460
552
  /**
461
553
  * Register a **durable entity** (a virtual object): a keyed actor whose `handlers` run **serialized
@@ -511,6 +603,18 @@ export declare class WorkflowEngine {
511
603
  /** Send an operation to an entity (fire-and-forget). Ordered + exactly-once per key. */
512
604
  signalEntity(name: string, key: string, op: string, arg?: unknown): Promise<void>;
513
605
  /** Read an entity's current durable state (published after each op), or undefined if it has none yet. */
606
+ /**
607
+ * The entity lost-wake reconcile: wake (start, if need be) every entity that has ops sitting in its
608
+ * buffer, up to `limit` of them. A workflow-side send stores the op and THEN wakes the entity, so a
609
+ * process that dies in between leaves the op buffered while the entity sleeps; this pass is what
610
+ * applies it. Idempotent — a wake carries a no-op nudge, and the entity reads its buffer in arrival
611
+ * order, consuming each op once (an op with a stable id is skipped if already applied). The worker
612
+ * tick runs it, throttled to one pass a minute. Returns how many entities were woken. A no-op on a
613
+ * store without `listBufferedSignalTokens`.
614
+ */
615
+ reconcileEntityWakes(limit?: number): Promise<number>;
616
+ /** {@link reconcileEntityWakes}, at most once a minute — the worker tick's entry point. */
617
+ sweepEntityWakes(now?: number): Promise<number>;
514
618
  getEntityState<S = unknown>(name: string, key: string): Promise<S | undefined>;
515
619
  /**
516
620
  * Be notified when a run is enqueued on ANOTHER instance (via the control plane), so a worker can
@@ -532,6 +636,10 @@ export declare class WorkflowEngine {
532
636
  concurrency?: ConcurrencyConfig | undefined;
533
637
  executionTimeout?: string | number | undefined;
534
638
  validateInput?: ((input: unknown) => void | Promise<void>) | undefined;
639
+ /** Standard Schema validated at start, before `validateInput` — see `RegisteredWorkflow.inputSchema`. */
640
+ inputSchema?: StandardSchemaV1 | undefined;
641
+ /** Typed/validated `searchAttributes` shape — see `RegisteredWorkflow.searchAttributesSchema`. */
642
+ searchAttributesSchema?: SearchAttributesSchema | undefined;
535
643
  onEvent?: string[] | undefined;
536
644
  eventBatch?: EventBatchConfig | undefined;
537
645
  requires?: string[] | undefined;
@@ -539,6 +647,8 @@ export declare class WorkflowEngine {
539
647
  origin?: string | undefined;
540
648
  /** Handler workflows a matching failure is routed to — see {@link OnErrorRoute}. */
541
649
  onError?: OnErrorRoute[] | undefined;
650
+ /** Where a `dead` run of this workflow is handed (overrides the engine's default). */
651
+ deadLetterWorkflow?: WorkflowRef | undefined;
542
652
  }): void;
543
653
  /**
544
654
  * Register a workflow whose body runs in another SDK (e.g. Python). The engine owns the run exactly
@@ -554,18 +664,37 @@ export declare class WorkflowEngine {
554
664
  concurrency?: ConcurrencyConfig;
555
665
  executionTimeout?: string | number;
556
666
  validateInput?: (input: unknown) => void | Promise<void>;
667
+ /** Standard Schema validated at start, before `validateInput` — see `RegisteredWorkflow.inputSchema`. */
668
+ inputSchema?: StandardSchemaV1;
557
669
  requires?: string[];
558
670
  /** Handler workflows a matching failure is routed to — see {@link OnErrorRoute}. */
559
671
  onError?: OnErrorRoute[];
672
+ /** Where a `dead` run of this workflow is handed (overrides the engine's default). */
673
+ deadLetterWorkflow?: WorkflowRef;
560
674
  }): void;
561
675
  /**
562
- * Convention-dispatch resolution: when `name` isn't locally registered but a LIVE worker group of
563
- * the same name exists, auto-register a remote workflow routed to that group (bare group, no
564
- * partition) so its runs advance over the broker like any `registerRemote`'d one. Always on — the
565
- * queue name IS the routing, exactly as the aviary engine does — so a Python/NestJS/thin-worker
566
- * workflow is reached by NAME with no `registerRemote` boilerplate. Returns whether `name` is
567
- * registered afterwards. A no-op (returns `false`) when no transport is wired (nothing to route
568
- * over) or no matching worker group is live (then `start`/`resume` throws `is not registered`).
676
+ * Register a workflow whose body runs in another SDK, routed to worker `group` — {@link
677
+ * registerRemote} with the executor built for you over this engine's primary transport. `version`
678
+ * defaults to `'1'`; register `name` at a version explicitly to pin starts to it
679
+ * ({@link StartOptions.version}).
680
+ */
681
+ remote(name: string, opts: Omit<Parameters<WorkflowEngine['registerRemote']>[2], 'executor'> & {
682
+ version?: string | undefined;
683
+ }): void;
684
+ /**
685
+ * Convention-dispatch resolution: when `name` isn't locally registered but a LIVE worker group for
686
+ * it exists, auto-register a remote workflow routed to that group so its runs advance over the
687
+ * broker like any `registerRemote`'d one. Always on — the queue name IS the routing, exactly as the
688
+ * aviary engine does — so a Python/NestJS/thin-worker workflow is reached by NAME with no
689
+ * `registerRemote` boilerplate. Returns whether `name` is registered (and routable for `namespace`)
690
+ * afterwards. A no-op (returns `false`) when no transport is wired (nothing to route over) or no
691
+ * matching worker group is live (then `start`/`resume` throws `is not registered`).
692
+ *
693
+ * TENANT-AWARE, per run: the group is the one the RUN's namespace routes to,
694
+ * `<name>@<namespace>` (bare for an absent/`default` namespace) — the token a tenant worker
695
+ * (`partition: <tenant>`) serves, and the same rule steps follow ({@link stepGroup}). So an operator
696
+ * starting a tenant's run reaches the tenant's live `<name>@<tenant>` group, not the bare one, and a
697
+ * convention workflow is re-checked for the namespace of every start.
569
698
  */
570
699
  private ensureConventionWorkflow;
571
700
  /**
@@ -579,7 +708,7 @@ export declare class WorkflowEngine {
579
708
  private readonly heartbeatPersists;
580
709
  /**
581
710
  * Persist a worker heartbeat on its step's checkpoint (see {@link StepCheckpoint.lastHeartbeatAt}),
582
- * best-effort and THROTTLED per step (≥10s between writes) — beats may arrive every few seconds
711
+ * best-effort and THROTTLED per step (≥ `heartbeatPersistThrottleMs`, default 10s, between writes) — beats may arrive every few seconds
583
712
  * from a hot batch loop, and liveness observability must not turn into a per-item UPDATE storm.
584
713
  * One exemption: a beat CARRYING progress punches through when the last persisted one had none —
585
714
  * the automatic pickup beat lands first (no payload) and must not shadow the handler's first real
@@ -596,6 +725,22 @@ export declare class WorkflowEngine {
596
725
  * never defer (in-process transports, or a broker transport left eager).
597
726
  */
598
727
  startConsumers(): void;
728
+ /**
729
+ * Serve a step body by name on this engine's primary transport — the same
730
+ * `transport.handle(name, fn)` the provider calls for every `stepsPath` handler. It makes the
731
+ * engine a {@link import('./step-discovery.js').StepServer}, so app code can serve steps from an
732
+ * extra directory without reaching for the (provider-private) transport:
733
+ *
734
+ * ```ts
735
+ * // start/durable.ts (a preload)
736
+ * import engine from '@adonis-agora/durable/services/main'
737
+ * import { registerStepsFromDir } from '@adonis-agora/durable'
738
+ * await registerStepsFromDir(engine, app.makePath('app/engine/steps'))
739
+ * ```
740
+ *
741
+ * Throws when no transport is wired or the wired one can't serve handlers (no `handle`).
742
+ */
743
+ handle(name: string, fn: StepHandler): void;
599
744
  /** Subscribe to lifecycle events. Returns an unsubscribe function. */
600
745
  subscribe(listener: EngineListener): () => void;
601
746
  /**
@@ -628,6 +773,15 @@ export declare class WorkflowEngine {
628
773
  */
629
774
  onDead(listener: (run: WorkflowRun) => void): () => void;
630
775
  private notifyDead;
776
+ /**
777
+ * Hand a `dead` run to its dead-letter workflow, when one is configured: the workflow's own
778
+ * `deadLetterWorkflow`, else the engine's default. Started as `dlq:<runId>` (idempotent: a run
779
+ * dead-lettered twice is handled once; the dashboard links the pair by that id) in the dead run's
780
+ * namespace, with a {@link DeadLetter} input. Tracked, so a drain waits for the start; a failure
781
+ * (e.g. the handler is not registered here) is warned, never thrown into recovery. A handler run
782
+ * that itself goes dead is not routed again — no `dlq:dlq:…` chain.
783
+ */
784
+ private routeDeadLetter;
631
785
  /** Emit a locally-produced lifecycle event: deliver to subscribers AND broadcast it on the
632
786
  * control plane so other instances (e.g. a dashboard pod) can live-tail this run. */
633
787
  private emit;
@@ -681,6 +835,20 @@ export declare class WorkflowEngine {
681
835
  runValueFacets(axis: RunValueAxis, query: RunFacetQuery, opts?: RunValueFacetOptions): Promise<RunValueFacetRow[]>;
682
836
  /** List a run's step checkpoints (its timeline). Part of the engine's read API; see {@link listRuns}. */
683
837
  listCheckpoints(runId: string): Promise<StepCheckpoint[]>;
838
+ /**
839
+ * How the runs matching `query` split across `(status, origin)`, in one aggregate when the store
840
+ * has {@link StateStore.runFacets} (else counted over a listing). Part of the engine's read API.
841
+ */
842
+ runFacets(query: RunFacetQuery): Promise<RunFacetRow[]>;
843
+ /**
844
+ * A run's detail (see `loadRunDetail`), built from the store's checkpoint aggregates when it has
845
+ * `checkpointStats` + `queryCheckpoints` — a summarized detail then reads a few rows per wide fan
846
+ * instead of the whole history, and carries an `etag` that `options.ifNoneMatch` can match.
847
+ */
848
+ getRunDetail(runId: string, options?: RunDetailOptions): Promise<RunDetail | null>;
849
+ /** One page of a summarized fan's members (see `loadFanMembers`), versioned like {@link getRunDetail}. */
850
+ getFanMembers(runId: string, group: string, query: FanPageQuery): Promise<FanPage | null>;
851
+ private detailSource;
684
852
  /**
685
853
  * Hard-delete a run and its whole child subtree from the store, returning the number of runs
686
854
  * removed (0 if `runId` is unknown). Children are collected BEFORE this run's checkpoints are
@@ -720,6 +888,21 @@ export declare class WorkflowEngine {
720
888
  * parents.
721
889
  */
722
890
  sweepRetention(now?: number): Promise<number>;
891
+ /**
892
+ * The {@link eventBufferTtlMs} prune: delete buffered events older than the TTL, a bounded batch
893
+ * at a time, through the store's `pruneBufferedEvents`. Runs in {@link sweepRetention}'s pass.
894
+ * Best-effort — a failure is warned and retried on the next pass.
895
+ */
896
+ private pruneExpiredBufferedEvents;
897
+ /**
898
+ * Delete the runs `rootIds` and their child subtrees: the ids are collected children-first (the
899
+ * edge is read from the parent's checkpoints, so before anything is deleted), deleted in one
900
+ * {@link deleteRunIds} batch, then each one's token-keyed buffered signals are swept like
901
+ * {@link deleteRun} does. Returns how many runs were deleted.
902
+ */
903
+ private deleteRunTrees;
904
+ /** Bulk-delete through the store's `deleteRuns`, or one `deleteRun` per id when it has none. */
905
+ private deleteRunIds;
723
906
  deleteRun(runId: string): Promise<number>;
724
907
  /**
725
908
  * Sweep a deleted run's token-keyed buffered signals — they are not run-scoped rows, so the store's
@@ -807,7 +990,15 @@ export declare class WorkflowEngine {
807
990
  private recheckStepFans;
808
991
  /** {@link StateStore.countCheckpoints} of `pending` steps, or a JS count on a store without it. */
809
992
  private countPendingSteps;
810
- /** Track an in-flight execution so {@link drain} can wait for it. */
993
+ /**
994
+ * Track an in-flight execution so {@link drain} can wait for it.
995
+ *
996
+ * The bookkeeping chain must not re-raise: `p.finally(...)` returns a NEW promise that rejects
997
+ * whenever `p` does, and nothing awaits it. So a turn whose store write failed (a dropped
998
+ * connection, a missing table) became a process-level unhandled rejection even though the caller
999
+ * awaiting `p` handled the very same error — fatal under Node's default `--unhandled-rejections`.
1000
+ * The rejection is reported once, to the caller that owns `p`.
1001
+ */
811
1002
  private track;
812
1003
  /**
813
1004
  * Track a post-settle side effect (parent notify, singleton wake) so {@link drain} waits for it,
@@ -822,8 +1013,14 @@ export declare class WorkflowEngine {
822
1013
  * (and their per-run lock loops) start nothing once this is called, and {@link runOne} refuses
823
1014
  * broker-delivered runs. Runs already started finish. {@link drain} calls it first; the worker loop
824
1015
  * calls it the moment a stop is requested, so a tick still in flight stops leasing a backlog.
1016
+ *
1017
+ * `spentMs` is wait time ALREADY spent on this shutdown (the worker loop waiting for its in-flight
1018
+ * tick). It is deducted from the budget of a later {@link drain}, so that wait plus the drain stay
1019
+ * within one `drainTimeoutMs`; a drain already waiting is shortened. Only time actually spent
1020
+ * waiting is charged, and reports add up. Non-finite or negative values are ignored, so one bad
1021
+ * report can't turn every later drain into a 0 ms wait.
825
1022
  */
826
- beginDrain(): void;
1023
+ beginDrain(spentMs?: number): void;
827
1024
  /** True once {@link beginDrain} (or {@link drain}) has been called. */
828
1025
  isDraining(): boolean;
829
1026
  /**
@@ -844,8 +1041,14 @@ export declare class WorkflowEngine {
844
1041
  * process is going away, so a held lease would only delay the next pod's `recoverIncomplete` until
845
1042
  * expiry; after release the next pod replays deterministically (a pending step re-dispatches the same
846
1043
  * `stepId`, idempotent by the durable contract; a completed step short-circuits).
1044
+ *
1045
+ * `timeoutMs` is the TOTAL shutdown budget: wait already reported through {@link beginDrain}
1046
+ * (`spentMs`) is deducted, so a worker that first waited for its in-flight tick drains only for
1047
+ * what is left. With nothing reported, the full `timeoutMs` applies.
847
1048
  */
848
1049
  drain(timeoutMs?: number): Promise<void>;
1050
+ /** The {@link drain} wait loop, bounded by `timer`. */
1051
+ private drainUntil;
849
1052
  /**
850
1053
  * Best-effort release of the recovery locks still held by in-flight executions. Called on `drain()`
851
1054
  * return so a shutting-down process hands off fast. Never throws (swallowed per-run).
@@ -862,6 +1065,33 @@ export declare class WorkflowEngine {
862
1065
  * replay from their checkpoints, so only the work that had not finished runs again.
863
1066
  */
864
1067
  recoverIncomplete(nowMs?: number): Promise<RunResult[]>;
1068
+ /**
1069
+ * Whether a lease held under this engine's own {@link instanceId} was left behind by a previous
1070
+ * process that ran under the same id, rather than held by this one. Both must hold:
1071
+ * - nothing in this process executes the run: no execution in its run gate and no execution lease
1072
+ * tracked for it;
1073
+ * - the lease was written before this engine booted: every lease this process writes (acquire or
1074
+ * renew) ends at `clock() + leaseMs >= bootedAt + leaseMs`, so an earlier end proves an earlier
1075
+ * writer. This also covers the brief windows where a path of this process holds the lease
1076
+ * without having entered the run gate yet (a sweep between `tryLockRun` and the resume).
1077
+ *
1078
+ * Sound only when no two LIVE processes share an instance id — the documented requirement.
1079
+ */
1080
+ private selfReclaimable;
1081
+ /**
1082
+ * Release a lease this instance's previous incarnation left on `listed` (see {@link selfReclaimable}),
1083
+ * so the recovery sweep can take the run like any orphan. The release runs inside the run's
1084
+ * in-process gate, so a resume landing meanwhile here queues behind it instead of executing on the
1085
+ * lease being given up; the run is re-read there and only released if it is still a stale `running`
1086
+ * self-lease. Returns whether the lease was released.
1087
+ */
1088
+ private releaseStaleSelfLease;
1089
+ /**
1090
+ * The remote step this run dispatched and still awaits a result for, if any — the durable marker
1091
+ * that tells {@link recoverIncomplete} "this run's work is on the transport, not in a crashed
1092
+ * process". A `pending` remote checkpoint is written before the dispatch and settled by the result.
1093
+ */
1094
+ private inFlightRemoteStep;
865
1095
  /**
866
1096
  * Per-recovery bookkeeping (called once the lease is held): count the attempt, or — past
867
1097
  * `maxRecoveryAttempts` — move a poison pill to the `dead` dead-letter state. Returns a terminal
@@ -904,8 +1134,9 @@ export declare class WorkflowEngine {
904
1134
  * Explicitly re-dispatch every remote step of `runId` stuck `pending` — the OPERATOR escape hatch for
905
1135
  * a run whose dispatched step job was LOST (worker crashed with no result, or a transport that dropped
906
1136
  * the job). No automatic recovery re-drives these: a reconcile re-drive re-suspends a still-pending
907
- * step by design (unless `remoteRedispatchMs` is set), `recoverIncomplete` only reclaims LEASED runs,
908
- * and `requeue` just replays back to the pending guard. This re-enqueues the same `stepId` so the
1137
+ * step by design (unless `remoteRedispatchMs` is set), `recoverIncomplete` parks a run with an
1138
+ * in-flight `pending` remote step back to `suspended` instead of re-driving it, and `requeue` just
1139
+ * replays back to the pending guard. This re-enqueues the same `stepId` so the
909
1140
  * (idempotent) step re-runs and its result resumes the run. Safe to call on a healthy run — it re-runs
910
1141
  * only in-flight `pending` remote steps, which are idempotent by the durable contract. Returns the
911
1142
  * run's current status (with the count re-dispatched), or null if the run is unknown.
@@ -946,6 +1177,12 @@ export declare class WorkflowEngine {
946
1177
  /**
947
1178
  * Resume each run only if this instance can acquire its recovery lease — so when several
948
1179
  * replicas recover or poll at once, each run is picked up by exactly one of them.
1180
+ *
1181
+ * A run this instance cannot resume is SKIPPED (and logged), never allowed to abort the batch: a
1182
+ * leased batch is a mixed bag during a rolling deploy (a pod without a run's workflow throws for
1183
+ * it by design) and a store hiccup can fail one row, but the rest of the batch is runs it can drive
1184
+ * — and every caller is a recovery/poll sweep. The skipped run stays locked only until its lease
1185
+ * expires, then another instance (or a later tick) picks it up.
949
1186
  */
950
1187
  private resumeLeased;
951
1188
  /**
@@ -1021,6 +1258,13 @@ export declare class WorkflowEngine {
1021
1258
  * side's write, and the destructive `take*` ops arbitrate so a signal is never delivered twice.
1022
1259
  */
1023
1260
  signal(token: string, payload: unknown): Promise<RunResult | null>;
1261
+ /**
1262
+ * Wake the run parked on `token` with `payload`, if there is one; otherwise do nothing — unlike
1263
+ * {@link signal}, it never buffers. For a wake-up whose real payload is ALREADY buffered on the
1264
+ * token (an entity op, see `Entities.kick`): a run that isn't parked reads that buffer at its next
1265
+ * wait, so there is nothing to keep.
1266
+ */
1267
+ private wakeSignalWaiter;
1024
1268
  /**
1025
1269
  * Ensure a run exists for `runId`, then deliver a signal to it — atomically race-free thanks to
1026
1270
  * signal buffering: if the run is new (or busy / not yet waiting), the signal is buffered and
@@ -1131,6 +1375,21 @@ export declare class WorkflowEngine {
1131
1375
  * the full listing for a store without one.
1132
1376
  */
1133
1377
  countRuns(query: Omit<RunQuery, 'page' | 'size'>): Promise<number>;
1378
+ /**
1379
+ * Every workflow the live fleet can execute in this engine's partition: what workers DECLARE in
1380
+ * their descriptors, plus the live routing tokens no descriptor explains (a worker too old to
1381
+ * describe itself). Each entry says which kind of evidence it rests on. See
1382
+ * {@link workflowDirectory} for why the list is the length it is.
1383
+ */
1384
+ announcedWorkflows(): Promise<AnnouncedWorkflow[]>;
1385
+ /**
1386
+ * {@link announcedWorkflows}, plus why it is empty when it is: no transport here can read the fleet
1387
+ * (`supported: false`), nothing is live, or workers are live only on OTHER partitions
1388
+ * (`otherPartitions`), which otherwise reads exactly like an empty fleet. Two live reads, neither
1389
+ * retained: the descriptor advertisement and the heartbeat keyspace. Only the BullMQ transport reads
1390
+ * them today.
1391
+ */
1392
+ workflowDirectory(): Promise<WorkflowDirectory>;
1134
1393
  /** The worker groups this engine dispatches to: every registered remote workflow's group, plus any
1135
1394
  * `extra` the caller declares. Local-step groups (a group consumed by in-process `@DurableStep`
1136
1395
  * workers, e.g. `pipeline`) aren't derivable from registrations — pass them via `extra` so a group
@@ -1140,7 +1399,7 @@ export declare class WorkflowEngine {
1140
1399
  * (so a registered group with backlog and ZERO workers still reports — the alert case) UNION the
1141
1400
  * groups discovered from live heartbeats (so a local-step group like `pipeline`, not derivable
1142
1401
  * from registrations, shows once its workers beat). Empty when no transport can introspect health
1143
- * (only the BullMQ transport implements `groupHealth`). */
1402
+ * (only the BullMQ, queue and db transports implement `groupHealth`). */
1144
1403
  workerHealth(extra?: string[]): Promise<GroupHealth[]>;
1145
1404
  /**
1146
1405
  * Persist a streamed local-step lifecycle event from a remote workflow worker (see
@@ -1231,7 +1490,101 @@ export declare class WorkflowEngine {
1231
1490
  * fallback is disabled (`reconcileMs: 0`). See {@link WorkflowEngineDeps.reconcileMs}.
1232
1491
  */
1233
1492
  private reconcileWakeAt;
1493
+ /**
1494
+ * The result of a turn the singleton gate denied. The store already parked the run `suspended` on
1495
+ * its retry timer INSIDE the admission claim (or left it alone, when a concurrent cancel had
1496
+ * settled it), so report what is persisted rather than writing over it.
1497
+ */
1498
+ /**
1499
+ * A run's first execution: `pending` -> `running`. A singleton run not yet admitted is stamped
1500
+ * {@link SINGLETON_TRACKED_TAG} in the same write, BEFORE it competes for a slot, so a run this
1501
+ * replica is about to admit or deny is never mistaken for a holder admitted by a replica older than
1502
+ * 0.51 (see `legacySingletonHolders`).
1503
+ */
1504
+ private markStarted;
1505
+ private singletonWaitResult;
1234
1506
  private runRemoteExecution;
1507
+ /**
1508
+ * Apply a remote workflow turn's {@link WorkflowDecision}: persist the local steps it ran, then settle
1509
+ * (completed/failed/cancelled) or dispatch its blocking ops and suspend (continue). Shared by the
1510
+ * INLINE advance path ({@link runRemoteExecution}, under the execution's lease) and the dispatched
1511
+ * path ({@link completeRemoteDecision}, which holds the lease itself and has already checked that
1512
+ * this is the decision of the turn the run awaits).
1513
+ */
1514
+ private applyDecision;
1515
+ /**
1516
+ * The dispatched-turn path of {@link runRemoteExecution}: SUSPEND, then ENQUEUE. The engine mints a
1517
+ * globally unique turn `taskId`, records it on the run as the awaited marker, and releases the lease,
1518
+ * all BEFORE the turn is enqueued, so a worker's decision, however fast and on whichever replica it
1519
+ * lands, always finds both its marker and a free lease (see {@link completeRemoteDecision}). The
1520
+ * deadline is the turn's liveness window ({@link decisionWindowMs}); when it lapses with no decision
1521
+ * and no run-scoped heartbeat, the timer poller re-drives the run, which dispatches a fresh turn.
1522
+ */
1523
+ private dispatchRemoteTurn;
1524
+ /**
1525
+ * The turn could not be enqueued (the transport cannot carry workflow turns, or the broker refused the
1526
+ * write): fail the run, as an advance that throws does — but only while it still awaits THIS turn, so
1527
+ * a cancel or a re-drive that landed meanwhile wins.
1528
+ */
1529
+ private failUndispatchedTurn;
1530
+ /**
1531
+ * Give the run lease away mid-execution on purpose: stop the execution's renew loop from renewing it
1532
+ * (or reading the release as a takeover), then release it. Owner-scoped, so a lease another instance
1533
+ * already holds is left alone.
1534
+ */
1535
+ private releaseTurnLease;
1536
+ /**
1537
+ * The liveness window of a dispatched turn: the engine's `remoteAdvanceSilenceMs` and the executor's
1538
+ * own `decisionTimeoutMs` (a `RemoteWorkflowExecutor`'s `timeoutMs`), the shorter of the two when both
1539
+ * are set. `undefined` = no deadline: the run waits for its decision.
1540
+ */
1541
+ private decisionWindowMs;
1542
+ /**
1543
+ * Await an INLINE advance, rejecting with {@link RemoteWorkflowTimeout} once `silenceMs` passes with
1544
+ * neither a decision nor a run-scoped heartbeat for `runId`. Each beat rearms the window. The beat
1545
+ * hook is registered before this returns, so a beat can't slip in unseen.
1546
+ */
1547
+ private awaitTurnWithSilence;
1548
+ /** Whether `run` is awaiting exactly `decision`'s turn (and so may still apply it). */
1549
+ private awaitsDecision;
1550
+ /**
1551
+ * Apply a remote workflow-turn {@link WorkflowDecision} durably, on WHICHEVER replica consumes it: the
1552
+ * dispatching replica may be gone, or a point-to-point decisions queue handed the decision to a
1553
+ * sibling. The durable twin of {@link completeRemoteResult} for a workflow TURN: look the run up by
1554
+ * `decision.runId`, check that it still awaits this `taskId` (a stale, duplicate, foreign or
1555
+ * superseded decision is dropped, so a decision applies at most once), lease it, and apply it.
1556
+ * Serialized with this replica's own executions of the run through the run gate.
1557
+ */
1558
+ private completeRemoteDecision;
1559
+ /**
1560
+ * Run `fn` in the run's in-process execution gate (see {@link resumeRun}): after the execution in
1561
+ * flight here, before any resume that lands later. A resume already queued (not started) runs first.
1562
+ */
1563
+ private serializeOnRun;
1564
+ /** {@link completeRemoteDecision} inside the run gate: lease, re-check, claim the turn, apply. */
1565
+ private applyRemoteDecision;
1566
+ /**
1567
+ * Acquire the run lease to apply `decision`. The dispatcher released it before enqueueing, so this
1568
+ * normally succeeds at once; another replica may still hold it briefly (its wake found the turn in
1569
+ * flight and is releasing), so retry until the lease frees, the run stops awaiting this turn, or one
1570
+ * lease window passes (a crashed holder's lease expires within it). A lease THIS instance holds is
1571
+ * reused: we are inside the run gate, so no execution of it is running here.
1572
+ */
1573
+ private leaseForDecision;
1574
+ /**
1575
+ * A run-scoped heartbeat for a run awaiting a dispatched turn's decision: push its re-drive deadline
1576
+ * (`wakeAt`) forward so a worker still replaying the turn is not re-driven. The durable counterpart
1577
+ * of an inline advance's in-memory rearm, and the run-scoped sibling of {@link rearmStepLease}.
1578
+ *
1579
+ * Throttled the same way: a renewal buys at most one window, so it is only worth a write once the
1580
+ * window is half spent. A beat that finds more than half of it left writes nothing and records the
1581
+ * half-spent mark, so later beats skip even the read until then. That bounds the cost at one read and
1582
+ * one write per half window per awaited turn, whatever the beat cadence. The mark is in memory only
1583
+ * (cold on another replica, which re-derives the same answer from the run's `wakeAt`). Best-effort:
1584
+ * it runs inside the transport's serial beat handler, so a store error is swallowed, and the mark only
1585
+ * moves after a write commits, so the next beat retries.
1586
+ */
1587
+ private rearmDecisionDeadline;
1235
1588
  /**
1236
1589
  * A polyglot workflow's re-emitted `call` whose checkpoint already exists: decide whether the step is
1237
1590
  * still out for delivery, or whether its dispatch was LOST and has to be re-driven. Returns the
@@ -1314,6 +1667,22 @@ export declare class WorkflowEngine {
1314
1667
  * {@link redriveLostCall}). A terminal turn (completed/failed/cancelled) applies its commands only to
1315
1668
  * record what already ran, and must never dispatch anything new. */
1316
1669
  private applyCommands;
1670
+ /**
1671
+ * Whether a compensating cancel is pending for this run, consuming the in-memory flag. The intent
1672
+ * lives in TWO places: the in-memory `cancelRequested` fast path (same pod, same boot), and the
1673
+ * DURABLE buffered `cancel:<id>` marker `cancel()` persisted — so a crash or a cross-pod handoff
1674
+ * mid-cancellation can't silently forget the cancel. The marker is peeked (take + re-buffer), not
1675
+ * consumed: it must survive a crash mid-unwind; {@link compensateAndCancel} drains it once
1676
+ * `cancelled` is durably recorded.
1677
+ */
1678
+ private takeCancelIntent;
1679
+ /**
1680
+ * Undo the registered saga compensations in reverse and settle the run `cancelled`. Shared by EVERY
1681
+ * exit of a turn with a compensating cancel pending — the body returned, suspended, hit
1682
+ * continue-as-new or threw — so the cancel is never lost because the body didn't stop at a
1683
+ * suspension point.
1684
+ */
1685
+ private compensateAndCancel;
1317
1686
  /** The run body, lease held + renewed by {@link execute}. */
1318
1687
  private runExecution;
1319
1688
  /**
@@ -1459,7 +1828,36 @@ export declare class WorkflowEngine {
1459
1828
  */
1460
1829
  /** {@link StateStore.saveCheckpointIfPending}, or a plain save on a store without it. */
1461
1830
  private savePendingCheckpoint;
1831
+ /**
1832
+ * Settle a durable step result and (barrier permitting) resume its run in the background.
1833
+ *
1834
+ * One run's results are settled in batches: the first starts a batch, and the ones that arrive
1835
+ * while it is being written (with `resultConcurrency > 1`) form the next, up to
1836
+ * {@link RESULT_BATCH_MAX}. A batch of several claims its checkpoints with one
1837
+ * {@link StateStore.saveCheckpointsIfPending} call, asks each step fan's barrier once, and resumes
1838
+ * the run at most once; a batch of one takes the single-result path unchanged. A result resolves
1839
+ * only once its own write committed, so the transport acknowledges nothing that is not durable.
1840
+ */
1462
1841
  private completeRemoteResult;
1842
+ /** Settle `queue` (one run's queued results) batch by batch until it is empty, then retire it. */
1843
+ private drainResultQueue;
1844
+ /** The single-result path: settle `result`, then resume its run unless its fan is still running. */
1845
+ private completeOneRemoteResult;
1846
+ private resumeInBackground;
1847
+ /**
1848
+ * Settle a batch of one run's step results; per result, `true` (settled or dropped) or the error
1849
+ * that kept it from settling, which the transport then redelivers. Same outcome per result as
1850
+ * {@link completeOneRemoteResult} on each in turn, with one resume for the batch: that resume
1851
+ * replays every write of the batch, so a second would only replay it again.
1852
+ */
1853
+ private completeRemoteResults;
1854
+ /**
1855
+ * {@link savePendingCheckpoint} for checkpoints at distinct seqs: one
1856
+ * {@link StateStore.saveCheckpointsIfPending} call when the store has it, else one write each.
1857
+ * Never rejects as a whole: a failed write is reported as that entry's error, and the batch call
1858
+ * rejecting means it wrote nothing, so the entries are then written one by one.
1859
+ */
1860
+ private savePendingCheckpoints;
1463
1861
  /**
1464
1862
  * The join barrier of a step fan: a result for one of its steps resumes the parent only once no
1465
1863
  * step of the fan is still `pending` (a settled wave), or on a failure (failFast surfaces it, and a