Persistence
One persist option on usePgTable. Four modes, schema versioning, optional TTL, restore on mount. Drafts and dashboards survive refresh without a separate layer — and the code that reads the table doesn't change one line.
Four modes
Persistence is a property of the table, not a plugin or a middleware. You pick a mode at mount time and the store does the rest. The four modes form a spectrum: nothing, memory-only, per-tab, per-browser.
// 'none' — no persistence, entry cleared on unmount. (default) // 'memory' — entry survives unmount, no webstorage write. // 'session' — sessionStorage write-through on every data / state change. // 'local' — localStorage write-through on every data / state change. usePgTable(charactersColumns, charactersState, { pgTableId: 'docs:concepts:characters', persist: { mode: 'local', schemaVersion: 1 }, })
Mount, write, unmount
The lifecycle is deliberately small. There's no separate restore step in your code, no useEffect that hydrates from storage, no manual flush.
// 1. usePgTable mounts with a stable, caller-assigned pgTableId. // 2. If mode is 'session' or 'local' and autoRestore is true (the default), // pgTable reads the persisted payload and applies it before returning. // 3. Every committed setData / setState writes the new payload back synchronously. // 4. On unmount: // - 'none' → store entry is dropped. // - 'memory' / 'session' / 'local' → store entry stays; reach it by id from // anywhere until you removeTable().
Schema versioning
Persisted payloads carry the schemaVersion they were written under. On restore, pgTable compares the persisted version against the current one and — if they differ — calls onPersistSchemaMismatch so you can migrate or discard. With no handler, mismatched payloads are dropped (the safe default).
usePgTable(charactersColumns, charactersState, { pgTableId: 'docs:concepts:characters', persist: { mode: 'local', schemaVersion: 3, // bump when shape changes onPersistSchemaMismatch: ({ persistedVersion, persistedPayload }) => { if (persistedVersion === 2) { // Migrate v2 → v3 in place. const next = migrateV2toV3(persistedPayload) return { shouldRestore: true, payload: next } } // Default: drop the old payload, start fresh. return { shouldRestore: false } }, }, })
Expiry
Pass expireAfter (ms) and the payload carries an expiresAt timestamp. On restore, expired payloads are deleted before they reach your subscribers — no migration handler, no warning. Use it for caches that shouldn't outlive a session of work.
usePgTable(charactersColumns, charactersState, { pgTableId: 'docs:concepts:characters', persist: { mode: 'local', expireAfter: 1000 * 60 * 60 * 24 * 7, // 7 days }, })
Inspect and clear
pgTable doesn't lock you out of the webstorage entries it owns. getPersistedTableProps returns a list of every persisted table in the current browser with its mode, schema version, and last-updated time. clearStoredTables wipes them (all, by id, or by mode). dropTableEverywhere is the all-in-one: in-memory entry plus webstorage entry, gone.
import { clearStoredTables, dropTableEverywhere, getPersistedTableProps } from '@westopp/pgtable' // Inspect every persisted table in the current browser. getPersistedTableProps() // [{ pgTableId, mode, ... }, ...] // Wipe a specific table from both memory and webstorage. dropTableEverywhere('docs:concepts:characters') // Clear all persisted tables from one storage (e.g. on sign-out). clearStoredTables({ mode: 'local' })