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.

Kinetic Logo Demo

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:

tsx
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 tiempoSetup de una llamadaRutas tipadas / tipos de extremo a extremo
Redux Toolkit✗ selectores + memo✓ (por eso muchos se quedan)✗ boilerplateparcial
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).

Dónde encaja Yoltra en tu código. Tus componentes, el resto de tu código y las librerías llegan a un store, y la persistencia y las DevTools se conectan por su punto de instrumentaciónDónde encaja Yoltra en tu código. Tus componentes, el resto de tu código y las librerías llegan a un store, y la persistencia y las DevTools se conectan por su punto de instrumentación

#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.

Cómo funciona un store. Un evento emitido puede pasar por dedup, luego el middleware y los reducers preparan cada slice y confirman un nuevo estado, que llega a suscriptores por ruta, listeners, instrumentación y efectosCómo funciona un store. Un evento emitido puede pasar por dedup, luego el middleware y los reducers preparan cada slice y confirman un nuevo estado, que llega a suscriptores por ruta, listeners, instrumentación y efectos

#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.

Cómo despacha when. Un when con keys usa el despacho por claves, una búsqueda O(1), mientras any, channel y channels usan el despacho por patrón evaluado en cada evento, y ambos ejecutan el handlerCómo despacha when. Un when con keys usa el despacho por claves, una búsqueda O(1), mientras any, channel y channels usan el despacho por patrón evaluado en cada evento, y ambos ejecutan el handler

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

PiezaDónde encaja
codecDedup 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
persistenciahydrate() 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
devtoolsinstrument() 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
registrosregisterSlice, 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 cascadaUn 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.

tsx
// 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:

tsx
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

PaqueteDescripción
@yoltra/coreStore agnóstico de framework: reducers, middleware, efectos, detección de cambios de grano fino, instrumentación tipada, entity adapter, persistencia + hidratación
@yoltra/reactHooks de React: suscripciones de grano fino, accessors de ruta tipados, createYoltra, hooks de entidades, Suspense
@yoltra/dsSistema 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.

Cómo encajan los paquetes. Los paquetes y de qué depende cada uno, desde el store en la base hasta los paneles de DevToolsCómo encajan los paquetes. Los paquetes y de qué depende cada uno, desde el store en la base hasta los paneles de DevTools

#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

EjemploDescripció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 ProfilerComparación de flamegraph lado a lado con Redux (resultados) · ▶ Demo en vivo
ContadorEl ejemplo mínimo de extremo a extremo · ▶ Demo en vivo
Selector de tema en Next.jsYoltra del lado del cliente dentro de una app Next.js (Pages Router) · ▶ Demo en vivo

#Documentación


#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)

bash
npm i -g @microsoft/rush
rush install
rush build
rush test

Consulta 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

Reportar un problema con esta página