Overview
Mates has four execution contexts. Rules for creating primitives, tracking reads, and cleanup all depend on which one you are in.
1. ES module scope — top of the file, outside any function. Runs once on import. On a SPA, fine for shared atoms/events (no component lifecycle — __subscribe cleanup is manual). During SSR, App.ts is evaluated once per Node process and reused for every GET — module-level atoms/stores/actions throw. Full table: Restricted Usage.
2. Component outer (setup) — the outer function of a component. Runs once on mount. The safe default in SPA and SSR. Create atoms, effects, events, and on* hooks here. Lifecycle-aware APIs (on(), effect(), on-hooks) auto-clean on unmount.
3. Component inner (template) — the function returned by the outer. Re-runs on every reactive update. Read atoms here (tracked). Never create primitives or call on-hooks — that throws.
4. Handler / callback — event handlers, action callbacks, timers, promises. Safe to mutate existing state. Creating primitives works but is discouraged (no lifecycle). Creating inside effect()/memo() callbacks throws.
Creation rules (what can be created where)
|
Primitive
|
Module / Outer
|
Template / Handler
|
|---|---|---|
atom / memo / effect
| / | throws in template · don't create in handlers · throws inside effect/memo callbacks |
event / channel / action
| / | throws in template · don't create in handlers |
scope / useScope
| / | Outer only — needs an active component host |
On hooks (onMount, on, …)
| / | Outer only — throws in template; no host in handlers |
Correct: create in outer, read in template
Atoms live in the setup closure. The template only reads count() — that read is tracked, so the button label updates on set.
Anti-pattern avoided: never atom() in template
Do not call atom() (or onMount) inside the inner function — it throws. Create in outer, read in template, mutate in handlers.