@yoltra/core 0.3.0 → 0.4.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.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @yoltra/core v0.3.0
2
+ * @yoltra/core v0.4.0
3
3
  * (c) 2026 Manu Ramirez <@pixerael>
4
4
  * License: MIT
5
5
  * Homepage: https://yoltra.dev
@@ -7,16 +7,16 @@
7
7
  * This source code is licensed under the MIT license found in the
8
8
  * LICENSE file in the root directory of this source tree
9
9
  */
10
- var P = Object.defineProperty;
11
- var z = (a, e, t) => e in a ? P(a, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : a[e] = t;
12
- var d = (a, e, t) => z(a, typeof e != "symbol" ? e + "" : e, t);
13
- class O {
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
14
  constructor() {
15
15
  /**
16
16
  * Internal registry: `channel → type → Set<handler>`.
17
17
  * @internal
18
18
  */
19
- d(this, "handlers", /* @__PURE__ */ new Map());
19
+ p(this, "handlers", /* @__PURE__ */ new Map());
20
20
  }
21
21
  /**
22
22
  * Subscribes a handler to an exact `(channel, type)`.
@@ -25,7 +25,10 @@ class O {
25
25
  * @typeParam T - Type key within channel `C` (must be a string key of `EM[C]`).
26
26
  * @param channel - Channel name to subscribe to.
27
27
  * @param type - Event type within the channel.
28
- * @param handler - Function invoked with the payload type `EM[C][T]`.
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.
29
32
  * @returns An **unsubscribe** function that removes this handler.
30
33
  *
31
34
  * @example
@@ -38,13 +41,20 @@ class O {
38
41
  * off();
39
42
  * ```
40
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
+ *
41
51
  * @public
42
52
  */
43
- on(e, t, r) {
44
- let n = this.handlers.get(e);
45
- n || (n = /* @__PURE__ */ new Map(), this.handlers.set(e, n));
46
- let s = n.get(t);
47
- return s || (s = /* @__PURE__ */ new Set(), n.set(t, s)), s.add(r), () => this.off(e, t, r);
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);
48
58
  }
49
59
  /**
50
60
  * Removes a specific handler previously added with {@link EventBus.on | `on`}.
@@ -66,11 +76,11 @@ class O {
66
76
  *
67
77
  * @public
68
78
  */
69
- off(e, t, r) {
70
- const n = this.handlers.get(e);
71
- if (!n) return;
72
- const s = n.get(t);
73
- s && (s.delete(r), s.size === 0 && n.delete(t), n.size === 0 && this.handlers.delete(e));
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));
74
84
  }
75
85
  /**
76
86
  * Emits an event to all subscribers of the exact `(channel, type)`.
@@ -83,6 +93,9 @@ class O {
83
93
  * @param channel - Channel name to emit on.
84
94
  * @param type - Event type to emit.
85
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.
86
99
  *
87
100
  * @example
88
101
  * ```ts
@@ -91,16 +104,16 @@ class O {
91
104
  *
92
105
  * @public
93
106
  */
94
- emit(e, t, r) {
95
- const n = this.handlers.get(e);
96
- if (!n) return;
97
- const s = n.get(t);
98
- if (!(!s || s.size === 0))
99
- for (const c of [...s])
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])
100
113
  try {
101
- c(r);
102
- } catch (i) {
103
- console.error("EventBus handler error:", i);
114
+ a(n, s);
115
+ } catch (r) {
116
+ console.error("EventBus handler error:", r);
104
117
  }
105
118
  }
106
119
  /**
@@ -120,18 +133,36 @@ class O {
120
133
  this.handlers.clear();
121
134
  }
122
135
  }
123
- class k {
136
+ class H {
124
137
  constructor() {
125
138
  /**
126
139
  * Exact handlers: `channel → type → [handlers]`.
127
140
  * @internal
128
141
  */
129
- d(this, "handlers", /* @__PURE__ */ new Map());
142
+ p(this, "handlers", /* @__PURE__ */ new Map());
130
143
  /**
131
144
  * Pattern handlers with `*` and `**`: `channel → pattern(string) → [handlers]`.
132
145
  * @internal
133
146
  */
134
- d(this, "patternHandlers", /* @__PURE__ */ new Map());
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());
135
166
  }
136
167
  /**
137
168
  * Subscribes a handler to either an **exact** type or a **pattern**.
@@ -165,18 +196,18 @@ class k {
165
196
  *
166
197
  * @public
167
198
  */
168
- on(e, t, r) {
169
- const n = String(t);
170
- if (this.isPattern(n)) {
171
- const s = n;
199
+ on(e, t, n) {
200
+ const s = String(t);
201
+ if (this.isPattern(s)) {
202
+ const i = s;
172
203
  this.patternHandlers.has(e) || this.patternHandlers.set(e, /* @__PURE__ */ new Map());
173
- const c = this.patternHandlers.get(e);
174
- return c.has(s) || c.set(s, []), c.get(s).push(r), () => this.offPattern(e, s, r);
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);
175
206
  } else {
176
- const s = this.normalizeTypeKey(n);
207
+ const i = this.normalizeTypeKey(s);
177
208
  this.handlers.has(e) || this.handlers.set(e, /* @__PURE__ */ new Map());
178
- const c = this.handlers.get(e);
179
- return c.has(s) || c.set(s, []), c.get(s).push(r), () => this.offExactNormalized(e, s, r);
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);
180
211
  }
181
212
  }
182
213
  /**
@@ -197,9 +228,9 @@ class k {
197
228
  *
198
229
  * @public
199
230
  */
200
- off(e, t, r) {
201
- const n = this.normalizeTypeKey(String(t));
202
- this.offExactNormalized(e, n, r);
231
+ off(e, t, n) {
232
+ const s = this.normalizeTypeKey(String(t));
233
+ this.offExactNormalized(e, s, n);
203
234
  }
204
235
  /**
205
236
  * Internal exact unsubscription using an already **normalized** type key.
@@ -209,13 +240,13 @@ class k {
209
240
  * @param handler - Handler to remove.
210
241
  * @internal
211
242
  */
212
- offExactNormalized(e, t, r) {
213
- const n = this.handlers.get(e);
214
- if (!n) return;
215
- const s = n.get(t);
243
+ offExactNormalized(e, t, n) {
244
+ const s = this.handlers.get(e);
216
245
  if (!s) return;
217
- const c = s.indexOf(r);
218
- c !== -1 && s.splice(c, 1), s.length === 0 && n.delete(t), n.size === 0 && this.handlers.delete(e);
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);
219
250
  }
220
251
  /**
221
252
  * Internal removal for a **pattern** subscription. No-ops if missing.
@@ -225,13 +256,13 @@ class k {
225
256
  * @param handler - Handler to remove.
226
257
  * @internal
227
258
  */
228
- offPattern(e, t, r) {
229
- const n = this.patternHandlers.get(e);
230
- if (!n) return;
231
- const s = n.get(t);
259
+ offPattern(e, t, n) {
260
+ const s = this.patternHandlers.get(e);
232
261
  if (!s) return;
233
- const c = s.indexOf(r);
234
- c !== -1 && s.splice(c, 1), s.length === 0 && n.delete(t), n.size === 0 && this.patternHandlers.delete(e);
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));
235
266
  }
236
267
  /**
237
268
  * Emits an event to all exact subscribers first, then to **matching pattern** subscribers.
@@ -253,27 +284,55 @@ class k {
253
284
  *
254
285
  * @public
255
286
  */
256
- emit(e, t, r) {
257
- const n = String(t), s = this.normalizeTypeKey(n), c = this.handlers.get(e)?.get(s) ?? [], i = this.patternHandlers.get(e), o = [];
258
- if (i && i.size) {
259
- const l = this.normalizeTypeKey(n);
260
- for (const [u, p] of i.entries())
261
- this.matchPattern(u, l) && o.push(p);
262
- }
263
- const f = /* @__PURE__ */ new Set(), h = (l) => {
264
- for (const u of [...l])
265
- if (!f.has(u)) {
266
- f.add(u);
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);
267
326
  try {
268
- u(r);
269
- } catch (p) {
270
- console.error(p);
327
+ l(r);
328
+ } catch (y) {
329
+ console.error(y);
271
330
  continue;
272
331
  }
273
332
  }
274
333
  };
275
- h(c);
276
- for (const l of o) h(l);
334
+ c(d);
335
+ for (const u of a) c(u);
277
336
  }
278
337
  /**
279
338
  * Determines if a string is a **pattern** (contains `*`).
@@ -308,42 +367,100 @@ class k {
308
367
  return this.normalizeTypeKey(e).split(".").filter(Boolean);
309
368
  }
310
369
  /**
311
- * Pattern matcher over dot-separated segments.
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.
312
425
  *
313
426
  * Rules:
314
427
  * - **literal**: exact match.
315
428
  * - `*` : matches exactly **one** segment.
316
429
  * - `**` : matches **zero or more** remaining segments (including empty).
317
430
  *
318
- * @param pattern - Pattern (may include `*`/`**`).
319
- * @param path - Subject event type key to test.
320
- * @returns `true` if the pattern matches the path; otherwise `false`.
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`.
321
440
  *
322
441
  * @example
323
442
  * ```ts
324
- * matchPattern('a.*', 'a.b') // true
325
- * matchPattern('a.*', 'a.b.c') // false
326
- * matchPattern('a.**', 'a') // true
327
- * matchPattern('a.**', 'a.b.c') // true
328
- * matchPattern('**.end', 'x.y.end') // true
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
329
447
  * ```
330
448
  *
331
449
  * @internal
332
450
  */
333
- matchPattern(e, t) {
334
- const r = this.splitPath(e), n = this.splitPath(t);
335
- let s = 0, c = 0, i = -1, o = 0;
336
- for (; c < n.length; )
337
- if (s < r.length && (r[s] === "*" || r[s] === n[c]))
338
- s++, c++;
339
- else if (s < r.length && r[s] === "**")
340
- i = s, o = c, s++;
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++;
341
458
  else if (i !== -1)
342
- s = i + 1, c = ++o;
459
+ n = i + 1, s = ++d;
343
460
  else
344
461
  return !1;
345
- for (; s < r.length && r[s] === "**"; ) s++;
346
- return s === r.length;
462
+ for (; n < e.length && e[n] === "**"; ) n++;
463
+ return n === e.length;
347
464
  }
348
465
  /**
349
466
  * Removes **all** listeners (exact and pattern). Useful for tests/HMR teardown.
@@ -356,7 +473,7 @@ class k {
356
473
  * @public
357
474
  */
