The Cache Storage API provides a web-standard interface for caching HTTP responses and other data. It's designed for storing network requests and responses, enabling offline functionality and improved performance.

Overview

The Cache Storage API in Andromeda allows you to:

  • Store HTTP requests and responses for offline access
  • Cache static assets and API responses
  • Implement custom caching strategies
  • Manage multiple named caches
  • Follow the web standards for cache management

Basic Usage

Opening a Cache

// Open or create a cache with a specific name
const cache = await caches.open("my-cache-v1");

// Open cache for API responses
const apiCache = await caches.open("api-cache");

// Open cache for static assets
const assetCache = await caches.open("static-assets");

Storing Responses

// Cache a fetch response
const response = await fetch("https://api.example.com/data");
await cache.put("https://api.example.com/data", response);

// Cache multiple requests
const urls = [
  "https://api.example.com/users",
  "https://api.example.com/posts",
  "https://api.example.com/comments",
];

for (const url of urls) {
  const response = await fetch(url);
  await cache.put(url, response);
}

Retrieving Cached Responses

// Get a cached response
const cachedResponse = await cache.match("https://api.example.com/data");

if (cachedResponse) {
  const data = await cachedResponse.json();
  console.log("Using cached data:", data);
} else {
  console.log("No cached response found");
}

API Reference

caches Global Object

The global caches object provides access to cache management methods.

caches.open(cacheName)

Opens an existing cache or creates a new one with the specified name.

Parameters:

  • cacheName (string): Name of the cache to open or create

Returns:

  • Promise<Cache>: A Cache object for the specified cache

Example:

const cache = await caches.open("my-app-cache");

caches.has(cacheName)

Checks if a cache with the specified name exists.

Parameters:

  • cacheName (string): Name of the cache to check

Returns:

  • Promise<boolean>: True if the cache exists, false otherwise

Example:

const exists = await caches.has("my-cache");
if (exists) {
  console.log("Cache exists");
}

caches.delete(cacheName)

Deletes a cache and all its contents.

Parameters:

  • cacheName (string): Name of the cache to delete

Returns:

  • Promise<boolean>: True if the cache was deleted, false if it didn't exist

Example:

const deleted = await caches.delete("old-cache");
if (deleted) {
  console.log("Cache deleted successfully");
}

caches.keys()

Returns a list of all cache names.

Returns:

  • Promise<string[]>: Array of cache names

Example:

const cacheNames = await caches.keys();
console.log("Available caches:", cacheNames);

Cache Object Methods

cache.put(request, response)

Stores a request/response pair in the cache.

Parameters:

  • request (string | Request): URL string or Request object
  • response (Response): Response object to cache

Returns:

  • Promise<void>

Example:

const response = await fetch("/api/data");
await cache.put("/api/data", response);

cache.match(request, options?)

Retrieves a cached response for the given request.

Parameters:

  • request (string | Request): URL string or Request object to match
  • options (object, optional): Match options

Returns:

  • Promise<Response | undefined>: Cached response or undefined if not found

Example:

const cached = await cache.match("/api/data");
if (cached) {
  const data = await cached.json();
}

cache.matchAll(request?, options?)

Retrieves all cached responses that match the given request.

Parameters:

  • request (string | Request, optional): URL string or Request object to match
  • options (object, optional): Match options

Returns:

  • Promise<Response[]>: Array of matching cached responses

Example:

// Get all cached responses
const allResponses = await cache.matchAll();

// Get responses matching a pattern
const apiResponses = await cache.matchAll("/api/");

cache.add(request)

Fetches a URL and stores the response in the cache.

Parameters:

  • request (string | Request): URL string or Request object to fetch and cache

Returns:

  • Promise<void>

Example:

// Fetch and cache in one operation
await cache.add("https://api.example.com/config");

cache.addAll(requests)

Fetches multiple URLs and stores all responses in the cache.

Parameters:

  • requests (Array<string | Request>): Array of URLs or Request objects

Returns:

  • Promise<void>

Example:

await cache.addAll([
  "/api/users",
  "/api/posts",
  "/static/app.css",
  "/static/app.js",
]);

cache.delete(request, options?)

Removes a cached response.

Parameters:

  • request (string | Request): URL string or Request object to remove
  • options (object, optional): Match options

Returns:

  • Promise<boolean>: True if the entry was deleted, false if not found

