Skip to content
Effect Days 2026 Get your ticket

Cache

Caches values loaded by an Effect lookup function.

A cache stores successful and failed lookup results, shares an in-progress lookup when multiple callers request the same missing key, and limits entries by capacity and optional time-to-live rules. This module includes helpers for reading, setting, refreshing, invalidating, and inspecting cache contents.

17 exports Added in v2.0.0 Source

Combinators

entries

Added in v4.0.0 Source

Retrieves all key-value pairs from the cache as an iterable. This function only returns entries with successfully resolved values, filtering out any failed lookups or expired entries.

Gotchas

Expired entries are removed from the cache while entries filters them out.

See

  • keys for retrieving only cached keys
  • values for retrieving only cached values

Signature

declare function entries<Key, A, E, R>(self: Cache<Key, A, E, R>): Effect<Iterable<[Key, A], any, any>>

get

Added in v4.0.0 Source

Retrieves the value for a key, invoking the lookup function on a cache miss or expired entry.

Details

Concurrent get calls for the same missing key share the same pending lookup. The cache stores the lookup Exit, so failed lookups are cached and will fail again until the entry expires, is invalidated, or is refreshed.

Signature

declare const get: {
<Key, A>(key: Key): <E, R>(self: Cache<Key, A, E, R>) => Effect<A, E, R>;
<Key, A, E, R>(self: Cache<Key, A, E, R>, key: Key): Effect<A, E, R>;
}

Example

(Getting cached values)

import { Cache, Effect } from "effect"
const program = Effect.gen(function*() {
const cache = yield* Cache.make({
capacity: 10,
lookup: (key: string) => Effect.succeed(key.length)
})
// Cache miss - triggers lookup function
const result1 = yield* Cache.get(cache, "hello")
// Cache hit - returns cached value without lookup
const result2 = yield* Cache.get(cache, "hello")
return { result1, result2 }
})
const actual = await Effect.runPromise(program)
actual // => { result1: 5, result2: 5 }

Example

(Handling lookup failures)

import { Cache, Effect, Exit } from "effect"
// Error handling when lookup fails
const program = Effect.gen(function*() {
const cache = yield* Cache.make<string, number, string>({
capacity: 10,
lookup: (key: string) =>
key === "error"
? Effect.fail("Lookup failed")
: Effect.succeed(key.length)
})
// Successful lookup
const success = yield* Cache.get(cache, "hello")
// Failed lookup - returns error
const failure = yield* Effect.exit(Cache.get(cache, "error"))
return [success, failure] as const
})
const actual = await Effect.runPromise(program)
actual // => [5, Exit.fail("Lookup failed")]

Example

(Sharing concurrent lookups)

import { Cache, Effect } from "effect"
// Concurrent access - multiple gets of same key only invoke lookup once
const program = Effect.gen(function*() {
let lookupCount = 0
const cache = yield* Cache.make({
capacity: 10,
lookup: (key: string) =>
Effect.sync(() => {
lookupCount++
return key.length
})
})
// Multiple concurrent gets
const results = yield* Effect.all([
Cache.get(cache, "hello"),
Cache.get(cache, "hello"),
Cache.get(cache, "hello")
], { concurrency: "unbounded" })
return { results, lookupCount }
})
const actual = await Effect.runPromise(program)
actual // => { results: [5, 5, 5], lookupCount: 1 }

getOption

Added in v4.0.0 Source

Reads an existing cache entry without invoking the lookup function.

Details

Returns Option.none() when the key is missing or expired, and Option.some when a cached lookup has succeeded. If the entry is still pending, waits for it to complete. If the cached or pending lookup fails, this effect fails with the same error.

Signature

declare const getOption: {
<Key, A>(key: Key): <E, R>(self: Cache<Key, A, E, R>) => Effect<Option<A>, E>;
<Key, A, E, R>(self: Cache<Key, A, E, R>, key: Key): Effect<Option<A>, E>;
}

Example

(Reading cached values without lookup)

