On this page
  1. Install
  2. Compose and use
  3. Configuration reference
  4. Tag and lock behavior
  5. Batches, bounded memory, and observations
  6. Driver capabilities
  7. Bind shared Next.js caching
  8. Boundary
  9. API entry points and requirements
  10. Peer dependencies

@playstack/cache

JSON caching with millisecond TTLs, tag generations, stampede-safe reads, and explicit capabilities.

Free. MIT licensed. Check preview availability before installing. See package access.

@playstack/cache provides a small exact-JSON cache with millisecond TTLs, canonical tag generations, stampede-safe remember, explicit capabilities, and native client access.

Install

After confirming preview access, install the package at your application's shared Playstack version:

sh
npm install --save-exact @playstack/cache@0.1.0-beta.1

Check the peer requirements below before choosing a runtime or provider.

Compose and use

ts
import { createCache } from '@playstack/cache'
import { RedisCacheStorage } from '@playstack/cache/redis'

const cache = createCache({
  app: 'billing',
  environment: 'production',
  driver: new RedisCacheStorage(redis),
  lockTtlMs: 30_000,
})

const plan = await cache.remember('plans:account-1', 60_000, () => loadPlan())

const accountCache = cache.tags('accounts', 'region:us')
await accountCache.put('summary', { total: 4 }, 60_000)
await accountCache.flush()

The physical namespace is {app}:{environment}:cache. Keys allow up to 512 UTF-8 bytes and no control characters. TTLs are positive integer milliseconds.

Configuration reference

PropertyRequiredPurpose
app, environmentYesCanonical physical namespace.
driverYesJSON entry, tag-generation, and optional lock bridge.
lockTtlMsNoDistributed stampede lease; defaults to 30 seconds.

Values cross a versioned exact-JSON boundary. Class instances, undefined, non-finite numbers, sparse arrays, symbols, and cycles fail before the driver. Cached null is retained by remember, while get<T>() intentionally uses T | null for conventional misses.

Tag and lock behavior

Tags are sorted and deduplicated. Tagged and untagged keys remain separate. flush() increments canonical tag generations, making old entries unreachable without scanning; they expire on their original TTL.

Concurrent remember calls on one cache instance always share a promise. A driver with atomic locks extends that across processes. Loader errors remain unchanged, are not cached, and release the lease.

Native lock-capable storage fences publication with putIfLockOwner: a loader whose lease expired cannot overwrite a successor's cached value. Its original caller can still receive the computed, uncached result; the loader is not automatically rerun. This is not an exactly-once execution guarantee.

Batches, bounded memory, and observations

getMany<T>(keys) returns ordered values or nulls and preserves duplicate read keys. putMany([{ key, value, ttlMs }]) validates and snapshots the whole batch before I/O and rejects duplicate write keys. Batches must be dense arrays of at most 1,000 entries; empty batches are no-ops. Tagged batches use one generation snapshot. Redis uses a native multi-get or Lua write; other drivers can fall back to single-key operations. A failed write may be partial or unacknowledged: there is no portable batch transaction or automatic retry.

MemoryCacheStorage is exported from the package root and can serve bounded process-local caches, not just tests. Its defaults are 1,000 values, 16 MiB of UTF-8 key/value bytes, 1,000 tags, and 1,000 locks. Pass positive integer maxEntries, maxBytes, maxTags, and maxLocks options as the second constructor argument (after an optional clock). The byte limit is not a total JavaScript heap bound.

Capacity pressure removes expired entries before least-recently-read values. An oversized value is rejected without evicting other values. pruneExpired(budget = 100) performs a bounded, resumable sweep without installing a timer. Live locks are not evicted, and tag capacity exhaustion fails rather than resetting generations and reviving stale values.

cache.getStats() returns an immutable storage-instance observation of hits, misses, acknowledged writes, and available eviction/expiration counts. These are not namespace-scoped or global request metrics. Unobservable remote counts and unsupported custom-driver statistics are null, not fabricated zeroes.

Driver capabilities

DriverDistributed lockTag consistency
Bounded memoryNo, process-localImmediate
RedisYes, token-checked leaseImmediate
Cloudflare Workers KVNo, process-localEventual

Each driver exposes client and capabilities. Redis uses SET NX PX, Lua release, atomic generation increments, and native millisecond expiry. Workers KV stores logical millisecond expiry while accepting its 60-second minimum physical cleanup.

Bind shared Next.js caching

@playstack/cache-next adapts a configured cache to Next's stable incremental cacheHandler contract and the Next 16 Cache Components cacheHandlers contract. It preserves structured-clone values and streamed Cache Component entries at the framework boundary rather than weakening the core exact-JSON cache.

The package also supplies a timestamped HMAC revalidation handler for both App Router and Pages Router applications. Route placement, cache policy, authorization, and deployment-specific Redis lifecycle remain application-owned.

Boundary

Cache is not authoritative application state, and provider consistency differences are not hidden. Storage/malformed-entry failures become retryable CacheUnavailableError. The application creates, observes, and closes the native client and chooses loaders that finish within the lock lease.

API entry points and requirements

Reference snapshot: @playstack/cache@0.1.0-beta.1. Import only the entry point your runtime needs. Paths below are relative to the installed package; use Go to Definition in your editor to inspect exact parameters, return types and overloads. Do not import the declaration-file paths directly.

Public entry pointDeclaration file
@playstack/cache./dist/index.d.ts
@playstack/cache/errors./dist/errors.d.ts
@playstack/cache/types./dist/types.d.ts
@playstack/cache/testing./dist/testing.d.ts
@playstack/cache/redis./dist/redis.d.ts
@playstack/cache/cloudflare-kv./dist/cloudflare-kv.d.ts
@playstack/cache/package.jsonNo TypeScript declaration (asset or metadata export).

Node.js engine requirement: >=20. This is not a claim that every entry point works in browsers or Workers.

Peer dependencies

This package declares no peer dependencies. Its ordinary dependencies are resolved by the package manager.

For a complete first program, start with Getting started. For API lookup and partial-example conventions, see Reading the reference. Provider failures, lifecycle requirements and application responsibilities remain described in the guide above; types alone do not establish production safety.

Go

Playstack Pro tag
OriginsPricingBlogNewsletterChangelogStatusRoadmap
ContributorsCommunityIn Use ShowcaseCase StudiesPartnersSponsors
FAQsSupportContact

© 2026 Playstack. All rights reserved.

With OSS
Terms of ServicePrivacy PolicyCookie PolicyImprint

By

Commune Software