Documentación
Yoltra
Estado reactivo de grano fino, basado en eventos (event-sourced), con devtools que incluyen viaje en el tiempo. Para aplicaciones complejas e interactivas.

3000 círculos, cada uno suscrito a su propia posición. Cada círculo se re-renderiza de forma independiente - el resto del árbol no se toca. Sin selectores. Sin memoización. Ver el código fuente de la demo. · ▶ Abrir la demo en vivo
#La propuesta en 30 segundos
Una sola llamada te da el store y los hooks totalmente tipados. Te suscribes a una ruta con un accessor tipado, y el componente se re-renderiza solo cuando esa ruta exacta cambia:
import { createYoltra } from "@yoltra/react";
// Una llamada: store + hooks tipados. Sin context, sin createHooks, sin boilerplate.
export const { useAtomicProp, useEmit } = createYoltra({
name: "App",
reducer: {
todos: {
state: { items: [{ id: "1", title: "Buy milk", done: false }] },
when: { keys: [["todos", "rename"]] },
reducer: (s, e) =>
e.type === "rename"
? { items: s.items.map((t) => (t.id === e.payload.id ? { ...t, title: e.payload.title } : t)) }
: s,
},
},
});
function TodoTitle() {
// Forma objeto: suscríbete a la ruta exacta `items.0.title`.
// Se re-renderiza SOLO cuando esa ruta exacta cambia. Sin selectores, sin memo.
const title = useAtomicProp({ reducer: "todos", property: "items.0.title" });
const emit = useEmit();
return <span onClick={() => emit("todos", "rename", { id: "1", title: "New title" })}>{title}</span>;
}La suscripción es la optimización.
Yoltra es un fork de Quo.js. Dejamos de usar el nombre Quo.js para no luchar en SEO con librerías zombis.
#Para quién es Yoltra
Para equipos que construyen aplicaciones complejas e interactivas (dashboards operativos, UIs de trading y back-office, productos multi-pestaña, plataformas de micro-frontends) cansados de intercambiar depurabilidad por rendimiento de render, Yoltra es un ecosistema de estado basado en eventos que entrega re-renders de grano fino y un log de eventos totalmente observable y reproducible. A diferencia de Redux (observable, pero grueso y verboso) o Jotai, Valtio y signals (de grano fino, pero opacos), Yoltra rechaza ese intercambio.
#Qué hace diferente a Yoltra
La mayoría de las librerías de estado te obligan a elegir dos de las siguientes. Yoltra está construido para darte las cuatro a la vez - y en esa intersección es donde vive:
| Grano fino (sin memo manual) | Log de eventos + viaje en el tiempo | Setup de una llamada | Rutas tipadas / tipos de extremo a extremo | |
|---|---|---|---|---|
| Redux Toolkit | ✗ selectores + memo | ✓ (por eso muchos se quedan) | ✗ boilerplate | parcial |
| Zustand | ✗ igualdad manual | ✗ | ✓ | parcial |
| Jotai / Recoil | ✓ átomos | ✗ | ✓ | ✓ |
| Valtio / MobX | ✓ magia de proxy | ✗ | ✓ | parcial |
| Signals | ✓ | ✗ | ✓ | ✓ |
| Yoltra | ✓ suscripciones por ruta | ✓ integrado | ✓ createYoltra | ✓ accessors tipados |
El campo de grano fino (Jotai, Valtio, signals) tiene devtools pobres y no tiene log de eventos. El campo basado en eventos (Redux) tiene grandes devtools pero reactividad gruesa y boilerplate. Yoltra es el único lugar donde obtienes reactividad de grano fino, un log de eventos con viaje en el tiempo real, setup de una llamada y tipado completo - juntos. Una comparación más profunda y honesta está en la comparación de librerías.
#Dónde encaja Yoltra en tu código
Tu código habla con un solo store. Los componentes de React llegan a él a través de
@yoltra/react; el resto del código (un web worker, una prueba, una librería que decora el store)
lo llama directamente. Todo lo que observa desde fuera (la persistencia, las DevTools) se conecta por
el mismo punto de instrument(), así que nada de eso le pide algo a tus reducers. Las flechas
punteadas son comandos de DevTools, que el agente ejecuta solo cuando están activados
(allowReplay, allowEmit).
#Cómo funciona un store
Un evento, de principio a fin. La fase de reducción es síncrona, así que getState() es correcto
en el instante en que emit() retorna; los efectos corren después, como una tarea independiente.
#when: un matcher, dos rutas de despacho
Reducers, middleware y efectos apuntan a los eventos de la misma forma, y la forma que elijas decide cómo los encuentra el store.
keys es exacto y está tipado contra tu mapa de eventos, así que un typo es un error de
compilación. Los otros tres se resuelven en tiempo de ejecución, que es lo que permite que un
solo handler cubra un canal completo.
#Las comodidades, y dónde se conectan
| Pieza | Dónde encaja |
|---|---|
| codec | Dedup por contenido, persistencia al leer y al escribir, snapshots y payloads de eventos en devtools. Lleva y trae Map, Set, Date, RegExp, Error, BigInt, typed arrays, ciclos y referencias compartidas, y reporta lo que no puede representar en lugar de descartarlo |
| persistencia | hydrate() siembra el estado inicial antes de que el store exista, así que no hay parpadeo al arrancar; persist() se monta sobre la costura de instrumentación |
| devtools | instrument() transmite eventos y parches; el viaje en el tiempo devuelve estado por __applyExternalState. El replay no vuelve a ejecutar tus handlers de onEvent salvo que lo pidan |
| registros | registerSlice, registerMiddleware y registerEffect agregan a un store vivo y amplían sus tipos. replace* reemplaza solo lo que escribió la aplicación, así que una recarga en caliente deja en paz la slice de una librería |
| guard de cascada | Un evento emitido desde un handler lleva su causa y su profundidad, así que un ciclo se detiene y se nombra en lugar de colgar la pestaña |
#Lo que dejas de hacer - los dolores que Yoltra elimina
#Optimización manual de renders - borra tus useMemo
Suscríbete a items.0.title o al comodín items.*.done y re-renderiza solo cuando esa ruta exacta
cambie - a través de objetos anidados, arrays y claves dinámicas. Sin selectores, sin memoización,
sin React.memo en cada hoja.
// Forma objeto: suscríbete a la ruta exacta
const title = useAtomicProp({ reducer: "todos", property: "items.0.title" });
// Ruta con comodín + deriva con un mapper
const allDone = useAtomicProp({ reducer: "todos", property: "items.*.done" }, (s) =>
s.items.every((i) => i.done),
);Ver la comparación de flamegraph (Redux vs Yoltra).
#Cableado del store y boilerplate
createYoltra(spec) devuelve el store y cada hook tipado (useAtomicProp, useEmit, useEvent,
useSelector, …). Los hooks usan ese store por defecto, así que un <Provider> es opcional. Sin
archivo de context aparte, sin cableado.
#Adivinar cuándo el estado está al día
La fase de reducción (middleware → reducers → suscriptores → oyentes) se ejecuta de forma
síncrona, así que getState() es correcto en el instante en que emit() retorna, incluso con
middleware. Los efectos corren después, de forma asíncrona, y la promesa devuelta se resuelve solo
cuando los efectos de ese evento terminan. Sin lecturas obsoletas, sin "a veces síncrono, a veces
asíncrono".
#Sorpresas silenciosas de estado
La deduplicación por contenido está desactivada por defecto - Yoltra nunca traga en silencio
dos eventos rápidos legítimos (doble-clic, +1 repetido). Actívala con dedupWindowMs, o usa un
dedupKey por-emit para dedup basado en identidad (p. ej. un doble-render de React Strict Mode). Las
escrituras cuestan O(cambio), no O(tamaño del estado): una actualización de un solo campo nunca clona
ni vuelve a congelar toda la slice.
#Lo que empiezas a entregar - las ganancias que Yoltra crea
#DevTools de viaje en el tiempo que muestran exactamente qué cambió
Como Yoltra está basado en eventos, sus devtools son de primera clase, no algo agregado después.
El store reporta las rutas precisas que cambiaron en cada evento, así que el panel renderiza
parches RFC-6902 exactos (replace /todos/items/0/title), un log de eventos filtrable con eventos
confirmados/rechazados, métricas reales (tiempo de reducción, aciertos de dedup, profundidad de cola)
y viaje en el tiempo + repetición de eventos. Esta es la capacidad que el campo de grano fino no
puede igualar fácilmente.
Véelo en vivo → Orbital Mission Control ejecuta el store, el hub y este mismo panel en una sola página - sin instalar nada. Pausa la telemetría, recorre la línea de tiempo de la misión y mira cómo se reconstruye el estado. (Tour guiado.) · ▶ Abrir la demo en vivo
#Eventos que puedes interceptar, rechazar y auditar
Los eventos son tuplas (channel, type, payload), un namespacing natural que escala sin
colisiones. Fluyen a través de un pipeline interceptable. El middleware puede rechazar un evento,
produciendo un evento no confirmado al que tu UI puede reaccionar, ideal para autorización,
validación y UI optimista:
await emit("auth", "login", credentials);
await emit("analytics", "track", event);
// Reacciona cuando el middleware bloquea un delete
useEvent("ui", "delete", () => showToast("La eliminación fue bloqueada por permisos"), "uncommitted");#Baterías para apps reales: entidades, persistencia, Suspense
createEntityAdapter da a las colecciones rutas estables por identidad (entities.<id>.title)
que sobreviven reordenamientos; persist/hydrate toman snapshots de slices con envelopes
versionados y migraciones (web storage, adaptadores propios, dehydrate() para el traspaso en
SSR); los hooks de Suspense cubren lecturas asíncronas. Todo dentro de los presupuestos de tamaño
de bundle que el CI hace cumplir.
#Paquetes
| Paquete | Descripción |
|---|---|
| @yoltra/core | Store agnóstico de framework: reducers, middleware, efectos, detección de cambios de grano fino, instrumentación tipada, entity adapter, persistencia + hidratación |
| @yoltra/react | Hooks de React: suscripciones de grano fino, accessors de ruta tipados, createYoltra, hooks de entidades, Suspense |
| @yoltra/ds | Sistema de diseño: primitivas de React accesibles (formularios, tablas, overlays, menús, pestañas), tokens de diseño --yl-* en tres niveles, temas claro/oscuro con el contraste verificado en ambos - independiente, usable sin el store |
| @yoltra/devtools-* | Suite de DevTools: protocolo, servidor hub, agente de navegador y la UI del panel (extensión de navegador + CLI) |
Cómo dependen los paquetes unos de otros. @yoltra/core no depende de nada; @yoltra/react y el
agente de navegador de DevTools lo toman como dependencia peer, así que tu app siempre tiene exactamente una
copia. @yoltra/ds es independiente, y todos los paquetes de DevTools hablan el formato de cable
definido en @yoltra/devtools-protocol.
#Inicio rápido (React)
Guía de inicio rápido, una app funcional en menos de 3 minutos.
#DevTools
El store de Yoltra expone una costura de instrumentación tipada (store.instrument(...)) que el
agente de DevTools consume con cero casts as any. Un pequeño hub retransmite los eventos de tu app en
ejecución hacia el panel; el panel renderiza el log de eventos, el árbol de estado en vivo, los
parches precisos por evento, las métricas y el viaje en el tiempo. Un evento que no se confirmó
dice por qué, y nombra al middleware que lo vetó cuando ese middleware tiene nombre.
#Ejemplos en vivo
#🛰️ Orbital Mission Control - la demo insignia
Empieza aquí. Cada funcionalidad de Yoltra y el panel de DevTools en vivo en una sola pantalla - contadores de render de grano fino, suscripciones con comodín, efectos asíncronos, veto de middleware y viaje en el tiempo, corriendo sobre un hub en memoria sin instalar nada. → Tour guiado · ▶ Abrir la demo en vivo
| Ejemplo | Descripción |
|---|---|
| Logo cinético (3000 partículas) | Simulación de física con una suscripción de ruta independiente por círculo · ▶ Demo en vivo |
| App de tareas con Profiler | Comparación de flamegraph lado a lado con Redux (resultados) · ▶ Demo en vivo |
| Contador | El ejemplo mínimo de extremo a extremo · ▶ Demo en vivo |
| Selector de tema en Next.js | Yoltra del lado del cliente dentro de una app Next.js (Pages Router) · ▶ Demo en vivo |
#Documentación
- Guía de inicio rápido - cinco pasos hacia una app funcional
- Guía de migración - si vienes de Redux, Zustand o Jotai
- Actualizar a 0.10.0 - qué cambió, cómo lo notarías, y qué hacer
- Actualizar a 0.8.0 - qué cambió, cómo lo notarías, y qué hacer
- Actualizar @yoltra/ds a 0.4.0 - 38 tokens renombrados, el codemod que viene en el paquete, y un tamaño por defecto más compacto
- Guía de decoración - agregar una slice, middleware o efecto al store de alguien más, con los tipos
- Petición y respuesta -
store.call(): correlación sin ids, progreso en streaming con backpressure real - Guía de testing - prueba stores, efectos, middleware y componentes
- Guía de Next.js - uso en cliente con Pages y App Router
- API de @yoltra/core - store, middleware, efectos, matchers
When, instrumentación - API de @yoltra/react - hooks, accessors tipados,
createYoltra, Suspense - @yoltra/ds - componentes, tokens, temas y el contrato con SSR
- Arquitectura del pipeline de eventos - cómo funciona el pipeline de reducción síncrona / efectos asíncronos
- Comparación de librerías - comparación arquitectónica honesta con Redux, Zustand, Jotai y otras
#Contribuir
¡Damos la bienvenida a las contribuciones! Por favor, lee la Guía de contribución, el Código de conducta, la Gobernanza y la Política de seguridad.
#Desarrollo (Monorepo)
npm i -g @microsoft/rush
rush install
rush build
rush testConsulta la Guía del desarrollador para más detalles.
#Estado
Yoltra está en etapa de Release Candidate:
- Las APIs de core y React son estables y se usan en aplicaciones en producción.
- Los tipos de TypeScript son estrictos y completos; el CI hace cumplir umbrales de cobertura, presupuestos de tamaño de bundle y benchmarks.
- La suite de DevTools se conecta sin configuración en el navegador, y además ofrece un panel embebible y una UI de terminal.
- Las APIs menores aún pueden evolucionar antes de v1.0.
Los comentarios y PRs son bienvenidos.
#Licencia
MIT: libre para usar en proyectos comerciales y de código abierto. Cada paquete
@yoltra/* publicado se distribuye bajo la misma licencia MIT. Consulta LICENSE para más
detalles.
Marcas registradas: «Yoltra» y el logo de Yoltra son marcas. La licencia MIT cubre el código, no las marcas, consulta TRADEMARKS.
#Comunidad
- Sitio web: yoltra.dev
- Twitter/X: @yoltra_dev
- GitHub Discussions: Únete a la conversación
- Issues: Reporta errores o solicita funcionalidades