@ferricstore/ferricstore 0.12.2 → 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.
- package/README.md +2 -2
- package/dist/index.cjs +591 -133
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +61 -7
- package/dist/index.d.ts +61 -7
- package/dist/index.js +586 -129
- package/dist/index.js.map +1 -1
- package/dist/langgraph.cjs.map +1 -1
- package/dist/langgraph.d.cts +1 -1
- package/dist/langgraph.d.ts +1 -1
- package/dist/langgraph.js.map +1 -1
- package/dist/{outcomes-DmBwnq0Y.d.cts → outcomes-CcX956h4.d.cts} +20 -1
- package/dist/{outcomes-BbFDp3AH.d.ts → outcomes-DLJYTrX8.d.ts} +20 -1
- package/docs/api/assets/hierarchy.js +1 -1
- package/docs/api/assets/navigation.js +1 -1
- package/docs/api/assets/search.js +1 -1
- package/docs/api/classes/ConnectionClosedError.html +1 -1
- package/docs/api/classes/FerricStoreClient.html +68 -23
- package/docs/api/classes/FerricStoreError.html +1 -1
- package/docs/api/classes/FlowAlreadyExistsError.html +1 -1
- package/docs/api/classes/FlowBatchError.html +1 -1
- package/docs/api/classes/FlowNotFoundError.html +1 -1
- package/docs/api/classes/FlowQueryError.html +1 -1
- package/docs/api/classes/FlowWrongStateError.html +1 -1
- package/docs/api/classes/HTTPTransportError.html +1 -1
- package/docs/api/classes/InvalidCommandError.html +1 -1
- package/docs/api/classes/LeaseRenewalError.html +3 -3
- package/docs/api/classes/LockHeldError.html +1 -1
- package/docs/api/classes/LockNotOwnedError.html +1 -1
- package/docs/api/classes/OverloadedError.html +1 -1
- package/docs/api/classes/QueueCompletionError.html +1 -1
- package/docs/api/classes/RequestTimeoutError.html +1 -1
- package/docs/api/classes/RerouteError.html +1 -1
- package/docs/api/classes/StaleLeaseError.html +1 -1
- package/docs/api/classes/StalePolicyGenerationError.html +1 -1
- package/docs/api/classes/Workflow.html +19 -19
- package/docs/api/classes/WorkflowClient.html +4 -4
- package/docs/api/classes/WorkflowContext.html +74 -36
- package/docs/api/classes/WorkflowFlowCommands.html +26 -22
- package/docs/api/classes/WorkflowWorker.html +6 -6
- package/docs/api/functions/complete.html +1 -1
- package/docs/api/functions/fail.html +1 -1
- package/docs/api/functions/isAlreadyAppliedOutcome.html +41 -0
- package/docs/api/functions/isOutcome.html +1 -1
- package/docs/api/functions/retry.html +1 -1
- package/docs/api/functions/transition.html +1 -1
- package/docs/api/hierarchy.html +55 -47
- package/docs/api/index.html +2 -2
- package/docs/api/interfaces/AdminListOptions.html +1 -1
- package/docs/api/interfaces/AdvanceOptions.html +141 -0
- package/docs/api/interfaces/AlreadyAppliedOutcome.html +69 -0
- package/docs/api/interfaces/AppliedStepOutcome.html +87 -0
- package/docs/api/interfaces/CancelOptions.html +6 -6
- package/docs/api/interfaces/CompleteOptions.html +4 -4
- package/docs/api/interfaces/CompleteOutcome.html +13 -13
- package/docs/api/interfaces/CreateManyOptions.html +1 -1
- package/docs/api/interfaces/EffectCompensateOptions.html +1 -1
- package/docs/api/interfaces/EffectConfirmOptions.html +1 -1
- package/docs/api/interfaces/EffectFailOptions.html +1 -1
- package/docs/api/interfaces/EffectStatusOptions.html +1 -1
- package/docs/api/interfaces/FailOptions.html +4 -4
- package/docs/api/interfaces/FailOutcome.html +13 -13
- package/docs/api/interfaces/FerricStoreClientFromUrlOptions.html +1 -1
- package/docs/api/interfaces/FerricStoreClientOptions.html +1 -1
- package/docs/api/interfaces/FlowQueryCountResult.html +1 -1
- package/docs/api/interfaces/FlowQueryIndexBuild.html +1 -1
- package/docs/api/interfaces/FlowQueryIndexProgress.html +1 -1
- package/docs/api/interfaces/FlowQueryIndexValidation.html +1 -1
- package/docs/api/interfaces/FlowQueryRecordsResult.html +1 -1
- package/docs/api/interfaces/HistoryOptions.html +16 -16
- package/docs/api/interfaces/InvocationCreateOptions.html +1 -1
- package/docs/api/interfaces/LeaseMutationOptions.html +1 -1
- package/docs/api/interfaces/LimitListOptions.html +1 -1
- package/docs/api/interfaces/MutateOptions.html +1 -1
- package/docs/api/interfaces/NamedValueMutation.html +9 -9
- package/docs/api/interfaces/ReadOptions.html +12 -12
- package/docs/api/interfaces/ReclaimOptions.html +1 -1
- package/docs/api/interfaces/ReplayedStepResult.html +73 -0
- package/docs/api/interfaces/RequestContextOptions.html +1 -1
- package/docs/api/interfaces/RetryOptions.html +4 -4
- package/docs/api/interfaces/RetryOutcome.html +13 -13
- package/docs/api/interfaces/RunStepsItem.html +3 -3
- package/docs/api/interfaces/RunStepsManyOptions.html +11 -11
- package/docs/api/interfaces/SearchOptions.html +13 -13
- package/docs/api/interfaces/StepContinueOptions.html +1 -1
- package/docs/api/interfaces/StepOptions.html +159 -0
- package/docs/api/interfaces/StepResult.html +67 -0
- package/docs/api/interfaces/TDigestCreateOptions.html +1 -1
- package/docs/api/interfaces/TDigestMergeOptions.html +1 -1
- package/docs/api/interfaces/TransitionOptions.html +1 -1
- package/docs/api/interfaces/TransitionOutcome.html +14 -14
- package/docs/api/modules.html +6 -6
- package/docs/api/types/ConnectionRequestDisposition.html +1 -1
- package/docs/api/types/ManagementPairs.html +1 -1
- package/docs/api/types/Outcome.html +1 -1
- package/docs/api/types/SearchStateMeta.html +1 -1
- package/docs/api/types/WorkflowStepResult.html +38 -0
- package/package.json +1 -1
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
|
-
|
|
18503
|
+
const requestedState = options.state;
|
|
18504
|
+
const includeState = options.includeState ?? requestedState == null;
|
|
18505
|
+
const jobs = await this.claimDue(type, {
|
|
18502
18506
|
...options,
|
|
18503
|
-
includeState
|
|
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(
|
|
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-
|
|
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.
|
|
19374
|
-
|
|
19375
|
-
|
|
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
|
-
|
|
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
|
-
|
|
19394
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
19493
|
-
|
|
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
|
|
19834
|
+
return positiveInteger5(options.claimDrainBatches, 1);
|
|
19501
19835
|
}
|
|
19502
19836
|
function workerLeaseMs(options, fallback = 3e4) {
|
|
19503
|
-
return
|
|
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 =
|
|
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 =
|
|
19527
|
-
const maxClaimSize =
|
|
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
|
|
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-
|
|
20592
|
-
function
|
|
20593
|
-
const
|
|
20594
|
-
|
|
20595
|
-
|
|
20596
|
-
|
|
20597
|
-
|
|
20598
|
-
|
|
20599
|
-
|
|
20600
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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-
|
|
20964
|
-
function
|
|
20965
|
-
|
|
20966
|
-
const
|
|
20967
|
-
|
|
20968
|
-
|
|
20969
|
-
|
|
20970
|
-
|
|
20971
|
-
|
|
20972
|
-
handler
|
|
20973
|
-
|
|
20974
|
-
|
|
20975
|
-
|
|
20976
|
-
|
|
20977
|
-
|
|
20978
|
-
|
|
20979
|
-
|
|
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
|
-
|
|
21606
|
+
try {
|
|
21607
|
+
await guard.stop();
|
|
21608
|
+
} catch (error) {
|
|
21609
|
+
recordFailure(error);
|
|
21610
|
+
}
|
|
21122
21611
|
}
|
|
21123
21612
|
}
|
|
21124
21613
|
};
|
|
21125
21614
|
try {
|
|
21126
|
-
|
|
21615
|
+
await Promise.all(
|
|
21127
21616
|
Array.from({ length: Math.min(concurrency, jobs.length) }, () => runNext())
|
|
21128
21617
|
);
|
|
21129
|
-
|
|
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
|
|
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,
|