Example:

const deleted = await cache.delete("/api/old-data");
if (deleted) {
  console.log("Cached entry removed");
}

cache.keys(request?, options?)

Returns a list of all cached request URLs.

Parameters:

  • request (string | Request, optional): URL pattern to filter by
  • options (object, optional): Match options

Returns:

  • Promise<Request[]>: Array of cached Request objects

Example:

// Get all cached request URLs
const allKeys = await cache.keys();

// Get API-related cached URLs
const apiKeys = await cache.keys("/api/");

Examples

Basic Caching Strategy

async function fetchWithCache(url: string) {
  const cache = await caches.open("api-cache");

  // Try to get from cache first
  let response = await cache.match(url);

  if (response) {
    console.log("Using cached response");
    return response;
  }

  // Fetch from network
  console.log("Fetching from network");
  response = await fetch(url);

  // Cache the response
  if (response.ok) {
    await cache.put(url, response.clone());
  }

  return response;
}

// Usage
const data = await fetchWithCache("https://api.example.com/users");
const users = await data.json();

Cache-First Strategy

async function cacheFirst(url: string, cacheName: string = "default") {
  const cache = await caches.open(cacheName);

  // Always try cache first
  const cachedResponse = await cache.match(url);
  if (cachedResponse) {
    return cachedResponse;
  }

  // Fallback to network
  try {
    const networkResponse = await fetch(url);
    if (networkResponse.ok) {
      await cache.put(url, networkResponse.clone());
    }
    return networkResponse;
  } catch (error) {
    console.error("Network failed:", error);
    throw error;
  }
}

Network-First Strategy

async function networkFirst(url: string, cacheName: string = "default") {
  const cache = await caches.open(cacheName);

  try {
    // Try network first
    const networkResponse = await fetch(url);
    if (networkResponse.ok) {
      await cache.put(url, networkResponse.clone());
      return networkResponse;
    }
  } catch (error) {
    console.warn("Network failed, trying cache:", error);
  }

  // Fallback to cache
  const cachedResponse = await cache.match(url);
  if (cachedResponse) {
    return cachedResponse;
  }

  throw new Error("Both network and cache failed");
}

Cache Management

class CacheManager {
  private static readonly CACHE_VERSION = "v1";
  private static readonly MAX_CACHE_AGE = 24 * 60 * 60 * 1000; // 24 hours

  static async preloadEssentialResources() {
    const cache = await caches.open(`app-${this.CACHE_VERSION}`);

    const essentialUrls = [
      "/api/config",
      "/api/user/profile",
      "/static/critical.css",
      "/static/app.js",
    ];

    await cache.addAll(essentialUrls);
    console.log("Essential resources cached");
  }

  static async cleanupOldCaches() {
    const cacheNames = await caches.keys();
    const currentCache = `app-${this.CACHE_VERSION}`;

    const deletePromises = cacheNames
      .filter((name) => name.startsWith("app-") && name !== currentCache)
      .map((name) => caches.delete(name));

    await Promise.all(deletePromises);
    console.log("Old caches cleaned up");
  }

  static async clearExpiredEntries(cacheName: string) {
    const cache = await caches.open(cacheName);
    const requests = await cache.keys();

    for (const request of requests) {
      const response = await cache.match(request);
      if (response) {
        const dateHeader = response.headers.get("date");
        if (dateHeader) {
          const age = Date.now() - new Date(dateHeader).getTime();
          if (age > this.MAX_CACHE_AGE) {
            await cache.delete(request);
            console.log(`Expired entry removed: ${request.url}`);
          }
        }
      }
    }
  }
}

// Initialize cache management
await CacheManager.preloadEssentialResources();
await CacheManager.cleanupOldCaches();

Selective Caching

async function smartCache(url: string) {
  const cache = await caches.open("smart-cache");

  // Check if URL should be cached
  const shouldCache = (url: string) => {
    return url.includes("/api/") &&
      !url.includes("/user/") &&
      !url.includes("/auth/");
  };

  if (!shouldCache(url)) {
    return fetch(url);
  }

  // Use cache for cacheable URLs
  const cached = await cache.match(url);
  if (cached) {
    // Check if cache is still fresh (5 minutes)
    const cacheTime = cached.headers.get("x-cache-time");
    if (cacheTime && Date.now() - parseInt(cacheTime) < 5 * 60 * 1000) {
      return cached;
    }
  }

  // Fetch fresh data
  const response = await fetch(url);
  if (response.ok) {
    // Add cache timestamp
    const responseToCache = new Response(response.body, {
      status: response.status,
      statusText: response.statusText,
      headers: {
        ...Object.fromEntries(response.headers.entries()),
        "x-cache-time": Date.now().toString(),
      },
    });

    await cache.put(url, responseToCache);
    return response;
  }

  return response;
}

