alepha@docs:~/docs/guides/persistence$
cat 5-caching.md | pretty
4 min read
Last commit:

#Caching

$cache provides a caching system with two modes: automatic function result caching and manual key-value store operations. In-memory by default, with Redis support for distributed environments.

typescript
1import { $cache } from "alepha/cache";

#Function Result Caching

Define a cache with a handler function. The return value is automatically cached based on the input arguments.

typescript
 1import { $cache } from "alepha/cache"; 2  3class UserService { 4  getUserData = $cache({ 5    name: "user-data", 6    ttl: [10, "minutes"], 7    handler: async (userId: string) => { 8      return await this.repo.findById(userId); 9    },10  });11}

Call it like a regular function. The first call executes the handler and caches the result. Subsequent calls with the same arguments return the cached value until the TTL expires.

typescript
1const user = await this.getUserData("some-uuid");

The cache key is derived from the function arguments by default (via JSON.stringify). If the handler throws an error, the result is not cached.

#Manual Store Operations

Omit the handler to use the cache as a manual key-value store.

typescript
 1class SessionService { 2  sessions = $cache({ name: "sessions", ttl: [1, "hour"] }); 3  4  async store(id: string, data: any) { 5    await this.sessions.set(id, data); 6  } 7  8  async get(id: string) { 9    return await this.sessions.get(id);10  }11 12  async clear(id: string) {13    await this.sessions.invalidate(id);14  }15}

#Options

Option Type Default Description
name string ClassName:propertyKey Cache namespace. Keys are stored as cache:<name>:<key>
handler Function - Function whose results are cached automatically
ttl DurationLike [300, "seconds"] (5 min) Time-to-live. Set 0 to disable expiration
key Function JSON.stringify(args) Custom key generator from handler arguments
provider Class or "memory" Injected CacheProvider Override the cache backend
compress boolean false Enable gzip compression (60-80% size reduction)
disabled boolean false Disable caching entirely
memory true or options off Add an in-process L1 memory tier in front of provider
stale DurationLike off Stale-while-revalidate window after ttl expires

#L1 Memory Tier

memory: true puts a process-local memory cache in front of the provider. Reads check memory first and fall back to the provider on miss; writes go to both tiers, so your own writes are immediately visible. Each process/isolate has its own L1 — invalidate() clears the local L1 plus the remote provider, but other processes keep their L1 until its TTL expires, so use a short L1 TTL to bound the staleness window.

#Stale-While-Revalidate

With stale set, a value that outlived its ttl remains servable for the stale window: reads return the stale value immediately and trigger one background refresh (single-flight per key). Requires a handler, since the cache needs to know how to recompute.

typescript
1getPrices = $cache({2  ttl: [1, "minute"],3  stale: [10, "minutes"],4  memory: true,5  handler: async () => this.fetchPrices(),6});

#TTL Format

TTL accepts a DurationLike tuple: [amount, unit].

typescript
1ttl: [30, "seconds"]2ttl: [10, "minutes"]3ttl: [1, "hour"]4ttl: [7, "days"]

The default TTL is 5 minutes (300 seconds). It can be overridden globally via the cacheOptions atom:

typescript
1import { cacheOptions } from "alepha/cache";2 3alepha.store.mut(cacheOptions, (c) => ({ ...c, defaultTtl: 600 }));

#Methods

#run

Execute the handler function with caching. This is what gets called when you invoke the cache as a function. Only available when a handler is defined.

typescript
1const result = await this.getUserData("some-uuid");2// equivalent to:3const result = await this.getUserData.run("some-uuid");

#get

Retrieve a cached value by key. Returns undefined if the key does not exist or has expired.

typescript
1const value = await this.sessions.get("session-123");

#set

Store a value in the cache with an optional TTL override.

typescript
1await this.sessions.set("session-123", sessionData);2await this.sessions.set("session-123", sessionData, [30, "minutes"]);

#invalidate

Remove one or more keys from the cache. Supports pattern-based invalidation with a trailing wildcard *.

typescript
1// Invalidate a specific key2await this.sessions.invalidate("session-123");3 4// Invalidate multiple keys5await this.sessions.invalidate("session-123", "session-456");6 7// Pattern-based invalidation: delete all keys starting with "user:"8await this.cache.invalidate("user:*");

Calling invalidate() with no arguments deletes all entries in the cache's namespace.

#incr

Atomically increment a numeric value. If the key does not exist, it is set to 0 before incrementing.

typescript
1const newCount = await this.counter.incr("page-views", 1);

#key

Get the cache key that would be generated for the given arguments (useful for debugging).

typescript
1const cacheKey = this.getUserData.key("some-uuid");

#Custom Key Generation

Override the default key derivation:

typescript
1getUserData = $cache({2  name: "user-data",3  ttl: [10, "minutes"],4  key: (userId: string) => `user:${userId}`,5  handler: async (userId: string) => {6    return await this.repo.findById(userId);7  },8});

#Compression

Enable gzip compression for cached values to reduce storage size. Useful for large JSON payloads.

typescript
1largeData = $cache({2  name: "large-data",3  ttl: [1, "hour"],4  compress: true,5  handler: async () => {6    return await this.repo.findMany({});7  },8});

Compression adds CPU overhead but reduces storage by 60-80% for typical JSON data.

#Redis Backend

By default, caching uses in-memory storage (MemoryCacheProvider). For distributed caching across multiple instances, switch to Redis.

#Module Registration

typescript
1import { AlephaCacheRedis } from "alepha/cache/redis";2 3const alepha = Alepha.create().with(AlephaCacheRedis);

AlephaCacheRedis automatically registers RedisCacheProvider as the CacheProvider implementation and includes the base AlephaCache module.

#Direct Provider Override

typescript
1import { AlephaCache, CacheProvider } from "alepha/cache";2import { RedisCacheProvider } from "alepha/cache/redis";3 4const alepha = Alepha.create()5  .with(AlephaCache)6  .with({ provide: CacheProvider, use: RedisCacheProvider });

#Redis Key Prefix

Set a prefix for all Redis cache keys via the redisCacheOptions atom — useful for multi-tenant applications or isolating test environments:

typescript
1import { redisCacheOptions } from "alepha/cache/redis";2 3alepha.store.mut(redisCacheOptions, () => ({ prefix: "tenant-a" }));

#Global Configuration

Caching is configured through state atoms, not environment variables:

Atom Field Default Description
cacheOptions enabled true Enable or disable all caching globally
cacheOptions defaultTtl 300 Default TTL in seconds
redisCacheOptions prefix - Prefix for all Redis cache keys
typescript
1import { cacheOptions } from "alepha/cache";2 3alepha.store.mut(cacheOptions, (c) => ({ ...c, enabled: false }));

#Serialization

The cache automatically handles serialization:

  • JSON values: Serialized with JSON.stringify / JSON.parse
  • Strings: Stored as raw UTF-8
  • Uint8Array: Stored as raw binary

The serialization format is detected automatically during deserialization based on a type byte prefix.