@yoltra/core 0.1.0 → 0.3.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,19 +1,22 @@
1
1
  /*!
2
- * @yoltra/core v0.1.0
2
+ * @yoltra/core v0.3.0
3
3
  * (c) 2026 Manu Ramirez <@pixerael>
4
4
  * License: MIT
5
5
  * Homepage: https://yoltra.dev
6
+ *
7
+ * This source code is licensed under the MIT license found in the
8
+ * LICENSE file in the root directory of this source tree
6
9
  */
7
- var M = Object.defineProperty;
8
- var P = (a, e, t) => e in a ? M(a, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : a[e] = t;
9
- var u = (a, e, t) => P(a, typeof e != "symbol" ? e + "" : e, t);
10
- class z {
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 {
11
14
  constructor() {
12
15
  /**
13
16
  * Internal registry: `channel → type → Set<handler>`.
14
17
  * @internal
15
18
  */
16
- u(this, "handlers", /* @__PURE__ */ new Map());
19
+ d(this, "handlers", /* @__PURE__ */ new Map());
17
20
  }
18
21
  /**
19
22
  * Subscribes a handler to an exact `(channel, type)`.
@@ -37,11 +40,11 @@ class z {
37
40
  *
38
41
  * @public
39
42
  */
40
- on(e, t, s) {
43
+ on(e, t, r) {
41
44
  let n = this.handlers.get(e);
42
45
  n || (n = /* @__PURE__ */ new Map(), this.handlers.set(e, n));
43
- let r = n.get(t);
44
- return r || (r = /* @__PURE__ */ new Set(), n.set(t, r)), r.add(s), () => this.off(e, t, s);
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);
45
48
  }
46
49
  /**
47
50
  * Removes a specific handler previously added with {@link EventBus.on | `on`}.
@@ -63,11 +66,11 @@ class z {
63
66
  *
64
67
  * @public
65
68
  */
66
- off(e, t, s) {
69
+ off(e, t, r) {
67
70
  const n = this.handlers.get(e);
68
71
  if (!n) return;
69
- const r = n.get(t);
70
- r && (r.delete(s), r.size === 0 && n.delete(t), n.size === 0 && this.handlers.delete(e));
72
+ const s = n.get(t);
73
+ s && (s.delete(r), s.size === 0 && n.delete(t), n.size === 0 && this.handlers.delete(e));
71
74
  }
72
75
  /**
73
76
  * Emits an event to all subscribers of the exact `(channel, type)`.
@@ -88,14 +91,14 @@ class z {
88
91
  *
89
92
  * @public
90
93
  */
91
- emit(e, t, s) {
94
+ emit(e, t, r) {
92
95
  const n = this.handlers.get(e);
93
96
  if (!n) return;
94
- const r = n.get(t);
95
- if (!(!r || r.size === 0))
96
- for (const o of [...r])
97
+ const s = n.get(t);
98
+ if (!(!s || s.size === 0))
99
+ for (const c of [...s])
97
100
  try {
98
- o(s);
101
+ c(r);
99
102
  } catch (i) {
100
103
  console.error("EventBus handler error:", i);
101
104
  }
@@ -123,12 +126,12 @@ class k {
123
126
  * Exact handlers: `channel → type → [handlers]`.
124
127
  * @internal
125
128
  */
126
- u(this, "handlers", /* @__PURE__ */ new Map());
129
+ d(this, "handlers", /* @__PURE__ */ new Map());
127
130
  /**
128
131
  * Pattern handlers with `*` and `**`: `channel → pattern(string) → [handlers]`.
129
132
  * @internal
130
133
  */
131
- u(this, "patternHandlers", /* @__PURE__ */ new Map());
134
+ d(this, "patternHandlers", /* @__PURE__ */ new Map());
132
135
  }
133
136
  /**
134
137
  * Subscribes a handler to either an **exact** type or a **pattern**.
@@ -162,18 +165,18 @@ class k {
162
165
  *
163
166
  * @public
164
167
  */
165
- on(e, t, s) {
168
+ on(e, t, r) {
166
169
  const n = String(t);
167
170
  if (this.isPattern(n)) {
168
- const r = n;
171
+ const s = n;
169
172
  this.patternHandlers.has(e) || this.patternHandlers.set(e, /* @__PURE__ */ new Map());
170
- const o = this.patternHandlers.get(e);
171
- return o.has(r) || o.set(r, []), o.get(r).push(s), () => this.offPattern(e, r, s);
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);
172
175
  } else {
173
- const r = this.normalizeTypeKey(n);
176
+ const s = this.normalizeTypeKey(n);
174
177
  this.handlers.has(e) || this.handlers.set(e, /* @__PURE__ */ new Map());
175
- const o = this.handlers.get(e);
176
- return o.has(r) || o.set(r, []), o.get(r).push(s), () => this.offExactNormalized(e, r, s);
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);
177
180
  }
178
181
  }
179
182
  /**
@@ -194,9 +197,9 @@ class k {
194
197
  *
195
198
  * @public
196
199
  */
197
- off(e, t, s) {
200
+ off(e, t, r) {
198
201
  const n = this.normalizeTypeKey(String(t));
199
- this.offExactNormalized(e, n, s);
202
+ this.offExactNormalized(e, n, r);
200
203
  }
201
204
  /**
202
205
  * Internal exact unsubscription using an already **normalized** type key.
@@ -206,13 +209,13 @@ class k {
206
209
  * @param handler - Handler to remove.
207
210
  * @internal
208
211
  */
209
- offExactNormalized(e, t, s) {
212
+ offExactNormalized(e, t, r) {
210
213
  const n = this.handlers.get(e);
211
214
  if (!n) return;
212
- const r = n.get(t);
213
- if (!r) return;
214
- const o = r.indexOf(s);
215
- o !== -1 && r.splice(o, 1), r.length === 0 && n.delete(t), n.size === 0 && this.handlers.delete(e);
215
+ const s = n.get(t);
216
+ 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);
216
219
  }
217
220
  /**
218
221
  * Internal removal for a **pattern** subscription. No-ops if missing.
@@ -222,13 +225,13 @@ class k {
222
225
  * @param handler - Handler to remove.
223
226
  * @internal
224
227
  */
225
- offPattern(e, t, s) {
228
+ offPattern(e, t, r) {
226
229
  const n = this.patternHandlers.get(e);
227
230
  if (!n) return;
228
- const r = n.get(t);
229
- if (!r) return;
230
- const o = r.indexOf(s);
231
- o !== -1 && r.splice(o, 1), r.length === 0 && n.delete(t), n.size === 0 && this.patternHandlers.delete(e);
231
+ const s = n.get(t);
232
+ 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);
232
235
  }
233
236
  /**
234
237
  * Emits an event to all exact subscribers first, then to **matching pattern** subscribers.
@@ -250,27 +253,27 @@ class k {
250
253
  *
251
254
  * @public
252
255
  */
253
- emit(e, t, s) {
254
- const n = String(t), r = this.normalizeTypeKey(n), o = this.handlers.get(e)?.get(r) ?? [], i = this.patternHandlers.get(e), c = [];
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 = [];
255
258
  if (i && i.size) {
256
259
  const l = this.normalizeTypeKey(n);
257
- for (const [p, d] of i.entries())
258
- this.matchPattern(p, l) && c.push(d);
260
+ for (const [u, p] of i.entries())
261
+ this.matchPattern(u, l) && o.push(p);
259
262
  }
260
263
  const f = /* @__PURE__ */ new Set(), h = (l) => {
261
- for (const p of [...l])
262
- if (!f.has(p)) {
263
- f.add(p);
264
+ for (const u of [...l])
265
+ if (!f.has(u)) {
266
+ f.add(u);
264
267
  try {
265
- p(s);
266
- } catch (d) {
267
- console.error(d);
268
+ u(r);
269
+ } catch (p) {
270
+ console.error(p);
268
271
  continue;
269
272
  }
270
273
  }
271
274
  };
272
- h(o);
273
- for (const l of c) h(l);
275
+ h(c);
276
+ for (const l of o) h(l);
274
277
  }
275
278
  /**
276
279
  * Determines if a string is a **pattern** (contains `*`).
@@ -328,29 +331,19 @@ class k {
328
331
  * @internal
329
332
  */
330
333
  matchPattern(e, t) {
331
- const s = this.splitPath(e), n = this.splitPath(t);
332
- let r = 0, o = 0;
333
- for (; r < s.length && o < n.length; ) {
334
- const i = s[r];
335
- if (i === "**") {
336
- if (r === s.length - 1 || s.slice(r).filter((f) => f !== "**").length === 0) return !0;
337
- for (let f = o; f <= n.length; f++)
338
- if (this.matchPattern(s.slice(r + 1).join("."), n.slice(f).join(".")))
339
- return !0;
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++;
341
+ else if (i !== -1)
342
+ s = i + 1, c = ++o;
343
+ else
340
344
  return !1;
341
- }
342
- if (i === "*" || i === n[o]) {
343
- r++, o++;
344
- continue;
345
- }
346
- return !1;
347
- }
348
- if (o === n.length) {
349
- for (; r < s.length; r++)
350
- if (s[r] !== "**") return !1;
351
- return !0;
352
- }
353
- return !1;
345
+ for (; s < r.length && r[s] === "**"; ) s++;
346
+ return s === r.length;
354
347
  }
355
348
  /**
356
349
  * Removes **all** listeners (exact and pattern). Useful for tests/HMR teardown.
@@ -375,12 +368,12 @@ class k {
375
368
  */
376
369
  __introspect() {
377
370
  const e = [];
378
- for (const [t, s] of this.handlers)
379
- for (const [n, r] of s)
380
- r.length > 0 && e.push({ channel: t, type: n, count: r.length });
381
- for (const [t, s] of this.patternHandlers)
382
- for (const [n, r] of s)
383
- r.length > 0 && e.push({ channel: t, type: n, count: r.length });
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 });
384
377
  return e;
385
378
  }
386
379
  }
@@ -405,7 +398,7 @@ class _ {
405
398
  * The underlying pure reducer function.
406
399
  * @internal
407
400
  */
408
- u(this, "_reduce");
401
+ d(this, "_reduce");
409
402
  this._reduce = e;
410
403
  }
411
404
  /**
@@ -426,67 +419,67 @@ class _ {
426
419
  return this._reduce(e, t);
427
420
  }
428
421
  }
429
- function b(a, e, t = "", s = /* @__PURE__ */ new WeakMap()) {
422
+ function g(a, e, t = "", r = /* @__PURE__ */ new Map()) {
430
423
  if (a === e) return [];
431
424
  if (typeof a != "object" || typeof e != "object" || a === null || e === null)
432
- return [t];
425
+ return typeof a == "number" && Number.isNaN(a) && Number.isNaN(e) ? [] : [t];
433
426
  if (a instanceof Date && e instanceof Date)
434
427
  return a.getTime() === e.getTime() ? [] : [t];
435
428
  if (a instanceof RegExp && e instanceof RegExp)
436
429
  return a.source === e.source && e.flags === a.flags ? [] : [t];
437
- const n = a, r = e;
438
- let o = s.get(n);
439
- if (o) {
440
- if (o.has(r)) return [];
441
- o.add(r);
442
- } else
443
- o = /* @__PURE__ */ new WeakSet(), o.add(r), s.set(n, o);
444
- const i = Array.isArray(a), c = Array.isArray(e);
445
- if (i !== c) return [t];
446
- const f = [];
447
- if (i) {
448
- const d = a, m = e;
449
- if (d.length !== m.length) return [t];
450
- for (let y = 0; y < d.length; y++) {
451
- const g = t ? `${t}.${y}` : `${y}`, E = b(d[y], m[y], g, s);
452
- f.push(...E);
453
- }
454
- return f.filter(Boolean);
455
- }
456
- const h = Object.keys(a), l = Object.keys(e), p = /* @__PURE__ */ new Set([...h, ...l]);
457
- for (const d of p) {
458
- const m = t ? `${t}.${d}` : d, y = Object.prototype.hasOwnProperty.call(a, d), g = Object.prototype.hasOwnProperty.call(e, d);
459
- if (!y) {
460
- f.push(m);
461
- continue;
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);
434
+ 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);
462
446
  }
463
- if (!g) {
464
- f.push(m);
465
- continue;
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);
452
+ continue;
453
+ }
454
+ h.push(...g(a[u], e[u], p, r));
466
455
  }
467
- const E = b(a[d], e[d], m, s);
468
- f.push(...E);
456
+ return h.filter(Boolean);
457
+ } finally {
458
+ i.delete(s), i.size === 0 && r.delete(n);
469
459
  }
470
- return f.filter(Boolean);
471
460
  }
