@yoltra/core 0.7.0 → 0.8.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/README.es.md CHANGED
@@ -5,8 +5,10 @@
5
5
  > 👉 🇲🇽 Versión en Español  |
6
6
  >  [ 🇺🇸 English Versión](./README.md) 
7
7
 
8
- ![npm downloads](https://badgen.net/npm/dm/@yoltra/core)
9
- ![License](https://badgen.net/npm/license/@yoltra/core)
8
+ [![versión npm](https://img.shields.io/npm/v/@yoltra/core)](https://www.npmjs.com/package/@yoltra/core)
9
+ [![descargas npm](https://img.shields.io/npm/dm/@yoltra/core)](https://www.npmjs.com/package/@yoltra/core)
10
+ [![tipos](https://img.shields.io/npm/types/@yoltra/core)](https://www.npmjs.com/package/@yoltra/core)
11
+ [![Licencia](https://img.shields.io/npm/l/@yoltra/core)](https://github.com/yoltra/yoltra/blob/main/LICENSE)
10
12
 
11
13
  **Contenedor de estado orientado a eventos, agnóstico de framework, con suscripciones de grano
12
14
  fino por ruta.**
@@ -231,7 +233,8 @@ const adminGuard: MiddlewareSpec<AppState, AppEM> = {
231
233
  meta: { type: "middleware", name: "adminGuard" },
232
234
  };
233
235
 
234
- // Middleware global: se ejecuta para todos los eventos (sincrono: devuelve un boolean, nunca una Promise)
236
+ // Middleware global: se ejecuta para todos los eventos. Sincrono, nunca una Promise: solo un
237
+ // `false` explicito veta, asi que un middleware que solo observa puede no devolver nada.
235
238
  const logger = (state, event) => {
236
239
  console.log("Event:", event.channel, event.type);
237
240
  return true;
@@ -331,6 +334,25 @@ store.onEvent(
331
334
  );
332
335
  ```
333
336
 
337
+ ### Suscriptores de eventos y viaje en el tiempo
338
+
339
+ **El replay no llama a tus handlers.** Recorrer una línea de tiempo de DevTools vuelve a reducir
340
+ los eventos, así que el estado sigue el recorrido, pero los handlers de `onEvent` permanecen en
341
+ silencio. Antes se ejecutaban igual que con un evento real, así que arrastrar la línea de tiempo
342
+ volvía a publicar a los pares, a escribir en sockets y a disparar analítica por eventos que no
343
+ estaban ocurriendo de nuevo, sin nada dentro del handler que permitiera notar la diferencia.
344
+
345
+ Un handler que deriva estado de vista puramente del flujo de eventos, y que no hace E/S, puede
346
+ activarlo:
347
+
348
+ ```ts
349
+ store.onEvent("ui", "save", handler, "committed", { duringReplay: true });
350
+ ```
351
+
352
+ `store.isReplaying` existe para lo que deba ramificar en lugar de simplemente omitirse. Los
353
+ suscriptores gruesos de `subscribe` y las suscripciones de `connect` siguen disparándose, porque
354
+ el estado sí cambió y la interfaz tiene que seguir el recorrido.
355
+
334
356
  ---
335
357
 
336
358
  ## Los commits son atómicos entre slices
@@ -577,6 +599,65 @@ const dispose = store.registerReducer("filters", {
577
599
  dispose();
578
600
  ```
579
601
 
602
+ ### Decorar un store, con sus tipos
603
+
604
+ Una slice agregada en runtime era invisible para el sistema de tipos: `registerReducer`
605
+ recibía un `string` y devolvía un disposer, así que nada aguas abajo sabía que la slice
606
+ existía ni qué forma tenía. `withSlice` devuelve **el mismo store, re-tipado**:
607
+
608
+ ```typescript
609
+ type TransferEM = { transfer: { granted: { id: string } } };
610
+
611
+ const transfers = defineSlice<TransferEM>()({
612
+ state: { granted: [] as string[] },
613
+ when: { keys: [["transfer", "granted"]] },
614
+ reducer: (s, e) => (e.type === "granted" ? { granted: [...s.granted, e.payload.id] } : s),
615
+ });
616
+
617
+ const app = store.withSlice("transfers", transfers, { owner: "@scope/transfers" });
618
+
619
+ app.getState().transfers.granted; // string[]
620
+ app.emit("transfer", "granted", { id: "a1" }); // el canal nuevo ya es emitible
621
+ ```
622
+
623
+ `withMiddleware` y `withEffect` hacen lo mismo para el mapa de eventos. Las llamadas se
624
+ encadenan, y una librería publica un decorador tomando un store y devolviendo otro:
625
+
626
+ ```typescript
627
+ export function withTransfers<R extends string, S extends Record<R, any>, EM extends EventMapBase>(
628
+ store: StoreInstance<R, S, EM>,
629
+ config: TransfersConfig,
630
+ ) {
631
+ return store.withSlice("transfers", transfers, { owner: "@scope/transfers" });
632
+ }
633
+
634
+ // Los decoradores se anidan, en cualquier orden.
635
+ const decorated = withTransfers(withDevtools(store, dtConfig), config);
636
+ ```
637
+
638
+ **Por qué los builders.** El `when` de un spec lleva cadenas de canal y tipo, no tipos de
639
+ payload, así que el mapa de eventos que aporta una decoración no puede inferirse de ahí, y
640
+ TypeScript no tiene inferencia parcial de argumentos de tipo. `defineSlice<EM>()` lo coloca en
641
+ posición de valor, donde la inferencia sí funciona, así que ningún sitio de registro necesita
642
+ un argumento de tipo ni un cast. Una consecuencia que conviene conocer: **una función de
643
+ middleware sin spec nunca puede ampliar el mapa de eventos**, porque el parámetro de evento de
644
+ `MiddlewareFunction` es un tipo mapeado del que no se puede inferir nada de vuelta. Solo la
645
+ forma de spec de `defineMiddleware` puede.
646
+
647
+ **Es el mismo objeto.** Nada se vuelve a suscribir, ningún estado se mueve, y una llamada
648
+ `store.call()` en vuelo no se ve afectada. Solo cambia el tipo.
649
+
650
+ **Orden.** Decora en el ámbito del módulo, una vez, antes del primer render. Entre
651
+ `createStore` y la decoración la slice realmente no existe, y un componente que la lea verá
652
+ `undefined` hasta que exista.
653
+
654
+ **Disposición.** `withSlice` no devuelve disposer a propósito: después de ejecutarlo, el tipo
655
+ ampliado sigue prometiendo una slice que ya no está, y ningún sistema de tipos puede expresar
656
+ "válido hasta esa llamada". Usa `registerSlice` cuando la slice sea tuya y necesites
657
+ desmontarla, y mantén ese disposer privado a la librería. Leer una slice desmontada lanza un
658
+ error con nombre en desarrollo, en lugar de devolver `undefined` desde un tipo que prometía un
659
+ valor.
660
+
580
661
  ---
581
662
 
582
663
  ## Hot Module Replacement
@@ -605,6 +686,32 @@ if (import.meta.hot) {
605
686
  }
606
687
  ```
607
688
 
689
+ ### `replace*` reemplaza lo que tú escribiste, no lo que agregó una librería
690
+
691
+ Un reducer, middleware o efecto registrado **después** de la construcción, con
692
+ `registerReducer`, `registerMiddleware` o `registerEffect`, sobrevive a una llamada a
693
+ `replace*`. Esos registros nunca formaron parte del conjunto que estás reemplazando: nadie que
694
+ escribe `replaceReducers(myReducers)` quiere decir "y ademas borra la slice que montó devtools,
695
+ junto con su estado".
696
+
697
+ Antes ocurría lo contrario, lo que hacía que la línea de HMR de arriba borrara la slice de una
698
+ librería y su estado al primer guardado de archivo, sin error y sin advertencia. Es también la
699
+ razón por la que una llamada `store.call()` en vuelo ya no muere a mitad de recarga: su
700
+ listener de respuesta pertenece al propio store.
701
+
702
+ Pasa `{ scope: "all" }` para el comportamiento anterior, que un arnés de pruebas que reinicia un
703
+ store entre casos sí puede querer:
704
+
705
+ ```typescript
706
+ store.replaceReducers(nextReducers, { scope: "all" });
707
+ store.hotReplace({ reducer: nextReducers, scope: "all" }); // se reenvía a los tres
708
+ ```
709
+
710
+ Una aplicación que declara una slice que una librería ya montó recibe un error que nombra la
711
+ slice, en lugar de una apropiación silenciosa que deja a la librería con un disposer de algo que
712
+ ya no es suyo. En desarrollo, `replace*` registra en nivel debug cuando preservó algo, así que
713
+ "por qué sigue disparándose ese efecto tras la recarga" tiene respuesta.
714
+
608
715
  ---
609
716
 
610
717
  ## Mejores Prácticas
@@ -667,7 +774,8 @@ store.registerEffect({
667
774
  | `store.getState()` | Obtener snapshot del estado actual (solo lectura) |
668
775
  | `store.subscribe(listener)` | Suscripción gruesa (cualquier cambio de estado) |
669
776
  | `store.connect(spec, handler)` | Suscripción de grano fino por ruta con wildcards |
670
- | `store.onEvent(channel, type, handler, phase?)` | Suscripción a eventos (committed/uncommitted/all) |
777
+ | `store.onEvent(channel, type, handler, phase?, options?)` | Suscripción a eventos (committed/uncommitted/written/all). Silenciosa durante el replay salvo `{ duringReplay: true }` |
778
+ | `store.onRegistrationChange(observer, opts?)` | Avisa cuando el store gana o pierde un reducer, middleware o efecto |
671
779
  | `store.onEffect(channel, type, handler)` | Shorthand de efecto para un solo evento |
672
780
  | `store.dispose()` | Limpiar timers y recursos |
673
781
 
@@ -675,6 +783,10 @@ store.registerEffect({
675
783
 
676
784
  | API | Descripción |
677
785
  | ----------------------------------- | ----------------------------------------- |
786
+ | `store.registerSlice(name, spec, opts?)` | Agrega un slice en runtime; devuelve el store re-tipado y un disposer |
787
+ | `store.withSlice(name, spec, opts?)` | Igual, devolviendo el store re-tipado para encadenar |
788
+ | `store.withMiddleware(mw)`, `store.withEffect(spec)` | Registra y amplía el mapa de eventos |
789
+ | `defineSlice<EM>()`, `defineMiddleware<EM>()`, `defineEffect<EM>()` | Declara el mapa de eventos que aporta un spec |
678
790
  | `store.registerReducer(name, spec)` | Agregar un slice en tiempo de ejecución |
679
791
  | `store.registerMiddleware(fn)` | Agregar middleware en tiempo de ejecución |
680
792
  | `store.registerEffect(spec)` | Agregar un efecto en tiempo de ejecución |
@@ -683,10 +795,10 @@ store.registerEffect({
683
795
 
684
796
  | API | Descripción |
685
797
  | --------------------------------------- | ------------------------------------------- |
686
- | `store.replaceReducers(reducers, opts)` | Reemplazar todos los reducers |
687
- | `store.replaceMiddleware(middleware)` | Reemplazar todos los middleware |
688
- | `store.replaceEffects(effects)` | Reemplazar todos los efectos |
689
- | `store.hotReplace(partial)` | Reemplazar cualquier subconjunto de una vez |
798
+ | `store.replaceReducers(reducers, opts)` | Reemplaza los reducers del spec; los de runtime sobreviven salvo `{ scope: "all" }` |
799
+ | `store.replaceMiddleware(middleware, opts)` | Reemplaza el middleware del spec; misma regla |
800
+ | `store.replaceEffects(effects, opts)` | Reemplaza los efectos del spec; misma regla |
801
+ | `store.hotReplace(partial)` | Reemplaza cualquier subconjunto; reenvía `scope` |
690
802
 
691
803
  ### Helpers
692
804
 
@@ -810,9 +922,9 @@ La cifra que importa es lo que importas, no lo que el paquete exporta:
810
922
  <!-- size-table:start -->
811
923
  | Import | Tamaño | Presupuesto |
812
924
  | --- | --- | --- |
813
- | `{ createStore }` | 8.3 KB | 14 KB |
814
- | `{ createStore, hydrate, persist }` | 9.7 KB | 16 KB |
815
- | todo | 11.2 KB | 18 KB |
925
+ | `{ createStore }` | 11.5 KB | 14 KB |
926
+ | `{ createStore, hydrate, persist }` | 12.8 KB | 16 KB |
927
+ | todo | 14.2 KB | 18 KB |
816
928
  <!-- size-table:end -->
817
929
 
818
930
  Estas son cifras de **producción**: lo que públicas una vez que tu empaquetador define
@@ -837,6 +949,10 @@ es algo que nadie escriba.
837
949
  Hooks de React y Suspense
838
950
  - **[Guia de Inicio Rápido](https://github.com/yoltra/yoltra/blob/main/docs/en/QUICK_START_GUIDE.md)**:
839
951
  Cinco pasos hacia una app funcional
952
+ - **[Actualizar a 0.8.0](https://github.com/yoltra/yoltra/blob/main/docs/es/UPGRADE_0.8.md)**:
953
+ Cinco cambios de comportamiento, y un riesgo si haces rollback
954
+ - **[Guía de Decoración](https://github.com/yoltra/yoltra/blob/main/docs/es/DECORATION_GUIDE.md)**:
955
+ Agregar una slice, middleware o efecto al store de alguien más, con los tipos
840
956
  - **[Arquitectura de Cola de Eventos](https://github.com/yoltra/yoltra/blob/main/docs/en/design/event-queue-architecture.md)**:
841
957
  Inmersión técnica profunda
842
958
  - **[Comparación de Bibliotecas](https://github.com/yoltra/yoltra/blob/main/docs/en/design/state-management-library-comparison.md)**:
package/README.md CHANGED
@@ -5,8 +5,10 @@
5
5
  > [ 🇲🇽 Versión en Español](./README.es.md)&nbsp;
6
6
  > | &nbsp; 👉 🇺🇸 English Version
7
7
 
8
- ![npm downloads](https://badgen.net/npm/dm/@yoltra/core)
9
- ![License](https://badgen.net/npm/license/@yoltra/core)
8
+ [![npm version](https://img.shields.io/npm/v/@yoltra/core)](https://www.npmjs.com/package/@yoltra/core)
9
+ [![npm downloads](https://img.shields.io/npm/dm/@yoltra/core)](https://www.npmjs.com/package/@yoltra/core)
10
+ [![types](https://img.shields.io/npm/types/@yoltra/core)](https://www.npmjs.com/package/@yoltra/core)
11
+ [![License](https://img.shields.io/npm/l/@yoltra/core)](https://github.com/yoltra/yoltra/blob/main/LICENSE)
10
12
 
11
13
  **Framework-agnostic event-driven state container with fine-grained path subscriptions.**
12
14
 
@@ -212,7 +214,10 @@ const globalLogger = {
212
214
  ## Middleware
213
215
 
214
216
  Middleware runs **synchronously, before** reducers and can cancel event propagation (return
215
- `false` to reject → "uncommitted" event). Async work belongs in effects, not middleware. Supports
217
+ `false` to reject → "uncommitted" event; returning nothing allows it). Async work belongs in
218
+ effects, not middleware. When an event does not commit, `emit` says why: `reason` is
219
+ `"vetoed"`, `"deduped"` or `"cascade"`, and a veto names the middleware in `vetoedBy`, so a
220
+ guard refusing an action is distinguishable from a double-click being collapsed. Supports
216
221
  both raw functions (legacy) and `MiddlewareSpec` objects with targeting:
217
222
 
218
223
  ```typescript
@@ -228,7 +233,8 @@ const adminGuard: MiddlewareSpec<AppState, AppEM> = {
228
233
  meta: { type: "middleware", name: "adminGuard" },
229
234
  };
230
235
 
231
- // Global middleware: runs for all events (synchronous: return a boolean, never a Promise)
236
+ // Global middleware: runs for all events. Synchronous, never a Promise: only an explicit
237
+ // `false` vetoes, so middleware that just observes can return nothing at all.
232
238
  const logger = (state, event) => {
233
239
  console.log("Event:", event.channel, event.type);
234
240
  return true;
@@ -343,6 +349,24 @@ all. `written` is the stricter fact, added rather than substituted, so toasts an
343
349
  working unchanged. `all` stays `committed | uncommitted`; folding `written` in would hand existing
344
350
  subscribers a second notification per event.
345
351
 
352
+ ### Event subscribers and time-travel
353
+
354
+ **Replay does not call your handlers.** Scrubbing a DevTools timeline reduces the events again,
355
+ so state follows the scrub, but `onEvent` handlers stay silent. They used to run exactly as they
356
+ do for a live event, which meant dragging a timeline re-published to peers, re-wrote to sockets
357
+ and re-fired analytics for events that were not happening again, with nothing available inside a
358
+ handler to tell the difference.
359
+
360
+ A handler that derives view state purely from the event stream, and performs no I/O, can opt in:
361
+
362
+ ```ts
363
+ store.onEvent("ui", "save", handler, "committed", { duringReplay: true });
364
+ ```
365
+
366
+ `store.isReplaying` is there for anything that has to branch rather than simply skip. Coarse
367
+ `subscribe` listeners and `connect` subscriptions keep firing throughout, because the state
368
+ genuinely did change and the UI has to follow the scrub.
369
+
346
370
  ---
347
371
 
348
372
  ## Commits are atomic across slices
@@ -605,6 +629,63 @@ const dispose = store.registerReducer("filters", {
605
629
  dispose();
606
630
  ```
607
631
 
632
+ ### Decorating a store, with its types
633
+
634
+ A slice added at runtime used to be invisible to the type system: `registerReducer` took a
635
+ plain `string` and returned a bare disposer, so nothing downstream knew the slice existed or
636
+ what shape it had. `withSlice` returns **the same store, re-typed**:
637
+
638
+ ```typescript
639
+ type TransferEM = { transfer: { granted: { id: string } } };
640
+
641
+ const transfers = defineSlice<TransferEM>()({
642
+ state: { granted: [] as string[] },
643
+ when: { keys: [["transfer", "granted"]] },
644
+ reducer: (s, e) => (e.type === "granted" ? { granted: [...s.granted, e.payload.id] } : s),
645
+ });
646
+
647
+ const app = store.withSlice("transfers", transfers, { owner: "@scope/transfers" });
648
+
649
+ app.getState().transfers.granted; // string[]
650
+ app.emit("transfer", "granted", { id: "a1" }); // the new channel is emittable
651
+ ```
652
+
653
+ `withMiddleware` and `withEffect` do the same for the event map. Calls chain, and a library
654
+ publishes a decorator by taking a store and returning one:
655
+
656
+ ```typescript
657
+ export function withTransfers<R extends string, S extends Record<R, any>, EM extends EventMapBase>(
658
+ store: StoreInstance<R, S, EM>,
659
+ config: TransfersConfig,
660
+ ) {
661
+ return store.withSlice("transfers", transfers, { owner: "@scope/transfers" });
662
+ }
663
+
664
+ // Decorators nest, in any order.
665
+ const decorated = withTransfers(withDevtools(store, dtConfig), config);
666
+ ```
667
+
668
+ **Why the builders.** A spec's `when` carries channel and type strings and no payload types,
669
+ so the event map a decoration contributes cannot be inferred from it, and TypeScript has no
670
+ partial type-argument inference. `defineSlice<EM>()` puts it in a value position, where
671
+ inference works, so no registration site needs a type argument or a cast. One consequence
672
+ worth knowing: **a bare middleware function can never widen the event map**, because
673
+ `MiddlewareFunction`'s event parameter is a mapped type nothing can be inferred back out of.
674
+ Only the spec form from `defineMiddleware` can.
675
+
676
+ **It is the same object.** Nothing re-subscribes, no state moves, and any in-flight
677
+ `store.call()` is unaffected. Only the type changes.
678
+
679
+ **Ordering.** Decorate at module scope, once, before the first render. Between `createStore`
680
+ and the decoration the slice genuinely does not exist, and a component reading it sees
681
+ `undefined` until it does.
682
+
683
+ **Disposal.** `withSlice` hands back no disposer on purpose: after one runs, the widened type
684
+ still promises a slice that is gone, and no type system can express "valid until that call".
685
+ Use `registerSlice` when you own the slice and need teardown, and keep that disposer private
686
+ to the library. Reading a disposed slice throws a named error in development rather than
687
+ returning `undefined` from a type that promised a value.
688
+
608
689
  ---
609
690
 
610
691
  ## Hot Module Replacement
@@ -633,6 +714,30 @@ if (import.meta.hot) {
633
714
  }
634
715
  ```
635
716
 
717
+ ### `replace*` replaces what you authored, not what a library added
718
+
719
+ A reducer, middleware or effect registered **after** construction, with `registerReducer`,
720
+ `registerMiddleware` or `registerEffect`, survives a `replace*` call. Those registrations were
721
+ never part of the set you are replacing: nobody writing `replaceReducers(myReducers)` means "and
722
+ also delete the slice devtools mounted, along with its state".
723
+
724
+ This used to go the other way, which made the HMR line above delete a library's slice and its
725
+ state on the first file save, with no error and no warning. It is also why an in-flight
726
+ `store.call()` no longer dies mid-reload: its reply listener belongs to the store itself.
727
+
728
+ Pass `{ scope: "all" }` for the old wholesale behaviour, which a test harness resetting a store
729
+ between cases may genuinely want:
730
+
731
+ ```typescript
732
+ store.replaceReducers(nextReducers, { scope: "all" });
733
+ store.hotReplace({ reducer: nextReducers, scope: "all" }); // forwards to all three
734
+ ```
735
+
736
+ An application that authors a slice a library already mounted gets an error naming the slice,
737
+ rather than a silent takeover that leaves the library holding a disposer for something no longer
738
+ its own. In development, `replace*` logs at debug level when it preserved anything, so "why is
739
+ that effect still firing after a reload" has an answer.
740
+
636
741
  ---
637
742
 
638
743
  ## Best Practices
@@ -694,7 +799,8 @@ store.registerEffect({
694
799
  | `store.getState()` | Get current readonly state snapshot |
695
800
  | `store.subscribe(listener)` | Coarse subscription (any state change) |
696
801
  | `store.connect(spec, handler)` | Fine-grained path subscription with wildcards |
697
- | `store.onEvent(channel, type, handler, phase?)` | Event subscription (committed/uncommitted/all) |
802
+ | `store.onEvent(channel, type, handler, phase?, options?)` | Event subscription (committed/uncommitted/written/all). Silent during replay unless `{ duringReplay: true }` |
803
+ | `store.onRegistrationChange(observer, opts?)` | Fires when the store gains or loses a reducer, middleware or effect |
698
804
  | `store.onEffect(channel, type, handler)` | Single-event effect shorthand |
699
805
  | `store.dispose()` | Cleanup timers and resources |
700
806
 
@@ -702,6 +808,10 @@ store.registerEffect({
702
808
 
703
809
  | API | Description |
704
810
  | ----------------------------------- | ------------------------- |
811
+ | `store.registerSlice(name, spec, opts?)` | Add a slice at runtime; returns the widened store plus a disposer |
812
+ | `store.withSlice(name, spec, opts?)` | Same, returning the widened store for chaining |
813
+ | `store.withMiddleware(mw)`, `store.withEffect(spec)` | Register and widen the event map |
814
+ | `defineSlice<EM>()`, `defineMiddleware<EM>()`, `defineEffect<EM>()` | Declare the event map a spec contributes |
705
815
  | `store.registerReducer(name, spec)` | Add a slice at runtime |
706
816
  | `store.registerMiddleware(fn)` | Add middleware at runtime |
707
817
  | `store.registerEffect(spec)` | Add an effect at runtime |
@@ -710,10 +820,10 @@ store.registerEffect({
710
820
 
711
821
  | API | Description |
712
822
  | --------------------------------------- | -------------------------- |
713
- | `store.replaceReducers(reducers, opts)` | Replace all reducers |
714
- | `store.replaceMiddleware(middleware)` | Replace all middleware |
715
- | `store.replaceEffects(effects)` | Replace all effects |
716
- | `store.hotReplace(partial)` | Replace any subset at once |
823
+ | `store.replaceReducers(reducers, opts)` | Replace spec reducers; runtime ones survive unless `{ scope: "all" }` |
824
+ | `store.replaceMiddleware(middleware, opts)` | Replace spec middleware; same rule |
825
+ | `store.replaceEffects(effects, opts)` | Replace spec effects; same rule |
826
+ | `store.hotReplace(partial)` | Replace any subset at once; forwards `scope` |
717
827
 
718
828
  ### Helpers
719
829
 
@@ -830,9 +940,9 @@ The number that matters is what you import, not what the package exports:
830
940
  <!-- size-table:start -->
831
941
  | Import | Size | Budget |
832
942
  | --- | --- | --- |
833
- | `{ createStore }` | 8.3 KB | 14 KB |
834
- | `{ createStore, hydrate, persist }` | 9.7 KB | 16 KB |
835
- | everything | 11.2 KB | 18 KB |
943
+ | `{ createStore }` | 11.5 KB | 14 KB |
944
+ | `{ createStore, hydrate, persist }` | 12.8 KB | 16 KB |
945
+ | everything | 14.2 KB | 18 KB |
836
946
  <!-- size-table:end -->
837
947
 
838
948
  These are **production** figures: what you ship once your bundler defines
@@ -861,6 +971,10 @@ stops a runaway from hanging the tab.
861
971
  React hooks and Suspense
862
972
  - **[Quick Start Guide](https://github.com/yoltra/yoltra/blob/main/docs/en/QUICK_START_GUIDE.md)**:
863
973
  Five steps to a working app
974
+ - **[Upgrading to 0.8.0](https://github.com/yoltra/yoltra/blob/main/docs/en/UPGRADE_0.8.md)**:
975
+ Five behaviour changes, and one hazard if you roll back
976
+ - **[Decoration Guide](https://github.com/yoltra/yoltra/blob/main/docs/en/DECORATION_GUIDE.md)**:
977
+ Adding a slice, middleware or effect to somebody else's store, with the types
864
978
  - **[Event Queue Architecture](https://github.com/yoltra/yoltra/blob/main/docs/en/design/event-queue-architecture.md)**:
865
979
  Technical deep-dive
866
980
  - **[Library Comparison](https://github.com/yoltra/yoltra/blob/main/docs/en/design/state-management-library-comparison.md)**:
@@ -17,7 +17,8 @@ export { detectChangedProps } from './utils/detectChangedProps.js';
17
17
  export { freezeState } from './utils/immutability.js';
18
18
  export type { AliasWatch } from './utils/immutability.js';
19
19
  export { eventKeys } from './types.js';
20
- export type { EventMapBase, EventKey, Event, EventUnion, Change, Emit, EmitOptions, EmitResult, ConnectOptions, EventMeta, InstrumentedEvent, CascadeInfo, InstrumentationObserver, Unsubscribe, StoreSpec, StoreInstance, ReducerSpec, ReducerFunction, ReducersMapAny, StateFromReducers, EMFromReducersStrict, EffectSpec, EffectFunction, MiddlewareFunction, MiddlewareSpec, MiddlewareInput, DeepReadonly, DeepRO, Primitive, RootValue, Path, PathValue, WithGlob, Dotted, EventPhase, NotifiedPhase, EventSubscriptionHandler, NarrowedEventHandler, When, EventFromWhen, EventConsumerType, EventConsumerMeta, } from './types.js';
20
+ export { defineSlice, defineMiddleware, defineEffect } from './types.js';
21
+ export type { Prettify, Merge, WidenNames, WidenState, EventMapCarrier, EMAddOf, Decoration, Decorated, StoreDecorator, Origin, RegistrationChange, RegistrationObserver, ReplaceScope, EmptyEventMap, NotCommittedReason, StateOfSpec, SatisfiesSlices, DecoratableStore, WidenedSlice, StoreDecoration, EventMapBase, EventKey, Event, EventUnion, Change, Emit, EmitOptions, EmitResult, ConnectOptions, EventMeta, InstrumentedEvent, CascadeInfo, InstrumentationObserver, Unsubscribe, StoreSpec, StoreInstance, ReducerSpec, ReducerFunction, ReducersMapAny, StateFromReducers, EMFromReducersStrict, EffectSpec, EffectFunction, MiddlewareFunction, MiddlewareSpec, MiddlewareInput, DeepReadonly, DeepRO, Primitive, RootValue, Path, PathValue, WithGlob, Dotted, EventPhase, NotifiedPhase, EventSubscriptionHandler, NarrowedEventHandler, When, EventFromWhen, EventConsumerType, EventConsumerMeta, } from './types.js';
21
22
  export { createEntityAdapter } from './entity/entityAdapter.js';
22
23
  export type { EntityAdapter, EntityAdapterOptions, EntityId, EntityState, EntityUpdate, } from './entity/entityAdapter.js';
23
24
  export { decodeState, encodeState, encodeStateBounded } from './serialize/codec.js';
@@ -20,7 +20,7 @@ export interface PersistenceAdapter {
20
20
  remove(key: string): void | Promise<void>;
21
21
  }
22
22
  /** Where a failure happened, so a handler can tell a bad write from a bad payload. */
23
- export type PersistencePhase = "read" | "write" | "decode" | "migrate";
23
+ export type PersistencePhase = "read" | "write" | "decode" | "migrate" | "encode";
24
24
  /** Shared configuration. */
25
25
  export interface PersistOptions {
26
26
  /** Storage key. */
@@ -120,4 +120,4 @@ export declare function persist(store: PersistableStore, options: PersistOptions
120
120
  *
121
121
  * @public
122
122
  */
123
- export declare function dehydrate(store: Pick<PersistableStore, "getState">, options: Pick<PersistOptions, "version" | "slices">): string;
123
+ export declare function dehydrate(store: Pick<PersistableStore, "getState">, options: Pick<PersistOptions, "version" | "slices" | "onError">): string;