@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.cjs
CHANGED
|
@@ -2920,6 +2920,7 @@ async function truncate_and_warm_cache(guarded, seed_states, guard_events, corre
|
|
|
2920
2920
|
}
|
|
2921
2921
|
|
|
2922
2922
|
// src/internal/correlate-cycle.ts
|
|
2923
|
+
var DEFAULT_COLD_START_BACK_SCAN = 1e4;
|
|
2923
2924
|
var CorrelateCycle = class {
|
|
2924
2925
|
_checkpoint = -1;
|
|
2925
2926
|
_initialized = false;
|
|
@@ -2948,8 +2949,24 @@ var CorrelateCycle = class {
|
|
|
2948
2949
|
* `_scoped` (identity for a non-scoped Act), so it's required.
|
|
2949
2950
|
*/
|
|
2950
2951
|
_run_scoped;
|
|
2951
|
-
|
|
2952
|
-
|
|
2952
|
+
/**
|
|
2953
|
+
* Tail re-scan window applied to the cold-start checkpoint (ACT-1207).
|
|
2954
|
+
* See {@link DEFAULT_COLD_START_BACK_SCAN}. Constructor arg (not a
|
|
2955
|
+
* public option) so tests can shrink it; defaults otherwise.
|
|
2956
|
+
*/
|
|
2957
|
+
_cold_start_back_scan;
|
|
2958
|
+
constructor({
|
|
2959
|
+
registry,
|
|
2960
|
+
static_targets,
|
|
2961
|
+
has_dynamic_resolvers,
|
|
2962
|
+
cd,
|
|
2963
|
+
max_subscribed_streams,
|
|
2964
|
+
run_scoped,
|
|
2965
|
+
on_init,
|
|
2966
|
+
on_init_async,
|
|
2967
|
+
cold_start_back_scan = DEFAULT_COLD_START_BACK_SCAN
|
|
2968
|
+
}) {
|
|
2969
|
+
this._subscribed = new LruSet(max_subscribed_streams);
|
|
2953
2970
|
this._registry = registry;
|
|
2954
2971
|
this._static_targets = static_targets;
|
|
2955
2972
|
this._has_dynamic_resolvers = has_dynamic_resolvers;
|
|
@@ -2957,6 +2974,7 @@ var CorrelateCycle = class {
|
|
|
2957
2974
|
this._on_init = on_init;
|
|
2958
2975
|
this._run_scoped = run_scoped;
|
|
2959
2976
|
this._on_init_async = on_init_async;
|
|
2977
|
+
this._cold_start_back_scan = cold_start_back_scan;
|
|
2960
2978
|
}
|
|
2961
2979
|
/** Last correlated event id. */
|
|
2962
2980
|
get checkpoint() {
|
|
@@ -2964,7 +2982,10 @@ var CorrelateCycle = class {
|
|
|
2964
2982
|
}
|
|
2965
2983
|
/**
|
|
2966
2984
|
* Initialize correlation state on first call.
|
|
2967
|
-
* - Reads max(at) from store
|
|
2985
|
+
* - Reads max(at) from store, then floors the cold-start checkpoint at
|
|
2986
|
+
* `watermark - back_scan` when dynamic resolvers exist, so an event
|
|
2987
|
+
* committed-but-not-correlated before a crash is re-scanned on
|
|
2988
|
+
* restart instead of skipped (ACT-1207)
|
|
2968
2989
|
* - Subscribes static resolver targets (idempotent upsert)
|
|
2969
2990
|
* - Populates the subscribed-streams LRU
|
|
2970
2991
|
* - Fires `on_init` once (Act uses this to flag a cold-start drain)
|
|
@@ -2973,7 +2994,7 @@ var CorrelateCycle = class {
|
|
|
2973
2994
|
if (this._initialized) return;
|
|
2974
2995
|
this._initialized = true;
|
|
2975
2996
|
const { watermark } = await store2().subscribe([...this._static_targets]);
|
|
2976
|
-
this._checkpoint = watermark;
|
|
2997
|
+
this._checkpoint = this._has_dynamic_resolvers ? Math.max(-1, watermark - this._cold_start_back_scan) : watermark;
|
|
2977
2998
|
this._on_init?.();
|
|
2978
2999
|
for (const { stream } of this._static_targets) {
|
|
2979
3000
|
this._subscribed.add(stream);
|
|
@@ -3239,6 +3260,15 @@ function compute_backoff_delay(retry, opts) {
|
|
|
3239
3260
|
}
|
|
3240
3261
|
|
|
3241
3262
|
// src/internal/event-sourcing.ts
|
|
3263
|
+
var bare_patch = (_me, state2, partial) => (0, import_act_patch.patch)(state2, partial);
|
|
3264
|
+
var validating_patch = (me, state2, partial, event) => {
|
|
3265
|
+
const next = (0, import_act_patch.patch)(state2, partial);
|
|
3266
|
+
return validate(
|
|
3267
|
+
`${me.name}.${String(event.name)}#${event.id}`,
|
|
3268
|
+
next,
|
|
3269
|
+
me.state
|
|
3270
|
+
);
|
|
3271
|
+
};
|
|
3242
3272
|
var DEFAULT_BATCH = 500;
|
|
3243
3273
|
async function snap(snapshot) {
|
|
3244
3274
|
const { id, stream, name, meta, version } = snapshot.event;
|
|
@@ -3393,7 +3423,7 @@ async function scan(source, opts = {}, callback) {
|
|
|
3393
3423
|
}
|
|
3394
3424
|
};
|
|
3395
3425
|
}
|
|
3396
|
-
async function load(me, target, callback) {
|
|
3426
|
+
async function load(me, target, callback, patch_fn = bare_patch) {
|
|
3397
3427
|
const { stream, actor, asOf } = target;
|
|
3398
3428
|
const time_travel = !!asOf && Object.values(asOf).some((v) => v !== void 0);
|
|
3399
3429
|
const cached = time_travel ? void 0 : await cache2().get(stream);
|
|
@@ -3412,7 +3442,7 @@ async function load(me, target, callback) {
|
|
|
3412
3442
|
patches = 0;
|
|
3413
3443
|
replayed++;
|
|
3414
3444
|
} else if (me.patch[event.name]) {
|
|
3415
|
-
state2 = (
|
|
3445
|
+
state2 = patch_fn(me, state2, me.patch[event.name](event, state2), event);
|
|
3416
3446
|
patches++;
|
|
3417
3447
|
replayed++;
|
|
3418
3448
|
} else if (event.name !== TOMBSTONE_EVENT) {
|
|
@@ -3424,6 +3454,7 @@ async function load(me, target, callback) {
|
|
|
3424
3454
|
event,
|
|
3425
3455
|
state: state2,
|
|
3426
3456
|
version: event.version,
|
|
3457
|
+
id: event.id,
|
|
3427
3458
|
patches,
|
|
3428
3459
|
snaps,
|
|
3429
3460
|
cache_hit,
|
|
@@ -3445,19 +3476,24 @@ async function load(me, target, callback) {
|
|
|
3445
3476
|
event_id: event.id,
|
|
3446
3477
|
patches,
|
|
3447
3478
|
snaps
|
|
3448
|
-
});
|
|
3479
|
+
}).catch((err) => log().error(err));
|
|
3449
3480
|
}
|
|
3450
3481
|
return {
|
|
3451
3482
|
event,
|
|
3452
3483
|
state: state2,
|
|
3453
3484
|
version: event?.version ?? cached?.version ?? -1,
|
|
3485
|
+
// Head event id, captured atomically with `state`: the last replayed
|
|
3486
|
+
// event's id on a cache miss, or the cached checkpoint's event_id on a
|
|
3487
|
+
// warm hit with no new events. Consumers read this instead of a
|
|
3488
|
+
// separate cache lookup that could race a concurrent commit (ACT-1204).
|
|
3489
|
+
id: event?.id ?? cached?.event_id ?? -1,
|
|
3454
3490
|
patches,
|
|
3455
3491
|
snaps,
|
|
3456
3492
|
cache_hit,
|
|
3457
3493
|
replayed
|
|
3458
3494
|
};
|
|
3459
3495
|
}
|
|
3460
|
-
async function action(me, action2, target, payload, options) {
|
|
3496
|
+
async function action(me, action2, target, payload, options, patch_fn = bare_patch) {
|
|
3461
3497
|
const { stream, expectedVersion, actor } = target;
|
|
3462
3498
|
if (!stream) throw new Error("Missing target stream");
|
|
3463
3499
|
const reactingTo = options?.reactingTo;
|
|
@@ -3467,7 +3503,12 @@ async function action(me, action2, target, payload, options) {
|
|
|
3467
3503
|
const max_retries = opts?.maxRetries ?? 0;
|
|
3468
3504
|
for (let attempt = 0; ; attempt++) {
|
|
3469
3505
|
try {
|
|
3470
|
-
const snapshot = await load(
|
|
3506
|
+
const snapshot = await load(
|
|
3507
|
+
me,
|
|
3508
|
+
{ stream, actor: target.actor },
|
|
3509
|
+
void 0,
|
|
3510
|
+
patch_fn
|
|
3511
|
+
);
|
|
3471
3512
|
if (snapshot.event?.name === TOMBSTONE_EVENT)
|
|
3472
3513
|
throw new StreamClosedError(stream);
|
|
3473
3514
|
const expected = expectedVersion ?? (snapshot.version >= 0 ? snapshot.version : void 0);
|
|
@@ -3538,12 +3579,13 @@ async function action(me, action2, target, payload, options) {
|
|
|
3538
3579
|
const snapshots = committed.map((row, i) => {
|
|
3539
3580
|
const event = { ...row, data: valid[i].data };
|
|
3540
3581
|
const p = me.patch[event.name](event, state2);
|
|
3541
|
-
state2 = (
|
|
3582
|
+
state2 = patch_fn(me, state2, p, event);
|
|
3542
3583
|
patches++;
|
|
3543
3584
|
return {
|
|
3544
3585
|
event,
|
|
3545
3586
|
state: state2,
|
|
3546
3587
|
version: event.version,
|
|
3588
|
+
id: event.id,
|
|
3547
3589
|
patches,
|
|
3548
3590
|
snaps: snapshot.snaps,
|
|
3549
3591
|
patch: p,
|
|
@@ -3571,6 +3613,7 @@ async function action(me, action2, target, payload, options) {
|
|
|
3571
3613
|
return snapshots;
|
|
3572
3614
|
} catch (error) {
|
|
3573
3615
|
if (!(error instanceof ConcurrencyError)) throw error;
|
|
3616
|
+
if (expectedVersion !== void 0) throw error;
|
|
3574
3617
|
if (attempt >= max_retries) throw error;
|
|
3575
3618
|
if (opts?.backoff) {
|
|
3576
3619
|
const delay_ms = compute_backoff_delay(attempt, opts.backoff);
|
|
@@ -3632,12 +3675,20 @@ var traced = (inner, exit, entry) => (async (...args) => {
|
|
|
3632
3675
|
exit?.(result, ...args);
|
|
3633
3676
|
return result;
|
|
3634
3677
|
});
|
|
3635
|
-
function build_es(logger, correlator = default_correlator) {
|
|
3636
|
-
const bound_action = (me, action_name, target, payload, options) => action(
|
|
3678
|
+
function build_es(logger, correlator = default_correlator, patch_fn = bare_patch) {
|
|
3679
|
+
const bound_action = (me, action_name, target, payload, options) => action(
|
|
3680
|
+
me,
|
|
3681
|
+
action_name,
|
|
3682
|
+
target,
|
|
3683
|
+
payload,
|
|
3684
|
+
{ correlator, ...options },
|
|
3685
|
+
patch_fn
|
|
3686
|
+
);
|
|
3687
|
+
const bound_load = (me, target, callback) => load(me, target, callback, patch_fn);
|
|
3637
3688
|
if (logger.level !== "trace") {
|
|
3638
3689
|
return {
|
|
3639
3690
|
snap,
|
|
3640
|
-
load,
|
|
3691
|
+
load: bound_load,
|
|
3641
3692
|
action: bound_action,
|
|
3642
3693
|
tombstone
|
|
3643
3694
|
};
|
|
@@ -3652,7 +3703,7 @@ function build_es(logger, correlator = default_correlator) {
|
|
|
3652
3703
|
)
|
|
3653
3704
|
);
|
|
3654
3705
|
}),
|
|
3655
|
-
load: traced(
|
|
3706
|
+
load: traced(bound_load, (result, _me, target) => {
|
|
3656
3707
|
const stats = stats_marker(
|
|
3657
3708
|
result.version,
|
|
3658
3709
|
result.replayed,
|
|
@@ -3963,6 +4014,83 @@ var DrainController = class {
|
|
|
3963
4014
|
}
|
|
3964
4015
|
};
|
|
3965
4016
|
|
|
4017
|
+
// src/internal/projection-fold.ts
|
|
4018
|
+
var import_zod10 = require("zod");
|
|
4019
|
+
var DEFAULT_FOLD_FLUSH_EVERY = 1e3;
|
|
4020
|
+
var DEFAULT_MAX_CACHED_STATES = 1e4;
|
|
4021
|
+
var FoldOptionsSchema = import_zod10.z.object({
|
|
4022
|
+
flushEvery: import_zod10.z.number().int().min(1).default(DEFAULT_FOLD_FLUSH_EVERY),
|
|
4023
|
+
maxCachedStates: import_zod10.z.number().int().min(1).default(DEFAULT_MAX_CACHED_STATES)
|
|
4024
|
+
});
|
|
4025
|
+
function resolveFoldConfig(options) {
|
|
4026
|
+
return FoldOptionsSchema.parse(options);
|
|
4027
|
+
}
|
|
4028
|
+
function make_fold_handler(me, flush, config2, patch_fn = bare_patch) {
|
|
4029
|
+
const cache3 = /* @__PURE__ */ new Map();
|
|
4030
|
+
const row = ({ dirty: _, ...row2 }) => row2;
|
|
4031
|
+
const flush_dirty = async () => {
|
|
4032
|
+
const rows = [];
|
|
4033
|
+
const flushed = [];
|
|
4034
|
+
for (const f of cache3.values())
|
|
4035
|
+
if (f.dirty) {
|
|
4036
|
+
rows.push(row(f));
|
|
4037
|
+
flushed.push(f);
|
|
4038
|
+
}
|
|
4039
|
+
if (rows.length === 0) return;
|
|
4040
|
+
await flush(rows);
|
|
4041
|
+
for (const f of flushed) f.dirty = false;
|
|
4042
|
+
};
|
|
4043
|
+
return async (events) => {
|
|
4044
|
+
let folded_since_flush = 0;
|
|
4045
|
+
for (const event of events) {
|
|
4046
|
+
const stream = event.stream;
|
|
4047
|
+
let fold = cache3.get(stream);
|
|
4048
|
+
if (fold) {
|
|
4049
|
+
cache3.delete(stream);
|
|
4050
|
+
cache3.set(stream, fold);
|
|
4051
|
+
} else {
|
|
4052
|
+
if (cache3.size >= config2.maxCachedStates) {
|
|
4053
|
+
const oldest = cache3.keys().next().value;
|
|
4054
|
+
const evictee = cache3.get(oldest);
|
|
4055
|
+
if (evictee.dirty) await flush([row(evictee)]);
|
|
4056
|
+
cache3.delete(oldest);
|
|
4057
|
+
}
|
|
4058
|
+
const snapshot = await load(me, { stream }, void 0, patch_fn);
|
|
4059
|
+
fold = {
|
|
4060
|
+
stream,
|
|
4061
|
+
state: snapshot.state,
|
|
4062
|
+
version: snapshot.version,
|
|
4063
|
+
event_id: snapshot.id,
|
|
4064
|
+
patches: snapshot.patches,
|
|
4065
|
+
snaps: snapshot.snaps,
|
|
4066
|
+
dirty: true
|
|
4067
|
+
};
|
|
4068
|
+
cache3.set(stream, fold);
|
|
4069
|
+
}
|
|
4070
|
+
if (event.id > fold.event_id) {
|
|
4071
|
+
const reducer = me.patch[event.name];
|
|
4072
|
+
fold.state = patch_fn(
|
|
4073
|
+
me,
|
|
4074
|
+
fold.state,
|
|
4075
|
+
reducer(event, fold.state),
|
|
4076
|
+
event
|
|
4077
|
+
);
|
|
4078
|
+
fold.version = event.version;
|
|
4079
|
+
fold.event_id = event.id;
|
|
4080
|
+
fold.patches++;
|
|
4081
|
+
fold.dirty = true;
|
|
4082
|
+
} else {
|
|
4083
|
+
fold.dirty = true;
|
|
4084
|
+
}
|
|
4085
|
+
if (++folded_since_flush >= config2.flushEvery) {
|
|
4086
|
+
await flush_dirty();
|
|
4087
|
+
folded_since_flush = 0;
|
|
4088
|
+
}
|
|
4089
|
+
}
|
|
4090
|
+
await flush_dirty();
|
|
4091
|
+
};
|
|
4092
|
+
}
|
|
4093
|
+
|
|
3966
4094
|
// src/internal/reactions.ts
|
|
3967
4095
|
function finalize(lease, handled, at, error, options, logger, failed_at) {
|
|
3968
4096
|
if (!error) return { lease, handled, acked_at: at };
|
|
@@ -4093,6 +4221,16 @@ function build_handle_batch(logger) {
|
|
|
4093
4221
|
var SettleLoop = class {
|
|
4094
4222
|
_timer = void 0;
|
|
4095
4223
|
_running = false;
|
|
4224
|
+
/**
|
|
4225
|
+
* Set when a `schedule()` timer fires while a cycle is still running
|
|
4226
|
+
* (ACT-1205). The in-flight cycle's `finally` re-arms one more pass so
|
|
4227
|
+
* the wake-up isn't dropped — a commit landing during the final
|
|
4228
|
+
* no-progress drain pass would otherwise leave armed controllers with
|
|
4229
|
+
* nothing to re-drain on an instance with no lane `cycleMs` and no
|
|
4230
|
+
* polling. Carries the options of the dropped call so the re-armed
|
|
4231
|
+
* pass honors its `debounceMs`/`maxPasses`/drain overrides.
|
|
4232
|
+
*/
|
|
4233
|
+
_pending = void 0;
|
|
4096
4234
|
_deps;
|
|
4097
4235
|
/** Debounce window applied when the caller doesn't override via `SettleOptions.debounceMs`. */
|
|
4098
4236
|
_default_debounce_ms;
|
|
@@ -4117,7 +4255,10 @@ var SettleLoop = class {
|
|
|
4117
4255
|
if (this._timer) clearTimeout(this._timer);
|
|
4118
4256
|
this._timer = setTimeout(() => {
|
|
4119
4257
|
this._timer = void 0;
|
|
4120
|
-
if (this._running)
|
|
4258
|
+
if (this._running) {
|
|
4259
|
+
this._pending = options;
|
|
4260
|
+
return;
|
|
4261
|
+
}
|
|
4121
4262
|
this._running = true;
|
|
4122
4263
|
(async () => {
|
|
4123
4264
|
await this._deps.init();
|
|
@@ -4137,11 +4278,17 @@ var SettleLoop = class {
|
|
|
4137
4278
|
this._deps.breaker.failed(Date.now(), err);
|
|
4138
4279
|
}).finally(() => {
|
|
4139
4280
|
this._running = false;
|
|
4281
|
+
const pending = this._pending;
|
|
4282
|
+
if (pending !== void 0) {
|
|
4283
|
+
this._pending = void 0;
|
|
4284
|
+
this.schedule(pending);
|
|
4285
|
+
}
|
|
4140
4286
|
});
|
|
4141
4287
|
}, debounceMs);
|
|
4142
4288
|
}
|
|
4143
4289
|
/** Cancel any pending or active settle cycle. Idempotent. */
|
|
4144
4290
|
stop() {
|
|
4291
|
+
this._pending = void 0;
|
|
4145
4292
|
if (this._timer) {
|
|
4146
4293
|
clearTimeout(this._timer);
|
|
4147
4294
|
this._timer = void 0;
|
|
@@ -4149,89 +4296,6 @@ var SettleLoop = class {
|
|
|
4149
4296
|
}
|
|
4150
4297
|
};
|
|
4151
4298
|
|
|
4152
|
-
// src/internal/state-fold.ts
|
|
4153
|
-
var import_act_patch2 = require("@rotorsoft/act-patch");
|
|
4154
|
-
var import_zod10 = require("zod");
|
|
4155
|
-
var DEFAULT_FOLD_FLUSH_EVERY = 1e3;
|
|
4156
|
-
var DEFAULT_MAX_CACHED_STATES = 1e4;
|
|
4157
|
-
var FoldOptionsSchema = import_zod10.z.object({
|
|
4158
|
-
flushEvery: import_zod10.z.number().int().min(1).default(DEFAULT_FOLD_FLUSH_EVERY),
|
|
4159
|
-
maxCachedStates: import_zod10.z.number().int().min(1).default(DEFAULT_MAX_CACHED_STATES)
|
|
4160
|
-
});
|
|
4161
|
-
function resolveFoldConfig(options) {
|
|
4162
|
-
return FoldOptionsSchema.parse(options);
|
|
4163
|
-
}
|
|
4164
|
-
function make_fold_handler(me, flush, config2) {
|
|
4165
|
-
const cache3 = /* @__PURE__ */ new Map();
|
|
4166
|
-
const row = ({ dirty: _, ...row2 }) => row2;
|
|
4167
|
-
const flush_dirty = async () => {
|
|
4168
|
-
const rows = [];
|
|
4169
|
-
const flushed = [];
|
|
4170
|
-
for (const f of cache3.values())
|
|
4171
|
-
if (f.dirty) {
|
|
4172
|
-
rows.push(row(f));
|
|
4173
|
-
flushed.push(f);
|
|
4174
|
-
}
|
|
4175
|
-
if (rows.length === 0) return;
|
|
4176
|
-
await flush(rows);
|
|
4177
|
-
for (const f of flushed) f.dirty = false;
|
|
4178
|
-
};
|
|
4179
|
-
return async (events) => {
|
|
4180
|
-
let folded_since_flush = 0;
|
|
4181
|
-
for (const event of events) {
|
|
4182
|
-
const stream = event.stream;
|
|
4183
|
-
let fold = cache3.get(stream);
|
|
4184
|
-
if (fold) {
|
|
4185
|
-
cache3.delete(stream);
|
|
4186
|
-
cache3.set(stream, fold);
|
|
4187
|
-
} else {
|
|
4188
|
-
if (cache3.size >= config2.maxCachedStates) {
|
|
4189
|
-
const oldest = cache3.keys().next().value;
|
|
4190
|
-
const evictee = cache3.get(oldest);
|
|
4191
|
-
if (evictee.dirty) await flush([row(evictee)]);
|
|
4192
|
-
cache3.delete(oldest);
|
|
4193
|
-
}
|
|
4194
|
-
const snapshot = await load(me, { stream });
|
|
4195
|
-
const entry = await cache2().get(stream);
|
|
4196
|
-
const frontier = entry ?? {
|
|
4197
|
-
version: snapshot.event.version,
|
|
4198
|
-
event_id: snapshot.event.id,
|
|
4199
|
-
patches: snapshot.patches,
|
|
4200
|
-
snaps: snapshot.snaps
|
|
4201
|
-
};
|
|
4202
|
-
fold = {
|
|
4203
|
-
stream,
|
|
4204
|
-
state: snapshot.state,
|
|
4205
|
-
version: frontier.version,
|
|
4206
|
-
event_id: frontier.event_id,
|
|
4207
|
-
patches: frontier.patches,
|
|
4208
|
-
snaps: frontier.snaps,
|
|
4209
|
-
dirty: true
|
|
4210
|
-
};
|
|
4211
|
-
cache3.set(stream, fold);
|
|
4212
|
-
}
|
|
4213
|
-
if (event.id > fold.event_id) {
|
|
4214
|
-
const reducer = me.patch[event.name];
|
|
4215
|
-
fold.state = (0, import_act_patch2.patch)(
|
|
4216
|
-
fold.state,
|
|
4217
|
-
reducer(event, fold.state)
|
|
4218
|
-
);
|
|
4219
|
-
fold.version = event.version;
|
|
4220
|
-
fold.event_id = event.id;
|
|
4221
|
-
fold.patches++;
|
|
4222
|
-
fold.dirty = true;
|
|
4223
|
-
} else {
|
|
4224
|
-
fold.dirty = true;
|
|
4225
|
-
}
|
|
4226
|
-
if (++folded_since_flush >= config2.flushEvery) {
|
|
4227
|
-
await flush_dirty();
|
|
4228
|
-
folded_since_flush = 0;
|
|
4229
|
-
}
|
|
4230
|
-
}
|
|
4231
|
-
await flush_dirty();
|
|
4232
|
-
};
|
|
4233
|
-
}
|
|
4234
|
-
|
|
4235
4299
|
// src/act.ts
|
|
4236
4300
|
var DEFAULT_MAX_SUBSCRIBED_STREAMS = 1e3;
|
|
4237
4301
|
var DEFAULT_SETTLE_DEBOUNCE_MS = 10;
|
|
@@ -4388,8 +4452,14 @@ var Act = class {
|
|
|
4388
4452
|
* @param lanes Declared drain lanes (ACT-1103). The builder collects
|
|
4389
4453
|
* these from `.withLane(...)` calls. Slice 1 records them on the
|
|
4390
4454
|
* instance; later slices fan out one `DrainController` per lane.
|
|
4455
|
+
* @param patch_fn The per-event patch step selected once by the builder
|
|
4456
|
+
* from `ActOptions.validateFoldedState` (ACT-1238) — `bare_patch` by
|
|
4457
|
+
* default, `validating_patch` when the flag is on. The builder uses
|
|
4458
|
+
* the same value for its projection-fold handlers, so there is a
|
|
4459
|
+
* single selection site. Defaults to `bare_patch` for direct
|
|
4460
|
+
* construction.
|
|
4391
4461
|
*/
|
|
4392
|
-
constructor(registry, states = /* @__PURE__ */ new Map(), batch_handlers = /* @__PURE__ */ new Map(), options = {}, lanes = []) {
|
|
4462
|
+
constructor(registry, states = /* @__PURE__ */ new Map(), batch_handlers = /* @__PURE__ */ new Map(), options = {}, lanes = [], patch_fn = bare_patch) {
|
|
4393
4463
|
this.registry = registry;
|
|
4394
4464
|
this._states = states;
|
|
4395
4465
|
this._batch_handlers = batch_handlers;
|
|
@@ -4397,7 +4467,7 @@ var Act = class {
|
|
|
4397
4467
|
validate_only_lanes(options, lanes);
|
|
4398
4468
|
this._scoped = options.scoped ? (fn) => scoped.run(options.scoped, fn) : (fn) => fn();
|
|
4399
4469
|
this._correlator = options.correlator ?? default_correlator;
|
|
4400
|
-
this._es = build_es(this._logger, this._correlator);
|
|
4470
|
+
this._es = build_es(this._logger, this._correlator, patch_fn);
|
|
4401
4471
|
this._cd = build_drain(this._logger);
|
|
4402
4472
|
this._handle = build_handle({
|
|
4403
4473
|
logger: this._logger,
|
|
@@ -4564,22 +4634,22 @@ var Act = class {
|
|
|
4564
4634
|
* except on writer-only instances (`drain: false`).
|
|
4565
4635
|
*/
|
|
4566
4636
|
_build_correlate(options, classification) {
|
|
4567
|
-
return new CorrelateCycle(
|
|
4568
|
-
this.registry,
|
|
4569
|
-
classification.static_targets,
|
|
4570
|
-
classification.has_dynamic_resolvers,
|
|
4571
|
-
this._cd,
|
|
4572
|
-
options.maxSubscribedStreams ?? DEFAULT_MAX_SUBSCRIBED_STREAMS,
|
|
4573
|
-
() => {
|
|
4637
|
+
return new CorrelateCycle({
|
|
4638
|
+
registry: this.registry,
|
|
4639
|
+
static_targets: classification.static_targets,
|
|
4640
|
+
has_dynamic_resolvers: classification.has_dynamic_resolvers,
|
|
4641
|
+
cd: this._cd,
|
|
4642
|
+
max_subscribed_streams: options.maxSubscribedStreams ?? DEFAULT_MAX_SUBSCRIBED_STREAMS,
|
|
4643
|
+
on_init: () => {
|
|
4574
4644
|
if (this._drain && this._reactive_events.size > 0) this._arm_all();
|
|
4575
4645
|
},
|
|
4576
4646
|
// Re-scope the background `start_correlations` timer so its
|
|
4577
4647
|
// correlate resolves the scoped ports, not the singleton (#1191).
|
|
4578
|
-
this._scoped,
|
|
4648
|
+
run_scoped: this._scoped,
|
|
4579
4649
|
// Cold-start defer re-seed (#1221). Skipped on writer-only instances
|
|
4580
4650
|
// (`drain: false`) — they run no local controllers to re-arm.
|
|
4581
|
-
this._drain ? () => this._seed_persisted_defers() : void 0
|
|
4582
|
-
);
|
|
4651
|
+
on_init_async: this._drain ? () => this._seed_persisted_defers() : void 0
|
|
4652
|
+
});
|
|
4583
4653
|
}
|
|
4584
4654
|
/**
|
|
4585
4655
|
* Re-seed every active lane controller's process-local defer timer from
|
|
@@ -5638,6 +5708,7 @@ function act() {
|
|
|
5638
5708
|
},
|
|
5639
5709
|
on: (event) => reaction_on(event, registry.events, builder),
|
|
5640
5710
|
build: (options) => {
|
|
5711
|
+
const patch_fn = options?.validateFoldedState === true ? validating_patch : bare_patch;
|
|
5641
5712
|
if (!_built) {
|
|
5642
5713
|
for (const proj of pending_projections) {
|
|
5643
5714
|
merge_projection(proj, registry.events);
|
|
@@ -5660,7 +5731,7 @@ function act() {
|
|
|
5660
5731
|
);
|
|
5661
5732
|
batch_handlers.set(
|
|
5662
5733
|
proj.target,
|
|
5663
|
-
make_fold_handler(merged, fold.flush, fold.config)
|
|
5734
|
+
make_fold_handler(merged, fold.flush, fold.config, patch_fn)
|
|
5664
5735
|
);
|
|
5665
5736
|
}
|
|
5666
5737
|
finalize_deprecations();
|
|
@@ -5733,7 +5804,8 @@ function act() {
|
|
|
5733
5804
|
states,
|
|
5734
5805
|
batch_handlers,
|
|
5735
5806
|
options,
|
|
5736
|
-
lanes
|
|
5807
|
+
lanes,
|
|
5808
|
+
patch_fn
|
|
5737
5809
|
);
|
|
5738
5810
|
},
|
|
5739
5811
|
events: registry.events
|