Summary#
A store is a component minus render — the same reactive members (state/derived/[live] data/
effect/action/a plain method), sharable rather than local to one component instance. It carries no backend
identity: no table, no security block, nothing persists to the database. It is scoped by lifetime, and that
scope word is a closed set of exactly two keywords.
⛔ What a store cannot do TODAY is the whole reason to read this page before writing one. Its declaration
resolves and emits cleanly, but no mechanism anywhere — resolver, emitter, or client runtime — lets a component
reach a store's name. Reading store.Whatever or calling store.SomeAction() from inside a component body fails
with "unknown identifier", exactly as if the store did not exist. See [[#gap]].
Signature#
store app Name { … } // one in-memory instance for the running app, gone on reload
store session Name { … } // like `app`, but persisted across reloads and cleared on logoutDescription#
The scope word — a closed set of exactly two keywords#
store <scope> Name { … } takes a scope immediately after the store keyword. It is a closed vocabulary of
exactly two words:
| Scope | Lifetime |
|---|---|
app | One instance for the running app, in memory, gone on reload. |
session | Like app, but persisted across reloads (cleared on logout). |
A third word is a compile error naming the two real options — it used to be accepted SILENTLY: any identifier that
was not app or session fell through to a third, unimplemented lifetime (ComponentProvided — see
[[#component-provided]]) with no diagnostic at all, so a typo of either keyword (store sesion Cart { … })
compiled clean into a scope nothing could ever reach. That is fixed: an unrecognized scope word is refused with a
did-you-mean.
Both scopes declare and resolve cleanly — this half of store genuinely works:
store app Toast {
int Count = 0;
live var any = Count > 0;
action Error(string msg) { Count = Count + 1; }
}
store session Wizard {
string Step = "start";
action Advance(string step) { Step = step; }
}Can a component read or call one today?#
No. A store's name is registered nowhere a component body looks names up — only routed/composable
components are wired into the name table a component's body resolves against. Reaching for the store by its
declared name from inside a component is refused as an unknown identifier, the same refusal an author would get
for any name that was never declared:
store app Toast {
int Count = 0;
action Error(string msg) { Count = Count + 1; }
}
component ToastCheck() {
action Boom() { Toast.Error("bad"); } // error: unknown identifier 'Toast'
render { Stack { Text($"{Toast.Count}"); } } // error: unknown identifier 'Toast'
}So today, store is a place to write shared reactive logic down, but not a place anything can reach it from.
Until this lands, reach for one of these instead:
- Shared, stateless logic (formatting, a computation more than one component needs) — a
publicmethod on a class, held as a field on whichever component needs it. This part is real and works today — see [[ui-action#choosing]]. - Shared, PERSISTED truth (a cart's contents, anything that should survive a refresh and be visible to more
than the browser tab that wrote it) — an
entitywith a reactivelive varquery, not UI state at all. This is what component calls "most 'global state' is actually backend data." - Genuinely ephemeral, cross-component UI-only state (is the command palette open, a toast queue, an
in-progress wizard that must NOT touch the database) has no working substitute today — that gap is real, and
storeis the intended answer once the read/invoke path is built.
The third scope — reserved, not usable#
The underlying model (UiStoreScope) carries a THIRD lifetime beyond app/session: a store a component
instantiates and provides to its own subtree, injected by type — for control-internal shared state (a multi-step
form's fields shared across its own inputs; a grid's selection). This is a real, deliberate design point, not a
guess: store's own scopes are lifetimes, and "shared with descendants of one component instance" is the third
lifetime a scoped-state primitive naturally has.
It has no syntax today — no keyword spells it, nothing declares it, and no client runtime reads it. Do not guess at a spelling for it; none has been decided. The enum value stays reserved for when it is actually built, and the closed-set check above ([[#scopes]]) is what stops an attempt at writing it from being silently misread as something else.
See also#
- component — the member grammar a store shares (
state/derived/[live] data/effect/action/method), and where "most global state is actually backend data" is explained in full. - action vs. method — the
action/methoddistinction, unchanged inside a store. - class methods — the reuse mechanism that DOES work today for shared, stateless logic.