358
475
  clear() {
359
- this.handlers.clear(), this.patternHandlers.clear();
476
+ this.handlers.clear(), this.patternHandlers.clear(), this.patternIndex.clear();
360
477
  }
361
478
  /**
362
479
  * Returns a snapshot of all registered subscriptions for DevTools introspection.
@@ -368,16 +485,16 @@ class k {
368
485
  */
369
486
  __introspect() {
370
487
  const e = [];
371
- for (const [t, r] of this.handlers)
372
- for (const [n, s] of r)
373
- s.length > 0 && e.push({ channel: t, type: n, count: s.length });
374
- for (const [t, r] of this.patternHandlers)
375
- for (const [n, s] of r)
376
- s.length > 0 && e.push({ channel: t, type: n, count: s.length });
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 });
377
494
  return e;
378
495
  }
379
496
  }
380
- class _ {
497
+ class K {
381
498
  /**
382
499
  * Creates a new {@link Reducer} from a pure reducer function.
383
500
  *
@@ -398,7 +515,7 @@ class _ {
398
515
  * The underlying pure reducer function.
399
516
  * @internal
400
517
  */
401
- d(this, "_reduce");
518
+ p(this, "_reduce");
402
519
  this._reduce = e;
403
520
  }
404
521
  /**
@@ -419,68 +536,128 @@ class _ {
419
536
  return this._reduce(e, t);
420
537
  }
421
538
  }
422
- function g(a, e, t = "", r = /* @__PURE__ */ new Map()) {
423
- if (a === e) return [];
424
- if (typeof a != "object" || typeof e != "object" || a === null || e === null)
425
- return typeof a == "number" && Number.isNaN(a) && Number.isNaN(e) ? [] : [t];
426
- if (a instanceof Date && e instanceof Date)
427
- return a.getTime() === e.getTime() ? [] : [t];
428
- if (a instanceof RegExp && e instanceof RegExp)
429
- return a.source === e.source && e.flags === a.flags ? [] : [t];
430
- const n = a, s = e, c = r.get(n);
431
- if (c?.has(s)) return [];
432
- const i = c ?? /* @__PURE__ */ new Set();
433
- i.add(s), c || r.set(n, i);
539
+ const P = /* @__PURE__ */ new Set();
540
+ function M(o, e) {
541
+ const t = o ? `${o}.${e}` : e;
542
+ P.has(t) || (P.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);
434
578
  try {
435
- const o = Array.isArray(a), f = Array.isArray(e);
436
- if (o !== f) return [t];
437
- const h = [];
438
- if (o) {
439
- const u = a, p = e;
440
- if (u.length !== p.length) return [t];
441
- for (let y = 0; y < u.length; y++) {
442
- const m = t ? `${t}.${y}` : `${y}`;
443
- h.push(...g(u[y], p[y], m, r));
444
- }
445
- return h.filter(Boolean);
579
+ const f = Array.isArray(o), c = Array.isArray(e);
580
+ if (f !== c) {
581
+ s.push(t);
582
+ return;
446
583
  }
447
- const l = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(e)]);
448
- for (const u of l) {
449
- const p = t ? `${t}.${u}` : u, y = Object.prototype.hasOwnProperty.call(a, u), m = Object.prototype.hasOwnProperty.call(e, u);
450
- if (!y || !m) {
451
- h.push(p);
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);
452
619
  continue;
453
620
  }
454
- h.push(...g(a[u], e[u], p, r));
621
+ v(o[h], e[h], g, n, s);
455
622
  }
456
- return h.filter(Boolean);
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));
457
625
  } finally {
458
- i.delete(s), i.size === 0 && r.delete(n);
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);
459
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);
460
646
  }
461
- function E(a, e = /* @__PURE__ */ new WeakSet()) {
462
- if (a === null || typeof a != "object" || e.has(a) || Object.isFrozen(a)) return a;
463
- if (e.add(a), Array.isArray(a)) {
464
- const t = a;
465
- for (let r = 0; r < t.length; r++)
466
- t[r] = E(t[r], e);
467
- return Object.freeze(t);
468
- }
469
- for (const t of Object.getOwnPropertyNames(a)) {
470
- const r = Object.getOwnPropertyDescriptor(a, t);
471
- !r || !("value" in r) || (a[t] = E(a[t], e));
472
- }
473
- for (const t of Object.getOwnPropertySymbols(a)) {
474
- const r = Object.getOwnPropertyDescriptor(a, t);
475
- !r || !("value" in r) || (a[t] = E(a[t], e));
476
- }
477
- return Object.freeze(a);
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
+ }
478
655
  }
