@yoltra/core 0.3.0 → 0.5.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.
@@ -48,7 +48,30 @@ export type EventKey<EM extends EventMapBase> = {
48
48
  [C in keyof EM & string]: [C, keyof EM[C] & string];
49
49
  }[keyof EM & string];
50
50
  /**
51
- * A single event object: `{ channel, type, payload, id }`.
51
+ * Opaque, optional envelope metadata carried alongside an {@link Event}.
52
+ *
53
+ * @remarks
54
+ * The store never reads, validates or acts on this — it only carries it end to end, so
55
+ * reducers, middleware, effects, event subscribers and instrumentation all observe the same
56
+ * value. It is deliberately untyped at this level: consumers namespace their own keys (for
57
+ * example a tracing integration keeping provenance under `meta.trace`) rather than
58
+ * extending core with domain concepts.
59
+ *
60
+ * It is **not** part of the deduplication fingerprint, which is computed from
61
+ * `(channel, type, payload)` only. Two events differing solely in `meta` still dedupe.
62
+ *
63
+ * @example
64
+ * ```ts
65
+ * await store.emit('orders', 'created', payload, {
66
+ * meta: { trace: { origin: 'checkout-service', hop: 1 } },
67
+ * });
68
+ * ```
69
+ *
70
+ * @public
71
+ */
72
+ export type EventMeta = Readonly<Record<string, unknown>>;
73
+ /**
74
+ * A single event object: `{ channel, type, payload, id }`, plus optional `meta`.
52
75
  *
53
76
  * @typeParam EM - Event map.
54
77
  * @typeParam C - Channel key.
@@ -56,14 +79,16 @@ export type EventKey<EM extends EventMapBase> = {
56
79
  * @typeParam P - Payload type (defaults to `EM[C][T]`).
57
80
  *
58
81
  * @remarks
59
- * - The `id` field is automatically added by the store to enable deduplication.
82
+ * - The `id` field is automatically added by the store to enable deduplication, unless the
83
+ * emitter supplies one via {@link EmitOptions.id}.
60
84
  * - Used for preventing duplicate event processing (e.g., React Strict Mode).
85
+ * - `meta` is present only when {@link EmitOptions.meta} was supplied. See {@link EventMeta}.
61
86
  *
62
87
  * @example
63
88
  * ```ts
64
89
  * type EM = { ui: { toggle: boolean } };
65
90
  * type Evt = Event<EM, 'ui', 'toggle'>;
66
- * // { channel: 'ui'; type: 'toggle'; payload: boolean; id: string }
91
+ * // { channel: 'ui'; type: 'toggle'; payload: boolean; id: string; meta?: EventMeta }
67
92
  * ```
68
93
  *
69
94
  * @public
@@ -74,6 +99,11 @@ export interface Event<EM extends EventMapBase = EventMapBase, C extends keyof E
74
99
  payload: P;
75
100
  /** Unique identifier for deduplication and devtools tracking (automatically added by store) */
76
101
  id: string;
102
+ /**
103
+ * Optional caller-supplied metadata, carried through the pipeline untouched.
104
+ * Absent entirely unless {@link EmitOptions.meta} was supplied. See {@link EventMeta}.
105
+ */
106
+ readonly meta?: EventMeta;
77
107
  }
78
108
  /**
79
109
  * Generic "old → new" wrapper for fine-grained change notifications.
@@ -129,6 +159,41 @@ export interface EmitOptions {
129
159
  * is 0, using a short default window.
130
160
  */
131
161
  dedupKey?: string;
162
+ /**
163
+ * Use this exact id for the event instead of generating one.
164
+ *
165
+ * @remarks
166
+ * Intended for **idempotent re-emission**: a caller replaying an event from elsewhere (a
167
+ * peer store, a durable log) can preserve the original id so the same logical event keeps
168
+ * one identity everywhere, which makes it traceable across systems and in DevTools.
169
+ *
170
+ * The store does **not** enforce uniqueness — supplying a duplicate id does not dedupe the
171
+ * event. Deduplication is a separate, opt-in concern; see {@link EmitOptions.dedupKey}.
172
+ */
173
+ id?: string;
174
+ /**
175
+ * Metadata to attach to this event, carried through the pipeline untouched and visible to
176
+ * reducers, middleware, effects, subscribers and instrumentation. See {@link EventMeta}.
177
+ *
178
+ * @remarks
179
+ * Omitting this leaves `event.meta` genuinely absent rather than `undefined`, so event
180
+ * objects are byte-identical to those produced before this option existed.
181
+ */
182
+ meta?: EventMeta;
183
+ /**
184
+ * Bypass deduplication for this emit entirely, even when the store was created with
185
+ * {@link StoreSpec.dedupWindowMs} greater than 0.
186
+ *
187
+ * @remarks
188
+ * Content-based dedup fingerprints `(channel, type, payload)`, so a store with a dedup
189
+ * window silently collapses genuinely distinct events that happen to share a payload —
190
+ * repeated ticks with an empty payload, or the same event legitimately arriving twice from
191
+ * two different sources. Set this when the caller already guarantees distinctness by other
192
+ * means and needs every emit to land.
193
+ *
194
+ * Takes precedence over both {@link EmitOptions.dedupKey} and the store-level window.
195
+ */
196
+ skipDedup?: boolean;
132
197
  }
133
198
  export type Emit<EM extends EventMapBase> = <C extends keyof EM & string, T extends keyof EM[C] & string>(channel: C, type: T, payload: EM[C][T], opts?: EmitOptions) => Promise<void>;
134
199
  /**
@@ -145,12 +210,16 @@ export type Unsubscribe = () => void;
145
210
  * @public
146
211
  */
147
212
  export interface InstrumentedEvent<EM extends EventMapBase = EventMapBase> {
148
- /** The processed event, including its generated `id`. */
213
+ /**
214
+ * The processed event, including its `id` and any {@link EventMeta} the emitter attached.
215
+ * `meta` is absent unless it was supplied.
216
+ */
149
217
  event: {
150
218
  id: string;
151
219
  channel: string;
152
220
  type: string;
153
221
  payload: unknown;
222
+ meta?: EventMeta;
154
223
  };
155
224
  /** `true` if the event passed middleware and ran reducers; `false` if vetoed. */
156
225
  committed: boolean;
@@ -295,6 +364,26 @@ export type StoreSpec<R extends string, S extends Record<R, any>, EM extends Eve
295
364
  * @default 0 (disabled)
296
365
  */
297
366
  dedupWindowMs?: number;
367
+ /**
368
+ * Generates the `id` for each emitted event. Defaults to `crypto.randomUUID()`.
369
+ *
370
+ * @remarks
371
+ * Two reasons to override it. First, portability: `crypto.randomUUID` requires a **secure
372
+ * context** in browsers and is absent on some runtimes (React Native / Hermes), where the
373
+ * default would throw on every emit. Second, determinism: injecting a counter makes event
374
+ * ids stable across runs, which is what allows byte-exact assertions in tests.
375
+ *
376
+ * The factory must return a string. Uniqueness is the caller's responsibility.
377
+ *
378
+ * @default () => crypto.randomUUID()
379
+ *
380
+ * @example
381
+ * ```ts
382
+ * let n = 0;
383
+ * const store = createStore({ name: 'Test', reducer, idFactory: () => `evt-${++n}` });
384
+ * ```
385
+ */
386
+ idFactory?: () => string;
298
387
  /**
299
388
  * DevTools configuration options.
300
389
  *
@@ -324,6 +413,25 @@ export type StoreSpec<R extends string, S extends Record<R, any>, EM extends Eve
324
413
  * @param event - The event whose effect failed.
325
414
  */
326
415
  onEffectError?: (error: unknown, event: EventUnion<EM>) => void;
416
+ /**
417
+ * Invoked when a reducer throws.
418
+ *
419
+ * @remarks
420
+ * A reducer is meant to be pure and total, so a throw is a bug in application code — and it
421
+ * used to be almost invisible. Keyed reducers ran through a bus that logged and moved on,
422
+ * letting the event commit and its effects run; pattern reducers threw straight out of the
423
+ * drain, aborting the commit and notifying nobody. Both paths now isolate the failing slice
424
+ * and report here.
425
+ *
426
+ * The failing slice keeps its previous state; every other slice still reduces, and the event
427
+ * still commits if anything else changed. `emit()` never rejects because of a reducer error,
428
+ * so this hook is how a caller observes one.
429
+ *
430
+ * @param error - The thrown value.
431
+ * @param event - The event being reduced when it threw.
432
+ * @param slice - Name of the slice whose reducer threw.
433
+ */
434
+ onReducerError?: (error: unknown, event: EventUnion<EM>, slice: string) => void;
327
435
  };
