CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-swrv

A library using the Vue Composition API for remote data fetching with stale-while-revalidate caching strategy

Pending
Overview
Eval results
Files

cache-system.mddocs/

Cache System

Configurable cache implementations for storing fetched data with TTL support, automatic expiration, and pluggable storage adapters.

Capabilities

SWRVCache Class

Default in-memory cache implementation with automatic expiration and key serialization.

/**
 * In-memory cache implementation with TTL support
 * @param ttl - Default time to live in milliseconds (0 = no expiration)
 */
class SWRVCache<CacheData> {
  constructor(ttl?: number);
  
  /** Retrieve cached item by key */
  get(k: string): ICacheItem<CacheData>;
  
  /** Store item in cache with optional TTL override */
  set(k: string, v: any, ttl: number): void;
  
  /** Remove item from cache */
  delete(serializedKey: string): void;
  
  /** Convert complex keys to stable string identifiers */
  serializeKey(key: IKey): string;
}

Usage Examples:

import { SWRVCache } from "swrv";

// Basic cache usage
const cache = new SWRVCache();
cache.set('/api/users', userData, 0); // No expiration
const item = cache.get('/api/users');

// Cache with default TTL
const shortCache = new SWRVCache(5000); // 5 second default TTL
shortCache.set('/api/temp', tempData, 0); // Override with no expiration
shortCache.set('/api/quick', quickData, 1000); // Override with 1 second TTL

// Using with swrv configuration
const { data } = useSWRV('/api/data', fetcher, {
  cache: new SWRVCache(300000) // 5 minute cache
});

LocalStorageCache Class

Persistent cache implementation using browser localStorage with JSON serialization.

/**
 * localStorage-based cache adapter extending SWRVCache
 * Data persists across browser sessions
 * @param key - localStorage key prefix for all cache entries
 * @param ttl - Default time to live in milliseconds
 */
class LocalStorageCache extends SWRVCache<any> {
  constructor(key?: string, ttl?: number);
  
  /** Retrieve cached item from localStorage */
  get(k: string): ICacheItem<any>;
  
  /** Store item in localStorage with TTL */
  set(k: string, v: any, ttl: number): void;
  
  /** Remove item from localStorage */
  delete(serializedKey: string): void;
}

Usage Examples:

import LocalStorageCache from "swrv/dist/cache/adapters/localStorage";

// Basic localStorage cache
const persistentCache = new LocalStorageCache();

// Custom storage key and TTL
const appCache = new LocalStorageCache('myapp-cache', 86400000); // 24 hour TTL

// Using with swrv for offline support
const { data } = useSWRV('/api/users', fetcher, {
  cache: new LocalStorageCache('user-cache'),
  shouldRetryOnError: false // Don't retry when offline
});

// Multiple cache instances for different data types
const userCache = new LocalStorageCache('users', 3600000); // 1 hour
const settingsCache = new LocalStorageCache('settings', 86400000); // 24 hours

const { data: users } = useSWRV('/api/users', fetcher, { cache: userCache });
const { data: settings } = useSWRV('/api/settings', fetcher, { cache: settingsCache });

Cache Item Structure

Structure of cached data with metadata for expiration and timestamps.

interface ICacheItem<Data> {
  /** The actual cached data */
  data: Data;
  /** Timestamp when item was created */
  createdAt: number;
  /** Timestamp when item expires (Infinity for no expiration) */
  expiresAt: number;
}

Cache Item Examples:

// Accessing cache item metadata
const cache = new SWRVCache();
cache.set('/api/data', { users: [] }, 5000);

const item = cache.get('/api/data');
if (item) {
  console.log('Data:', item.data);
  console.log('Created:', new Date(item.createdAt));
  console.log('Expires:', new Date(item.expiresAt));
  console.log('Age:', Date.now() - item.createdAt);
}

// Check if item is expired
function isExpired(item) {
  return Date.now() >= item.expiresAt;
}

Key Serialization

Convert complex cache keys to stable string identifiers for consistent caching.

/**
 * Serialize complex keys to stable strings
 * @param key - Key to serialize (string, array, function, or reactive)
 * @returns Serialized string key
 */
serializeKey(key: IKey): string;

Key Serialization Examples:

const cache = new SWRVCache();

// String keys (passed through)
cache.serializeKey('/api/users'); // '/api/users'

// Array keys (hashed to stable string)
cache.serializeKey(['/api/search', { q: 'vue', page: 1 }]); // 'arg@"/api/search"@0'

