@lostgradient/weft 0.11.0 → 0.13.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 (242) hide show
  1. package/README.md +1 -1
  2. package/dist/cli/api.d.ts +2 -0
  3. package/dist/cli/api.js +3 -4
  4. package/dist/cli/generated/operation-catalog.snapshot.json +5167 -0
  5. package/dist/cli/generated/operation-client.generated.d.ts +119 -10
  6. package/dist/cli/generated/operation-client.generated.js +74 -1
  7. package/dist/cli/index.js +1 -0
  8. package/dist/cli/operation-catalog-snapshot.d.ts +11 -2
  9. package/dist/cli/operation-catalog-snapshot.js +14 -5
  10. package/dist/cli/operation-client-runtime.d.ts +29 -0
  11. package/dist/cli-main.js +227 -401
  12. package/dist/client/client-storage.d.ts +24 -0
  13. package/dist/client/client-storage.js +14 -0
  14. package/dist/client/handle-delegation.d.ts +3 -3
  15. package/dist/client/handle-delegation.js +2 -2
  16. package/dist/client/http-client-requests.d.ts +5 -3
  17. package/dist/client/http-client-requests.js +20 -2
  18. package/dist/client/http-client-storage.d.ts +3 -0
  19. package/dist/client/http-client-storage.js +110 -0
  20. package/dist/client/http-client.d.ts +9 -6
  21. package/dist/client/http-client.js +13 -18
  22. package/dist/client/http-operations.d.ts +3 -1
  23. package/dist/client/http-operations.js +84 -2
  24. package/dist/client/http-request.d.ts +35 -6
  25. package/dist/client/http-request.js +31 -10
  26. package/dist/client/in-process-operations.d.ts +2 -2
  27. package/dist/client/in-process-operations.js +2 -2
  28. package/dist/client/index.d.ts +6 -0
  29. package/dist/client/index.js +18 -0
  30. package/dist/client/interface.d.ts +26 -26
  31. package/dist/client/local.d.ts +9 -6
  32. package/dist/client/local.js +9 -4
  33. package/dist/client/search-params.js +10 -6
  34. package/dist/client/start-body.d.ts +3 -1
  35. package/dist/client/start-body.js +9 -0
  36. package/dist/connection.d.ts +8 -0
  37. package/dist/connection.js +41 -17
  38. package/dist/core/bulk-workflow-filter.d.ts +2 -1
  39. package/dist/core/bulk-workflow-filter.js +5 -1
  40. package/dist/core/engine/async-activity-records.d.ts +12 -1
  41. package/dist/core/engine/async-activity-records.js +81 -2
  42. package/dist/core/engine/bulk-operations-purge.js +16 -1
  43. package/dist/core/engine/bulk-operations-shared.js +2 -0
  44. package/dist/core/engine/callback-creators-core.js +2 -1
  45. package/dist/core/engine/callback-creators-schedule.d.ts +2 -1
  46. package/dist/core/engine/callback-creators-schedule.js +3 -3
  47. package/dist/core/engine/checkpoint-reads.js +11 -1
  48. package/dist/core/engine/child-workflow.js +15 -6
  49. package/dist/core/engine/construction.js +3 -0
  50. package/dist/core/engine/disposal.js +2 -0
  51. package/dist/core/engine/engine-internal-types.d.ts +4 -1
  52. package/dist/core/engine/fenced-write.d.ts +5 -3
  53. package/dist/core/engine/finalizer-status.d.ts +10 -0
  54. package/dist/core/engine/finalizer-status.js +82 -0
  55. package/dist/core/engine/index.d.ts +69 -2
  56. package/dist/core/engine/index.js +71 -8
  57. package/dist/core/engine/internals.d.ts +3 -0
  58. package/dist/core/engine/lease-health.d.ts +64 -0
  59. package/dist/core/engine/lease-health.js +0 -0
  60. package/dist/core/engine/lease-manager.d.ts +4 -2
  61. package/dist/core/engine/lease-manager.js +32 -4
  62. package/dist/core/engine/lifecycle/recovered-services.js +0 -2
  63. package/dist/core/engine/lifecycle/shared.d.ts +28 -0
  64. package/dist/core/engine/lifecycle/start-batch.js +12 -0
  65. package/dist/core/engine/lifecycle/start.d.ts +1 -1
  66. package/dist/core/engine/lifecycle/start.js +34 -5
  67. package/dist/core/engine/lifecycle/transition.js +19 -9
  68. package/dist/core/engine/operations-coordination.d.ts +1 -12
  69. package/dist/core/engine/operations-coordination.js +57 -10
  70. package/dist/core/engine/operations-speculate.d.ts +1 -1
  71. package/dist/core/engine/operations-speculate.js +19 -5
  72. package/dist/core/engine/registration.js +14 -0
  73. package/dist/core/engine/schedule-handle.d.ts +4 -4
  74. package/dist/core/engine/schedule-handle.js +2 -2
  75. package/dist/core/engine/schedule-overlap.d.ts +11 -0
  76. package/dist/core/engine/schedule-overlap.js +50 -0
  77. package/dist/core/engine/schedule-run.d.ts +11 -3
  78. package/dist/core/engine/schedule-run.js +27 -4
  79. package/dist/core/engine/schedule-timer.js +10 -1
  80. package/dist/core/engine/schedules.d.ts +5 -4
  81. package/dist/core/engine/schedules.js +62 -56
  82. package/dist/core/engine/state-utilities.js +1 -1
  83. package/dist/core/engine/storage-io.d.ts +12 -0
  84. package/dist/core/engine/storage-io.js +49 -10
  85. package/dist/core/engine/termination/finalizer-claim.d.ts +8 -6
  86. package/dist/core/engine/termination/finalizer-claim.js +2 -1
  87. package/dist/core/engine/termination/finalizer.js +10 -14
  88. package/dist/core/engine/timeline-coordinator-constants.d.ts +3 -0
  89. package/dist/core/engine/timeline-coordinator-constants.js +1 -0
  90. package/dist/core/engine/timeline-coordinator-detail.d.ts +13 -0
  91. package/dist/core/engine/timeline-coordinator-detail.js +76 -0
  92. package/dist/core/engine/validation/schedule-options.d.ts +8 -1
  93. package/dist/core/engine/validation/schedule-options.js +18 -0
  94. package/dist/core/engine/validation/schedule.d.ts +1 -1
  95. package/dist/core/engine/validation/schedule.js +27 -3
  96. package/dist/core/engine/validation.js +66 -2
  97. package/dist/core/engine/workflow-state-stream.d.ts +5 -1
  98. package/dist/core/engine/workflow-state-stream.js +37 -14
  99. package/dist/core/events/schedule-events.d.ts +3 -4
  100. package/dist/core/events/workflow-events.d.ts +2 -1
  101. package/dist/core/list-filter-validation.d.ts +3 -0
  102. package/dist/core/list-filter-validation.js +11 -0
  103. package/dist/core/registry-snapshot.d.ts +32 -0
  104. package/dist/core/registry-snapshot.js +28 -0
  105. package/dist/core/review/index.d.ts +4 -4
  106. package/dist/core/types/activity.d.ts +52 -5
  107. package/dist/core/types/bulk.d.ts +1 -0
  108. package/dist/core/types/definition-schema-to-json.d.ts +11 -0
  109. package/dist/core/types/definition-schema-to-json.js +11 -2
  110. package/dist/core/types/list-options.d.ts +6 -0
  111. package/dist/core/types/options.d.ts +6 -5
  112. package/dist/core/types/reviews.d.ts +2 -2
  113. package/dist/core/types/schedules.d.ts +45 -2
  114. package/dist/core/types/services-resolution.d.ts +5 -5
  115. package/dist/core/types/state.d.ts +64 -0
  116. package/dist/core/types/workflow-log.d.ts +3 -2
  117. package/dist/core/types/workflow-observability.d.ts +50 -0
  118. package/dist/core/types/workflow-observability.js +0 -0
  119. package/dist/core/types/workflow-registry.d.ts +7 -0
  120. package/dist/core/types.d.ts +1 -0
  121. package/dist/core/types.js +1 -0
  122. package/dist/http.js +2 -0
  123. package/dist/index.d.ts +4 -3
  124. package/dist/indexeddb.js +1 -0
  125. package/dist/json-schema.js +3 -3
  126. package/dist/mcp/cli.js +63 -61
  127. package/dist/mcp/http.d.ts +7 -2
  128. package/dist/mcp/session.d.ts +3 -3
  129. package/dist/mcp/session.js +3 -2
  130. package/dist/mcp/stdio.d.ts +7 -2
  131. package/dist/observability/index.js +2 -2
  132. package/dist/runtime/portable.d.ts +33 -0
  133. package/dist/runtime/portable.js +17 -6
  134. package/dist/server/access-policy-metadata.d.ts +92 -0
  135. package/dist/server/access-policy-metadata.js +78 -0
  136. package/dist/server/engine-event-feed-backend.d.ts +22 -3
  137. package/dist/server/fault-to-http.d.ts +3 -7
  138. package/dist/server/fault-to-http.js +2 -34
  139. package/dist/server/fleet-event-feed.d.ts +65 -0
  140. package/dist/server/handler/index.d.ts +11 -2
  141. package/dist/server/handler/index.js +8 -1
  142. package/dist/server/handler/route-dispatch.d.ts +10 -3
  143. package/dist/server/handler/route-dispatch.js +19 -4
  144. package/dist/server/handler.js +1 -56
  145. package/dist/server/index.d.ts +13 -2
  146. package/dist/server/index.js +88 -64
  147. package/dist/server/interactive-operations.js +2 -0
  148. package/dist/server/openapi-error-responses.js +55 -4
  149. package/dist/server/openapi.d.ts +3 -1
  150. package/dist/server/openapi.js +4 -0
  151. package/dist/server/openrpc-document-schema.d.ts +64 -0
  152. package/dist/server/openrpc-document-schema.js +6 -26
  153. package/dist/server/openrpc.d.ts +4 -0
  154. package/dist/server/openrpc.js +9 -33
  155. package/dist/server/operation-fault.d.ts +24 -7
  156. package/dist/server/operation-fault.js +65 -13
  157. package/dist/server/operations/aggregate-workflows.d.ts +6 -0
  158. package/dist/server/operations/aggregate-workflows.js +3 -2
  159. package/dist/server/operations/async-activity.d.ts +14 -0
  160. package/dist/server/operations/async-activity.js +88 -4
  161. package/dist/server/operations/bulk-filter-helpers.d.ts +3 -0
  162. package/dist/server/operations/bulk-filter-helpers.js +27 -3
  163. package/dist/server/operations/bulk-mutate-workflow-tags.d.ts +6 -0
  164. package/dist/server/operations/bulk-signal-workflows.d.ts +6 -0
  165. package/dist/server/operations/get-stream-chunks.js +2 -7
  166. package/dist/server/operations/get-system-lease.d.ts +19 -0
  167. package/dist/server/operations/get-system-lease.js +48 -0
  168. package/dist/server/operations/get-workflow-observability.d.ts +24 -0
  169. package/dist/server/operations/get-workflow-observability.js +80 -0
  170. package/dist/server/operations/get-workflow-result.js +2 -2
  171. package/dist/server/operations/get-workflow.d.ts +2 -2
  172. package/dist/server/operations/list-filter-query-extractor.js +15 -0
  173. package/dist/server/operations/list-workflows.d.ts +6 -0
  174. package/dist/server/operations/list-workflows.js +12 -3
  175. package/dist/server/operations/operation-helpers.d.ts +9 -18
  176. package/dist/server/operations/operation-helpers.js +5 -23
  177. package/dist/server/operations/purge-workflows.d.ts +3 -0
  178. package/dist/server/operations/recover-all.js +2 -1
  179. package/dist/server/operations/single-workflow-tag-mutation.js +2 -2
  180. package/dist/server/operations/sse-stream.js +2 -7
  181. package/dist/server/operations/storage-capabilities.d.ts +34 -0
  182. package/dist/server/operations/storage-capabilities.js +38 -0
  183. package/dist/server/operations/storage.js +8 -17
  184. package/dist/server/operations/stream-workflow-sse.js +3 -9
  185. package/dist/server/operations/submit-review-decision.d.ts +2 -0
  186. package/dist/server/operations/submit-review-decision.js +19 -2
  187. package/dist/server/operations/update-schedule.d.ts +8 -0
  188. package/dist/server/operations/update-schedule.js +70 -7
  189. package/dist/server/operations/workflow-events-sse.js +2 -2
  190. package/dist/server/replay-live-feed-internals.d.ts +19 -0
  191. package/dist/server/replay-live-feed-internals.js +76 -0
  192. package/dist/server/rest-binding.d.ts +7 -6
  193. package/dist/server/rest-bindings.d.ts +2 -13
  194. package/dist/server/rest-bindings.js +19 -10
  195. package/dist/server/runtime/authentication-bridge.d.ts +2 -2
  196. package/dist/server/serve-internals.js +3 -3
  197. package/dist/server/task-queue.d.ts +7 -5
  198. package/dist/server/workflow-event-feed.d.ts +100 -11
  199. package/dist/server/workflow-event-feed.js +1 -140
  200. package/dist/service-worker/index.d.ts +8 -2
  201. package/dist/service-worker/index.js +37 -56
  202. package/dist/service-worker/setup.d.ts +6 -2
  203. package/dist/storage/auto.js +1 -1
  204. package/dist/storage/bun-sql.js +3 -3
  205. package/dist/storage/capabilities.d.ts +1 -1
  206. package/dist/storage/cloudflare-durable-object-sql.d.ts +75 -0
  207. package/dist/storage/cloudflare-durable-object-sql.js +0 -0
  208. package/dist/storage/cloudflare-value-codec.d.ts +67 -0
  209. package/dist/storage/cloudflare-value-codec.js +46 -0
  210. package/dist/storage/cloudflare.d.ts +140 -0
  211. package/dist/storage/cloudflare.js +127 -0
  212. package/dist/storage/compressed-storage.js +1 -1
  213. package/dist/storage/http.js +200 -2
  214. package/dist/storage/index.d.ts +8 -1
  215. package/dist/storage/indexeddb.js +311 -1
  216. package/dist/storage/interface.d.ts +16 -4
  217. package/dist/storage/interface.js +1 -1
  218. package/dist/storage/key-prefixes.d.ts +1 -1
  219. package/dist/storage/key-prefixes.js +4 -0
  220. package/dist/storage/lmdb.js +1 -1
  221. package/dist/storage/memory.js +1 -1
  222. package/dist/storage/neon.js +4 -4
  223. package/dist/storage/node-sqlite.js +3 -3
  224. package/dist/storage/postgres-key-value-queries.d.ts +6 -0
  225. package/dist/storage/postgres-key-value-queries.js +2 -3
  226. package/dist/storage/postgres.js +4 -4
  227. package/dist/storage/resolve.js +1 -1
  228. package/dist/storage/scoped-storage.js +1 -1
  229. package/dist/storage/sql-identifier.d.ts +35 -0
  230. package/dist/storage/sql-identifier.js +5 -0
  231. package/dist/storage/sqlite-key-value-queries.d.ts +11 -6
  232. package/dist/storage/sqlite-key-value-queries.js +3 -3
  233. package/dist/storage/testing.js +1 -1
  234. package/dist/storage/turso.js +2 -2
  235. package/dist/storage/typed-storage.js +138 -2
  236. package/dist/storage/web-extension.js +298 -1
  237. package/dist/testing/index.js +21 -60
  238. package/dist/version.d.ts +1 -1
  239. package/dist/version.js +1 -1
  240. package/dist/web-extension.js +1 -0
  241. package/dist/worker/protocol.js +1 -1
  242. package/package.json +8 -3