472
- function w(a, e = /* @__PURE__ */ new WeakSet()) {
461
+ function E(a, e = /* @__PURE__ */ new WeakSet()) {
473
462
  if (a === null || typeof a != "object" || e.has(a) || Object.isFrozen(a)) return a;
474
463
  if (e.add(a), Array.isArray(a)) {
475
464
  const t = a;
476
- for (let s = 0; s < t.length; s++)
477
- t[s] = w(t[s], e);
465
+ for (let r = 0; r < t.length; r++)
466
+ t[r] = E(t[r], e);
478
467
  return Object.freeze(t);
479
468
  }
480
469
  for (const t of Object.getOwnPropertyNames(a)) {
481
- const s = Object.getOwnPropertyDescriptor(a, t);
482
- !s || !("value" in s) || (a[t] = w(a[t], e));
470
+ const r = Object.getOwnPropertyDescriptor(a, t);
471
+ !r || !("value" in r) || (a[t] = E(a[t], e));
483
472
  }
484
473
  for (const t of Object.getOwnPropertySymbols(a)) {
485
- const s = Object.getOwnPropertyDescriptor(a, t);
486
- !s || !("value" in s) || (a[t] = w(a[t], e));
474
+ const r = Object.getOwnPropertyDescriptor(a, t);
475
+ !r || !("value" in r) || (a[t] = E(a[t], e));
487
476
  }
488
477
  return Object.freeze(a);
489
478
  }
479
+ function b(a) {
480
+ return process.env.NODE_ENV === "production" ? a : E(a);
481
+ }
482
+ const w = 100, S = () => typeof performance < "u" && typeof performance.now == "function" ? performance.now() : Date.now();
490
483
  class v {
491
484
  /**
492
485
  * Creates a store from a {@link StoreSpec}.
@@ -501,7 +494,7 @@ class v {
501
494
  *
502
495
  * @public
503
496
  */
504
- u(this, "name");
497
+ d(this, "name");
505
498
  /**
506
499
  * Registered middleware pipeline (run **before** reducers).
507
500
  * Stores either raw functions (legacy) or MiddlewareSpec objects.
@@ -509,45 +502,45 @@ class v {
509
502
  *
510
503
  * @internal
511
504
  */
512
- u(this, "middleware");
505
+ d(this, "middleware");
513
506
  /**
514
507
  * Installed slice reducers keyed by slice name.
515
508
  *
516
509
  * @internal
517
510
  */
518
- u(this, "reducers");
511
+ d(this, "reducers");
519
512
  /**
520
513
  * Current immutable snapshot of the store state.
521
514
  * This reference changes whenever any slice changes (shallow immutability).
522
515
  *
523
516
  * @internal
524
517
  */
525
- u(this, "state");
518
+ d(this, "state");
526
519
  /**
527
520
  * Bus for reducer wiring (emit by `(channel, type)`).
528
521
  *
529
522
  * @internal
530
523
  */
531
- u(this, "reducerBus");
524
+ d(this, "reducerBus");
532
525
  /**
533
526
  * Bus for **granular** connector events (emit by **dotted path** inside a slice).
534
527
  *
535
528
  * @internal
536
529
  */
537
- u(this, "connectorBus");
530
+ d(this, "connectorBus");
538
531
  /**
539
532
  * Coarse-grained listeners (called once per committed event, only if state changed).
540
533
  *
541
534
  * @internal
542
535
  */
543
- u(this, "listeners", /* @__PURE__ */ new Set());
536
+ d(this, "listeners", /* @__PURE__ */ new Set());
544
537
  /**
545
538
  * Registered effect handlers keyed by `"channel::type"` for O(1) lookup.
546
539
  * Used for effects with explicit `keys` or legacy `events` targeting.
547
540
  *
548
541
  * @internal
549
542
  */
550
- u(this, "effects", /* @__PURE__ */ new Map());
543
+ d(this, "effects", /* @__PURE__ */ new Map());
551
544
  /**
552
545
  * Pattern-based effects that need runtime matching.
553
546
  * Used for effects with `when: { any }`, `{ channel }`, or `{ channels }`.
@@ -555,34 +548,34 @@ class v {
555
548
  *
556
549
  * @internal
557
550
  */
558
- u(this, "patternEffects", /* @__PURE__ */ new Set());
551
+ d(this, "patternEffects", /* @__PURE__ */ new Set());
559
552
  /**
560
553
  * Committed event subscribers keyed by `"channel::type"` for O(1) lookup.
561
554
  * Notified after reducers, before effects, for events that passed middleware.
562
555
  *
563
556
  * @internal
564
557
  */
565
- u(this, "committedEventSubscribers", /* @__PURE__ */ new Map());
558
+ d(this, "committedEventSubscribers", /* @__PURE__ */ new Map());
566
559
  /**
567
560
  * Uncommitted event subscribers keyed by `"channel::type"` for O(1) lookup.
568
561
  * Notified when middleware rejects an event.
569
562
  *
570
563
  * @internal
571
564
  */
572
- u(this, "uncommittedEventSubscribers", /* @__PURE__ */ new Map());
565
+ d(this, "uncommittedEventSubscribers", /* @__PURE__ */ new Map());
573
566
  /**
574
567
  * All-events subscribers keyed by `"channel::type"` for O(1) lookup.
575
568
  * Notified for both committed and uncommitted events with phase parameter.
576
569
  *
577
570
  * @internal
578
571
  */
579
- u(this, "allEventSubscribers", /* @__PURE__ */ new Map());
572
+ d(this, "allEventSubscribers", /* @__PURE__ */ new Map());
580
573
  /**
581
574
  * Track reducerBus unsubs per slice for HMR/register/unregister.
582
575
  *
583
576
  * @internal
584
577
  */
585
- u(this, "sliceUnsubs", /* @__PURE__ */ new Map());
578
+ d(this, "sliceUnsubs", /* @__PURE__ */ new Map());
586
579
  /**
587
580
  * Pattern-based reducers that need runtime matching.
588
581
  * Used for reducers with `when: { any }`, `{ channel }`, or `{ channels }`.
@@ -590,26 +583,54 @@ class v {
590
583
  *
591
584
  * @internal
592
585
  */
593
- u(this, "patternReducers", /* @__PURE__ */ new Map());
586
+ d(this, "patternReducers", /* @__PURE__ */ new Map());
594
587
  /**
595
588
  * Whether `__replayEvents()` is allowed.
596
589
  * Set from `spec.devtools.allowReplay`.
597
590
  *
598
591
  * @internal
599
592
  */
600
- u(this, "replayEnabled");
593
+ d(this, "replayEnabled");
601
594
  /**
602
- * FIFO event queue for serialized emission.
595
+ * Optional hook invoked when an effect throws/rejects. See
596
+ * {@link StoreSpec.onEffectError}. `await emit()` never rejects on effect
597
+ * failure — this is how callers observe effect errors.
598
+ */
599
+ d(this, "onEffectError");
600
+ /**
601
+ * Pending events awaiting the **synchronous** reduce phase (middleware +
602
+ * reducers + subscribers + coarse listeners). Drained by {@link drainReduce}.
603
603
  *
604
604
  * @internal
605
605
  */
606
- u(this, "eventQueue", []);
606
+ d(this, "reduceQueue", []);
607
607
  /**
608
- * Re-entrancy guard while draining the queue.
608
+ * Re-entrancy guard for the synchronous reduce phase.
609
609
  *
610
610
  * @internal
611
611
  */
612
- u(this, "isProcessingQueue", !1);
612
+ d(this, "isReducing", !1);
613
+ /**
614
+ * Registered instrumentation observers (DevTools seam). See {@link instrument}.
615
+ *
616
+ * @internal
617
+ */
618
+ d(this, "instrumentObservers", /* @__PURE__ */ new Set());
619
+ /**
620
+ * Scratch array collecting slice-prefixed changed leaf paths during an
621
+ * instrumented reduce. Set by {@link drainReduce} while observers are active;
622
+ * appended to by {@link forwardEvent}. `null` when not instrumenting.
623
+ *
624
+ * @internal
625
+ */
626
+ d(this, "changedPathSink", null);
627
+ /**
628
+ * Count of effect tasks currently in flight; surfaced as queue depth by
629
+ * {@link __devtoolsIntrospect}.
630
+ *
631
+ * @internal
632
+ */
633
+ d(this, "inFlightEffects", 0);
613
634
  /**
614
635
  * Tracks processed events by fingerprint with timestamps for TTL-based deduplication.
615
636
  *
@@ -626,31 +647,44 @@ class v {
626
647
  *
627
648
  * @internal
628
649
  */
629
- u(this, "processedEvents", /* @__PURE__ */ new Map());
650
+ d(this, "processedEvents", /* @__PURE__ */ new Map());
651
+ /**
652
+ * Lifetime count of events suppressed by the deduplication cache.
653
+ * Exposed via {@link __devtoolsIntrospect} so the DevTools agent can
654
+ * surface it in the STORE_METRICS response without further core changes.
655
+ *
656
+ * @internal
657
+ */
658
+ d(this, "dedupCount", 0);
659
+ /**
660
+ * Store-owned metadata for registered effects, keyed by the effect function.
661
+ * Kept **off** the caller's function object: mutating a user-owned function
662
+ * (the old `fn.__quoMeta`) bled metadata across stores that share a handler
663
+ * and left it attached after unregister. Cleared on {@link dispose}.
664
+ *
665
+ * @internal
666
+ */
667
+ d(this, "effectMeta", /* @__PURE__ */ new WeakMap());
630
668
  /**
631
669
  * Configuration for event deduplication.
632
670
  * @internal
633
671
  */
634
- u(this, "dedupConfig");
672
+ d(this, "dedupConfig");
635
673
  /**
636
674
  * Timer for periodic cleanup of processed events.
637
675
  *
638
676
  * @internal
639
677
  */
640
- u(this, "eventCleanupTimer", null);
641
- this.name = e.name ?? "yoltra Store", this.reducerBus = new z(), this.connectorBus = new k(), this.middleware = [...e.middleware ?? []], this.reducers = {}, this.state = {}, this.replayEnabled = e.devtools?.allowReplay ?? !1;
642
- const t = process.env.NODE_ENV === "production" ? 100 : 50;
643
- if (this.dedupConfig = {
644
- windowMs: e.dedupWindowMs ?? t,
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 = {
680
+ windowMs: e.dedupWindowMs ?? 0,
645
681
  maxCacheSize: 1e3
646
- }, Object.entries(e.reducer).forEach(([s, n]) => {
647
- this.mountSlice(s, n, { preserveState: !1 });
682
+ }, Object.entries(e.reducer).forEach(([t, r]) => {
683
+ this.mountSlice(t, r, { preserveState: !1 });
648
684
  }), e.effects?.length)
649
- for (const s of e.effects)
650
- this.registerEffect(s);
651
- this.eventCleanupTimer = setInterval(() => {
652
- this.pruneProcessedEvents(Date.now());
653
- }, 5e3), this.dispose = this.dispose.bind(this), this.notifyEffects = this.notifyEffects.bind(this), this.forwardEvent = this.forwardEvent.bind(this), this.__applyExternalState = this.__applyExternalState.bind(this), this.__replayEvents = this.__replayEvents.bind(this), this.__devtoolsIntrospect = this.__devtoolsIntrospect.bind(this), this.mountSlice = this.mountSlice.bind(this), this.unmountSlice = this.unmountSlice.bind(this), this.getAtPath = this.getAtPath.bind(this), this.emit = this.emit.bind(this), this.subscribe = this.subscribe.bind(this), this.connect = this.connect.bind(this), this.onEffect = this.onEffect.bind(this), this.onEvent = this.onEvent.bind(this), this.getState = this.getState.bind(this), this.registerEffect = this.registerEffect.bind(this), this.registerMiddleware = this.registerMiddleware.bind(this), this.registerReducer = this.registerReducer.bind(this), this.replaceMiddleware = this.replaceMiddleware.bind(this), this.replaceEffects = this.replaceEffects.bind(this), this.replaceReducers = this.replaceReducers.bind(this), this.hotReplace = this.hotReplace.bind(this);
685
+ for (const t of e.effects)
686
+ this.registerEffect(t);
687
+ 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);
654
688
  }
655
689
  /**
656
690
  * Cleanup resources (timers, etc.) when disposing the store.
@@ -666,7 +700,7 @@ class v {
666
700
  * @public
667
701
  */
668
702
  dispose() {
669
- this.eventCleanupTimer && (clearInterval(this.eventCleanupTimer), this.eventCleanupTimer = null), this.processedEvents.clear(), this.effects.clear(), this.patternEffects.clear();
703
+ this.eventCleanupTimer && (clearInterval(this.eventCleanupTimer), this.eventCleanupTimer = null), this.processedEvents.clear(), this.effects.clear(), this.patternEffects.clear(), this.effectMeta = /* @__PURE__ */ new WeakMap(), this.listeners.clear(), this.committedEventSubscribers.clear(), this.uncommittedEventSubscribers.clear(), this.allEventSubscribers.clear(), this.instrumentObservers.clear(), this.connectorBus.clear(), this.reducerBus.clear(), this.patternReducers.clear(), this.sliceUnsubs.clear(), this.changedPathSink = null;
670
704
  }
671
705
  /**
672
706
  * Generates a fingerprint for an event for deduplication purposes.
@@ -679,15 +713,15 @@ class v {
679
713
  *
680
714
  * @internal
681
715
  */
682
- fingerprint(e, t, s) {
716
+ fingerprint(e, t, r) {
683
717
  const n = `${e}::${t}`;
684
718
  try {
685
- if (s == null)
719
+ if (r == null)
686
720
  return `${n}::null`;
687
- if (typeof s != "object")
688
- return `${n}::${String(s)}`;
689
- const r = JSON.stringify(s);
690
- return `${n}::${r}`;
721
+ if (typeof r != "object")
722
+ return `${n}::${String(r)}`;
723
+ const s = JSON.stringify(r);
724
+ return `${n}::${s}`;
691
725
  } catch {
692
726
  return `${n}::${Date.now()}::${Math.random()}`;
693
727
  }
@@ -701,9 +735,21 @@ class v {
701
735
  *
702
736
  * @internal
703
737
  */
704
- shouldDedupe(e) {
705
- const t = Date.now(), s = this.processedEvents.get(e);
706
- return s !== void 0 && t - s < this.dedupConfig.windowMs ? !0 : (this.processedEvents.set(e, t), this.processedEvents.size > this.dedupConfig.maxCacheSize && this.pruneProcessedEvents(t), !1);
738
+ 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);
741
+ }
742
+ /**
743
+ * Starts the periodic prune interval if it isn't already running. Called when
744
+ * the first entry is cached so the timer's lifetime tracks actual dedup use
745
+ * (content window or identity `dedupKey`), independent of `dedupWindowMs`.
746
+ *
747
+ * @internal
748
+ */
749
+ ensureCleanupTimer() {
750
+ this.eventCleanupTimer === null && (this.eventCleanupTimer = setInterval(() => {
751
+ this.pruneProcessedEvents(Date.now());
752
+ }, 5e3), this.eventCleanupTimer.unref?.());
707
753
  }
708
754
  /**
709
755
  * Removes expired entries from the processed events cache.
@@ -713,9 +759,10 @@ class v {
713
759
  * @internal
714
760
  */
715
761
  pruneProcessedEvents(e) {
716
- const t = e - this.dedupConfig.windowMs * 2;
717
- for (const [s, n] of this.processedEvents)
718
- n < t && this.processedEvents.delete(s);
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);
765
+ this.processedEvents.size === 0 && this.eventCleanupTimer !== null && (clearInterval(this.eventCleanupTimer), this.eventCleanupTimer = null);
719
766
  }
720
767
  /**
721
768
  * Checks if an event matches a `When` matcher.
@@ -735,7 +782,7 @@ class v {
735
782
  */
736
783
  matchesWhen(e, t) {
737
784
  return !e || "any" in e && e.any === !0 ? !0 : "keys" in e ? e.keys.some(
738
- ([s, n]) => t.channel === s && t.type === n
785
+ ([r, n]) => t.channel === r && t.type === n
739
786
  ) : "channel" in e ? t.channel === e.channel : "channels" in e ? e.channels.includes(t.channel) : !1;
740
787
  }
741
788
  /**
@@ -771,20 +818,20 @@ class v {
771
818
  * @internal
772
819
  */
773
820
  async notifyEffects(e) {
774
- const t = `${String(e.channel)}::${String(e.type)}`, s = this.effects.get(t);
775
- if (s && s.size > 0)
776
- for (const n of [...s])
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])
777
824
  try {
778
825
  await n(e, this.getState, this.emit);
779
- } catch (r) {
780
- console.error("Effect error:", r);
826
+ } catch (s) {
827
+ console.error("Effect error:", s), this.onEffectError?.(s, e);
781
828
  }
782
- for (const { effect: n, when: r } of this.patternEffects)
783
- if (this.matchesWhen(r, e))
829
+ for (const { effect: n, when: s } of this.patternEffects)
830
+ if (this.matchesWhen(s, e))
784
831
  try {
785
832
  await n(e, this.getState, this.emit);
786
- } catch (o) {
787
- console.error("Effect error:", o);
833
+ } catch (c) {
834
+ console.error("Effect error:", c), this.onEffectError?.(c, e);
788
835
  }
789
836
  }
790
837
  /**
@@ -797,23 +844,28 @@ class v {
797
844
  * @param phase - The phase ('committed' or 'uncommitted').
798
845
  * @internal
799
846
  */
800
- async notifyEventSubscribers(e, t) {
801
- const s = `${String(e.channel)}::${String(e.type)}`, r = (t === "committed" ? this.committedEventSubscribers : this.uncommittedEventSubscribers).get(s);
802
- if (r?.size)
803
- for (const i of [...r])
804
- try {
805
- await i(e, this.getState, this.emit, t);
806
- } catch (c) {
807
- console.error("Event subscription error:", c);
808
- }
809
- const o = this.allEventSubscribers.get(s);
810
- if (o?.size)
811
- for (const i of [...o])
812
- try {
813
- await i(e, this.getState, this.emit, t);
814
- } catch (c) {
815
- console.error("Event subscription error:", c);
816
- }
847
+ 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);
854
+ }
855
+ /**
856
+ * Invokes a single event-subscription handler **fire-and-forget**: synchronous
857
+ * throws and async rejections are logged but never block the emit pipeline.
858
+ * Event subscribers are notifications, not part of the committed reduce result.
859
+ *
860
+ * @internal
861
+ */
862
+ invokeEventSubscriber(e, t, r) {
863
+ 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);
868
+ }
817
869
  }