import { Cache, Effect, Option } from "effect"
const program = Effect.gen(function*() {
const cache = yield* Cache.make({
capacity: 10,
lookup: (key: string) => Effect.succeed(key.length)
})
// No value in cache yet - returns None without lookup
const empty = yield* Cache.getOption(cache, "hello")
// Populate cache using get
yield* Cache.get(cache, "hello")
// Now getOption returns the cached value
const cached = yield* Cache.getOption(cache, "hello")
return [empty, cached] as const
})
const actual = await Effect.runPromise(program)
actual // => [Option.none(), Option.some(5)]

Example

(Skipping expired entries)

import { Cache, Effect, Option } from "effect"
import { TestClock } from "effect/testing"
// Expired entries return None
const program = Effect.gen(function*() {
const cache = yield* Cache.make({
capacity: 10,
lookup: (key: string) => Effect.succeed(key.length),
timeToLive: "1 hour"
})
// Add value to cache
yield* Cache.get(cache, "hello")
// Value exists before expiration
const beforeExpiry = yield* Cache.getOption(cache, "hello")
// Simulate time passing
yield* TestClock.adjust("2 hours")
// Value expired - returns None
const afterExpiry = yield* Cache.getOption(cache, "hello")
return [beforeExpiry, afterExpiry] as const
})
const actual = await Effect.runPromise(Effect.provide(program, TestClock.layer()))
actual // => [Option.some(5), Option.none()]

Example

(Waiting for pending lookups)

import { Cache, Deferred, Effect, Fiber, Option } from "effect"
// Waits for ongoing computation to complete
const program = Effect.gen(function*() {
const deferred = yield* Deferred.make<void>()
const cache = yield* Cache.make({
capacity: 10,
lookup: (_key: string) => Deferred.await(deferred).pipe(Effect.as(42))
})
// Start lookup in background
const getFiber = yield* Effect.forkChild(Cache.get(cache, "key"))
// getOption waits for ongoing computation
const optionFiber = yield* Effect.forkChild(Cache.getOption(cache, "key"))
// Complete the computation
yield* Deferred.succeed(deferred, void 0)
const result = yield* Fiber.join(optionFiber)
const value = yield* Fiber.join(getFiber)
return [result, value] as const
})
const actual = await Effect.runPromise(program)
actual // => [Option.some(42), 42]

getSuccess

Added in v4.0.0 Source

Retrieves the value associated with the specified key from the cache, only if it contains a resolved successful value.

Details

This checks only an existing non-expired entry. It returns Option.some when the entry has already resolved successfully, and Option.none for missing, expired, failed, or still-pending entries.

See

  • get for triggering or awaiting the cache lookup
  • getOption for reading an existing entry as an optional effect

Signature

declare const getSuccess: {
<Key, A, R>(key: Key): <E>(self: Cache<Key, A, E, R>) => Effect<Option<A>>;
<Key, A, E, R>(self: Cache<Key, A, E, R>, key: Key): Effect<Option<A>>;
}

has

Added in v4.0.0 Source

Checks whether the cache contains an entry for the specified key.

Details

This checks for an existing non-expired entry without invoking the cache lookup function. Expired entries are treated as absent.

Signature

declare const has: {
<Key, A>(key: Key): <E, R>(self: Cache<Key, A, E, R>) => Effect<boolean>;
<Key, A, E, R>(self: Cache<Key, A, E, R>, key: Key): Effect<boolean>;
}

Example

(Checking for cached keys)

import { Cache, Effect } from "effect"
const program = Effect.gen(function*() {
const cache = yield* Cache.make({
capacity: 100,
lookup: (key: string) => Effect.succeed(key.length)
})
// Check non-existent key
const missing = yield* Cache.has(cache, "missing")
// Add entry and check existence
yield* Cache.get(cache, "hello")
const present = yield* Cache.has(cache, "hello")
return [missing, present]
})
const actual = await Effect.runPromise(program)
actual // => [false, true]

Example

(Checking TTL expiration)

import { Cache, Effect } from "effect"
import { TestClock } from "effect/testing"
// TTL expiration behavior
const program = Effect.gen(function*() {
const cache = yield* Cache.make({
capacity: 100,
lookup: (key: string) => Effect.succeed(key.length),
timeToLive: "1 hour"
})
// Add entry with TTL
yield* Cache.get(cache, "expires")
const initial = yield* Cache.has(cache, "expires")
// Still valid before expiration
yield* TestClock.adjust("30 minutes")
const beforeExpiry = yield* Cache.has(cache, "expires")
// Expired after TTL
yield* TestClock.adjust("31 minutes")
const afterExpiry = yield* Cache.has(cache, "expires")
return [initial, beforeExpiry, afterExpiry]
})
const actual = await Effect.runPromise(Effect.provide(program, TestClock.layer()))
actual // => [true, true, false]

