@yoltra/core 0.4.0 → 0.6.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/README.es.md +214 -7
- package/README.md +315 -13
- package/dist/types/eventBus/EventBus.d.ts +1 -1
- package/dist/types/eventBus/index.d.ts +2 -2
- package/dist/types/index.d.ts +21 -16
- package/dist/types/persistence/adapters.d.ts +1 -1
- package/dist/types/persistence/persist.d.ts +3 -6
- package/dist/types/reducer/Reducer.d.ts +4 -3
- package/dist/types/store/Store.d.ts +252 -17
- package/dist/types/store/call.d.ts +149 -0
- package/dist/types/store/callQueue.d.ts +79 -0
- package/dist/types/store/rejection.d.ts +58 -0
- package/dist/types/types.d.ts +279 -14
- package/dist/types/utils/detectChangedProps.d.ts +6 -0
- package/dist/types/utils/immutability.d.ts +1 -1
- package/dist/types/utils/index.d.ts +2 -2
- package/dist/yoltra.cjs +11 -0
- package/dist/yoltra.cjs.map +1 -0
- package/dist/yoltra.mjs +2902 -0
- package/dist/yoltra.mjs.map +1 -0
- package/dist/yoltra.umd.js +2 -2
- package/dist/yoltra.umd.js.map +1 -0
- package/package.json +21 -21
- package/dist/yoltra.cjs.js +0 -11
- package/dist/yoltra.esm.js +0 -2374
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @yoltra/core
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Brand identifying a {@link Rejection}.
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* `Symbol.for` rather than `Symbol()`, so the brand survives two copies of this package meeting
|
|
9
|
+
* at runtime — a duplicated dependency, a federated bundle, a consumer that pinned an older
|
|
10
|
+
* minor. With a unique symbol the check would silently answer `false` across that boundary and a
|
|
11
|
+
* refusal would read as ordinary state, which is the failure this whole feature exists to end.
|
|
12
|
+
*
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
declare const REJECTED: unique symbol;
|
|
16
|
+
/**
|
|
17
|
+
* A reducer's refusal to apply a write, carrying the reason.
|
|
18
|
+
*
|
|
19
|
+
* @remarks
|
|
20
|
+
* Distinct from a reducer returning its state unchanged, which is indistinguishable from "the
|
|
21
|
+
* event did not concern me". A `Rejection` says *this write was considered and declined*, and it
|
|
22
|
+
* says why — which is what a contended store needs and what a lost update otherwise costs.
|
|
23
|
+
*
|
|
24
|
+
* @public
|
|
25
|
+
*/
|
|
26
|
+
export interface Rejection {
|
|
27
|
+
readonly [REJECTED]: true;
|
|
28
|
+
/** Why the write was refused. Surfaced to the caller and to `onRejected`. */
|
|
29
|
+
readonly reason: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Builds a {@link Rejection} for a reducer to return instead of state.
|
|
33
|
+
*
|
|
34
|
+
* @param reason - Why the write is refused; surfaced verbatim to the caller.
|
|
35
|
+
*
|
|
36
|
+
* @remarks
|
|
37
|
+
* Rejecting is a whole-event act: no slice commits, no change notifications fire, and the
|
|
38
|
+
* caller's `emit` resolves reporting the refusal. A reducer that merely has nothing to do should
|
|
39
|
+
* return its state, not this.
|
|
40
|
+
*
|
|
41
|
+
* @example Compare-and-swap on a contended slice
|
|
42
|
+
* ```ts
|
|
43
|
+
* reducer: (state, event) =>
|
|
44
|
+
* event.payload.expectedVersion === state.version
|
|
45
|
+
* ? { ...state, ...event.payload.patch, version: state.version + 1 }
|
|
46
|
+
* : Rejected(`stale write: expected v${event.payload.expectedVersion}, have v${state.version}`)
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* @public
|
|
50
|
+
*/
|
|
51
|
+
export declare function Rejected(reason: string): Rejection;
|
|
52
|
+
/**
|
|
53
|
+
* Whether a reducer returned a {@link Rejection} rather than state.
|
|
54
|
+
*
|
|
55
|
+
* @public
|
|
56
|
+
*/
|
|
57
|
+
export declare function isRejected(value: unknown): value is Rejection;
|
|
58
|
+
export {};
|
package/dist/types/types.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*/
|
|
1
|
+
import { Rejection } from './store/rejection.js';
|
|
2
|
+
import { CallHandle, CallOptions } from './store/call.js';
|
|
4
3
|
/**
|
|
5
4
|
* A minimal "record of record" constraint for EventMaps.
|
|
6
5
|
*
|
|
@@ -104,6 +103,29 @@ export interface Event<EM extends EventMapBase = EventMapBase, C extends keyof E
|
|
|
104
103
|
* Absent entirely unless {@link EmitOptions.meta} was supplied. See {@link EventMeta}.
|
|
105
104
|
*/
|
|
106
105
|
readonly meta?: EventMeta;
|
|
106
|
+
/**
|
|
107
|
+
* The `id` of the event whose handling caused this one, when there was one.
|
|
108
|
+
*
|
|
109
|
+
* @remarks
|
|
110
|
+
* Absent on a **root** event — one emitted by application code rather than by a middleware,
|
|
111
|
+
* subscriber or effect reacting to another event. Together with {@link Event.depth} this makes
|
|
112
|
+
* a cascade legible after the fact: without it, a runaway chain is a pile of unrelated events
|
|
113
|
+
* with no way to tell which caused which.
|
|
114
|
+
*/
|
|
115
|
+
readonly parentId?: string;
|
|
116
|
+
/**
|
|
117
|
+
* How many events deep in a causal chain this one is. A root event is depth `0`; an event
|
|
118
|
+
* emitted while handling it is `1`, and so on.
|
|
119
|
+
*
|
|
120
|
+
* @remarks
|
|
121
|
+
* Absent on a root event rather than present as `0`, so an event emitted by application code
|
|
122
|
+
* stays byte-identical to one built before causality tracking existed — the same treatment
|
|
123
|
+
* {@link Event.meta} gets, and for the same reason: `Object.keys` and `toStrictEqual` are load
|
|
124
|
+
* bearing in consumer tests.
|
|
125
|
+
*
|
|
126
|
+
* This is the value {@link StoreSpec.maxReduceDepth} bounds.
|
|
127
|
+
*/
|
|
128
|
+
readonly depth?: number;
|
|
107
129
|
}
|
|
108
130
|
/**
|
|
109
131
|
* Generic "old → new" wrapper for fine-grained change notifications.
|
|
@@ -127,6 +149,20 @@ export interface Change<V = any> {
|
|
|
127
149
|
newValue: V;
|
|
128
150
|
/** Dotted path for fine-grained listeners; e.g., "data.items.0.title" */
|
|
129
151
|
path?: string;
|
|
152
|
+
/**
|
|
153
|
+
* The `id` of the event that caused this change.
|
|
154
|
+
*
|
|
155
|
+
* @remarks
|
|
156
|
+
* A change used to be anonymous, so a subscriber that needed to know *why* a value moved had
|
|
157
|
+
* to mirror the cause into state and store it twice. Absent when the change did not come from
|
|
158
|
+
* an event — a DevTools time-travel snapshot, for instance — which is itself the signal that
|
|
159
|
+
* no event caused it.
|
|
160
|
+
*/
|
|
161
|
+
eventId?: string;
|
|
162
|
+
/** Channel of the causing event. Absent for the same reason as {@link Change.eventId}. */
|
|
163
|
+
channel?: string;
|
|
164
|
+
/** Type of the causing event. Absent for the same reason as {@link Change.eventId}. */
|
|
165
|
+
type?: string;
|
|
130
166
|
}
|
|
131
167
|
/**
|
|
132
168
|
* Emit function narrowed to the developer's EventMap.
|
|
@@ -143,6 +179,58 @@ export interface Change<V = any> {
|
|
|
143
179
|
*
|
|
144
180
|
* @public
|
|
145
181
|
*/
|
|
182
|
+
/**
|
|
183
|
+
* What an `emit` resolves to once its effects have run.
|
|
184
|
+
*
|
|
185
|
+
* @remarks
|
|
186
|
+
* `emit` used to resolve to `void`, so a caller could not tell "the reducer applied my write"
|
|
187
|
+
* from "the reducer looked at my write and returned the state unchanged". On a single-writer
|
|
188
|
+
* store that distinction is academic; on a contended one it is a lost update the API could not
|
|
189
|
+
* report.
|
|
190
|
+
*
|
|
191
|
+
* Deliberately does **not** carry the changed paths. Building that list costs a string
|
|
192
|
+
* concatenation per changed path on every emit, and almost no caller reads it — the same reason
|
|
193
|
+
* change notifications are built lazily. Instrumentation already provides them to the observers
|
|
194
|
+
* that do want them.
|
|
195
|
+
*
|
|
196
|
+
* @public
|
|
197
|
+
*/
|
|
198
|
+
export interface EmitResult {
|
|
199
|
+
/**
|
|
200
|
+
* The event was not vetoed by middleware.
|
|
201
|
+
*
|
|
202
|
+
* @remarks
|
|
203
|
+
* Unchanged in meaning, and deliberately not narrowed to "state changed" — an event-only store
|
|
204
|
+
* commits every event and writes nothing, by construction.
|
|
205
|
+
*/
|
|
206
|
+
readonly committed: boolean;
|
|
207
|
+
/** A reducer actually changed state. */
|
|
208
|
+
readonly written: boolean;
|
|
209
|
+
/** Present when a reducer refused the write. See {@link Rejection}. */
|
|
210
|
+
readonly rejected?: Rejection;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Options for {@link StoreInstance.connect}.
|
|
214
|
+
*
|
|
215
|
+
* @public
|
|
216
|
+
*/
|
|
217
|
+
export interface ConnectOptions {
|
|
218
|
+
/**
|
|
219
|
+
* Deliver the current value once, immediately, before any change arrives.
|
|
220
|
+
*
|
|
221
|
+
* @remarks
|
|
222
|
+
* A subscription otherwise starts at "from now on", so a subscriber's first render has to read
|
|
223
|
+
* the path separately — the same path, spelled twice, which is one place for them to drift.
|
|
224
|
+
*
|
|
225
|
+
* The synthetic change has `oldValue: undefined` and no `eventId`, `channel` or `type`: no
|
|
226
|
+
* event caused it, and claiming one would be a lie a subscriber could act on.
|
|
227
|
+
*
|
|
228
|
+
* For a wildcard pattern the "current value" of a match set is not a thing, so the slice root
|
|
229
|
+
* is delivered with `path: ""`. React's hooks do not need this at all — `useSyncExternalStore`
|
|
230
|
+
* already reads a snapshot on mount — so it is aimed at imperative subscribers.
|
|
231
|
+
*/
|
|
232
|
+
readonly immediate?: boolean;
|
|
233
|
+
}
|
|
146
234
|
/**
|
|
147
235
|
* Per-emit options.
|
|
148
236
|
*
|
|
@@ -195,7 +283,7 @@ export interface EmitOptions {
|
|
|
195
283
|
*/
|
|
196
284
|
skipDedup?: boolean;
|
|
197
285
|
}
|
|
198
|
-
export type Emit<EM extends EventMapBase> = <C extends keyof EM & string, T extends keyof EM[C] & string>(channel: C, type: T, payload: EM[C][T], opts?: EmitOptions) => Promise<
|
|
286
|
+
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<EmitResult>;
|
|
199
287
|
/**
|
|
200
288
|
* Basic unsubscribe handle.
|
|
201
289
|
*
|
|
@@ -235,6 +323,15 @@ export interface InstrumentedEvent<EM extends EventMapBase = EventMapBase> {
|
|
|
235
323
|
nextValues: Record<string, unknown>;
|
|
236
324
|
/** Wall-clock milliseconds spent in the synchronous reduce phase for this event. */
|
|
237
325
|
reduceTimeMs: number;
|
|
326
|
+
/**
|
|
327
|
+
* Present when a reducer refused the write, carrying its reason.
|
|
328
|
+
*
|
|
329
|
+
* @remarks
|
|
330
|
+
* Distinct from `committed: false`, which means middleware vetoed the event before any reducer
|
|
331
|
+
* saw it. This is a reducer having considered the write and declined it — the two look
|
|
332
|
+
* identical in state and are entirely different in cause.
|
|
333
|
+
*/
|
|
334
|
+
rejected?: Rejection;
|
|
238
335
|
}
|
|
239
336
|
/**
|
|
240
337
|
* Observer for {@link StoreInstance.instrument}. Called once per emitted event
|
|
@@ -432,7 +529,104 @@ export type StoreSpec<R extends string, S extends Record<R, any>, EM extends Eve
|
|
|
432
529
|
* @param slice - Name of the slice whose reducer threw.
|
|
433
530
|
*/
|
|
434
531
|
onReducerError?: (error: unknown, event: EventUnion<EM>, slice: string) => void;
|
|
532
|
+
/**
|
|
533
|
+
* Maximum causal depth of an event chain before the store refuses to extend it.
|
|
534
|
+
*
|
|
535
|
+
* @remarks
|
|
536
|
+
* An event emitted while handling another is one deeper than its cause. Two reducers wired to
|
|
537
|
+
* each other, or an effect that emits the event its own reducer answers, climb this without
|
|
538
|
+
* bound — and the reduce queue drains synchronously, so in a browser that is a frozen tab with
|
|
539
|
+
* no error and no stack, and on a server a pinned core.
|
|
540
|
+
*
|
|
541
|
+
* **On by default**, because the whole point is that the failure mode does not require
|
|
542
|
+
* configuration to avoid. The default is far past any legitimate chain: an event caused by an
|
|
543
|
+
* event caused by an event is normal, sixty-four deep is a bug. Raise it if an application
|
|
544
|
+
* genuinely nests deeper, or set `Infinity` to opt out entirely and own the consequences.
|
|
545
|
+
*
|
|
546
|
+
* Breaching does not throw — see {@link StoreSpec.onCascade}.
|
|
547
|
+
*
|
|
548
|
+
* @default 64
|
|
549
|
+
*/
|
|
550
|
+
maxReduceDepth?: number;
|
|
551
|
+
/**
|
|
552
|
+
* Maximum number of events one synchronous drain will process before refusing more.
|
|
553
|
+
*
|
|
554
|
+
* @remarks
|
|
555
|
+
* A drain processes one root event plus every event emitted *while it runs* — so this counts a
|
|
556
|
+
* single causal burst, not application traffic. A plain loop is unaffected: `emit` drains to
|
|
557
|
+
* completion before it returns, so `for (const row of rows) store.emit(…)` is a thousand drains
|
|
558
|
+
* of one event each, never one drain of a thousand.
|
|
559
|
+
*
|
|
560
|
+
* **Off by default** because a wide burst is not by itself a bug. One `sync` event whose
|
|
561
|
+
* subscriber fans out to five hundred `upsert`s is a legitimate shape, and a default low enough
|
|
562
|
+
* to catch a runaway would refuse it. Depth is what separates a cascade from a fan-out — a
|
|
563
|
+
* fan-out is wide and shallow, a cascade is narrow and deep — which is why
|
|
564
|
+
* {@link StoreSpec.maxReduceDepth} carries the default and this does not.
|
|
565
|
+
*
|
|
566
|
+
* Set it when a store's bursts are known to be bounded and an unexpectedly wide one is itself
|
|
567
|
+
* the symptom worth catching.
|
|
568
|
+
*
|
|
569
|
+
* @default undefined (no limit)
|
|
570
|
+
*/
|
|
571
|
+
maxTransitionsPerDrain?: number;
|
|
572
|
+
/**
|
|
573
|
+
* Called when a ceiling is breached, instead of throwing.
|
|
574
|
+
*
|
|
575
|
+
* @remarks
|
|
576
|
+
* The offending emit is refused and the chain stops there; everything already committed
|
|
577
|
+
* stands. It does not throw, because the throw would surface in whichever frame happened to be
|
|
578
|
+
* emitting — a subscriber, an effect, a middleware — which is the same species of
|
|
579
|
+
* hard-to-attribute failure the ceiling exists to prevent. A cascade is a wiring bug, and this
|
|
580
|
+
* is where the wiring gets named.
|
|
581
|
+
*
|
|
582
|
+
* @param info - Which ceiling, the event that would have extended the chain, and its causal
|
|
583
|
+
* chain of ids, newest last.
|
|
584
|
+
*/
|
|
585
|
+
onCascade?: (info: CascadeInfo<EM>) => void;
|
|
586
|
+
/**
|
|
587
|
+
* Called when a reducer refuses a write by returning {@link Rejected}.
|
|
588
|
+
*
|
|
589
|
+
* @remarks
|
|
590
|
+
* The caller learns of its own refusal from the `emit` result; this is for everyone else —
|
|
591
|
+
* logging, metrics, alerting on a rate of rejected writes. Shaped as a callback rather than a
|
|
592
|
+
* subscription for the same reason {@link StoreSpec.onReducerError} is: it is a rare global
|
|
593
|
+
* signal, not something several independent parties register and unregister for.
|
|
594
|
+
*
|
|
595
|
+
* A refusal is a normal outcome, not an error. It means a reducer considered the write and
|
|
596
|
+
* declined it — a stale compare-and-swap, an unmet precondition — and the event is rejected
|
|
597
|
+
* whole, so no slice writes.
|
|
598
|
+
*
|
|
599
|
+
* @param rejection - The refusal and its reason.
|
|
600
|
+
* @param event - The event that was refused.
|
|
601
|
+
* @param slice - Name of the slice whose reducer refused.
|
|
602
|
+
*/
|
|
603
|
+
onRejected?: (rejection: Rejection, event: EventUnion<EM>, slice: string) => void;
|
|
435
604
|
};
|
|
605
|
+
/**
|
|
606
|
+
* What {@link StoreSpec.onCascade} receives when a ceiling is breached.
|
|
607
|
+
*
|
|
608
|
+
* @typeParam EM - Event map.
|
|
609
|
+
*
|
|
610
|
+
* @public
|
|
611
|
+
*/
|
|
612
|
+
export interface CascadeInfo<EM extends EventMapBase = EventMapBase> {
|
|
613
|
+
/** Which ceiling was hit. */
|
|
614
|
+
readonly limit: "maxReduceDepth" | "maxTransitionsPerDrain";
|
|
615
|
+
/** The configured value that was exceeded. */
|
|
616
|
+
readonly limitValue: number;
|
|
617
|
+
/** The event that was refused — the one that would have extended the chain. */
|
|
618
|
+
readonly event: EventUnion<EM>;
|
|
619
|
+
/** Causal depth the refused event would have had. */
|
|
620
|
+
readonly depth: number;
|
|
621
|
+
/**
|
|
622
|
+
* Ids from the root of the chain to the refused event's parent, newest last.
|
|
623
|
+
*
|
|
624
|
+
* @remarks
|
|
625
|
+
* Bounded to the most recent entries: a cascade is long by definition, and the useful part is
|
|
626
|
+
* the cycle at the end rather than the thousand identical hops before it.
|
|
627
|
+
*/
|
|
628
|
+
readonly chain: readonly string[];
|
|
629
|
+
}
|
|
436
630
|
/**
|
|
437
631
|
* Public Store surface.
|
|
438
632
|
*
|
|
@@ -474,7 +668,16 @@ export interface StoreInstance<R extends string = string, S extends Record<R, an
|
|
|
474
668
|
connect(spec: {
|
|
475
669
|
reducer: R;
|
|
476
670
|
property: string;
|
|
477
|
-
}, handler: (change: Change) => void): Unsubscribe;
|
|
671
|
+
}, handler: (change: Change) => void, options?: ConnectOptions): Unsubscribe;
|
|
672
|
+
/**
|
|
673
|
+
* Sends a request and waits for the reply, correlating the two automatically.
|
|
674
|
+
*
|
|
675
|
+
* @remarks
|
|
676
|
+
* Awaitable for the terminal reply, async-iterable for progress. See the implementation on
|
|
677
|
+
* {@link Store.call} for the full contract — correlation, backpressure, timeouts, and why it
|
|
678
|
+
* is a local primitive rather than something that federates.
|
|
679
|
+
*/
|
|
680
|
+
call<C extends keyof EM & string, T extends keyof EM[C] & string>(channel: C, type: T, payload: EM[C][T], opts: CallOptions<EM>): CallHandle<EventUnion<EM>, EventUnion<EM>>;
|
|
478
681
|
/**
|
|
479
682
|
* Convenience helper to register an **effect** filtered by a single `(channel, type)` pair.
|
|
480
683
|
*
|
|
@@ -664,7 +867,8 @@ export interface StoreInstance<R extends string = string, S extends Record<R, an
|
|
|
664
867
|
* Use `when` for event targeting (preferred). The `events` property is
|
|
665
868
|
* kept for backward compatibility but `when` is recommended for new code.
|
|
666
869
|
*
|
|
667
|
-
* @example
|
|
870
|
+
* @example
|
|
871
|
+
* Using `when` (recommended)
|
|
668
872
|
* ```ts
|
|
669
873
|
* const counterSpec: ReducerSpec<{ value: number }, MyEM> = {
|
|
670
874
|
* state: { value: 0 },
|
|
@@ -706,7 +910,7 @@ export interface ReducerSpec<S = any, EM extends EventMapBase = EventMapBase> {
|
|
|
706
910
|
*
|
|
707
911
|
* @public
|
|
708
912
|
*/
|
|
709
|
-
export type ReducerFunction<S = any, EM extends EventMapBase = EventMapBase> = (state: S, event: EventUnion<EM>) => S;
|
|
913
|
+
export type ReducerFunction<S = any, EM extends EventMapBase = EventMapBase> = (state: S, event: EventUnion<EM>) => S | Rejection;
|
|
710
914
|
/**
|
|
711
915
|
* Effect specification (stateless async event consumer).
|
|
712
916
|
*
|
|
@@ -719,7 +923,8 @@ export type ReducerFunction<S = any, EM extends EventMapBase = EventMapBase> = (
|
|
|
719
923
|
* - Effects are keyed by event for O(1) lookup (no scanning).
|
|
720
924
|
* - Use `when` for event targeting (preferred over `events`).
|
|
721
925
|
*
|
|
722
|
-
* @example
|
|
926
|
+
* @example
|
|
927
|
+
* Using `when` (recommended)
|
|
723
928
|
* ```ts
|
|
724
929
|
* const logEffect: EffectSpec<AppState, MyEM> = {
|
|
725
930
|
* when: { keys: eventKeys<MyEM>()([['ui', 'increment']]) },
|
|
@@ -994,9 +1199,14 @@ export type EventFromWhen<EM extends EventMapBase, W extends When<EM>> = W exten
|
|
|
994
1199
|
* type T3 = PathValue<S, 'todos'>; // Array<{ title: string; done: boolean }>
|
|
995
1200
|
* ```
|
|
996
1201
|
*
|
|
1202
|
+
* @remarks
|
|
1203
|
+
* The empty path resolves to `T` itself, matching what the code has always done: both the
|
|
1204
|
+
* store's internal path reader and the React one return the object unchanged for `""`. The type
|
|
1205
|
+
* used to say `never`, so a subscription to a root-value slice was typed as nothing at all.
|
|
1206
|
+
*
|
|
997
1207
|
* @public
|
|
998
1208
|
*/
|
|
999
|
-
export type PathValue<T, P extends string> = P extends `${infer K}.${infer Rest}` ? K extends keyof T ? PathValue<T[K], Rest> : K extends `${number}` ? T extends readonly (infer E)[] ? PathValue<E, Rest> : never : never : P extends keyof T ? T[P] : P extends `${number}` ? T extends readonly (infer E)[] ? E : never : never;
|
|
1209
|
+
export type PathValue<T, P extends string> = P extends "" ? T : P extends `${infer K}.${infer Rest}` ? K extends keyof T ? PathValue<T[K], Rest> : K extends `${number}` ? T extends readonly (infer E)[] ? PathValue<E, Rest> : never : never : P extends keyof T ? T[P] : P extends `${number}` ? T extends readonly (infer E)[] ? E : never : never;
|
|
1000
1210
|
/**
|
|
1001
1211
|
* Type discriminator for event consumers.
|
|
1002
1212
|
*
|
|
@@ -1045,6 +1255,23 @@ export type DeepRO<T> = DeepReadonly<T>;
|
|
|
1045
1255
|
* @public
|
|
1046
1256
|
*/
|
|
1047
1257
|
export type Primitive = string | number | boolean | bigint | symbol | null | undefined | Date | RegExp;
|
|
1258
|
+
/**
|
|
1259
|
+
* A value with **no addressable interior**: its changes are reported at the slice root rather
|
|
1260
|
+
* than at a path beneath it.
|
|
1261
|
+
*
|
|
1262
|
+
* @remarks
|
|
1263
|
+
* The distinction the path types were missing. `Map` and `Set` keep their contents outside own
|
|
1264
|
+
* enumerable keys, so walking them with `keyof` yields the names of their *methods* — which is
|
|
1265
|
+
* how `"byId.get"` and `"byId.size"` came to be offered as subscribable paths, and why a slice
|
|
1266
|
+
* holding a plain number autocompleted `"toFixed"`. Neither ever notified anything, because
|
|
1267
|
+
* `detectChangedProps` reports such a value at its own path and never descends into it.
|
|
1268
|
+
*
|
|
1269
|
+
* This is the type-level counterpart of that runtime rule: what the diff reports at the root,
|
|
1270
|
+
* the types address at the root, with the empty path.
|
|
1271
|
+
*
|
|
1272
|
+
* @public
|
|
1273
|
+
*/
|
|
1274
|
+
export type RootValue = Primitive | ReadonlyMap<unknown, unknown> | ReadonlySet<unknown>;
|
|
1048
1275
|
/**
|
|
1049
1276
|
* Compute dotted paths of T, including nested objects and arrays.
|
|
1050
1277
|
*
|
|
@@ -1052,7 +1279,7 @@ export type Primitive = string | number | boolean | bigint | symbol | null | und
|
|
|
1052
1279
|
*
|
|
1053
1280
|
* @public
|
|
1054
1281
|
*/
|
|
1055
|
-
export type Path<T> = T extends
|
|
1282
|
+
export type Path<T> = T extends RootValue ? never : T extends readonly (infer U)[] ? `${number}` | (Path<U> extends never ? never : `${number}.${Path<U>}`) : {
|
|
1056
1283
|
[K in keyof T & string]: T[K] extends Primitive ? K : K | (Path<T[K]> extends never ? never : `${K}.${Path<T[K]>}`);
|
|
1057
1284
|
}[keyof T & string];
|
|
1058
1285
|
/**
|
|
@@ -1068,9 +1295,19 @@ export type WithGlob<T extends string> = T | `${string}*${string}`;
|
|
|
1068
1295
|
*
|
|
1069
1296
|
* @typeParam Slice - Slice state type.
|
|
1070
1297
|
*
|
|
1298
|
+
* @remarks
|
|
1299
|
+
* A slice that **is** one value — a primitive, a `Map`, a `Set`, a `Date` — has no key to
|
|
1300
|
+
* address, and its only subscribable path is the empty one. Saying so is what makes
|
|
1301
|
+
* `{ reducer, property: "" }` type-check where it can actually fire, instead of falling through
|
|
1302
|
+
* to the untyped `property: string` overload and returning `unknown`.
|
|
1303
|
+
*
|
|
1304
|
+
* The conditional distributes over unions, which is why a nullable object slice gets both:
|
|
1305
|
+
* `Dotted<{ a: number } | null>` is `"" | "a"`. That is exactly right — such a slice really does
|
|
1306
|
+
* change at its root when it becomes `null`, and at `"a"` otherwise.
|
|
1307
|
+
*
|
|
1071
1308
|
* @public
|
|
1072
1309
|
*/
|
|
1073
|
-
export type Dotted<Slice> = (keyof Slice & string) | Path<Slice>;
|
|
1310
|
+
export type Dotted<Slice> = Slice extends RootValue ? "" : (keyof Slice & string) | Path<Slice>;
|
|
1074
1311
|
/**
|
|
1075
1312
|
* Deep readonly type: recursively makes all properties readonly.
|
|
1076
1313
|
*
|
|
@@ -1098,11 +1335,39 @@ export type DeepReadonly<T> = T extends (...args: never[]) => unknown ? T : T ex
|
|
|
1098
1335
|
*
|
|
1099
1336
|
* - `'committed'`: Events that passed middleware and reached reducers (default)
|
|
1100
1337
|
* - `'uncommitted'`: Events rejected by middleware
|
|
1338
|
+
* - `'written'`: Events that actually changed state
|
|
1101
1339
|
* - `'all'`: Both committed and uncommitted events
|
|
1102
1340
|
*
|
|
1341
|
+
* @remarks
|
|
1342
|
+
* `'committed'` means **not vetoed**, and always has. It fires for an event that passed
|
|
1343
|
+
* middleware whether or not any reducer wrote anything — including every event in a store with
|
|
1344
|
+
* no reducers at all, which is the shape a notification or analytics bus takes. Toasts,
|
|
1345
|
+
* animations and tracking depend on that, so it is not narrowed.
|
|
1346
|
+
*
|
|
1347
|
+
* `'written'` is the stricter fact, added rather than substituted: state changed. It fires
|
|
1348
|
+
* **after** the commit, so a subscriber reading `getState()` from it sees the new value — which
|
|
1349
|
+
* is what people tend to assume `'committed'` does.
|
|
1350
|
+
*
|
|
1351
|
+
* `'all'` deliberately stays `committed | uncommitted`. Folding `'written'` into it would hand
|
|
1352
|
+
* every existing `'all'` subscriber a second notification per written event and quietly double
|
|
1353
|
+
* their counts.
|
|
1354
|
+
*
|
|
1355
|
+
* @public
|
|
1356
|
+
*/
|
|
1357
|
+
export type EventPhase = "committed" | "uncommitted" | "written" | "all";
|
|
1358
|
+
/**
|
|
1359
|
+
* The phases a handler is actually *told about*.
|
|
1360
|
+
*
|
|
1361
|
+
* @remarks
|
|
1362
|
+
* `'all'` is a subscription selector, not an outcome — nothing is ever delivered "in the all
|
|
1363
|
+
* phase". Naming the difference keeps the two from being conflated in a handler signature, which
|
|
1364
|
+
* is where they were previously spelled out by hand and drifted: adding `'written'` to
|
|
1365
|
+
* {@link EventPhase} left three copies in `@yoltra/react` still claiming a handler could only
|
|
1366
|
+
* ever see two phases, and the build failed on the mismatch.
|
|
1367
|
+
*
|
|
1103
1368
|
* @public
|
|
1104
1369
|
*/
|
|
1105
|
-
export type
|
|
1370
|
+
export type NotifiedPhase = Exclude<EventPhase, "all">;
|
|
1106
1371
|
/**
|
|
1107
1372
|
* Handler function for event subscriptions (receives full event union).
|
|
1108
1373
|
*
|
|
@@ -1131,7 +1396,7 @@ export type EventPhase = "committed" | "uncommitted" | "all";
|
|
|
1131
1396
|
*
|
|
1132
1397
|
* @public
|
|
1133
1398
|
*/
|
|
1134
|
-
export type EventSubscriptionHandler<S = any, EM extends EventMapBase = EventMapBase> = (event: EventUnion<EM>, getState: () => S, emit: Emit<EM>, phase:
|
|
1399
|
+
export type EventSubscriptionHandler<S = any, EM extends EventMapBase = EventMapBase> = (event: EventUnion<EM>, getState: () => S, emit: Emit<EM>, phase: NotifiedPhase) => void | Promise<void>;
|
|
1135
1400
|
/**
|
|
1136
1401
|
* Narrowed event subscription handler for specific `(channel, type)` pairs.
|
|
1137
1402
|
* Provides better type inference when subscribing to a single event type.
|
|
@@ -1156,4 +1421,4 @@ export type EventSubscriptionHandler<S = any, EM extends EventMapBase = EventMap
|
|
|
1156
1421
|
*
|
|
1157
1422
|
* @public
|
|
1158
1423
|
*/
|
|
1159
|
-
export type NarrowedEventHandler<S, EM extends EventMapBase, C extends keyof EM & string, T extends keyof EM[C] & string> = (event: Event<EM, C, T>, getState: () => S, emit: Emit<EM>, phase:
|
|
1424
|
+
export type NarrowedEventHandler<S, EM extends EventMapBase, C extends keyof EM & string, T extends keyof EM[C] & string> = (event: Event<EM, C, T>, getState: () => S, emit: Emit<EM>, phase: NotifiedPhase) => void | Promise<void>;
|
|
@@ -61,6 +61,12 @@
|
|
|
61
61
|
*
|
|
62
62
|
* @remarks
|
|
63
63
|
* - If `oldState === newState` (same reference), returns `[]` immediately.
|
|
64
|
+
* - A change at the **root** — the values themselves differ and neither is a walkable object,
|
|
65
|
+
* as for a primitive, a `Map`/`Set`, or two `Date`s — is reported at the `path` given, which
|
|
66
|
+
* is `""` for the default root call. `[""]` therefore means *"the whole value changed"*, and
|
|
67
|
+
* is emphatically **not** the same as `[]`. Callers must not filter it out for falsiness:
|
|
68
|
+
* doing so is indistinguishable from "nothing changed", which is how a store slice holding a
|
|
69
|
+
* primitive once silently refused every update it was given.
|
|
64
70
|
* - For objects, only **own enumerable** keys are compared (via `Object.keys`).
|
|
65
71
|
* - Returned paths are **leaf paths** where a primitive/terminal difference was detected; for arrays,
|
|
66
72
|
* a length change is treated as a leaf change at the array path.
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from './detectChangedProps';
|
|
2
|
-
export * from './immutability';
|
|
1
|
+
export * from './detectChangedProps.js';
|
|
2
|
+
export * from './immutability.js';
|
package/dist/yoltra.cjs
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* @yoltra/core v0.6.0
|
|
3
|
+
* (c) 2026 Manu Ramirez <@pixerael>
|
|
4
|
+
* License: MIT
|
|
5
|
+
* Homepage: https://yoltra.dev
|
|
6
|
+
*
|
|
7
|
+
* This source code is licensed under the MIT license found in the
|
|
8
|
+
* LICENSE file in the root directory of this source tree
|
|
9
|
+
*/
|
|
10
|
+
"use strict";var G=Object.defineProperty;var J=(c,e,t)=>e in c?G(c,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):c[e]=t;var p=(c,e,t)=>J(c,typeof e!="symbol"?e+"":e,t);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class B{constructor(){p(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,n){const r=this.handlers.get(e);if(!r)return;const o=r.get(t);if(!(!o||o.size===0))for(const d of[...o])try{d(s,n)}catch(i){console.error("EventBus handler error:",i)}}clear(){this.handlers.clear()}}class H{constructor(){p(this,"handlers",new Map);p(this,"patternHandlers",new Map);p(this,"patternIndex",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,[]),this.indexPattern(e,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),this.unindexPattern(e,t)),n.size===0&&(this.patternHandlers.delete(e),this.patternIndex.delete(e))}emit(e,t,s){const n=String(t),r=this.normalizeTypeKey(n),o=this.handlers.get(e)?.get(r)??[],d=this.matchingPatternHandlers(e,n),i=new Set,a=l=>{for(const u of[...l])if(!i.has(u)){i.add(u);try{u(s)}catch(f){console.error(f);continue}}};a(o);for(const l of d)a(l)}emitWith(e,t,s){const n=String(t),r=this.normalizeTypeKey(n),o=this.handlers.get(e)?.get(r)??[],d=this.matchingPatternHandlers(e,n);if(o.length===0&&d.length===0)return;const i=s(),a=new Set,l=u=>{for(const f of[...u])if(!a.has(f)){a.add(f);try{f(i)}catch(m){console.error(m);continue}}};l(o);for(const u of d)l(u)}isPattern(e){return e.includes("*")}normalizeTypeKey(e){return e.replace(/^\./,"")}splitPath(e){return this.normalizeTypeKey(e).split(".").filter(Boolean)}indexPattern(e,t){let s=this.patternIndex.get(e);s===void 0&&(s={byHead:new Map,anyHead:[]},this.patternIndex.set(e,s));const n=this.splitPath(t),r={pattern:t,segments:n},o=n[0];if(o===void 0||o==="*"||o==="**"){s.anyHead.push(r);return}const d=s.byHead.get(o);d===void 0?s.byHead.set(o,[r]):d.push(r)}unindexPattern(e,t){const s=this.patternIndex.get(e);if(s===void 0)return;const n=this.splitPath(t)[0],r=n===void 0||n==="*"||n==="**"?s.anyHead:s.byHead.get(n);if(r===void 0)return;const o=r.findIndex(d=>d.pattern===t);o!==-1&&r.splice(o,1),r.length===0&&r!==s.anyHead&&n!==void 0&&s.byHead.delete(n)}matchingPatternHandlers(e,t){const s=this.patternHandlers.get(e),n=this.patternIndex.get(e);if(s===void 0||s.size===0||n===void 0)return[];const r=this.splitPath(t),o=[],d=a=>{for(const l of a){if(!this.matchSegments(l.segments,r))continue;const u=s.get(l.pattern);u!==void 0&&o.push(u)}},i=r[0];if(i!==void 0){const a=n.byHead.get(i);a!==void 0&&d(a)}return d(n.anyHead),o}matchSegments(e,t){let s=0,n=0,r=-1,o=0;for(;n<t.length;)if(s<e.length&&(e[s]==="*"||e[s]===t[n]))s++,n++;else if(s<e.length&&e[s]==="**")r=s,o=n,s++;else if(r!==-1)s=r+1,n=++o;else return!1;for(;s<e.length&&e[s]==="**";)s++;return s===e.length}clear(){this.handlers.clear(),this.patternHandlers.clear(),this.patternIndex.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 W{constructor(e){p(this,"_reduce");this._reduce=e}reduce(e,t){return this._reduce(e,t)}}const D=new Set;function x(c,e){const t=c?`${c}.${e}`:e;D.has(t)||(D.add(t),console.warn(`[yoltra] State key "${e}"${c?` under "${c}"`:""} contains a dot. Paths are dotted, so this key is indistinguishable from nested objects of the same name: a subscription to "${t}" may match the wrong value, and DevTools patches for it will address the wrong node. Rename the key, or nest it.`))}function T(c,e,t="",s=new Map){const n=[];return R(c,e,t,s,n),n}function R(c,e,t,s,n){if(c===e)return;if(typeof c!="object"||typeof e!="object"||c===null||e===null){if(typeof c=="number"&&Number.isNaN(c)&&Number.isNaN(e))return;n.push(t);return}if(c instanceof Date&&e instanceof Date){c.getTime()!==e.getTime()&&n.push(t);return}if(c instanceof RegExp&&e instanceof RegExp){(c.source!==e.source||e.flags!==c.flags)&&n.push(t);return}if(c instanceof Map||e instanceof Map){n.push(t);return}if(c instanceof Set||e instanceof Set){n.push(t);return}const r=c,o=e,d=s.get(r);if(d?.has(o))return;const i=d??new Set;i.add(o),d||s.set(r,i);try{const a=Array.isArray(c),l=Array.isArray(e);if(a!==l){n.push(t);return}if(a){const h=c,y=e;h.length!==y.length&&t&&n.push(t);const g=Math.min(h.length,y.length);for(let E=0;E<g;E++)h[E]!==y[E]&&R(h[E],y[E],t?`${t}.${E}`:`${E}`,s,n);for(let E=g;E<Math.max(h.length,y.length);E++)n.push(t?`${t}.${E}`:`${E}`);return}const u=Object.keys(c),f=Object.keys(e);if(u.length===0&&f.length===0){n.push(t);return}let m=u.length===f.length;if(m){for(let h=0;h<f.length;h++)if(!Object.prototype.hasOwnProperty.call(c,f[h])){m=!1;break}}if(m){for(const h of f)c[h]!==e[h]&&(process.env.NODE_ENV!=="production"&&h.includes(".")&&x(t,h),R(c[h],e[h],t?`${t}.${h}`:h,s,n));return}for(const h of f){const y=Object.prototype.hasOwnProperty.call(c,h);if(y&&c[h]===e[h])continue;process.env.NODE_ENV!=="production"&&h.includes(".")&&x(t,h);const g=t?`${t}.${h}`:h;if(!y){n.push(g);continue}R(c[h],e[h],g,s,n)}for(const h of u)Object.prototype.hasOwnProperty.call(e,h)||(process.env.NODE_ENV!=="production"&&h.includes(".")&&x(t,h),n.push(t?`${t}.${h}`:h))}finally{i.delete(o),i.size===0&&s.delete(r)}}function k(c,e=new WeakSet,t){if(c===null||typeof c!="object"||e.has(c)||(t!==void 0&&c===t.watch&&t.onFound(),Object.isFrozen(c)))return c;if(e.add(c),Array.isArray(c)){const s=c;for(let n=0;n<s.length;n++)s[n]=k(s[n],e,t);return Object.freeze(s)}for(const s of Object.getOwnPropertyNames(c)){const n=Object.getOwnPropertyDescriptor(c,s);!n||!("value"in n)||(c[s]=k(c[s],e,t))}for(const s of Object.getOwnPropertySymbols(c)){const n=Object.getOwnPropertyDescriptor(c,s);!n||!("value"in n)||(c[s]=k(c[s],e,t))}return Object.freeze(c)}const K=Symbol.for("yoltra.rejected");function q(c){return{[K]:!0,reason:c}}function F(c){return typeof c=="object"&&c!==null&&c[K]===!0}class Y{constructor(e){p(this,"highWaterMark");p(this,"buffer",[]);p(this,"takers",[]);p(this,"putters",[]);p(this,"consuming",!1);p(this,"ended",!1);p(this,"closed",!1);p(this,"dropped",0);this.highWaterMark=e}get droppedCount(){return this.dropped}beginConsuming(){this.consuming=!0}put(e){if(this.closed||this.ended)return Promise.resolve();const t=this.takers.shift();return t!==void 0?(t({value:e,done:!1}),Promise.resolve()):this.buffer.length<this.highWaterMark?(this.buffer.push(e),Promise.resolve()):this.consuming?new Promise(s=>{this.putters.push({item:e,release:s})}):(this.dropped++,Promise.resolve())}take(){this.consuming=!0;const e=this.buffer.shift();if(e!==void 0){const s=this.putters.shift();return s!==void 0&&(this.buffer.push(s.item),s.release()),Promise.resolve({value:e,done:!1})}const t=this.putters.shift();return t!==void 0?(t.release(),Promise.resolve({value:t.item,done:!1})):this.closed||this.ended?Promise.resolve({value:void 0,done:!0}):new Promise(s=>{this.takers.push(s)})}end(){if(this.ended||this.closed)return;this.ended=!0;let e=this.putters.shift();for(;e!==void 0;)this.buffer.push(e.item),e.release(),e=this.putters.shift();let t=this.takers.shift();for(;t!==void 0;){const s=this.buffer.shift();t(s!==void 0?{value:s,done:!1}:{value:void 0,done:!0}),t=this.takers.shift()}}close(){if(this.closed)return;this.closed=!0,this.buffer.length=0;let e=this.takers.shift();for(;e!==void 0;)e({value:void 0,done:!0}),e=this.takers.shift();let t=this.putters.shift();for(;t!==void 0;)t.release(),t=this.putters.shift()}}class U extends Error{constructor(t,s,n){super(`[yoltra] call to "${t}/${s}" saw no correlated reply for ${n}ms. The timeout is idle rather than total, so this means the responder went quiet, not that it was slow. Check that something handles "${t}/${s}" and that its reply is emitted through the \`emit\` it was handed — a reply emitted from an unrelated context carries no causal link, and needs an explicit correlationId instead.`);p(this,"channel");p(this,"type");p(this,"idleMs");this.name="CallTimeoutError",this.channel=t,this.type=s,this.idleMs=n}}class I extends Error{constructor(e){super(`[yoltra] call aborted: ${e}`),this.name="CallAbortedError"}}function X(c){const[e,t]=c;if(t===void 0)return{channel:e,isTerminal:()=>!0};if(typeof t=="string")return{channel:e,isTerminal:n=>n===t};const s=new Set(t);return{channel:e,isTerminal:n=>s.has(n)}}function Z(c,e,t){return c.parentId===e?!0:t===void 0?!1:c.meta?.correlationId===t}function ee(c,e){try{return structuredClone(e)}catch(t){throw new Error(`[yoltra] Initial state for slice "${String(c)}" could not be copied: ${t instanceof Error?t.message:String(t)}. State must be structured-cloneable — functions, class instances and DOM nodes are not. Keep behaviour out of state and store plain data.`)}}function O(c,e){return process.env.NODE_ENV==="production"?c:k(c,new WeakSet,e)}const z=100,te=64,A=16,ne=3e4,se=16,$=Object.freeze({committed:!1,written:!1}),re=Object.freeze({committed:!0,written:!1}),ie=Object.freeze({committed:!0,written:!0}),j=()=>typeof performance<"u"&&typeof performance.now=="function"?performance.now():Date.now();class P{constructor(e){p(this,"name");p(this,"middleware");p(this,"reducers");p(this,"state");p(this,"reducerBus");p(this,"connectorBus");p(this,"listeners",new Set);p(this,"effects",new Map);p(this,"patternEffects",new Set);p(this,"committedEventSubscribers",new Map);p(this,"uncommittedEventSubscribers",new Map);p(this,"writtenEventSubscribers",new Map);p(this,"allEventSubscribers",new Map);p(this,"sliceUnsubs",new Map);p(this,"patternReducers",new Map);p(this,"replayEnabled");p(this,"idFactory");p(this,"onEffectError");p(this,"onReducerError");p(this,"warnedPayloadAliases",new Set);p(this,"reduceQueue",[]);p(this,"isReducing",!1);p(this,"currentEvent",null);p(this,"transitionsThisDrain",0);p(this,"maxReduceDepth");p(this,"maxTransitionsPerDrain");p(this,"onCascade");p(this,"onRejected");p(this,"instrumentObservers",new Set);p(this,"changedPathSink",null);p(this,"stagingSink",null);p(this,"stagedRejection",null);p(this,"stagedRejectedBy","");p(this,"inFlightEffects",0);p(this,"processedEvents",new Map);p(this,"dedupCount",0);p(this,"effectMeta",new WeakMap);p(this,"dedupConfig");p(this,"eventCleanupTimer",null);if(this.name=e.name??"yoltra Store",this.reducerBus=new B,this.connectorBus=new H,this.middleware=[...e.middleware??[]],this.reducers={},this.state={},this.replayEnabled=e.devtools?.allowReplay??!1,this.idFactory=e.idFactory??(()=>crypto.randomUUID()),this.onEffectError=e.onEffectError,this.onReducerError=e.onReducerError,this.maxReduceDepth=e.maxReduceDepth??te,this.maxTransitionsPerDrain=e.maxTransitionsPerDrain??1/0,this.onCascade=e.onCascade,this.onRejected=e.onRejected,this.dedupConfig={windowMs:e.dedupWindowMs??0,maxCacheSize:1e3},Object.entries(e.reducer).forEach(([t,s])=>{this.mountSlice(t,s,{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.__applyExternalState=this.__applyExternalState.bind(this),this.__replayEvents=this.__replayEvents.bind(this),this.__devtoolsIntrospect=this.__devtoolsIntrospect.bind(this),this.mountSlice=this.mountSlice.bind(this),this.unmountSlice=this.unmountSlice.bind(this),this.getAtPath=this.getAtPath.bind(this),this.emit=this.emit.bind(this),this.subscribe=this.subscribe.bind(this),this.connect=this.connect.bind(this),this.onEffect=this.onEffect.bind(this),this.onEvent=this.onEvent.bind(this),this.getState=this.getState.bind(this),this.registerEffect=this.registerEffect.bind(this),this.registerMiddleware=this.registerMiddleware.bind(this),this.registerReducer=this.registerReducer.bind(this),this.replaceMiddleware=this.replaceMiddleware.bind(this),this.replaceEffects=this.replaceEffects.bind(this),this.replaceReducers=this.replaceReducers.bind(this),this.hotReplace=this.hotReplace.bind(this)}dispose(){this.eventCleanupTimer&&(clearInterval(this.eventCleanupTimer),this.eventCleanupTimer=null),this.processedEvents.clear(),this.effects.clear(),this.patternEffects.clear(),this.effectMeta=new WeakMap,this.warnedPayloadAliases.clear(),this.listeners.clear(),this.committedEventSubscribers.clear(),this.uncommittedEventSubscribers.clear(),this.writtenEventSubscribers.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,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,t){const s=Date.now(),n=this.processedEvents.get(e);return n!==void 0&&s-n<t?(this.dedupCount++,!0):(this.processedEvents.set(e,s),this.ensureCleanupTimer(),this.processedEvents.size>this.dedupConfig.maxCacheSize&&this.pruneProcessedEvents(s),!1)}ensureCleanupTimer(){this.eventCleanupTimer===null&&(this.eventCleanupTimer=setInterval(()=>{this.pruneProcessedEvents(Date.now())},5e3),this.eventCleanupTimer.unref?.())}pruneProcessedEvents(e){const t=Math.max(this.dedupConfig.windowMs,z),s=e-t*2;for(const[n,r]of this.processedEvents)r<s&&this.processedEvents.delete(n);this.processedEvents.size===0&&this.eventCleanupTimer!==null&&(clearInterval(this.eventCleanupTimer),this.eventCleanupTimer=null)}reportCascade(e,t,s,n,r){console.error(`[yoltra] Cascade stopped: "${s.channel}/${s.type}" would exceed ${e} (${t}). This event was refused and the chain ends here. A chain this long is almost always two consumers emitting into each other — check what reacts to "${s.channel}/${s.type}" and what that emits in turn.`+(r.length>0?` Recent causal chain: ${r.join(" → ")} → (refused).`:""));try{this.onCascade?.({limit:e,limitValue:t,event:s,depth:n,chain:r})}catch(o){console.error("onCascade handler error:",o)}}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=this.scopedEmit(e),s=`${String(e.channel)}::${String(e.type)}`,n=this.effects.get(s);if(n&&n.size>0)for(const r of[...n])try{await r(e,this.getState,t)}catch(o){console.error("Effect error:",o),this.onEffectError?.(o,e)}for(const{effect:r,when:o}of this.patternEffects)if(this.matchesWhen(o,e))try{await r(e,this.getState,t)}catch(d){console.error("Effect error:",d),this.onEffectError?.(d,e)}}scopedEmit(e){const t={id:e.id,depth:e.depth??0,chain:[...this.currentEvent?.chain??[],e.id].slice(-A)};return((s,n,r,o)=>this.emitCaused(t,s,n,r,o))}notifyEventSubscribers(e,t){const s=`${String(e.channel)}::${String(e.type)}`,r=(t==="committed"?this.committedEventSubscribers:t==="written"?this.writtenEventSubscribers:this.uncommittedEventSubscribers).get(s);if(r?.size)for(const d of[...r])this.invokeEventSubscriber(d,e,t);if(t==="written")return;const o=this.allEventSubscribers.get(s);if(o?.size)for(const d of[...o])this.invokeEventSubscriber(d,e,t)}invokeEventSubscriber(e,t,s){try{const n=e(t,this.getState,this.emit,s);n&&typeof n.then=="function"&&n.catch(r=>console.error("Event subscription error:",r))}catch(n){console.error("Event subscription error:",n)}}stageSliceGuarded(e,t,s){try{return this.stageSlice(e,t,s)}catch(n){return console.error(`Reducer error in slice "${e}":`,n),this.onReducerError?.(n,t,e),null}}stageSlice(e,t,s){const n=this.state[e],r=this.reducers[e].reduce(n,t);if(F(r))return r;if(n===r)return null;const o=T(n,r);if(o.length===0)return null;const d=t.payload,i=process.env.NODE_ENV!=="production"&&d!==null&&typeof d=="object"?{watch:d,onFound:()=>{const a=`${e}:${t.channel}:${t.type}`;this.warnedPayloadAliases.has(a)||(this.warnedPayloadAliases.add(a),console.warn(`[yoltra] Slice "${e}" stored the payload of "${t.channel}/${t.type}" by reference. It is now frozen along with the rest of the state, so the emitter mutating it later will throw in development and silently corrupt state in production. Copy the payload in the reducer instead.`))}}:void 0;return s.push({name:e,prev:n,frozen:O(r,i),leafPaths:o}),null}commitStaged(e,t){if(e.length===0)return!1;const s={...this.state};for(const n of e)s[n.name]=n.frozen;if(this.state=s,this.changedPathSink)for(const n of e)for(const r of n.leafPaths)this.changedPathSink.push(r?`${n.name}.${r}`:n.name);for(const n of e){const r=new Set;for(const o of n.leafPaths){if(o===""){r.add("");continue}for(const d of P.buildAncestorPaths(o))r.add(d)}for(const o of r)this.connectorBus.emitWith(n.name,o,()=>({oldValue:this.getAtPath(n.prev,o),newValue:this.getAtPath(n.frozen,o),path:o,eventId:t.id,channel:t.channel,type:t.type}))}return!0}__devtoolsIntrospect(){const e=Object.keys(this.reducers).map(d=>{const i=this.patternReducers.get(d);return{name:d,when:i}}),t=[];for(const[d,i]of this.effects){if(i.size===0)continue;const[a,l]=d.split("::");for(const u of i){const f=this.effectMeta.get(u);t.push({channel:a,type:l,name:f?.name,description:f?.description})}}for(const d of this.patternEffects){const i=this.effectMeta.get(d.effect);t.push({channel:"*",type:"*",name:i?.name,description:i?.description})}const s=[];for(const d of this.middleware)typeof d=="function"?s.push({name:d.name||void 0}):s.push({name:d.meta?.name,description:d.meta?.description,when:d.when});const n=[];for(const d of this.connectorBus.__introspect())for(let i=0;i<d.count;i++)n.push({reducer:d.channel,property:d.type});const r=[];for(const[d,i]of this.committedEventSubscribers){if(i.size===0)continue;const[a,l]=d.split("::");for(let u=0;u<i.size;u++)r.push({channel:a,type:l,phase:"committed"})}for(const[d,i]of this.uncommittedEventSubscribers){if(i.size===0)continue;const[a,l]=d.split("::");for(let u=0;u<i.size;u++)r.push({channel:a,type:l,phase:"uncommitted"})}for(const[d,i]of this.allEventSubscribers){if(i.size===0)continue;const[a,l]=d.split("::");for(let u=0;u<i.size;u++)r.push({channel:a,type:l,phase:"all"})}const o=this.listeners.size;return{reducers:e,effects:t,middleware:s,atomic:n,event:r,coarse:o,dedupHits:this.dedupCount,queueDepth:this.reduceQueue.length+this.inFlightEffects}}__applyExternalState(e){if(!this.replayEnabled)throw new Error("[yoltra] External state apply (time-travel) is disabled. Enable it with createStore({ devtools: { allowReplay: true } })");const t=this.state,s=e,n={...this.state};let r=!1;Object.keys(this.reducers).forEach(o=>{const d=t?.[o],i=s?.[o];if(i===void 0){process.env.NODE_ENV!=="production"&&console.warn(`[yoltra] External state is missing slice "${String(o)}"; retaining its current value. Time-travel snapshots should contain all slices.`);return}if(d===i)return;const a=O(i);n[o]=a,r=!0;const l=T(d,i);if(l.length===0)return;const u=new Set;for(const f of l){if(f===""){u.add("");continue}for(const m of P.buildAncestorPaths(f))u.add(m)}for(const f of u){const m=this.getAtPath(d,f),h=this.getAtPath(a,f);this.connectorBus.emit(o,f,{oldValue:m,newValue:h,path:f})}}),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.stagingSink=r;let o=null;try{this.reducerBus.emit(n.channel,n.type,n.payload,n),o=this.stagedRejection;for(const[i,a]of this.patternReducers){if(o!==null)break;if(this.matchesWhen(a,n)){const l=this.stageSliceGuarded(i,n,r);l!==null&&(o=l)}}}finally{this.stagingSink=null,this.stagedRejection=null,this.stagedRejectedBy=""}const d=o===null&&this.commitStaged(r,n);this.notifyEventSubscribers(n,"committed"),d&&(this.notifyEventSubscribers(n,"written"),this.listeners.forEach(i=>i()))}}async emit(e,t,s,n){return this.emitCaused(null,e,t,s,n)}async emitCaused(e,t,s,n,r){const o=r?.dedupKey,d=this.dedupConfig.windowMs;if(r?.skipDedup!==!0&&(d>0||o!==void 0)){const m=o!==void 0&&d<=0?z:d,h=o!==void 0?`${t}::${s}::#${o}`:this.fingerprint(t,s,n);if(this.shouldDedupe(h,m))return $}const i=r?.id??this.idFactory(),a=this.currentEvent??e,l=a===null?0:a.depth+1;if(a!==null&&l>this.maxReduceDepth)return this.reportCascade("maxReduceDepth",this.maxReduceDepth,{channel:t,type:s,payload:n,id:i,...r?.meta!==void 0?{meta:r.meta}:{},parentId:a.id,depth:l},l,a.chain),$;let u;const f=new Promise(m=>{u=m});return this.reduceQueue.push({channel:t,type:s,payload:n,id:i,meta:r?.meta,resolve:u,...a!==null?{parentId:a.id,depth:l,chain:a.chain}:{}}),this.drainReduce(),f}drainReduce(){if(!this.isReducing){this.isReducing=!0,this.transitionsThisDrain=0;try{for(;this.reduceQueue.length>0;){const e=this.reduceQueue.shift(),{channel:t,type:s,payload:n,id:r,meta:o,resolve:d,parentId:i,depth:a,chain:l}=e,u={channel:t,type:s,payload:n,id:r,...o!==void 0?{meta:o}:{},...i!==void 0?{parentId:i,depth:a}:{}};if(i!==void 0&&++this.transitionsThisDrain>this.maxTransitionsPerDrain){this.reportCascade("maxTransitionsPerDrain",this.maxTransitionsPerDrain,u,a,l),d($);continue}this.currentEvent={id:r,depth:a??0,chain:[...l??[],r].slice(-A)};const f=this.instrumentObservers.size>0,m=f?this.state:void 0,h=f?[]:void 0;h!==void 0&&(this.changedPathSink=h);const y=f?j():0;let g=$;try{g=this.applyEventSync(u)}catch(E){console.error("Emit reduce error:",E)}finally{f&&(this.changedPathSink=null),this.currentEvent=null}f&&this.emitInstrumentation(u,g,h??[],m,j()-y),this.runEventEffects(u,g,d)}}finally{this.isReducing=!1}}}applyEventSync(e){for(const o of this.middleware){const d=this.getMiddlewareWhen(o);if(!this.matchesWhen(d,e))continue;const i=this.getMiddlewareFunction(o);let a;try{a=i(this.state,e,this.emit),process.env.NODE_ENV!=="production"&&typeof a?.then=="function"&&console.error(`[yoltra] Middleware for "${e.channel}/${e.type}" returned a Promise. Middleware is synchronous: a Promise is truthy, so this event was allowed without waiting and a "return false" inside it can never veto. Do the check synchronously, and put anything that must await in an effect.`)}catch(l){console.error("Middleware error:",l),a=!1}if(!a)return this.notifyEventSubscribers(e,"uncommitted"),$}const t=[];this.stagingSink=t;let s=null,n="";try{this.reducerBus.emit(e.channel,e.type,e.payload,e),s=this.stagedRejection,n=this.stagedRejectedBy;for(const[o,d]of this.patternReducers){if(s!==null)break;if(this.matchesWhen(d,e)){const i=this.stageSliceGuarded(o,e,t);i!==null&&(s=i,n=o)}}}finally{this.stagingSink=null,this.stagedRejection=null,this.stagedRejectedBy=""}if(s!==null)return this.onRejected?.(s,e,n),this.notifyEventSubscribers(e,"committed"),{committed:!0,written:!1,rejected:s};const r=this.commitStaged(t,e);return this.notifyEventSubscribers(e,"committed"),r&&(this.notifyEventSubscribers(e,"written"),this.listeners.forEach(o=>o())),r?ie:re}async runEventEffects(e,t,s){this.inFlightEffects++;try{t.committed&&await this.notifyEffects(e)}catch(n){console.error("Effect error:",n)}finally{this.inFlightEffects--,s(t)}}instrument(e){return this.instrumentObservers.add(e),()=>{this.instrumentObservers.delete(e)}}emitInstrumentation(e,t,s,n,r){const o={},d={};for(const a of s)o[a]=this.getAtPath(n,a),d[a]=this.getAtPath(this.state,a);const i={event:{id:e.id,channel:e.channel,type:e.type,payload:e.payload,...e.meta!==void 0?{meta:e.meta}:{}},committed:t.committed,changedPaths:s,prevValues:o,nextValues:d,reduceTimeMs:r,...t.rejected!==void 0?{rejected:t.rejected}:{}};for(const a of[...this.instrumentObservers])try{a(i)}catch(l){console.error("Instrumentation observer error:",l)}}connect(e,t,s){const n=this.connectorBus.on(e.reducer,e.property,t);if(s?.immediate===!0){const r=this.state[e.reducer],o=e.property.includes("*")?"":e.property;t({oldValue:void 0,newValue:this.getAtPath(r,o),path:o})}return n}onEvent(e,t,s,n="committed"){const r=`${e}::${String(t)}`,o=n==="committed"?this.committedEventSubscribers:n==="uncommitted"?this.uncommittedEventSubscribers:n==="written"?this.writtenEventSubscribers:this.allEventSubscribers;return o.has(r)||o.set(r,new Set),o.get(r).add(s),()=>{const d=o.get(r);d&&(d.delete(s),d.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(Object.prototype.hasOwnProperty.call(this.reducers,e))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())}}call(e,t,s,n){const{channel:r,isTerminal:o}=X(n.reply),d=n.timeoutMs??ne,i=new Y(n.highWaterMark??se),a=this.idFactory();let l,u,f=!1;const m=new Promise((b,S)=>{l=b,u=S});m.catch(()=>{});let h=null,y=null;const g=(b,S=!1)=>{f||(f=!0,h!==null&&clearTimeout(h),h=null,y?.(),y=null,S?i.end():i.close(),n.signal?.removeEventListener("abort",E),b())};function E(){g(()=>u(new I(String(n.signal?.reason??"signal aborted"))))}const M=()=>{h!==null&&clearTimeout(h),h=setTimeout(()=>{g(()=>u(new U(e,t,d)))},d),h.unref?.()};return y=this.registerEffect({when:{channel:r},effect:async b=>{if(!f&&Z(b,a,n.correlationId)){if(M(),o(String(b.type))){g(()=>l(b),!0);return}await i.put(b)}}}),n.signal!==void 0&&(n.signal.aborted?E():n.signal.addEventListener("abort",E,{once:!0})),M(),this.emit(e,t,s,{id:a,...n.correlationId!==void 0?{meta:{correlationId:n.correlationId}}:{}}),{then:(b,S)=>m.then(b,S),catch:b=>m.catch(b),finally:b=>m.finally(b),get dropped(){return i.droppedCount},cancel:(b="cancelled")=>{g(()=>u(new I(b)))},[Symbol.asyncIterator]:()=>(i.beginConsuming(),{next:()=>i.take(),return:async()=>(i.close(),{value:void 0,done:!0})})}}registerEffect(e){const{effect:t,meta:s,when:n}=e,r=[];if(s&&this.effectMeta.set(t,s),n&&("any"in n&&n.any===!0||"channel"in n||"channels"in n)){const i={effect:t,when:n};return this.patternEffects.add(i),()=>{this.patternEffects.delete(i)}}const d=this.normalizeEventKeys(e);if(d.length===0&&!n){const i={effect:t,when:{any:!0}};return this.patternEffects.add(i),()=>{this.patternEffects.delete(i)}}for(const[i,a]of d){const l=`${String(i)}::${String(a)}`;this.effects.has(l)||this.effects.set(l,new Set),this.effects.get(l).add(t),r.push(()=>{const u=this.effects.get(l);u&&(u.delete(t),u.size===0&&this.effects.delete(l))})}return()=>{for(const i of r)i()}}onEffect(e,t,s){const n=async(r,o,d)=>{if(r.channel!==e||r.type!==t)return;const i=r;return s(i.payload,o,d,i)};return this.registerEffect({when:{keys:[[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(([d])=>d));for(const d of n)o.has(d)||this.unmountSlice(d,{deleteState:!0});for(const[d,i]of r)n.has(d)?(this.unmountSlice(d,{deleteState:!1}),this.mountSlice(d,i,{preserveState:s})):this.mountSlice(d,i,{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,{reducer:r,state:o,when:d}=t;if(this.reducers[e]=new W(r),(!s.preserveState||this.state[n]===void 0)&&(this.state={...this.state,[n]:O(ee(n,o))}),d&&("any"in d&&d.any===!0||"channel"in d||"channels"in d)){this.patternReducers.set(e,d),this.sliceUnsubs.set(n,[]);return}const a=this.normalizeEventKeys(t);if(a.length===0&&!d){this.patternReducers.set(e,{any:!0}),this.sliceUnsubs.set(n,[]);return}const l=[];for(const[u,f]of a){const m=this.reducerBus.on(u,f,(h,y)=>{const g=y??{channel:u,type:f,payload:h,id:this.idFactory()};if(this.stagingSink===null)return;const E=this.stageSliceGuarded(e,g,this.stagingSink);E!==null&&this.stagedRejection===null&&(this.stagedRejection=E,this.stagedRejectedBy=e)});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)}if(delete this.reducers[e],t.deleteState){const{[s]:r,...o}=this.state;this.state=o}}normalizeEventKeys(e){if(e.when){const t=e.when;if("keys"in t)return t.keys}return[]}getAtPath(e,t){if(!t)return e;const 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 oe(c){return new P({...c,reducer:c.reducer??{},middleware:c.middleware??[],effects:c.effects??[]})}const ce=c=>(e,t)=>t.map(s=>[e,s]),ae=()=>c=>c,_=new Set;function de(c){const e=String(c);_.has(e)||(_.add(e),console.warn(`[yoltra] Entity id "${e}" contains a dot. Paths are dotted, so a subscription to "entities.${e}" is indistinguishable from one to a nested object of the same name. Use ids without dots.`))}function le(c,e){if(c.length!==e.length)return e;for(let t=0;t<c.length;t++)if(c[t]!==e[t])return e;return c}function ue(c={}){const e=c.selectId??(i=>i.id),{sortComparer:t}=c,s=(i,a)=>{if(t===void 0)return a;const l=[...a].sort((u,f)=>{const m=i.entities[u],h=i.entities[f];return m===void 0||h===void 0?0:t(m,h)});return le(a,l)},n=(i,a,l)=>{const u={...i,entities:a,ids:l};return{...u,ids:s(u,l)}},r=(i,a,l)=>{let u=null,f=null;for(const m of a){const h=e(m);process.env.NODE_ENV!=="production"&&String(h).includes(".")&&de(h);const y=(u??i.entities)[h];if(y!==void 0&&l==="add")continue;const g=y!==void 0&&l==="upsert"?{...y,...m}:m;u??(u={...i.entities}),u[h]=g,y===void 0&&(f??(f=[...i.ids]),f.push(h))}return u===null?i:n(i,u,f??i.ids)},o=(i,a)=>{let l=null;for(const{id:u,changes:f}of a){const m=(l??i.entities)[u];m!==void 0&&(l??(l={...i.entities}),l[u]={...m,...f})}return l===null?i:n(i,l,i.ids)},d=(i,a)=>{const l=new Set(a.filter(f=>i.entities[f]!==void 0));if(l.size===0)return i;const u={...i.entities};for(const f of l)delete u[f];return n(i,u,i.ids.filter(f=>!l.has(f)))};return{getInitialState(i){const a={ids:[],entities:{}};return i===void 0?a:{...a,...i}},addOne:(i,a)=>r(i,[a],"add"),addMany:(i,a)=>r(i,a,"add"),setOne:(i,a)=>r(i,[a],"set"),setMany:(i,a)=>r(i,a,"set"),setAll:(i,a)=>{const l={},u=[];for(const f of a){const m=e(f);l[m]===void 0&&u.push(m),l[m]=f}return n(i,l,u)},updateOne:(i,a)=>o(i,[a]),updateMany:(i,a)=>o(i,a),upsertOne:(i,a)=>r(i,[a],"upsert"),upsertMany:(i,a)=>r(i,a,"upsert"),removeOne:(i,a)=>d(i,[a]),removeMany:(i,a)=>d(i,a),removeAll:i=>i.ids.length===0?i:n(i,{},[]),selectIds:i=>i.ids,selectEntities:i=>i.entities,selectAll:i=>i.ids.map(a=>i.entities[a]),selectById:(i,a)=>i.entities[a],selectTotal:i=>i.ids.length,idsPath:"ids",pathTo:(i,a)=>a===void 0?`entities.${i}`:`entities.${i}.${a}`,anyField:i=>`entities.*.${i}`}}const w="$yoltra";function C(c,e={}){const t=e.maxNodes??1e5,s=e.sanitize,n=[],r=new Map;let o=0,d=!1;function i(l,u){if(s!==void 0&&(l=s(u,l)),o+=1,o>t)return d=!0,{[w]:"unsupported",kind:"truncated"};switch(typeof l){case"undefined":return{[w]:"undefined"};case"bigint":return{[w]:"bigint",value:l.toString()};case"number":return Number.isNaN(l)?{[w]:"nan"}:l===1/0?{[w]:"infinity",sign:1}:l===-1/0?{[w]:"infinity",sign:-1}:l;case"function":case"symbol":return n.push(u),{[w]:"unsupported",kind:typeof l};case"string":case"boolean":return l}if(l===null)return null;const f=l,m=r.get(f);if(m!==void 0)return{[w]:"ref",path:m};if(r.set(f,u),l instanceof Date)return{[w]:"date",iso:l.toISOString()};if(l instanceof RegExp)return{[w]:"regexp",source:l.source,flags:l.flags};if(l instanceof Error)return{[w]:"error",name:l.name,message:l.message};if(l instanceof Map){const y=[];let g=0;for(const[E,M]of l)y.push([i(E,`${u}/@k${g}`),i(M,`${u}/${g}`)]),g+=1;return{[w]:"map",entries:y}}if(l instanceof Set){const y=[];let g=0;for(const E of l)y.push(i(E,`${u}/${g}`)),g+=1;return{[w]:"set",values:y}}if(Array.isArray(l))return l.map((y,g)=>i(y,`${u}/${g}`));const h={};for(const[y,g]of Object.entries(l))h[y]=i(g,`${u}/${V(y)}`);return w in h?{[w]:"escaped",value:h}:h}return{value:i(c,""),report:{truncated:d,unsupported:n}}}function L(c){const e=new Map,t=[];function s(o,d){if(o===null||typeof o!="object")return o;if(Array.isArray(o)){const a=[];return e.set(d,a),o.forEach((l,u)=>{if(N(l)){t.push({target:a,key:u,path:l.path}),a[u]=void 0;return}a[u]=s(l,`${d}/${u}`)}),a}if(typeof o[w]=="string"){const a=o;switch(a[w]){case"undefined":return;case"nan":return Number.NaN;case"infinity":return a.sign===1?1/0:-1/0;case"bigint":return BigInt(a.value);case"date":return new Date(a.iso);case"regexp":return new RegExp(a.source,a.flags);case"error":{const l=new Error(a.message);return l.name=a.name,l}case"unsupported":return;case"ref":return;case"map":{const l=new Map;return e.set(d,l),a.entries.forEach(([u,f],m)=>{l.set(s(u,`${d}/@k${m}`),s(f,`${d}/${m}`))}),l}case"set":{const l=new Set;return e.set(d,l),a.values.forEach((u,f)=>l.add(s(u,`${d}/${f}`))),l}case"escaped":return n(a.value,d);default:return}}return n(o,d)}function n(o,d){const i={};e.set(d,i);for(const[a,l]of Object.entries(o)){const u=`${d}/${V(a)}`;if(N(l)){t.push({target:i,key:a,path:l.path}),i[a]=void 0;continue}i[a]=s(l,u)}return i}const r=s(c,"");e.set("",r);for(const{target:o,key:d,path:i}of t)o[d]=e.get(i);return r}function N(c){return c!==null&&typeof c=="object"&&c[w]==="ref"&&typeof c.path=="string"}function V(c){return c.replace(/~/g,"~0").replace(/\//g,"~1")}function fe(c,e,t={}){let s=t.maxNodes??1e5;for(let n=0;n<8;n+=1){const{value:r,report:o}=C(c,{...t,maxNodes:s});let d;try{d=JSON.stringify(r)?.length??0}catch{d=Number.POSITIVE_INFINITY}if(d<=e)return o.truncated?{value:r,truncated:!0,note:`State was too large to send in full; parts beyond ${s} nodes are omitted.`}:{value:r,truncated:!1};const i=Math.floor(s*e*.8/d);if(s=Math.max(1,Math.min(i,s-1)),s<=1&&n>0)break}return{value:{[w]:"unsupported",kind:"truncated"},truncated:!0,note:`State exceeds the ${e}-byte transport limit and could not be reduced to fit.`}}function v(c,e,t){c.onError?.(e,t)}async function he(c){const e={slices:{},restored:!1};let t;try{t=c.source??await c.adapter.read(c.key)}catch(n){return v(c,n,"read"),e}if(t==null||t==="")return e;let s;try{s=L(JSON.parse(t))}catch(n){return v(c,n,"decode"),e}if(s===null||typeof s!="object"||typeof s.version!="number")return v(c,new Error("persisted payload is not a recognisable envelope"),"decode"),e;if(s.version!==c.version){if(c.migrate===void 0)return v(c,new Error(`persisted state is version ${s.version}, this build expects ${c.version}, and no migrate was supplied`),"migrate"),e;try{const n=c.migrate(s.slices,s.version);return n===null?e:{slices:n,restored:!0}}catch(n){return v(c,n,"migrate"),e}}return{slices:s.slices??{},restored:!0}}function pe(c,e){if(!e.restored)return c;const t={};for(const[s,n]of Object.entries(c)){const r=e.slices[s];t[s]=r===void 0?n:{...n,state:r}}return t}function Q(c,e){const t=c??{},s=e.slices===void 0?t:Object.fromEntries(e.slices.filter(n=>n in t).map(n=>[n,t[n]]));return JSON.stringify(C({version:e.version,slices:s}).value)}function me(c,e){const t=e.throttleMs??250,s=e.slices;let n=null,r=!1;const o=()=>{if(r){r=!1;try{const a=e.adapter.write(e.key,Q(c.getState(),e));a instanceof Promise&&a.catch(l=>v(e,l,"write"))}catch(a){v(e,a,"write")}}},d=()=>{if(r=!0,t<=0){o();return}n===null&&(n=setTimeout(()=>{n=null,o()},t),n.unref?.())},i=c.instrument(a=>{if(s===void 0){d();return}(a.changedPaths??[]).some(u=>s.some(f=>u===f||u.startsWith(`${f}.`)))&&d()});return()=>{i(),n!==null&&(clearTimeout(n),n=null),o()}}function ye(c,e){return Q(c.getState(),e)}function ge(c){return{read:e=>c.getItem(e),write:(e,t)=>c.setItem(e,t),remove:e=>c.removeItem(e)}}function Ee(c){const e=new Map(Object.entries(c??{}));return{read:t=>e.get(t)??null,write:(t,s)=>{e.set(t,s)},remove:t=>{e.delete(t)}}}exports.CallAbortedError=I;exports.CallTimeoutError=U;exports.EventBus=B;exports.LooseEventBus=H;exports.Reducer=W;exports.Rejected=q;exports.Store=P;exports.createEntityAdapter=ue;exports.createMemoryAdapter=Ee;exports.createStore=oe;exports.createWebStorageAdapter=ge;exports.decodeState=L;exports.dehydrate=ye;exports.detectChangedProps=T;exports.encodeState=C;exports.encodeStateBounded=fe;exports.eventKeys=ae;exports.freezeState=k;exports.hydrate=he;exports.isRejected=F;exports.persist=me;exports.typedEvents=ce;exports.withHydration=pe;
|
|
11
|
+
//# sourceMappingURL=yoltra.cjs.map
|