328
436
  /**
329
437
  * Public Store surface.
@@ -384,9 +492,9 @@ export interface StoreInstance<R extends string = string, S extends Record<R, an
384
492
  */
385
493
  registerEffect(spec: EffectSpec<DeepReadonly<S>, EM>): Unsubscribe;
386
494
  /**
387
- * Dynamically add middleware.
495
+ * Dynamically add middleware, in either the function or the spec form.
388
496
  */
389
- registerMiddleware(mw: MiddlewareFunction<DeepReadonly<S>, EM>): Unsubscribe;
497
+ registerMiddleware(mw: MiddlewareInput<DeepReadonly<S>, EM>): Unsubscribe;
390
498
  /**
391
499
  * Dynamically add/remove a namespaced reducer slice at runtime.
392
500
  */
@@ -466,7 +574,7 @@ export interface StoreInstance<R extends string = string, S extends Record<R, an
466
574
  */
467
575
  hotReplace(partial: {
468
576
  reducer?: Record<R, ReducerSpec<S[R], EM>>;
469
- middleware?: MiddlewareFunction<DeepReadonly<S>, EM>[];
577
+ middleware?: MiddlewareInput<DeepReadonly<S>, EM>[];
470
578
  effects?: Array<EffectSpec<DeepReadonly<S>, EM>>;
471
579
  preserveState?: boolean;
472
580
  }): void;
@@ -487,6 +595,7 @@ export interface StoreInstance<R extends string = string, S extends Record<R, an
487
595
  type: string;
488
596
  payload: any;
489
597
  id: string;
598
+ meta?: EventMeta;
490
599
  }>): void;
491
600
  /**
492
601
  * Returns a structured introspection snapshot for DevTools UIs.
@@ -569,15 +678,6 @@ export interface StoreInstance<R extends string = string, S extends Record<R, an
569
678
  * };
570
679
  * ```
571
680
  *
572
- * @example Using `events` (legacy)
573
- * ```ts
574
- * const counterSpec: ReducerSpec<{ value: number }, MyEM> = {
575
- * state: { value: 0 },
576
- * events: [['ui', 'increment'], ['ui', 'decrement']],
577
- * reducer(s, evt) { ... },
578
- * };
579
- * ```
580
- *
581
681
  * @public
582
682
  */
