API Reference

createFlux()

The single entry point. Give it an adapter and it hands back the engine object every other part of your app — store registrations, bootstrap calls, queue replays, session teardown — talks to for the lifetime of the page.

Prerequisites
  • An adapter created via one of the adapters/* packages
  • @tsworldtech/flux installed
1

Basic usage

Call createFlux() once, at module scope, outside any component. The returned object is what you pass to FluxProvider and what every flux.register() call in your app runs against.
typescript
1import { createFlux } from '@tsworldtech/flux'
2import { createSupabaseAdapter } from '@tsworldtech/flux-supabase'
3
4const flux = createFlux({
5 adapter: createSupabaseAdapter(supabaseClient),
6 license: process.env.NEXT_PUBLIC_FLUX_LICENSE_TOKEN,
7})
8
9// flux is now the single object your whole app talks to —
10// register() stores against it, call bootstrap() once on mount,
11// and read isHydrated / isBootstrapped / currentUserId wherever you need them.
One instance per app

Create flux exactly once and import it wherever you need it — don't call createFlux() inside a component body. A second instance means a second IDB connection, a second realtime leader election, and a second offline queue running in parallel with the first.

2

Config reference

Only adapter is required. Everything else has a sensible default and can be added later without touching anything else in your app.
typescript
1interface FluxConfig {
2 adapter: RealtimeAdapter // required — see adapters/*
3 license?: string // signed JWT — see license/setup
4 storagePrefix?: string // namespaces every IDB key + lock — see below
5 ttl?: { // default TTLs, override per-registration
6 short: number
7 medium: number
8 long: number
9 }
10 onStorageFallback?: (layer: 'localStorage' | 'memory', reason: string) => void
11 onConflictUnified?: (conflicts: UnifiedConflictFrame[]) => void
12}
3

storagePrefix

Namespaces every IndexedDB key, Web Lock, and BroadcastChannel this instance touches. You only need to set this if more than one Flux-powered app can run on the same origin at the same time — most single-app setups can leave it unset.
typescript
1// Two Flux-powered apps on the same origin (e.g. a marketing site
2// and a dashboard both served from app.yourcompany.com) need distinct prefixes
3// so their IDB stores, locks, and BroadcastChannels never collide:
4
5const flux = createFlux({
6 adapter: createSupabaseAdapter(supabaseClient),
7 storagePrefix: 'dashboard_',
8})
4

onConflictUnified

Fires whenever any store registered with replayStrategy: 'handshake' produces or resolves a write conflict on reconnect. Always called with the complete current list, not just what changed — this is the one callback a universal conflict-resolution UI needs.
typescript
1const flux = createFlux({
2 adapter: createSupabaseAdapter(supabaseClient),
3 onConflictUnified: (conflicts) => {
4 // Called with the FULL current list every time it changes —
5 // when a new conflict arrives, when one is resolved, and with []
6 // once everything is clear. Drive one conflict-resolution panel off this.
7 conflictStore.getState().setConflicts(conflicts)
8 },
9})
See core-concepts/conflict-resolution for how conflicts are produced, and api/resolve-unified-conflict for acting on them.
5

onStorageFallback

Fires the moment the offline write queue drops out of IndexedDB into a lower-durability layer. Useful for surfacing a "your changes may not survive a refresh" notice in browsers with restricted storage, such as Safari private browsing.
typescript
1const flux = createFlux({
2 adapter: createSupabaseAdapter(supabaseClient),
3 onStorageFallback: (layer, reason) => {
4 // Fires if IndexedDB is unavailable (e.g. Safari private browsing)
5 // and Flux has dropped to localStorage or in-memory for the offline queue.
6 console.warn(`Flux fell back to ${layer}: ${reason}`)
7 },
8})
6

What you get back

The returned FluxEngine is the full public surface of Flux. Most of these have their own dedicated reference page — this is the map.
typescript
1const flux: FluxEngine = createFlux(config)
2
3// Read-only state
4flux.isHydrated // boolean — IDB boot pass has completed
5flux.isBootstrapped // boolean — the 'global' bootstrap call has resolved at least once
6flux.clockSkewMs // number — server/client clock offset, computed during bootstrap()
7flux.realtime // RealtimeManager — the underlying connection manager
8flux.currentUserId // string | null — the active user scope, or null if none
9
10// Store & data
11flux.register(config) // () => void — registers a store, returns an unregister fn
12flux.unregister(channel) // void
13flux.bootstrap(config) // Promise<void> — one network call per scope
14flux.hydrate() // Promise<void> — force a fresh IDB boot pass
15flux.invalidate(channel) // Promise<void> — drop one channel's cached entry
16flux.replay(channel) // Promise<void> — replay one store's offline queue
17flux.replayAll() // Promise<void> — replay every registered queue
18
19// Jobs
20flux.trackJob(jobId, store, config) // () => void — track a job created after the fact
21
22// Conflicts (handshake replay only)
23flux.getActiveConflicts() // UnifiedConflictFrame[]
24flux.subscribeToConflicts(cb) // () => void — same data as onConflictUnified, as a subscription
25flux.resolveUnifiedConflict(id, decision, mergedPayload?) // Promise<void>
26flux.abandonQueueEntry(id) // Promise<void> — discard a conflicted entry outright
27
28// Sessions
29flux.clearUserSession() // Promise<void> — logout purge, see multi-tenant-scoping
30
31// Teardown
32flux.destroy() // void
Reach for the hook first

Inside components, prefer useFlux() over reading properties off flux directly — it re-renders your component when isHydrated, isBootstrapped, currentUserId, or activeConflicts change. Reading flux.isHydrated directly in a render body will not trigger a re-render when it flips.

7

Sessions and multi-tenant data

currentUserId and clearUserSession() exist on every tier, not just Pro — call clearUserSession() from your logout handler so any store registered with scope: 'user' is purged from IndexedDB, the offline queue, and every other open tab.
Full behavior — including what happens if a session ends without ever calling this — is covered in core-concepts/multi-tenant-scoping.
Once you have flux, the next step for most apps is registering your first store and firing a single bootstrap call — see api/register and api/bootstrap.

For per-store configuration, see api/register. For the single-network-call boot sequence, see api/bootstrap. For licensing, see license/setup.