@rotorsoft/act 1.25.1 → 1.25.3
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.map +1 -1
- package/dist/@types/builders/act-builder.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/internal/projection-fold.d.ts +22 -1
- package/dist/@types/internal/projection-fold.d.ts.map +1 -1
- package/dist/@types/internal/settle.d.ts +19 -0
- package/dist/@types/internal/settle.d.ts.map +1 -1
- package/dist/index.cjs +98 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +98 -26
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -2806,6 +2806,7 @@ var DrainController = class {
|
|
|
2806
2806
|
};
|
|
2807
2807
|
|
|
2808
2808
|
// src/internal/projection-fold.ts
|
|
2809
|
+
var FOLD_RESET = /* @__PURE__ */ Symbol("act.fold.reset");
|
|
2809
2810
|
function make_fold_handler(me, flush, config2, patch_fn = bare_patch, sensitive_fields) {
|
|
2810
2811
|
const load_me = {
|
|
2811
2812
|
...me,
|
|
@@ -2829,7 +2830,27 @@ function make_fold_handler(me, flush, config2, patch_fn = bare_patch, sensitive_
|
|
|
2829
2830
|
await flush(rows);
|
|
2830
2831
|
for (const f of flushed) f.dirty = false;
|
|
2831
2832
|
};
|
|
2832
|
-
|
|
2833
|
+
const first_sight = async (stream) => {
|
|
2834
|
+
if (cache2.size >= config2.maxCachedStates) {
|
|
2835
|
+
const oldest = cache2.keys().next().value;
|
|
2836
|
+
const evictee = cache2.get(oldest);
|
|
2837
|
+
if (evictee.dirty) await flush([row(evictee)]);
|
|
2838
|
+
cache2.delete(oldest);
|
|
2839
|
+
}
|
|
2840
|
+
const snapshot = await load(load_me, { stream }, void 0, patch_fn);
|
|
2841
|
+
const seated = {
|
|
2842
|
+
stream,
|
|
2843
|
+
state: snapshot.state,
|
|
2844
|
+
version: snapshot.version,
|
|
2845
|
+
event_id: snapshot.id,
|
|
2846
|
+
patches: snapshot.patches,
|
|
2847
|
+
snaps: snapshot.snaps,
|
|
2848
|
+
dirty: true
|
|
2849
|
+
};
|
|
2850
|
+
cache2.set(stream, seated);
|
|
2851
|
+
return seated;
|
|
2852
|
+
};
|
|
2853
|
+
const handler = async (events) => {
|
|
2833
2854
|
let folded_since_flush = 0;
|
|
2834
2855
|
for (const event of events) {
|
|
2835
2856
|
const stream = event.stream;
|
|
@@ -2838,23 +2859,12 @@ function make_fold_handler(me, flush, config2, patch_fn = bare_patch, sensitive_
|
|
|
2838
2859
|
cache2.delete(stream);
|
|
2839
2860
|
cache2.set(stream, fold);
|
|
2840
2861
|
} else {
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
const snapshot = await load(load_me, { stream }, void 0, patch_fn);
|
|
2848
|
-
fold = {
|
|
2849
|
-
stream,
|
|
2850
|
-
state: snapshot.state,
|
|
2851
|
-
version: snapshot.version,
|
|
2852
|
-
event_id: snapshot.id,
|
|
2853
|
-
patches: snapshot.patches,
|
|
2854
|
-
snaps: snapshot.snaps,
|
|
2855
|
-
dirty: true
|
|
2856
|
-
};
|
|
2857
|
-
cache2.set(stream, fold);
|
|
2862
|
+
fold = await first_sight(stream);
|
|
2863
|
+
}
|
|
2864
|
+
const contiguous = event.version === fold.version + 1;
|
|
2865
|
+
if (event.id > fold.event_id && !contiguous) {
|
|
2866
|
+
cache2.delete(stream);
|
|
2867
|
+
fold = await first_sight(stream);
|
|
2858
2868
|
}
|
|
2859
2869
|
if (event.id > fold.event_id) {
|
|
2860
2870
|
const reducer = me.patch[event.name];
|
|
@@ -2878,6 +2888,8 @@ function make_fold_handler(me, flush, config2, patch_fn = bare_patch, sensitive_
|
|
|
2878
2888
|
}
|
|
2879
2889
|
await flush_dirty();
|
|
2880
2890
|
};
|
|
2891
|
+
Object.defineProperty(handler, FOLD_RESET, { value: () => cache2.clear() });
|
|
2892
|
+
return handler;
|
|
2881
2893
|
}
|
|
2882
2894
|
|
|
2883
2895
|
// src/internal/reactions.ts
|
|
@@ -3010,6 +3022,19 @@ function build_handle_batch(logger) {
|
|
|
3010
3022
|
var SettleLoop = class {
|
|
3011
3023
|
_timer = void 0;
|
|
3012
3024
|
_running = false;
|
|
3025
|
+
/**
|
|
3026
|
+
* Resolves when the cycle currently in flight finishes; `undefined` when
|
|
3027
|
+
* idle (#1468). `_running` answers "is a cycle running?" for the
|
|
3028
|
+
* re-arm bookkeeping; this answers "tell me when it is done" for a
|
|
3029
|
+
* graceful shutdown.
|
|
3030
|
+
*
|
|
3031
|
+
* `stop()` only cancels *scheduling* — a cycle already inside its
|
|
3032
|
+
* correlate → drain loop keeps going, and because `DrainController.drain`
|
|
3033
|
+
* does not consult `_stopped`, it can claim a stream after teardown
|
|
3034
|
+
* returned and after the store adapter was disposed. Never rejects: the
|
|
3035
|
+
* cycle's own `catch` contains its errors, so awaiting this is safe.
|
|
3036
|
+
*/
|
|
3037
|
+
_inflight;
|
|
3013
3038
|
/**
|
|
3014
3039
|
* Set when a `schedule()` timer fires while a cycle is still running
|
|
3015
3040
|
* (ACT-1205). The in-flight cycle's `finally` re-arms one more pass so
|
|
@@ -3049,6 +3074,10 @@ var SettleLoop = class {
|
|
|
3049
3074
|
return;
|
|
3050
3075
|
}
|
|
3051
3076
|
this._running = true;
|
|
3077
|
+
let settle_done;
|
|
3078
|
+
this._inflight = new Promise((done) => {
|
|
3079
|
+
settle_done = done;
|
|
3080
|
+
});
|
|
3052
3081
|
(async () => {
|
|
3053
3082
|
await this._deps.init();
|
|
3054
3083
|
let settled_drain;
|
|
@@ -3074,6 +3103,8 @@ var SettleLoop = class {
|
|
|
3074
3103
|
this._deps.breaker.failed(Date.now(), err);
|
|
3075
3104
|
}).finally(() => {
|
|
3076
3105
|
this._running = false;
|
|
3106
|
+
this._inflight = void 0;
|
|
3107
|
+
settle_done();
|
|
3077
3108
|
const pending = this._pending;
|
|
3078
3109
|
if (pending !== void 0) {
|
|
3079
3110
|
this._pending = void 0;
|
|
@@ -3082,6 +3113,14 @@ var SettleLoop = class {
|
|
|
3082
3113
|
});
|
|
3083
3114
|
}, debounceMs);
|
|
3084
3115
|
}
|
|
3116
|
+
/**
|
|
3117
|
+
* The cycle currently in flight, or `undefined` when idle (#1468). A
|
|
3118
|
+
* graceful shutdown awaits this alongside the drain controllers so a
|
|
3119
|
+
* settle parked in `correlate` does not resume after teardown.
|
|
3120
|
+
*/
|
|
3121
|
+
get inflight() {
|
|
3122
|
+
return this._inflight;
|
|
3123
|
+
}
|
|
3085
3124
|
/** Cancel any pending or active settle cycle. Idempotent. */
|
|
3086
3125
|
stop() {
|
|
3087
3126
|
this._pending = void 0;
|
|
@@ -3624,10 +3663,12 @@ var Act = class {
|
|
|
3624
3663
|
const running = [...this._drain_controllers.values()].filter(
|
|
3625
3664
|
(c) => c.inflight !== void 0
|
|
3626
3665
|
);
|
|
3627
|
-
|
|
3666
|
+
const settling = this._settle.inflight;
|
|
3667
|
+
if (running.length === 0 && !settling) return;
|
|
3628
3668
|
const grace = grace_ms ?? this._derive_grace_ms(running);
|
|
3629
3669
|
if (grace <= 0) return;
|
|
3630
3670
|
const inflight = running.map((c) => c.inflight);
|
|
3671
|
+
if (settling) inflight.push(settling);
|
|
3631
3672
|
let timer;
|
|
3632
3673
|
const budget = new Promise((resolve) => {
|
|
3633
3674
|
timer = setTimeout(resolve, grace);
|
|
@@ -4179,6 +4220,8 @@ var Act = class {
|
|
|
4179
4220
|
async reset(input) {
|
|
4180
4221
|
return this._scoped(async () => {
|
|
4181
4222
|
const count = await store().reset(input);
|
|
4223
|
+
for (const handler of this._batch_handlers.values())
|
|
4224
|
+
handler[FOLD_RESET]?.();
|
|
4182
4225
|
if (count > 0 && this._reactive_events.size > 0) this._arm_all();
|
|
4183
4226
|
return count;
|
|
4184
4227
|
});
|
|
@@ -4577,17 +4620,30 @@ function act() {
|
|
|
4577
4620
|
autoclose_archiver: (state_name) => _aa.get(state_name) ?? null
|
|
4578
4621
|
};
|
|
4579
4622
|
const pending_projections = [];
|
|
4623
|
+
const projection_reactions = /* @__PURE__ */ new Set();
|
|
4624
|
+
const record_projection_reactions = (proj) => {
|
|
4625
|
+
for (const register of Object.values(
|
|
4626
|
+
proj.events
|
|
4627
|
+
))
|
|
4628
|
+
for (const reaction of register.reactions.values())
|
|
4629
|
+
projection_reactions.add(reaction);
|
|
4630
|
+
};
|
|
4580
4631
|
const fold_projections = [];
|
|
4581
4632
|
const batch_handlers = /* @__PURE__ */ new Map();
|
|
4582
4633
|
const fold_specs = [];
|
|
4583
4634
|
const lanes = [];
|
|
4584
4635
|
let _built = false;
|
|
4585
|
-
const pii_wrap = (original) =>
|
|
4586
|
-
const
|
|
4587
|
-
const
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4636
|
+
const pii_wrap = (original) => {
|
|
4637
|
+
const wrapped = async (events, stream) => {
|
|
4638
|
+
const stripped = events.map((e) => {
|
|
4639
|
+
const f = _sf.get(e.name);
|
|
4640
|
+
return f ? pii_strip(e, f) : e;
|
|
4641
|
+
});
|
|
4642
|
+
return original(stripped, stream);
|
|
4643
|
+
};
|
|
4644
|
+
const reset = original[FOLD_RESET];
|
|
4645
|
+
if (reset) Object.defineProperty(wrapped, FOLD_RESET, { value: reset });
|
|
4646
|
+
return wrapped;
|
|
4591
4647
|
};
|
|
4592
4648
|
const make_batch_handlers = (patch_fn) => {
|
|
4593
4649
|
const handlers = new Map(batch_handlers);
|
|
@@ -4668,6 +4724,7 @@ function act() {
|
|
|
4668
4724
|
return builder;
|
|
4669
4725
|
},
|
|
4670
4726
|
withProjection: (proj) => {
|
|
4727
|
+
record_projection_reactions(proj);
|
|
4671
4728
|
merge_projection(proj, registry.events);
|
|
4672
4729
|
register_batch_handler(proj, batch_handlers);
|
|
4673
4730
|
if (proj.fold)
|
|
@@ -4685,6 +4742,7 @@ function act() {
|
|
|
4685
4742
|
const patch_fn = options?.validateFoldedState === true ? validating_patch : bare_patch;
|
|
4686
4743
|
if (!_built) {
|
|
4687
4744
|
for (const proj of pending_projections) {
|
|
4745
|
+
record_projection_reactions(proj);
|
|
4688
4746
|
merge_projection(proj, registry.events);
|
|
4689
4747
|
register_batch_handler(proj, batch_handlers);
|
|
4690
4748
|
if (proj.fold) fold_projections.push(proj);
|
|
@@ -4703,17 +4761,31 @@ function act() {
|
|
|
4703
4761
|
throw new Error(
|
|
4704
4762
|
`State projection "${proj.target}" of "${fold.name}" is missing events ${missing.join(", ")} \u2014 pass every partial of the state to .of()`
|
|
4705
4763
|
);
|
|
4706
|
-
|
|
4764
|
+
const claimed = fold_specs.find((s) => s.target === proj.target);
|
|
4765
|
+
if (claimed?.projection === proj) continue;
|
|
4766
|
+
if (batch_handlers.has(proj.target) || claimed)
|
|
4707
4767
|
throw new Error(
|
|
4708
4768
|
`Duplicate projection target "${proj.target}" \u2014 a target is served by one batch handler or one state projection, never both`
|
|
4709
4769
|
);
|
|
4710
4770
|
fold_specs.push({
|
|
4711
4771
|
target: proj.target,
|
|
4772
|
+
projection: proj,
|
|
4712
4773
|
merged,
|
|
4713
4774
|
flush: fold.flush,
|
|
4714
4775
|
config: fold.config
|
|
4715
4776
|
});
|
|
4716
4777
|
}
|
|
4778
|
+
for (const register of Object.values(registry.events)) {
|
|
4779
|
+
for (const reaction of register.reactions.values()) {
|
|
4780
|
+
if (typeof reaction.resolver === "function") continue;
|
|
4781
|
+
const target = reaction.resolver.target;
|
|
4782
|
+
const claimed = batch_handlers.has(target) || fold_specs.some((f) => f.target === target);
|
|
4783
|
+
if (claimed && !projection_reactions.has(reaction))
|
|
4784
|
+
throw new Error(
|
|
4785
|
+
`Reaction on target "${target}" conflicts with the projection that already serves it \u2014 a target is served by one batch handler or one state projection, and a reaction to it would never run`
|
|
4786
|
+
);
|
|
4787
|
+
}
|
|
4788
|
+
}
|
|
4717
4789
|
finalize_deprecations();
|
|
4718
4790
|
validate_lane_references(registry, lanes);
|
|
4719
4791
|
for (const [event_name, reg] of Object.entries(
|