Local cache
FFDB uses multiple cache layers for different goals. This guide explains what each layer is for, which ones persist, and how to choose the right storage strategy for your app.
Why multiple cache layers exist
One cache strategy does not fit every runtime or UX need.
- Name
In-memory query cache- Description
Optimizes repeated reads during a single runtime session. Fastest access, no restart persistence.
- Name
Storage adapter cache and auth state- Description
Persists session and related client state depending on adapter choice.
- Name
Offline local database cache- Description
Durable local data layer used by offline mode and sync workflows.
Storage adapter interface
type StorageAdapter = {
get(key: string): Promise<string | null> | string | null
set(key: string, value: string): Promise<void> | void
remove(key: string): Promise<void> | void
}
This abstraction lets browser, desktop, mobile, and server apps use the same client API while plugging in runtime-specific persistence.
Built-in storage options
import { browserStorage, memoryStorage, createClient } from 'ffdb-client'
// Process-only cache/state. Resets on reload.
const memory = memoryStorage()
// Browser persistent options.
const local = browserStorage('local')
const session = browserStorage('session')
const indexed = browserStorage('indexeddb')
const client = await createClient({
config: { apiUrl: import.meta.env.VITE_FFDB_API_URL },
storage: local,
})
Choosing the right storage backend
- Name
memoryStorage()- Description
Best for tests and short-lived sessions. No persistence after reload or process restart.
- Name
browserStorage('local')- Description
Best default for browser apps that should survive tab reloads and browser restarts.
- Name
browserStorage('session')- Description
Useful when session should be isolated per tab.
- Name
browserStorage('indexeddb')- Description
Better when you prefer async storage semantics in browser contexts.
Query cache behavior
FFDB query hooks maintain an in-memory map of settled query results and in-flight promises. This reduces duplicate work during rerenders and concurrent reads.
type QueryCacheRecord = {
data: unknown[]
updatedAt: number
}
// Simplified behavior:
// - queryCache stores settled query results
// - inflightQueries deduplicates same-query concurrent requests
// - clearQueryCache clears both maps
End-to-end cache strategy example
This pattern balances persistence and freshness for browser apps.
import { browserStorage, createClient } from 'ffdb-client'
import type { Database } from './ffdb.types'
const { auth, sync, destroy } = await createClient<Database>({
config: {
apiUrl: import.meta.env.VITE_FFDB_API_URL,
},
storage: browserStorage('local'),
offline: {
adapter: offlineAdapter,
syncOnReconnect: true,
syncOnFocus: true,
},
})
await auth.getSession()
if (sync) {
await sync.run()
}
await destroy()
Practical guidance
- Use persistent storage when users expect to stay signed in across reloads.
- Treat in-memory query cache as performance-only, not a source of durable truth.
- Clear or refresh cached state after auth transitions and significant sync events.
- Keep storage choices explicit per runtime so behavior is predictable in web, mobile, and server contexts.
If your product requires durable offline reads and writes, storage adapters alone are not enough. Pair them with the offline adapter and sync lifecycle features.