479
- function b(a) {
480
- return process.env.NODE_ENV === "production" ? a : E(a);
656
+ function k(o, e) {
657
+ return process.env.NODE_ENV === "production" ? o : S(o, /* @__PURE__ */ new WeakSet(), e);
481
658
  }
482
- const w = 100, S = () => typeof performance < "u" && typeof performance.now == "function" ? performance.now() : Date.now();
483
- class v {
659
+ const x = 100, z = () => typeof performance < "u" && typeof performance.now == "function" ? performance.now() : Date.now();
660
+ class $ {
484
661
  /**
485
662
  * Creates a store from a {@link StoreSpec}.
486
663
  *
@@ -494,7 +671,7 @@ class v {
494
671
  *
495
672
  * @public
496
673
  */
497
- d(this, "name");
674
+ p(this, "name");
498
675
  /**
499
676
  * Registered middleware pipeline (run **before** reducers).
500
677
  * Stores either raw functions (legacy) or MiddlewareSpec objects.
@@ -502,45 +679,45 @@ class v {
502
679
  *
503
680
  * @internal
504
681
  */
505
- d(this, "middleware");
682
+ p(this, "middleware");
506
683
  /**
507
684
  * Installed slice reducers keyed by slice name.
508
685
  *
509
686
  * @internal
510
687
  */
511
- d(this, "reducers");
688
+ p(this, "reducers");
512
689
  /**
513
690
  * Current immutable snapshot of the store state.
514
691
  * This reference changes whenever any slice changes (shallow immutability).
515
692
  *
516
693
  * @internal
517
694
  */
518
- d(this, "state");
695
+ p(this, "state");
519
696
  /**
520
697
  * Bus for reducer wiring (emit by `(channel, type)`).
521
698
  *
522
699
  * @internal
523
700
  */
524
- d(this, "reducerBus");
701
+ p(this, "reducerBus");
525
702
  /**
526
703
  * Bus for **granular** connector events (emit by **dotted path** inside a slice).
527
704
  *
528
705
  * @internal
529
706
  */
530
- d(this, "connectorBus");
707
+ p(this, "connectorBus");
531
708
  /**
532
709
  * Coarse-grained listeners (called once per committed event, only if state changed).
533
710
  *
534
711
  * @internal
535
712
  */
536
- d(this, "listeners", /* @__PURE__ */ new Set());
713
+ p(this, "listeners", /* @__PURE__ */ new Set());
537
714
  /**
538
715
  * Registered effect handlers keyed by `"channel::type"` for O(1) lookup.
539
- * Used for effects with explicit `keys` or legacy `events` targeting.
716
+ * Used for effects with explicit `keys` targeting.
540
717
  *
541
718
  * @internal
542
719
  */
543
- d(this, "effects", /* @__PURE__ */ new Map());
720
+ p(this, "effects", /* @__PURE__ */ new Map());
544
721
  /**
545
722
  * Pattern-based effects that need runtime matching.
546
723
  * Used for effects with `when: { any }`, `{ channel }`, or `{ channels }`.
@@ -548,34 +725,34 @@ class v {
548
725
  *
549
726
  * @internal
550
727
  */
551
- d(this, "patternEffects", /* @__PURE__ */ new Set());
728
+ p(this, "patternEffects", /* @__PURE__ */ new Set());
552
729
  /**
553
730
  * Committed event subscribers keyed by `"channel::type"` for O(1) lookup.
554
731
  * Notified after reducers, before effects, for events that passed middleware.
555
732
  *
556
733
  * @internal
557
734
  */
558
- d(this, "committedEventSubscribers", /* @__PURE__ */ new Map());
735
+ p(this, "committedEventSubscribers", /* @__PURE__ */ new Map());
559
736
  /**
560
737
  * Uncommitted event subscribers keyed by `"channel::type"` for O(1) lookup.
561
738
  * Notified when middleware rejects an event.
562
739
  *
563
740
  * @internal
564
741
  */
565
- d(this, "uncommittedEventSubscribers", /* @__PURE__ */ new Map());
742
+ p(this, "uncommittedEventSubscribers", /* @__PURE__ */ new Map());
566
743
  /**
567
744
  * All-events subscribers keyed by `"channel::type"` for O(1) lookup.
568
745
  * Notified for both committed and uncommitted events with phase parameter.
569
746
  *
570
747
  * @internal
571
748
  */
572
- d(this, "allEventSubscribers", /* @__PURE__ */ new Map());
749
+ p(this, "allEventSubscribers", /* @__PURE__ */ new Map());
573
750
  /**
574
751
  * Track reducerBus unsubs per slice for HMR/register/unregister.
575
752
  *
576
753
  * @internal
577
754
  */
578
- d(this, "sliceUnsubs", /* @__PURE__ */ new Map());
755
+ p(this, "sliceUnsubs", /* @__PURE__ */ new Map());
579
756
  /**
580
757
  * Pattern-based reducers that need runtime matching.
581
758
  * Used for reducers with `when: { any }`, `{ channel }`, or `{ channels }`.
@@ -583,39 +760,60 @@ class v {
583
760
  *
584
761
  * @internal
585
762
  */
586
- d(this, "patternReducers", /* @__PURE__ */ new Map());
763
+ p(this, "patternReducers", /* @__PURE__ */ new Map());
587
764
  /**
588
765
  * Whether `__replayEvents()` is allowed.
589
766
  * Set from `spec.devtools.allowReplay`.
590
767
  *
591
768
  * @internal
592
769
  */
593
- d(this, "replayEnabled");
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");
594
778
  /**
595
779
  * Optional hook invoked when an effect throws/rejects. See
596
780
  * {@link StoreSpec.onEffectError}. `await emit()` never rejects on effect
597
781
  * failure — this is how callers observe effect errors.
598
782
  */
599
- d(this, "onEffectError");
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());
600
798
  /**
601
799
  * Pending events awaiting the **synchronous** reduce phase (middleware +
602
800
  * reducers + subscribers + coarse listeners). Drained by {@link drainReduce}.
603
801
  *
604
802
  * @internal
605
803
  */
606
- d(this, "reduceQueue", []);
804
+ p(this, "reduceQueue", []);
607
805
  /**
608
806
  * Re-entrancy guard for the synchronous reduce phase.
609
807
  *
610
808
  * @internal
611
809
  */
612
- d(this, "isReducing", !1);
810
+ p(this, "isReducing", !1);
613
811
  /**
614
812
  * Registered instrumentation observers (DevTools seam). See {@link instrument}.
615
813
  *
616
814
  * @internal
617
815
  */
618
- d(this, "instrumentObservers", /* @__PURE__ */ new Set());
816
+ p(this, "instrumentObservers", /* @__PURE__ */ new Set());
619
817
  /**
620
818
  * Scratch array collecting slice-prefixed changed leaf paths during an
621
819
  * instrumented reduce. Set by {@link drainReduce} while observers are active;
@@ -623,14 +821,14 @@ class v {
623
821
  *
624
822
  * @internal
625
823
  */
626
- d(this, "changedPathSink", null);
824
+ p(this, "changedPathSink", null);
627
825
  /**
628
826
  * Count of effect tasks currently in flight; surfaced as queue depth by
629
827
  * {@link __devtoolsIntrospect}.
630
828
  *
631
829
  * @internal
632
830
  */
633
- d(this, "inFlightEffects", 0);
831
+ p(this, "inFlightEffects", 0);
634
832
  /**
635
833
  * Tracks processed events by fingerprint with timestamps for TTL-based deduplication.
636
834
  *
@@ -647,7 +845,7 @@ class v {
647
845
  *
648
846
  * @internal
649
847
  */
650
- d(this, "processedEvents", /* @__PURE__ */ new Map());
848
+ p(this, "processedEvents", /* @__PURE__ */ new Map());
651
849
  /**
652
850
  * Lifetime count of events suppressed by the deduplication cache.
653
851
  * Exposed via {@link __devtoolsIntrospect} so the DevTools agent can
@@ -655,7 +853,7 @@ class v {
655
853
  *
656
854
  * @internal
657
855
  */
658
- d(this, "dedupCount", 0);
856
+ p(this, "dedupCount", 0);
659
857
  /**
660
858
  * Store-owned metadata for registered effects, keyed by the effect function.
661
859
  * Kept **off** the caller's function object: mutating a user-owned function
@@ -664,23 +862,23 @@ class v {
664
862
  *
665
863
  * @internal
666
864
  */
667
- d(this, "effectMeta", /* @__PURE__ */ new WeakMap());
865
+ p(this, "effectMeta", /* @__PURE__ */ new WeakMap());
668
866
  /**
669
867
  * Configuration for event deduplication.
670
868
  * @internal
671
869
  */
672
- d(this, "dedupConfig");
870
+ p(this, "dedupConfig");
673
871
  /**
674
872
  * Timer for periodic cleanup of processed events.
675
873
  *
676
874
  * @internal
677
875
  */
678
- d(this, "eventCleanupTimer", null);
679
- if (this.name = e.name ?? "yoltra Store", this.reducerBus = new O(), this.connectorBus = new k(), this.middleware = [...e.middleware ?? []], this.reducers = {}, this.state = {}, this.replayEnabled = e.devtools?.allowReplay ?? !1, this.onEffectError = e.onEffectError, this.dedupConfig = {
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 = {
680
878
  windowMs: e.dedupWindowMs ?? 0,
681
879
  maxCacheSize: 1e3
682
- }, Object.entries(e.reducer).forEach(([t, r]) => {
683
- this.mountSlice(t, r, { preserveState: !1 });
880
+ }, Object.entries(e.reducer).forEach(([t, n]) => {
881
+ this.mountSlice(t, n, { preserveState: !1 });
684
882
  }), e.effects?.length)
685
883
  for (const t of e.effects)
686
884
  this.registerEffect(t);
@@ -713,17 +911,17 @@ class v {
713
911
  *
714
912
  * @internal
715
913
  */
716
- fingerprint(e, t, r) {
717
- const n = `${e}::${t}`;
914
+ fingerprint(e, t, n) {
915
+ const s = `${e}::${t}`;
718
916
  try {
719
- if (r == null)
720
- return `${n}::null`;
721
- if (typeof r != "object")
722
- return `${n}::${String(r)}`;
723
- const s = JSON.stringify(r);
724
- return `${n}::${s}`;
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}`;
725
923
  } catch {
726
- return `${n}::${Date.now()}::${Math.random()}`;
924
+ return `${s}::${Date.now()}::${Math.random()}`;
727
925
  }
728
926
  }
729
927
  /**
@@ -736,8 +934,8 @@ class v {
736
934
  * @internal
737
935
  */
738
936
  shouldDedupe(e, t) {
739
- const r = Date.now(), n = this.processedEvents.get(e);
740
- return n !== void 0 && r - n < t ? (this.dedupCount++, !0) : (this.processedEvents.set(e, r), this.ensureCleanupTimer(), this.processedEvents.size > this.dedupConfig.maxCacheSize && this.pruneProcessedEvents(r), !1);
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);
741
939
  }
742
940
  /**
743
941
  * Starts the periodic prune interval if it isn't already running. Called when
@@ -759,9 +957,9 @@ class v {
759
957
  * @internal
760
958
  */
761
959
  pruneProcessedEvents(e) {
762
- const t = Math.max(this.dedupConfig.windowMs, w), r = e - t * 2;
763
- for (const [n, s] of this.processedEvents)
764
- s < r && this.processedEvents.delete(n);
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);
765
963
  this.processedEvents.size === 0 && this.eventCleanupTimer !== null && (clearInterval(this.eventCleanupTimer), this.eventCleanupTimer = null);
766
964
  }
767
965
  /**
@@ -782,7 +980,7 @@ class v {
782
980
  */
783
981
  matchesWhen(e, t) {
784
982
  return !e || "any" in e && e.any === !0 ? !0 : "keys" in e ? e.keys.some(
785
- ([r, n]) => t.channel === r && t.type === n
983
+ ([n, s]) => t.channel === n && t.type === s
786
984
  ) : "channel" in e ? t.channel === e.channel : "channels" in e ? e.channels.includes(t.channel) : !1;
787
985
  }
788
986
  /**
@@ -818,20 +1016,20 @@ class v {
818
1016
  * @internal
819
1017
  */
820
1018
  async notifyEffects(e) {
821
- const t = `${String(e.channel)}::${String(e.type)}`, r = this.effects.get(t);
822
- if (r && r.size > 0)
823
- for (const n of [...r])
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])
824
1022
  try {
825
- await n(e, this.getState, this.emit);
826
- } catch (s) {
827
- console.error("Effect error:", s), this.onEffectError?.(s, e);
1023
+ await s(e, this.getState, this.emit);
1024
+ } catch (i) {
1025
+ console.error("Effect error:", i), this.onEffectError?.(i, e);
828
1026
  }
829
- for (const { effect: n, when: s } of this.patternEffects)
830
- if (this.matchesWhen(s, e))
1027
+ for (const { effect: s, when: i } of this.patternEffects)
1028
+ if (this.matchesWhen(i, e))
831
1029
  try {
832
- await n(e, this.getState, this.emit);
833
- } catch (c) {
834
- console.error("Effect error:", c), this.onEffectError?.(c, e);
1030
+ await s(e, this.getState, this.emit);
1031
+ } catch (d) {
1032
+ console.error("Effect error:", d), this.onEffectError?.(d, e);
835
1033
  }
836
1034
  }
837
1035
  /**
@@ -845,12 +1043,12 @@ class v {
845
1043
  * @internal
846
1044
  */
847
1045
  notifyEventSubscribers(e, t) {
848
- const r = `${String(e.channel)}::${String(e.type)}`, s = (t === "committed" ? this.committedEventSubscribers : this.uncommittedEventSubscribers).get(r);
849
- if (s?.size)
850
- for (const i of [...s]) this.invokeEventSubscriber(i, e, t);
851
- const c = this.allEventSubscribers.get(r);
852
- if (c?.size)
853
- for (const i of [...c]) this.invokeEventSubscriber(i, 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);
854
1052
  }
855
1053
  /**
856
1054
  * Invokes a single event-subscription handler **fire-and-forget**: synchronous
@@ -859,12 +1057,12 @@ class v {
859
1057
  *
860
1058
  * @internal
861
1059
  */
862
- invokeEventSubscriber(e, t, r) {
1060
+ invokeEventSubscriber(e, t, n) {
863
1061
  try {
864
- const n = e(t, this.getState, this.emit, r);
865
- n && typeof n.then == "function" && n.catch((s) => console.error("Event subscription error:", s));
866
- } catch (n) {
867
- console.error("Event subscription error:", n);
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);
868
1066
  }
869
1067
  }
870
1068
  /**
@@ -883,22 +1081,62 @@ class v {
883
1081
  *
884
1082
  * @internal
885
1083
  */
886
- forwardEvent(e, t) {
887
- const r = this.state[e], n = this.reducers[e].reduce(r, t);
888
- if (r === n) return !1;
889
- const s = g(r, n).filter(Boolean);
890
- if (s.length === 0) return !1;
891
- const c = b(n);
892
- if (this.state = { ...this.state, [e]: c }, this.changedPathSink)
893
- for (const o of s)
894
- this.changedPathSink.push(o ? `${e}.${o}` : e);
895
- const i = /* @__PURE__ */ new Set();
896
- for (const o of s)
897
- for (const f of v.buildAncestorPaths(o)) i.add(f);
898
- for (const o of i) {
899
- const f = this.getAtPath(r, o), h = this.getAtPath(c, o);
900
- this.connectorBus.emit(e, o, { oldValue: f, newValue: h, path: o });
1084
+ /**
1085
+ * Reduces one slice and contains any error it raises.
1086
+ *
1087
+ * @returns `true` when the slice changed.
1088
+ *
1089
+ * @remarks
1090
+ * The single funnel both dispatch paths go through, which is the point. Keyed reducers run
1091
+ * through `reducerBus`, whose handler loop caught and logged; pattern reducers were called
1092
+ * straight from the drain, so their errors escaped to the caller instead. The same bug in the
1093
+ * same reducer therefore produced two different outcomes depending on how the slice happened
1094
+ * to be targeted — a keyed reducer's throw let the event commit and its effects run, while a
1095
+ * pattern reducer's throw aborted the commit and notified nobody, not even the uncommitted
1096
+ * subscribers a veto would have reached.
1097
+ *
1098
+ * The semantics are now the same either way: **the failing slice is isolated.** Its state is
1099
+ * unchanged, every other slice still reduces, and the event still commits if anything else
1100
+ * changed. Rolling the whole event back would be tidier in principle, but fine-grained
1101
+ * subscribers are notified inside `forwardEvent` as each slice commits, so an event that
1102
+ * reverted afterwards would have already told components about a value that no longer exists.
1103
+ * Isolation keeps every notification truthful.
1104
+ *
1105
+ * @internal
1106
+ */
1107
+ forwardEventGuarded(e, t) {
1108
+ try {
1109
+ return this.forwardEvent(e, t);
1110
+ } catch (n) {
1111
+ return console.error(`Reducer error in slice "${e}":`, n), this.onReducerError?.(n, t, e), !1;
901
1112
  }
1113
+ }
1114
+ forwardEvent(e, t) {
1115
+ const n = this.state[e], s = this.reducers[e].reduce(n, t);
1116
+ if (n === s) return !1;
1117
+ const i = O(n, s).filter(Boolean);
1118
+ if (i.length === 0) return !1;
1119
+ const d = t.payload, a = process.env.NODE_ENV !== "production" && d !== null && typeof d == "object" ? {
1120
+ watch: d,
1121
+ onFound: () => {
1122
+ const c = `${e}:${t.channel}:${t.type}`;
1123
+ this.warnedPayloadAliases.has(c) || (this.warnedPayloadAliases.add(c), console.warn(
1124
+ `[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.`
1125
+ ));
1126
+ }
1127
+ } : void 0, r = k(s, a);
1128
+ if (this.state = { ...this.state, [e]: r }, this.changedPathSink)
1129
+ for (const c of i)
1130
+ this.changedPathSink.push(c ? `${e}.${c}` : e);
1131
+ const f = /* @__PURE__ */ new Set();
1132
+ for (const c of i)
1133
+ for (const u of $.buildAncestorPaths(c)) f.add(u);
1134
+ for (const c of f)
1135
+ this.connectorBus.emitWith(e, c, () => ({
1136
+ oldValue: this.getAtPath(n, c),
1137
+ newValue: this.getAtPath(r, c),
1138
+ path: c
1139
+ }));
902
1140
  return !0;
903
1141
  }
904
1142
  /**
@@ -912,65 +1150,65 @@ class v {
912
1150
  * @public
913
1151
  */
914
1152
  __devtoolsIntrospect() {
915
- const e = Object.keys(this.reducers).map((i) => {
916
- const o = this.patternReducers.get(i);
917
- return { name: i, when: o };
1153
+ const e = Object.keys(this.reducers).map((a) => {
1154
+ const r = this.patternReducers.get(a);
1155
+ return { name: a, when: r };
918
1156
  }), t = [];
919
- for (const [i, o] of this.effects) {
920
- if (o.size === 0) continue;
921
- const [f, h] = i.split("::");
922
- for (const l of o) {
923
- const u = this.effectMeta.get(l);
924
- t.push({ channel: f, type: h, name: u?.name, description: u?.description });
1157
+ for (const [a, r] of this.effects) {
1158
+ if (r.size === 0) continue;
1159
+ const [f, c] = a.split("::");
1160
+ for (const u of r) {
1161
+ const l = this.effectMeta.get(u);
1162
+ t.push({ channel: f, type: c, name: l?.name, description: l?.description });
925
1163
  }
926
1164
  }
927
- for (const i of this.patternEffects) {
928
- const o = this.effectMeta.get(i.effect);
1165
+ for (const a of this.patternEffects) {
1166
+ const r = this.effectMeta.get(a.effect);
929
1167
  t.push({
930
1168
  channel: "*",
931
1169
  type: "*",
932
- name: o?.name,
933
- description: o?.description
1170
+ name: r?.name,
1171
+ description: r?.description
934
1172
  });
935
1173
  }
936
- const r = [];
937
- for (const i of this.middleware)
938
- typeof i == "function" ? r.push({ name: i.name || void 0 }) : r.push({
939
- name: i.meta?.name,
940
- description: i.meta?.description,
941
- when: i.when
942
- });
943
1174
  const n = [];
944
- for (const i of this.connectorBus.__introspect())
945
- for (let o = 0; o < i.count; o++)
946
- n.push({ reducer: i.channel, property: i.type });
1175
+ for (const a of this.middleware)
1176
+ typeof a == "function" ? n.push({ name: a.name || void 0 }) : n.push({
1177
+ name: a.meta?.name,
1178
+ description: a.meta?.description,
1179
+ when: a.when
1180
+ });
947
1181
  const s = [];
948
- for (const [i, o] of this.committedEventSubscribers) {
949
- if (o.size === 0) continue;
950
- const [f, h] = i.split("::");
951
- for (let l = 0; l < o.size; l++)
952
- s.push({ channel: f, type: h, phase: "committed" });
1182
+ for (const a of this.connectorBus.__introspect())
1183
+ for (let r = 0; r < a.count; r++)
1184
+ s.push({ reducer: a.channel, property: a.type });
1185
+ const i = [];
1186
+ for (const [a, r] of this.committedEventSubscribers) {
1187
+ if (r.size === 0) continue;
1188
+ const [f, c] = a.split("::");
1189
+ for (let u = 0; u < r.size; u++)
1190
+ i.push({ channel: f, type: c, phase: "committed" });
953
1191
  }
954
- for (const [i, o] of this.uncommittedEventSubscribers) {
955
- if (o.size === 0) continue;
956
- const [f, h] = i.split("::");
957
- for (let l = 0; l < o.size; l++)
958
- s.push({ channel: f, type: h, phase: "uncommitted" });
1192
+ for (const [a, r] of this.uncommittedEventSubscribers) {
1193
+ if (r.size === 0) continue;
1194
+ const [f, c] = a.split("::");
1195
+ for (let u = 0; u < r.size; u++)
1196
+ i.push({ channel: f, type: c, phase: "uncommitted" });
959
1197
  }
960
- for (const [i, o] of this.allEventSubscribers) {
961
- if (o.size === 0) continue;
962
- const [f, h] = i.split("::");
963
- for (let l = 0; l < o.size; l++)
964
- s.push({ channel: f, type: h, phase: "all" });
1198
+ for (const [a, r] of this.allEventSubscribers) {
1199
+ if (r.size === 0) continue;
1200
+ const [f, c] = a.split("::");
1201
+ for (let u = 0; u < r.size; u++)
1202
+ i.push({ channel: f, type: c, phase: "all" });
965
1203
  }
966
- const c = this.listeners.size;
1204
+ const d = this.listeners.size;
967
1205
  return {
968
1206
  reducers: e,
969
1207
  effects: t,
970
- middleware: r,
971
- atomic: n,
972
- event: s,
973
- coarse: c,
1208
+ middleware: n,
1209
+ atomic: s,
1210
+ event: i,
1211
+ coarse: d,
974
1212
  dedupHits: this.dedupCount,
975
1213
  queueDepth: this.reduceQueue.length + this.inFlightEffects
976
1214
  };
@@ -991,36 +1229,34 @@ class v {
991
1229
  * @internal
992
1230
  */
993
1231
  __applyExternalState(e) {
994
- if (!this.replayEnabled) {
995
- console.warn(
1232
+ if (!this.replayEnabled)
1233
+ throw new Error(
996
1234
  "[yoltra] External state apply (time-travel) is disabled. Enable it with createStore({ devtools: { allowReplay: true } })"
997
1235
  );
998
- return;
999
- }
1000
- const t = this.state, r = e, n = { ...this.state };
1001
- let s = !1;
1002
- Object.keys(this.reducers).forEach((c) => {
1003
- const i = t?.[c], o = r?.[c];
1004
- if (o === void 0) {
1236
+ const t = this.state, n = e, s = { ...this.state };
1237
+ let i = !1;
1238
+ Object.keys(this.reducers).forEach((d) => {
1239
+ const a = t?.[d], r = n?.[d];
1240
+ if (r === void 0) {
1005
1241
  process.env.NODE_ENV !== "production" && console.warn(
1006
1242
  `[yoltra] External state is missing slice "${String(
1007
- c
1243
+ d
1008
1244
  )}"; retaining its current value. Time-travel snapshots should contain all slices.`
1009
1245
  );
1010
1246
  return;
1011
1247
  }
1012
- if (i === o) return;
1013
- const f = b(o);
1014
- n[c] = f, s = !0;
1015
- const h = g(i, o).filter(Boolean);
1016
- if (h.length === 0) return;
1017
- const l = /* @__PURE__ */ new Set();
1018
- for (const u of h) for (const p of v.buildAncestorPaths(u)) l.add(p);
1019
- for (const u of l) {
1020
- const p = this.getAtPath(i, u), y = this.getAtPath(f, u);
1021
- this.connectorBus.emit(c, u, { oldValue: p, newValue: y, path: u });
1248
+ if (a === r) return;
1249
+ const f = k(r);
1250
+ s[d] = f, i = !0;
1251
+ const c = O(a, r).filter(Boolean);
1252
+ if (c.length === 0) return;
1253
+ const u = /* @__PURE__ */ new Set();
1254
+ for (const l of c) for (const y of $.buildAncestorPaths(l)) u.add(y);
1255
+ for (const l of u) {
1256
+ const y = this.getAtPath(a, l), h = this.getAtPath(f, l);
1257
+ this.connectorBus.emit(d, l, { oldValue: y, newValue: h, path: l });
1022
1258
  }
1023
- }), s && (this.state = n), s && this.listeners.forEach((c) => c());
1259
+ }), i && (this.state = s), i && this.listeners.forEach((d) => d());
1024
1260
  }
