-
Notifications
You must be signed in to change notification settings - Fork 1
Store
HTeaLeaf introduces a reactive state layer inspired by modern frontend frameworks.
It allows you to define global or user-specific state directly in Python, and bind it to HTML components that update automatically when the state changes.
HTeaLeaf includes a full server-driven reactivity system built around three components:
-
Store: Global shared state -
AuthStore: Per-user state bound to sessions -
SuperStore: The orchestrator that connects and synchronizes stores through HTTP and JavaScript
A Store is a shared, reactive state container that lives on the server side and synchronizes with the client through HTeaLeaf’s reactivity engine.
from htealeaf import Store
cstore = Store({"counter": 1})Each Store behaves like a reactive dictionary: read values server-side with .read(path) and mutate them from the browser through the .js namespace. After a client-side mutation, the page is re-fetched and the DOM is reconciled, so every element rendered from store data updates automatically. Each Store gets a unique auto-generated id, so several stores can coexist without collisions.
HTeaLeaf stores can be accessed using path-like keys, similar to JSON pointers. This allows deep updates inside nested data structures such as lists or dictionaries.
For example:
store = Store({"todo": [{"value": "Buy milk", "done": False}]})This is used by the JavaScript JSCode helper:
store.js.update("todo/0/done", True)@app.route("/counter")
def counter():
return div(
button("-").attr(onclick=cstore.js.update("counter", cstore.read("counter") - 1)),
h3(cstore.read("counter")),
button("+").attr(onclick=cstore.js.update("counter", cstore.read("counter") + 1)),
)AuthStore behaves just like Store, but each user Session has its own isolated copy.
from htealeaf import AuthStore
from htealeaf.server import SessionData
def auth_session(session: SessionData):
if session.has("userName"):
return session["userName"]
return None
todoStore = AuthStore(auth_session, {"todo": []})Each authenticated user gets their own instance of the todo data, independent from others.
⚠️ If the auth function returnsNone, the user is not rejected — all unauthenticated users share a single store keyed onNone. Gate access in your route handler (e.g. redirect to a login page) rather than relying on the auth function to deny access.
from htealeaf.js import js
from htealeaf.js.common import document
from htealeaf.server.utils import redirect
@app.route("/")
def home(session):
if not session.has("userName"):
return redirect("/login")
@js
def addTask(inputId):
val = document.getElementById(inputId).value
todoStore.set("todo", {"done": False, "value": val})
return div(
[todoItem(idx, t) for idx, t in enumerate(todoStore.auth(session).read("todo"))],
textInput().id("todo_item"),
button("Add Task").attr(onclick=addTask("todo_item")),
)Each logged-in user interacts with their own store content, persisted in memory and linked to their session ID. Note the two sides of the boundary: server-side reads use todoStore.auth(session).read(...), while inside a @js body the store is referenced directly (todoStore.set(...)) and the transpiler rewrites it to the client-side call.
SuperStore acts as the reactivity controller for all stores. It connects them to the app and automatically registers endpoints for synchronization and data mutation.
from htealeaf import HteaLeaf, SuperStore, adapters
app = HteaLeaf(adapters.WSGI)
SuperStore(app)Once initialized:
- All Store and AuthStore instances become discoverable.
- The framework exposes internal HTTP endpoints for reading and writing store data.
- The Store.js objects (injected into the frontend) communicate with these endpoints.
You typically only need one SuperStore per application.
When the client triggers an update, like:
store.js.update("todo/0/done", True)translated in the frontend to:
store_xyz.update("todo/0/done", true)the call is routed to an automatically exposed backend endpoint, typically something like
PATCH /api/_store/{api_id}/todo/0/done(set maps to POST, update to PATCH, delete to DELETE, and get to GET.)
SuperStore:
- Parses the path (todo/0/done).
- Locates the right Store or AuthStore instance.
- Applies the mutation, after which the client re-fetches the page and reconciles the DOM.
This allows fully reactive data flow over plain HTTP, no WebSockets required.
from htealeaf import HteaLeaf, SuperStore, Store, AuthStore, adapters
from htealeaf.server import SessionData
app = HteaLeaf(adapters.WSGI)
SuperStore(app) # register the orchestrator
globalStore = Store({"counter": 0})
def auth_session(session: SessionData):
return session["userName"] if session.has("userName") else None
todoStore = AuthStore(auth_session, {"todo": []})After this, globalStore and todoStore are automatically exposed and reactive.
+-------------+ +----------------+ +---------------+
| Python App | <---> | SuperStore | <---> | Client (JS) |
+-------------+ +----------------+ +---------------+
▲ ▲ ▲
| | |
| Store updates | HTTP fetch/update |
|-----------------------|------------------------|
- SuperStore handles orchestration, routing, and propagation.
- Store / AuthStore define the reactive data models.
- JSCode translates Python actions into client-side JS that calls the correct store endpoints.
Next: Session
🍃 HTeaLeaf
State
Client-side