Offline-First Application

class OfflineApp {
  private static readonly CACHE_NAME = "offline-app-v1";

  static async initialize() {
    await this.cacheEssentialResources();
    this.setupFetchHandler();
  }

  private static async cacheEssentialResources() {
    const cache = await caches.open(this.CACHE_NAME);

    const resources = [
      "/",
      "/offline.html",
      "/app.css",
      "/app.js",
      "/api/config",
    ];

    await cache.addAll(resources);
  }

  private static setupFetchHandler() {
    // In a service worker context, you would use:
    // self.addEventListener('fetch', this.handleFetch);

    // For demonstration, wrapping fetch
    const originalFetch = globalThis.fetch;
    globalThis.fetch = this.handleFetch.bind(this);
  }

  private static async handleFetch(request: RequestInfo): Promise<Response> {
    const cache = await caches.open(this.CACHE_NAME);
    const url = typeof request === "string" ? request : request.url;

    try {
      // Try network first for API calls
      if (url.includes("/api/")) {
        const networkResponse = await fetch(request);
        if (networkResponse.ok) {
          await cache.put(request, networkResponse.clone());
        }
        return networkResponse;
      }

      // For other resources, try cache first
      const cachedResponse = await cache.match(request);
      if (cachedResponse) {
        return cachedResponse;
      }

      const networkResponse = await fetch(request);
      if (networkResponse.ok) {
        await cache.put(request, networkResponse.clone());
      }
      return networkResponse;
    } catch (error) {
      console.warn("Network failed, trying cache");

      const cachedResponse = await cache.match(request);
      if (cachedResponse) {
        return cachedResponse;
      }

      // Return offline page for navigation requests
      if (url.endsWith(".html") || !url.includes(".")) {
        const offlinePage = await cache.match("/offline.html");
        if (offlinePage) {
          return offlinePage;
        }
      }

      throw error;
    }
  }
}

// Initialize offline capabilities
await OfflineApp.initialize();

Best Practices

Cache Naming

// Use versioned cache names
const CACHE_VERSION = "v1.2.0";
const cache = await caches.open(`my-app-${CACHE_VERSION}`);

// Use descriptive names for different types of content
const apiCache = await caches.open("api-responses-v1");
const staticCache = await caches.open("static-assets-v1");
const imageCache = await caches.open("images-v1");

Response Cloning

// Always clone responses when caching
const response = await fetch(url);
await cache.put(url, response.clone());
return response; // Original can still be consumed

Cache Size Management

async function manageCacheSize(cacheName: string, maxEntries: number) {
  const cache = await caches.open(cacheName);
  const keys = await cache.keys();

  if (keys.length > maxEntries) {
    // Remove oldest entries (FIFO)
    const entriesToRemove = keys.slice(0, keys.length - maxEntries);
    await Promise.all(
      entriesToRemove.map((key) => cache.delete(key)),
    );
  }
}

Error Handling

async function robustCacheOperation(url: string) {
  try {
    const cache = await caches.open("my-cache");

    try {
      const cached = await cache.match(url);
      if (cached) {
        return cached;
      }
    } catch (cacheError) {
      console.warn("Cache read failed:", cacheError);
    }

    const response = await fetch(url);

    try {
      if (response.ok) {
        await cache.put(url, response.clone());
      }
    } catch (cacheError) {
      console.warn("Cache write failed:", cacheError);
    }

    return response;
  } catch (error) {
    console.error("Operation failed:", error);
    throw error;
  }
}

Notes

  • Cache Storage follows web standards and is persistent across application restarts
  • Cached responses maintain their original headers and status codes
  • Always clone responses when you need to cache and consume them
  • Consider cache size limits and implement cleanup strategies
  • Cache names are case-sensitive and should follow a consistent naming convention

See Also

Found an issue with this page?Edit on GitHub
Last updated: