Skip to content

Custom Storage ​

Learn how to create custom storage adapters for @pubflow/flowfull-client.

TIP

@pubflow/flowfull-client automatically detects and uses localStorage (browser) or AsyncStorage (React Native). You can also provide a custom storage adapter for advanced use cases.

Storage Adapter Interface ​

All storage adapters must implement the StorageAdapter interface:

typescript
interface StorageAdapter {
  getItem(key: string): Promise<string | null>;
  setItem(key: string, value: string): Promise<void>;
  removeItem(key: string): Promise<void>;
  clear(): Promise<void>;
}

Creating a Custom Adapter ​

Example: IndexedDB Adapter ​

For browsers, you might want to use IndexedDB instead of localStorage for larger storage capacity:

typescript
// src/storage/IndexedDBAdapter.ts

export class IndexedDBAdapter implements StorageAdapter {
  private dbName: string;
  private storeName: string;
  private db: IDBDatabase | null = null;

  constructor(dbName: string = 'pubflow', storeName: string = 'storage') {
    this.dbName = dbName;
    this.storeName = storeName;
  }

  private async getDB(): Promise<IDBDatabase> {
    if (this.db) return this.db;

    return new Promise((resolve, reject) => {
      const request = indexedDB.open(this.dbName, 1);

      request.onerror = () => reject(request.error);
      request.onsuccess = () => {
        this.db = request.result;
        resolve(this.db);
      };

      request.onupgradeneeded = (event) => {
        const db = (event.target as IDBOpenDBRequest).result;
        if (!db.objectStoreNames.contains(this.storeName)) {
          db.createObjectStore(this.storeName);
        }
      };
    });
  }

  async getItem(key: string): Promise<string | null> {
    const db = await this.getDB();
    return new Promise((resolve, reject) => {
      const transaction = db.transaction([this.storeName], 'readonly');
      const store = transaction.objectStore(this.storeName);
      const request = store.get(key);

      request.onerror = () => reject(request.error);
      request.onsuccess = () => resolve(request.result || null);
    });
  }

  async setItem(key: string, value: string): Promise<void> {
    const db = await this.getDB();
    return new Promise((resolve, reject) => {
      const transaction = db.transaction([this.storeName], 'readwrite');
      const store = transaction.objectStore(this.storeName);
      const request = store.put(value, key);

      request.onerror = () => reject(request.error);
      request.onsuccess = () => resolve();
    });
  }

  async removeItem(key: string): Promise<void> {
    const db = await this.getDB();
    return new Promise((resolve, reject) => {
      const transaction = db.transaction([this.storeName], 'readwrite');
      const store = transaction.objectStore(this.storeName);
      const request = store.delete(key);

      request.onerror = () => reject(request.error);
      request.onsuccess = () => resolve();
    });
  }

  async clear(): Promise<void> {
    const db = await this.getDB();
    return new Promise((resolve, reject) => {
      const transaction = db.transaction([this.storeName], 'readwrite');
      const store = transaction.objectStore(this.storeName);
      const request = store.clear();

      request.onerror = () => reject(request.error);
      request.onsuccess = () => resolve();
    });
  }
}

Example: Redis Adapter (Node.js/Bun) ​

For server-side applications, you might want to use Redis:

typescript
// src/storage/RedisAdapter.ts
import { StorageAdapter } from '@pubflow/flowfull-client';
import Redis from 'ioredis';

export class RedisAdapter implements StorageAdapter {
  private client: Redis;
  private prefix: string;

  constructor(redisUrl: string, prefix: string = 'flowfull:') {
    this.client = new Redis(redisUrl);
    this.prefix = prefix;
  }

  private getKey(key: string): string {
    return `${this.prefix}${key}`;
  }

  async getItem(key: string): Promise<string | null> {
    return await this.client.get(this.getKey(key));
  }

  async setItem(key: string, value: string): Promise<void> {
    await this.client.set(this.getKey(key), value);
  }

  async removeItem(key: string): Promise<void> {
    await this.client.del(this.getKey(key));
  }

  async clear(): Promise<void> {
    const keys = await this.client.keys(`${this.prefix}*`);
    if (keys.length > 0) {
      await this.client.del(...keys);
    }
  }

  async disconnect(): Promise<void> {
    await this.client.quit();
  }
}

Using Custom Storage ​

Browser (React/Vue/etc.) ​

typescript
// src/api/client.ts
import { createFlowfull } from '@pubflow/flowfull-client';
import { IndexedDBAdapter } from './storage/IndexedDBAdapter';

const customStorage = new IndexedDBAdapter();

export const api = createFlowfull(import.meta.env.VITE_API_URL, {
  storage: customStorage
});

React Native/Expo ​

typescript
// src/api/client.ts
import { createFlowfull } from '@pubflow/flowfull-client';
import * as SecureStore from 'expo-secure-store';

// Create a secure storage adapter
class SecureStorageAdapter {
  async getItem(key: string): Promise<string | null> {
    return await SecureStore.getItemAsync(key);
  }

  async setItem(key: string, value: string): Promise<void> {
    await SecureStore.setItemAsync(key, value);
  }

  async removeItem(key: string): Promise<void> {
    await SecureStore.deleteItemAsync(key);
  }

  async clear(): Promise<void> {
    // SecureStore doesn't have a clear method
    // You'll need to track keys manually
  }
}

const secureStorage = new SecureStorageAdapter();

export const api = createFlowfull(process.env.EXPO_PUBLIC_API_URL!, {
  storage: secureStorage
});

Node.js/Bun Server ​

typescript
// src/lib/api.ts
import { createFlowfull } from '@pubflow/flowfull-client';
import { RedisAdapter } from './storage/RedisAdapter';

const redisStorage = new RedisAdapter(process.env.REDIS_URL!);

export function createServerApi(sessionId?: string) {
  return createFlowfull(process.env.API_URL!, {
    sessionId,
    storage: redisStorage
  });
}

Storage Keys ​

@pubflow/flowfull-client uses the following storage keys:

  • pubflow_session_id - Session ID for authentication
  • pubflow_user_data - Cached user data (optional)

TIP

The client automatically prefixes keys with pubflow_ to avoid conflicts with other data in storage.

Best Practices ​

  1. Implement Error Handling: Always handle storage errors gracefully
  2. Use Prefixes: Prefix your keys to avoid conflicts with other data
  3. Consider TTL: Implement time-to-live for cached data if needed
  4. Test Thoroughly: Test your adapter with different scenarios
  5. Handle Quota Exceeded: Implement fallback for storage quota errors
  6. Secure Sensitive Data: Use secure storage for sensitive information

Next Steps ​