@lostgradient/weft 0.2.0 → 0.3.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 (207) hide show
  1. package/README.md +47 -22
  2. package/dist/cli/generated/operation-client.generated.d.ts +28 -1
  3. package/dist/cli/generated/operation-client.generated.js +2 -0
  4. package/dist/cli-main.js +79 -79
  5. package/dist/client/handle-delegation.d.ts +4 -0
  6. package/dist/client/handle-delegation.js +6 -0
  7. package/dist/client/http-client-requests.d.ts +2 -0
  8. package/dist/client/http-client-requests.js +3 -0
  9. package/dist/client/http-client.d.ts +4 -1
  10. package/dist/client/http-client.js +9 -1
  11. package/dist/client/interface.d.ts +57 -2
  12. package/dist/client/local.d.ts +4 -1
  13. package/dist/client/local.js +7 -0
  14. package/dist/client/start-body.d.ts +7 -1
  15. package/dist/client/start-body.js +13 -4
  16. package/dist/core/codec/extension-codec.js +4 -2
  17. package/dist/core/codec/index.d.ts +1 -0
  18. package/dist/core/codec/index.js +1 -0
  19. package/dist/core/codec/serializer-registry.d.ts +122 -0
  20. package/dist/core/codec/serializer-registry.js +51 -0
  21. package/dist/core/context/index.d.ts +10 -0
  22. package/dist/core/context/index.js +3 -0
  23. package/dist/core/context/internals.d.ts +10 -0
  24. package/dist/core/context/internals.js +5 -1
  25. package/dist/core/context/run-operation.d.ts +16 -3
  26. package/dist/core/context/run-operation.js +16 -7
  27. package/dist/core/context/speculative-child.js +2 -0
  28. package/dist/core/context/types.d.ts +6 -0
  29. package/dist/core/engine/bulk-operations-purge.js +1 -0
  30. package/dist/core/engine/bulk-operations.js +1 -1
  31. package/dist/core/engine/callback-creators-bundles.js +2 -1
  32. package/dist/core/engine/callback-creators-core.js +2 -1
  33. package/dist/core/engine/construction.d.ts +1 -1
  34. package/dist/core/engine/construction.js +15 -3
  35. package/dist/core/engine/disposal.js +12 -0
  36. package/dist/core/engine/engine-create-types.d.ts +0 -14
  37. package/dist/core/engine/engine-internal-types.d.ts +17 -0
  38. package/dist/core/engine/engine-leak-warnings.d.ts +6 -0
  39. package/dist/core/engine/engine-leak-warnings.js +4 -0
  40. package/dist/core/engine/engine-runtime-helpers.d.ts +17 -0
  41. package/dist/core/engine/engine-runtime-helpers.js +26 -5
  42. package/dist/core/engine/errors.d.ts +74 -0
  43. package/dist/core/engine/errors.js +25 -1
  44. package/dist/core/engine/handle-result.js +1 -1
  45. package/dist/core/engine/handles.d.ts +89 -40
  46. package/dist/core/engine/handles.js +25 -27
  47. package/dist/core/engine/index.d.ts +122 -4
  48. package/dist/core/engine/index.js +82 -5
  49. package/dist/core/engine/inline-launch-queue.d.ts +14 -0
  50. package/dist/core/engine/inline-launch-queue.js +32 -7
  51. package/dist/core/engine/internals.d.ts +26 -10
  52. package/dist/core/engine/lifecycle/fork-helpers.js +1 -7
  53. package/dist/core/engine/lifecycle/persist.js +5 -20
  54. package/dist/core/engine/lifecycle/recovered-services.d.ts +45 -0
  55. package/dist/core/engine/lifecycle/recovered-services.js +34 -0
  56. package/dist/core/engine/lifecycle/resume.js +33 -5
  57. package/dist/core/engine/lifecycle/shared.d.ts +8 -0
  58. package/dist/core/engine/lifecycle/start-batch.js +23 -12
  59. package/dist/core/engine/lifecycle/start-commit.d.ts +47 -0
  60. package/dist/core/engine/lifecycle/start-commit.js +27 -0
  61. package/dist/core/engine/lifecycle/start-exec.d.ts +30 -2
  62. package/dist/core/engine/lifecycle/start-exec.js +38 -0
  63. package/dist/core/engine/lifecycle/start-or-signal-resolution.d.ts +79 -0
  64. package/dist/core/engine/lifecycle/start-or-signal-resolution.js +60 -0
  65. package/dist/core/engine/lifecycle/start-or-signal.d.ts +45 -0
  66. package/dist/core/engine/lifecycle/start-or-signal.js +141 -0
  67. package/dist/core/engine/lifecycle/start.d.ts +3 -3
  68. package/dist/core/engine/lifecycle/start.js +42 -37
  69. package/dist/core/engine/lifecycle.d.ts +3 -2
  70. package/dist/core/engine/lifecycle.js +9 -2
  71. package/dist/core/engine/listing.js +1 -1
  72. package/dist/core/engine/operations-data.d.ts +16 -0
  73. package/dist/core/engine/operations-data.js +6 -0
  74. package/dist/core/engine/operations-time.d.ts +3 -2
  75. package/dist/core/engine/operations-time.js +6 -1
  76. package/dist/core/engine/persisted-data-version.d.ts +5 -9
  77. package/dist/core/engine/persisted-data-version.js +4 -5
  78. package/dist/core/engine/schedule-handle.d.ts +45 -0
  79. package/dist/core/engine/schedule-handle.js +26 -0
  80. package/dist/core/engine/schedules.d.ts +1 -1
  81. package/dist/core/engine/schedules.js +7 -3
  82. package/dist/core/engine/second-instance-detector.d.ts +96 -0
  83. package/dist/core/engine/second-instance-detector.js +108 -0
  84. package/dist/core/engine/signals.d.ts +22 -0
  85. package/dist/core/engine/signals.js +15 -0
  86. package/dist/core/engine/termination/cleanup.d.ts +25 -0
  87. package/dist/core/engine/termination/cleanup.js +21 -1
  88. package/dist/core/engine/termination/complete.js +4 -3
  89. package/dist/core/engine/termination/suspend.d.ts +68 -0
  90. package/dist/core/engine/termination/suspend.js +41 -0
  91. package/dist/core/engine/termination.d.ts +4 -2
  92. package/dist/core/engine/termination.js +2 -0
  93. package/dist/core/engine/validation.js +25 -1
  94. package/dist/core/engine/workflow-feed.d.ts +5 -3
  95. package/dist/core/events/event-map.d.ts +2 -1
  96. package/dist/core/events/workflow-events.d.ts +23 -0
  97. package/dist/core/events/workflow-events.js +9 -0
  98. package/dist/core/inline-execution-strategy.d.ts +5 -0
  99. package/dist/core/inline-execution-strategy.js +2 -1
  100. package/dist/core/list-filter-validation.js +2 -1
  101. package/dist/core/start-workflow-validation.d.ts +22 -0
  102. package/dist/core/start-workflow-validation.js +11 -1
  103. package/dist/core/step-context.d.ts +10 -6
  104. package/dist/core/step-context.js +7 -15
  105. package/dist/core/types/activity.d.ts +6 -3
  106. package/dist/core/types/identity.d.ts +8 -1
  107. package/dist/core/types/launch-metadata.d.ts +33 -0
  108. package/dist/core/types/launch-metadata.js +0 -0
  109. package/dist/core/types/message-handles.d.ts +25 -0
  110. package/dist/core/types/options.d.ts +90 -7
  111. package/dist/core/types/reviews.d.ts +2 -1
  112. package/dist/core/types/services-resolution.d.ts +47 -0
  113. package/dist/core/types/services-resolution.js +0 -0
  114. package/dist/core/types/state.d.ts +11 -11
  115. package/dist/core/types/workflow-builder.d.ts +5 -4
  116. package/dist/core/types/workflow-context.d.ts +25 -0
  117. package/dist/core/types/workflow-function.d.ts +17 -0
  118. package/dist/core/types/workflow-snapshot.d.ts +29 -0
  119. package/dist/core/types/workflow-snapshot.js +0 -0
  120. package/dist/core/types.d.ts +3 -0
  121. package/dist/core/types.js +3 -0
  122. package/dist/core/weft-error.d.ts +46 -14
  123. package/dist/core/weft-error.js +12 -1
  124. package/dist/diagnostics/doctor.js +6 -3
  125. package/dist/diagnostics/format.js +2 -2
  126. package/dist/diagnostics/types.d.ts +1 -0
  127. package/dist/diagnostics/version-check.js +6 -4
  128. package/dist/index.d.ts +10 -5
  129. package/dist/index.js +11 -2
  130. package/dist/json-schema.js +3 -3
  131. package/dist/mcp/cli.js +35 -35
  132. package/dist/mcp/list-filter.js +2 -1
  133. package/dist/mcp/session.js +1 -0
  134. package/dist/observability/index.js +2 -2
  135. package/dist/server/handler.js +30 -30
  136. package/dist/server/index.js +33 -33
  137. package/dist/server/interactive-operations.js +1 -0
  138. package/dist/server/operations/resume-workflow.js +2 -2
  139. package/dist/server/operations/start-or-signal-workflow.d.ts +39 -0
  140. package/dist/server/operations/start-or-signal-workflow.js +140 -0
  141. package/dist/server/operations/start-workflow-options.d.ts +32 -0
  142. package/dist/server/operations/start-workflow-options.js +63 -0
  143. package/dist/server/operations/start-workflow.js +7 -69
  144. package/dist/server/operations/suspend-workflow.d.ts +13 -0
  145. package/dist/server/operations/suspend-workflow.js +36 -0
  146. package/dist/server/rest-binding.d.ts +18 -7
  147. package/dist/server/rest-bindings.js +12 -0
  148. package/dist/server/runtime/task-dispatch.js +5 -3
  149. package/dist/server/runtime/task-polling.d.ts +16 -2
  150. package/dist/server/runtime/task-polling.js +20 -5
  151. package/dist/server/runtime/websocket-worker.js +8 -0
  152. package/dist/server/serve-internals.d.ts +8 -0
  153. package/dist/server/serve-internals.js +4 -2
  154. package/dist/server/task-state.d.ts +8 -0
  155. package/dist/service-worker/index.js +28 -28
  156. package/dist/storage/capabilities.d.ts +10 -2
  157. package/dist/storage/capabilities.js +2 -2
  158. package/dist/storage/http.js +2 -2
  159. package/dist/storage/index.d.ts +7 -1
  160. package/dist/storage/indexeddb.js +1 -1
  161. package/dist/storage/interface.d.ts +40 -0
  162. package/dist/storage/interface.js +1 -1
  163. package/dist/storage/key-prefixes.d.ts +1 -1
  164. package/dist/storage/key-prefixes.js +3 -0
  165. package/dist/storage/lmdb.js +1 -1
  166. package/dist/storage/memory.js +1 -1
  167. package/dist/storage/neon-value-mapping.d.ts +47 -0
  168. package/dist/storage/neon-value-mapping.js +11 -0
  169. package/dist/storage/neon.d.ts +108 -0
  170. package/dist/storage/neon.js +10 -0
  171. package/dist/storage/node-sqlite-loader.d.ts +71 -0
  172. package/dist/storage/node-sqlite-loader.js +41 -0
  173. package/dist/storage/node-sqlite.d.ts +1 -19
  174. package/dist/storage/node-sqlite.js +38 -32
  175. package/dist/storage/postgres-key-value-queries.d.ts +79 -0
  176. package/dist/storage/postgres-key-value-queries.js +63 -0
  177. package/dist/storage/resolve.d.ts +2 -165
  178. package/dist/storage/resolve.js +1 -1
  179. package/dist/storage/scoped-storage.js +1 -1
  180. package/dist/storage/storage-configuration.d.ts +209 -0
  181. package/dist/storage/storage-configuration.js +0 -0
  182. package/dist/storage/text-value-store.d.ts +13 -10
  183. package/dist/storage/turso.js +2 -2
  184. package/dist/storage/typed-storage.js +1 -1
  185. package/dist/storage/web-extension.js +1 -1
  186. package/dist/testing/event-loop.d.ts +36 -2
  187. package/dist/testing/index.d.ts +31 -1
  188. package/dist/testing/index.js +33 -33
  189. package/dist/version.d.ts +1 -1
  190. package/dist/version.js +1 -1
  191. package/dist/worker/index.js +9 -5
  192. package/dist/worker/long-poll.js +4 -0
  193. package/dist/worker/protocol-messages.d.ts +20 -0
  194. package/dist/worker/protocol-schemas.d.ts +32 -0
  195. package/dist/worker/protocol-schemas.js +8 -4
  196. package/dist/worker/protocol-task-result.d.ts +28 -0
  197. package/dist/worker/protocol-task-result.js +76 -0
  198. package/dist/worker/protocol.d.ts +4 -15
  199. package/dist/worker/protocol.js +1 -1
  200. package/dist/worker/registry/fair-share.d.ts +29 -0
  201. package/dist/worker/registry/fair-share.js +30 -0
  202. package/dist/worker/registry/routing.d.ts +18 -0
  203. package/dist/worker/registry/routing.js +14 -0
  204. package/dist/worker/registry/types.d.ts +7 -0
  205. package/dist/worker/registry.d.ts +16 -1
  206. package/dist/worker/registry.js +24 -36
  207. package/package.json +17 -4
