@lostgradient/weft 0.8.0 → 0.10.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 (155) hide show
  1. package/README.md +9 -5
  2. package/dist/alerting/alert-manager.d.ts +3 -1
  3. package/dist/alerting/alert-manager.js +5 -2
  4. package/dist/cli/generated/operation-client.generated.d.ts +1 -0
  5. package/dist/cli-main.js +78 -78
  6. package/dist/core/context/durable-operations.js +1 -1
  7. package/dist/core/context/index.d.ts +3 -1
  8. package/dist/core/context/index.js +10 -0
  9. package/dist/core/context/operation-request.d.ts +4 -0
  10. package/dist/core/context/parallel-cache-entry.d.ts +1 -1
  11. package/dist/core/context/parallel-cache-entry.js +12 -8
  12. package/dist/core/context/parallel-operations.d.ts +1 -1
  13. package/dist/core/context/parallel-operations.js +50 -8
  14. package/dist/core/context/run-operation.js +3 -2
  15. package/dist/core/context/speculative-child.js +1 -0
  16. package/dist/core/context/types.d.ts +1 -0
  17. package/dist/core/engine/activity-heartbeat-tracking.d.ts +1 -1
  18. package/dist/core/engine/activity-heartbeat-tracking.js +5 -1
  19. package/dist/core/engine/anonymous-signal-sequence.js +0 -2
  20. package/dist/core/engine/async-activity-completion.d.ts +24 -54
  21. package/dist/core/engine/async-activity-completion.js +25 -90
  22. package/dist/core/engine/async-activity-records.d.ts +106 -0
  23. package/dist/core/engine/async-activity-records.js +124 -0
  24. package/dist/core/engine/bulk-operations-purge.js +2 -1
  25. package/dist/core/engine/bulk-operations.js +10 -29
  26. package/dist/core/engine/callback-creators-bundles.js +1 -1
  27. package/dist/core/engine/callback-creators-core.js +1 -0
  28. package/dist/core/engine/construction.d.ts +1 -3
  29. package/dist/core/engine/construction.js +2 -5
  30. package/dist/core/engine/deferred-consume-envelope.d.ts +10 -1
  31. package/dist/core/engine/deferred-consume-envelope.js +9 -1
  32. package/dist/core/engine/disposal.js +1 -0
  33. package/dist/core/engine/engine-internal-types.d.ts +2 -0
  34. package/dist/core/engine/engine-runtime-helpers.d.ts +8 -0
  35. package/dist/core/engine/engine-runtime-helpers.js +9 -0
  36. package/dist/core/engine/errors.js +1 -1
  37. package/dist/core/engine/index.d.ts +12 -2
  38. package/dist/core/engine/index.js +35 -16
  39. package/dist/core/engine/inline-launch-queue.js +1 -1
  40. package/dist/core/engine/internals.d.ts +25 -3
  41. package/dist/core/engine/lifecycle/fork-helpers.js +1 -0
  42. package/dist/core/engine/lifecycle/recovered-services.d.ts +4 -2
  43. package/dist/core/engine/lifecycle/recovered-services.js +37 -7
  44. package/dist/core/engine/lifecycle/resume.d.ts +2 -2
  45. package/dist/core/engine/lifecycle/resume.js +45 -7
  46. package/dist/core/engine/lifecycle/shared.d.ts +34 -0
  47. package/dist/core/engine/lifecycle/start-exec.d.ts +2 -2
  48. package/dist/core/engine/lifecycle/start-exec.js +6 -4
  49. package/dist/core/engine/lifecycle/start.js +1 -0
  50. package/dist/core/engine/lifecycle/transition.d.ts +1 -1
  51. package/dist/core/engine/lifecycle/transition.js +9 -3
  52. package/dist/core/engine/lifecycle.d.ts +1 -1
  53. package/dist/core/engine/memo-durable-activity.js +8 -10
  54. package/dist/core/engine/operations-activity.d.ts +1 -1
  55. package/dist/core/engine/operations-activity.js +8 -6
  56. package/dist/core/engine/operations-coordination.d.ts +15 -1
  57. package/dist/core/engine/operations-coordination.js +43 -10
  58. package/dist/core/engine/operations-time.d.ts +2 -2
  59. package/dist/core/engine/operations-time.js +40 -6
  60. package/dist/core/engine/ownership-options.d.ts +1 -0
  61. package/dist/core/engine/ownership-options.js +14 -0
  62. package/dist/core/engine/retention.js +4 -0
  63. package/dist/core/engine/schedule-run-metadata.d.ts +3 -0
  64. package/dist/core/engine/schedule-run-metadata.js +29 -0
  65. package/dist/core/engine/schedule-run.js +19 -4
  66. package/dist/core/engine/schedules.js +7 -3
  67. package/dist/core/engine/sub-operation.js +6 -13
  68. package/dist/core/engine/termination/cleanup.js +18 -14
  69. package/dist/core/engine/termination/finalizer-activity.d.ts +1 -1
  70. package/dist/core/engine/termination/finalizer-activity.js +5 -1
  71. package/dist/core/engine/termination/finalizer.js +1 -1
  72. package/dist/core/engine/validation/schedule-options.d.ts +9 -0
  73. package/dist/core/engine/validation/schedule-options.js +59 -0
  74. package/dist/core/engine/validation/schedule.d.ts +3 -8
  75. package/dist/core/engine/validation/schedule.js +40 -56
  76. package/dist/core/engine/validation.js +1 -0
  77. package/dist/core/execution-strategy.d.ts +1 -0
  78. package/dist/core/inline-execution-strategy.context-options.d.ts +1 -0
  79. package/dist/core/inline-execution-strategy.context-options.js +3 -0
  80. package/dist/core/types/activity.d.ts +11 -0
  81. package/dist/core/types/checkpoint.d.ts +1 -0
  82. package/dist/core/types/options.d.ts +12 -0
  83. package/dist/core/types/schedules.d.ts +10 -0
  84. package/dist/core/types/services-resolution.d.ts +8 -3
  85. package/dist/core/types/state.d.ts +6 -0
  86. package/dist/core/types/workflow-builder.d.ts +2 -2
  87. package/dist/core/types/workflow-context.d.ts +40 -0
  88. package/dist/core/worker-execution-strategy.d.ts +1 -0
  89. package/dist/core/worker-inbound-message.d.ts +1 -0
  90. package/dist/core/worker-inbound-message.js +3 -0
  91. package/dist/index.d.ts +2 -2
  92. package/dist/json-schema.js +2 -2
  93. package/dist/mcp/cli.js +32 -32
  94. package/dist/observability/index.js +2 -2
  95. package/dist/runtime/portable.d.ts +1 -1
  96. package/dist/server/authorization.d.ts +3 -3
  97. package/dist/server/fault-to-json-rpc.d.ts +2 -1
  98. package/dist/server/handler.js +28 -28
  99. package/dist/server/index.d.ts +2 -0
  100. package/dist/server/index.js +30 -30
  101. package/dist/server/json-rpc-dispatch.d.ts +2 -2
  102. package/dist/server/json-rpc-parse.d.ts +1 -1
  103. package/dist/server/json-rpc-protocol.d.ts +2 -2
  104. package/dist/server/json-rpc-websocket.d.ts +1 -1
  105. package/dist/server/operation-fault.d.ts +3 -1
  106. package/dist/server/operations/async-activity.js +2 -2
  107. package/dist/server/operations/create-schedule.d.ts +2 -0
  108. package/dist/server/operations/create-schedule.js +13 -2
  109. package/dist/server/principal.d.ts +2 -2
  110. package/dist/server/rest-binding.d.ts +1 -1
  111. package/dist/server/runtime/task-dispatch.js +6 -0
  112. package/dist/server/runtime/task-polling.js +9 -1
  113. package/dist/server/runtime/task-reconciliation.js +2 -1
  114. package/dist/server/stdio-session.d.ts +4 -4
  115. package/dist/server/task-queue-types.d.ts +2 -0
  116. package/dist/server/task-state.d.ts +4 -0
  117. package/dist/service-worker/index.d.ts +15 -14
  118. package/dist/service-worker/index.js +28 -28
  119. package/dist/service-worker/scheduler.d.ts +41 -1
  120. package/dist/storage/auto.d.ts +36 -18
  121. package/dist/storage/auto.js +1 -1
  122. package/dist/storage/compressed-storage.js +1 -1
  123. package/dist/storage/http.js +2 -2
  124. package/dist/storage/index.d.ts +1 -0
  125. package/dist/storage/indexeddb.d.ts +6 -1
  126. package/dist/storage/indexeddb.js +1 -1
  127. package/dist/storage/interface.d.ts +1 -0
  128. package/dist/storage/interface.js +1 -1
  129. package/dist/storage/lmdb.js +1 -1
  130. package/dist/storage/memory.js +1 -1
  131. package/dist/storage/neon.js +1 -1
  132. package/dist/storage/resolve.js +1 -1
  133. package/dist/storage/scoped-storage.js +1 -1
  134. package/dist/storage/testing.js +1 -1
  135. package/dist/storage/turso.js +1 -1
  136. package/dist/storage/typed-storage.js +1 -1
  137. package/dist/storage/web-extension.d.ts +1 -1
  138. package/dist/storage/web-extension.js +1 -1
  139. package/dist/testing/index.js +33 -33
  140. package/dist/version.d.ts +1 -1
  141. package/dist/version.js +1 -1
  142. package/dist/worker/execute-with-interceptors.d.ts +9 -3
  143. package/dist/worker/execute-with-interceptors.js +14 -2
  144. package/dist/worker/long-poll.d.ts +2 -1
  145. package/dist/worker/long-poll.js +1 -1
  146. package/dist/worker/protocol-messages.d.ts +2 -0
  147. package/dist/worker/protocol-schemas.d.ts +8 -0
  148. package/dist/worker/protocol-schemas.js +1 -0
  149. package/dist/worker/protocol.js +1 -1
  150. package/dist/worker/remote-activity-context.d.ts +2 -0
  151. package/dist/workers/activity-runner.d.ts +2 -0
  152. package/dist/workers/activity-runner.js +9 -1
  153. package/dist/workers/workflow-runner.d.ts +2 -1
  154. package/dist/workers/workflow-runner.js +1 -0
  155. package/package.json +2 -2
