Lease-based single-flight that prevents thundering herd on cache misses.
This library coordinates cache fills with short-lived leases so only one caller does the expensive work while others wait and re-check. The public API is a single Effect entrypoint: Cache.getOrSet.
- Single-call
Cache.getOrSetAPI with Effect-only fetchers and hooks - Lease-based single-flight with bounded waiting
- Pluggable adapters (memory, Redis, Memcached, DynamoDB, MongoDB, Postgres, S3/R2, Cloudflare KV/D1)
- Abort support, typed errors, and customizable wait strategies
pnpm add cache-locking- Node.js >= 18
import { Effect } from 'effect';
import { Cache } from 'cache-locking';
const program = Cache.getOrSet({
adapter: 'memory',
key: 'user:42',
fetcher: () =>
Effect.gen(function* () {
yield* Effect.log('fetching user');
return 'expensive-value';
}),
hooks: {
onLeader: (_value, context) => Effect.log('cache fill', { key: context.key, cached: context.cached }),
},
});
const { value, meta } = await Effect.runPromise(program);
console.log(value, meta.cache);Time options accept Effect Duration inputs (for example Duration.seconds(60) or millisecond numbers).
Fetchers and hooks must return Effect values.
Use adapter config objects for backends that need options:
import { Effect } from 'effect';
import { Cache, type AdapterConfig } from 'cache-locking';
const adapter: AdapterConfig = {
type: 'redis',
options: {
client,
cache: { keyPrefix: 'cache:' },
leases: { keyPrefix: 'lease:' },
},
} as const;
const program = Cache.getOrSet({
adapter,
key: 'user:42',
fetcher: () => Effect.succeed('value'),
});If an adapter only provides a cache (for example Memcached or DynamoDB), supply a leases implementation in the options.
Cache.getOrSet fails with CacheLockingError or your fetcher/hook errors.
import { Effect } from 'effect';
import { formatCacheLockingError, matchCacheLockingError } from 'cache-locking';
const handled = program.pipe(
Effect.catchAll((error) =>
Effect.sync(() =>
matchCacheLockingError(error, {
CACHE_GET_FAILED: (err) => `cache read failed for ${err.context.key}`,
_: (err) => formatCacheLockingError(err),
}),
),
),
);Supported adapter types:
memoryredismemcacheddynamodbmongodbpostgrescloudflare-kvcloudflare-d1s3r2