@yoltra/core 0.7.0 → 0.8.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/README.es.md +127 -11
- package/README.md +126 -12
- package/dist/types/index.d.ts +2 -1
- package/dist/types/persistence/persist.d.ts +2 -2
- package/dist/types/store/Store.d.ts +368 -14
- package/dist/types/store/fingerprint.d.ts +62 -0
- package/dist/types/types.d.ts +524 -14
- package/dist/yoltra.cjs +2 -2
- package/dist/yoltra.cjs.map +1 -1
- package/dist/yoltra.mjs +1668 -880
- package/dist/yoltra.mjs.map +1 -1
- package/dist/yoltra.umd.js +2 -2
- package/dist/yoltra.umd.js.map +1 -1
- package/package.json +3 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Event, EventMapBase, EventKey, EventUnion, Change, DeepReadonly, EffectSpec, EventMeta, MiddlewareInput, ReducersMapAny, ReducerSpec, StateFromReducers, StoreInstance, StoreSpec, Unsubscribe, EMFromReducersStrict, Emit, EmitOptions, EmitResult, ConnectOptions, InstrumentationObserver, CascadeInfo, EventPhase, NarrowedEventHandler, When } from '../types.js';
|
|
1
|
+
import { Event, EventMapBase, EventKey, EventUnion, Change, DeepReadonly, EffectSpec, EventMeta, MiddlewareInput, ReducersMapAny, ReducerSpec, StateFromReducers, StoreInstance, StoreSpec, Unsubscribe, EMFromReducersStrict, Emit, EmitOptions, EmitResult, ConnectOptions, InstrumentationObserver, CascadeInfo, EventPhase, Origin, RegistrationObserver, ReplaceScope, NotifiedPhase, NarrowedEventHandler, When } from '../types.js';
|
|
2
2
|
import { CallHandle, CallOptions } from './call.js';
|
|
3
3
|
import { Rejection } from './rejection.js';
|
|
4
4
|
export declare class Store<EM extends EventMapBase, R extends string, S extends Record<R, any>> implements StoreInstance<R, S, EM> {
|
|
@@ -11,7 +11,9 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
|
|
|
11
11
|
/**
|
|
12
12
|
* Registered middleware pipeline (run **before** reducers).
|
|
13
13
|
* Stores either raw functions (legacy) or MiddlewareSpec objects.
|
|
14
|
-
*
|
|
14
|
+
*
|
|
15
|
+
* Only an explicit `false` stops propagation. Returning nothing allows the event, so
|
|
16
|
+
* middleware that only logs or measures needs no `return` at all.
|
|
15
17
|
*
|
|
16
18
|
* @internal
|
|
17
19
|
*/
|
|
@@ -95,6 +97,17 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
|
|
|
95
97
|
*/
|
|
96
98
|
private readonly writtenEventSubscribers;
|
|
97
99
|
private readonly allEventSubscribers;
|
|
100
|
+
/**
|
|
101
|
+
* True while a devtools time-travel is applying a snapshot or replaying events.
|
|
102
|
+
*
|
|
103
|
+
* @remarks
|
|
104
|
+
* Saved and restored rather than set and cleared to `false`: `__replayEvents` calls
|
|
105
|
+
* `__applyExternalState` as its first step, so clearing on the inner call's way out would
|
|
106
|
+
* unset the flag for the entire event loop that follows it.
|
|
107
|
+
*
|
|
108
|
+
* @internal
|
|
109
|
+
*/
|
|
110
|
+
private replaying;
|
|
98
111
|
/**
|
|
99
112
|
* Track reducerBus unsubs per slice for HMR/register/unregister.
|
|
100
113
|
*
|
|
@@ -109,6 +122,70 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
|
|
|
109
122
|
* @internal
|
|
110
123
|
*/
|
|
111
124
|
private readonly patternReducers;
|
|
125
|
+
/**
|
|
126
|
+
* Where each mounted slice came from. See {@link Origin}.
|
|
127
|
+
*
|
|
128
|
+
* @remarks
|
|
129
|
+
* This is what makes `replace*` mean "replace mine" rather than "replace everything". It
|
|
130
|
+
* is written by `mountSlice` and never by a caller.
|
|
131
|
+
*
|
|
132
|
+
* @internal
|
|
133
|
+
*/
|
|
134
|
+
private readonly sliceOrigin;
|
|
135
|
+
/**
|
|
136
|
+
* Who claims each dynamically mounted slice, for introspection only.
|
|
137
|
+
*
|
|
138
|
+
* @remarks
|
|
139
|
+
* Surfaced in `__devtoolsIntrospect()` and named in the collision error. **Never read by
|
|
140
|
+
* `replace*`.** Correctness comes from {@link Origin}, which nobody has to remember to
|
|
141
|
+
* pass; if preservation depended on this string, forgetting it would silently delete a
|
|
142
|
+
* library's state.
|
|
143
|
+
*
|
|
144
|
+
* @internal
|
|
145
|
+
*/
|
|
146
|
+
private readonly sliceOwner;
|
|
147
|
+
/**
|
|
148
|
+
* Slices unmounted by their owner, for a development-time diagnostic.
|
|
149
|
+
*
|
|
150
|
+
* @remarks
|
|
151
|
+
* Decoration re-types the store, and after a disposer runs the widened type still claims
|
|
152
|
+
* a slice that is gone. Reading it would hand a component `undefined` from a type that
|
|
153
|
+
* promised a value, which is the silent failure this whole feature exists to remove.
|
|
154
|
+
* Populated only for `dynamic` and `internal` slices: a `spec` slice removed by a
|
|
155
|
+
* `replace*` was not promised by anyone's widened type.
|
|
156
|
+
*
|
|
157
|
+
* Bounded, because a long development session that mounts and disposes repeatedly would
|
|
158
|
+
* otherwise accumulate an entry per cycle forever. The oldest is dropped: the diagnostic
|
|
159
|
+
* exists for a slice someone has just stopped using, and a name disposed hundreds of
|
|
160
|
+
* mounts ago is not the one being read by mistake. Maps to the owner, or `undefined`
|
|
161
|
+
* when the library did not name itself.
|
|
162
|
+
*
|
|
163
|
+
* @internal
|
|
164
|
+
*/
|
|
165
|
+
private readonly disposedSlices;
|
|
166
|
+
/** @internal */
|
|
167
|
+
private readonly registrationObservers;
|
|
168
|
+
/**
|
|
169
|
+
* Changes accumulated inside the current public call, flushed once at its end.
|
|
170
|
+
*
|
|
171
|
+
* @internal
|
|
172
|
+
*/
|
|
173
|
+
private pendingRegistrationChanges;
|
|
174
|
+
/**
|
|
175
|
+
* Depth of nested registration transactions.
|
|
176
|
+
*
|
|
177
|
+
* @remarks
|
|
178
|
+
* `hotReplace` calls three `replace*` methods, and each of those registers repeatedly.
|
|
179
|
+
* Only the outermost public entry point flushes, so one `hotReplace` produces one batch
|
|
180
|
+
* spanning all three kinds rather than three batches of intermediate topology.
|
|
181
|
+
*
|
|
182
|
+
* @internal
|
|
183
|
+
*/
|
|
184
|
+
private registrationDepth;
|
|
185
|
+
/** True while observers are being notified, so a re-entrant change is queued. @internal */
|
|
186
|
+
private notifyingRegistrations;
|
|
187
|
+
/** Batches produced by an observer's own registrations, drained after the current one. @internal */
|
|
188
|
+
private readonly queuedRegistrationBatches;
|
|
112
189
|
/**
|
|
113
190
|
* Whether `__replayEvents()` is allowed.
|
|
114
191
|
* Set from `spec.devtools.allowReplay`.
|
|
@@ -135,6 +212,8 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
|
|
|
135
212
|
* signal that a reducer misbehaved.
|
|
136
213
|
*/
|
|
137
214
|
private readonly onReducerError?;
|
|
215
|
+
/** @internal */
|
|
216
|
+
private readonly onSubscriberError?;
|
|
138
217
|
/**
|
|
139
218
|
* `slice:channel:type` combinations already warned about for payload aliasing.
|
|
140
219
|
*
|
|
@@ -224,13 +303,15 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
|
|
|
224
303
|
* Tracks processed events by fingerprint with timestamps for TTL-based deduplication.
|
|
225
304
|
*
|
|
226
305
|
* **Deduplication Behavior:**
|
|
227
|
-
* - Events are fingerprinted
|
|
306
|
+
* - Events are fingerprinted through the codec, so `Map`, `Set`, `Date`, `BigInt`, binary
|
|
307
|
+
* and cyclic payloads all compare by content rather than collapsing to `{}`
|
|
308
|
+
* - Plain-object keys are sorted, so key order is not content; array and `Map` order is
|
|
228
309
|
* - If an identical fingerprint is seen within the dedup window, it's skipped
|
|
229
|
-
* - The window is
|
|
310
|
+
* - The window is `dedupWindowMs`, which defaults to `0` (dedup off)
|
|
230
311
|
*
|
|
231
312
|
* **Limitations:**
|
|
232
|
-
* -
|
|
233
|
-
*
|
|
313
|
+
* - A payload larger than the fingerprint node budget is never deduplicated, which is the
|
|
314
|
+
* safe direction: a missed dedup costs a duplicate, a false one drops a real event
|
|
234
315
|
* - Legitimate rapid-fire identical events may be incorrectly deduplicated
|
|
235
316
|
* - The cache is bounded to 1000 entries with lazy pruning
|
|
236
317
|
*
|
|
@@ -479,17 +560,21 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
|
|
|
479
560
|
reducers: {
|
|
480
561
|
name: string;
|
|
481
562
|
when: When<EM> | undefined;
|
|
563
|
+
origin: Origin;
|
|
564
|
+
owner: string | undefined;
|
|
482
565
|
}[];
|
|
483
566
|
effects: {
|
|
484
567
|
channel: string;
|
|
485
568
|
type: string;
|
|
486
569
|
name?: string;
|
|
487
570
|
description?: string;
|
|
571
|
+
origin: Origin;
|
|
488
572
|
}[];
|
|
489
573
|
middleware: {
|
|
490
574
|
name?: string;
|
|
491
575
|
description?: string;
|
|
492
576
|
when?: unknown;
|
|
577
|
+
origin: Origin;
|
|
493
578
|
}[];
|
|
494
579
|
atomic: {
|
|
495
580
|
reducer: string;
|
|
@@ -499,6 +584,7 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
|
|
|
499
584
|
channel: string;
|
|
500
585
|
type: string;
|
|
501
586
|
phase: string;
|
|
587
|
+
duringReplay: boolean;
|
|
502
588
|
}[];
|
|
503
589
|
coarse: number;
|
|
504
590
|
dedupHits: number;
|
|
@@ -520,6 +606,12 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
|
|
|
520
606
|
* @internal
|
|
521
607
|
*/
|
|
522
608
|
__applyExternalState(nextPlain: any): void;
|
|
609
|
+
/**
|
|
610
|
+
* The body of {@link __applyExternalState}, with the replay flag already set.
|
|
611
|
+
*
|
|
612
|
+
* @internal
|
|
613
|
+
*/
|
|
614
|
+
private applyExternalStateInner;
|
|
523
615
|
/**
|
|
524
616
|
* Replays a sequence of events from a snapshot through reducers and event
|
|
525
617
|
* subscribers ONLY. Skips dedup, middleware, and effects.
|
|
@@ -539,6 +631,12 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
|
|
|
539
631
|
id: string;
|
|
540
632
|
meta?: EventMeta;
|
|
541
633
|
}>): void;
|
|
634
|
+
/**
|
|
635
|
+
* The body of {@link __replayEvents}, with the replay flag already set.
|
|
636
|
+
*
|
|
637
|
+
* @internal
|
|
638
|
+
*/
|
|
639
|
+
private replayEventsInner;
|
|
542
640
|
/**
|
|
543
641
|
* Emits a typed event `(channel, type, payload)`.
|
|
544
642
|
* Events are queued and processed **sequentially** (FIFO).
|
|
@@ -674,6 +772,80 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
|
|
|
674
772
|
reducer: R;
|
|
675
773
|
property: string;
|
|
676
774
|
}, h: (chg: Change) => void, options?: ConnectOptions): () => void;
|
|
775
|
+
/**
|
|
776
|
+
* Subscribe to reducer, middleware and effect registrations.
|
|
777
|
+
*
|
|
778
|
+
* Delivered as an array, one batch per public call: `replaceReducers` unmounts and then
|
|
779
|
+
* remounts, and a per-change observer would see a spurious unmount of a slice that is only
|
|
780
|
+
* being updated. Observers run after the state broadcast, and a registration made by an
|
|
781
|
+
* observer is queued rather than delivered re-entrantly.
|
|
782
|
+
*
|
|
783
|
+
* Replay never produces a change, so there is no `duringReplay` option here. `dispose()`
|
|
784
|
+
* fires nothing.
|
|
785
|
+
*
|
|
786
|
+
* @param observer - Receives one batch per registration change.
|
|
787
|
+
* @param options - `emitCurrent` synthesizes a `'mounted'` batch for everything already
|
|
788
|
+
* installed, delivered synchronously before this call returns, carrying each registration's
|
|
789
|
+
* real origin rather than a synthetic marker.
|
|
790
|
+
* @returns Unsubscribe function.
|
|
791
|
+
*
|
|
792
|
+
* @example
|
|
793
|
+
* ```ts
|
|
794
|
+
* const off = store.onRegistrationChange((changes) => {
|
|
795
|
+
* for (const c of changes) {
|
|
796
|
+
* console.log(c.op, c.kind, c.name, c.origin);
|
|
797
|
+
* }
|
|
798
|
+
* }, { emitCurrent: true });
|
|
799
|
+
* off();
|
|
800
|
+
* ```
|
|
801
|
+
*
|
|
802
|
+
* @public
|
|
803
|
+
*/
|
|
804
|
+
onRegistrationChange(observer: RegistrationObserver<EM>, options?: {
|
|
805
|
+
emitCurrent?: boolean;
|
|
806
|
+
}): Unsubscribe;
|
|
807
|
+
/**
|
|
808
|
+
* Everything currently installed, as `"mounted"` changes.
|
|
809
|
+
*
|
|
810
|
+
* @internal
|
|
811
|
+
*/
|
|
812
|
+
private describeCurrentRegistrations;
|
|
813
|
+
/**
|
|
814
|
+
* Runs `fn` as one registration transaction, flushing a single batch at the outermost end.
|
|
815
|
+
*
|
|
816
|
+
* @internal
|
|
817
|
+
*/
|
|
818
|
+
private inRegistrationTransaction;
|
|
819
|
+
/**
|
|
820
|
+
* Records a change, to be delivered when the current transaction ends.
|
|
821
|
+
*
|
|
822
|
+
* @remarks
|
|
823
|
+
* Guarded on observer count **before** anything is allocated. These call sites run inside
|
|
824
|
+
* `createStore`, so a twenty-slice store with nobody listening must build no objects and
|
|
825
|
+
* no arrays at all - the same discipline `emitInstrumentation` already follows.
|
|
826
|
+
*
|
|
827
|
+
* @internal
|
|
828
|
+
*/
|
|
829
|
+
private recordRegistrationChange;
|
|
830
|
+
/** @internal */
|
|
831
|
+
private flushRegistrationChanges;
|
|
832
|
+
/**
|
|
833
|
+
* Delivers batches an observer produced while being notified.
|
|
834
|
+
*
|
|
835
|
+
* @remarks
|
|
836
|
+
* Bounded like the reduce depth, and for the same reason: two observers registering in
|
|
837
|
+
* response to each other would otherwise loop forever. Logged rather than thrown - the
|
|
838
|
+
* topology is correct at that point, and throwing would truncate the stream *and* unwind a
|
|
839
|
+
* caller that did nothing wrong.
|
|
840
|
+
*
|
|
841
|
+
* @internal
|
|
842
|
+
*/
|
|
843
|
+
private drainQueuedRegistrationBatches;
|
|
844
|
+
/** @internal */
|
|
845
|
+
private deliverRegistrationBatch;
|
|
846
|
+
/** @internal */
|
|
847
|
+
private invokeRegistrationObserver;
|
|
848
|
+
get isReplaying(): boolean;
|
|
677
849
|
/**
|
|
678
850
|
* Subscribe to events by channel and type.
|
|
679
851
|
*
|
|
@@ -685,8 +857,15 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
|
|
|
685
857
|
* - `'committed'` (default): Events that passed middleware and reached reducers.
|
|
686
858
|
* Notified after reducers, before effects.
|
|
687
859
|
* - `'uncommitted'`: Events rejected by middleware. Notified immediately after rejection.
|
|
860
|
+
* - `'written'`: Events that actually changed state. Stricter than `committed`, which
|
|
861
|
+
* fires for every event a store accepts including one with no reducers at all.
|
|
688
862
|
* - `'all'`: Both committed and uncommitted events. Handler receives the phase parameter
|
|
689
|
-
* to distinguish between the two.
|
|
863
|
+
* to distinguish between the two. Deliberately not `written` as well: an event that
|
|
864
|
+
* writes is also committed, so folding it in would notify every existing `all`
|
|
865
|
+
* subscriber twice for one event.
|
|
866
|
+
*
|
|
867
|
+
* **Replay:** a handler is not called while devtools is replaying, unless it opted in with
|
|
868
|
+
* `{ duringReplay: true }`.
|
|
690
869
|
*
|
|
691
870
|
* @typeParam C - Channel key within `EM`.
|
|
692
871
|
* @typeParam T - Event type key within channel `C`.
|
|
@@ -720,7 +899,9 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
|
|
|
720
899
|
*
|
|
721
900
|
* @public
|
|
722
901
|
*/
|
|
723
|
-
onEvent<C extends keyof EM & string, T extends keyof EM[C] & string>(channel: C, type: T, handler: NarrowedEventHandler<DeepReadonly<S>, EM, C, T>, phase?: EventPhase
|
|
902
|
+
onEvent<C extends keyof EM & string, T extends keyof EM[C] & string>(channel: C, type: T, handler: NarrowedEventHandler<DeepReadonly<S>, EM, C, T>, phase?: EventPhase, options?: {
|
|
903
|
+
duringReplay?: boolean;
|
|
904
|
+
}): Unsubscribe;
|
|
724
905
|
/**
|
|
725
906
|
* Subscribes to **coarse-grained** commits (called once per successful event, only if state changed).
|
|
726
907
|
*
|
|
@@ -787,7 +968,50 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
|
|
|
787
968
|
*
|
|
788
969
|
* @public
|
|
789
970
|
*/
|
|
790
|
-
registerMiddleware(mw: MiddlewareInput<DeepReadonly<S>, EM>):
|
|
971
|
+
registerMiddleware(mw: MiddlewareInput<DeepReadonly<S>, EM>): any;
|
|
972
|
+
/**
|
|
973
|
+
* Turns a disposer into the callable object `register*` returns.
|
|
974
|
+
*
|
|
975
|
+
* @remarks
|
|
976
|
+
* `Object.assign` onto the function rather than a new object, so every existing call site
|
|
977
|
+
* keeps working verbatim: `const off = store.registerEffect(spec); off();` compiles and
|
|
978
|
+
* runs exactly as before, while `.store` and `.dispose` become available to a library that
|
|
979
|
+
* wants the widened type.
|
|
980
|
+
*
|
|
981
|
+
* A callable object rather than an overload or a second method: an overload cannot change
|
|
982
|
+
* the return shape based on nothing, and a parallel `registerSliceX` family would leave
|
|
983
|
+
* the originals permanently second class and force libraries to branch on the core version.
|
|
984
|
+
*
|
|
985
|
+
* @internal
|
|
986
|
+
*/
|
|
987
|
+
private recordMiddlewareChange;
|
|
988
|
+
/**
|
|
989
|
+
* Drops an effect's metadata only once nothing is still registered with it.
|
|
990
|
+
*
|
|
991
|
+
* @remarks
|
|
992
|
+
* `effectMeta` is keyed by the effect *function*, and the same function can legitimately
|
|
993
|
+
* back several registrations. Deleting on the first disposal stripped the name and
|
|
994
|
+
* description of the ones still live, which a devtools panel then showed as unnamed.
|
|
995
|
+
*
|
|
996
|
+
* @internal
|
|
997
|
+
*/
|
|
998
|
+
private releaseEffectMeta;
|
|
999
|
+
/** @internal */
|
|
1000
|
+
private recordEffectChange;
|
|
1001
|
+
/** @internal */
|
|
1002
|
+
private asRegistration;
|
|
1003
|
+
/**
|
|
1004
|
+
* The one place the widening cast lives.
|
|
1005
|
+
*
|
|
1006
|
+
* @remarks
|
|
1007
|
+
* Decoration is type-level only. This returns the **same runtime object**: subscriptions,
|
|
1008
|
+
* effects, middleware, the dedup cache, both buses and any in-flight `call()` are
|
|
1009
|
+
* untouched, and nothing re-subscribes. Keeping the cast here means no call site needs one,
|
|
1010
|
+
* which is the entire point of the feature.
|
|
1011
|
+
*
|
|
1012
|
+
* @internal
|
|
1013
|
+
*/
|
|
1014
|
+
private widened;
|
|
791
1015
|
/**
|
|
792
1016
|
* Dynamically **adds** a named slice reducer at runtime.
|
|
793
1017
|
*
|
|
@@ -810,7 +1034,50 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
|
|
|
810
1034
|
*
|
|
811
1035
|
* @public
|
|
812
1036
|
*/
|
|
813
|
-
registerReducer(name: string, spec: ReducerSpec<any, EM
|
|
1037
|
+
registerReducer(name: string, spec: ReducerSpec<any, EM>, options?: {
|
|
1038
|
+
owner?: string;
|
|
1039
|
+
}): any;
|
|
1040
|
+
/**
|
|
1041
|
+
* Mounts a slice and hands back the widened store alongside a disposer.
|
|
1042
|
+
*
|
|
1043
|
+
* @remarks
|
|
1044
|
+
* The name `registerSlice` is what the guide uses; `registerReducer` keeps its broader
|
|
1045
|
+
* `name: string` signature and delegates here, so existing call sites are untouched.
|
|
1046
|
+
*
|
|
1047
|
+
* @public
|
|
1048
|
+
*/
|
|
1049
|
+
registerSlice(name: string, spec: ReducerSpec<any, EM>, options?: {
|
|
1050
|
+
owner?: string;
|
|
1051
|
+
}): any;
|
|
1052
|
+
/** @internal */
|
|
1053
|
+
private registerSliceInner;
|
|
1054
|
+
/**
|
|
1055
|
+
* Mounts a slice and returns the widened store, for chaining.
|
|
1056
|
+
*
|
|
1057
|
+
* @remarks
|
|
1058
|
+
* No disposer, deliberately. After a disposer runs, the widened type still promises a
|
|
1059
|
+
* slice that is gone, and TypeScript cannot express "valid until that call". The chaining
|
|
1060
|
+
* API therefore does not hand one out, so the footgun does not exist on the path most
|
|
1061
|
+
* people take; {@link registerSlice} carries one for the library that owns the slice, and
|
|
1062
|
+
* the documented rule is that a disposer stays library-private.
|
|
1063
|
+
*
|
|
1064
|
+
* @public
|
|
1065
|
+
*/
|
|
1066
|
+
withSlice(name: string, spec: ReducerSpec<any, EM>, options?: {
|
|
1067
|
+
owner?: string;
|
|
1068
|
+
}): any;
|
|
1069
|
+
/**
|
|
1070
|
+
* Registers middleware and returns the widened store, for chaining.
|
|
1071
|
+
*
|
|
1072
|
+
* @public
|
|
1073
|
+
*/
|
|
1074
|
+
withMiddleware(mw: MiddlewareInput<DeepReadonly<S>, EM>): any;
|
|
1075
|
+
/**
|
|
1076
|
+
* Registers an effect and returns the widened store, for chaining.
|
|
1077
|
+
*
|
|
1078
|
+
* @public
|
|
1079
|
+
*/
|
|
1080
|
+
withEffect(spec: EffectSpec<DeepReadonly<S>, EM>): any;
|
|
814
1081
|
/**
|
|
815
1082
|
* Registers an **effect** (stateless async event consumer) that runs after reducers.
|
|
816
1083
|
*
|
|
@@ -923,7 +1190,17 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
|
|
|
923
1190
|
* @public
|
|
924
1191
|
*/
|
|
925
1192
|
call<C extends keyof EM & string, T extends keyof EM[C] & string>(channel: C, type: T, payload: EM[C][T], opts: CallOptions<EM>): CallHandle<EventUnion<EM>, EventUnion<EM>>;
|
|
926
|
-
registerEffect(spec: EffectSpec<DeepReadonly<S>, EM>):
|
|
1193
|
+
registerEffect(spec: EffectSpec<DeepReadonly<S>, EM>): any;
|
|
1194
|
+
/**
|
|
1195
|
+
* {@link registerEffect}, with the provenance the caller cannot set.
|
|
1196
|
+
*
|
|
1197
|
+
* @remarks
|
|
1198
|
+
* Kept private so no origin parameter leaks into `StoreInstance`. Three callers: the
|
|
1199
|
+
* constructor (`spec`), the public method (`dynamic`), and `store.call()` (`internal`).
|
|
1200
|
+
*
|
|
1201
|
+
* @internal
|
|
1202
|
+
*/
|
|
1203
|
+
private registerEffectWithOrigin;
|
|
927
1204
|
/**
|
|
928
1205
|
* Convenience helper to register an **effect** filtered by a single `(channel, type)` pair.
|
|
929
1206
|
*
|
|
@@ -962,7 +1239,11 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
|
|
|
962
1239
|
*
|
|
963
1240
|
* @public
|
|
964
1241
|
*/
|
|
965
|
-
replaceMiddleware(next: MiddlewareInput<DeepReadonly<S>, EM>[]
|
|
1242
|
+
replaceMiddleware(next: MiddlewareInput<DeepReadonly<S>, EM>[], opts?: {
|
|
1243
|
+
scope?: ReplaceScope;
|
|
1244
|
+
}): void;
|
|
1245
|
+
/** @internal */
|
|
1246
|
+
private replaceMiddlewareInner;
|
|
966
1247
|
/**
|
|
967
1248
|
* Replaces all registered **effects** (HMR-friendly).
|
|
968
1249
|
*
|
|
@@ -979,12 +1260,24 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
|
|
|
979
1260
|
*
|
|
980
1261
|
* @public
|
|
981
1262
|
*/
|
|
982
|
-
replaceEffects(next: Array<EffectSpec<DeepReadonly<S>, EM
|
|
1263
|
+
replaceEffects(next: Array<EffectSpec<DeepReadonly<S>, EM>>, opts?: {
|
|
1264
|
+
scope?: ReplaceScope;
|
|
1265
|
+
}): void;
|
|
1266
|
+
/** @internal */
|
|
1267
|
+
private replaceEffectsInner;
|
|
983
1268
|
/**
|
|
984
1269
|
* Replaces the entire **reducer set** (HMR-friendly).
|
|
985
1270
|
*
|
|
986
1271
|
* @param next - Map of slice specs keyed by slice name.
|
|
987
|
-
* @param opts - `{ preserveState?: boolean }` (default `true`)
|
|
1272
|
+
* @param opts - `{ preserveState?: boolean }` (default `true`) and
|
|
1273
|
+
* `{ scope?: "spec" | "all" }` (default `"spec"`).
|
|
1274
|
+
*
|
|
1275
|
+
* @remarks
|
|
1276
|
+
* Replaces **spec-provenance slices only**. A slice mounted after construction with
|
|
1277
|
+
* `registerSlice` survives, along with its state: it was never part of the set this call
|
|
1278
|
+
* is replacing. Pass `{ scope: "all" }` for the pre-0.8.0 wholesale behaviour.
|
|
1279
|
+
*
|
|
1280
|
+
* Throws, before mutating anything, if `next` names a slice a library mounted at runtime.
|
|
988
1281
|
*
|
|
989
1282
|
* @example Hot module replacement
|
|
990
1283
|
* ```ts
|
|
@@ -999,7 +1292,34 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
|
|
|
999
1292
|
*/
|
|
1000
1293
|
replaceReducers(next: Record<R, ReducerSpec<S[R], EM>>, opts?: {
|
|
1001
1294
|
preserveState?: boolean;
|
|
1295
|
+
scope?: ReplaceScope;
|
|
1002
1296
|
}): void;
|
|
1297
|
+
/** @internal */
|
|
1298
|
+
private replaceReducersInner;
|
|
1299
|
+
/**
|
|
1300
|
+
* Refuses, before anything is mutated, to take over a slice mounted at runtime.
|
|
1301
|
+
*
|
|
1302
|
+
* @remarks
|
|
1303
|
+
* An application authoring a slice a library owns is a real mistake, and a silent takeover
|
|
1304
|
+
* is the worst available outcome: the library keeps a disposer for a slice that is no
|
|
1305
|
+
* longer its own. Throwing part-way through would be worse still, which is why this runs
|
|
1306
|
+
* as a pre-flight and why `hotReplace` calls it before swapping anything at all.
|
|
1307
|
+
*
|
|
1308
|
+
* @internal
|
|
1309
|
+
*/
|
|
1310
|
+
private assertNoSliceCollision;
|
|
1311
|
+
/**
|
|
1312
|
+
* Says what a `replace*` call left alone, when it left anything alone.
|
|
1313
|
+
*
|
|
1314
|
+
* @remarks
|
|
1315
|
+
* Development only, and silent unless something was actually preserved, so the normal HMR
|
|
1316
|
+
* path stays quiet. `console.debug` rather than `warn`: this is correct operation, and
|
|
1317
|
+
* every existing `warn` in this file marks a genuine problem. It exists so "why is that
|
|
1318
|
+
* effect still firing after a reload" has an answer that does not require reading core.
|
|
1319
|
+
*
|
|
1320
|
+
* @internal
|
|
1321
|
+
*/
|
|
1322
|
+
private reportPreserved;
|
|
1003
1323
|
/**
|
|
1004
1324
|
* Convenience API to replace **any subset** of store parts (HMR patterns).
|
|
1005
1325
|
*
|
|
@@ -1022,6 +1342,7 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
|
|
|
1022
1342
|
middleware?: MiddlewareInput<DeepReadonly<S>, EM>[];
|
|
1023
1343
|
effects?: Array<EffectSpec<DeepReadonly<S>, EM>>;
|
|
1024
1344
|
preserveState?: boolean;
|
|
1345
|
+
scope?: ReplaceScope;
|
|
1025
1346
|
}): void;
|
|
1026
1347
|
/**
|
|
1027
1348
|
* Mounts a slice: installs reducer, initializes state (unless preserved),
|
|
@@ -1034,6 +1355,37 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
|
|
|
1034
1355
|
* @internal
|
|
1035
1356
|
*/
|
|
1036
1357
|
private mountSlice;
|
|
1358
|
+
/** @internal */
|
|
1359
|
+
private mountSliceInner;
|
|
1360
|
+
/**
|
|
1361
|
+
* Records a slice mount or unmount for {@link onRegistrationChange}.
|
|
1362
|
+
*
|
|
1363
|
+
* @internal
|
|
1364
|
+
*/
|
|
1365
|
+
private recordSliceChange;
|
|
1366
|
+
/**
|
|
1367
|
+
* Announces a newly mounted slice on the connector bus.
|
|
1368
|
+
*
|
|
1369
|
+
* @remarks
|
|
1370
|
+
* `registerReducer` has always broadcast to `listeners`, which wakes `subscribe` and so
|
|
1371
|
+
* `useSelector`. It emitted **nothing** on `connectorBus`, which is what `connect` rides,
|
|
1372
|
+
* so `useAtomicProp`, `useAtomicProps` and the Suspense hooks never woke for a slice
|
|
1373
|
+
* mounted after creation: a component subscribed to a path inside it simply never
|
|
1374
|
+
* re-rendered. That makes the decoration story ship a documented-as-working path that does
|
|
1375
|
+
* not work, which is why this is here rather than filed as a follow-up.
|
|
1376
|
+
*
|
|
1377
|
+
* Scope, stated precisely because it is narrower than it looks: this emits the slice root
|
|
1378
|
+
* and its **top-level** keys, which is exactly what an ordinary commit emits when a
|
|
1379
|
+
* subtree first appears - `detectChangedProps` reports a newly-appearing branch at its
|
|
1380
|
+
* root, not leaf by leaf. So a `connect` on `"deep.n"` does not fire here, and does not
|
|
1381
|
+
* fire on a normal commit that first creates `deep` either. Consistent, not complete.
|
|
1382
|
+
*
|
|
1383
|
+
* Skipped when nothing is subscribed, and skipped entirely during construction, where no
|
|
1384
|
+
* subscriber can exist yet.
|
|
1385
|
+
*
|
|
1386
|
+
* @internal
|
|
1387
|
+
*/
|
|
1388
|
+
private announceMountedSlice;
|
|
1037
1389
|
/**
|
|
1038
1390
|
* Unmounts a slice: disposes reducer-bus listeners, removes reducer,
|
|
1039
1391
|
* and optionally deletes the slice state.
|
|
@@ -1130,6 +1482,7 @@ export declare function createStore<S extends Record<string, any>, EM extends Ev
|
|
|
1130
1482
|
};
|
|
1131
1483
|
onEffectError?: (error: unknown, event: EventUnion<EM>) => void;
|
|
1132
1484
|
onReducerError?: (error: unknown, event: EventUnion<EM>, slice: string) => void;
|
|
1485
|
+
onSubscriberError?: (error: unknown, event: EventUnion<EM>, phase: NotifiedPhase) => void;
|
|
1133
1486
|
maxReduceDepth?: number;
|
|
1134
1487
|
maxTransitionsPerDrain?: number;
|
|
1135
1488
|
onCascade?: (info: CascadeInfo<EM>) => void;
|
|
@@ -1175,6 +1528,7 @@ export declare function createStore<RM extends ReducersMapAny>(cfg: {
|
|
|
1175
1528
|
};
|
|
1176
1529
|
onEffectError?: (error: unknown, event: EventUnion<EMFromReducersStrict<RM>>) => void;
|
|
1177
1530
|
onReducerError?: (error: unknown, event: EventUnion<EMFromReducersStrict<RM>>, slice: string) => void;
|
|
1531
|
+
onSubscriberError?: (error: unknown, event: EventUnion<EMFromReducersStrict<RM>>, phase: NotifiedPhase) => void;
|
|
1178
1532
|
maxReduceDepth?: number;
|
|
1179
1533
|
maxTransitionsPerDrain?: number;
|
|
1180
1534
|
onCascade?: (info: CascadeInfo<EMFromReducersStrict<RM>>) => void;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content fingerprints for event deduplication.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* Extracted from `Store.ts` following the `matching.ts` / `paths.ts` precedent: nothing here
|
|
6
|
+
* touches an instance field.
|
|
7
|
+
*
|
|
8
|
+
* The old fingerprint was `channel::type::JSON.stringify(payload)`, which produced two
|
|
9
|
+
* inconsistent failures on the one path whose entire job is deciding whether two payloads are
|
|
10
|
+
* the same. A `Map`, `Set` or typed array stringifies to `{}`, so **distinct payloads
|
|
11
|
+
* collided and the second event was silently swallowed** - precisely the behaviour the README
|
|
12
|
+
* says Yoltra refuses to do by default. A `BigInt` or a cycle threw, hit a timestamp fallback,
|
|
13
|
+
* and was never deduped at all.
|
|
14
|
+
*
|
|
15
|
+
* `encodeState` already produces a faithful, JSON-stringifiable representation of all of
|
|
16
|
+
* those, so fingerprinting through it makes content dedup mean what it says.
|
|
17
|
+
*
|
|
18
|
+
* @module
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* JSON with plain-object keys sorted, for a stable content fingerprint.
|
|
22
|
+
*
|
|
23
|
+
* @remarks
|
|
24
|
+
* Insertion order is not content: `{a:1,b:2}` and `{b:2,a:1}` are the same payload and must
|
|
25
|
+
* fingerprint alike, which `JSON.stringify` alone does not deliver.
|
|
26
|
+
*
|
|
27
|
+
* **Arrays and `Map` entries are never sorted.** Their order is semantic - `[1,2]` is not
|
|
28
|
+
* `[2,1]`, and a `Map` preserves insertion order by specification. Sorting them would make
|
|
29
|
+
* genuinely different payloads share a fingerprint, which is worse than the bug this module
|
|
30
|
+
* exists to fix: it would silently drop real events rather than merely failing to dedup.
|
|
31
|
+
*
|
|
32
|
+
* Runs over the **already-encoded** value, so `Map`, `Set`, `Date` and binary have already
|
|
33
|
+
* become plain JSON shapes and there is nothing exotic left to handle.
|
|
34
|
+
*
|
|
35
|
+
* `JSON.stringify(v, keyArray)` cannot do this: the replacer-array form applies one global
|
|
36
|
+
* key list at every depth.
|
|
37
|
+
*
|
|
38
|
+
* @internal
|
|
39
|
+
*/
|
|
40
|
+
export declare function stableStringify(value: unknown): string;
|
|
41
|
+
/**
|
|
42
|
+
* A content fingerprint for one event.
|
|
43
|
+
*
|
|
44
|
+
* @param channel - Event channel.
|
|
45
|
+
* @param type - Event type.
|
|
46
|
+
* @param payload - Event payload.
|
|
47
|
+
* @returns A string that is equal for two events with equal content.
|
|
48
|
+
*
|
|
49
|
+
* @remarks
|
|
50
|
+
* Primitives keep a fast path, but a typed one: `String(payload)` alone made the number `1`
|
|
51
|
+
* and the string `"1"` the same event, as it did `true` and `"true"`. `null` and `undefined`
|
|
52
|
+
* are likewise distinguished, having previously shared `::null`.
|
|
53
|
+
*
|
|
54
|
+
* When the payload exceeds the node budget the fingerprint degrades to **never dedupe**
|
|
55
|
+
* rather than maybe-wrongly-dedupe. Two large payloads differing only past the cutoff would
|
|
56
|
+
* otherwise collide and the second would be dropped; refusing to dedup merely costs a
|
|
57
|
+
* duplicate, which is the safe direction and matches what already happened to payloads the
|
|
58
|
+
* old implementation could not serialize.
|
|
59
|
+
*
|
|
60
|
+
* @internal
|
|
61
|
+
*/
|
|
62
|
+
export declare function fingerprint(channel: string, type: string, payload: unknown): string;
|