Example

(Checking multiple keys)

import { Cache, Effect } from "effect"
// Checking multiple keys efficiently
const program = Effect.gen(function*() {
const cache = yield* Cache.make({
capacity: 100,
lookup: (key: string) => Effect.succeed(key.length)
})
// Populate some entries
yield* Cache.set(cache, "apple", 5)
yield* Cache.set(cache, "banana", 6)
// Check multiple keys
const keys = ["apple", "banana", "cherry", "date"]
const results: Array<string> = []
for (const key of keys) {
const exists = yield* Cache.has(cache, key)
results.push(`${key}: ${exists}`)
}
return results
})
const actual = await Effect.runPromise(program)
actual // => ["apple: true", "banana: true", "cherry: false", "date: false"]

invalidate

Added in v4.0.0 Source

Invalidates the entry associated with the specified key in the cache.

Signature

declare const invalidate: {
<Key, A>(key: Key): <E, R>(self: Cache<Key, A, E, R>) => Effect<void>;
<Key, A, E, R>(self: Cache<Key, A, E, R>, key: Key): Effect<void>;
}

Example

(Invalidating cached entries)

import { Cache, Effect } from "effect"
const program = Effect.gen(function*() {
const cache = yield* Cache.make({
capacity: 10,
lookup: (key: string) => Effect.succeed(key.length)
})
// Add a value to the cache
yield* Cache.get(cache, "hello")
const beforeInvalidation = yield* Cache.has(cache, "hello")
// Invalidate the entry
yield* Cache.invalidate(cache, "hello")
const afterInvalidation = yield* Cache.has(cache, "hello")
// Invalidating non-existent keys doesn't error
yield* Cache.invalidate(cache, "nonexistent")
// Get after invalidation will invoke lookup again
let lookupCount = 0
const cache2 = yield* Cache.make({
capacity: 10,
lookup: (key: string) =>
Effect.sync(() => {
lookupCount++
return key.length
})
})
yield* Cache.get(cache2, "test") // lookupCount = 1
yield* Cache.invalidate(cache2, "test")
yield* Cache.get(cache2, "test") // lookupCount = 2 (lookup called again)
return { beforeInvalidation, afterInvalidation, lookupCount }
})
const actual = await Effect.runPromise(program)
actual // => { beforeInvalidation: true, afterInvalidation: false, lookupCount: 2 }

Invalidates all entries in the cache.

Signature

declare function invalidateAll<Key, A, E, R>(self: Cache<Key, A, E, R>): Effect<void>

Example

(Invalidating all entries)

import { Cache, Effect } from "effect"
// Clear all cached entries at once
const program = Effect.gen(function*() {
const cache = yield* Cache.make({
capacity: 10,
lookup: (key: string) => Effect.succeed(key.length)
})
// Populate cache with multiple entries
yield* Cache.get(cache, "apple")
yield* Cache.get(cache, "banana")
yield* Cache.get(cache, "cherry")
const sizeBeforeInvalidation = yield* Cache.size(cache)
const hasAppleBeforeInvalidation = yield* Cache.has(cache, "apple")
// Clear all entries
yield* Cache.invalidateAll(cache)
// Verify cache is empty
const sizeAfterInvalidation = yield* Cache.size(cache)
const hasAppleAfterInvalidation = yield* Cache.has(cache, "apple")
const hasBananaAfterInvalidation = yield* Cache.has(cache, "banana")
const hasCherryAfterInvalidation = yield* Cache.has(cache, "cherry")
return [
sizeBeforeInvalidation,
hasAppleBeforeInvalidation,
sizeAfterInvalidation,
hasAppleAfterInvalidation,
hasBananaAfterInvalidation,
hasCherryAfterInvalidation
]
})
const actual = await Effect.runPromise(program)
actual // => [3, true, 0, false, false, false]

Invalidates the entry associated with the specified key in the cache when the predicate returns true for the cached value.

Signature

declare const invalidateWhen: {
<Key, A>(key: Key, f: Predicate<A>): <E, R>(self: Cache<Key, A, E, R>) => Effect<boolean>;
<Key, A, E, R>(self: Cache<Key, A, E, R>, key: Key, f: Predicate<A>): Effect<boolean>;
}

Example

(Invalidating entries conditionally)

import { Cache, Effect } from "effect"
const program = Effect.gen(function*() {
const cache = yield* Cache.make({
capacity: 10,
lookup: (key: string) => Effect.succeed(key.length)
})
// Add values to the cache
yield* Cache.get(cache, "hello") // value = 5
yield* Cache.get(cache, "hi") // value = 2
// Invalidate when value equals 5
const invalidated1 = yield* Cache.invalidateWhen(
cache,
"hello",
(value) => value === 5
)
const hasHello = yield* Cache.has(cache, "hello")
// Don't invalidate when predicate doesn't match
const invalidated2 = yield* Cache.invalidateWhen(
cache,
"hi",
(value) => value === 5
)
const hasHi = yield* Cache.has(cache, "hi")
// Returns false for non-existent keys
const invalidated3 = yield* Cache.invalidateWhen(
cache,
"nonexistent",
() => true
)
// Returns false for failed cached values
const cacheWithErrors = yield* Cache.make<string, number, string>({
capacity: 10,
lookup: (key: string) =>
key === "fail" ? Effect.fail("error") : Effect.succeed(key.length)
})
yield* Effect.exit(Cache.get(cacheWithErrors, "fail"))
const invalidated4 = yield* Cache.invalidateWhen(
cacheWithErrors,
"fail",
() => true
)
return [invalidated1, hasHello, invalidated2, hasHi, invalidated3, invalidated4]
})
const actual = await Effect.runPromise(program)
actual // => [true, false, false, true, false, false]

keys

Added in v4.0.0 Source

Retrieves all active keys from the cache, automatically filtering out expired entries.

Signature

declare function keys<Key, A, E, R>(self: Cache<Key, A, E, R>): Effect<Iterable<Key, any, any>>

Example

(Reading active keys)

import { Cache, Effect } from "effect"
// Basic key enumeration
const program = Effect.gen(function*() {
const cache = yield* Cache.make({
capacity: 10,
lookup: (key: string) => Effect.succeed(key.length)
})
// Add some entries to the cache
yield* Cache.get(cache, "hello")
yield* Cache.get(cache, "world")
yield* Cache.get(cache, "cache")
// Retrieve all active keys
const keys = yield* Cache.keys(cache)
return Array.from(keys).sort()
})
const actual = await Effect.runPromise(program)
actual // => ["cache", "hello", "world"]

refresh

Added in v4.0.0 Source

Forces a refresh of the value associated with the specified key in the cache.

Details

It will always invoke the lookup function to construct a new value, overwriting any existing value for that key.

Signature

declare const refresh: {
<Key, A>(key: Key): <E, R>(self: Cache<Key, A, E, R>) => Effect<A, E, R>;
<Key, A, E, R>(self: Cache<Key, A, E, R>, key: Key): Effect<A, E, R>;
}

Example

(Refreshing cached values)

import { Cache, Effect } from "effect"
// Force refresh of existing cached values
const program = Effect.gen(function*() {
let counter = 0
const cache = yield* Cache.make({
capacity: 10,
lookup: (key: string) => Effect.sync(() => `${key}-${++counter}`)
})
// Initial cache population
const value1 = yield* Cache.get(cache, "user")
// Get from cache (no lookup)
const value2 = yield* Cache.get(cache, "user")
// Force refresh - always calls lookup
const refreshed = yield* Cache.refresh(cache, "user")
// Subsequent gets return refreshed value
const value3 = yield* Cache.get(cache, "user")
return [value1, value2, refreshed, value3, counter]
})
const actual = await Effect.runPromise(program)
actual // => ["user-1", "user-1", "user-2", "user-2", 2]

Example

(Resetting TTL on refresh)

import { Cache, Effect } from "effect"
import { TestClock } from "effect/testing"
// Refresh resets TTL (Time To Live)
const program = Effect.gen(function*() {
const cache = yield* Cache.make({
capacity: 10,
lookup: (key: string) => Effect.succeed(key.length),
timeToLive: "1 hour"
})
yield* Cache.get(cache, "test")
yield* TestClock.adjust("45 minutes")
// Entry would normally expire in 15 minutes
const beforeRefresh = yield* Cache.has(cache, "test")
// Refresh resets the TTL to full 1 hour
yield* Cache.refresh(cache, "test")
yield* TestClock.adjust("30 minutes")
// Still valid because TTL was reset
const afterRefresh = yield* Cache.has(cache, "test")
return [beforeRefresh, afterRefresh]
})
const actual = await Effect.runPromise(Effect.provide(program, TestClock.layer()))
actual // => [true, true]

Example

(Refreshing missing keys)

import { Cache, Effect } from "effect"
// Refresh non-existent keys
const program = Effect.gen(function*() {
const cache = yield* Cache.make({
capacity: 10,
lookup: (key: string) => Effect.succeed(`value-for-${key}`)
})
// Refresh non-existent key creates new entry
const result = yield* Cache.refresh(cache, "newKey")
// Verify it's now cached
const cached = yield* Cache.has(cache, "newKey")
return [result, cached]
})
const actual = await Effect.runPromise(program)
actual // => ["value-for-newKey", true]

set

Added in v4.0.0 Source

Sets the value associated with the specified key in the cache. This will overwrite any existing value for that key, skipping the lookup function.

Signature

declare const set: {
<Key, A>(key: Key, value: A): <E, R>(self: Cache<Key, A, E, R>) => Effect<void>;
<Key, A, E, R>(self: Cache<Key, A, E, R>, key: Key, value: A): Effect<void>;
}

Example

(Setting values directly)

import { Cache, Effect } from "effect"
const program = Effect.gen(function*() {
const cache = yield* Cache.make({
capacity: 100,
lookup: (key: string) => Effect.succeed(key.length)
})
// Set a value directly without invoking lookup
yield* Cache.set(cache, "hello", 42)
return yield* Cache.get(cache, "hello")
})
const actual = await Effect.runPromise(program)
actual // => 42

Example

(Overwriting cached values)

import { Cache, Effect } from "effect"
// Overwriting existing cached values
const program = Effect.gen(function*() {
const cache = yield* Cache.make({
capacity: 100,
lookup: (key: string) => Effect.succeed(key.length)
})
// First get populates via lookup
const original = yield* Cache.get(cache, "test") // 4
// Set overwrites the cached value
yield* Cache.set(cache, "test", 999)
const updated = yield* Cache.get(cache, "test") // 999
return { original, updated }
})
const actual = await Effect.runPromise(program)
actual // => { original: 4, updated: 999 }

Example

(Applying TTL to set values)

import { Cache, Effect } from "effect"
import { TestClock } from "effect/testing"
// TTL behavior with set operations
const program = Effect.gen(function*() {
const cache = yield* Cache.make({
capacity: 100,
lookup: (key: string) => Effect.succeed(key.length),
timeToLive: "1 hour"
})
// Set value with TTL applied
yield* Cache.set(cache, "temporary", 123)
const beforeExpiry = yield* Cache.has(cache, "temporary")
// Advance time past TTL
yield* TestClock.adjust("2 hours")
const afterExpiry = yield* Cache.has(cache, "temporary")
return [beforeExpiry, afterExpiry]
})
const actual = await Effect.runPromise(Effect.provide(program, TestClock.layer()))
actual // => [true, false]

Example

(Enforcing capacity when setting values)

import { Cache, Effect } from "effect"
// Capacity enforcement with set operations
const program = Effect.gen(function*() {
const cache = yield* Cache.make({
capacity: 2,
lookup: (key: string) => Effect.succeed(key.length)
})
// Fill cache to capacity
yield* Cache.set(cache, "a", 1)
yield* Cache.set(cache, "b", 2)
const sizeBeforeEviction = yield* Cache.size(cache)
// Adding another entry evicts oldest
yield* Cache.set(cache, "c", 3)
const sizeAfterEviction = yield* Cache.size(cache)
const hasOldest = yield* Cache.has(cache, "a")
const hasNewest = yield* Cache.has(cache, "c")
return [sizeBeforeEviction, sizeAfterEviction, hasOldest, hasNewest]
})
const actual = await Effect.runPromise(program)
actual // => [2, 2, false, true]

