@adonis-agora/durable 0.1.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 (316) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +29 -0
  3. package/dist/assets/dashboard.html +446 -0
  4. package/dist/commands/main.d.ts +15 -0
  5. package/dist/commands/main.d.ts.map +1 -0
  6. package/dist/commands/main.js +15 -0
  7. package/dist/commands/main.js.map +1 -0
  8. package/dist/commands/retry.d.ts +16 -0
  9. package/dist/commands/retry.d.ts.map +1 -0
  10. package/dist/commands/retry.js +34 -0
  11. package/dist/commands/retry.js.map +1 -0
  12. package/dist/commands/runs.d.ts +17 -0
  13. package/dist/commands/runs.d.ts.map +1 -0
  14. package/dist/commands/runs.js +40 -0
  15. package/dist/commands/runs.js.map +1 -0
  16. package/dist/commands/work.d.ts +18 -0
  17. package/dist/commands/work.d.ts.map +1 -0
  18. package/dist/commands/work.js +58 -0
  19. package/dist/commands/work.js.map +1 -0
  20. package/dist/configure.d.ts +15 -0
  21. package/dist/configure.d.ts.map +1 -0
  22. package/dist/configure.js +26 -0
  23. package/dist/configure.js.map +1 -0
  24. package/dist/providers/dashboard_provider.d.ts +27 -0
  25. package/dist/providers/dashboard_provider.d.ts.map +1 -0
  26. package/dist/providers/dashboard_provider.js +94 -0
  27. package/dist/providers/dashboard_provider.js.map +1 -0
  28. package/dist/providers/durable_provider.d.ts +36 -0
  29. package/dist/providers/durable_provider.d.ts.map +1 -0
  30. package/dist/providers/durable_provider.js +113 -0
  31. package/dist/providers/durable_provider.js.map +1 -0
  32. package/dist/src/admission-redis/index.d.ts +2 -0
  33. package/dist/src/admission-redis/index.d.ts.map +1 -0
  34. package/dist/src/admission-redis/index.js +2 -0
  35. package/dist/src/admission-redis/index.js.map +1 -0
  36. package/dist/src/admission-redis/redis-admission-backend.d.ts +83 -0
  37. package/dist/src/admission-redis/redis-admission-backend.d.ts.map +1 -0
  38. package/dist/src/admission-redis/redis-admission-backend.js +200 -0
  39. package/dist/src/admission-redis/redis-admission-backend.js.map +1 -0
  40. package/dist/src/admission.d.ts +49 -0
  41. package/dist/src/admission.d.ts.map +1 -0
  42. package/dist/src/admission.js +30 -0
  43. package/dist/src/admission.js.map +1 -0
  44. package/dist/src/backoff.d.ts +5 -0
  45. package/dist/src/backoff.d.ts.map +1 -0
  46. package/dist/src/backoff.js +11 -0
  47. package/dist/src/backoff.js.map +1 -0
  48. package/dist/src/checkpoints.d.ts +42 -0
  49. package/dist/src/checkpoints.d.ts.map +1 -0
  50. package/dist/src/checkpoints.js +57 -0
  51. package/dist/src/checkpoints.js.map +1 -0
  52. package/dist/src/codec-state-store.d.ts +60 -0
  53. package/dist/src/codec-state-store.d.ts.map +1 -0
  54. package/dist/src/codec-state-store.js +129 -0
  55. package/dist/src/codec-state-store.js.map +1 -0
  56. package/dist/src/commands/index.d.ts +3 -0
  57. package/dist/src/commands/index.d.ts.map +1 -0
  58. package/dist/src/commands/index.js +3 -0
  59. package/dist/src/commands/index.js.map +1 -0
  60. package/dist/src/commands/runs.d.ts +24 -0
  61. package/dist/src/commands/runs.d.ts.map +1 -0
  62. package/dist/src/commands/runs.js +39 -0
  63. package/dist/src/commands/runs.js.map +1 -0
  64. package/dist/src/commands/worker.d.ts +61 -0
  65. package/dist/src/commands/worker.d.ts.map +1 -0
  66. package/dist/src/commands/worker.js +74 -0
  67. package/dist/src/commands/worker.js.map +1 -0
  68. package/dist/src/completion.d.ts +15 -0
  69. package/dist/src/completion.d.ts.map +1 -0
  70. package/dist/src/completion.js +10 -0
  71. package/dist/src/completion.js.map +1 -0
  72. package/dist/src/dashboard/define_config.d.ts +48 -0
  73. package/dist/src/dashboard/define_config.d.ts.map +1 -0
  74. package/dist/src/dashboard/define_config.js +57 -0
  75. package/dist/src/dashboard/define_config.js.map +1 -0
  76. package/dist/src/dashboard/handlers.d.ts +47 -0
  77. package/dist/src/dashboard/handlers.d.ts.map +1 -0
  78. package/dist/src/dashboard/handlers.js +163 -0
  79. package/dist/src/dashboard/handlers.js.map +1 -0
  80. package/dist/src/dashboard/html.d.ts +7 -0
  81. package/dist/src/dashboard/html.d.ts.map +1 -0
  82. package/dist/src/dashboard/html.js +24 -0
  83. package/dist/src/dashboard/html.js.map +1 -0
  84. package/dist/src/dashboard/index.d.ts +8 -0
  85. package/dist/src/dashboard/index.d.ts.map +1 -0
  86. package/dist/src/dashboard/index.js +6 -0
  87. package/dist/src/dashboard/index.js.map +1 -0
  88. package/dist/src/define_config.d.ts +72 -0
  89. package/dist/src/define_config.d.ts.map +1 -0
  90. package/dist/src/define_config.js +8 -0
  91. package/dist/src/define_config.js.map +1 -0
  92. package/dist/src/diagnostics-bridge.d.ts +22 -0
  93. package/dist/src/diagnostics-bridge.d.ts.map +1 -0
  94. package/dist/src/diagnostics-bridge.js +25 -0
  95. package/dist/src/diagnostics-bridge.js.map +1 -0
  96. package/dist/src/duration.d.ts +6 -0
  97. package/dist/src/duration.d.ts.map +1 -0
  98. package/dist/src/duration.js +39 -0
  99. package/dist/src/duration.js.map +1 -0
  100. package/dist/src/engine.d.ts +583 -0
  101. package/dist/src/engine.d.ts.map +1 -0
  102. package/dist/src/engine.js +1894 -0
  103. package/dist/src/engine.js.map +1 -0
  104. package/dist/src/entities.d.ts +44 -0
  105. package/dist/src/entities.d.ts.map +1 -0
  106. package/dist/src/entities.js +64 -0
  107. package/dist/src/entities.js.map +1 -0
  108. package/dist/src/errors.d.ts +68 -0
  109. package/dist/src/errors.d.ts.map +1 -0
  110. package/dist/src/errors.js +102 -0
  111. package/dist/src/errors.js.map +1 -0
  112. package/dist/src/event-accumulators.d.ts +39 -0
  113. package/dist/src/event-accumulators.d.ts.map +1 -0
  114. package/dist/src/event-accumulators.js +70 -0
  115. package/dist/src/event-accumulators.js.map +1 -0
  116. package/dist/src/events.d.ts +9 -0
  117. package/dist/src/events.d.ts.map +1 -0
  118. package/dist/src/events.js +35 -0
  119. package/dist/src/events.js.map +1 -0
  120. package/dist/src/index.d.ts +36 -0
  121. package/dist/src/index.d.ts.map +1 -0
  122. package/dist/src/index.js +37 -0
  123. package/dist/src/index.js.map +1 -0
  124. package/dist/src/interfaces.d.ts +909 -0
  125. package/dist/src/interfaces.d.ts.map +1 -0
  126. package/dist/src/interfaces.js +2 -0
  127. package/dist/src/interfaces.js.map +1 -0
  128. package/dist/src/metrics.d.ts +44 -0
  129. package/dist/src/metrics.d.ts.map +1 -0
  130. package/dist/src/metrics.js +78 -0
  131. package/dist/src/metrics.js.map +1 -0
  132. package/dist/src/otel/durable-metrics.d.ts +42 -0
  133. package/dist/src/otel/durable-metrics.d.ts.map +1 -0
  134. package/dist/src/otel/durable-metrics.js +75 -0
  135. package/dist/src/otel/durable-metrics.js.map +1 -0
  136. package/dist/src/otel/durable-otel.d.ts +20 -0
  137. package/dist/src/otel/durable-otel.d.ts.map +1 -0
  138. package/dist/src/otel/durable-otel.js +74 -0
  139. package/dist/src/otel/durable-otel.js.map +1 -0
  140. package/dist/src/otel/index.d.ts +4 -0
  141. package/dist/src/otel/index.d.ts.map +1 -0
  142. package/dist/src/otel/index.js +4 -0
  143. package/dist/src/otel/index.js.map +1 -0
  144. package/dist/src/otel/traceparent.d.ts +17 -0
  145. package/dist/src/otel/traceparent.d.ts.map +1 -0
  146. package/dist/src/otel/traceparent.js +21 -0
  147. package/dist/src/otel/traceparent.js.map +1 -0
  148. package/dist/src/pollers.d.ts +33 -0
  149. package/dist/src/pollers.d.ts.map +1 -0
  150. package/dist/src/pollers.js +76 -0
  151. package/dist/src/pollers.js.map +1 -0
  152. package/dist/src/protocol.d.ts +17 -0
  153. package/dist/src/protocol.d.ts.map +1 -0
  154. package/dist/src/protocol.js +47 -0
  155. package/dist/src/protocol.js.map +1 -0
  156. package/dist/src/queue.d.ts +113 -0
  157. package/dist/src/queue.d.ts.map +1 -0
  158. package/dist/src/queue.js +128 -0
  159. package/dist/src/queue.js.map +1 -0
  160. package/dist/src/remote-step-factory.d.ts +15 -0
  161. package/dist/src/remote-step-factory.d.ts.map +1 -0
  162. package/dist/src/remote-step-factory.js +12 -0
  163. package/dist/src/remote-step-factory.js.map +1 -0
  164. package/dist/src/remote-workflow-executor.d.ts +24 -0
  165. package/dist/src/remote-workflow-executor.d.ts.map +1 -0
  166. package/dist/src/remote-workflow-executor.js +70 -0
  167. package/dist/src/remote-workflow-executor.js.map +1 -0
  168. package/dist/src/scheduler.d.ts +67 -0
  169. package/dist/src/scheduler.d.ts.map +1 -0
  170. package/dist/src/scheduler.js +111 -0
  171. package/dist/src/scheduler.js.map +1 -0
  172. package/dist/src/search-attributes.d.ts +48 -0
  173. package/dist/src/search-attributes.d.ts.map +1 -0
  174. package/dist/src/search-attributes.js +98 -0
  175. package/dist/src/search-attributes.js.map +1 -0
  176. package/dist/src/singleton-gate.d.ts +44 -0
  177. package/dist/src/singleton-gate.d.ts.map +1 -0
  178. package/dist/src/singleton-gate.js +90 -0
  179. package/dist/src/singleton-gate.js.map +1 -0
  180. package/dist/src/step-logger.d.ts +9 -0
  181. package/dist/src/step-logger.d.ts.map +1 -0
  182. package/dist/src/step-logger.js +89 -0
  183. package/dist/src/step-logger.js.map +1 -0
  184. package/dist/src/stores/factory.d.ts +42 -0
  185. package/dist/src/stores/factory.d.ts.map +1 -0
  186. package/dist/src/stores/factory.js +29 -0
  187. package/dist/src/stores/factory.js.map +1 -0
  188. package/dist/src/stores/lucid-helpers.d.ts +11 -0
  189. package/dist/src/stores/lucid-helpers.d.ts.map +1 -0
  190. package/dist/src/stores/lucid-helpers.js +38 -0
  191. package/dist/src/stores/lucid-helpers.js.map +1 -0
  192. package/dist/src/stores/lucid-mappers.d.ts +58 -0
  193. package/dist/src/stores/lucid-mappers.d.ts.map +1 -0
  194. package/dist/src/stores/lucid-mappers.js +174 -0
  195. package/dist/src/stores/lucid-mappers.js.map +1 -0
  196. package/dist/src/stores/lucid-schema.d.ts +28 -0
  197. package/dist/src/stores/lucid-schema.d.ts.map +1 -0
  198. package/dist/src/stores/lucid-schema.js +115 -0
  199. package/dist/src/stores/lucid-schema.js.map +1 -0
  200. package/dist/src/stores/lucid.d.ts +69 -0
  201. package/dist/src/stores/lucid.d.ts.map +1 -0
  202. package/dist/src/stores/lucid.js +342 -0
  203. package/dist/src/stores/lucid.js.map +1 -0
  204. package/dist/src/telescope/dashboard.d.ts +15 -0
  205. package/dist/src/telescope/dashboard.d.ts.map +1 -0
  206. package/dist/src/telescope/dashboard.js +113 -0
  207. package/dist/src/telescope/dashboard.js.map +1 -0
  208. package/dist/src/telescope/data-providers.d.ts +20 -0
  209. package/dist/src/telescope/data-providers.d.ts.map +1 -0
  210. package/dist/src/telescope/data-providers.js +322 -0
  211. package/dist/src/telescope/data-providers.js.map +1 -0
  212. package/dist/src/telescope/extension.d.ts +22 -0
  213. package/dist/src/telescope/extension.d.ts.map +1 -0
  214. package/dist/src/telescope/extension.js +38 -0
  215. package/dist/src/telescope/extension.js.map +1 -0
  216. package/dist/src/telescope/index.d.ts +5 -0
  217. package/dist/src/telescope/index.d.ts.map +1 -0
  218. package/dist/src/telescope/index.js +4 -0
  219. package/dist/src/telescope/index.js.map +1 -0
  220. package/dist/src/telescope/telescope-sdk.d.ts +123 -0
  221. package/dist/src/telescope/telescope-sdk.d.ts.map +1 -0
  222. package/dist/src/telescope/telescope-sdk.js +12 -0
  223. package/dist/src/telescope/telescope-sdk.js.map +1 -0
  224. package/dist/src/test-helpers.d.ts +11 -0
  225. package/dist/src/test-helpers.d.ts.map +1 -0
  226. package/dist/src/test-helpers.js +12 -0
  227. package/dist/src/test-helpers.js.map +1 -0
  228. package/dist/src/testing/in-memory-state-store.d.ts +64 -0
  229. package/dist/src/testing/in-memory-state-store.d.ts.map +1 -0
  230. package/dist/src/testing/in-memory-state-store.js +259 -0
  231. package/dist/src/testing/in-memory-state-store.js.map +1 -0
  232. package/dist/src/testing/in-memory-transport.d.ts +25 -0
  233. package/dist/src/testing/in-memory-transport.d.ts.map +1 -0
  234. package/dist/src/testing/in-memory-transport.js +49 -0
  235. package/dist/src/testing/in-memory-transport.js.map +1 -0
  236. package/dist/src/testing-kit/admission-backend-conformance.d.ts +18 -0
  237. package/dist/src/testing-kit/admission-backend-conformance.d.ts.map +1 -0
  238. package/dist/src/testing-kit/admission-backend-conformance.js +81 -0
  239. package/dist/src/testing-kit/admission-backend-conformance.js.map +1 -0
  240. package/dist/src/testing-kit/assertions.d.ts +10 -0
  241. package/dist/src/testing-kit/assertions.d.ts.map +1 -0
  242. package/dist/src/testing-kit/assertions.js +41 -0
  243. package/dist/src/testing-kit/assertions.js.map +1 -0
  244. package/dist/src/testing-kit/harness.d.ts +28 -0
  245. package/dist/src/testing-kit/harness.d.ts.map +1 -0
  246. package/dist/src/testing-kit/harness.js +42 -0
  247. package/dist/src/testing-kit/harness.js.map +1 -0
  248. package/dist/src/testing-kit/index.d.ts +8 -0
  249. package/dist/src/testing-kit/index.d.ts.map +1 -0
  250. package/dist/src/testing-kit/index.js +8 -0
  251. package/dist/src/testing-kit/index.js.map +1 -0
  252. package/dist/src/testing-kit/replay.d.ts +22 -0
  253. package/dist/src/testing-kit/replay.d.ts.map +1 -0
  254. package/dist/src/testing-kit/replay.js +38 -0
  255. package/dist/src/testing-kit/replay.js.map +1 -0
  256. package/dist/src/testing-kit/state-store-conformance.d.ts +51 -0
  257. package/dist/src/testing-kit/state-store-conformance.d.ts.map +1 -0
  258. package/dist/src/testing-kit/state-store-conformance.js +430 -0
  259. package/dist/src/testing-kit/state-store-conformance.js.map +1 -0
  260. package/dist/src/testing-kit/steps.d.ts +5 -0
  261. package/dist/src/testing-kit/steps.d.ts.map +1 -0
  262. package/dist/src/testing-kit/steps.js +15 -0
  263. package/dist/src/testing-kit/steps.js.map +1 -0
  264. package/dist/src/testing-kit/transport-conformance.d.ts +16 -0
  265. package/dist/src/testing-kit/transport-conformance.d.ts.map +1 -0
  266. package/dist/src/testing-kit/transport-conformance.js +64 -0
  267. package/dist/src/testing-kit/transport-conformance.js.map +1 -0
  268. package/dist/src/tokens.d.ts +18 -0
  269. package/dist/src/tokens.d.ts.map +1 -0
  270. package/dist/src/tokens.js +18 -0
  271. package/dist/src/tokens.js.map +1 -0
  272. package/dist/src/transport-pool.d.ts +30 -0
  273. package/dist/src/transport-pool.d.ts.map +1 -0
  274. package/dist/src/transport-pool.js +82 -0
  275. package/dist/src/transport-pool.js.map +1 -0
  276. package/dist/src/transports/db-helpers.d.ts +10 -0
  277. package/dist/src/transports/db-helpers.d.ts.map +1 -0
  278. package/dist/src/transports/db-helpers.js +33 -0
  279. package/dist/src/transports/db-helpers.js.map +1 -0
  280. package/dist/src/transports/db-schema.d.ts +28 -0
  281. package/dist/src/transports/db-schema.d.ts.map +1 -0
  282. package/dist/src/transports/db-schema.js +98 -0
  283. package/dist/src/transports/db-schema.js.map +1 -0
  284. package/dist/src/transports/db.d.ts +81 -0
  285. package/dist/src/transports/db.d.ts.map +1 -0
  286. package/dist/src/transports/db.js +340 -0
  287. package/dist/src/transports/db.js.map +1 -0
  288. package/dist/src/transports/factory.d.ts +79 -0
  289. package/dist/src/transports/factory.d.ts.map +1 -0
  290. package/dist/src/transports/factory.js +57 -0
  291. package/dist/src/transports/factory.js.map +1 -0
  292. package/dist/src/transports/queue-mock-adapter.d.ts +39 -0
  293. package/dist/src/transports/queue-mock-adapter.d.ts.map +1 -0
  294. package/dist/src/transports/queue-mock-adapter.js +86 -0
  295. package/dist/src/transports/queue-mock-adapter.js.map +1 -0
  296. package/dist/src/transports/queue.d.ts +84 -0
  297. package/dist/src/transports/queue.d.ts.map +1 -0
  298. package/dist/src/transports/queue.js +210 -0
  299. package/dist/src/transports/queue.js.map +1 -0
  300. package/dist/src/workflow-ctx.d.ts +64 -0
  301. package/dist/src/workflow-ctx.d.ts.map +1 -0
  302. package/dist/src/workflow-ctx.js +457 -0
  303. package/dist/src/workflow-ctx.js.map +1 -0
  304. package/dist/src/workflow-ref.d.ts +34 -0
  305. package/dist/src/workflow-ref.d.ts.map +1 -0
  306. package/dist/src/workflow-ref.js +28 -0
  307. package/dist/src/workflow-ref.js.map +1 -0
  308. package/dist/stubs/config/durable.stub +49 -0
  309. package/dist/stubs/config/durable_dashboard.stub +17 -0
  310. package/dist/stubs/database/migrations/create_durable_tables.stub +87 -0
  311. package/dist/stubs/database/migrations/create_durable_transport_tables.stub +75 -0
  312. package/dist/stubs/main.d.ts +3 -0
  313. package/dist/stubs/main.d.ts.map +1 -0
  314. package/dist/stubs/main.js +4 -0
  315. package/dist/stubs/main.js.map +1 -0
  316. package/package.json +154 -0
