Overview
Every Mates factory is legal in some call sites and throws in others. This page is the full map: what you called, where it may run, SPA vs SSR, why, and who cleans up.
SPA (browser). ES module scope runs once per tab. A module-level atom is a shared client singleton — that is OK. The template still cannot create primitives (it re-runs).
SSR. Fullstack import(App) evaluates App.ts once per Node process, then reuses that module for every GET. A module-level atom/store/action would be one instance for all users. Guards throw: You can't call 'Atom' in ES module scope during server-side rendering. Move it into the component outer function so each request gets its own graph. Same idea as Next.js and other SSR frameworks.
The outer function is always the safe default (SPA and SSR). scope() class setup() counts as outer. The inner template and effect/memo callbacks must only read.
Restricted Usage
Scroll sideways on small screens. First column stays pinned.
Reactive factories
|
API
|
SPA · module
|
SPA · outer
|
Template / effect
|
SSR · App.ts module
|
SSR · outer
|
Why
|
Cleanup / notes
|
|---|---|---|---|---|---|---|---|
atom / iAtom / atom.*
|
SPA module = one shared client singleton. SSR App.ts is one module for every request — a module-level atom would leak one value to all users.
|
No host cleanup required. Template / effect / memo throws (new instance every run).
|
|||||
store()
| SPA module is the shared-bag pattern. During SSR, create in the outer function so each request gets its own bag. |
Calling store() in SPA outer creates a per-component bag (like useState). Prefer module on SPA, outer on SSR.
|
|||||
asyncAtom / cacheAtom / formAtom
|
Same as atom — factories that hold request data cannot live on a process-wide module during SSR.
|
Inner timers/fetches: prefer outer so they die with the component. registerCleanup is a no-op without a host.
|
|||||
debouncedAtom / throttledAtom / delayAtom
| Timers on a module singleton outlive any one screen. SSR module would share timers across users. |
Outer: pending timeouts cancel via registerCleanup on unmount. Module: you own cancel.
|
|||||
LSAtom / lsStore / LSList (and SS*)
| Browser storage is per-origin, not per-user-on-the-server. A process cache during SSR would mix requests. |
Persistence is localStorage / sessionStorage. Prefer utils.lsStore in apps.
|
|||||
on / watch
| SSR module subscription would fan out to every GET. SPA module is allowed but has no host. | Outer: unsub on unmount. Module: keep the returned cleanup and call it yourself. | |||||
effect / memo
| Same pollution rule as atoms. Callbacks must only read — creating primitives inside them throws. |
Outer: auto-dispose on unmount. Module: call the returned dispose(). Return a cleanup from effect(fn) for DOM/subscriptions.
|
|||||
setter / _
| Reactive function wrapper — same create rules as other primitives. |
Cannot be created inside setup() (define as a class field first). Needs a host for view scheduling.
|
|||||
event / cleanupEvent / channel
| A module-level event on the server is one bus for every user. |
Component-scoped: subscribers via on() auto-clean. Module: you own unsub. channel.join is also guarded.
|
|||||
action / asyncAction / taskAction / fetchAction
| In-flight status/cache on a process singleton would mix users during SSR. | Abort/cancel follows the action instance. Create in outer during SSR so teardown is per request. | |||||
molecule()
| A molecule is a bag of atoms — same pollution rule as creating those atoms at module scope during SSR. | No extra cleanup. Prefer outer on SSR so each request gets its own bag. |
Host-required (always outer)
|
API
|
SPA · module
|
SPA · outer
|
Template / effect
|
SSR · App.ts module
|
SSR · outer
|
Why
|
Cleanup / notes
|
|---|---|---|---|---|---|---|---|
scope() / getParentScope()
| Needs an active component host to register on the tree. Always outer — SPA and SSR. |
Torn down with the providing component. setup() on the class is also an allowed create site.
|
|||||
useUtils()
|
Looks up MatesUtils on the host. Same store as utils, but still needs a component outer.
|
Prefer utils outside components. Do not import pathAtom / themeAtom / lsStore as globals.
|
|||||
getPageStore / createPageStore
| Registry + cleanup are host-scoped. Module create would skip unmount cleanup and share one store on the server. |
Creator unmount removes the registry entry. Named unique classes required. SSR snapshots skip load().
|
|||||
createGlobalStore / getGlobalStore
| createGlobalStore needs a host so each SSR request gets its own window registry. |
createGlobalStore in App outer. getGlobalStore is a lookup — safe outside components after create.
|
|||||
useState / useForm
| Need a host for tracking / devtools. Not a process singleton. | Per-component instance. Destroyed with the component — no extra cleanup. | |||||
ws()
| Must attach reconnect/close to a host. A module socket would never disconnect. |
Unmount disconnects. Prefer onSocket for handlers (auto-unsub). SSR: connection is a no-op.
|
|||||
useStore()
| Needs a host to subscribe the component to an existing store. No host at module scope. |
Unsub on unmount. The store itself is created separately (store() / class store).
|
Hooks & lifecycle
|
API
|
SPA · module
|
SPA · outer
|
Template / effect
|
SSR · App.ts module
|
SSR · outer
|
Why
|
Cleanup / notes
|
|---|---|---|---|---|---|---|---|
onMount / onCleanup / onPaint / onAllMount / onError
|
Registered on the host. Template would add a new handler every render. SSR App.ts has no host.
|
Return a cleanup from sync onMount, or call onCleanup. Async onMount cannot return cleanup — use onCleanup beside it. SSR awaits async mounts before HTML.
|
|||||
onWindow / onKeyDown / onResize / clipboard / visibility / …
| Listeners must be tied to a component so they are removed on unmount. |
Auto removeEventListener on unmount. Optional returned cleanup runs before the next fire and on unmount. SSR: most are no-ops.
|
|||||
onInterval / onTimeout / onCountdown
| Timers without a host leak. SSR module would start process-wide timers. | Cleared on unmount. SSR returns noop handles — do not seed UI from ticks. | |||||
onSocket / onNavigate / onUpdate
|
Subscribe on the host. onNavigate needs router scope from utils.
|
Unsub on unmount. Create ws() in outer, then onSocket(fn, [socket]).
|
CSS-in-JS (must be module-level)
|
API
|
SPA · module
|
SPA · outer
|
Template / effect
|
SSR · App.ts module
|
SSR · outer
|
Why
|
Cleanup / notes
|
|---|---|---|---|---|---|---|---|
stylesheet() / globalCSS / keyframes / globalTheme
| Must | Must | Opposite of atoms: CSS-in-JS must stay at ES module scope so class names are stable. Calling in a component would mint new classes every mount. | SSR render pass skips emitting CSS (the client sheet loads). No per-request pollution — this is a stylesheet, not user state. |
Always OK to use in the template
|
API
|
SPA · module
|
SPA · outer
|
Template / effect
|
SSR · App.ts module
|
SSR · outer
|
Why
|
Cleanup / notes
|
|---|---|---|---|---|---|---|---|
html / svg / x() / view()
|
Templates belong in the inner function. Module-level html is a static snippet, not a component.
| No cleanup. Read atoms in the inner function so tracking works. | |||||
utils (property access)
|
Import of the proxy is always fine. Reading members looks up the window MatesUtils store and throws until renderApp.
|
Do not destructure at module load. After mount, helpers / timers / fetch handlers may call utils.navigateTo(). One app per window.
|
|||||
Reading atom() / directives (classes, when, …)
|
Reads in the template (and inside effect/memo) are tracked. Reads in outer/module are untracked snapshots.
| Nothing to clean up. Do not create primitives in directives’ render paths. |
Exceptions & legacy exports
|
API
|
SPA · module
|
SPA · outer
|
Template / effect
|
SSR · App.ts module
|
SSR · outer
|
Why
|
Cleanup / notes
|
|---|---|---|---|---|---|---|---|
xTabEvent
| Process singleton by design (BroadcastChannel / storage). Not request state. | Create in outer or module on the client. Do not put user data on it during SSR. | |||||
Exported themeAtom / titleAtom / pathAtom / lsStore
|
Legacy process singletons. Importing them from mates during SSR App load can evaluate a shared instance + effect().
|
App code: utils (or useUtils()). The public exports exist for packages like mates-charts.
|
Error messages
SSR module: You can't call 'Atom' in ES module scope during server-side rendering (SSR). Call it in the outer function of the component instead. This is to make sure ES module scope is not polluted — that module graph is shared across all users.
Template: You can't call 'Atom' in the template function. You can only call it in the outer function of the component. The template reruns on every update…
Host-only: You can't call 'ws' at ES module scope or inside inner functions… You can only call it in the outer function of the component (or in a scope class setup())…
Outer function — SPA and SSR
Create atoms and register onMount in the outer function. Read chrome via utils after renderApp.
CSS-in-JS must stay at module scope
stylesheet() is the opposite of atom(): it must run at ES module scope so class names stay stable. Calling it in a component would mint new classes every mount.