818
870
  /**
819
871
  * Applies a reduced event to a slice and emits **precise** connector events.
@@ -832,18 +884,20 @@ class v {
832
884
  * @internal
833
885
  */
834
886
  forwardEvent(e, t) {
835
- const s = this.state[e], n = this.reducers[e].reduce(s, t);
836
- if (s === n) return !1;
837
- const r = b(s, n).filter(Boolean);
838
- if (r.length === 0) return !1;
839
- const o = w(structuredClone(n));
840
- this.state = { ...this.state, [e]: o };
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);
841
895
  const i = /* @__PURE__ */ new Set();
842
- for (const c of r)
843
- for (const f of v.buildAncestorPaths(c)) i.add(f);
844
- for (const c of i) {
845
- const f = this.getAtPath(s, c), h = this.getAtPath(o, c);
846
- this.connectorBus.emit(e, c, { oldValue: f, newValue: h, path: c });
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 });
847
901
  }
848
902
  return !0;
849
903
  }
@@ -859,58 +913,67 @@ class v {
859
913
  */
860
914
  __devtoolsIntrospect() {
861
915
  const e = Object.keys(this.reducers).map((i) => {
862
- const c = this.patternReducers.get(i);
863
- return { name: i, when: c };
916
+ const o = this.patternReducers.get(i);
917
+ return { name: i, when: o };
864
918
  }), t = [];
865
- for (const [i, c] of this.effects) {
866
- if (c.size === 0) continue;
919
+ for (const [i, o] of this.effects) {
920
+ if (o.size === 0) continue;
867
921
  const [f, h] = i.split("::");
868
- for (const l of c) {
869
- const p = l.__quoMeta;
870
- t.push({ channel: f, type: h, name: p?.name, description: p?.description });
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 });
871
925
  }
872
926
  }
873
927
  for (const i of this.patternEffects) {
874
- const c = i.effect.__quoMeta;
928
+ const o = this.effectMeta.get(i.effect);
875
929
  t.push({
876
930
  channel: "*",
877
931
  type: "*",
878
- name: c?.name,
879
- description: c?.description
932
+ name: o?.name,
933
+ description: o?.description
880
934
  });
881
935
  }
882
- const s = [];
936
+ const r = [];
883
937
  for (const i of this.middleware)
884
- typeof i == "function" ? s.push({ name: i.name || void 0 }) : s.push({
938
+ typeof i == "function" ? r.push({ name: i.name || void 0 }) : r.push({
885
939
  name: i.meta?.name,
886
940
  description: i.meta?.description,
887
941
  when: i.when
888
942
  });
889
943
  const n = [];
890
944
  for (const i of this.connectorBus.__introspect())
891
- for (let c = 0; c < i.count; c++)
945
+ for (let o = 0; o < i.count; o++)
892
946
  n.push({ reducer: i.channel, property: i.type });
893
- const r = [];
894
- for (const [i, c] of this.committedEventSubscribers) {
895
- if (c.size === 0) continue;
947
+ const s = [];
948
+ for (const [i, o] of this.committedEventSubscribers) {
949
+ if (o.size === 0) continue;
896
950
  const [f, h] = i.split("::");
897
- for (let l = 0; l < c.size; l++)
898
- r.push({ channel: f, type: h, phase: "committed" });
951
+ for (let l = 0; l < o.size; l++)
952
+ s.push({ channel: f, type: h, phase: "committed" });
899
953
  }
900
- for (const [i, c] of this.uncommittedEventSubscribers) {
901
- if (c.size === 0) continue;
954
+ for (const [i, o] of this.uncommittedEventSubscribers) {
955
+ if (o.size === 0) continue;
902
956
  const [f, h] = i.split("::");
903
- for (let l = 0; l < c.size; l++)
904
- r.push({ channel: f, type: h, phase: "uncommitted" });
957
+ for (let l = 0; l < o.size; l++)
958
+ s.push({ channel: f, type: h, phase: "uncommitted" });
905
959
  }
906
- for (const [i, c] of this.allEventSubscribers) {
907
- if (c.size === 0) continue;
960
+ for (const [i, o] of this.allEventSubscribers) {
961
+ if (o.size === 0) continue;
908
962
  const [f, h] = i.split("::");
909
- for (let l = 0; l < c.size; l++)
910
- r.push({ channel: f, type: h, phase: "all" });
963
+ for (let l = 0; l < o.size; l++)
964
+ s.push({ channel: f, type: h, phase: "all" });
911
965
  }
912
- const o = this.listeners.size;
913
- return { reducers: e, effects: t, middleware: s, atomic: n, event: r, coarse: o };
966
+ const c = this.listeners.size;
967
+ return {
968
+ reducers: e,
969
+ effects: t,
970
+ middleware: r,
971
+ atomic: n,
972
+ event: s,
973
+ coarse: c,
974
+ dedupHits: this.dedupCount,
975
+ queueDepth: this.reduceQueue.length + this.inFlightEffects
976
+ };
914
977
  }
915
978
  /**
916
979
  * Applies an externally provided **whole-state** (e.g., DevTools time travel) and emits
@@ -919,27 +982,45 @@ class v {
919
982
  * **State Immutability**: If any slices change, a new state object is created via
920
983
  * shallow spread. This ensures consistent immutability with {@link forwardEvent}.
921
984
  *
985
+ * **Missing slices**: the snapshot should contain every slice. A slice absent
986
+ * from `nextPlain` is **retained at its current value** (not blanked to
987
+ * `undefined`, which would make `getState().<slice>` throw on next access).
988
+ *
922
989
  * @param nextPlain - Plain JS object to become the new state.
923
990
  *
924
991
  * @internal
925
992
  */
926
993
  __applyExternalState(e) {
927
- const t = this.state, s = e;
928
- let n = { ...this.state }, r = !1;
929
- Object.keys(this.reducers).forEach((o) => {
930
- const i = t?.[o], c = s?.[o];
931
- if (i === c) return;
932
- const f = w(structuredClone(c));
933
- n[o] = f, r = !0;
934
- const h = b(i, c).filter(Boolean);
994
+ if (!this.replayEnabled) {
995
+ console.warn(
996
+ "[yoltra] External state apply (time-travel) is disabled. Enable it with createStore({ devtools: { allowReplay: true } })"
997
+ );
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) {
1005
+ process.env.NODE_ENV !== "production" && console.warn(
1006
+ `[yoltra] External state is missing slice "${String(
1007
+ c
1008
+ )}"; retaining its current value. Time-travel snapshots should contain all slices.`
1009
+ );
1010
+ return;
1011
+ }
1012
+ if (i === o) return;
1013
+ const f = b(o);
1014
+ n[c] = f, s = !0;
1015
+ const h = g(i, o).filter(Boolean);
935
1016
  if (h.length === 0) return;
936
1017
  const l = /* @__PURE__ */ new Set();
937
- for (const p of h) for (const d of v.buildAncestorPaths(p)) l.add(d);
938
- for (const p of l) {
939
- const d = this.getAtPath(i, p), m = this.getAtPath(f, p);
940
- this.connectorBus.emit(o, p, { oldValue: d, newValue: m, path: p });
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 });
941
1022
  }
942
- }), r && (this.state = n), r && this.listeners.forEach((o) => o());
1023
+ }), s && (this.state = n), s && this.listeners.forEach((c) => c());
943
1024
  }
944
1025
  /**
945
1026
  * Replays a sequence of events from a snapshot through reducers and event
@@ -959,25 +1040,27 @@ class v {
959
1040
  "[yoltra] Event replay is disabled. Enable it with createStore({ devtools: { allowReplay: true } })"
960
1041
  );
961
1042
  this.__applyExternalState(e);
962
- for (const s of t) {
963
- const n = s, r = this.state;
1043
+ for (const r of t) {
1044
+ const n = r, s = this.state;
964
1045
  this.reducerBus.emit(n.channel, n.type, n.payload);
965
- for (const [c, f] of this.patternReducers)
966
- this.matchesWhen(f, n) && this.forwardEvent(c, n);
967
- const o = this.state, i = r !== o;
968
- this.notifyEventSubscribers(n, "committed"), i && this.listeners.forEach((c) => c());
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());
969
1050
  }
970
1051
  }
971
1052
  /**
972
1053
  * Emits a typed event `(channel, type, payload)`.
973
1054
  * Events are queued and processed **sequentially** (FIFO).
974
1055
  *
975
- * **Pipeline per event:**
976
- * 1. **Deduplication check** - Skip if event ID already processed (React Strict Mode safety)
977
- * 2. **Middleware** - Pre-reducer hooks; may cancel by returning `false`
978
- * 3. **Reducers** - Synchronous state updates via internal event bus
979
- * 4. **Effects** - Async side-effects keyed by `(channel, type)` for O(1) lookup
980
- * 5. **Coarse subscribers** - External store subscribers (only if state changed)
1056
+ * **Pipeline per event:** the *reduce phase* (steps 1-4) runs **synchronously**,
1057
+ * so `getState()` reflects the change as soon as `emit()` returns; the *effect
1058
+ * phase* (step 5) runs afterwards, asynchronously.
1059
+ * 1. **Deduplication** (opt-in) - Skip when content-dedup is enabled (`dedupWindowMs > 0`) or a matching `dedupKey` recurs; off by default
1060
+ * 2. **Middleware** (sync) - Pre-reducer hooks; may cancel by returning `false`
1061
+ * 3. **Reducers** (sync) - state updates + fine-grained path notifications
1062
+ * 4. **Subscribers + coarse** (sync) - event subscribers (fire-and-forget) then coarse listeners (only if state changed)
1063
+ * 5. **Effects** (async) - side-effects keyed by `(channel, type)`; the returned promise resolves once they complete
981
1064
  *
982
1065
  * **Change Detection**: Uses reference equality (`===`) on `this.state` to determine
983
1066
  * if any slice changed. Works because {@link forwardEvent} creates a new state reference
@@ -988,7 +1071,9 @@ class v {
988
1071
  * @param channel - Channel name.
989
1072
  * @param type - Event type name.
990
1073
  * @param payload - Payload typed as `EM[C][T]`.
991
- * @returns A promise that resolves when the event has finished processing.
1074
+ * @param opts - Optional per-emit options (e.g. `dedupKey` for identity-based dedup).
1075
+ * @returns A promise that resolves once this event's effects have finished.
1076
+ * State is already updated synchronously before `emit()` returns.
992
1077
  *
993
1078
  * @example Basic usage
994
1079
  * ```ts
@@ -1007,55 +1092,156 @@ class v {
1007
1092
  *
1008
1093
  * @public
1009
1094
  */
1010
- async emit(e, t, s) {
1011
- const n = this.fingerprint(e, t, s);
1012
- if (this.shouldDedupe(n))
1013
- return;
1014
- const r = crypto.randomUUID();
1015
- if (this.eventQueue.push({
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))
1100
+ return;
1101
+ }
1102
+ const i = crypto.randomUUID();
1103
+ let o;
1104
+ const f = new Promise((h) => {
1105
+ o = h;
1106
+ });
1107
+ return this.reduceQueue.push({
1016
1108
  channel: e,
1017
1109
  type: t,
1018
- payload: s,
1019
- id: r
1020
- }), !this.isProcessingQueue) {
1021
- this.isProcessingQueue = !0;
1110
+ payload: r,
1111
+ id: i,
1112
+ resolve: o
1113
+ }), this.drainReduce(), f;
1114
+ }
1115
+ /**
1116
+ * Drains the reduce queue **synchronously**. For each event it runs middleware,
1117
+ * reducers, event subscribers, and coarse listeners in the same tick, so
1118
+ * `getState()` reflects the change the moment {@link emit} returns. Re-entrant
1119
+ * emits (from middleware or subscribers) are appended and drained in the same
1120
+ * pass — preserving FIFO order without interleaving reducers. Each committed
1121
+ * event's effects then run in an independent task (see {@link runEventEffects}).
1122
+ *
1123
+ * @internal
1124
+ */
1125
+ drainReduce() {
1126
+ if (!this.isReducing) {
1127
+ this.isReducing = !0;
1022
1128
  try {
1023
- for (; this.eventQueue.length; ) {
1024
- const { channel: o, type: i, payload: c, id: f } = this.eventQueue.shift(), h = { channel: o, type: i, payload: c, id: f };
1025
- let l = !0;
1026
- for (const y of this.middleware) {
1027
- const g = this.getMiddlewareWhen(y);
1028
- if (!this.matchesWhen(g, h))
1029
- continue;
1030
- const E = this.getMiddlewareFunction(y);
1031
- try {
1032
- if (!await E(this.state, h, this.emit)) {
1033
- l = !1;
1034
- break;
1035
- }
1036
- } catch (S) {
1037
- console.error("Middleware error:", S), l = !1;
1038
- break;
1039
- }
1040
- }
1041
- if (!l) {
1042
- await this.notifyEventSubscribers(h, "uncommitted");
1043
- continue;
1129
+ 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;
1133
+ let l = !1;
1134
+ try {
1135
+ l = this.applyEventSync(c);
1136
+ } catch (u) {
1137
+ console.error("Emit reduce error:", u);
1138
+ } finally {
1139
+ i && (this.changedPathSink = null);
1044
1140
  }
1045
- const p = this.state;
1046
- this.reducerBus.emit(o, i, c);
1047
- for (const [y, g] of this.patternReducers)
1048
- this.matchesWhen(g, h) && this.forwardEvent(y, h);
1049
- const d = this.state, m = p !== d;
1050
- await this.notifyEventSubscribers(h, "committed"), await this.notifyEffects(h), m && this.listeners.forEach((y) => y());
1141
+ i && this.emitInstrumentation(
1142
+ c,
1143
+ l,
1144
+ f,
1145
+ o,
1146
+ S() - h
1147
+ ), this.runEventEffects(c, l, s);
1051
1148
  }
1052
- } catch (o) {
1053
- console.error("Emit queue error:", o);
1054
1149
  } finally {
1055
- this.isProcessingQueue = !1;
1150
+ this.isReducing = !1;
1151
+ }
1152
+ }
1153
+ }
1154
+ /**
1155
+ * Runs the **synchronous** part of the pipeline for a single event: middleware
1156
+ * (may veto), key- and pattern-based reducers, committed/uncommitted event
1157
+ * subscribers (fire-and-forget), and coarse listeners.
1158
+ *
1159
+ * @returns `true` if the event was committed (passed middleware), `false` if a
1160
+ * middleware vetoed it.
1161
+ *
1162
+ * @internal
1163
+ */
1164
+ 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;
1170
+ try {
1171
+ i = c(this.state, e, this.emit);
1172
+ } catch (o) {
1173
+ console.error("Middleware error:", o), i = !1;
1056
1174
  }
1175
+ if (!i)
1176
+ return this.notifyEventSubscribers(e, "uncommitted"), !1;
1177
+ }
1178
+ 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;
1184
+ }
1185
+ /**
1186
+ * Runs a single committed event's effects as an **independent async task**,
1187
+ * then resolves that event's completion deferred so `await emit(...)` settles
1188
+ * once its effects finish. Per-event tasks (rather than one shared serialized
1189
+ * loop) let an effect `await` a re-entrant emit without deadlocking.
1190
+ *
1191
+ * @internal
1192
+ */
1193
+ async runEventEffects(e, t, r) {
1194
+ this.inFlightEffects++;
1195
+ try {
1196
+ t && await this.notifyEffects(e);
1197
+ } catch (n) {
1198
+ console.error("Effect error:", n);
1199
+ } finally {
1200
+ this.inFlightEffects--, r();
1057
1201
  }
1058
1202
  }
