@yoltra/core 0.1.0 → 0.2.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.
@@ -113,13 +113,69 @@ export interface Change<V = any> {
113
113
  *
114
114
  * @public
115
115
  */
116
- 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]) => Promise<void>;
116
+ /**
117
+ * Per-emit options.
118
+ *
119
+ * @public
120
+ */
121
+ export interface EmitOptions {
122
+ /**
123
+ * Opt this specific emit into **identity-based** deduplication: if another
124
+ * event with the same `(channel, type, dedupKey)` was emitted within the dedup
125
+ * window, this one is skipped. Unlike content-based dedup
126
+ * ({@link StoreSpec.dedupWindowMs}), it never coalesces two *distinct* logical
127
+ * emits that merely share a payload — only re-fires of the *same* keyed emit
128
+ * (e.g. a React Strict Mode double-invoke). Works even when `dedupWindowMs`
129
+ * is 0, using a short default window.
130
+ */
131
+ dedupKey?: string;
132
+ }
133
+ 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>;
117
134
  /**
118
135
  * Basic unsubscribe handle.
119
136
  *
120
137
  * @public
121
138
  */
122
139
  export type Unsubscribe = () => void;
140
+ /**
141
+ * A single observed event delivered to an {@link InstrumentationObserver}.
142
+ *
143
+ * @typeParam EM - Event map.
144
+ *
145
+ * @public
146
+ */
147
+ export interface InstrumentedEvent<EM extends EventMapBase = EventMapBase> {
148
+ /** The processed event, including its generated `id`. */
149
+ event: {
150
+ id: string;
151
+ channel: string;
152
+ type: string;
153
+ payload: unknown;
154
+ };
155
+ /** `true` if the event passed middleware and ran reducers; `false` if vetoed. */
156
+ committed: boolean;
157
+ /**
158
+ * Dotted **leaf** paths that changed, prefixed with the slice name (e.g.
159
+ * `"todos.items.0.title"`). Empty when nothing changed. These are the exact
160
+ * paths the store computed while reducing — no re-diff required.
161
+ */
162
+ changedPaths: string[];
163
+ /** Old value at each changed path, keyed by path. */
164
+ prevValues: Record<string, unknown>;
165
+ /** New value at each changed path, keyed by path. */
166
+ nextValues: Record<string, unknown>;
167
+ /** Wall-clock milliseconds spent in the synchronous reduce phase for this event. */
168
+ reduceTimeMs: number;
169
+ }
170
+ /**
171
+ * Observer for {@link StoreInstance.instrument}. Called once per emitted event
172
+ * (committed or vetoed), after the synchronous reduce phase.
173
+ *
174
+ * @typeParam EM - Event map.
175
+ *
176
+ * @public
177
+ */
178
+ export type InstrumentationObserver<EM extends EventMapBase = EventMapBase> = (info: InstrumentedEvent<EM>) => void;
123
179
  /**
124
180
  * Store spec - what you feed into the constructor / factory.
125
181
  *
@@ -226,13 +282,17 @@ export type StoreSpec<R extends string, S extends Record<R, any>, EM extends Eve
226
282
  */
227
283
  effects?: Array<EffectSpec<DeepReadonly<S>, EM>>;
228
284
  /**
229
- * Time window in milliseconds for event deduplication.
230
- * Events with identical fingerprints (channel + type + serialized payload)
231
- * within this window are considered duplicates and skipped.
285
+ * Time window in milliseconds for **content-based** event deduplication.
286
+ * When greater than 0, events with identical fingerprints
287
+ * (channel + type + serialized payload) within this window are treated as
288
+ * duplicates and skipped.
232
289
  *
233
- * This helps prevent double-firing in React Strict Mode.
290
+ * **Off by default.** Content-based dedup can silently drop legitimate
291
+ * rapid-fire identical events (double-clicks, repeated `+1`, sliders emitting
292
+ * the same value), so it is opt-in. To safely coalesce a *specific* re-fired
293
+ * emit (e.g. React Strict Mode), prefer the per-emit {@link EmitOptions.dedupKey}.
234
294
  *
235
- * @default 50 in development, 100 in production
295
+ * @default 0 (disabled)
236
296
  */
237
297
  dedupWindowMs?: number;
238
298
  /**
@@ -250,6 +310,20 @@ export type StoreSpec<R extends string, S extends Record<R, any>, EM extends Eve
250
310
  */
251
311
  allowReplay?: boolean;
252
312
  };
313
+ /**
314
+ * Called when an effect throws or its returned promise rejects.
315
+ *
316
+ * @remarks
317
+ * `await emit(...)` **never rejects** on effect failure: the reduce phase has
318
+ * already committed synchronously, and effects run as independent per-event
319
+ * tasks. Effect errors are logged to the console and delivered here (when
320
+ * provided), so this is the single place to observe and route them — e.g.
321
+ * report to a service or emit a failure event. Other effects still run.
322
+ *
323
+ * @param error - The thrown value or rejection reason.
324
+ * @param event - The event whose effect failed.
325
+ */
326
+ onEffectError?: (error: unknown, event: EventUnion<EM>) => void;
253
327
  };
254
328
  /**
255
329
  * Public Store surface.
@@ -417,7 +491,8 @@ export interface StoreInstance<R extends string = string, S extends Record<R, an
417
491
  /**
418
492
  * Returns a structured introspection snapshot for DevTools UIs.
419
493
  *
420
- * @returns Reducers, effects, middleware, event subscriptions, and coarse subscriber count.
494
+ * @returns Reducers, effects, middleware, event subscriptions, coarse
495
+ * subscriber count, dedup hit count, and current queue depth.
421
496
  *
422
497
  * @internal
423
498
  */
@@ -447,7 +522,28 @@ export interface StoreInstance<R extends string = string, S extends Record<R, an
447
522
  phase: string;
448
523
  }>;
449
524
  coarse: number;
525
+ dedupHits: number;
526
+ queueDepth: number;
450
527
  };
528
+ /**
529
+ * Registers an instrumentation observer, called once per emitted event
530
+ * (committed or vetoed) after the synchronous reduce phase, with the exact
531
+ * changed paths, their old/new values, and reduce timing. This is the typed
532
+ * seam DevTools agents consume — no `as any` bridging required.
533
+ *
534
+ * @param observer - Receives an {@link InstrumentedEvent} per emit.
535
+ * @returns Unsubscribe function.
536
+ */
537
+ instrument(observer: InstrumentationObserver<EM>): Unsubscribe;
538
+ /**
539
+ * Applies an externally-provided whole-state snapshot (DevTools time-travel),
540
+ * emitting fine-grained path changes and notifying coarse subscribers.
541
+ *
542
+ * @param next - Plain state object to apply.
543
+ *
544
+ * @internal
545
+ */
546
+ __applyExternalState(next: unknown): void;
451
547
  }
