@dudousxd/nestjs-durable-core 0.85.1 → 0.85.3
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 +44 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -1
- package/dist/index.d.ts +21 -1
- package/dist/index.js +44 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -5704,6 +5704,22 @@ declare class WorkflowEngine {
|
|
|
5704
5704
|
private markStarted;
|
|
5705
5705
|
private singletonWaitResult;
|
|
5706
5706
|
private execute;
|
|
5707
|
+
/**
|
|
5708
|
+
* Keep a wake that found `runId`'s lease held by a turn executing elsewhere, durably, so that turn
|
|
5709
|
+
* runs again once it ends instead of suspending on a snapshot that predates what the wake brought
|
|
5710
|
+
* (a signal, a child's completion). The request is a buffered signal on a token no workflow waits
|
|
5711
|
+
* on; the holder takes it after it releases the lease ({@link honorWakeRequests}).
|
|
5712
|
+
*
|
|
5713
|
+
* Written first and looked at second, against a holder that releases first and looks second:
|
|
5714
|
+
* whichever look runs last sees the other side's write. So when the run is no longer executing by
|
|
5715
|
+
* the time this re-reads it, the holder may have looked already, and this side takes the request
|
|
5716
|
+
* and wakes the run itself once the lease is free.
|
|
5717
|
+
*/
|
|
5718
|
+
private requestWakeAfterTurn;
|
|
5719
|
+
/** After a turn that suspended released its lease: run again if a wake was kept for it. */
|
|
5720
|
+
private honorWakeRequests;
|
|
5721
|
+
/** Take every wake request kept for `runId`; true when there was one. */
|
|
5722
|
+
private takeWakeRequests;
|
|
5707
5723
|
/**
|
|
5708
5724
|
* A wake that found the run leased by another instance is dropped, and normally that is fine: the
|
|
5709
5725
|
* holder's turn either sees what the wake wrote, or re-checks once it releases the lease
|
|
@@ -7102,7 +7118,11 @@ declare function scheduleRunIdAt(s: ScheduledWorkflow, nowMs: number): string;
|
|
|
7102
7118
|
* Start each schedule's current-window run. The run id is the time bucket (a fixed-interval index,
|
|
7103
7119
|
* or the cron fire time) and `engine.start` is idempotent, so firing this on an interval — or
|
|
7104
7120
|
* racing two instances on the same tick — starts **each window exactly once**. Wire it to a
|
|
7105
|
-
* `setInterval`, the durable timer poller, or `@nestjs/schedule`.
|
|
7121
|
+
* `setInterval`, the durable timer poller, or `@nestjs/schedule`.
|
|
7122
|
+
*
|
|
7123
|
+
* Pass the SAME `engine` object on every call: which windows already fired, and the jittered fire
|
|
7124
|
+
* time of each deferred window, are remembered per engine object. A fresh wrapper per call would
|
|
7125
|
+
* redraw a deferred window's fire time on every tick, so it might never fire. Returns the run ids this call
|
|
7106
7126
|
* started: a window that already has a run is not started or returned again, and once this process
|
|
7107
7127
|
* has seen a window's run exist, later calls in that window skip it without a store read. A
|
|
7108
7128
|
* jittered window is deferred to a later call, never slept on.
|
package/dist/index.d.ts
CHANGED
|
@@ -5704,6 +5704,22 @@ declare class WorkflowEngine {
|
|
|
5704
5704
|
private markStarted;
|
|
5705
5705
|
private singletonWaitResult;
|
|
5706
5706
|
private execute;
|
|
5707
|
+
/**
|
|
5708
|
+
* Keep a wake that found `runId`'s lease held by a turn executing elsewhere, durably, so that turn
|
|
5709
|
+
* runs again once it ends instead of suspending on a snapshot that predates what the wake brought
|
|
5710
|
+
* (a signal, a child's completion). The request is a buffered signal on a token no workflow waits
|
|
5711
|
+
* on; the holder takes it after it releases the lease ({@link honorWakeRequests}).
|
|
5712
|
+
*
|
|
5713
|
+
* Written first and looked at second, against a holder that releases first and looks second:
|
|
5714
|
+
* whichever look runs last sees the other side's write. So when the run is no longer executing by
|
|
5715
|
+
* the time this re-reads it, the holder may have looked already, and this side takes the request
|
|
5716
|
+
* and wakes the run itself once the lease is free.
|
|
5717
|
+
*/
|
|
5718
|
+
private requestWakeAfterTurn;
|
|
5719
|
+
/** After a turn that suspended released its lease: run again if a wake was kept for it. */
|
|
5720
|
+
private honorWakeRequests;
|
|
5721
|
+
/** Take every wake request kept for `runId`; true when there was one. */
|
|
5722
|
+
private takeWakeRequests;
|
|
5707
5723
|
/**
|
|
5708
5724
|
* A wake that found the run leased by another instance is dropped, and normally that is fine: the
|
|
5709
5725
|
* holder's turn either sees what the wake wrote, or re-checks once it releases the lease
|
|
@@ -7102,7 +7118,11 @@ declare function scheduleRunIdAt(s: ScheduledWorkflow, nowMs: number): string;
|
|
|
7102
7118
|
* Start each schedule's current-window run. The run id is the time bucket (a fixed-interval index,
|
|
7103
7119
|
* or the cron fire time) and `engine.start` is idempotent, so firing this on an interval — or
|
|
7104
7120
|
* racing two instances on the same tick — starts **each window exactly once**. Wire it to a
|
|
7105
|
-
* `setInterval`, the durable timer poller, or `@nestjs/schedule`.
|
|
7121
|
+
* `setInterval`, the durable timer poller, or `@nestjs/schedule`.
|
|
7122
|
+
*
|
|
7123
|
+
* Pass the SAME `engine` object on every call: which windows already fired, and the jittered fire
|
|
7124
|
+
* time of each deferred window, are remembered per engine object. A fresh wrapper per call would
|
|
7125
|
+
* redraw a deferred window's fire time on every tick, so it might never fire. Returns the run ids this call
|
|
7106
7126
|
* started: a window that already has a run is not started or returned again, and once this process
|
|
7107
7127
|
* has seen a window's run exist, later calls in that window skip it without a store read. A
|
|
7108
7128
|
* jittered window is deferred to a later call, never slept on.
|
package/dist/index.js
CHANGED
|
@@ -2532,6 +2532,7 @@ async function runSchedules(engine, schedules, nowMs, opts) {
|
|
|
2532
2532
|
if (prev && (prev.status === "running" || prev.status === "suspended")) continue;
|
|
2533
2533
|
}
|
|
2534
2534
|
}
|
|
2535
|
+
if (!(s.jitter && s.jitter > 0)) jitterFireAt.get(engine)?.delete(s.key);
|
|
2535
2536
|
const firing = s.jitter && s.jitter > 0 ? jitterDue(engine, s, candidates, nowMs, random) : candidates;
|
|
2536
2537
|
if (firing.length === 0) continue;
|
|
2537
2538
|
for (const runId of firing) {
|
|
@@ -3823,7 +3824,6 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
|
|
|
3823
3824
|
}));
|
|
3824
3825
|
return unwrapCompletion(buffered.payload, `child "${id}"`);
|
|
3825
3826
|
}
|
|
3826
|
-
if (!await store.getRun(id)) host.startChild(workflowName(workflow2), input, id, priority, version);
|
|
3827
3827
|
if (!existing) {
|
|
3828
3828
|
await writeCheckpoint(instantCheckpoint({
|
|
3829
3829
|
runId,
|
|
@@ -3833,6 +3833,7 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
|
|
|
3833
3833
|
status: "running"
|
|
3834
3834
|
}));
|
|
3835
3835
|
}
|
|
3836
|
+
if (!await store.getRun(id)) host.startChild(workflowName(workflow2), input, id, priority, version);
|
|
3836
3837
|
throw new WorkflowSuspended(void 0, fanJoin([
|
|
3837
3838
|
{
|
|
3838
3839
|
waiterPrefix: `child:${id}`,
|
|
@@ -4451,6 +4452,7 @@ function deferred() {
|
|
|
4451
4452
|
__name(deferred, "deferred");
|
|
4452
4453
|
var pendingStateKey = /* @__PURE__ */ __name((at) => String(new Date(at).getTime()), "pendingStateKey");
|
|
4453
4454
|
var freshPendingKey = /* @__PURE__ */ __name(() => `${Date.now()}-${globalThis.crypto.randomUUID().slice(0, 8)}`, "freshPendingKey");
|
|
4455
|
+
var wakeRequestToken = /* @__PURE__ */ __name((runId) => `durable:wake-after-turn:${runId}`, "wakeRequestToken");
|
|
4454
4456
|
var CHILD_SIGNAL_PREFIX = "signal:child:";
|
|
4455
4457
|
var DELIVER_CHUNK = 50;
|
|
4456
4458
|
function childCompletion(child) {
|
|
@@ -7637,6 +7639,7 @@ var WorkflowEngine = class {
|
|
|
7637
7639
|
const lockNow = this.clock();
|
|
7638
7640
|
if (run.lockedBy !== this.instanceId || (run.lockedUntil ?? 0) <= lockNow) {
|
|
7639
7641
|
if (!await this.store.tryLockRun(run.id, this.instanceId, lockNow + this.leaseMs, lockNow)) {
|
|
7642
|
+
if (!this.leaseRetries.has(run.id)) this.inBackground(this.requestWakeAfterTurn(run.id));
|
|
7640
7643
|
this.recheckAfterLease(run.id, run.lockedUntil);
|
|
7641
7644
|
return {
|
|
7642
7645
|
runId: run.id,
|
|
@@ -7650,12 +7653,52 @@ var WorkflowEngine = class {
|
|
|
7650
7653
|
if (registered?.singleton && (result.status === "completed" || result.status === "failed" || result.status === "cancelled" || result.status === "dead")) {
|
|
7651
7654
|
void this.singletons.wakeNext(run).catch(() => void 0);
|
|
7652
7655
|
}
|
|
7656
|
+
if (result.status === "suspended") this.inBackground(this.honorWakeRequests(run.id));
|
|
7657
|
+
else if (TERMINAL_RUN_STATUSES.includes(result.status)) {
|
|
7658
|
+
this.inBackground(this.takeWakeRequests(run.id));
|
|
7659
|
+
}
|
|
7653
7660
|
return result;
|
|
7654
7661
|
} finally {
|
|
7655
7662
|
held.stop();
|
|
7656
7663
|
}
|
|
7657
7664
|
}
|
|
7658
7665
|
/**
|
|
7666
|
+
* Keep a wake that found `runId`'s lease held by a turn executing elsewhere, durably, so that turn
|
|
7667
|
+
* runs again once it ends instead of suspending on a snapshot that predates what the wake brought
|
|
7668
|
+
* (a signal, a child's completion). The request is a buffered signal on a token no workflow waits
|
|
7669
|
+
* on; the holder takes it after it releases the lease ({@link honorWakeRequests}).
|
|
7670
|
+
*
|
|
7671
|
+
* Written first and looked at second, against a holder that releases first and looks second:
|
|
7672
|
+
* whichever look runs last sees the other side's write. So when the run is no longer executing by
|
|
7673
|
+
* the time this re-reads it, the holder may have looked already, and this side takes the request
|
|
7674
|
+
* and wakes the run itself once the lease is free.
|
|
7675
|
+
*/
|
|
7676
|
+
async requestWakeAfterTurn(runId) {
|
|
7677
|
+
await this.store.bufferSignal(wakeRequestToken(runId), null);
|
|
7678
|
+
const run = await this.store.getRun(runId);
|
|
7679
|
+
if (run?.status === "running") return;
|
|
7680
|
+
if (!run) {
|
|
7681
|
+
await this.takeWakeRequests(runId);
|
|
7682
|
+
return;
|
|
7683
|
+
}
|
|
7684
|
+
if (!await this.takeWakeRequests(runId)) return;
|
|
7685
|
+
if (TERMINAL_RUN_STATUSES.includes(run.status)) return;
|
|
7686
|
+
this.recheckAfterLease(runId, run.lockedUntil);
|
|
7687
|
+
}
|
|
7688
|
+
/** After a turn that suspended released its lease: run again if a wake was kept for it. */
|
|
7689
|
+
async honorWakeRequests(runId) {
|
|
7690
|
+
if (!await this.takeWakeRequests(runId)) return;
|
|
7691
|
+
await this.wake(runId).catch((err) => {
|
|
7692
|
+
if (!(err instanceof NamespaceMismatch)) throw err;
|
|
7693
|
+
});
|
|
7694
|
+
}
|
|
7695
|
+
/** Take every wake request kept for `runId`; true when there was one. */
|
|
7696
|
+
async takeWakeRequests(runId) {
|
|
7697
|
+
let taken = false;
|
|
7698
|
+
while (await this.store.takeBufferedSignal(wakeRequestToken(runId))) taken = true;
|
|
7699
|
+
return taken;
|
|
7700
|
+
}
|
|
7701
|
+
/**
|
|
7659
7702
|
* A wake that found the run leased by another instance is dropped, and normally that is fine: the
|
|
7660
7703
|
* holder's turn either sees what the wake wrote, or re-checks once it releases the lease
|
|
7661
7704
|
* ({@link redriveSettledSteps}, {@link redriveSettledChildren}). Not if the holder goes down first
|