API Reference

clearUserSession()

The explicit half of Flux's multi-tenant safety model. Tears down every piece of the current user's private state — cache, queue, jobs, sync anchors, conflicts — across every open tab, while leaving public data completely untouched.

Prerequisites
  • At least one store registered with scope: 'user' — see api/register
  • core-concepts/multi-tenant-scoping for the full three-layer isolation model this API is part of
1

Basic usage

Call this from your logout handler, and ideally also from an auth-state-change listener — a silently expired session should be cleaned up just as thoroughly as an explicit logout click.
typescript
1// Call this from wherever your app learns a session has ended —
2// a logout button handler, and/or an auth-state-change listener
3// so a silent session expiry is covered too, not just an explicit click.
4
5async function handleLogout() {
6 await supabase.auth.signOut()
7 await flux.clearUserSession()
8}
9
10// Auth-state-change listener — covers expiry, not just explicit logout
11supabase.auth.onAuthStateChange((event) => {
12 if (event === 'SIGNED_OUT') {
13 flux.clearUserSession()
14 }
15})
2

What's cleared, what isn't

Only 'user'-scoped data is touched. 'global'-scoped stores — pricing, homepage, docs, anything public — are completely untouched and survive exactly as before.
typescript
1// clearUserSession() only ever touches 'user'-scoped data.
2// 'global'-scoped stores and cached data are completely untouched —
3// pricing, homepage, docs content, anything public stays exactly
4// as it was, surviving the logout indefinitely as designed.
5
6flux.register({ channel: 'pricing', scope: 'global', /* untouched */ })
7flux.register({ channel: 'dashboard', scope: 'user', /* purged */ })
3

The full purge routine

Ten steps run in a fixed order internally. This is what makes the call thorough rather than a single best-guess cache wipe.
typescript
1// executeLocalSessionPurge() — what actually runs, in order:
2
3// 1. Aborts the 'user'-scope AbortController immediately — any
4// in-flight bootstrap fetch or queue replay for that scope is
5// cancelled, then a fresh controller is installed for next time
6// 2. Bumps the 'user'-scope generation counter, so any network
7// response still landing after this point is rejected by
8// dispatchToStore's generation barrier instead of resurrecting
9// purged data
10// 3. Finds every 'user'-scoped registration via registry.getByScope('user')
11// and for each: unregisters it (realtime + poller teardown reused
12// from the normal unregister path), then resets its bound store
13// to baseline (store.getState().reset() if available, else
14// setState(null) or setState([]) depending on ingestion type)
15// 4. Clears sync anchors and ActivityBus state for every user-scoped channel
16// 5. Purges tracked job state for 'user' scope only — global job
17// trackers (e.g. a public system-status tracker) are untouched
18// 6. Clears only this tenant's queued offline mutations, across all
19// three storage layers — never the full unconditional clearQueue(),
20// which would also wipe still-pending global-scoped writes
21// 7. Sweeps every IDB cache entry namespaced under this user
22// 8. Filters the universal conflict register, removing any frame
23// whose owning store is 'user'-scoped, and re-fires onConflictUnified
24// 9. Clears the engine's currentUserId to null
25// 10. Broadcasts the purge to every other open tab (see below)
4

Cross-tab propagation

A logout in one tab doesn't leave a second open tab holding onto the old session — the purge gossips itself to every sibling tab automatically.
typescript
1// Step 10 closes the multi-tab gap: without it, logging out in one
2// tab would leave a second open tab still holding the previous
3// session's data in memory indefinitely.
4
5// If this purge was triggered locally (not received from another
6// tab), it's gossiped over the same shared BroadcastChannel used
7// for cross-tab data mirroring:
8// FLUX_USER_SESSION_PURGE_TRIGGER
9
10// Every sibling tab receives this message and runs the exact same
11// local purge routine — you never need to call clearUserSession()
12// in more than one tab yourself.
5

Why in-flight requests can't resurrect purged data

This is the part that makes the purge a real guarantee rather than a race with whatever network call happened to be in flight when the user logged out.
typescript
1// The generation counter + AbortController pair is what actually
2// prevents a stale response from resurrecting purged data — not
3// just a "best effort" cleanup pass.
4
5// Without this: a bootstrap fetch in flight when you log out could
6// still resolve afterward, land in dispatchToStore, and repopulate
7// a 'user'-scoped store you just cleared.
8
9// With it: the fetch is aborted outright, and even if it somehow
10// wasn't, its generation number no longer matches the engine's
11// current counter for that scope — dispatchToStore drops it silently.
Two independent safety nets

The abort and the generation barrier are deliberately redundant — if the abort signal somehow doesn't land in time, the generation check catches it anyway. See core-concepts/multi-tenant-scoping for the full explanation of both primitives.

6

Safe to call unconditionally

You don't need to check whether a session is active first — calling this with no user logged in is a harmless no-op.
typescript
1// Safe to call even if no user session is currently active — it's
2// a no-op purge in that case, not an error. This means you can call
3// it unconditionally from a shared logout/cleanup path without
4// checking currentUserId first.
5await flux.clearUserSession()
7

Reading session state after clearing

flux.currentUserId is set to null as the second-to-last step of the purge. useFlux() mirrors it directly for components that need to react to session start/end.
typescript
1// Read-only getter on the engine, mirrored by useFlux()
2console.log(flux.currentUserId) // null after clearUserSession()
3
4import { useFlux } from '@tsworldtech/flux-next'
5
6function Nav() {
7 const { currentUserId } = useFlux()
8 return currentUserId ? <UserMenu /> : <LoginButton />
9}

For the structural namespacing layer this purge sits on top of, and the boot-time sweep that catches sessions that never call this at all, see core-concepts/multi-tenant-scoping. For the conflicts this purge filters out of the register, see api/get-active-conflicts.