size

Added in v4.0.0 Source

Retrieves the approximate number of entries in the cache.

Details

Note that expired entries are counted until they are accessed and removed. The size reflects the current number of entries stored, not the number of valid entries.

Signature

declare function size<Key, A, E, R>(self: Cache<Key, A, E, R>): Effect<number>

Example

(Reading cache size)

import { Cache, Effect } from "effect"
const program = Effect.gen(function*() {
const cache = yield* Cache.make({
capacity: 10,
lookup: (key: string) => Effect.succeed(key.length)
})
// Empty cache has size 0
const emptySize = yield* Cache.size(cache)
// Add entries and check size
yield* Cache.get(cache, "hello")
yield* Cache.get(cache, "world")
const sizeAfterAdding = yield* Cache.size(cache)
// Size decreases after invalidation
yield* Cache.invalidate(cache, "hello")
const sizeAfterInvalidation = yield* Cache.size(cache)
return [emptySize, sizeAfterAdding, sizeAfterInvalidation]
})
const actual = await Effect.runPromise(program)
actual // => [0, 2, 1]

values

Added in v4.0.0 Source

Retrieves all successfully cached values from the cache, excluding failed lookups and expired entries.

Signature

declare function values<Key, A, E, R>(self: Cache<Key, A, E, R>): Effect<Iterable<A, any, any>>

Example

(Reading all cached values)

import { Cache, Effect } from "effect"
const program = Effect.gen(function*() {
const cache = yield* Cache.make({
capacity: 10,
lookup: (key: string) => Effect.succeed(key.length)
})
// Add some values to the cache
yield* Cache.get(cache, "a")
yield* Cache.get(cache, "ab")
yield* Cache.get(cache, "abc")
// Retrieve all cached values
const values = yield* Cache.values(cache)
return Array.from(values).sort()
})
const actual = await Effect.runPromise(program)
actual // => [1, 2, 3]

Constructors

make

Added in v2.0.0 Source

Creates a cache with a fixed time-to-live for all entries.

Details

This is the basic cache constructor where all entries share the same TTL. The lookup function will be called when a key is not found or has expired.

Signature

declare function make<Key, A, E = never, R = never, ServiceMode extends "lookup" | "construction" = never>(options: {
readonly capacity: number;
readonly lookup: (key: Key) => Effect<A, E, R>;
readonly requireServicesAt?: ServiceMode;
readonly timeToLive?: Input;
}): Effect<Cache<Key, A, E, "lookup" extends ServiceMode ? R : never>, never, "lookup" extends ServiceMode ? never : R>

Example

(Creating a basic cache)

import { Cache, Effect } from "effect"
// Basic cache with string keys
const program = Effect.gen(function*() {
const cache = yield* Cache.make<string, number>({
capacity: 100,
lookup: (key) => Effect.succeed(key.length)
})
const result1 = yield* Cache.get(cache, "hello")
const result2 = yield* Cache.get(cache, "world")
return { result1, result2 }
})
const actual = await Effect.runPromise(program)
actual // => { result1: 5, result2: 5 }

Example

(Creating a cache with TTL)

import { Cache, Effect } from "effect"
const program = Effect.gen(function*() {
const users = new Map([
[123, { name: "Ada", email: "ada@example.com" }],
[456, { name: "Grace", email: "grace@example.com" }]
])
const cache = yield* Cache.make<
number,
{ name: string; email: string },
string
>({
capacity: 500,
lookup: (userId) =>
Effect.suspend(() => {
const user = users.get(userId)
return user === undefined
? Effect.fail(`User ${userId} not found`)
: Effect.succeed(user)
}),
timeToLive: "15 minutes"
})
const user1 = yield* Cache.get(cache, 123)
const user2 = yield* Cache.get(cache, 123)
return [user1, user2, user1 === user2] as const
})
const actual = await Effect.runPromise(program)
actual // => [{ name: "Ada", email: "ada@example.com" }, { name: "Ada", email: "ada@example.com" }, true]

makeWith

Added in v2.0.0 Source

Creates a cache with dynamic time-to-live based on the result and key.

When to use

Use when you need different cache entry lifetimes based on the lookup result or key characteristics.

Details

