Core Concepts

Multi-Tenant Session Scoping

A shared browser — a family computer, a work kiosk, a laptop passed between roommates — can have two different people log into the same app minutes apart. Without an explicit boundary, one person's cached dashboard, queued edits, or in-progress uploads can still be sitting there when the next person logs in. Scoping is how Flux draws that boundary, and keeps it intact even when a logout never happens cleanly.

Why logging out isn't enough on its own

A clean logout button is easy to build and easy to rely on — until a tab crashes, a laptop lid closes mid-session, or an auth cookie silently expires with no logout event ever firing. Any of those leaves cached data belonging to person A still sitting in the browser when person B opens the same app. A caching layer that only clears itself when explicitly told to has a gap exactly where it matters most.

Flux closes that gap with three independent safeguards rather than one, so that even if the first one is skipped entirely, the other two still catch it.

1

Every store declares who it belongs to

Every registered channel carries an explicit scope — either 'global' (the default) for data that's the same for everyone and safe to keep indefinitely, or 'user' for data that belongs to one signed-in person specifically: a dashboard, a billing panel, a private queue of edits. A 'user'-scoped channel is physically kept apart from every other user's copy of that same channel, so one account's data is never reachable under another account's identity, even by accident.
lib/flux.ts (excerpt)
1// A public store — the default. Survives logout, shared by every visitor.
2flux.register({
3 channel: 'pricing',
4 idbKey: 'pricing:latest',
5 ttl: 'long',
6 event: 'UPDATE',
7 store: pricingStoreAdapter,
8})
9
10// A private store — namespaced per user, purged on logout.
11flux.register({
12 channel: 'account_dashboard',
13 idbKey: 'account_dashboard:latest',
14 ttl: 'short',
15 scope: 'user',
16 event: 'UPDATE',
17 store: dashboardStoreAdapter,
18})
2

A single call tears down everything private

Calling flux.clearUserSession() from your logout handler resets every 'user'-scoped store back to empty, cancels any of that user's writes or fetches still in flight, drops their queued offline mutations, stops tracking their in-progress jobs, and clears any conflict prompts waiting on their decision — all in one call. Public, 'global'-scoped data is left completely untouched, since it never belonged to any one person in the first place.
lib/auth.ts (excerpt)
1async function handleLogout() {
2 await flux.clearUserSession();
3 await supabase.auth.signOut();
4 router.push('/login');
5}
Every open tab hears about it

Logging out in one tab clears the session in every other tab open to the same app on that browser, not just the tab where the button was clicked. A second tab left open in the background never keeps holding a signed-out user's data in memory.

3

A self-healing check for the logout that never happened

Not every session ends with a logout call — a crashed tab, a closed laptop lid, or a cookie that silently expired all skip it entirely. To cover this, Flux checks the identity of whoever's session is starting up against whoever's session was there last. If they don't match, the previous person's private data is swept before the new session's data is ever loaded, with no explicit logout required to have triggered it.
This check is deliberately conservative about the reverse case — a public page loading before your app has confirmed whether anyone is logged in at all is the normal, expected order of events on every page load, not a sign that someone logged out. Flux only treats an anonymous state as confirmed once your app has explicitly said so, so a normal page load is never mistaken for a logout and never wipes a still-valid session by mistake.
scope is optional and defaults to 'global', so registrations written before this existed keep working unchanged. In development, Flux will point out — with a console warning only, never a blocking error — if a channel name that sounds private (dashboard, billing, account, and similar) was left on the public default, so a private-looking store doesn't quietly go unscoped by accident.
Available on every tier

Multi-tenant scoping is not a paid feature. Every tier, including Free, gets the same isolation and cleanup guarantees. An app with authenticated dashboards is expected to need this correctly, regardless of what it's paying for — it's treated as a baseline correctness guarantee, not something to upsell.

For the field itself, see api/register and api/clear-user-session. For how scoping interacts with the offline queue and job tracker specifically, see core-concepts/offline-queue and core-concepts/job-tracker.