@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.
- 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 +3 -3
package/dist/index.cjs
CHANGED
|
@@ -89,6 +89,7 @@ __export(index_exports, {
|
|
|
89
89
|
complete: () => complete,
|
|
90
90
|
fail: () => fail2,
|
|
91
91
|
httpCommandDisposition: () => httpCommandDisposition,
|
|
92
|
+
isAlreadyAppliedOutcome: () => isAlreadyAppliedOutcome,
|
|
92
93
|
isOutcome: () => isOutcome,
|
|
93
94
|
isReconnectableClosedConnectionError: () => isReconnectableClosedConnectionError,
|
|
94
95
|
mapException: () => mapException,
|
|
@@ -9122,6 +9123,8 @@ var HTTPTransport = class {
|
|
|
9122
9123
|
const timer = timeoutMs == null ? void 0 : setLongTimeout(() => controller.abort(requestTimeoutReason), timeoutMs);
|
|
9123
9124
|
timer?.unref();
|
|
9124
9125
|
try {
|
|
9126
|
+
const response = await this.request(this.config.commandUrl, "POST", body, 0, controller.signal);
|
|
9127
|
+
if (response.status !== 401 || this.config.headers.authorization == null) return response;
|
|
9125
9128
|
return await this.request(this.config.commandUrl, "POST", body, 0, controller.signal);
|
|
9126
9129
|
} catch (error) {
|
|
9127
9130
|
if (controller.signal.aborted) {
|
|
@@ -18598,12 +18601,20 @@ var FerricStoreClaimClient = class extends FerricStoreMutationClient {
|
|
|
18598
18601
|
return this.records(items);
|
|
18599
18602
|
}
|
|
18600
18603
|
async claimJobs(type, options) {
|
|
18601
|
-
|
|
18604
|
+
const requestedState = options.state;
|
|
18605
|
+
const includeState = options.includeState ?? requestedState == null;
|
|
18606
|
+
const jobs = await this.claimDue(type, {
|
|
18602
18607
|
...options,
|
|
18603
|
-
includeState
|
|
18608
|
+
includeState,
|
|
18604
18609
|
jobOnly: true,
|
|
18605
18610
|
limit: options.limit ?? 100
|
|
18606
18611
|
});
|
|
18612
|
+
if (!includeState && requestedState != null) {
|
|
18613
|
+
for (const job of jobs) {
|
|
18614
|
+
if (job[CLAIMED_ITEM_WIRE] != null) job.runState ??= requestedState;
|
|
18615
|
+
}
|
|
18616
|
+
}
|
|
18617
|
+
return jobs;
|
|
18607
18618
|
}
|
|
18608
18619
|
async reclaim(type, options) {
|
|
18609
18620
|
const captured = snapshotClaimOptions(options);
|
|
@@ -18626,7 +18637,7 @@ var FerricStoreClaimClient = class extends FerricStoreMutationClient {
|
|
|
18626
18637
|
this.appendPartitionOptions(args, captured);
|
|
18627
18638
|
append(args, "PRIORITY", captured.priority);
|
|
18628
18639
|
if (jobOnly) {
|
|
18629
|
-
append(args, "RETURN", compactClaimReturnMode2(
|
|
18640
|
+
append(args, "RETURN", compactClaimReturnMode2(true, captured.includeAttributes === true));
|
|
18630
18641
|
}
|
|
18631
18642
|
appendPayloadRead(args, captured.payload, captured.payloadMaxBytes);
|
|
18632
18643
|
appendValueReturn(args, { values: captured.values, valueMaxBytes: captured.valueMaxBytes });
|
|
@@ -19007,6 +19018,254 @@ var FerricStoreProducerClient = class extends FerricStoreClaimClient {
|
|
|
19007
19018
|
}
|
|
19008
19019
|
};
|
|
19009
19020
|
|
|
19021
|
+
// src/client-durable-step.ts
|
|
19022
|
+
var import_node_crypto2 = require("crypto");
|
|
19023
|
+
|
|
19024
|
+
// src/workflow-utilities.ts
|
|
19025
|
+
function resolveWorkflowStates(configured, registrationFor) {
|
|
19026
|
+
const stateNames = [...new Set(configured)];
|
|
19027
|
+
if (stateNames.length === 0) throw new Error("Workflow worker requires at least one state");
|
|
19028
|
+
return stateNames.map((stateName) => {
|
|
19029
|
+
const registration = registrationFor(stateName);
|
|
19030
|
+
if (registration == null) {
|
|
19031
|
+
throw new Error(`No handler registered for workflow state '${stateName}'`);
|
|
19032
|
+
}
|
|
19033
|
+
return { registration, stateName };
|
|
19034
|
+
});
|
|
19035
|
+
}
|
|
19036
|
+
function normalizeStateMode(mode) {
|
|
19037
|
+
if (mode === "fifo" || mode === "parallel") return mode;
|
|
19038
|
+
throw new Error("state mode must be 'fifo' or 'parallel'");
|
|
19039
|
+
}
|
|
19040
|
+
function valueRefToString(value) {
|
|
19041
|
+
if (typeof value === "string") return value;
|
|
19042
|
+
if (Buffer.isBuffer(value)) return value.toString("utf8");
|
|
19043
|
+
if (typeof value === "object" && value != null && Object.hasOwn(value, "ref")) {
|
|
19044
|
+
const ref = value.ref;
|
|
19045
|
+
return typeof ref === "string" ? ref : Buffer.isBuffer(ref) ? ref.toString("utf8") : void 0;
|
|
19046
|
+
}
|
|
19047
|
+
return void 0;
|
|
19048
|
+
}
|
|
19049
|
+
|
|
19050
|
+
// src/client-durable-step.ts
|
|
19051
|
+
var DURABLE_STEP_VALUE_PREFIX = "__ferricstore_step__:sha256:";
|
|
19052
|
+
function durableMutationMayHaveCommitted(error) {
|
|
19053
|
+
if (error instanceof ConnectionClosedError || error instanceof RequestTimeoutError) {
|
|
19054
|
+
return error.requestDisposition === "possibly_sent";
|
|
19055
|
+
}
|
|
19056
|
+
if (error instanceof HTTPTransportError) return error.safeToRetry !== true;
|
|
19057
|
+
if (error instanceof FlowNotFoundError || error instanceof FlowWrongStateError || error instanceof InvalidCommandError || error instanceof StaleLeaseError) {
|
|
19058
|
+
return false;
|
|
19059
|
+
}
|
|
19060
|
+
if (error instanceof FerricStoreError && error.safeToRetry === true) return false;
|
|
19061
|
+
return true;
|
|
19062
|
+
}
|
|
19063
|
+
async function advanceClaim(client, job, options) {
|
|
19064
|
+
assertDurableClaim(job);
|
|
19065
|
+
assertNonEmptyString(options.toState, "toState");
|
|
19066
|
+
const partitionKey = job.partitionKey === "" ? void 0 : job.partitionKey;
|
|
19067
|
+
const response = await client.stepContinue(job.id, {
|
|
19068
|
+
attributesDelete: options.attributesDelete,
|
|
19069
|
+
attributesMerge: options.attributesMerge,
|
|
19070
|
+
dropValues: options.dropValues,
|
|
19071
|
+
fencingToken: job.fencingToken,
|
|
19072
|
+
fromState: job.runState,
|
|
19073
|
+
leaseMs: options.leaseMs,
|
|
19074
|
+
leaseToken: job.leaseToken,
|
|
19075
|
+
nowMs: options.nowMs,
|
|
19076
|
+
overrideValues: options.overrideValues,
|
|
19077
|
+
partitionKey,
|
|
19078
|
+
payload: options.payload,
|
|
19079
|
+
returnJob: true,
|
|
19080
|
+
stateMeta: options.stateMeta,
|
|
19081
|
+
type: job.type,
|
|
19082
|
+
toState: options.toState,
|
|
19083
|
+
valueRefs: options.valueRefs,
|
|
19084
|
+
values: options.values
|
|
19085
|
+
});
|
|
19086
|
+
const refreshed = response;
|
|
19087
|
+
if (refreshed[CLAIMED_ITEM_WIRE] != null) {
|
|
19088
|
+
if (refreshed.runState != null && refreshed.runState !== options.toState) {
|
|
19089
|
+
throw new FerricStoreError("compact continuation response returned the wrong runState");
|
|
19090
|
+
}
|
|
19091
|
+
refreshed.runState ??= options.toState;
|
|
19092
|
+
if (refreshed.type == null && job.type != null) refreshed.type = job.type;
|
|
19093
|
+
}
|
|
19094
|
+
assertContinuationClaim(job, refreshed, options.toState);
|
|
19095
|
+
return refreshed;
|
|
19096
|
+
}
|
|
19097
|
+
async function runDurableStep(client, job, options, hooks = {}) {
|
|
19098
|
+
assertDurableClaim(job);
|
|
19099
|
+
const name = options.name;
|
|
19100
|
+
const run2 = options.run;
|
|
19101
|
+
const continuation = snapshotAdvanceOptions(options);
|
|
19102
|
+
assertNonEmptyString(name, "name");
|
|
19103
|
+
assertNonEmptyString(continuation.toState, "toState");
|
|
19104
|
+
if (typeof run2 !== "function") throw new TypeError("run must be a function");
|
|
19105
|
+
const resultName = durableStepValueName(name);
|
|
19106
|
+
assertStepValueIsReserved(continuation, resultName);
|
|
19107
|
+
const leaseMs = continuation.leaseMs ?? 3e4;
|
|
19108
|
+
const partitionKey = job.partitionKey === "" ? void 0 : job.partitionKey;
|
|
19109
|
+
const validated = await client.extendLease(job.id, {
|
|
19110
|
+
fencingToken: job.fencingToken,
|
|
19111
|
+
leaseMs,
|
|
19112
|
+
leaseToken: job.leaseToken,
|
|
19113
|
+
nowMs: continuation.nowMs,
|
|
19114
|
+
partitionKey
|
|
19115
|
+
});
|
|
19116
|
+
assertValidatedLease(job, validated);
|
|
19117
|
+
if (validated.valueRefs != null && Object.hasOwn(validated.valueRefs, resultName)) {
|
|
19118
|
+
if (validated.runState !== continuation.toState) {
|
|
19119
|
+
throw new FerricStoreError("committed durable step result does not match the requested target state");
|
|
19120
|
+
}
|
|
19121
|
+
const ref = valueRefToString(validated.valueRefs[resultName]);
|
|
19122
|
+
if (ref == null) {
|
|
19123
|
+
throw new FerricStoreError("committed durable step result has invalid reference metadata");
|
|
19124
|
+
}
|
|
19125
|
+
const entry = (await valueMGetEntries(client, [ref]))[0];
|
|
19126
|
+
if (entry?.found !== true) {
|
|
19127
|
+
throw new FerricStoreError("committed durable step result is missing");
|
|
19128
|
+
}
|
|
19129
|
+
const replayedJob = claimedItemFromRecord(validated);
|
|
19130
|
+
hooks.replayed?.(replayedJob);
|
|
19131
|
+
return { job: replayedJob, result: entry.value };
|
|
19132
|
+
}
|
|
19133
|
+
const result = normalizeStepResult(client, await run2());
|
|
19134
|
+
await hooks.beforeCommit?.();
|
|
19135
|
+
let refreshed;
|
|
19136
|
+
try {
|
|
19137
|
+
refreshed = await advanceClaim(client, validated, {
|
|
19138
|
+
...continuation,
|
|
19139
|
+
leaseMs,
|
|
19140
|
+
values: { ...continuation.values, [resultName]: result }
|
|
19141
|
+
});
|
|
19142
|
+
} catch (error) {
|
|
19143
|
+
hooks.commitFailed?.(error);
|
|
19144
|
+
throw error;
|
|
19145
|
+
}
|
|
19146
|
+
hooks.committed?.(refreshed);
|
|
19147
|
+
return { job: refreshed, result };
|
|
19148
|
+
}
|
|
19149
|
+
function normalizeStepResult(client, value) {
|
|
19150
|
+
return client.codec.decode(client.codec.encode(value));
|
|
19151
|
+
}
|
|
19152
|
+
function durableStepValueName(name) {
|
|
19153
|
+
const digest = (0, import_node_crypto2.createHash)("sha256").update(name, "utf8").digest("hex");
|
|
19154
|
+
return `${DURABLE_STEP_VALUE_PREFIX}${digest}`;
|
|
19155
|
+
}
|
|
19156
|
+
function snapshotAdvanceOptions(options) {
|
|
19157
|
+
return {
|
|
19158
|
+
...options.attributesDelete == null ? {} : { attributesDelete: [...options.attributesDelete] },
|
|
19159
|
+
...options.attributesMerge == null ? {} : { attributesMerge: { ...options.attributesMerge } },
|
|
19160
|
+
...options.dropValues == null ? {} : { dropValues: [...options.dropValues] },
|
|
19161
|
+
...options.leaseMs == null ? {} : { leaseMs: options.leaseMs },
|
|
19162
|
+
...options.nowMs == null ? {} : { nowMs: options.nowMs },
|
|
19163
|
+
...options.overrideValues == null ? {} : { overrideValues: [...options.overrideValues] },
|
|
19164
|
+
...options.payload === void 0 ? {} : { payload: options.payload },
|
|
19165
|
+
...options.stateMeta == null ? {} : { stateMeta: { ...options.stateMeta } },
|
|
19166
|
+
toState: options.toState,
|
|
19167
|
+
...options.valueRefs == null ? {} : { valueRefs: { ...options.valueRefs } },
|
|
19168
|
+
...options.values == null ? {} : { values: { ...options.values } }
|
|
19169
|
+
};
|
|
19170
|
+
}
|
|
19171
|
+
function assertDurableClaim(job) {
|
|
19172
|
+
if (typeof job !== "object" || job == null) throw new TypeError("job must be a claimed Flow");
|
|
19173
|
+
assertNonEmptyString(job.id, "job.id");
|
|
19174
|
+
assertNonEmptyString(job.runState, "job.runState");
|
|
19175
|
+
if (!Buffer.isBuffer(job.leaseToken) || job.leaseToken.byteLength === 0) {
|
|
19176
|
+
throw new TypeError("job.leaseToken must be a non-empty Buffer");
|
|
19177
|
+
}
|
|
19178
|
+
if ((typeof job.fencingToken !== "number" || !Number.isSafeInteger(job.fencingToken)) && typeof job.fencingToken !== "bigint") {
|
|
19179
|
+
throw new TypeError("job.fencingToken must be an integer");
|
|
19180
|
+
}
|
|
19181
|
+
if (fencingValue(job.fencingToken) <= 0n) {
|
|
19182
|
+
throw new TypeError("job.fencingToken must be a positive integer");
|
|
19183
|
+
}
|
|
19184
|
+
if (job.state !== "running") {
|
|
19185
|
+
throw new TypeError("job.state must be running");
|
|
19186
|
+
}
|
|
19187
|
+
}
|
|
19188
|
+
function assertValidatedLease(expected, actual) {
|
|
19189
|
+
try {
|
|
19190
|
+
assertDurableClaim(actual);
|
|
19191
|
+
} catch (error) {
|
|
19192
|
+
throw new FerricStoreError("lease validation returned an invalid claim", { cause: error });
|
|
19193
|
+
}
|
|
19194
|
+
if (actual.id !== expected.id) throw leaseValidationMismatch("id");
|
|
19195
|
+
if (normalizedPartition(actual.partitionKey) !== normalizedPartition(expected.partitionKey)) {
|
|
19196
|
+
throw leaseValidationMismatch("partition");
|
|
19197
|
+
}
|
|
19198
|
+
if (!actual.leaseToken.equals(expected.leaseToken)) throw leaseValidationMismatch("lease token");
|
|
19199
|
+
if (fencingValue(actual.fencingToken) !== fencingValue(expected.fencingToken)) {
|
|
19200
|
+
throw leaseValidationMismatch("fencing token");
|
|
19201
|
+
}
|
|
19202
|
+
if (actual.state !== expected.state) throw leaseValidationMismatch("physical state");
|
|
19203
|
+
if (actual.runState !== expected.runState) throw leaseValidationMismatch("runState");
|
|
19204
|
+
}
|
|
19205
|
+
function assertContinuationClaim(previous, refreshed, toState) {
|
|
19206
|
+
try {
|
|
19207
|
+
assertDurableClaim(refreshed);
|
|
19208
|
+
} catch (error) {
|
|
19209
|
+
throw new FerricStoreError("continuation response returned an invalid claim", { cause: error });
|
|
19210
|
+
}
|
|
19211
|
+
if (refreshed.id !== previous.id) throw continuationMismatch("id");
|
|
19212
|
+
if (normalizedPartition(refreshed.partitionKey) !== normalizedPartition(previous.partitionKey)) {
|
|
19213
|
+
throw continuationMismatch("partition");
|
|
19214
|
+
}
|
|
19215
|
+
if (refreshed.leaseToken.equals(previous.leaseToken)) {
|
|
19216
|
+
throw continuationMismatch("lease token did not change");
|
|
19217
|
+
}
|
|
19218
|
+
if (fencingValue(refreshed.fencingToken) <= fencingValue(previous.fencingToken)) {
|
|
19219
|
+
throw continuationMismatch("fencing token did not increase");
|
|
19220
|
+
}
|
|
19221
|
+
if (refreshed.state !== "running") throw continuationMismatch("physical state");
|
|
19222
|
+
if (refreshed.runState !== toState) throw continuationMismatch("runState");
|
|
19223
|
+
}
|
|
19224
|
+
function leaseValidationMismatch(field3) {
|
|
19225
|
+
return new FerricStoreError(`lease validation returned a mismatched ${field3}`);
|
|
19226
|
+
}
|
|
19227
|
+
function continuationMismatch(field3) {
|
|
19228
|
+
return new FerricStoreError(`continuation response returned a mismatched ${field3}`);
|
|
19229
|
+
}
|
|
19230
|
+
function normalizedPartition(value) {
|
|
19231
|
+
return value == null || value === "" ? void 0 : value;
|
|
19232
|
+
}
|
|
19233
|
+
function fencingValue(value) {
|
|
19234
|
+
return typeof value === "bigint" ? value : BigInt(value);
|
|
19235
|
+
}
|
|
19236
|
+
function assertNonEmptyString(value, name) {
|
|
19237
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
19238
|
+
throw new TypeError(`${name} must be a non-empty string`);
|
|
19239
|
+
}
|
|
19240
|
+
}
|
|
19241
|
+
function assertStepValueIsReserved(options, resultName) {
|
|
19242
|
+
if (options.values != null && Object.hasOwn(options.values, resultName)) {
|
|
19243
|
+
throw new TypeError("values cannot mutate the reserved durable step result");
|
|
19244
|
+
}
|
|
19245
|
+
if (options.valueRefs != null && Object.hasOwn(options.valueRefs, resultName)) {
|
|
19246
|
+
throw new TypeError("valueRefs cannot mutate the reserved durable step result");
|
|
19247
|
+
}
|
|
19248
|
+
if (options.dropValues?.includes(resultName) === true) {
|
|
19249
|
+
throw new TypeError("dropValues cannot mutate the reserved durable step result");
|
|
19250
|
+
}
|
|
19251
|
+
if (options.overrideValues?.includes(resultName) === true) {
|
|
19252
|
+
throw new TypeError("overrideValues cannot mutate the reserved durable step result");
|
|
19253
|
+
}
|
|
19254
|
+
}
|
|
19255
|
+
function claimedItemFromRecord(record) {
|
|
19256
|
+
return {
|
|
19257
|
+
...record.attributes == null ? {} : { attributes: record.attributes },
|
|
19258
|
+
fencingToken: record.fencingToken,
|
|
19259
|
+
id: record.id,
|
|
19260
|
+
leaseToken: Buffer.from(record.leaseToken),
|
|
19261
|
+
...record.partitionKey === "" ? {} : { partitionKey: record.partitionKey },
|
|
19262
|
+
...record.payload === void 0 ? {} : { payload: record.payload },
|
|
19263
|
+
...record.runState == null ? {} : { runState: record.runState },
|
|
19264
|
+
state: record.state,
|
|
19265
|
+
type: record.type
|
|
19266
|
+
};
|
|
19267
|
+
}
|
|
19268
|
+
|
|
19010
19269
|
// src/client.ts
|
|
19011
19270
|
var FerricStoreClient = class _FerricStoreClient extends FerricStoreProducerClient {
|
|
19012
19271
|
static async fromUrl(url, options = {}) {
|
|
@@ -19121,6 +19380,10 @@ var FerricStoreClient = class _FerricStoreClient extends FerricStoreProducerClie
|
|
|
19121
19380
|
appendNamedValues(args, this.codec, options);
|
|
19122
19381
|
return await this.recordOrGet(await this.commandArgs(args), id, partitionKey);
|
|
19123
19382
|
}
|
|
19383
|
+
/**
|
|
19384
|
+
* @deprecated Low-level compatibility API. Use {@link advance} for state-only
|
|
19385
|
+
* continuation or {@link step} for a durable closure.
|
|
19386
|
+
*/
|
|
19124
19387
|
async stepContinue(id, options) {
|
|
19125
19388
|
const partitionKey = options.partitionKey;
|
|
19126
19389
|
const returnJob = options.returnJob === true;
|
|
@@ -19148,6 +19411,18 @@ var FerricStoreClient = class _FerricStoreClient extends FerricStoreProducerClie
|
|
|
19148
19411
|
const response = await this.commandArgs(args);
|
|
19149
19412
|
return returnJob ? claimedItemFromResp(response, this.codec, { type }) : await this.recordOrGet(response, id, partitionKey);
|
|
19150
19413
|
}
|
|
19414
|
+
/** Advance a claimed Flow and return its renewed claim. */
|
|
19415
|
+
async advance(job, options) {
|
|
19416
|
+
return await advanceClaim(this, job, options);
|
|
19417
|
+
}
|
|
19418
|
+
/**
|
|
19419
|
+
* Run a closure after validating the claim, then atomically journal its
|
|
19420
|
+
* result and advance the Flow. A committed result is replayed without
|
|
19421
|
+
* invoking the closure again.
|
|
19422
|
+
*/
|
|
19423
|
+
async step(job, options) {
|
|
19424
|
+
return await runDurableStep(this, job, options);
|
|
19425
|
+
}
|
|
19151
19426
|
async runStepsMany(items, options) {
|
|
19152
19427
|
if (items.length === 0) return Buffer.from("OK");
|
|
19153
19428
|
const hasStates = options.states != null;
|
|
@@ -19397,6 +19672,8 @@ function validateUnicodeScalarText2(value) {
|
|
|
19397
19672
|
|
|
19398
19673
|
// src/outcomes.ts
|
|
19399
19674
|
var OUTCOME_BRAND = /* @__PURE__ */ Symbol("ferricstore.outcome");
|
|
19675
|
+
var ALREADY_APPLIED_BRAND = /* @__PURE__ */ Symbol("ferricstore.alreadyApplied");
|
|
19676
|
+
var REPLAYED_STEP_BRAND = /* @__PURE__ */ Symbol("ferricstore.replayedStep");
|
|
19400
19677
|
function transition(toState, options = {}) {
|
|
19401
19678
|
return brandOutcome({ ...options, kind: "transition", toState });
|
|
19402
19679
|
}
|
|
@@ -19409,6 +19686,22 @@ function retry(options = {}) {
|
|
|
19409
19686
|
function fail2(options = {}) {
|
|
19410
19687
|
return brandOutcome({ ...options, kind: "fail" });
|
|
19411
19688
|
}
|
|
19689
|
+
function alreadyApplied(job) {
|
|
19690
|
+
return brandAlreadyApplied({ job, kind: "already_applied" });
|
|
19691
|
+
}
|
|
19692
|
+
function appliedStep(job, result) {
|
|
19693
|
+
return brandAlreadyApplied({ applied: true, job, kind: "already_applied", result });
|
|
19694
|
+
}
|
|
19695
|
+
function replayedStep(job, result) {
|
|
19696
|
+
const replayed = { applied: false, job, result };
|
|
19697
|
+
Object.defineProperty(replayed, REPLAYED_STEP_BRAND, {
|
|
19698
|
+
configurable: false,
|
|
19699
|
+
enumerable: false,
|
|
19700
|
+
value: true,
|
|
19701
|
+
writable: false
|
|
19702
|
+
});
|
|
19703
|
+
return replayed;
|
|
19704
|
+
}
|
|
19412
19705
|
function isOutcome(value) {
|
|
19413
19706
|
if (typeof value !== "object" || value == null || !Object.hasOwn(value, OUTCOME_BRAND) || value[OUTCOME_BRAND] !== true) {
|
|
19414
19707
|
return false;
|
|
@@ -19419,6 +19712,12 @@ function isOutcome(value) {
|
|
|
19419
19712
|
}
|
|
19420
19713
|
return outcome.kind === "complete" || outcome.kind === "retry" || outcome.kind === "fail";
|
|
19421
19714
|
}
|
|
19715
|
+
function isAlreadyAppliedOutcome(value) {
|
|
19716
|
+
return typeof value === "object" && value != null && Object.hasOwn(value, ALREADY_APPLIED_BRAND) && value[ALREADY_APPLIED_BRAND] === true && value.kind === "already_applied";
|
|
19717
|
+
}
|
|
19718
|
+
function isReplayedStepResult(value) {
|
|
19719
|
+
return typeof value === "object" && value != null && Object.hasOwn(value, REPLAYED_STEP_BRAND) && value[REPLAYED_STEP_BRAND] === true && value.applied === false;
|
|
19720
|
+
}
|
|
19422
19721
|
function brandOutcome(outcome) {
|
|
19423
19722
|
Object.defineProperty(outcome, "kind", {
|
|
19424
19723
|
configurable: false,
|
|
@@ -19442,6 +19741,21 @@ function brandOutcome(outcome) {
|
|
|
19442
19741
|
});
|
|
19443
19742
|
return outcome;
|
|
19444
19743
|
}
|
|
19744
|
+
function brandAlreadyApplied(outcome) {
|
|
19745
|
+
Object.defineProperty(outcome, "kind", {
|
|
19746
|
+
configurable: false,
|
|
19747
|
+
enumerable: true,
|
|
19748
|
+
value: "already_applied",
|
|
19749
|
+
writable: false
|
|
19750
|
+
});
|
|
19751
|
+
Object.defineProperty(outcome, ALREADY_APPLIED_BRAND, {
|
|
19752
|
+
configurable: false,
|
|
19753
|
+
enumerable: false,
|
|
19754
|
+
value: true,
|
|
19755
|
+
writable: false
|
|
19756
|
+
});
|
|
19757
|
+
return outcome;
|
|
19758
|
+
}
|
|
19445
19759
|
|
|
19446
19760
|
// src/worker-config.ts
|
|
19447
19761
|
function snapshotWorkerConfig(options) {
|
|
@@ -19458,8 +19772,7 @@ function snapshotWorkerConfig(options) {
|
|
|
19458
19772
|
return Object.freeze(snapshot);
|
|
19459
19773
|
}
|
|
19460
19774
|
|
|
19461
|
-
// src/worker-
|
|
19462
|
-
var DEFAULT_FLOW_MANY_BATCH_LIMIT2 = 1e3;
|
|
19775
|
+
// src/worker-lease-guard.ts
|
|
19463
19776
|
var LeaseRenewalError = class extends Error {
|
|
19464
19777
|
constructor(cause) {
|
|
19465
19778
|
super("FerricStore lease renewal failed", { cause });
|
|
@@ -19470,37 +19783,58 @@ var LeaseRenewalGuard = class {
|
|
|
19470
19783
|
constructor(client, leasedJob, leaseMs, options) {
|
|
19471
19784
|
this.client = client;
|
|
19472
19785
|
this.leaseMs = leaseMs;
|
|
19473
|
-
this.
|
|
19474
|
-
|
|
19475
|
-
|
|
19476
|
-
|
|
19477
|
-
const intervalMs = positiveInteger4(options.leaseRenewIntervalMs, Math.max(1, Math.trunc(leaseMs / 2)));
|
|
19478
|
-
this.timer = setLongInterval(() => this.renew(), intervalMs);
|
|
19479
|
-
this.timer.unref();
|
|
19786
|
+
this.currentJob = snapshotLeasedJob(leasedJob);
|
|
19787
|
+
this.renewalEnabled = options.leaseRenewal !== false;
|
|
19788
|
+
this.intervalMs = positiveInteger4(options.leaseRenewIntervalMs, Math.max(1, Math.trunc(leaseMs / 2)));
|
|
19789
|
+
this.startRenewal();
|
|
19480
19790
|
}
|
|
19481
19791
|
client;
|
|
19482
19792
|
leaseMs;
|
|
19483
19793
|
error;
|
|
19484
19794
|
inFlight;
|
|
19485
|
-
|
|
19795
|
+
currentJob;
|
|
19796
|
+
intervalMs;
|
|
19797
|
+
renewalEnabled;
|
|
19798
|
+
state = "active";
|
|
19486
19799
|
timer;
|
|
19800
|
+
get job() {
|
|
19801
|
+
return this.currentJob;
|
|
19802
|
+
}
|
|
19487
19803
|
assertActive() {
|
|
19488
|
-
if (this.error != null)
|
|
19489
|
-
throw this.error;
|
|
19490
|
-
}
|
|
19804
|
+
if (this.error != null) throw this.error;
|
|
19491
19805
|
}
|
|
19492
19806
|
async stop() {
|
|
19493
|
-
|
|
19494
|
-
|
|
19495
|
-
this.timer = void 0;
|
|
19496
|
-
}
|
|
19807
|
+
this.state = "stopped";
|
|
19808
|
+
this.cancelRenewal();
|
|
19497
19809
|
await this.inFlight;
|
|
19498
19810
|
this.assertActive();
|
|
19499
19811
|
}
|
|
19812
|
+
async pauseForLeaseMutation() {
|
|
19813
|
+
if (this.state === "stopped") throw new Error("lease renewal guard is stopped");
|
|
19814
|
+
if (this.state === "paused") return;
|
|
19815
|
+
this.state = "paused";
|
|
19816
|
+
this.cancelRenewal();
|
|
19817
|
+
await this.inFlight;
|
|
19818
|
+
this.assertActive();
|
|
19819
|
+
}
|
|
19820
|
+
resumeWith(job) {
|
|
19821
|
+
if (this.state !== "paused") throw new Error("lease renewal guard is not paused");
|
|
19822
|
+
this.currentJob = snapshotLeasedJob(job);
|
|
19823
|
+
this.state = "active";
|
|
19824
|
+
this.startRenewal();
|
|
19825
|
+
}
|
|
19826
|
+
cancelRenewal() {
|
|
19827
|
+
if (this.timer == null) return;
|
|
19828
|
+
this.timer.cancel();
|
|
19829
|
+
this.timer = void 0;
|
|
19830
|
+
}
|
|
19831
|
+
startRenewal() {
|
|
19832
|
+
if (!this.renewalEnabled || this.state !== "active") return;
|
|
19833
|
+
this.timer = setLongInterval(() => this.renew(), this.intervalMs);
|
|
19834
|
+
this.timer.unref();
|
|
19835
|
+
}
|
|
19500
19836
|
renew() {
|
|
19501
|
-
if (this.error != null || this.inFlight != null)
|
|
19502
|
-
return;
|
|
19503
|
-
}
|
|
19837
|
+
if (this.state !== "active" || this.error != null || this.inFlight != null) return;
|
|
19504
19838
|
const renewal = this.client.extendLease(this.job.id, {
|
|
19505
19839
|
fencingToken: this.job.fencingToken,
|
|
19506
19840
|
leaseMs: this.leaseMs,
|
|
@@ -19511,17 +19845,12 @@ var LeaseRenewalGuard = class {
|
|
|
19511
19845
|
() => void 0,
|
|
19512
19846
|
(error) => {
|
|
19513
19847
|
this.error = new LeaseRenewalError(error);
|
|
19514
|
-
|
|
19515
|
-
this.timer.cancel();
|
|
19516
|
-
this.timer = void 0;
|
|
19517
|
-
}
|
|
19848
|
+
this.cancelRenewal();
|
|
19518
19849
|
}
|
|
19519
19850
|
);
|
|
19520
19851
|
this.inFlight = renewal;
|
|
19521
19852
|
void renewal.finally(() => {
|
|
19522
|
-
if (this.inFlight === renewal)
|
|
19523
|
-
this.inFlight = void 0;
|
|
19524
|
-
}
|
|
19853
|
+
if (this.inFlight === renewal) this.inFlight = void 0;
|
|
19525
19854
|
});
|
|
19526
19855
|
}
|
|
19527
19856
|
};
|
|
@@ -19545,8 +19874,14 @@ function snapshotLeasedJob(job) {
|
|
|
19545
19874
|
}
|
|
19546
19875
|
return snapshot;
|
|
19547
19876
|
}
|
|
19877
|
+
function positiveInteger4(value, fallback) {
|
|
19878
|
+
return value == null || !Number.isFinite(value) ? fallback : Math.max(1, Math.trunc(value));
|
|
19879
|
+
}
|
|
19880
|
+
|
|
19881
|
+
// src/worker-internal.ts
|
|
19882
|
+
var DEFAULT_FLOW_MANY_BATCH_LIMIT2 = 1e3;
|
|
19548
19883
|
function workerConcurrency(options) {
|
|
19549
|
-
return
|
|
19884
|
+
return positiveInteger5(options.concurrency ?? options.workers, 1);
|
|
19550
19885
|
}
|
|
19551
19886
|
function workerRefillStrategy(options) {
|
|
19552
19887
|
return options.refillStrategy === "wave" ? "wave" : "continuous";
|
|
@@ -19589,18 +19924,18 @@ function workerErrorPayload(error, options, codec) {
|
|
|
19589
19924
|
}
|
|
19590
19925
|
function workerBatchSize(options, flowManyBatchLimit = DEFAULT_FLOW_MANY_BATCH_LIMIT2) {
|
|
19591
19926
|
return Math.min(
|
|
19592
|
-
|
|
19593
|
-
|
|
19927
|
+
positiveInteger5(options.batchSize, 10),
|
|
19928
|
+
positiveInteger5(flowManyBatchLimit, DEFAULT_FLOW_MANY_BATCH_LIMIT2)
|
|
19594
19929
|
);
|
|
19595
19930
|
}
|
|
19596
19931
|
function workerClaimLimit(options, flowManyBatchLimit = DEFAULT_FLOW_MANY_BATCH_LIMIT2) {
|
|
19597
19932
|
return Math.min(workerBatchSize(options, flowManyBatchLimit), workerConcurrency(options));
|
|
19598
19933
|
}
|
|
19599
19934
|
function workerDrainBatches(options) {
|
|
19600
|
-
return
|
|
19935
|
+
return positiveInteger5(options.claimDrainBatches, 1);
|
|
19601
19936
|
}
|
|
19602
19937
|
function workerLeaseMs(options, fallback = 3e4) {
|
|
19603
|
-
return
|
|
19938
|
+
return positiveInteger5(options.leaseMs, positiveInteger5(fallback, 3e4));
|
|
19604
19939
|
}
|
|
19605
19940
|
function workerIdleSleepMs(options) {
|
|
19606
19941
|
return nonNegativeDuration(options.idleSleepMs, 250);
|
|
@@ -19612,7 +19947,7 @@ function workerClaimBlockMs(options, useBlocking) {
|
|
|
19612
19947
|
if (options.signal == null || !Number.isFinite(options.blockMs) || options.blockMs < 0) {
|
|
19613
19948
|
return options.blockMs;
|
|
19614
19949
|
}
|
|
19615
|
-
const abortPollMs =
|
|
19950
|
+
const abortPollMs = positiveInteger5(options.abortPollMs, 1e3);
|
|
19616
19951
|
return options.blockMs === 0 ? abortPollMs : Math.min(Math.trunc(options.blockMs), abortPollMs);
|
|
19617
19952
|
}
|
|
19618
19953
|
function workerMaxIdleSleepMs(options, idleSleepMs = workerIdleSleepMs(options)) {
|
|
@@ -19623,8 +19958,8 @@ function nextWorkerIdleSleepMs(currentIdleSleepMs, maxIdleSleepMs) {
|
|
|
19623
19958
|
return Math.min(maxIdleSleepMs, Math.max(1, currentIdleSleepMs * 2));
|
|
19624
19959
|
}
|
|
19625
19960
|
async function runContinuousWorkerPool(options) {
|
|
19626
|
-
const concurrency =
|
|
19627
|
-
const maxClaimSize =
|
|
19961
|
+
const concurrency = positiveInteger5(options.concurrency, 1);
|
|
19962
|
+
const maxClaimSize = positiveInteger5(options.maxClaimSize, concurrency);
|
|
19628
19963
|
const initialIdleSleepMs = nonNegativeDuration(options.idleSleepMs, 250);
|
|
19629
19964
|
const maxIdleSleepMs = Math.max(
|
|
19630
19965
|
initialIdleSleepMs,
|
|
@@ -19746,7 +20081,7 @@ function normalizeWorkflowWorkerConfig(options) {
|
|
|
19746
20081
|
...captured
|
|
19747
20082
|
});
|
|
19748
20083
|
}
|
|
19749
|
-
function
|
|
20084
|
+
function positiveInteger5(value, fallback) {
|
|
19750
20085
|
return value == null || !Number.isFinite(value) ? fallback : Math.max(1, Math.trunc(value));
|
|
19751
20086
|
}
|
|
19752
20087
|
function nonNegativeInteger5(value, fallback) {
|
|
@@ -19799,7 +20134,7 @@ async function waitForRefillWindow(delayMs, signal) {
|
|
|
19799
20134
|
}
|
|
19800
20135
|
|
|
19801
20136
|
// src/queue.ts
|
|
19802
|
-
var
|
|
20137
|
+
var import_node_crypto3 = require("crypto");
|
|
19803
20138
|
|
|
19804
20139
|
// src/queue-completion-batcher.ts
|
|
19805
20140
|
function queueClaimOptions(queue, options, overrides = {}) {
|
|
@@ -20664,7 +20999,7 @@ var Queue = class {
|
|
|
20664
20999
|
this.client = client;
|
|
20665
21000
|
this.type = captured.type;
|
|
20666
21001
|
this.state = captured.state ?? "queued";
|
|
20667
|
-
this.defaultWorker = captured.worker ?? `${this.type}-${(0,
|
|
21002
|
+
this.defaultWorker = captured.worker ?? `${this.type}-${(0, import_node_crypto3.randomUUID)()}`;
|
|
20668
21003
|
}
|
|
20669
21004
|
async enqueue(id, options = {}) {
|
|
20670
21005
|
return await this.client.enqueue(id, {
|
|
@@ -20686,53 +21021,55 @@ var Queue = class {
|
|
|
20686
21021
|
};
|
|
20687
21022
|
|
|
20688
21023
|
// src/workflow.ts
|
|
20689
|
-
var
|
|
21024
|
+
var import_node_crypto4 = require("crypto");
|
|
20690
21025
|
|
|
20691
|
-
// src/workflow-
|
|
20692
|
-
function
|
|
20693
|
-
const
|
|
20694
|
-
|
|
20695
|
-
|
|
20696
|
-
|
|
20697
|
-
|
|
20698
|
-
|
|
20699
|
-
|
|
20700
|
-
|
|
21026
|
+
// src/workflow-registration.ts
|
|
21027
|
+
function createWorkflowStateRegistration(name, handler, options, defaultValueMaxBytes) {
|
|
21028
|
+
const own2 = { ...options };
|
|
21029
|
+
const claimValues = own2.claimValues == null ? void 0 : snapshotOwnStringArray(own2.claimValues, "claimValues");
|
|
21030
|
+
const retryPolicy = own2.retryPolicy == null ? void 0 : Object.freeze({ ...own2.retryPolicy });
|
|
21031
|
+
return Object.freeze({
|
|
21032
|
+
claimPayload: own2.claimPayload ?? true,
|
|
21033
|
+
claimRecord: own2.claimRecord ?? true,
|
|
21034
|
+
exceptionPolicy: normalizeExceptionPolicy(own2.exceptionPolicy),
|
|
21035
|
+
handler,
|
|
21036
|
+
leaseMs: own2.leaseMs ?? 3e4,
|
|
21037
|
+
...own2.mode == null ? {} : { mode: normalizeStateMode(own2.mode) },
|
|
21038
|
+
name,
|
|
21039
|
+
returnRecord: own2.returnRecord ?? false,
|
|
21040
|
+
valueMaxBytes: own2.valueMaxBytes ?? defaultValueMaxBytes,
|
|
21041
|
+
...claimValues == null ? {} : { claimValues },
|
|
21042
|
+
...retryPolicy == null ? {} : { retryPolicy }
|
|
20701
21043
|
});
|
|
20702
21044
|
}
|
|
20703
|
-
function normalizeStateMode(mode) {
|
|
20704
|
-
if (mode === "fifo" || mode === "parallel") return mode;
|
|
20705
|
-
throw new Error("state mode must be 'fifo' or 'parallel'");
|
|
20706
|
-
}
|
|
20707
|
-
function valueRefToString(value) {
|
|
20708
|
-
if (typeof value === "string") return value;
|
|
20709
|
-
if (Buffer.isBuffer(value)) return value.toString("utf8");
|
|
20710
|
-
if (typeof value === "object" && value != null && Object.hasOwn(value, "ref")) {
|
|
20711
|
-
const ref = value.ref;
|
|
20712
|
-
return typeof ref === "string" ? ref : Buffer.isBuffer(ref) ? ref.toString("utf8") : void 0;
|
|
20713
|
-
}
|
|
20714
|
-
return void 0;
|
|
20715
|
-
}
|
|
20716
21045
|
|
|
20717
21046
|
// src/workflow-context.ts
|
|
20718
21047
|
var MISSING_VALUE = /* @__PURE__ */ Symbol("ferricstore.missingValue");
|
|
20719
21048
|
var WorkflowContext = class {
|
|
20720
|
-
workflow
|
|
20721
|
-
|
|
20722
|
-
stateName;
|
|
20723
|
-
flow;
|
|
20724
|
-
leaseJob;
|
|
20725
|
-
valueCache = /* @__PURE__ */ new Map();
|
|
20726
|
-
constructor(workflow, job, stateName, leaseJob = job) {
|
|
21049
|
+
constructor(workflow, job, stateName, leaseJob = job, mutationCoordinator) {
|
|
21050
|
+
this.mutationCoordinator = mutationCoordinator;
|
|
20727
21051
|
this.workflow = workflow;
|
|
20728
21052
|
this.job = job;
|
|
20729
21053
|
this.leaseJob = leaseJob;
|
|
20730
|
-
this.
|
|
21054
|
+
this.currentStateName = stateName;
|
|
20731
21055
|
this.flow = new WorkflowFlowCommands(this);
|
|
20732
21056
|
}
|
|
21057
|
+
mutationCoordinator;
|
|
21058
|
+
workflow;
|
|
21059
|
+
job;
|
|
21060
|
+
flow;
|
|
21061
|
+
appliedMutation = false;
|
|
21062
|
+
currentStateName;
|
|
21063
|
+
leaseJob;
|
|
21064
|
+
mutationFailure;
|
|
21065
|
+
mutationPhase = "idle";
|
|
21066
|
+
valueCache = /* @__PURE__ */ new Map();
|
|
20733
21067
|
get client() {
|
|
20734
21068
|
return this.workflow.client;
|
|
20735
21069
|
}
|
|
21070
|
+
get stateName() {
|
|
21071
|
+
return this.currentStateName;
|
|
21072
|
+
}
|
|
20736
21073
|
get id() {
|
|
20737
21074
|
return this.leaseJob.id;
|
|
20738
21075
|
}
|
|
@@ -20743,7 +21080,7 @@ var WorkflowContext = class {
|
|
|
20743
21080
|
return this.leaseJob.state;
|
|
20744
21081
|
}
|
|
20745
21082
|
get logicalState() {
|
|
20746
|
-
return this.
|
|
21083
|
+
return this.currentStateName;
|
|
20747
21084
|
}
|
|
20748
21085
|
get partitionKey() {
|
|
20749
21086
|
return this.leaseJob.partitionKey;
|
|
@@ -20781,6 +21118,53 @@ var WorkflowContext = class {
|
|
|
20781
21118
|
get correlationId() {
|
|
20782
21119
|
return "correlationId" in this.job ? this.job.correlationId : void 0;
|
|
20783
21120
|
}
|
|
21121
|
+
async advance(toState, options = {}) {
|
|
21122
|
+
if (typeof toState !== "string" || toState.length === 0) {
|
|
21123
|
+
throw new TypeError("toState must be a non-empty string");
|
|
21124
|
+
}
|
|
21125
|
+
const current = this.durableClaim();
|
|
21126
|
+
await this.beforeCommit();
|
|
21127
|
+
let refreshed;
|
|
21128
|
+
try {
|
|
21129
|
+
refreshed = await advanceClaim(this.client, current, { ...options, toState });
|
|
21130
|
+
} catch (error) {
|
|
21131
|
+
this.commitFailed(error);
|
|
21132
|
+
throw error;
|
|
21133
|
+
}
|
|
21134
|
+
this.committed(refreshed);
|
|
21135
|
+
return alreadyApplied(refreshed);
|
|
21136
|
+
}
|
|
21137
|
+
async step(options) {
|
|
21138
|
+
let applied = false;
|
|
21139
|
+
const stepped = await runDurableStep(this.client, this.durableClaim(), options, {
|
|
21140
|
+
beforeCommit: async () => await this.beforeCommit(),
|
|
21141
|
+
commitFailed: (error) => this.commitFailed(error),
|
|
21142
|
+
committed: (job) => {
|
|
21143
|
+
applied = true;
|
|
21144
|
+
this.committed(job);
|
|
21145
|
+
},
|
|
21146
|
+
replayed: (job) => this.replayed(job)
|
|
21147
|
+
});
|
|
21148
|
+
return applied ? appliedStep(stepped.job, stepped.result) : replayedStep(stepped.job, stepped.result);
|
|
21149
|
+
}
|
|
21150
|
+
/** @internal */
|
|
21151
|
+
get hasAppliedMutation() {
|
|
21152
|
+
return this.appliedMutation;
|
|
21153
|
+
}
|
|
21154
|
+
/** @internal */
|
|
21155
|
+
get uncertainMutationFailure() {
|
|
21156
|
+
return this.mutationPhase === "uncertain" ? this.mutationFailure : void 0;
|
|
21157
|
+
}
|
|
21158
|
+
/** @internal */
|
|
21159
|
+
get hasUncertainMutation() {
|
|
21160
|
+
return this.mutationPhase === "uncertain";
|
|
21161
|
+
}
|
|
21162
|
+
/** @internal */
|
|
21163
|
+
assertAppliedOutcome(outcome) {
|
|
21164
|
+
if (!this.appliedMutation || !outcome.job.leaseToken.equals(this.leaseJob.leaseToken) || outcome.job.fencingToken !== this.leaseJob.fencingToken) {
|
|
21165
|
+
throw new Error("already-applied outcome does not match the workflow's refreshed claim");
|
|
21166
|
+
}
|
|
21167
|
+
}
|
|
20784
21168
|
async value(name, defaultValue, options = {}) {
|
|
20785
21169
|
const useLocalCache = options.localCache ?? this.workflow.valueConfig.localCache;
|
|
20786
21170
|
if (useLocalCache && this.valueCache.has(name)) {
|
|
@@ -20860,7 +21244,49 @@ var WorkflowContext = class {
|
|
|
20860
21244
|
return result;
|
|
20861
21245
|
}
|
|
20862
21246
|
valueMaxBytes() {
|
|
20863
|
-
return this.workflow.stateRegistration(this.
|
|
21247
|
+
return this.workflow.stateRegistration(this.currentStateName)?.valueMaxBytes;
|
|
21248
|
+
}
|
|
21249
|
+
durableClaim() {
|
|
21250
|
+
const runState = this.leaseJob.runState ?? (this.leaseJob[CLAIMED_ITEM_WIRE] == null ? void 0 : this.currentStateName);
|
|
21251
|
+
if (typeof runState !== "string" || runState.length === 0) {
|
|
21252
|
+
throw new TypeError("job.runState must be a non-empty string");
|
|
21253
|
+
}
|
|
21254
|
+
return {
|
|
21255
|
+
...this.leaseJob,
|
|
21256
|
+
leaseToken: Buffer.from(this.leaseJob.leaseToken),
|
|
21257
|
+
runState
|
|
21258
|
+
};
|
|
21259
|
+
}
|
|
21260
|
+
async beforeCommit() {
|
|
21261
|
+
if (this.mutationPhase !== "idle") {
|
|
21262
|
+
throw new Error("a durable workflow mutation is already in progress or has an uncertain result");
|
|
21263
|
+
}
|
|
21264
|
+
await this.mutationCoordinator?.pause();
|
|
21265
|
+
this.mutationPhase = "committing";
|
|
21266
|
+
}
|
|
21267
|
+
commitFailed(error) {
|
|
21268
|
+
if (!durableMutationMayHaveCommitted(error)) {
|
|
21269
|
+
this.mutationFailure = void 0;
|
|
21270
|
+
this.mutationPhase = "idle";
|
|
21271
|
+
this.mutationCoordinator?.resume(this.leaseJob);
|
|
21272
|
+
return;
|
|
21273
|
+
}
|
|
21274
|
+
this.mutationFailure = error;
|
|
21275
|
+
this.mutationPhase = "uncertain";
|
|
21276
|
+
}
|
|
21277
|
+
committed(job) {
|
|
21278
|
+
this.acceptJob(job);
|
|
21279
|
+
this.appliedMutation = true;
|
|
21280
|
+
this.mutationCoordinator?.resume(job);
|
|
21281
|
+
}
|
|
21282
|
+
replayed(job) {
|
|
21283
|
+
this.acceptJob(job);
|
|
21284
|
+
}
|
|
21285
|
+
acceptJob(job) {
|
|
21286
|
+
this.leaseJob = job;
|
|
21287
|
+
this.currentStateName = job.runState ?? this.currentStateName;
|
|
21288
|
+
this.mutationFailure = void 0;
|
|
21289
|
+
this.mutationPhase = "idle";
|
|
20864
21290
|
}
|
|
20865
21291
|
};
|
|
20866
21292
|
var WorkflowFlowCommands = class {
|
|
@@ -20898,6 +21324,7 @@ var WorkflowFlowCommands = class {
|
|
|
20898
21324
|
toState
|
|
20899
21325
|
});
|
|
20900
21326
|
}
|
|
21327
|
+
/** @deprecated Use `ctx.advance(toState)`. */
|
|
20901
21328
|
async stepContinue(toState, options = {}) {
|
|
20902
21329
|
return await this.ctx.client.stepContinue(this.ctx.id, {
|
|
20903
21330
|
...options,
|
|
@@ -21060,24 +21487,77 @@ async function applyFail(client, ctx, outcome, returnRecord) {
|
|
|
21060
21487
|
});
|
|
21061
21488
|
}
|
|
21062
21489
|
|
|
21063
|
-
// src/workflow-
|
|
21064
|
-
function
|
|
21065
|
-
|
|
21066
|
-
const
|
|
21067
|
-
|
|
21068
|
-
|
|
21069
|
-
claimPayload: own2.claimPayload ?? true,
|
|
21070
|
-
claimRecord: own2.claimRecord ?? true,
|
|
21071
|
-
exceptionPolicy: normalizeExceptionPolicy(own2.exceptionPolicy),
|
|
21072
|
-
handler,
|
|
21073
|
-
leaseMs: own2.leaseMs ?? 3e4,
|
|
21074
|
-
...own2.mode == null ? {} : { mode: normalizeStateMode(own2.mode) },
|
|
21075
|
-
name,
|
|
21076
|
-
returnRecord: own2.returnRecord ?? false,
|
|
21077
|
-
valueMaxBytes: own2.valueMaxBytes ?? defaultValueMaxBytes,
|
|
21078
|
-
...claimValues == null ? {} : { claimValues },
|
|
21079
|
-
...retryPolicy == null ? {} : { retryPolicy }
|
|
21490
|
+
// src/workflow-job-execution.ts
|
|
21491
|
+
async function executeWorkflowJob(workflow, workerOptions, job, registration, guard) {
|
|
21492
|
+
guard.assertActive();
|
|
21493
|
+
const ctx = new WorkflowContext(workflow, job, registration.name, guard.job, {
|
|
21494
|
+
pause: async () => await guard.pauseForLeaseMutation(),
|
|
21495
|
+
resume: (refreshed) => guard.resumeWith(refreshed)
|
|
21080
21496
|
});
|
|
21497
|
+
let value;
|
|
21498
|
+
try {
|
|
21499
|
+
value = await registration.handler(ctx);
|
|
21500
|
+
} catch (error) {
|
|
21501
|
+
await guard.stop();
|
|
21502
|
+
if (ctx.hasUncertainMutation) throw asError(error, "durable workflow mutation result is uncertain");
|
|
21503
|
+
await applyHandlerError(workflow, workerOptions, ctx, registration, error);
|
|
21504
|
+
return;
|
|
21505
|
+
}
|
|
21506
|
+
await guard.stop();
|
|
21507
|
+
if (ctx.hasUncertainMutation) {
|
|
21508
|
+
throw asError(ctx.uncertainMutationFailure, "durable workflow mutation result is uncertain");
|
|
21509
|
+
}
|
|
21510
|
+
if (isAlreadyAppliedOutcome(value)) {
|
|
21511
|
+
ctx.assertAppliedOutcome(value);
|
|
21512
|
+
await releaseAppliedMutation(workflow, ctx);
|
|
21513
|
+
return;
|
|
21514
|
+
}
|
|
21515
|
+
if (isReplayedStepResult(value)) {
|
|
21516
|
+
await applyWorkflowOutcome(
|
|
21517
|
+
workflow.client,
|
|
21518
|
+
ctx,
|
|
21519
|
+
complete({ result: value.result }),
|
|
21520
|
+
registration.returnRecord
|
|
21521
|
+
);
|
|
21522
|
+
return;
|
|
21523
|
+
}
|
|
21524
|
+
if (isOutcome(value)) {
|
|
21525
|
+
await applyWorkflowOutcome(workflow.client, ctx, value, registration.returnRecord);
|
|
21526
|
+
return;
|
|
21527
|
+
}
|
|
21528
|
+
if (ctx.hasAppliedMutation) {
|
|
21529
|
+
await releaseAppliedMutation(workflow, ctx);
|
|
21530
|
+
return;
|
|
21531
|
+
}
|
|
21532
|
+
await applyWorkflowOutcome(
|
|
21533
|
+
workflow.client,
|
|
21534
|
+
ctx,
|
|
21535
|
+
complete({ result: value }),
|
|
21536
|
+
registration.returnRecord
|
|
21537
|
+
);
|
|
21538
|
+
}
|
|
21539
|
+
function asError(value, message) {
|
|
21540
|
+
return value instanceof Error ? value : new Error(message, { cause: value });
|
|
21541
|
+
}
|
|
21542
|
+
async function releaseAppliedMutation(workflow, ctx) {
|
|
21543
|
+
await workflow.client.transition(ctx.id, {
|
|
21544
|
+
fencingToken: ctx.fencingToken,
|
|
21545
|
+
fromState: ctx.state,
|
|
21546
|
+
leaseToken: ctx.leaseToken,
|
|
21547
|
+
partitionKey: ctx.partitionKey,
|
|
21548
|
+
toState: ctx.logicalState
|
|
21549
|
+
});
|
|
21550
|
+
}
|
|
21551
|
+
async function applyHandlerError(workflow, workerOptions, ctx, registration, error) {
|
|
21552
|
+
const policy = workerOptions.exceptionPolicy ?? registration.exceptionPolicy;
|
|
21553
|
+
if (policy === "raise") throw error;
|
|
21554
|
+
const encodedError = workerErrorPayload(error, workerOptions, workflow.client.codec);
|
|
21555
|
+
await applyWorkflowOutcome(
|
|
21556
|
+
workflow.client,
|
|
21557
|
+
ctx,
|
|
21558
|
+
policy === "fail" ? fail2({ error: encodedError }) : retry({ error: encodedError }),
|
|
21559
|
+
registration.returnRecord
|
|
21560
|
+
);
|
|
21081
21561
|
}
|
|
21082
21562
|
|
|
21083
21563
|
// src/workflow.ts
|
|
@@ -21106,7 +21586,7 @@ var Workflow = class {
|
|
|
21106
21586
|
localCache: captured.valueConfig?.localCache ?? false,
|
|
21107
21587
|
valueMaxBytes: captured.valueConfig?.valueMaxBytes
|
|
21108
21588
|
};
|
|
21109
|
-
this.defaultWorker = captured.worker ?? `${this.type}-${(0,
|
|
21589
|
+
this.defaultWorker = captured.worker ?? `${this.type}-${(0, import_node_crypto4.randomUUID)()}`;
|
|
21110
21590
|
}
|
|
21111
21591
|
state(name, handler, options = {}) {
|
|
21112
21592
|
this.states.set(name, createWorkflowStateRegistration(
|
|
@@ -21207,6 +21687,10 @@ var WorkflowWorker = class {
|
|
|
21207
21687
|
claimed += jobs.length;
|
|
21208
21688
|
const guards = jobs.map((job) => new LeaseRenewalGuard(this.workflow.client, job, leaseMs, this.options));
|
|
21209
21689
|
let cursor = 0;
|
|
21690
|
+
let batchFailure;
|
|
21691
|
+
const recordFailure = (error) => {
|
|
21692
|
+
batchFailure ??= { error };
|
|
21693
|
+
};
|
|
21210
21694
|
const runNext = async () => {
|
|
21211
21695
|
while (cursor < jobs.length) {
|
|
21212
21696
|
const index = cursor;
|
|
@@ -21217,19 +21701,22 @@ var WorkflowWorker = class {
|
|
|
21217
21701
|
try {
|
|
21218
21702
|
await this.applyJob(job, registration, guard);
|
|
21219
21703
|
applied += 1;
|
|
21704
|
+
} catch (error) {
|
|
21705
|
+
recordFailure(error);
|
|
21220
21706
|
} finally {
|
|
21221
|
-
|
|
21707
|
+
try {
|
|
21708
|
+
await guard.stop();
|
|
21709
|
+
} catch (error) {
|
|
21710
|
+
recordFailure(error);
|
|
21711
|
+
}
|
|
21222
21712
|
}
|
|
21223
21713
|
}
|
|
21224
21714
|
};
|
|
21225
21715
|
try {
|
|
21226
|
-
|
|
21716
|
+
await Promise.all(
|
|
21227
21717
|
Array.from({ length: Math.min(concurrency, jobs.length) }, () => runNext())
|
|
21228
21718
|
);
|
|
21229
|
-
|
|
21230
|
-
if (failed != null) {
|
|
21231
|
-
throw failed.reason;
|
|
21232
|
-
}
|
|
21719
|
+
if (batchFailure != null) throwError(batchFailure.error);
|
|
21233
21720
|
} finally {
|
|
21234
21721
|
await Promise.allSettled(
|
|
21235
21722
|
guards.slice(cursor).map(async (guard) => await guard.stop())
|
|
@@ -21340,42 +21827,12 @@ var WorkflowWorker = class {
|
|
|
21340
21827
|
});
|
|
21341
21828
|
}
|
|
21342
21829
|
async applyJob(job, registration, guard) {
|
|
21343
|
-
guard
|
|
21344
|
-
const ctx = new WorkflowContext(this.workflow, job, registration.name, guard.job);
|
|
21345
|
-
let outcome;
|
|
21346
|
-
try {
|
|
21347
|
-
const value = await registration.handler(ctx);
|
|
21348
|
-
outcome = isOutcome(value) ? value : complete({ result: value });
|
|
21349
|
-
} catch (error) {
|
|
21350
|
-
await guard.stop();
|
|
21351
|
-
await this.applyHandlerError(ctx, registration, error);
|
|
21352
|
-
return;
|
|
21353
|
-
}
|
|
21354
|
-
await guard.stop();
|
|
21355
|
-
await applyWorkflowOutcome(this.workflow.client, ctx, outcome, registration.returnRecord);
|
|
21356
|
-
}
|
|
21357
|
-
async applyHandlerError(ctx, registration, error) {
|
|
21358
|
-
const policy = this.options.exceptionPolicy ?? registration.exceptionPolicy;
|
|
21359
|
-
if (policy === "raise") {
|
|
21360
|
-
throw error;
|
|
21361
|
-
}
|
|
21362
|
-
if (policy === "fail") {
|
|
21363
|
-
await applyWorkflowOutcome(
|
|
21364
|
-
this.workflow.client,
|
|
21365
|
-
ctx,
|
|
21366
|
-
fail2({ error: workerErrorPayload(error, this.options, this.workflow.client.codec) }),
|
|
21367
|
-
registration.returnRecord
|
|
21368
|
-
);
|
|
21369
|
-
return;
|
|
21370
|
-
}
|
|
21371
|
-
await applyWorkflowOutcome(
|
|
21372
|
-
this.workflow.client,
|
|
21373
|
-
ctx,
|
|
21374
|
-
retry({ error: workerErrorPayload(error, this.options, this.workflow.client.codec) }),
|
|
21375
|
-
registration.returnRecord
|
|
21376
|
-
);
|
|
21830
|
+
await executeWorkflowJob(this.workflow, this.options, job, registration, guard);
|
|
21377
21831
|
}
|
|
21378
21832
|
};
|
|
21833
|
+
function throwError(value) {
|
|
21834
|
+
throw value instanceof Error ? value : new Error("workflow worker failed", { cause: value });
|
|
21835
|
+
}
|
|
21379
21836
|
|
|
21380
21837
|
// src/version.ts
|
|
21381
21838
|
var FERRICSTORE_SDK_VERSION = "0.12.0";
|
|
@@ -21442,6 +21899,7 @@ var FERRICSTORE_NATIVE_PROTOCOL_VERSION = 1;
|
|
|
21442
21899
|
complete,
|
|
21443
21900
|
fail,
|
|
21444
21901
|
httpCommandDisposition,
|
|
21902
|
+
isAlreadyAppliedOutcome,
|
|
21445
21903
|
isOutcome,
|
|
21446
21904
|
isReconnectableClosedConnectionError,
|
|
21447
21905
|
mapException,
|