persist
The full surface of the persist option on usePgTable. Mode plus optional knobs for versioning, TTL, lifecycle callbacks, and restore control. All of it is mount-time configuration — pgTable captures the resolved config when it creates the store entry.
Shape
One required field (mode); the rest are optional. Schema versioning and the lifecycle callbacks come together — versioning gives you the signal, onPersistSchemaMismatch gives you the handler.
type PgTablePersistConfig = { mode: 'none' | 'memory' | 'session' | 'local' // required expireAfter?: number // ms; payload expires this long after the last write schemaVersion?: number // default: 1 autoRestore?: boolean // default: true (only meaningful for 'session' | 'local') // Lifecycle hooks — fired during persist / restore. onPersist?: (payload, ctx) => void onPersistError?: (error, ctx) => void onBeforeRestore?: (payload, ctx) => { shouldRestore: boolean } onPersistSchemaMismatch?: (args) => { shouldRestore: boolean; payload?: PgTableSerialisedPayload } }
Mode
Four modes. 'none' is the default — explicit opt-in keeps the contract small. 'memory' survives unmount without touching webstorage. 'session' and 'local' are write-through to sessionStorage / localStorage on every commit.
// 'none' (default) — no persistence; store entry is dropped on hook unmount. // 'memory' — entry survives unmount; nothing is written to webstorage. // 'session' — write-through to sessionStorage on every commit. // 'local' — write-through to localStorage on every commit. usePgTable(charactersColumns, charactersState, { pgTableId: 'characters', persist: { mode: 'local' }, })
schemaVersion + onPersistSchemaMismatch
Bump schemaVersion whenever you change the shape of columns or state in an incompatible way. On restore, pgTable compares against the persisted version and — if they differ — calls your handler. Return { shouldRestore: true, payload } to migrate; return { shouldRestore: false } (or omit the handler) to drop the old payload.
// Bump schemaVersion whenever the shape of data/state changes in an // incompatible way. Persisted payloads carry the version they were written // under; on restore, mismatches go through onPersistSchemaMismatch. usePgTable(charactersColumns, charactersState, { pgTableId: 'characters', persist: { mode: 'local', schemaVersion: 3, onPersistSchemaMismatch: ({ persistedVersion, persistedPayload }) => { if (persistedVersion === 2) { return { shouldRestore: true, payload: migrateV2toV3(persistedPayload) } } return { shouldRestore: false } // default behaviour: drop the old payload }, }, })
expireAfter
Time-to-live, in milliseconds, measured from the last successful write. The serialised payload carries an expiresAt timestamp; restores past that timestamp delete the payload before any subscriber sees it.
// expireAfter is milliseconds from the last write. Expired payloads are // deleted on the next restore attempt, before any subscriber sees them. usePgTable(charactersColumns, charactersState, { pgTableId: 'characters', persist: { mode: 'local', expireAfter: 1000 * 60 * 60 * 24 * 7, // 7 days }, })
onPersist / onPersistError
onPersist fires after every successful serialise, with the full payload and { pgTableId }. onPersistError fires when webstorage throws (quota, disabled, denied) — pgTable surfaces the error but does not retry.
// onPersist fires after a successful write. ctx is { pgTableId }. // onPersistError fires when the webstorage write throws (quota exceeded, // disabled storage, etc.) — pgTable surfaces it but does not retry. usePgTable(charactersColumns, charactersState, { pgTableId: 'characters', persist: { mode: 'local', onPersist: (payload, ctx) => analytics.track('table.persist', { id: ctx.pgTableId, at: payload.lastUpdatedAt }), onPersistError: (error, ctx) => console.warn('persist failed', ctx.pgTableId, error), }, })
onBeforeRestore
A per-mount veto. The handler receives the persisted payload and returns { shouldRestore: boolean }. Use it to gate restore on user/tenant identity, feature flags, or staleness checks beyond the simple expireAfter rule. A cancelled restore drops the payload before any subscriber sees it.
// onBeforeRestore lets you veto a restore before it lands. Use it for // per-user predicates (e.g. discard another user's data after a session // switch). Returning { shouldRestore: false } drops the payload. usePgTable(charactersColumns, charactersState, { pgTableId: 'characters', persist: { mode: 'local', onBeforeRestore: (payload) => ({ shouldRestore: payload.state.userId === currentUserId, }), }, })
autoRestore
Defaults to true when mode is 'session' or 'local'. Set it to false to disable the on-mount restore without disabling writes — useful when restore should be deferred until you have fetched a remote source of truth and confirmed the local payload is the right baseline.
// autoRestore defaults to true for 'session'/'local'. Set it to false when // you want to control restore timing manually — pgTable will write but not // auto-read on mount, and you can decide how to reconcile with a remote // source of truth yourself. usePgTable(charactersColumns, charactersState, { pgTableId: 'characters', persist: { mode: 'local', autoRestore: false }, })