583
683
  export interface ReducerSpec<S = any, EM extends EventMapBase = EventMapBase> {
@@ -587,14 +687,8 @@ export interface ReducerSpec<S = any, EM extends EventMapBase = EventMapBase> {
587
687
  state: S;
588
688
  /**
589
689
  * Event targeting using the unified `When` matcher.
590
- * Preferred over `events` for new code.
591
690
  */
592
691
  when?: When<EM>;
593
- /**
594
- * List of EventKeys `[channel, type]` that this reducer responds to.
595
- * @deprecated Use `when: { keys: [...] }` instead for better type inference.
596
- */
597
- events?: ReadonlyArray<EventKey<EM>>;
598
692
  /**
599
693
  * Pure reducer function: `(state, event) => nextState`.
600
694
  */
@@ -651,14 +745,8 @@ export type ReducerFunction<S = any, EM extends EventMapBase = EventMapBase> = (
651
745
  export interface EffectSpec<S = any, EM extends EventMapBase = EventMapBase> {
652
746
  /**
653
747
  * Event targeting using the unified `When` matcher.
654
- * Preferred over `events` for new code.
655
748
  */
656
749
  when?: When<EM>;
657
- /**
658
- * List of EventKeys `[channel, type]` that this effect responds to.
659
- * @deprecated Use `when: { keys: [...] }` instead for better type inference.
660
- */
661
- events?: ReadonlyArray<EventKey<EM>>;
662
750
  /**
663
751
  * Async effect handler: `(event, getState, emit) => void | Promise<void>`.
664
752
  */
@@ -906,9 +994,14 @@ export type EventFromWhen<EM extends EventMapBase, W extends When<EM>> = W exten
906
994
  * type T3 = PathValue<S, 'todos'>; // Array<{ title: string; done: boolean }>
907
995
  * ```
908
996
  *
997
+ * @remarks
998
+ * The empty path resolves to `T` itself, matching what the code has always done: both the
999
+ * store's internal path reader and the React one return the object unchanged for `""`. The type
1000
+ * used to say `never`, so a subscription to a root-value slice was typed as nothing at all.
1001
+ *
909
1002
  * @public
910
1003
  */
911
- export type PathValue<T, P extends string> = P extends `${infer K}.${infer Rest}` ? K extends keyof T ? PathValue<T[K], Rest> : K extends `${number}` ? T extends readonly (infer E)[] ? PathValue<E, Rest> : never : never : P extends keyof T ? T[P] : P extends `${number}` ? T extends readonly (infer E)[] ? E : never : never;
1004
+ export type PathValue<T, P extends string> = P extends "" ? T : P extends `${infer K}.${infer Rest}` ? K extends keyof T ? PathValue<T[K], Rest> : K extends `${number}` ? T extends readonly (infer E)[] ? PathValue<E, Rest> : never : never : P extends keyof T ? T[P] : P extends `${number}` ? T extends readonly (infer E)[] ? E : never : never;
912
1005
  /**
913
1006
  * Type discriminator for event consumers.
914
1007
  *
@@ -957,6 +1050,23 @@ export type DeepRO<T> = DeepReadonly<T>;
957
1050
  * @public
958
1051
  */
959
1052
  export type Primitive = string | number | boolean | bigint | symbol | null | undefined | Date | RegExp;
1053
+ /**
1054
+ * A value with **no addressable interior**: its changes are reported at the slice root rather
1055
+ * than at a path beneath it.
1056
+ *
1057
+ * @remarks
1058
+ * The distinction the path types were missing. `Map` and `Set` keep their contents outside own
1059
+ * enumerable keys, so walking them with `keyof` yields the names of their *methods* — which is
1060
+ * how `"byId.get"` and `"byId.size"` came to be offered as subscribable paths, and why a slice
1061
+ * holding a plain number autocompleted `"toFixed"`. Neither ever notified anything, because
1062
+ * `detectChangedProps` reports such a value at its own path and never descends into it.
1063
+ *
1064
+ * This is the type-level counterpart of that runtime rule: what the diff reports at the root,
1065
+ * the types address at the root, with the empty path.
1066
+ *
1067
+ * @public
1068
+ */
1069
+ export type RootValue = Primitive | ReadonlyMap<unknown, unknown> | ReadonlySet<unknown>;
960
1070
  /**
961
1071
  * Compute dotted paths of T, including nested objects and arrays.
962
1072
  *
@@ -964,7 +1074,7 @@ export type Primitive = string | number | boolean | bigint | symbol | null | und
964
1074
  *
965
1075
  * @public
966
1076
  */
967
- export type Path<T> = T extends Primitive ? never : T extends readonly (infer U)[] ? `${number}` | (Path<U> extends never ? never : `${number}.${Path<U>}`) : {
1077
+ export type Path<T> = T extends RootValue ? never : T extends readonly (infer U)[] ? `${number}` | (Path<U> extends never ? never : `${number}.${Path<U>}`) : {
968
1078
  [K in keyof T & string]: T[K] extends Primitive ? K : K | (Path<T[K]> extends never ? never : `${K}.${Path<T[K]>}`);
969
1079
  }[keyof T & string];
970
1080
  /**
@@ -980,17 +1090,39 @@ export type WithGlob<T extends string> = T | `${string}*${string}`;
980
1090
  *
981
1091
  * @typeParam Slice - Slice state type.
982
1092
  *
1093
+ * @remarks
1094
+ * A slice that **is** one value — a primitive, a `Map`, a `Set`, a `Date` — has no key to
1095
+ * address, and its only subscribable path is the empty one. Saying so is what makes
1096
+ * `{ reducer, property: "" }` type-check where it can actually fire, instead of falling through
1097
+ * to the untyped `property: string` overload and returning `unknown`.
1098
+ *
1099
+ * The conditional distributes over unions, which is why a nullable object slice gets both:
1100
+ * `Dotted<{ a: number } | null>` is `"" | "a"`. That is exactly right — such a slice really does
1101
+ * change at its root when it becomes `null`, and at `"a"` otherwise.
1102
+ *
983
1103
  * @public
984
1104
  */
985
- export type Dotted<Slice> = (keyof Slice & string) | Path<Slice>;
1105
+ export type Dotted<Slice> = Slice extends RootValue ? "" : (keyof Slice & string) | Path<Slice>;
986
1106
  /**
987
1107
  * Deep readonly type: recursively makes all properties readonly.
988
1108
  *
1109
+ * @remarks
1110
+ * The built-in object types are handled before the general mapped-object case, because
1111
+ * mapping over one destroys it. `{ readonly [K in keyof Map<K, V>]: ... }` produces an object
1112
+ * carrying the *names* of a Map's methods with their signatures rewritten, so reading a Map
1113
+ * out of state and calling `.get()` on it was a type error even though the value at runtime
1114
+ * is an ordinary Map. The same applied to `Set`, `Date`, `RegExp` and any function stored in
1115
+ * state.
1116
+ *
1117
+ * Collections become their `Readonly*` counterparts, which is the same treatment arrays
1118
+ * already had. Functions are returned untouched: a function's properties are not state, and
1119
+ * mapping over them makes it uncallable.
1120
+ *
989
1121
  * @typeParam T - Type to make readonly.
990
1122
  *
991
1123
  * @public
992
1124
  */
993
- export type DeepReadonly<T> = T extends (infer A)[] ? ReadonlyArray<DeepReadonly<A>> : T extends object ? {
1125
+ export type DeepReadonly<T> = T extends (...args: never[]) => unknown ? T : T extends (infer A)[] ? ReadonlyArray<DeepReadonly<A>> : T extends ReadonlyMap<infer K, infer V> ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>> : T extends ReadonlySet<infer V> ? ReadonlySet<DeepReadonly<V>> : T extends Date | RegExp | Promise<unknown> | Error ? T : T extends object ? {
994
1126
  readonly [K in keyof T]: DeepReadonly<T[K]>;
995
1127
  } : T;
996
1128
  /**
@@ -61,6 +61,12 @@
61
61
  *
62
62
  * @remarks
63
63
  * - If `oldState === newState` (same reference), returns `[]` immediately.
64
+ * - A change at the **root** — the values themselves differ and neither is a walkable object,
65
+ * as for a primitive, a `Map`/`Set`, or two `Date`s — is reported at the `path` given, which
66
+ * is `""` for the default root call. `[""]` therefore means *"the whole value changed"*, and
67
+ * is emphatically **not** the same as `[]`. Callers must not filter it out for falsiness:
68
+ * doing so is indistinguishable from "nothing changed", which is how a store slice holding a
69
+ * primitive once silently refused every update it was given.
64
70
  * - For objects, only **own enumerable** keys are compared (via `Object.keys`).
65
71
  * - Returned paths are **leaf paths** where a primitive/terminal difference was detected; for arrays,
66
72
  * a length change is treated as a leaf change at the array path.
@@ -1,4 +1,4 @@
1
- import { DeepReadonly } from '../types';
1
+ import { DeepReadonly } from '../types.js';
2
2
  /**
3
3
  * Deep-freezes a value **in place** and returns it as {@link DeepReadonly | `DeepReadonly<T>`}.
4
4
  *
@@ -44,4 +44,27 @@ import { DeepReadonly } from '../types';
44
44
  *
45
45
  * @public
46
46
  */
47
- export declare function freezeState<T>(obj: T, seen?: WeakSet<object>): DeepReadonly<T>;
47
+ export declare function freezeState<T>(obj: T, seen?: WeakSet<object>, alias?: AliasWatch): DeepReadonly<T>;
48
+ /**
49
+ * Watches the freeze walk for one specific reference.
50
+ *
51
+ * @remarks
52
+ * Exists to turn a dev-only heisenbug into a named warning. Because the freeze is deep and
53
+ * in place, anything a reducer stores **by reference** is frozen too — the event payload, a
54
+ * module-level default, a cached response. Mutating that object afterwards then throws, only in
55
+ * development, from a stack that has nothing to do with the store, and the same code works in
56
+ * production because the freeze is compiled out.
57
+ *
58
+ * Freezing it is not the mistake: an object reachable from state genuinely must not be mutated,
59
+ * or state changes behind the store's back. Keeping the reference is. The walk already visits
60
+ * every node, so recognising one of them costs an identity comparison and lets the store say so
61
+ * at the moment it happens.
62
+ *
63
+ * @public
64
+ */
65
+ export interface AliasWatch {
66
+ /** The reference to look for while freezing. */
67
+ readonly watch: object;
68
+ /** Called if `watch` is reachable from the value being frozen. */
69
+ readonly onFound: () => void;
70
+ }
@@ -1,2 +1,2 @@
1
- export * from './detectChangedProps';
2
- export * from './immutability';
1
+ export * from './detectChangedProps.js';
2
+ export * from './immutability.js';
@@ -0,0 +1,11 @@
1
+ /*!
2
+ * @yoltra/core v0.5.0
3
+ * (c) 2026 Manu Ramirez <@pixerael>
4
+ * License: MIT
5
+ * Homepage: https://yoltra.dev
6
+ *
7
+ * This source code is licensed under the MIT license found in the
8
+ * LICENSE file in the root directory of this source tree
9
+ */
10
+ "use strict";var K=Object.defineProperty;var W=(o,e,t)=>e in o?K(o,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):o[e]=t;var p=(o,e,t)=>W(o,typeof e!="symbol"?e+"":e,t);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class _{constructor(){p(this,"handlers",new Map)}on(e,t,n){let s=this.handlers.get(e);s||(s=new Map,this.handlers.set(e,s));let i=s.get(t);return i||(i=new Set,s.set(t,i)),i.add(n),()=>this.off(e,t,n)}off(e,t,n){const s=this.handlers.get(e);if(!s)return;const i=s.get(t);i&&(i.delete(n),i.size===0&&s.delete(t),s.size===0&&this.handlers.delete(e))}emit(e,t,n,s){const i=this.handlers.get(e);if(!i)return;const d=i.get(t);if(!(!d||d.size===0))for(const a of[...d])try{a(n,s)}catch(r){console.error("EventBus handler error:",r)}}clear(){this.handlers.clear()}}class N{constructor(){p(this,"handlers",new Map);p(this,"patternHandlers",new Map);p(this,"patternIndex",new Map)}on(e,t,n){const s=String(t);if(this.isPattern(s)){const i=s;this.patternHandlers.has(e)||this.patternHandlers.set(e,new Map);const d=this.patternHandlers.get(e);return d.has(i)||(d.set(i,[]),this.indexPattern(e,i)),d.get(i).push(n),()=>this.offPattern(e,i,n)}else{const i=this.normalizeTypeKey(s);this.handlers.has(e)||this.handlers.set(e,new Map);const d=this.handlers.get(e);return d.has(i)||d.set(i,[]),d.get(i).push(n),()=>this.offExactNormalized(e,i,n)}}off(e,t,n){const s=this.normalizeTypeKey(String(t));this.offExactNormalized(e,s,n)}offExactNormalized(e,t,n){const s=this.handlers.get(e);if(!s)return;const i=s.get(t);if(!i)return;const d=i.indexOf(n);d!==-1&&i.splice(d,1),i.length===0&&s.delete(t),s.size===0&&this.handlers.delete(e)}offPattern(e,t,n){const s=this.patternHandlers.get(e);if(!s)return;const i=s.get(t);if(!i)return;const d=i.indexOf(n);d!==-1&&i.splice(d,1),i.length===0&&(s.delete(t),this.unindexPattern(e,t)),s.size===0&&(this.patternHandlers.delete(e),this.patternIndex.delete(e))}emit(e,t,n){const s=String(t),i=this.normalizeTypeKey(s),d=this.handlers.get(e)?.get(i)??[],a=this.matchingPatternHandlers(e,s),r=new Set,f=c=>{for(const u of[...c])if(!r.has(u)){r.add(u);try{u(n)}catch(l){console.error(l);continue}}};f(d);for(const c of a)f(c)}emitWith(e,t,n){const s=String(t),i=this.normalizeTypeKey(s),d=this.handlers.get(e)?.get(i)??[],a=this.matchingPatternHandlers(e,s);if(d.length===0&&a.length===0)return;const r=n(),f=new Set,c=u=>{for(const l of[...u])if(!f.has(l)){f.add(l);try{l(r)}catch(y){console.error(y);continue}}};c(d);for(const u of a)c(u)}isPattern(e){return e.includes("*")}normalizeTypeKey(e){return e.replace(/^\./,"")}splitPath(e){return this.normalizeTypeKey(e).split(".").filter(Boolean)}indexPattern(e,t){let n=this.patternIndex.get(e);n===void 0&&(n={byHead:new Map,anyHead:[]},this.patternIndex.set(e,n));const s=this.splitPath(t),i={pattern:t,segments:s},d=s[0];if(d===void 0||d==="*"||d==="**"){n.anyHead.push(i);return}const a=n.byHead.get(d);a===void 0?n.byHead.set(d,[i]):a.push(i)}unindexPattern(e,t){const n=this.patternIndex.get(e);if(n===void 0)return;const s=this.splitPath(t)[0],i=s===void 0||s==="*"||s==="**"?n.anyHead:n.byHead.get(s);if(i===void 0)return;const d=i.findIndex(a=>a.pattern===t);d!==-1&&i.splice(d,1),i.length===0&&i!==n.anyHead&&s!==void 0&&n.byHead.delete(s)}matchingPatternHandlers(e,t){const n=this.patternHandlers.get(e),s=this.patternIndex.get(e);if(n===void 0||n.size===0||s===void 0)return[];const i=this.splitPath(t),d=[],a=f=>{for(const c of f){if(!this.matchSegments(c.segments,i))continue;const u=n.get(c.pattern);u!==void 0&&d.push(u)}},r=i[0];if(r!==void 0){const f=s.byHead.get(r);f!==void 0&&a(f)}return a(s.anyHead),d}matchSegments(e,t){let n=0,s=0,i=-1,d=0;for(;s<t.length;)if(n<e.length&&(e[n]==="*"||e[n]===t[s]))n++,s++;else if(n<e.length&&e[n]==="**")i=n,d=s,n++;else if(i!==-1)n=i+1,s=++d;else return!1;for(;n<e.length&&e[n]==="**";)n++;return n===e.length}clear(){this.handlers.clear(),this.patternHandlers.clear(),this.patternIndex.clear()}__introspect(){const e=[];for(const[t,n]of this.handlers)for(const[s,i]of n)i.length>0&&e.push({channel:t,type:s,count:i.length});for(const[t,n]of this.patternHandlers)for(const[s,i]of n)i.length>0&&e.push({channel:t,type:s,count:i.length});return e}}class D{constructor(e){p(this,"_reduce");this._reduce=e}reduce(e,t){return this._reduce(e,t)}}const x=new Set;function M(o,e){const t=o?`${o}.${e}`:e;x.has(t)||(x.add(t),console.warn(`[yoltra] State key "${e}"${o?` under "${o}"`:""} contains a dot. Paths are dotted, so this key is indistinguishable from nested objects of the same name: a subscription to "${t}" may match the wrong value, and DevTools patches for it will address the wrong node. Rename the key, or nest it.`))}function k(o,e,t="",n=new Map){const s=[];return $(o,e,t,n,s),s}function $(o,e,t,n,s){if(o===e)return;if(typeof o!="object"||typeof e!="object"||o===null||e===null){if(typeof o=="number"&&Number.isNaN(o)&&Number.isNaN(e))return;s.push(t);return}if(o instanceof Date&&e instanceof Date){o.getTime()!==e.getTime()&&s.push(t);return}if(o instanceof RegExp&&e instanceof RegExp){(o.source!==e.source||e.flags!==o.flags)&&s.push(t);return}if(o instanceof Map||e instanceof Map){s.push(t);return}if(o instanceof Set||e instanceof Set){s.push(t);return}const i=o,d=e,a=n.get(i);if(a?.has(d))return;const r=a??new Set;r.add(d),a||n.set(i,r);try{const f=Array.isArray(o),c=Array.isArray(e);if(f!==c){s.push(t);return}if(f){const h=o,m=e;h.length!==m.length&&t&&s.push(t);const g=Math.min(h.length,m.length);for(let w=0;w<g;w++)h[w]!==m[w]&&$(h[w],m[w],t?`${t}.${w}`:`${w}`,n,s);for(let w=g;w<Math.max(h.length,m.length);w++)s.push(t?`${t}.${w}`:`${w}`);return}const u=Object.keys(o),l=Object.keys(e);if(u.length===0&&l.length===0){s.push(t);return}let y=u.length===l.length;if(y){for(let h=0;h<l.length;h++)if(!Object.prototype.hasOwnProperty.call(o,l[h])){y=!1;break}}if(y){for(const h of l)o[h]!==e[h]&&(process.env.NODE_ENV!=="production"&&h.includes(".")&&M(t,h),$(o[h],e[h],t?`${t}.${h}`:h,n,s));return}for(const h of l){const m=Object.prototype.hasOwnProperty.call(o,h);if(m&&o[h]===e[h])continue;process.env.NODE_ENV!=="production"&&h.includes(".")&&M(t,h);const g=t?`${t}.${h}`:h;if(!m){s.push(g);continue}$(o[h],e[h],g,n,s)}for(const h of u)Object.prototype.hasOwnProperty.call(e,h)||(process.env.NODE_ENV!=="production"&&h.includes(".")&&M(t,h),s.push(t?`${t}.${h}`:h))}finally{r.delete(d),r.size===0&&n.delete(i)}}function v(o,e=new WeakSet,t){if(o===null||typeof o!="object"||e.has(o)||(t!==void 0&&o===t.watch&&t.onFound(),Object.isFrozen(o)))return o;if(e.add(o),Array.isArray(o)){const n=o;for(let s=0;s<n.length;s++)n[s]=v(n[s],e,t);return Object.freeze(n)}for(const n of Object.getOwnPropertyNames(o)){const s=Object.getOwnPropertyDescriptor(o,n);!s||!("value"in s)||(o[n]=v(o[n],e,t))}for(const n of Object.getOwnPropertySymbols(o)){const s=Object.getOwnPropertyDescriptor(o,n);!s||!("value"in s)||(o[n]=v(o[n],e,t))}return Object.freeze(o)}function j(o,e){try{return structuredClone(e)}catch(t){throw new Error(`[yoltra] Initial state for slice "${String(o)}" could not be copied: ${t instanceof Error?t.message:String(t)}. State must be structured-cloneable — functions, class instances and DOM nodes are not. Keep behaviour out of state and store plain data.`)}}function P(o,e){return process.env.NODE_ENV==="production"?o:v(o,new WeakSet,e)}const z=100,I=()=>typeof performance<"u"&&typeof performance.now=="function"?performance.now():Date.now();class S{constructor(e){p(this,"name");p(this,"middleware");p(this,"reducers");p(this,"state");p(this,"reducerBus");p(this,"connectorBus");p(this,"listeners",new Set);p(this,"effects",new Map);p(this,"patternEffects",new Set);p(this,"committedEventSubscribers",new Map);p(this,"uncommittedEventSubscribers",new Map);p(this,"allEventSubscribers",new Map);p(this,"sliceUnsubs",new Map);p(this,"patternReducers",new Map);p(this,"replayEnabled");p(this,"idFactory");p(this,"onEffectError");p(this,"onReducerError");p(this,"warnedPayloadAliases",new Set);p(this,"reduceQueue",[]);p(this,"isReducing",!1);p(this,"instrumentObservers",new Set);p(this,"changedPathSink",null);p(this,"inFlightEffects",0);p(this,"processedEvents",new Map);p(this,"dedupCount",0);p(this,"effectMeta",new WeakMap);p(this,"dedupConfig");p(this,"eventCleanupTimer",null);if(this.name=e.name??"yoltra Store",this.reducerBus=new _,this.connectorBus=new N,this.middleware=[...e.middleware??[]],this.reducers={},this.state={},this.replayEnabled=e.devtools?.allowReplay??!1,this.idFactory=e.idFactory??(()=>crypto.randomUUID()),this.onEffectError=e.onEffectError,this.onReducerError=e.onReducerError,this.dedupConfig={windowMs:e.dedupWindowMs??0,maxCacheSize:1e3},Object.entries(e.reducer).forEach(([t,n])=>{this.mountSlice(t,n,{preserveState:!1})}),e.effects?.length)for(const t of e.effects)this.registerEffect(t);this.dispose=this.dispose.bind(this),this.notifyEffects=this.notifyEffects.bind(this),this.forwardEvent=this.forwardEvent.bind(this),this.__applyExternalState=this.__applyExternalState.bind(this),this.__replayEvents=this.__replayEvents.bind(this),this.__devtoolsIntrospect=this.__devtoolsIntrospect.bind(this),this.mountSlice=this.mountSlice.bind(this),this.unmountSlice=this.unmountSlice.bind(this),this.getAtPath=this.getAtPath.bind(this),this.emit=this.emit.bind(this),this.subscribe=this.subscribe.bind(this),this.connect=this.connect.bind(this),this.onEffect=this.onEffect.bind(this),this.onEvent=this.onEvent.bind(this),this.getState=this.getState.bind(this),this.registerEffect=this.registerEffect.bind(this),this.registerMiddleware=this.registerMiddleware.bind(this),this.registerReducer=this.registerReducer.bind(this),this.replaceMiddleware=this.replaceMiddleware.bind(this),this.replaceEffects=this.replaceEffects.bind(this),this.replaceReducers=this.replaceReducers.bind(this),this.hotReplace=this.hotReplace.bind(this)}dispose(){this.eventCleanupTimer&&(clearInterval(this.eventCleanupTimer),this.eventCleanupTimer=null),this.processedEvents.clear(),this.effects.clear(),this.patternEffects.clear(),this.effectMeta=new WeakMap,this.warnedPayloadAliases.clear(),this.listeners.clear(),this.committedEventSubscribers.clear(),this.uncommittedEventSubscribers.clear(),this.allEventSubscribers.clear(),this.instrumentObservers.clear(),this.connectorBus.clear(),this.reducerBus.clear(),this.patternReducers.clear(),this.sliceUnsubs.clear(),this.changedPathSink=null}fingerprint(e,t,n){const s=`${e}::${t}`;try{if(n==null)return`${s}::null`;if(typeof n!="object")return`${s}::${String(n)}`;const i=JSON.stringify(n);return`${s}::${i}`}catch{return`${s}::${Date.now()}::${Math.random()}`}}shouldDedupe(e,t){const n=Date.now(),s=this.processedEvents.get(e);return s!==void 0&&n-s<t?(this.dedupCount++,!0):(this.processedEvents.set(e,n),this.ensureCleanupTimer(),this.processedEvents.size>this.dedupConfig.maxCacheSize&&this.pruneProcessedEvents(n),!1)}ensureCleanupTimer(){this.eventCleanupTimer===null&&(this.eventCleanupTimer=setInterval(()=>{this.pruneProcessedEvents(Date.now())},5e3),this.eventCleanupTimer.unref?.())}pruneProcessedEvents(e){const t=Math.max(this.dedupConfig.windowMs,z),n=e-t*2;for(const[s,i]of this.processedEvents)i<n&&this.processedEvents.delete(s);this.processedEvents.size===0&&this.eventCleanupTimer!==null&&(clearInterval(this.eventCleanupTimer),this.eventCleanupTimer=null)}matchesWhen(e,t){return!e||"any"in e&&e.any===!0?!0:"keys"in e?e.keys.some(([n,s])=>t.channel===n&&t.type===s):"channel"in e?t.channel===e.channel:"channels"in e?e.channels.includes(t.channel):!1}getMiddlewareFunction(e){return typeof e=="function"?e:e.middleware}getMiddlewareWhen(e){if(typeof e!="function")return e.when}async notifyEffects(e){const t=`${String(e.channel)}::${String(e.type)}`,n=this.effects.get(t);if(n&&n.size>0)for(const s of[...n])try{await s(e,this.getState,this.emit)}catch(i){console.error("Effect error:",i),this.onEffectError?.(i,e)}for(const{effect:s,when:i}of this.patternEffects)if(this.matchesWhen(i,e))try{await s(e,this.getState,this.emit)}catch(d){console.error("Effect error:",d),this.onEffectError?.(d,e)}}notifyEventSubscribers(e,t){const n=`${String(e.channel)}::${String(e.type)}`,i=(t==="committed"?this.committedEventSubscribers:this.uncommittedEventSubscribers).get(n);if(i?.size)for(const a of[...i])this.invokeEventSubscriber(a,e,t);const d=this.allEventSubscribers.get(n);if(d?.size)for(const a of[...d])this.invokeEventSubscriber(a,e,t)}invokeEventSubscriber(e,t,n){try{const s=e(t,this.getState,this.emit,n);s&&typeof s.then=="function"&&s.catch(i=>console.error("Event subscription error:",i))}catch(s){console.error("Event subscription error:",s)}}forwardEventGuarded(e,t){try{return this.forwardEvent(e,t)}catch(n){return console.error(`Reducer error in slice "${e}":`,n),this.onReducerError?.(n,t,e),!1}}forwardEvent(e,t){const n=this.state[e],s=this.reducers[e].reduce(n,t);if(n===s)return!1;const i=k(n,s);if(i.length===0)return!1;const d=t.payload,a=process.env.NODE_ENV!=="production"&&d!==null&&typeof d=="object"?{watch:d,onFound:()=>{const c=`${e}:${t.channel}:${t.type}`;this.warnedPayloadAliases.has(c)||(this.warnedPayloadAliases.add(c),console.warn(`[yoltra] Slice "${e}" stored the payload of "${t.channel}/${t.type}" by reference. It is now frozen along with the rest of the state, so the emitter mutating it later will throw in development and silently corrupt state in production. Copy the payload in the reducer instead.`))}}:void 0,r=P(s,a);if(this.state={...this.state,[e]:r},this.changedPathSink)for(const c of i)this.changedPathSink.push(c?`${e}.${c}`:e);const f=new Set;for(const c of i){if(c===""){f.add("");continue}for(const u of S.buildAncestorPaths(c))f.add(u)}for(const c of f)this.connectorBus.emitWith(e,c,()=>({oldValue:this.getAtPath(n,c),newValue:this.getAtPath(r,c),path:c}));return!0}__devtoolsIntrospect(){const e=Object.keys(this.reducers).map(a=>{const r=this.patternReducers.get(a);return{name:a,when:r}}),t=[];for(const[a,r]of this.effects){if(r.size===0)continue;const[f,c]=a.split("::");for(const u of r){const l=this.effectMeta.get(u);t.push({channel:f,type:c,name:l?.name,description:l?.description})}}for(const a of this.patternEffects){const r=this.effectMeta.get(a.effect);t.push({channel:"*",type:"*",name:r?.name,description:r?.description})}const n=[];for(const a of this.middleware)typeof a=="function"?n.push({name:a.name||void 0}):n.push({name:a.meta?.name,description:a.meta?.description,when:a.when});const s=[];for(const a of this.connectorBus.__introspect())for(let r=0;r<a.count;r++)s.push({reducer:a.channel,property:a.type});const i=[];for(const[a,r]of this.committedEventSubscribers){if(r.size===0)continue;const[f,c]=a.split("::");for(let u=0;u<r.size;u++)i.push({channel:f,type:c,phase:"committed"})}for(const[a,r]of this.uncommittedEventSubscribers){if(r.size===0)continue;const[f,c]=a.split("::");for(let u=0;u<r.size;u++)i.push({channel:f,type:c,phase:"uncommitted"})}for(const[a,r]of this.allEventSubscribers){if(r.size===0)continue;const[f,c]=a.split("::");for(let u=0;u<r.size;u++)i.push({channel:f,type:c,phase:"all"})}const d=this.listeners.size;return{reducers:e,effects:t,middleware:n,atomic:s,event:i,coarse:d,dedupHits:this.dedupCount,queueDepth:this.reduceQueue.length+this.inFlightEffects}}__applyExternalState(e){if(!this.replayEnabled)throw new Error("[yoltra] External state apply (time-travel) is disabled. Enable it with createStore({ devtools: { allowReplay: true } })");const t=this.state,n=e,s={...this.state};let i=!1;Object.keys(this.reducers).forEach(d=>{const a=t?.[d],r=n?.[d];if(r===void 0){process.env.NODE_ENV!=="production"&&console.warn(`[yoltra] External state is missing slice "${String(d)}"; retaining its current value. Time-travel snapshots should contain all slices.`);return}if(a===r)return;const f=P(r);s[d]=f,i=!0;const c=k(a,r);if(c.length===0)return;const u=new Set;for(const l of c){if(l===""){u.add("");continue}for(const y of S.buildAncestorPaths(l))u.add(y)}for(const l of u){const y=this.getAtPath(a,l),h=this.getAtPath(f,l);this.connectorBus.emit(d,l,{oldValue:y,newValue:h,path:l})}}),i&&(this.state=s),i&&this.listeners.forEach(d=>d())}__replayEvents(e,t){if(!this.replayEnabled)throw new Error("[yoltra] Event replay is disabled. Enable it with createStore({ devtools: { allowReplay: true } })");this.__applyExternalState(e);for(const n of t){const s=n,i=this.state;this.reducerBus.emit(s.channel,s.type,s.payload,s);for(const[r,f]of this.patternReducers)this.matchesWhen(f,s)&&this.forwardEventGuarded(r,s);const d=this.state,a=i!==d;this.notifyEventSubscribers(s,"committed"),a&&this.listeners.forEach(r=>r())}}async emit(e,t,n,s){const i=s?.dedupKey,d=this.dedupConfig.windowMs;if(s?.skipDedup!==!0&&(d>0||i!==void 0)){const c=i!==void 0&&d<=0?z:d,u=i!==void 0?`${e}::${t}::#${i}`:this.fingerprint(e,t,n);if(this.shouldDedupe(u,c))return}const a=s?.id??this.idFactory();let r;const f=new Promise(c=>{r=c});return this.reduceQueue.push({channel:e,type:t,payload:n,id:a,meta:s?.meta,resolve:r}),this.drainReduce(),f}drainReduce(){if(!this.isReducing){this.isReducing=!0;try{for(;this.reduceQueue.length>0;){const{channel:e,type:t,payload:n,id:s,meta:i,resolve:d}=this.reduceQueue.shift(),a={channel:e,type:t,payload:n,id:s,...i!==void 0?{meta:i}:{}},r=this.instrumentObservers.size>0,f=r?this.state:void 0,c=r?[]:void 0;c!==void 0&&(this.changedPathSink=c);const u=r?I():0;let l=!1;try{l=this.applyEventSync(a)}catch(y){console.error("Emit reduce error:",y)}finally{r&&(this.changedPathSink=null)}r&&this.emitInstrumentation(a,l,c??[],f,I()-u),this.runEventEffects(a,l,d)}}finally{this.isReducing=!1}}}applyEventSync(e){for(const s of this.middleware){const i=this.getMiddlewareWhen(s);if(!this.matchesWhen(i,e))continue;const d=this.getMiddlewareFunction(s);let a;try{a=d(this.state,e,this.emit),process.env.NODE_ENV!=="production"&&typeof a?.then=="function"&&console.error(`[yoltra] Middleware for "${e.channel}/${e.type}" returned a Promise. Middleware is synchronous: a Promise is truthy, so this event was allowed without waiting and a "return false" inside it can never veto. Do the check synchronously, and put anything that must await in an effect.`)}catch(r){console.error("Middleware error:",r),a=!1}if(!a)return this.notifyEventSubscribers(e,"uncommitted"),!1}const t=this.state;this.reducerBus.emit(e.channel,e.type,e.payload,e);for(const[s,i]of this.patternReducers)this.matchesWhen(i,e)&&this.forwardEventGuarded(s,e);const n=t!==this.state;return this.notifyEventSubscribers(e,"committed"),n&&this.listeners.forEach(s=>s()),!0}async runEventEffects(e,t,n){this.inFlightEffects++;try{t&&await this.notifyEffects(e)}catch(s){console.error("Effect error:",s)}finally{this.inFlightEffects--,n()}}instrument(e){return this.instrumentObservers.add(e),()=>{this.instrumentObservers.delete(e)}}emitInstrumentation(e,t,n,s,i){const d={},a={};for(const f of n)d[f]=this.getAtPath(s,f),a[f]=this.getAtPath(this.state,f);const r={event:{id:e.id,channel:e.channel,type:e.type,payload:e.payload,...e.meta!==void 0?{meta:e.meta}:{}},committed:t,changedPaths:n,prevValues:d,nextValues:a,reduceTimeMs:i};for(const f of[...this.instrumentObservers])try{f(r)}catch(c){console.error("Instrumentation observer error:",c)}}connect(e,t){return this.connectorBus.on(e.reducer,e.property,t)}onEvent(e,t,n,s="committed"){const i=`${e}::${String(t)}`,d=s==="committed"?this.committedEventSubscribers:s==="uncommitted"?this.uncommittedEventSubscribers:this.allEventSubscribers;return d.has(i)||d.set(i,new Set),d.get(i).add(n),()=>{const a=d.get(i);a&&(a.delete(n),a.size===0&&d.delete(i))}}subscribe(e){return this.listeners.add(e),()=>this.listeners.delete(e)}getState(){return this.state}registerMiddleware(e){return this.middleware.push(e),()=>{const t=this.middleware.indexOf(e);t!==-1&&this.middleware.splice(t,1)}}registerReducer(e,t){if(Object.prototype.hasOwnProperty.call(this.reducers,e))throw new Error(`Reducer ${e} already exists`);return this.mountSlice(e,t,{preserveState:!1}),this.listeners.forEach(n=>n()),()=>{this.unmountSlice(e,{deleteState:!0}),this.listeners.forEach(n=>n())}}registerEffect(e){const{effect:t,meta:n,when:s}=e,i=[];if(n&&this.effectMeta.set(t,n),s&&("any"in s&&s.any===!0||"channel"in s||"channels"in s)){const r={effect:t,when:s};return this.patternEffects.add(r),()=>{this.patternEffects.delete(r)}}const a=this.normalizeEventKeys(e);if(a.length===0&&!s){const r={effect:t,when:{any:!0}};return this.patternEffects.add(r),()=>{this.patternEffects.delete(r)}}for(const[r,f]of a){const c=`${String(r)}::${String(f)}`;this.effects.has(c)||this.effects.set(c,new Set),this.effects.get(c).add(t),i.push(()=>{const u=this.effects.get(c);u&&(u.delete(t),u.size===0&&this.effects.delete(c))})}return()=>{for(const r of i)r()}}onEffect(e,t,n){const s=async(i,d,a)=>{if(i.channel!==e||i.type!==t)return;const r=i;return n(r.payload,d,a,r)};return this.registerEffect({when:{keys:[[e,t]]},effect:s})}replaceMiddleware(e){this.middleware.length=0;for(const t of e)this.middleware.push(t)}replaceEffects(e){this.effects.clear(),this.patternEffects.clear();for(const t of e)this.registerEffect(t)}replaceReducers(e,t={}){const n=t.preserveState!==!1,s=new Set(Object.keys(this.reducers)),i=Object.entries(e),d=new Set(i.map(([a])=>a));for(const a of s)d.has(a)||this.unmountSlice(a,{deleteState:!0});for(const[a,r]of i)s.has(a)?(this.unmountSlice(a,{deleteState:!1}),this.mountSlice(a,r,{preserveState:n})):this.mountSlice(a,r,{preserveState:!1})}hotReplace(e){e.middleware&&this.replaceMiddleware(e.middleware),e.effects&&this.replaceEffects(e.effects),e.reducer&&this.replaceReducers(e.reducer,{preserveState:e.preserveState})}mountSlice(e,t,n){const s=e,{reducer:i,state:d,when:a}=t;if(this.reducers[e]=new D(i),(!n.preserveState||this.state[s]===void 0)&&(this.state={...this.state,[s]:P(j(s,d))}),a&&("any"in a&&a.any===!0||"channel"in a||"channels"in a)){this.patternReducers.set(e,a),this.sliceUnsubs.set(s,[]);return}const f=this.normalizeEventKeys(t);if(f.length===0&&!a){this.patternReducers.set(e,{any:!0}),this.sliceUnsubs.set(s,[]);return}const c=[];for(const[u,l]of f){const y=this.reducerBus.on(u,l,(h,m)=>{const g=m??{channel:u,type:l,payload:h,id:this.idFactory()};this.forwardEventGuarded(e,g)});c.push(y)}this.sliceUnsubs.set(s,c)}unmountSlice(e,t){const n=e;this.patternReducers.delete(e);const s=this.sliceUnsubs.get(n);if(s){for(const i of s)try{i()}catch(d){console.error(`[Store error]: ${d}`)}this.sliceUnsubs.delete(n)}if(delete this.reducers[e],t.deleteState){const{[n]:i,...d}=this.state;this.state=d}}normalizeEventKeys(e){if(e.when){const t=e.when;if("keys"in t)return t.keys}return[]}getAtPath(e,t){if(!t)return e;const s=(t[0]==="."?t.slice(1):t).split(".");let i=e;for(const d of s){if(i==null)return;i=i[d]}return i}static buildAncestorPaths(e){if(!e)return[];const n=(e[0]==="."?e.slice(1):e).split("."),s=[];for(let i=0;i<n.length;i++)s.push(n.slice(0,i+1).join("."));return s}}function F(o){return new S({name:o.name,reducer:o.reducer??{},middleware:o.middleware??[],effects:o.effects??[],dedupWindowMs:o.dedupWindowMs,idFactory:o.idFactory,devtools:o.devtools,onEffectError:o.onEffectError,onReducerError:o.onReducerError})}const V=o=>(e,t)=>t.map(n=>[e,n]),U=()=>o=>o,R=new Set;function L(o){const e=String(o);R.has(e)||(R.add(e),console.warn(`[yoltra] Entity id "${e}" contains a dot. Paths are dotted, so a subscription to "entities.${e}" is indistinguishable from one to a nested object of the same name. Use ids without dots.`))}function G(o,e){if(o.length!==e.length)return e;for(let t=0;t<o.length;t++)if(o[t]!==e[t])return e;return o}function Q(o={}){const e=o.selectId??(r=>r.id),{sortComparer:t}=o,n=(r,f)=>{if(t===void 0)return f;const c=[...f].sort((u,l)=>{const y=r.entities[u],h=r.entities[l];return y===void 0||h===void 0?0:t(y,h)});return G(f,c)},s=(r,f,c)=>{const u={...r,entities:f,ids:c};return{...u,ids:n(u,c)}},i=(r,f,c)=>{let u=null,l=null;for(const y of f){const h=e(y);process.env.NODE_ENV!=="production"&&String(h).includes(".")&&L(h);const m=(u??r.entities)[h];if(m!==void 0&&c==="add")continue;const g=m!==void 0&&c==="upsert"?{...m,...y}:y;u??(u={...r.entities}),u[h]=g,m===void 0&&(l??(l=[...r.ids]),l.push(h))}return u===null?r:s(r,u,l??r.ids)},d=(r,f)=>{let c=null;for(const{id:u,changes:l}of f){const y=(c??r.entities)[u];y!==void 0&&(c??(c={...r.entities}),c[u]={...y,...l})}return c===null?r:s(r,c,r.ids)},a=(r,f)=>{const c=new Set(f.filter(l=>r.entities[l]!==void 0));if(c.size===0)return r;const u={...r.entities};for(const l of c)delete u[l];return s(r,u,r.ids.filter(l=>!c.has(l)))};return{getInitialState(r){const f={ids:[],entities:{}};return r===void 0?f:{...f,...r}},addOne:(r,f)=>i(r,[f],"add"),addMany:(r,f)=>i(r,f,"add"),setOne:(r,f)=>i(r,[f],"set"),setMany:(r,f)=>i(r,f,"set"),setAll:(r,f)=>{const c={},u=[];for(const l of f){const y=e(l);c[y]===void 0&&u.push(y),c[y]=l}return s(r,c,u)},updateOne:(r,f)=>d(r,[f]),updateMany:(r,f)=>d(r,f),upsertOne:(r,f)=>i(r,[f],"upsert"),upsertMany:(r,f)=>i(r,f,"upsert"),removeOne:(r,f)=>a(r,[f]),removeMany:(r,f)=>a(r,f),removeAll:r=>r.ids.length===0?r:s(r,{},[]),selectIds:r=>r.ids,selectEntities:r=>r.entities,selectAll:r=>r.ids.map(f=>r.entities[f]),selectById:(r,f)=>r.entities[f],selectTotal:r=>r.ids.length,idsPath:"ids",pathTo:(r,f)=>f===void 0?`entities.${r}`:`entities.${r}.${f}`,anyField:r=>`entities.*.${r}`}}const E="$yoltra";function O(o,e={}){const t=e.maxNodes??1e5,n=e.sanitize,s=[],i=new Map;let d=0,a=!1;function r(c,u){if(n!==void 0&&(c=n(u,c)),d+=1,d>t)return a=!0,{[E]:"unsupported",kind:"truncated"};switch(typeof c){case"undefined":return{[E]:"undefined"};case"bigint":return{[E]:"bigint",value:c.toString()};case"number":return Number.isNaN(c)?{[E]:"nan"}:c===1/0?{[E]:"infinity",sign:1}:c===-1/0?{[E]:"infinity",sign:-1}:c;case"function":case"symbol":return s.push(u),{[E]:"unsupported",kind:typeof c};case"string":case"boolean":return c}if(c===null)return null;const l=c,y=i.get(l);if(y!==void 0)return{[E]:"ref",path:y};if(i.set(l,u),c instanceof Date)return{[E]:"date",iso:c.toISOString()};if(c instanceof RegExp)return{[E]:"regexp",source:c.source,flags:c.flags};if(c instanceof Error)return{[E]:"error",name:c.name,message:c.message};if(c instanceof Map){const m=[];let g=0;for(const[w,H]of c)m.push([r(w,`${u}/@k${g}`),r(H,`${u}/${g}`)]),g+=1;return{[E]:"map",entries:m}}if(c instanceof Set){const m=[];let g=0;for(const w of c)m.push(r(w,`${u}/${g}`)),g+=1;return{[E]:"set",values:m}}if(Array.isArray(c))return c.map((m,g)=>r(m,`${u}/${g}`));const h={};for(const[m,g]of Object.entries(c))h[m]=r(g,`${u}/${C(m)}`);return E in h?{[E]:"escaped",value:h}:h}return{value:r(o,""),report:{truncated:a,unsupported:s}}}function T(o){const e=new Map,t=[];function n(d,a){if(d===null||typeof d!="object")return d;if(Array.isArray(d)){const f=[];return e.set(a,f),d.forEach((c,u)=>{if(A(c)){t.push({target:f,key:u,path:c.path}),f[u]=void 0;return}f[u]=n(c,`${a}/${u}`)}),f}if(typeof d[E]=="string"){const f=d;switch(f[E]){case"undefined":return;case"nan":return Number.NaN;case"infinity":return f.sign===1?1/0:-1/0;case"bigint":return BigInt(f.value);case"date":return new Date(f.iso);case"regexp":return new RegExp(f.source,f.flags);case"error":{const c=new Error(f.message);return c.name=f.name,c}case"unsupported":return;case"ref":return;case"map":{const c=new Map;return e.set(a,c),f.entries.forEach(([u,l],y)=>{c.set(n(u,`${a}/@k${y}`),n(l,`${a}/${y}`))}),c}case"set":{const c=new Set;return e.set(a,c),f.values.forEach((u,l)=>c.add(n(u,`${a}/${l}`))),c}case"escaped":return s(f.value,a);default:return}}return s(d,a)}function s(d,a){const r={};e.set(a,r);for(const[f,c]of Object.entries(d)){const u=`${a}/${C(f)}`;if(A(c)){t.push({target:r,key:f,path:c.path}),r[f]=void 0;continue}r[f]=n(c,u)}return r}const i=n(o,"");e.set("",i);for(const{target:d,key:a,path:r}of t)d[a]=e.get(r);return i}function A(o){return o!==null&&typeof o=="object"&&o[E]==="ref"&&typeof o.path=="string"}function C(o){return o.replace(/~/g,"~0").replace(/\//g,"~1")}function J(o,e,t={}){let n=t.maxNodes??1e5;for(let s=0;s<8;s+=1){const{value:i,report:d}=O(o,{...t,maxNodes:n});let a;try{a=JSON.stringify(i)?.length??0}catch{a=Number.POSITIVE_INFINITY}if(a<=e)return d.truncated?{value:i,truncated:!0,note:`State was too large to send in full; parts beyond ${n} nodes are omitted.`}:{value:i,truncated:!1};const r=Math.floor(n*e*.8/a);if(n=Math.max(1,Math.min(r,n-1)),n<=1&&s>0)break}return{value:{[E]:"unsupported",kind:"truncated"},truncated:!0,note:`State exceeds the ${e}-byte transport limit and could not be reduced to fit.`}}function b(o,e,t){o.onError?.(e,t)}async function Y(o){const e={slices:{},restored:!1};let t;try{t=o.source??await o.adapter.read(o.key)}catch(s){return b(o,s,"read"),e}if(t==null||t==="")return e;let n;try{n=T(JSON.parse(t))}catch(s){return b(o,s,"decode"),e}if(n===null||typeof n!="object"||typeof n.version!="number")return b(o,new Error("persisted payload is not a recognisable envelope"),"decode"),e;if(n.version!==o.version){if(o.migrate===void 0)return b(o,new Error(`persisted state is version ${n.version}, this build expects ${o.version}, and no migrate was supplied`),"migrate"),e;try{const s=o.migrate(n.slices,n.version);return s===null?e:{slices:s,restored:!0}}catch(s){return b(o,s,"migrate"),e}}return{slices:n.slices??{},restored:!0}}function q(o,e){if(!e.restored)return o;const t={};for(const[n,s]of Object.entries(o)){const i=e.slices[n];t[n]=i===void 0?s:{...s,state:i}}return t}function B(o,e){const t=o??{},n=e.slices===void 0?t:Object.fromEntries(e.slices.filter(s=>s in t).map(s=>[s,t[s]]));return JSON.stringify(O({version:e.version,slices:n}).value)}function X(o,e){const t=e.throttleMs??250,n=e.slices;let s=null,i=!1;const d=()=>{if(i){i=!1;try{const f=e.adapter.write(e.key,B(o.getState(),e));f instanceof Promise&&f.catch(c=>b(e,c,"write"))}catch(f){b(e,f,"write")}}},a=()=>{if(i=!0,t<=0){d();return}s===null&&(s=setTimeout(()=>{s=null,d()},t),s.unref?.())},r=o.instrument(f=>{if(n===void 0){a();return}(f.changedPaths??[]).some(u=>n.some(l=>u===l||u.startsWith(`${l}.`)))&&a()});return()=>{r(),s!==null&&(clearTimeout(s),s=null),d()}}function Z(o,e){return B(o.getState(),e)}function ee(o){return{read:e=>o.getItem(e),write:(e,t)=>o.setItem(e,t),remove:e=>o.removeItem(e)}}function te(o){const e=new Map(Object.entries(o??{}));return{read:t=>e.get(t)??null,write:(t,n)=>{e.set(t,n)},remove:t=>{e.delete(t)}}}exports.EventBus=_;exports.LooseEventBus=N;exports.Reducer=D;exports.Store=S;exports.createEntityAdapter=Q;exports.createMemoryAdapter=te;exports.createStore=F;exports.createWebStorageAdapter=ee;exports.decodeState=T;exports.dehydrate=Z;exports.detectChangedProps=k;exports.encodeState=O;exports.encodeStateBounded=J;exports.eventKeys=U;exports.freezeState=v;exports.hydrate=Y;exports.persist=X;exports.typedEvents=V;exports.withHydration=q;
11
+ //# sourceMappingURL=yoltra.cjs.map