@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.
package/LICENSE CHANGED
File without changes
package/README.es.md CHANGED
@@ -3,7 +3,7 @@
3
3
  # @yoltra/core
4
4
 
5
5
  > πŸ‘‰ πŸ‡²πŸ‡½ VersiΓ³n en EspaΓ±ol  |
6
- >  [ πŸ‡ΊπŸ‡Έ English Version](https://github.com/yoltra/yoltra/blob/main/packages/core/README.md) 
6
+ >  [ πŸ‡ΊπŸ‡Έ English Version](./README.md) 
7
7
 
8
8
  ![npm downloads](https://badgen.net/npm/dm/@yoltra/core)
9
9
  ![License](https://badgen.net/npm/license/@yoltra/core)
@@ -11,7 +11,7 @@
11
11
  **Contenedor de estado orientado a eventos, agnostico de framework, con suscripciones de grano
12
12
  fino por ruta.**
13
13
 
14
- `@yoltra/core` es la base de [yoltra](https://github.com/yoltra/yoltra/blob/main/README.md).
14
+ `@yoltra/core` es la base de [yoltra](../../README.md).
15
15
  Proporciona el store, el pipeline de eventos, middleware, efectos y el sistema de suscripciones
16
16
  `connect()`. Cero dependencias de framework.
17
17
 
@@ -32,22 +32,24 @@ Cada llamada a `emit()` fluye a traves de un pipeline determinista:
32
32
  ```
33
33
  emit(channel, type, payload)
34
34
  β”‚
35
- β”œβ”€ 1. Dedup ─── Omitir si la huella es identica dentro de la ventana de tiempo
35
+ β”œβ”€ 0. Dedup (opt-in) ─── Omite un duplicado solo si dedupWindowMs > 0 o se pasa un dedupKey
36
36
  β”‚
37
- β”œβ”€ 2. Middleware ─── Hooks pre-reducer (pueden rechazar β†’ evento "no confirmado")
37
+ β”‚ ══ fase de reduccion SINCRONA β€” corre antes de que emit() retorne ══
38
+ β”œβ”€ 1. Middleware ─── Hooks pre-reducer sincronos (devolver false para rechazar β†’ evento "no confirmado")
39
+ β”œβ”€ 2. Reducers ─── Actualizaciones de estado sincronas, deteccion de cambios de grano fino por ruta
40
+ β”œβ”€ 3. Suscriptores de eventos ─── Notificaciones de eventos confirmados/no confirmados
41
+ β”œβ”€ 4. Suscriptores gruesos ─── Listeners externos del store (useSyncExternalStore, etc.), si el estado cambio
38
42
  β”‚
39
- β”œβ”€ 3. Reducers ─── Actualizaciones de estado sincronas, deteccion de cambios de grano fino por ruta
40
- β”‚
41
- β”œβ”€ 4. Suscriptores de eventos ─── Notificaciones de eventos confirmados/no confirmados
42
- β”‚
43
- β”œβ”€ 5. Efectos ─── Efectos secundarios async (post-reducer, indexados para busqueda O(1))
44
- β”‚
45
- └─ 6. Suscriptores gruesos ─── Listeners externos del store (useSyncExternalStore, etc.)
43
+ └─ 5. Efectos ─── Efectos secundarios ASYNC, una tarea independiente por evento (indexados para busqueda O(1))
46
44
  ```
47
45
 
48
- Cada etapa es interceptable. El middleware puede cancelar eventos, creando eventos "no
49
- confirmados" a los que la UI aun puede reaccionar. Los efectos se ejecutan despues de los
50
- reducers y ven el estado final.
46
+ La fase de reduccion (1–4) es **sincrona**, asi que `getState()` es correcto en el instante en que
47
+ `emit()` retorna β€” incluso con middleware. Los efectos (5) corren despues como una tarea async
48
+ independiente; la promesa de `emit()` se resuelve cuando terminan los efectos de ese evento. Cada
49
+ etapa es interceptable, y `store.instrument()` expone todo el flujo β€” rutas hoja cambiadas, tiempos
50
+ de reduccion, fase confirmado/rechazado β€” a las DevTools sin ningun `as any`. Ver la
51
+ [Arquitectura del Pipeline de Eventos](../../docs/es/design/event-queue-architecture.md) para el
52
+ modelo completo.
51
53
 
52
54
  ---
53
55
 
@@ -98,7 +100,7 @@ state.counter.value = 999; // TypeError: Cannot assign to read-only property
98
100
 
99
101
  ---
100
102
 
101
- ## Targeting de Eventos con Matchers `When`
103
+ ## Consumo de Eventos con Matchers `When`
102
104
 
103
105
  Los reducers, efectos y middleware usan un matcher `When` unificado para declarar a cuales
104
106
  eventos responden:
@@ -154,8 +156,10 @@ const globalLogger = {
154
156
 
155
157
  ## Middleware
156
158
 
157
- El middleware se ejecuta **antes** de los reducers y puede cancelar la propagacion de eventos.
158
- Soporta tanto funciones directas (legacy) como objetos `MiddlewareSpec` con targeting:
159
+ El middleware se ejecuta **sincronamente, antes** de los reducers y puede cancelar la propagacion
160
+ de eventos (devolver `false` para rechazar β†’ evento "no confirmado"). El trabajo async va en los
161
+ efectos, no en el middleware. Soporta tanto funciones directas (legacy) como objetos
162
+ `MiddlewareSpec` con targeting:
159
163
 
160
164
  ```typescript
161
165
  import type { MiddlewareSpec } from "@yoltra/core";
@@ -170,8 +174,8 @@ const adminGuard: MiddlewareSpec<AppState, AppEM> = {
170
174
  meta: { type: "middleware", name: "adminGuard" },
171
175
  };
172
176
 
173
- // Middleware global β€” se ejecuta para todos los eventos
174
- const logger = async (state, event, emit) => {
177
+ // Middleware global β€” se ejecuta para todos los eventos (sincrono: devuelve un boolean, nunca una Promise)
178
+ const logger = (state, event) => {
175
179
  console.log("Event:", event.channel, event.type);
176
180
  return true;
177
181
  };
@@ -188,7 +192,7 @@ const store = createStore({
188
192
  ### Middleware dinamico
189
193
 
190
194
  ```typescript
191
- const off = store.registerMiddleware(async (state, event) => {
195
+ const off = store.registerMiddleware((state, event) => {
192
196
  return event.type !== "forbidden";
193
197
  });
194
198
  off(); // Remover despues
@@ -272,19 +276,24 @@ store.onEvent(
272
276
 
273
277
  ---
274
278
 
275
- ## Deduplicacion de Eventos
279
+ ## Deduplicacion de Eventos (opt-in)
276
280
 
277
- yoltra deduplica automaticamente eventos identicos dentro de una ventana de tiempo configurable.
278
- Esto previene el doble procesamiento en React Strict Mode:
281
+ La deduplicacion esta **desactivada por defecto** β€” yoltra nunca descarta en silencio eventos
282
+ identicos legitimos y rapidos (doble-clics, `+1` repetidos). ActΓ­vala solo cuando de verdad quieras
283
+ coalescer:
279
284
 
280
285
  ```typescript
286
+ // Por contenido: coalescer (channel, type, payload) identicos dentro de una ventana.
281
287
  const store = createStore({
282
288
  name: "App",
283
289
  reducer: {
284
290
  /* ... */
285
291
  },
286
- dedupWindowMs: 100, // default: 50ms dev, 100ms prod
292
+ dedupWindowMs: 100, // default: 0 (desactivado)
287
293
  });
294
+
295
+ // Por identidad: dedup por una clave explicita β€” p. ej. un doble-invoke de React Strict Mode en un efecto.
296
+ await store.emit("analytics", "pageView", { page }, { dedupKey: `pageView:${page}` });
288
297
  ```
289
298
 
290
299
  ---
@@ -336,16 +345,22 @@ if (import.meta.hot) {
336
345
 
337
346
  ## Mejores Practicas
338
347
 
339
- ### Siempre hacer await de `emit()`
348
+ ### El estado es sincrono; haz `await` solo por los efectos
349
+
350
+ La fase de reduccion es sincrona, asi que el estado refleja tu evento en el instante en que `emit()`
351
+ retorna β€” sin `await` para leerlo. Haz `await` de `emit()` cuando ademas quieras que los efectos de
352
+ _ese evento_ hayan terminado:
340
353
 
341
354
  ```typescript
342
- await emit("todo", "add", todo);
343
- const state = store.getState(); // Garantiza que refleja la nueva tarea
355
+ emit("todo", "add", todo);
356
+ store.getState(); // Ya refleja la nueva tarea β€” sin await
357
+
358
+ await emit("todo", "save", todo); // se resuelve cuando terminan los efectos de save
344
359
  ```
345
360
 
346
361
  ### Mantener los reducers rapidos
347
362
 
348
- Los reducers son sincronos y bloquean la cola de eventos. Mueve el trabajo costoso a los
363
+ Los reducers son sincronos y corren en el mismo tick que `emit()`. Mueve el trabajo costoso a los
349
364
  efectos:
350
365
 
351
366
  ```typescript
@@ -430,9 +445,9 @@ store.registerEffect({
430
445
 
431
446
  ## Documentacion
432
447
 
433
- - **[README raiz de yoltra](https://github.com/yoltra/yoltra/blob/main/README.md)** --
448
+ - **[README raiz de yoltra](../../README.md)** --
434
449
  Descripcion general y configuracion rapida
435
- - **[@yoltra/react](https://github.com/yoltra/yoltra/blob/main/packages/react/README.md)** --
450
+ - **[@yoltra/react](../react/README.md)** --
436
451
  Hooks de React y Suspense
437
452
  - **[Guia de Inicio Rapido](https://github.com/yoltra/yoltra/blob/main/docs/en/QUICK_START_GUIDE.md)**
438
453
  -- Cinco pasos hacia una app funcional
@@ -446,17 +461,17 @@ store.registerEffect({
446
461
  ## Ejemplos
447
462
 
448
463
  - **[App de Tareas](https://github.com/yoltra/yoltra/blob/main/examples/v0/yoltra-in-react)** --
449
- CRUD completo con perfilado de rendimiento
464
+ CRUD completo con perfilado de rendimiento Β· [β–Ά Abrir la demo en vivo](https://yoltra.dev/es/demos/in-react)
450
465
  - **[Logo Cinetico](https://github.com/yoltra/yoltra/blob/main/examples/v0/yoltra-kinetic-logo)**
451
- -- 3000 cΓ­rculos con simulaciΓ³n fΓ­sica.
466
+ -- 3000 cΓ­rculos con simulaciΓ³n fΓ­sica. Β· [β–Ά Abrir la demo en vivo](https://yoltra.dev/es/demos/kinetic-logo)
452
467
  - **[Integracion con Next.js](https://github.com/yoltra/yoltra/blob/main/examples/v0/yoltra-in-nextjs)**
453
- -- SSR + App Router + cambio de tema
468
+ -- Pages Router, estado de cliente + cambio de tema Β· [β–Ά Abrir la demo en vivo](https://yoltra.dev/es/demos/in-nextjs)
454
469
 
455
470
  ---
456
471
 
457
472
  ## Contribuir
458
473
 
459
- - [Raiz del Monorepo](https://github.com/yoltra/yoltra/blob/main/README.md)
474
+ - [Raiz del Monorepo](../../README.md)
460
475
  - [Guia de Contribucion](https://github.com/yoltra/yoltra/blob/main/CONTRIBUTING.md)
461
476
 
462
477
  ---
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @yoltra/core
4
4
 
5
- > [ πŸ‡²πŸ‡½ VersiΓ³n en EspaΓ±ol](https://github.com/yoltra/yoltra/blob/main/packages/core/README.es.md)&nbsp;
5
+ > [ πŸ‡²πŸ‡½ VersiΓ³n en EspaΓ±ol](./README.es.md)&nbsp;
6
6
  > | &nbsp; πŸ‘‰ πŸ‡ΊπŸ‡Έ English Version
7
7
 
8
8
  ![npm downloads](https://badgen.net/npm/dm/@yoltra/core)
@@ -11,7 +11,7 @@
11
11
  **Framework-agnostic event-driven state container with fine-grained path subscriptions.**
12
12
 
13
13
  `@yoltra/core` is the foundation of
14
- [yoltra](https://github.com/yoltra/yoltra/blob/main/README.md). It provides the store, event
14
+ [yoltra](../../README.md). It provides the store, event
15
15
  pipeline, middleware, effects, and the `connect()` subscription system. Zero framework
16
16
  dependencies.
17
17
 
@@ -32,21 +32,23 @@ Every `emit()` call flows through a deterministic pipeline:
32
32
  ```
33
33
  emit(channel, type, payload)
34
34
  β”‚
35
- β”œβ”€ 1. Dedup ─── Skip if identical fingerprint within time window
35
+ β”œβ”€ 0. Dedup (opt-in) ─── Skip a duplicate only when dedupWindowMs > 0 or a dedupKey is given
36
36
  β”‚
37
- β”œβ”€ 2. Middleware ─── Pre-reducer hooks (can reject β†’ "uncommitted" event)
37
+ β”‚ ══ SYNCHRONOUS reduce phase β€” runs before emit() returns ══
38
+ β”œβ”€ 1. Middleware ─── Synchronous pre-reducer hooks (return false to reject β†’ "uncommitted" event)
39
+ β”œβ”€ 2. Reducers ─── Synchronous state updates, fine-grained path change detection
40
+ β”œβ”€ 3. Event subscribers ─── Committed/uncommitted event notifications
41
+ β”œβ”€ 4. Coarse subscribers ─── External store listeners (useSyncExternalStore, etc.), if state changed
38
42
  β”‚
39
- β”œβ”€ 3. Reducers ─── Synchronous state updates, fine-grained path change detection
40
- β”‚
41
- β”œβ”€ 4. Event subscribers ─── Committed/uncommitted event notifications
42
- β”‚
43
- β”œβ”€ 5. Effects ─── Async side-effects (post-reducer, keyed for O(1) lookup)
44
- β”‚
45
- └─ 6. Coarse subscribers ─── External store listeners (useSyncExternalStore, etc.)
43
+ └─ 5. Effects ─── ASYNC side-effects, one independent task per event (keyed for O(1) lookup)
46
44
  ```
47
45
 
48
- Every stage is hook-able. Middleware can cancel events, creating "uncommitted" events that the
49
- UI can still react to. Effects run after reducers and see the final state.
46
+ The reduce phase (1–4) is **synchronous**, so `getState()` is correct the instant `emit()` returns
47
+ β€” even with middleware. Effects (5) run afterward as an independent async task; the promise from
48
+ `emit()` resolves when that event's effects finish. Every stage is hook-able, and
49
+ `store.instrument()` exposes the whole flow β€” changed leaf paths, reduce timing, committed/rejected
50
+ phase β€” to the DevTools with no `as any`. See the
51
+ [Event Pipeline Architecture](../../docs/en/design/event-queue-architecture.md) for the full model.
50
52
 
51
53
  ---
52
54
 
@@ -152,8 +154,9 @@ const globalLogger = {
152
154
 
153
155
  ## Middleware
154
156
 
155
- Middleware runs **before** reducers and can cancel event propagation. Supports both raw
156
- functions (legacy) and `MiddlewareSpec` objects with targeting:
157
+ Middleware runs **synchronously, before** reducers and can cancel event propagation (return
158
+ `false` to reject β†’ "uncommitted" event). Async work belongs in effects, not middleware. Supports
159
+ both raw functions (legacy) and `MiddlewareSpec` objects with targeting:
157
160
 
158
161
  ```typescript
159
162
  import type { MiddlewareSpec } from "@yoltra/core";
@@ -168,8 +171,8 @@ const adminGuard: MiddlewareSpec<AppState, AppEM> = {
168
171
  meta: { type: "middleware", name: "adminGuard" },
169
172
  };
170
173
 
171
- // Global middleware β€” runs for all events
172
- const logger = async (state, event, emit) => {
174
+ // Global middleware β€” runs for all events (synchronous: return a boolean, never a Promise)
175
+ const logger = (state, event) => {
173
176
  console.log("Event:", event.channel, event.type);
174
177
  return true;
175
178
  };
@@ -186,7 +189,7 @@ const store = createStore({
186
189
  ### Dynamic middleware
187
190
 
188
191
  ```typescript
189
- const off = store.registerMiddleware(async (state, event) => {
192
+ const off = store.registerMiddleware((state, event) => {
190
193
  return event.type !== "forbidden";
191
194
  });
192
195
  off(); // Remove later
@@ -269,19 +272,23 @@ store.onEvent(
269
272
 
270
273
  ---
271
274
 
272
- ## Event Deduplication
275
+ ## Event Deduplication (opt-in)
273
276
 
274
- Yoltra automatically deduplicates identical events within a configurable time window. This
275
- prevents double-processing in React Strict Mode:
277
+ Deduplication is **off by default** β€” Yoltra never silently drops legitimate rapid-fire identical
278
+ events (double-clicks, repeated `+1`). Opt in only when you actually want coalescing:
276
279
 
277
280
  ```typescript
281
+ // Content-based: coalesce identical (channel, type, payload) within a window.
278
282
  const store = createStore({
279
283
  name: "Yoltra_Rocks",
280
284
  reducer: {
281
285
  /* ... */
282
286
  },
283
- dedupWindowMs: 100, // default: 50ms dev, 100ms prod
287
+ dedupWindowMs: 100, // default: 0 (disabled)
284
288
  });
289
+
290
+ // Identity-based: dedupe by an explicit key β€” e.g. a React Strict Mode double-invoke in an effect.
291
+ await store.emit("analytics", "pageView", { page }, { dedupKey: `pageView:${page}` });
285
292
  ```
286
293
 
287
294
  ---
@@ -333,16 +340,22 @@ if (import.meta.hot) {
333
340
 
334
341
  ## Best Practices
335
342
 
336
- ### Always await `emit()`
343
+ ### State is synchronous; `await` only for effects
344
+
345
+ The reduce phase is synchronous, so state reflects your event the moment `emit()` returns β€” no
346
+ `await` needed to read it back. Await `emit()` when you also want _this event's_ effects to have
347
+ finished:
337
348
 
338
349
  ```typescript
339
- await emit("todo", "add", todo);
340
- const state = store.getState(); // Guaranteed to reflect the new todo
350
+ emit("todo", "add", todo);
351
+ store.getState(); // Already reflects the new todo β€” no await required
352
+
353
+ await emit("todo", "save", todo); // resolves once save's effects complete
341
354
  ```
342
355
 
343
356
  ### Keep reducers fast
344
357
 
345
- Reducers are synchronous and block the event queue. Move expensive work to effects:
358
+ Reducers are synchronous and run in the same tick as `emit()`. Move expensive work to effects:
346
359
 
347
360
  ```typescript
348
361
  // Reducer: just set a loading flag
@@ -426,9 +439,9 @@ store.registerEffect({
426
439
 
427
440
  ## Documentation
428
441
 
429
- - **[yoltra Root README](https://github.com/yoltra/yoltra/blob/main/README.md)** β€” Overview and
442
+ - **[yoltra Root README](../../README.md)** β€” Overview and
430
443
  quick start
431
- - **[@yoltra/react](https://github.com/yoltra/yoltra/blob/main/packages/react/README.md)** β€”
444
+ - **[@yoltra/react](../react/README.md)** β€”
432
445
  React hooks and Suspense
433
446
  - **[Quick Start Guide](https://github.com/yoltra/yoltra/blob/main/docs/en/QUICK_START_GUIDE.md)**
434
447
  β€” Five steps to a working app
@@ -442,17 +455,17 @@ store.registerEffect({
442
455
  ## Examples
443
456
 
444
457
  - **[Todo App](https://github.com/yoltra/yoltra/blob/main/examples/v0/yoltra-in-react)** β€” Full
445
- CRUD with performance profiling
458
+ CRUD with performance profiling Β· [β–Ά Open the live demo](https://yoltra.dev/en/demos/in-react)
446
459
  - **[Kinetic Logo](https://github.com/yoltra/yoltra/blob/main/examples/v0/yoltra-kinetic-logo)**
447
- β€” 3000 circles with physics simulation
460
+ β€” 3000 circles with physics simulation Β· [β–Ά Open the live demo](https://yoltra.dev/en/demos/kinetic-logo)
448
461
  - **[Next.js Integration](https://github.com/yoltra/yoltra/blob/main/examples/v0/yoltra-in-nextjs)**
449
- β€” SSR + App Router + theme switcher
462
+ β€” Pages Router, client-side state + theme switcher Β· [β–Ά Open the live demo](https://yoltra.dev/en/demos/in-nextjs)
450
463
 
451
464
  ---
452
465
 
453
466
  ## Contributing
454
467
 
455
- - [Monorepo Root](https://github.com/yoltra/yoltra/blob/main/README.md)
468
+ - [Monorepo Root](../../README.md)
456
469
  - [Contributing Guide](https://github.com/yoltra/yoltra/blob/main/CONTRIBUTING.md)
457
470
 
458
471
  ---
@@ -12,4 +12,4 @@ export { Store, createStore, typedEvents } from './store/Store';
12
12
  export { detectChangedProps } from './utils/detectChangedProps';
13
13
  export { freezeState } from './utils/immutability';
14
14
  export { eventKeys } from './types';
15
- export type { EventMapBase, EventKey, Event, EventUnion, Change, Emit, Unsubscribe, StoreSpec, StoreInstance, ReducerSpec, ReducerFunction, EffectSpec, EffectFunction, MiddlewareFunction, MiddlewareSpec, MiddlewareInput, DeepReadonly, DeepRO, Primitive, Path, PathValue, WithGlob, Dotted, EventPhase, EventSubscriptionHandler, NarrowedEventHandler, When, EventFromWhen, EventConsumerType, EventConsumerMeta, } from './types';
15
+ export type { EventMapBase, EventKey, Event, EventUnion, Change, Emit, EmitOptions, InstrumentedEvent, InstrumentationObserver, Unsubscribe, StoreSpec, StoreInstance, ReducerSpec, ReducerFunction, ReducersMapAny, StateFromReducers, EMFromReducersStrict, EffectSpec, EffectFunction, MiddlewareFunction, MiddlewareSpec, MiddlewareInput, DeepReadonly, DeepRO, Primitive, Path, PathValue, WithGlob, Dotted, EventPhase, EventSubscriptionHandler, NarrowedEventHandler, When, EventFromWhen, EventConsumerType, EventConsumerMeta, } from './types';
@@ -1,4 +1,4 @@
1
- import { Event, EventMapBase, EventKey, Change, DeepReadonly, EffectSpec, MiddlewareFunction, ReducersMapAny, ReducerSpec, StateFromReducers, StoreInstance, StoreSpec, Unsubscribe, EMFromReducersStrict, Emit, EventPhase, NarrowedEventHandler, When } from '../types';
1
+ import { Event, EventMapBase, EventKey, EventUnion, Change, DeepReadonly, EffectSpec, MiddlewareFunction, ReducersMapAny, ReducerSpec, StateFromReducers, StoreInstance, StoreSpec, Unsubscribe, EMFromReducersStrict, Emit, EmitOptions, InstrumentationObserver, EventPhase, NarrowedEventHandler, When } from '../types';
2
2
  export declare class Store<EM extends EventMapBase, R extends string, S extends Record<R, any>> implements StoreInstance<R, S, EM> {
3
3
  /**
4
4
  * Store name (used by DevTools & diagnostics).
@@ -103,17 +103,45 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
103
103
  */
104
104
  private readonly replayEnabled;
105
105
  /**
106
- * FIFO event queue for serialized emission.
106
+ * Optional hook invoked when an effect throws/rejects. See
107
+ * {@link StoreSpec.onEffectError}. `await emit()` never rejects on effect
108
+ * failure β€” this is how callers observe effect errors.
109
+ */
110
+ private readonly onEffectError?;
111
+ /**
112
+ * Pending events awaiting the **synchronous** reduce phase (middleware +
113
+ * reducers + subscribers + coarse listeners). Drained by {@link drainReduce}.
114
+ *
115
+ * @internal
116
+ */
117
+ private readonly reduceQueue;
118
+ /**
119
+ * Re-entrancy guard for the synchronous reduce phase.
120
+ *
121
+ * @internal
122
+ */
123
+ private isReducing;
124
+ /**
125
+ * Registered instrumentation observers (DevTools seam). See {@link instrument}.
126
+ *
127
+ * @internal
128
+ */
129
+ private readonly instrumentObservers;
130
+ /**
131
+ * Scratch array collecting slice-prefixed changed leaf paths during an
132
+ * instrumented reduce. Set by {@link drainReduce} while observers are active;
133
+ * appended to by {@link forwardEvent}. `null` when not instrumenting.
107
134
  *
108
135
  * @internal
109
136
  */
110
- private readonly eventQueue;
137
+ private changedPathSink;
111
138
  /**
112
- * Re-entrancy guard while draining the queue.
139
+ * Count of effect tasks currently in flight; surfaced as queue depth by
140
+ * {@link __devtoolsIntrospect}.
113
141
  *
114
142
  * @internal
115
143
  */
116
- private isProcessingQueue;
144
+ private inFlightEffects;
117
145
  /**
118
146
  * Tracks processed events by fingerprint with timestamps for TTL-based deduplication.
119
147
  *
@@ -131,6 +159,23 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
131
159
  * @internal
132
160
  */
133
161
  private readonly processedEvents;
162
+ /**
163
+ * Lifetime count of events suppressed by the deduplication cache.
164
+ * Exposed via {@link __devtoolsIntrospect} so the DevTools agent can
165
+ * surface it in the STORE_METRICS response without further core changes.
166
+ *
167
+ * @internal
168
+ */
169
+ private dedupCount;
170
+ /**
171
+ * Store-owned metadata for registered effects, keyed by the effect function.
172
+ * Kept **off** the caller's function object: mutating a user-owned function
173
+ * (the old `fn.__quoMeta`) bled metadata across stores that share a handler
174
+ * and left it attached after unregister. Cleared on {@link dispose}.
175
+ *
176
+ * @internal
177
+ */
178
+ private effectMeta;
134
179
  /**
135
180
  * Configuration for event deduplication.
136
181
  * @internal
@@ -186,6 +231,14 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
186
231
  * @internal
187
232
  */
188
233
  private shouldDedupe;
234
+ /**
235
+ * Starts the periodic prune interval if it isn't already running. Called when
236
+ * the first entry is cached so the timer's lifetime tracks actual dedup use
237
+ * (content window or identity `dedupKey`), independent of `dedupWindowMs`.
238
+ *
239
+ * @internal
240
+ */
241
+ private ensureCleanupTimer;
189
242
  /**
190
243
  * Removes expired entries from the processed events cache.
191
244
  *
@@ -250,6 +303,14 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
250
303
  * @internal
251
304
  */
252
305
  private notifyEventSubscribers;
306
+ /**
307
+ * Invokes a single event-subscription handler **fire-and-forget**: synchronous
308
+ * throws and async rejections are logged but never block the emit pipeline.
309
+ * Event subscribers are notifications, not part of the committed reduce result.
310
+ *
311
+ * @internal
312
+ */
313
+ private invokeEventSubscriber;
253
314
  /**
254
315
  * Applies a reduced event to a slice and emits **precise** connector events.
255
316
  *
@@ -303,6 +364,8 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
303
364
  phase: string;
304
365
  }[];
305
366
  coarse: number;
367
+ dedupHits: number;
368
+ queueDepth: number;
306
369
  };
307
370
  /**
308
371
  * Applies an externally provided **whole-state** (e.g., DevTools time travel) and emits
@@ -311,11 +374,15 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
311
374
  * **State Immutability**: If any slices change, a new state object is created via
312
375
  * shallow spread. This ensures consistent immutability with {@link forwardEvent}.
313
376
  *
377
+ * **Missing slices**: the snapshot should contain every slice. A slice absent
378
+ * from `nextPlain` is **retained at its current value** (not blanked to
379
+ * `undefined`, which would make `getState().<slice>` throw on next access).
380
+ *
314
381
  * @param nextPlain - Plain JS object to become the new state.
315
382
  *
316
383
  * @internal
317
384
  */
318
- private __applyExternalState;
385
+ __applyExternalState(nextPlain: any): void;
319
386
  /**
320
387
  * Replays a sequence of events from a snapshot through reducers and event
321
388
  * subscribers ONLY. Skips dedup, middleware, and effects.
@@ -338,12 +405,14 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
338
405
  * Emits a typed event `(channel, type, payload)`.
339
406
  * Events are queued and processed **sequentially** (FIFO).
340
407
  *
341
- * **Pipeline per event:**
342
- * 1. **Deduplication check** - Skip if event ID already processed (React Strict Mode safety)
343
- * 2. **Middleware** - Pre-reducer hooks; may cancel by returning `false`
344
- * 3. **Reducers** - Synchronous state updates via internal event bus
345
- * 4. **Effects** - Async side-effects keyed by `(channel, type)` for O(1) lookup
346
- * 5. **Coarse subscribers** - External store subscribers (only if state changed)
408
+ * **Pipeline per event:** the *reduce phase* (steps 1-4) runs **synchronously**,
409
+ * so `getState()` reflects the change as soon as `emit()` returns; the *effect
410
+ * phase* (step 5) runs afterwards, asynchronously.
411
+ * 1. **Deduplication** (opt-in) - Skip when content-dedup is enabled (`dedupWindowMs > 0`) or a matching `dedupKey` recurs; off by default
412
+ * 2. **Middleware** (sync) - Pre-reducer hooks; may cancel by returning `false`
413
+ * 3. **Reducers** (sync) - state updates + fine-grained path notifications
414
+ * 4. **Subscribers + coarse** (sync) - event subscribers (fire-and-forget) then coarse listeners (only if state changed)
415
+ * 5. **Effects** (async) - side-effects keyed by `(channel, type)`; the returned promise resolves once they complete
347
416
  *
348
417
  * **Change Detection**: Uses reference equality (`===`) on `this.state` to determine
349
418
  * if any slice changed. Works because {@link forwardEvent} creates a new state reference
@@ -354,7 +423,9 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
354
423
  * @param channel - Channel name.
355
424
  * @param type - Event type name.
356
425
  * @param payload - Payload typed as `EM[C][T]`.
357
- * @returns A promise that resolves when the event has finished processing.
426
+ * @param opts - Optional per-emit options (e.g. `dedupKey` for identity-based dedup).
427
+ * @returns A promise that resolves once this event's effects have finished.
428
+ * State is already updated synchronously before `emit()` returns.
358
429
  *
359
430
  * @example Basic usage
360
431
  * ```ts
@@ -373,7 +444,53 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
373
444
  *
374
445
  * @public
375
446
  */
376
- emit<C extends keyof EM & string, T extends keyof EM[C] & string>(channel: C, type: T, payload: EM[C][T]): Promise<void>;
447
+ emit<C extends keyof EM & string, T extends keyof EM[C] & string>(channel: C, type: T, payload: EM[C][T], opts?: EmitOptions): Promise<void>;
448
+ /**
449
+ * Drains the reduce queue **synchronously**. For each event it runs middleware,
450
+ * reducers, event subscribers, and coarse listeners in the same tick, so
451
+ * `getState()` reflects the change the moment {@link emit} returns. Re-entrant
452
+ * emits (from middleware or subscribers) are appended and drained in the same
453
+ * pass β€” preserving FIFO order without interleaving reducers. Each committed
454
+ * event's effects then run in an independent task (see {@link runEventEffects}).
455
+ *
456
+ * @internal
457
+ */
458
+ private drainReduce;
459
+ /**
460
+ * Runs the **synchronous** part of the pipeline for a single event: middleware
461
+ * (may veto), key- and pattern-based reducers, committed/uncommitted event
462
+ * subscribers (fire-and-forget), and coarse listeners.
463
+ *
464
+ * @returns `true` if the event was committed (passed middleware), `false` if a
465
+ * middleware vetoed it.
466
+ *
467
+ * @internal
468
+ */
469
+ private applyEventSync;
470
+ /**
471
+ * Runs a single committed event's effects as an **independent async task**,
472
+ * then resolves that event's completion deferred so `await emit(...)` settles
473
+ * once its effects finish. Per-event tasks (rather than one shared serialized
474
+ * loop) let an effect `await` a re-entrant emit without deadlocking.
475
+ *
476
+ * @internal
477
+ */
478
+ private runEventEffects;
479
+ /**
480
+ * Registers an instrumentation observer. See {@link StoreInstance.instrument}.
481
+ *
482
+ * @public
483
+ */
484
+ instrument(observer: InstrumentationObserver<EM>): Unsubscribe;
485
+ /**
486
+ * Builds an {@link InstrumentedEvent} from the reduce result and notifies
487
+ * observers. `changedPaths` are the exact slice-prefixed leaf paths recorded
488
+ * by {@link forwardEvent} during this reduce, so DevTools patches need no
489
+ * re-diff.
490
+ *
491
+ * @internal
492
+ */
493
+ private emitInstrumentation;
377
494
  /**
378
495
  * Connects a **fine-grained** listener to a dotted path under a slice.
379
496
  *
@@ -776,6 +893,7 @@ export declare function createStore<S extends Record<string, any>, EM extends Ev
776
893
  devtools?: {
777
894
  allowReplay?: boolean;
778
895
  };
896
+ onEffectError?: (error: unknown, event: EventUnion<EM>) => void;
779
897
  }): StoreInstance<keyof S & string, S, EM>;
780
898
  /**
781
899
  * Creates a store with types inferred from the reducers map.
@@ -814,6 +932,7 @@ export declare function createStore<RM extends ReducersMapAny>(cfg: {
814
932
  devtools?: {
815
933
  allowReplay?: boolean;
816
934
  };
935
+ onEffectError?: (error: unknown, event: EventUnion<EMFromReducersStrict<RM>>) => void;
817
936
  }): StoreInstance<keyof RM & string, StateFromReducers<RM>, EMFromReducersStrict<RM>>;
818
937
  /**
819
938
  * Utility to define **typed** `(channel, events[])` definitions for reducer specs.