Skip to main content

Overview

Introduction

@keq-request/cache is a lightweight transport-layer request caching middleware. It intervenes before and after requests are sent, working independently of UI frameworks and runtime environments — whether it's browser, Node.js, React Native, or mini-programs, it provides consistent caching capabilities.

Core Features

  • 🎯 Multiple Cache Strategies - Network First, Cache First, Stale-While-Revalidate, Network Only, with support for custom strategies and strategy composition
  • 💾 Multi-Tier Storage Architecture - Memory, IndexedDB, multi-level cache coordination, with built-in LRU/LFU/TTL/Random eviction policies
  • 🔒 Request Serialization - Concurrent requests with the same cache key are automatically queued to prevent cache stampede and duplicate requests
  • 📡 Observable - cache:hit / cache:miss / cache:update events + Server-Timing response headers
  • 🎨 Rule-Based Configuration - Global rule matching + request-level overrides for flexible granularity control
  • 🔧 Extensible - Custom cache strategy functions, custom storage implementations

Quick Start

Basic Usage

import { request } from "keq"
import { cache, MemoryStorage } from "@keq-request/cache"

// Create a storage instance
const storage = new MemoryStorage()

// Use the cache middleware
// By default, no requests are cached
request.use(cache({ storage }))

// Make a request
request
  .get("/api/data")
  // Enable caching
  .option('cache', {
    key: '/api/data',
    strategy: Strategy.CACHE_FIRST,
    ttl: 60, // Cache for 60 seconds
  })

Configuring Cache Rules

import { request } from "keq"
import { cache, Strategy, MemoryStorage } from "@keq-request/cache"

request.use(
  cache({
    storage: new MemoryStorage(),
    rules: [
      {
        // Match all GET requests
        pattern: (ctx) => ctx.request.method === "get",

        // Use SWR strategy
        strategy: Strategy.STALE_WHILE_REVALIDATE,

        // Cache TTL of 5 minutes
        ttl: 5 * 60,

        // Custom cache key
        key: (ctx) => ctx.request.__url__.href,

        // Exclude non-200 responses
        exclude: async (response) => response.status !== 200,
      },
    ],
  })
)

Request-Level Configuration

You can override global configuration when making requests:

import { request } from "keq"
import { Strategy } from "@keq-request/cache"

request
  .get("/cats")
  // Override global cache configuration
  .options({
    cache: {
      strategy: Strategy.NETWORK_FIRST,
      key: 'custom-cache-key',
      // Properties not set will use global configuration
      // exclude: async (response) => response.status !== 200,
      ttl: 60,
    },
  })

  // Or disable caching entirely
  .option('cache', false)

Configuration Options

ParameterTypeDefaultDescription
storageKeqCacheStorage-Cache storage instance, see Storage
keyFactory(context) => string(context) => context.locationIdCache key generator function; requests generating the same key share cache
serverTimingbooleanfalseWhether to add Server-Timing to response headers
rulesRule[][]Array of cache rules

rules

ParameterTypeDefaultDescription
patternboolean | (ctx) => booleantrueMatch rule; determines whether this cache rule applies to a request
key(ctx) => stringUses global keyFactoryCustom cache key generator function
strategyStrategyStrategy.NETWORK_FIRSTCache strategy, see Strategies
ttlnumberInfinityCache TTL (seconds)
concurrentbooleanfalseWhether to allow same-key requests to execute concurrently. When false, same-key requests are queued serially
exclude(response) => boolean | Promise<boolean>-Exclude function; returns true to skip caching the response
serverTimingbooleanfalseWhether to add Server-Timing to response headers

Extensions

.on(eventName, callback)

Event NameDescriptionCallback Parameters
cache:hitTriggered on cache hit({ key: string, response: Response, context: KeqContext })
cache:missTriggered on cache miss({ key: string, context: KeqContext})
cache:updateTriggered on cache update({ key: string, oldResponse: Response, newResponse: Response, context: KeqContext })

.option('cache', options)

ParameterTypeDescription
key(ctx) => stringCustom cache key generator function
debugbooleanPrint debug logs
strategyStrategyCache strategy, see Strategies
ttlnumberCache TTL (seconds)
concurrentbooleanWhether to allow same-key requests to execute concurrently. Default false; same-key requests are queued serially to prevent cache stampede
exclude(response) => boolean | Promise<boolean>Exclude function; returns true to skip caching the response
serverTimingbooleanWhether to add Server-Timing to response headers