Skip to content
Guides contents

GuidesUI & local state

Persisting local tables

Persist local-only client tables in IndexedDB, restore them after reloads, and coordinate updates across tabs.

View as Markdown

The optimistic client’s persistLocal option stores eligible local tables in IndexedDB. It restores them during startup and shares updates between tabs with the same origin and storage identity.

This guide continues Local-only tables. It persists local tables such as drafts. It does not persist the synced dataset or make server queries available offline.

Enable storage

Add persistLocal to that guide’s src/rindle-client.ts configuration:

export const app = await createRindleClient({
  schema: clientSchema,
  mutators,
  user: currentUser,
  api: { url: "", headers: () => ({ "x-user": currentUser() }) },
  persistLocal: {
    user: currentUser(),
    onError: (error) => console.error("Local persistence failed", error),
  },
  onRejected: (envelope, reason) => window.alert(`${envelope.name}: ${reason}`),
});

The imports and development identity come from the preceding guide. With storage available, createRindleClient waits for the initial restore before it returns. Your first local query can therefore read restored drafts.

All { local: true } tables participate. Tables declared with { local: "session" } remain ephemeral and per-tab.

Use a stable storage identity

persistLocal.user selects one IndexedDB database per origin and user string. It stays fixed for that client’s lifetime. It is separate from the authenticated principal that authorizes server reads and mutations.

Close and recreate the client when the signed-in user changes. Choose anonymous storage deliberately: all clients using the same anonymous identity share its local data on that origin.

Understand the durability boundary

A local write updates the engine before asynchronous storage completes. Awaiting store.writeLocal does not acknowledge a durable disk write. A crash can lose a recent update, and the browser can evict storage.

The persistence layer uses IndexedDB, BroadcastChannel, and Web Locks to coordinate tabs. One tab sequences durable writes; other tabs forward and mirror updates. Storage failures can report through onError. Missing browser APIs can instead produce console warnings and disable persistence or cross-tab coordination. Local writes can continue without durable storage.

requestPersistentStorage: true also requests the browser’s persistent-storage permission. The browser controls whether it grants the request; this option does not turn local drafts into a server backup.

A changed local schema hash resets the stored local dataset. There is no application migration callback in this option. Plan an explicit export or other migration path if local data must survive schema changes.

Clear data on logout

Closing a client does not delete its stored rows. If your logout policy requires removal, close the client and then delete its local database:

import { deleteLocalPersistence } from "@rindle/optimistic";
import { app } from "./rindle-client.ts";

export async function clearLocalData(storageUser: string) {
  app.close();
  await deleteLocalPersistence(storageUser);
}

Pass the same string used for persistLocal.user. Coordinate logout across your application’s tabs so another live client does not continue using that identity.