@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.
@@ -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
  *
@@ -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): 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;
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>): 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;
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>): () => 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;
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>): () => 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;
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>[]): void;
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>>): void;
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;