@@ -18,10 +18,10 @@ import {
18
18
  import { getActivityFunctionWithMetadata, resolveActivityFunction } from "./activity-resolution.js";
19
19
  import {
20
20
  AsyncActivityDeferral,
21
- deriveAsyncActivityToken,
22
21
  driveWorkflowInterceptorGenerator,
23
22
  parkDeferredAsyncActivity
24
23
  } from "./async-activity-completion.js";
24
+ import { deriveAsyncActivityToken } from "./async-activity-records.js";
25
25
  import { callActivityFunction } from "./state-utilities.js";
26
26
  export function buildActivityVerification(_internals, activityName, verify, context, result) {
27
27
  return (async () => {
@@ -40,7 +40,7 @@ export function buildActivityCompensation(internals, workflowId, operation, resu
40
40
  await activity.compensate?.(operation.input, result);
41
41
  };
42
42
  }
43
- export async function invokeWorkerActivity(internals, operationId, activityName, input, attempt) {
43
+ export async function invokeWorkerActivity(internals, operationId, activityName, input, attempt, workflowExecutionToken, activityAttemptToken) {
44
44
  const dispatcher = internals.activityWorkerDispatcher;
45
45
  if (!dispatcher)
46
46
  throw Error(`No activity worker dispatcher available for "${activityName}"`);
@@ -48,7 +48,9 @@ export async function invokeWorkerActivity(internals, operationId, activityName,
48
48
  operationId,
49
49
  activityName,
50
50
  input,
51
- attempt
51
+ attempt,
52
+ ...workflowExecutionToken !== void 0 && { workflowExecutionToken },
53
+ ...activityAttemptToken !== void 0 && { activityAttemptToken }
52
54
  });
53
55
  if (result.status === "failed") {
54
56
  const error = Error(result.error);
@@ -74,11 +76,11 @@ function getActivityStateKey(operation) {
74
76
  return operation.activityStateKey ?? operation.step ?? 0;
75
77
  }
76
78
  export async function executeActivity(internals, workflowId, operation, callbacks, attempt = getActivityAttempt(operation), coordinatorSignal) {
77
- const activityInput = operation.input, activityStateKey = getActivityStateKey(operation);
79
+ const activityInput = operation.input, activityStateKey = getActivityStateKey(operation), workflowExecutionToken = operation.workflowExecutionToken;
78
80
  warnIfRetryMissingHeartbeat(internals, workflowId, activityStateKey, attempt);
79
- const asyncToken = deriveAsyncActivityToken(workflowId, activityStateKey, attempt), { perAttemptTimeoutMs, attemptAbortController, activitySignal } = resolvePerAttemptTimeout(internals, workflowId, operation, coordinatorSignal), activityContext = buildActivityContext(internals, workflowId, activityStateKey, activitySignal, () => {
81
+ const asyncToken = deriveAsyncActivityToken(workflowId, activityStateKey, attempt), { perAttemptTimeoutMs, attemptAbortController, activitySignal } = resolvePerAttemptTimeout(internals, workflowId, operation, coordinatorSignal), activityContext = buildActivityContext(internals, workflowId, workflowExecutionToken, activityStateKey, attempt, activitySignal, () => {
80
82
  throw new AsyncActivityDeferral(asyncToken);
81
- }), invokeActivity = internals.activityWorkerDispatcher ? (activityName, input) => invokeWorkerActivity(internals, operation.operationId, activityName, input, attempt) : (activityName, input) => withPerAttemptTimeout(invokeInlineActivity(internals, workflowId, operation, activityContext, activityName, input), perAttemptTimeoutMs, activityName, attempt, attemptAbortController), composedActivity = callbacks.getComposedActivityInterceptor(), executeWithActivityInterceptors = async (activityName, input, headers) => {
83
+ }), activityAttemptToken = activityContext.activityAttemptToken, invokeActivity = internals.activityWorkerDispatcher ? (activityName, input) => invokeWorkerActivity(internals, operation.operationId, activityName, input, attempt, workflowExecutionToken, activityAttemptToken) : (activityName, input) => withPerAttemptTimeout(invokeInlineActivity(internals, workflowId, operation, activityContext, activityName, input), perAttemptTimeoutMs, activityName, attempt, attemptAbortController), composedActivity = callbacks.getComposedActivityInterceptor(), executeWithActivityInterceptors = async (activityName, input, headers) => {
82
84
  if (!composedActivity)
83
85
  return invokeActivity(activityName, input);
84
86
  const activityInterception = {
@@ -33,7 +33,21 @@ export type CoordinationOperationCallbacks = {
33
33
  };
34
34
  export declare function processWaitSignalOperation(internals: EngineInternals, workflowId: string, operation: WaitSignalOperation, callbacks: Pick<CoordinationOperationCallbacks, 'completeOperation'>): Promise<void>;
35
35
  export declare function processParallelOperation(internals: EngineInternals, workflowId: string, operation: ParallelOperation, callbacks: Pick<CoordinationOperationCallbacks, 'executeSubOperation' | 'runOperationWithResult'>): Promise<void>;
36
- export declare function processRaceOperation(_internals: EngineInternals, workflowId: string, operation: RaceOperation, callbacks: Pick<CoordinationOperationCallbacks, 'executeSubOperation' | 'runOperationWithResult'>): Promise<void>;
36
+ export declare function processRaceOperation(internals: EngineInternals, workflowId: string, operation: RaceOperation, callbacks: Pick<CoordinationOperationCallbacks, 'executeSubOperation' | 'runOperationWithResult'>): Promise<void>;
37
+ export declare function assertValidRaceBranchNames(operation: RaceOperation): void;
38
+ export declare function wrapRaceWinner(operation: RaceOperation, winnerIndex: number, value: unknown): unknown;
39
+ /**
40
+ * Execute race branches while giving an already-buffered signal priority over a
41
+ * literal zero-duration sleep. The preflight is non-destructive: the selected
42
+ * wait-signal branch still returns its deferred-consume envelope, so the owning
43
+ * coordinator remains the only place that consumes the durable record.
44
+ *
45
+ * Positive-duration sleeps deliberately bypass this preflight, including when
46
+ * their deadline becomes past-due before dispatch. That keeps ordinary timeout
47
+ * races on the existing Promise.race path and limits the stronger ordering
48
+ * guarantee to the explicit non-blocking-drain idiom.
49
+ */
50
+ export declare function executeRaceSubOperations(internals: EngineInternals, workflowId: string, operations: readonly ContextOperationRequest[], execute: (operation: ContextOperationRequest, signal: AbortSignal) => Promise<unknown>, parentSignal?: AbortSignal, wrapWinner?: (winnerIndex: number, value: unknown) => unknown): Promise<unknown>;
37
51
  export declare function processRunAllOperation(internals: EngineInternals, workflowId: string, operation: RunAllOperation, callbacks: Pick<CoordinationOperationCallbacks, 'runOperationWithResult'>): Promise<void>;
38
52
  export declare function isConfiguredInlineActivity(fn: Function): fn is RunAllOperation['branches'][string][0] & ActivityFunctionWithMetadata;
39
53
  /**
@@ -5,7 +5,7 @@ import {
5
5
  executeRunAllBranches,
6
6
  executeRunAllBranchesSettled
7
7
  } from "../engine-helpers.js";
8
- import { finalizeAndUnwrap } from "./deferred-consume-envelope.js";
8
+ import { createKeyedRaceResultEnvelope, finalizeAndUnwrap } from "./deferred-consume-envelope.js";
9
9
  import {
10
10
  executeActivityOperationResult as executeActivityOperationResultFromInternals
11
11
  } from "./operations-activity.js";
@@ -16,6 +16,7 @@ import {
16
16
  } from "./parallel-dispatch.js";
17
17
  import {
18
18
  consumeSignalWithAtomicWorkflowCommit,
19
+ peekSignal,
19
20
  trackWaiterKey,
20
21
  untrackWaiterKey
21
22
  } from "./signals.js";
@@ -106,20 +107,52 @@ function assertPartialFailurePersistenceSupported(partialEntryWritten, slots, op
106
107
  return;
107
108
  throw Error(`${operationName} partial-failure preservation is not supported in ${executionMode}: the engine cannot persist fulfilled branch slots after a sibling branch fails. Run the workflow inline or make branch side effects idempotent.`);
108
109
  }
109
- export async function processRaceOperation(_internals, workflowId, operation, callbacks) {
110
+ export async function processRaceOperation(internals, workflowId, operation, callbacks) {
110
111
  return callbacks.runOperationWithResult(workflowId, operation, async () => {
111
112
  assertSupportedSignalBranches(operation.operations);
112
- const controller = new AbortController, subOperations = operation.operations.map((subOperation) => callbacks.executeSubOperation(workflowId, subOperation, controller.signal));
113
- Promise.allSettled(subOperations);
114
- let winner;
115
- try {
116
- winner = await Promise.race(subOperations);
117
- } finally {
118
- controller.abort();
119
- }
113
+ 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));
120
115
  return finalizeAndUnwrap(winner);
121
116
  });
122
117
  }
118
+ export function assertValidRaceBranchNames(operation) {
119
+ if (operation.branchNames !== void 0 && operation.branchNames.length !== operation.operations.length)
120
+ throw Error("ctx.raceKeyed branch names must match its operation count");
121
+ }
122
+ export function wrapRaceWinner(operation, winnerIndex, value) {
123
+ const key = operation.branchNames?.[winnerIndex];
124
+ return key === void 0 ? value : createKeyedRaceResultEnvelope(key, value);
125
+ }
126
+ export async function executeRaceSubOperations(internals, workflowId, operations, execute, parentSignal, wrapWinner) {
127
+ parentSignal?.throwIfAborted();
128
+ const controller = new AbortController, abortFromParent = () => controller.abort(parentSignal?.reason);
129
+ parentSignal?.addEventListener("abort", abortFromParent, { once: !0 });
130
+ try {
131
+ const bufferedSignal = await findBufferedSignalDrainBranch(internals, workflowId, operations);
132
+ 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 })));
138
+ Promise.allSettled(subOperations);
139
+ const winner = await Promise.race(subOperations);
140
+ return wrapWinner === void 0 ? winner.value : wrapWinner(winner.index, winner.value);
141
+ } finally {
142
+ parentSignal?.removeEventListener("abort", abortFromParent);
143
+ controller.abort();
144
+ }
145
+ }
146
+ async function findBufferedSignalDrainBranch(internals, workflowId, operations) {
147
+ if (operations.length !== 2)
148
+ return;
149
+ const waitSignalOperation = operations.find((operation) => operation.type === "wait-signal"), zeroDurationSleep = operations.find((operation) => operation.type === "sleep" && operation.duration === 0);
150
+ if (waitSignalOperation === void 0 || zeroDurationSleep === void 0)
151
+ return;
152
+ if (!(await peekSignal(internals, workflowId, waitSignalOperation.signalName)).found)
153
+ return;
154
+ return { index: operations.indexOf(waitSignalOperation), operation: waitSignalOperation };
155
+ }
123
156
  export async function processRunAllOperation(internals, workflowId, operation, callbacks) {
124
157
  return callbacks.runOperationWithResult(workflowId, operation, async () => {
125
158
  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);
@@ -12,7 +12,7 @@ export type TimeOperationCallbacks = {
12
12
  loadWorkflowState: (workflowId: string) => Promise<WorkflowState | null>;
13
13
  failWorkflow: (workflowId: string, error: Error) => Promise<void>;
14
14
  runSerializedWorkflowStateWrite: <Result>(workflowId: string, writeOperation: () => Promise<Result>) => Promise<Result>;
15
- beginWorkflowExecution: (workflowId: string, workflowType: string, input: unknown, checkpoint: Checkpoint, executionDeadline: number | undefined, executionStateOwnerId: string, registration: RegistrationEntry) => void;
15
+ beginWorkflowExecution: (workflowId: string, workflowExecutionToken: string | undefined, workflowType: string, input: unknown, checkpoint: Checkpoint, executionDeadline: number | undefined, executionStateOwnerId: string, registration: RegistrationEntry) => void;
16
16
  workflowVersionTupleFromState: (state: WorkflowState) => WorkflowVersionTuple;
17
17
  setWorkflowStartHeaders: (workflowId: string, headers: Map<string, string> | undefined) => void;
18
18
  loadWorkflowStartHeaders: (workflowId: string) => Promise<Map<string, string> | undefined>;
@@ -25,7 +25,7 @@ export type TimeOperationCallbacks = {
25
25
  };
26
26
  export declare function createDelayedStartTimerEntry(_internals: EngineInternals, workflowId: string, scheduledStartAt: number, options: StartOptions | undefined, callbacks: Pick<TimeOperationCallbacks, 'parseStartOptionDuration'>): TimerEntry;
27
27
  export declare function processSleepOperation(internals: EngineInternals, workflowId: string, operation: SleepOperation, callbacks: Pick<TimeOperationCallbacks, 'completeOperation' | 'loadWorkflowState'>): Promise<void>;
28
- export declare function registerSleepResolver(internals: EngineInternals, workflowId: string, operationId: string, resolve: () => void): void;
28
+ export declare function registerSleepResolver(internals: EngineInternals, workflowId: string, operationId: string, resolve: () => void, scheduledFireAt: number): void;
29
29
  export declare function startDelayedWorkflow(internals: EngineInternals, entry: TimerEntry, callbacks: Pick<TimeOperationCallbacks, 'beginWorkflowExecution' | 'dispatchEvent' | 'failWorkflow' | 'handleCleanupError' | 'loadWorkflowStartHeaders' | 'loadWorkflowState' | 'runSerializedWorkflowStateWrite' | 'setWorkflowStartHeaders' | 'workflowVersionTupleFromState'>): Promise<void>;
30
30
  export declare function handleTimerFired(internals: EngineInternals, entry: TimerEntry, callbacks: Pick<TimeOperationCallbacks, 'failWorkflow' | 'handleCleanupError' | 'loadWorkflowStartHeaders' | 'loadWorkflowState' | 'runDeferredTerminalCleanup' | 'runWorkflowFinalizer' | 'runSerializedWorkflowStateWrite' | 'handleScheduleTimer' | 'setWorkflowStartHeaders' | 'timeout' | 'beginWorkflowExecution' | 'dispatchEvent' | 'workflowVersionTupleFromState'>): Promise<void>;
31
31
  export {};
@@ -16,6 +16,18 @@ export function createDelayedStartTimerEntry(_internals, workflowId, scheduledSt
16
16
  }
17
17
  };
18
18
  }
19
+ function sleepTimerFiredEarly(internals, workflowId, operation) {
20
+ const workflowMarkers = internals.sleepTimersFiredWithoutResolver.get(workflowId);
21
+ if (!workflowMarkers)
22
+ return !1;
23
+ const markedFireAt = workflowMarkers.get(operation.operationId);
24
+ if (markedFireAt === void 0)
25
+ return !1;
26
+ workflowMarkers.delete(operation.operationId);
27
+ if (workflowMarkers.size === 0)
28
+ internals.sleepTimersFiredWithoutResolver.delete(workflowId);
29
+ return markedFireAt >= operation.scheduledFireAt;
30
+ }
19
31
  export async function processSleepOperation(internals, workflowId, operation, callbacks) {
20
32
  if (operation.scheduledFireAt <= internals.options.getNow()) {
21
33
  callbacks.completeOperation(workflowId, void 0);
@@ -28,13 +40,24 @@ export async function processSleepOperation(internals, workflowId, operation, ca
28
40
  fireAt: operation.scheduledFireAt,
29
41
  kind: "sleep"
30
42
  });
31
- registerSleepResolver(internals, workflowId, operation.operationId, resolve);
43
+ registerSleepResolver(internals, workflowId, operation.operationId, resolve, operation.scheduledFireAt);
44
+ const resolverKey = `${workflowId}:${operation.operationId}`;
45
+ if (sleepTimerFiredEarly(internals, workflowId, operation) && internals.sleepResolvers.has(resolverKey))
46
+ resolveSleepTimer(internals, {
47
+ id: `sleep:${operation.operationId}`,
48
+ workflowId,
49
+ fireAt: operation.scheduledFireAt,
50
+ kind: "sleep"
51
+ });
32
52
  await promise;
33
53
  if ((await callbacks.loadWorkflowState(workflowId))?.status === "running")
34
54
  callbacks.completeOperation(workflowId, void 0);
35
55
  }
36
- export function registerSleepResolver(internals, workflowId, operationId, resolve) {
37
- internals.sleepResolvers.set(`${workflowId}:${operationId}`, resolve);
56
+ export function registerSleepResolver(internals, workflowId, operationId, resolve, scheduledFireAt) {
57
+ internals.sleepResolvers.set(`${workflowId}:${operationId}`, {
58
+ resolve,
59
+ fireAt: scheduledFireAt
60
+ });
38
61
  let workflowOperations = internals.sleepResolversByWorkflow.get(workflowId);
39
62
  if (!workflowOperations) {
40
63
  workflowOperations = new Set;
@@ -94,7 +117,7 @@ export async function startDelayedWorkflow(internals, entry, callbacks) {
94
117
  internals.checkpoints.set(entry.workflowId, checkpoint);
95
118
  internals.workflowVersionTuples.set(entry.workflowId, callbacks.workflowVersionTupleFromState(runningState));
96
119
  callbacks.setWorkflowStartHeaders(entry.workflowId, await callbacks.loadWorkflowStartHeaders(entry.workflowId));
97
- callbacks.beginWorkflowExecution(entry.workflowId, runningState.type, runningState.input, checkpoint, executionDeadline, runningState.executionStateOwnerId ?? entry.workflowId, registration);
120
+ callbacks.beginWorkflowExecution(entry.workflowId, runningState.workflowExecutionToken, runningState.type, runningState.input, checkpoint, executionDeadline, runningState.executionStateOwnerId ?? entry.workflowId, registration);
98
121
  }
99
122
  async function loadDelayedWorkflowCheckpoint(internals, entry, callbacks) {
100
123
  const checkpointBytes = await internals.storage.get(KEYS.checkpoint(entry.workflowId));
@@ -168,11 +191,22 @@ async function handleReviewTimer(internals, entry, callbacks) {
168
191
  }
169
192
  function resolveSleepTimer(internals, entry) {
170
193
  const operationId = entry.id.replace("sleep:", ""), resolverKey = `${entry.workflowId}:${operationId}`, resolver = internals.sleepResolvers.get(resolverKey);
171
- if (!resolver)
194
+ if (!resolver) {
195
+ let workflowMarkers = internals.sleepTimersFiredWithoutResolver.get(entry.workflowId);
196
+ if (!workflowMarkers) {
197
+ workflowMarkers = new Map;
198
+ internals.sleepTimersFiredWithoutResolver.set(entry.workflowId, workflowMarkers);
199
+ }
200
+ const existing = workflowMarkers.get(operationId);
201
+ if (existing === void 0 || entry.fireAt > existing)
202
+ workflowMarkers.set(operationId, entry.fireAt);
203
+ return;
204
+ }
205
+ if (entry.fireAt < resolver.fireAt)
172
206
  return;
173
207
  internals.sleepResolvers.delete(resolverKey);
174
208
  untrackSleepResolver(internals, entry.workflowId, operationId);
175
- resolver();
209
+ resolver.resolve();
176
210
  }
177
211
  function untrackSleepResolver(internals, workflowId, operationId) {
178
212
  const workflowOperations = internals.sleepResolversByWorkflow.get(workflowId);
@@ -13,6 +13,7 @@ export declare const DEFAULT_LEASE_TTL_MS = 30000;
13
13
  export declare const DEFAULT_LEASE_RENEW_INTERVAL_MS = 5000;
14
14
  /** Default boot-time lease acquisition wait window for `ownership: 'lease'`. */
15
15
  export declare const DEFAULT_LEASE_WAIT_TIMEOUT_MS = 60000;
16
+ export declare function resolveBackgroundTaskMode(options: EngineConstructorOptions | undefined): ResolvedOptions['backgroundTaskMode'];
16
17
  /**
17
18
  * Resolve the ownership posture and lease tuning into their `ResolvedOptions`
18
19
  * fields. Defaults to `'none'`. The lease durations are documented as "ignored
@@ -1,5 +1,19 @@
1
1
  import { normalizeRetentionDuration } from "./validation.js";
2
2
  export const DEFAULT_LEASE_TTL_MS = 30000, DEFAULT_LEASE_RENEW_INTERVAL_MS = 5000, DEFAULT_LEASE_WAIT_TIMEOUT_MS = 60000;
3
+ export function resolveBackgroundTaskMode(options) {
4
+ const mode = options?.backgroundTasks ?? "automatic";
5
+ if (mode !== "automatic" && mode !== "manual")
6
+ throw Error('options.backgroundTasks must be "automatic" or "manual" when provided');
7
+ if (mode === "manual")
8
+ assertManualBackgroundTaskCompatibility(options);
9
+ return mode;
10
+ }
11
+ function assertManualBackgroundTaskCompatibility(options) {
12
+ if (options?.detectSecondInstance === !0)
13
+ throw Error('detectSecondInstance cannot be enabled when backgroundTasks is "manual"');
14
+ if (options?.ownership === "lease")
15
+ throw Error('ownership cannot be "lease" when backgroundTasks is "manual"');
16
+ }
3
17
  export function resolveOwnershipFields(options) {
4
18
  const ownershipMode = assertKnownOwnershipMode(options?.ownership ?? "none");
5
19
  if (ownershipMode !== "lease")
@@ -11,6 +11,10 @@ export function setNextRetentionSweepAt(internals) {
11
11
  internals.nextRetentionSweepAt = internals.options.getNow() + internals.options.retentionSweepIntervalMs;
12
12
  }
13
13
  export function ensureRetentionSweepInterval(internals, callbacks) {
14
+ if (internals.options.backgroundTaskMode === "manual") {
15
+ internals.nextRetentionSweepAt = null;
16
+ return;
17
+ }
14
18
  if (!callbacks.hasConfiguredRetention()) {
15
19
  if (internals.retentionSweepInterval !== null) {
16
20
  clearInterval(internals.retentionSweepInterval ?? void 0);
@@ -0,0 +1,3 @@
1
+ import type { WorkflowServicesResolverScheduleInfo } from '../types.ts';
2
+ export declare function encodeScheduleRunMetadata(scheduleId: string, occurrence: number | undefined): Uint8Array;
3
+ export declare function decodeScheduleRunMetadata(bytes: Uint8Array): WorkflowServicesResolverScheduleInfo | null;
@@ -0,0 +1,29 @@
1
+ import { decode, encode } from "../codec.js";
2
+ import { isRecord } from "../debug-output.js";
3
+ import { isValidScheduleIdentifier } from "./validation/schedule.js";
4
+ export function encodeScheduleRunMetadata(scheduleId, occurrence) {
5
+ return encode({
6
+ id: scheduleId,
7
+ ...occurrence !== void 0 ? { occurrence } : {}
8
+ });
9
+ }
10
+ export function decodeScheduleRunMetadata(bytes) {
11
+ let decoded;
12
+ try {
13
+ decoded = decode(bytes);
14
+ } catch {
15
+ return null;
16
+ }
17
+ if (typeof decoded === "string")
18
+ return isValidScheduleIdentifier(decoded) ? { id: decoded } : null;
19
+ if (!isRecord(decoded))
20
+ return null;
21
+ const { id, occurrence } = decoded;
22
+ if (!isValidScheduleIdentifier(id))
23
+ return null;
24
+ if (occurrence === void 0)
25
+ return { id };
26
+ if (typeof occurrence !== "number" || !Number.isSafeInteger(occurrence))
27
+ return null;
28
+ return { id, occurrence };
29
+ }
@@ -1,16 +1,31 @@
1
1
  import { KEYS } from "../../storage/interface.js";
2
- import { encode } from "../codec.js";
3
2
  import { ScheduleFiredEvent } from "../events.js";
4
3
  import { unavailableServicesError } from "./lifecycle/recovered-services.js";
5
4
  import { EMPTY_STORAGE_VALUE } from "./lifecycle/shared.js";
5
+ import { encodeScheduleRunMetadata } from "./schedule-run-metadata.js";
6
6
  export async function startScheduledRun(internals, state, callbacks, occurrence) {
7
- const workflowId = crypto.randomUUID(), scheduleRunOperations = state.overlap === "allow" ? [] : [{ type: "put", key: KEYS.scheduleRun(workflowId), value: encode(state.id) }], resolution = await resolveScheduledRunServices(internals, workflowId, state, occurrence);
7
+ const workflowId = crypto.randomUUID(), scheduleRunOperations = [
8
+ {
9
+ type: "put",
10
+ key: KEYS.scheduleRun(workflowId),
11
+ value: encodeScheduleRunMetadata(state.id, occurrence)
12
+ },
13
+ {
14
+ type: "put",
15
+ key: KEYS.terminalCleanupNeeded(workflowId),
16
+ value: EMPTY_STORAGE_VALUE
17
+ }
18
+ ], resolution = await resolveScheduledRunServices(internals, workflowId, state, occurrence);
8
19
  if (resolution !== null) {
9
- scheduleRunOperations.push({ type: "put", key: KEYS.workflowHasServices(workflowId), value: EMPTY_STORAGE_VALUE }, { type: "put", key: KEYS.terminalCleanupNeeded(workflowId), value: EMPTY_STORAGE_VALUE });
10
- internals.workflowsNeedingTerminalCleanup.add(workflowId);
20
+ scheduleRunOperations.push({
21
+ type: "put",
22
+ key: KEYS.workflowHasServices(workflowId),
23
+ value: EMPTY_STORAGE_VALUE
24
+ });
11
25
  if (resolution.status === "available")
12
26
  internals.workflowServices.set(workflowId, resolution.services);
13
27
  }
28
+ internals.workflowsNeedingTerminalCleanup.add(workflowId);
14
29
  await callbacks.startWorkflow(state.workflowType, state.input, { id: workflowId }, scheduleRunOperations);
15
30
  internals.engine.dispatchEvent(new ScheduleFiredEvent(state.id, workflowId, internals.options.getNow(), occurrence));
16
31
  if (resolution !== null && resolution.status === "unavailable") {
@@ -4,6 +4,7 @@ import { WorkflowNotRegisteredError } from "./errors.js";
4
4
  import { ScheduleHandle } from "./schedule-handle.js";
5
5
  import { resolveEffectiveScheduleFireAt } from "./schedule-jitter.js";
6
6
  import { getNextScheduleOccurrence } from "./schedule-occurrence.js";
7
+ import { decodeScheduleRunMetadata } from "./schedule-run-metadata.js";
7
8
  import {
8
9
  clearScheduleCurrentWorkflow,
9
10
  createScheduleTimerId,
@@ -35,6 +36,9 @@ export async function schedule(internals, type, input, spec, options) {
35
36
  id: scheduleId,
36
37
  workflowType: type,
37
38
  input,
39
+ ...normalizedOptions.description !== void 0 && {
40
+ description: normalizedOptions.description
41
+ },
38
42
  ...cadenceFields,
39
43
  status: "active",
40
44
  overlap: normalizedOptions.overlap,
@@ -205,10 +209,10 @@ export async function handleScheduledWorkflowTerminal(internals, workflowId, cal
205
209
  if (!scheduleRunBytes)
206
210
  return;
207
211
  await internals.storage.delete(KEYS.scheduleRun(workflowId));
208
- const decodedScheduleId = decode(scheduleRunBytes);
209
- if (!isValidScheduleIdentifier(decodedScheduleId))
212
+ const metadata = decodeScheduleRunMetadata(scheduleRunBytes);
213
+ if (metadata === null || !isValidScheduleIdentifier(metadata.id))
210
214
  return;
211
- const state = await loadScheduleState(internals, decodedScheduleId);
215
+ const scheduleId = metadata.id, state = await loadScheduleState(internals, scheduleId);
212
216
  if (!state || state.currentWorkflowId !== workflowId)
213
217
  return;
214
218
  const now = internals.options.getNow(), clearedState = {
@@ -16,7 +16,10 @@ import {
16
16
  executeActivityOperationResult
17
17
  } from "./operations-activity.js";
18
18
  import {
19
- executeRunAllOperationResult
19
+ assertValidRaceBranchNames,
20
+ executeRaceSubOperations,
21
+ executeRunAllOperationResult,
22
+ wrapRaceWinner
20
23
  } from "./operations-coordination.js";
21
24
  import { callMemoFunction } from "./state-utilities.js";
22
25
  export async function processWaitReviewOperation(_internals, workflowId, operation, callbacks) {
@@ -82,16 +85,6 @@ async function executeParallelSubOperation(internals, workflowId, operation, cal
82
85
  }
83
86
  async function executeRaceSubOperation(internals, workflowId, operation, callbacks, signal, speculativeState) {
84
87
  signal?.throwIfAborted();
85
- const controller = new AbortController, abortNestedRace = () => {
86
- controller.abort(signal?.reason);
87
- };
88
- signal?.addEventListener("abort", abortNestedRace, { once: !0 });
89
- const subOperations = operation.operations.map((subOperation) => executeSubOperation(internals, workflowId, subOperation, callbacks, controller.signal, speculativeState));
90
- Promise.allSettled(subOperations);
91
- try {
92
- return await Promise.race(subOperations);
93
- } finally {
94
- signal?.removeEventListener("abort", abortNestedRace);
95
- controller.abort();
96
- }
88
+ assertValidRaceBranchNames(operation);
89
+ return executeRaceSubOperations(internals, workflowId, operation.operations, (subOperation, branchSignal) => executeSubOperation(internals, workflowId, subOperation, callbacks, branchSignal, speculativeState), signal, (winnerIndex, value) => wrapRaceWinner(operation, winnerIndex, value));
97
90
  }
@@ -1,6 +1,6 @@
1
1
  import { KEYS, encodeStorageKeyComponent, storageHas } from "../../../storage/interface.js";
2
2
  import { CleanupWarningEvent } from "../../events.js";
3
- import { asyncActivityWorkflowPrefix } from "../async-activity-completion.js";
3
+ import { asyncActivityWorkflowPrefix } from "../async-activity-records.js";
4
4
  import { forgetCommittedCheckpointBytes } from "../checkpoint-commit-snapshots.js";
5
5
  import { parseTerminalCleanupTimerId, workflowFeedListenerKey } from "../state-utilities.js";
6
6
  import { releaseWorkflowConcurrencySlot } from "../workflow-concurrency.js";
@@ -43,23 +43,25 @@ function cleanupTrackedWaiter(internals, workflowId, kind) {
43
43
  }
44
44
  function cleanupSleepResolvers(internals, workflowId) {
45
45
  const sleepOps = internals.sleepResolversByWorkflow.get(workflowId);
46
- if (!sleepOps)
47
- return;
48
- for (const operationId of sleepOps) {
49
- const key = `${workflowId}:${operationId}`, resolver = internals.sleepResolvers.get(key);
50
- if (resolver)
51
- resolver();
52
- internals.sleepResolvers.delete(key);
46
+ if (sleepOps) {
47
+ for (const operationId of sleepOps) {
48
+ const key = `${workflowId}:${operationId}`, resolver = internals.sleepResolvers.get(key);
49
+ if (resolver)
50
+ resolver.resolve();
51
+ internals.sleepResolvers.delete(key);
52
+ }
53
+ internals.sleepResolversByWorkflow.delete(workflowId);
53
54
  }
54
- internals.sleepResolversByWorkflow.delete(workflowId);
55
+ internals.sleepTimersFiredWithoutResolver.delete(workflowId);
55
56
  }
56
57
  function evictSleepResolversWithoutResolving(internals, workflowId) {
57
58
  const sleepOps = internals.sleepResolversByWorkflow.get(workflowId);
58
- if (!sleepOps)
59
- return;
60
- for (const operationId of sleepOps)
61
- internals.sleepResolvers.delete(`${workflowId}:${operationId}`);
62
- internals.sleepResolversByWorkflow.delete(workflowId);
59
+ if (sleepOps) {
60
+ for (const operationId of sleepOps)
61
+ internals.sleepResolvers.delete(`${workflowId}:${operationId}`);
62
+ internals.sleepResolversByWorkflow.delete(workflowId);
63
+ }
64
+ internals.sleepTimersFiredWithoutResolver.delete(workflowId);
63
65
  }
64
66
  function cleanupConditionWaiters(internals, workflowId) {
65
67
  const resolver = internals.conditionWaiters.get(workflowId);
@@ -192,7 +194,9 @@ export async function runDeferredTerminalCleanup(internals, workflowId, timerId,
192
194
  if (state.terminalCleanupToken !== parsedTimer.terminalCleanupToken)
193
195
  return;
194
196
  try {
197
+ await finalizeScheduledWorkflowTerminal(internals, workflowId, callbacks);
195
198
  await cleanupTerminalWorkflowDurableState(internals, workflowId, parsedTimer.includeOutputArtifacts, callbacks);
199
+ await internals.storage.delete(KEYS.scheduleRun(workflowId));
196
200
  } catch (error) {
197
201
  callbacks.handleCleanupError("cleanupTerminalWorkflowDurableState", error, workflowId);
198
202
  throw error;
@@ -50,4 +50,4 @@ export type FinalizerAttemptResult = {
50
50
  * @param attempt - 1-based attempt number, for the per-attempt timeout error message
51
51
  * @param shutdownSignal - the engine's dispose/shutdown abort signal; aborting it stops a cooperating finalizer
52
52
  */
53
- export declare function runFinalizerActivity(finalizer: RunnableFinalizer, input: unknown, attempt: number, shutdownSignal: AbortSignal): Promise<FinalizerAttemptResult>;
53
+ export declare function runFinalizerActivity(finalizer: RunnableFinalizer, input: unknown, attempt: number, shutdownSignal: AbortSignal, workflowExecutionToken?: string): Promise<FinalizerAttemptResult>;
@@ -1,6 +1,6 @@
1
1
  import { parseDuration } from "../../scheduler.js";
2
2
  import { withPerAttemptTimeout } from "../activity-per-attempt-timeout.js";
3
- export async function runFinalizerActivity(finalizer, input, attempt, shutdownSignal) {
3
+ export async function runFinalizerActivity(finalizer, input, attempt, shutdownSignal, workflowExecutionToken) {
4
4
  const perAttemptTimeoutMs = finalizer.timeout === void 0 ? void 0 : parseDuration(finalizer.timeout), attemptController = new AbortController, relayShutdownAbort = () => attemptController.abort(shutdownSignal.reason);
5
5
  if (shutdownSignal.aborted)
6
6
  attemptController.abort(shutdownSignal.reason);
@@ -8,6 +8,10 @@ export async function runFinalizerActivity(finalizer, input, attempt, shutdownSi
8
8
  shutdownSignal.addEventListener("abort", relayShutdownAbort, { once: !0 });
9
9
  const activityContext = {
10
10
  signal: attemptController.signal,
11
+ ...workflowExecutionToken !== void 0 && {
12
+ workflowExecutionToken,
13
+ activityAttemptToken: `${workflowExecutionToken}:finalizer:${String(attempt)}`
14
+ },
11
15
  heartbeat: () => {},
12
16
  completeAsync: () => {
13
17
  throw Error(`Finalizer "${finalizer.name}" called ctx.completeAsync(), which is not supported in a workflow finalizer: the finalizer runs after the workflow is already terminal, so there is no durable step to complete out of band. Run the teardown synchronously instead.`);
@@ -103,7 +103,7 @@ async function driveResolvedTeardown(internals, workflowId, token, drive, callba
103
103
  await rearmTeardownTimer(internals, workflowId, token, TEARDOWN_SELF_HEAL_DELAY_MS);
104
104
  return;
105
105
  }
106
- const result = await runFinalizerActivity(finalizer, finalizerInput, attempt, internals.abortController.signal);
106
+ const result = await runFinalizerActivity(finalizer, finalizerInput, attempt, internals.abortController.signal, state.workflowExecutionToken);
107
107
  if (result.ok) {
108
108
  await settleTeardownSuccess(internals, workflowId, state.type, token, attempt, runningBytes, callbacks);
109
109
  return;
@@ -0,0 +1,9 @@
1
+ import type { ScheduleOptions, ScheduleOverlapPolicy } from '../../types.ts';
2
+ export declare const SCHEDULE_OVERLAP_POLICIES: Set<ScheduleOverlapPolicy>;
3
+ type NormalizedScheduleOptions = Required<Pick<ScheduleOptions, 'overlap' | 'backfill'>> & {
4
+ id?: string;
5
+ description?: string;
6
+ jitterMs?: number;
7
+ };
8
+ export declare function normalizeScheduleOptions(options: ScheduleOptions | undefined): NormalizedScheduleOptions;
9
+ export {};
@@ -0,0 +1,59 @@
1
+ import { parseDuration } from "../../scheduler.js";
2
+ import { coerceStartWorkflowId } from "../../start-workflow-validation.js";
3
+ export const SCHEDULE_OVERLAP_POLICIES = new Set([
4
+ "skip",
5
+ "queue",
6
+ "cancel-running",
7
+ "allow"
8
+ ]);
9
+ export function normalizeScheduleOptions(options) {
10
+ if (options === void 0)
11
+ return { overlap: "skip", backfill: !1 };
12
+ if (typeof options !== "object" || options === null)
13
+ throw Error("options must be an object when provided");
14
+ return {
15
+ overlap: normalizeScheduleOverlap(options.overlap),
16
+ backfill: normalizeScheduleBackfill(options.backfill),
17
+ ...options.id !== void 0 && { id: coerceStartWorkflowId(options.id, "options.id") },
18
+ ...options.description !== void 0 && {
19
+ description: normalizeScheduleDescription(options.description)
20
+ },
21
+ ...options.jitter !== void 0 && {
22
+ jitterMs: normalizeScheduleJitter(options.jitter, "options.jitter")
23
+ }
24
+ };
25
+ }
26
+ function normalizeScheduleDescription(description) {
27
+ if (typeof description !== "string")
28
+ throw Error("options.description must be a string when provided");
29
+ return description;
30
+ }
31
+ function normalizeScheduleOverlap(overlap) {
32
+ if (overlap === void 0)
33
+ return "skip";
34
+ if (!SCHEDULE_OVERLAP_POLICIES.has(overlap))
35
+ throw Error(`options.overlap must be one of ${[...SCHEDULE_OVERLAP_POLICIES].join(", ")}`);
36
+ return overlap;
37
+ }
38
+ function normalizeScheduleBackfill(backfill) {
39
+ if (backfill === void 0)
40
+ return !1;
41
+ if (typeof backfill !== "boolean")
42
+ throw Error("options.backfill must be a boolean when provided");
43
+ return backfill;
44
+ }
45
+ function normalizeScheduleJitter(value, fieldName) {
46
+ if (typeof value !== "string" && typeof value !== "number")
47
+ throw Error(`${fieldName} must be a duration string or a number of milliseconds`);
48
+ let milliseconds;
49
+ try {
50
+ milliseconds = parseDuration(value);
51
+ } catch (error) {
52
+ const message = error instanceof Error ? error.message : String(error);
53
+ throw Error(`Invalid ${fieldName}: ${message}`, { cause: error });
54
+ }
55
+ const jitterMs = Math.ceil(milliseconds);
56
+ if (!Number.isSafeInteger(jitterMs) || jitterMs <= 0)
57
+ throw Error(`${fieldName} must resolve to a positive number of milliseconds`);
58
+ return jitterMs;
59
+ }
@@ -1,15 +1,11 @@
1
- import type { ScheduleFilter, ScheduleOptions, ScheduleOverlapPolicy, ScheduleSpec, ScheduleState, ScheduleStatus } from '../../types.ts';
1
+ import type { ScheduleFilter, ScheduleOverlapPolicy, ScheduleSpec, ScheduleState, ScheduleStatus } from '../../types.ts';
2
+ export { normalizeScheduleOptions, SCHEDULE_OVERLAP_POLICIES } from './schedule-options.ts';
2
3
  export declare const SCHEDULE_STATUSES: Set<ScheduleStatus>;
3
- export declare const SCHEDULE_OVERLAP_POLICIES: Set<ScheduleOverlapPolicy>;
4
4
  export declare function isValidScheduleTimestamp(value: unknown): value is number;
5
5
  export declare function isValidScheduleStatus(value: unknown): value is ScheduleStatus;
6
6
  export declare function isValidScheduleOverlapPolicy(value: unknown): value is ScheduleOverlapPolicy;
7
7
  export declare function isValidScheduleIdentifier(value: unknown): value is string;
8
8
  export declare function coerceScheduleId(scheduleId: string, fieldName: string): string;
9
- export declare function normalizeScheduleOptions(options: ScheduleOptions | undefined): Required<Pick<ScheduleOptions, 'overlap' | 'backfill'>> & {
10
- id?: string;
11
- jitterMs?: number;
12
- };
13
9
  /**
14
10
  * A recurrence specification normalized into the discriminated cadence the
15
11
  * engine persists. Interval periods are resolved to whole milliseconds.
@@ -33,7 +29,6 @@ export declare function decodeScheduleIdentityFields(decoded: Record<string, unk
33
29
  cronExpression?: string;
34
30
  intervalMs?: number;
35
31
  }) | null;
36
- type ScheduleRuntimeFields = Pick<ScheduleState, 'backfill' | 'jitterMs' | 'createdAt' | 'updatedAt' | 'lastFireAt' | 'lastMissedFireAt' | 'nextFireAt' | 'currentWorkflowId' | 'missedFireCount' | 'queuedRuns'>;
32
+ type ScheduleRuntimeFields = Pick<ScheduleState, 'description' | 'backfill' | 'jitterMs' | 'createdAt' | 'updatedAt' | 'lastFireAt' | 'lastMissedFireAt' | 'nextFireAt' | 'currentWorkflowId' | 'missedFireCount' | 'queuedRuns'>;
37
33
  export declare function decodeScheduleRuntimeFields(decoded: Record<string, unknown>, scheduleId: string): ScheduleRuntimeFields | null;
38
34
  export declare function decodeScheduleState(bytes: Uint8Array): ScheduleState | null;
39
- export {};