@@ -0,0 +1,909 @@
1
+ import type { z } from 'zod';
2
+ import type { WorkflowClass, WorkflowInputOf, WorkflowOutputOf } from './workflow-ref.js';
3
+ /**
4
+ * Core type contracts for nestjs-durable.
5
+ *
6
+ * These are intentionally framework-agnostic: `@dudousxd/nestjs-durable-core` knows only
7
+ * these interfaces, never a concrete transport, store or ORM. Adapters implement them.
8
+ */
9
+ export type RunStatus =
10
+ /** Created + enqueued by `start`, not yet picked up — a worker will lease and execute it. */
11
+ 'pending' | 'running' | 'suspended' | 'completed' | 'failed' | 'cancelled'
12
+ /** Dead-letter: recovery gave up after `maxRecoveryAttempts` (a poison pill). Terminal; inspect it. */
13
+ | 'dead';
14
+ /** One execution of a workflow. The unit of durability and the unit shown in the dashboard. */
15
+ export interface WorkflowRun {
16
+ id: string;
17
+ /** Registered workflow name, e.g. `checkout`. */
18
+ workflow: string;
19
+ /** Code version at start; old runs must resume on the version they began on. */
20
+ workflowVersion: string;
21
+ status: RunStatus;
22
+ /** Serialized workflow input (the args the run was started with). */
23
+ input: unknown;
24
+ /** Serialized workflow output, once `completed`. */
25
+ output?: unknown;
26
+ /** Structured error, once `failed`. */
27
+ error?: StepError | undefined;
28
+ /** When `suspended` on a durable sleep: epoch ms at which the run becomes due to resume. */
29
+ wakeAt?: number | undefined;
30
+ /** Recovery lease owner (engine instance id) while a run is being resumed. */
31
+ lockedBy?: string | undefined;
32
+ /** Recovery lease expiry (epoch ms); another instance may take over once it passes. */
33
+ lockedUntil?: number | undefined;
34
+ /** How many times crash-recovery has picked this run up — caps poison pills (see maxRecoveryAttempts). */
35
+ recoveryAttempts?: number | undefined;
36
+ /** Searchable labels: the workflow's static `@Workflow({ tags })` merged with the run's start-time tags. */
37
+ tags?: string[] | undefined;
38
+ /** Typed, queryable run data (e.g. `{ amount: 200, tier: 'pro' }`) — see {@link RunQuery.attributes}. */
39
+ searchAttributes?: SearchAttributes | undefined;
40
+ /**
41
+ * Dispatch priority for a REMOTE run (one advanced by a {@link WorkflowExecutor} over a broker):
42
+ * carried onto each {@link WorkflowTask} so an urgent child workflow can jump ahead of enqueued
43
+ * lower-priority ones at the worker. Higher wins; absent = unprioritised. Best-effort ordering, not
44
+ * durable state required for correctness — a transport without priority support ignores it.
45
+ */
46
+ priority?: number | undefined;
47
+ createdAt: Date;
48
+ updatedAt: Date;
49
+ }
50
+ export type StepKind = 'local' | 'remote' | 'sleep' | 'signal';
51
+ /**
52
+ * The recorded result of a single step at a deterministic logical position (`seq`).
53
+ * On replay, a `completed` checkpoint means the step is NOT re-executed — its `output` is
54
+ * returned. A non-terminal checkpoint (`pending`/`running`) does not short-circuit: the step is
55
+ * re-awaited (remote) or re-run (local).
56
+ */
57
+ export interface StepCheckpoint {
58
+ runId: string;
59
+ /** Deterministic logical position of the step within the run. */
60
+ seq: number;
61
+ /** Registered step name (matches the remote handler name for remote steps). */
62
+ name: string;
63
+ kind: StepKind;
64
+ /** Stable id passed to remote workers so they can dedupe a re-delivered task. */
65
+ stepId: string;
66
+ /**
67
+ * `pending` = a remote step dispatched and awaiting its worker result (the run is durably
68
+ * suspended, not held in memory); it becomes `completed`/`failed` when the result arrives.
69
+ * `running` = a local step whose body is executing in-process right now (see `trackStepStart`);
70
+ * it's overwritten by `completed`/`failed` when the body settles, and on a crash mid-body it
71
+ * simply re-runs on replay (only `completed` short-circuits).
72
+ */
73
+ status: 'pending' | 'running' | 'completed' | 'failed';
74
+ /** What the step was called with — the `ctx.call` args for a remote step (a local step has none). */
75
+ input?: unknown;
76
+ output?: unknown;
77
+ error?: StepError | undefined;
78
+ attempts: number;
79
+ /** For remote steps: which worker group ran it. */
80
+ workerGroup?: string | undefined;
81
+ /** Structured events/logs the step emitted (sub-step outcomes, debug/error lines). */
82
+ events?: StepEvent[] | undefined;
83
+ /** For sleep steps: epoch ms the sleep elapses at. */
84
+ wakeAt?: number | undefined;
85
+ /**
86
+ * When the step entered the system: for a remote step, when the engine dispatched it to the
87
+ * transport; for a local step, when it began. Queue-wait time = `startedAt − enqueuedAt`.
88
+ */
89
+ enqueuedAt: Date;
90
+ /** When processing actually began: worker pickup for a remote step, execution start for a local one. */
91
+ startedAt: Date;
92
+ finishedAt: Date;
93
+ }
94
+ /**
95
+ * A structured event a step (or its worker) emits while running — a log line and/or a sub-step
96
+ * outcome. The dashboard renders these under the step, so you can see what happened inside a step
97
+ * that the workflow treats as one unit (e.g. which of N parallel sub-processes ok/failed/skipped).
98
+ */
99
+ export interface StepEvent {
100
+ /** Epoch ms. */
101
+ at: number;
102
+ level: 'debug' | 'info' | 'warn' | 'error';
103
+ message: string;
104
+ /** Stable run identity for a sub-process. Distinct invocations of the same `name` carry distinct
105
+ * ids, so their phases and log trails never collapse into one. Absent on events emitted by the
106
+ * legacy `sub()` path, which keys by `name` instead. */
107
+ subId?: string;
108
+ /** For a sub-step/sub-process within the step: its name. */
109
+ name?: string;
110
+ /** Open, consumer-defined grouping label for a sub-process (e.g. a handler/lane). The dashboard
111
+ * groups rows by it. The library never interprets it. */
112
+ group?: string;
113
+ /** For a sub-step: its terminal outcome (closed enum — drives colour + aggregation). */
114
+ status?: 'ok' | 'failed' | 'skipped';
115
+ /** Open, consumer-defined intermediate phase label for a sub-process transition. Carries no
116
+ * terminal `status`; the library timestamps and orders it but never interprets it. */
117
+ phase?: string;
118
+ /** For a log line emitted *inside* a sub-process: that owning sub-process's name, so the dashboard
119
+ * can group a step's log trail under each sub-process instead of one flat list. Set on logs (no
120
+ * `status`); a worker stamps it from the sub-process it's running.
121
+ * @deprecated Superseded by `subId` for run-distinct grouping; kept so existing workers/runs render. */
122
+ process?: string;
123
+ /** Optional structured payload. `data.durationMs` (number) overrides the derived duration. */
124
+ data?: unknown;
125
+ }
126
+ /**
127
+ * Handed to a local step's body (`ctx.step(name, (log) => …)`) so it can record what happened
128
+ * inside the step — debug/info/warn/error lines and per-sub-process outcomes. The events are
129
+ * checkpointed with the step and rendered under it in the dashboard. The remote/cross-language
130
+ * counterpart is the worker attaching the same `StepEvent[]` to its `StepResult` (see the Python
131
+ * SDK's `StepContext`), so observability is symmetric regardless of where the step ran.
132
+ */
133
+ export interface StepLogger {
134
+ debug(message: string, data?: unknown): void;
135
+ info(message: string, data?: unknown): void;
136
+ warn(message: string, data?: unknown): void;
137
+ error(message: string, data?: unknown): void;
138
+ /** Record a sub-step / sub-process outcome (e.g. one of N parallel p-processes). */
139
+ sub(name: string, status: 'ok' | 'failed' | 'skipped', message?: string, data?: unknown): void;
140
+ /** Record a sub-process event. Typically pass `phase` for an intermediate transition (carrying no
141
+ * terminal status), or `status` for the terminal outcome — the type permits either; the engine
142
+ * interprets which is set. `id` is the run identity (distinct per invocation); `group` is an
143
+ * open grouping label. */
144
+ subEvent(e: {
145
+ id: string;
146
+ name: string;
147
+ group?: string | undefined;
148
+ phase?: string | undefined;
149
+ status?: 'ok' | 'failed' | 'skipped' | undefined;
150
+ message?: string | undefined;
151
+ data?: unknown;
152
+ }): void;
153
+ /**
154
+ * Ergonomic sub-process lifecycle: run `body`, timing it, and record a terminal `ok` with the
155
+ * measured `durationMs` on success — or `failed` (with the error message) if it throws, then
156
+ * re-throw. `sp.phase(label)` records an intermediate transition; `sp.skip(reason)` a terminal
157
+ * `skipped`. Logs emitted inside `body` are tagged to this sub-process so the dashboard groups
158
+ * them under it. Returns whatever `body` returns. The TS twin of the Python SDK's `sub_process`.
159
+ *
160
+ * ```ts
161
+ * const rows = await log.subProcess('fetch-data', async () => readEverything());
162
+ * await log.subProcess('export-file', () => upload(rows));
163
+ * ```
164
+ */
165
+ subProcess<T>(name: string, body: (sp: SubProcessHandle) => Promise<T> | T, opts?: {
166
+ group?: string;
167
+ id?: string;
168
+ }): Promise<T>;
169
+ }
170
+ /** The handle a {@link StepLogger.subProcess} body receives to mark phases / a non-`ok` outcome. */
171
+ export interface SubProcessHandle {
172
+ /** Record an intermediate phase transition (a consumer-defined label, no terminal status). */
173
+ phase(phase: string, data?: unknown): SubProcessHandle;
174
+ /** Record a terminal `skipped` outcome (e.g. nothing to do / validation failed). */
175
+ skip(reason?: string, data?: unknown): void;
176
+ /** Record a terminal `failed` outcome explicitly (the wrapper also does this if the body throws). */
177
+ fail(reason?: string, data?: unknown): void;
178
+ }
179
+ export interface StepError {
180
+ message: string;
181
+ /** Optional machine-readable code, e.g. `declined`, `timeout`. */
182
+ code?: string | undefined;
183
+ /** Whether the engine should treat this as retryable. */
184
+ retryable?: boolean | undefined;
185
+ stack?: string | undefined;
186
+ }
187
+ export interface StateStore {
188
+ /**
189
+ * Provision the tables/collections this store needs, idempotently. Called on boot when the
190
+ * module's `autoSchema` is on. Optional: stores that need no setup (in-memory) omit it.
191
+ */
192
+ ensureSchema?(): Promise<void>;
193
+ createRun(run: WorkflowRun): Promise<void>;
194
+ updateRun(runId: string, patch: Partial<WorkflowRun>): Promise<void>;
195
+ getRun(runId: string): Promise<WorkflowRun | null>;
196
+ /**
197
+ * Hard-delete a run and everything tied to it (its checkpoints, signal waiters, and search-attribute
198
+ * index rows). Idempotent: deleting an unknown run is a no-op. This removes exactly the one run — the
199
+ * engine's {@link WorkflowEngine.deleteRun} handles the child-subtree cascade.
200
+ */
201
+ deleteRun(runId: string): Promise<void>;
202
+ getCheckpoint(runId: string, seq: number): Promise<StepCheckpoint | null>;
203
+ /**
204
+ * Persist a checkpoint and advance the run atomically. Durable semantics depend on this
205
+ * being a single transaction; stores without transactions cannot give the strong guarantee.
206
+ */
207
+ saveCheckpoint(checkpoint: StepCheckpoint): Promise<void>;
208
+ /** Used by recovery on boot to find runs to resume (crashed, left `running`). */
209
+ listIncompleteRuns(): Promise<WorkflowRun[]>;
210
+ /** The oldest `pending` runs awaiting dispatch (FIFO, by `createdAt`), capped at `limit`. */
211
+ listPendingRuns(limit: number): Promise<WorkflowRun[]>;
212
+ /** Suspended runs whose durable timer is due (`wakeAt <= nowMs`), ready to resume. */
213
+ listDueTimers(nowMs: number): Promise<WorkflowRun[]>;
214
+ /**
215
+ * Atomically acquire the recovery lease on a run for `owner` until `leaseUntilMs`, but only if
216
+ * it is currently unlocked or its lease has expired (`<= nowMs`). Returns whether it was
217
+ * acquired — so concurrent engine instances never recover the same run twice.
218
+ */
219
+ tryLockRun(runId: string, owner: string, leaseUntilMs: number, nowMs: number): Promise<boolean>;
220
+ /** Release a run's recovery lease so another instance can pick it up (e.g. once it suspends). */
221
+ releaseRunLock(runId: string): Promise<void>;
222
+ /**
223
+ * Extend a run's lease to `leaseUntilMs`, but ONLY if `owner` still holds it — so a live worker
224
+ * heartbeating its long run keeps the lease, while a dead worker's lease still expires and gets
225
+ * reclaimed. Returns false if the lease was lost (taken over or released).
226
+ */
227
+ renewRunLock(runId: string, owner: string, leaseUntilMs: number): Promise<boolean>;
228
+ /** Record that a run is suspended waiting for an external signal `token`. */
229
+ putSignalWaiter(waiter: SignalWaiter): Promise<void>;
230
+ /** Atomically take (and remove) the run waiting on `token`, if any. */
231
+ takeSignalWaiter(token: string): Promise<SignalWaiter | null>;
232
+ /** List waiters whose `token` starts with `prefix` — used to fan out an event to its subscribers. */
233
+ listSignalWaiters(prefix: string): Promise<SignalWaiter[]>;
234
+ /**
235
+ * Buffer a signal whose waiter hasn't arrived yet, so the next `waitForSignal(token)` consumes it
236
+ * instead of it being lost (FIFO per token). Makes signals reliable regardless of timing and
237
+ * powers `signalWithStart`.
238
+ */
239
+ bufferSignal(token: string, payload: unknown): Promise<void>;
240
+ /** Take the oldest buffered signal for `token` (FIFO), or null if none is buffered. */
241
+ takeBufferedSignal(token: string): Promise<{
242
+ payload: unknown;
243
+ } | null>;
244
+ /**
245
+ * Run `work` in a SINGLE store transaction — giving it the store-native transaction handle (`raw`)
246
+ * for the caller's own DB writes plus a `saveCheckpoint` that commits IN THE SAME transaction, so a
247
+ * business write and the step's "done" checkpoint are atomic (exactly-once). Optional — only the SQL
248
+ * adapters implement it; `ctx.transaction` errors on a store without it.
249
+ */
250
+ transaction?<T>(work: (tx: StoreTransaction) => Promise<T>): Promise<T>;
251
+ listRuns(query: RunQuery): Promise<WorkflowRun[]>;
252
+ listCheckpoints(runId: string): Promise<StepCheckpoint[]>;
253
+ /**
254
+ * The LATEST checkpoint for `runId` whose `name` equals `name` exactly (highest `seq` wins), or
255
+ * `undefined` if none. A targeted read that avoids fetching + deserializing every checkpoint just to
256
+ * keep one match — the store does the filter (`WHERE name = … ORDER BY seq DESC LIMIT 1`). Preserves
257
+ * the "last in seq order wins" semantics the engine relies on for `getEvent` (a re-published key
258
+ * overwrites the prior value at a higher seq, so the highest-seq match is the current value).
259
+ *
260
+ * Optional: a store that omits it still works — the engine falls back to {@link listCheckpoints}
261
+ * plus an in-JS filter that produces the identical result.
262
+ */
263
+ getLatestCheckpointByName?(runId: string, name: string): Promise<StepCheckpoint | undefined>;
264
+ /**
265
+ * All checkpoints for `runId` whose `name` starts with ANY of `prefixes`, ordered by `seq` ascending
266
+ * (same order as {@link listCheckpoints}). A targeted read that avoids scanning every checkpoint just
267
+ * to keep the prefix matches — the store does the filter (`WHERE name LIKE 'prefix%' …`). Used by the
268
+ * run-tree to find a parent's child edges (`signal:child:` / `spawn:` checkpoints) without loading the
269
+ * whole history. An empty `prefixes` array matches nothing.
270
+ *
271
+ * Optional: a store that omits it still works — the engine falls back to {@link listCheckpoints}
272
+ * plus an in-JS prefix scan that produces the identical result.
273
+ */
274
+ listCheckpointsByNamePrefix?(runId: string, prefixes: string[]): Promise<StepCheckpoint[]>;
275
+ }
276
+ /** Typed, queryable per-run data — exact values for `eq`/`ne`, numbers/strings for range ops. */
277
+ export type SearchAttributes = Record<string, string | number | boolean>;
278
+ export type AttributeOp = 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte';
279
+ /** One predicate over a run's {@link SearchAttributes}; a {@link RunQuery} ANDs them all. */
280
+ export interface AttributeFilter {
281
+ key: string;
282
+ op: AttributeOp;
283
+ value: string | number | boolean;
284
+ }
285
+ export interface RunQuery {
286
+ workflow?: string | undefined;
287
+ status?: RunStatus | undefined;
288
+ /**
289
+ * Match any of these statuses (a `status IN (...)` filter). ORed together, and ANDed with the other
290
+ * predicates. Use this instead of issuing one {@link listRuns} call per status — e.g. the singleton
291
+ * admission gate counts `running` + `suspended` in-flight runs in a single scan. If both `status` and
292
+ * `statuses` are set, both must hold (the single `status` further narrows the set). Empty array =
293
+ * matches nothing.
294
+ */
295
+ statuses?: RunStatus[] | undefined;
296
+ /** Only runs carrying this tag (exact match against {@link WorkflowRun.tags}). */
297
+ tag?: string | undefined;
298
+ /**
299
+ * Typed/range predicates over {@link WorkflowRun.searchAttributes}, ANDed together (e.g. `amount`
300
+ * >= 200 and `tier` = 'pro'). Applied in-process after the coarse filters, so pair with
301
+ * `workflow`/`status`/`tag` to bound the scan on large stores.
302
+ */
303
+ attributes?: AttributeFilter[] | undefined;
304
+ limit?: number | undefined;
305
+ offset?: number | undefined;
306
+ }
307
+ /** The transaction handle `StateStore.transaction` hands to its work callback. */
308
+ export interface StoreTransaction {
309
+ /** The store-native transaction handle (TypeORM `EntityManager`, Prisma tx client, MikroORM `EntityManager`,
310
+ * Drizzle tx) — do your business DB writes on THIS so they commit atomically with the checkpoint. */
311
+ readonly raw: unknown;
312
+ /** Persist the step checkpoint inside this transaction. */
313
+ saveCheckpoint(checkpoint: StepCheckpoint): Promise<void>;
314
+ }
315
+ /** Binds an external signal `token` to the suspended run/step position waiting for it. */
316
+ export interface SignalWaiter {
317
+ token: string;
318
+ runId: string;
319
+ seq: number;
320
+ }
321
+ /** A unit of work dispatched to a remote worker. This is the documented wire payload. */
322
+ export interface RemoteTask {
323
+ runId: string;
324
+ seq: number;
325
+ /** Handler name the worker registered, e.g. `payments.charge-card`. */
326
+ name: string;
327
+ stepId: string;
328
+ /** Worker group expected to handle this task. */
329
+ group: string;
330
+ input: unknown;
331
+ /** W3C traceparent so the worker can continue the distributed trace. */
332
+ traceparent?: string | undefined;
333
+ /**
334
+ * Opaque context carrier (tenant / user / correlation ids) the worker re-exposes to the step
335
+ * handler, for cross-process propagation alongside the {@link traceparent}. The engine treats it
336
+ * as a pass-through object and never inspects its shape — the producer (e.g. `@dudousxd/nestjs-context`)
337
+ * owns the keys. Absent when no `context` provider is configured.
338
+ */
339
+ context?: Record<string, unknown> | undefined;
340
+ /**
341
+ * Id of the transport this task was dispatched on (when the engine runs a pool — see
342
+ * {@link NamedTransport}). A worker that consumes several transports replies via the matching one,
343
+ * so failover is symmetric without the worker choosing a transport. Absent for a single transport.
344
+ */
345
+ transport?: string | undefined;
346
+ /**
347
+ * Admission priority carried through to the broker (the queue job `priority`) so a transport that
348
+ * supports priority ordering lets an urgent task jump ahead of already-enqueued lower-priority
349
+ * ones at the worker. Mirrors the per-call `priority` from `ctx.call(..., { priority })`. Higher
350
+ * wins; absent means default/unprioritised. Transports without priority support ignore it.
351
+ */
352
+ priority?: number | undefined;
353
+ attempt: number;
354
+ }
355
+ export interface StepResult {
356
+ runId: string;
357
+ seq: number;
358
+ stepId: string;
359
+ status: 'completed' | 'failed';
360
+ output?: unknown;
361
+ error?: StepError | undefined;
362
+ /** Epoch ms when the worker began processing — lets the engine report queue-wait time. */
363
+ startedAt?: number | undefined;
364
+ /** Structured events the worker emitted while running the step (sub-step outcomes, logs). */
365
+ events?: StepEvent[] | undefined;
366
+ }
367
+ export interface Heartbeat {
368
+ runId: string;
369
+ seq: number;
370
+ stepId: string;
371
+ group: string;
372
+ }
373
+ /** engine → workflow worker: advance this run one turn by replaying the function against `history`. */
374
+ export interface WorkflowTask {
375
+ /** Dedupe id for this turn (a re-delivered task must be idempotent). */
376
+ taskId: string;
377
+ runId: string;
378
+ /** Registered workflow name + the version the run started on — replay must use that version. */
379
+ workflow: string;
380
+ workflowVersion: string;
381
+ input: unknown;
382
+ /** Completed durable ops so far, ordered by seq — what the worker replays its results from. */
383
+ history: HistoryEvent[];
384
+ /** Signals delivered to the run but not yet consumed, so `wait_signal` resolves on replay. */
385
+ pendingSignals?: Array<{
386
+ seq: number;
387
+ signal: string;
388
+ payload: unknown;
389
+ }>;
390
+ group: string;
391
+ /** Id of the transport this task was dispatched on (pool failover) — see {@link NamedTransport}. */
392
+ transport?: string;
393
+ traceparent?: string;
394
+ /**
395
+ * Admission priority carried to the broker (the queue job `priority`) so an urgent child workflow
396
+ * can jump ahead of already-enqueued lower-priority ones at the worker. Higher wins; absent means
397
+ * default/unprioritised. Transports without priority support ignore it.
398
+ */
399
+ priority?: number | undefined;
400
+ attempt: number;
401
+ }
402
+ /** One resolved durable op in a run's history — a superset of a completed {@link StepCheckpoint}. */
403
+ export interface HistoryEvent {
404
+ seq: number;
405
+ kind: 'step' | 'call' | 'timer' | 'signal' | 'child';
406
+ name?: string | undefined;
407
+ /** Resolved value: a step/call output, a child run's output, a signal payload. */
408
+ output?: unknown;
409
+ /** Set when the op resolved to a failure (e.g. a failed remote step the workflow may catch). */
410
+ error?: StepError | undefined;
411
+ }
412
+ /** A decision the workflow function produced at a `seq` that was not yet in history. */
413
+ export type WorkflowCommand =
414
+ /** `ctx.call(remoteStep, input)` — dispatch a remote step and await it. */
415
+ {
416
+ kind: 'call';
417
+ seq: number;
418
+ name: string;
419
+ group: string;
420
+ input: unknown;
421
+ }
422
+ /** `ctx.step(name, body)` — a LOCAL step the worker already ran this turn; the engine persists its
423
+ * result so replay returns it instead of re-running (durability for side-effectful work).
424
+ * `startedAt`/`finishedAt` (epoch ms) carry the step's real wall-clock window so the dashboard
425
+ * shows a true duration instead of 0ms, and `events` carry the sub-process/log trail the step
426
+ * emitted (so each handler's p-processes show under it). All optional for back-compat. */
427
+ | {
428
+ kind: 'recordStep';
429
+ seq: number;
430
+ name: string;
431
+ output?: unknown;
432
+ error?: StepError;
433
+ startedAt?: number;
434
+ finishedAt?: number;
435
+ events?: StepEvent[];
436
+ }
437
+ /** `ctx.sleep(ms)` — a durable timer of `ms` duration. The engine computes the absolute deadline
438
+ * (now + ms) when it applies the command, so the worker never reads the clock (determinism). */
439
+ | {
440
+ kind: 'sleep';
441
+ seq: number;
442
+ ms: number;
443
+ }
444
+ /** `ctx.wait_signal(name)` — block until a signal `name` is delivered to the run. */
445
+ | {
446
+ kind: 'waitSignal';
447
+ seq: number;
448
+ signal: string;
449
+ }
450
+ /** `ctx.start_child(workflow, input)` — start a child run with its own lifecycle. */
451
+ | {
452
+ kind: 'startChild';
453
+ seq: number;
454
+ workflow: string;
455
+ input: unknown;
456
+ };
457
+ /** workflow worker → engine: the result of replaying one turn of a remote workflow. */
458
+ export interface WorkflowDecision {
459
+ taskId: string;
460
+ runId: string;
461
+ /** `continue` = produced `commands` and is blocked on an await; otherwise the run settles.
462
+ * `cancelled` = the worker bailed at an op boundary because the run was cancelled mid-turn. */
463
+ status: 'continue' | 'completed' | 'failed' | 'cancelled';
464
+ /** New durable ops the replay produced this turn (status === 'continue'), ordered by seq. */
465
+ commands: WorkflowCommand[];
466
+ /** Final workflow output (status === 'completed'). */
467
+ output?: unknown;
468
+ /** Terminal error (status === 'failed'). */
469
+ error?: StepError;
470
+ }
471
+ /**
472
+ * workflow worker → engine: a LOCAL step's lifecycle, streamed AS IT HAPPENS (not batched into the
473
+ * turn's final {@link WorkflowDecision}). A Python `@workflow` runs its `ctx.step`s inline over one
474
+ * turn that can last minutes; without this the engine learns of the steps only when the turn ends,
475
+ * so the dashboard shows nothing mid-run. The worker emits `running` when a step's body starts and
476
+ * `completed`/`failed` when it settles; the engine checkpoints each immediately, so a step appears
477
+ * in-flight and then resolves live. The turn's final `recordStep` command re-persists the same
478
+ * checkpoint idempotently (replay history), so this is purely additive observability.
479
+ */
480
+ export interface WorkflowStepEvent {
481
+ runId: string;
482
+ seq: number;
483
+ name: string;
484
+ phase: 'running' | 'completed' | 'failed';
485
+ /** Epoch ms the step body began (all phases) and settled (`completed`/`failed`). */
486
+ startedAt: number;
487
+ finishedAt?: number;
488
+ /** The replayed result / failure for the settled phases. */
489
+ output?: unknown;
490
+ error?: StepError;
491
+ /** Sub-process + log trail the step emitted so far (the handler's p-processes). */
492
+ events?: StepEvent[];
493
+ }
494
+ /**
495
+ * Advances a workflow run one turn. The engine has one per workflow: the default {@link InProcess}
496
+ * one runs a registered TS function with the in-process replay machinery; a remote one dispatches a
497
+ * {@link WorkflowTask} to a worker (Python) and awaits its {@link WorkflowDecision}. Either way the
498
+ * engine applies the returned decision — so recovery, timers, singleton and dead-letter stay engine
499
+ * concerns, identical for in-process and remote workflows.
500
+ */
501
+ export interface WorkflowExecutor {
502
+ advance(run: WorkflowRun, history: HistoryEvent[], pendingSignals?: WorkflowTask['pendingSignals']): Promise<WorkflowDecision>;
503
+ }
504
+ /**
505
+ * A transport in an ordered pool, identified by `id`. The engine dispatches on the first by default
506
+ * and fails over to the next on a dispatch error; a step can pin one via `ctx.call(…, { transport })`.
507
+ * The chosen `id` is stamped on the {@link RemoteTask} so a worker replies through the matching one.
508
+ */
509
+ export interface NamedTransport {
510
+ id: string;
511
+ transport: Transport;
512
+ }
513
+ /**
514
+ * Decides where a freshly-`start`ed run executes. `start` creates the run as `pending` and hands its
515
+ * id here instead of running the body inline — so the API/caller never blocks on workflow execution.
516
+ * The default in-process dispatcher runs it on this instance (a microtask); a broker-backed one
517
+ * enqueues the id for a worker pool to consume (`engine.runOne(runId)`); a no-op one leaves it
518
+ * `pending` in the store for a worker's `runPending` poll to pick up (DB-only, caller-doesn't-execute).
519
+ */
520
+ export interface RunDispatcher {
521
+ dispatch(runId: string): void | Promise<void>;
522
+ }
523
+ export interface Transport {
524
+ /** engine → worker */
525
+ dispatch(task: RemoteTask): Promise<void>;
526
+ /** worker → engine: a step finished (ok or error). */
527
+ onResult(handler: (result: StepResult) => Promise<void>): void;
528
+ /** worker → engine: liveness signal for an in-flight long step. */
529
+ onHeartbeat(handler: (beat: Heartbeat) => Promise<void>): void;
530
+ /** Release the transport's resources (broker workers, queues, connections) for a clean shutdown.
531
+ * Optional — an in-process transport has nothing to close. Called on `onApplicationShutdown`
532
+ * after the engine drains, so a deploy hands off instead of leaving the broker to time out. */
533
+ close?(): Promise<void>;
534
+ /** engine → workflow worker: dispatch a {@link WorkflowTask} (the polyglot-workflow path). Optional
535
+ * — only transports that carry workflow tasks (BullMQ) implement it; the {@link RemoteWorkflowExecutor}
536
+ * uses it + {@link onDecision} to advance a remote workflow over the broker. */
537
+ dispatchWorkflowTask?(task: WorkflowTask): Promise<void>;
538
+ /** workflow worker → engine: a replayed turn's {@link WorkflowDecision}. Pair with dispatchWorkflowTask. */
539
+ onDecision?(handler: (decision: WorkflowDecision) => Promise<void>): void;
540
+ /** workflow worker → engine: a LOCAL step's {@link WorkflowStepEvent}, streamed mid-turn so the
541
+ * engine can checkpoint it live. Point-to-point (a single engine instance consumes each event and
542
+ * persists it once — no cross-pod duplicate writes). Optional; only broker transports carry it. */
543
+ dispatchStepEvent?(event: WorkflowStepEvent): Promise<void>;
544
+ /** engine ← workflow worker: consume streamed {@link WorkflowStepEvent}s. Pair with dispatchStepEvent. */
545
+ onStepEvent?(handler: (event: WorkflowStepEvent) => Promise<void>): void;
546
+ /** Worker-health for a group: queue backlog + live worker heartbeats. Optional — only broker
547
+ * transports (BullMQ) that can introspect the task queue and the worker-heartbeat keys implement
548
+ * it. The engine aggregates this across its groups in {@link WorkflowEngine.workerHealth}. */
549
+ groupHealth?(group: string): Promise<GroupHealth>;
550
+ /** Distinct groups that currently have a live worker heartbeat — discovered from the heartbeat
551
+ * keyspace, so a group with workers but no engine-side registration (e.g. a local-step group)
552
+ * still surfaces. Pairs with {@link groupHealth}. */
553
+ listWorkerGroups?(): Promise<string[]>;
554
+ }
555
+ /** One worker's liveness record — a TTL'd heartbeat a worker refreshes while it's consuming. Its
556
+ * ABSENCE (the key expired) is the signal: a worker that died or stalled stops refreshing. */
557
+ export interface WorkerHeartbeat {
558
+ /** The worker group this instance serves (e.g. `pipeline`, `processing-workflows`). */
559
+ group: string;
560
+ /** Stable per-process id (host + pid), so N replicas of a group each show as a distinct worker. */
561
+ instanceId: string;
562
+ /** Epoch ms of the worker's most recent heartbeat. */
563
+ lastBeatAt: number;
564
+ }
565
+ /** Per-group worker-health snapshot: how much work is queued vs. how many workers are alive to do it.
566
+ * The actionable alert state is `depth > 0 && liveWorkers.length === 0` — work piling up with no
567
+ * consumer (exactly the failure where a worker is "alive but not consuming"). */
568
+ export interface GroupHealth {
569
+ group: string;
570
+ /** Outstanding jobs in the group's task queue (waiting + active + delayed + prioritized). */
571
+ depth: number;
572
+ /** Workers with a non-expired heartbeat for this group. */
573
+ liveWorkers: WorkerHeartbeat[];
574
+ }
575
+ /**
576
+ * The **control plane** — a broadcast pub/sub across ALL engine instances (every pod), separate
577
+ * from the {@link Transport}'s point-to-point work queues (`dispatch`/`onResult`). It carries what
578
+ * every instance may need regardless of who runs a given run: lifecycle events (so a dashboard-only
579
+ * pod can live-tail a run executing on a worker pod) and cancellation (so the pod actually running a
580
+ * run learns it was cancelled elsewhere). In-process implementations broadcast locally; a
581
+ * cross-process one (BullMQ) fans out over its broker (Redis pub/sub). Give the engine a
582
+ * `controlPlane` to enable cross-instance events/cancellation; omit it and the engine is local-only.
583
+ * A transport that can broadcast may implement this too and be passed as both.
584
+ */
585
+ export interface ControlPlane {
586
+ publishControl(msg: ControlMessage): Promise<void>;
587
+ onControl(handler: (msg: ControlMessage) => void): void;
588
+ }
589
+ /** A message on the {@link ControlPlane}. `from` is the originating engine's `instanceId`, so a
590
+ * broker that echoes a publish back to its own subscriber (e.g. Redis pub/sub) can be deduped by
591
+ * the originator. */
592
+ export type ControlMessage = {
593
+ from?: string;
594
+ } & ({
595
+ kind: 'event';
596
+ event: EngineEvent;
597
+ } | {
598
+ kind: 'cancel';
599
+ runId: string;
600
+ } | {
601
+ kind: 'enqueued';
602
+ runId: string;
603
+ });
604
+ export type BackoffStrategy = 'fixed' | 'exp';
605
+ /**
606
+ * Options for `ctx.child` / `ctx.startChild`. A bare string passed instead is shorthand for
607
+ * `{ childId }`, so the existing `ctx.child(ref, input, 'my-id')` form keeps working.
608
+ */
609
+ export interface ChildCallOptions {
610
+ /** Deterministic child run id; defaults to one derived from the parent run id + call position. */
611
+ childId?: string | undefined;
612
+ /**
613
+ * Dispatch priority for a REMOTE child workflow — stamped on the child run and carried onto every
614
+ * {@link WorkflowTask} dispatched to advance it, so an urgent child can jump ahead of enqueued
615
+ * lower-priority ones at the worker. Higher wins; absent = unprioritised. Ignored for an in-process
616
+ * (TS class) child, which runs in the engine and never hits a broker queue.
617
+ */
618
+ priority?: number | undefined;
619
+ }
620
+ export interface StepOptions {
621
+ /** Max attempts before the step (and run) fails. */
622
+ retries?: number;
623
+ /** How the delay between retries grows: `fixed` (constant) or `exp` (doubles each attempt). */
624
+ backoff?: BackoffStrategy;
625
+ /** Base delay in ms between retries. Omit (or 0) to retry with no delay. */
626
+ backoffMs?: number;
627
+ /** Upper bound on the (exponential) backoff delay. */
628
+ backoffMaxMs?: number;
629
+ /** Add random jitter (50–100% of the computed delay) to avoid thundering-herd retries. */
630
+ jitter?: boolean;
631
+ /**
632
+ * Liveness window for a **remote** step (`ctx.call`): if the worker produces no result and no
633
+ * heartbeat within this many ms, the engine presumes it dead and fails the dispatch with a
634
+ * `RemoteStepTimeout` (retryable — it re-dispatches per `retries`). Each heartbeat resets the
635
+ * window. Ignored for local steps. Omit to wait indefinitely.
636
+ */
637
+ timeoutMs?: number;
638
+ /**
639
+ * Saga compensation: if this step completes but the run later **fails**, the engine runs the
640
+ * registered `compensate` callbacks in reverse order (undo what was done). Local steps only.
641
+ * Idempotency note: a step is already deduplicated by its deterministic `stepId` (runId:seq) —
642
+ * remote workers can use it as the idempotency key, so there's no separate key option.
643
+ */
644
+ compensate?: () => Promise<void>;
645
+ }
646
+ /**
647
+ * A typed handle to a step that runs on a remote worker. The `name` string is the contract:
648
+ * the worker registers a handler under the same name. `input`/`output` validate at the boundary.
649
+ */
650
+ export interface RemoteStepDef<TInput = unknown, TOutput = unknown> extends StepOptions {
651
+ name: string;
652
+ /** Worker group expected to handle this step. */
653
+ group: string;
654
+ input: z.ZodType<TInput>;
655
+ output: z.ZodType<TOutput>;
656
+ /** Branding so `ctx.call` can infer types. */
657
+ readonly __remote: true;
658
+ }
659
+ /**
660
+ * A durable webhook handle minted by {@link WorkflowCtx.webhook}. Hand `url` to a third party,
661
+ * then `await wait()` — the run suspends with zero compute until the external system POSTs the
662
+ * callback (delivered as `engine.signal(token, body)`), and resumes with the body.
663
+ */
664
+ export interface DurableWebhook<TPayload = unknown> {
665
+ /** Deterministic signal token (`wh:<runId>:<seq>`) the callback delivers on — stable across replay. */
666
+ readonly token: string;
667
+ /**
668
+ * Public callback URL for `token`, built by the engine's `webhookUrl` option. Hand this to the
669
+ * third party. `undefined` when no builder is configured (use {@link DurableWebhook.token} to
670
+ * build your own).
671
+ */
672
+ readonly url?: string | undefined;
673
+ /** Suspend until the callback arrives, then resume with its payload. */
674
+ wait(): Promise<TPayload>;
675
+ }
676
+ /**
677
+ * The context handed to a workflow function. Every interaction with the outside world goes
678
+ * through it so the engine can checkpoint — the workflow body itself stays deterministic.
679
+ */
680
+ export interface WorkflowCtx {
681
+ readonly runId: string;
682
+ /**
683
+ * Run a local durable step: executed once, then its result is checkpointed and replayed. The
684
+ * body receives a {@link StepLogger} to record debug/error lines and sub-process outcomes — these
685
+ * are checkpointed with the step and shown under it in the dashboard.
686
+ */
687
+ step<TOutput>(name: string, fn: (log: StepLogger) => Promise<TOutput>, options?: StepOptions): Promise<TOutput>;
688
+ /**
689
+ * **Exactly-once** durable step for DB work: runs `fn` and writes the step's checkpoint in ONE
690
+ * store transaction, so the business write and the "done" marker commit atomically — a crash can
691
+ * never leave the write done-but-not-checkpointed (which a plain `ctx.step` would re-run). `fn`
692
+ * receives the store-native transaction handle (`tx` — a TypeORM/MikroORM `EntityManager`, a Prisma
693
+ * tx client, or a Drizzle tx); do your writes on it. Needs a SQL store that supports transactions
694
+ * (the bundled SQL adapters do); throws otherwise.
695
+ */
696
+ transaction<TOutput>(name: string, fn: (tx: unknown) => Promise<TOutput>): Promise<TOutput>;
697
+ /**
698
+ * Call a durable **entity** op and await its result — the entity (`engine.registerEntity`) runs the
699
+ * op serialized per `key` over durable state. e.g. `await ctx.callEntity('cart', userId, 'add', item)`.
700
+ */
701
+ callEntity<TResult = unknown>(name: string, key: string, op: string, arg?: unknown): Promise<TResult>;
702
+ /** Send a durable entity op without awaiting a result (fire-and-forget, dispatched once). */
703
+ signalEntity(name: string, key: string, op: string, arg?: unknown): Promise<void>;
704
+ /**
705
+ * Dispatch a typed remote step and await its checkpointed result. Options:
706
+ * - `queue` — subject the dispatch to a registered flow-control queue (concurrency / rate limit).
707
+ * - `priority` — admission priority within that queue; higher is admitted first when a slot is
708
+ * contended (default 0). No effect without a `queue`.
709
+ * - `fairnessKey` — the fairness bucket for a queue with `fairness: 'key'` (e.g. a tenant id);
710
+ * the queue round-robins across distinct keys so one key can't monopolize the budget. Defaults
711
+ * to the run id when omitted. No effect without a `queue`.
712
+ * - `transport` — pin the dispatch to a named transport in the pool (else the pool's first, with
713
+ * failover to the rest). See `engine.registerQueue` / the engine's `transports` option.
714
+ */
715
+ call<TInput, TOutput>(step: RemoteStepDef<TInput, TOutput>, input: TInput, opts?: {
716
+ queue?: string;
717
+ priority?: number;
718
+ fairnessKey?: string;
719
+ transport?: string;
720
+ }): Promise<TOutput>;
721
+ /**
722
+ * Durable sleep: suspends the run for `duration` (e.g. `'30s'`, `'2h'`, `'7 days'`, or ms as a
723
+ * number) without consuming resources, resuming automatically once the timer is due — even
724
+ * across restarts.
725
+ */
726
+ sleep(duration: string | number): Promise<void>;
727
+ /**
728
+ * Durable sleep until an **absolute** time (a `Date` or epoch ms) — like {@link sleep} but for a
729
+ * fixed deadline (e.g. "resume at midnight"). Resumes automatically once the time passes, across
730
+ * restarts. The recorded wake time is fixed on the first run, so it's replay-stable.
731
+ */
732
+ sleepUntil(when: Date | number): Promise<void>;
733
+ /**
734
+ * End this run and **continue as a fresh execution** of the same workflow with `input` and a clean
735
+ * history — for long-running / looping workflows that would otherwise accumulate unbounded
736
+ * checkpoints (and slow replays). The next run gets id `<runId>~N`. Terminal: it always throws, so
737
+ * code after it never runs. Carry forward whatever state the next iteration needs in `input`.
738
+ */
739
+ continueAsNew(input?: unknown): Promise<never>;
740
+ /**
741
+ * Suspend the run until an external `engine.signal(token, payload)` arrives (e.g. a webhook or
742
+ * human approval), then resume with the payload. Waits indefinitely by default — no compute
743
+ * consumed. Pass `{ timeoutMs }` to bound the wait: if the deadline passes first the call throws
744
+ * a `SignalTimeoutError` (catch it in the workflow to branch).
745
+ */
746
+ waitForSignal<TPayload>(token: string, opts?: {
747
+ timeoutMs?: number;
748
+ }): Promise<TPayload>;
749
+ /**
750
+ * Wait for a named **event** published via `engine.publishEvent(name, payload)`, then resume with
751
+ * the payload. Unlike a signal (point-to-point by token), events are name-based pub/sub: pass an
752
+ * optional `match` (a subset of the payload that must deep-equal) so a publish fans out only to the
753
+ * runs it concerns — e.g. `ctx.waitForEvent('payment.settled', { match: { orderId } })`. `timeoutMs`
754
+ * bounds the wait (throws `SignalTimeoutError`). No compute consumed while waiting.
755
+ */
756
+ waitForEvent<TPayload>(name: string, opts?: {
757
+ match?: Record<string, unknown>;
758
+ timeoutMs?: number;
759
+ }): Promise<TPayload>;
760
+ /**
761
+ * An external task with **async completion**: run `dispatch` once (checkpointed — e.g. send to a
762
+ * queue, kick off a non-durable worker or a foreign service like a Python process), then suspend
763
+ * with zero compute until `engine.completeTask(runId, name, result)` (or `failTask`) reports back,
764
+ * and resume with the result. The durable, first-class counterpart of the hand-rolled
765
+ * "dispatch over SQS → wait for COMPLETE_PHASE → signal" pattern. `name` must be unique per run.
766
+ */
767
+ task<TResult>(name: string, dispatch: () => Promise<void>, options?: StepOptions): Promise<TResult>;
768
+ /**
769
+ * Run another registered workflow as a **tracked child** and await its result: starts it once and
770
+ * suspends — zero compute — until the child reaches a terminal state, then resumes with the child's
771
+ * output (or throws a FatalError if the child failed). `childId` defaults to a deterministic id
772
+ * derived from this run and the call position, so it's stable across replay.
773
+ *
774
+ * Pass the child's **class** (`ctx.child(ShippingWorkflow, input)`) for a typed input + result; pass
775
+ * a **string** name for a cross-runtime child (e.g. a Python workflow) where there's no class.
776
+ */
777
+ child<C extends WorkflowClass>(workflow: C, input: WorkflowInputOf<C>, options?: string | ChildCallOptions): Promise<WorkflowOutputOf<C>>;
778
+ child<TOutput>(workflow: string, input: unknown, options?: string | ChildCallOptions): Promise<TOutput>;
779
+ /**
780
+ * Start a child workflow **fire-and-forget**: dispatches it once (checkpointed, replay-safe) and
781
+ * returns its run id immediately — the parent keeps running instead of suspending. Use it to kick
782
+ * off side work (an audit log, a notification) you don't need to wait on, or to fan out: collect
783
+ * the ids, then later `await ctx.child(...)` each with the same id to join (the start is idempotent
784
+ * by id, so the child runs exactly once). Class or string ref, like {@link child}.
785
+ */
786
+ startChild<C extends WorkflowClass>(workflow: C, input: WorkflowInputOf<C>, options?: string | ChildCallOptions): Promise<string>;
787
+ startChild(workflow: string, input: unknown, options?: string | ChildCallOptions): Promise<string>;
788
+ /**
789
+ * Pause the run at this point until a human resumes it from the dashboard (or
790
+ * `engine.continue(runId)`). Records a visible `pending` checkpoint so the breakpoint shows up
791
+ * in the timeline, then suspends with zero compute — the durable equivalent of a debugger
792
+ * breakpoint. Gate it on your own config to make breakpoints opt-in per run:
793
+ * `if (cfg.breakAfterExtraction) await ctx.breakpoint('after-extraction')`.
794
+ */
795
+ breakpoint(label?: string): Promise<void>;
796
+ /**
797
+ * Mint a durable webhook: returns a handle with a deterministic `token` and (if the engine has a
798
+ * `webhookUrl` builder) a public `url`. Hand the url to a third party — inside a `ctx.step` — then
799
+ * `await handle.wait()` to suspend with zero compute until they POST the callback (the dashboard
800
+ * turns that POST into `engine.signal(token, body)`). The first-class, replay-safe version of
801
+ * "expose a callback URL and wait for it".
802
+ */
803
+ webhook<TPayload>(): DurableWebhook<TPayload>;
804
+ /**
805
+ * Publish a named, queryable value from inside the run — the latest value for `key` is readable
806
+ * externally via `engine.getEvent(runId, key)` while the run is still in flight (progress, a
807
+ * partial result, a status). Checkpointed and replay-safe (overwrites the previous value for the
808
+ * same key). The read side has no effect on the run — the durable, suspend-model counterpart of a
809
+ * Temporal query.
810
+ */
811
+ setEvent<TValue>(key: string, value: TValue): Promise<void>;
812
+ /**
813
+ * Expose a named **update point**: suspend until an external `engine.update(runId, name, arg)`
814
+ * delivers `arg`, then resume with it. The update is run-scoped (`name` need only be unique within
815
+ * the run) and gated by any validator registered via `engine.registerUpdateValidator` — a rejected
816
+ * update never reaches here. Pass `{ timeoutMs }` to bound the wait (throws `SignalTimeoutError`).
817
+ * The durable counterpart of a Temporal update handler.
818
+ */
819
+ onUpdate<TArg>(name: string, opts?: {
820
+ timeoutMs?: number;
821
+ }): Promise<TArg>;
822
+ /**
823
+ * Guard an in-place workflow change without a new version. Wrap the changed code in
824
+ * `if (await ctx.patched('my-change')) { …new… } else { …old… }`: a fresh run records a marker and
825
+ * takes the new branch (`true`); a run already recorded under the old code keeps the old branch
826
+ * (`false`), because its history has a real step where the marker would sit. The marker is
827
+ * position-transparent for old runs (it doesn't shift their recorded steps), so guarding code is
828
+ * replay-safe. Once every old run has drained, remove the guard (keep the new branch).
829
+ */
830
+ patched(id: string): Promise<boolean>;
831
+ /**
832
+ * Deterministic wall-clock (epoch ms): records the time on the first run and replays the SAME
833
+ * value afterwards. Use this instead of `Date.now()` inside a workflow — a raw `Date.now()` returns
834
+ * a different value on every replay, which silently corrupts a durable run.
835
+ */
836
+ now(): Promise<number>;
837
+ /**
838
+ * Deterministic random in `[0, 1)`: recorded once, then replayed. Use instead of `Math.random()`
839
+ * (same replay-safety reason as {@link now}).
840
+ */
841
+ random(): Promise<number>;
842
+ /** Deterministic UUID v4: recorded once, then replayed. Use instead of `crypto.randomUUID()`. */
843
+ uuid(): Promise<string>;
844
+ }
845
+ /** Result of executing or resuming a workflow run. */
846
+ export interface RunResult {
847
+ runId: string;
848
+ status: RunStatus;
849
+ output?: unknown;
850
+ error?: StepError | undefined;
851
+ }
852
+ /**
853
+ * Validates an incoming `engine.update` before it is delivered to the run. Throw (or return a
854
+ * non-empty string) to reject — the run is left untouched. Return nothing/void to accept. May be
855
+ * async (e.g. a business-rule check against a DB).
856
+ */
857
+ export type UpdateValidator<TArg = unknown> = (arg: TArg) => void | string | Promise<void | string>;
858
+ /** Outcome of `engine.update`: rejected by the validator, or accepted and delivered. */
859
+ export type UpdateResult = {
860
+ accepted: false;
861
+ reason: string;
862
+ } | {
863
+ accepted: true;
864
+ run: RunResult | null;
865
+ };
866
+ export type EngineEventType = 'run.started' | 'run.completed' | 'run.failed' | 'run.suspended' | 'step.started' | 'step.completed' | 'step.failed' | 'step.progress';
867
+ /**
868
+ * A lifecycle event emitted by the engine. The observability surfaces (dashboard, OTel, the
869
+ * Telescope integration) all subscribe to these rather than reaching into the store.
870
+ */
871
+ export interface EngineEvent {
872
+ type: EngineEventType;
873
+ runId: string;
874
+ workflow?: string | undefined;
875
+ seq?: number | undefined;
876
+ name?: string | undefined;
877
+ kind?: StepKind | undefined;
878
+ output?: unknown;
879
+ error?: StepError | undefined;
880
+ /** Wall-clock duration of the unit that just finished (step or run), when known. */
881
+ durationMs?: number | undefined;
882
+ /** For a remote step: how long it waited in the queue before a worker picked it up. */
883
+ queueMs?: number | undefined;
884
+ /** The live step event carried by a `step.progress` (the single log line / sub-process outcome a
885
+ * running step just emitted). Absent on lifecycle events. */
886
+ event?: StepEvent | undefined;
887
+ at: Date;
888
+ }
889
+ export type EngineListener = (event: EngineEvent) => void;
890
+ /** What a {@link StepInterceptor} is told about the local step it is wrapping. */
891
+ export interface StepInvocation {
892
+ readonly runId: string;
893
+ readonly workflow: string;
894
+ /** The step name passed to `ctx.step(name, ...)` (also `'now'`/`'random'`/`'uuid'` internals). */
895
+ readonly stepName: string;
896
+ /** The step's logical position within the run. */
897
+ readonly seq: number;
898
+ /** 1-based attempt number — increments across `ctx.step` retries. */
899
+ readonly attempt: number;
900
+ }
901
+ /**
902
+ * Wraps the **real execution** of a local `ctx.step` (Template/Nest-style onion middleware). Call
903
+ * `next()` to run the step body (or the next interceptor) and return — or transform — its result;
904
+ * throw to fail the step. First-registered runs outermost. Interceptors fire only when a step
905
+ * actually executes, NOT on replay (a replayed step returns its recorded output without running),
906
+ * so they see true execution timing. Register with `engine.use`.
907
+ */
908
+ export type StepInterceptor = (invocation: StepInvocation, next: () => Promise<unknown>) => Promise<unknown>;
909
+ //# sourceMappingURL=interfaces.d.ts.map