@rotorsoft/act 1.32.20 → 1.32.22
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/act.d.ts +1 -2
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/adapters/in-memory-store.d.ts +2 -6
- package/dist/@types/adapters/in-memory-store.d.ts.map +1 -1
- package/dist/@types/builders/act-builder.d.ts.map +1 -1
- package/dist/@types/internal/audit.d.ts +1 -4
- package/dist/@types/internal/audit.d.ts.map +1 -1
- package/dist/@types/internal/config.d.ts +3 -9
- package/dist/@types/internal/config.d.ts.map +1 -1
- package/dist/@types/internal/correlate-cycle.d.ts +12 -11
- package/dist/@types/internal/correlate-cycle.d.ts.map +1 -1
- package/dist/@types/internal/defer-timer.d.ts +15 -10
- package/dist/@types/internal/defer-timer.d.ts.map +1 -1
- package/dist/@types/internal/drain-cycle.d.ts +6 -5
- package/dist/@types/internal/drain-cycle.d.ts.map +1 -1
- package/dist/@types/internal/index.d.ts +1 -1
- package/dist/@types/internal/index.d.ts.map +1 -1
- package/dist/@types/types/ports.d.ts +10 -10
- package/dist/@types/types/ports.d.ts.map +1 -1
- package/dist/@types/utils.d.ts +3 -2
- package/dist/@types/utils.d.ts.map +1 -1
- package/dist/{chunk-QOSQI3CA.js → chunk-T3CWXVGT.js} +14 -55
- package/dist/chunk-T3CWXVGT.js.map +1 -0
- package/dist/index.cjs +106 -113
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +94 -60
- package/dist/index.js.map +1 -1
- package/dist/test/index.cjs +13 -54
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-QOSQI3CA.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -641,8 +641,7 @@ var InMemoryStream = class {
|
|
|
641
641
|
// in-process backoff, this is durable store state shared across workers.
|
|
642
642
|
_deferred_at = void 0;
|
|
643
643
|
// Work mark: highest event id observed to resolve to this target.
|
|
644
|
-
// `undefined` means
|
|
645
|
-
// back to the legacy has-work probe rather than treating it as "no work".
|
|
644
|
+
// `undefined` means the row was never correlated, so `claim` skips it.
|
|
646
645
|
_correlated_at = void 0;
|
|
647
646
|
constructor(stream, source, priority = 0, lane = DEFAULT_LANE) {
|
|
648
647
|
this.stream = stream;
|
|
@@ -845,12 +844,6 @@ var InMemoryStore = class {
|
|
|
845
844
|
_correlators = /* @__PURE__ */ new Map();
|
|
846
845
|
// last committed version per stream — O(1) replacement for filter-on-commit
|
|
847
846
|
_stream_versions = /* @__PURE__ */ new Map();
|
|
848
|
-
// max non-snapshot event id per stream — drives the has-work probe in
|
|
849
|
-
// claim(): an O(1) lookup for a literal source, and the per-stream max
|
|
850
|
-
// that a pattern source scans, without touching the full event log.
|
|
851
|
-
_max_event_id_by_stream = /* @__PURE__ */ new Map();
|
|
852
|
-
// global max non-snapshot event id — fast pre-check for source-less streams in claim()
|
|
853
|
-
_max_non_snap_event_id = -1;
|
|
854
847
|
// stream → (event_id → cloned sensitive payload). Two-level so `forget_pii`
|
|
855
848
|
// is O(1) — drop the inner Map for the stream and the wipe is done — mirroring
|
|
856
849
|
// the `DELETE WHERE stream = ?` scope that durable adapters get from their
|
|
@@ -862,10 +855,17 @@ var InMemoryStore = class {
|
|
|
862
855
|
this._next_id = 0;
|
|
863
856
|
this._correlated_at = -1;
|
|
864
857
|
this._stream_versions.clear();
|
|
865
|
-
this._max_event_id_by_stream.clear();
|
|
866
|
-
this._max_non_snap_event_id = -1;
|
|
867
858
|
this._pii.clear();
|
|
868
859
|
}
|
|
860
|
+
// Stores a cloned copy, so caller-side mutation can't reach it.
|
|
861
|
+
_put_pii(stream, id, pii) {
|
|
862
|
+
let per_stream = this._pii.get(stream);
|
|
863
|
+
if (!per_stream) {
|
|
864
|
+
per_stream = /* @__PURE__ */ new Map();
|
|
865
|
+
this._pii.set(stream, per_stream);
|
|
866
|
+
}
|
|
867
|
+
per_stream.set(id, structuredClone(pii));
|
|
868
|
+
}
|
|
869
869
|
// First index whose event id is greater than `after`. `_events` stays
|
|
870
870
|
// sorted ascending by id (append-only commits; deletions preserve
|
|
871
871
|
// order), so id-bounded scans binary-search their start instead of
|
|
@@ -985,7 +985,6 @@ var InMemoryStore = class {
|
|
|
985
985
|
);
|
|
986
986
|
}
|
|
987
987
|
let version = current_version + 1;
|
|
988
|
-
let last_non_snap_id = -1;
|
|
989
988
|
const committed = msgs.map(({ name, data, pii }) => {
|
|
990
989
|
const c = {
|
|
991
990
|
id: this._next_id++,
|
|
@@ -997,23 +996,11 @@ var InMemoryStore = class {
|
|
|
997
996
|
meta
|
|
998
997
|
};
|
|
999
998
|
this._events.push(c);
|
|
1000
|
-
if (pii != null)
|
|
1001
|
-
let per_stream = this._pii.get(stream);
|
|
1002
|
-
if (!per_stream) {
|
|
1003
|
-
per_stream = /* @__PURE__ */ new Map();
|
|
1004
|
-
this._pii.set(stream, per_stream);
|
|
1005
|
-
}
|
|
1006
|
-
per_stream.set(c.id, structuredClone(pii));
|
|
1007
|
-
}
|
|
1008
|
-
if (name !== SNAP_EVENT) last_non_snap_id = c.id;
|
|
999
|
+
if (pii != null) this._put_pii(stream, c.id, pii);
|
|
1009
1000
|
version++;
|
|
1010
1001
|
return this._with_pii(c);
|
|
1011
1002
|
});
|
|
1012
1003
|
this._stream_versions.set(stream, version - 1);
|
|
1013
|
-
if (last_non_snap_id >= 0) {
|
|
1014
|
-
this._max_event_id_by_stream.set(stream, last_non_snap_id);
|
|
1015
|
-
this._max_non_snap_event_id = last_non_snap_id;
|
|
1016
|
-
}
|
|
1017
1004
|
return committed;
|
|
1018
1005
|
}
|
|
1019
1006
|
/**
|
|
@@ -1363,10 +1350,7 @@ var InMemoryStore = class {
|
|
|
1363
1350
|
* Scope rules:
|
|
1364
1351
|
* - Array `input` — explicit stream names, regardless of subscription.
|
|
1365
1352
|
* - Filter `input` — `stream`/`stream_exact` match against event-bearing
|
|
1366
|
-
* stream names
|
|
1367
|
-
* corresponding subscription in `_streams` (those are subscription
|
|
1368
|
-
* concepts, not event concepts). Empty filter `{}` matches every
|
|
1369
|
-
* event-bearing stream.
|
|
1353
|
+
* stream names. Empty filter `{}` matches every event-bearing stream.
|
|
1370
1354
|
*/
|
|
1371
1355
|
async query_stats(input, options) {
|
|
1372
1356
|
await sleep();
|
|
@@ -1472,7 +1456,6 @@ var InMemoryStore = class {
|
|
|
1472
1456
|
return false;
|
|
1473
1457
|
});
|
|
1474
1458
|
this._stream_versions.delete(stream);
|
|
1475
|
-
this._max_event_id_by_stream.delete(stream);
|
|
1476
1459
|
this._pii.delete(stream);
|
|
1477
1460
|
const event = {
|
|
1478
1461
|
id: this._next_id++,
|
|
@@ -1485,15 +1468,8 @@ var InMemoryStore = class {
|
|
|
1485
1468
|
};
|
|
1486
1469
|
this._events.push(event);
|
|
1487
1470
|
this._stream_versions.set(stream, 0);
|
|
1488
|
-
if (event.name !== SNAP_EVENT) {
|
|
1489
|
-
this._max_event_id_by_stream.set(stream, event.id);
|
|
1490
|
-
}
|
|
1491
1471
|
result.set(stream, { deleted, committed: event });
|
|
1492
1472
|
}
|
|
1493
|
-
let max = -1;
|
|
1494
|
-
for (const id of this._max_event_id_by_stream.values())
|
|
1495
|
-
if (id > max) max = id;
|
|
1496
|
-
this._max_non_snap_event_id = max;
|
|
1497
1473
|
return result;
|
|
1498
1474
|
}
|
|
1499
1475
|
/**
|
|
@@ -1514,8 +1490,6 @@ var InMemoryStore = class {
|
|
|
1514
1490
|
const prev_next_id = this._next_id;
|
|
1515
1491
|
const prev_streams = this._streams;
|
|
1516
1492
|
const prev_stream_versions = this._stream_versions;
|
|
1517
|
-
const prev_max_event_id_by_stream = this._max_event_id_by_stream;
|
|
1518
|
-
const prev_max_non_snap_event_id = this._max_non_snap_event_id;
|
|
1519
1493
|
const prev_pii = this._pii;
|
|
1520
1494
|
const prev_correlated_at = this._correlated_at;
|
|
1521
1495
|
const prev_correlators = new Map(
|
|
@@ -1527,8 +1501,6 @@ var InMemoryStore = class {
|
|
|
1527
1501
|
this._next_id = 0;
|
|
1528
1502
|
this._streams = /* @__PURE__ */ new Map();
|
|
1529
1503
|
this._stream_versions = /* @__PURE__ */ new Map();
|
|
1530
|
-
this._max_event_id_by_stream = /* @__PURE__ */ new Map();
|
|
1531
|
-
this._max_non_snap_event_id = -1;
|
|
1532
1504
|
this._pii = /* @__PURE__ */ new Map();
|
|
1533
1505
|
try {
|
|
1534
1506
|
await driver(async (event) => {
|
|
@@ -1536,19 +1508,8 @@ var InMemoryStore = class {
|
|
|
1536
1508
|
const { pii, ...rest } = event;
|
|
1537
1509
|
const committed = { ...rest, id };
|
|
1538
1510
|
this._events.push(committed);
|
|
1539
|
-
if (pii != null)
|
|
1540
|
-
let per_stream = this._pii.get(event.stream);
|
|
1541
|
-
if (!per_stream) {
|
|
1542
|
-
per_stream = /* @__PURE__ */ new Map();
|
|
1543
|
-
this._pii.set(event.stream, per_stream);
|
|
1544
|
-
}
|
|
1545
|
-
per_stream.set(id, structuredClone(pii));
|
|
1546
|
-
}
|
|
1511
|
+
if (pii != null) this._put_pii(event.stream, id, pii);
|
|
1547
1512
|
this._stream_versions.set(event.stream, event.version);
|
|
1548
|
-
if (event.name !== SNAP_EVENT) {
|
|
1549
|
-
this._max_event_id_by_stream.set(event.stream, id);
|
|
1550
|
-
this._max_non_snap_event_id = id;
|
|
1551
|
-
}
|
|
1552
1513
|
return id;
|
|
1553
1514
|
});
|
|
1554
1515
|
} catch (err) {
|
|
@@ -1556,8 +1517,6 @@ var InMemoryStore = class {
|
|
|
1556
1517
|
this._next_id = prev_next_id;
|
|
1557
1518
|
this._streams = prev_streams;
|
|
1558
1519
|
this._stream_versions = prev_stream_versions;
|
|
1559
|
-
this._max_event_id_by_stream = prev_max_event_id_by_stream;
|
|
1560
|
-
this._max_non_snap_event_id = prev_max_non_snap_event_id;
|
|
1561
1520
|
this._pii = prev_pii;
|
|
1562
1521
|
this._correlated_at = prev_correlated_at;
|
|
1563
1522
|
this._correlators = prev_correlators;
|
|
@@ -2561,7 +2520,6 @@ function resolveSettleConfig(options) {
|
|
|
2561
2520
|
return options;
|
|
2562
2521
|
}
|
|
2563
2522
|
var MAX_SHUTDOWN_GRACE_MS = 3e4;
|
|
2564
|
-
var DEFAULT_SHUTDOWN_GRACE_MS = DEFAULT_LEASE_MILLIS;
|
|
2565
2523
|
var ShutdownOptionsSchema = import_zod6.z.object({
|
|
2566
2524
|
// `0` is legal and is exactly today's behavior: stop scheduling and
|
|
2567
2525
|
// return without waiting.
|
|
@@ -3196,8 +3154,8 @@ function report_once(seen, key, message) {
|
|
|
3196
3154
|
}
|
|
3197
3155
|
|
|
3198
3156
|
// src/internal/correlate-cycle.ts
|
|
3199
|
-
var
|
|
3200
|
-
var
|
|
3157
|
+
var COLD_START_BACK_SCAN = 1e4;
|
|
3158
|
+
var CORRELATION_LEASE_MS = 5e3;
|
|
3201
3159
|
var registry_key = (events) => {
|
|
3202
3160
|
const shape = Object.keys(events).filter((name) => events[name].reactions.size > 0).sort().map((name) => [name, [...events[name].reactions.keys()].sort()]);
|
|
3203
3161
|
return (0, import_node_crypto.createHash)("sha256").update(JSON.stringify(shape)).digest("hex").slice(0, 32);
|
|
@@ -3226,8 +3184,6 @@ var CorrelateCycle = class {
|
|
|
3226
3184
|
* same holder and a restarted process never inherits a stale claim.
|
|
3227
3185
|
*/
|
|
3228
3186
|
_by = (0, import_node_crypto.randomUUID)();
|
|
3229
|
-
/** How long the correlation lease is taken for. */
|
|
3230
|
-
_lease_millis;
|
|
3231
3187
|
/**
|
|
3232
3188
|
* Which correlator this is. Computed once from the registry so identical
|
|
3233
3189
|
* workers share a lease and unrelated applications never do.
|
|
@@ -3280,12 +3236,6 @@ var CorrelateCycle = class {
|
|
|
3280
3236
|
* here, so an idle deferred stream re-arms across a restart.
|
|
3281
3237
|
*/
|
|
3282
3238
|
_on_init_async;
|
|
3283
|
-
/**
|
|
3284
|
-
* Tail re-scan window applied to the cold-start checkpoint.
|
|
3285
|
-
* See {@link DEFAULT_COLD_START_BACK_SCAN}. Constructor arg (not a
|
|
3286
|
-
* public option) so tests can shrink it; defaults otherwise.
|
|
3287
|
-
*/
|
|
3288
|
-
_cold_start_back_scan;
|
|
3289
3239
|
/** Lanes a controller exists for. See {@link CorrelateCycleDeps}. */
|
|
3290
3240
|
_declared_lanes;
|
|
3291
3241
|
/**
|
|
@@ -3302,11 +3252,8 @@ var CorrelateCycle = class {
|
|
|
3302
3252
|
declared_lanes,
|
|
3303
3253
|
shard,
|
|
3304
3254
|
on_init,
|
|
3305
|
-
on_init_async
|
|
3306
|
-
cold_start_back_scan = DEFAULT_COLD_START_BACK_SCAN,
|
|
3307
|
-
lease_millis = DEFAULT_CORRELATION_LEASE_MS
|
|
3255
|
+
on_init_async
|
|
3308
3256
|
}) {
|
|
3309
|
-
this._lease_millis = lease_millis;
|
|
3310
3257
|
this._key = shard ? `${registry_key(registry.events)}:${[...shard].sort().join(",")}` : registry_key(registry.events);
|
|
3311
3258
|
this._dynamic_subscriptions = new LruMap(max_subscribed_streams);
|
|
3312
3259
|
this._registry = registry;
|
|
@@ -3315,7 +3262,6 @@ var CorrelateCycle = class {
|
|
|
3315
3262
|
this._cd = cd;
|
|
3316
3263
|
this._on_init = on_init;
|
|
3317
3264
|
this._on_init_async = on_init_async;
|
|
3318
|
-
this._cold_start_back_scan = cold_start_back_scan;
|
|
3319
3265
|
}
|
|
3320
3266
|
/** Last correlated event id. */
|
|
3321
3267
|
get checkpoint() {
|
|
@@ -3354,7 +3300,7 @@ var CorrelateCycle = class {
|
|
|
3354
3300
|
const { watermark, correlated_at } = await this._cd.subscribe([
|
|
3355
3301
|
...this._static_targets
|
|
3356
3302
|
]);
|
|
3357
|
-
this._checkpoint = correlated_at >= 0 ? correlated_at : Math.max(-1, watermark -
|
|
3303
|
+
this._checkpoint = correlated_at >= 0 ? correlated_at : Math.max(-1, watermark - COLD_START_BACK_SCAN);
|
|
3358
3304
|
this._on_init?.();
|
|
3359
3305
|
for (const { stream, priority = 0, lane } of this._static_targets) {
|
|
3360
3306
|
this._static_subscriptions.set(stream, {
|
|
@@ -3428,13 +3374,13 @@ var CorrelateCycle = class {
|
|
|
3428
3374
|
scanned: false
|
|
3429
3375
|
};
|
|
3430
3376
|
const now = Date.now();
|
|
3431
|
-
if (lease && now >= this._lease_until -
|
|
3377
|
+
if (lease && now >= this._lease_until - CORRELATION_LEASE_MS / 2) {
|
|
3432
3378
|
const { correlating } = await this._cd.subscribe([], void 0, {
|
|
3433
3379
|
key: this._key,
|
|
3434
3380
|
by: this._by,
|
|
3435
|
-
millis:
|
|
3381
|
+
millis: CORRELATION_LEASE_MS
|
|
3436
3382
|
});
|
|
3437
|
-
this._lease_until = correlating ? now +
|
|
3383
|
+
this._lease_until = correlating ? now + CORRELATION_LEASE_MS : 0;
|
|
3438
3384
|
if (correlating === false)
|
|
3439
3385
|
return {
|
|
3440
3386
|
subscribed: 0,
|
|
@@ -3474,6 +3420,8 @@ var CorrelateCycle = class {
|
|
|
3474
3420
|
priority: carried.priority,
|
|
3475
3421
|
lane: carried.lane,
|
|
3476
3422
|
upgraded,
|
|
3423
|
+
missed: !recorded,
|
|
3424
|
+
handler: reaction.handler.name,
|
|
3477
3425
|
correlated_at: void 0
|
|
3478
3426
|
};
|
|
3479
3427
|
const seen_in_scan = correlated.has(resolved.target);
|
|
@@ -3492,6 +3440,7 @@ var CorrelateCycle = class {
|
|
|
3492
3440
|
entry.priority = carried.priority;
|
|
3493
3441
|
entry.lane = carried.lane;
|
|
3494
3442
|
entry.upgraded = upgraded;
|
|
3443
|
+
entry.handler = reaction.handler.name;
|
|
3495
3444
|
}
|
|
3496
3445
|
if (this._in_fetch_window(resolved.source, event.stream))
|
|
3497
3446
|
entry.correlated_at = event.id;
|
|
@@ -3505,6 +3454,7 @@ var CorrelateCycle = class {
|
|
|
3505
3454
|
// actor-less, so plaintext here would be un-gated disclosure.
|
|
3506
3455
|
{ ...query, after, with_pii: false }
|
|
3507
3456
|
);
|
|
3457
|
+
if (this._declared_lanes.size > 1) await this._recall(correlated);
|
|
3508
3458
|
const streams = [];
|
|
3509
3459
|
for (const [stream, entry] of correlated) {
|
|
3510
3460
|
if (entry.upgraded || entry.correlated_at !== void 0)
|
|
@@ -3521,10 +3471,10 @@ var CorrelateCycle = class {
|
|
|
3521
3471
|
const { subscribed, correlating } = await this._cd.subscribe(
|
|
3522
3472
|
streams,
|
|
3523
3473
|
last_id,
|
|
3524
|
-
lease ? { key: this._key, by: this._by, millis:
|
|
3474
|
+
lease ? { key: this._key, by: this._by, millis: CORRELATION_LEASE_MS } : void 0
|
|
3525
3475
|
);
|
|
3526
3476
|
if (lease && correlating !== false)
|
|
3527
|
-
this._lease_until = renewed_at +
|
|
3477
|
+
this._lease_until = renewed_at + CORRELATION_LEASE_MS;
|
|
3528
3478
|
const marked = streams.filter(
|
|
3529
3479
|
(entry) => entry.correlated_at !== void 0
|
|
3530
3480
|
).length;
|
|
@@ -3543,6 +3493,50 @@ var CorrelateCycle = class {
|
|
|
3543
3493
|
this._armed = found === query.limit;
|
|
3544
3494
|
return { subscribed: 0, last_id, marked: 0, scanned: true };
|
|
3545
3495
|
}
|
|
3496
|
+
/**
|
|
3497
|
+
* Give each target this scan had no record of the record its row holds.
|
|
3498
|
+
*
|
|
3499
|
+
* A target with no record is either new or forgotten. Its row, when it
|
|
3500
|
+
* has one, says what lane and priority it was subscribed at; a resolution
|
|
3501
|
+
* that doesn't beat that priority carries the row's values, exactly as if
|
|
3502
|
+
* the record had never been evicted. One read per target with no record,
|
|
3503
|
+
* and only when more than one lane is declared: with one lane there is no
|
|
3504
|
+
* lane to lose, and the store already keeps the higher priority.
|
|
3505
|
+
*/
|
|
3506
|
+
async _recall(correlated) {
|
|
3507
|
+
const missed = [...correlated].filter(([, entry]) => entry.missed);
|
|
3508
|
+
await Promise.all(
|
|
3509
|
+
missed.map(async ([stream, entry]) => {
|
|
3510
|
+
let row;
|
|
3511
|
+
await store2().query_streams(
|
|
3512
|
+
(position) => {
|
|
3513
|
+
row = position;
|
|
3514
|
+
},
|
|
3515
|
+
{ stream, stream_exact: true, limit: 1 }
|
|
3516
|
+
);
|
|
3517
|
+
if (!row) return;
|
|
3518
|
+
this._dynamic_subscriptions.set(stream, {
|
|
3519
|
+
floor: row.priority,
|
|
3520
|
+
priority: row.priority,
|
|
3521
|
+
lane: row.lane
|
|
3522
|
+
});
|
|
3523
|
+
if (entry.priority > row.priority) return;
|
|
3524
|
+
const held_lane = row.lane ?? DEFAULT_LANE;
|
|
3525
|
+
const resolved_lane = entry.lane ?? DEFAULT_LANE;
|
|
3526
|
+
if (entry.priority === row.priority && held_lane !== resolved_lane)
|
|
3527
|
+
report_lane_conflict(
|
|
3528
|
+
this._reported,
|
|
3529
|
+
entry.handler,
|
|
3530
|
+
stream,
|
|
3531
|
+
held_lane,
|
|
3532
|
+
resolved_lane
|
|
3533
|
+
);
|
|
3534
|
+
entry.priority = row.priority;
|
|
3535
|
+
entry.lane = row.lane;
|
|
3536
|
+
entry.upgraded = false;
|
|
3537
|
+
})
|
|
3538
|
+
);
|
|
3539
|
+
}
|
|
3546
3540
|
/**
|
|
3547
3541
|
* Hand the correlation lease back early.
|
|
3548
3542
|
*
|
|
@@ -3560,7 +3554,7 @@ var CorrelateCycle = class {
|
|
|
3560
3554
|
});
|
|
3561
3555
|
} catch (error) {
|
|
3562
3556
|
log().warn(
|
|
3563
|
-
`Could not hand back the correlation lease during shutdown; it expires on its own within ${
|
|
3557
|
+
`Could not hand back the correlation lease during shutdown; it expires on its own within ${CORRELATION_LEASE_MS}ms and correlation resumes normally after that. On a single-writer store this usually means another connection holds the database. Cause: ${String(error)}`
|
|
3564
3558
|
);
|
|
3565
3559
|
}
|
|
3566
3560
|
}
|
|
@@ -3702,9 +3696,8 @@ var DeferTimer = class {
|
|
|
3702
3696
|
_timer;
|
|
3703
3697
|
_on_wake;
|
|
3704
3698
|
/**
|
|
3705
|
-
* @param on_wake - invoked once each time the earliest due-time elapses
|
|
3706
|
-
*
|
|
3707
|
-
* for whatever is still parked.
|
|
3699
|
+
* @param on_wake - invoked once each time the earliest due-time elapses.
|
|
3700
|
+
* The consumer re-schedules for whatever is still parked.
|
|
3708
3701
|
*/
|
|
3709
3702
|
constructor(on_wake) {
|
|
3710
3703
|
this._on_wake = on_wake;
|
|
@@ -3713,14 +3706,6 @@ var DeferTimer = class {
|
|
|
3713
3706
|
get size() {
|
|
3714
3707
|
return this._due.size;
|
|
3715
3708
|
}
|
|
3716
|
-
/**
|
|
3717
|
-
* True while `stream` is parked with a due-time still in the future.
|
|
3718
|
-
* Consumers skip work for deferred streams until their window elapses.
|
|
3719
|
-
*/
|
|
3720
|
-
is_deferred = (stream) => {
|
|
3721
|
-
const next = this._due.get(stream);
|
|
3722
|
-
return next !== void 0 && next > Date.now();
|
|
3723
|
-
};
|
|
3724
3709
|
/**
|
|
3725
3710
|
* Park `stream` for a re-visit at `at` (ms since epoch). A plain
|
|
3726
3711
|
* overwrite: the caller computes the authoritative next-visit for the
|
|
@@ -3734,6 +3719,16 @@ var DeferTimer = class {
|
|
|
3734
3719
|
delete(stream) {
|
|
3735
3720
|
this._due.delete(stream);
|
|
3736
3721
|
}
|
|
3722
|
+
/**
|
|
3723
|
+
* Drop every entry due at or before `before`. The drain calls it when a
|
|
3724
|
+
* pass that started at `before` claimed nothing: whatever was due by then
|
|
3725
|
+
* was handled elsewhere (another worker) or has no work left, so waking
|
|
3726
|
+
* for it again would spin.
|
|
3727
|
+
*/
|
|
3728
|
+
prune(before) {
|
|
3729
|
+
for (const [stream, at] of this._due)
|
|
3730
|
+
if (at <= before) this._due.delete(stream);
|
|
3731
|
+
}
|
|
3737
3732
|
/**
|
|
3738
3733
|
* (Re)schedule the wake timer at the earliest pending due-time. Idempotent
|
|
3739
3734
|
* — collapses many parked streams into a single timer. A no-op clears any
|
|
@@ -3756,9 +3751,6 @@ var DeferTimer = class {
|
|
|
3756
3751
|
);
|
|
3757
3752
|
this._timer = setTimeout(() => {
|
|
3758
3753
|
this._timer = void 0;
|
|
3759
|
-
const now = Date.now();
|
|
3760
|
-
for (const [stream, at] of this._due)
|
|
3761
|
-
if (at <= now) this._due.delete(stream);
|
|
3762
3754
|
this._on_wake();
|
|
3763
3755
|
}, delay);
|
|
3764
3756
|
this._timer.unref();
|
|
@@ -4396,7 +4388,7 @@ function trace_cycle(logger, leased, fetched, handled, acked, blocked) {
|
|
|
4396
4388
|
function budget_exhausted(lease, options, store_failing) {
|
|
4397
4389
|
if (store_failing || !options?.blockOnError || lease.retry <= options.maxRetries)
|
|
4398
4390
|
return void 0;
|
|
4399
|
-
const error = `Blocking ${lease.stream} after ${lease.retry} claims with no acknowledged progress \u2014 the retry budget (${options.maxRetries}) was spent without the handler ever reporting an error. That means every attempt lost its lease before it could ack:
|
|
4391
|
+
const error = `Blocking ${lease.stream} after ${lease.retry} claims with no acknowledged progress \u2014 the retry budget (${options.maxRetries}) was spent without the handler ever reporting an error. That means every attempt lost its lease before it could ack: move the reaction to a lane whose leaseMillis covers the handler, then unblock the stream.`;
|
|
4400
4392
|
log().error(error);
|
|
4401
4393
|
return { lease, handled: 0, acked_at: lease.at, error, block: true };
|
|
4402
4394
|
}
|
|
@@ -4476,7 +4468,9 @@ async function run_drain_cycle(deps, cycle) {
|
|
|
4476
4468
|
handled: 0,
|
|
4477
4469
|
acked_at: lease.at,
|
|
4478
4470
|
error,
|
|
4479
|
-
...block2 ? { block: true } : {
|
|
4471
|
+
...block2 ? { block: true } : {
|
|
4472
|
+
next_attempt_at: Date.now() + compute_backoff_delay(lease.retry, DEFAULT_BACKOFF)
|
|
4473
|
+
}
|
|
4480
4474
|
};
|
|
4481
4475
|
return Promise.resolve(failed);
|
|
4482
4476
|
}
|
|
@@ -4527,6 +4521,9 @@ var DrainController = class {
|
|
|
4527
4521
|
* schedule itself is persisted in the store; this runs the drain at the
|
|
4528
4522
|
* earliest pending visit. A parked stream already carries its mark, so
|
|
4529
4523
|
* the drain alone finds it, and on an idle app nothing else would.
|
|
4524
|
+
* Every drain re-aims it when it ends, and a due entry stays until a pass
|
|
4525
|
+
* handles it or a pass that started after it came due claims nothing, so
|
|
4526
|
+
* the wake keeps draining until every due stream is reached.
|
|
4530
4527
|
* `stop()` cancels the timer and a stopped drain never re-schedules it,
|
|
4531
4528
|
* so a wake never runs after a stop.
|
|
4532
4529
|
*/
|
|
@@ -4622,17 +4619,18 @@ var DrainController = class {
|
|
|
4622
4619
|
if (this._locked) return EMPTY_DRAIN;
|
|
4623
4620
|
const d = this._deps.defaults ?? {};
|
|
4624
4621
|
const streamLimit = d.streamLimit ?? options.streamLimit ?? DEFAULT_STREAM_LIMIT;
|
|
4625
|
-
const eventLimit =
|
|
4622
|
+
const eventLimit = options.eventLimit ?? DEFAULT_EVENT_LIMIT;
|
|
4626
4623
|
const leaseMillis = d.leaseMillis ?? options.leaseMillis ?? DEFAULT_LEASE_MILLIS;
|
|
4624
|
+
if (this._deps.breaker.state(Date.now()) === "open")
|
|
4625
|
+
return EMPTY_DRAIN;
|
|
4627
4626
|
try {
|
|
4628
|
-
if (this._deps.breaker.state(Date.now()) === "open")
|
|
4629
|
-
return EMPTY_DRAIN;
|
|
4630
4627
|
this._locked = true;
|
|
4631
4628
|
this._inflight = new Promise((done) => {
|
|
4632
4629
|
this._inflight_done = done;
|
|
4633
4630
|
});
|
|
4634
4631
|
const lagging = Math.ceil(streamLimit * this._ratio);
|
|
4635
4632
|
const leading = streamLimit - lagging;
|
|
4633
|
+
const started = Date.now();
|
|
4636
4634
|
const cycle = await run_drain_cycle(this._deps, {
|
|
4637
4635
|
lagging,
|
|
4638
4636
|
leading,
|
|
@@ -4642,6 +4640,7 @@ var DrainController = class {
|
|
|
4642
4640
|
store_failing: this._deps.breaker.failing
|
|
4643
4641
|
});
|
|
4644
4642
|
if (!cycle) {
|
|
4643
|
+
this._defer.prune(started);
|
|
4645
4644
|
this._deps.breaker.passed();
|
|
4646
4645
|
this._armed = false;
|
|
4647
4646
|
return EMPTY_DRAIN;
|
|
@@ -5284,9 +5283,7 @@ var Act = class {
|
|
|
5284
5283
|
_build_audit_deps() {
|
|
5285
5284
|
return {
|
|
5286
5285
|
store: store2,
|
|
5287
|
-
logger: this._logger,
|
|
5288
5286
|
event_to_state: this._event_to_state,
|
|
5289
|
-
states: this._states,
|
|
5290
5287
|
// Declared lanes, not this process's controllers: a lane another
|
|
5291
5288
|
// worker drains is not `unknown-lane`.
|
|
5292
5289
|
declared_lanes: new Set(this._declared_lanes),
|
|
@@ -5393,9 +5390,9 @@ var Act = class {
|
|
|
5393
5390
|
* fallback, capped at the maximum. Idle lanes don't count.
|
|
5394
5391
|
*/
|
|
5395
5392
|
_derive_grace_ms(running, settling) {
|
|
5396
|
-
let max = settling ?
|
|
5393
|
+
let max = settling ? DEFAULT_LEASE_MILLIS : 0;
|
|
5397
5394
|
for (const c of running)
|
|
5398
|
-
max = Math.max(max, c.lease_millis ||
|
|
5395
|
+
max = Math.max(max, c.lease_millis || DEFAULT_LEASE_MILLIS);
|
|
5399
5396
|
return Math.min(max, MAX_SHUTDOWN_GRACE_MS);
|
|
5400
5397
|
}
|
|
5401
5398
|
async _await_inflight(grace_ms) {
|
|
@@ -5705,8 +5702,7 @@ var Act = class {
|
|
|
5705
5702
|
* Call `correlate()` before `drain()`. It is not only how dynamic targets
|
|
5706
5703
|
* are discovered: a stream is claimable while `at < correlated_at`, and
|
|
5707
5704
|
* `correlate` is the only component that raises that mark, so a
|
|
5708
|
-
* commit no correlate has seen is not drainable
|
|
5709
|
-
* targets, which were served by a probe of the event log before. For a
|
|
5705
|
+
* commit no correlate has seen is not drainable, static targets included. For a
|
|
5710
5706
|
* higher-level API that handles debouncing, correlation, and signaling
|
|
5711
5707
|
* automatically, use {@link settle}.
|
|
5712
5708
|
*
|
|
@@ -6712,6 +6708,12 @@ function act() {
|
|
|
6712
6708
|
};
|
|
6713
6709
|
const fold_projections = [];
|
|
6714
6710
|
const batch_handlers = /* @__PURE__ */ new Map();
|
|
6711
|
+
const add_projection = (proj) => {
|
|
6712
|
+
record_projection_reactions(proj);
|
|
6713
|
+
merge_projection(proj, registry.events);
|
|
6714
|
+
register_batch_handler(proj, batch_handlers);
|
|
6715
|
+
if (proj.fold) fold_projections.push(proj);
|
|
6716
|
+
};
|
|
6715
6717
|
const fold_specs = [];
|
|
6716
6718
|
const lanes = [];
|
|
6717
6719
|
let _built = false;
|
|
@@ -6811,11 +6813,7 @@ function act() {
|
|
|
6811
6813
|
},
|
|
6812
6814
|
withProjection: (proj) => {
|
|
6813
6815
|
if (_built) closed("withProjection");
|
|
6814
|
-
|
|
6815
|
-
merge_projection(proj, registry.events);
|
|
6816
|
-
register_batch_handler(proj, batch_handlers);
|
|
6817
|
-
if (proj.fold)
|
|
6818
|
-
fold_projections.push(proj);
|
|
6816
|
+
add_projection(proj);
|
|
6819
6817
|
return builder;
|
|
6820
6818
|
},
|
|
6821
6819
|
withActor: () => builder,
|
|
@@ -6832,12 +6830,7 @@ function act() {
|
|
|
6832
6830
|
resolveActConfig(options);
|
|
6833
6831
|
const patch_fn = options?.validateFoldedState === true ? validating_patch : bare_patch;
|
|
6834
6832
|
if (!_built) {
|
|
6835
|
-
for (const proj of pending_projections)
|
|
6836
|
-
record_projection_reactions(proj);
|
|
6837
|
-
merge_projection(proj, registry.events);
|
|
6838
|
-
register_batch_handler(proj, batch_handlers);
|
|
6839
|
-
if (proj.fold) fold_projections.push(proj);
|
|
6840
|
-
}
|
|
6833
|
+
for (const proj of pending_projections) add_projection(proj);
|
|
6841
6834
|
for (const proj of fold_projections) {
|
|
6842
6835
|
const fold = proj.fold;
|
|
6843
6836
|
const merged = states.get(fold.name);
|