@yoltra/core 0.6.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.
@@ -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
- * Return `false` from the middleware function to stop propagation.
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 using `channel::type::JSON(payload)`
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 50ms in development, 100ms in production
310
+ * - The window is `dedupWindowMs`, which defaults to `0` (dedup off)
230
311
  *
231
312
  * **Limitations:**
232
- * - Non-serializable payloads (functions, symbols, circular refs) get unique
233
- * fingerprints and won't be deduplicated
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
  *
@@ -337,42 +418,6 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
337
418
  * @internal
338
419
  */
339
420
  private reportCascade;
340
- /**
341
- * Checks if an event matches a `When` matcher.
342
- *
343
- * @param when - The When matcher (or undefined for "all events").
344
- * @param event - The event to check.
345
- * @returns `true` if the event matches, `false` otherwise.
346
- *
347
- * @remarks
348
- * - `undefined` or missing `when` matches ALL events.
349
- * - `{ any: true }` matches ALL events.
350
- * - `{ keys: [...] }` matches if event's `[channel, type]` is in the array.
351
- * - `{ channel: 'x' }` matches if event's channel equals 'x'.
352
- * - `{ channels: ['x', 'y'] }` matches if event's channel is in the array.
353
- *
354
- * @internal
355
- */
356
- private matchesWhen;
357
- /**
358
- * Extracts the middleware function from a MiddlewareInput.
359
- * Handles both raw functions (legacy) and MiddlewareSpec objects.
360
- *
361
- * @param input - MiddlewareInput (function or spec).
362
- * @returns The middleware function.
363
- *
364
- * @internal
365
- */
366
- private getMiddlewareFunction;
367
- /**
368
- * Gets the `when` matcher from a MiddlewareInput.
369
- *
370
- * @param input - MiddlewareInput (function or spec).
371
- * @returns The `when` matcher, or `undefined` for raw functions (match all).
372
- *
373
- * @internal
374
- */
375
- private getMiddlewareWhen;
376
421
  /**
377
422
  * Invokes all registered **effects** for a given event.
378
423
  * Handles both key-based effects (O(1) lookup) and pattern-based effects (runtime matching).
@@ -515,17 +560,21 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
515
560
  reducers: {
516
561
  name: string;
517
562
  when: When<EM> | undefined;
563
+ origin: Origin;
564
+ owner: string | undefined;
518
565
  }[];
519
566
  effects: {
520
567
  channel: string;
521
568
  type: string;
522
569
  name?: string;
523
570
  description?: string;
571
+ origin: Origin;
524
572
  }[];
525
573
  middleware: {
526
574
  name?: string;
527
575
  description?: string;
528
576
  when?: unknown;
577
+ origin: Origin;
529
578
  }[];
530
579
  atomic: {
531
580
  reducer: string;
@@ -535,6 +584,7 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
535
584
  channel: string;
536
585
  type: string;
537
586
  phase: string;
587
+ duringReplay: boolean;
538
588
  }[];
539
589
  coarse: number;
540
590
  dedupHits: number;
@@ -556,6 +606,12 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
556
606
  * @internal
557
607
  */
558
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;
559
615
  /**
560
616
  * Replays a sequence of events from a snapshot through reducers and event
561
617
  * subscribers ONLY. Skips dedup, middleware, and effects.
@@ -575,6 +631,12 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
575
631
  id: string;
576
632
  meta?: EventMeta;
577
633
  }>): void;
634
+ /**
635
+ * The body of {@link __replayEvents}, with the replay flag already set.
636
+ *
637
+ * @internal
638
+ */
639
+ private replayEventsInner;
578
640
  /**
579
641
  * Emits a typed event `(channel, type, payload)`.
580
642
  * Events are queued and processed **sequentially** (FIFO).
@@ -710,6 +772,80 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
710
772
  reducer: R;
711
773
  property: string;
712
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;
713
849
  /**
714
850
  * Subscribe to events by channel and type.
715
851
  *
@@ -721,8 +857,15 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
721
857
  * - `'committed'` (default): Events that passed middleware and reached reducers.
722
858
  * Notified after reducers, before effects.
723
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.
724
862
  * - `'all'`: Both committed and uncommitted events. Handler receives the phase parameter
725
- * 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 }`.
726
869
  *
727
870
  * @typeParam C - Channel key within `EM`.
728
871
  * @typeParam T - Event type key within channel `C`.
@@ -756,7 +899,9 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
756
899
  *
757
900
  * @public
758
901
  */
