CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-tanstack--react-query-persist-client

React bindings to work with persisters in TanStack/react-query

Pending
Overview
Eval results
Files

persister-interface.mddocs/

Persister Interface

The Persister interface provides an abstraction for implementing custom storage solutions. It defines the contract for storing, retrieving, and removing persisted cache data across different storage backends.

Capabilities

Persister Interface

Core interface that all storage implementations must follow.

/**
 * Interface for storing and restoring cache to/from a persisted location
 * Implementations can use any storage backend (localStorage, IndexedDB, etc.)
 */
interface Persister {
  /** 
   * Store persisted client data to storage
   * @param persistClient - The client data to persist including metadata
   * @returns Promise or void when storage completes
   */
  persistClient: (persistClient: PersistedClient) => Promisable<void>;
  
  /** 
   * Retrieve persisted client data from storage
   * @returns Promise resolving to stored data or undefined if none exists
   */
  restoreClient: () => Promisable<PersistedClient | undefined>;
  
  /** 
   * Remove persisted client data from storage
   * Called when data is expired, invalid, or needs cleanup
   * @returns Promise or void when removal completes
   */
  removeClient: () => Promisable<void>;
}

interface PersistedClient {
  /** Unix timestamp when the data was persisted */
  timestamp: number;
  /** Cache invalidation string for version control */
  buster: string;
  /** Dehydrated query client state containing queries and mutations */
  clientState: DehydratedState;
}

type Promisable<T> = T | PromiseLike<T>;

Storage Implementation Examples

localStorage Implementation

import type { Persister, PersistedClient } from '@tanstack/react-query-persist-client';

function createLocalStoragePersister(key: string = 'queryClient'): Persister {
  return {
    persistClient: (client: PersistedClient) => {
      localStorage.setItem(key, JSON.stringify(client));
    },
    
    restoreClient: (): PersistedClient | undefined => {
      const stored = localStorage.getItem(key);
      return stored ? JSON.parse(stored) : undefined;
    },
    
    removeClient: () => {
      localStorage.removeItem(key);
    },
  };
}

// Usage
const persister = createLocalStoragePersister('myAppCache');

IndexedDB Implementation

function createIndexedDBPersister(dbName: string, storeName: string): Persister {
  const openDB = () => {
    return new Promise<IDBDatabase>((resolve, reject) => {
      const request = indexedDB.open(dbName, 1);
      
      request.onerror = () => reject(request.error);
      request.onsuccess = () => resolve(request.result);
      
      request.onupgradeneeded = () => {
        const db = request.result;
        if (!db.objectStoreNames.contains(storeName)) {
          db.createObjectStore(storeName);
        }
      };
    });
  };

  return {
    persistClient: async (client: PersistedClient) => {
      const db = await openDB();
      const transaction = db.transaction([storeName], 'readwrite');
      const store = transaction.objectStore(storeName);
      store.put(client, 'queryClient');
    },
    
    restoreClient: async (): Promise<PersistedClient | undefined> => {
      const db = await openDB();
      const transaction = db.transaction([storeName], 'readonly');
      const store = transaction.objectStore(storeName);
      
      return new Promise((resolve, reject) => {
        const request = store.get('queryClient');
        request.onerror = () => reject(request.error);
        request.onsuccess = () => resolve(request.result);
      });
    },
    
    removeClient: async () => {
      const db = await openDB();
      const transaction = db.transaction([storeName], 'readwrite');
      const store = transaction.objectStore(storeName);
      store.delete('queryClient');
    },
  };
}

// Usage
const persister = createIndexedDBPersister('MyAppDB', 'queryCache');

Async Storage Implementation (React Native)

import AsyncStorage from '@react-native-async-storage/async-storage';

function createAsyncStoragePersister(key: string = 'queryClient'): Persister {
  return {
    persistClient: async (client: PersistedClient) => {
      await AsyncStorage.setItem(key, JSON.stringify(client));
    },
    
    restoreClient: async (): Promise<PersistedClient | undefined> => {
      const stored = await AsyncStorage.getItem(key);
      return stored ? JSON.parse(stored) : undefined;
    },
    
    removeClient: async () => {
      await AsyncStorage.removeItem(key);
    },
  };
}

Remote Storage Implementation

function createRemoteStoragePersister(
  apiUrl: string,
  headers: Record<string, string> = {}
): Persister {
  return {
    persistClient: async (client: PersistedClient) => {
      await fetch(`${apiUrl}/cache`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', ...headers },
        body: JSON.stringify(client),
      });
    },
    
    restoreClient: async (): Promise<PersistedClient | undefined> => {
      try {
        const response = await fetch(`${apiUrl}/cache`, {
          headers,
        });
        
        if (response.ok) {
          return await response.json();
        }
        return undefined;
      } catch {
        return undefined;
      }
    },
    
    removeClient: async () => {
      await fetch(`${apiUrl}/cache`, {
        method: 'DELETE',
        headers,
      });
    },
  };
}

Compressed Storage Implementation

import { compress, decompress } from 'lz-string';

function createCompressedPersister(basePersister: Persister): Persister {
  return {
    persistClient: async (client: PersistedClient) => {
      const compressed = {
        ...client,
        clientState: compress(JSON.stringify(client.clientState)),
      };
      await basePersister.persistClient(compressed as any);
    },
    
    restoreClient: async (): Promise<PersistedClient | undefined> => {
      const stored = await basePersister.restoreClient() as any;
      if (!stored) return undefined;
      
      return {
        ...stored,
        clientState: JSON.parse(decompress(stored.clientState)),
      };
    },
    
    removeClient: () => basePersister.removeClient(),
  };
}

// Usage
const compressedPersister = createCompressedPersister(
  createLocalStoragePersister()
);

Multi-Storage Implementation

function createMultiStoragePersister(persisters: Persister[]): Persister {
  return {
    persistClient: async (client: PersistedClient) => {
      // Save to all storage backends
      await Promise.allSettled(
        persisters.map(p => p.persistClient(client))
      );
    },
    
    restoreClient: async (): Promise<PersistedClient | undefined> => {
      // Try each persister until one succeeds
      for (const persister of persisters) {
        try {
          const result = await persister.restoreClient();
          if (result) return result;
        } catch {
          // Continue to next persister
        }
      }
      return undefined;
    },
    
    removeClient: async () => {
      // Remove from all storage backends
      await Promise.allSettled(
        persisters.map(p => p.removeClient())
      );
    },
  };
}

// Usage: Try IndexedDB first, fallback to localStorage
const persister = createMultiStoragePersister([
  createIndexedDBPersister('MyApp', 'cache'),
  createLocalStoragePersister(),
]);

PersistedClient Structure

The PersistedClient interface contains all data needed to restore a query client:

interface PersistedClient {
  /** 
   * Unix timestamp (Date.now()) when the data was persisted
   * Used for age validation against maxAge option
   */
  timestamp: number;
  
  /** 
   * Version string used for cache invalidation
   * Data is removed if buster doesn't match current buster
   */
  buster: string;
  
  /** 
   * Dehydrated state from @tanstack/query-core
   * Contains serialized queries, mutations, and metadata
   */
  clientState: DehydratedState;
}

The clientState contains the actual query and mutation data as produced by TanStack Query's dehydrate() function, including query keys, data, timestamps, and status information.

Install with Tessl CLI

npx tessl i tessl/npm-tanstack--react-query-persist-client

docs

core-persistence.md

experimental-features.md

index.md

persister-interface.md

react-provider.md

tile.json