@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.
- package/LICENSE +0 -0
- package/README.es.md +47 -32
- package/README.md +43 -30
- package/dist/types/index.d.ts +1 -1
- package/dist/types/store/Store.d.ts +133 -14
- package/dist/types/types.d.ts +134 -14
- package/dist/types/utils/detectChangedProps.d.ts +9 -6
- package/dist/yoltra.cjs.js +5 -2
- package/dist/yoltra.esm.js +547 -360
- package/dist/yoltra.umd.js +5 -2
- package/package.json +15 -16
package/dist/types/types.d.ts
CHANGED
|
@@ -113,13 +113,69 @@ export interface Change<V = any> {
|
|
|
113
113
|
*
|
|
114
114
|
* @public
|
|
115
115
|
*/
|
|
116
|
-
|
|
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
|
-
*
|
|
231
|
-
* within this window are
|
|
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
|
-
*
|
|
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
|
|
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,
|
|
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:
|
|
589
|
-
* Return true to continue
|
|
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
|
|
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
|
|
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:
|
|
673
|
-
*
|
|
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
|
|
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
|
|
17
|
-
*
|
|
18
|
-
*
|
|
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
|
|
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,
|
|
70
|
+
export declare function detectChangedProps(oldState: any, newState: any, path?: string, ancestors?: Map<object, Set<object>>): string[];
|
package/dist/yoltra.cjs.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* @yoltra/core v0.
|
|
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
|