759
- 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): Unsubscribe;
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;
760
905
  /**
761
906
  * Subscribes to **coarse-grained** commits (called once per successful event, only if state changed).
762
907
  *
@@ -823,7 +968,50 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
823
968
  *
824
969
  * @public
825
970
  */
826
- registerMiddleware(mw: MiddlewareInput<DeepReadonly<S>, EM>): Unsubscribe;
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;
827
1015
  /**
828
1016
  * Dynamically **adds** a named slice reducer at runtime.
829
1017
  *
@@ -846,7 +1034,50 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
846
1034
  *
847
1035
  * @public
848
1036
  */
849
- registerReducer(name: string, spec: ReducerSpec<any, EM>): () => void;
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;
850
1081
  /**
851
1082
  * Registers an **effect** (stateless async event consumer) that runs after reducers.
852
1083
  *
@@ -942,13 +1173,7 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
942
1173
  * the terminal event from ever being sent. Un-iterated progress therefore buffers to
943
1174
  * `highWaterMark` and is then counted on {@link CallHandle.dropped} rather than blocking.
944
1175
  *
945
- * **This is a local primitive.** A reply cannot reach it from a federated peer: the federation
946
- * envelope carries neither `meta` nor `parentId`, and ingress namespaces the channel, so
947
- * neither correlation nor the reply route survives the hop. That is not an oversight to route
948
- * around — federation answers cross-node request/reply with typed peer *queries*, which are
949
- * gated by a responder policy that may concede or deny. A call that federated silently would
950
- * turn that access decision into an accident of which channel someone named. Ask a peer with a
951
- * query; use `call` within a process.
1176
+ * **This is a local primitive.**
952
1177
  *
953
1178
  * @example Timeout is idle, not total
954
1179
  * ```ts
@@ -965,7 +1190,17 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
965
1190
  * @public
966
1191
  */
967
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>>;
968
- registerEffect(spec: EffectSpec<DeepReadonly<S>, EM>): () => void;
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;
969
1204
  /**
970
1205
  * Convenience helper to register an **effect** filtered by a single `(channel, type)` pair.
971
1206
  *
@@ -1004,7 +1239,11 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
1004
1239
  *
1005
1240
  * @public
1006
1241
  */
1007
- replaceMiddleware(next: MiddlewareInput<DeepReadonly<S>, EM>[]): void;
1242
+ replaceMiddleware(next: MiddlewareInput<DeepReadonly<S>, EM>[], opts?: {
1243
+ scope?: ReplaceScope;
1244
+ }): void;
1245
+ /** @internal */
1246
+ private replaceMiddlewareInner;
1008
1247
  /**
1009
1248
  * Replaces all registered **effects** (HMR-friendly).
1010
1249
  *
@@ -1021,12 +1260,24 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
1021
1260
  *
1022
1261
  * @public
1023
1262
  */
1024
- replaceEffects(next: Array<EffectSpec<DeepReadonly<S>, EM>>): void;
1263
+ replaceEffects(next: Array<EffectSpec<DeepReadonly<S>, EM>>, opts?: {
1264
+ scope?: ReplaceScope;
1265
+ }): void;
1266
+ /** @internal */
1267
+ private replaceEffectsInner;
1025
1268
  /**
1026
1269
  * Replaces the entire **reducer set** (HMR-friendly).
1027
1270
  *
1028
1271
  * @param next - Map of slice specs keyed by slice name.
1029
- * @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.
1030
1281
  *
1031
1282
  * @example Hot module replacement
1032
1283
  * ```ts
@@ -1041,7 +1292,34 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
1041
1292
  */
