Stores — shared state#
A store is a bifacial component without a view: schema-typed state, derived values, intents, events — projected to agents as store://<name>.
import { store, intent, schema, str, obj, enums } from 'janux';
export const Session = store({
name: 'session',
description: 'Cross-component session state.',
state: schema({
user: obj({ id: str(), name: str() }).nullable(),
locale: enums(['en', 'es']).default('en'),
}),
derived: { isLoggedIn: (s) => s.user !== null },
intents: {
setLocale: intent({
input: schema({ locale: enums(['en', 'es']) }),
run: ({ state, input }) => (state.locale = input.locale),
}),
logout: intent({
guard: 'confirm',
run: ({ state, emit }) => { state.user = null; emit('auth.loggedOut', {}); },
}),
},
emits: { 'auth.loggedOut': schema({}) },
});Consuming a store#
Components declare the dependency — no provider wrapping, no hook call:
export const Header = component({
name: 'header',
use: { session: Session },
view: ({ use }) => <nav>{use.session.derived.isLoggedIn ? '👤' : <a href="/login">Log in</a>}</nav>,
intents: {
switchLocale: intent({
run: ({ use }) => use.session.intents.setLocale({ locale: 'es' }),
}),
},
});The rules (and why they differ from Zustand)#
- Mutations only through store intents. There is no
set()you can call from anywhere: every mutation of shared state is named, schema-validated, guard-checked and auditable — identical for humans and agents. Direct writes outside arun()throw. - The manifest lists readers. Agents see which mounted components read each store (
readers: ["ui://header"]) — the blast radius of a mutation is visible before making it. - SSR-safe by construction. On the server every request gets its own store instances (no shared-singleton leaks); state serializes once per page and resumes on the client like any island.
- Server-side stores register via
src/stores.ts(exported defs) so SSR can render store-dependent views; the client re-creates them from the serialized snapshot inboot({ defs }).
Store events, sources and effects work exactly as in components, and settled() covers stores too.
See it running:
examples/shop— the cart store persisted across islands with a confirm-guarded checkout. More in Examples.