1203
+ /**
1204
+ * Registers an instrumentation observer. See {@link StoreInstance.instrument}.
1205
+ *
1206
+ * @public
1207
+ */
1208
+ instrument(e) {
1209
+ return this.instrumentObservers.add(e), () => {
1210
+ this.instrumentObservers.delete(e);
1211
+ };
1212
+ }
1213
+ /**
1214
+ * Builds an {@link InstrumentedEvent} from the reduce result and notifies
1215
+ * observers. `changedPaths` are the exact slice-prefixed leaf paths recorded
1216
+ * by {@link forwardEvent} during this reduce, so DevTools patches need no
1217
+ * re-diff.
1218
+ *
1219
+ * @internal
1220
+ */
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 = {
1226
+ event: {
1227
+ id: e.id,
1228
+ channel: e.channel,
1229
+ type: e.type,
1230
+ payload: e.payload
1231
+ },
1232
+ committed: t,
1233
+ changedPaths: r,
1234
+ prevValues: c,
1235
+ nextValues: i,
1236
+ reduceTimeMs: s
1237
+ };
1238
+ for (const f of [...this.instrumentObservers])
1239
+ try {
1240
+ f(o);
1241
+ } catch (h) {
1242
+ console.error("Instrumentation observer error:", h);
1243
+ }
1244
+ }
1059
1245
  /**
1060
1246
  * Connects a **fine-grained** listener to a dotted path under a slice.
1061
1247
  *
@@ -1133,11 +1319,11 @@ class v {
1133
1319
  *
1134
1320
  * @public
1135
1321
  */