@@ -1,9 +1,12 @@
1
- import type { QueryDefinition, ScheduleSpec, ScheduleSummary, SearchAttributeValue, SignalDefinition, SignalDeliveryOptions, UpdateDefinition, WorkflowState } from '../types.ts';
1
+ import type { LaunchMetadata, QueryDefinition, SearchAttributeValue, SignalDefinition, SignalDeliveryOptions, UpdateDefinition, WorkflowState } from '../types.ts';
2
+ import type { WorkflowSnapshot } from '../types/workflow-snapshot.ts';
2
3
  export declare function getWorkflowExecutionStartedAt(state: Pick<WorkflowState, 'createdAt' | 'startedAt'>): number;
3
4
  export declare const HANDLE_RESULT_PROMISE: unique symbol;
4
5
  export interface WorkflowHandleEngine extends EventTarget {
5
6
  [HANDLE_RESULT_PROMISE](workflowId: string): Promise<unknown>;
6
7
  cancel(workflowId: string): Promise<void>;
8
+ suspend(workflowId: string): Promise<void>;
9
+ resume(workflowId: string): Promise<WorkflowHandle>;
7
10
  signal(workflowId: string, name: string, payload?: unknown, options?: SignalDeliveryOptions): Promise<void>;
8
11
  update(workflowId: string, name: string, payload?: unknown, options?: {
9
12
  timeout?: number;
@@ -14,13 +17,13 @@ export interface WorkflowHandleEngine extends EventTarget {
14
17
  addTags(workflowId: string, ...tags: string[]): Promise<void>;
15
18
  removeTags(workflowId: string, ...tags: string[]): Promise<void>;
16
19
  get(workflowId: string): Promise<WorkflowState | null>;
17
- }
18
- export interface ScheduleHandleEngine {
19
- pauseSchedule(scheduleId: string): Promise<void>;
20
- resumeSchedule(scheduleId: string): Promise<void>;
21
- cancelSchedule(scheduleId: string): Promise<void>;
22
- updateSchedule(scheduleId: string, newSpec: string | ScheduleSpec): Promise<void>;
23
- getSchedule(scheduleId: string): Promise<ScheduleSummary | null>;
20
+ /**
21
+ * Current checkpoint step (the run's cursor) for a workflow, or `null` when no
22
+ * checkpoint exists. Reads the in-memory checkpoint when the run is live in
23
+ * this engine, otherwise the durably persisted checkpoint — so it is correct
24
+ * for both an in-flight run and one recovered or inspected in a fresh process.
25
+ */
26
+ getCurrentCheckpointStep(workflowId: string): Promise<number | null>;
24
27
  }
25
28
  /**
26
29
  * Handle to a running or completed workflow. Returned by {@link Engine.start}
@@ -72,6 +75,84 @@ export declare class WorkflowHandle<TResult = unknown> extends EventTarget imple
72
75
  constructor(id: string, engine: WorkflowHandleEngine);
73
76
  result(): Promise<TResult>;
74
77
  cancel(): Promise<void>;
78
+ /**
79
+ * Suspend this workflow without terminating it: it transitions to the
80
+ * non-terminal `'suspended'` status, keeps its durable checkpoint, and is
81
+ * later resumable via {@link WorkflowHandle.resume}. Unlike `cancel()`, this
82
+ * does not run cancel handlers and does not settle `result()` — the result
83
+ * promise stays pending until a later `resume()` completes the run. A
84
+ * suspended workflow is NOT auto-recovered by `engine.recoverAll()`; resume it
85
+ * explicitly. Suspending a workflow that is not running is a no-op.
86
+ */
87
+ suspend(): Promise<void>;
88
+ /**
89
+ * Resume this workflow from its persisted checkpoint after it was suspended
90
+ * (or left `'running'` by a prior process). The run is re-driven on this
91
+ * engine; `result()` on this handle resolves when the resumed run completes.
92
+ * Throws if the workflow is in a status that cannot be resumed (terminal,
93
+ * pending, or not found).
94
+ */
95
+ resume(): Promise<void>;
96
+ /**
97
+ * Reconstruct this workflow's launch context — its original `input` and the
98
+ * launch options recoverable from durable state — from the persisted
99
+ * {@link WorkflowState}. Resolves `null` if the workflow no longer exists
100
+ * (never started, or purged).
101
+ *
102
+ * Designed for the post-`recoverAll()` case: a recovered handle can recover
103
+ * the input a run was started with (and its `id`/`tags`) without the caller
104
+ * keeping a side table correlating recovered workflows back to their launch
105
+ * context. This is an async read (it loads state) so it behaves identically
106
+ * on handles from `start()`, `recoverAll()`, and `getHandle()` — none of which
107
+ * is special-cased — rather than a sync property that would be `undefined` on
108
+ * a handle created without a state load.
109
+ *
110
+ * @example
111
+ * ```ts
112
+ * import { Engine } from '@lostgradient/weft';
113
+ *
114
+ * const engine = new Engine();
115
+ * const handles = await engine.recoverAll();
116
+ * for (const handle of handles) {
117
+ * const metadata = await handle.getLaunchMetadata();
118
+ * if (metadata) {
119
+ * // rebuild this run's dependencies from metadata.input
120
+ * void metadata.input;
121
+ * }
122
+ * }
123
+ * ```
124
+ */
125
+ getLaunchMetadata(): Promise<LaunchMetadata | null>;
126
+ /**
127
+ * A point-in-time view of this workflow's progress: its status and current
128
+ * checkpoint step (cursor). Resolves `null` if the workflow no longer exists.
129
+ * The `status` matches `engine.get(id)` — notably it reports `'pending'` for a
130
+ * run whose inline start is still queued, even though its persisted status is
131
+ * `'running'`.
132
+ *
133
+ * Designed for observing a recovered run: after `engine.recoverAll()`, a
134
+ * caller can read where a resumed run currently is — and rebuild its own
135
+ * progress adapter to re-register the run on a live surface — without waiting
136
+ * for the run's final `result()`. It is an async read (loads state +
137
+ * checkpoint), so it behaves identically on handles from `start()`,
138
+ * `recoverAll()`, and `getHandle()`.
139
+ *
140
+ * @example
141
+ * ```ts
142
+ * import { Engine } from '@lostgradient/weft';
143
+ *
144
+ * const engine = new Engine();
145
+ * const handles = await engine.recoverAll();
146
+ * for (const handle of handles) {
147
+ * const snapshot = await handle.snapshot();
148
+ * if (snapshot) {
149
+ * // re-register a progress adapter at snapshot.step
150
+ * void snapshot.step;
151
+ * }
152
+ * }
153
+ * ```
154
+ */
155
+ snapshot(): Promise<WorkflowSnapshot | null>;
75
156
  signal(name: SignalDefinition): Promise<void>;
76
157
  signal<TInput>(name: SignalDefinition<TInput>, payload: TInput, options?: SignalDeliveryOptions): Promise<void>;
77
158
  signal(name: string, payload?: unknown, options?: SignalDeliveryOptions): Promise<void>;
@@ -103,35 +184,3 @@ export declare class WorkflowHandle<TResult = unknown> extends EventTarget imple
103
184
  };
104
185
  [Symbol.asyncDispose](): Promise<void>;
105
186
  }
106
- /**
107
- * Handle to a recurring schedule created by {@link Engine.schedule}. Use
108
- * `handle.pause()`, `handle.resume()`, `handle.cancel()`, or
109
- * `handle.update(cronExpression)` to manage the schedule lifecycle.
110
- * `handle.describe()` returns the current {@link ScheduleSummary}.
111
- *
112
- * @example
113
- * ```ts
114
- * import { workflow, Engine, ScheduleHandle } from '@lostgradient/weft';
115
- *
116
- * const engine = new Engine();
117
- * engine.register(workflow({ name: 'daily-report' }).execute(async function* () { return 'ok'; }));
118
- *
119
- * const handle = await engine.schedule('daily-report', null, '0 9 * * *');
120
- * const typedHandle: ScheduleHandle = handle;
121
- * await handle.pause();
122
- * const summary = await handle.describe();
123
- * void typedHandle;
124
- * console.log(summary.status); // 'paused'
125
- * await handle.cancel();
126
- * ```
127
- */
128
- export declare class ScheduleHandle {
129
- #private;
130
- readonly id: string;
131
- constructor(id: string, engine: ScheduleHandleEngine);
132
- pause(): Promise<void>;
133
- resume(): Promise<void>;
134
- cancel(): Promise<void>;
135
- update(newSpec: string | ScheduleSpec): Promise<void>;
136
- describe(): Promise<ScheduleSummary>;
137
- }
@@ -51,6 +51,31 @@ export class WorkflowHandle extends EventTarget {
51
51
  async cancel() {
52
52
  return this.#engine.cancel(this.id);
53
53
  }
54
+ async suspend() {
55
+ return this.#engine.suspend(this.id);
56
+ }
57
+ async resume() {
58
+ await this.#engine.resume(this.id);
59
+ }
60
+ async getLaunchMetadata() {
61
+ const state = await this.#engine.get(this.id);
62
+ if (state === null)
63
+ return null;
64
+ return {
65
+ input: state.input,
66
+ launchOptions: {
67
+ id: state.id,
68
+ ...state.tags !== void 0 && state.tags.length > 0 && { tags: state.tags }
69
+ }
70
+ };
71
+ }
72
+ async snapshot() {
73
+ const state = await this.#engine.get(this.id);
74
+ if (state === null)
75
+ return null;
76
+ const step = await this.#engine.getCurrentCheckpointStep(this.id);
77
+ return { status: state.status, step: step ?? 0 };
78
+ }
54
79
  async signal(nameOrDefinition, payload, options) {
55
80
  return this.#engine.signal(this.id, messageName(nameOrDefinition), payload, options);
56
81
  }
@@ -144,30 +169,3 @@ export class WorkflowHandle extends EventTarget {
144
169
  }
145
170
  async[Symbol.asyncDispose]() {}
146
171
  }
147
-
148
- export class ScheduleHandle {
149
- id;
150
- #engine;
151
- constructor(id, engine) {
152
- this.id = id;
153
- this.#engine = engine;
154
- }
155
- async pause() {
156
- await this.#engine.pauseSchedule(this.id);
157
- }
158
- async resume() {
159
- await this.#engine.resumeSchedule(this.id);
160
- }
161
- async cancel() {
162
- await this.#engine.cancelSchedule(this.id);
163
- }
164
- async update(newSpec) {
165
- await this.#engine.updateSchedule(this.id, newSpec);
166
- }
167
- async describe() {
168
- const schedule = await this.#engine.getSchedule(this.id);
169
- if (!schedule)
170
- throw Error(`Schedule "${this.id}" not found`);
171
- return schedule;
172
- }
173
- }
@@ -4,7 +4,7 @@ import type { StoredStreamChunk } from '../context.ts';
4
4
  import type { Interceptor } from '../interceptor.ts';
5
5
  import { type ReviewRequest } from '../review/index.ts';
6
6
  import { Scheduler } from '../scheduler.ts';
7
- import { type AnyActivityDefinition, type AnyWorkflowDefinition, type AttributeFilterKey, type BulkCancelResult, type BulkDeleteResult, type BulkOperationCommitOptions, type BulkOperationDryRunOptions, type BulkOperationDryRunResult, type BulkSignalAllCommitOptions, type BulkSignalAllDryRunOptions, type BulkSignalResult, type BulkTagResult, type CheckpointState, type CheckpointSummary, type CoordinatedUpdateResult, type DefaultActivityTypes, type DefaultWorkflowRegistry, type ForkOptions, type InferActivityEntries, type InferActivityEntry, type InferWorkflowEntries, type InferWorkflowEntry, type ListFilter, type ListOptions, type PaginatedResult, type PurgeResult, type QueryDefinition, type RegisteredWorkflowDefinition, type RetentionOverview, type ReviewListEntry, type ReviewListFilter, type ScheduleDefinition, type ScheduleFilter, type ScheduleOptions, type ScheduleSpec, type ScheduleSummary, type SearchAttributeValue, type SignalDefinition, type SignalDeliveryOptions, type StartOptions, type SubmitReviewOptions, type TypedListFilter, type UpdateDefinition, type WorkflowEvent, type WorkflowInput, type WorkflowOutput, type WorkflowReplay, type WorkflowState, type WorkflowSummary, type WorkflowTimelineEntry } from '../types.ts';
7
+ import { type AnyActivityDefinition, type AnyWorkflowDefinition, type AttributeFilterKey, type BulkCancelResult, type BulkDeleteResult, type BulkOperationCommitOptions, type BulkOperationDryRunOptions, type BulkOperationDryRunResult, type BulkSignalAllCommitOptions, type BulkSignalAllDryRunOptions, type BulkSignalResult, type BulkTagResult, type CheckpointState, type CheckpointSummary, type CoordinatedUpdateResult, type DefaultActivityTypes, type DefaultWorkflowRegistry, type ForkOptions, type InferActivityEntries, type InferActivityEntry, type InferWorkflowEntries, type InferWorkflowEntry, type ListFilter, type ListOptions, type PaginatedResult, type PurgeResult, type QueryDefinition, type RegisteredWorkflowDefinition, type RetentionOverview, type ReviewListEntry, type ReviewListFilter, type ScheduleDefinition, type ScheduleFilter, type ScheduleOptions, type ScheduleSpec, type ScheduleSummary, type SearchAttributeValue, type SignalDefinition, type SignalDeliveryOptions, type StartOptions, type StartOrSignalSignal, type SubmitReviewOptions, type TypedListFilter, type UpdateDefinition, type WorkflowEvent, type WorkflowInput, type WorkflowOutput, type WorkflowReplay, type WorkflowState, type WorkflowSummary, type WorkflowTimelineEntry } from '../types.ts';
8
8
  import type { TimerEntry } from '../types/checkpoint.ts';
9
9
  import type { WorkflowAlreadyRegistered } from '../types/workflow-builder.ts';
10
10
  import { type AggregateOptions, type AggregateResult } from './aggregate.ts';
@@ -12,17 +12,19 @@ import { type EmptyActivityDefinitions, type EmptyWorkflowDefinitions, type Know
12
12
  import { type ActivityDefinitionName, type EngineCreateOptions, type RegisteredActivityDefinitionExecute, type UnknownWorkflowNameWhenDefaultRegistryIsEmpty } from './engine-create-types.ts';
13
13
  import type { EngineConstructorOptions } from './engine-internal-types.ts';
14
14
  import type { EngineStateNamespace } from './engine-state-namespace.ts';
15
- import { HANDLE_RESULT_PROMISE, ScheduleHandle, WorkflowHandle } from './handles.ts';
15
+ import { HANDLE_RESULT_PROMISE, WorkflowHandle } from './handles.ts';
16
16
  import { type RecoverAllOptions } from './lifecycle.ts';
17
17
  import { assertCompatiblePersistedDataVersion } from './persisted-data-version.ts';
18
+ import { ScheduleHandle } from './schedule-handle.ts';
18
19
  import { type WorkflowFeedListener, type WorkflowFeedRecord, type WorkflowFeedSelector } from './workflow-feed.ts';
19
20
  export { ActivityReconciliationCapabilityError, ActivityReconciliationConflictError, ActivityReconciliationIndeterminateError, } from './activity-reconciliation.ts';
20
21
  export { AsyncActivityTokenNotFoundError } from './async-activity-completion.ts';
21
22
  export type { PendingAsyncActivity } from './async-activity-completion.ts';
22
23
  export type { PendingTimelineEntry, RegistrationEntry, ResolvedOptions, TrackedWaiterKeys, WorkflowResultWaiter, } from './engine-internal-types.ts';
23
- export { ActivityResolutionError, BulkDeleteRequiresTerminalWorkflowsError, BulkOperationConfirmationError, EngineCreateNameMismatchError, EngineDisposedError, PersistedDataIncompatibleError, WorkflowAlreadyExistsError, WorkflowNotFoundError, WorkflowNotRegisteredError, WorkflowTypeNotRegisteredForRecoveryError, } from './errors.ts';
24
- export { HANDLE_RESULT_PROMISE, ScheduleHandle, WorkflowHandle } from './handles.ts';
24
+ export { ActivityResolutionError, BulkDeleteRequiresTerminalWorkflowsError, BulkOperationConfirmationError, EngineCreateNameMismatchError, EngineDisposedError, IdempotencyKeyPurgedError, PersistedDataIncompatibleError, StartOrSignalConflictError, WorkflowAlreadyExistsError, WorkflowNotFoundError, WorkflowNotRegisteredError, WorkflowSuspendNotSupportedError, WorkflowTypeNotRegisteredForRecoveryError, } from './errors.ts';
25
+ export { HANDLE_RESULT_PROMISE, WorkflowHandle } from './handles.ts';
25
26
  export type { RecoverAllOptions } from './lifecycle.ts';
27
+ export { ScheduleHandle } from './schedule-handle.ts';
26
28
  export type { WorkflowFeedListener, WorkflowFeedRecord, WorkflowFeedSelector, } from './workflow-feed.ts';
27
29
  export type { EngineCreateOptions } from './engine-create-types.ts';
28
30
  export { clearEngineLeakWarningTokenForTesting, getEngineLeakCollectionCountForTesting, hasEngineLeakWarningTokenForTesting, setEngineLeakWarningOverrideForTesting, setNextEngineLeakWarningTokenForTesting, shouldEmitEngineLeakWarningForTesting, } from './engine-leak-warnings.ts';
@@ -30,6 +32,7 @@ export type { EngineStateNamespace } from './engine-state-namespace.ts';
30
32
  export { assertCompatiblePersistedDataVersion };
31
33
  export declare const ENGINE_PARKED_WORKFLOW_COUNT_FOR_TESTING: unique symbol;
32
34
  export declare const ENGINE_SIGNAL_WAITER_COUNT_FOR_TESTING: unique symbol;
35
+ export declare const ENGINE_SLEEP_RESOLVER_COUNT_FOR_TESTING: unique symbol;
33
36
  /**
34
37
  * Durable execution engine.
35
38
  *
@@ -185,6 +188,25 @@ export declare class Engine<TWorkflows extends object = DefaultWorkflowRegistry,
185
188
  listActivityDefinitions(): ActivityMetadata[];
186
189
  start<TName extends KnownWorkflowNames<TWorkflows>>(type: TName, input: WorkflowInput<TWorkflows, TName>, options?: StartOptions): Promise<WorkflowHandle<WorkflowOutput<TWorkflows, TName>>>;
187
190
  start<TName extends string>(type: UnknownWorkflowNameWhenDefaultRegistryIsEmpty<TWorkflows, TName>, input: unknown, options?: StartOptions): Promise<WorkflowHandle>;
191
+ /**
192
+ * Atomically start a workflow or signal it if it already exists
193
+ * (signal-with-start). With an absent target, the workflow record and the
194
+ * first signal commit in one batch and the freshly-launched run consumes the
195
+ * signal on its first drive. A non-terminal target (running, pending, or
196
+ * suspended) is signalled through the normal signal path; a terminal target
197
+ * throws {@link StartOrSignalConflictError} rather than starting a new run or
198
+ * dropping the signal.
199
+ *
200
+ * Concurrent callers converge on one workflow and one delivered signal. Pass
201
+ * `options.idempotencyKey` to dedup independent callers (e.g. retried
202
+ * webhooks); the signal id derives from the key when `signal.signalId` is
203
+ * omitted, so callers that share only the key still converge. `signal.signalId`
204
+ * and `options.idempotencyKey` are mutually exclusive (provide exactly one), as
205
+ * are `options.id` and `options.idempotencyKey`. Requires a storage backend
206
+ * with `conditionalBatch`.
207
+ */
208
+ startOrSignal<TName extends KnownWorkflowNames<TWorkflows>>(type: TName, input: WorkflowInput<TWorkflows, TName>, signal: StartOrSignalSignal, options?: StartOptions): Promise<WorkflowHandle<WorkflowOutput<TWorkflows, TName>>>;
209
+ startOrSignal<TName extends string>(type: UnknownWorkflowNameWhenDefaultRegistryIsEmpty<TWorkflows, TName>, input: unknown, signal: StartOrSignalSignal, options?: StartOptions): Promise<WorkflowHandle>;
188
210
  getHandle(workflowId: string): WorkflowHandle;
189
211
  list<const TAttributeKeys extends readonly AttributeFilterKey[] = readonly AttributeFilterKey[]>(filter?: TypedListFilter<TAttributeKeys>, options?: ListOptions): Promise<PaginatedResult<WorkflowSummary>>;
190
212
  aggregate(filter: ListFilter | undefined, options: AggregateOptions): Promise<AggregateResult>;
@@ -201,6 +223,37 @@ export declare class Engine<TWorkflows extends object = DefaultWorkflowRegistry,
201
223
  tagAll(filter: ListFilter, tags: string[], options?: BulkOperationCommitOptions): Promise<BulkTagResult>;
202
224
  untagAll(filter: ListFilter, tags: string[], options: BulkOperationDryRunOptions): Promise<BulkOperationDryRunResult>;
203
225
  untagAll(filter: ListFilter, tags: string[], options?: BulkOperationCommitOptions): Promise<BulkTagResult>;
226
+ /**
227
+ * Register a recurring schedule that starts a workflow on a cron expression or
228
+ * fixed interval. Returns a {@link ScheduleHandle} for pausing, resuming,
229
+ * updating, or cancelling the schedule.
230
+ *
231
+ * Two call forms:
232
+ * - A {@link ScheduleDefinition} object: `engine.schedule({ workflow, cron, input })`.
233
+ * Carries the workflow (definition or type name), the `cron`/`every` spec,
234
+ * optional `input`, `id`, `overlapPolicy`, and `backfill`.
235
+ * - Positional: `engine.schedule(type, input, spec, options?)` where `spec` is
236
+ * a cron string or a {@link ScheduleSpec} (`{ cron }` or `{ every }`).
237
+ *
238
+ * The {@link ScheduleOptions.overlap} policy governs what happens when a tick
239
+ * fires while the previous run is still in flight.
240
+ *
241
+ * @example
242
+ * ```ts
243
+ * import { workflow, Engine } from '@lostgradient/weft';
244
+ *
245
+ * const engine = new Engine();
246
+ * engine.register(workflow({ name: 'sweep' }).execute(async function* () { return 'ok'; }));
247
+ *
248
+ * // Definition form: every day at 09:00, skip a tick if the prior run is still running.
249
+ * const handle = await engine.schedule({
250
+ * workflow: 'sweep',
251
+ * cron: '0 9 * * *',
252
+ * overlapPolicy: 'skip',
253
+ * });
254
+ * await handle.pause();
255
+ * ```
256
+ */
204
257
  schedule<TInput>(definition: ScheduleDefinition<TInput>): Promise<ScheduleHandle>;
205
258
  schedule(type: string, input: unknown, spec: string | ScheduleSpec, options?: ScheduleOptions): Promise<ScheduleHandle>;
206
259
  getSchedule(scheduleId: string): Promise<ScheduleSummary | null>;
@@ -212,6 +265,7 @@ export declare class Engine<TWorkflows extends object = DefaultWorkflowRegistry,
212
265
  [HANDLE_RESULT_PROMISE](workflowId: string): Promise<unknown>;
213
266
  [ENGINE_PARKED_WORKFLOW_COUNT_FOR_TESTING](): number;
214
267
  [ENGINE_SIGNAL_WAITER_COUNT_FOR_TESTING](): number;
268
+ [ENGINE_SLEEP_RESOLVER_COUNT_FOR_TESTING](): number;
215
269
  signal(workflowId: string, name: SignalDefinition): Promise<void>;
216
270
  signal<TInput>(workflowId: string, name: SignalDefinition<TInput>, payload: TInput, options?: SignalDeliveryOptions): Promise<void>;
217
271
  signal(workflowId: string, name: string, payload?: unknown, options?: SignalDeliveryOptions): Promise<void>;
@@ -230,7 +284,40 @@ export declare class Engine<TWorkflows extends object = DefaultWorkflowRegistry,
230
284
  getStreamChunks(workflowId: string, key: string, options?: {
231
285
  after?: number;
232
286
  }): Promise<StoredStreamChunk[]>;
287
+ /**
288
+ * Read a value a workflow offloaded with `ctx.offload(key, ...)` back out of
289
+ * storage by `workflowId` + `key`.
290
+ *
291
+ * This is the external, post-completion reader for offloaded artifacts — the
292
+ * missing sibling of {@link getStreamChunks} and {@link getEvents}. Offloaded
293
+ * values survive normal completion (`completeWorkflow`/`failWorkflow` preserve
294
+ * them) so a consumer can read a finished workflow's offloaded output after
295
+ * `handle.result()` resolves. They are swept only when a workflow is
296
+ * terminated, cancelled, or times out.
297
+ *
298
+ * @returns The decoded offload value, or `null` when no value is stored under
299
+ * that key (key was never written, workflow ID unknown, or artifact swept).
300
+ *
301
+ * @example
302
+ * ```ts
303
+ * import { Engine } from '@lostgradient/weft';
304
+ *
305
+ * async function readReport(engine: Engine, workflowId: string): Promise<unknown> {
306
+ * // `null` when the workflow offloaded nothing under this key, or after a
307
+ * // terminated workflow swept its output artifacts.
308
+ * return engine.getOffload(workflowId, 'report');
309
+ * }
310
+ * ```
311
+ */
312
+ getOffload(workflowId: string, key: string): Promise<unknown>;
233
313
  fork(sourceWorkflowId: string, options?: ForkOptions): Promise<WorkflowHandle>;
314
+ /**
315
+ * Re-drive a workflow from its persisted checkpoint and return a live handle.
316
+ * Accepts a workflow left `'running'` (e.g. recovered after a process restart)
317
+ * or one explicitly `'suspended'` via {@link Engine.suspend} — a suspended
318
+ * workflow is durably flipped back to `'running'` as part of resuming. Throws
319
+ * if the workflow is in any other status (terminal, pending) or not found.
320
+ */
234
321
  resume(workflowId: string): Promise<WorkflowHandle>;
235
322
  /**
236
323
  * Recover every running workflow found in storage. By default, recovery
@@ -265,8 +352,23 @@ export declare class Engine<TWorkflows extends object = DefaultWorkflowRegistry,
265
352
  */
266
353
  failAsyncActivity(token: string, error: unknown): Promise<void>;
267
354
  cancel(workflowId: string): Promise<void>;
355
+ /**
356
+ * Suspend a running workflow without terminating it. The workflow transitions
357
+ * to the non-terminal `'suspended'` status, keeps its durable checkpoint, and
358
+ * is later resumable via {@link Engine.resume} (or `handle.resume()`). Unlike
359
+ * {@link Engine.cancel}, this does not run cancel handlers and does not settle
360
+ * the result promise — `handle.result()` stays pending until a later `resume()`
361
+ * drives the run to completion.
362
+ *
363
+ * Suspension is client-driven preemption, so a suspended workflow is NOT
364
+ * auto-recovered by {@link Engine.recoverAll}; resume it explicitly. Calling
365
+ * `suspend` on a workflow that is not running (already terminal, or never
366
+ * started) is a no-op.
367
+ */
368
+ suspend(workflowId: string): Promise<void>;
268
369
  timeout(workflowId: string): Promise<void>;
269
370
  get(workflowId: string): Promise<WorkflowState | null>;
371
+ getCurrentCheckpointStep(workflowId: string): Promise<number | null>;
270
372
  getAttributes(workflowId: string): Promise<Record<string, SearchAttributeValue> | null>;
271
373
  setAttributes(workflowId: string, attributes: Record<string, SearchAttributeValue>): Promise<void>;
272
374
  addTags(workflowId: string, ...tags: string[]): Promise<void>;
@@ -287,7 +389,23 @@ export declare class Engine<TWorkflows extends object = DefaultWorkflowRegistry,
287
389
  timeout?: number;
288
390
  idempotencyKey?: string;
289
391
  }): Promise<CoordinatedUpdateResult>;
