Skip to content

Custom Storage ​

Learn how to create custom storage adapters for Flowfull Clients.

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 ​

typescript
// storage/IndexedDBAdapter.ts
import { StorageAdapter } from '@pubflow/core';

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) ​

typescript
// storage/RedisAdapter.ts
import { StorageAdapter } from '@pubflow/core';
import Redis from 'ioredis';

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

  constructor(redisUrl: string, prefix: string = 'pubflow:') {
    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 ​

With PubflowProvider ​

typescript
import { PubflowProvider } from '@pubflow/react';
import { IndexedDBAdapter } from './storage/IndexedDBAdapter';

const storage = new IndexedDBAdapter('myapp', 'storage');

function App() {
  return (
    <PubflowProvider
      config={{
        baseUrl: 'https://api.example.com',
        storageConfig: {
          adapter: storage
        }
      }}
    >
      <YourApp />
    </PubflowProvider>
  );
}

With Core Services ​

typescript
import { ApiClient, AuthService } from '@pubflow/core';
import { RedisAdapter } from './storage/RedisAdapter';

const storage = new RedisAdapter('redis://localhost:6379');

const apiClient = new ApiClient({
  baseUrl: 'https://api.example.com',
  storage
});

const authService = new AuthService({
  baseUrl: 'https://api.example.com',
  storage
});

Best Practices ​

  1. Error Handling: Always handle errors gracefully
  2. Async Operations: All methods should be async
  3. Key Prefixing: Use prefixes to avoid key collisions
  4. Cleanup: Implement proper cleanup methods
  5. Testing: Test all storage operations thoroughly

Next Steps ​