1136
- onEvent(e, t, s, n = "committed") {
1137
- const r = `${e}::${String(t)}`, o = n === "committed" ? this.committedEventSubscribers : n === "uncommitted" ? this.uncommittedEventSubscribers : this.allEventSubscribers;
1138
- return o.has(r) || o.set(r, /* @__PURE__ */ new Set()), o.get(r).add(s), () => {
1139
- const i = o.get(r);
1140
- i && (i.delete(s), i.size === 0 && o.delete(r));
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));
1141
1327
  };
1142
1328
  }
1143
1329
  /**
@@ -1234,8 +1420,8 @@ class v {
1234
1420
  if (e in this.reducers) throw new Error(`Reducer ${e} already exists`);
1235
1421
  return this.mountSlice(e, t, {
1236
1422
  preserveState: !1
1237
- }), this.listeners.forEach((s) => s()), () => {
1238
- this.unmountSlice(e, { deleteState: !0 }), this.listeners.forEach((s) => s());
1423
+ }), this.listeners.forEach((r) => r()), () => {
1424
+ this.unmountSlice(e, { deleteState: !0 }), this.listeners.forEach((r) => r());
1239
1425
  };
1240
1426
  }
1241
1427
  /**
@@ -1271,29 +1457,29 @@ class v {
1271
1457
  * @public
1272
1458
  */