452
548
  /**
453
549
  * One reducer's definition blob (stateful event consumer).
@@ -585,15 +681,20 @@ export type EventUnion<EM extends EventMapBase> = {
585
681
  }[keyof EM[C] & string];
586
682
  }[keyof EM & string];
587
683
  /**
588
- * Middleware function: may mutate, log, side-effect, or veto an event.
589
- * Return true to continue; false to swallow / cancel propagation.
684
+ * Middleware function: log, guard, or veto an event **synchronously**.
685
+ * Return `true` to continue, `false` to swallow / cancel propagation.
686
+ *
687
+ * @remarks
688
+ * Middleware runs in the synchronous reduce phase (so `getState()` is correct
689
+ * immediately after `emit()`), and therefore must be synchronous. Perform async
690
+ * work in effects instead.
590
691
  *
591
692
  * @typeParam S - Store state (readonly).
592
693
  * @typeParam EM - Event map.
593
694
  *
594
695
  * @public
595
696
  */
596
- export type MiddlewareFunction<S = any, EM extends EventMapBase = EventMapBase> = (state: S, event: EventUnion<EM>, emit: Emit<EM>) => boolean | Promise<boolean>;
697
+ export type MiddlewareFunction<S = any, EM extends EventMapBase = EventMapBase> = (state: S, event: EventUnion<EM>, emit: Emit<EM>) => boolean;
597
698
  /**
598
699
  * Middleware specification with optional event targeting and metadata.
599
700
  *
@@ -636,7 +737,7 @@ export interface MiddlewareSpec<S = any, EM extends EventMapBase = EventMapBase>
636
737
  */
637
738
  when?: When<EM>;
638
739
  /**
639
- * Middleware function: `(state, event, emit) => boolean | Promise<boolean>`.
740
+ * Middleware function: `(state, event, emit) => boolean` (synchronous).
640
741
  * Return `false` to cancel event propagation.
641
742
  */
642
743
  middleware: MiddlewareFunction<S, EM>;
@@ -669,12 +770,31 @@ export type StateFromReducers<R> = {
669
770
  [K in keyof R]: R[K] extends ReducerSpec<infer S, any> ? S : never;
670
771
  };
671
772
  /**
672
- * Helper: derive event map from a reducers map (strict).
673
- * Used by createStore inference overload.
773
+ * Helper: turn a union into an intersection.
774
+ *
775
+ * @internal
776
+ */
777
+ export type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
778
+ /**
779
+ * Helper: the event map of a single reducer spec.
780
+ *
781
+ * @internal
782
+ */
783
+ export type EMOfSpec<Spec> = Spec extends ReducerSpec<any, infer EM> ? EM : never;
784
+ /**
785
+ * Helper: derive the combined event map from a reducers map (strict).
786
+ * Used by the createStore inference overload.
787
+ *
788
+ * Each slice contributes its own event map; those maps are **merged** (channels,
789
+ * and each channel's `type → payload` entries, combined across slices) rather
790
+ * than collapsed to a single slice's map. `EMOfSpec` distributes over the union
791
+ * of specs to yield the union of per-slice event maps, and `UnionToIntersection`
792
+ * merges them — so a store whose slices declare divergent event maps still types
793
+ * `emit` against the union of every slice's channels/types.
674
794
  *
675
795
  * @internal
676
796
  */
677
- export type EMFromReducersStrict<RM extends ReducersMapAny> = RM[keyof RM] extends ReducerSpec<any, infer EM> ? RM[keyof RM] extends ReducerSpec<any, EM> ? EM : never : never;
797
+ export type EMFromReducersStrict<RM extends ReducersMapAny> = UnionToIntersection<EMOfSpec<RM[keyof RM]>> extends infer Merged ? Merged extends EventMapBase ? Merged : EventMapBase : EventMapBase;
678
798
  /**
679
799
  * Matcher for event targeting across reducers, effects, middleware, and subscriptions.
680
800
  *
@@ -5,7 +5,7 @@
5
5
  * Computes the list of **dotted leaf paths** that changed between two values.
6
6
  *
7
7
  * The algorithm performs a deep structural comparison with special handling for:
8
- * - **Primitives / null** → treated as leafs (change = current `path`)
8
+ * - **Primitives / null** → treated as leafs (change = current `path`; two `NaN`s are equal)
9
9
  * - **Date** → compares `getTime()`
10
10
  * - **RegExp** → compares `source` and `flags`
11
11
  * - **Arrays** → if lengths differ, the whole array path is marked changed; otherwise compares
@@ -13,14 +13,17 @@
13
13
  * - **Objects** → compares by the **union of keys**, recursing into shared keys and marking
14
14
  * added/removed keys as changed at their **full path**
15
15
  *
16
- * Cycles and repeated object aliases are handled via **pair-wise** tracking using a
17
- * `WeakMap<object, WeakSet<object>>` so recursion doesn't loop and shared subgraphs do not
18
- * produce false negatives.
16
+ * Cycles are handled by tracking the `(old, new)` pairs currently on the **recursion path**
17
+ * (added on entry, removed on unwind). A pair is skipped only when it is a genuine ancestor of
18
+ * itself (a real cycle) — a pair that merely appears again at a *sibling* path (legitimate
19
+ * aliasing, e.g. the same object referenced from two keys) is still diffed, so real changes at
20
+ * the second site are never dropped.
19
21
  *
20
22
  * @param oldState - Previous value to diff.
21
23
  * @param newState - Next value to diff.
22
24
  * @param path - Current dotted path (callers pass `""` for root; recursion appends segments).
23
- * @param seenPairs - (Advanced) Pair tracker for cycle/alias detection. You generally never pass this.
25
+ * @param ancestors - (Advanced) Pairs on the current recursion path, for cycle detection. You
26
+ * generally never pass this.
24
27
  * @returns An array of **dotted leaf paths** that changed. Paths use `"."` as a separator and
25
28
  * indices for arrays (e.g., `"todos.0.title"`). If nothing changed, returns `[]`.
26
29
  *
@@ -64,4 +67,4 @@
64
67
  *
65
68
  * @public
66
69
  */
