@yoltra/core 0.1.0 β 0.2.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 +0 -0
- package/README.es.md +47 -32
- package/README.md +43 -30
- package/dist/types/index.d.ts +1 -1
- package/dist/types/store/Store.d.ts +133 -14
- package/dist/types/types.d.ts +134 -14
- package/dist/types/utils/detectChangedProps.d.ts +9 -6
- package/dist/yoltra.cjs.js +5 -2
- package/dist/yoltra.esm.js +547 -360
- package/dist/yoltra.umd.js +5 -2
- package/package.json +15 -16
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](
|
|
6
|
+
> [ πΊπΈ English Version](./README.md)
|
|
7
7
|
|
|
8
8
|

|
|
9
9
|

|
|
@@ -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](
|
|
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
|
-
ββ
|
|
35
|
+
ββ 0. Dedup (opt-in) βββ Omite un duplicado solo si dedupWindowMs > 0 o se pasa un dedupKey
|
|
36
36
|
β
|
|
37
|
-
|
|
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
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
##
|
|
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
|
|
158
|
-
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
278
|
-
|
|
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:
|
|
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
|
-
###
|
|
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
|
-
|
|
343
|
-
|
|
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
|
|
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](
|
|
448
|
+
- **[README raiz de yoltra](../../README.md)** --
|
|
434
449
|
Descripcion general y configuracion rapida
|
|
435
|
-
- **[@yoltra/react](
|
|
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
|
|
@@ -450,13 +465,13 @@ store.registerEffect({
|
|
|
450
465
|
- **[Logo Cinetico](https://github.com/yoltra/yoltra/blob/main/examples/v0/yoltra-kinetic-logo)**
|
|
451
466
|
-- 3000 cΓrculos con simulaciΓ³n fΓsica.
|
|
452
467
|
- **[Integracion con Next.js](https://github.com/yoltra/yoltra/blob/main/examples/v0/yoltra-in-nextjs)**
|
|
453
|
-
--
|
|
468
|
+
-- Pages Router, estado de cliente + cambio de tema
|
|
454
469
|
|
|
455
470
|
---
|
|
456
471
|
|
|
457
472
|
## Contribuir
|
|
458
473
|
|
|
459
|
-
- [Raiz del Monorepo](
|
|
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](
|
|
5
|
+
> [ π²π½ VersiΓ³n en EspaΓ±ol](./README.es.md)
|
|
6
6
|
> | π πΊπΈ English Version
|
|
7
7
|
|
|
8
8
|

|
|
@@ -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](
|
|
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
|
-
ββ
|
|
35
|
+
ββ 0. Dedup (opt-in) βββ Skip a duplicate only when dedupWindowMs > 0 or a dedupKey is given
|
|
36
36
|
β
|
|
37
|
-
|
|
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
|
-
|
|
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
|
-
|
|
49
|
-
|
|
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
|
|
156
|
-
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
275
|
-
|
|
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:
|
|
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
|
-
###
|
|
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
|
-
|
|
340
|
-
|
|
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
|
|
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](
|
|
442
|
+
- **[yoltra Root README](../../README.md)** β Overview and
|
|
430
443
|
quick start
|
|
431
|
-
- **[@yoltra/react](
|
|
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
|
|
@@ -446,13 +459,13 @@ store.registerEffect({
|
|
|
446
459
|
- **[Kinetic Logo](https://github.com/yoltra/yoltra/blob/main/examples/v0/yoltra-kinetic-logo)**
|
|
447
460
|
β 3000 circles with physics simulation
|
|
448
461
|
- **[Next.js Integration](https://github.com/yoltra/yoltra/blob/main/examples/v0/yoltra-in-nextjs)**
|
|
449
|
-
β
|
|
462
|
+
β Pages Router, client-side state + theme switcher
|
|
450
463
|
|
|
451
464
|
---
|
|
452
465
|
|
|
453
466
|
## Contributing
|
|
454
467
|
|
|
455
|
-
- [Monorepo Root](
|
|
468
|
+
- [Monorepo Root](../../README.md)
|
|
456
469
|
- [Contributing Guide](https://github.com/yoltra/yoltra/blob/main/CONTRIBUTING.md)
|
|
457
470
|
|
|
458
471
|
---
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
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
|
|
137
|
+
private changedPathSink;
|
|
111
138
|
/**
|
|
112
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
-
*
|
|
343
|
-
*
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
-
*
|
|
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
|
-
* @
|
|
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.
|