@rotorsoft/act 1.22.8 → 1.23.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/act.d.ts +33 -2
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/builders/act-builder.d.ts.map +1 -1
- package/dist/@types/internal/correlate-cycle.d.ts +28 -2
- package/dist/@types/internal/correlate-cycle.d.ts.map +1 -1
- package/dist/@types/internal/event-sourcing.d.ts +59 -2
- package/dist/@types/internal/event-sourcing.d.ts.map +1 -1
- package/dist/@types/internal/index.d.ts +3 -3
- package/dist/@types/internal/index.d.ts.map +1 -1
- package/dist/@types/internal/projection-fold.d.ts +46 -0
- package/dist/@types/internal/projection-fold.d.ts.map +1 -0
- package/dist/@types/internal/settle.d.ts +10 -0
- package/dist/@types/internal/settle.d.ts.map +1 -1
- package/dist/@types/internal/tracing.d.ts +2 -2
- package/dist/@types/internal/tracing.d.ts.map +1 -1
- package/dist/@types/types/action.d.ts +13 -0
- package/dist/@types/types/action.d.ts.map +1 -1
- package/dist/index.cjs +184 -112
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +184 -112
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/@types/internal/state-fold.d.ts +0 -18
- package/dist/@types/internal/state-fold.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1396,6 +1396,7 @@ async function truncate_and_warm_cache(guarded, seed_states, guard_events, corre
|
|
|
1396
1396
|
}
|
|
1397
1397
|
|
|
1398
1398
|
// src/internal/correlate-cycle.ts
|
|
1399
|
+
var DEFAULT_COLD_START_BACK_SCAN = 1e4;
|
|
1399
1400
|
var CorrelateCycle = class {
|
|
1400
1401
|
_checkpoint = -1;
|
|
1401
1402
|
_initialized = false;
|
|
@@ -1424,8 +1425,24 @@ var CorrelateCycle = class {
|
|
|
1424
1425
|
* `_scoped` (identity for a non-scoped Act), so it's required.
|
|
1425
1426
|
*/
|
|
1426
1427
|
_run_scoped;
|
|
1427
|
-
|
|
1428
|
-
|
|
1428
|
+
/**
|
|
1429
|
+
* Tail re-scan window applied to the cold-start checkpoint (ACT-1207).
|
|
1430
|
+
* See {@link DEFAULT_COLD_START_BACK_SCAN}. Constructor arg (not a
|
|
1431
|
+
* public option) so tests can shrink it; defaults otherwise.
|
|
1432
|
+
*/
|
|
1433
|
+
_cold_start_back_scan;
|
|
1434
|
+
constructor({
|
|
1435
|
+
registry,
|
|
1436
|
+
static_targets,
|
|
1437
|
+
has_dynamic_resolvers,
|
|
1438
|
+
cd,
|
|
1439
|
+
max_subscribed_streams,
|
|
1440
|
+
run_scoped,
|
|
1441
|
+
on_init,
|
|
1442
|
+
on_init_async,
|
|
1443
|
+
cold_start_back_scan = DEFAULT_COLD_START_BACK_SCAN
|
|
1444
|
+
}) {
|
|
1445
|
+
this._subscribed = new LruSet(max_subscribed_streams);
|
|
1429
1446
|
this._registry = registry;
|
|
1430
1447
|
this._static_targets = static_targets;
|
|
1431
1448
|
this._has_dynamic_resolvers = has_dynamic_resolvers;
|
|
@@ -1433,6 +1450,7 @@ var CorrelateCycle = class {
|
|
|
1433
1450
|
this._on_init = on_init;
|
|
1434
1451
|
this._run_scoped = run_scoped;
|
|
1435
1452
|
this._on_init_async = on_init_async;
|
|
1453
|
+
this._cold_start_back_scan = cold_start_back_scan;
|
|
1436
1454
|
}
|
|
1437
1455
|
/** Last correlated event id. */
|
|
1438
1456
|
get checkpoint() {
|
|
@@ -1440,7 +1458,10 @@ var CorrelateCycle = class {
|
|
|
1440
1458
|
}
|
|
1441
1459
|
/**
|
|
1442
1460
|
* Initialize correlation state on first call.
|
|
1443
|
-
* - Reads max(at) from store
|
|
1461
|
+
* - Reads max(at) from store, then floors the cold-start checkpoint at
|
|
1462
|
+
* `watermark - back_scan` when dynamic resolvers exist, so an event
|
|
1463
|
+
* committed-but-not-correlated before a crash is re-scanned on
|
|
1464
|
+
* restart instead of skipped (ACT-1207)
|
|
1444
1465
|
* - Subscribes static resolver targets (idempotent upsert)
|
|
1445
1466
|
* - Populates the subscribed-streams LRU
|
|
1446
1467
|
* - Fires `on_init` once (Act uses this to flag a cold-start drain)
|
|
@@ -1449,7 +1470,7 @@ var CorrelateCycle = class {
|
|
|
1449
1470
|
if (this._initialized) return;
|
|
1450
1471
|
this._initialized = true;
|
|
1451
1472
|
const { watermark } = await store().subscribe([...this._static_targets]);
|
|
1452
|
-
this._checkpoint = watermark;
|
|
1473
|
+
this._checkpoint = this._has_dynamic_resolvers ? Math.max(-1, watermark - this._cold_start_back_scan) : watermark;
|
|
1453
1474
|
this._on_init?.();
|
|
1454
1475
|
for (const { stream } of this._static_targets) {
|
|
1455
1476
|
this._subscribed.add(stream);
|
|
@@ -1715,6 +1736,15 @@ function compute_backoff_delay(retry, opts) {
|
|
|
1715
1736
|
}
|
|
1716
1737
|
|
|
1717
1738
|
// src/internal/event-sourcing.ts
|
|
1739
|
+
var bare_patch = (_me, state2, partial) => patch(state2, partial);
|
|
1740
|
+
var validating_patch = (me, state2, partial, event) => {
|
|
1741
|
+
const next = patch(state2, partial);
|
|
1742
|
+
return validate(
|
|
1743
|
+
`${me.name}.${String(event.name)}#${event.id}`,
|
|
1744
|
+
next,
|
|
1745
|
+
me.state
|
|
1746
|
+
);
|
|
1747
|
+
};
|
|
1718
1748
|
var DEFAULT_BATCH = 500;
|
|
1719
1749
|
async function snap(snapshot) {
|
|
1720
1750
|
const { id, stream, name, meta, version } = snapshot.event;
|
|
@@ -1869,7 +1899,7 @@ async function scan(source, opts = {}, callback) {
|
|
|
1869
1899
|
}
|
|
1870
1900
|
};
|
|
1871
1901
|
}
|
|
1872
|
-
async function load(me, target, callback) {
|
|
1902
|
+
async function load(me, target, callback, patch_fn = bare_patch) {
|
|
1873
1903
|
const { stream, actor, asOf } = target;
|
|
1874
1904
|
const time_travel = !!asOf && Object.values(asOf).some((v) => v !== void 0);
|
|
1875
1905
|
const cached = time_travel ? void 0 : await cache().get(stream);
|
|
@@ -1888,7 +1918,7 @@ async function load(me, target, callback) {
|
|
|
1888
1918
|
patches = 0;
|
|
1889
1919
|
replayed++;
|
|
1890
1920
|
} else if (me.patch[event.name]) {
|
|
1891
|
-
state2 =
|
|
1921
|
+
state2 = patch_fn(me, state2, me.patch[event.name](event, state2), event);
|
|
1892
1922
|
patches++;
|
|
1893
1923
|
replayed++;
|
|
1894
1924
|
} else if (event.name !== TOMBSTONE_EVENT) {
|
|
@@ -1900,6 +1930,7 @@ async function load(me, target, callback) {
|
|
|
1900
1930
|
event,
|
|
1901
1931
|
state: state2,
|
|
1902
1932
|
version: event.version,
|
|
1933
|
+
id: event.id,
|
|
1903
1934
|
patches,
|
|
1904
1935
|
snaps,
|
|
1905
1936
|
cache_hit,
|
|
@@ -1921,19 +1952,24 @@ async function load(me, target, callback) {
|
|
|
1921
1952
|
event_id: event.id,
|
|
1922
1953
|
patches,
|
|
1923
1954
|
snaps
|
|
1924
|
-
});
|
|
1955
|
+
}).catch((err) => log().error(err));
|
|
1925
1956
|
}
|
|
1926
1957
|
return {
|
|
1927
1958
|
event,
|
|
1928
1959
|
state: state2,
|
|
1929
1960
|
version: event?.version ?? cached?.version ?? -1,
|
|
1961
|
+
// Head event id, captured atomically with `state`: the last replayed
|
|
1962
|
+
// event's id on a cache miss, or the cached checkpoint's event_id on a
|
|
1963
|
+
// warm hit with no new events. Consumers read this instead of a
|
|
1964
|
+
// separate cache lookup that could race a concurrent commit (ACT-1204).
|
|
1965
|
+
id: event?.id ?? cached?.event_id ?? -1,
|
|
1930
1966
|
patches,
|
|
1931
1967
|
snaps,
|
|
1932
1968
|
cache_hit,
|
|
1933
1969
|
replayed
|
|
1934
1970
|
};
|
|
1935
1971
|
}
|
|
1936
|
-
async function action(me, action2, target, payload, options) {
|
|
1972
|
+
async function action(me, action2, target, payload, options, patch_fn = bare_patch) {
|
|
1937
1973
|
const { stream, expectedVersion, actor } = target;
|
|
1938
1974
|
if (!stream) throw new Error("Missing target stream");
|
|
1939
1975
|
const reactingTo = options?.reactingTo;
|
|
@@ -1943,7 +1979,12 @@ async function action(me, action2, target, payload, options) {
|
|
|
1943
1979
|
const max_retries = opts?.maxRetries ?? 0;
|
|
1944
1980
|
for (let attempt = 0; ; attempt++) {
|
|
1945
1981
|
try {
|
|
1946
|
-
const snapshot = await load(
|
|
1982
|
+
const snapshot = await load(
|
|
1983
|
+
me,
|
|
1984
|
+
{ stream, actor: target.actor },
|
|
1985
|
+
void 0,
|
|
1986
|
+
patch_fn
|
|
1987
|
+
);
|
|
1947
1988
|
if (snapshot.event?.name === TOMBSTONE_EVENT)
|
|
1948
1989
|
throw new StreamClosedError(stream);
|
|
1949
1990
|
const expected = expectedVersion ?? (snapshot.version >= 0 ? snapshot.version : void 0);
|
|
@@ -2014,12 +2055,13 @@ async function action(me, action2, target, payload, options) {
|
|
|
2014
2055
|
const snapshots = committed.map((row, i) => {
|
|
2015
2056
|
const event = { ...row, data: valid[i].data };
|
|
2016
2057
|
const p = me.patch[event.name](event, state2);
|
|
2017
|
-
state2 =
|
|
2058
|
+
state2 = patch_fn(me, state2, p, event);
|
|
2018
2059
|
patches++;
|
|
2019
2060
|
return {
|
|
2020
2061
|
event,
|
|
2021
2062
|
state: state2,
|
|
2022
2063
|
version: event.version,
|
|
2064
|
+
id: event.id,
|
|
2023
2065
|
patches,
|
|
2024
2066
|
snaps: snapshot.snaps,
|
|
2025
2067
|
patch: p,
|
|
@@ -2047,6 +2089,7 @@ async function action(me, action2, target, payload, options) {
|
|
|
2047
2089
|
return snapshots;
|
|
2048
2090
|
} catch (error) {
|
|
2049
2091
|
if (!(error instanceof ConcurrencyError)) throw error;
|
|
2092
|
+
if (expectedVersion !== void 0) throw error;
|
|
2050
2093
|
if (attempt >= max_retries) throw error;
|
|
2051
2094
|
if (opts?.backoff) {
|
|
2052
2095
|
const delay_ms = compute_backoff_delay(attempt, opts.backoff);
|
|
@@ -2108,12 +2151,20 @@ var traced = (inner, exit, entry) => (async (...args) => {
|
|
|
2108
2151
|
exit?.(result, ...args);
|
|
2109
2152
|
return result;
|
|
2110
2153
|
});
|
|
2111
|
-
function build_es(logger, correlator = default_correlator) {
|
|
2112
|
-
const bound_action = (me, action_name, target, payload, options) => action(
|
|
2154
|
+
function build_es(logger, correlator = default_correlator, patch_fn = bare_patch) {
|
|
2155
|
+
const bound_action = (me, action_name, target, payload, options) => action(
|
|
2156
|
+
me,
|
|
2157
|
+
action_name,
|
|
2158
|
+
target,
|
|
2159
|
+
payload,
|
|
2160
|
+
{ correlator, ...options },
|
|
2161
|
+
patch_fn
|
|
2162
|
+
);
|
|
2163
|
+
const bound_load = (me, target, callback) => load(me, target, callback, patch_fn);
|
|
2113
2164
|
if (logger.level !== "trace") {
|
|
2114
2165
|
return {
|
|
2115
2166
|
snap,
|
|
2116
|
-
load,
|
|
2167
|
+
load: bound_load,
|
|
2117
2168
|
action: bound_action,
|
|
2118
2169
|
tombstone
|
|
2119
2170
|
};
|
|
@@ -2128,7 +2179,7 @@ function build_es(logger, correlator = default_correlator) {
|
|
|
2128
2179
|
)
|
|
2129
2180
|
);
|
|
2130
2181
|
}),
|
|
2131
|
-
load: traced(
|
|
2182
|
+
load: traced(bound_load, (result, _me, target) => {
|
|
2132
2183
|
const stats = stats_marker(
|
|
2133
2184
|
result.version,
|
|
2134
2185
|
result.replayed,
|
|
@@ -2439,6 +2490,83 @@ var DrainController = class {
|
|
|
2439
2490
|
}
|
|
2440
2491
|
};
|
|
2441
2492
|
|
|
2493
|
+
// src/internal/projection-fold.ts
|
|
2494
|
+
import { z as z5 } from "zod";
|
|
2495
|
+
var DEFAULT_FOLD_FLUSH_EVERY = 1e3;
|
|
2496
|
+
var DEFAULT_MAX_CACHED_STATES = 1e4;
|
|
2497
|
+
var FoldOptionsSchema = z5.object({
|
|
2498
|
+
flushEvery: z5.number().int().min(1).default(DEFAULT_FOLD_FLUSH_EVERY),
|
|
2499
|
+
maxCachedStates: z5.number().int().min(1).default(DEFAULT_MAX_CACHED_STATES)
|
|
2500
|
+
});
|
|
2501
|
+
function resolveFoldConfig(options) {
|
|
2502
|
+
return FoldOptionsSchema.parse(options);
|
|
2503
|
+
}
|
|
2504
|
+
function make_fold_handler(me, flush, config2, patch_fn = bare_patch) {
|
|
2505
|
+
const cache2 = /* @__PURE__ */ new Map();
|
|
2506
|
+
const row = ({ dirty: _, ...row2 }) => row2;
|
|
2507
|
+
const flush_dirty = async () => {
|
|
2508
|
+
const rows = [];
|
|
2509
|
+
const flushed = [];
|
|
2510
|
+
for (const f of cache2.values())
|
|
2511
|
+
if (f.dirty) {
|
|
2512
|
+
rows.push(row(f));
|
|
2513
|
+
flushed.push(f);
|
|
2514
|
+
}
|
|
2515
|
+
if (rows.length === 0) return;
|
|
2516
|
+
await flush(rows);
|
|
2517
|
+
for (const f of flushed) f.dirty = false;
|
|
2518
|
+
};
|
|
2519
|
+
return async (events) => {
|
|
2520
|
+
let folded_since_flush = 0;
|
|
2521
|
+
for (const event of events) {
|
|
2522
|
+
const stream = event.stream;
|
|
2523
|
+
let fold = cache2.get(stream);
|
|
2524
|
+
if (fold) {
|
|
2525
|
+
cache2.delete(stream);
|
|
2526
|
+
cache2.set(stream, fold);
|
|
2527
|
+
} else {
|
|
2528
|
+
if (cache2.size >= config2.maxCachedStates) {
|
|
2529
|
+
const oldest = cache2.keys().next().value;
|
|
2530
|
+
const evictee = cache2.get(oldest);
|
|
2531
|
+
if (evictee.dirty) await flush([row(evictee)]);
|
|
2532
|
+
cache2.delete(oldest);
|
|
2533
|
+
}
|
|
2534
|
+
const snapshot = await load(me, { stream }, void 0, patch_fn);
|
|
2535
|
+
fold = {
|
|
2536
|
+
stream,
|
|
2537
|
+
state: snapshot.state,
|
|
2538
|
+
version: snapshot.version,
|
|
2539
|
+
event_id: snapshot.id,
|
|
2540
|
+
patches: snapshot.patches,
|
|
2541
|
+
snaps: snapshot.snaps,
|
|
2542
|
+
dirty: true
|
|
2543
|
+
};
|
|
2544
|
+
cache2.set(stream, fold);
|
|
2545
|
+
}
|
|
2546
|
+
if (event.id > fold.event_id) {
|
|
2547
|
+
const reducer = me.patch[event.name];
|
|
2548
|
+
fold.state = patch_fn(
|
|
2549
|
+
me,
|
|
2550
|
+
fold.state,
|
|
2551
|
+
reducer(event, fold.state),
|
|
2552
|
+
event
|
|
2553
|
+
);
|
|
2554
|
+
fold.version = event.version;
|
|
2555
|
+
fold.event_id = event.id;
|
|
2556
|
+
fold.patches++;
|
|
2557
|
+
fold.dirty = true;
|
|
2558
|
+
} else {
|
|
2559
|
+
fold.dirty = true;
|
|
2560
|
+
}
|
|
2561
|
+
if (++folded_since_flush >= config2.flushEvery) {
|
|
2562
|
+
await flush_dirty();
|
|
2563
|
+
folded_since_flush = 0;
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
await flush_dirty();
|
|
2567
|
+
};
|
|
2568
|
+
}
|
|
2569
|
+
|
|
2442
2570
|
// src/internal/reactions.ts
|
|
2443
2571
|
function finalize(lease, handled, at, error, options, logger, failed_at) {
|
|
2444
2572
|
if (!error) return { lease, handled, acked_at: at };
|
|
@@ -2569,6 +2697,16 @@ function build_handle_batch(logger) {
|
|
|
2569
2697
|
var SettleLoop = class {
|
|
2570
2698
|
_timer = void 0;
|
|
2571
2699
|
_running = false;
|
|
2700
|
+
/**
|
|
2701
|
+
* Set when a `schedule()` timer fires while a cycle is still running
|
|
2702
|
+
* (ACT-1205). The in-flight cycle's `finally` re-arms one more pass so
|
|
2703
|
+
* the wake-up isn't dropped — a commit landing during the final
|
|
2704
|
+
* no-progress drain pass would otherwise leave armed controllers with
|
|
2705
|
+
* nothing to re-drain on an instance with no lane `cycleMs` and no
|
|
2706
|
+
* polling. Carries the options of the dropped call so the re-armed
|
|
2707
|
+
* pass honors its `debounceMs`/`maxPasses`/drain overrides.
|
|
2708
|
+
*/
|
|
2709
|
+
_pending = void 0;
|
|
2572
2710
|
_deps;
|
|
2573
2711
|
/** Debounce window applied when the caller doesn't override via `SettleOptions.debounceMs`. */
|
|
2574
2712
|
_default_debounce_ms;
|
|
@@ -2593,7 +2731,10 @@ var SettleLoop = class {
|
|
|
2593
2731
|
if (this._timer) clearTimeout(this._timer);
|
|
2594
2732
|
this._timer = setTimeout(() => {
|
|
2595
2733
|
this._timer = void 0;
|
|
2596
|
-
if (this._running)
|
|
2734
|
+
if (this._running) {
|
|
2735
|
+
this._pending = options;
|
|
2736
|
+
return;
|
|
2737
|
+
}
|
|
2597
2738
|
this._running = true;
|
|
2598
2739
|
(async () => {
|
|
2599
2740
|
await this._deps.init();
|
|
@@ -2613,11 +2754,17 @@ var SettleLoop = class {
|
|
|
2613
2754
|
this._deps.breaker.failed(Date.now(), err);
|
|
2614
2755
|
}).finally(() => {
|
|
2615
2756
|
this._running = false;
|
|
2757
|
+
const pending = this._pending;
|
|
2758
|
+
if (pending !== void 0) {
|
|
2759
|
+
this._pending = void 0;
|
|
2760
|
+
this.schedule(pending);
|
|
2761
|
+
}
|
|
2616
2762
|
});
|
|
2617
2763
|
}, debounceMs);
|
|
2618
2764
|
}
|
|
2619
2765
|
/** Cancel any pending or active settle cycle. Idempotent. */
|
|
2620
2766
|
stop() {
|
|
2767
|
+
this._pending = void 0;
|
|
2621
2768
|
if (this._timer) {
|
|
2622
2769
|
clearTimeout(this._timer);
|
|
2623
2770
|
this._timer = void 0;
|
|
@@ -2625,89 +2772,6 @@ var SettleLoop = class {
|
|
|
2625
2772
|
}
|
|
2626
2773
|
};
|
|
2627
2774
|
|
|
2628
|
-
// src/internal/state-fold.ts
|
|
2629
|
-
import { patch as patch2 } from "@rotorsoft/act-patch";
|
|
2630
|
-
import { z as z5 } from "zod";
|
|
2631
|
-
var DEFAULT_FOLD_FLUSH_EVERY = 1e3;
|
|
2632
|
-
var DEFAULT_MAX_CACHED_STATES = 1e4;
|
|
2633
|
-
var FoldOptionsSchema = z5.object({
|
|
2634
|
-
flushEvery: z5.number().int().min(1).default(DEFAULT_FOLD_FLUSH_EVERY),
|
|
2635
|
-
maxCachedStates: z5.number().int().min(1).default(DEFAULT_MAX_CACHED_STATES)
|
|
2636
|
-
});
|
|
2637
|
-
function resolveFoldConfig(options) {
|
|
2638
|
-
return FoldOptionsSchema.parse(options);
|
|
2639
|
-
}
|
|
2640
|
-
function make_fold_handler(me, flush, config2) {
|
|
2641
|
-
const cache2 = /* @__PURE__ */ new Map();
|
|
2642
|
-
const row = ({ dirty: _, ...row2 }) => row2;
|
|
2643
|
-
const flush_dirty = async () => {
|
|
2644
|
-
const rows = [];
|
|
2645
|
-
const flushed = [];
|
|
2646
|
-
for (const f of cache2.values())
|
|
2647
|
-
if (f.dirty) {
|
|
2648
|
-
rows.push(row(f));
|
|
2649
|
-
flushed.push(f);
|
|
2650
|
-
}
|
|
2651
|
-
if (rows.length === 0) return;
|
|
2652
|
-
await flush(rows);
|
|
2653
|
-
for (const f of flushed) f.dirty = false;
|
|
2654
|
-
};
|
|
2655
|
-
return async (events) => {
|
|
2656
|
-
let folded_since_flush = 0;
|
|
2657
|
-
for (const event of events) {
|
|
2658
|
-
const stream = event.stream;
|
|
2659
|
-
let fold = cache2.get(stream);
|
|
2660
|
-
if (fold) {
|
|
2661
|
-
cache2.delete(stream);
|
|
2662
|
-
cache2.set(stream, fold);
|
|
2663
|
-
} else {
|
|
2664
|
-
if (cache2.size >= config2.maxCachedStates) {
|
|
2665
|
-
const oldest = cache2.keys().next().value;
|
|
2666
|
-
const evictee = cache2.get(oldest);
|
|
2667
|
-
if (evictee.dirty) await flush([row(evictee)]);
|
|
2668
|
-
cache2.delete(oldest);
|
|
2669
|
-
}
|
|
2670
|
-
const snapshot = await load(me, { stream });
|
|
2671
|
-
const entry = await cache().get(stream);
|
|
2672
|
-
const frontier = entry ?? {
|
|
2673
|
-
version: snapshot.event.version,
|
|
2674
|
-
event_id: snapshot.event.id,
|
|
2675
|
-
patches: snapshot.patches,
|
|
2676
|
-
snaps: snapshot.snaps
|
|
2677
|
-
};
|
|
2678
|
-
fold = {
|
|
2679
|
-
stream,
|
|
2680
|
-
state: snapshot.state,
|
|
2681
|
-
version: frontier.version,
|
|
2682
|
-
event_id: frontier.event_id,
|
|
2683
|
-
patches: frontier.patches,
|
|
2684
|
-
snaps: frontier.snaps,
|
|
2685
|
-
dirty: true
|
|
2686
|
-
};
|
|
2687
|
-
cache2.set(stream, fold);
|
|
2688
|
-
}
|
|
2689
|
-
if (event.id > fold.event_id) {
|
|
2690
|
-
const reducer = me.patch[event.name];
|
|
2691
|
-
fold.state = patch2(
|
|
2692
|
-
fold.state,
|
|
2693
|
-
reducer(event, fold.state)
|
|
2694
|
-
);
|
|
2695
|
-
fold.version = event.version;
|
|
2696
|
-
fold.event_id = event.id;
|
|
2697
|
-
fold.patches++;
|
|
2698
|
-
fold.dirty = true;
|
|
2699
|
-
} else {
|
|
2700
|
-
fold.dirty = true;
|
|
2701
|
-
}
|
|
2702
|
-
if (++folded_since_flush >= config2.flushEvery) {
|
|
2703
|
-
await flush_dirty();
|
|
2704
|
-
folded_since_flush = 0;
|
|
2705
|
-
}
|
|
2706
|
-
}
|
|
2707
|
-
await flush_dirty();
|
|
2708
|
-
};
|
|
2709
|
-
}
|
|
2710
|
-
|
|
2711
2775
|
// src/act.ts
|
|
2712
2776
|
var DEFAULT_MAX_SUBSCRIBED_STREAMS = 1e3;
|
|
2713
2777
|
var DEFAULT_SETTLE_DEBOUNCE_MS = 10;
|
|
@@ -2864,8 +2928,14 @@ var Act = class {
|
|
|
2864
2928
|
* @param lanes Declared drain lanes (ACT-1103). The builder collects
|
|
2865
2929
|
* these from `.withLane(...)` calls. Slice 1 records them on the
|
|
2866
2930
|
* instance; later slices fan out one `DrainController` per lane.
|
|
2931
|
+
* @param patch_fn The per-event patch step selected once by the builder
|
|
2932
|
+
* from `ActOptions.validateFoldedState` (ACT-1238) — `bare_patch` by
|
|
2933
|
+
* default, `validating_patch` when the flag is on. The builder uses
|
|
2934
|
+
* the same value for its projection-fold handlers, so there is a
|
|
2935
|
+
* single selection site. Defaults to `bare_patch` for direct
|
|
2936
|
+
* construction.
|
|
2867
2937
|
*/
|
|
2868
|
-
constructor(registry, states = /* @__PURE__ */ new Map(), batch_handlers = /* @__PURE__ */ new Map(), options = {}, lanes = []) {
|
|
2938
|
+
constructor(registry, states = /* @__PURE__ */ new Map(), batch_handlers = /* @__PURE__ */ new Map(), options = {}, lanes = [], patch_fn = bare_patch) {
|
|
2869
2939
|
this.registry = registry;
|
|
2870
2940
|
this._states = states;
|
|
2871
2941
|
this._batch_handlers = batch_handlers;
|
|
@@ -2873,7 +2943,7 @@ var Act = class {
|
|
|
2873
2943
|
validate_only_lanes(options, lanes);
|
|
2874
2944
|
this._scoped = options.scoped ? (fn) => scoped.run(options.scoped, fn) : (fn) => fn();
|
|
2875
2945
|
this._correlator = options.correlator ?? default_correlator;
|
|
2876
|
-
this._es = build_es(this._logger, this._correlator);
|
|
2946
|
+
this._es = build_es(this._logger, this._correlator, patch_fn);
|
|
2877
2947
|
this._cd = build_drain(this._logger);
|
|
2878
2948
|
this._handle = build_handle({
|
|
2879
2949
|
logger: this._logger,
|
|
@@ -3040,22 +3110,22 @@ var Act = class {
|
|
|
3040
3110
|
* except on writer-only instances (`drain: false`).
|
|
3041
3111
|
*/
|
|
3042
3112
|
_build_correlate(options, classification) {
|
|
3043
|
-
return new CorrelateCycle(
|
|
3044
|
-
this.registry,
|
|
3045
|
-
classification.static_targets,
|
|
3046
|
-
classification.has_dynamic_resolvers,
|
|
3047
|
-
this._cd,
|
|
3048
|
-
options.maxSubscribedStreams ?? DEFAULT_MAX_SUBSCRIBED_STREAMS,
|
|
3049
|
-
() => {
|
|
3113
|
+
return new CorrelateCycle({
|
|
3114
|
+
registry: this.registry,
|
|
3115
|
+
static_targets: classification.static_targets,
|
|
3116
|
+
has_dynamic_resolvers: classification.has_dynamic_resolvers,
|
|
3117
|
+
cd: this._cd,
|
|
3118
|
+
max_subscribed_streams: options.maxSubscribedStreams ?? DEFAULT_MAX_SUBSCRIBED_STREAMS,
|
|
3119
|
+
on_init: () => {
|
|
3050
3120
|
if (this._drain && this._reactive_events.size > 0) this._arm_all();
|
|
3051
3121
|
},
|
|
3052
3122
|
// Re-scope the background `start_correlations` timer so its
|
|
3053
3123
|
// correlate resolves the scoped ports, not the singleton (#1191).
|
|
3054
|
-
this._scoped,
|
|
3124
|
+
run_scoped: this._scoped,
|
|
3055
3125
|
// Cold-start defer re-seed (#1221). Skipped on writer-only instances
|
|
3056
3126
|
// (`drain: false`) — they run no local controllers to re-arm.
|
|
3057
|
-
this._drain ? () => this._seed_persisted_defers() : void 0
|
|
3058
|
-
);
|
|
3127
|
+
on_init_async: this._drain ? () => this._seed_persisted_defers() : void 0
|
|
3128
|
+
});
|
|
3059
3129
|
}
|
|
3060
3130
|
/**
|
|
3061
3131
|
* Re-seed every active lane controller's process-local defer timer from
|
|
@@ -4114,6 +4184,7 @@ function act() {
|
|
|
4114
4184
|
},
|
|
4115
4185
|
on: (event) => reaction_on(event, registry.events, builder),
|
|
4116
4186
|
build: (options) => {
|
|
4187
|
+
const patch_fn = options?.validateFoldedState === true ? validating_patch : bare_patch;
|
|
4117
4188
|
if (!_built) {
|
|
4118
4189
|
for (const proj of pending_projections) {
|
|
4119
4190
|
merge_projection(proj, registry.events);
|
|
@@ -4136,7 +4207,7 @@ function act() {
|
|
|
4136
4207
|
);
|
|
4137
4208
|
batch_handlers.set(
|
|
4138
4209
|
proj.target,
|
|
4139
|
-
make_fold_handler(merged, fold.flush, fold.config)
|
|
4210
|
+
make_fold_handler(merged, fold.flush, fold.config, patch_fn)
|
|
4140
4211
|
);
|
|
4141
4212
|
}
|
|
4142
4213
|
finalize_deprecations();
|
|
@@ -4209,7 +4280,8 @@ function act() {
|
|
|
4209
4280
|
states,
|
|
4210
4281
|
batch_handlers,
|
|
4211
4282
|
options,
|
|
4212
|
-
lanes
|
|
4283
|
+
lanes,
|
|
4284
|
+
patch_fn
|
|
4213
4285
|
);
|
|
4214
4286
|
},
|
|
4215
4287
|
events: registry.events
|