@rotorsoft/act 0.40.0 → 0.41.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/.tsbuildinfo +1 -1
- package/dist/@types/builders/act-builder.d.ts.map +1 -1
- package/dist/@types/builders/slice-builder.d.ts.map +1 -1
- package/dist/@types/internal/backoff.d.ts +22 -0
- package/dist/@types/internal/backoff.d.ts.map +1 -0
- package/dist/@types/internal/correlate-cycle.d.ts.map +1 -1
- package/dist/@types/internal/drain-cycle.d.ts +34 -1
- package/dist/@types/internal/drain-cycle.d.ts.map +1 -1
- package/dist/@types/internal/lru-map.d.ts.map +1 -0
- package/dist/@types/internal/reactions.d.ts.map +1 -1
- package/dist/@types/types/reaction.d.ts +44 -0
- package/dist/@types/types/reaction.d.ts.map +1 -1
- package/dist/{chunk-TP2OZWHP.js → chunk-M5YFKVRV.js} +2 -2
- package/dist/chunk-M5YFKVRV.js.map +1 -0
- package/dist/index.cjs +87 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +87 -8
- package/dist/index.js.map +1 -1
- package/dist/test/index.cjs +1 -1
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.js +1 -1
- package/package.json +2 -2
- package/dist/@types/lru-map.d.ts.map +0 -1
- package/dist/chunk-TP2OZWHP.js.map +0 -1
- /package/dist/@types/{lru-map.d.ts → internal/lru-map.d.ts} +0 -0
package/dist/index.js
CHANGED
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
sleep,
|
|
19
19
|
store,
|
|
20
20
|
validate
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-M5YFKVRV.js";
|
|
22
22
|
import {
|
|
23
23
|
ActorSchema,
|
|
24
24
|
CausationEventSchema,
|
|
@@ -392,10 +392,20 @@ function computeLagLeadRatio(handled, lagging, leading) {
|
|
|
392
392
|
}
|
|
393
393
|
|
|
394
394
|
// src/internal/drain-cycle.ts
|
|
395
|
-
async function runDrainCycle(ops, registry, batchHandlers, handle, handleBatch, lagging, leading, eventLimit, leaseMillis) {
|
|
395
|
+
async function runDrainCycle(ops, registry, batchHandlers, handle, handleBatch, lagging, leading, eventLimit, leaseMillis, isDeferred) {
|
|
396
396
|
const leased = await ops.claim(lagging, leading, randomUUID2(), leaseMillis);
|
|
397
397
|
if (!leased.length) return void 0;
|
|
398
|
-
const
|
|
398
|
+
const active = isDeferred ? leased.filter((l) => !isDeferred(l.stream)) : leased;
|
|
399
|
+
if (!active.length) {
|
|
400
|
+
return {
|
|
401
|
+
leased,
|
|
402
|
+
fetched: [],
|
|
403
|
+
handled: [],
|
|
404
|
+
acked: [],
|
|
405
|
+
blocked: []
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
const fetched = await ops.fetch(active, eventLimit);
|
|
399
409
|
const fetchMap = /* @__PURE__ */ new Map();
|
|
400
410
|
const fetch_window_at = fetched.reduce(
|
|
401
411
|
(max, { at, events }) => Math.max(max, events.at(-1)?.id || at),
|
|
@@ -414,7 +424,7 @@ async function runDrainCycle(ops, registry, batchHandlers, handle, handleBatch,
|
|
|
414
424
|
fetchMap.set(stream, { fetch: f, payloads });
|
|
415
425
|
}
|
|
416
426
|
const handled = await Promise.all(
|
|
417
|
-
|
|
427
|
+
active.map((lease) => {
|
|
418
428
|
const entry = fetchMap.get(lease.stream);
|
|
419
429
|
const at = entry.fetch.events.at(-1)?.id || fetch_window_at;
|
|
420
430
|
const { payloads } = entry;
|
|
@@ -446,6 +456,15 @@ var DrainController = class {
|
|
|
446
456
|
_armed = false;
|
|
447
457
|
_locked = false;
|
|
448
458
|
_ratio = 0.5;
|
|
459
|
+
/**
|
|
460
|
+
* Per-stream backoff: `stream → nextAttemptAt` (ms since epoch). Set by
|
|
461
|
+
* `_finalize` via `HandleResult.nextAttemptAt`; cleared on successful
|
|
462
|
+
* ack or terminal block. Lives in process memory — per-worker pacing
|
|
463
|
+
* by design (see {@link BackoffOptions} for the multi-worker trade-off).
|
|
464
|
+
*/
|
|
465
|
+
_backoff = /* @__PURE__ */ new Map();
|
|
466
|
+
/** Timer re-arming drain at the earliest pending `nextAttemptAt`. */
|
|
467
|
+
_backoffTimer;
|
|
449
468
|
/**
|
|
450
469
|
* Signal that a commit (or reset / cold-start) may have produced work.
|
|
451
470
|
* Subsequent `drain()` calls will run the pipeline; once the pipeline
|
|
@@ -458,6 +477,32 @@ var DrainController = class {
|
|
|
458
477
|
get armed() {
|
|
459
478
|
return this._armed;
|
|
460
479
|
}
|
|
480
|
+
/** Returns true when `stream` is currently within a backoff window. */
|
|
481
|
+
isDeferred = (stream) => {
|
|
482
|
+
const next = this._backoff.get(stream);
|
|
483
|
+
return next !== void 0 && next > Date.now();
|
|
484
|
+
};
|
|
485
|
+
/**
|
|
486
|
+
* Schedule the next drain re-arm at the earliest pending backoff
|
|
487
|
+
* expiry. Called only when the backoff map is non-empty (caller guard).
|
|
488
|
+
* Idempotent — collapses many simultaneously deferred streams into a
|
|
489
|
+
* single timer.
|
|
490
|
+
*/
|
|
491
|
+
scheduleBackoffWake() {
|
|
492
|
+
if (this._backoffTimer) clearTimeout(this._backoffTimer);
|
|
493
|
+
let earliest = Number.POSITIVE_INFINITY;
|
|
494
|
+
for (const t of this._backoff.values()) if (t < earliest) earliest = t;
|
|
495
|
+
const delay = Math.max(0, earliest - Date.now());
|
|
496
|
+
this._backoffTimer = setTimeout(() => {
|
|
497
|
+
this._backoffTimer = void 0;
|
|
498
|
+
const now = Date.now();
|
|
499
|
+
for (const [stream, at] of this._backoff) {
|
|
500
|
+
if (at <= now) this._backoff.delete(stream);
|
|
501
|
+
}
|
|
502
|
+
this._armed = true;
|
|
503
|
+
}, delay);
|
|
504
|
+
this._backoffTimer.unref();
|
|
505
|
+
}
|
|
461
506
|
/** Run one drain pass. Short-circuits when not armed or already running. */
|
|
462
507
|
async drain({
|
|
463
508
|
streamLimit = 10,
|
|
@@ -479,7 +524,8 @@ var DrainController = class {
|
|
|
479
524
|
lagging,
|
|
480
525
|
leading,
|
|
481
526
|
eventLimit,
|
|
482
|
-
leaseMillis
|
|
527
|
+
leaseMillis,
|
|
528
|
+
this._backoff.size > 0 ? this.isDeferred : void 0
|
|
483
529
|
);
|
|
484
530
|
if (!cycle) {
|
|
485
531
|
this._armed = false;
|
|
@@ -487,6 +533,14 @@ var DrainController = class {
|
|
|
487
533
|
}
|
|
488
534
|
const { leased, fetched, handled, acked, blocked } = cycle;
|
|
489
535
|
this._ratio = computeLagLeadRatio(handled, lagging, leading);
|
|
536
|
+
for (const lease of acked) this._backoff.delete(lease.stream);
|
|
537
|
+
for (const lease of blocked) this._backoff.delete(lease.stream);
|
|
538
|
+
for (const h of handled) {
|
|
539
|
+
if (h.nextAttemptAt !== void 0 && !h.block) {
|
|
540
|
+
this._backoff.set(h.lease.stream, h.nextAttemptAt);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
if (this._backoff.size > 0) this.scheduleBackoffWake();
|
|
490
544
|
if (acked.length) this.deps.onAcked(acked);
|
|
491
545
|
if (blocked.length) this.deps.onBlocked(blocked);
|
|
492
546
|
const hasErrors = handled.some(({ error }) => error);
|
|
@@ -681,6 +735,27 @@ var _this_ = ({ stream }) => ({
|
|
|
681
735
|
target: stream
|
|
682
736
|
});
|
|
683
737
|
|
|
738
|
+
// src/internal/backoff.ts
|
|
739
|
+
function computeBackoffDelay(retry, opts) {
|
|
740
|
+
if (!opts || opts.baseMs <= 0) return 0;
|
|
741
|
+
const r = Math.max(0, retry);
|
|
742
|
+
let delay;
|
|
743
|
+
switch (opts.strategy) {
|
|
744
|
+
case "fixed":
|
|
745
|
+
delay = opts.baseMs;
|
|
746
|
+
break;
|
|
747
|
+
case "linear":
|
|
748
|
+
delay = opts.baseMs * (r + 1);
|
|
749
|
+
break;
|
|
750
|
+
case "exponential":
|
|
751
|
+
delay = opts.baseMs * 2 ** r;
|
|
752
|
+
if (opts.maxMs !== void 0) delay = Math.min(delay, opts.maxMs);
|
|
753
|
+
break;
|
|
754
|
+
}
|
|
755
|
+
if (opts.jitter) delay = delay * (0.5 + Math.random());
|
|
756
|
+
return Math.max(0, Math.floor(delay));
|
|
757
|
+
}
|
|
758
|
+
|
|
684
759
|
// src/internal/reactions.ts
|
|
685
760
|
function finalize(lease, handled, at, error, options, logger) {
|
|
686
761
|
if (!error) return { lease, handled, at };
|
|
@@ -688,12 +763,14 @@ function finalize(lease, handled, at, error, options, logger) {
|
|
|
688
763
|
const block2 = lease.retry >= options.maxRetries && options.blockOnError;
|
|
689
764
|
if (block2)
|
|
690
765
|
logger.error(`Blocking ${lease.stream} after ${lease.retry} retries.`);
|
|
766
|
+
const nextAttemptAt = !block2 && options.backoff ? Date.now() + computeBackoffDelay(lease.retry, options.backoff) : void 0;
|
|
691
767
|
return {
|
|
692
768
|
lease,
|
|
693
769
|
handled,
|
|
694
770
|
at,
|
|
695
771
|
error: handled === 0 ? error.message : void 0,
|
|
696
|
-
block: block2
|
|
772
|
+
block: block2,
|
|
773
|
+
nextAttemptAt
|
|
697
774
|
};
|
|
698
775
|
}
|
|
699
776
|
function buildHandle(deps) {
|
|
@@ -2013,7 +2090,8 @@ function act() {
|
|
|
2013
2090
|
resolver: _this_,
|
|
2014
2091
|
options: {
|
|
2015
2092
|
blockOnError: options?.blockOnError ?? true,
|
|
2016
|
-
maxRetries: options?.maxRetries ?? 3
|
|
2093
|
+
maxRetries: options?.maxRetries ?? 3,
|
|
2094
|
+
backoff: options?.backoff
|
|
2017
2095
|
}
|
|
2018
2096
|
};
|
|
2019
2097
|
if (!handler.name)
|
|
@@ -2139,7 +2217,8 @@ function slice() {
|
|
|
2139
2217
|
resolver: _this_,
|
|
2140
2218
|
options: {
|
|
2141
2219
|
blockOnError: options?.blockOnError ?? true,
|
|
2142
|
-
maxRetries: options?.maxRetries ?? 3
|
|
2220
|
+
maxRetries: options?.maxRetries ?? 3,
|
|
2221
|
+
backoff: options?.backoff
|
|
2143
2222
|
}
|
|
2144
2223
|
};
|
|
2145
2224
|
if (!handler.name)
|