@yoltra/react 0.6.0 → 0.7.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 +70 -71
- package/README.md +34 -34
- package/dist/index.cjs +4 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +295 -307
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -5
package/README.es.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# @yoltra/react
|
|
4
4
|
|
|
5
5
|
> 👉 🇲🇽 Versión en Español |
|
|
6
|
-
> [ 🇺🇸 English
|
|
6
|
+
> [ 🇺🇸 English Versión](./README.md)
|
|
7
7
|
|
|
8
8
|

|
|
9
9
|

|
|
@@ -11,14 +11,14 @@
|
|
|
11
11
|
**Hooks de React para [yoltra](../../README.md) con
|
|
12
12
|
suscripciones de grano fino por ruta.**
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
esa ruta exacta cambia. Sin selectores, sin
|
|
14
|
+
Suscríbete a `"items.0.title"` o `"items.*.done"`. El componente se re-renderiza solo cuando
|
|
15
|
+
esa ruta exacta cambia. Sin selectores, sin memoización, sin optimización manual.
|
|
16
16
|
|
|
17
|
-
[Ver la
|
|
17
|
+
[Ver la comparación de flamegraph (Redux vs yoltra).](https://github.com/yoltra/yoltra/blob/main/examples/v0/yoltra-in-react/redux-yoltra-profiler.md)
|
|
18
18
|
|
|
19
19
|
---
|
|
20
20
|
|
|
21
|
-
##
|
|
21
|
+
## Instalación
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
24
|
npm install @yoltra/core @yoltra/react
|
|
@@ -28,11 +28,11 @@ npm install @yoltra/core @yoltra/react
|
|
|
28
28
|
|
|
29
29
|
---
|
|
30
30
|
|
|
31
|
-
##
|
|
31
|
+
## Configuración con `createYoltra` (recomendado)
|
|
32
32
|
|
|
33
|
-
`createYoltra` crea el store **y** todos los hooks tipados en una sola llamada
|
|
34
|
-
context aparte, sin cableado de `createHooks`, sin provider obligatorio. Todos los
|
|
35
|
-
tipo se infieren de tu reducer,
|
|
33
|
+
`createYoltra` crea el store **y** todos los hooks tipados en una sola llamada, sin archivo de
|
|
34
|
+
context aparte, sin cableado de `createHooks`, sin provider obligatorio. Todos los parámetros de
|
|
35
|
+
tipo se infieren de tu reducer, así que los componentes no necesitan generics explícitos.
|
|
36
36
|
|
|
37
37
|
### 1. Crea el store y los hooks
|
|
38
38
|
|
|
@@ -74,10 +74,10 @@ export const { store, useAtomicProp, useEmit, StoreProvider } = createYoltra({
|
|
|
74
74
|
});
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
### 2. Usa los hooks
|
|
77
|
+
### 2. Usa los hooks, sin provider
|
|
78
78
|
|
|
79
|
-
Los hooks usan por defecto el store de arriba,
|
|
80
|
-
|
|
79
|
+
Los hooks usan por defecto el store de arriba, así que puedes renderizar componentes directamente.
|
|
80
|
+
Suscríbete con una spec **`{ reducer, property }`**: el `property` con puntos nombra la ruta exacta
|
|
81
81
|
a leer.
|
|
82
82
|
|
|
83
83
|
```tsx
|
|
@@ -85,7 +85,7 @@ a leer.
|
|
|
85
85
|
import { useAtomicProp, useEmit } from "./yoltra";
|
|
86
86
|
|
|
87
87
|
export function Counter() {
|
|
88
|
-
// Forma objeto
|
|
88
|
+
// Forma objeto: se re-renderiza solo cuando counter.value cambia. Sin selectores, sin memo.
|
|
89
89
|
const value = useAtomicProp({ reducer: "counter", property: "value" });
|
|
90
90
|
const emit = useEmit();
|
|
91
91
|
|
|
@@ -101,13 +101,13 @@ export function Counter() {
|
|
|
101
101
|
```
|
|
102
102
|
|
|
103
103
|
Un `<StoreProvider>` solo se necesita para acotar una instancia **diferente** del store a un
|
|
104
|
-
subarbol (p. ej. un store nuevo por test)
|
|
104
|
+
subarbol (p. ej. un store nuevo por test). `createYoltra` devuelve uno justo para eso.
|
|
105
105
|
|
|
106
106
|
---
|
|
107
107
|
|
|
108
108
|
## Avanzado: cableado manual con `createHooks`
|
|
109
109
|
|
|
110
|
-
Cuando necesites un mismo conjunto de hooks compartido entre varias instancias de store a
|
|
110
|
+
Cuando necesites un mismo conjunto de hooks compartido entre varias instancias de store a través
|
|
111
111
|
de tu propio context de React, vinculalos tu mismo con `createHooks(context)`. `createYoltra` es
|
|
112
112
|
este mismo cableado colapsado en una sola llamada.
|
|
113
113
|
|
|
@@ -135,7 +135,7 @@ export const {
|
|
|
135
135
|
} = createHooks(AppStoreContext);
|
|
136
136
|
```
|
|
137
137
|
|
|
138
|
-
Provee el store con `<AppStoreContext.Provider value={store}>` en tu
|
|
138
|
+
Provee el store con `<AppStoreContext.Provider value={store}>` en tu raíz.
|
|
139
139
|
|
|
140
140
|
---
|
|
141
141
|
|
|
@@ -143,21 +143,21 @@ Provee el store con `<AppStoreContext.Provider value={store}>` en tu raiz.
|
|
|
143
143
|
|
|
144
144
|
### `useAtomicProp({ reducer, property }, map?, isEqual?)`
|
|
145
145
|
|
|
146
|
-
Selector de ruta
|
|
147
|
-
`property` con puntos nombra la ruta exacta
|
|
146
|
+
Selector de ruta única con grano fino. Se re-renderiza solo cuando la hoja especificada cambia. El
|
|
147
|
+
`property` con puntos nombra la ruta exacta, incluyendo rutas dinámicas
|
|
148
148
|
(`` `items.${id}.title` ``) y con comodines.
|
|
149
149
|
|
|
150
150
|
```tsx
|
|
151
|
-
// Forma objeto (recomendada)
|
|
151
|
+
// Forma objeto (recomendada): suscribete a la ruta exacta
|
|
152
152
|
const title = useAtomicProp({ reducer: "todos", property: "items.0.title" });
|
|
153
153
|
|
|
154
|
-
// Ruta dinamica
|
|
154
|
+
// Ruta dinamica: interpola la clave
|
|
155
155
|
const byId = useAtomicProp({ reducer: "todos", property: `items.${id}.title` });
|
|
156
156
|
|
|
157
|
-
// Con mapper
|
|
157
|
+
// Con mapper: derivar un valor de la ruta
|
|
158
158
|
const count = useAtomicProp({ reducer: "todos", property: "items" }, (items) => items.length);
|
|
159
159
|
|
|
160
|
-
// Patron wildcard
|
|
160
|
+
// Patron wildcard: se re-renderiza cuando cualquier item cambia
|
|
161
161
|
const allTitles = useAtomicProp(
|
|
162
162
|
{ reducer: "todos", property: "items.**" },
|
|
163
163
|
(state) => state.items.map((t) => t.title),
|
|
@@ -165,20 +165,20 @@ const allTitles = useAtomicProp(
|
|
|
165
165
|
);
|
|
166
166
|
```
|
|
167
167
|
|
|
168
|
-
>
|
|
169
|
-
>
|
|
168
|
+
> También existe una sobrecarga con accessor tipado, `useAtomicProp("todos", (s) => s.items[0].title)`,
|
|
169
|
+
> para rutas estáticas; autocompleta la forma del estado e infiere el tipo de retorno.
|
|
170
170
|
|
|
171
171
|
**Patrones soportados:**
|
|
172
172
|
|
|
173
|
-
- `"items.0.title"
|
|
174
|
-
- `"items.*.title"
|
|
175
|
-
- `"items.**"
|
|
173
|
+
- `"items.0.title"`: ruta exacta (incluyendo índices numéricos de array)
|
|
174
|
+
- `"items.*.title"`: `*` coincide con un segmento
|
|
175
|
+
- `"items.**"`: `**` coincide con cero o más segmentos
|
|
176
176
|
|
|
177
177
|
---
|
|
178
178
|
|
|
179
179
|
### `useAtomicProps(specs, selector, isEqual?)`
|
|
180
180
|
|
|
181
|
-
Selector de
|
|
181
|
+
Selector de múltiples rutas. Se suscribe a varias rutas y recalcula cuando alguna cambia.
|
|
182
182
|
|
|
183
183
|
```tsx
|
|
184
184
|
const filtered = useAtomicProps(
|
|
@@ -195,16 +195,15 @@ const filtered = useAtomicProps(
|
|
|
195
195
|
|
|
196
196
|
### `useEvent(channel, type, handler, phase?)`
|
|
197
197
|
|
|
198
|
-
|
|
199
|
-
fire-and-forget.
|
|
198
|
+
Suscríbete a eventos del store desde un componente. No afecta el flujo de eventos. Es fire-and-forget.
|
|
200
199
|
|
|
201
200
|
```tsx
|
|
202
|
-
// Eventos confirmados (por defecto)
|
|
201
|
+
// Eventos confirmados (por defecto): eventos que pasaron el middleware
|
|
203
202
|
useEvent("ui", "save", (event) => {
|
|
204
203
|
showToast("Saved!");
|
|
205
204
|
});
|
|
206
205
|
|
|
207
|
-
// Eventos no confirmados
|
|
206
|
+
// Eventos no confirmados: eventos rechazados por el middleware
|
|
208
207
|
useEvent(
|
|
209
208
|
"ui",
|
|
210
209
|
"delete",
|
|
@@ -214,7 +213,7 @@ useEvent(
|
|
|
214
213
|
"uncommitted",
|
|
215
214
|
);
|
|
216
215
|
|
|
217
|
-
// Todos los eventos
|
|
216
|
+
// Todos los eventos: distinguir por fase
|
|
218
217
|
useEvent(
|
|
219
218
|
"ui",
|
|
220
219
|
"action",
|
|
@@ -227,15 +226,15 @@ useEvent(
|
|
|
227
226
|
|
|
228
227
|
**Fases:**
|
|
229
228
|
|
|
230
|
-
- `'committed'` (por defecto)
|
|
231
|
-
- `'uncommitted'
|
|
232
|
-
- `'all'
|
|
229
|
+
- `'committed'` (por defecto): eventos que pasaron el middleware y llegaron a los reducers
|
|
230
|
+
- `'uncommitted'`: eventos rechazados por el middleware
|
|
231
|
+
- `'all'`: ambos, con parámetro `phase` para distinguir
|
|
233
232
|
|
|
234
233
|
---
|
|
235
234
|
|
|
236
235
|
### `useEmit()`
|
|
237
236
|
|
|
238
|
-
Retorna la
|
|
237
|
+
Retorna la función `emit` tipada del store (referencia estable).
|
|
239
238
|
|
|
240
239
|
```tsx
|
|
241
240
|
const emit = useEmit();
|
|
@@ -246,7 +245,7 @@ await emit("counter", "increment", 1);
|
|
|
246
245
|
|
|
247
246
|
### `useSelector(selector, isEqual?)`
|
|
248
247
|
|
|
249
|
-
Selector de grano grueso
|
|
248
|
+
Selector de grano grueso vía `useSyncExternalStore`. Se re-renderiza cuando el valor
|
|
250
249
|
seleccionado cambia.
|
|
251
250
|
|
|
252
251
|
```tsx
|
|
@@ -270,7 +269,7 @@ const value = store.getState().counter.value;
|
|
|
270
269
|
```
|
|
271
270
|
|
|
272
271
|
`getState()` es una lectura, no una suscripción. Llamado durante el render, el componente se
|
|
273
|
-
renderiza una vez con ese valor y nunca más
|
|
272
|
+
renderiza una vez con ese valor y nunca más, porque nada le avisó de que el valor cambió. Parece que
|
|
274
273
|
funciona hasta que el estado cambia y la pantalla no. Lee con `useAtomicProp` o `useSelector` lo
|
|
275
274
|
que vayas a renderizar, y deja `getState()` para callbacks y efectos, que es para lo que es.
|
|
276
275
|
|
|
@@ -280,8 +279,8 @@ que vayas a renderizar, y deja `getState()` para callbacks y efectos, que es par
|
|
|
280
279
|
|
|
281
280
|
### `useSuspenseAtomicProp(spec, options)`
|
|
282
281
|
|
|
283
|
-
|
|
284
|
-
por el boundary `<Suspense>`
|
|
282
|
+
Versión compatible con Suspense de `useAtomicProp`. Lanza una promesa mientras carga, capturada
|
|
283
|
+
por el boundary `<Suspense>` más cercano.
|
|
285
284
|
|
|
286
285
|
```tsx
|
|
287
286
|
function UserName({ userId }: { userId: string }) {
|
|
@@ -303,7 +302,7 @@ function UserName({ userId }: { userId: string }) {
|
|
|
303
302
|
|
|
304
303
|
### `useSuspenseAtomicProps(specs, options)`
|
|
305
304
|
|
|
306
|
-
Selector Suspense de
|
|
305
|
+
Selector Suspense de múltiples rutas.
|
|
307
306
|
|
|
308
307
|
```tsx
|
|
309
308
|
const stats = useSuspenseAtomicProps(
|
|
@@ -318,11 +317,11 @@ const stats = useSuspenseAtomicProps(
|
|
|
318
317
|
### Importalos de tu conjunto de hooks, no del barrel
|
|
319
318
|
|
|
320
319
|
`createYoltra` y `createHooks` devuelven estos dos junto con el resto, ligados al mismo contexto.
|
|
321
|
-
Deliberadamente **no** se exportan desde el barrel del paquete: una copia a nivel de paquete
|
|
322
|
-
|
|
323
|
-
|
|
320
|
+
Deliberadamente **no** se exportan desde el barrel del paquete: una copia a nivel de paquete sería
|
|
321
|
+
idéntica en forma y aun así lanzaría `useStore must be used inside <StoreProvider>` en tiempo de
|
|
322
|
+
ejecución cuando el contexto que lee nunca se lleno, un error que los tipos no podian atrapar.
|
|
324
323
|
Importarlos desde cualquier sitio que no sea el resultado de tu propio `createYoltra`/`createHooks`
|
|
325
|
-
es ahora un error de
|
|
324
|
+
es ahora un error de compilación, que es el mismo aviso llegando en el momento correcto.
|
|
326
325
|
|
|
327
326
|
```tsx
|
|
328
327
|
// store.ts
|
|
@@ -332,8 +331,8 @@ export const { store, useAtomicProp, useSuspenseAtomicProp } = createYoltra({ ..
|
|
|
332
331
|
import { useSuspenseAtomicProp } from "./store"; // ✅ conoce el store
|
|
333
332
|
```
|
|
334
333
|
|
|
335
|
-
Los valores en cache tienen alcance por store,
|
|
336
|
-
y ruta mantienen entradas separadas; las utilidades de
|
|
334
|
+
Los valores en cache tienen alcance por store, así que dos stores que compartan nombre de reducer
|
|
335
|
+
y ruta mantienen entradas separadas; las utilidades de invalidación de abajo reciben una ruta y la
|
|
337
336
|
limpian en todos los stores que la hayan cacheado.
|
|
338
337
|
|
|
339
338
|
### Utilidades de cache
|
|
@@ -359,7 +358,7 @@ clearSuspenseCache();
|
|
|
359
358
|
|
|
360
359
|
## `shallowEqual`
|
|
361
360
|
|
|
362
|
-
Comparador de igualdad superficial de objetos.
|
|
361
|
+
Comparador de igualdad superficial de objetos. Úsalo como argumento `isEqual` cuando tu valor
|
|
363
362
|
derivado es un objeto plano:
|
|
364
363
|
|
|
365
364
|
```tsx
|
|
@@ -372,7 +371,7 @@ const todos = useAtomicProp(
|
|
|
372
371
|
|
|
373
372
|
---
|
|
374
373
|
|
|
375
|
-
## Rendimiento: Antes y
|
|
374
|
+
## Rendimiento: Antes y Después
|
|
376
375
|
|
|
377
376
|
### Antes (grano grueso)
|
|
378
377
|
|
|
@@ -384,7 +383,7 @@ function TodoList() {
|
|
|
384
383
|
}
|
|
385
384
|
```
|
|
386
385
|
|
|
387
|
-
###
|
|
386
|
+
### Después (grano fino con yoltra)
|
|
388
387
|
|
|
389
388
|
```tsx
|
|
390
389
|
// Cada TodoItem se re-renderiza SOLO cuando sus propios datos cambian
|
|
@@ -401,14 +400,14 @@ function TodoItem({ index }: { index: number }) {
|
|
|
401
400
|
}
|
|
402
401
|
```
|
|
403
402
|
|
|
404
|
-
[Ver la
|
|
403
|
+
[Ver la comparación completa de flamegraph.](https://github.com/yoltra/yoltra/blob/main/examples/v0/yoltra-in-react/redux-yoltra-profiler.md)
|
|
405
404
|
|
|
406
405
|
---
|
|
407
406
|
|
|
408
407
|
## Compatibilidad con React 18+
|
|
409
408
|
|
|
410
409
|
- **Concurrent Mode:** Totalmente compatible. Todos los hooks usan `useSyncExternalStore`.
|
|
411
|
-
- **Strict Mode:** La
|
|
410
|
+
- **Strict Mode:** La deduplicación de eventos previene el doble procesamiento.
|
|
412
411
|
- **Suspense:** `useSuspenseAtomicProp` y `useSuspenseAtomicProps` lanzan promesas para
|
|
413
412
|
boundaries `<Suspense>`.
|
|
414
413
|
|
|
@@ -416,37 +415,37 @@ function TodoItem({ index }: { index: number }) {
|
|
|
416
415
|
|
|
417
416
|
## Ejemplos
|
|
418
417
|
|
|
419
|
-
- **[App de Tareas con Profiler](../../examples/v0/yoltra-in-react)
|
|
420
|
-
|
|
421
|
-
- **[Logo
|
|
418
|
+
- **[App de Tareas con Profiler](../../examples/v0/yoltra-in-react)**: CRUD completo con
|
|
419
|
+
comparación de flamegraph · [▶ Abrir la demo en vivo](https://yoltra.dev/es/demos/in-react)
|
|
420
|
+
- **[Logo Cinético (3000 particulas)](../../examples/v0/yoltra-kinetic-logo)**: Suscripciones
|
|
422
421
|
independientes por circulo SVG · [▶ Abrir la demo en vivo](https://yoltra.dev/es/demos/kinetic-logo)
|
|
423
|
-
- **[Next.js (Pages Router)](../../examples/v0/yoltra-in-nextjs)
|
|
422
|
+
- **[Next.js (Pages Router)](../../examples/v0/yoltra-in-nextjs)**: estado de cliente + cambio de tema · [▶ Abrir la demo en vivo](https://yoltra.dev/es/demos/in-nextjs)
|
|
424
423
|
|
|
425
424
|
---
|
|
426
425
|
|
|
427
|
-
##
|
|
426
|
+
## Documentación
|
|
428
427
|
|
|
429
|
-
- **[README
|
|
430
|
-
|
|
431
|
-
- **[API de @yoltra/core](../core/README.md)
|
|
432
|
-
|
|
433
|
-
- **[Guia de Inicio
|
|
434
|
-
|
|
435
|
-
- **[
|
|
436
|
-
|
|
428
|
+
- **[README raíz de yoltra](../../README.md)**:
|
|
429
|
+
Descripción general y configuración rápida
|
|
430
|
+
- **[API de @yoltra/core](../core/README.md)**:
|
|
431
|
+
Store, middleware, efectos, matchers `When`
|
|
432
|
+
- **[Guia de Inicio Rápido](https://github.com/yoltra/yoltra/blob/main/docs/en/QUICK_START_GUIDE.md)**:
|
|
433
|
+
Cinco pasos hacia una app funcional
|
|
434
|
+
- **[Comparación de Bibliotecas](https://github.com/yoltra/yoltra/blob/main/docs/en/design/state-management-library-comparison.md)**:
|
|
435
|
+
Comparación arquitectónica
|
|
437
436
|
|
|
438
437
|
---
|
|
439
438
|
|
|
440
439
|
## Contribuir
|
|
441
440
|
|
|
442
|
-
- [
|
|
443
|
-
- [Guia de
|
|
441
|
+
- [Raíz del Monorepo](../../)
|
|
442
|
+
- [Guia de Contribución](../../CONTRIBUTING.md)
|
|
444
443
|
|
|
445
444
|
---
|
|
446
445
|
|
|
447
446
|
## Estado
|
|
448
447
|
|
|
449
|
-
**Release Candidate
|
|
448
|
+
**Release Candidate**. Las APIs son estables, usadas en producción, cambios menores posibles
|
|
450
449
|
antes de v1.0.0.
|
|
451
450
|
|
|
452
451
|
---
|
|
@@ -454,7 +453,7 @@ antes de v1.0.0.
|
|
|
454
453
|
## Colecciones normalizadas
|
|
455
454
|
|
|
456
455
|
`useEntityIds`, `useEntity` y `useEntityField` se emparejan con `createEntityAdapter` de
|
|
457
|
-
`@yoltra/core`. Son envoltorios delgados sobre `useAtomicProp`; el valor
|
|
456
|
+
`@yoltra/core`. Son envoltorios delgados sobre `useAtomicProp`; el valor está en que la ruta viene
|
|
458
457
|
del adapter en vez de escribirse a mano en un componente, donde nada la verifica.
|
|
459
458
|
|
|
460
459
|
```tsx
|
|
@@ -472,4 +471,4 @@ function Row({ id }: { id: string }) {
|
|
|
472
471
|
|
|
473
472
|
## Licencia
|
|
474
473
|
|
|
475
|
-
**MIT
|
|
474
|
+
**MIT**. Libre para usar en proyectos comerciales y de código abierto.
|
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
**React hooks for [yoltra](../../README.md) with
|
|
12
12
|
fine-grained path subscriptions.**
|
|
13
13
|
|
|
14
|
-
Subscribe to `"items.0.title"` or `"items.*.done"
|
|
14
|
+
Subscribe to `"items.0.title"` or `"items.*.done"`. The component re-renders only when that
|
|
15
15
|
exact path changes. No selectors, no memoization, no manual optimization.
|
|
16
16
|
|
|
17
17
|
[See the flamegraph comparison (Redux vs yoltra).](https://github.com/yoltra/yoltra/blob/main/examples/v0/yoltra-in-react/redux-yoltra-profiler.md)
|
|
@@ -30,7 +30,7 @@ npm install @yoltra/core @yoltra/react
|
|
|
30
30
|
|
|
31
31
|
## Setup with `createYoltra` (recommended)
|
|
32
32
|
|
|
33
|
-
`createYoltra` creates the store **and** every fully-typed hook in one call
|
|
33
|
+
`createYoltra` creates the store **and** every fully-typed hook in one call, with no separate context
|
|
34
34
|
file, no `createHooks` wiring, no required provider. All type parameters are inferred from your
|
|
35
35
|
reducer, so components need no explicit generics.
|
|
36
36
|
|
|
@@ -74,7 +74,7 @@ export const { store, useAtomicProp, useEmit, StoreProvider } = createYoltra({
|
|
|
74
74
|
});
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
### 2. Use the hooks
|
|
77
|
+
### 2. Use the hooks, no provider required
|
|
78
78
|
|
|
79
79
|
The hooks default to the store above, so you can render components directly. Subscribe with a
|
|
80
80
|
**`{ reducer, property }`** spec: the dotted `property` names the exact path to read.
|
|
@@ -84,7 +84,7 @@ The hooks default to the store above, so you can render components directly. Sub
|
|
|
84
84
|
import { useAtomicProp, useEmit } from "./yoltra";
|
|
85
85
|
|
|
86
86
|
export function Counter() {
|
|
87
|
-
// Object form
|
|
87
|
+
// Object form: re-renders only when counter.value changes. No selectors, no memo.
|
|
88
88
|
const value = useAtomicProp({ reducer: "counter", property: "value" });
|
|
89
89
|
const emit = useEmit();
|
|
90
90
|
|
|
@@ -100,7 +100,7 @@ export function Counter() {
|
|
|
100
100
|
```
|
|
101
101
|
|
|
102
102
|
A `<StoreProvider>` is only needed to scope a **different** store instance to a subtree (e.g. a
|
|
103
|
-
fresh store per test)
|
|
103
|
+
fresh store per test). `createYoltra` returns one for exactly that.
|
|
104
104
|
|
|
105
105
|
---
|
|
106
106
|
|
|
@@ -143,19 +143,19 @@ Provide the store with `<AppStoreContext.Provider value={store}>` at your root.
|
|
|
143
143
|
### `useAtomicProp({ reducer, property }, map?, isEqual?)`
|
|
144
144
|
|
|
145
145
|
Fine-grained single-path selector. Re-renders only when the specified leaf changes. The dotted
|
|
146
|
-
`property` names the exact path
|
|
146
|
+
`property` names the exact path, including dynamic (`` `items.${id}.title` ``) and wildcard paths.
|
|
147
147
|
|
|
148
148
|
```tsx
|
|
149
|
-
// Object form (recommended)
|
|
149
|
+
// Object form (recommended): subscribe to the exact path
|
|
150
150
|
const title = useAtomicProp({ reducer: "todos", property: "items.0.title" });
|
|
151
151
|
|
|
152
|
-
// Dynamic path
|
|
152
|
+
// Dynamic path: interpolate the key
|
|
153
153
|
const byId = useAtomicProp({ reducer: "todos", property: `items.${id}.title` });
|
|
154
154
|
|
|
155
|
-
// With mapper
|
|
155
|
+
// With mapper: derive a value from the path
|
|
156
156
|
const count = useAtomicProp({ reducer: "todos", property: "items" }, (items) => items.length);
|
|
157
157
|
|
|
158
|
-
// Wildcard pattern
|
|
158
|
+
// Wildcard pattern: re-renders when any item changes
|
|
159
159
|
const allTitles = useAtomicProp(
|
|
160
160
|
{ reducer: "todos", property: "items.**" },
|
|
161
161
|
(state) => state.items.map((t) => t.title),
|
|
@@ -163,14 +163,14 @@ const allTitles = useAtomicProp(
|
|
|
163
163
|
);
|
|
164
164
|
```
|
|
165
165
|
|
|
166
|
-
> A typed-accessor overload
|
|
166
|
+
> A typed-accessor overload, `useAtomicProp("todos", (s) => s.items[0].title)`, is also available
|
|
167
167
|
> for static paths; it autocompletes the state shape and infers the return type.
|
|
168
168
|
|
|
169
169
|
**Supported patterns:**
|
|
170
170
|
|
|
171
|
-
- `"items.0.title"
|
|
172
|
-
- `"items.*.title"
|
|
173
|
-
- `"items.**"
|
|
171
|
+
- `"items.0.title"`: exact path (including numeric array indices)
|
|
172
|
+
- `"items.*.title"`: `*` matches one segment
|
|
173
|
+
- `"items.**"`: `**` matches zero or more segments
|
|
174
174
|
|
|
175
175
|
---
|
|
176
176
|
|
|
@@ -193,15 +193,15 @@ const filtered = useAtomicProps(
|
|
|
193
193
|
|
|
194
194
|
### `useEvent(channel, type, handler, phase?)`
|
|
195
195
|
|
|
196
|
-
Subscribe to store events from a component. Does not affect event flow
|
|
196
|
+
Subscribe to store events from a component. Does not affect event flow. Fire-and-forget.
|
|
197
197
|
|
|
198
198
|
```tsx
|
|
199
|
-
// Committed events (default)
|
|
199
|
+
// Committed events (default): events that passed middleware
|
|
200
200
|
useEvent("ui", "save", (event) => {
|
|
201
201
|
showToast("Saved!");
|
|
202
202
|
});
|
|
203
203
|
|
|
204
|
-
// Uncommitted events
|
|
204
|
+
// Uncommitted events: events rejected by middleware
|
|
205
205
|
useEvent(
|
|
206
206
|
"ui",
|
|
207
207
|
"delete",
|
|
@@ -211,7 +211,7 @@ useEvent(
|
|
|
211
211
|
"uncommitted",
|
|
212
212
|
);
|
|
213
213
|
|
|
214
|
-
// All events
|
|
214
|
+
// All events: distinguish by phase
|
|
215
215
|
useEvent(
|
|
216
216
|
"ui",
|
|
217
217
|
"action",
|
|
@@ -224,9 +224,9 @@ useEvent(
|
|
|
224
224
|
|
|
225
225
|
**Phases:**
|
|
226
226
|
|
|
227
|
-
- `'committed'` (default)
|
|
228
|
-
- `'uncommitted'
|
|
229
|
-
- `'all'
|
|
227
|
+
- `'committed'` (default): events that passed middleware and reached reducers
|
|
228
|
+
- `'uncommitted'`: events rejected by middleware
|
|
229
|
+
- `'all'`: both, with `phase` parameter to distinguish
|
|
230
230
|
|
|
231
231
|
---
|
|
232
232
|
|
|
@@ -266,7 +266,7 @@ const value = store.getState().counter.value;
|
|
|
266
266
|
```
|
|
267
267
|
|
|
268
268
|
`getState()` is a read, not a subscription. Called while rendering, the component renders once
|
|
269
|
-
with that value and never again
|
|
269
|
+
with that value and never again, because nothing told it the value moved. It looks like it works right
|
|
270
270
|
up until the state changes and the screen does not. Read what you render with `useAtomicProp` or
|
|
271
271
|
`useSelector`, and keep `getState()` for callbacks and effects, which is what it is for.
|
|
272
272
|
|
|
@@ -316,7 +316,7 @@ const stats = useSuspenseAtomicProps(
|
|
|
316
316
|
`createYoltra` and `createHooks` return these two alongside the rest, bound to the same context.
|
|
317
317
|
They are deliberately **not** exported from the package barrel: a package-level copy would be
|
|
318
318
|
identical in shape and still throw `useStore must be used inside <StoreProvider>` at runtime
|
|
319
|
-
whenever the context it reads was never filled
|
|
319
|
+
whenever the context it reads was never filled, a mistake the types could not catch. Importing
|
|
320
320
|
them from anywhere but your own `createYoltra`/`createHooks` result is now a compile error,
|
|
321
321
|
which is the same warning arriving at the right time.
|
|
322
322
|
|
|
@@ -430,24 +430,24 @@ function Row({ id }: { id: string }) {
|
|
|
430
430
|
|
|
431
431
|
## Examples
|
|
432
432
|
|
|
433
|
-
- **[Todo App with Profiler](../../examples/v0/yoltra-in-react)
|
|
433
|
+
- **[Todo App with Profiler](../../examples/v0/yoltra-in-react)**: Full CRUD with flamegraph
|
|
434
434
|
comparison · [▶ Open the live demo](https://yoltra.dev/en/demos/in-react)
|
|
435
|
-
- **[Kinetic Logo (3000 particles)](../../examples/v0/yoltra-kinetic-logo)
|
|
435
|
+
- **[Kinetic Logo (3000 particles)](../../examples/v0/yoltra-kinetic-logo)**: Independent
|
|
436
436
|
subscriptions per circle · [▶ Open the live demo](https://yoltra.dev/en/demos/kinetic-logo)
|
|
437
|
-
- **[Next.js (Pages Router)](../../examples/v0/yoltra-in-nextjs)
|
|
437
|
+
- **[Next.js (Pages Router)](../../examples/v0/yoltra-in-nextjs)**: client-side state + theme switcher · [▶ Open the live demo](https://yoltra.dev/en/demos/in-nextjs)
|
|
438
438
|
|
|
439
439
|
---
|
|
440
440
|
|
|
441
441
|
## Documentation
|
|
442
442
|
|
|
443
|
-
- **[yoltra Root README](../../README.md)
|
|
443
|
+
- **[yoltra Root README](../../README.md)**: Overview and
|
|
444
444
|
quick start
|
|
445
|
-
- **[@yoltra/core API](../core/README.md)
|
|
445
|
+
- **[@yoltra/core API](../core/README.md)**:
|
|
446
446
|
Store, middleware, effects, `When` matchers
|
|
447
|
-
- **[Quick Start Guide](https://github.com/yoltra/yoltra/blob/main/docs/en/QUICK_START_GUIDE.md)
|
|
448
|
-
|
|
449
|
-
- **[Library Comparison](https://github.com/yoltra/yoltra/blob/main/docs/en/design/state-management-library-comparison.md)
|
|
450
|
-
|
|
447
|
+
- **[Quick Start Guide](https://github.com/yoltra/yoltra/blob/main/docs/en/QUICK_START_GUIDE.md)**:
|
|
448
|
+
Five steps to a working app
|
|
449
|
+
- **[Library Comparison](https://github.com/yoltra/yoltra/blob/main/docs/en/design/state-management-library-comparison.md)**:
|
|
450
|
+
Architectural comparison
|
|
451
451
|
|
|
452
452
|
---
|
|
453
453
|
|
|
@@ -460,11 +460,11 @@ function Row({ id }: { id: string }) {
|
|
|
460
460
|
|
|
461
461
|
## Status
|
|
462
462
|
|
|
463
|
-
**Release Candidate
|
|
463
|
+
**Release Candidate**. APIs are stable, used in production, minor changes possible before
|
|
464
464
|
v1.0.0.
|
|
465
465
|
|
|
466
466
|
---
|
|
467
467
|
|
|
468
468
|
## License
|
|
469
469
|
|
|
470
|
-
**MIT
|
|
470
|
+
**MIT**. Free to use in commercial and open-source projects.
|
package/dist/index.cjs
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* @yoltra/react v0.
|
|
2
|
+
* @yoltra/react v0.7.0
|
|
3
3
|
* (c) 2026 Manu Ramirez <@pixerael>
|
|
4
|
-
* License: MIT
|
|
5
|
-
|
|
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
|
|
9
|
-
*/
|
|
10
|
-
"use strict";var Ee=Object.defineProperty;var Re=(t,e,n)=>e in t?Ee(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n;var ce=(t,e,n)=>Re(t,typeof e!="symbol"?e+"":e,n);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const u=require("react"),Ae=require("@yoltra/core"),K=u.createContext(null);var W={exports:{}},D={};var ue;function Pe(){if(ue)return D;ue=1;var t=Symbol.for("react.transitional.element"),e=Symbol.for("react.fragment");function n(o,s,c){var a=null;if(c!==void 0&&(a=""+c),s.key!==void 0&&(a=""+s.key),"key"in s){c={};for(var h in s)h!=="key"&&(c[h]=s[h])}else c=s;return s=c.ref,{$$typeof:t,type:o,key:a,ref:s!==void 0?s:null,props:c}}return D.Fragment=e,D.jsx=n,D.jsxs=n,D}var F={};var ae;function ge(){return ae||(ae=1,process.env.NODE_ENV!=="production"&&(function(){function t(r){if(r==null)return null;if(typeof r=="function")return r.$$typeof===V?null:r.displayName||r.name||null;if(typeof r=="string")return r;switch(r){case y:return"Fragment";case k:return"Profiler";case b:return"StrictMode";case $:return"Suspense";case I:return"SuspenseList";case O:return"Activity"}if(typeof r=="object")switch(typeof r.tag=="number"&&console.error("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."),r.$$typeof){case v:return"Portal";case P:return(r.displayName||"Context")+".Provider";case _:return(r._context.displayName||"Context")+".Consumer";case j:var i=r.render;return r=r.displayName,r||(r=i.displayName||i.name||"",r=r!==""?"ForwardRef("+r+")":"ForwardRef"),r;case M:return i=r.displayName||null,i!==null?i:t(r.type)||"Memo";case A:i=r._payload,r=r._init;try{return t(r(i))}catch{}}return null}function e(r){return""+r}function n(r){try{e(r);var i=!1}catch{i=!0}if(i){i=console;var p=i.error,E=typeof Symbol=="function"&&Symbol.toStringTag&&r[Symbol.toStringTag]||r.constructor.name||"Object";return p.call(i,"The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",E),e(r)}}function o(r){if(r===y)return"<>";if(typeof r=="object"&&r!==null&&r.$$typeof===A)return"<...>";try{var i=t(r);return i?"<"+i+">":"<...>"}catch{return"<...>"}}function s(){var r=w.A;return r===null?null:r.getOwner()}function c(){return Error("react-stack-top-frame")}function a(r){if(B.call(r,"key")){var i=Object.getOwnPropertyDescriptor(r,"key").get;if(i&&i.isReactWarning)return!1}return r.key!==void 0}function h(r,i){function p(){te||(te=!0,console.error("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",i))}p.isReactWarning=!0,Object.defineProperty(r,"key",{get:p,configurable:!0})}function S(){var r=t(this.type);return re[r]||(re[r]=!0,console.error("Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release.")),r=this.props.ref,r!==void 0?r:null}function g(r,i,p,E,C,x,X,H){return p=x.ref,r={$$typeof:f,type:r,key:i,props:x,_owner:C},(p!==void 0?p:null)!==null?Object.defineProperty(r,"ref",{enumerable:!1,get:S}):Object.defineProperty(r,"ref",{enumerable:!1,value:null}),r._store={},Object.defineProperty(r._store,"validated",{configurable:!1,enumerable:!1,writable:!0,value:0}),Object.defineProperty(r,"_debugInfo",{configurable:!1,enumerable:!1,writable:!0,value:null}),Object.defineProperty(r,"_debugStack",{configurable:!1,enumerable:!1,writable:!0,value:X}),Object.defineProperty(r,"_debugTask",{configurable:!1,enumerable:!1,writable:!0,value:H}),Object.freeze&&(Object.freeze(r.props),Object.freeze(r)),r}function d(r,i,p,E,C,x,X,H){var R=i.children;if(R!==void 0)if(E)if(J(R)){for(E=0;E<R.length;E++)l(R[E]);Object.freeze&&Object.freeze(R)}else console.error("React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead.");else l(R);if(B.call(i,"key")){R=t(r);var Y=Object.keys(i).filter(function(be){return be!=="key"});E=0<Y.length?"{key: someKey, "+Y.join(": ..., ")+": ...}":"{key: someKey}",se[R+E]||(Y=0<Y.length?"{"+Y.join(": ..., ")+": ...}":"{}",console.error(`A props object containing a "key" prop is being spread into JSX:
|
|
4
|
+
* License: MIT */
|
|
5
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const c=require("react"),be=require("@yoltra/core"),K=c.createContext(null);var W={exports:{}},D={};var ue;function Ee(){if(ue)return D;ue=1;var t=Symbol.for("react.transitional.element"),e=Symbol.for("react.fragment");function o(n,s,u){var a=null;if(u!==void 0&&(a=""+u),s.key!==void 0&&(a=""+s.key),"key"in s){u={};for(var m in s)m!=="key"&&(u[m]=s[m])}else u=s;return s=u.ref,{$$typeof:t,type:n,key:a,ref:s!==void 0?s:null,props:u}}return D.Fragment=e,D.jsx=o,D.jsxs=o,D}var F={};var ce;function Re(){return ce||(ce=1,process.env.NODE_ENV!=="production"&&(function(){function t(r){if(r==null)return null;if(typeof r=="function")return r.$$typeof===U?null:r.displayName||r.name||null;if(typeof r=="string")return r;switch(r){case h:return"Fragment";case k:return"Profiler";case P:return"StrictMode";case $:return"Suspense";case I:return"SuspenseList";case O:return"Activity"}if(typeof r=="object")switch(typeof r.tag=="number"&&console.error("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."),r.$$typeof){case v:return"Portal";case A:return(r.displayName||"Context")+".Provider";case _:return(r._context.displayName||"Context")+".Consumer";case j:var i=r.render;return r=r.displayName,r||(r=i.displayName||i.name||"",r=r!==""?"ForwardRef("+r+")":"ForwardRef"),r;case M:return i=r.displayName||null,i!==null?i:t(r.type)||"Memo";case R:i=r._payload,r=r._init;try{return t(r(i))}catch{}}return null}function e(r){return""+r}function o(r){try{e(r);var i=!1}catch{i=!0}if(i){i=console;var p=i.error,b=typeof Symbol=="function"&&Symbol.toStringTag&&r[Symbol.toStringTag]||r.constructor.name||"Object";return p.call(i,"The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",b),e(r)}}function n(r){if(r===h)return"<>";if(typeof r=="object"&&r!==null&&r.$$typeof===R)return"<...>";try{var i=t(r);return i?"<"+i+">":"<...>"}catch{return"<...>"}}function s(){var r=w.A;return r===null?null:r.getOwner()}function u(){return Error("react-stack-top-frame")}function a(r){if(V.call(r,"key")){var i=Object.getOwnPropertyDescriptor(r,"key").get;if(i&&i.isReactWarning)return!1}return r.key!==void 0}function m(r,i){function p(){te||(te=!0,console.error("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",i))}p.isReactWarning=!0,Object.defineProperty(r,"key",{get:p,configurable:!0})}function y(){var r=t(this.type);return re[r]||(re[r]=!0,console.error("Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release.")),r=this.props.ref,r!==void 0?r:null}function g(r,i,p,b,C,x,X,H){return p=x.ref,r={$$typeof:f,type:r,key:i,props:x,_owner:C},(p!==void 0?p:null)!==null?Object.defineProperty(r,"ref",{enumerable:!1,get:y}):Object.defineProperty(r,"ref",{enumerable:!1,value:null}),r._store={},Object.defineProperty(r._store,"validated",{configurable:!1,enumerable:!1,writable:!0,value:0}),Object.defineProperty(r,"_debugInfo",{configurable:!1,enumerable:!1,writable:!0,value:null}),Object.defineProperty(r,"_debugStack",{configurable:!1,enumerable:!1,writable:!0,value:X}),Object.defineProperty(r,"_debugTask",{configurable:!1,enumerable:!1,writable:!0,value:H}),Object.freeze&&(Object.freeze(r.props),Object.freeze(r)),r}function d(r,i,p,b,C,x,X,H){var E=i.children;if(E!==void 0)if(b)if(B(E)){for(b=0;b<E.length;b++)l(E[b]);Object.freeze&&Object.freeze(E)}else console.error("React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead.");else l(E);if(V.call(i,"key")){E=t(r);var Y=Object.keys(i).filter(function(ve){return ve!=="key"});b=0<Y.length?"{key: someKey, "+Y.join(": ..., ")+": ...}":"{key: someKey}",se[E+b]||(Y=0<Y.length?"{"+Y.join(": ..., ")+": ...}":"{}",console.error(`A props object containing a "key" prop is being spread into JSX:
|
|
11
6
|
let props = %s;
|
|
12
7
|
<%s {...props} />
|
|
13
8
|
React keys must be passed directly to JSX without using spread:
|
|
14
9
|
let props = %s;
|
|
15
|
-
<%s key={someKey} {...props} />`,E,R,Y,R),se[R+E]=!0)}if(R=null,p!==void 0&&(n(p),R=""+p),a(i)&&(n(i.key),R=""+i.key),"key"in i){p={};for(var Z in i)Z!=="key"&&(p[Z]=i[Z])}else p=i;return R&&h(p,typeof r=="function"?r.displayName||r.name||"Unknown":r),g(r,R,x,C,s(),p,X,H)}function l(r){typeof r=="object"&&r!==null&&r.$$typeof===f&&r._store&&(r._store.validated=1)}var m=u,f=Symbol.for("react.transitional.element"),v=Symbol.for("react.portal"),y=Symbol.for("react.fragment"),b=Symbol.for("react.strict_mode"),k=Symbol.for("react.profiler"),_=Symbol.for("react.consumer"),P=Symbol.for("react.context"),j=Symbol.for("react.forward_ref"),$=Symbol.for("react.suspense"),I=Symbol.for("react.suspense_list"),M=Symbol.for("react.memo"),A=Symbol.for("react.lazy"),O=Symbol.for("react.activity"),V=Symbol.for("react.client.reference"),w=m.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,B=Object.prototype.hasOwnProperty,J=Array.isArray,q=console.createTask?console.createTask:function(){return null};m={react_stack_bottom_frame:function(r){return r()}};var te,re={},ne=m.react_stack_bottom_frame.bind(m,c)(),oe=q(o(c)),se={};F.Fragment=y,F.jsx=function(r,i,p,E,C){var x=1e4>w.recentlyCreatedOwnerStacks++;return d(r,i,p,!1,E,C,x?Error("react-stack-top-frame"):ne,x?q(o(r)):oe)},F.jsxs=function(r,i,p,E,C){var x=1e4>w.recentlyCreatedOwnerStacks++;return d(r,i,p,!0,E,C,x?Error("react-stack-top-frame"):ne,x?q(o(r)):oe)}})()),F}var ie;function _e(){return ie||(ie=1,process.env.NODE_ENV==="production"?W.exports=Pe():W.exports=ge()),W.exports}var de=_e();const we=({store:t,children:e})=>de.jsx(K.Provider,{value:t,children:e}),le=new Set;function xe(t,e){process.env.NODE_ENV!=="production"&&(le.has(t)||(le.add(t),console.warn(e)))}function G(t){return t.includes("*")}function T(t){return t.replace(/^\./,"")}function Te(t){return T(t).split(".").filter(Boolean)}function ke(t){const e=n=>`${n.length}:${n}`;return t.map(n=>{const o=Array.isArray(n.property)?n.property:[n.property];return e(n.reducer)+e(String(o.length))+o.map(e).join("")}).join("")}const me=Symbol("yoltra.pathSegments");function he(t){const e=()=>{};return new Proxy(e,{get(n,o){if(o===me)return t;if(typeof o!="symbol")return he([...t,String(o)])},apply(){throw new Error(`[yoltra] A typed path accessor called a method (near "${t.join(".")}"). The accessor must be a plain member chain like \`p => p.items[0].title\` — it cannot call functions such as \`.map()\` or \`.toString()\`. Compute derived values in the component or a selector, or use the \`{ reducer, property }\` string form.`)}})}function je(t){const e=t(he([])),n=e!=null?e[me]:void 0,s=(Array.isArray(n)?n:[]).join(".");return s===""&&xe("yoltra.toDottedPath.empty","[yoltra] A typed path accessor recorded no property access, so it subscribed to the slice root. That fires only when the slice's whole value is replaced — which for a slice holding an object means never, since its changes are reported at their leaves. The accessor must be a plain member chain like `p => p.items[0].title` and cannot return a computed value or a default. For a dynamic subscription, or for a slice whose state is a single value, use the `{ reducer, property }` string form instead."),s}function L(t,e){if(!e)return t;let n=t;for(const o of Te(e)){if(n==null)return;n=n[o]}return n}function U(t,e,n){const o=u.useRef(t);o.current=t;const s=u.useRef(e);s.current=e;const c=u.useMemo(()=>({has:!1,value:void 0}),n);return u.useCallback(()=>{const a=o.current();return(!c.has||!s.current(c.value,a))&&(c.has=!0,c.value=a),c.value},[c])}function ye(t,e){if(Object.is(t,e))return!0;if(!t||!e)return!1;const n=Object.keys(t),o=Object.keys(e);if(n.length!==o.length)return!1;for(const s of n)if(!Object.is(t[s],e[s]))return!1;return!0}function z(){const t=u.useContext(K);if(!t)throw new Error("useStore must be used inside <StoreProvider>");return t}function Oe(){return z().emit}function Ce(t,e=Object.is){const n=z(),o=u.useMemo(()=>c=>n.subscribe(c),[n]),s=U(()=>t(n.getState()),e,[n]);return u.useSyncExternalStore(o,s,s)}function Ne(t,e,n=Object.is){const o=z(),s=u.useMemo(()=>{const S=T(t.property);return{reducer:t.reducer,property:S}},[t.reducer,t.property]),c=u.useMemo(()=>S=>o.connect({reducer:s.reducer,property:s.property},()=>S()),[o,s]),a=G(s.property),h=U(()=>{const g=o.getState()[s.reducer];return a?g:L(g,s.property)},n,[o,s]);return u.useSyncExternalStore(c,h,h)}function Q(t,e,n=Object.is){return Ne(t,e,n)}function Me(t,e,n,o="committed"){const s=z(),c=u.useRef(n);c.current=n,u.useEffect(()=>s.onEvent(t,e,(a,h,S,g)=>{c.current(a,h,S,g)},o),[s,t,e,o])}function $e(t){return t==null||t<=0?null:Date.now()+t}function Ie(t){return t===null?null:t===void 0||t<=0?0:Date.now()+t}const Ye=2e3;class De{constructor(){ce(this,"store",new Map)}touch(e){this.store.has(e)&&this.store.delete(e)}evict(){for(;this.store.size>Ye;){const e=this.store.keys().next();if(e.done===!0)return;const n=this.store.get(e.value);if((n==null?void 0:n.status)==="pending"){this.store.delete(e.value),this.store.set(e.value,n);continue}this.store.delete(e.value)}}get size(){return this.store.size}read(e,n,o,s){const c=Date.now(),a=this.store.get(e);if(a&&a.status==="ready"&&(a.expiresAt==null||a.expiresAt>c))return this.touch(e),this.store.set(e,a),a.value;if(a&&a.status==="pending")throw a.promise;if(a&&a.status==="error"){if(a.delivered!==!0)throw this.store.set(e,{...a,delivered:!0}),a.error;if(a.expiresAt===null||a.expiresAt>c)throw a.error;this.store.delete(e)}const h=Promise.resolve().then(n).then(S=>{this.store.set(e,{status:"ready",value:S,expiresAt:$e(o)})}).catch(S=>{this.store.set(e,{status:"error",error:S,expiresAt:Ie(s)})});throw this.store.set(e,{status:"pending",promise:h,expiresAt:null}),this.evict(),h}invalidate(e){this.store.delete(e)}invalidatePathKey(e){for(const n of this.store.keys())pe(n)===e&&this.store.delete(n)}invalidateReducer(e){const n=`${e}::`;for(const o of this.store.keys())pe(o).split("||").some(s=>s.startsWith(n))&&this.store.delete(o)}clear(){this.store.clear()}}const N=new De,fe=new WeakMap;let Fe=0;function Se(t){let e=fe.get(t);return e===void 0&&(e=`s${++Fe}`,fe.set(t,e)),e}function pe(t){return t.slice(t.indexOf("::")+2)}function ee(t,e,n){const o=Array.isArray(e)?e.map(T).sort().join("|"):T(e);return n?`${t}::${o}::${n}`:`${t}::${o}`}function ze(t,e,n){const o=t(),s=e.reducer,c=T(e.property),a=`${Se(o)}::${ee(s,c,n.key)}`,h=u.useMemo(()=>l=>o.connect({reducer:s,property:c},()=>{N.invalidate(a),l()}),[o,s,c,a]),S=u.useRef(n);S.current=n;const g=u.useMemo(()=>{const l=G(c);return()=>{var b;const f=o.getState()[s],v=l?f:L(f,c),y=S.current;return N.read(a,()=>y.load(v,f),(b=y.staleTime)!=null?b:0,y.errorTtlMs)}},[o,s,c,a]),d=u.useMemo(()=>{const l=G(c);return()=>{const f=o.getState()[s];return l?f:L(f,c)}},[o,s,c]);return u.useSyncExternalStore(h,g,d)}function We(t,e,n){const o=t(),s=u.useMemo(()=>e.map(d=>({reducer:d.reducer,property:Array.isArray(d.property)?d.property.map(l=>T(l)):T(d.property)})),[JSON.stringify(e)]),c=u.useMemo(()=>{const d=s.map(l=>ee(l.reducer,l.property)).sort().join("||");return`${Se(o)}::${n.key?`${d}::${n.key}`:d}`},[o,s,n.key]),a=u.useMemo(()=>d=>{const l=()=>{N.invalidate(c),d()},m=s.map(f=>o.connect(f,l));return()=>{for(const f of m)f()}},[o,s,c]),h=u.useRef(n);h.current=n;const S=u.useMemo(()=>()=>{var m;const d=o.getState(),l=h.current;return N.read(c,()=>l.load(d),(m=l.staleTime)!=null?m:0,l.errorTtlMs)},[o,c]),g=()=>{const d=h.current.load(o.getState());return d instanceof Promise?void 0:d};return u.useSyncExternalStore(a,S,g)}function Ge(t,e,n){N.invalidatePathKey(ee(t,e,n))}function Le(t){N.invalidateReducer(t)}function Ue(){N.clear()}function Ve(t){return{useSuspenseAtomicProp:(o,s)=>ze(t,o,s),useSuspenseAtomicProps:(o,s)=>We(t,o,s)}}function ve(t){function e(){const l=u.useContext(t);if(!l)throw new Error("[yoltra] No store in context. Wrap your tree in <StoreProvider store={...}>, or use the hooks returned by createYoltra (which default to their own store).");return l}function n(){return e().emit}function o(l,m=Object.is){const f=e(),v=u.useMemo(()=>b=>f.subscribe(b),[f]),y=U(()=>l(f.getState()),m,[f]);return u.useSyncExternalStore(v,y,y)}const c=(l,m,f)=>{const v=e(),y=typeof l=="string",b=y?l:l.reducer,k=y?je(m):l.property,_=y?void 0:m,P=u.useMemo(()=>({reducer:b,property:T(k)}),[b,k]),j=u.useMemo(()=>M=>v.connect({reducer:P.reducer,property:P.property},()=>M()),[v,P]),$=G(P.property),I=U(()=>{const A=v.getState()[P.reducer],O=$?A:L(A,P.property);return _?_(O):O},f!=null?f:Object.is,[v,P]);return u.useSyncExternalStore(j,I,I)},h=(l,m,f=Object.is)=>{const v=e(),y=u.useRef(0),b=u.useRef(void 0),k=u.useRef(-1),_=u.useRef(!1),P=u.useRef(m);P.current=m;const j=u.useRef(f);j.current=f;const $=u.useMemo(()=>l.map(A=>({reducer:A.reducer,property:Array.isArray(A.property)?A.property.map(O=>T(O)):T(A.property)})),[ke(l)]),I=u.useMemo(()=>A=>{const O=()=>{y.current++,A()},V=$.flatMap(w=>(Array.isArray(w.property)?w.property:[w.property]).map(J=>v.connect({reducer:w.reducer,property:J},O)));return()=>{for(const w of V)w()}},[v,$]),M=u.useCallback(()=>{if(k.current!==y.current||!_.current){const A=P.current(v.getState());(!_.current||!j.current(b.current,A))&&(b.current=A,_.current=!0),k.current=y.current}return b.current},[v]);return u.useSyncExternalStore(I,M,M)},S=(l,m,f,v="committed")=>{const y=e(),b=u.useRef(f);b.current=f,u.useEffect(()=>y.onEvent(l,m,(k,_,P,j)=>{b.current(k,_,P,j)},v),[y,l,m,v])},{useSuspenseAtomicProp:g,useSuspenseAtomicProps:d}=Ve(e);return{useStore:e,useEmit:n,useSelector:o,useAtomicProp:c,useAtomicProps:h,useEvent:S,useSuspenseAtomicProp:g,useSuspenseAtomicProps:d,shallowEqual:ye}}function Be(t){const e=Ae.createStore(t),n=u.createContext(e),o=ve(n);return{store:e,StoreContext:n,StoreProvider:({store:c,children:a})=>de.jsx(n.Provider,{value:c!=null?c:e,children:a}),...o}}function Je(t,e){return Q({reducer:t,property:e.idsPath})}function qe(t,e,n){return Q({reducer:t,property:e.pathTo(n)})}function Xe(t,e,n,o){return Q({reducer:t,property:e.pathTo(n,o)})}exports.StoreContext=K;exports.StoreProvider=we;exports.clearSuspenseCache=Ue;exports.createHooks=ve;exports.createYoltra=Be;exports.invalidateAtomicProp=Ge;exports.invalidateAtomicPropsByReducer=Le;exports.shallowEqual=ye;exports.suspenseCache=N;exports.useEmit=Oe;exports.useEntity=qe;exports.useEntityField=Xe;exports.useEntityIds=Je;exports.useEvent=Me;exports.useSelector=Ce;exports.useStore=z;
|
|
10
|
+
<%s key={someKey} {...props} />`,b,E,Y,E),se[E+b]=!0)}if(E=null,p!==void 0&&(o(p),E=""+p),a(i)&&(o(i.key),E=""+i.key),"key"in i){p={};for(var Z in i)Z!=="key"&&(p[Z]=i[Z])}else p=i;return E&&m(p,typeof r=="function"?r.displayName||r.name||"Unknown":r),g(r,E,x,C,s(),p,X,H)}function l(r){typeof r=="object"&&r!==null&&r.$$typeof===f&&r._store&&(r._store.validated=1)}var S=c,f=Symbol.for("react.transitional.element"),v=Symbol.for("react.portal"),h=Symbol.for("react.fragment"),P=Symbol.for("react.strict_mode"),k=Symbol.for("react.profiler"),_=Symbol.for("react.consumer"),A=Symbol.for("react.context"),j=Symbol.for("react.forward_ref"),$=Symbol.for("react.suspense"),I=Symbol.for("react.suspense_list"),M=Symbol.for("react.memo"),R=Symbol.for("react.lazy"),O=Symbol.for("react.activity"),U=Symbol.for("react.client.reference"),w=S.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,V=Object.prototype.hasOwnProperty,B=Array.isArray,J=console.createTask?console.createTask:function(){return null};S={react_stack_bottom_frame:function(r){return r()}};var te,re={},oe=S.react_stack_bottom_frame.bind(S,u)(),ne=J(n(u)),se={};F.Fragment=h,F.jsx=function(r,i,p,b,C){var x=1e4>w.recentlyCreatedOwnerStacks++;return d(r,i,p,!1,b,C,x?Error("react-stack-top-frame"):oe,x?J(n(r)):ne)},F.jsxs=function(r,i,p,b,C){var x=1e4>w.recentlyCreatedOwnerStacks++;return d(r,i,p,!0,b,C,x?Error("react-stack-top-frame"):oe,x?J(n(r)):ne)}})()),F}var ae;function Ae(){return ae||(ae=1,process.env.NODE_ENV==="production"?W.exports=Ee():W.exports=Re()),W.exports}var pe=Ae();const Pe=({store:t,children:e})=>pe.jsx(K.Provider,{value:t,children:e}),ie=new Set;function ge(t,e){process.env.NODE_ENV!=="production"&&(ie.has(t)||(ie.add(t),console.warn(e)))}function q(t){return t.includes("*")}function T(t){return t.replace(/^\./,"")}function _e(t){return T(t).split(".").filter(Boolean)}function we(t){const e=o=>`${o.length}:${o}`;return t.map(o=>{const n=Array.isArray(o.property)?o.property:[o.property];return e(o.reducer)+e(String(n.length))+n.map(e).join("")}).join("")}const de=Symbol("yoltra.pathSegments");function me(t){const e=()=>{};return new Proxy(e,{get(o,n){if(n===de)return t;if(typeof n!="symbol")return me([...t,String(n)])},apply(){throw new Error(`[yoltra] A typed path accessor called a method (near "${t.join(".")}"). The accessor must be a plain member chain like \`p => p.items[0].title\` — it cannot call functions such as \`.map()\` or \`.toString()\`. Compute derived values in the component or a selector, or use the \`{ reducer, property }\` string form.`)}})}function xe(t){const e=t(me([])),o=e?.[de],s=(Array.isArray(o)?o:[]).join(".");return s===""&&ge("yoltra.toDottedPath.empty","[yoltra] A typed path accessor recorded no property access, so it subscribed to the slice root. That fires only when the slice's whole value is replaced — which for a slice holding an object means never, since its changes are reported at their leaves. The accessor must be a plain member chain like `p => p.items[0].title` and cannot return a computed value or a default. For a dynamic subscription, or for a slice whose state is a single value, use the `{ reducer, property }` string form instead."),s}function G(t,e){if(!e)return t;let o=t;for(const n of _e(e)){if(o==null)return;o=o[n]}return o}function L(t,e,o){const n=c.useRef(t);n.current=t;const s=c.useRef(e);s.current=e;const u=c.useMemo(()=>({has:!1,value:void 0}),o);return c.useCallback(()=>{const a=n.current();return(!u.has||!s.current(u.value,a))&&(u.has=!0,u.value=a),u.value},[u])}function he(t,e){if(Object.is(t,e))return!0;if(!t||!e)return!1;const o=Object.keys(t),n=Object.keys(e);if(o.length!==n.length)return!1;for(const s of o)if(!Object.is(t[s],e[s]))return!1;return!0}function z(){const t=c.useContext(K);if(!t)throw new Error("useStore must be used inside <StoreProvider>");return t}function Te(){return z().emit}function ke(t,e=Object.is){const o=z(),n=c.useMemo(()=>u=>o.subscribe(u),[o]),s=L(()=>t(o.getState()),e,[o]);return c.useSyncExternalStore(n,s,s)}function je(t,e,o=Object.is){const n=z(),s=c.useMemo(()=>{const y=T(t.property);return{reducer:t.reducer,property:y}},[t.reducer,t.property]),u=c.useMemo(()=>y=>n.connect({reducer:s.reducer,property:s.property},()=>y()),[n,s]),a=q(s.property),m=L(()=>{const g=n.getState()[s.reducer];return a?g:G(g,s.property)},o,[n,s]);return c.useSyncExternalStore(u,m,m)}function Q(t,e,o=Object.is){return je(t,e,o)}function Oe(t,e,o,n="committed"){const s=z(),u=c.useRef(o);u.current=o,c.useEffect(()=>s.onEvent(t,e,(a,m,y,g)=>{u.current(a,m,y,g)},n),[s,t,e,n])}function Ce(t){return t==null||t<=0?null:Date.now()+t}function Ne(t){return t===null?null:t===void 0||t<=0?0:Date.now()+t}const Me=2e3;class $e{store=new Map;touch(e){this.store.has(e)&&this.store.delete(e)}evict(){for(;this.store.size>Me;){const e=this.store.keys().next();if(e.done===!0)return;const o=this.store.get(e.value);if(o?.status==="pending"){this.store.delete(e.value),this.store.set(e.value,o);continue}this.store.delete(e.value)}}get size(){return this.store.size}read(e,o,n,s){const u=Date.now(),a=this.store.get(e);if(a&&a.status==="ready"&&(a.expiresAt==null||a.expiresAt>u))return this.touch(e),this.store.set(e,a),a.value;if(a&&a.status==="pending")throw a.promise;if(a&&a.status==="error"){if(a.delivered!==!0)throw this.store.set(e,{...a,delivered:!0}),a.error;if(a.expiresAt===null||a.expiresAt>u)throw a.error;this.store.delete(e)}const m=Promise.resolve().then(o).then(y=>{this.store.set(e,{status:"ready",value:y,expiresAt:Ce(n)})}).catch(y=>{this.store.set(e,{status:"error",error:y,expiresAt:Ne(s)})});throw this.store.set(e,{status:"pending",promise:m,expiresAt:null}),this.evict(),m}invalidate(e){this.store.delete(e)}invalidatePathKey(e){for(const o of this.store.keys())fe(o)===e&&this.store.delete(o)}invalidateReducer(e){const o=`${e}::`;for(const n of this.store.keys())fe(n).split("||").some(s=>s.startsWith(o))&&this.store.delete(n)}clear(){this.store.clear()}}const N=new $e,le=new WeakMap;let Ie=0;function ye(t){let e=le.get(t);return e===void 0&&(e=`s${++Ie}`,le.set(t,e)),e}function fe(t){return t.slice(t.indexOf("::")+2)}function ee(t,e,o){const n=Array.isArray(e)?e.map(T).sort().join("|"):T(e);return o?`${t}::${n}::${o}`:`${t}::${n}`}function Ye(t,e,o){const n=t(),s=e.reducer,u=T(e.property),a=`${ye(n)}::${ee(s,u,o.key)}`,m=c.useMemo(()=>l=>n.connect({reducer:s,property:u},()=>{N.invalidate(a),l()}),[n,s,u,a]),y=c.useRef(o);y.current=o;const g=c.useMemo(()=>{const l=q(u);return()=>{const f=n.getState()[s],v=l?f:G(f,u),h=y.current;return N.read(a,()=>h.load(v,f),h.staleTime??0,h.errorTtlMs)}},[n,s,u,a]),d=c.useMemo(()=>{const l=q(u);return()=>{const f=n.getState()[s];return l?f:G(f,u)}},[n,s,u]);return c.useSyncExternalStore(m,g,d)}function De(t,e,o){const n=t(),s=c.useMemo(()=>e.map(d=>({reducer:d.reducer,property:Array.isArray(d.property)?d.property.map(l=>T(l)):T(d.property)})),[JSON.stringify(e)]),u=c.useMemo(()=>{const d=s.map(l=>ee(l.reducer,l.property)).sort().join("||");return`${ye(n)}::${o.key?`${d}::${o.key}`:d}`},[n,s,o.key]),a=c.useMemo(()=>d=>{const l=()=>{N.invalidate(u),d()},S=s.map(f=>n.connect(f,l));return()=>{for(const f of S)f()}},[n,s,u]),m=c.useRef(o);m.current=o;const y=c.useMemo(()=>()=>{const d=n.getState(),l=m.current;return N.read(u,()=>l.load(d),l.staleTime??0,l.errorTtlMs)},[n,u]),g=()=>{const d=m.current.load(n.getState());return d instanceof Promise?void 0:d};return c.useSyncExternalStore(a,y,g)}function Fe(t,e,o){N.invalidatePathKey(ee(t,e,o))}function ze(t){N.invalidateReducer(t)}function We(){N.clear()}function qe(t){return{useSuspenseAtomicProp:(n,s)=>Ye(t,n,s),useSuspenseAtomicProps:(n,s)=>De(t,n,s)}}function Se(t){function e(){const l=c.useContext(t);if(!l)throw new Error("[yoltra] No store in context. Wrap your tree in <StoreProvider store={...}>, or use the hooks returned by createYoltra (which default to their own store).");return l}function o(){return e().emit}function n(l,S=Object.is){const f=e(),v=c.useMemo(()=>P=>f.subscribe(P),[f]),h=L(()=>l(f.getState()),S,[f]);return c.useSyncExternalStore(v,h,h)}const u=(l,S,f)=>{const v=e(),h=typeof l=="string",P=h?l:l.reducer,k=h?xe(S):l.property,_=h?void 0:S,A=c.useMemo(()=>({reducer:P,property:T(k)}),[P,k]),j=c.useMemo(()=>M=>v.connect({reducer:A.reducer,property:A.property},()=>M()),[v,A]),$=q(A.property),I=L(()=>{const R=v.getState()[A.reducer],O=$?R:G(R,A.property);return _?_(O):O},f??Object.is,[v,A]);return c.useSyncExternalStore(j,I,I)},m=(l,S,f=Object.is)=>{const v=e(),h=c.useRef(0),P=c.useRef(void 0),k=c.useRef(-1),_=c.useRef(!1),A=c.useRef(S);A.current=S;const j=c.useRef(f);j.current=f;const $=c.useMemo(()=>l.map(R=>({reducer:R.reducer,property:Array.isArray(R.property)?R.property.map(O=>T(O)):T(R.property)})),[we(l)]),I=c.useMemo(()=>R=>{const O=()=>{h.current++,R()},U=$.flatMap(w=>(Array.isArray(w.property)?w.property:[w.property]).map(B=>v.connect({reducer:w.reducer,property:B},O)));return()=>{for(const w of U)w()}},[v,$]),M=c.useCallback(()=>{if(k.current!==h.current||!_.current){const R=A.current(v.getState());(!_.current||!j.current(P.current,R))&&(P.current=R,_.current=!0),k.current=h.current}return P.current},[v]);return c.useSyncExternalStore(I,M,M)},y=(l,S,f,v="committed")=>{const h=e(),P=c.useRef(f);P.current=f,c.useEffect(()=>h.onEvent(l,S,(k,_,A,j)=>{P.current(k,_,A,j)},v),[h,l,S,v])},{useSuspenseAtomicProp:g,useSuspenseAtomicProps:d}=qe(e);return{useStore:e,useEmit:o,useSelector:n,useAtomicProp:u,useAtomicProps:m,useEvent:y,useSuspenseAtomicProp:g,useSuspenseAtomicProps:d,shallowEqual:he}}function Ge(t){const e=be.createStore(t),o=c.createContext(e),n=Se(o);return{store:e,StoreContext:o,StoreProvider:({store:u,children:a})=>pe.jsx(o.Provider,{value:u??e,children:a}),...n}}function Le(t,e){return Q({reducer:t,property:e.idsPath})}function Ue(t,e,o){return Q({reducer:t,property:e.pathTo(o)})}function Ve(t,e,o,n){return Q({reducer:t,property:e.pathTo(o,n)})}exports.StoreContext=K;exports.StoreProvider=Pe;exports.clearSuspenseCache=We;exports.createHooks=Se;exports.createYoltra=Ge;exports.invalidateAtomicProp=Fe;exports.invalidateAtomicPropsByReducer=ze;exports.shallowEqual=he;exports.suspenseCache=N;exports.useEmit=Te;exports.useEntity=Ue;exports.useEntityField=Ve;exports.useEntityIds=Le;exports.useEvent=Oe;exports.useSelector=ke;exports.useStore=z;
|
|
16
11
|
//# sourceMappingURL=index.cjs.map
|