@dudousxd/nestjs-durable-core 0.79.0 → 0.80.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/dist/index.cjs +198 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +111 -0
- package/dist/index.d.ts +111 -0
- package/dist/index.js +198 -30
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -4052,6 +4052,16 @@ function isNewerVersion(a, b) {
|
|
|
4052
4052
|
}) > 0;
|
|
4053
4053
|
}
|
|
4054
4054
|
__name(isNewerVersion, "isNewerVersion");
|
|
4055
|
+
var SETTLE_ELIGIBLE_STATUSES = [
|
|
4056
|
+
"pending",
|
|
4057
|
+
"running",
|
|
4058
|
+
"suspended",
|
|
4059
|
+
"blocked",
|
|
4060
|
+
"failed",
|
|
4061
|
+
"cancelling"
|
|
4062
|
+
];
|
|
4063
|
+
var SUSPEND_ELIGIBLE_STATUSES = SETTLE_ELIGIBLE_STATUSES.filter((status) => status !== "cancelling");
|
|
4064
|
+
var RECOVERY_BATCH = 500;
|
|
4055
4065
|
var NamespaceMismatch = class NamespaceMismatch2 extends Error {
|
|
4056
4066
|
static {
|
|
4057
4067
|
__name(this, "NamespaceMismatch");
|
|
@@ -4151,6 +4161,18 @@ var WorkflowEngine = class {
|
|
|
4151
4161
|
awaitedStepFans = /* @__PURE__ */ new Map();
|
|
4152
4162
|
/** Step fans whose join barrier this instance opened, by `runId:firstSeq` — see {@link settledHere}. */
|
|
4153
4163
|
stepFansFiredHere = /* @__PURE__ */ new Map();
|
|
4164
|
+
/**
|
|
4165
|
+
* The in-process execution gate, by run id: the execution of the run currently in flight in THIS
|
|
4166
|
+
* process (`tail`), and at most one resume queued behind it (`queued`). See {@link resumeRun}.
|
|
4167
|
+
*
|
|
4168
|
+
* The run lease is keyed by engine instance, so it fences OTHER processes only: to the store, a
|
|
4169
|
+
* second execution in this same process holds the very same lease (and {@link execute} skips
|
|
4170
|
+
* `tryLockRun` when its snapshot says this instance holds it). Without this gate, a resume landing
|
|
4171
|
+
* while this process was still executing the run (a signal, a late step result, a child wake, an
|
|
4172
|
+
* explicit `resume()`) went straight past the lease and ran the body concurrently: a
|
|
4173
|
+
* not-yet-checkpointed step executed twice.
|
|
4174
|
+
*/
|
|
4175
|
+
runGates = /* @__PURE__ */ new Map();
|
|
4154
4176
|
/** The tail of each run's queue of results being handled on this instance — see {@link serializedPerRun}. */
|
|
4155
4177
|
resultChains = /* @__PURE__ */ new Map();
|
|
4156
4178
|
/** Each run's results waiting to be settled on this instance — see {@link completeRemoteResult}. */
|
|
@@ -4619,11 +4641,16 @@ var WorkflowEngine = class {
|
|
|
4619
4641
|
if (registered.singleton) {
|
|
4620
4642
|
await this.singletons.assertCapacity(name, registered.singleton, input);
|
|
4621
4643
|
}
|
|
4644
|
+
const startAtMs = opts?.startAt instanceof Date ? opts.startAt.getTime() : opts?.startAt;
|
|
4645
|
+
const delayed = startAtMs !== void 0 && Number.isFinite(startAtMs) && startAtMs > this.clock();
|
|
4622
4646
|
const run = {
|
|
4623
4647
|
id: runId,
|
|
4624
4648
|
workflow: name,
|
|
4625
4649
|
workflowVersion: registered.version,
|
|
4626
|
-
status: "pending",
|
|
4650
|
+
status: delayed ? "suspended" : "pending",
|
|
4651
|
+
...delayed ? {
|
|
4652
|
+
wakeAt: startAtMs
|
|
4653
|
+
} : {},
|
|
4627
4654
|
namespace: opts?.namespace ?? this.namespace,
|
|
4628
4655
|
// DERIVED from the registration, exactly like `namespace` is derived from this engine — there is
|
|
4629
4656
|
// deliberately no `opts.origin`, so a caller cannot label its run as another package's work.
|
|
@@ -4649,6 +4676,18 @@ var WorkflowEngine = class {
|
|
|
4649
4676
|
error: winner.error
|
|
4650
4677
|
};
|
|
4651
4678
|
}
|
|
4679
|
+
if (delayed) {
|
|
4680
|
+
this.emit({
|
|
4681
|
+
type: "run.suspended",
|
|
4682
|
+
runId,
|
|
4683
|
+
workflow: name,
|
|
4684
|
+
namespace: run.namespace
|
|
4685
|
+
});
|
|
4686
|
+
return {
|
|
4687
|
+
runId,
|
|
4688
|
+
status: "suspended"
|
|
4689
|
+
};
|
|
4690
|
+
}
|
|
4652
4691
|
await this.runDispatcher.dispatch(runId);
|
|
4653
4692
|
if (this.controlPlane) {
|
|
4654
4693
|
void this.controlPlane.publishControl({
|
|
@@ -4686,7 +4725,104 @@ var WorkflowEngine = class {
|
|
|
4686
4725
|
resumeImplicit(runId) {
|
|
4687
4726
|
return this.resumeRun(runId, false);
|
|
4688
4727
|
}
|
|
4689
|
-
|
|
4728
|
+
/**
|
|
4729
|
+
* One execution of a run at a time in this process. A resume that lands while the run executes
|
|
4730
|
+
* here neither joins that execution (the double-run) nor no-ops against its held lease (a lost
|
|
4731
|
+
* wake: its checkpoint may postdate the running turn's snapshot): it is QUEUED, and re-drives the
|
|
4732
|
+
* run once the running execution has settled and released its lease, re-reading the run then
|
|
4733
|
+
* (terminal: echoed, never re-executed). Resumes landing while one is already queued coalesce
|
|
4734
|
+
* into it: it has not started yet, so it will observe every write they made. Their callers get
|
|
4735
|
+
* the queued execution's result, i.e. the run's state after their own wake was applied.
|
|
4736
|
+
*
|
|
4737
|
+
* Across processes, exclusion stays the lease's job: {@link execute} still `tryLockRun`s when it
|
|
4738
|
+
* does not hold the lease.
|
|
4739
|
+
*/
|
|
4740
|
+
resumeRun(runId, allowFailed) {
|
|
4741
|
+
const gate = this.runGates.get(runId);
|
|
4742
|
+
if (!gate) {
|
|
4743
|
+
const tail = this.resumeRunNow(runId, allowFailed);
|
|
4744
|
+
const fresh = {
|
|
4745
|
+
tail
|
|
4746
|
+
};
|
|
4747
|
+
this.runGates.set(runId, fresh);
|
|
4748
|
+
this.closeGateAfter(runId, fresh, tail);
|
|
4749
|
+
return tail;
|
|
4750
|
+
}
|
|
4751
|
+
let queued = gate.queued;
|
|
4752
|
+
if (queued) {
|
|
4753
|
+
queued.allowFailed ||= allowFailed;
|
|
4754
|
+
} else {
|
|
4755
|
+
const entry = {
|
|
4756
|
+
allowFailed
|
|
4757
|
+
};
|
|
4758
|
+
const result = gate.tail.catch(() => void 0).then(() => {
|
|
4759
|
+
if (gate.queued === entry) gate.queued = void 0;
|
|
4760
|
+
return this.resumeRunNow(runId, entry.allowFailed);
|
|
4761
|
+
});
|
|
4762
|
+
entry.result = result;
|
|
4763
|
+
queued = entry;
|
|
4764
|
+
gate.queued = queued;
|
|
4765
|
+
gate.tail = result;
|
|
4766
|
+
this.closeGateAfter(runId, gate, result);
|
|
4767
|
+
void this.track(result).catch(() => void 0);
|
|
4768
|
+
}
|
|
4769
|
+
if (currentWorkflowCtx()?.runId === runId) return this.echoCurrentStatus(runId);
|
|
4770
|
+
return queued.result;
|
|
4771
|
+
}
|
|
4772
|
+
/** Drop `gate` once `tail` settles, unless another execution queued behind it meanwhile. */
|
|
4773
|
+
closeGateAfter(runId, gate, tail) {
|
|
4774
|
+
const close = /* @__PURE__ */ __name(() => {
|
|
4775
|
+
if (this.runGates.get(runId) === gate && gate.tail === tail) this.runGates.delete(runId);
|
|
4776
|
+
}, "close");
|
|
4777
|
+
void tail.then(close, close);
|
|
4778
|
+
}
|
|
4779
|
+
/**
|
|
4780
|
+
* Release a run's lease AFTER its outcome is decided (persisted, or about to be returned). Best
|
|
4781
|
+
* effort, and that matters most on the failure path: the thing that failed the run is frequently
|
|
4782
|
+
* the very store this release talks to (a step that violated a DB constraint leaves Postgres
|
|
4783
|
+
* refusing every further statement on that connection), so the release throws too. A throwing
|
|
4784
|
+
* release in a `finally` REPLACES the run's real outcome with a lease write nobody asked about,
|
|
4785
|
+
* skips the post-turn re-drives after it, and, for a turn driven as a background resume, surfaces
|
|
4786
|
+
* as an unhandled rejection instead of a failed run.
|
|
4787
|
+
*
|
|
4788
|
+
* Letting it go is safe: the lease expires on its own and `recoverIncomplete` re-drives whatever
|
|
4789
|
+
* is still held. A lost release costs one lease window of latency; a masked error costs the
|
|
4790
|
+
* diagnosis.
|
|
4791
|
+
*/
|
|
4792
|
+
async releaseLeaseBestEffort(runId) {
|
|
4793
|
+
try {
|
|
4794
|
+
await this.store.releaseRunLock(runId);
|
|
4795
|
+
} catch (err) {
|
|
4796
|
+
console.warn(`[nestjs-durable] releasing the lease of run ${runId} failed (it expires on its own and recovery re-drives): ${err instanceof Error ? err.message : String(err)}`);
|
|
4797
|
+
}
|
|
4798
|
+
}
|
|
4799
|
+
/**
|
|
4800
|
+
* The run's persisted state as a {@link RunResult}, without executing anything: the answer to a
|
|
4801
|
+
* resume issued from inside the run's own execution, and to a settle whose compare-and-set lost
|
|
4802
|
+
* (the path that won already emitted and notified). A run that is gone reads as `cancelled`, the
|
|
4803
|
+
* safest terminal reading, never a fabricated success.
|
|
4804
|
+
*/
|
|
4805
|
+
async echoCurrentStatus(runId) {
|
|
4806
|
+
const run = await this.store.getRun(runId);
|
|
4807
|
+
return {
|
|
4808
|
+
runId,
|
|
4809
|
+
status: run?.status ?? "cancelled",
|
|
4810
|
+
output: run?.output,
|
|
4811
|
+
error: run?.error
|
|
4812
|
+
};
|
|
4813
|
+
}
|
|
4814
|
+
/**
|
|
4815
|
+
* The engine's stand-in for {@link StateStore.updateRunIf} on a store without it, called with the
|
|
4816
|
+
* store as `this`: read-then-write, which narrows the race to the gap between the read and the
|
|
4817
|
+
* write (the pre-CAS behavior, now applied to every settle) but cannot close it.
|
|
4818
|
+
*/
|
|
4819
|
+
async updateRunIfFallback(runId, expected, patch) {
|
|
4820
|
+
const current = await this.getRun(runId);
|
|
4821
|
+
if (!current || !expected.includes(current.status)) return false;
|
|
4822
|
+
await this.updateRun(runId, patch);
|
|
4823
|
+
return true;
|
|
4824
|
+
}
|
|
4825
|
+
async resumeRunNow(runId, allowFailed) {
|
|
4690
4826
|
const run = await this.store.getRun(runId);
|
|
4691
4827
|
if (!run) throw new Error(`run ${runId} not found`);
|
|
4692
4828
|
if (run.status === "cancelled" || run.status === "completed" || run.status === "dead" || run.status === "failed" && !allowFailed) {
|
|
@@ -4992,7 +5128,7 @@ var WorkflowEngine = class {
|
|
|
4992
5128
|
async recoverIncomplete(nowMs = this.clock()) {
|
|
4993
5129
|
if (this.draining) return [];
|
|
4994
5130
|
const results = [];
|
|
4995
|
-
for (const listed of await this.
|
|
5131
|
+
for (const listed of await this.orphanedRuns(nowMs)) {
|
|
4996
5132
|
if (this.draining) break;
|
|
4997
5133
|
const acquired = await this.store.tryLockRun(listed.id, this.instanceId, nowMs + this.leaseMs, nowMs);
|
|
4998
5134
|
if (!acquired) continue;
|
|
@@ -5038,6 +5174,21 @@ var WorkflowEngine = class {
|
|
|
5038
5174
|
return results;
|
|
5039
5175
|
}
|
|
5040
5176
|
/**
|
|
5177
|
+
* The runs {@link recoverIncomplete} should try to reclaim: incomplete runs whose lease is free or
|
|
5178
|
+
* expired, never the healthy ones a live turn is renewing. Every worker runs recovery on every
|
|
5179
|
+
* timer tick, so listing every running run and lock-probing each (one doomed UPDATE per healthy
|
|
5180
|
+
* run per worker per tick) was pure database load. The store answers it directly when it can
|
|
5181
|
+
* ({@link StateStore.listOrphanedRuns}, bounded by {@link RECOVERY_BATCH}); else the full list is
|
|
5182
|
+
* filtered here, which still spares the probes. Shuffled, like the due-timer pass, so instances
|
|
5183
|
+
* recovering at once spread over the batch instead of contending for the same leases.
|
|
5184
|
+
*/
|
|
5185
|
+
async orphanedRuns(nowMs) {
|
|
5186
|
+
if (this.store.listOrphanedRuns) {
|
|
5187
|
+
return shuffle(await this.store.listOrphanedRuns(nowMs, RECOVERY_BATCH, this.namespace));
|
|
5188
|
+
}
|
|
5189
|
+
return shuffle((await this.store.listIncompleteRuns(this.namespace)).filter((run) => run.lockedUntil == null || run.lockedUntil <= nowMs));
|
|
5190
|
+
}
|
|
5191
|
+
/**
|
|
5041
5192
|
* The remote step this run dispatched and is still awaiting a result for, if any — the signature
|
|
5042
5193
|
* that tells {@link recoverIncomplete} "this run's work is ON THE TRANSPORT, not in a crashed
|
|
5043
5194
|
* process". A `pending` remote checkpoint is written BEFORE the dispatch and settled by the
|
|
@@ -6554,12 +6705,13 @@ ${run.workflowVersion}`;
|
|
|
6554
6705
|
async settleRun(run, outcome) {
|
|
6555
6706
|
const updatedAt = /* @__PURE__ */ new Date();
|
|
6556
6707
|
if (outcome.kind === "completed") {
|
|
6557
|
-
await this.store.
|
|
6708
|
+
const applied2 = await (this.store.updateRunIf ?? this.updateRunIfFallback).call(this.store, run.id, SETTLE_ELIGIBLE_STATUSES, {
|
|
6558
6709
|
status: "completed",
|
|
6559
6710
|
output: outcome.output,
|
|
6560
6711
|
error: void 0,
|
|
6561
6712
|
updatedAt
|
|
6562
6713
|
});
|
|
6714
|
+
if (!applied2) return this.echoCurrentStatus(run.id);
|
|
6563
6715
|
this.emit({
|
|
6564
6716
|
type: "run.completed",
|
|
6565
6717
|
runId: run.id,
|
|
@@ -6581,11 +6733,12 @@ ${run.workflowVersion}`;
|
|
|
6581
6733
|
const handed = await this.routeFailure(run, outcome.error);
|
|
6582
6734
|
if (handed && handed.kind !== "failed") return this.settleRun(run, handed);
|
|
6583
6735
|
const error2 = handed?.error ?? outcome.error;
|
|
6584
|
-
await this.store.
|
|
6736
|
+
const applied2 = await (this.store.updateRunIf ?? this.updateRunIfFallback).call(this.store, run.id, SETTLE_ELIGIBLE_STATUSES, {
|
|
6585
6737
|
status: "failed",
|
|
6586
6738
|
error: error2,
|
|
6587
6739
|
updatedAt
|
|
6588
6740
|
});
|
|
6741
|
+
if (!applied2) return this.echoCurrentStatus(run.id);
|
|
6589
6742
|
this.emit({
|
|
6590
6743
|
type: "run.failed",
|
|
6591
6744
|
runId: run.id,
|
|
@@ -6601,19 +6754,12 @@ ${run.workflowVersion}`;
|
|
|
6601
6754
|
};
|
|
6602
6755
|
}
|
|
6603
6756
|
if (outcome.kind === "blocked") return this.parkBlocked(run, outcome.blocked);
|
|
6604
|
-
const
|
|
6605
|
-
if (latest?.status === "cancelled") {
|
|
6606
|
-
return {
|
|
6607
|
-
runId: run.id,
|
|
6608
|
-
status: "cancelled",
|
|
6609
|
-
error: latest.error
|
|
6610
|
-
};
|
|
6611
|
-
}
|
|
6612
|
-
await this.store.updateRun(run.id, {
|
|
6757
|
+
const applied = await (this.store.updateRunIf ?? this.updateRunIfFallback).call(this.store, run.id, SUSPEND_ELIGIBLE_STATUSES, {
|
|
6613
6758
|
status: "suspended",
|
|
6614
6759
|
wakeAt: this.reconcileWakeAt(outcome.wakeAt),
|
|
6615
6760
|
updatedAt
|
|
6616
6761
|
});
|
|
6762
|
+
if (!applied) return this.echoCurrentStatus(run.id);
|
|
6617
6763
|
this.emit({
|
|
6618
6764
|
type: "run.suspended",
|
|
6619
6765
|
runId: run.id,
|
|
@@ -6660,7 +6806,7 @@ ${run.workflowVersion}`;
|
|
|
6660
6806
|
from: this.instanceId
|
|
6661
6807
|
}).catch(() => void 0);
|
|
6662
6808
|
}
|
|
6663
|
-
await this.
|
|
6809
|
+
await this.releaseLeaseBestEffort(run.id);
|
|
6664
6810
|
return {
|
|
6665
6811
|
runId: run.id,
|
|
6666
6812
|
status: "cancelled",
|
|
@@ -6690,7 +6836,7 @@ ${run.workflowVersion}`;
|
|
|
6690
6836
|
const handoff = liveHandoff(run.id, checkpoints);
|
|
6691
6837
|
if (handoff) {
|
|
6692
6838
|
const result = await this.settleRun(run, await this.followHandoff(run, handoff));
|
|
6693
|
-
await this.
|
|
6839
|
+
await this.releaseLeaseBestEffort(run.id);
|
|
6694
6840
|
return result;
|
|
6695
6841
|
}
|
|
6696
6842
|
const history = await this.remoteHistory(run.id, checkpoints);
|
|
@@ -6728,7 +6874,7 @@ ${run.workflowVersion}`;
|
|
|
6728
6874
|
decision = silenceMs != null ? await this.awaitWithLivenessDeadline(run.id, advance, silenceMs, () => new RemoteWorkflowTimeout(run.id, silenceMs)) : await advance;
|
|
6729
6875
|
} catch (err) {
|
|
6730
6876
|
if (err instanceof RemoteWorkflowTimeout) {
|
|
6731
|
-
await this.
|
|
6877
|
+
await this.releaseLeaseBestEffort(run.id);
|
|
6732
6878
|
return {
|
|
6733
6879
|
runId: run.id,
|
|
6734
6880
|
status: "running"
|
|
@@ -7399,7 +7545,7 @@ ${run.workflowVersion}`;
|
|
|
7399
7545
|
this.awaitedRemoteSteps.delete(run.id);
|
|
7400
7546
|
}
|
|
7401
7547
|
if (this.awaitedStepFans.get(run.id) === awaitedFans) this.awaitedStepFans.delete(run.id);
|
|
7402
|
-
await this.
|
|
7548
|
+
await this.releaseLeaseBestEffort(run.id);
|
|
7403
7549
|
if (suspendedOnFan) {
|
|
7404
7550
|
void this.redriveSettledChildren(run.id, suspendedOnFan).catch(() => void 0);
|
|
7405
7551
|
}
|
|
@@ -7770,27 +7916,21 @@ ${run.workflowVersion}`;
|
|
|
7770
7916
|
* own `finally` also releases (the run-execution path) is safe.
|
|
7771
7917
|
*/
|
|
7772
7918
|
async parkBlocked(run, blocked) {
|
|
7773
|
-
const current = await this.store.getRun(run.id);
|
|
7774
|
-
if (current && (current.status === "cancelled" || current.status === "completed")) {
|
|
7775
|
-
await this.store.releaseRunLock(run.id);
|
|
7776
|
-
return {
|
|
7777
|
-
runId: run.id,
|
|
7778
|
-
status: current.status,
|
|
7779
|
-
output: current.output,
|
|
7780
|
-
error: current.error
|
|
7781
|
-
};
|
|
7782
|
-
}
|
|
7783
7919
|
const error2 = {
|
|
7784
7920
|
message: blocked.reason,
|
|
7785
7921
|
code: blocked.code,
|
|
7786
7922
|
retryable: true
|
|
7787
7923
|
};
|
|
7788
|
-
await this.store.
|
|
7924
|
+
const applied = await (this.store.updateRunIf ?? this.updateRunIfFallback).call(this.store, run.id, SUSPEND_ELIGIBLE_STATUSES, {
|
|
7789
7925
|
status: "blocked",
|
|
7790
7926
|
error: error2,
|
|
7791
7927
|
wakeAt: this.clock() + this.blockedPollMs,
|
|
7792
7928
|
updatedAt: /* @__PURE__ */ new Date()
|
|
7793
7929
|
});
|
|
7930
|
+
if (!applied) {
|
|
7931
|
+
await this.store.releaseRunLock(run.id);
|
|
7932
|
+
return this.echoCurrentStatus(run.id);
|
|
7933
|
+
}
|
|
7794
7934
|
this.emit({
|
|
7795
7935
|
type: "run.blocked",
|
|
7796
7936
|
runId: run.id,
|
|
@@ -9148,6 +9288,12 @@ var CodecStateStore = class {
|
|
|
9148
9288
|
/** Forwarded verbatim (deletes touch no payload). Bound only when the inner store has it, so the
|
|
9149
9289
|
* engine's per-run fallback still kicks in through the wrapper. */
|
|
9150
9290
|
deleteRuns;
|
|
9291
|
+
/** Decodes the runs like {@link listIncompleteRuns}. Bound only when the inner store has it, so
|
|
9292
|
+
* the engine's list-and-filter fallback still kicks in through the wrapper. */
|
|
9293
|
+
listOrphanedRuns;
|
|
9294
|
+
/** Encodes the patch like {@link updateRun}. Bound only when the inner store has it, so the
|
|
9295
|
+
* engine's read-then-write fallback still kicks in through the wrapper. */
|
|
9296
|
+
updateRunIf;
|
|
9151
9297
|
/** Encodes like {@link saveCheckpoint}. Bound only when the inner store has it, so the engine's
|
|
9152
9298
|
* plain-save fallback still kicks in through the wrapper. */
|
|
9153
9299
|
saveCheckpointIfPending;
|
|
@@ -9188,6 +9334,10 @@ var CodecStateStore = class {
|
|
|
9188
9334
|
if (inner.tryAdmitSingleton) this.tryAdmitSingleton = inner.tryAdmitSingleton.bind(inner);
|
|
9189
9335
|
const deleteRuns = inner.deleteRuns;
|
|
9190
9336
|
if (deleteRuns) this.deleteRuns = (ids) => deleteRuns.call(inner, ids);
|
|
9337
|
+
const orphans = inner.listOrphanedRuns;
|
|
9338
|
+
if (orphans) this.listOrphanedRuns = async (nowMs, limit, namespace) => (await orphans.call(inner, nowMs, limit, namespace)).map((r) => this.decRun(r));
|
|
9339
|
+
const updateIf = inner.updateRunIf;
|
|
9340
|
+
if (updateIf) this.updateRunIf = (runId, expected, patch) => updateIf.call(inner, runId, expected, this.encRunPatch(patch));
|
|
9191
9341
|
const saveIfPending = inner.saveCheckpointIfPending;
|
|
9192
9342
|
if (saveIfPending) this.saveCheckpointIfPending = (cp) => saveIfPending.call(inner, this.encCp(cp));
|
|
9193
9343
|
const saveManyIfPending = inner.saveCheckpointsIfPending;
|
|
@@ -9294,12 +9444,15 @@ var CodecStateStore = class {
|
|
|
9294
9444
|
return this.inner.createRun(this.encRun(run));
|
|
9295
9445
|
}
|
|
9296
9446
|
updateRun(runId, patch) {
|
|
9447
|
+
return this.inner.updateRun(runId, this.encRunPatch(patch));
|
|
9448
|
+
}
|
|
9449
|
+
encRunPatch(patch) {
|
|
9297
9450
|
const next = {
|
|
9298
9451
|
...patch
|
|
9299
9452
|
};
|
|
9300
9453
|
if ("input" in patch) next.input = this.enc(patch.input);
|
|
9301
9454
|
if ("output" in patch) next.output = this.enc(patch.output);
|
|
9302
|
-
return
|
|
9455
|
+
return next;
|
|
9303
9456
|
}
|
|
9304
9457
|
async getRun(runId) {
|
|
9305
9458
|
return this.decRun(await this.inner.getRun(runId));
|
|
@@ -9708,6 +9861,14 @@ var InMemoryStateStore = class {
|
|
|
9708
9861
|
this.reindexAttributes(run.id, run.searchAttributes);
|
|
9709
9862
|
}
|
|
9710
9863
|
async updateRun(runId, inputPatch) {
|
|
9864
|
+
this.applyRunPatch(runId, inputPatch);
|
|
9865
|
+
}
|
|
9866
|
+
updateRunIf(runId, expectedStatuses, patch) {
|
|
9867
|
+
const existing = this.runs.get(runId);
|
|
9868
|
+
if (!existing || !expectedStatuses.includes(existing.status)) return Promise.resolve(false);
|
|
9869
|
+
return this.updateRun(runId, patch).then(() => true);
|
|
9870
|
+
}
|
|
9871
|
+
applyRunPatch(runId, inputPatch) {
|
|
9711
9872
|
const patch = inputPatch.status && TERMINAL_RUN_STATUSES.includes(inputPatch.status) ? {
|
|
9712
9873
|
...inputPatch,
|
|
9713
9874
|
tags: ("tags" in inputPatch ? inputPatch.tags : this.runs.get(runId)?.tags)?.filter((tag) => tag !== SINGLETON_ADMITTED_TAG)
|
|
@@ -9798,6 +9959,13 @@ var InMemoryStateStore = class {
|
|
|
9798
9959
|
return true;
|
|
9799
9960
|
});
|
|
9800
9961
|
}
|
|
9962
|
+
async listOrphanedRuns(nowMs, limit, namespace) {
|
|
9963
|
+
return [
|
|
9964
|
+
...this.runs.values()
|
|
9965
|
+
].filter((r) => (r.status === "running" || r.status === "cancelling") && (r.lockedUntil == null || r.lockedUntil <= nowMs) && (namespace === void 0 || r.namespace === namespace)).sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime()).slice(0, Math.max(0, limit)).map((r) => ({
|
|
9966
|
+
...r
|
|
9967
|
+
}));
|
|
9968
|
+
}
|
|
9801
9969
|
async listIncompleteRuns(namespace) {
|
|
9802
9970
|
return [
|
|
9803
9971
|
...this.runs.values()
|