392
+ /**
393
+ * Synchronous teardown (`using engine = ...`). Pending inline launches that
394
+ * have not yet run are **discarded**, not executed. When you need queued
395
+ * starts to complete before teardown — or want a clean event loop with no
396
+ * dangling deferred-launch macrotask — prefer {@link Engine[Symbol.asyncDispose]}
397
+ * via `await using`.
398
+ */
290
399
  [Symbol.dispose](): void;
400
+ /**
401
+ * Async teardown (`await using engine = ...`). Drains pending inline launches
402
+ * so each queued workflow completes its first turn before disposal, leaving no
403
+ * deferred-launch macrotask to fire against torn-down state. The drain is
404
+ * bounded (a pass cap and the abort signal); in the pathological case where it
405
+ * cannot converge, anything still queued is discarded by the synchronous
406
+ * teardown that always follows. Prefer this over the synchronous
407
+ * {@link Engine[Symbol.dispose]} in async contexts and tests.
408
+ */
291
409
  [Symbol.asyncDispose](): Promise<void>;
292
410
  get storage(): WeftStorage;
293
411
  get scheduler(): Scheduler;
@@ -3,6 +3,7 @@ import {
3
3
  ActivityRegistry
4
4
  } from "../activity-registry.js";
5
5
  import { AtomicState } from "../atomic-state.js";
