@rotorsoft/act 0.39.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/action.d.ts +2 -2
- package/dist/@types/types/ports.d.ts +13 -8
- package/dist/@types/types/ports.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-RHS57BUR.js → chunk-M5YFKVRV.js} +7 -7
- package/dist/chunk-M5YFKVRV.js.map +1 -0
- package/dist/index.cjs +92 -13
- 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 +8 -8
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.js +3 -3
- package/dist/test/index.js.map +1 -1
- package/package.json +4 -1
- package/dist/@types/lru-map.d.ts.map +0 -1
- package/dist/chunk-RHS57BUR.js.map +0 -1
- /package/dist/@types/{lru-map.d.ts → internal/lru-map.d.ts} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -190,7 +190,7 @@ var ConsoleLogger = class _ConsoleLogger {
|
|
|
190
190
|
}
|
|
191
191
|
};
|
|
192
192
|
|
|
193
|
-
// src/lru-map.ts
|
|
193
|
+
// src/internal/lru-map.ts
|
|
194
194
|
var LruMap = class {
|
|
195
195
|
constructor(_maxSize) {
|
|
196
196
|
this._maxSize = _maxSize;
|
|
@@ -644,7 +644,7 @@ var InMemoryStore = class {
|
|
|
644
644
|
if (query.stream) {
|
|
645
645
|
if (query.stream_exact) {
|
|
646
646
|
if (e.stream !== query.stream) return false;
|
|
647
|
-
} else if (!RegExp(
|
|
647
|
+
} else if (!RegExp(query.stream).test(e.stream)) return false;
|
|
648
648
|
}
|
|
649
649
|
if (query.names && !query.names.includes(e.name)) return false;
|
|
650
650
|
if (query.correlation && e.meta?.correlation !== query.correlation)
|
|
@@ -860,8 +860,8 @@ var InMemoryStore = class {
|
|
|
860
860
|
*/
|
|
861
861
|
async prioritize(filter, priority) {
|
|
862
862
|
await sleep();
|
|
863
|
-
const streamRe = filter.stream && !filter.stream_exact ? new RegExp(
|
|
864
|
-
const sourceRe = filter.source && !filter.source_exact ? new RegExp(
|
|
863
|
+
const streamRe = filter.stream && !filter.stream_exact ? new RegExp(filter.stream) : void 0;
|
|
864
|
+
const sourceRe = filter.source && !filter.source_exact ? new RegExp(filter.source) : void 0;
|
|
865
865
|
let count = 0;
|
|
866
866
|
for (const s of this._streams.values()) {
|
|
867
867
|
if (filter.stream !== void 0) {
|
|
@@ -892,8 +892,8 @@ var InMemoryStore = class {
|
|
|
892
892
|
const limit = query?.limit ?? 100;
|
|
893
893
|
const after = query?.after;
|
|
894
894
|
const blocked = query?.blocked;
|
|
895
|
-
const streamRe = query?.stream && !query.stream_exact ? new RegExp(
|
|
896
|
-
const sourceRe = query?.source && !query.source_exact ? new RegExp(
|
|
895
|
+
const streamRe = query?.stream && !query.stream_exact ? new RegExp(query.stream) : void 0;
|
|
896
|
+
const sourceRe = query?.source && !query.source_exact ? new RegExp(query.source) : void 0;
|
|
897
897
|
const sorted = [...this._streams.values()].sort(
|
|
898
898
|
(a, b) => a.stream.localeCompare(b.stream)
|
|
899
899
|
);
|
|
@@ -1387,10 +1387,20 @@ function computeLagLeadRatio(handled, lagging, leading) {
|
|
|
1387
1387
|
}
|
|
1388
1388
|
|
|
1389
1389
|
// src/internal/drain-cycle.ts
|
|
1390
|
-
async function runDrainCycle(ops, registry, batchHandlers, handle, handleBatch, lagging, leading, eventLimit, leaseMillis) {
|
|
1390
|
+
async function runDrainCycle(ops, registry, batchHandlers, handle, handleBatch, lagging, leading, eventLimit, leaseMillis, isDeferred) {
|
|
1391
1391
|
const leased = await ops.claim(lagging, leading, (0, import_node_crypto2.randomUUID)(), leaseMillis);
|
|
1392
1392
|
if (!leased.length) return void 0;
|
|
1393
|
-
const
|
|
1393
|
+
const active = isDeferred ? leased.filter((l) => !isDeferred(l.stream)) : leased;
|
|
1394
|
+
if (!active.length) {
|
|
1395
|
+
return {
|
|
1396
|
+
leased,
|
|
1397
|
+
fetched: [],
|
|
1398
|
+
handled: [],
|
|
1399
|
+
acked: [],
|
|
1400
|
+
blocked: []
|
|
1401
|
+
};
|
|
1402
|
+
}
|
|
1403
|
+
const fetched = await ops.fetch(active, eventLimit);
|
|
1394
1404
|
const fetchMap = /* @__PURE__ */ new Map();
|
|
1395
1405
|
const fetch_window_at = fetched.reduce(
|
|
1396
1406
|
(max, { at, events }) => Math.max(max, events.at(-1)?.id || at),
|
|
@@ -1409,7 +1419,7 @@ async function runDrainCycle(ops, registry, batchHandlers, handle, handleBatch,
|
|
|
1409
1419
|
fetchMap.set(stream, { fetch: f, payloads });
|
|
1410
1420
|
}
|
|
1411
1421
|
const handled = await Promise.all(
|
|
1412
|
-
|
|
1422
|
+
active.map((lease) => {
|
|
1413
1423
|
const entry = fetchMap.get(lease.stream);
|
|
1414
1424
|
const at = entry.fetch.events.at(-1)?.id || fetch_window_at;
|
|
1415
1425
|
const { payloads } = entry;
|
|
@@ -1441,6 +1451,15 @@ var DrainController = class {
|
|
|
1441
1451
|
_armed = false;
|
|
1442
1452
|
_locked = false;
|
|
1443
1453
|
_ratio = 0.5;
|
|
1454
|
+
/**
|
|
1455
|
+
* Per-stream backoff: `stream → nextAttemptAt` (ms since epoch). Set by
|
|
1456
|
+
* `_finalize` via `HandleResult.nextAttemptAt`; cleared on successful
|
|
1457
|
+
* ack or terminal block. Lives in process memory — per-worker pacing
|
|
1458
|
+
* by design (see {@link BackoffOptions} for the multi-worker trade-off).
|
|
1459
|
+
*/
|
|
1460
|
+
_backoff = /* @__PURE__ */ new Map();
|
|
1461
|
+
/** Timer re-arming drain at the earliest pending `nextAttemptAt`. */
|
|
1462
|
+
_backoffTimer;
|
|
1444
1463
|
/**
|
|
1445
1464
|
* Signal that a commit (or reset / cold-start) may have produced work.
|
|
1446
1465
|
* Subsequent `drain()` calls will run the pipeline; once the pipeline
|
|
@@ -1453,6 +1472,32 @@ var DrainController = class {
|
|
|
1453
1472
|
get armed() {
|
|
1454
1473
|
return this._armed;
|
|
1455
1474
|
}
|
|
1475
|
+
/** Returns true when `stream` is currently within a backoff window. */
|
|
1476
|
+
isDeferred = (stream) => {
|
|
1477
|
+
const next = this._backoff.get(stream);
|
|
1478
|
+
return next !== void 0 && next > Date.now();
|
|
1479
|
+
};
|
|
1480
|
+
/**
|
|
1481
|
+
* Schedule the next drain re-arm at the earliest pending backoff
|
|
1482
|
+
* expiry. Called only when the backoff map is non-empty (caller guard).
|
|
1483
|
+
* Idempotent — collapses many simultaneously deferred streams into a
|
|
1484
|
+
* single timer.
|
|
1485
|
+
*/
|
|
1486
|
+
scheduleBackoffWake() {
|
|
1487
|
+
if (this._backoffTimer) clearTimeout(this._backoffTimer);
|
|
1488
|
+
let earliest = Number.POSITIVE_INFINITY;
|
|
1489
|
+
for (const t of this._backoff.values()) if (t < earliest) earliest = t;
|
|
1490
|
+
const delay = Math.max(0, earliest - Date.now());
|
|
1491
|
+
this._backoffTimer = setTimeout(() => {
|
|
1492
|
+
this._backoffTimer = void 0;
|
|
1493
|
+
const now = Date.now();
|
|
1494
|
+
for (const [stream, at] of this._backoff) {
|
|
1495
|
+
if (at <= now) this._backoff.delete(stream);
|
|
1496
|
+
}
|
|
1497
|
+
this._armed = true;
|
|
1498
|
+
}, delay);
|
|
1499
|
+
this._backoffTimer.unref();
|
|
1500
|
+
}
|
|
1456
1501
|
/** Run one drain pass. Short-circuits when not armed or already running. */
|
|
1457
1502
|
async drain({
|
|
1458
1503
|
streamLimit = 10,
|
|
@@ -1474,7 +1519,8 @@ var DrainController = class {
|
|
|
1474
1519
|
lagging,
|
|
1475
1520
|
leading,
|
|
1476
1521
|
eventLimit,
|
|
1477
|
-
leaseMillis
|
|
1522
|
+
leaseMillis,
|
|
1523
|
+
this._backoff.size > 0 ? this.isDeferred : void 0
|
|
1478
1524
|
);
|
|
1479
1525
|
if (!cycle) {
|
|
1480
1526
|
this._armed = false;
|
|
@@ -1482,6 +1528,14 @@ var DrainController = class {
|
|
|
1482
1528
|
}
|
|
1483
1529
|
const { leased, fetched, handled, acked, blocked } = cycle;
|
|
1484
1530
|
this._ratio = computeLagLeadRatio(handled, lagging, leading);
|
|
1531
|
+
for (const lease of acked) this._backoff.delete(lease.stream);
|
|
1532
|
+
for (const lease of blocked) this._backoff.delete(lease.stream);
|
|
1533
|
+
for (const h of handled) {
|
|
1534
|
+
if (h.nextAttemptAt !== void 0 && !h.block) {
|
|
1535
|
+
this._backoff.set(h.lease.stream, h.nextAttemptAt);
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
if (this._backoff.size > 0) this.scheduleBackoffWake();
|
|
1485
1539
|
if (acked.length) this.deps.onAcked(acked);
|
|
1486
1540
|
if (blocked.length) this.deps.onBlocked(blocked);
|
|
1487
1541
|
const hasErrors = handled.some(({ error }) => error);
|
|
@@ -1676,6 +1730,27 @@ var _this_ = ({ stream }) => ({
|
|
|
1676
1730
|
target: stream
|
|
1677
1731
|
});
|
|
1678
1732
|
|
|
1733
|
+
// src/internal/backoff.ts
|
|
1734
|
+
function computeBackoffDelay(retry, opts) {
|
|
1735
|
+
if (!opts || opts.baseMs <= 0) return 0;
|
|
1736
|
+
const r = Math.max(0, retry);
|
|
1737
|
+
let delay;
|
|
1738
|
+
switch (opts.strategy) {
|
|
1739
|
+
case "fixed":
|
|
1740
|
+
delay = opts.baseMs;
|
|
1741
|
+
break;
|
|
1742
|
+
case "linear":
|
|
1743
|
+
delay = opts.baseMs * (r + 1);
|
|
1744
|
+
break;
|
|
1745
|
+
case "exponential":
|
|
1746
|
+
delay = opts.baseMs * 2 ** r;
|
|
1747
|
+
if (opts.maxMs !== void 0) delay = Math.min(delay, opts.maxMs);
|
|
1748
|
+
break;
|
|
1749
|
+
}
|
|
1750
|
+
if (opts.jitter) delay = delay * (0.5 + Math.random());
|
|
1751
|
+
return Math.max(0, Math.floor(delay));
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1679
1754
|
// src/internal/reactions.ts
|
|
1680
1755
|
function finalize(lease, handled, at, error, options, logger) {
|
|
1681
1756
|
if (!error) return { lease, handled, at };
|
|
@@ -1683,12 +1758,14 @@ function finalize(lease, handled, at, error, options, logger) {
|
|
|
1683
1758
|
const block2 = lease.retry >= options.maxRetries && options.blockOnError;
|
|
1684
1759
|
if (block2)
|
|
1685
1760
|
logger.error(`Blocking ${lease.stream} after ${lease.retry} retries.`);
|
|
1761
|
+
const nextAttemptAt = !block2 && options.backoff ? Date.now() + computeBackoffDelay(lease.retry, options.backoff) : void 0;
|
|
1686
1762
|
return {
|
|
1687
1763
|
lease,
|
|
1688
1764
|
handled,
|
|
1689
1765
|
at,
|
|
1690
1766
|
error: handled === 0 ? error.message : void 0,
|
|
1691
|
-
block: block2
|
|
1767
|
+
block: block2,
|
|
1768
|
+
nextAttemptAt
|
|
1692
1769
|
};
|
|
1693
1770
|
}
|
|
1694
1771
|
function buildHandle(deps) {
|
|
@@ -3008,7 +3085,8 @@ function act() {
|
|
|
3008
3085
|
resolver: _this_,
|
|
3009
3086
|
options: {
|
|
3010
3087
|
blockOnError: options?.blockOnError ?? true,
|
|
3011
|
-
maxRetries: options?.maxRetries ?? 3
|
|
3088
|
+
maxRetries: options?.maxRetries ?? 3,
|
|
3089
|
+
backoff: options?.backoff
|
|
3012
3090
|
}
|
|
3013
3091
|
};
|
|
3014
3092
|
if (!handler.name)
|
|
@@ -3134,7 +3212,8 @@ function slice() {
|
|
|
3134
3212
|
resolver: _this_,
|
|
3135
3213
|
options: {
|
|
3136
3214
|
blockOnError: options?.blockOnError ?? true,
|
|
3137
|
-
maxRetries: options?.maxRetries ?? 3
|
|
3215
|
+
maxRetries: options?.maxRetries ?? 3,
|
|
3216
|
+
backoff: options?.backoff
|
|
3138
3217
|
}
|
|
3139
3218
|
};
|
|
3140
3219
|
if (!handler.name)
|