Documentación
Inicio rápido
Yoltra es un contenedor de estado orientado a eventos y agnóstico al framework,
con suscripciones reactivas de grano fino. Tú escribes eventos; los
reducers los pliegan en el estado; una comparación estructural notifica a los
suscriptores exactamente en las rutas que cambiaron; los efectos corren después.
Sin proxies, sin magia — solo diff(prev, next) → rutas → suscriptores.
#Instalación
Yoltra requiere CPython ≥ 3.11 y no tiene dependencias de ejecución.
pip install yoltra#Tu primer store
from yoltra import create_store
store = create_store("Todos")
# Un reducer es dueño de un slice del estado y pliega en él los eventos que coincidan.
@store.reducer(name="todos", state={"items": []}, when={"keys": [("ui", "add")]})
def todos(prev, event):
return {"items": [*prev["items"], {"title": event.payload, "done": False}]}
# Emite un evento. El estado se confirma de forma síncrona — en el instante en que emit() retorna.
store.emit("ui", "add", "Buy milk")
print(store.get_state()["todos"]["items"])
# [{'title': 'Buy milk', 'done': False}]Pasaron tres cosas: emit("ui", "add", "Buy milk") creó un evento; el reducer
todos (registrado para la clave ("ui", "add")) produjo el siguiente slice; y
el store lo confirmó antes de que emit retornara.
#Reacciona a los cambios con connect
connect se suscribe a una ruta con puntos dentro de un slice. El store
compara cada cambio y dispara solo a los suscriptores cuya ruta cambió:
store.connect(
reducer="todos",
property="items",
handler=lambda change: print("items changed:", change.new_value),
)
store.emit("ui", "add", "Walk the dog")
# items changed: [{'title': 'Buy milk', ...}, {'title': 'Walk the dog', ...}]Las rutas admiten globs: * coincide con un segmento, ** coincide con
cualquier profundidad. Un monitor sobre items.*.title se dispara cuando cambia
el título de cualquier item:
store = create_store("Todos")
@store.reducer(name="todos", state={"items": []},
when={"keys": [("ui", "add"), ("ui", "rename")]})
def todos(prev, event):
if event.type == "add":
return {"items": [*prev["items"], {"title": event.payload, "done": False}]}
# rename: (índice, nuevo_título)
i, title = event.payload
items = list(prev["items"])
items[i] = {**items[i], "title": title}
return {"items": items}
store.connect(reducer="todos", property="items.*.title",
handler=lambda c: print(f"title {c.path}: {c.old_value!r} → {c.new_value!r}"))
store.emit("ui", "add", "Buy milk")
store.emit("ui", "rename", (0, "Buy oat milk"))
# title items.0.title: 'Buy milk' → 'Buy oat milk'Un detalle que conviene saber pronto: agregar un item cambia la longitud
de la lista, así que la comparación marca la ruta completa items (no
items.0.title). Una edición en el lugar de un item existente (misma
longitud) produce la ruta hoja precisa. Esto se desprende de la comparación
estructural y se explica en la guía para desarrolladores.
#Ejecuta efectos secundarios
Los efectos corren después de que un evento se confirma — ideales para persistencia, logging o disparar la siguiente acción. Pueden ser síncronos o asíncronos:
@store.effect(when={"keys": [("ui", "add")]})
def persist(event, get_state, emit):
save_to_disk(get_state()["todos"]["items"]) # tu códigoemit() retorna un EmitHandle. Desde código síncrono puedes bloquear hasta
que el efecto termine; desde código asíncrono puedes esperarlo:
handle = store.emit("ui", "add", "Buy milk")
handle.wait() # síncrono: bloquea hasta que terminen los efectos de este evento
# o, dentro de código async:
# await store.emit("ui", "add", "Buy milk")#Protege las escrituras con middleware
El middleware corre antes de los reducers y puede vetar un evento retornando
False:
@store.middleware()
def reject_empty(state, event, emit):
return not (event.type == "add" and event.payload == "")
store.emit("ui", "add", "") # vetado — no se agrega ningún item#Hacia dónde seguir
- Guía para desarrolladores — el modelo completo y cada función.
- Migrar desde
@yoltra/core— si ya conoces la versión de TS. - Pizarra reactiva para agentes — el caso de uso agéntico.