1273
1459
  registerEffect(e) {
1274
- const { effect: t, meta: s, when: n } = e, r = [];
1275
- if (s && (t.__quoMeta = s), n && ("any" in n && n.any === !0 || "channel" in n || "channels" in n)) {
1276
- const c = { effect: t, when: n };
1277
- return this.patternEffects.add(c), () => {
1278
- this.patternEffects.delete(c);
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);
1279
1465
  };
1280
1466
  }
1281
1467
  const i = this.normalizeEventKeys(e);
1282
1468
  if (i.length === 0 && !n && !e.events) {
1283
- const c = { effect: t, when: { any: !0 } };
1284
- return this.patternEffects.add(c), () => {
1285
- this.patternEffects.delete(c);
1469
+ const o = { effect: t, when: { any: !0 } };
1470
+ return this.patternEffects.add(o), () => {
1471
+ this.patternEffects.delete(o);
1286
1472
  };
1287
1473
  }
1288
- for (const [c, f] of i) {
1289
- const h = `${String(c)}::${String(f)}`;
1290
- this.effects.has(h) || this.effects.set(h, /* @__PURE__ */ new Set()), this.effects.get(h).add(t), r.push(() => {
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(() => {
1291
1477
  const l = this.effects.get(h);
1292
1478
  l && (l.delete(t), l.size === 0 && this.effects.delete(h));
1293
1479
  });
1294
1480
  }
1295
1481
  return () => {
1296
- for (const c of r) c();
1482
+ for (const o of s) o();
1297
1483
  };
1298
1484
  }
1299
1485
  /**
@@ -1317,11 +1503,11 @@ class v {
1317
1503
  *
1318
1504
  * @public
1319
1505
  */
1320
- onEffect(e, t, s) {
1321
- const n = async (r, o, i) => {
1322
- if (r.channel !== e || r.type !== t) return;
1323
- const c = r;
1324
- return s(c.payload, o, i, c);
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);
1325
1511
  };
1326
1512
  return this.registerEffect({
1327
1513
  events: [[e, t]],
@@ -1387,11 +1573,11 @@ class v {
1387
1573
  * @public
1388
1574
  */
1389
1575
  replaceReducers(e, t = {}) {
1390
- const s = t.preserveState !== !1, n = new Set(Object.keys(this.reducers)), r = Object.entries(e), o = new Set(r.map(([i]) => i));
1576
+ const r = t.preserveState !== !1, n = new Set(Object.keys(this.reducers)), s = Object.entries(e), c = new Set(s.map(([i]) => i));
1391
1577
  for (const i of n)
1392
- o.has(i) || this.unmountSlice(i, { deleteState: !0 });
1393
- for (const [i, c] of r)
1394
- n.has(i) ? (this.unmountSlice(i, { deleteState: !1 }), this.mountSlice(i, c, { preserveState: s })) : this.mountSlice(i, c, { preserveState: !1 });
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 });
1395
1581
  }
1396
1582
  /**
1397
1583
  * Convenience API to replace **any subset** of store parts (HMR patterns).
@@ -1423,24 +1609,24 @@ class v {
1423
1609
  *
1424
1610
  * @internal
1425
1611
  */
1426
- mountSlice(e, t, s) {
1427
- const n = e, { events: r, reducer: o, state: i, when: c } = t;
1428
- if (this.reducers[e] = new _(o), (!s.preserveState || this.state[n] === void 0) && (this.state[n] = w(structuredClone(i))), c && ("any" in c && c.any === !0 || "channel" in c || "channels" in c)) {
1429
- this.patternReducers.set(e, c), this.sliceUnsubs.set(n, []);
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, []);
1430
1616
  return;
1431
1617
  }
1432
1618
  const h = this.normalizeEventKeys(t);
1433
- if (h.length === 0 && !c && !r) {
1619
+ if (h.length === 0 && !o && !s) {
1434
1620
  this.patternReducers.set(e, { any: !0 }), this.sliceUnsubs.set(n, []);
1435
1621
  return;
1436
1622
  }
1437
1623
  const l = [];
1438
- for (const [p, d] of h) {
1439
- const m = this.reducerBus.on(p, d, (y) => {
1440
- const g = { channel: p, type: d, payload: y, id: crypto.randomUUID() };
1441
- this.forwardEvent(e, g);
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);
1442
1628
  });
1443
- l.push(m);
1629
+ l.push(y);
1444
1630
  }
1445
1631
  this.sliceUnsubs.set(n, l);
1446
1632
  }
@@ -1454,19 +1640,19 @@ class v {
1454
1640
  * @internal
1455
1641
  */
1456
1642
  unmountSlice(e, t) {
1457
- const s = e;
1643
+ const r = e;
1458
1644
  this.patternReducers.delete(e);
1459
- const n = this.sliceUnsubs.get(s);
1645
+ const n = this.sliceUnsubs.get(r);
1460
1646
  if (n) {
1461
- for (const r of n)
1647
+ for (const s of n)
1462
1648
  try {
1463
- r();
1464
- } catch (o) {
1465
- console.error(`[Store error]: ${o}`);
1649
+ s();
1650
+ } catch (c) {
1651
+ console.error(`[Store error]: ${c}`);
1466
1652
  }
1467
- this.sliceUnsubs.delete(s);
1653
+ this.sliceUnsubs.delete(r);
1468
1654
  }
1469
- delete this.reducers[e], t.deleteState && delete this.state[s];
1655
+ delete this.reducers[e], t.deleteState && delete this.state[r];
1470
1656
  }
1471
1657
  /**
1472
1658
  * Normalizes event targeting from `when` or legacy `events` to an array of EventKeys.
@@ -1502,12 +1688,12 @@ class v {
1502
1688
  getAtPath(e, t) {
1503
1689
  if (!t) return e;
1504
1690
  const n = (t[0] === "." ? t.slice(1) : t).split(".");
1505
- let r = e;
1506
- for (const o of n) {
1507
- if (r == null) return;
1508
- r = r[o];
1691
+ let s = e;
1692
+ for (const c of n) {
1693
+ if (s == null) return;
1694
+ s = s[c];
1509
1695
  }
1510
- return r;
1696
+ return s;
1511
1697
  }
1512
1698
  /**
1513
1699
  * Builds ancestor paths for a dotted path.
@@ -1526,32 +1712,33 @@ class v {
1526
1712
  */
1527
1713
  static buildAncestorPaths(e) {
1528
1714
  if (!e) return [];
1529
- const s = (e[0] === "." ? e.slice(1) : e).split("."), n = [];
1530
- for (let r = 0; r < s.length; r++)
1531
- n.push(s.slice(0, r + 1).join("."));
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("."));
1532
1718
  return n;
1533
1719
  }
1534
1720
  }
1535
- function x(a) {
1721
+ function R(a) {
1536
1722
  return new v({
1537
1723
  name: a.name,
1538
1724
  reducer: a.reducer ?? {},
1539
1725
  middleware: a.middleware ?? [],
1540
1726
  effects: a.effects ?? [],
1541
1727
  dedupWindowMs: a.dedupWindowMs,
1542
- devtools: a.devtools
1728
+ devtools: a.devtools,
1729
+ onEffectError: a.onEffectError
1543
1730
  });
1544
1731
  }
1545
- const B = (a) => (e, t) => t.map((s) => [e, s]), $ = () => (a) => a;
1732
+ const $ = (a) => (e, t) => t.map((r) => [e, r]), B = () => (a) => a;
1546
1733
  export {
1547
- z as EventBus,
1734
+ O as EventBus,
1548
1735
  k as LooseEventBus,
1549
1736
  _ as Reducer,
1550
1737
  v as Store,
1551
- x as createStore,
1552
- b as detectChangedProps,
1553
- $ as eventKeys,
1554
- w as freezeState,
1555
- B as typedEvents
1738
+ R as createStore,
1739
+ g as detectChangedProps,
1740
+ B as eventKeys,
1741
+ E as freezeState,
1742
+ $ as typedEvents
1556
1743
  };
1557
1744
  //# sourceMappingURL=yoltra.esm.js.map