Cache-Tag Middleware

Tag-based HTTP caching with automatic invalidation driven by endpoint annotations

Basic Usage

import { cacheTags } from '@cleverbrush/client/cache';

const client = createClient(api, {
    middlewares: [cacheTags({ defaultTtl: 5000 })],
});

// First call hits the network
await client.todos.list({ query: { page: 1 } });

// Within 5 seconds — cache hit, no network request
await client.todos.list({ query: { page: 1 } });

Server Integration

Cache tags are declared on the server-side endpoint definition via .clearsCacheTag(). Tags flow through the contract metadata to the client automatically.

// contract.ts
const ListTodos = todosResource
    .get()
    .query(TodoListQuerySchema)
    .clearsCacheTag('todo-list', p => ({
        page: p.query.page,
        limit: p.query.limit
    }))
    .returns(array(TodoSchema));

const UpdateTodo = todosResource
    .patch(ById)
    .body(UpdateTodoBodySchema)
    .clearsCacheTag('todo-list')            // invalidates list
    .clearsCacheTag('todo', p => ({        // invalidates entity
        id: p.params.id
    }))
    .returns(TodoSchema);

How It Works

  • On GET:Computes cache key from the endpoint's cache tag names and property selectors. Serves cached response if within TTL.
  • On mutation (POST/PUT/PATCH/DELETE): Invalidates all entries whose key starts with any of the endpoint's tag names — no manual callbacks needed.
  • Property-based keys: Tags with properties differentiate cache entries by request params, query, body, or headers (e.g. different pages get different cache keys).
  • TanStack Query bridge: When used with @cleverbrush/client/react, useMutation hooks automatically invalidate TanStack Query cache for the affected group.

Options

OptionTypeDefault
defaultTtlnumber0
ttlByTag{ [tagName: string]: number }{ }
condition(response) => booleanresponse.ok

Set defaultTtl: 0 for invalidation-only mode: GET responses are not cached, but mutations still invalidate entries created by other endpoints.