// Function keys (evaluated then serialized)
const userId = ref(123);
cache.serializeKey(() => `/api/users/${userId.value}`); // '/api/users/123'

// Null/undefined keys
cache.serializeKey(null); // ''
cache.serializeKey(undefined); // ''

Custom Cache Implementation

Create custom cache adapters by extending SWRVCache or implementing the same interface.

Custom Cache Examples:

// Redis cache adapter example
class RedisCache extends SWRVCache<any> {
  constructor(private redisClient, ttl = 0) {
    super(ttl);
  }
  
  async get(k: string) {
    const key = this.serializeKey(k);
    const data = await this.redisClient.get(key);
    return data ? JSON.parse(data) : undefined;
  }
  
  async set(k: string, v: any, ttl: number) {
    const key = this.serializeKey(k);
    const timeToLive = ttl || this.ttl;
    const item = {
      data: v,
      createdAt: Date.now(),
      expiresAt: timeToLive ? Date.now() + timeToLive : Infinity
    };
    
    if (timeToLive) {
      await this.redisClient.setex(key, Math.ceil(timeToLive / 1000), JSON.stringify(item));
    } else {
      await this.redisClient.set(key, JSON.stringify(item));
    }
  }
  
  async delete(serializedKey: string) {
    await this.redisClient.del(serializedKey);
  }
}

// IndexedDB cache adapter example
class IndexedDBCache extends SWRVCache<any> {
  constructor(private dbName = 'swrv-cache', ttl = 0) {
    super(ttl);
  }
  
  async get(k: string) {
    const key = this.serializeKey(k);
    const db = await this.openDB();
    const tx = db.transaction(['cache'], 'readonly');
    const store = tx.objectStore('cache');
    const result = await store.get(key);
    
    if (result && Date.now() < result.expiresAt) {
      return result;
    }
    
    return undefined;
  }
  
  async set(k: string, v: any, ttl: number) {
    const key = this.serializeKey(k);
    const timeToLive = ttl || this.ttl;
    const item = {
      key,
      data: v,
      createdAt: Date.now(),
      expiresAt: timeToLive ? Date.now() + timeToLive : Infinity
    };
    
    const db = await this.openDB();
    const tx = db.transaction(['cache'], 'readwrite');
    const store = tx.objectStore('cache');
    await store.put(item);
  }
  
  private async openDB() {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(this.dbName, 1);
      request.onerror = () => reject(request.error);
      request.onsuccess = () => resolve(request.result);
      request.onupgradeneeded = () => {
        const db = request.result;
        if (!db.objectStoreNames.contains('cache')) {
          db.createObjectStore('cache', { keyPath: 'key' });
        }
      };
    });
  }
}

Cache Configuration Patterns

Common patterns for configuring caches in different scenarios.

Configuration Examples:

// Development vs Production caching
const isDev = process.env.NODE_ENV === 'development';
const cache = isDev 
  ? new SWRVCache(1000) // Short TTL for dev
  : new LocalStorageCache('prod-cache', 300000); // Persistent cache for prod

// Multi-tier caching strategy
const l1Cache = new SWRVCache(30000); // 30 second memory cache
const l2Cache = new LocalStorageCache('l2-cache', 3600000); // 1 hour persistent

class MultiTierCache extends SWRVCache<any> {
  get(k: string) {
    // Try L1 first
    let item = l1Cache.get(k);
    if (item) return item;
    
    // Fall back to L2
    item = l2Cache.get(k);
    if (item) {
      // Promote to L1
      l1Cache.set(k, item.data, 30000);
      return item;
    }
    
    return undefined;
  }
  
  set(k: string, v: any, ttl: number) {
    l1Cache.set(k, v, Math.min(ttl, 30000)); // Max 30s in L1
    l2Cache.set(k, v, ttl); // Full TTL in L2
  }
}

// Cache per user/session
function createUserCache(userId: string) {
  return new LocalStorageCache(`user-${userId}`, 3600000);
}

// Conditional caching based on data size
function createAdaptiveCache() {
  return new (class extends SWRVCache<any> {
    set(k: string, v: any, ttl: number) {
      const size = JSON.stringify(v).length;
      
      if (size > 100000) { // Large data to localStorage
        const lsCache = new LocalStorageCache();
        lsCache.set(k, v, ttl);
      } else { // Small data to memory
        super.set(k, v, ttl);
      }
    }
  })();
}

Install with Tessl CLI

npx tessl i tessl/npm-swrv

docs

cache-system.md

data-fetching.md

global-state.md

index.md

tile.json