1025
1261
  /**
1026
1262
  * Replays a sequence of events from a snapshot through reducers and event
@@ -1040,13 +1276,13 @@ class v {
1040
1276
  "[yoltra] Event replay is disabled. Enable it with createStore({ devtools: { allowReplay: true } })"
1041
1277
  );
1042
1278
  this.__applyExternalState(e);
1043
- for (const r of t) {
1044
- const n = r, s = this.state;
1045
- this.reducerBus.emit(n.channel, n.type, n.payload);
1046
- for (const [o, f] of this.patternReducers)
1047
- this.matchesWhen(f, n) && this.forwardEvent(o, n);
1048
- const c = this.state, i = s !== c;
1049
- this.notifyEventSubscribers(n, "committed"), i && this.listeners.forEach((o) => o());
1279
+ for (const n of t) {
1280
+ const s = n, i = this.state;
1281
+ this.reducerBus.emit(s.channel, s.type, s.payload, s);
1282
+ for (const [r, f] of this.patternReducers)
1283
+ this.matchesWhen(f, s) && this.forwardEventGuarded(r, s);
1284
+ const d = this.state, a = i !== d;
1285
+ this.notifyEventSubscribers(s, "committed"), a && this.listeners.forEach((r) => r());
1050
1286
  }
1051
1287
  }
1052
1288
  /**
@@ -1092,24 +1328,25 @@ class v {
1092
1328
  *
1093
1329
  * @public
1094
1330
  */