The timeToLive function receives both the exit result and the key, allowing for flexible TTL policies based on success/failure state and key characteristics.

See

  • make for a simpler cache constructor with a fixed time-to-live for all entries

Signature

declare function makeWith<Key, A, E = never, R = never, ServiceMode extends "lookup" | "construction" = never>(lookup: (key: Key) => Effect<A, E, R>, options: {
readonly capacity: number;
readonly requireServicesAt?: ServiceMode;
readonly timeToLive?: (exit: Exit<A, E>, key: Key) => Input;
}): Effect<Cache<Key, A, E, "lookup" extends ServiceMode ? R : never>, never, "lookup" extends ServiceMode ? never : R>

Example

(Configuring dynamic time to live)

import { Cache, Effect, Exit } from "effect"
// Cache with TTL based on computed value
const program = Effect.gen(function*() {
const cache = yield* Cache.makeWith(
(id: number) => Effect.succeed({ id, active: id % 2 === 0 }),
{
capacity: 1000,
timeToLive(exit) {
if (Exit.isSuccess(exit)) {
const user = exit.value
return user.active ? "1 hour" : "5 minutes"
}
return "30 seconds"
}
}
)
return cache.capacity
})
const actual = await Effect.runPromise(program)
actual // => 1000

Models

Cache interface

Added in v2.0.0 Source

A cache interface that provides a mutable key-value store with automatic TTL management, capacity limits, and lookup functions for cache misses.

Signature

interface Cache<in out Key, in out A, in out E = never, out R = never> extends Pipeable {
readonly "~effect/Cache": "~effect/Cache";
readonly capacity: number;
readonly lookup: (key: Key) => Effect<A, E, R>;
readonly map: MutableHashMap<Key, Entry<A, E>>;
readonly timeToLive: (exit: Exit<A, E>, key: Key) => Duration;
}

Example

(Creating a basic cache)

import { Cache, Effect } from "effect"
// Basic cache with string keys and number values
const program = Effect.gen(function*() {
const cache = yield* Cache.make<string, number>({
capacity: 100,
lookup: (key: string) => Effect.succeed(key.length)
})
// Cache operations
const value1 = yield* Cache.get(cache, "hello") // 5
const value2 = yield* Cache.get(cache, "world") // 5
const value3 = yield* Cache.get(cache, "hello") // 5 (cached)
return [value1, value2, value3]
})
const actual = await Effect.runPromise(program)
actual // => [5, 5, 5]

Example

(Handling lookup failures)

import { Cache, Effect, Exit } from "effect"
// Cache with error handling
const program = Effect.gen(function*() {
const cache = yield* Cache.make<string, number, string>({
capacity: 10,
lookup: (key: string) =>
key === "error"
? Effect.fail("Lookup failed")
: Effect.succeed(key.length)
})
// Handle successful and failed lookups
const success = yield* Cache.get(cache, "test")
const failure = yield* Effect.exit(Cache.get(cache, "error"))
return [success, failure] as const
})
const actual = await Effect.runPromise(program)
actual // => [4, Exit.fail("Lookup failed")]

Example

(Using complex keys with TTL)

import { Cache, Data, Duration, Effect } from "effect"
// Cache with complex key types and TTL
class UserId extends Data.Class<{ id: number }> {}
const program = Effect.gen(function*() {
const userCache = yield* Cache.make<UserId, string>({
capacity: 1000,
lookup: (userId: UserId) => Effect.succeed(`User-${userId.id}`),
timeToLive: Duration.minutes(5)
})
const userId = new UserId({ id: 123 })
const userName = yield* Cache.get(userCache, userId)
return userName
})
const actual = await Effect.runPromise(program)
actual // => "User-123"

Entry interface

Added in v4.0.0 Source

Represents a low-level cache entry containing a deferred lookup result and an optional expiration timestamp.

When to use

Use when inspecting a Cache's low-level map and you need the stored deferred lookup result or expiration timestamp for a key.

Details

An expiresAt value of undefined means the entry does not expire.

See

  • Cache for the public cache API that manages entries through combinators

Signature

interface Entry<A, E> {
awaiters: number;
expiresAt: number | undefined;
readonly fiber: Fiber<A, E>;
await(this: Entry<A, E>): Effect<A, E>;
}