@ferricstore/ferricstore 0.12.1 → 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 (98) hide show
  1. package/README.md +2 -2
  2. package/dist/index.cjs +591 -133
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +61 -7
  5. package/dist/index.d.ts +61 -7
  6. package/dist/index.js +586 -129
  7. package/dist/index.js.map +1 -1
  8. package/dist/langgraph.cjs.map +1 -1
  9. package/dist/langgraph.d.cts +1 -1
  10. package/dist/langgraph.d.ts +1 -1
  11. package/dist/langgraph.js.map +1 -1
  12. package/dist/{outcomes-DmBwnq0Y.d.cts → outcomes-CcX956h4.d.cts} +20 -1
  13. package/dist/{outcomes-BbFDp3AH.d.ts → outcomes-DLJYTrX8.d.ts} +20 -1
  14. package/docs/api/assets/hierarchy.js +1 -1
  15. package/docs/api/assets/navigation.js +1 -1
  16. package/docs/api/assets/search.js +1 -1
  17. package/docs/api/classes/ConnectionClosedError.html +1 -1
  18. package/docs/api/classes/FerricStoreClient.html +68 -23
  19. package/docs/api/classes/FerricStoreError.html +1 -1
  20. package/docs/api/classes/FlowAlreadyExistsError.html +1 -1
  21. package/docs/api/classes/FlowBatchError.html +1 -1
  22. package/docs/api/classes/FlowNotFoundError.html +1 -1
  23. package/docs/api/classes/FlowQueryError.html +1 -1
  24. package/docs/api/classes/FlowWrongStateError.html +1 -1
  25. package/docs/api/classes/HTTPTransportError.html +1 -1
  26. package/docs/api/classes/InvalidCommandError.html +1 -1
  27. package/docs/api/classes/LeaseRenewalError.html +3 -3
  28. package/docs/api/classes/LockHeldError.html +1 -1
  29. package/docs/api/classes/LockNotOwnedError.html +1 -1
  30. package/docs/api/classes/OverloadedError.html +1 -1
  31. package/docs/api/classes/QueueCompletionError.html +1 -1
  32. package/docs/api/classes/RequestTimeoutError.html +1 -1
  33. package/docs/api/classes/RerouteError.html +1 -1
  34. package/docs/api/classes/StaleLeaseError.html +1 -1
  35. package/docs/api/classes/StalePolicyGenerationError.html +1 -1
  36. package/docs/api/classes/Workflow.html +19 -19
  37. package/docs/api/classes/WorkflowClient.html +4 -4
  38. package/docs/api/classes/WorkflowContext.html +74 -36
  39. package/docs/api/classes/WorkflowFlowCommands.html +26 -22
  40. package/docs/api/classes/WorkflowWorker.html +6 -6
  41. package/docs/api/functions/complete.html +1 -1
  42. package/docs/api/functions/fail.html +1 -1
  43. package/docs/api/functions/isAlreadyAppliedOutcome.html +41 -0
  44. package/docs/api/functions/isOutcome.html +1 -1
  45. package/docs/api/functions/retry.html +1 -1
  46. package/docs/api/functions/transition.html +1 -1
  47. package/docs/api/hierarchy.html +55 -47
  48. package/docs/api/index.html +2 -2
  49. package/docs/api/interfaces/AdminListOptions.html +1 -1
  50. package/docs/api/interfaces/AdvanceOptions.html +141 -0
  51. package/docs/api/interfaces/AlreadyAppliedOutcome.html +69 -0
  52. package/docs/api/interfaces/AppliedStepOutcome.html +87 -0
  53. package/docs/api/interfaces/CancelOptions.html +6 -6
  54. package/docs/api/interfaces/CompleteOptions.html +4 -4
  55. package/docs/api/interfaces/CompleteOutcome.html +13 -13
  56. package/docs/api/interfaces/CreateManyOptions.html +1 -1
  57. package/docs/api/interfaces/EffectCompensateOptions.html +1 -1
  58. package/docs/api/interfaces/EffectConfirmOptions.html +1 -1
  59. package/docs/api/interfaces/EffectFailOptions.html +1 -1
  60. package/docs/api/interfaces/EffectStatusOptions.html +1 -1
  61. package/docs/api/interfaces/FailOptions.html +4 -4
  62. package/docs/api/interfaces/FailOutcome.html +13 -13
  63. package/docs/api/interfaces/FerricStoreClientFromUrlOptions.html +1 -1
  64. package/docs/api/interfaces/FerricStoreClientOptions.html +1 -1
  65. package/docs/api/interfaces/FlowQueryCountResult.html +1 -1
  66. package/docs/api/interfaces/FlowQueryIndexBuild.html +1 -1
  67. package/docs/api/interfaces/FlowQueryIndexProgress.html +1 -1
  68. package/docs/api/interfaces/FlowQueryIndexValidation.html +1 -1
  69. package/docs/api/interfaces/FlowQueryRecordsResult.html +1 -1
  70. package/docs/api/interfaces/HistoryOptions.html +16 -16
  71. package/docs/api/interfaces/InvocationCreateOptions.html +1 -1
  72. package/docs/api/interfaces/LeaseMutationOptions.html +1 -1
  73. package/docs/api/interfaces/LimitListOptions.html +1 -1
  74. package/docs/api/interfaces/MutateOptions.html +1 -1
  75. package/docs/api/interfaces/NamedValueMutation.html +9 -9
  76. package/docs/api/interfaces/ReadOptions.html +12 -12
  77. package/docs/api/interfaces/ReclaimOptions.html +1 -1
  78. package/docs/api/interfaces/ReplayedStepResult.html +73 -0
  79. package/docs/api/interfaces/RequestContextOptions.html +1 -1
  80. package/docs/api/interfaces/RetryOptions.html +4 -4
  81. package/docs/api/interfaces/RetryOutcome.html +13 -13
  82. package/docs/api/interfaces/RunStepsItem.html +3 -3
  83. package/docs/api/interfaces/RunStepsManyOptions.html +11 -11
  84. package/docs/api/interfaces/SearchOptions.html +13 -13
  85. package/docs/api/interfaces/StepContinueOptions.html +1 -1
  86. package/docs/api/interfaces/StepOptions.html +159 -0
  87. package/docs/api/interfaces/StepResult.html +67 -0
  88. package/docs/api/interfaces/TDigestCreateOptions.html +1 -1
  89. package/docs/api/interfaces/TDigestMergeOptions.html +1 -1
  90. package/docs/api/interfaces/TransitionOptions.html +1 -1
  91. package/docs/api/interfaces/TransitionOutcome.html +14 -14
  92. package/docs/api/modules.html +6 -6
  93. package/docs/api/types/ConnectionRequestDisposition.html +1 -1
  94. package/docs/api/types/ManagementPairs.html +1 -1
  95. package/docs/api/types/Outcome.html +1 -1
  96. package/docs/api/types/SearchStateMeta.html +1 -1
  97. package/docs/api/types/WorkflowStepResult.html +38 -0
  98. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -9022,6 +9022,8 @@ var HTTPTransport = class {
9022
9022
  const timer = timeoutMs == null ? void 0 : setLongTimeout(() => controller.abort(requestTimeoutReason), timeoutMs);
9023
9023
  timer?.unref();
9024
9024
  try {
9025
+ const response = await this.request(this.config.commandUrl, "POST", body, 0, controller.signal);
9026
+ if (response.status !== 401 || this.config.headers.authorization == null) return response;
9025
9027
  return await this.request(this.config.commandUrl, "POST", body, 0, controller.signal);
9026
9028
  } catch (error) {
9027
9029
  if (controller.signal.aborted) {
@@ -18498,12 +18500,20 @@ var FerricStoreClaimClient = class extends FerricStoreMutationClient {
18498
18500
  return this.records(items);
18499
18501
  }
18500
18502
  async claimJobs(type, options) {
18501
- return await this.claimDue(type, {
18503
+ const requestedState = options.state;
18504
+ const includeState = options.includeState ?? requestedState == null;
18505
+ const jobs = await this.claimDue(type, {
18502
18506
  ...options,
18503
- includeState: options.includeState ?? false,
18507
+ includeState,
18504
18508
  jobOnly: true,
18505
18509
  limit: options.limit ?? 100
18506
18510
  });
18511
+ if (!includeState && requestedState != null) {
18512
+ for (const job of jobs) {
18513
+ if (job[CLAIMED_ITEM_WIRE] != null) job.runState ??= requestedState;
18514
+ }
18515
+ }
18516
+ return jobs;
18507
18517
  }
18508
18518
  async reclaim(type, options) {
18509
18519
  const captured = snapshotClaimOptions(options);
@@ -18526,7 +18536,7 @@ var FerricStoreClaimClient = class extends FerricStoreMutationClient {
18526
18536
  this.appendPartitionOptions(args, captured);
18527
18537
  append(args, "PRIORITY", captured.priority);
18528
18538
  if (jobOnly) {
18529
- append(args, "RETURN", compactClaimReturnMode2(false, captured.includeAttributes === true));
18539
+ append(args, "RETURN", compactClaimReturnMode2(true, captured.includeAttributes === true));
18530
18540
  }
18531
18541
  appendPayloadRead(args, captured.payload, captured.payloadMaxBytes);
18532
18542
  appendValueReturn(args, { values: captured.values, valueMaxBytes: captured.valueMaxBytes });
@@ -18907,6 +18917,254 @@ var FerricStoreProducerClient = class extends FerricStoreClaimClient {
18907
18917
  }
18908
18918
  };
18909
18919
 
18920
+ // src/client-durable-step.ts
18921
+ import { createHash as createHash2 } from "crypto";
18922
+
18923
+ // src/workflow-utilities.ts
18924
+ function resolveWorkflowStates(configured, registrationFor) {
18925
+ const stateNames = [...new Set(configured)];
18926
+ if (stateNames.length === 0) throw new Error("Workflow worker requires at least one state");
18927
+ return stateNames.map((stateName) => {
18928
+ const registration = registrationFor(stateName);
18929
+ if (registration == null) {
18930
+ throw new Error(`No handler registered for workflow state '${stateName}'`);
18931
+ }
18932
+ return { registration, stateName };
18933
+ });
18934
+ }
18935
+ function normalizeStateMode(mode) {
18936
+ if (mode === "fifo" || mode === "parallel") return mode;
18937
+ throw new Error("state mode must be 'fifo' or 'parallel'");
18938
+ }
18939
+ function valueRefToString(value) {
18940
+ if (typeof value === "string") return value;
18941
+ if (Buffer.isBuffer(value)) return value.toString("utf8");
18942
+ if (typeof value === "object" && value != null && Object.hasOwn(value, "ref")) {
18943
+ const ref = value.ref;
18944
+ return typeof ref === "string" ? ref : Buffer.isBuffer(ref) ? ref.toString("utf8") : void 0;
18945
+ }
18946
+ return void 0;
18947
+ }
18948
+
18949
+ // src/client-durable-step.ts
18950
+ var DURABLE_STEP_VALUE_PREFIX = "__ferricstore_step__:sha256:";
18951
+ function durableMutationMayHaveCommitted(error) {
18952
+ if (error instanceof ConnectionClosedError || error instanceof RequestTimeoutError) {
18953
+ return error.requestDisposition === "possibly_sent";
18954
+ }
18955
+ if (error instanceof HTTPTransportError) return error.safeToRetry !== true;
18956
+ if (error instanceof FlowNotFoundError || error instanceof FlowWrongStateError || error instanceof InvalidCommandError || error instanceof StaleLeaseError) {
18957
+ return false;
18958
+ }
18959
+ if (error instanceof FerricStoreError && error.safeToRetry === true) return false;
18960
+ return true;
18961
+ }
18962
+ async function advanceClaim(client, job, options) {
18963
+ assertDurableClaim(job);
18964
+ assertNonEmptyString(options.toState, "toState");
18965
+ const partitionKey = job.partitionKey === "" ? void 0 : job.partitionKey;
18966
+ const response = await client.stepContinue(job.id, {
18967
+ attributesDelete: options.attributesDelete,
18968
+ attributesMerge: options.attributesMerge,
18969
+ dropValues: options.dropValues,
18970
+ fencingToken: job.fencingToken,
18971
+ fromState: job.runState,
18972
+ leaseMs: options.leaseMs,
18973
+ leaseToken: job.leaseToken,
18974
+ nowMs: options.nowMs,
18975
+ overrideValues: options.overrideValues,
18976
+ partitionKey,
18977
+ payload: options.payload,
18978
+ returnJob: true,
18979
+ stateMeta: options.stateMeta,
18980
+ type: job.type,
18981
+ toState: options.toState,
18982
+ valueRefs: options.valueRefs,
18983
+ values: options.values
18984
+ });
18985
+ const refreshed = response;
18986
+ if (refreshed[CLAIMED_ITEM_WIRE] != null) {
18987
+ if (refreshed.runState != null && refreshed.runState !== options.toState) {
18988
+ throw new FerricStoreError("compact continuation response returned the wrong runState");
18989
+ }
18990
+ refreshed.runState ??= options.toState;
18991
+ if (refreshed.type == null && job.type != null) refreshed.type = job.type;
18992
+ }
18993
+ assertContinuationClaim(job, refreshed, options.toState);
18994
+ return refreshed;
18995
+ }
18996
+ async function runDurableStep(client, job, options, hooks = {}) {
18997
+ assertDurableClaim(job);
18998
+ const name = options.name;
18999
+ const run2 = options.run;
19000
+ const continuation = snapshotAdvanceOptions(options);
19001
+ assertNonEmptyString(name, "name");
19002
+ assertNonEmptyString(continuation.toState, "toState");
19003
+ if (typeof run2 !== "function") throw new TypeError("run must be a function");
19004
+ const resultName = durableStepValueName(name);
19005
+ assertStepValueIsReserved(continuation, resultName);
19006
+ const leaseMs = continuation.leaseMs ?? 3e4;
19007
+ const partitionKey = job.partitionKey === "" ? void 0 : job.partitionKey;
19008
+ const validated = await client.extendLease(job.id, {
19009
+ fencingToken: job.fencingToken,
19010
+ leaseMs,
19011
+ leaseToken: job.leaseToken,
19012
+ nowMs: continuation.nowMs,
19013
+ partitionKey
19014
+ });
19015
+ assertValidatedLease(job, validated);
19016
+ if (validated.valueRefs != null && Object.hasOwn(validated.valueRefs, resultName)) {
19017
+ if (validated.runState !== continuation.toState) {
19018
+ throw new FerricStoreError("committed durable step result does not match the requested target state");
19019
+ }
19020
+ const ref = valueRefToString(validated.valueRefs[resultName]);
19021
+ if (ref == null) {
19022
+ throw new FerricStoreError("committed durable step result has invalid reference metadata");
19023
+ }
19024
+ const entry = (await valueMGetEntries(client, [ref]))[0];
19025
+ if (entry?.found !== true) {
19026
+ throw new FerricStoreError("committed durable step result is missing");
19027
+ }
19028
+ const replayedJob = claimedItemFromRecord(validated);
19029
+ hooks.replayed?.(replayedJob);
19030
+ return { job: replayedJob, result: entry.value };
19031
+ }
19032
+ const result = normalizeStepResult(client, await run2());
19033
+ await hooks.beforeCommit?.();
19034
+ let refreshed;
19035
+ try {
19036
+ refreshed = await advanceClaim(client, validated, {
19037
+ ...continuation,
19038
+ leaseMs,
19039
+ values: { ...continuation.values, [resultName]: result }
19040
+ });
19041
+ } catch (error) {
19042
+ hooks.commitFailed?.(error);
19043
+ throw error;
19044
+ }
19045
+ hooks.committed?.(refreshed);
19046
+ return { job: refreshed, result };
19047
+ }
19048
+ function normalizeStepResult(client, value) {
19049
+ return client.codec.decode(client.codec.encode(value));
19050
+ }
19051
+ function durableStepValueName(name) {
19052
+ const digest = createHash2("sha256").update(name, "utf8").digest("hex");
19053
+ return `${DURABLE_STEP_VALUE_PREFIX}${digest}`;
19054
+ }
19055
+ function snapshotAdvanceOptions(options) {
19056
+ return {
19057
+ ...options.attributesDelete == null ? {} : { attributesDelete: [...options.attributesDelete] },
19058
+ ...options.attributesMerge == null ? {} : { attributesMerge: { ...options.attributesMerge } },
19059
+ ...options.dropValues == null ? {} : { dropValues: [...options.dropValues] },
19060
+ ...options.leaseMs == null ? {} : { leaseMs: options.leaseMs },
19061
+ ...options.nowMs == null ? {} : { nowMs: options.nowMs },
19062
+ ...options.overrideValues == null ? {} : { overrideValues: [...options.overrideValues] },
19063
+ ...options.payload === void 0 ? {} : { payload: options.payload },
19064
+ ...options.stateMeta == null ? {} : { stateMeta: { ...options.stateMeta } },
19065
+ toState: options.toState,
19066
+ ...options.valueRefs == null ? {} : { valueRefs: { ...options.valueRefs } },
19067
+ ...options.values == null ? {} : { values: { ...options.values } }
19068
+ };
19069
+ }
19070
+ function assertDurableClaim(job) {
19071
+ if (typeof job !== "object" || job == null) throw new TypeError("job must be a claimed Flow");
19072
+ assertNonEmptyString(job.id, "job.id");
19073
+ assertNonEmptyString(job.runState, "job.runState");
19074
+ if (!Buffer.isBuffer(job.leaseToken) || job.leaseToken.byteLength === 0) {
19075
+ throw new TypeError("job.leaseToken must be a non-empty Buffer");
19076
+ }
19077
+ if ((typeof job.fencingToken !== "number" || !Number.isSafeInteger(job.fencingToken)) && typeof job.fencingToken !== "bigint") {
19078
+ throw new TypeError("job.fencingToken must be an integer");
19079
+ }
19080
+ if (fencingValue(job.fencingToken) <= 0n) {
19081
+ throw new TypeError("job.fencingToken must be a positive integer");
19082
+ }
19083
+ if (job.state !== "running") {
19084
+ throw new TypeError("job.state must be running");
19085
+ }
19086
+ }
19087
+ function assertValidatedLease(expected, actual) {
19088
+ try {
19089
+ assertDurableClaim(actual);
19090
+ } catch (error) {
19091
+ throw new FerricStoreError("lease validation returned an invalid claim", { cause: error });
19092
+ }
19093
+ if (actual.id !== expected.id) throw leaseValidationMismatch("id");
19094
+ if (normalizedPartition(actual.partitionKey) !== normalizedPartition(expected.partitionKey)) {
19095
+ throw leaseValidationMismatch("partition");
19096
+ }
19097
+ if (!actual.leaseToken.equals(expected.leaseToken)) throw leaseValidationMismatch("lease token");
19098
+ if (fencingValue(actual.fencingToken) !== fencingValue(expected.fencingToken)) {
19099
+ throw leaseValidationMismatch("fencing token");
19100
+ }
19101
+ if (actual.state !== expected.state) throw leaseValidationMismatch("physical state");
19102
+ if (actual.runState !== expected.runState) throw leaseValidationMismatch("runState");
19103
+ }
19104
+ function assertContinuationClaim(previous, refreshed, toState) {
19105
+ try {
19106
+ assertDurableClaim(refreshed);
19107
+ } catch (error) {
19108
+ throw new FerricStoreError("continuation response returned an invalid claim", { cause: error });
19109
+ }
19110
+ if (refreshed.id !== previous.id) throw continuationMismatch("id");
19111
+ if (normalizedPartition(refreshed.partitionKey) !== normalizedPartition(previous.partitionKey)) {
19112
+ throw continuationMismatch("partition");
19113
+ }
19114
+ if (refreshed.leaseToken.equals(previous.leaseToken)) {
19115
+ throw continuationMismatch("lease token did not change");
19116
+ }
19117
+ if (fencingValue(refreshed.fencingToken) <= fencingValue(previous.fencingToken)) {
19118
+ throw continuationMismatch("fencing token did not increase");
19119
+ }
19120
+ if (refreshed.state !== "running") throw continuationMismatch("physical state");
19121
+ if (refreshed.runState !== toState) throw continuationMismatch("runState");
19122
+ }
19123
+ function leaseValidationMismatch(field3) {
19124
+ return new FerricStoreError(`lease validation returned a mismatched ${field3}`);
19125
+ }
19126
+ function continuationMismatch(field3) {
19127
+ return new FerricStoreError(`continuation response returned a mismatched ${field3}`);
19128
+ }
19129
+ function normalizedPartition(value) {
19130
+ return value == null || value === "" ? void 0 : value;
19131
+ }
19132
+ function fencingValue(value) {
19133
+ return typeof value === "bigint" ? value : BigInt(value);
19134
+ }
19135
+ function assertNonEmptyString(value, name) {
19136
+ if (typeof value !== "string" || value.length === 0) {
19137
+ throw new TypeError(`${name} must be a non-empty string`);
19138
+ }
19139
+ }
19140
+ function assertStepValueIsReserved(options, resultName) {
19141
+ if (options.values != null && Object.hasOwn(options.values, resultName)) {
19142
+ throw new TypeError("values cannot mutate the reserved durable step result");
19143
+ }
19144
+ if (options.valueRefs != null && Object.hasOwn(options.valueRefs, resultName)) {
19145
+ throw new TypeError("valueRefs cannot mutate the reserved durable step result");
19146
+ }
19147
+ if (options.dropValues?.includes(resultName) === true) {
19148
+ throw new TypeError("dropValues cannot mutate the reserved durable step result");
19149
+ }
19150
+ if (options.overrideValues?.includes(resultName) === true) {
19151
+ throw new TypeError("overrideValues cannot mutate the reserved durable step result");
19152
+ }
19153
+ }
19154
+ function claimedItemFromRecord(record) {
19155
+ return {
19156
+ ...record.attributes == null ? {} : { attributes: record.attributes },
19157
+ fencingToken: record.fencingToken,
19158
+ id: record.id,
19159
+ leaseToken: Buffer.from(record.leaseToken),
19160
+ ...record.partitionKey === "" ? {} : { partitionKey: record.partitionKey },
19161
+ ...record.payload === void 0 ? {} : { payload: record.payload },
19162
+ ...record.runState == null ? {} : { runState: record.runState },
19163
+ state: record.state,
19164
+ type: record.type
19165
+ };
19166
+ }
19167
+
18910
19168
  // src/client.ts
18911
19169
  var FerricStoreClient = class _FerricStoreClient extends FerricStoreProducerClient {
18912
19170
  static async fromUrl(url, options = {}) {
@@ -19021,6 +19279,10 @@ var FerricStoreClient = class _FerricStoreClient extends FerricStoreProducerClie
19021
19279
  appendNamedValues(args, this.codec, options);
19022
19280
  return await this.recordOrGet(await this.commandArgs(args), id, partitionKey);
19023
19281
  }
19282
+ /**
19283
+ * @deprecated Low-level compatibility API. Use {@link advance} for state-only
19284
+ * continuation or {@link step} for a durable closure.
19285
+ */
19024
19286
  async stepContinue(id, options) {
19025
19287
  const partitionKey = options.partitionKey;
19026
19288
  const returnJob = options.returnJob === true;
@@ -19048,6 +19310,18 @@ var FerricStoreClient = class _FerricStoreClient extends FerricStoreProducerClie
19048
19310
  const response = await this.commandArgs(args);
19049
19311
  return returnJob ? claimedItemFromResp(response, this.codec, { type }) : await this.recordOrGet(response, id, partitionKey);
19050
19312
  }
19313
+ /** Advance a claimed Flow and return its renewed claim. */
19314
+ async advance(job, options) {
19315
+ return await advanceClaim(this, job, options);
19316
+ }
19317
+ /**
19318
+ * Run a closure after validating the claim, then atomically journal its
19319
+ * result and advance the Flow. A committed result is replayed without
19320
+ * invoking the closure again.
19321
+ */
19322
+ async step(job, options) {
19323
+ return await runDurableStep(this, job, options);
19324
+ }
19051
19325
  async runStepsMany(items, options) {
19052
19326
  if (items.length === 0) return Buffer.from("OK");
19053
19327
  const hasStates = options.states != null;
@@ -19297,6 +19571,8 @@ function validateUnicodeScalarText2(value) {
19297
19571
 
19298
19572
  // src/outcomes.ts
19299
19573
  var OUTCOME_BRAND = /* @__PURE__ */ Symbol("ferricstore.outcome");
19574
+ var ALREADY_APPLIED_BRAND = /* @__PURE__ */ Symbol("ferricstore.alreadyApplied");
19575
+ var REPLAYED_STEP_BRAND = /* @__PURE__ */ Symbol("ferricstore.replayedStep");
19300
19576
  function transition(toState, options = {}) {
19301
19577
  return brandOutcome({ ...options, kind: "transition", toState });
19302
19578
  }
@@ -19309,6 +19585,22 @@ function retry(options = {}) {
19309
19585
  function fail2(options = {}) {
19310
19586
  return brandOutcome({ ...options, kind: "fail" });
19311
19587
  }
19588
+ function alreadyApplied(job) {
19589
+ return brandAlreadyApplied({ job, kind: "already_applied" });
19590
+ }
19591
+ function appliedStep(job, result) {
19592
+ return brandAlreadyApplied({ applied: true, job, kind: "already_applied", result });
19593
+ }
19594
+ function replayedStep(job, result) {
19595
+ const replayed = { applied: false, job, result };
19596
+ Object.defineProperty(replayed, REPLAYED_STEP_BRAND, {
19597
+ configurable: false,
19598
+ enumerable: false,
19599
+ value: true,
19600
+ writable: false
19601
+ });
19602
+ return replayed;
19603
+ }
19312
19604
  function isOutcome(value) {
19313
19605
  if (typeof value !== "object" || value == null || !Object.hasOwn(value, OUTCOME_BRAND) || value[OUTCOME_BRAND] !== true) {
19314
19606
  return false;
@@ -19319,6 +19611,12 @@ function isOutcome(value) {
19319
19611
  }
19320
19612
  return outcome.kind === "complete" || outcome.kind === "retry" || outcome.kind === "fail";
19321
19613
  }
19614
+ function isAlreadyAppliedOutcome(value) {
19615
+ return typeof value === "object" && value != null && Object.hasOwn(value, ALREADY_APPLIED_BRAND) && value[ALREADY_APPLIED_BRAND] === true && value.kind === "already_applied";
19616
+ }
19617
+ function isReplayedStepResult(value) {
19618
+ return typeof value === "object" && value != null && Object.hasOwn(value, REPLAYED_STEP_BRAND) && value[REPLAYED_STEP_BRAND] === true && value.applied === false;
19619
+ }
19322
19620
  function brandOutcome(outcome) {
19323
19621
  Object.defineProperty(outcome, "kind", {
19324
19622
  configurable: false,
@@ -19342,6 +19640,21 @@ function brandOutcome(outcome) {
19342
19640
  });
19343
19641
  return outcome;
19344
19642
  }
19643
+ function brandAlreadyApplied(outcome) {
19644
+ Object.defineProperty(outcome, "kind", {
19645
+ configurable: false,
19646
+ enumerable: true,
19647
+ value: "already_applied",
19648
+ writable: false
19649
+ });
19650
+ Object.defineProperty(outcome, ALREADY_APPLIED_BRAND, {
19651
+ configurable: false,
19652
+ enumerable: false,
19653
+ value: true,
19654
+ writable: false
19655
+ });
19656
+ return outcome;
19657
+ }
19345
19658
 
19346
19659
  // src/worker-config.ts
19347
19660
  function snapshotWorkerConfig(options) {
@@ -19358,8 +19671,7 @@ function snapshotWorkerConfig(options) {
19358
19671
  return Object.freeze(snapshot);
19359
19672
  }
19360
19673
 
19361
- // src/worker-internal.ts
19362
- var DEFAULT_FLOW_MANY_BATCH_LIMIT2 = 1e3;
19674
+ // src/worker-lease-guard.ts
19363
19675
  var LeaseRenewalError = class extends Error {
19364
19676
  constructor(cause) {
19365
19677
  super("FerricStore lease renewal failed", { cause });
@@ -19370,37 +19682,58 @@ var LeaseRenewalGuard = class {
19370
19682
  constructor(client, leasedJob, leaseMs, options) {
19371
19683
  this.client = client;
19372
19684
  this.leaseMs = leaseMs;
19373
- this.job = snapshotLeasedJob(leasedJob);
19374
- if (options.leaseRenewal === false) {
19375
- return;
19376
- }
19377
- const intervalMs = positiveInteger4(options.leaseRenewIntervalMs, Math.max(1, Math.trunc(leaseMs / 2)));
19378
- this.timer = setLongInterval(() => this.renew(), intervalMs);
19379
- this.timer.unref();
19685
+ this.currentJob = snapshotLeasedJob(leasedJob);
19686
+ this.renewalEnabled = options.leaseRenewal !== false;
19687
+ this.intervalMs = positiveInteger4(options.leaseRenewIntervalMs, Math.max(1, Math.trunc(leaseMs / 2)));
19688
+ this.startRenewal();
19380
19689
  }
19381
19690
  client;
19382
19691
  leaseMs;
19383
19692
  error;
19384
19693
  inFlight;
19385
- job;
19694
+ currentJob;
19695
+ intervalMs;
19696
+ renewalEnabled;
19697
+ state = "active";
19386
19698
  timer;
19699
+ get job() {
19700
+ return this.currentJob;
19701
+ }
19387
19702
  assertActive() {
19388
- if (this.error != null) {
19389
- throw this.error;
19390
- }
19703
+ if (this.error != null) throw this.error;
19391
19704
  }
19392
19705
  async stop() {
19393
- if (this.timer != null) {
19394
- this.timer.cancel();
19395
- this.timer = void 0;
19396
- }
19706
+ this.state = "stopped";
19707
+ this.cancelRenewal();
19397
19708
  await this.inFlight;
19398
19709
  this.assertActive();
19399
19710
  }
19711
+ async pauseForLeaseMutation() {
19712
+ if (this.state === "stopped") throw new Error("lease renewal guard is stopped");
19713
+ if (this.state === "paused") return;
19714
+ this.state = "paused";
19715
+ this.cancelRenewal();
19716
+ await this.inFlight;
19717
+ this.assertActive();
19718
+ }
19719
+ resumeWith(job) {
19720
+ if (this.state !== "paused") throw new Error("lease renewal guard is not paused");
19721
+ this.currentJob = snapshotLeasedJob(job);
19722
+ this.state = "active";
19723
+ this.startRenewal();
19724
+ }
19725
+ cancelRenewal() {
19726
+ if (this.timer == null) return;
19727
+ this.timer.cancel();
19728
+ this.timer = void 0;
19729
+ }
19730
+ startRenewal() {
19731
+ if (!this.renewalEnabled || this.state !== "active") return;
19732
+ this.timer = setLongInterval(() => this.renew(), this.intervalMs);
19733
+ this.timer.unref();
19734
+ }
19400
19735
  renew() {
19401
- if (this.error != null || this.inFlight != null) {
19402
- return;
19403
- }
19736
+ if (this.state !== "active" || this.error != null || this.inFlight != null) return;
19404
19737
  const renewal = this.client.extendLease(this.job.id, {
19405
19738
  fencingToken: this.job.fencingToken,
19406
19739
  leaseMs: this.leaseMs,
@@ -19411,17 +19744,12 @@ var LeaseRenewalGuard = class {
19411
19744
  () => void 0,
19412
19745
  (error) => {
19413
19746
  this.error = new LeaseRenewalError(error);
19414
- if (this.timer != null) {
19415
- this.timer.cancel();
19416
- this.timer = void 0;
19417
- }
19747
+ this.cancelRenewal();
19418
19748
  }
19419
19749
  );
19420
19750
  this.inFlight = renewal;
19421
19751
  void renewal.finally(() => {
19422
- if (this.inFlight === renewal) {
19423
- this.inFlight = void 0;
19424
- }
19752
+ if (this.inFlight === renewal) this.inFlight = void 0;
19425
19753
  });
19426
19754
  }
19427
19755
  };
@@ -19445,8 +19773,14 @@ function snapshotLeasedJob(job) {
19445
19773
  }
19446
19774
  return snapshot;
19447
19775
  }
19776
+ function positiveInteger4(value, fallback) {
19777
+ return value == null || !Number.isFinite(value) ? fallback : Math.max(1, Math.trunc(value));
19778
+ }
19779
+
19780
+ // src/worker-internal.ts
19781
+ var DEFAULT_FLOW_MANY_BATCH_LIMIT2 = 1e3;
19448
19782
  function workerConcurrency(options) {
19449
- return positiveInteger4(options.concurrency ?? options.workers, 1);
19783
+ return positiveInteger5(options.concurrency ?? options.workers, 1);
19450
19784
  }
19451
19785
  function workerRefillStrategy(options) {
19452
19786
  return options.refillStrategy === "wave" ? "wave" : "continuous";
@@ -19489,18 +19823,18 @@ function workerErrorPayload(error, options, codec) {
19489
19823
  }
19490
19824
  function workerBatchSize(options, flowManyBatchLimit = DEFAULT_FLOW_MANY_BATCH_LIMIT2) {
19491
19825
  return Math.min(
19492
- positiveInteger4(options.batchSize, 10),
19493
- positiveInteger4(flowManyBatchLimit, DEFAULT_FLOW_MANY_BATCH_LIMIT2)
19826
+ positiveInteger5(options.batchSize, 10),
19827
+ positiveInteger5(flowManyBatchLimit, DEFAULT_FLOW_MANY_BATCH_LIMIT2)
19494
19828
  );
19495
19829
  }
19496
19830
  function workerClaimLimit(options, flowManyBatchLimit = DEFAULT_FLOW_MANY_BATCH_LIMIT2) {
19497
19831
  return Math.min(workerBatchSize(options, flowManyBatchLimit), workerConcurrency(options));
19498
19832
  }
19499
19833
  function workerDrainBatches(options) {
19500
- return positiveInteger4(options.claimDrainBatches, 1);
19834
+ return positiveInteger5(options.claimDrainBatches, 1);
19501
19835
  }
19502
19836
  function workerLeaseMs(options, fallback = 3e4) {
19503
- return positiveInteger4(options.leaseMs, positiveInteger4(fallback, 3e4));
19837
+ return positiveInteger5(options.leaseMs, positiveInteger5(fallback, 3e4));
19504
19838
  }
19505
19839
  function workerIdleSleepMs(options) {
19506
19840
  return nonNegativeDuration(options.idleSleepMs, 250);
@@ -19512,7 +19846,7 @@ function workerClaimBlockMs(options, useBlocking) {
19512
19846
  if (options.signal == null || !Number.isFinite(options.blockMs) || options.blockMs < 0) {
19513
19847
  return options.blockMs;
19514
19848
  }
19515
- const abortPollMs = positiveInteger4(options.abortPollMs, 1e3);
19849
+ const abortPollMs = positiveInteger5(options.abortPollMs, 1e3);
19516
19850
  return options.blockMs === 0 ? abortPollMs : Math.min(Math.trunc(options.blockMs), abortPollMs);
19517
19851
  }
19518
19852
  function workerMaxIdleSleepMs(options, idleSleepMs = workerIdleSleepMs(options)) {
@@ -19523,8 +19857,8 @@ function nextWorkerIdleSleepMs(currentIdleSleepMs, maxIdleSleepMs) {
19523
19857
  return Math.min(maxIdleSleepMs, Math.max(1, currentIdleSleepMs * 2));
19524
19858
  }
19525
19859
  async function runContinuousWorkerPool(options) {
19526
- const concurrency = positiveInteger4(options.concurrency, 1);
19527
- const maxClaimSize = positiveInteger4(options.maxClaimSize, concurrency);
19860
+ const concurrency = positiveInteger5(options.concurrency, 1);
19861
+ const maxClaimSize = positiveInteger5(options.maxClaimSize, concurrency);
19528
19862
  const initialIdleSleepMs = nonNegativeDuration(options.idleSleepMs, 250);
19529
19863
  const maxIdleSleepMs = Math.max(
19530
19864
  initialIdleSleepMs,
@@ -19646,7 +19980,7 @@ function normalizeWorkflowWorkerConfig(options) {
19646
19980
  ...captured
19647
19981
  });
19648
19982
  }
19649
- function positiveInteger4(value, fallback) {
19983
+ function positiveInteger5(value, fallback) {
19650
19984
  return value == null || !Number.isFinite(value) ? fallback : Math.max(1, Math.trunc(value));
19651
19985
  }
19652
19986
  function nonNegativeInteger5(value, fallback) {
@@ -20588,51 +20922,53 @@ var Queue = class {
20588
20922
  // src/workflow.ts
20589
20923
  import { randomUUID as randomUUID2 } from "crypto";
20590
20924
 
20591
- // src/workflow-utilities.ts
20592
- function resolveWorkflowStates(configured, registrationFor) {
20593
- const stateNames = [...new Set(configured)];
20594
- if (stateNames.length === 0) throw new Error("Workflow worker requires at least one state");
20595
- return stateNames.map((stateName) => {
20596
- const registration = registrationFor(stateName);
20597
- if (registration == null) {
20598
- throw new Error(`No handler registered for workflow state '${stateName}'`);
20599
- }
20600
- return { registration, stateName };
20925
+ // src/workflow-registration.ts
20926
+ function createWorkflowStateRegistration(name, handler, options, defaultValueMaxBytes) {
20927
+ const own2 = { ...options };
20928
+ const claimValues = own2.claimValues == null ? void 0 : snapshotOwnStringArray(own2.claimValues, "claimValues");
20929
+ const retryPolicy = own2.retryPolicy == null ? void 0 : Object.freeze({ ...own2.retryPolicy });
20930
+ return Object.freeze({
20931
+ claimPayload: own2.claimPayload ?? true,
20932
+ claimRecord: own2.claimRecord ?? true,
20933
+ exceptionPolicy: normalizeExceptionPolicy(own2.exceptionPolicy),
20934
+ handler,
20935
+ leaseMs: own2.leaseMs ?? 3e4,
20936
+ ...own2.mode == null ? {} : { mode: normalizeStateMode(own2.mode) },
20937
+ name,
20938
+ returnRecord: own2.returnRecord ?? false,
20939
+ valueMaxBytes: own2.valueMaxBytes ?? defaultValueMaxBytes,
20940
+ ...claimValues == null ? {} : { claimValues },
20941
+ ...retryPolicy == null ? {} : { retryPolicy }
20601
20942
  });
20602
20943
  }
20603
- function normalizeStateMode(mode) {
20604
- if (mode === "fifo" || mode === "parallel") return mode;
20605
- throw new Error("state mode must be 'fifo' or 'parallel'");
20606
- }
20607
- function valueRefToString(value) {
20608
- if (typeof value === "string") return value;
20609
- if (Buffer.isBuffer(value)) return value.toString("utf8");
20610
- if (typeof value === "object" && value != null && Object.hasOwn(value, "ref")) {
20611
- const ref = value.ref;
20612
- return typeof ref === "string" ? ref : Buffer.isBuffer(ref) ? ref.toString("utf8") : void 0;
20613
- }
20614
- return void 0;
20615
- }
20616
20944
 
20617
20945
  // src/workflow-context.ts
20618
20946
  var MISSING_VALUE = /* @__PURE__ */ Symbol("ferricstore.missingValue");
20619
20947
  var WorkflowContext = class {
20620
- workflow;
20621
- job;
20622
- stateName;
20623
- flow;
20624
- leaseJob;
20625
- valueCache = /* @__PURE__ */ new Map();
20626
- constructor(workflow, job, stateName, leaseJob = job) {
20948
+ constructor(workflow, job, stateName, leaseJob = job, mutationCoordinator) {
20949
+ this.mutationCoordinator = mutationCoordinator;
20627
20950
  this.workflow = workflow;
20628
20951
  this.job = job;
20629
20952
  this.leaseJob = leaseJob;
20630
- this.stateName = stateName;
20953
+ this.currentStateName = stateName;
20631
20954
  this.flow = new WorkflowFlowCommands(this);
20632
20955
  }
20956
+ mutationCoordinator;
20957
+ workflow;
20958
+ job;
20959
+ flow;
20960
+ appliedMutation = false;
20961
+ currentStateName;
20962
+ leaseJob;
20963
+ mutationFailure;
20964
+ mutationPhase = "idle";
20965
+ valueCache = /* @__PURE__ */ new Map();
20633
20966
  get client() {
20634
20967
  return this.workflow.client;
20635
20968
  }
20969
+ get stateName() {
20970
+ return this.currentStateName;
20971
+ }
20636
20972
  get id() {
20637
20973
  return this.leaseJob.id;
20638
20974
  }
@@ -20643,7 +20979,7 @@ var WorkflowContext = class {
20643
20979
  return this.leaseJob.state;
20644
20980
  }
20645
20981
  get logicalState() {
20646
- return this.stateName;
20982
+ return this.currentStateName;
20647
20983
  }
20648
20984
  get partitionKey() {
20649
20985
  return this.leaseJob.partitionKey;
@@ -20681,6 +21017,53 @@ var WorkflowContext = class {
20681
21017
  get correlationId() {
20682
21018
  return "correlationId" in this.job ? this.job.correlationId : void 0;
20683
21019
  }
21020
+ async advance(toState, options = {}) {
21021
+ if (typeof toState !== "string" || toState.length === 0) {
21022
+ throw new TypeError("toState must be a non-empty string");
21023
+ }
21024
+ const current = this.durableClaim();
21025
+ await this.beforeCommit();
21026
+ let refreshed;
21027
+ try {
21028
+ refreshed = await advanceClaim(this.client, current, { ...options, toState });
21029
+ } catch (error) {
21030
+ this.commitFailed(error);
21031
+ throw error;
21032
+ }
21033
+ this.committed(refreshed);
21034
+ return alreadyApplied(refreshed);
21035
+ }
21036
+ async step(options) {
21037
+ let applied = false;
21038
+ const stepped = await runDurableStep(this.client, this.durableClaim(), options, {
21039
+ beforeCommit: async () => await this.beforeCommit(),
21040
+ commitFailed: (error) => this.commitFailed(error),
21041
+ committed: (job) => {
21042
+ applied = true;
21043
+ this.committed(job);
21044
+ },
21045
+ replayed: (job) => this.replayed(job)
21046
+ });
21047
+ return applied ? appliedStep(stepped.job, stepped.result) : replayedStep(stepped.job, stepped.result);
21048
+ }
21049
+ /** @internal */
21050
+ get hasAppliedMutation() {
21051
+ return this.appliedMutation;
21052
+ }
21053
+ /** @internal */
21054
+ get uncertainMutationFailure() {
21055
+ return this.mutationPhase === "uncertain" ? this.mutationFailure : void 0;
21056
+ }
21057
+ /** @internal */
21058
+ get hasUncertainMutation() {
21059
+ return this.mutationPhase === "uncertain";
21060
+ }
21061
+ /** @internal */
21062
+ assertAppliedOutcome(outcome) {
21063
+ if (!this.appliedMutation || !outcome.job.leaseToken.equals(this.leaseJob.leaseToken) || outcome.job.fencingToken !== this.leaseJob.fencingToken) {
21064
+ throw new Error("already-applied outcome does not match the workflow's refreshed claim");
21065
+ }
21066
+ }
20684
21067
  async value(name, defaultValue, options = {}) {
20685
21068
  const useLocalCache = options.localCache ?? this.workflow.valueConfig.localCache;
20686
21069
  if (useLocalCache && this.valueCache.has(name)) {
@@ -20760,7 +21143,49 @@ var WorkflowContext = class {
20760
21143
  return result;
20761
21144
  }
20762
21145
  valueMaxBytes() {
20763
- return this.workflow.stateRegistration(this.stateName)?.valueMaxBytes;
21146
+ return this.workflow.stateRegistration(this.currentStateName)?.valueMaxBytes;
21147
+ }
21148
+ durableClaim() {
21149
+ const runState = this.leaseJob.runState ?? (this.leaseJob[CLAIMED_ITEM_WIRE] == null ? void 0 : this.currentStateName);
21150
+ if (typeof runState !== "string" || runState.length === 0) {
21151
+ throw new TypeError("job.runState must be a non-empty string");
21152
+ }
21153
+ return {
21154
+ ...this.leaseJob,
21155
+ leaseToken: Buffer.from(this.leaseJob.leaseToken),
21156
+ runState
21157
+ };
21158
+ }
21159
+ async beforeCommit() {
21160
+ if (this.mutationPhase !== "idle") {
21161
+ throw new Error("a durable workflow mutation is already in progress or has an uncertain result");
21162
+ }
21163
+ await this.mutationCoordinator?.pause();
21164
+ this.mutationPhase = "committing";
21165
+ }
21166
+ commitFailed(error) {
21167
+ if (!durableMutationMayHaveCommitted(error)) {
21168
+ this.mutationFailure = void 0;
21169
+ this.mutationPhase = "idle";
21170
+ this.mutationCoordinator?.resume(this.leaseJob);
21171
+ return;
21172
+ }
21173
+ this.mutationFailure = error;
21174
+ this.mutationPhase = "uncertain";
21175
+ }
21176
+ committed(job) {
21177
+ this.acceptJob(job);
21178
+ this.appliedMutation = true;
21179
+ this.mutationCoordinator?.resume(job);
21180
+ }
21181
+ replayed(job) {
21182
+ this.acceptJob(job);
21183
+ }
21184
+ acceptJob(job) {
21185
+ this.leaseJob = job;
21186
+ this.currentStateName = job.runState ?? this.currentStateName;
21187
+ this.mutationFailure = void 0;
21188
+ this.mutationPhase = "idle";
20764
21189
  }
20765
21190
  };
20766
21191
  var WorkflowFlowCommands = class {
@@ -20798,6 +21223,7 @@ var WorkflowFlowCommands = class {
20798
21223
  toState
20799
21224
  });
20800
21225
  }
21226
+ /** @deprecated Use `ctx.advance(toState)`. */
20801
21227
  async stepContinue(toState, options = {}) {
20802
21228
  return await this.ctx.client.stepContinue(this.ctx.id, {
20803
21229
  ...options,
@@ -20960,25 +21386,78 @@ async function applyFail(client, ctx, outcome, returnRecord) {
20960
21386
  });
20961
21387
  }
20962
21388
 
20963
- // src/workflow-registration.ts
20964
- function createWorkflowStateRegistration(name, handler, options, defaultValueMaxBytes) {
20965
- const own2 = { ...options };
20966
- const claimValues = own2.claimValues == null ? void 0 : snapshotOwnStringArray(own2.claimValues, "claimValues");
20967
- const retryPolicy = own2.retryPolicy == null ? void 0 : Object.freeze({ ...own2.retryPolicy });
20968
- return Object.freeze({
20969
- claimPayload: own2.claimPayload ?? true,
20970
- claimRecord: own2.claimRecord ?? true,
20971
- exceptionPolicy: normalizeExceptionPolicy(own2.exceptionPolicy),
20972
- handler,
20973
- leaseMs: own2.leaseMs ?? 3e4,
20974
- ...own2.mode == null ? {} : { mode: normalizeStateMode(own2.mode) },
20975
- name,
20976
- returnRecord: own2.returnRecord ?? false,
20977
- valueMaxBytes: own2.valueMaxBytes ?? defaultValueMaxBytes,
20978
- ...claimValues == null ? {} : { claimValues },
20979
- ...retryPolicy == null ? {} : { retryPolicy }
21389
+ // src/workflow-job-execution.ts
21390
+ async function executeWorkflowJob(workflow, workerOptions, job, registration, guard) {
21391
+ guard.assertActive();
21392
+ const ctx = new WorkflowContext(workflow, job, registration.name, guard.job, {
21393
+ pause: async () => await guard.pauseForLeaseMutation(),
21394
+ resume: (refreshed) => guard.resumeWith(refreshed)
21395
+ });
21396
+ let value;
21397
+ try {
21398
+ value = await registration.handler(ctx);
21399
+ } catch (error) {
21400
+ await guard.stop();
21401
+ if (ctx.hasUncertainMutation) throw asError(error, "durable workflow mutation result is uncertain");
21402
+ await applyHandlerError(workflow, workerOptions, ctx, registration, error);
21403
+ return;
21404
+ }
21405
+ await guard.stop();
21406
+ if (ctx.hasUncertainMutation) {
21407
+ throw asError(ctx.uncertainMutationFailure, "durable workflow mutation result is uncertain");
21408
+ }
21409
+ if (isAlreadyAppliedOutcome(value)) {
21410
+ ctx.assertAppliedOutcome(value);
21411
+ await releaseAppliedMutation(workflow, ctx);
21412
+ return;
21413
+ }
21414
+ if (isReplayedStepResult(value)) {
21415
+ await applyWorkflowOutcome(
21416
+ workflow.client,
21417
+ ctx,
21418
+ complete({ result: value.result }),
21419
+ registration.returnRecord
21420
+ );
21421
+ return;
21422
+ }
21423
+ if (isOutcome(value)) {
21424
+ await applyWorkflowOutcome(workflow.client, ctx, value, registration.returnRecord);
21425
+ return;
21426
+ }
21427
+ if (ctx.hasAppliedMutation) {
21428
+ await releaseAppliedMutation(workflow, ctx);
21429
+ return;
21430
+ }
21431
+ await applyWorkflowOutcome(
21432
+ workflow.client,
21433
+ ctx,
21434
+ complete({ result: value }),
21435
+ registration.returnRecord
21436
+ );
21437
+ }
21438
+ function asError(value, message) {
21439
+ return value instanceof Error ? value : new Error(message, { cause: value });
21440
+ }
21441
+ async function releaseAppliedMutation(workflow, ctx) {
21442
+ await workflow.client.transition(ctx.id, {
21443
+ fencingToken: ctx.fencingToken,
21444
+ fromState: ctx.state,
21445
+ leaseToken: ctx.leaseToken,
21446
+ partitionKey: ctx.partitionKey,
21447
+ toState: ctx.logicalState
20980
21448
  });
20981
21449
  }
21450
+ async function applyHandlerError(workflow, workerOptions, ctx, registration, error) {
21451
+ const policy = workerOptions.exceptionPolicy ?? registration.exceptionPolicy;
21452
+ if (policy === "raise") throw error;
21453
+ const encodedError = workerErrorPayload(error, workerOptions, workflow.client.codec);
21454
+ await applyWorkflowOutcome(
21455
+ workflow.client,
21456
+ ctx,
21457
+ policy === "fail" ? fail2({ error: encodedError }) : retry({ error: encodedError }),
21458
+ registration.returnRecord
21459
+ );
21460
+ }
20982
21461
 
20983
21462
  // src/workflow.ts
20984
21463
  var WorkflowClient = class {
@@ -21107,6 +21586,10 @@ var WorkflowWorker = class {
21107
21586
  claimed += jobs.length;
21108
21587
  const guards = jobs.map((job) => new LeaseRenewalGuard(this.workflow.client, job, leaseMs, this.options));
21109
21588
  let cursor = 0;
21589
+ let batchFailure;
21590
+ const recordFailure = (error) => {
21591
+ batchFailure ??= { error };
21592
+ };
21110
21593
  const runNext = async () => {
21111
21594
  while (cursor < jobs.length) {
21112
21595
  const index = cursor;
@@ -21117,19 +21600,22 @@ var WorkflowWorker = class {
21117
21600
  try {
21118
21601
  await this.applyJob(job, registration, guard);
21119
21602
  applied += 1;
21603
+ } catch (error) {
21604
+ recordFailure(error);
21120
21605
  } finally {
21121
- await guard.stop();
21606
+ try {
21607
+ await guard.stop();
21608
+ } catch (error) {
21609
+ recordFailure(error);
21610
+ }
21122
21611
  }
21123
21612
  }
21124
21613
  };
21125
21614
  try {
21126
- const handlers = await Promise.allSettled(
21615
+ await Promise.all(
21127
21616
  Array.from({ length: Math.min(concurrency, jobs.length) }, () => runNext())
21128
21617
  );
21129
- const failed = handlers.find((handler) => handler.status === "rejected");
21130
- if (failed != null) {
21131
- throw failed.reason;
21132
- }
21618
+ if (batchFailure != null) throwError(batchFailure.error);
21133
21619
  } finally {
21134
21620
  await Promise.allSettled(
21135
21621
  guards.slice(cursor).map(async (guard) => await guard.stop())
@@ -21240,42 +21726,12 @@ var WorkflowWorker = class {
21240
21726
  });
21241
21727
  }
21242
21728
  async applyJob(job, registration, guard) {
21243
- guard.assertActive();
21244
- const ctx = new WorkflowContext(this.workflow, job, registration.name, guard.job);
21245
- let outcome;
21246
- try {
21247
- const value = await registration.handler(ctx);
21248
- outcome = isOutcome(value) ? value : complete({ result: value });
21249
- } catch (error) {
21250
- await guard.stop();
21251
- await this.applyHandlerError(ctx, registration, error);
21252
- return;
21253
- }
21254
- await guard.stop();
21255
- await applyWorkflowOutcome(this.workflow.client, ctx, outcome, registration.returnRecord);
21256
- }
21257
- async applyHandlerError(ctx, registration, error) {
21258
- const policy = this.options.exceptionPolicy ?? registration.exceptionPolicy;
21259
- if (policy === "raise") {
21260
- throw error;
21261
- }
21262
- if (policy === "fail") {
21263
- await applyWorkflowOutcome(
21264
- this.workflow.client,
21265
- ctx,
21266
- fail2({ error: workerErrorPayload(error, this.options, this.workflow.client.codec) }),
21267
- registration.returnRecord
21268
- );
21269
- return;
21270
- }
21271
- await applyWorkflowOutcome(
21272
- this.workflow.client,
21273
- ctx,
21274
- retry({ error: workerErrorPayload(error, this.options, this.workflow.client.codec) }),
21275
- registration.returnRecord
21276
- );
21729
+ await executeWorkflowJob(this.workflow, this.options, job, registration, guard);
21277
21730
  }
21278
21731
  };
21732
+ function throwError(value) {
21733
+ throw value instanceof Error ? value : new Error("workflow worker failed", { cause: value });
21734
+ }
21279
21735
 
21280
21736
  // src/version.ts
21281
21737
  var FERRICSTORE_SDK_VERSION = "0.12.0";
@@ -21341,6 +21797,7 @@ export {
21341
21797
  complete,
21342
21798
  fail2 as fail,
21343
21799
  httpCommandDisposition,
21800
+ isAlreadyAppliedOutcome,
21344
21801
  isOutcome,
21345
21802
  isReconnectableClosedConnectionError,
21346
21803
  mapException,