@dudousxd/nestjs-durable-core 0.85.0 → 0.85.2
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 +37 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -4
- package/dist/index.d.ts +14 -4
- package/dist/index.js +37 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -7038,6 +7038,8 @@ interface ScheduledWorkflow<W extends WorkflowRef = WorkflowRef> {
|
|
|
7038
7038
|
/**
|
|
7039
7039
|
* Randomly delay the dispatch by up to this many ms BEFORE firing, to spread load when many
|
|
7040
7040
|
* instances tick on the same boundary (avoids a thundering herd). Opt-in; absent = fire immediately.
|
|
7041
|
+
* The window is deferred to a later tick, never waited for inside one, so a jittered schedule does
|
|
7042
|
+
* not hold up any other schedule or the rest of the timer poll.
|
|
7041
7043
|
*
|
|
7042
7044
|
* This is dispatch-path jitter only — it does NOT affect the run id (still the time bucket), so
|
|
7043
7045
|
* idempotency is unchanged: two instances jittering the same window still start it exactly once.
|
|
@@ -7066,9 +7068,13 @@ interface ScheduledWorkflow<W extends WorkflowRef = WorkflowRef> {
|
|
|
7066
7068
|
namespace?: string;
|
|
7067
7069
|
}
|
|
7068
7070
|
/** Injectable dispatch-path effects (kept out of the deterministic run-id logic). Defaults to real
|
|
7069
|
-
* `Math.random
|
|
7071
|
+
* `Math.random`; overridden in tests so jitter is deterministic. */
|
|
7070
7072
|
interface RunSchedulesOptions {
|
|
7071
7073
|
random?: () => number;
|
|
7074
|
+
/**
|
|
7075
|
+
* @deprecated No longer called: jitter defers a window to a later tick instead of sleeping inside
|
|
7076
|
+
* this one. Kept so existing callers still type-check.
|
|
7077
|
+
*/
|
|
7072
7078
|
sleep?: (ms: number) => Promise<void>;
|
|
7073
7079
|
}
|
|
7074
7080
|
/** Deterministic run id for a fixed-interval schedule's current time window. */
|
|
@@ -7096,10 +7102,14 @@ declare function scheduleRunIdAt(s: ScheduledWorkflow, nowMs: number): string;
|
|
|
7096
7102
|
* Start each schedule's current-window run. The run id is the time bucket (a fixed-interval index,
|
|
7097
7103
|
* or the cron fire time) and `engine.start` is idempotent, so firing this on an interval — or
|
|
7098
7104
|
* racing two instances on the same tick — starts **each window exactly once**. Wire it to a
|
|
7099
|
-
* `setInterval`, the durable timer poller, or `@nestjs/schedule`.
|
|
7105
|
+
* `setInterval`, the durable timer poller, or `@nestjs/schedule`.
|
|
7106
|
+
*
|
|
7107
|
+
* Pass the SAME `engine` object on every call: which windows already fired, and the jittered fire
|
|
7108
|
+
* time of each deferred window, are remembered per engine object. A fresh wrapper per call would
|
|
7109
|
+
* redraw a deferred window's fire time on every tick, so it might never fire. Returns the run ids this call
|
|
7100
7110
|
* started: a window that already has a run is not started or returned again, and once this process
|
|
7101
|
-
* has seen a window's run exist, later calls in that window skip it without a store read
|
|
7102
|
-
*
|
|
7111
|
+
* has seen a window's run exist, later calls in that window skip it without a store read. A
|
|
7112
|
+
* jittered window is deferred to a later call, never slept on.
|
|
7103
7113
|
*/
|
|
7104
7114
|
declare function runSchedules(engine: Pick<WorkflowEngine, 'start' | 'getRun'> & Partial<Pick<WorkflowEngine, 'isDraining'>>, schedules: readonly ScheduledWorkflow[], nowMs: number, opts?: RunSchedulesOptions): Promise<string[]>;
|
|
7105
7115
|
|
package/dist/index.d.ts
CHANGED
|
@@ -7038,6 +7038,8 @@ interface ScheduledWorkflow<W extends WorkflowRef = WorkflowRef> {
|
|
|
7038
7038
|
/**
|
|
7039
7039
|
* Randomly delay the dispatch by up to this many ms BEFORE firing, to spread load when many
|
|
7040
7040
|
* instances tick on the same boundary (avoids a thundering herd). Opt-in; absent = fire immediately.
|
|
7041
|
+
* The window is deferred to a later tick, never waited for inside one, so a jittered schedule does
|
|
7042
|
+
* not hold up any other schedule or the rest of the timer poll.
|
|
7041
7043
|
*
|
|
7042
7044
|
* This is dispatch-path jitter only — it does NOT affect the run id (still the time bucket), so
|
|
7043
7045
|
* idempotency is unchanged: two instances jittering the same window still start it exactly once.
|
|
@@ -7066,9 +7068,13 @@ interface ScheduledWorkflow<W extends WorkflowRef = WorkflowRef> {
|
|
|
7066
7068
|
namespace?: string;
|
|
7067
7069
|
}
|
|
7068
7070
|
/** Injectable dispatch-path effects (kept out of the deterministic run-id logic). Defaults to real
|
|
7069
|
-
* `Math.random
|
|
7071
|
+
* `Math.random`; overridden in tests so jitter is deterministic. */
|
|
7070
7072
|
interface RunSchedulesOptions {
|
|
7071
7073
|
random?: () => number;
|
|
7074
|
+
/**
|
|
7075
|
+
* @deprecated No longer called: jitter defers a window to a later tick instead of sleeping inside
|
|
7076
|
+
* this one. Kept so existing callers still type-check.
|
|
7077
|
+
*/
|
|
7072
7078
|
sleep?: (ms: number) => Promise<void>;
|
|
7073
7079
|
}
|
|
7074
7080
|
/** Deterministic run id for a fixed-interval schedule's current time window. */
|
|
@@ -7096,10 +7102,14 @@ declare function scheduleRunIdAt(s: ScheduledWorkflow, nowMs: number): string;
|
|
|
7096
7102
|
* Start each schedule's current-window run. The run id is the time bucket (a fixed-interval index,
|
|
7097
7103
|
* or the cron fire time) and `engine.start` is idempotent, so firing this on an interval — or
|
|
7098
7104
|
* racing two instances on the same tick — starts **each window exactly once**. Wire it to a
|
|
7099
|
-
* `setInterval`, the durable timer poller, or `@nestjs/schedule`.
|
|
7105
|
+
* `setInterval`, the durable timer poller, or `@nestjs/schedule`.
|
|
7106
|
+
*
|
|
7107
|
+
* Pass the SAME `engine` object on every call: which windows already fired, and the jittered fire
|
|
7108
|
+
* time of each deferred window, are remembered per engine object. A fresh wrapper per call would
|
|
7109
|
+
* redraw a deferred window's fire time on every tick, so it might never fire. Returns the run ids this call
|
|
7100
7110
|
* started: a window that already has a run is not started or returned again, and once this process
|
|
7101
|
-
* has seen a window's run exist, later calls in that window skip it without a store read
|
|
7102
|
-
*
|
|
7111
|
+
* has seen a window's run exist, later calls in that window skip it without a store read. A
|
|
7112
|
+
* jittered window is deferred to a later call, never slept on.
|
|
7103
7113
|
*/
|
|
7104
7114
|
declare function runSchedules(engine: Pick<WorkflowEngine, 'start' | 'getRun'> & Partial<Pick<WorkflowEngine, 'isDraining'>>, schedules: readonly ScheduledWorkflow[], nowMs: number, opts?: RunSchedulesOptions): Promise<string[]>;
|
|
7105
7115
|
|
package/dist/index.js
CHANGED
|
@@ -2417,7 +2417,31 @@ __name(resolveCronParse, "resolveCronParse");
|
|
|
2417
2417
|
|
|
2418
2418
|
// src/scheduler.ts
|
|
2419
2419
|
var nodeRequire = createRequire(import.meta.url);
|
|
2420
|
-
var
|
|
2420
|
+
var jitterFireAt = /* @__PURE__ */ new WeakMap();
|
|
2421
|
+
function deferredWindows(engine, key) {
|
|
2422
|
+
return [
|
|
2423
|
+
...jitterFireAt.get(engine)?.get(key)?.keys() ?? []
|
|
2424
|
+
];
|
|
2425
|
+
}
|
|
2426
|
+
__name(deferredWindows, "deferredWindows");
|
|
2427
|
+
function jitterDue(engine, s, candidates, nowMs, random) {
|
|
2428
|
+
let byKey = jitterFireAt.get(engine);
|
|
2429
|
+
if (byKey === void 0) {
|
|
2430
|
+
byKey = /* @__PURE__ */ new Map();
|
|
2431
|
+
jitterFireAt.set(engine, byKey);
|
|
2432
|
+
}
|
|
2433
|
+
const previous = byKey.get(s.key);
|
|
2434
|
+
const current = /* @__PURE__ */ new Map();
|
|
2435
|
+
const due = [];
|
|
2436
|
+
for (const runId of candidates) {
|
|
2437
|
+
const at = previous?.get(runId) ?? nowMs + Math.floor(random() * s.jitter);
|
|
2438
|
+
if (nowMs >= at) due.push(runId);
|
|
2439
|
+
else current.set(runId, at);
|
|
2440
|
+
}
|
|
2441
|
+
byKey.set(s.key, current);
|
|
2442
|
+
return due;
|
|
2443
|
+
}
|
|
2444
|
+
__name(jitterDue, "jitterDue");
|
|
2421
2445
|
function scheduledRunId(key, everyMs, nowMs) {
|
|
2422
2446
|
return `sched:${key}:${Math.floor(nowMs / everyMs)}`;
|
|
2423
2447
|
}
|
|
@@ -2487,13 +2511,19 @@ function scheduleRunIdsAt(s, nowMs) {
|
|
|
2487
2511
|
__name(scheduleRunIdsAt, "scheduleRunIdsAt");
|
|
2488
2512
|
async function runSchedules(engine, schedules, nowMs, opts) {
|
|
2489
2513
|
const random = opts?.random ?? Math.random;
|
|
2490
|
-
const sleep = opts?.sleep ?? defaultSleep;
|
|
2491
2514
|
const settled = settledIdsFor(engine);
|
|
2492
2515
|
const ids = [];
|
|
2493
2516
|
for (const s of schedules) {
|
|
2494
2517
|
if (engine.isDraining?.()) break;
|
|
2495
2518
|
if (s.paused) continue;
|
|
2496
|
-
const
|
|
2519
|
+
const due = scheduleRunIdsAt(s, nowMs);
|
|
2520
|
+
const windows = s.jitter && s.jitter > 0 ? [
|
|
2521
|
+
.../* @__PURE__ */ new Set([
|
|
2522
|
+
...deferredWindows(engine, s.key),
|
|
2523
|
+
...due
|
|
2524
|
+
])
|
|
2525
|
+
] : due;
|
|
2526
|
+
const candidates = windows.filter((id) => !settled.get(s.key)?.has(id));
|
|
2497
2527
|
if (candidates.length === 0) continue;
|
|
2498
2528
|
if (s.overlap === "skip" && s.everyMs) {
|
|
2499
2529
|
const prevBucket = Math.floor(nowMs / s.everyMs) - 1;
|
|
@@ -2502,8 +2532,10 @@ async function runSchedules(engine, schedules, nowMs, opts) {
|
|
|
2502
2532
|
if (prev && (prev.status === "running" || prev.status === "suspended")) continue;
|
|
2503
2533
|
}
|
|
2504
2534
|
}
|
|
2505
|
-
if (s.jitter && s.jitter > 0)
|
|
2506
|
-
|
|
2535
|
+
if (!(s.jitter && s.jitter > 0)) jitterFireAt.get(engine)?.delete(s.key);
|
|
2536
|
+
const firing = s.jitter && s.jitter > 0 ? jitterDue(engine, s, candidates, nowMs, random) : candidates;
|
|
2537
|
+
if (firing.length === 0) continue;
|
|
2538
|
+
for (const runId of firing) {
|
|
2507
2539
|
if (engine.isDraining?.()) break;
|
|
2508
2540
|
const existing = await engine.getRun(runId);
|
|
2509
2541
|
remember(settled, s.key, runId);
|