@yoltra/core 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2391 @@
1
+ /*!
2
+ * @yoltra/core v0.5.0
3
+ * (c) 2026 Manu Ramirez <@pixerael>
4
+ * License: MIT
5
+ * Homepage: https://yoltra.dev
6
+ *
7
+ * This source code is licensed under the MIT license found in the
8
+ * LICENSE file in the root directory of this source tree
9
+ */
10
+ var T = Object.defineProperty;
11
+ var C = (o, e, t) => e in o ? T(o, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : o[e] = t;
12
+ var p = (o, e, t) => C(o, typeof e != "symbol" ? e + "" : e, t);
13
+ class B {
14
+ constructor() {
15
+ /**
16
+ * Internal registry: `channel → type → Set<handler>`.
17
+ * @internal
18
+ */
19
+ p(this, "handlers", /* @__PURE__ */ new Map());
20
+ }
21
+ /**
22
+ * Subscribes a handler to an exact `(channel, type)`.
23
+ *
24
+ * @typeParam C - Channel key (must be a string key of `EM`).
25
+ * @typeParam T - Type key within channel `C` (must be a string key of `EM[C]`).
26
+ * @param channel - Channel name to subscribe to.
27
+ * @param type - Event type within the channel.
28
+ * @param handler - Function invoked with the payload type `EM[C][T]`. It optionally
29
+ * receives the **source event** as a second argument when the emitter supplies one, so
30
+ * subscribers can read the true `id` (and any `meta`) instead of reconstructing an event
31
+ * from the payload alone. Handlers that declare only `payload` remain valid.
32
+ * @returns An **unsubscribe** function that removes this handler.
33
+ *
34
+ * @example
35
+ * ```ts
36
+ * const off = bus.on('data', 'loaded', ({ items }) => {
37
+ * console.log('Loaded', items.length, 'items');
38
+ * });
39
+ *
40
+ * // Later, stop listening:
41
+ * off();
42
+ * ```
43
+ *
44
+ * @example Reading the source event
45
+ * ```ts
46
+ * bus.on('data', 'loaded', (payload, event) => {
47
+ * console.log('event id:', event?.id);
48
+ * });
49
+ * ```
50
+ *
51
+ * @public
52
+ */
53
+ on(e, t, n) {
54
+ let s = this.handlers.get(e);
55
+ s || (s = /* @__PURE__ */ new Map(), this.handlers.set(e, s));
56
+ let i = s.get(t);
57
+ return i || (i = /* @__PURE__ */ new Set(), s.set(t, i)), i.add(n), () => this.off(e, t, n);
58
+ }
59
+ /**
60
+ * Removes a specific handler previously added with {@link EventBus.on | `on`}.
61
+ *
62
+ * @typeParam C - Channel key (string key of `EM`).
63
+ * @typeParam T - Type key within channel `C` (string key of `EM[C]`).
64
+ * @param channel - Channel name of the subscription to remove.
65
+ * @param type - Event type of the subscription to remove.
66
+ * @param handler - The same handler reference that was passed to `on`.
67
+ *
68
+ * @example
69
+ * ```ts
70
+ * const h = (n: number) => console.log('inc', n);
71
+ * bus.on('math', 'inc', h);
72
+ *
73
+ * // Explicitly remove this handler:
74
+ * bus.off('math', 'inc', h);
75
+ * ```
76
+ *
77
+ * @public
78
+ */
79
+ off(e, t, n) {
80
+ const s = this.handlers.get(e);
81
+ if (!s) return;
82
+ const i = s.get(t);
83
+ i && (i.delete(n), i.size === 0 && s.delete(t), s.size === 0 && this.handlers.delete(e));
84
+ }
85
+ /**
86
+ * Emits an event to all subscribers of the exact `(channel, type)`.
87
+ *
88
+ * Handlers are invoked **synchronously**. Any exception thrown by a handler is
89
+ * caught and logged, and other handlers still run.
90
+ *
91
+ * @typeParam C - Channel key (string key of `EM`).
92
+ * @typeParam T - Type key within channel `C` (string key of `EM[C]`).
93
+ * @param channel - Channel name to emit on.
94
+ * @param type - Event type to emit.
95
+ * @param payload - Payload matching `EM[C][T]`.
96
+ * @param event - Optional **source event**, forwarded to handlers as a second argument.
97
+ * Supply it whenever the caller already holds the real event so subscribers observe its
98
+ * true `id` rather than reconstructing one; omitting it keeps the original behaviour.
99
+ *
100
+ * @example
101
+ * ```ts
102
+ * bus.emit('ui', 'toggle', false);
103
+ * ```
104
+ *
105
+ * @public
106
+ */
107
+ emit(e, t, n, s) {
108
+ const i = this.handlers.get(e);
109
+ if (!i) return;
110
+ const d = i.get(t);
111
+ if (!(!d || d.size === 0))
112
+ for (const a of [...d])
113
+ try {
114
+ a(n, s);
115
+ } catch (r) {
116
+ console.error("EventBus handler error:", r);
117
+ }
118
+ }
119
+ /**
120
+ * Clears **all** listeners across all channels/types.
121
+ *
122
+ * Useful for tests or during HMR teardown to avoid duplicate handlers.
123
+ *
124
+ * @example
125
+ * ```ts
126
+ * // In a test teardown:
127
+ * afterEach(() => bus.clear());
128
+ * ```
129
+ *
130
+ * @public
131
+ */
132
+ clear() {
133
+ this.handlers.clear();
134
+ }
135
+ }
136
+ class H {
137
+ constructor() {
138
+ /**
139
+ * Exact handlers: `channel → type → [handlers]`.
140
+ * @internal
141
+ */
142
+ p(this, "handlers", /* @__PURE__ */ new Map());
143
+ /**
144
+ * Pattern handlers with `*` and `**`: `channel → pattern(string) → [handlers]`.
145
+ * @internal
146
+ */
147
+ p(this, "patternHandlers", /* @__PURE__ */ new Map());
148
+ /**
149
+ * Patterns bucketed by their first segment, so an emit tests only what could match.
150
+ *
151
+ * @remarks
152
+ * Delivery used to walk every pattern registered on the channel and run the full segment
153
+ * matcher against each. That is linear in the number of patterns rather than in the number
154
+ * that match, and it re-split both the pattern and the subject on every test — for a thousand
155
+ * patterns, two thousand string splits to deliver one event.
156
+ *
157
+ * A subject's first segment can only be matched by a pattern whose first segment is that same
158
+ * literal, or is `*` or `**`. Bucketing on that turns the common shape — distinct event
159
+ * families like `panel.*` and `order.**` — from a scan of everything into a map lookup plus
160
+ * the handful that begin with a wildcard.
161
+ *
162
+ * It buys nothing for a channel where every pattern starts with `**`, since all of those must
163
+ * still be tested. That is the honest worst case, and it is unchanged rather than worsened.
164
+ */
165
+ p(this, "patternIndex", /* @__PURE__ */ new Map());
166
+ }
167
+ /**
168
+ * Subscribes a handler to either an **exact** type or a **pattern**.
169
+ *
170
+ * @param channel - Channel to subscribe on.
171
+ * @param type - Exact event type (e.g. `"a.b"`) or pattern (contains `*`/`**`).
172
+ * @param handler - Function invoked with the emitted payload.
173
+ * @returns An **unsubscribe** function that removes this handler.
174
+ *
175
+ * @remarks
176
+ * - Exact subscriptions are stored under a **normalized** key (leading `.` removed).
177
+ * - Pattern subscriptions are stored **as provided**; matching normalizes the subject.
178
+ *
179
+ * @example Exact subscription
180
+ * ```ts
181
+ * const off = bus.on('data', 'items.loaded', ({ count }) => {
182
+ * console.log('Loaded', count);
183
+ * });
184
+ * // Later
185
+ * off();
186
+ * ```
187
+ *
188
+ * @example Pattern subscription
189
+ * ```ts
190
+ * // Match any single sub-event: 'panel.open', 'panel.close', etc.
191
+ * const offStar = bus.on('ui', 'panel.*', () => {});
192
+ *
193
+ * // Match any depth: 'panel.open', 'panel.items.add', 'panel', etc.
194
+ * const offGlob = bus.on('ui', 'panel.**', () => {});
195
+ * ```
196
+ *
197
+ * @public
198
+ */
199
+ on(e, t, n) {
200
+ const s = String(t);
201
+ if (this.isPattern(s)) {
202
+ const i = s;
203
+ this.patternHandlers.has(e) || this.patternHandlers.set(e, /* @__PURE__ */ new Map());
204
+ const d = this.patternHandlers.get(e);
205
+ return d.has(i) || (d.set(i, []), this.indexPattern(e, i)), d.get(i).push(n), () => this.offPattern(e, i, n);
206
+ } else {
207
+ const i = this.normalizeTypeKey(s);
208
+ this.handlers.has(e) || this.handlers.set(e, /* @__PURE__ */ new Map());
209
+ const d = this.handlers.get(e);
210
+ return d.has(i) || d.set(i, []), d.get(i).push(n), () => this.offExactNormalized(e, i, n);
211
+ }
212
+ }
213
+ /**
214
+ * Unsubscribes an **exact** handler. The `type` key is normalized internally,
215
+ * so callers can pass `"foo"` or `".foo"` interchangeably.
216
+ *
217
+ * @param channel - Channel name.
218
+ * @param type - Exact event type key to remove (normalization applied).
219
+ * @param handler - The same handler reference previously passed to {@link LooseEventBus.on | `on`}.
220
+ *
221
+ * @example
222
+ * ```ts
223
+ * const h = () => {};
224
+ * bus.on('ui', 'panel.open', h);
225
+ * // Remove it (with or without leading dot)
226
+ * bus.off('ui', '.panel.open', h);
227
+ * ```
228
+ *
229
+ * @public
230
+ */
231
+ off(e, t, n) {
232
+ const s = this.normalizeTypeKey(String(t));
233
+ this.offExactNormalized(e, s, n);
234
+ }
235
+ /**
236
+ * Internal exact unsubscription using an already **normalized** type key.
237
+ *
238
+ * @param channel - Channel name.
239
+ * @param normalizedType - Event type key with leading dot removed.
240
+ * @param handler - Handler to remove.
241
+ * @internal
242
+ */
243
+ offExactNormalized(e, t, n) {
244
+ const s = this.handlers.get(e);
245
+ if (!s) return;
246
+ const i = s.get(t);
247
+ if (!i) return;
248
+ const d = i.indexOf(n);
249
+ d !== -1 && i.splice(d, 1), i.length === 0 && s.delete(t), s.size === 0 && this.handlers.delete(e);
250
+ }
251
+ /**
252
+ * Internal removal for a **pattern** subscription. No-ops if missing.
253
+ *
254
+ * @param channel - Channel name.
255
+ * @param pattern - Pattern string as originally subscribed.
256
+ * @param handler - Handler to remove.
257
+ * @internal
258
+ */
259
+ offPattern(e, t, n) {
260
+ const s = this.patternHandlers.get(e);
261
+ if (!s) return;
262
+ const i = s.get(t);
263
+ if (!i) return;
264
+ const d = i.indexOf(n);
265
+ d !== -1 && i.splice(d, 1), i.length === 0 && (s.delete(t), this.unindexPattern(e, t)), s.size === 0 && (this.patternHandlers.delete(e), this.patternIndex.delete(e));
266
+ }
267
+ /**
268
+ * Emits an event to all exact subscribers first, then to **matching pattern** subscribers.
269
+ * Duplicate handler references are called **once** (de-duped).
270
+ *
271
+ * @param channel - Channel to emit on.
272
+ * @param type - Event type (subject). A leading dot is ignored for matching.
273
+ * @param payload - Payload delivered to handlers.
274
+ *
275
+ * @example
276
+ * ```ts
277
+ * // Suppose:
278
+ * // - on('ui', 'panel.open', h)
279
+ * // - on('ui', 'panel.*', h) // same handler ref!
280
+ * // - on('ui', 'panel.**', other)
281
+ * bus.emit('ui', 'panel.open', { id: 1 });
282
+ * // => 'h' runs once (de-duped), then 'other'
283
+ * ```
284
+ *
285
+ * @public
286
+ */
287
+ emit(e, t, n) {
288
+ const s = String(t), i = this.normalizeTypeKey(s), d = this.handlers.get(e)?.get(i) ?? [], a = this.matchingPatternHandlers(e, s), r = /* @__PURE__ */ new Set(), f = (c) => {
289
+ for (const u of [...c])
290
+ if (!r.has(u)) {
291
+ r.add(u);
292
+ try {
293
+ u(n);
294
+ } catch (l) {
295
+ console.error(l);
296
+ continue;
297
+ }
298
+ }
299
+ };
300
+ f(d);
301
+ for (const c of a) f(c);
302
+ }
303
+ /**
304
+ * Emits a payload that is only built if somebody is listening.
305
+ *
306
+ * @param channel - Channel to emit on.
307
+ * @param type - Concrete event type.
308
+ * @param make - Builds the payload. Called at most once, and only when a handler matched.
309
+ *
310
+ * @remarks
311
+ * Same matching as {@link LooseEventBus.emit}; the difference is *when* the payload exists.
312
+ * The store's change notification carries the old and new value at a path, and reading those
313
+ * means walking the state tree twice per path. Doing that eagerly meant a slice nobody had
314
+ * subscribed to paid the full cost of describing changes to an audience of nobody — the
315
+ * matching work was already being done to discover there were no handlers.
316
+ *
317
+ * @public
318
+ */
319
+ emitWith(e, t, n) {
320
+ const s = String(t), i = this.normalizeTypeKey(s), d = this.handlers.get(e)?.get(i) ?? [], a = this.matchingPatternHandlers(e, s);
321
+ if (d.length === 0 && a.length === 0) return;
322
+ const r = n(), f = /* @__PURE__ */ new Set(), c = (u) => {
323
+ for (const l of [...u])
324
+ if (!f.has(l)) {
325
+ f.add(l);
326
+ try {
327
+ l(r);
328
+ } catch (y) {
329
+ console.error(y);
330
+ continue;
331
+ }
332
+ }
333
+ };
334
+ c(d);
335
+ for (const u of a) c(u);
336
+ }
337
+ /**
338
+ * Determines if a string is a **pattern** (contains `*`).
339
+ * @param s - Event type or pattern string.
340
+ * @returns `true` if it contains at least one `*`, else `false`.
341
+ * @internal
342
+ */
343
+ isPattern(e) {
344
+ return e.includes("*");
345
+ }
346
+ /**
347
+ * Normalizes event type keys for exact matching by stripping a **single** leading dot.
348
+ *
349
+ * @param s - Event type key.
350
+ * @returns Normalized key without a leading dot.
351
+ * @example
352
+ * ```ts
353
+ * normalizeTypeKey('.a.b') // 'a.b'
354
+ * normalizeTypeKey('a.b') // 'a.b'
355
+ * ```
356
+ * @internal
357
+ */
358
+ normalizeTypeKey(e) {
359
+ return e.replace(/^\./, "");
360
+ }
361
+ /**
362
+ * Splits a path into dot-separated segments after normalization and removes empties.
363
+ * @param p - Event type or pattern string.
364
+ * @internal
365
+ */
366
+ splitPath(e) {
367
+ return this.normalizeTypeKey(e).split(".").filter(Boolean);
368
+ }
369
+ /**
370
+ * Files a pattern under the first segment that could select it.
371
+ * @internal
372
+ */
373
+ indexPattern(e, t) {
374
+ let n = this.patternIndex.get(e);
375
+ n === void 0 && (n = { byHead: /* @__PURE__ */ new Map(), anyHead: [] }, this.patternIndex.set(e, n));
376
+ const s = this.splitPath(t), i = { pattern: t, segments: s }, d = s[0];
377
+ if (d === void 0 || d === "*" || d === "**") {
378
+ n.anyHead.push(i);
379
+ return;
380
+ }
381
+ const a = n.byHead.get(d);
382
+ a === void 0 ? n.byHead.set(d, [i]) : a.push(i);
383
+ }
384
+ /**
385
+ * Removes a pattern from the index. Paired with {@link LooseEventBus.offPattern}.
386
+ * @internal
387
+ */
388
+ unindexPattern(e, t) {
389
+ const n = this.patternIndex.get(e);
390
+ if (n === void 0) return;
391
+ const s = this.splitPath(t)[0], i = s === void 0 || s === "*" || s === "**" ? n.anyHead : n.byHead.get(s);
392
+ if (i === void 0) return;
393
+ const d = i.findIndex((a) => a.pattern === t);
394
+ d !== -1 && i.splice(d, 1), i.length === 0 && i !== n.anyHead && s !== void 0 && n.byHead.delete(s);
395
+ }
396
+ /**
397
+ * The handler lists of every pattern matching this subject.
398
+ *
399
+ * @remarks
400
+ * Shared by `emit` and `emitWith` so the two cannot drift on what "matching" means — which
401
+ * they could, being two copies of the same walk before.
402
+ *
403
+ * The subject is split once here rather than once per pattern tested.
404
+ *
405
+ * @internal
406
+ */
407
+ matchingPatternHandlers(e, t) {
408
+ const n = this.patternHandlers.get(e), s = this.patternIndex.get(e);
409
+ if (n === void 0 || n.size === 0 || s === void 0) return [];
410
+ const i = this.splitPath(t), d = [], a = (f) => {
411
+ for (const c of f) {
412
+ if (!this.matchSegments(c.segments, i)) continue;
413
+ const u = n.get(c.pattern);
414
+ u !== void 0 && d.push(u);
415
+ }
416
+ }, r = i[0];
417
+ if (r !== void 0) {
418
+ const f = s.byHead.get(r);
419
+ f !== void 0 && a(f);
420
+ }
421
+ return a(s.anyHead), d;
422
+ }
423
+ /**
424
+ * Pattern matcher over dot-separated segments, which arrive already split.
425
+ *
426
+ * Rules:
427
+ * - **literal**: exact match.
428
+ * - `*` : matches exactly **one** segment.
429
+ * - `**` : matches **zero or more** remaining segments (including empty).
430
+ *
431
+ * @remarks
432
+ * Takes segments rather than strings so delivery can split each pattern once at registration
433
+ * and the subject once per emit, instead of both once per test. Re-splitting per test was most
434
+ * of what made wildcard delivery expensive: a thousand patterns meant two thousand string
435
+ * splits to deliver one event.
436
+ *
437
+ * @param pSegs - Pattern segments (may include `*`/`**`).
438
+ * @param sSegs - Subject segments to test.
439
+ * @returns `true` if the pattern matches; otherwise `false`.
440
+ *
441
+ * @example
442
+ * ```ts
443
+ * matchSegments(['a', '*'], ['a', 'b']) // true
444
+ * matchSegments(['a', '*'], ['a', 'b', 'c']) // false
445
+ * matchSegments(['a', '**'], ['a']) // true
446
+ * matchSegments(['**', 'end'], ['x', 'y', 'end']) // true
447
+ * ```
448
+ *
449
+ * @internal
450
+ */
451
+ matchSegments(e, t) {
452
+ let n = 0, s = 0, i = -1, d = 0;
453
+ for (; s < t.length; )
454
+ if (n < e.length && (e[n] === "*" || e[n] === t[s]))
455
+ n++, s++;
456
+ else if (n < e.length && e[n] === "**")
457
+ i = n, d = s, n++;
458
+ else if (i !== -1)
459
+ n = i + 1, s = ++d;
460
+ else
461
+ return !1;
462
+ for (; n < e.length && e[n] === "**"; ) n++;
463
+ return n === e.length;
464
+ }
465
+ /**
466
+ * Removes **all** listeners (exact and pattern). Useful for tests/HMR teardown.
467
+ *
468
+ * @example
469
+ * ```ts
470
+ * afterEach(() => bus.clear());
471
+ * ```
472
+ *
473
+ * @public
474
+ */
475
+ clear() {
476
+ this.handlers.clear(), this.patternHandlers.clear(), this.patternIndex.clear();
477
+ }
478
+ /**
479
+ * Returns a snapshot of all registered subscriptions for DevTools introspection.
480
+ *
481
+ * @returns An array of `{ channel, type, count }` entries for each distinct
482
+ * (channel, type/pattern) pair with at least one handler.
483
+ *
484
+ * @internal
485
+ */
486
+ __introspect() {
487
+ const e = [];
488
+ for (const [t, n] of this.handlers)
489
+ for (const [s, i] of n)
490
+ i.length > 0 && e.push({ channel: t, type: s, count: i.length });
491
+ for (const [t, n] of this.patternHandlers)
492
+ for (const [s, i] of n)
493
+ i.length > 0 && e.push({ channel: t, type: s, count: i.length });
494
+ return e;
495
+ }
496
+ }
497
+ class K {
498
+ /**
499
+ * Creates a new {@link Reducer} from a pure reducer function.
500
+ *
501
+ * @param reduce - A function `(state, event) => nextState` that implements your update logic.
502
+ *
503
+ * @example
504
+ * ```ts
505
+ * const reducer = new Reducer<MyState, MyEM>((state, event) => {
506
+ * // implement your transitions here
507
+ * return state;
508
+ * });
509
+ * ```
510
+ *
511
+ * @public
512
+ */
513
+ constructor(e) {
514
+ /**
515
+ * The underlying pure reducer function.
516
+ * @internal
517
+ */
518
+ p(this, "_reduce");
519
+ this._reduce = e;
520
+ }
521
+ /**
522
+ * Applies the reducer to produce the next state.
523
+ *
524
+ * @param state - Current state.
525
+ * @param event - An event drawn from {@link EventUnion | `EventUnion<EM>`}.
526
+ * @returns The next state produced by the underlying reducer function.
527
+ *
528
+ * @example
529
+ * ```ts
530
+ * const next = reducer.reduce(curr, someEvent as EventUnion<MyEM>);
531
+ * ```
532
+ *
533
+ * @public
534
+ */
535
+ reduce(e, t) {
536
+ return this._reduce(e, t);
537
+ }
538
+ }
539
+ const k = /* @__PURE__ */ new Set();
540
+ function M(o, e) {
541
+ const t = o ? `${o}.${e}` : e;
542
+ k.has(t) || (k.add(t), console.warn(
543
+ `[yoltra] State key "${e}"${o ? ` under "${o}"` : ""} contains a dot. Paths are dotted, so this key is indistinguishable from nested objects of the same name: a subscription to "${t}" may match the wrong value, and DevTools patches for it will address the wrong node. Rename the key, or nest it.`
544
+ ));
545
+ }
546
+ function O(o, e, t = "", n = /* @__PURE__ */ new Map()) {
547
+ const s = [];
548
+ return v(o, e, t, n, s), s;
549
+ }
550
+ function v(o, e, t, n, s) {
551
+ if (o === e) return;
552
+ if (typeof o != "object" || typeof e != "object" || o === null || e === null) {
553
+ if (typeof o == "number" && Number.isNaN(o) && Number.isNaN(e))
554
+ return;
555
+ s.push(t);
556
+ return;
557
+ }
558
+ if (o instanceof Date && e instanceof Date) {
559
+ o.getTime() !== e.getTime() && s.push(t);
560
+ return;
561
+ }
562
+ if (o instanceof RegExp && e instanceof RegExp) {
563
+ (o.source !== e.source || e.flags !== o.flags) && s.push(t);
564
+ return;
565
+ }
566
+ if (o instanceof Map || e instanceof Map) {
567
+ s.push(t);
568
+ return;
569
+ }
570
+ if (o instanceof Set || e instanceof Set) {
571
+ s.push(t);
572
+ return;
573
+ }
574
+ const i = o, d = e, a = n.get(i);
575
+ if (a?.has(d)) return;
576
+ const r = a ?? /* @__PURE__ */ new Set();
577
+ r.add(d), a || n.set(i, r);
578
+ try {
579
+ const f = Array.isArray(o), c = Array.isArray(e);
580
+ if (f !== c) {
581
+ s.push(t);
582
+ return;
583
+ }
584
+ if (f) {
585
+ const h = o, m = e;
586
+ h.length !== m.length && t && s.push(t);
587
+ const g = Math.min(h.length, m.length);
588
+ for (let w = 0; w < g; w++)
589
+ h[w] !== m[w] && v(h[w], m[w], t ? `${t}.${w}` : `${w}`, n, s);
590
+ for (let w = g; w < Math.max(h.length, m.length); w++)
591
+ s.push(t ? `${t}.${w}` : `${w}`);
592
+ return;
593
+ }
594
+ const u = Object.keys(o), l = Object.keys(e);
595
+ if (u.length === 0 && l.length === 0) {
596
+ s.push(t);
597
+ return;
598
+ }
599
+ let y = u.length === l.length;
600
+ if (y) {
601
+ for (let h = 0; h < l.length; h++)
602
+ if (!Object.prototype.hasOwnProperty.call(o, l[h])) {
603
+ y = !1;
604
+ break;
605
+ }
606
+ }
607
+ if (y) {
608
+ for (const h of l)
609
+ o[h] !== e[h] && (process.env.NODE_ENV !== "production" && h.includes(".") && M(t, h), v(o[h], e[h], t ? `${t}.${h}` : h, n, s));
610
+ return;
611
+ }
612
+ for (const h of l) {
613
+ const m = Object.prototype.hasOwnProperty.call(o, h);
614
+ if (m && o[h] === e[h]) continue;
615
+ process.env.NODE_ENV !== "production" && h.includes(".") && M(t, h);
616
+ const g = t ? `${t}.${h}` : h;
617
+ if (!m) {
618
+ s.push(g);
619
+ continue;
620
+ }
621
+ v(o[h], e[h], g, n, s);
622
+ }
623
+ for (const h of u)
624
+ Object.prototype.hasOwnProperty.call(e, h) || (process.env.NODE_ENV !== "production" && h.includes(".") && M(t, h), s.push(t ? `${t}.${h}` : h));
625
+ } finally {
626
+ r.delete(d), r.size === 0 && n.delete(i);
627
+ }
628
+ }
629
+ function S(o, e = /* @__PURE__ */ new WeakSet(), t) {
630
+ if (o === null || typeof o != "object" || e.has(o) || (t !== void 0 && o === t.watch && t.onFound(), Object.isFrozen(o))) return o;
631
+ if (e.add(o), Array.isArray(o)) {
632
+ const n = o;
633
+ for (let s = 0; s < n.length; s++)
634
+ n[s] = S(n[s], e, t);
635
+ return Object.freeze(n);
636
+ }
637
+ for (const n of Object.getOwnPropertyNames(o)) {
638
+ const s = Object.getOwnPropertyDescriptor(o, n);
639
+ !s || !("value" in s) || (o[n] = S(o[n], e, t));
640
+ }
641
+ for (const n of Object.getOwnPropertySymbols(o)) {
642
+ const s = Object.getOwnPropertyDescriptor(o, n);
643
+ !s || !("value" in s) || (o[n] = S(o[n], e, t));
644
+ }
645
+ return Object.freeze(o);
646
+ }
647
+ function W(o, e) {
648
+ try {
649
+ return structuredClone(e);
650
+ } catch (t) {
651
+ throw new Error(
652
+ `[yoltra] Initial state for slice "${String(o)}" could not be copied: ${t instanceof Error ? t.message : String(t)}. State must be structured-cloneable — functions, class instances and DOM nodes are not. Keep behaviour out of state and store plain data.`
653
+ );
654
+ }
655
+ }
656
+ function P(o, e) {
657
+ return process.env.NODE_ENV === "production" ? o : S(o, /* @__PURE__ */ new WeakSet(), e);
658
+ }
659
+ const x = 100, z = () => typeof performance < "u" && typeof performance.now == "function" ? performance.now() : Date.now();
660
+ class $ {
661
+ /**
662
+ * Creates a store from a {@link StoreSpec}.
663
+ *
664
+ * @param spec - Store configuration (name, reducers, middleware, optional effects).
665
+ *
666
+ * @public
667
+ */
668
+ constructor(e) {
669
+ /**
670
+ * Store name (used by DevTools & diagnostics).
671
+ *
672
+ * @public
673
+ */
674
+ p(this, "name");
675
+ /**
676
+ * Registered middleware pipeline (run **before** reducers).
677
+ * Stores either raw functions (legacy) or MiddlewareSpec objects.
678
+ * Return `false` from the middleware function to stop propagation.
679
+ *
680
+ * @internal
681
+ */
682
+ p(this, "middleware");
683
+ /**
684
+ * Installed slice reducers keyed by slice name.
685
+ *
686
+ * @internal
687
+ */
688
+ p(this, "reducers");
689
+ /**
690
+ * Current immutable snapshot of the store state.
691
+ * This reference changes whenever any slice changes (shallow immutability).
692
+ *
693
+ * @internal
694
+ */
695
+ p(this, "state");
696
+ /**
697
+ * Bus for reducer wiring (emit by `(channel, type)`).
698
+ *
699
+ * @internal
700
+ */
701
+ p(this, "reducerBus");
702
+ /**
703
+ * Bus for **granular** connector events (emit by **dotted path** inside a slice).
704
+ *
705
+ * @internal
706
+ */
707
+ p(this, "connectorBus");
708
+ /**
709
+ * Coarse-grained listeners (called once per committed event, only if state changed).
710
+ *
711
+ * @internal
712
+ */
713
+ p(this, "listeners", /* @__PURE__ */ new Set());
714
+ /**
715
+ * Registered effect handlers keyed by `"channel::type"` for O(1) lookup.
716
+ * Used for effects with explicit `keys` targeting.
717
+ *
718
+ * @internal
719
+ */
720
+ p(this, "effects", /* @__PURE__ */ new Map());
721
+ /**
722
+ * Pattern-based effects that need runtime matching.
723
+ * Used for effects with `when: { any }`, `{ channel }`, or `{ channels }`.
724
+ * Stores tuples of [effect function, when matcher].
725
+ *
726
+ * @internal
727
+ */
728
+ p(this, "patternEffects", /* @__PURE__ */ new Set());
729
+ /**
730
+ * Committed event subscribers keyed by `"channel::type"` for O(1) lookup.
731
+ * Notified after reducers, before effects, for events that passed middleware.
732
+ *
733
+ * @internal
734
+ */
735
+ p(this, "committedEventSubscribers", /* @__PURE__ */ new Map());
736
+ /**
737
+ * Uncommitted event subscribers keyed by `"channel::type"` for O(1) lookup.
738
+ * Notified when middleware rejects an event.
739
+ *
740
+ * @internal
741
+ */
742
+ p(this, "uncommittedEventSubscribers", /* @__PURE__ */ new Map());
743
+ /**
744
+ * All-events subscribers keyed by `"channel::type"` for O(1) lookup.
745
+ * Notified for both committed and uncommitted events with phase parameter.
746
+ *
747
+ * @internal
748
+ */
749
+ p(this, "allEventSubscribers", /* @__PURE__ */ new Map());
750
+ /**
751
+ * Track reducerBus unsubs per slice for HMR/register/unregister.
752
+ *
753
+ * @internal
754
+ */
755
+ p(this, "sliceUnsubs", /* @__PURE__ */ new Map());
756
+ /**
757
+ * Pattern-based reducers that need runtime matching.
758
+ * Used for reducers with `when: { any }`, `{ channel }`, or `{ channels }`.
759
+ * Maps slice name to the `when` matcher.
760
+ *
761
+ * @internal
762
+ */
763
+ p(this, "patternReducers", /* @__PURE__ */ new Map());
764
+ /**
765
+ * Whether `__replayEvents()` is allowed.
766
+ * Set from `spec.devtools.allowReplay`.
767
+ *
768
+ * @internal
769
+ */
770
+ p(this, "replayEnabled");
771
+ /**
772
+ * Produces the `id` for each emitted event. Defaults to `crypto.randomUUID()`; overridable
773
+ * via {@link StoreSpec.idFactory} for runtimes lacking it or for deterministic tests.
774
+ *
775
+ * @internal
776
+ */
777
+ p(this, "idFactory");
778
+ /**
779
+ * Optional hook invoked when an effect throws/rejects. See
780
+ * {@link StoreSpec.onEffectError}. `await emit()` never rejects on effect
781
+ * failure — this is how callers observe effect errors.
782
+ */
783
+ p(this, "onEffectError");
784
+ /**
785
+ * Optional hook invoked when a reducer throws. See {@link StoreSpec.onReducerError}. The
786
+ * failing slice is isolated rather than the event being rolled back, so this is the only
787
+ * signal that a reducer misbehaved.
788
+ */
789
+ p(this, "onReducerError");
790
+ /**
791
+ * `slice:channel:type` combinations already warned about for payload aliasing.
792
+ *
793
+ * @remarks
794
+ * Development-only diagnostics have to stay quiet enough to be read. One warning names the
795
+ * pattern; repeating it once per event would bury it.
796
+ */
797
+ p(this, "warnedPayloadAliases", /* @__PURE__ */ new Set());
798
+ /**
799
+ * Pending events awaiting the **synchronous** reduce phase (middleware +
800
+ * reducers + subscribers + coarse listeners). Drained by {@link drainReduce}.
801
+ *
802
+ * @internal
803
+ */
804
+ p(this, "reduceQueue", []);
805
+ /**
806
+ * Re-entrancy guard for the synchronous reduce phase.
807
+ *
808
+ * @internal
809
+ */
810
+ p(this, "isReducing", !1);
811
+ /**
812
+ * Registered instrumentation observers (DevTools seam). See {@link instrument}.
813
+ *
814
+ * @internal
815
+ */
816
+ p(this, "instrumentObservers", /* @__PURE__ */ new Set());
817
+ /**
818
+ * Scratch array collecting slice-prefixed changed leaf paths during an
819
+ * instrumented reduce. Set by {@link drainReduce} while observers are active;
820
+ * appended to by {@link forwardEvent}. `null` when not instrumenting.
821
+ *
822
+ * @internal
823
+ */
824
+ p(this, "changedPathSink", null);
825
+ /**
826
+ * Count of effect tasks currently in flight; surfaced as queue depth by
827
+ * {@link __devtoolsIntrospect}.
828
+ *
829
+ * @internal
830
+ */
831
+ p(this, "inFlightEffects", 0);
832
+ /**
833
+ * Tracks processed events by fingerprint with timestamps for TTL-based deduplication.
834
+ *
835
+ * **Deduplication Behavior:**
836
+ * - Events are fingerprinted using `channel::type::JSON(payload)`
837
+ * - If an identical fingerprint is seen within the dedup window, it's skipped
838
+ * - The window is 50ms in development, 100ms in production
839
+ *
840
+ * **Limitations:**
841
+ * - Non-serializable payloads (functions, symbols, circular refs) get unique
842
+ * fingerprints and won't be deduplicated
843
+ * - Legitimate rapid-fire identical events may be incorrectly deduplicated
844
+ * - The cache is bounded to 1000 entries with lazy pruning
845
+ *
846
+ * @internal
847
+ */
848
+ p(this, "processedEvents", /* @__PURE__ */ new Map());
849
+ /**
850
+ * Lifetime count of events suppressed by the deduplication cache.
851
+ * Exposed via {@link __devtoolsIntrospect} so the DevTools agent can
852
+ * surface it in the STORE_METRICS response without further core changes.
853
+ *
854
+ * @internal
855
+ */
856
+ p(this, "dedupCount", 0);
857
+ /**
858
+ * Store-owned metadata for registered effects, keyed by the effect function.
859
+ * Kept **off** the caller's function object: mutating a user-owned function
860
+ * (the old `fn.__quoMeta`) bled metadata across stores that share a handler
861
+ * and left it attached after unregister. Cleared on {@link dispose}.
862
+ *
863
+ * @internal
864
+ */
865
+ p(this, "effectMeta", /* @__PURE__ */ new WeakMap());
866
+ /**
867
+ * Configuration for event deduplication.
868
+ * @internal
869
+ */
870
+ p(this, "dedupConfig");
871
+ /**
872
+ * Timer for periodic cleanup of processed events.
873
+ *
874
+ * @internal
875
+ */
876
+ p(this, "eventCleanupTimer", null);
877
+ 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.dedupConfig = {
878
+ windowMs: e.dedupWindowMs ?? 0,
879
+ maxCacheSize: 1e3
880
+ }, Object.entries(e.reducer).forEach(([t, n]) => {
881
+ this.mountSlice(t, n, { preserveState: !1 });
882
+ }), e.effects?.length)
883
+ for (const t of e.effects)
884
+ this.registerEffect(t);
885
+ 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);
886
+ }
887
+ /**
888
+ * Cleanup resources (timers, etc.) when disposing the store.
889
+ * Call this if you're dynamically creating/destroying stores.
890
+ *
891
+ * @example
892
+ * ```ts
893
+ * const store = createStore({ ... });
894
+ * // later
895
+ * store.dispose();
896
+ * ```
897
+ *
898
+ * @public
899
+ */
900
+ dispose() {
901
+ this.eventCleanupTimer && (clearInterval(this.eventCleanupTimer), this.eventCleanupTimer = null), this.processedEvents.clear(), this.effects.clear(), this.patternEffects.clear(), this.effectMeta = /* @__PURE__ */ new WeakMap(), this.warnedPayloadAliases.clear(), this.listeners.clear(), this.committedEventSubscribers.clear(), this.uncommittedEventSubscribers.clear(), this.allEventSubscribers.clear(), this.instrumentObservers.clear(), this.connectorBus.clear(), this.reducerBus.clear(), this.patternReducers.clear(), this.sliceUnsubs.clear(), this.changedPathSink = null;
902
+ }
903
+ /**
904
+ * Generates a fingerprint for an event for deduplication purposes.
905
+ * Falls back gracefully for non-serializable payloads.
906
+ *
907
+ * @param channel - Event channel.
908
+ * @param type - Event type.
909
+ * @param payload - Event payload.
910
+ * @returns A string fingerprint for the event.
911
+ *
912
+ * @internal
913
+ */
914
+ fingerprint(e, t, n) {
915
+ const s = `${e}::${t}`;
916
+ try {
917
+ if (n == null)
918
+ return `${s}::null`;
919
+ if (typeof n != "object")
920
+ return `${s}::${String(n)}`;
921
+ const i = JSON.stringify(n);
922
+ return `${s}::${i}`;
923
+ } catch {
924
+ return `${s}::${Date.now()}::${Math.random()}`;
925
+ }
926
+ }
927
+ /**
928
+ * Checks if an event should be deduplicated.
929
+ * Returns true if this is a duplicate that should be skipped.
930
+ *
931
+ * @param fp - Event fingerprint.
932
+ * @returns `true` if duplicate (should skip), `false` otherwise.
933
+ *
934
+ * @internal
935
+ */
936
+ shouldDedupe(e, t) {
937
+ const n = Date.now(), s = this.processedEvents.get(e);
938
+ return s !== void 0 && n - s < t ? (this.dedupCount++, !0) : (this.processedEvents.set(e, n), this.ensureCleanupTimer(), this.processedEvents.size > this.dedupConfig.maxCacheSize && this.pruneProcessedEvents(n), !1);
939
+ }
940
+ /**
941
+ * Starts the periodic prune interval if it isn't already running. Called when
942
+ * the first entry is cached so the timer's lifetime tracks actual dedup use
943
+ * (content window or identity `dedupKey`), independent of `dedupWindowMs`.
944
+ *
945
+ * @internal
946
+ */
947
+ ensureCleanupTimer() {
948
+ this.eventCleanupTimer === null && (this.eventCleanupTimer = setInterval(() => {
949
+ this.pruneProcessedEvents(Date.now());
950
+ }, 5e3), this.eventCleanupTimer.unref?.());
951
+ }
952
+ /**
953
+ * Removes expired entries from the processed events cache.
954
+ *
955
+ * @param now - Current timestamp.
956
+ *
957
+ * @internal
958
+ */
959
+ pruneProcessedEvents(e) {
960
+ const t = Math.max(this.dedupConfig.windowMs, x), n = e - t * 2;
961
+ for (const [s, i] of this.processedEvents)
962
+ i < n && this.processedEvents.delete(s);
963
+ this.processedEvents.size === 0 && this.eventCleanupTimer !== null && (clearInterval(this.eventCleanupTimer), this.eventCleanupTimer = null);
964
+ }
965
+ /**
966
+ * Checks if an event matches a `When` matcher.
967
+ *
968
+ * @param when - The When matcher (or undefined for "all events").
969
+ * @param event - The event to check.
970
+ * @returns `true` if the event matches, `false` otherwise.
971
+ *
972
+ * @remarks
973
+ * - `undefined` or missing `when` matches ALL events.
974
+ * - `{ any: true }` matches ALL events.
975
+ * - `{ keys: [...] }` matches if event's `[channel, type]` is in the array.
976
+ * - `{ channel: 'x' }` matches if event's channel equals 'x'.
977
+ * - `{ channels: ['x', 'y'] }` matches if event's channel is in the array.
978
+ *
979
+ * @internal
980
+ */
981
+ matchesWhen(e, t) {
982
+ return !e || "any" in e && e.any === !0 ? !0 : "keys" in e ? e.keys.some(
983
+ ([n, s]) => t.channel === n && t.type === s
984
+ ) : "channel" in e ? t.channel === e.channel : "channels" in e ? e.channels.includes(t.channel) : !1;
985
+ }
986
+ /**
987
+ * Extracts the middleware function from a MiddlewareInput.
988
+ * Handles both raw functions (legacy) and MiddlewareSpec objects.
989
+ *
990
+ * @param input - MiddlewareInput (function or spec).
991
+ * @returns The middleware function.
992
+ *
993
+ * @internal
994
+ */
995
+ getMiddlewareFunction(e) {
996
+ return typeof e == "function" ? e : e.middleware;
997
+ }
998
+ /**
999
+ * Gets the `when` matcher from a MiddlewareInput.
1000
+ *
1001
+ * @param input - MiddlewareInput (function or spec).
1002
+ * @returns The `when` matcher, or `undefined` for raw functions (match all).
1003
+ *
1004
+ * @internal
1005
+ */
1006
+ getMiddlewareWhen(e) {
1007
+ if (typeof e != "function")
1008
+ return e.when;
1009
+ }
1010
+ /**
1011
+ * Invokes all registered **effects** for a given event.
1012
+ * Handles both key-based effects (O(1) lookup) and pattern-based effects (runtime matching).
1013
+ * Errors are caught and logged.
1014
+ *
1015
+ * @param event - The event that was reduced.
1016
+ * @internal
1017
+ */
1018
+ async notifyEffects(e) {
1019
+ const t = `${String(e.channel)}::${String(e.type)}`, n = this.effects.get(t);
1020
+ if (n && n.size > 0)
1021
+ for (const s of [...n])
1022
+ try {
1023
+ await s(e, this.getState, this.emit);
1024
+ } catch (i) {
1025
+ console.error("Effect error:", i), this.onEffectError?.(i, e);
1026
+ }
1027
+ for (const { effect: s, when: i } of this.patternEffects)
1028
+ if (this.matchesWhen(i, e))
1029
+ try {
1030
+ await s(e, this.getState, this.emit);
1031
+ } catch (d) {
1032
+ console.error("Effect error:", d), this.onEffectError?.(d, e);
1033
+ }
1034
+ }
1035
+ /**
1036
+ * Notifies event subscribers for a specific phase.
1037
+ *
1038
+ * Calls both phase-specific subscribers and 'all' subscribers.
1039
+ * Errors are caught and logged, allowing other subscribers to continue.
1040
+ *
1041
+ * @param event - The event to notify about.
1042
+ * @param phase - The phase ('committed' or 'uncommitted').
1043
+ * @internal
1044
+ */
1045
+ notifyEventSubscribers(e, t) {
1046
+ const n = `${String(e.channel)}::${String(e.type)}`, i = (t === "committed" ? this.committedEventSubscribers : this.uncommittedEventSubscribers).get(n);
1047
+ if (i?.size)
1048
+ for (const a of [...i]) this.invokeEventSubscriber(a, e, t);
1049
+ const d = this.allEventSubscribers.get(n);
1050
+ if (d?.size)
1051
+ for (const a of [...d]) this.invokeEventSubscriber(a, e, t);
1052
+ }
1053
+ /**
1054
+ * Invokes a single event-subscription handler **fire-and-forget**: synchronous
1055
+ * throws and async rejections are logged but never block the emit pipeline.
1056
+ * Event subscribers are notifications, not part of the committed reduce result.
1057
+ *
1058
+ * @internal
1059
+ */
1060
+ invokeEventSubscriber(e, t, n) {
1061
+ try {
1062
+ const s = e(t, this.getState, this.emit, n);
1063
+ s && typeof s.then == "function" && s.catch((i) => console.error("Event subscription error:", i));
1064
+ } catch (s) {
1065
+ console.error("Event subscription error:", s);
1066
+ }
1067
+ }
1068
+ /**
1069
+ * Applies a reduced event to a slice and emits **precise** connector events.
1070
+ *
1071
+ * For each changed **leaf path** (via {@link detectChangedProps}), emits that leaf and
1072
+ * all of its **ancestors** once (e.g., `"data"`, `"data.123"`, `"data.123.title"`).
1073
+ *
1074
+ * A slice whose state **is** a single value — a primitive, a `Map`/`Set`, a `Date` — has no
1075
+ * leaf below its root, and `detectChangedProps` reports its change as the empty path `""`.
1076
+ * That path is emitted as-is, so `connect({ reducer, property: "" })` (and any `**` pattern)
1077
+ * hears it. It has no ancestors to walk.
1078
+ *
1079
+ * **State Immutability**: When a slice changes, a new state object is created via
1080
+ * shallow spread: `{ ...this.state, [sliceName]: newSlice }`. This ensures that
1081
+ * `this.state` reference changes, enabling efficient change detection via `===`.
1082
+ *
1083
+ * @param rName - Slice name being updated.
1084
+ * @param event - Reduced event with typed payload.
1085
+ * @returns `true` if the slice actually changed, `false` otherwise.
1086
+ *
1087
+ * @internal
1088
+ */
1089
+ /**
1090
+ * Reduces one slice and contains any error it raises.
1091
+ *
1092
+ * @returns `true` when the slice changed.
1093
+ *
1094
+ * @remarks
1095
+ * The single funnel both dispatch paths go through, which is the point. Keyed reducers run
1096
+ * through `reducerBus`, whose handler loop caught and logged; pattern reducers were called
1097
+ * straight from the drain, so their errors escaped to the caller instead. The same bug in the
1098
+ * same reducer therefore produced two different outcomes depending on how the slice happened
1099
+ * to be targeted — a keyed reducer's throw let the event commit and its effects run, while a
1100
+ * pattern reducer's throw aborted the commit and notified nobody, not even the uncommitted
1101
+ * subscribers a veto would have reached.
1102
+ *
1103
+ * The semantics are now the same either way: **the failing slice is isolated.** Its state is
1104
+ * unchanged, every other slice still reduces, and the event still commits if anything else
1105
+ * changed. Rolling the whole event back would be tidier in principle, but fine-grained
1106
+ * subscribers are notified inside `forwardEvent` as each slice commits, so an event that
1107
+ * reverted afterwards would have already told components about a value that no longer exists.
1108
+ * Isolation keeps every notification truthful.
1109
+ *
1110
+ * @internal
1111
+ */
1112
+ forwardEventGuarded(e, t) {
1113
+ try {
1114
+ return this.forwardEvent(e, t);
1115
+ } catch (n) {
1116
+ return console.error(`Reducer error in slice "${e}":`, n), this.onReducerError?.(n, t, e), !1;
1117
+ }
1118
+ }
1119
+ forwardEvent(e, t) {
1120
+ const n = this.state[e], s = this.reducers[e].reduce(n, t);
1121
+ if (n === s) return !1;
1122
+ const i = O(n, s);
1123
+ if (i.length === 0) return !1;
1124
+ const d = t.payload, a = process.env.NODE_ENV !== "production" && d !== null && typeof d == "object" ? {
1125
+ watch: d,
1126
+ onFound: () => {
1127
+ const c = `${e}:${t.channel}:${t.type}`;
1128
+ this.warnedPayloadAliases.has(c) || (this.warnedPayloadAliases.add(c), console.warn(
1129
+ `[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.`
1130
+ ));
1131
+ }
1132
+ } : void 0, r = P(s, a);
1133
+ if (this.state = { ...this.state, [e]: r }, this.changedPathSink)
1134
+ for (const c of i)
1135
+ this.changedPathSink.push(c ? `${e}.${c}` : e);
1136
+ const f = /* @__PURE__ */ new Set();
1137
+ for (const c of i) {
1138
+ if (c === "") {
1139
+ f.add("");
1140
+ continue;
1141
+ }
1142
+ for (const u of $.buildAncestorPaths(c)) f.add(u);
1143
+ }
1144
+ for (const c of f)
1145
+ this.connectorBus.emitWith(e, c, () => ({
1146
+ oldValue: this.getAtPath(n, c),
1147
+ newValue: this.getAtPath(r, c),
1148
+ path: c
1149
+ }));
1150
+ return !0;
1151
+ }
1152
+ /**
1153
+ * Returns a structured introspection snapshot for DevTools UIs.
1154
+ *
1155
+ * @remarks
1156
+ * Reads the internal middleware, effects, reducers, and subscriber
1157
+ * registries and returns a plain-object summary matching the
1158
+ * `STORE_SUBSCRIPTIONS` protocol message shape.
1159
+ *
1160
+ * @public
1161
+ */
1162
+ __devtoolsIntrospect() {
1163
+ const e = Object.keys(this.reducers).map((a) => {
1164
+ const r = this.patternReducers.get(a);
1165
+ return { name: a, when: r };
1166
+ }), t = [];
1167
+ for (const [a, r] of this.effects) {
1168
+ if (r.size === 0) continue;
1169
+ const [f, c] = a.split("::");
1170
+ for (const u of r) {
1171
+ const l = this.effectMeta.get(u);
1172
+ t.push({ channel: f, type: c, name: l?.name, description: l?.description });
1173
+ }
1174
+ }
1175
+ for (const a of this.patternEffects) {
1176
+ const r = this.effectMeta.get(a.effect);
1177
+ t.push({
1178
+ channel: "*",
1179
+ type: "*",
1180
+ name: r?.name,
1181
+ description: r?.description
1182
+ });
1183
+ }
1184
+ const n = [];
1185
+ for (const a of this.middleware)
1186
+ typeof a == "function" ? n.push({ name: a.name || void 0 }) : n.push({
1187
+ name: a.meta?.name,
1188
+ description: a.meta?.description,
1189
+ when: a.when
1190
+ });
1191
+ const s = [];
1192
+ for (const a of this.connectorBus.__introspect())
1193
+ for (let r = 0; r < a.count; r++)
1194
+ s.push({ reducer: a.channel, property: a.type });
1195
+ const i = [];
1196
+ for (const [a, r] of this.committedEventSubscribers) {
1197
+ if (r.size === 0) continue;
1198
+ const [f, c] = a.split("::");
1199
+ for (let u = 0; u < r.size; u++)
1200
+ i.push({ channel: f, type: c, phase: "committed" });
1201
+ }
1202
+ for (const [a, r] of this.uncommittedEventSubscribers) {
1203
+ if (r.size === 0) continue;
1204
+ const [f, c] = a.split("::");
1205
+ for (let u = 0; u < r.size; u++)
1206
+ i.push({ channel: f, type: c, phase: "uncommitted" });
1207
+ }
1208
+ for (const [a, r] of this.allEventSubscribers) {
1209
+ if (r.size === 0) continue;
1210
+ const [f, c] = a.split("::");
1211
+ for (let u = 0; u < r.size; u++)
1212
+ i.push({ channel: f, type: c, phase: "all" });
1213
+ }
1214
+ const d = this.listeners.size;
1215
+ return {
1216
+ reducers: e,
1217
+ effects: t,
1218
+ middleware: n,
1219
+ atomic: s,
1220
+ event: i,
1221
+ coarse: d,
1222
+ dedupHits: this.dedupCount,
1223
+ queueDepth: this.reduceQueue.length + this.inFlightEffects
1224
+ };
1225
+ }
1226
+ /**
1227
+ * Applies an externally provided **whole-state** (e.g., DevTools time travel) and emits
1228
+ * fine-grained path changes for each slice.
1229
+ *
1230
+ * **State Immutability**: If any slices change, a new state object is created via
1231
+ * shallow spread. This ensures consistent immutability with {@link forwardEvent}.
1232
+ *
1233
+ * **Missing slices**: the snapshot should contain every slice. A slice absent
1234
+ * from `nextPlain` is **retained at its current value** (not blanked to
1235
+ * `undefined`, which would make `getState().<slice>` throw on next access).
1236
+ *
1237
+ * @param nextPlain - Plain JS object to become the new state.
1238
+ *
1239
+ * @internal
1240
+ */
1241
+ __applyExternalState(e) {
1242
+ if (!this.replayEnabled)
1243
+ throw new Error(
1244
+ "[yoltra] External state apply (time-travel) is disabled. Enable it with createStore({ devtools: { allowReplay: true } })"
1245
+ );
1246
+ const t = this.state, n = e, s = { ...this.state };
1247
+ let i = !1;
1248
+ Object.keys(this.reducers).forEach((d) => {
1249
+ const a = t?.[d], r = n?.[d];
1250
+ if (r === void 0) {
1251
+ process.env.NODE_ENV !== "production" && console.warn(
1252
+ `[yoltra] External state is missing slice "${String(
1253
+ d
1254
+ )}"; retaining its current value. Time-travel snapshots should contain all slices.`
1255
+ );
1256
+ return;
1257
+ }
1258
+ if (a === r) return;
1259
+ const f = P(r);
1260
+ s[d] = f, i = !0;
1261
+ const c = O(a, r);
1262
+ if (c.length === 0) return;
1263
+ const u = /* @__PURE__ */ new Set();
1264
+ for (const l of c) {
1265
+ if (l === "") {
1266
+ u.add("");
1267
+ continue;
1268
+ }
1269
+ for (const y of $.buildAncestorPaths(l)) u.add(y);
1270
+ }
1271
+ for (const l of u) {
1272
+ const y = this.getAtPath(a, l), h = this.getAtPath(f, l);
1273
+ this.connectorBus.emit(d, l, { oldValue: y, newValue: h, path: l });
1274
+ }
1275
+ }), i && (this.state = s), i && this.listeners.forEach((d) => d());
1276
+ }
1277
+ /**
1278
+ * Replays a sequence of events from a snapshot through reducers and event
1279
+ * subscribers ONLY. Skips dedup, middleware, and effects.
1280
+ *
1281
+ * This method is gated by the `devtools.allowReplay` runtime config.
1282
+ * If replay is not enabled, this method throws.
1283
+ *
1284
+ * @param snapshot - The state snapshot to restore before replaying.
1285
+ * @param events - Array of events to replay (in order).
1286
+ *
1287
+ * @internal
1288
+ */
1289
+ __replayEvents(e, t) {
1290
+ if (!this.replayEnabled)
1291
+ throw new Error(
1292
+ "[yoltra] Event replay is disabled. Enable it with createStore({ devtools: { allowReplay: true } })"
1293
+ );
1294
+ this.__applyExternalState(e);
1295
+ for (const n of t) {
1296
+ const s = n, i = this.state;
1297
+ this.reducerBus.emit(s.channel, s.type, s.payload, s);
1298
+ for (const [r, f] of this.patternReducers)
1299
+ this.matchesWhen(f, s) && this.forwardEventGuarded(r, s);
1300
+ const d = this.state, a = i !== d;
1301
+ this.notifyEventSubscribers(s, "committed"), a && this.listeners.forEach((r) => r());
1302
+ }
1303
+ }
1304
+ /**
1305
+ * Emits a typed event `(channel, type, payload)`.
1306
+ * Events are queued and processed **sequentially** (FIFO).
1307
+ *
1308
+ * **Pipeline per event:** the *reduce phase* (steps 1-4) runs **synchronously**,
1309
+ * so `getState()` reflects the change as soon as `emit()` returns; the *effect
1310
+ * phase* (step 5) runs afterwards, asynchronously.
1311
+ * 1. **Deduplication** (opt-in) - Skip when content-dedup is enabled (`dedupWindowMs > 0`) or a matching `dedupKey` recurs; off by default
1312
+ * 2. **Middleware** (sync) - Pre-reducer hooks; may cancel by returning `false`
1313
+ * 3. **Reducers** (sync) - state updates + fine-grained path notifications
1314
+ * 4. **Subscribers + coarse** (sync) - event subscribers (fire-and-forget) then coarse listeners (only if state changed)
1315
+ * 5. **Effects** (async) - side-effects keyed by `(channel, type)`; the returned promise resolves once they complete
1316
+ *
1317
+ * **Change Detection**: Uses reference equality (`===`) on `this.state` to determine
1318
+ * if any slice changed. Works because {@link forwardEvent} creates a new state reference
1319
+ * via shallow spread when any slice changes.
1320
+ *
1321
+ * @typeParam C - Channel key in `EM`.
1322
+ * @typeParam T - Type key within channel `C`.
1323
+ * @param channel - Channel name.
1324
+ * @param type - Event type name.
1325
+ * @param payload - Payload typed as `EM[C][T]`.
1326
+ * @param opts - Optional per-emit options (e.g. `dedupKey` for identity-based dedup).
1327
+ * @returns A promise that resolves once this event's effects have finished.
1328
+ * State is already updated synchronously before `emit()` returns.
1329
+ *
1330
+ * @example Basic usage
1331
+ * ```ts
1332
+ * await store.emit('ui', 'increment', 1);
1333
+ * ```
1334
+ *
1335
+ * @example With middleware cancellation
1336
+ * ```ts
1337
+ * store.registerMiddleware((state, event) => {
1338
+ * if (event.type === 'dangerous') return false; // cancel
1339
+ * return true; // allow
1340
+ * });
1341
+ *
1342
+ * await store.emit('ui', 'dangerous', null); // cancelled, no state change
1343
+ * ```
1344
+ *
1345
+ * @public
1346
+ */
1347
+ async emit(e, t, n, s) {
1348
+ const i = s?.dedupKey, d = this.dedupConfig.windowMs;
1349
+ if (s?.skipDedup !== !0 && (d > 0 || i !== void 0)) {
1350
+ const c = i !== void 0 && d <= 0 ? x : d, u = i !== void 0 ? `${e}::${t}::#${i}` : this.fingerprint(e, t, n);
1351
+ if (this.shouldDedupe(u, c))
1352
+ return;
1353
+ }
1354
+ const a = s?.id ?? this.idFactory();
1355
+ let r;
1356
+ const f = new Promise((c) => {
1357
+ r = c;
1358
+ });
1359
+ return this.reduceQueue.push({
1360
+ channel: e,
1361
+ type: t,
1362
+ payload: n,
1363
+ id: a,
1364
+ meta: s?.meta,
1365
+ resolve: r
1366
+ }), this.drainReduce(), f;
1367
+ }
1368
+ /**
1369
+ * Drains the reduce queue **synchronously**. For each event it runs middleware,
1370
+ * reducers, event subscribers, and coarse listeners in the same tick, so
1371
+ * `getState()` reflects the change the moment {@link emit} returns. Re-entrant
1372
+ * emits (from middleware or subscribers) are appended and drained in the same
1373
+ * pass — preserving FIFO order without interleaving reducers. Each committed
1374
+ * event's effects then run in an independent task (see {@link runEventEffects}).
1375
+ *
1376
+ * @internal
1377
+ */
1378
+ drainReduce() {
1379
+ if (!this.isReducing) {
1380
+ this.isReducing = !0;
1381
+ try {
1382
+ for (; this.reduceQueue.length > 0; ) {
1383
+ const { channel: e, type: t, payload: n, id: s, meta: i, resolve: d } = this.reduceQueue.shift(), a = {
1384
+ channel: e,
1385
+ type: t,
1386
+ payload: n,
1387
+ id: s,
1388
+ ...i !== void 0 ? { meta: i } : {}
1389
+ }, r = this.instrumentObservers.size > 0, f = r ? this.state : void 0, c = r ? [] : void 0;
1390
+ c !== void 0 && (this.changedPathSink = c);
1391
+ const u = r ? z() : 0;
1392
+ let l = !1;
1393
+ try {
1394
+ l = this.applyEventSync(a);
1395
+ } catch (y) {
1396
+ console.error("Emit reduce error:", y);
1397
+ } finally {
1398
+ r && (this.changedPathSink = null);
1399
+ }
1400
+ r && this.emitInstrumentation(
1401
+ a,
1402
+ l,
1403
+ c ?? [],
1404
+ f,
1405
+ z() - u
1406
+ ), this.runEventEffects(a, l, d);
1407
+ }
1408
+ } finally {
1409
+ this.isReducing = !1;
1410
+ }
1411
+ }
1412
+ }
1413
+ /**
1414
+ * Runs the **synchronous** part of the pipeline for a single event: middleware
1415
+ * (may veto), key- and pattern-based reducers, committed/uncommitted event
1416
+ * subscribers (fire-and-forget), and coarse listeners.
1417
+ *
1418
+ * @returns `true` if the event was committed (passed middleware), `false` if a
1419
+ * middleware vetoed it.
1420
+ *
1421
+ * @internal
1422
+ */
1423
+ applyEventSync(e) {
1424
+ for (const s of this.middleware) {
1425
+ const i = this.getMiddlewareWhen(s);
1426
+ if (!this.matchesWhen(i, e)) continue;
1427
+ const d = this.getMiddlewareFunction(s);
1428
+ let a;
1429
+ try {
1430
+ a = d(this.state, e, this.emit), process.env.NODE_ENV !== "production" && typeof a?.then == "function" && console.error(
1431
+ `[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.`
1432
+ );
1433
+ } catch (r) {
1434
+ console.error("Middleware error:", r), a = !1;
1435
+ }
1436
+ if (!a)
1437
+ return this.notifyEventSubscribers(e, "uncommitted"), !1;
1438
+ }
1439
+ const t = this.state;
1440
+ this.reducerBus.emit(
1441
+ e.channel,
1442
+ e.type,
1443
+ e.payload,
1444
+ e
1445
+ );
1446
+ for (const [s, i] of this.patternReducers)
1447
+ this.matchesWhen(i, e) && this.forwardEventGuarded(s, e);
1448
+ const n = t !== this.state;
1449
+ return this.notifyEventSubscribers(e, "committed"), n && this.listeners.forEach((s) => s()), !0;
1450
+ }
1451
+ /**
1452
+ * Runs a single committed event's effects as an **independent async task**,
1453
+ * then resolves that event's completion deferred so `await emit(...)` settles
1454
+ * once its effects finish. Per-event tasks (rather than one shared serialized
1455
+ * loop) let an effect `await` a re-entrant emit without deadlocking.
1456
+ *
1457
+ * @internal
1458
+ */
1459
+ async runEventEffects(e, t, n) {
1460
+ this.inFlightEffects++;
1461
+ try {
1462
+ t && await this.notifyEffects(e);
1463
+ } catch (s) {
1464
+ console.error("Effect error:", s);
1465
+ } finally {
1466
+ this.inFlightEffects--, n();
1467
+ }
1468
+ }
1469
+ /**
1470
+ * Registers an instrumentation observer. See {@link StoreInstance.instrument}.
1471
+ *
1472
+ * @public
1473
+ */
1474
+ instrument(e) {
1475
+ return this.instrumentObservers.add(e), () => {
1476
+ this.instrumentObservers.delete(e);
1477
+ };
1478
+ }
1479
+ /**
1480
+ * Builds an {@link InstrumentedEvent} from the reduce result and notifies
1481
+ * observers. `changedPaths` are the exact slice-prefixed leaf paths recorded
1482
+ * by {@link forwardEvent} during this reduce, so DevTools patches need no
1483
+ * re-diff.
1484
+ *
1485
+ * @internal
1486
+ */
1487
+ emitInstrumentation(e, t, n, s, i) {
1488
+ const d = {}, a = {};
1489
+ for (const f of n)
1490
+ d[f] = this.getAtPath(s, f), a[f] = this.getAtPath(this.state, f);
1491
+ const r = {
1492
+ event: {
1493
+ id: e.id,
1494
+ channel: e.channel,
1495
+ type: e.type,
1496
+ payload: e.payload,
1497
+ // Conditional, so an event without metadata produces an observer payload
1498
+ // byte-identical to the pre-`meta` shape.
1499
+ ...e.meta !== void 0 ? { meta: e.meta } : {}
1500
+ },
1501
+ committed: t,
1502
+ changedPaths: n,
1503
+ prevValues: d,
1504
+ nextValues: a,
1505
+ reduceTimeMs: i
1506
+ };
1507
+ for (const f of [...this.instrumentObservers])
1508
+ try {
1509
+ f(r);
1510
+ } catch (c) {
1511
+ console.error("Instrumentation observer error:", c);
1512
+ }
1513
+ }
1514
+ /**
1515
+ * Connects a **fine-grained** listener to a dotted path under a slice.
1516
+ *
1517
+ * @param spec - `{ reducer, property }` where `property` is a dotted path (e.g., `"items.0.title"`).
1518
+ * Supports wildcards: `*` (one segment) and `**` (zero or more segments).
1519
+ * @param h - Handler receiving a {@link Change} with `{ oldValue, newValue, path }`.
1520
+ * @returns Unsubscribe function.
1521
+ *
1522
+ * @example Exact path
1523
+ * ```ts
1524
+ * const off = store.connect(
1525
+ * { reducer: 'todos', property: 'items.0.title' },
1526
+ * (chg) => console.log('title changed:', chg.newValue)
1527
+ * );
1528
+ * off();
1529
+ * ```
1530
+ *
1531
+ * @example Wildcard pattern
1532
+ * ```ts
1533
+ * // Listen to any item title change
1534
+ * const off = store.connect(
1535
+ * { reducer: 'todos', property: 'items.*.title' },
1536
+ * (chg) => console.log('some title changed')
1537
+ * );
1538
+ * ```
1539
+ *
1540
+ * @public
1541
+ */
1542
+ connect(e, t) {
1543
+ return this.connectorBus.on(e.reducer, e.property, t);
1544
+ }
1545
+ /**
1546
+ * Subscribe to events by channel and type.
1547
+ *
1548
+ * Event subscriptions are intended for the View layer (e.g., React components)
1549
+ * to react to events without affecting the event flow. They are fire-and-forget
1550
+ * and cannot cancel event propagation.
1551
+ *
1552
+ * **Phases:**
1553
+ * - `'committed'` (default): Events that passed middleware and reached reducers.
1554
+ * Notified after reducers, before effects.
1555
+ * - `'uncommitted'`: Events rejected by middleware. Notified immediately after rejection.
1556
+ * - `'all'`: Both committed and uncommitted events. Handler receives the phase parameter
1557
+ * to distinguish between the two.
1558
+ *
1559
+ * @typeParam C - Channel key within `EM`.
1560
+ * @typeParam T - Event type key within channel `C`.
1561
+ * @param channel - Channel to subscribe to.
1562
+ * @param type - Event type to subscribe to.
1563
+ * @param handler - Handler function `(event, getState, emit, phase)`.
1564
+ * @param phase - Event phase to subscribe to (default: `'committed'`).
1565
+ * @returns Unsubscribe function.
1566
+ *
1567
+ * @example Committed events (default)
1568
+ * ```ts
1569
+ * const off = store.onEvent('ui', 'save', (event, getState, emit, phase) => {
1570
+ * console.log('Save committed:', event.payload);
1571
+ * });
1572
+ * off();
1573
+ * ```
1574
+ *
1575
+ * @example Uncommitted (rejected) events
1576
+ * ```ts
1577
+ * store.onEvent('ui', 'delete', (event, getState, emit, phase) => {
1578
+ * console.log('Delete was rejected by middleware');
1579
+ * }, 'uncommitted');
1580
+ * ```
1581
+ *
1582
+ * @example All events
1583
+ * ```ts
1584
+ * store.onEvent('ui', 'action', (event, getState, emit, phase) => {
1585
+ * console.log('Action:', phase); // 'committed' or 'uncommitted'
1586
+ * }, 'all');
1587
+ * ```
1588
+ *
1589
+ * @public
1590
+ */
1591
+ onEvent(e, t, n, s = "committed") {
1592
+ const i = `${e}::${String(t)}`, d = s === "committed" ? this.committedEventSubscribers : s === "uncommitted" ? this.uncommittedEventSubscribers : this.allEventSubscribers;
1593
+ return d.has(i) || d.set(i, /* @__PURE__ */ new Set()), d.get(i).add(n), () => {
1594
+ const a = d.get(i);
1595
+ a && (a.delete(n), a.size === 0 && d.delete(i));
1596
+ };
1597
+ }
1598
+ /**
1599
+ * Subscribes to **coarse-grained** commits (called once per successful event, only if state changed).
1600
+ *
1601
+ * **Use Case**: React's `useSyncExternalStore` or similar external store integrations.
1602
+ *
1603
+ * @param fn - Listener invoked after reducers/effects have run and state has changed.
1604
+ * @returns Unsubscribe function.
1605
+ *
1606
+ * @example
1607
+ * ```ts
1608
+ * const off = store.subscribe(() => console.log('state committed'));
1609
+ * // Later:
1610
+ * off();
1611
+ * ```
1612
+ *
1613
+ * @public
1614
+ */
1615
+ subscribe(e) {
1616
+ return this.listeners.add(e), () => this.listeners.delete(e);
1617
+ }
1618
+ /**
1619
+ * Returns the current immutable state snapshot.
1620
+ *
1621
+ * @returns Deep-readonly state object.
1622
+ *
1623
+ * @example
1624
+ * ```ts
1625
+ * const state = store.getState();
1626
+ * console.log(state.counter.value);
1627
+ * ```
1628
+ *
1629
+ * @public
1630
+ */
1631
+ getState() {
1632
+ return this.state;
1633
+ }
1634
+ /**
1635
+ * Registers a middleware (runs **before** reducers).
1636
+ *
1637
+ * @param mw - Middleware `(state, event, emit) => boolean`. Return `false` to cancel event
1638
+ * propagation.
1639
+ * @returns Unsubscribe function that removes this middleware.
1640
+ *
1641
+ * @remarks
1642
+ * **Synchronous, and that is the contract.** The reduce phase completes before `emit()`
1643
+ * returns, so the commit decision has to be available in the same tick. An `async` middleware
1644
+ * returns a Promise, every Promise is truthy, and the veto would therefore never fire — the
1645
+ * event would commit while the middleware was still deciding. The type rejects it; this note
1646
+ * exists because the examples here used to teach it. Do authorization and validation here, and
1647
+ * anything that needs to await in an effect.
1648
+ *
1649
+ * @example Logging middleware
1650
+ * ```ts
1651
+ * const off = store.registerMiddleware((state, event) => {
1652
+ * console.log('Event:', event.channel, event.type, event.payload);
1653
+ * return true; // allow
1654
+ * });
1655
+ * off();
1656
+ * ```
1657
+ *
1658
+ * @example Cancellation middleware
1659
+ * ```ts
1660
+ * store.registerMiddleware((state, event) => {
1661
+ * if (event.type === 'forbidden') return false; // cancel
1662
+ * return true;
1663
+ * });
1664
+ * ```
1665
+ *
1666
+ * @public
1667
+ */
1668
+ registerMiddleware(e) {
1669
+ return this.middleware.push(e), () => {
1670
+ const t = this.middleware.indexOf(e);
1671
+ t !== -1 && this.middleware.splice(t, 1);
1672
+ };
1673
+ }
1674
+ /**
1675
+ * Dynamically **adds** a named slice reducer at runtime.
1676
+ *
1677
+ * @param name - New slice name (must not already exist).
1678
+ * @param spec - Reducer spec (state, when, reducer).
1679
+ * @returns Disposer function that **removes** the slice (and its state).
1680
+ *
1681
+ * @example
1682
+ * ```ts
1683
+ * const dispose = store.registerReducer('filters', {
1684
+ * state: { q: '' },
1685
+ * events: [['ui', 'setQuery']],
1686
+ * reducer(s, evt) {
1687
+ * return evt.type === 'setQuery' ? { q: evt.payload } : s;
1688
+ * }
1689
+ * });
1690
+ * // Later:
1691
+ * dispose();
1692
+ * ```
1693
+ *
1694
+ * @public
1695
+ */
1696
+ registerReducer(e, t) {
1697
+ if (Object.prototype.hasOwnProperty.call(this.reducers, e))
1698
+ throw new Error(`Reducer ${e} already exists`);
1699
+ return this.mountSlice(e, t, {
1700
+ preserveState: !1
1701
+ }), this.listeners.forEach((n) => n()), () => {
1702
+ this.unmountSlice(e, { deleteState: !0 }), this.listeners.forEach((n) => n());
1703
+ };
1704
+ }
1705
+ /**
1706
+ * Registers an **effect** (stateless async event consumer) that runs after reducers.
1707
+ *
1708
+ * Effects are **keyed** by `(channel, type)` for O(1) lookup (no scanning all effects).
1709
+ *
1710
+ * @param spec - Effect specification with `when` targeting and `effect` (handler).
1711
+ * @returns Unsubscribe function.
1712
+ *
1713
+ * @example Logging effect
1714
+ * ```ts
1715
+ * const off = store.registerEffect({
1716
+ * events: [['ui', 'increment']],
1717
+ * effect: async (evt, getState, emit) => {
1718
+ * console.log('increment', evt.payload, getState().counter.value);
1719
+ * }
1720
+ * });
1721
+ * off();
1722
+ * ```
1723
+ *
1724
+ * @example Multi-event effect
1725
+ * ```ts
1726
+ * store.registerEffect({
1727
+ * events: [['ui', 'increment'], ['ui', 'decrement']],
1728
+ * effect: async (evt, getState, emit) => {
1729
+ * // Runs for both increment and decrement
1730
+ * await saveToServer(getState());
1731
+ * }
1732
+ * });
1733
+ * ```
1734
+ *
1735
+ * @public
1736
+ */
1737
+ registerEffect(e) {
1738
+ const { effect: t, meta: n, when: s } = e, i = [];
1739
+ if (n && this.effectMeta.set(t, n), s && ("any" in s && s.any === !0 || "channel" in s || "channels" in s)) {
1740
+ const r = { effect: t, when: s };
1741
+ return this.patternEffects.add(r), () => {
1742
+ this.patternEffects.delete(r);
1743
+ };
1744
+ }
1745
+ const a = this.normalizeEventKeys(e);
1746
+ if (a.length === 0 && !s) {
1747
+ const r = { effect: t, when: { any: !0 } };
1748
+ return this.patternEffects.add(r), () => {
1749
+ this.patternEffects.delete(r);
1750
+ };
1751
+ }
1752
+ for (const [r, f] of a) {
1753
+ const c = `${String(r)}::${String(f)}`;
1754
+ this.effects.has(c) || this.effects.set(c, /* @__PURE__ */ new Set()), this.effects.get(c).add(t), i.push(() => {
1755
+ const u = this.effects.get(c);
1756
+ u && (u.delete(t), u.size === 0 && this.effects.delete(c));
1757
+ });
1758
+ }
1759
+ return () => {
1760
+ for (const r of i) r();
1761
+ };
1762
+ }
1763
+ /**
1764
+ * Convenience helper to register an **effect** filtered by a single `(channel, type)` pair.
1765
+ *
1766
+ * @typeParam C - Channel key within `EM`.
1767
+ * @typeParam T - Event type key within channel `C`.
1768
+ * @param channel - Channel to filter.
1769
+ * @param type - Event type to filter.
1770
+ * @param handler - Effect handler `(payload, getState, emit, event)`.
1771
+ * @returns Unsubscribe/teardown function.
1772
+ *
1773
+ * @example
1774
+ * ```ts
1775
+ * const off = store.onEffect('ui', 'increment', async (n, get, emit) => {
1776
+ * if (n > 10) await emit('ui', 'increment', -10);
1777
+ * });
1778
+ * // later
1779
+ * off();
1780
+ * ```
1781
+ *
1782
+ * @public
1783
+ */
1784
+ onEffect(e, t, n) {
1785
+ const s = async (i, d, a) => {
1786
+ if (i.channel !== e || i.type !== t) return;
1787
+ const r = i;
1788
+ return n(r.payload, d, a, r);
1789
+ };
1790
+ return this.registerEffect({
1791
+ when: { keys: [[e, t]] },
1792
+ effect: s
1793
+ });
1794
+ }
1795
+ /**
1796
+ * Replaces the **entire** middleware pipeline (HMR-friendly).
1797
+ *
1798
+ * @param next - New middleware array.
1799
+ *
1800
+ * @example Hot module replacement
1801
+ * ```ts
1802
+ * if (import.meta.hot) {
1803
+ * import.meta.hot.accept('./middleware', (newModule) => {
1804
+ * store.replaceMiddleware(newModule.middleware);
1805
+ * });
1806
+ * }
1807
+ * ```
1808
+ *
1809
+ * @public
1810
+ */
1811
+ replaceMiddleware(e) {
1812
+ this.middleware.length = 0;
1813
+ for (const t of e) this.middleware.push(t);
1814
+ }
1815
+ /**
1816
+ * Replaces all registered **effects** (HMR-friendly).
1817
+ *
1818
+ * @param next - New effects array (as EffectSpecs).
1819
+ *
1820
+ * @example Hot module replacement
1821
+ * ```ts
1822
+ * if (import.meta.hot) {
1823
+ * import.meta.hot.accept('./effects', (newModule) => {
1824
+ * store.replaceEffects(newModule.effects);
1825
+ * });
1826
+ * }
1827
+ * ```
1828
+ *
1829
+ * @public
1830
+ */
1831
+ replaceEffects(e) {
1832
+ this.effects.clear(), this.patternEffects.clear();
1833
+ for (const t of e)
1834
+ this.registerEffect(t);
1835
+ }
1836
+ /**
1837
+ * Replaces the entire **reducer set** (HMR-friendly).
1838
+ *
1839
+ * @param next - Map of slice specs keyed by slice name.
1840
+ * @param opts - `{ preserveState?: boolean }` (default `true`).
1841
+ *
1842
+ * @example Hot module replacement
1843
+ * ```ts
1844
+ * if (import.meta.hot) {
1845
+ * import.meta.hot.accept('./reducers', (newModule) => {
1846
+ * store.replaceReducers(newModule.reducers, { preserveState: true });
1847
+ * });
1848
+ * }
1849
+ * ```
1850
+ *
1851
+ * @public
1852
+ */
1853
+ replaceReducers(e, t = {}) {
1854
+ const n = t.preserveState !== !1, s = new Set(Object.keys(this.reducers)), i = Object.entries(e), d = new Set(i.map(([a]) => a));
1855
+ for (const a of s)
1856
+ d.has(a) || this.unmountSlice(a, { deleteState: !0 });
1857
+ for (const [a, r] of i)
1858
+ s.has(a) ? (this.unmountSlice(a, { deleteState: !1 }), this.mountSlice(a, r, { preserveState: n })) : this.mountSlice(a, r, { preserveState: !1 });
1859
+ }
1860
+ /**
1861
+ * Convenience API to replace **any subset** of store parts (HMR patterns).
1862
+ *
1863
+ * @param partial - Partial replacement set.
1864
+ *
1865
+ * @example Replace everything
1866
+ * ```ts
1867
+ * store.hotReplace({
1868
+ * reducer: newReducers,
1869
+ * middleware: newMiddleware,
1870
+ * effects: newEffects,
1871
+ * preserveState: true
1872
+ * });
1873
+ * ```
1874
+ *
1875
+ * @public
1876
+ */
1877
+ hotReplace(e) {
1878
+ e.middleware && this.replaceMiddleware(e.middleware), e.effects && this.replaceEffects(e.effects), e.reducer && this.replaceReducers(e.reducer, { preserveState: e.preserveState });
1879
+ }
1880
+ /**
1881
+ * Mounts a slice: installs reducer, initializes state (unless preserved),
1882
+ * and wires `(channel, type)` listeners on the reducer bus.
1883
+ *
1884
+ * @param name - Slice name.
1885
+ * @param rSpec - Reducer spec (state, when, reducer).
1886
+ * @param opts - `{ preserveState: boolean }` whether to keep existing state.
1887
+ *
1888
+ * @internal
1889
+ */
1890
+ mountSlice(e, t, n) {
1891
+ const s = e, { reducer: i, state: d, when: a } = t;
1892
+ if (this.reducers[e] = new K(i), (!n.preserveState || this.state[s] === void 0) && (this.state = {
1893
+ ...this.state,
1894
+ [s]: P(W(s, d))
1895
+ }), a && ("any" in a && a.any === !0 || "channel" in a || "channels" in a)) {
1896
+ this.patternReducers.set(e, a), this.sliceUnsubs.set(s, []);
1897
+ return;
1898
+ }
1899
+ const f = this.normalizeEventKeys(t);
1900
+ if (f.length === 0 && !a) {
1901
+ this.patternReducers.set(e, { any: !0 }), this.sliceUnsubs.set(s, []);
1902
+ return;
1903
+ }
1904
+ const c = [];
1905
+ for (const [u, l] of f) {
1906
+ const y = this.reducerBus.on(u, l, (h, m) => {
1907
+ const g = m ?? {
1908
+ channel: u,
1909
+ type: l,
1910
+ payload: h,
1911
+ id: this.idFactory()
1912
+ };
1913
+ this.forwardEventGuarded(e, g);
1914
+ });
1915
+ c.push(y);
1916
+ }
1917
+ this.sliceUnsubs.set(s, c);
1918
+ }
1919
+ /**
1920
+ * Unmounts a slice: disposes reducer-bus listeners, removes reducer,
1921
+ * and optionally deletes the slice state.
1922
+ *
1923
+ * @param name - Slice name.
1924
+ * @param opts - `{ deleteState: boolean }`.
1925
+ *
1926
+ * @internal
1927
+ */
1928
+ unmountSlice(e, t) {
1929
+ const n = e;
1930
+ this.patternReducers.delete(e);
1931
+ const s = this.sliceUnsubs.get(n);
1932
+ if (s) {
1933
+ for (const i of s)
1934
+ try {
1935
+ i();
1936
+ } catch (d) {
1937
+ console.error(`[Store error]: ${d}`);
1938
+ }
1939
+ this.sliceUnsubs.delete(n);
1940
+ }
1941
+ if (delete this.reducers[e], t.deleteState) {
1942
+ const { [n]: i, ...d } = this.state;
1943
+ this.state = d;
1944
+ }
1945
+ }
1946
+ /**
1947
+ * Normalizes event targeting from `when` to an array of EventKeys.
1948
+ *
1949
+ * @param spec - Object with an optional `when` matcher.
1950
+ * @returns Array of `[channel, type]` pairs.
1951
+ *
1952
+ * @internal
1953
+ */
1954
+ normalizeEventKeys(e) {
1955
+ if (e.when) {
1956
+ const t = e.when;
1957
+ if ("keys" in t)
1958
+ return t.keys;
1959
+ }
1960
+ return [];
1961
+ }
1962
+ /**
1963
+ * Reads a dotted path from an object (supports numeric array indices via string keys).
1964
+ *
1965
+ * @param obj - Root object (slice or value).
1966
+ * @param path - Dotted path; leading dot is ignored.
1967
+ * @returns The value at the path, or `undefined`.
1968
+ *
1969
+ * @internal
1970
+ */
1971
+ getAtPath(e, t) {
1972
+ if (!t) return e;
1973
+ const s = (t[0] === "." ? t.slice(1) : t).split(".");
1974
+ let i = e;
1975
+ for (const d of s) {
1976
+ if (i == null) return;
1977
+ i = i[d];
1978
+ }
1979
+ return i;
1980
+ }
1981
+ /**
1982
+ * Builds ancestor paths for a dotted path.
1983
+ *
1984
+ * For `"a.b.c"`, returns `["a", "a.b", "a.b.c"]`. Leading dots are trimmed.
1985
+ *
1986
+ * @param path - Dotted path string.
1987
+ * @returns Array of ancestor paths.
1988
+ *
1989
+ * @example
1990
+ * ```ts
1991
+ * Store.buildAncestorPaths('x.y.z'); // ['x','x.y','x.y.z']
1992
+ * ```
1993
+ *
1994
+ * @public
1995
+ */
1996
+ static buildAncestorPaths(e) {
1997
+ if (!e) return [];
1998
+ const n = (e[0] === "." ? e.slice(1) : e).split("."), s = [];
1999
+ for (let i = 0; i < n.length; i++)
2000
+ s.push(n.slice(0, i + 1).join("."));
2001
+ return s;
2002
+ }
2003
+ }
2004
+ function L(o) {
2005
+ return new $({
2006
+ name: o.name,
2007
+ reducer: o.reducer ?? {},
2008
+ middleware: o.middleware ?? [],
2009
+ effects: o.effects ?? [],
2010
+ dedupWindowMs: o.dedupWindowMs,
2011
+ idFactory: o.idFactory,
2012
+ devtools: o.devtools,
2013
+ onEffectError: o.onEffectError,
2014
+ onReducerError: o.onReducerError
2015
+ });
2016
+ }
2017
+ const G = (o) => (e, t) => t.map((n) => [e, n]), Q = () => (o) => o, I = /* @__PURE__ */ new Set();
2018
+ function j(o) {
2019
+ const e = String(o);
2020
+ I.has(e) || (I.add(e), console.warn(
2021
+ `[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.`
2022
+ ));
2023
+ }
2024
+ function F(o, e) {
2025
+ if (o.length !== e.length) return e;
2026
+ for (let t = 0; t < o.length; t++)
2027
+ if (o[t] !== e[t]) return e;
2028
+ return o;
2029
+ }
2030
+ function J(o = {}) {
2031
+ const e = o.selectId ?? ((r) => r.id), { sortComparer: t } = o, n = (r, f) => {
2032
+ if (t === void 0) return f;
2033
+ const c = [...f].sort((u, l) => {
2034
+ const y = r.entities[u], h = r.entities[l];
2035
+ return y === void 0 || h === void 0 ? 0 : t(y, h);
2036
+ });
2037
+ return F(f, c);
2038
+ }, s = (r, f, c) => {
2039
+ const u = { ...r, entities: f, ids: c };
2040
+ return { ...u, ids: n(u, c) };
2041
+ }, i = (r, f, c) => {
2042
+ let u = null, l = null;
2043
+ for (const y of f) {
2044
+ const h = e(y);
2045
+ process.env.NODE_ENV !== "production" && String(h).includes(".") && j(h);
2046
+ const m = (u ?? r.entities)[h];
2047
+ if (m !== void 0 && c === "add") continue;
2048
+ const g = m !== void 0 && c === "upsert" ? { ...m, ...y } : y;
2049
+ u ?? (u = { ...r.entities }), u[h] = g, m === void 0 && (l ?? (l = [...r.ids]), l.push(h));
2050
+ }
2051
+ return u === null ? r : s(r, u, l ?? r.ids);
2052
+ }, d = (r, f) => {
2053
+ let c = null;
2054
+ for (const { id: u, changes: l } of f) {
2055
+ const y = (c ?? r.entities)[u];
2056
+ y !== void 0 && (c ?? (c = { ...r.entities }), c[u] = { ...y, ...l });
2057
+ }
2058
+ return c === null ? r : s(r, c, r.ids);
2059
+ }, a = (r, f) => {
2060
+ const c = new Set(f.filter((l) => r.entities[l] !== void 0));
2061
+ if (c.size === 0) return r;
2062
+ const u = { ...r.entities };
2063
+ for (const l of c) delete u[l];
2064
+ return s(
2065
+ r,
2066
+ u,
2067
+ r.ids.filter((l) => !c.has(l))
2068
+ );
2069
+ };
2070
+ return {
2071
+ getInitialState(r) {
2072
+ const f = { ids: [], entities: {} };
2073
+ return r === void 0 ? f : { ...f, ...r };
2074
+ },
2075
+ addOne: (r, f) => i(r, [f], "add"),
2076
+ addMany: (r, f) => i(r, f, "add"),
2077
+ setOne: (r, f) => i(r, [f], "set"),
2078
+ setMany: (r, f) => i(r, f, "set"),
2079
+ setAll: (r, f) => {
2080
+ const c = {}, u = [];
2081
+ for (const l of f) {
2082
+ const y = e(l);
2083
+ c[y] === void 0 && u.push(y), c[y] = l;
2084
+ }
2085
+ return s(r, c, u);
2086
+ },
2087
+ updateOne: (r, f) => d(r, [f]),
2088
+ updateMany: (r, f) => d(r, f),
2089
+ upsertOne: (r, f) => i(r, [f], "upsert"),
2090
+ upsertMany: (r, f) => i(r, f, "upsert"),
2091
+ removeOne: (r, f) => a(r, [f]),
2092
+ removeMany: (r, f) => a(r, f),
2093
+ removeAll: (r) => r.ids.length === 0 ? r : s(r, {}, []),
2094
+ selectIds: (r) => r.ids,
2095
+ selectEntities: (r) => r.entities,
2096
+ selectAll: (r) => r.ids.map((f) => r.entities[f]),
2097
+ selectById: (r, f) => r.entities[f],
2098
+ selectTotal: (r) => r.ids.length,
2099
+ idsPath: "ids",
2100
+ pathTo: (r, f) => f === void 0 ? `entities.${r}` : `entities.${r}.${f}`,
2101
+ anyField: (r) => `entities.*.${r}`
2102
+ };
2103
+ }
2104
+ const E = "$yoltra";
2105
+ function _(o, e = {}) {
2106
+ const t = e.maxNodes ?? 1e5, n = e.sanitize, s = [], i = /* @__PURE__ */ new Map();
2107
+ let d = 0, a = !1;
2108
+ function r(c, u) {
2109
+ if (n !== void 0 && (c = n(u, c)), d += 1, d > t)
2110
+ return a = !0, { [E]: "unsupported", kind: "truncated" };
2111
+ switch (typeof c) {
2112
+ case "undefined":
2113
+ return { [E]: "undefined" };
2114
+ case "bigint":
2115
+ return { [E]: "bigint", value: c.toString() };
2116
+ case "number":
2117
+ return Number.isNaN(c) ? { [E]: "nan" } : c === 1 / 0 ? { [E]: "infinity", sign: 1 } : c === -1 / 0 ? { [E]: "infinity", sign: -1 } : c;
2118
+ case "function":
2119
+ case "symbol":
2120
+ return s.push(u), { [E]: "unsupported", kind: typeof c };
2121
+ case "string":
2122
+ case "boolean":
2123
+ return c;
2124
+ }
2125
+ if (c === null) return null;
2126
+ const l = c, y = i.get(l);
2127
+ if (y !== void 0) return { [E]: "ref", path: y };
2128
+ if (i.set(l, u), c instanceof Date)
2129
+ return { [E]: "date", iso: c.toISOString() };
2130
+ if (c instanceof RegExp)
2131
+ return { [E]: "regexp", source: c.source, flags: c.flags };
2132
+ if (c instanceof Error)
2133
+ return { [E]: "error", name: c.name, message: c.message };
2134
+ if (c instanceof Map) {
2135
+ const m = [];
2136
+ let g = 0;
2137
+ for (const [w, D] of c)
2138
+ m.push([r(w, `${u}/@k${g}`), r(D, `${u}/${g}`)]), g += 1;
2139
+ return { [E]: "map", entries: m };
2140
+ }
2141
+ if (c instanceof Set) {
2142
+ const m = [];
2143
+ let g = 0;
2144
+ for (const w of c)
2145
+ m.push(r(w, `${u}/${g}`)), g += 1;
2146
+ return { [E]: "set", values: m };
2147
+ }
2148
+ if (Array.isArray(c))
2149
+ return c.map((m, g) => r(m, `${u}/${g}`));
2150
+ const h = {};
2151
+ for (const [m, g] of Object.entries(c))
2152
+ h[m] = r(g, `${u}/${A(m)}`);
2153
+ return E in h ? { [E]: "escaped", value: h } : h;
2154
+ }
2155
+ return { value: r(o, ""), report: { truncated: a, unsupported: s } };
2156
+ }
2157
+ function V(o) {
2158
+ const e = /* @__PURE__ */ new Map(), t = [];
2159
+ function n(d, a) {
2160
+ if (d === null || typeof d != "object") return d;
2161
+ if (Array.isArray(d)) {
2162
+ const f = [];
2163
+ return e.set(a, f), d.forEach((c, u) => {
2164
+ if (R(c)) {
2165
+ t.push({ target: f, key: u, path: c.path }), f[u] = void 0;
2166
+ return;
2167
+ }
2168
+ f[u] = n(c, `${a}/${u}`);
2169
+ }), f;
2170
+ }
2171
+ if (typeof d[E] == "string") {
2172
+ const f = d;
2173
+ switch (f[E]) {
2174
+ case "undefined":
2175
+ return;
2176
+ case "nan":
2177
+ return Number.NaN;
2178
+ case "infinity":
2179
+ return f.sign === 1 ? 1 / 0 : -1 / 0;
2180
+ case "bigint":
2181
+ return BigInt(f.value);
2182
+ case "date":
2183
+ return new Date(f.iso);
2184
+ case "regexp":
2185
+ return new RegExp(f.source, f.flags);
2186
+ case "error": {
2187
+ const c = new Error(f.message);
2188
+ return c.name = f.name, c;
2189
+ }
2190
+ case "unsupported":
2191
+ return;
2192
+ case "ref":
2193
+ return;
2194
+ case "map": {
2195
+ const c = /* @__PURE__ */ new Map();
2196
+ return e.set(a, c), f.entries.forEach(([u, l], y) => {
2197
+ c.set(n(u, `${a}/@k${y}`), n(l, `${a}/${y}`));
2198
+ }), c;
2199
+ }
2200
+ case "set": {
2201
+ const c = /* @__PURE__ */ new Set();
2202
+ return e.set(a, c), f.values.forEach((u, l) => c.add(n(u, `${a}/${l}`))), c;
2203
+ }
2204
+ case "escaped":
2205
+ return s(f.value, a);
2206
+ default:
2207
+ return;
2208
+ }
2209
+ }
2210
+ return s(d, a);
2211
+ }
2212
+ function s(d, a) {
2213
+ const r = {};
2214
+ e.set(a, r);
2215
+ for (const [f, c] of Object.entries(d)) {
2216
+ const u = `${a}/${A(f)}`;
2217
+ if (R(c)) {
2218
+ t.push({ target: r, key: f, path: c.path }), r[f] = void 0;
2219
+ continue;
2220
+ }
2221
+ r[f] = n(c, u);
2222
+ }
2223
+ return r;
2224
+ }
2225
+ const i = n(o, "");
2226
+ e.set("", i);
2227
+ for (const { target: d, key: a, path: r } of t)
2228
+ d[a] = e.get(r);
2229
+ return i;
2230
+ }
2231
+ function R(o) {
2232
+ return o !== null && typeof o == "object" && o[E] === "ref" && typeof o.path == "string";
2233
+ }
2234
+ function A(o) {
2235
+ return o.replace(/~/g, "~0").replace(/\//g, "~1");
2236
+ }
2237
+ function Y(o, e, t = {}) {
2238
+ let n = t.maxNodes ?? 1e5;
2239
+ for (let s = 0; s < 8; s += 1) {
2240
+ const { value: i, report: d } = _(o, { ...t, maxNodes: n });
2241
+ let a;
2242
+ try {
2243
+ a = JSON.stringify(i)?.length ?? 0;
2244
+ } catch {
2245
+ a = Number.POSITIVE_INFINITY;
2246
+ }
2247
+ if (a <= e)
2248
+ return d.truncated ? {
2249
+ value: i,
2250
+ truncated: !0,
2251
+ note: `State was too large to send in full; parts beyond ${n} nodes are omitted.`
2252
+ } : { value: i, truncated: !1 };
2253
+ const r = Math.floor(n * e * 0.8 / a);
2254
+ if (n = Math.max(1, Math.min(r, n - 1)), n <= 1 && s > 0)
2255
+ break;
2256
+ }
2257
+ return {
2258
+ value: { [E]: "unsupported", kind: "truncated" },
2259
+ truncated: !0,
2260
+ note: `State exceeds the ${e}-byte transport limit and could not be reduced to fit.`
2261
+ };
2262
+ }
2263
+ function b(o, e, t) {
2264
+ o.onError?.(e, t);
2265
+ }
2266
+ async function q(o) {
2267
+ const e = { slices: {}, restored: !1 };
2268
+ let t;
2269
+ try {
2270
+ t = o.source ?? await o.adapter.read(o.key);
2271
+ } catch (s) {
2272
+ return b(o, s, "read"), e;
2273
+ }
2274
+ if (t == null || t === "") return e;
2275
+ let n;
2276
+ try {
2277
+ n = V(JSON.parse(t));
2278
+ } catch (s) {
2279
+ return b(o, s, "decode"), e;
2280
+ }
2281
+ if (n === null || typeof n != "object" || typeof n.version != "number")
2282
+ return b(o, new Error("persisted payload is not a recognisable envelope"), "decode"), e;
2283
+ if (n.version !== o.version) {
2284
+ if (o.migrate === void 0)
2285
+ return b(
2286
+ o,
2287
+ new Error(
2288
+ `persisted state is version ${n.version}, this build expects ${o.version}, and no migrate was supplied`
2289
+ ),
2290
+ "migrate"
2291
+ ), e;
2292
+ try {
2293
+ const s = o.migrate(n.slices, n.version);
2294
+ return s === null ? e : { slices: s, restored: !0 };
2295
+ } catch (s) {
2296
+ return b(o, s, "migrate"), e;
2297
+ }
2298
+ }
2299
+ return { slices: n.slices ?? {}, restored: !0 };
2300
+ }
2301
+ function X(o, e) {
2302
+ if (!e.restored) return o;
2303
+ const t = {};
2304
+ for (const [n, s] of Object.entries(o)) {
2305
+ const i = e.slices[n];
2306
+ t[n] = i === void 0 ? s : { ...s, state: i };
2307
+ }
2308
+ return t;
2309
+ }
2310
+ function N(o, e) {
2311
+ const t = o ?? {}, n = e.slices === void 0 ? t : Object.fromEntries(e.slices.filter((s) => s in t).map((s) => [s, t[s]]));
2312
+ return JSON.stringify(_({ version: e.version, slices: n }).value);
2313
+ }
2314
+ function Z(o, e) {
2315
+ const t = e.throttleMs ?? 250, n = e.slices;
2316
+ let s = null, i = !1;
2317
+ const d = () => {
2318
+ if (i) {
2319
+ i = !1;
2320
+ try {
2321
+ const f = e.adapter.write(e.key, N(o.getState(), e));
2322
+ f instanceof Promise && f.catch((c) => b(e, c, "write"));
2323
+ } catch (f) {
2324
+ b(e, f, "write");
2325
+ }
2326
+ }
2327
+ }, a = () => {
2328
+ if (i = !0, t <= 0) {
2329
+ d();
2330
+ return;
2331
+ }
2332
+ s === null && (s = setTimeout(() => {
2333
+ s = null, d();
2334
+ }, t), s.unref?.());
2335
+ }, r = o.instrument((f) => {
2336
+ if (n === void 0) {
2337
+ a();
2338
+ return;
2339
+ }
2340
+ (f.changedPaths ?? []).some(
2341
+ (u) => n.some((l) => u === l || u.startsWith(`${l}.`))
2342
+ ) && a();
2343
+ });
2344
+ return () => {
2345
+ r(), s !== null && (clearTimeout(s), s = null), d();
2346
+ };
2347
+ }
2348
+ function ee(o, e) {
2349
+ return N(o.getState(), e);
2350
+ }
2351
+ function te(o) {
2352
+ return {
2353
+ read: (e) => o.getItem(e),
2354
+ write: (e, t) => o.setItem(e, t),
2355
+ remove: (e) => o.removeItem(e)
2356
+ };
2357
+ }
2358
+ function se(o) {
2359
+ const e = new Map(Object.entries(o ?? {}));
2360
+ return {
2361
+ read: (t) => e.get(t) ?? null,
2362
+ write: (t, n) => {
2363
+ e.set(t, n);
2364
+ },
2365
+ remove: (t) => {
2366
+ e.delete(t);
2367
+ }
2368
+ };
2369
+ }
2370
+ export {
2371
+ B as EventBus,
2372
+ H as LooseEventBus,
2373
+ K as Reducer,
2374
+ $ as Store,
2375
+ J as createEntityAdapter,
2376
+ se as createMemoryAdapter,
2377
+ L as createStore,
2378
+ te as createWebStorageAdapter,
2379
+ V as decodeState,
2380
+ ee as dehydrate,
2381
+ O as detectChangedProps,
2382
+ _ as encodeState,
2383
+ Y as encodeStateBounded,
2384
+ Q as eventKeys,
2385
+ S as freezeState,
2386
+ q as hydrate,
2387
+ Z as persist,
2388
+ G as typedEvents,
2389
+ X as withHydration
2390
+ };
2391
+ //# sourceMappingURL=yoltra.mjs.map