@@ -21,6 +21,12 @@ import {
21
21
  untrackWaiterKey
22
22
  } from "./signals.js";
23
23
  import { callActivityFunction } from "./state-utilities.js";
24
+ import {
25
+ parallelTimelineDetails,
26
+ raceTimelineDetails,
27
+ recordTimelineBranches,
28
+ runAllTimelineDetails
29
+ } from "./timeline-coordinator-detail.js";
24
30
  export function assertSupportedSignalBranches(operations) {
25
31
  const seen = new Set, walk = (subOperations) => {
26
32
  for (const subOperation of subOperations)
@@ -74,6 +80,7 @@ export async function processParallelOperation(internals, workflowId, operation,
74
80
  await finalizeFulfilledSlots(slots);
75
81
  const entry = buildEntryFromSlots("all", slots);
76
82
  writePartialEntry(internals, workflowId, operation.step, entry);
83
+ recordTimelineBranches(internals, workflowId, parallelTimelineDetails(operation.operations, slots));
77
84
  if (hasFirstError)
78
85
  throw firstError;
79
86
  return valuesFromSlots(slots);
@@ -111,8 +118,22 @@ export async function processRaceOperation(internals, workflowId, operation, cal
111
118
  return callbacks.runOperationWithResult(workflowId, operation, async () => {
112
119
  assertSupportedSignalBranches(operation.operations);
113
120
  assertValidRaceBranchNames(operation);
114
- const winner = await executeRaceSubOperations(internals, workflowId, operation.operations, (subOperation, signal) => callbacks.executeSubOperation(workflowId, subOperation, signal), void 0, (winnerIndex, value) => wrapRaceWinner(operation, winnerIndex, value));
115
- return finalizeAndUnwrap(winner);
121
+ let winnerIndex, winnerError;
122
+ try {
123
+ const winner = await executeRaceSubOperations(internals, workflowId, operation.operations, (subOperation, signal) => callbacks.executeSubOperation(workflowId, subOperation, signal), void 0, (selectedIndex, value) => wrapRaceWinner(operation, selectedIndex, value), (selectedIndex, error) => {
124
+ winnerIndex = selectedIndex;
125
+ winnerError = error;
126
+ });
127
+ try {
128
+ return await finalizeAndUnwrap(winner);
129
+ } catch (error) {
130
+ winnerError = error;
131
+ throw error;
132
+ }
133
+ } finally {
134
+ if (winnerIndex !== void 0)
135
+ recordTimelineBranches(internals, workflowId, raceTimelineDetails(operation.operations, winnerIndex, operation.branchNames, winnerError));
136
+ }
116
137
  });
117
138
  }
118
139
  export function assertValidRaceBranchNames(operation) {
@@ -123,21 +144,46 @@ export function wrapRaceWinner(operation, winnerIndex, value) {
123
144
  const key = operation.branchNames?.[winnerIndex];
124
145
  return key === void 0 ? value : createKeyedRaceResultEnvelope(key, value);
125
146
  }
126
- export async function executeRaceSubOperations(internals, workflowId, operations, execute, parentSignal, wrapWinner) {
147
+ function createRaceWinnerObservers(wrapWinner, observeWinner) {
148
+ const observers = {};
149
+ if (wrapWinner !== void 0)
150
+ observers.wrapWinner = wrapWinner;
151
+ if (observeWinner !== void 0)
152
+ observers.observeWinner = observeWinner;
153
+ return observers;
154
+ }
155
+ async function settleRaceBranch(operation, index, signal, execute) {
156
+ try {
157
+ return { index, status: "fulfilled", value: await execute(operation, signal) };
158
+ } catch (error) {
159
+ return { error, index, status: "rejected" };
160
+ }
161
+ }
162
+ function resolveRaceSettlement(settlement, observers) {
163
+ if (settlement.status === "rejected") {
164
+ observers.observeWinner?.(settlement.index, settlement.error);
165
+ throw settlement.error;
166
+ }
167
+ observers.observeWinner?.(settlement.index);
168
+ return observers.wrapWinner === void 0 ? settlement.value : observers.wrapWinner(settlement.index, settlement.value);
169
+ }
170
+ async function executeBufferedRaceWinner(bufferedSignal, signal, execute, observers) {
171
+ const settlement = await settleRaceBranch(bufferedSignal.operation, bufferedSignal.index, signal, execute);
172
+ return resolveRaceSettlement(settlement, observers);
173
+ }
174
+ export async function executeRaceSubOperations(internals, workflowId, operations, execute, parentSignal, wrapWinner, observeWinner) {
127
175
  parentSignal?.throwIfAborted();
128
- const controller = new AbortController, abortFromParent = () => controller.abort(parentSignal?.reason);
176
+ const controller = new AbortController, abortFromParent = () => controller.abort(parentSignal?.reason), observers = createRaceWinnerObservers(wrapWinner, observeWinner);
129
177
  parentSignal?.addEventListener("abort", abortFromParent, { once: !0 });
130
178
  try {
131
179
  const bufferedSignal = await findBufferedSignalDrainBranch(internals, workflowId, operations);
132
180
  parentSignal?.throwIfAborted();
133
- if (bufferedSignal !== void 0) {
134
- const value = await execute(bufferedSignal.operation, controller.signal);
135
- return wrapWinner === void 0 ? value : wrapWinner(bufferedSignal.index, value);
136
- }
137
- const subOperations = operations.map((operation, index) => execute(operation, controller.signal).then((value) => ({ index, value })));
181
+ if (bufferedSignal !== void 0)
182
+ return await executeBufferedRaceWinner(bufferedSignal, controller.signal, execute, observers);
183
+ const subOperations = operations.map((operation, index) => settleRaceBranch(operation, index, controller.signal, execute));
138
184
  Promise.allSettled(subOperations);
139
185
  const winner = await Promise.race(subOperations);
140
- return wrapWinner === void 0 ? winner.value : wrapWinner(winner.index, winner.value);
186
+ return resolveRaceSettlement(winner, observers);
141
187
  } finally {
142
188
  parentSignal?.removeEventListener("abort", abortFromParent);
143
189
  controller.abort();
@@ -156,6 +202,7 @@ async function findBufferedSignalDrainBranch(internals, workflowId, operations)
156
202
  export async function processRunAllOperation(internals, workflowId, operation, callbacks) {
157
203
  return callbacks.runOperationWithResult(workflowId, operation, async () => {
158
204
  const branchNames = Object.keys(operation.branches), operationIds = branchNames.map((name) => `run-all:${operation.step}:${name}`), resumedSlots = extractResumedSlots(operation.resumedCacheEntry), resumedSlotsByName = mapResumedSlotsByName(resumedSlots, branchNames), branchesToRun = filterBranchesToRun(operation.branches, branchNames, resumedSlotsByName), { outcomes, hasFirstError, firstError } = await executeRunAllBranchesSettled(branchesToRun, (fn, input) => callActivityFunction(fn, input)), slots = mergeRunAllSlots(branchNames, operationIds, resumedSlotsByName, outcomes), entry = buildEntryFromSlots("run-all", slots, branchNames), partialEntryWritten = writePartialEntry(internals, workflowId, operation.step, entry);
205
+ recordTimelineBranches(internals, workflowId, runAllTimelineDetails(branchNames, slots, operation.branches));
159
206
  if (hasFirstError) {
160
207
  assertPartialFailurePersistenceSupported(partialEntryWritten, slots, "ctx.runAll", "worker execution mode");
161
208
  throw firstError;
@@ -12,5 +12,5 @@ export type SpeculateOperationCallbacks = {
12
12
  };
13
13
  export declare function processSpeculateOperation(internals: EngineInternals, workflowId: string, operation: SpeculateOperation, callbacks: SpeculateOperationCallbacks): Promise<void>;
14
14
  export declare function executeSpeculativeBranch(internals: EngineInternals, workflowId: string, operation: SpeculateOperation, callbacks: Pick<SpeculateOperationCallbacks, 'executeSubOperation'>): Promise<unknown>;
15
- export declare function driveSpeculativeGenerator(workflowId: string, generator: SpeculativeOperationGenerator, speculativeState: SpeculativeExecutionState, callbacks: Pick<SpeculateOperationCallbacks, 'executeSubOperation'>): Promise<unknown>;
15
+ export declare function driveSpeculativeGenerator(workflowId: string, generator: SpeculativeOperationGenerator, speculativeState: SpeculativeExecutionState, callbacks: Pick<SpeculateOperationCallbacks, 'executeSubOperation'>, observeChild?: (operation: ContextOperationRequest, index: number, error?: unknown) => void): Promise<unknown>;
16
16
  export {};
@@ -1,6 +1,10 @@
1
1
  import { finalizeAndUnwrap } from "./deferred-consume-envelope.js";
2
2
  import { assertSupportedSignalBranches } from "./operations-coordination.js";
3
3
  import { SpeculativeExecutionState } from "./speculative-execution-state.js";
4
+ import {
5
+ operationTimelineDetail,
6
+ recordTimelineSpeculation
7
+ } from "./timeline-coordinator-detail.js";
4
8
  export async function processSpeculateOperation(internals, workflowId, operation, callbacks) {
5
9
  return callbacks.runOperationWithResult(workflowId, operation, () => executeSpeculativeBranch(internals, workflowId, operation, callbacks));
6
10
  }
@@ -11,32 +15,42 @@ export async function executeSpeculativeBranch(internals, workflowId, operation,
11
15
  const parentContext = inlineStrategy.getContext(workflowId);
12
16
  if (!parentContext)
13
17
  throw Error(`No active inline context for workflow "${workflowId}"`);
14
- const speculativeContext = parentContext.createSpeculativeChild(), speculativeState = new SpeculativeExecutionState, generator = createSpeculativeOperationGenerator(operation, speculativeContext);
18
+ const speculativeContext = parentContext.createSpeculativeChild(), speculativeState = new SpeculativeExecutionState, generator = createSpeculativeOperationGenerator(operation, speculativeContext), children = [];
15
19
  try {
16
- const result = await driveSpeculativeGenerator(workflowId, generator, speculativeState, callbacks);
20
+ const result = await driveSpeculativeGenerator(workflowId, generator, speculativeState, callbacks, (child, index, error) => {
21
+ children.push(operationTimelineDetail(child, index, error === void 0 ? "fulfilled" : "rejected", error === void 0 ? {} : { error }));
22
+ });
17
23
  await speculativeState.drainVerifications();
18
24
  parentContext.commitSpeculativeChild(speculativeContext);
25
+ recordTimelineSpeculation(internals, workflowId, children, "committed");
19
26
  return result;
20
27
  } catch (error) {
21
- await speculativeState.rollback();
28
+ try {
29
+ await speculativeState.rollback();
30
+ } finally {
31
+ recordTimelineSpeculation(internals, workflowId, children, "rolled-back");
32
+ }
22
33
  throw error;
23
34
  }
24
35
  }
25
36
  function createSpeculativeOperationGenerator(operation, speculativeContext) {
26
37
  return operation.execute(speculativeContext);
27
38
  }
28
- export async function driveSpeculativeGenerator(workflowId, generator, speculativeState, callbacks) {
39
+ export async function driveSpeculativeGenerator(workflowId, generator, speculativeState, callbacks, observeChild) {
40
+ let childIndex = 0;
29
41
  const advance = async (lastResult, errorToThrow) => {
30
42
  const iterationResult = errorToThrow === void 0 ? await generator.next(lastResult) : await generator.throw(errorToThrow);
31
43
  if (iterationResult.done)
32
44
  return iterationResult.value;
33
- const nextOperation = iterationResult.value;
45
+ const nextOperation = iterationResult.value, currentChildIndex = childIndex++;
34
46
  try {
35
47
  if (nextOperation.type === "race" || nextOperation.type === "parallel")
36
48
  assertSupportedSignalBranches(nextOperation.operations);
37
49
  const nextResult = await callbacks.executeSubOperation(workflowId, nextOperation, void 0, speculativeState), finalizedResult = await finalizeAndUnwrap(nextResult);
50
+ observeChild?.(nextOperation, currentChildIndex);
38
51
  return advance(finalizedResult, void 0);
39
52
  } catch (error) {
53
+ observeChild?.(nextOperation, currentChildIndex, error);
40
54
  return advance(lastResult, error instanceof Error ? error : Error(String(error)));
41
55
  }
42
56
  };
@@ -59,8 +59,22 @@ function applyOptionalRegistrationFields(entry, registration) {
59
59
  function buildRegistrationEntry(name, registration) {
60
60
  const entry = buildBaseRegistrationEntry(name, registration);
61
61
  applyOptionalRegistrationFields(entry, registration);
62
+ if (isBuilderWorkflowDefinition(registration)) {
63
+ entry.signals = normalizeMessageDefinitions(name, "signal", registration.signals);
64
+ entry.updates = normalizeMessageDefinitions(name, "update", registration.updates);
65
+ entry.queries = normalizeMessageDefinitions(name, "query", registration.queries);
66
+ }
62
67
  return entry;
63
68
  }
69
+ function normalizeMessageDefinitions(workflowType, messageKind, definitions) {
70
+ const normalized = Object.create(null), sortedDefinitions = Object.values(definitions).toSorted((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
71
+ for (const definition of sortedDefinitions) {
72
+ if (Object.hasOwn(normalized, definition.name))
73
+ throw Error(`Duplicate ${messageKind} runtime name "${definition.name}" in workflow "${workflowType}"`);
74
+ normalized[definition.name] = definition;
75
+ }
76
+ return normalized;
77
+ }
64
78
  function commitWorkflowDefinition(internals, definition, callbacks) {
65
79
  const name = definition.name;
66
80
  validateWorkflowOrActivityName(name, "workflow");
@@ -1,4 +1,4 @@
1
- import type { ScheduleSpec, ScheduleSummary } from '../types.ts';
1
+ import type { ScheduleSpec, ScheduleSummary, ScheduleUpdateOptions } from '../types.ts';
2
2
  /**
3
3
  * Narrow engine view a {@link ScheduleHandle} delegates to. The full
4
4
  * {@link Engine} implements it; the handle only depends on these schedule
@@ -8,13 +8,13 @@ export interface ScheduleHandleEngine {
8
8
  pauseSchedule(scheduleId: string): Promise<void>;
9
9
  resumeSchedule(scheduleId: string): Promise<void>;
10
10
  cancelSchedule(scheduleId: string): Promise<void>;
11
- updateSchedule(scheduleId: string, newSpec: string | ScheduleSpec): Promise<void>;
11
+ updateSchedule(scheduleId: string, newSpec: string | ScheduleSpec, options?: ScheduleUpdateOptions): Promise<void>;
12
12
  getSchedule(scheduleId: string): Promise<ScheduleSummary | null>;
13
13
  }
14
14
  /**
15
15
  * Handle to a recurring schedule created by {@link Engine.schedule}. Use
16
16
  * `handle.pause()`, `handle.resume()`, `handle.cancel()`, or
17
- * `handle.update(cronExpression)` to manage the schedule lifecycle.
17
+ * `handle.update(spec, options?)` to manage the schedule lifecycle.
18
18
  * `handle.describe()` returns the current {@link ScheduleSummary}.
19
19
  *
20
20
  * @example
@@ -40,6 +40,6 @@ export declare class ScheduleHandle {
40
40
  pause(): Promise<void>;
41
41
  resume(): Promise<void>;
42
42
  cancel(): Promise<void>;
43
- update(newSpec: string | ScheduleSpec): Promise<void>;
43
+ update(newSpec: string | ScheduleSpec, options?: ScheduleUpdateOptions): Promise<void>;
44
44
  describe(): Promise<ScheduleSummary>;
45
45
  }
@@ -14,8 +14,8 @@ export class ScheduleHandle {
14
14
  async cancel() {
15
15
  await this.#engine.cancelSchedule(this.id);
16
16
  }
17
- async update(newSpec) {
18
- await this.#engine.updateSchedule(this.id, newSpec);
17
+ async update(newSpec, options) {
18
+ await this.#engine.updateSchedule(this.id, newSpec, options);
19
19
  }
20
20
  async describe() {
21
21
  const schedule = await this.#engine.getSchedule(this.id);
@@ -0,0 +1,11 @@
1
+ import type { ScheduleState } from '../types.ts';
2
+ import type { EngineInternals } from './internals.ts';
3
+ import type { ScheduledRunStartOptions } from './schedule-run.ts';
4
+ type ScheduleOverlapCallbacks = {
5
+ cancelWorkflow: (workflowId: string) => Promise<void>;
6
+ getWorkflowResult: (workflowId: string) => Promise<unknown>;
7
+ startScheduledRun: (state: ScheduleState, options?: ScheduledRunStartOptions) => Promise<string>;
8
+ };
9
+ export declare function applyBlockedScheduleOccurrence(internals: EngineInternals, state: ScheduleState, hasActiveWorkflow: boolean, callbacks: ScheduleOverlapCallbacks, occurrence?: number): Promise<ScheduleState>;
10
+ export declare function drainQueuedScheduleRun(state: ScheduleState, callbacks: Pick<ScheduleOverlapCallbacks, 'startScheduledRun'>, completedWorkflowId?: string): Promise<ScheduleState>;
11
+ export {};
@@ -0,0 +1,50 @@
1
+ import { clearScheduleCurrentWorkflow } from "./state-utilities.js";
2
+ export async function applyBlockedScheduleOccurrence(internals, state, hasActiveWorkflow, callbacks, occurrence) {
3
+ if (!hasActiveWorkflow)
4
+ return startScheduleRunInOccupiedSlot(state, callbacks, occurrence);
5
+ if (state.overlap === "cancel-running") {
6
+ if (state.currentWorkflowId) {
7
+ callbacks.getWorkflowResult(state.currentWorkflowId).catch(() => {});
8
+ await callbacks.cancelWorkflow(state.currentWorkflowId);
9
+ }
10
+ return startScheduleRunInOccupiedSlot(clearScheduleCurrentWorkflow(state), callbacks, occurrence);
11
+ }
12
+ if (state.overlap === "queue")
13
+ return {
14
+ ...state,
15
+ queuedRuns: [
16
+ ...state.queuedRuns,
17
+ {
18
+ workflowId: crypto.randomUUID(),
19
+ queuedAt: internals.options.getNow(),
20
+ ...occurrence !== void 0 && { occurrence }
21
+ }
22
+ ]
23
+ };
24
+ return state;
25
+ }
26
+ async function startScheduleRunInOccupiedSlot(state, callbacks, occurrence) {
27
+ const workflowId = crypto.randomUUID(), stateAfterStart = { ...state, currentWorkflowId: workflowId };
28
+ await callbacks.startScheduledRun(state, {
29
+ workflowId,
30
+ ...occurrence !== void 0 && { occurrence }
31
+ });
32
+ return stateAfterStart;
33
+ }
34
+ export async function drainQueuedScheduleRun(state, callbacks, completedWorkflowId) {
35
+ const [queuedRun, ...remainingQueuedRuns] = state.queuedRuns;
36
+ if (queuedRun === void 0)
37
+ return state;
38
+ const stateAfterStart = {
39
+ ...state,
40
+ currentWorkflowId: queuedRun.workflowId,
41
+ queuedRuns: remainingQueuedRuns
42
+ };
43
+ await callbacks.startScheduledRun(state, {
44
+ workflowId: queuedRun.workflowId,
45
+ ...queuedRun.occurrence !== void 0 && { occurrence: queuedRun.occurrence },
46
+ scheduleStateAfterStart: stateAfterStart,
47
+ ...completedWorkflowId !== void 0 && { completedWorkflowId }
48
+ });
49
+ return stateAfterStart;
50
+ }
@@ -1,6 +1,15 @@
1
1
  import type { ScheduleState } from '../types.ts';
2
2
  import type { EngineInternals } from './internals.ts';
3
3
  import type { ScheduleCallbacks } from './schedules.ts';
4
+ export type ScheduledRunStartOptions = {
5
+ occurrence?: number;
6
+ /** Reserved queue identity. A fresh id is minted when omitted. */
7
+ workflowId?: string;
8
+ /** Schedule state committed atomically with the workflow start. */
9
+ scheduleStateAfterStart?: ScheduleState;
10
+ /** Prior terminal run whose transient schedule metadata is now settled. */
11
+ completedWorkflowId?: string;
12
+ };
4
13
  /**
5
14
  * Launch one scheduled occurrence's workflow run and emit `schedule:fired`.
6
15
  *
@@ -11,7 +20,6 @@ import type { ScheduleCallbacks } from './schedules.ts';
11
20
  * active) never reach here, so they correctly do not fire.
12
21
  *
13
22
  * `occurrence` is the scheduled grid timestamp the run was due, threaded from the
14
- * timer loop. It is `undefined` for a `queue`-drained run, whose grid slot was
15
- * tracked only as a count and whose original timestamp is therefore gone.
23
+ * timer loop or retained on its durable queue entry until drain.
16
24
  */
17
- export declare function startScheduledRun(internals: EngineInternals, state: ScheduleState, callbacks: Pick<ScheduleCallbacks, 'startWorkflow' | 'failWorkflow' | 'handleCleanupError'>, occurrence?: number): Promise<string>;
25
+ export declare function startScheduledRun(internals: EngineInternals, state: ScheduleState, callbacks: Pick<ScheduleCallbacks, 'startWorkflow' | 'failWorkflow' | 'handleCleanupError'>, options?: ScheduledRunStartOptions): Promise<string>;
@@ -1,21 +1,44 @@
1
1
  import { KEYS } from "../../storage/interface.js";
2
+ import { encode } from "../codec.js";
2
3
  import { ScheduleFiredEvent } from "../events.js";
3
4
  import { unavailableServicesError } from "./lifecycle/recovered-services.js";
4
5
  import { EMPTY_STORAGE_VALUE } from "./lifecycle/shared.js";
5
6
  import { encodeScheduleRunMetadata } from "./schedule-run-metadata.js";
6
- export async function startScheduledRun(internals, state, callbacks, occurrence) {
7
- const workflowId = crypto.randomUUID(), scheduleRunOperations = [
7
+ export async function startScheduledRun(internals, state, callbacks, options = {}) {
8
+ const workflowId = options.workflowId ?? crypto.randomUUID(), occurrence = options.occurrence, metadata = encodeScheduleRunMetadata(state.id, occurrence), scheduleRunOperations = [
8
9
  {
9
10
  type: "put",
10
11
  key: KEYS.scheduleRun(workflowId),
11
- value: encodeScheduleRunMetadata(state.id, occurrence)
12
+ value: metadata
13
+ },
14
+ {
15
+ type: "put",
16
+ key: KEYS.scheduleRunLink(workflowId),
17
+ value: metadata
18
+ },
19
+ {
20
+ type: "put",
21
+ key: KEYS.scheduleRunBySchedule(state.id, workflowId),
22
+ value: EMPTY_STORAGE_VALUE
12
23
  },
13
24
  {
14
25
  type: "put",
15
26
  key: KEYS.terminalCleanupNeeded(workflowId),
16
27
  value: EMPTY_STORAGE_VALUE
17
28
  }
18
- ], resolution = await resolveScheduledRunServices(internals, workflowId, state, occurrence);
29
+ ];
30
+ if (options.scheduleStateAfterStart !== void 0)
31
+ scheduleRunOperations.push({
32
+ type: "put",
33
+ key: KEYS.schedule(options.scheduleStateAfterStart.id),
34
+ value: encode(options.scheduleStateAfterStart)
35
+ });
36
+ if (options.completedWorkflowId !== void 0)
37
+ scheduleRunOperations.push({
38
+ type: "delete",
39
+ key: KEYS.scheduleRun(options.completedWorkflowId)
40
+ });
41
+ const resolution = await resolveScheduledRunServices(internals, workflowId, state, occurrence);
19
42
  if (resolution !== null) {
20
43
  scheduleRunOperations.push({
21
44
  type: "put",
@@ -1,7 +1,11 @@
1
1
  import { ScheduleMissedFireEvent } from "../events/schedule-events.js";
2
2
  import { resolveEffectiveScheduleFireAt } from "./schedule-jitter.js";
3
3
  import { collectDueScheduleOccurrences, getNextScheduleOccurrence } from "./schedule-occurrence.js";
4
- import { loadScheduleState, writeScheduleState } from "./storage-io.js";
4
+ import {
5
+ loadScheduleState,
6
+ runSerializedScheduleStateOperation,
7
+ writeScheduleState
8
+ } from "./storage-io.js";
5
9
  const SCHEDULE_LATE_GRACE_MILLISECONDS = 1000, MAX_SCHEDULE_BACKFILL_OCCURRENCES_PER_TICK = 256, MAX_MISSED_FIRE_COUNT_PER_TICK = MAX_SCHEDULE_BACKFILL_OCCURRENCES_PER_TICK;
6
10
 
7
11
  class ScheduleStatePersistenceError extends Error {
@@ -12,6 +16,11 @@ class ScheduleStatePersistenceError extends Error {
12
16
  }
13
17
  }
14
18
  export async function handleScheduleTimer(internals, entry, callbacks) {
19
+ await runSerializedScheduleStateOperation(internals, entry.workflowId, async () => {
20
+ await handleSerializedScheduleTimer(internals, entry, callbacks);
21
+ });
22
+ }
23
+ async function handleSerializedScheduleTimer(internals, entry, callbacks) {
15
24
  if (internals.deposed)
16
25
  return;
17
26
  const state = await loadScheduleState(internals, entry.workflowId);
@@ -1,7 +1,8 @@
1
1
  import type { BatchOperation } from '../../storage/interface.ts';
2
- import type { PaginatedResult, ScheduleFilter, ScheduleOptions, ScheduleSpec, ScheduleState, ScheduleSummary, WorkflowState } from '../types.ts';
2
+ import type { PaginatedResult, ScheduleFilter, ScheduleOptions, ScheduleSpec, ScheduleState, ScheduleSummary, ScheduleUpdateOptions, WorkflowState } from '../types.ts';
3
3
  import type { EngineInternals } from './internals.ts';
4
4
  import { ScheduleHandle } from './schedule-handle.ts';
5
+ import type { ScheduledRunStartOptions } from './schedule-run.ts';
5
6
  export { startScheduledRun } from './schedule-run.ts';
6
7
  export { handleScheduleTimer } from './schedule-timer.ts';
7
8
  export type RefreshedScheduleState = {
@@ -16,7 +17,7 @@ export type ScheduleCallbacks = {
16
17
  cancelWorkflow: (workflowId: string) => Promise<void>;
17
18
  getWorkflowResult: (workflowId: string) => Promise<unknown>;
18
19
  refreshScheduledWorkflowState: (state: ScheduleState) => Promise<RefreshedScheduleState>;
19
- startScheduledRun: (state: ScheduleState, occurrence?: number) => Promise<string>;
20
+ startScheduledRun: (state: ScheduleState, options?: ScheduledRunStartOptions) => Promise<string>;
20
21
  applyScheduleOccurrence: (state: ScheduleState, occurrence?: number) => Promise<ScheduleState>;
21
22
  settleBackfillScheduleState: (state: ScheduleState) => Promise<ScheduleState>;
22
23
  flushQueuedInlineWorkflowStartsDirectly: () => Promise<void>;
@@ -30,8 +31,8 @@ export declare function toScheduleSummary(state: ScheduleState): ScheduleSummary
30
31
  export declare function pauseSchedule(internals: EngineInternals, scheduleId: string): Promise<void>;
31
32
  export declare function resumeSchedule(internals: EngineInternals, scheduleId: string): Promise<void>;
32
33
  export declare function cancelSchedule(internals: EngineInternals, scheduleId: string): Promise<void>;
33
- export declare function updateSchedule(internals: EngineInternals, scheduleId: string, newSpec: string | ScheduleSpec): Promise<void>;
34
+ export declare function updateSchedule(internals: EngineInternals, scheduleId: string, newSpec: string | ScheduleSpec, options?: ScheduleUpdateOptions): Promise<void>;
34
35
  export declare function refreshScheduledWorkflowState(internals: EngineInternals, state: ScheduleState, callbacks: Pick<ScheduleCallbacks, 'loadWorkflowState'>): Promise<RefreshedScheduleState>;
35
- export declare function applyScheduleOccurrence(_internals: EngineInternals, state: ScheduleState, callbacks: ScheduleCallbacks, occurrence?: number): Promise<ScheduleState>;
36
+ export declare function applyScheduleOccurrence(internals: EngineInternals, state: ScheduleState, callbacks: ScheduleCallbacks, occurrence?: number): Promise<ScheduleState>;
36
37
  export declare function settleBackfillScheduleState(internals: EngineInternals, state: ScheduleState, callbacks: Pick<ScheduleCallbacks, 'flushQueuedInlineWorkflowStartsDirectly' | 'refreshScheduledWorkflowState'>): Promise<ScheduleState>;
37
38
  export declare function handleScheduledWorkflowTerminal(internals: EngineInternals, workflowId: string, callbacks: ScheduleCallbacks): Promise<void>;
@@ -1,9 +1,11 @@
1
1
  import { KEYS } from "../../storage/interface.js";
2
2
  import { decode } from "../codec.js";
3
3
  import { WorkflowNotRegisteredError } from "./errors.js";
4
+ import { commitFencedEngineWrite } from "./fenced-write.js";
4
5
  import { ScheduleHandle } from "./schedule-handle.js";
5
6
  import { resolveEffectiveScheduleFireAt } from "./schedule-jitter.js";
6
7
  import { getNextScheduleOccurrence } from "./schedule-occurrence.js";
8
+ import { applyBlockedScheduleOccurrence, drainQueuedScheduleRun } from "./schedule-overlap.js";
7
9
  import { decodeScheduleRunMetadata } from "./schedule-run-metadata.js";
8
10
  import {
9
11
  clearScheduleCurrentWorkflow,
@@ -11,14 +13,20 @@ import {
11
13
  matchesScheduleFilter,
12
14
  paginateScheduleSummaries
13
15
  } from "./state-utilities.js";
14
- import { loadScheduleState, requireScheduleState, writeScheduleState } from "./storage-io.js";
16
+ import {
17
+ loadScheduleState,
18
+ requireScheduleState,
19
+ runSerializedScheduleStateOperation,
20
+ writeScheduleState
21
+ } from "./storage-io.js";
15
22
  import {
16
23
  coerceScheduleId,
17
24
  decodeScheduleState,
18
25
  isValidScheduleIdentifier,
19
26
  normalizeScheduleFilter,
20
27
  normalizeScheduleOptions,
21
- normalizeScheduleSpec
28
+ normalizeScheduleSpec,
29
+ normalizeScheduleUpdateOptions
22
30
  } from "./validation/schedule.js";
23
31
  export { startScheduledRun } from "./schedule-run.js";
24
32
  export { handleScheduleTimer } from "./schedule-timer.js";
@@ -48,7 +56,7 @@ export async function schedule(internals, type, input, spec, options) {
48
56
  updatedAt: now,
49
57
  nextFireAt: getNextScheduleOccurrence({ ...cadenceFields, createdAt: now }, now),
50
58
  missedFireCount: 0,
51
- queuedRuns: 0
59
+ queuedRuns: []
52
60
  };
53
61
  await writeScheduleState(internals, state);
54
62
  return new ScheduleHandle(scheduleId, internals.engine);
@@ -64,8 +72,7 @@ export async function listSchedules(internals, filter) {
64
72
  const state = decodeScheduleState(value);
65
73
  if (!state || !matchesScheduleFilter(state, normalizedFilter))
66
74
  continue;
67
- const { input: _input, ...summary } = state;
68
- items.push(summary);
75
+ items.push(toScheduleSummary(state));
69
76
  }
70
77
  return paginateScheduleSummaries(items, normalizedFilter);
71
78
  }
@@ -93,7 +100,7 @@ async function hasCurrentScheduleTimer(internals, state) {
93
100
  }
94
101
  export function toScheduleSummary(state) {
95
102
  const { input: _input, ...summary } = state;
96
- return summary;
103
+ return { ...summary, queuedRuns: summary.queuedRuns.map((queuedRun) => ({ ...queuedRun })) };
97
104
  }
98
105
  export async function pauseSchedule(internals, scheduleId) {
99
106
  const normalizedScheduleId = coerceScheduleId(scheduleId, "scheduleId"), state = await requireScheduleState(internals, normalizedScheduleId);
@@ -105,7 +112,7 @@ export async function pauseSchedule(internals, scheduleId) {
105
112
  status: "paused",
106
113
  updatedAt: now,
107
114
  nextFireAt: getNextScheduleOccurrence(state, now),
108
- queuedRuns: 0
115
+ queuedRuns: []
109
116
  };
110
117
  await writeScheduleState(internals, updatedState, { includeTimer: !1 });
111
118
  }
@@ -132,23 +139,30 @@ export async function cancelSchedule(internals, scheduleId) {
132
139
  status: "cancelled",
133
140
  updatedAt: internals.options.getNow(),
134
141
  nextFireAt: null,
135
- queuedRuns: 0
142
+ queuedRuns: []
136
143
  };
137
144
  await writeScheduleState(internals, updatedState, { includeTimer: !1 });
138
145
  }
139
- export async function updateSchedule(internals, scheduleId, newSpec) {
140
- const normalizedScheduleId = coerceScheduleId(scheduleId, "scheduleId"), normalizedSpec = normalizeScheduleSpec(newSpec), state = await requireScheduleState(internals, normalizedScheduleId), now = internals.options.getNow(), {
141
- cronExpression: _droppedCron,
142
- intervalMs: _droppedInterval,
143
- ...stateWithoutCadence
144
- } = state, cadenceFields = normalizedSpec.kind === "interval" ? { intervalMs: normalizedSpec.intervalMs } : { cronExpression: normalizedSpec.cronExpression }, anchorFields = normalizedSpec.kind === "interval" ? { createdAt: now } : {}, updatedState = {
145
- ...stateWithoutCadence,
146
- ...cadenceFields,
147
- ...anchorFields,
148
- updatedAt: now,
149
- nextFireAt: state.status === "cancelled" ? null : getNextScheduleOccurrence({ ...cadenceFields, createdAt: now }, now)
150
- };
151
- await writeScheduleState(internals, updatedState, { includeTimer: state.status === "active" });
146
+ export async function updateSchedule(internals, scheduleId, newSpec, options) {
147
+ const normalizedScheduleId = coerceScheduleId(scheduleId, "scheduleId"), normalizedSpec = normalizeScheduleSpec(newSpec), normalizedOptions = normalizeScheduleUpdateOptions(options);
148
+ await runSerializedScheduleStateOperation(internals, normalizedScheduleId, async () => {
149
+ const state = await requireScheduleState(internals, normalizedScheduleId), now = internals.options.getNow(), {
150
+ cronExpression: _droppedCron,
151
+ intervalMs: _droppedInterval,
152
+ ...stateWithoutCadence
153
+ } = state, cadenceFields = normalizedSpec.kind === "interval" ? { intervalMs: normalizedSpec.intervalMs } : { cronExpression: normalizedSpec.cronExpression }, anchorFields = normalizedSpec.kind === "interval" ? { createdAt: now } : {}, updatedState = {
154
+ ...stateWithoutCadence,
155
+ ...normalizedOptions,
156
+ ...cadenceFields,
157
+ ...anchorFields,
158
+ updatedAt: now,
159
+ nextFireAt: state.status === "cancelled" ? null : getNextScheduleOccurrence({ ...cadenceFields, createdAt: now }, now)
160
+ };
161
+ await writeScheduleState(internals, updatedState, {
162
+ includeTimer: state.status === "active",
163
+ replaceTimerFrom: state
164
+ });
165
+ });
152
166
  }
153
167
  function scheduledRunOccupiesSlot(currentWorkflowState) {
154
168
  const status = currentWorkflowState?.status;
@@ -169,31 +183,20 @@ export async function refreshScheduledWorkflowState(internals, state, callbacks)
169
183
  function hasActiveScheduledWorkflow(currentWorkflowState) {
170
184
  return scheduledRunOccupiesSlot(currentWorkflowState);
171
185
  }
172
- async function applyBlockedScheduleOccurrence(state, hasActiveWorkflow, callbacks, occurrence) {
173
- if (!hasActiveWorkflow)
174
- return { ...state, currentWorkflowId: await callbacks.startScheduledRun(state, occurrence) };
175
- if (state.overlap === "cancel-running") {
176
- if (state.currentWorkflowId) {
177
- callbacks.getWorkflowResult(state.currentWorkflowId).catch(() => {});
178
- await callbacks.cancelWorkflow(state.currentWorkflowId);
179
- }
180
- const stateForStart = clearScheduleCurrentWorkflow(state);
181
- return {
182
- ...state,
183
- currentWorkflowId: await callbacks.startScheduledRun(stateForStart, occurrence)
184
- };
186
+ export async function applyScheduleOccurrence(internals, state, callbacks, occurrence) {
187
+ const { state: refreshedState, currentWorkflowState } = await callbacks.refreshScheduledWorkflowState(state);
188
+ let stateForOccurrence = refreshedState, hasActiveWorkflow = hasActiveScheduledWorkflow(currentWorkflowState);
189
+ if (!hasActiveWorkflow && stateForOccurrence.queuedRuns.length > 0) {
190
+ stateForOccurrence = await drainQueuedScheduleRun(stateForOccurrence, callbacks);
191
+ hasActiveWorkflow = !0;
185
192
  }
186
- if (state.overlap === "queue")
187
- return { ...state, queuedRuns: state.queuedRuns + 1 };
188
- return state;
189
- }
190
- export async function applyScheduleOccurrence(_internals, state, callbacks, occurrence) {
191
- const { state: refreshedState, currentWorkflowState } = await callbacks.refreshScheduledWorkflowState(state), hasActiveWorkflow = hasActiveScheduledWorkflow(currentWorkflowState);
192
- if (refreshedState.overlap === "allow") {
193
- await callbacks.startScheduledRun(refreshedState, occurrence);
194
- return refreshedState;
193
+ if (stateForOccurrence.overlap === "allow") {
194
+ await callbacks.startScheduledRun(stateForOccurrence, {
195
+ ...occurrence !== void 0 && { occurrence }
196
+ });
197
+ return stateForOccurrence;
195
198
  }
196
- return applyBlockedScheduleOccurrence(refreshedState, hasActiveWorkflow, callbacks, occurrence);
199
+ return applyBlockedScheduleOccurrence(internals, stateForOccurrence, hasActiveWorkflow, callbacks, occurrence);
197
200
  }
198
201
  export async function settleBackfillScheduleState(internals, state, callbacks) {
199
202
  if (!state.currentWorkflowId)
@@ -208,26 +211,29 @@ export async function handleScheduledWorkflowTerminal(internals, workflowId, cal
208
211
  const scheduleRunBytes = await internals.storage.get(KEYS.scheduleRun(workflowId));
209
212
  if (!scheduleRunBytes)
210
213
  return;
211
- await internals.storage.delete(KEYS.scheduleRun(workflowId));
212
214
  const metadata = decodeScheduleRunMetadata(scheduleRunBytes);
213
- if (metadata === null || !isValidScheduleIdentifier(metadata.id))
215
+ if (metadata === null || !isValidScheduleIdentifier(metadata.id)) {
216
+ await deleteTransientScheduleRunMetadata(internals, workflowId);
214
217
  return;
218
+ }
215
219
  const scheduleId = metadata.id, state = await loadScheduleState(internals, scheduleId);
216
- if (!state || state.currentWorkflowId !== workflowId)
220
+ if (!state || state.currentWorkflowId !== workflowId) {
221
+ await deleteTransientScheduleRunMetadata(internals, workflowId);
217
222
  return;
223
+ }
218
224
  const now = internals.options.getNow(), clearedState = {
219
225
  ...clearScheduleCurrentWorkflow(state),
220
226
  updatedAt: now
221
227
  };
222
- if (clearedState.status === "active" && clearedState.overlap === "queue" && clearedState.queuedRuns > 0) {
223
- const nextWorkflowId = await callbacks.startScheduledRun(clearedState);
224
- await writeScheduleState(internals, {
225
- ...clearedState,
226
- currentWorkflowId: nextWorkflowId,
227
- queuedRuns: clearedState.queuedRuns - 1,
228
- updatedAt: now
229
- }, { includeTimer: !1 });
228
+ if (clearedState.status === "active" && clearedState.queuedRuns.length > 0) {
229
+ await drainQueuedScheduleRun({ ...clearedState, updatedAt: now }, callbacks, workflowId);
230
230
  return;
231
231
  }
232
- await writeScheduleState(internals, clearedState, { includeTimer: !1 });
232
+ await writeScheduleState(internals, clearedState, {
233
+ includeTimer: !1,
234
+ additionalOperations: [{ type: "delete", key: KEYS.scheduleRun(workflowId) }]
235
+ });
236
+ }
237
+ async function deleteTransientScheduleRunMetadata(internals, workflowId) {
238
+ await commitFencedEngineWrite(internals, [{ type: "delete", key: KEYS.scheduleRun(workflowId) }], [], () => Error(`Schedule-run cleanup for workflow "${workflowId}" lost its precondition.`));
233
239
  }