6
+ import { deserializeCheckpoint } from "../checkpoint.js";
6
7
  import { createHandleCacheFinalizer } from "../engine-helpers.js";
7
8
  import { ReviewCoordinator } from "../review/index.js";
8
9
  import { Scheduler } from "../scheduler.js";
@@ -63,6 +64,8 @@ import {
63
64
  import {
64
65
  createCleanupIntervalTick,
65
66
  createQueuedInlineWorkflowStartHandler,
67
+ createSecondInstanceDetectorResolver,
68
+ drainQueuedInlineWorkflowStartsForEngine,
66
69
  isActivityDefinition
67
70
  } from "./engine-runtime-helpers.js";
68
71
  import { EngineCreateNameMismatchError } from "./errors.js";
@@ -70,7 +73,7 @@ import {
70
73
  createWorkflowHandleWithResultPromise as createWorkflowHandleWithResultPromiseFromInternals,
71
74
  getWorkflowResultPromise as getWorkflowResultPromiseFromInternals
72
75
  } from "./handle-result.js";
73
- import { HANDLE_RESULT_PROMISE, ScheduleHandle, WorkflowHandle } from "./handles.js";
76
+ import { HANDLE_RESULT_PROMISE, WorkflowHandle } from "./handles.js";
74
77
  import { hasQueuedInlineWorkflowStart } from "./inline-launch-queue.js";
75
78
  import {
76
79
  handleStrategyMessage as handleStrategyMessageFromInternals,
@@ -81,6 +84,8 @@ import {
81
84
  fork as forkFromLifecycle,
82
85
  recoverAll as recoverAllFromLifecycle,
83
86
  resume as resumeFromLifecycle,
87
+ startOrSignal as startOrSignalFromLifecycle,
88
+ startWithIdempotency as startWithIdempotencyFromLifecycle,
84
89
  startWorkflow as startWorkflowFromLifecycle
85
90
  } from "./lifecycle.js";
86
91
  import {
@@ -90,6 +95,7 @@ import {
90
95
  removeTags as removeWorkflowTags,
91
96
  setAttributes as setWorkflowAttributes
92
97
  } from "./listing.js";
98
+ import { getOffloadFromInternals } from "./operations-data.js";
93
99
  import { getStreamChunksFromInternals } from "./operations-stream.js";
94
100
  import {
95
101
  handleTimerFired as handleTimerFiredFromInternals
@@ -113,6 +119,7 @@ import {
113
119
  listReviews as listReviewsFromInternals,
114
120
  submitReview as submitReviewFromInternals
115
121
  } from "./reviews.js";
122
+ import { ScheduleHandle } from "./schedule-handle.js";
116
123
  import {
117
124
  cancelSchedule as cancelScheduleFromInternals,
118
125
  listSchedules as listSchedulesFromInternals,
@@ -122,6 +129,10 @@ import {
122
129
  toScheduleSummary,
123
130
  updateSchedule as updateScheduleFromInternals
124
131
  } from "./schedules.js";
132
+ import {
133
+ createSecondInstanceDetectionTick,
134
+ createSecondInstanceDetector
135
+ } from "./second-instance-detector.js";
125
136
  import { signal as signalWorkflow } from "./signals.js";
126
137
  import { loadScheduleState, loadWorkflowState } from "./storage-io.js";
127
138
  import {
@@ -133,6 +144,7 @@ import {
133
144
  cancelWorkflow as cancelWorkflowFromTermination,
134
145
  cleanupWaiters as cleanupWaitersFromTermination,
135
146
  finalizePendingTimelineEntry,
147
+ suspendWorkflow as suspendWorkflowFromTermination,
136
148
  timeoutWorkflow as timeoutWorkflowFromTermination
137
149
  } from "./termination.js";
138
150
  import {
@@ -158,13 +170,17 @@ export {
158
170
  BulkOperationConfirmationError,
159
171
  EngineCreateNameMismatchError,
160
172
  EngineDisposedError,
173
+ IdempotencyKeyPurgedError,
161
174
  PersistedDataIncompatibleError,
175
+ StartOrSignalConflictError,
162
176
  WorkflowAlreadyExistsError,
163
177
  WorkflowNotFoundError,
164
178
  WorkflowNotRegisteredError,
179
+ WorkflowSuspendNotSupportedError,
165
180
  WorkflowTypeNotRegisteredForRecoveryError
166
181
  } from "./errors.js";
167
- export { HANDLE_RESULT_PROMISE, ScheduleHandle, WorkflowHandle } from "./handles.js";
182
+ export { HANDLE_RESULT_PROMISE, WorkflowHandle } from "./handles.js";
183
+ export { ScheduleHandle } from "./schedule-handle.js";
168
184
  export {
169
185
  clearEngineLeakWarningTokenForTesting,
170
186
  getEngineLeakCollectionCountForTesting,
@@ -174,13 +190,13 @@ export {
174
190
  shouldEmitEngineLeakWarningForTesting
175
191
  } from "./engine-leak-warnings.js";
176
192
  export { assertCompatiblePersistedDataVersion };
177
- export const ENGINE_PARKED_WORKFLOW_COUNT_FOR_TESTING = Symbol("engineParkedWorkflowCountForTesting"), ENGINE_SIGNAL_WAITER_COUNT_FOR_TESTING = Symbol("engineSignalWaiterCountForTesting");
193
+ export const ENGINE_PARKED_WORKFLOW_COUNT_FOR_TESTING = Symbol("engineParkedWorkflowCountForTesting"), ENGINE_SIGNAL_WAITER_COUNT_FOR_TESTING = Symbol("engineSignalWaiterCountForTesting"), ENGINE_SLEEP_RESOLVER_COUNT_FOR_TESTING = Symbol("engineSleepResolverCountForTesting");
178
194
 
179
195
  export class Engine extends EventTarget {
180
196
  static async create(options) {
181
197
  const engine = new Engine(options);
182
198
  try {
183
- await assertCompatiblePersistedDataVersion(getInternals(engine).storage, options.allowLegacyData === void 0 ? {} : { allowLegacyData: options.allowLegacyData });
199
+ await assertCompatiblePersistedDataVersion(getInternals(engine).storage);
184
200
  for (const [name, definition] of definitionEntries(options.activities)) {
185
201
  if (name !== definition.name)
186
202
  throw new EngineCreateNameMismatchError("activity", name, definition.name);
@@ -213,7 +229,8 @@ export class Engine extends EventTarget {
213
229
  getRegistration: getInternals(this).registrations.get.bind(getInternals(this).registrations),
214
230
  getComposedWorkflowInterceptor: () => getComposedWorkflowInterceptor(getInternals(this)),
215
231
  resolveWorkflowType: this.#resolveWorkflowTypeTarget.bind(this),
216
- registerCancelHandler: (workflowId, handler) => registerCancelHandler(getInternals(this), workflowId, handler)
232
+ registerCancelHandler: (workflowId, handler) => registerCancelHandler(getInternals(this), workflowId, handler),
233
+ getWorkflowServices: (workflowId) => getInternals(this).workflowServices.get(workflowId)
217
234
  });
218
235
  getInternals(this).storage = storage;
219
236
  getInternals(this).abortController = new AbortController;
@@ -261,6 +278,7 @@ export class Engine extends EventTarget {
261
278
  if (queuedInlineWorkflowStartChannel !== null)
262
279
  queuedInlineWorkflowStartChannel.port1.onmessage = createQueuedInlineWorkflowStartHandler(weakEngine, queuedInlineWorkflowStartChannel);
263
280
  getInternals(this).heartbeatDetails = new Map;
281
+ getInternals(this).workflowServices = new Map;
264
282
  getInternals(this).pendingAsyncActivities = new Map;
265
283
  getInternals(this).pendingStarts = new Set;
266
284
  getInternals(this).pendingScheduleCreations = new Set;
@@ -280,6 +298,7 @@ export class Engine extends EventTarget {
280
298
  const cleanupIntervalDisposalTracker = {
281
299
  disposed: !1,
282
300
  cleanupInterval: null,
301
+ secondInstanceDetectionInterval: null,
283
302
  testToken: consumeNextEngineLeakWarningTokenForTesting()
284
303
  }, cleanupInterval = setInterval(createCleanupIntervalTick(weakEngine, cleanupIntervalDisposalTracker), 60000);
285
304
  cleanupIntervalDisposalTracker.cleanupInterval = cleanupInterval;
@@ -289,6 +308,8 @@ export class Engine extends EventTarget {
289
308
  getInternals(this).retentionSweepInterval = null;
290
309
  getInternals(this).retentionSweepInFlight = null;
291
310
  getInternals(this).nextRetentionSweepAt = null;
311
+ getInternals(this).secondInstanceDetectionInterval = null;
312
+ getInternals(this).secondInstanceDetector = null;
292
313
  getInternals(this).eventLogHeads = new Map;
293
314
  getInternals(this).workflowFeedListeners = new Map;
294
315
  getInternals(this).workflowVersionTuples = new Map;
@@ -296,6 +317,25 @@ export class Engine extends EventTarget {
296
317
  getInternals(this).strategy.onMessage(this.#handleStrategyMessage.bind(this));
297
318
  getInternals(this).alertManager = createAlertManagerForEngine(this, options?.alerts, getNow);
298
319
  this.#ensureRetentionSweepInterval();
320
+ this.#startSecondInstanceDetection();
321
+ }
322
+ #startSecondInstanceDetection() {
323
+ const internals = getInternals(this);
324
+ if (!internals.options.secondInstanceDetectionEnabled)
325
+ return;
326
+ const detector = createSecondInstanceDetector({
327
+ storage: internals.storage,
328
+ instanceId: crypto.randomUUID(),
329
+ getNow: internals.options.getNow,
330
+ intervalMs: internals.options.secondInstanceHeartbeatIntervalMs
331
+ });
332
+ internals.secondInstanceDetector = detector;
333
+ const tracker = internals.cleanupIntervalDisposalTracker;
334
+ if (tracker === null)
335
+ return;
336
+ const weakEngine = new WeakRef(this), detectionInterval = setInterval(createSecondInstanceDetectionTick(createSecondInstanceDetectorResolver(weakEngine), tracker), internals.options.secondInstanceHeartbeatIntervalMs);
337
+ internals.secondInstanceDetectionInterval = detectionInterval;
338
+ tracker.secondInstanceDetectionInterval = detectionInterval;
299
339
  }
300
340
  #hasConfiguredRetention() {
301
341
  return hasConfiguredRetention(getInternals(this));
@@ -397,8 +437,19 @@ export class Engine extends EventTarget {
397
437
  return getInternals(this).activityRegistry.listDefinitions();
398
438
  }
399
439
  async start(type, input, options) {
440
+ if (options?.idempotencyKey !== void 0)
441
+ return startWithIdempotencyFromLifecycle(getInternals(this), type, input, options, this.#createLifecycleCallbacks());
400
442
  return startWorkflowFromLifecycle(getInternals(this), type, input, options, void 0, this.#createLifecycleCallbacks());
401
443
  }
444
+ async startOrSignal(type, input, signal, options) {
445
+ return startOrSignalFromLifecycle(getInternals(this), type, input, signal, options, this.#createStartOrSignalCallbacks());
446
+ }
447
+ #createStartOrSignalCallbacks() {
448
+ return {
449
+ ...this.#createLifecycleCallbacks(),
450
+ signalExistingWorkflow: (workflowId, signalName, payload, signalId) => this.signal(workflowId, signalName, payload, { signalId })
451
+ };
452
+ }
402
453
  getHandle(workflowId) {
403
454
  const entry = getInternals(this).handleCache.get(workflowId);
404
455
  if (entry) {
@@ -478,6 +529,9 @@ export class Engine extends EventTarget {
478
529
  [ENGINE_SIGNAL_WAITER_COUNT_FOR_TESTING]() {
479
530
  return getInternals(this).signalWaiters.size;
480
531
  }
532
+ [ENGINE_SLEEP_RESOLVER_COUNT_FOR_TESTING]() {
533
+ return getInternals(this).sleepResolvers.size;
534
+ }
481
535
  async signal(workflowId, nameOrDefinition, payload, options) {
482
536
  return signalWorkflow(getInternals(this), workflowId, messageName(nameOrDefinition), payload, {
483
537
  loadWorkflowState: (id) => loadWorkflowState(getInternals(this), id),
@@ -496,6 +550,9 @@ export class Engine extends EventTarget {
496
550
  async getStreamChunks(workflowId, key, options) {
497
551
  return getStreamChunksFromInternals(getInternals(this), workflowId, key, options);
498
552
  }
553
+ async getOffload(workflowId, key) {
554
+ return getOffloadFromInternals(getInternals(this), workflowId, key);
555
+ }
499
556
  async fork(sourceWorkflowId, options) {
500
557
  return forkFromLifecycle(getInternals(this), sourceWorkflowId, options, this.#createLifecycleCallbacks());
501
558
  }
@@ -515,6 +572,9 @@ export class Engine extends EventTarget {
515
572
  async cancel(workflowId) {
516
573
  await cancelWorkflowFromTermination(getInternals(this), workflowId, this.#createTerminationCallbacks());
517
574
  }
575
+ async suspend(workflowId) {
576
+ await suspendWorkflowFromTermination(getInternals(this), workflowId, this.#createTerminationCallbacks());
577
+ }
518
578
  async timeout(workflowId) {
519
579
  await timeoutWorkflowFromTermination(getInternals(this), workflowId, this.#createTerminationCallbacks());
520
580
  }
@@ -524,6 +584,15 @@ export class Engine extends EventTarget {
524
584
  return { ...state, status: "pending" };
525
585
  return state;
526
586
  }
587
+ async getCurrentCheckpointStep(workflowId) {
588
+ const inMemory = getInternals(this).checkpoints.get(workflowId);
589
+ if (inMemory !== void 0)
590
+ return inMemory.step;
591
+ const bytes = await getInternals(this).storage.get(KEYS.checkpoint(workflowId));
592
+ if (bytes === null)
593
+ return null;
594
+ return deserializeCheckpoint(bytes).step;
595
+ }
527
596
  async getAttributes(workflowId) {
528
597
  return getWorkflowAttributes(getInternals(this), workflowId);
529
598
  }
@@ -581,6 +650,14 @@ export class Engine extends EventTarget {
581
650
  disposeEngine(getInternals(this));
582
651
  }
583
652
  async[Symbol.asyncDispose]() {
653
+ if (!getInternals(this).disposed) {
654
+ try {
655
+ await drainQueuedInlineWorkflowStartsForEngine(this);
656
+ } finally {
657
+ this[Symbol.dispose]();
658
+ }
659
+ return;
660
+ }
584
661
  this[Symbol.dispose]();
585
662
  }
586
663
  get storage() {