1095
- async emit(e, t, r, n) {
1096
- const s = n?.dedupKey, c = this.dedupConfig.windowMs;
1097
- if (c > 0 || s !== void 0) {
1098
- const h = s !== void 0 && c <= 0 ? w : c, l = s !== void 0 ? `${e}::${t}::#${s}` : this.fingerprint(e, t, r);
1099
- if (this.shouldDedupe(l, h))
1331
+ async emit(e, t, n, s) {
1332
+ const i = s?.dedupKey, d = this.dedupConfig.windowMs;
1333
+ if (s?.skipDedup !== !0 && (d > 0 || i !== void 0)) {
1334
+ const c = i !== void 0 && d <= 0 ? x : d, u = i !== void 0 ? `${e}::${t}::#${i}` : this.fingerprint(e, t, n);
1335
+ if (this.shouldDedupe(u, c))
1100
1336
  return;
1101
1337
  }
1102
- const i = crypto.randomUUID();
1103
- let o;
1104
- const f = new Promise((h) => {
1105
- o = h;
1338
+ const a = s?.id ?? this.idFactory();
1339
+ let r;
1340
+ const f = new Promise((c) => {
1341
+ r = c;
1106
1342
  });
1107
1343
  return this.reduceQueue.push({
1108
1344
  channel: e,
1109
1345
  type: t,
1110
- payload: r,
1111
- id: i,
1112
- resolve: o
1346
+ payload: n,
1347
+ id: a,
1348
+ meta: s?.meta,
1349
+ resolve: r
1113
1350
  }), this.drainReduce(), f;
1114
1351
  }
1115
1352
  /**
@@ -1127,24 +1364,30 @@ class v {
1127
1364
  this.isReducing = !0;
1128
1365
  try {
1129
1366
  for (; this.reduceQueue.length > 0; ) {
1130
- const { channel: e, type: t, payload: r, id: n, resolve: s } = this.reduceQueue.shift(), c = { channel: e, type: t, payload: r, id: n }, i = this.instrumentObservers.size > 0, o = i ? this.state : void 0, f = [];
1131
- i && (this.changedPathSink = f);
1132
- const h = i ? S() : 0;
1367
+ const { channel: e, type: t, payload: n, id: s, meta: i, resolve: d } = this.reduceQueue.shift(), a = {
1368
+ channel: e,
1369
+ type: t,
1370
+ payload: n,
1371
+ id: s,
1372
+ ...i !== void 0 ? { meta: i } : {}
1373
+ }, r = this.instrumentObservers.size > 0, f = r ? this.state : void 0, c = r ? [] : void 0;
1374
+ c !== void 0 && (this.changedPathSink = c);
1375
+ const u = r ? z() : 0;
1133
1376
  let l = !1;
1134
1377
  try {
1135
- l = this.applyEventSync(c);
1136
- } catch (u) {
1137
- console.error("Emit reduce error:", u);
1378
+ l = this.applyEventSync(a);
1379
+ } catch (y) {
1380
+ console.error("Emit reduce error:", y);
1138
1381
  } finally {
1139
- i && (this.changedPathSink = null);
1382
+ r && (this.changedPathSink = null);
1140
1383
  }
1141
- i && this.emitInstrumentation(
1142
- c,
1384
+ r && this.emitInstrumentation(
1385
+ a,
1143
1386
  l,
1387
+ c ?? [],
1144
1388
  f,
1145
- o,
1146
- S() - h
1147
- ), this.runEventEffects(c, l, s);
1389
+ z() - u
1390
+ ), this.runEventEffects(a, l, d);
1148
1391
  }
1149
1392
  } finally {
1150
1393
  this.isReducing = !1;
@@ -1162,25 +1405,32 @@ class v {
1162
1405
  * @internal
1163
1406
  */
1164
1407
  applyEventSync(e) {
1165
- for (const n of this.middleware) {
1166
- const s = this.getMiddlewareWhen(n);
1167
- if (!this.matchesWhen(s, e)) continue;
1168
- const c = this.getMiddlewareFunction(n);
1169
- let i;
1408
+ for (const s of this.middleware) {
1409
+ const i = this.getMiddlewareWhen(s);
1410
+ if (!this.matchesWhen(i, e)) continue;
1411
+ const d = this.getMiddlewareFunction(s);
1412
+ let a;
1170
1413
  try {
1171
- i = c(this.state, e, this.emit);
1172
- } catch (o) {
1173
- console.error("Middleware error:", o), i = !1;
1414
+ a = d(this.state, e, this.emit), process.env.NODE_ENV !== "production" && typeof a?.then == "function" && console.error(
1415
+ `[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.`
1416
+ );
1417
+ } catch (r) {
1418
+ console.error("Middleware error:", r), a = !1;
1174
1419
  }
1175
- if (!i)
1420
+ if (!a)
1176
1421
  return this.notifyEventSubscribers(e, "uncommitted"), !1;
1177
1422
  }
1178
1423
  const t = this.state;
1179
- this.reducerBus.emit(e.channel, e.type, e.payload);
1180
- for (const [n, s] of this.patternReducers)
1181
- this.matchesWhen(s, e) && this.forwardEvent(n, e);
1182
- const r = t !== this.state;
1183
- return this.notifyEventSubscribers(e, "committed"), r && this.listeners.forEach((n) => n()), !0;
1424
+ this.reducerBus.emit(
1425
+ e.channel,
1426
+ e.type,
1427
+ e.payload,
1428
+ e
1429
+ );
1430
+ for (const [s, i] of this.patternReducers)
1431
+ this.matchesWhen(i, e) && this.forwardEventGuarded(s, e);
1432
+ const n = t !== this.state;
1433
+ return this.notifyEventSubscribers(e, "committed"), n && this.listeners.forEach((s) => s()), !0;
1184
1434
  }
1185
1435
  /**
1186
1436
  * Runs a single committed event's effects as an **independent async task**,
@@ -1190,14 +1440,14 @@ class v {
1190
1440
  *
1191
1441
  * @internal
1192
1442
  */
1193
- async runEventEffects(e, t, r) {
1443
+ async runEventEffects(e, t, n) {
1194
1444
  this.inFlightEffects++;
1195
1445
  try {
1196
1446
  t && await this.notifyEffects(e);
1197
- } catch (n) {
1198
- console.error("Effect error:", n);
1447
+ } catch (s) {
1448
+ console.error("Effect error:", s);
1199
1449
  } finally {
1200
- this.inFlightEffects--, r();
1450
+ this.inFlightEffects--, n();
1201
1451
  }
1202
1452
  }
1203
1453
  /**
@@ -1218,28 +1468,31 @@ class v {
1218
1468
  *
1219
1469
  * @internal
1220
1470
  */
1221
- emitInstrumentation(e, t, r, n, s) {
1222
- const c = {}, i = {};
1223
- for (const f of r)
1224
- c[f] = this.getAtPath(n, f), i[f] = this.getAtPath(this.state, f);
1225
- const o = {
1471
+ emitInstrumentation(e, t, n, s, i) {
1472
+ const d = {}, a = {};
1473
+ for (const f of n)
1474
+ d[f] = this.getAtPath(s, f), a[f] = this.getAtPath(this.state, f);
1475
+ const r = {
1226
1476
  event: {
1227
1477
  id: e.id,
1228
1478
  channel: e.channel,
1229
1479
  type: e.type,
1230
- payload: e.payload
1480
+ payload: e.payload,
1481
+ // Conditional, so an event without metadata produces an observer payload
1482
+ // byte-identical to the pre-`meta` shape.
1483
+ ...e.meta !== void 0 ? { meta: e.meta } : {}
1231
1484
  },
1232
1485
  committed: t,
1233
- changedPaths: r,
1234
- prevValues: c,
1235
- nextValues: i,
1236
- reduceTimeMs: s
1486
+ changedPaths: n,
1487
+ prevValues: d,
1488
+ nextValues: a,
1489
+ reduceTimeMs: i
1237
1490
  };
1238
1491
  for (const f of [...this.instrumentObservers])
1239
1492
  try {
1240
- f(o);
1241
- } catch (h) {
1242
- console.error("Instrumentation observer error:", h);
1493
+ f(r);
1494
+ } catch (c) {
1495
+ console.error("Instrumentation observer error:", c);
1243
1496
  }
1244
1497
  }
1245
1498
  /**
@@ -1319,11 +1572,11 @@ class v {
1319
1572
  *
1320
1573
  * @public
1321
1574
  */
1322
- onEvent(e, t, r, n = "committed") {
1323
- const s = `${e}::${String(t)}`, c = n === "committed" ? this.committedEventSubscribers : n === "uncommitted" ? this.uncommittedEventSubscribers : this.allEventSubscribers;
1324
- return c.has(s) || c.set(s, /* @__PURE__ */ new Set()), c.get(s).add(r), () => {
1325
- const i = c.get(s);
1326
- i && (i.delete(r), i.size === 0 && c.delete(s));
1575
+ onEvent(e, t, n, s = "committed") {
1576
+ const i = `${e}::${String(t)}`, d = s === "committed" ? this.committedEventSubscribers : s === "uncommitted" ? this.uncommittedEventSubscribers : this.allEventSubscribers;
1577
+ return d.has(i) || d.set(i, /* @__PURE__ */ new Set()), d.get(i).add(n), () => {
1578
+ const a = d.get(i);
1579
+ a && (a.delete(n), a.size === 0 && d.delete(i));
1327
1580
  };
1328
1581
  }
1329
1582
  /**
@@ -1365,13 +1618,21 @@ class v {
1365
1618
  /**
1366
1619
  * Registers a middleware (runs **before** reducers).
1367
1620
  *
1368
- * @param mw - Middleware `(state, event, emit) => boolean|Promise<boolean>`.
1369
- * Return `false` to cancel event propagation.
1621
+ * @param mw - Middleware `(state, event, emit) => boolean`. Return `false` to cancel event
1622
+ * propagation.
1370
1623
  * @returns Unsubscribe function that removes this middleware.
1371
1624
  *
1625
+ * @remarks
1626
+ * **Synchronous, and that is the contract.** The reduce phase completes before `emit()`
1627
+ * returns, so the commit decision has to be available in the same tick. An `async` middleware
1628
+ * returns a Promise, every Promise is truthy, and the veto would therefore never fire — the
1629
+ * event would commit while the middleware was still deciding. The type rejects it; this note
1630
+ * exists because the examples here used to teach it. Do authorization and validation here, and
1631
+ * anything that needs to await in an effect.
1632
+ *
1372
1633
  * @example Logging middleware
1373
1634
  * ```ts
1374
- * const off = store.registerMiddleware(async (state, event) => {
1635
+ * const off = store.registerMiddleware((state, event) => {
1375
1636
  * console.log('Event:', event.channel, event.type, event.payload);
1376
1637
  * return true; // allow
1377
1638
  * });
@@ -1398,7 +1659,7 @@ class v {
1398
1659
  * Dynamically **adds** a named slice reducer at runtime.
1399
1660
  *
1400
1661
  * @param name - New slice name (must not already exist).
1401
- * @param spec - Reducer spec (state, events, reducer).
1662
+ * @param spec - Reducer spec (state, when, reducer).
1402
1663
  * @returns Disposer function that **removes** the slice (and its state).
1403
1664
  *
1404
1665
  * @example
@@ -1420,8 +1681,8 @@ class v {
1420
1681
  if (e in this.reducers) throw new Error(`Reducer ${e} already exists`);
1421
1682
  return this.mountSlice(e, t, {
1422
1683
  preserveState: !1
1423
- }), this.listeners.forEach((r) => r()), () => {
1424
- this.unmountSlice(e, { deleteState: !0 }), this.listeners.forEach((r) => r());
1684
+ }), this.listeners.forEach((n) => n()), () => {
1685
+ this.unmountSlice(e, { deleteState: !0 }), this.listeners.forEach((n) => n());
1425
1686
  };
1426
1687
  }
1427
1688
  /**
@@ -1429,7 +1690,7 @@ class v {
1429
1690
  *
1430
1691
  * Effects are **keyed** by `(channel, type)` for O(1) lookup (no scanning all effects).
1431
1692
  *
1432
- * @param spec - Effect specification with `events` (EventKeys) and `effect` (handler).
1693
+ * @param spec - Effect specification with `when` targeting and `effect` (handler).
1433
1694
  * @returns Unsubscribe function.
1434
1695
  *
1435
1696
  * @example Logging effect
@@ -1457,29 +1718,29 @@ class v {
1457
1718
  * @public
1458
1719
  */
1459
1720
  registerEffect(e) {
1460
- const { effect: t, meta: r, when: n } = e, s = [];
1461
- if (r && this.effectMeta.set(t, r), n && ("any" in n && n.any === !0 || "channel" in n || "channels" in n)) {
1462
- const o = { effect: t, when: n };
1463
- return this.patternEffects.add(o), () => {
1464
- this.patternEffects.delete(o);
1721
+ const { effect: t, meta: n, when: s } = e, i = [];
1722
+ if (n && this.effectMeta.set(t, n), s && ("any" in s && s.any === !0 || "channel" in s || "channels" in s)) {
1723
+ const r = { effect: t, when: s };
1724
+ return this.patternEffects.add(r), () => {
1725
+ this.patternEffects.delete(r);
1465
1726
  };
1466
1727
  }
1467
- const i = this.normalizeEventKeys(e);
1468
- if (i.length === 0 && !n && !e.events) {
1469
- const o = { effect: t, when: { any: !0 } };
1470
- return this.patternEffects.add(o), () => {
1471
- this.patternEffects.delete(o);
1728
+ const a = this.normalizeEventKeys(e);
1729
+ if (a.length === 0 && !s) {
1730
+ const r = { effect: t, when: { any: !0 } };
1731
+ return this.patternEffects.add(r), () => {
1732
+ this.patternEffects.delete(r);
1472
1733
  };
1473
1734
  }
1474
- for (const [o, f] of i) {
1475
- const h = `${String(o)}::${String(f)}`;
1476
- this.effects.has(h) || this.effects.set(h, /* @__PURE__ */ new Set()), this.effects.get(h).add(t), s.push(() => {
1477
- const l = this.effects.get(h);
1478
- l && (l.delete(t), l.size === 0 && this.effects.delete(h));
1735
+ for (const [r, f] of a) {
1736
+ const c = `${String(r)}::${String(f)}`;
1737
+ this.effects.has(c) || this.effects.set(c, /* @__PURE__ */ new Set()), this.effects.get(c).add(t), i.push(() => {
1738
+ const u = this.effects.get(c);
1739
+ u && (u.delete(t), u.size === 0 && this.effects.delete(c));
1479
1740
  });
1480
1741
  }
1481
1742
  return () => {
1482
- for (const o of s) o();
1743
+ for (const r of i) r();
1483
1744
  };
1484
1745
  }
1485
1746
  /**
@@ -1503,15 +1764,15 @@ class v {
1503
1764
  *
1504
1765
  * @public
1505
1766
  */
1506
- onEffect(e, t, r) {
1507
- const n = async (s, c, i) => {
1508
- if (s.channel !== e || s.type !== t) return;
1509
- const o = s;
1510
- return r(o.payload, c, i, o);
1767
+ onEffect(e, t, n) {
1768
+ const s = async (i, d, a) => {
1769
+ if (i.channel !== e || i.type !== t) return;
1770
+ const r = i;
1771
+ return n(r.payload, d, a, r);
1511
1772
  };
1512
1773
  return this.registerEffect({
1513
- events: [[e, t]],
1514
- effect: n
1774
+ when: { keys: [[e, t]] },
1775
+ effect: s
1515
1776
  });
1516
1777
  }
1517
1778
  /**
@@ -1573,11 +1834,11 @@ class v {
1573
1834
  * @public
1574
1835
  */
1575
1836
  replaceReducers(e, t = {}) {
1576
- const r = t.preserveState !== !1, n = new Set(Object.keys(this.reducers)), s = Object.entries(e), c = new Set(s.map(([i]) => i));
1577
- for (const i of n)
1578
- c.has(i) || this.unmountSlice(i, { deleteState: !0 });
1579
- for (const [i, o] of s)
1580
- n.has(i) ? (this.unmountSlice(i, { deleteState: !1 }), this.mountSlice(i, o, { preserveState: r })) : this.mountSlice(i, o, { preserveState: !1 });
1837
+ const n = t.preserveState !== !1, s = new Set(Object.keys(this.reducers)), i = Object.entries(e), d = new Set(i.map(([a]) => a));
1838
+ for (const a of s)
1839
+ d.has(a) || this.unmountSlice(a, { deleteState: !0 });
1840
+ for (const [a, r] of i)
1841
+ s.has(a) ? (this.unmountSlice(a, { deleteState: !1 }), this.mountSlice(a, r, { preserveState: n })) : this.mountSlice(a, r, { preserveState: !1 });
1581
1842
  }
1582
1843
  /**
1583
1844
  * Convenience API to replace **any subset** of store parts (HMR patterns).
@@ -1604,31 +1865,39 @@ class v {
1604
1865
  * and wires `(channel, type)` listeners on the reducer bus.
1605
1866
  *
1606
1867
  * @param name - Slice name.
1607
- * @param rSpec - Reducer spec (state, events, reducer).
1868
+ * @param rSpec - Reducer spec (state, when, reducer).
1608
1869
  * @param opts - `{ preserveState: boolean }` whether to keep existing state.
1609
1870
  *
1610
1871
  * @internal
1611
1872
  */
1612
- mountSlice(e, t, r) {
1613
- const n = e, { events: s, reducer: c, state: i, when: o } = t;
1614
- if (this.reducers[e] = new _(c), (!r.preserveState || this.state[n] === void 0) && (this.state[n] = b(structuredClone(i))), o && ("any" in o && o.any === !0 || "channel" in o || "channels" in o)) {
1615
- this.patternReducers.set(e, o), this.sliceUnsubs.set(n, []);
1873
+ mountSlice(e, t, n) {
1874
+ const s = e, { reducer: i, state: d, when: a } = t;
1875
+ if (this.reducers[e] = new K(i), (!n.preserveState || this.state[s] === void 0) && (this.state = {
1876
+ ...this.state,
1877
+ [s]: k(W(s, d))
1878
+ }), a && ("any" in a && a.any === !0 || "channel" in a || "channels" in a)) {
1879
+ this.patternReducers.set(e, a), this.sliceUnsubs.set(s, []);
1616
1880
  return;
1617
1881
  }
1618
- const h = this.normalizeEventKeys(t);
1619
- if (h.length === 0 && !o && !s) {
1620
- this.patternReducers.set(e, { any: !0 }), this.sliceUnsubs.set(n, []);
1882
+ const f = this.normalizeEventKeys(t);
1883
+ if (f.length === 0 && !a) {
1884
+ this.patternReducers.set(e, { any: !0 }), this.sliceUnsubs.set(s, []);
1621
1885
  return;
1622
1886
  }
1623
- const l = [];
1624
- for (const [u, p] of h) {
1625
- const y = this.reducerBus.on(u, p, (m) => {
1626
- const M = { channel: u, type: p, payload: m, id: crypto.randomUUID() };
1627
- this.forwardEvent(e, M);
1887
+ const c = [];
1888
+ for (const [u, l] of f) {
1889
+ const y = this.reducerBus.on(u, l, (h, m) => {
1890
+ const g = m ?? {
1891
+ channel: u,
1892
+ type: l,
1893
+ payload: h,
1894
+ id: this.idFactory()
1895
+ };
1896
+ this.forwardEventGuarded(e, g);
1628
1897
  });
1629
- l.push(y);
1898
+ c.push(y);
1630
1899
  }
1631
- this.sliceUnsubs.set(n, l);
1900
+ this.sliceUnsubs.set(s, c);
1632
1901
  }
1633
1902
  /**
1634
1903
  * Unmounts a slice: disposes reducer-bus listeners, removes reducer,
@@ -1640,24 +1909,27 @@ class v {
1640
1909
  * @internal
1641
1910
  */
1642
1911
  unmountSlice(e, t) {
1643
- const r = e;
1912
+ const n = e;
1644
1913
  this.patternReducers.delete(e);
1645
- const n = this.sliceUnsubs.get(r);
1646
- if (n) {
1647
- for (const s of n)
1914
+ const s = this.sliceUnsubs.get(n);
1915
+ if (s) {
1916
+ for (const i of s)
1648
1917
  try {
1649
- s();
1650
- } catch (c) {
1651
- console.error(`[Store error]: ${c}`);
1918
+ i();
1919
+ } catch (d) {
1920
+ console.error(`[Store error]: ${d}`);
1652
1921
  }
1653
- this.sliceUnsubs.delete(r);
1922
+ this.sliceUnsubs.delete(n);
1923
+ }
1924
+ if (delete this.reducers[e], t.deleteState) {
1925
+ const { [n]: i, ...d } = this.state;
1926
+ this.state = d;
1654
1927
  }
1655
- delete this.reducers[e], t.deleteState && delete this.state[r];
1656
1928
  }
1657
1929
  /**
1658
- * Normalizes event targeting from `when` or legacy `events` to an array of EventKeys.
1930
+ * Normalizes event targeting from `when` to an array of EventKeys.
1659
1931
  *
1660
- * @param spec - Object with optional `when` and/or `events` properties.
1932
+ * @param spec - Object with an optional `when` matcher.
1661
1933
  * @returns Array of `[channel, type]` pairs.
1662
1934
  *
1663
1935
  * @internal
@@ -1665,16 +1937,10 @@ class v {
1665
1937
  normalizeEventKeys(e) {
1666
1938
  if (e.when) {
1667
1939
  const t = e.when;
1668
- if ("any" in t && t.any === !0)
1669
- return [];
1670
1940
  if ("keys" in t)
1671
1941
  return t.keys;
1672
- if ("channel" in t)
1673
- return [];
1674
- if ("channels" in t)
1675
- return [];
1676
1942
  }
1677
- return e.events ? e.events : [];
1943
+ return [];
1678
1944
  }
1679
1945
  /**
1680
1946
  * Reads a dotted path from an object (supports numeric array indices via string keys).
@@ -1687,13 +1953,13 @@ class v {
1687
1953
  */
1688
1954
  getAtPath(e, t) {
1689
1955
  if (!t) return e;
1690
- const n = (t[0] === "." ? t.slice(1) : t).split(".");
1691
- let s = e;
1692
- for (const c of n) {
1693
- if (s == null) return;
1694
- s = s[c];
1956
+ const s = (t[0] === "." ? t.slice(1) : t).split(".");
1957
+ let i = e;
1958
+ for (const d of s) {
1959
+ if (i == null) return;
1960
+ i = i[d];
1695
1961
  }
1696
- return s;
1962
+ return i;
1697
1963
  }
1698
1964
  /**
1699
1965
  * Builds ancestor paths for a dotted path.
@@ -1712,33 +1978,397 @@ class v {
1712
1978
  */
1713
1979
  static buildAncestorPaths(e) {
1714
1980
  if (!e) return [];
1715
- const r = (e[0] === "." ? e.slice(1) : e).split("."), n = [];
1716
- for (let s = 0; s < r.length; s++)
1717
- n.push(r.slice(0, s + 1).join("."));
1718
- return n;
1981
+ const n = (e[0] === "." ? e.slice(1) : e).split("."), s = [];
1982
+ for (let i = 0; i < n.length; i++)
1983
+ s.push(n.slice(0, i + 1).join("."));
1984
+ return s;
1719
1985
  }
1720
1986
  }
1721
- function R(a) {
1722
- return new v({
1723
- name: a.name,
1724
- reducer: a.reducer ?? {},
1725
- middleware: a.middleware ?? [],
1726
- effects: a.effects ?? [],
1727
- dedupWindowMs: a.dedupWindowMs,
1728
- devtools: a.devtools,
1729
- onEffectError: a.onEffectError
1987
+ function L(o) {
1988
+ return new $({
1989
+ name: o.name,
1990
+ reducer: o.reducer ?? {},
1991
+ middleware: o.middleware ?? [],
1992
+ effects: o.effects ?? [],
1993
+ dedupWindowMs: o.dedupWindowMs,
1994
+ idFactory: o.idFactory,
1995
+ devtools: o.devtools,
1996
+ onEffectError: o.onEffectError,
1997
+ onReducerError: o.onReducerError
1730
1998
  });
1731
1999
  }
1732
- const $ = (a) => (e, t) => t.map((r) => [e, r]), B = () => (a) => a;
2000
+ const G = (o) => (e, t) => t.map((n) => [e, n]), Q = () => (o) => o, I = /* @__PURE__ */ new Set();
2001
+ function j(o) {
2002
+ const e = String(o);
2003
+ I.has(e) || (I.add(e), console.warn(
2004
+ `[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.`
2005
+ ));
2006
+ }
2007
+ function F(o, e) {
2008
+ if (o.length !== e.length) return e;
2009
+ for (let t = 0; t < o.length; t++)
2010
+ if (o[t] !== e[t]) return e;
2011
+ return o;
2012
+ }
2013
+ function J(o = {}) {
2014
+ const e = o.selectId ?? ((r) => r.id), { sortComparer: t } = o, n = (r, f) => {
2015
+ if (t === void 0) return f;
2016
+ const c = [...f].sort((u, l) => {
2017
+ const y = r.entities[u], h = r.entities[l];
2018
+ return y === void 0 || h === void 0 ? 0 : t(y, h);
2019
+ });
2020
+ return F(f, c);
2021
+ }, s = (r, f, c) => {
2022
+ const u = { ...r, entities: f, ids: c };
2023
+ return { ...u, ids: n(u, c) };
2024
+ }, i = (r, f, c) => {
2025
+ let u = null, l = null;
2026
+ for (const y of f) {
2027
+ const h = e(y);
2028
+ process.env.NODE_ENV !== "production" && String(h).includes(".") && j(h);
2029
+ const m = (u ?? r.entities)[h];
2030
+ if (m !== void 0 && c === "add") continue;
2031
+ const g = m !== void 0 && c === "upsert" ? { ...m, ...y } : y;
2032
+ u ?? (u = { ...r.entities }), u[h] = g, m === void 0 && (l ?? (l = [...r.ids]), l.push(h));
2033
+ }
2034
+ return u === null ? r : s(r, u, l ?? r.ids);
2035
+ }, d = (r, f) => {
2036
+ let c = null;
2037
+ for (const { id: u, changes: l } of f) {
2038
+ const y = (c ?? r.entities)[u];
2039
+ y !== void 0 && (c ?? (c = { ...r.entities }), c[u] = { ...y, ...l });
2040
+ }
2041
+ return c === null ? r : s(r, c, r.ids);
2042
+ }, a = (r, f) => {
2043
+ const c = new Set(f.filter((l) => r.entities[l] !== void 0));
2044
+ if (c.size === 0) return r;
2045
+ const u = { ...r.entities };
2046
+ for (const l of c) delete u[l];
2047
+ return s(
2048
+ r,
2049
+ u,
2050
+ r.ids.filter((l) => !c.has(l))
2051
+ );
2052
+ };
2053
+ return {
2054
+ getInitialState(r) {
2055
+ const f = { ids: [], entities: {} };
2056
+ return r === void 0 ? f : { ...f, ...r };
2057
+ },
2058
+ addOne: (r, f) => i(r, [f], "add"),
2059
+ addMany: (r, f) => i(r, f, "add"),
2060
+ setOne: (r, f) => i(r, [f], "set"),
2061
+ setMany: (r, f) => i(r, f, "set"),
2062
+ setAll: (r, f) => {
2063
+ const c = {}, u = [];
2064
+ for (const l of f) {
2065
+ const y = e(l);
2066
+ c[y] === void 0 && u.push(y), c[y] = l;
2067
+ }
2068
+ return s(r, c, u);
2069
+ },
2070
+ updateOne: (r, f) => d(r, [f]),
2071
+ updateMany: (r, f) => d(r, f),
2072
+ upsertOne: (r, f) => i(r, [f], "upsert"),
2073
+ upsertMany: (r, f) => i(r, f, "upsert"),
2074
+ removeOne: (r, f) => a(r, [f]),
2075
+ removeMany: (r, f) => a(r, f),
2076
+ removeAll: (r) => r.ids.length === 0 ? r : s(r, {}, []),
2077
+ selectIds: (r) => r.ids,
2078
+ selectEntities: (r) => r.entities,
2079
+ selectAll: (r) => r.ids.map((f) => r.entities[f]),
2080
+ selectById: (r, f) => r.entities[f],
2081
+ selectTotal: (r) => r.ids.length,
2082
+ idsPath: "ids",
2083
+ pathTo: (r, f) => f === void 0 ? `entities.${r}` : `entities.${r}.${f}`,
2084
+ anyField: (r) => `entities.*.${r}`
2085
+ };
2086
+ }
2087
+ const E = "$yoltra";
2088
+ function _(o, e = {}) {
2089
+ const t = e.maxNodes ?? 1e5, n = e.sanitize, s = [], i = /* @__PURE__ */ new Map();
2090
+ let d = 0, a = !1;
2091
+ function r(c, u) {
2092
+ if (n !== void 0 && (c = n(u, c)), d += 1, d > t)
2093
+ return a = !0, { [E]: "unsupported", kind: "truncated" };
2094
+ switch (typeof c) {
2095
+ case "undefined":
2096
+ return { [E]: "undefined" };
2097
+ case "bigint":
2098
+ return { [E]: "bigint", value: c.toString() };
2099
+ case "number":
2100
+ return Number.isNaN(c) ? { [E]: "nan" } : c === 1 / 0 ? { [E]: "infinity", sign: 1 } : c === -1 / 0 ? { [E]: "infinity", sign: -1 } : c;
2101
+ case "function":
2102
+ case "symbol":
2103
+ return s.push(u), { [E]: "unsupported", kind: typeof c };
2104
+ case "string":
2105
+ case "boolean":
2106
+ return c;
2107
+ }
2108
+ if (c === null) return null;
2109
+ const l = c, y = i.get(l);
2110
+ if (y !== void 0) return { [E]: "ref", path: y };
2111
+ if (i.set(l, u), c instanceof Date)
2112
+ return { [E]: "date", iso: c.toISOString() };
2113
+ if (c instanceof RegExp)
2114
+ return { [E]: "regexp", source: c.source, flags: c.flags };
2115
+ if (c instanceof Error)
2116
+ return { [E]: "error", name: c.name, message: c.message };
2117
+ if (c instanceof Map) {
2118
+ const m = [];
2119
+ let g = 0;
2120
+ for (const [w, D] of c)
2121
+ m.push([r(w, `${u}/@k${g}`), r(D, `${u}/${g}`)]), g += 1;
2122
+ return { [E]: "map", entries: m };
2123
+ }
2124
+ if (c instanceof Set) {
2125
+ const m = [];
2126
+ let g = 0;
2127
+ for (const w of c)
2128
+ m.push(r(w, `${u}/${g}`)), g += 1;
2129
+ return { [E]: "set", values: m };
2130
+ }
2131
+ if (Array.isArray(c))
2132
+ return c.map((m, g) => r(m, `${u}/${g}`));
2133
+ const h = {};
2134
+ for (const [m, g] of Object.entries(c))
2135
+ h[m] = r(g, `${u}/${N(m)}`);
2136
+ return E in h ? { [E]: "escaped", value: h } : h;
2137
+ }
2138
+ return { value: r(o, ""), report: { truncated: a, unsupported: s } };
2139
+ }
2140
+ function V(o) {
2141
+ const e = /* @__PURE__ */ new Map(), t = [];
2142
+ function n(d, a) {
2143
+ if (d === null || typeof d != "object") return d;
2144
+ if (Array.isArray(d)) {
2145
+ const f = [];
2146
+ return e.set(a, f), d.forEach((c, u) => {
2147
+ if (R(c)) {
2148
+ t.push({ target: f, key: u, path: c.path }), f[u] = void 0;
2149
+ return;
2150
+ }
2151
+ f[u] = n(c, `${a}/${u}`);
2152
+ }), f;
2153
+ }
2154
+ if (typeof d[E] == "string") {
2155
+ const f = d;
2156
+ switch (f[E]) {
2157
+ case "undefined":
2158
+ return;
2159
+ case "nan":
2160
+ return Number.NaN;
2161
+ case "infinity":
2162
+ return f.sign === 1 ? 1 / 0 : -1 / 0;
2163
+ case "bigint":
2164
+ return BigInt(f.value);
2165
+ case "date":
2166
+ return new Date(f.iso);
2167
+ case "regexp":
2168
+ return new RegExp(f.source, f.flags);
2169
+ case "error": {
2170
+ const c = new Error(f.message);
2171
+ return c.name = f.name, c;
2172
+ }
2173
+ case "unsupported":
2174
+ return;
2175
+ case "ref":
2176
+ return;
2177
+ case "map": {
2178
+ const c = /* @__PURE__ */ new Map();
2179
+ return e.set(a, c), f.entries.forEach(([u, l], y) => {
2180
+ c.set(n(u, `${a}/@k${y}`), n(l, `${a}/${y}`));
2181
+ }), c;
2182
+ }
2183
+ case "set": {
2184
+ const c = /* @__PURE__ */ new Set();
2185
+ return e.set(a, c), f.values.forEach((u, l) => c.add(n(u, `${a}/${l}`))), c;
2186
+ }
2187
+ case "escaped":
2188
+ return s(f.value, a);
2189
+ default:
2190
+ return;
2191
+ }
2192
+ }
2193
+ return s(d, a);
2194
+ }
2195
+ function s(d, a) {
2196
+ const r = {};
2197
+ e.set(a, r);
2198
+ for (const [f, c] of Object.entries(d)) {
2199
+ const u = `${a}/${N(f)}`;
2200
+ if (R(c)) {
2201
+ t.push({ target: r, key: f, path: c.path }), r[f] = void 0;
2202
+ continue;
2203
+ }
2204
+ r[f] = n(c, u);
2205
+ }
2206
+ return r;
2207
+ }
2208
+ const i = n(o, "");
2209
+ e.set("", i);
2210
+ for (const { target: d, key: a, path: r } of t)
2211
+ d[a] = e.get(r);
2212
+ return i;
2213
+ }
2214
+ function R(o) {
2215
+ return o !== null && typeof o == "object" && o[E] === "ref" && typeof o.path == "string";
2216
+ }
2217
+ function N(o) {
2218
+ return o.replace(/~/g, "~0").replace(/\//g, "~1");
2219
+ }
2220
+ function Y(o, e, t = {}) {
2221
+ let n = t.maxNodes ?? 1e5;
2222
+ for (let s = 0; s < 8; s += 1) {
2223
+ const { value: i, report: d } = _(o, { ...t, maxNodes: n });
2224
+ let a;
2225
+ try {
2226
+ a = JSON.stringify(i)?.length ?? 0;
2227
+ } catch {
2228
+ a = Number.POSITIVE_INFINITY;
2229
+ }
2230
+ if (a <= e)
2231
+ return d.truncated ? {
2232
+ value: i,
2233
+ truncated: !0,
2234
+ note: `State was too large to send in full; parts beyond ${n} nodes are omitted.`
2235
+ } : { value: i, truncated: !1 };
2236
+ const r = Math.floor(n * e * 0.8 / a);
2237
+ if (n = Math.max(1, Math.min(r, n - 1)), n <= 1 && s > 0)
2238
+ break;
2239
+ }
2240
+ return {
2241
+ value: { [E]: "unsupported", kind: "truncated" },
2242
+ truncated: !0,
2243
+ note: `State exceeds the ${e}-byte transport limit and could not be reduced to fit.`
2244
+ };
2245
+ }
2246
+ function b(o, e, t) {
2247
+ o.onError?.(e, t);
2248
+ }
2249
+ async function q(o) {
2250
+ const e = { slices: {}, restored: !1 };
2251
+ let t;
2252
+ try {
2253
+ t = o.source ?? await o.adapter.read(o.key);
2254
+ } catch (s) {
2255
+ return b(o, s, "read"), e;
2256
+ }
2257
+ if (t == null || t === "") return e;
2258
+ let n;
2259
+ try {
2260
+ n = V(JSON.parse(t));
2261
+ } catch (s) {
2262
+ return b(o, s, "decode"), e;
2263
+ }
2264
+ if (n === null || typeof n != "object" || typeof n.version != "number")
2265
+ return b(o, new Error("persisted payload is not a recognisable envelope"), "decode"), e;
2266
+ if (n.version !== o.version) {
2267
+ if (o.migrate === void 0)
2268
+ return b(
2269
+ o,
2270
+ new Error(
2271
+ `persisted state is version ${n.version}, this build expects ${o.version}, and no migrate was supplied`
2272
+ ),
2273
+ "migrate"
2274
+ ), e;
2275
+ try {
2276
+ const s = o.migrate(n.slices, n.version);
2277
+ return s === null ? e : { slices: s, restored: !0 };
2278
+ } catch (s) {
2279
+ return b(o, s, "migrate"), e;
2280
+ }
2281
+ }
2282
+ return { slices: n.slices ?? {}, restored: !0 };
2283
+ }
2284
+ function X(o, e) {
2285
+ if (!e.restored) return o;
2286
+ const t = {};
2287
+ for (const [n, s] of Object.entries(o)) {
2288
+ const i = e.slices[n];
2289
+ t[n] = i === void 0 ? s : { ...s, state: i };
2290
+ }
2291
+ return t;
2292
+ }
2293
+ function A(o, e) {
2294
+ const t = o ?? {}, n = e.slices === void 0 ? t : Object.fromEntries(e.slices.filter((s) => s in t).map((s) => [s, t[s]]));
2295
+ return JSON.stringify(_({ version: e.version, slices: n }).value);
2296
+ }
2297
+ function Z(o, e) {
2298
+ const t = e.throttleMs ?? 250, n = e.slices;
2299
+ let s = null, i = !1;
2300
+ const d = () => {
2301
+ if (i) {
2302
+ i = !1;
2303
+ try {
2304
+ const f = e.adapter.write(e.key, A(o.getState(), e));
2305
+ f instanceof Promise && f.catch((c) => b(e, c, "write"));
2306
+ } catch (f) {
2307
+ b(e, f, "write");
2308
+ }
2309
+ }
2310
+ }, a = () => {
2311
+ if (i = !0, t <= 0) {
2312
+ d();
2313
+ return;
2314
+ }
2315
+ s === null && (s = setTimeout(() => {
2316
+ s = null, d();
2317
+ }, t), s.unref?.());
2318
+ }, r = o.instrument((f) => {
2319
+ if (n === void 0) {
2320
+ a();
2321
+ return;
2322
+ }
2323
+ (f.changedPaths ?? []).some(
2324
+ (u) => n.some((l) => u === l || u.startsWith(`${l}.`))
2325
+ ) && a();
2326
+ });
2327
+ return () => {
2328
+ r(), s !== null && (clearTimeout(s), s = null), d();
2329
+ };
2330
+ }
2331
+ function ee(o, e) {
2332
+ return A(o.getState(), e);
2333
+ }
2334
+ function te(o) {
2335
+ return {
2336
+ read: (e) => o.getItem(e),
2337
+ write: (e, t) => o.setItem(e, t),
2338
+ remove: (e) => o.removeItem(e)
2339
+ };
2340
+ }
2341
+ function se(o) {
2342
+ const e = new Map(Object.entries(o ?? {}));
2343
+ return {
2344
+ read: (t) => e.get(t) ?? null,
2345
+ write: (t, n) => {
2346
+ e.set(t, n);
2347
+ },
2348
+ remove: (t) => {
2349
+ e.delete(t);
2350
+ }
2351
+ };
2352
+ }
1733
2353
  export {
1734
- O as EventBus,
1735
- k as LooseEventBus,
1736
- _ as Reducer,
1737
- v as Store,
1738
- R as createStore,
1739
- g as detectChangedProps,
1740
- B as eventKeys,
1741
- E as freezeState,
1742
- $ as typedEvents
2354
+ B as EventBus,
2355
+ H as LooseEventBus,
2356
+ K as Reducer,
2357
+ $ as Store,
2358
+ J as createEntityAdapter,
2359
+ se as createMemoryAdapter,
2360
+ L as createStore,
2361
+ te as createWebStorageAdapter,
2362
+ V as decodeState,
2363
+ ee as dehydrate,
2364
+ O as detectChangedProps,
2365
+ _ as encodeState,
2366
+ Y as encodeStateBounded,
2367
+ Q as eventKeys,
2368
+ S as freezeState,
2369
+ q as hydrate,
2370
+ Z as persist,
2371
+ G as typedEvents,
2372
+ X as withHydration
1743
2373
  };
1744
2374
  //# sourceMappingURL=yoltra.esm.js.map