next-cool-cache
API Reference

createCache()

Factory function to create a typed cache object from a schema

createCache()

The createCache function is the main entry point for next-cool-cache. It takes a schema and scopes, and returns a fully typed cache object.

Import

import { createCache } from 'next-cool-cache';

Signature

function createCache<T, S extends readonly string[]>(
  schema: T,
  scopes: S
): ScopedCache<T, S>

Parameters

schema

Type: T extends Record<string, unknown> Required: Yes

The cache schema defining your resources and their parameters.

const schema = {
  users: {
    list: {},
    byId: { _params: ['id'] as const },
  },
} as const;

Rules:

  • Must use as const for full type inference
  • Leaf nodes are either {} (no params) or { _params: [...] as const } (with params)
  • Branch nodes are nested objects without _params
  • _params is a reserved key

scopes

Type: S extends readonly string[] Required: Yes

Array of scope names for your cache namespaces.

const scopes = ['admin', 'public', 'user'] as const;

Rules:

  • Must use as const for full type inference
  • Each scope becomes a namespace in the returned cache object
  • Common patterns: ['admin', 'public'], ['admin', 'public', 'user']

Return Value

Returns a ScopedCache<T, S> object with:

  1. Scoped namespaces - One property per scope (e.g., cache.admin, cache.public)
  2. Unscoped access - Direct access to resources for cross-scope operations (e.g., cache.users)
  3. Type-safe methods - All paths and parameters are typed

Usage

Basic Usage

import { createCache } from 'next-cool-cache';

const schema = {
  users: {
    list: {},
    byId: { _params: ['id'] as const },
  },
  posts: {
    list: {},
    byId: { _params: ['id'] as const },
  },
} as const;

const scopes = ['admin', 'public'] as const;

export const cache = createCache(schema, scopes);

Accessing Scoped Namespaces

// Admin scope
cache.admin.users.list.cacheTag();
cache.admin.users.byId.revalidateTag({ id: '123' });

// Public scope
cache.public.users.list.cacheTag();
cache.public.users.byId.revalidateTag({ id: '123' });

Cross-Scope Operations

// Invalidate across all scopes
cache.users.byId.revalidateTag({ id: '123' });

// This invalidates 'users/byId:123' which affects all scopes

Testing

Mock the next/cache module in your tests:

import { jest } from '@jest/globals';

const mockCacheTag = jest.fn();
const mockRevalidateTag = jest.fn();
const mockUpdateTag = jest.fn();

jest.mock('next/cache', () => ({
  cacheTag: (...args: unknown[]) => mockCacheTag(...args),
  revalidateTag: (...args: unknown[]) => mockRevalidateTag(...args),
  updateTag: (...args: unknown[]) => mockUpdateTag(...args),
}));

import { createCache } from 'next-cool-cache';

const cache = createCache(schema, scopes);

// Test your code
cache.admin.users.byId.cacheTag({ id: '123' });

// Assert
expect(mockCacheTag).toHaveBeenCalledWith(
  'admin/users/byId:123',
  'admin/users',
  'admin',
  'users/byId:123',
  'users'
);

Examples

Complex Schema

const schema = {
  blog: {
    posts: {
      list: {},
      byId: { _params: ['id'] as const },
      bySlug: { _params: ['slug'] as const },
      byAuthor: { _params: ['authorId'] as const },
    },
    drafts: {
      list: {},
      byId: { _params: ['id'] as const },
    },
    categories: {
      list: {},
      bySlug: { _params: ['slug'] as const },
    },
  },
  users: {
    list: {},
    byId: { _params: ['id'] as const },
    settings: {
      byUserId: { _params: ['userId'] as const },
    },
  },
} as const;

const scopes = ['admin', 'public', 'author'] as const;

const cache = createCache(schema, scopes);

// Access patterns
cache.admin.blog.posts.list.cacheTag();
cache.admin.blog.posts.byId.cacheTag({ id: 'post-123' });
cache.public.blog.categories.bySlug.revalidateTag({ slug: 'tech' });
cache.author.blog.drafts.byId.updateTag({ id: 'draft-456' });

// Branch operations
cache.admin.blog.posts.revalidateTag();  // All posts
cache.admin.blog.revalidateTag();        // All blog content

Multiple Parameters

const schema = {
  comments: {
    byPostAndUser: { _params: ['postId', 'userId'] as const },
  },
  projects: {
    byOwnerAndSlug: { _params: ['ownerId', 'slug'] as const },
  },
} as const;

const cache = createCache(schema, ['admin'] as const);

// Must provide all params
cache.admin.comments.byPostAndUser.cacheTag({
  postId: 'post-1',
  userId: 'user-1',
});

// TypeScript error if missing param
cache.admin.comments.byPostAndUser.cacheTag({ postId: 'post-1' });
// Error: Property 'userId' is missing

Type Safety

The returned cache object is fully typed:

// Autocomplete works
cache.admin.users.b // suggests: byId

// TypeScript catches typos
cache.admin.user.byId // Error: 'user' doesn't exist, did you mean 'users'?

// Params are enforced
cache.admin.users.byId.cacheTag({ userId: '123' }); // Error: Expected 'id', got 'userId'
cache.admin.users.byId.cacheTag(); // Error: Expected params object
cache.admin.users.list.cacheTag({ id: '123' }); // Error: 'list' takes no params

See Also

On this page