1042
1293
  replaceReducers(next: Record<R, ReducerSpec<S[R], EM>>, opts?: {
1043
1294
  preserveState?: boolean;
1295
+ scope?: ReplaceScope;
1044
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;
1045
1323
  /**
1046
1324
  * Convenience API to replace **any subset** of store parts (HMR patterns).
1047
1325
  *
@@ -1064,6 +1342,7 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
1064
1342
  middleware?: MiddlewareInput<DeepReadonly<S>, EM>[];
1065
1343
  effects?: Array<EffectSpec<DeepReadonly<S>, EM>>;
1066
1344
  preserveState?: boolean;
1345
+ scope?: ReplaceScope;
1067
1346
  }): void;
1068
1347
  /**
1069
1348
  * Mounts a slice: installs reducer, initializes state (unless preserved),
@@ -1076,25 +1355,47 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
1076
1355
  * @internal
1077
1356
  */
1078
1357
  private mountSlice;
1358
+ /** @internal */
1359
+ private mountSliceInner;
1079
1360
  /**
1080
- * Unmounts a slice: disposes reducer-bus listeners, removes reducer,
1081
- * and optionally deletes the slice state.
1361
+ * Records a slice mount or unmount for {@link onRegistrationChange}.
1082
1362
  *
1083
- * @param name - Slice name.
1084
- * @param opts - `{ deleteState: boolean }`.
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.
1085
1385
  *
1086
1386
  * @internal
1087
1387
  */
1088
- private unmountSlice;
1388
+ private announceMountedSlice;
1089
1389
  /**
1090
- * Normalizes event targeting from `when` to an array of EventKeys.
1390
+ * Unmounts a slice: disposes reducer-bus listeners, removes reducer,
1391
+ * and optionally deletes the slice state.
1091
1392
  *
1092
- * @param spec - Object with an optional `when` matcher.
1093
- * @returns Array of `[channel, type]` pairs.
1393
+ * @param name - Slice name.
1394
+ * @param opts - `{ deleteState: boolean }`.
1094
1395
  *
1095
1396
  * @internal
1096
1397
  */
1097
- private normalizeEventKeys;
1398
+ private unmountSlice;
1098
1399
  /**
1099
1400
  * Reads a dotted path from an object (supports numeric array indices via string keys).
1100
1401
  *
@@ -1102,6 +1403,10 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
1102
1403
  * @param path - Dotted path; leading dot is ignored.
1103
1404
  * @returns The value at the path, or `undefined`.
1104
1405
  *
1406
+ * @remarks
1407
+ * A member rather than a bare import: a test replaces this on the instance to count how many
1408
+ * walks describing a change costs, which only works while the callers go through `this`.
1409
+ *
1105
1410
  * @internal
1106
1411
  */
1107
1412
  private getAtPath;
@@ -1177,6 +1482,7 @@ export declare function createStore<S extends Record<string, any>, EM extends Ev
1177
1482
  };
1178
1483
  onEffectError?: (error: unknown, event: EventUnion<EM>) => void;
1179
1484
  onReducerError?: (error: unknown, event: EventUnion<EM>, slice: string) => void;
1485
+ onSubscriberError?: (error: unknown, event: EventUnion<EM>, phase: NotifiedPhase) => void;
1180
1486
  maxReduceDepth?: number;
1181
1487
  maxTransitionsPerDrain?: number;
1182
1488
  onCascade?: (info: CascadeInfo<EM>) => void;
@@ -1222,6 +1528,7 @@ export declare function createStore<RM extends ReducersMapAny>(cfg: {
1222
1528
  };
1223
1529
  onEffectError?: (error: unknown, event: EventUnion<EMFromReducersStrict<RM>>) => void;
1224
1530
  onReducerError?: (error: unknown, event: EventUnion<EMFromReducersStrict<RM>>, slice: string) => void;
1531
+ onSubscriberError?: (error: unknown, event: EventUnion<EMFromReducersStrict<RM>>, phase: NotifiedPhase) => void;
1225
1532
  maxReduceDepth?: number;
1226
1533
  maxTransitionsPerDrain?: number;
1227
1534
  onCascade?: (info: CascadeInfo<EMFromReducersStrict<RM>>) => void;