67
- export declare function detectChangedProps(oldState: any, newState: any, path?: string, seenPairs?: WeakMap<object, WeakSet<object>>): string[];
70
+ export declare function detectChangedProps(oldState: any, newState: any, path?: string, ancestors?: Map<object, Set<object>>): string[];
@@ -1,8 +1,11 @@
1
1
  /*!
2
- * @yoltra/core v0.1.0
2
+ * @yoltra/core v0.2.0
3
3
  * (c) 2026 Manu Ramirez <@pixerael>
4
4
  * License: MIT
5
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
6
9
  */
7
- "use strict";var k=Object.defineProperty;var _=(a,e,t)=>e in a?k(a,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):a[e]=t;var u=(a,e,t)=>_(a,typeof e!="symbol"?e+"":e,t);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class M{constructor(){u(this,"handlers",new Map)}on(e,t,s){let n=this.handlers.get(e);n||(n=new Map,this.handlers.set(e,n));let r=n.get(t);return r||(r=new Set,n.set(t,r)),r.add(s),()=>this.off(e,t,s)}off(e,t,s){const n=this.handlers.get(e);if(!n)return;const r=n.get(t);r&&(r.delete(s),r.size===0&&n.delete(t),n.size===0&&this.handlers.delete(e))}emit(e,t,s){const n=this.handlers.get(e);if(!n)return;const r=n.get(t);if(!(!r||r.size===0))for(const o of[...r])try{o(s)}catch(i){console.error("EventBus handler error:",i)}}clear(){this.handlers.clear()}}class P{constructor(){u(this,"handlers",new Map);u(this,"patternHandlers",new Map)}on(e,t,s){const n=String(t);if(this.isPattern(n)){const r=n;this.patternHandlers.has(e)||this.patternHandlers.set(e,new Map);const o=this.patternHandlers.get(e);return o.has(r)||o.set(r,[]),o.get(r).push(s),()=>this.offPattern(e,r,s)}else{const r=this.normalizeTypeKey(n);this.handlers.has(e)||this.handlers.set(e,new Map);const o=this.handlers.get(e);return o.has(r)||o.set(r,[]),o.get(r).push(s),()=>this.offExactNormalized(e,r,s)}}off(e,t,s){const n=this.normalizeTypeKey(String(t));this.offExactNormalized(e,n,s)}offExactNormalized(e,t,s){const n=this.handlers.get(e);if(!n)return;const r=n.get(t);if(!r)return;const o=r.indexOf(s);o!==-1&&r.splice(o,1),r.length===0&&n.delete(t),n.size===0&&this.handlers.delete(e)}offPattern(e,t,s){const n=this.patternHandlers.get(e);if(!n)return;const r=n.get(t);if(!r)return;const o=r.indexOf(s);o!==-1&&r.splice(o,1),r.length===0&&n.delete(t),n.size===0&&this.patternHandlers.delete(e)}emit(e,t,s){const n=String(t),r=this.normalizeTypeKey(n),o=this.handlers.get(e)?.get(r)??[],i=this.patternHandlers.get(e),c=[];if(i&&i.size){const l=this.normalizeTypeKey(n);for(const[p,d]of i.entries())this.matchPattern(p,l)&&c.push(d)}const f=new Set,h=l=>{for(const p of[...l])if(!f.has(p)){f.add(p);try{p(s)}catch(d){console.error(d);continue}}};h(o);for(const l of c)h(l)}isPattern(e){return e.includes("*")}normalizeTypeKey(e){return e.replace(/^\./,"")}splitPath(e){return this.normalizeTypeKey(e).split(".").filter(Boolean)}matchPattern(e,t){const s=this.splitPath(e),n=this.splitPath(t);let r=0,o=0;for(;r<s.length&&o<n.length;){const i=s[r];if(i==="**"){if(r===s.length-1||s.slice(r).filter(f=>f!=="**").length===0)return!0;for(let f=o;f<=n.length;f++)if(this.matchPattern(s.slice(r+1).join("."),n.slice(f).join(".")))return!0;return!1}if(i==="*"||i===n[o]){r++,o++;continue}return!1}if(o===n.length){for(;r<s.length;r++)if(s[r]!=="**")return!1;return!0}return!1}clear(){this.handlers.clear(),this.patternHandlers.clear()}__introspect(){const e=[];for(const[t,s]of this.handlers)for(const[n,r]of s)r.length>0&&e.push({channel:t,type:n,count:r.length});for(const[t,s]of this.patternHandlers)for(const[n,r]of s)r.length>0&&e.push({channel:t,type:n,count:r.length});return e}}class z{constructor(e){u(this,"_reduce");this._reduce=e}reduce(e,t){return this._reduce(e,t)}}function b(a,e,t="",s=new WeakMap){if(a===e)return[];if(typeof a!="object"||typeof e!="object"||a===null||e===null)return[t];if(a instanceof Date&&e instanceof Date)return a.getTime()===e.getTime()?[]:[t];if(a instanceof RegExp&&e instanceof RegExp)return a.source===e.source&&e.flags===a.flags?[]:[t];const n=a,r=e;let o=s.get(n);if(o){if(o.has(r))return[];o.add(r)}else o=new WeakSet,o.add(r),s.set(n,o);const i=Array.isArray(a),c=Array.isArray(e);if(i!==c)return[t];const f=[];if(i){const d=a,m=e;if(d.length!==m.length)return[t];for(let y=0;y<d.length;y++){const g=t?`${t}.${y}`:`${y}`,w=b(d[y],m[y],g,s);f.push(...w)}return f.filter(Boolean)}const h=Object.keys(a),l=Object.keys(e),p=new Set([...h,...l]);for(const d of p){const m=t?`${t}.${d}`:d,y=Object.prototype.hasOwnProperty.call(a,d),g=Object.prototype.hasOwnProperty.call(e,d);if(!y){f.push(m);continue}if(!g){f.push(m);continue}const w=b(a[d],e[d],m,s);f.push(...w)}return f.filter(Boolean)}function E(a,e=new WeakSet){if(a===null||typeof a!="object"||e.has(a)||Object.isFrozen(a))return a;if(e.add(a),Array.isArray(a)){const t=a;for(let s=0;s<t.length;s++)t[s]=E(t[s],e);return Object.freeze(t)}for(const t of Object.getOwnPropertyNames(a)){const s=Object.getOwnPropertyDescriptor(a,t);!s||!("value"in s)||(a[t]=E(a[t],e))}for(const t of Object.getOwnPropertySymbols(a)){const s=Object.getOwnPropertyDescriptor(a,t);!s||!("value"in s)||(a[t]=E(a[t],e))}return Object.freeze(a)}class v{constructor(e){u(this,"name");u(this,"middleware");u(this,"reducers");u(this,"state");u(this,"reducerBus");u(this,"connectorBus");u(this,"listeners",new Set);u(this,"effects",new Map);u(this,"patternEffects",new Set);u(this,"committedEventSubscribers",new Map);u(this,"uncommittedEventSubscribers",new Map);u(this,"allEventSubscribers",new Map);u(this,"sliceUnsubs",new Map);u(this,"patternReducers",new Map);u(this,"replayEnabled");u(this,"eventQueue",[]);u(this,"isProcessingQueue",!1);u(this,"processedEvents",new Map);u(this,"dedupConfig");u(this,"eventCleanupTimer",null);this.name=e.name??"yoltra Store",this.reducerBus=new M,this.connectorBus=new P,this.middleware=[...e.middleware??[]],this.reducers={},this.state={},this.replayEnabled=e.devtools?.allowReplay??!1;const t=process.env.NODE_ENV==="production"?100:50;if(this.dedupConfig={windowMs:e.dedupWindowMs??t,maxCacheSize:1e3},Object.entries(e.reducer).forEach(([s,n])=>{this.mountSlice(s,n,{preserveState:!1})}),e.effects?.length)for(const s of e.effects)this.registerEffect(s);this.eventCleanupTimer=setInterval(()=>{this.pruneProcessedEvents(Date.now())},5e3),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()}fingerprint(e,t,s){const n=`${e}::${t}`;try{if(s==null)return`${n}::null`;if(typeof s!="object")return`${n}::${String(s)}`;const r=JSON.stringify(s);return`${n}::${r}`}catch{return`${n}::${Date.now()}::${Math.random()}`}}shouldDedupe(e){const t=Date.now(),s=this.processedEvents.get(e);return s!==void 0&&t-s<this.dedupConfig.windowMs?!0:(this.processedEvents.set(e,t),this.processedEvents.size>this.dedupConfig.maxCacheSize&&this.pruneProcessedEvents(t),!1)}pruneProcessedEvents(e){const t=e-this.dedupConfig.windowMs*2;for(const[s,n]of this.processedEvents)n<t&&this.processedEvents.delete(s)}matchesWhen(e,t){return!e||"any"in e&&e.any===!0?!0:"keys"in e?e.keys.some(([s,n])=>t.channel===s&&t.type===n):"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)}`,s=this.effects.get(t);if(s&&s.size>0)for(const n of[...s])try{await n(e,this.getState,this.emit)}catch(r){console.error("Effect error:",r)}for(const{effect:n,when:r}of this.patternEffects)if(this.matchesWhen(r,e))try{await n(e,this.getState,this.emit)}catch(o){console.error("Effect error:",o)}}async notifyEventSubscribers(e,t){const s=`${String(e.channel)}::${String(e.type)}`,r=(t==="committed"?this.committedEventSubscribers:this.uncommittedEventSubscribers).get(s);if(r?.size)for(const i of[...r])try{await i(e,this.getState,this.emit,t)}catch(c){console.error("Event subscription error:",c)}const o=this.allEventSubscribers.get(s);if(o?.size)for(const i of[...o])try{await i(e,this.getState,this.emit,t)}catch(c){console.error("Event subscription error:",c)}}forwardEvent(e,t){const s=this.state[e],n=this.reducers[e].reduce(s,t);if(s===n)return!1;const r=b(s,n).filter(Boolean);if(r.length===0)return!1;const o=E(structuredClone(n));this.state={...this.state,[e]:o};const i=new Set;for(const c of r)for(const f of v.buildAncestorPaths(c))i.add(f);for(const c of i){const f=this.getAtPath(s,c),h=this.getAtPath(o,c);this.connectorBus.emit(e,c,{oldValue:f,newValue:h,path:c})}return!0}__devtoolsIntrospect(){const e=Object.keys(this.reducers).map(i=>{const c=this.patternReducers.get(i);return{name:i,when:c}}),t=[];for(const[i,c]of this.effects){if(c.size===0)continue;const[f,h]=i.split("::");for(const l of c){const p=l.__quoMeta;t.push({channel:f,type:h,name:p?.name,description:p?.description})}}for(const i of this.patternEffects){const c=i.effect.__quoMeta;t.push({channel:"*",type:"*",name:c?.name,description:c?.description})}const s=[];for(const i of this.middleware)typeof i=="function"?s.push({name:i.name||void 0}):s.push({name:i.meta?.name,description:i.meta?.description,when:i.when});const n=[];for(const i of this.connectorBus.__introspect())for(let c=0;c<i.count;c++)n.push({reducer:i.channel,property:i.type});const r=[];for(const[i,c]of this.committedEventSubscribers){if(c.size===0)continue;const[f,h]=i.split("::");for(let l=0;l<c.size;l++)r.push({channel:f,type:h,phase:"committed"})}for(const[i,c]of this.uncommittedEventSubscribers){if(c.size===0)continue;const[f,h]=i.split("::");for(let l=0;l<c.size;l++)r.push({channel:f,type:h,phase:"uncommitted"})}for(const[i,c]of this.allEventSubscribers){if(c.size===0)continue;const[f,h]=i.split("::");for(let l=0;l<c.size;l++)r.push({channel:f,type:h,phase:"all"})}const o=this.listeners.size;return{reducers:e,effects:t,middleware:s,atomic:n,event:r,coarse:o}}__applyExternalState(e){const t=this.state,s=e;let n={...this.state},r=!1;Object.keys(this.reducers).forEach(o=>{const i=t?.[o],c=s?.[o];if(i===c)return;const f=E(structuredClone(c));n[o]=f,r=!0;const h=b(i,c).filter(Boolean);if(h.length===0)return;const l=new Set;for(const p of h)for(const d of v.buildAncestorPaths(p))l.add(d);for(const p of l){const d=this.getAtPath(i,p),m=this.getAtPath(f,p);this.connectorBus.emit(o,p,{oldValue:d,newValue:m,path:p})}}),r&&(this.state=n),r&&this.listeners.forEach(o=>o())}__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 s of t){const n=s,r=this.state;this.reducerBus.emit(n.channel,n.type,n.payload);for(const[c,f]of this.patternReducers)this.matchesWhen(f,n)&&this.forwardEvent(c,n);const o=this.state,i=r!==o;this.notifyEventSubscribers(n,"committed"),i&&this.listeners.forEach(c=>c())}}async emit(e,t,s){const n=this.fingerprint(e,t,s);if(this.shouldDedupe(n))return;const r=crypto.randomUUID();if(this.eventQueue.push({channel:e,type:t,payload:s,id:r}),!this.isProcessingQueue){this.isProcessingQueue=!0;try{for(;this.eventQueue.length;){const{channel:o,type:i,payload:c,id:f}=this.eventQueue.shift(),h={channel:o,type:i,payload:c,id:f};let l=!0;for(const y of this.middleware){const g=this.getMiddlewareWhen(y);if(!this.matchesWhen(g,h))continue;const w=this.getMiddlewareFunction(y);try{if(!await w(this.state,h,this.emit)){l=!1;break}}catch(S){console.error("Middleware error:",S),l=!1;break}}if(!l){await this.notifyEventSubscribers(h,"uncommitted");continue}const p=this.state;this.reducerBus.emit(o,i,c);for(const[y,g]of this.patternReducers)this.matchesWhen(g,h)&&this.forwardEvent(y,h);const d=this.state,m=p!==d;await this.notifyEventSubscribers(h,"committed"),await this.notifyEffects(h),m&&this.listeners.forEach(y=>y())}}catch(o){console.error("Emit queue error:",o)}finally{this.isProcessingQueue=!1}}}connect(e,t){return this.connectorBus.on(e.reducer,e.property,t)}onEvent(e,t,s,n="committed"){const r=`${e}::${String(t)}`,o=n==="committed"?this.committedEventSubscribers:n==="uncommitted"?this.uncommittedEventSubscribers:this.allEventSubscribers;return o.has(r)||o.set(r,new Set),o.get(r).add(s),()=>{const i=o.get(r);i&&(i.delete(s),i.size===0&&o.delete(r))}}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(e in this.reducers)throw new Error(`Reducer ${e} already exists`);return this.mountSlice(e,t,{preserveState:!1}),this.listeners.forEach(s=>s()),()=>{this.unmountSlice(e,{deleteState:!0}),this.listeners.forEach(s=>s())}}registerEffect(e){const{effect:t,meta:s,when:n}=e,r=[];if(s&&(t.__quoMeta=s),n&&("any"in n&&n.any===!0||"channel"in n||"channels"in n)){const c={effect:t,when:n};return this.patternEffects.add(c),()=>{this.patternEffects.delete(c)}}const i=this.normalizeEventKeys(e);if(i.length===0&&!n&&!e.events){const c={effect:t,when:{any:!0}};return this.patternEffects.add(c),()=>{this.patternEffects.delete(c)}}for(const[c,f]of i){const h=`${String(c)}::${String(f)}`;this.effects.has(h)||this.effects.set(h,new Set),this.effects.get(h).add(t),r.push(()=>{const l=this.effects.get(h);l&&(l.delete(t),l.size===0&&this.effects.delete(h))})}return()=>{for(const c of r)c()}}onEffect(e,t,s){const n=async(r,o,i)=>{if(r.channel!==e||r.type!==t)return;const c=r;return s(c.payload,o,i,c)};return this.registerEffect({events:[[e,t]],effect:n})}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 s=t.preserveState!==!1,n=new Set(Object.keys(this.reducers)),r=Object.entries(e),o=new Set(r.map(([i])=>i));for(const i of n)o.has(i)||this.unmountSlice(i,{deleteState:!0});for(const[i,c]of r)n.has(i)?(this.unmountSlice(i,{deleteState:!1}),this.mountSlice(i,c,{preserveState:s})):this.mountSlice(i,c,{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,s){const n=e,{events:r,reducer:o,state:i,when:c}=t;if(this.reducers[e]=new z(o),(!s.preserveState||this.state[n]===void 0)&&(this.state[n]=E(structuredClone(i))),c&&("any"in c&&c.any===!0||"channel"in c||"channels"in c)){this.patternReducers.set(e,c),this.sliceUnsubs.set(n,[]);return}const h=this.normalizeEventKeys(t);if(h.length===0&&!c&&!r){this.patternReducers.set(e,{any:!0}),this.sliceUnsubs.set(n,[]);return}const l=[];for(const[p,d]of h){const m=this.reducerBus.on(p,d,y=>{const g={channel:p,type:d,payload:y,id:crypto.randomUUID()};this.forwardEvent(e,g)});l.push(m)}this.sliceUnsubs.set(n,l)}unmountSlice(e,t){const s=e;this.patternReducers.delete(e);const n=this.sliceUnsubs.get(s);if(n){for(const r of n)try{r()}catch(o){console.error(`[Store error]: ${o}`)}this.sliceUnsubs.delete(s)}delete this.reducers[e],t.deleteState&&delete this.state[s]}normalizeEventKeys(e){if(e.when){const t=e.when;if("any"in t&&t.any===!0)return[];if("keys"in t)return t.keys;if("channel"in t)return[];if("channels"in t)return[]}return e.events?e.events:[]}getAtPath(e,t){if(!t)return e;const n=(t[0]==="."?t.slice(1):t).split(".");let r=e;for(const o of n){if(r==null)return;r=r[o]}return r}static buildAncestorPaths(e){if(!e)return[];const s=(e[0]==="."?e.slice(1):e).split("."),n=[];for(let r=0;r<s.length;r++)n.push(s.slice(0,r+1).join("."));return n}}function O(a){return new v({name:a.name,reducer:a.reducer??{},middleware:a.middleware??[],effects:a.effects??[],dedupWindowMs:a.dedupWindowMs,devtools:a.devtools})}const B=a=>(e,t)=>t.map(s=>[e,s]),x=()=>a=>a;exports.EventBus=M;exports.LooseEventBus=P;exports.Reducer=z;exports.Store=v;exports.createStore=O;exports.detectChangedProps=b;exports.eventKeys=x;exports.freezeState=E;exports.typedEvents=B;
10
+ "use strict";var k=Object.defineProperty;var R=(a,e,t)=>e in a?k(a,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):a[e]=t;var d=(a,e,t)=>R(a,typeof e!="symbol"?e+"":e,t);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class M{constructor(){d(this,"handlers",new Map)}on(e,t,r){let n=this.handlers.get(e);n||(n=new Map,this.handlers.set(e,n));let s=n.get(t);return s||(s=new Set,n.set(t,s)),s.add(r),()=>this.off(e,t,r)}off(e,t,r){const n=this.handlers.get(e);if(!n)return;const s=n.get(t);s&&(s.delete(r),s.size===0&&n.delete(t),n.size===0&&this.handlers.delete(e))}emit(e,t,r){const n=this.handlers.get(e);if(!n)return;const s=n.get(t);if(!(!s||s.size===0))for(const c of[...s])try{c(r)}catch(i){console.error("EventBus handler error:",i)}}clear(){this.handlers.clear()}}class P{constructor(){d(this,"handlers",new Map);d(this,"patternHandlers",new Map)}on(e,t,r){const n=String(t);if(this.isPattern(n)){const s=n;this.patternHandlers.has(e)||this.patternHandlers.set(e,new Map);const c=this.patternHandlers.get(e);return c.has(s)||c.set(s,[]),c.get(s).push(r),()=>this.offPattern(e,s,r)}else{const s=this.normalizeTypeKey(n);this.handlers.has(e)||this.handlers.set(e,new Map);const c=this.handlers.get(e);return c.has(s)||c.set(s,[]),c.get(s).push(r),()=>this.offExactNormalized(e,s,r)}}off(e,t,r){const n=this.normalizeTypeKey(String(t));this.offExactNormalized(e,n,r)}offExactNormalized(e,t,r){const n=this.handlers.get(e);if(!n)return;const s=n.get(t);if(!s)return;const c=s.indexOf(r);c!==-1&&s.splice(c,1),s.length===0&&n.delete(t),n.size===0&&this.handlers.delete(e)}offPattern(e,t,r){const n=this.patternHandlers.get(e);if(!n)return;const s=n.get(t);if(!s)return;const c=s.indexOf(r);c!==-1&&s.splice(c,1),s.length===0&&n.delete(t),n.size===0&&this.patternHandlers.delete(e)}emit(e,t,r){const n=String(t),s=this.normalizeTypeKey(n),c=this.handlers.get(e)?.get(s)??[],i=this.patternHandlers.get(e),o=[];if(i&&i.size){const l=this.normalizeTypeKey(n);for(const[u,p]of i.entries())this.matchPattern(u,l)&&o.push(p)}const f=new Set,h=l=>{for(const u of[...l])if(!f.has(u)){f.add(u);try{u(r)}catch(p){console.error(p);continue}}};h(c);for(const l of o)h(l)}isPattern(e){return e.includes("*")}normalizeTypeKey(e){return e.replace(/^\./,"")}splitPath(e){return this.normalizeTypeKey(e).split(".").filter(Boolean)}matchPattern(e,t){const r=this.splitPath(e),n=this.splitPath(t);let s=0,c=0,i=-1,o=0;for(;c<n.length;)if(s<r.length&&(r[s]==="*"||r[s]===n[c]))s++,c++;else if(s<r.length&&r[s]==="**")i=s,o=c,s++;else if(i!==-1)s=i+1,c=++o;else return!1;for(;s<r.length&&r[s]==="**";)s++;return s===r.length}clear(){this.handlers.clear(),this.patternHandlers.clear()}__introspect(){const e=[];for(const[t,r]of this.handlers)for(const[n,s]of r)s.length>0&&e.push({channel:t,type:n,count:s.length});for(const[t,r]of this.patternHandlers)for(const[n,s]of r)s.length>0&&e.push({channel:t,type:n,count:s.length});return e}}class z{constructor(e){d(this,"_reduce");this._reduce=e}reduce(e,t){return this._reduce(e,t)}}function g(a,e,t="",r=new Map){if(a===e)return[];if(typeof a!="object"||typeof e!="object"||a===null||e===null)return typeof a=="number"&&Number.isNaN(a)&&Number.isNaN(e)?[]:[t];if(a instanceof Date&&e instanceof Date)return a.getTime()===e.getTime()?[]:[t];if(a instanceof RegExp&&e instanceof RegExp)return a.source===e.source&&e.flags===a.flags?[]:[t];const n=a,s=e,c=r.get(n);if(c?.has(s))return[];const i=c??new Set;i.add(s),c||r.set(n,i);try{const o=Array.isArray(a),f=Array.isArray(e);if(o!==f)return[t];const h=[];if(o){const u=a,p=e;if(u.length!==p.length)return[t];for(let y=0;y<u.length;y++){const m=t?`${t}.${y}`:`${y}`;h.push(...g(u[y],p[y],m,r))}return h.filter(Boolean)}const l=new Set([...Object.keys(a),...Object.keys(e)]);for(const u of l){const p=t?`${t}.${u}`:u,y=Object.prototype.hasOwnProperty.call(a,u),m=Object.prototype.hasOwnProperty.call(e,u);if(!y||!m){h.push(p);continue}h.push(...g(a[u],e[u],p,r))}return h.filter(Boolean)}finally{i.delete(s),i.size===0&&r.delete(n)}}function E(a,e=new WeakSet){if(a===null||typeof a!="object"||e.has(a)||Object.isFrozen(a))return a;if(e.add(a),Array.isArray(a)){const t=a;for(let r=0;r<t.length;r++)t[r]=E(t[r],e);return Object.freeze(t)}for(const t of Object.getOwnPropertyNames(a)){const r=Object.getOwnPropertyDescriptor(a,t);!r||!("value"in r)||(a[t]=E(a[t],e))}for(const t of Object.getOwnPropertySymbols(a)){const r=Object.getOwnPropertyDescriptor(a,t);!r||!("value"in r)||(a[t]=E(a[t],e))}return Object.freeze(a)}function b(a){return process.env.NODE_ENV==="production"?a:E(a)}const w=100,S=()=>typeof performance<"u"&&typeof performance.now=="function"?performance.now():Date.now();class v{constructor(e){d(this,"name");d(this,"middleware");d(this,"reducers");d(this,"state");d(this,"reducerBus");d(this,"connectorBus");d(this,"listeners",new Set);d(this,"effects",new Map);d(this,"patternEffects",new Set);d(this,"committedEventSubscribers",new Map);d(this,"uncommittedEventSubscribers",new Map);d(this,"allEventSubscribers",new Map);d(this,"sliceUnsubs",new Map);d(this,"patternReducers",new Map);d(this,"replayEnabled");d(this,"onEffectError");d(this,"reduceQueue",[]);d(this,"isReducing",!1);d(this,"instrumentObservers",new Set);d(this,"changedPathSink",null);d(this,"inFlightEffects",0);d(this,"processedEvents",new Map);d(this,"dedupCount",0);d(this,"effectMeta",new WeakMap);d(this,"dedupConfig");d(this,"eventCleanupTimer",null);if(this.name=e.name??"yoltra Store",this.reducerBus=new M,this.connectorBus=new P,this.middleware=[...e.middleware??[]],this.reducers={},this.state={},this.replayEnabled=e.devtools?.allowReplay??!1,this.onEffectError=e.onEffectError,this.dedupConfig={windowMs:e.dedupWindowMs??0,maxCacheSize:1e3},Object.entries(e.reducer).forEach(([t,r])=>{this.mountSlice(t,r,{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.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,r){const n=`${e}::${t}`;try{if(r==null)return`${n}::null`;if(typeof r!="object")return`${n}::${String(r)}`;const s=JSON.stringify(r);return`${n}::${s}`}catch{return`${n}::${Date.now()}::${Math.random()}`}}shouldDedupe(e,t){const r=Date.now(),n=this.processedEvents.get(e);return n!==void 0&&r-n<t?(this.dedupCount++,!0):(this.processedEvents.set(e,r),this.ensureCleanupTimer(),this.processedEvents.size>this.dedupConfig.maxCacheSize&&this.pruneProcessedEvents(r),!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,w),r=e-t*2;for(const[n,s]of this.processedEvents)s<r&&this.processedEvents.delete(n);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(([r,n])=>t.channel===r&&t.type===n):"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)}`,r=this.effects.get(t);if(r&&r.size>0)for(const n of[...r])try{await n(e,this.getState,this.emit)}catch(s){console.error("Effect error:",s),this.onEffectError?.(s,e)}for(const{effect:n,when:s}of this.patternEffects)if(this.matchesWhen(s,e))try{await n(e,this.getState,this.emit)}catch(c){console.error("Effect error:",c),this.onEffectError?.(c,e)}}notifyEventSubscribers(e,t){const r=`${String(e.channel)}::${String(e.type)}`,s=(t==="committed"?this.committedEventSubscribers:this.uncommittedEventSubscribers).get(r);if(s?.size)for(const i of[...s])this.invokeEventSubscriber(i,e,t);const c=this.allEventSubscribers.get(r);if(c?.size)for(const i of[...c])this.invokeEventSubscriber(i,e,t)}invokeEventSubscriber(e,t,r){try{const n=e(t,this.getState,this.emit,r);n&&typeof n.then=="function"&&n.catch(s=>console.error("Event subscription error:",s))}catch(n){console.error("Event subscription error:",n)}}forwardEvent(e,t){const r=this.state[e],n=this.reducers[e].reduce(r,t);if(r===n)return!1;const s=g(r,n).filter(Boolean);if(s.length===0)return!1;const c=b(n);if(this.state={...this.state,[e]:c},this.changedPathSink)for(const o of s)this.changedPathSink.push(o?`${e}.${o}`:e);const i=new Set;for(const o of s)for(const f of v.buildAncestorPaths(o))i.add(f);for(const o of i){const f=this.getAtPath(r,o),h=this.getAtPath(c,o);this.connectorBus.emit(e,o,{oldValue:f,newValue:h,path:o})}return!0}__devtoolsIntrospect(){const e=Object.keys(this.reducers).map(i=>{const o=this.patternReducers.get(i);return{name:i,when:o}}),t=[];for(const[i,o]of this.effects){if(o.size===0)continue;const[f,h]=i.split("::");for(const l of o){const u=this.effectMeta.get(l);t.push({channel:f,type:h,name:u?.name,description:u?.description})}}for(const i of this.patternEffects){const o=this.effectMeta.get(i.effect);t.push({channel:"*",type:"*",name:o?.name,description:o?.description})}const r=[];for(const i of this.middleware)typeof i=="function"?r.push({name:i.name||void 0}):r.push({name:i.meta?.name,description:i.meta?.description,when:i.when});const n=[];for(const i of this.connectorBus.__introspect())for(let o=0;o<i.count;o++)n.push({reducer:i.channel,property:i.type});const s=[];for(const[i,o]of this.committedEventSubscribers){if(o.size===0)continue;const[f,h]=i.split("::");for(let l=0;l<o.size;l++)s.push({channel:f,type:h,phase:"committed"})}for(const[i,o]of this.uncommittedEventSubscribers){if(o.size===0)continue;const[f,h]=i.split("::");for(let l=0;l<o.size;l++)s.push({channel:f,type:h,phase:"uncommitted"})}for(const[i,o]of this.allEventSubscribers){if(o.size===0)continue;const[f,h]=i.split("::");for(let l=0;l<o.size;l++)s.push({channel:f,type:h,phase:"all"})}const c=this.listeners.size;return{reducers:e,effects:t,middleware:r,atomic:n,event:s,coarse:c,dedupHits:this.dedupCount,queueDepth:this.reduceQueue.length+this.inFlightEffects}}__applyExternalState(e){if(!this.replayEnabled){console.warn("[yoltra] External state apply (time-travel) is disabled. Enable it with createStore({ devtools: { allowReplay: true } })");return}const t=this.state,r=e,n={...this.state};let s=!1;Object.keys(this.reducers).forEach(c=>{const i=t?.[c],o=r?.[c];if(o===void 0){process.env.NODE_ENV!=="production"&&console.warn(`[yoltra] External state is missing slice "${String(c)}"; retaining its current value. Time-travel snapshots should contain all slices.`);return}if(i===o)return;const f=b(o);n[c]=f,s=!0;const h=g(i,o).filter(Boolean);if(h.length===0)return;const l=new Set;for(const u of h)for(const p of v.buildAncestorPaths(u))l.add(p);for(const u of l){const p=this.getAtPath(i,u),y=this.getAtPath(f,u);this.connectorBus.emit(c,u,{oldValue:p,newValue:y,path:u})}}),s&&(this.state=n),s&&this.listeners.forEach(c=>c())}__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 r of t){const n=r,s=this.state;this.reducerBus.emit(n.channel,n.type,n.payload);for(const[o,f]of this.patternReducers)this.matchesWhen(f,n)&&this.forwardEvent(o,n);const c=this.state,i=s!==c;this.notifyEventSubscribers(n,"committed"),i&&this.listeners.forEach(o=>o())}}async emit(e,t,r,n){const s=n?.dedupKey,c=this.dedupConfig.windowMs;if(c>0||s!==void 0){const h=s!==void 0&&c<=0?w:c,l=s!==void 0?`${e}::${t}::#${s}`:this.fingerprint(e,t,r);if(this.shouldDedupe(l,h))return}const i=crypto.randomUUID();let o;const f=new Promise(h=>{o=h});return this.reduceQueue.push({channel:e,type:t,payload:r,id:i,resolve:o}),this.drainReduce(),f}drainReduce(){if(!this.isReducing){this.isReducing=!0;try{for(;this.reduceQueue.length>0;){const{channel:e,type:t,payload:r,id:n,resolve:s}=this.reduceQueue.shift(),c={channel:e,type:t,payload:r,id:n},i=this.instrumentObservers.size>0,o=i?this.state:void 0,f=[];i&&(this.changedPathSink=f);const h=i?S():0;let l=!1;try{l=this.applyEventSync(c)}catch(u){console.error("Emit reduce error:",u)}finally{i&&(this.changedPathSink=null)}i&&this.emitInstrumentation(c,l,f,o,S()-h),this.runEventEffects(c,l,s)}}finally{this.isReducing=!1}}}applyEventSync(e){for(const n of this.middleware){const s=this.getMiddlewareWhen(n);if(!this.matchesWhen(s,e))continue;const c=this.getMiddlewareFunction(n);let i;try{i=c(this.state,e,this.emit)}catch(o){console.error("Middleware error:",o),i=!1}if(!i)return this.notifyEventSubscribers(e,"uncommitted"),!1}const t=this.state;this.reducerBus.emit(e.channel,e.type,e.payload);for(const[n,s]of this.patternReducers)this.matchesWhen(s,e)&&this.forwardEvent(n,e);const r=t!==this.state;return this.notifyEventSubscribers(e,"committed"),r&&this.listeners.forEach(n=>n()),!0}async runEventEffects(e,t,r){this.inFlightEffects++;try{t&&await this.notifyEffects(e)}catch(n){console.error("Effect error:",n)}finally{this.inFlightEffects--,r()}}instrument(e){return this.instrumentObservers.add(e),()=>{this.instrumentObservers.delete(e)}}emitInstrumentation(e,t,r,n,s){const c={},i={};for(const f of r)c[f]=this.getAtPath(n,f),i[f]=this.getAtPath(this.state,f);const o={event:{id:e.id,channel:e.channel,type:e.type,payload:e.payload},committed:t,changedPaths:r,prevValues:c,nextValues:i,reduceTimeMs:s};for(const f of[...this.instrumentObservers])try{f(o)}catch(h){console.error("Instrumentation observer error:",h)}}connect(e,t){return this.connectorBus.on(e.reducer,e.property,t)}onEvent(e,t,r,n="committed"){const s=`${e}::${String(t)}`,c=n==="committed"?this.committedEventSubscribers:n==="uncommitted"?this.uncommittedEventSubscribers:this.allEventSubscribers;return c.has(s)||c.set(s,new Set),c.get(s).add(r),()=>{const i=c.get(s);i&&(i.delete(r),i.size===0&&c.delete(s))}}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(e in this.reducers)throw new Error(`Reducer ${e} already exists`);return this.mountSlice(e,t,{preserveState:!1}),this.listeners.forEach(r=>r()),()=>{this.unmountSlice(e,{deleteState:!0}),this.listeners.forEach(r=>r())}}registerEffect(e){const{effect:t,meta:r,when:n}=e,s=[];if(r&&this.effectMeta.set(t,r),n&&("any"in n&&n.any===!0||"channel"in n||"channels"in n)){const o={effect:t,when:n};return this.patternEffects.add(o),()=>{this.patternEffects.delete(o)}}const i=this.normalizeEventKeys(e);if(i.length===0&&!n&&!e.events){const o={effect:t,when:{any:!0}};return this.patternEffects.add(o),()=>{this.patternEffects.delete(o)}}for(const[o,f]of i){const h=`${String(o)}::${String(f)}`;this.effects.has(h)||this.effects.set(h,new Set),this.effects.get(h).add(t),s.push(()=>{const l=this.effects.get(h);l&&(l.delete(t),l.size===0&&this.effects.delete(h))})}return()=>{for(const o of s)o()}}onEffect(e,t,r){const n=async(s,c,i)=>{if(s.channel!==e||s.type!==t)return;const o=s;return r(o.payload,c,i,o)};return this.registerEffect({events:[[e,t]],effect:n})}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 r=t.preserveState!==!1,n=new Set(Object.keys(this.reducers)),s=Object.entries(e),c=new Set(s.map(([i])=>i));for(const i of n)c.has(i)||this.unmountSlice(i,{deleteState:!0});for(const[i,o]of s)n.has(i)?(this.unmountSlice(i,{deleteState:!1}),this.mountSlice(i,o,{preserveState:r})):this.mountSlice(i,o,{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,r){const n=e,{events:s,reducer:c,state:i,when:o}=t;if(this.reducers[e]=new z(c),(!r.preserveState||this.state[n]===void 0)&&(this.state[n]=b(structuredClone(i))),o&&("any"in o&&o.any===!0||"channel"in o||"channels"in o)){this.patternReducers.set(e,o),this.sliceUnsubs.set(n,[]);return}const h=this.normalizeEventKeys(t);if(h.length===0&&!o&&!s){this.patternReducers.set(e,{any:!0}),this.sliceUnsubs.set(n,[]);return}const l=[];for(const[u,p]of h){const y=this.reducerBus.on(u,p,m=>{const O={channel:u,type:p,payload:m,id:crypto.randomUUID()};this.forwardEvent(e,O)});l.push(y)}this.sliceUnsubs.set(n,l)}unmountSlice(e,t){const r=e;this.patternReducers.delete(e);const n=this.sliceUnsubs.get(r);if(n){for(const s of n)try{s()}catch(c){console.error(`[Store error]: ${c}`)}this.sliceUnsubs.delete(r)}delete this.reducers[e],t.deleteState&&delete this.state[r]}normalizeEventKeys(e){if(e.when){const t=e.when;if("any"in t&&t.any===!0)return[];if("keys"in t)return t.keys;if("channel"in t)return[];if("channels"in t)return[]}return e.events?e.events:[]}getAtPath(e,t){if(!t)return e;const n=(t[0]==="."?t.slice(1):t).split(".");let s=e;for(const c of n){if(s==null)return;s=s[c]}return s}static buildAncestorPaths(e){if(!e)return[];const r=(e[0]==="."?e.slice(1):e).split("."),n=[];for(let s=0;s<r.length;s++)n.push(r.slice(0,s+1).join("."));return n}}function _(a){return new v({name:a.name,reducer:a.reducer??{},middleware:a.middleware??[],effects:a.effects??[],dedupWindowMs:a.dedupWindowMs,devtools:a.devtools,onEffectError:a.onEffectError})}const $=a=>(e,t)=>t.map(r=>[e,r]),x=()=>a=>a;exports.EventBus=M;exports.LooseEventBus=P;exports.Reducer=z;exports.Store=v;exports.createStore=_;exports.detectChangedProps=g;exports.eventKeys=x;exports.freezeState=E;exports.typedEvents=$;
8
11
  //# sourceMappingURL=yoltra.cjs.js.map