@roastery-adapters/cache
v0.1.0
Published
Redis and in-memory cache adapter with safe error handling for the Roastery CMS ecosystem.
Maintainers
Readme
@roastery-adapters/cache
Redis and in-memory cache adapter with safe error handling for the Roastery CMS ecosystem.
Overview
@roastery-adapters/cache provides four primitives for integrating cache into Roastery-based applications:
Cachecapsule — A@roastery/blendcapsule manifest (package root export) that declares the adapter's identity, environment needs, and plugin so the orchestration layer can validate and register it.cacheplugin — A Barista plugin (/pluginssubpath) that decorates the host app with a Redis or in-memory cache instance based on theCACHE_PROVIDERenvironment variable.SafeCachedecorator — A method decorator (/decoratorssubpath) that wraps async methods with structured error handling for Redis connection failures, converting them into typedCacheUnavailableExceptionerrors.BaristaCacheInstancetype — The static type (/typessubpath) of the decorated cache instance.
Technologies
| Tool | Purpose |
|------|---------|
| Bun | Native RedisClient, runtime, test runner, and package manager |
| @roastery/blend | Capsule manifest contract (Blend, Plugin) |
| @roastery/barista | Host app abstraction (Elysia-based) with typed env decorator |
| @roastery/terroir | Typed exceptions and TypeBox schemas |
| ioredis-mock | In-memory Redis mock for development and testing |
| tsup | Bundling to ESM + CJS with .d.ts generation |
| Knip | Unused exports and dependency detection |
| Husky + commitlint | Git hooks and conventional commit enforcement |
Installation
bun add @roastery-adapters/cacheThe @roastery/* runtime packages (barista, beans, blend, terroir) are regular dependencies and install automatically.
Peer dependencies (provided by your project):
bun add -d typescript tsup @types/bunCache capsule (Blend)
The package root exports the Cache class, a @roastery/blend capsule manifest. It carries no behavior — it is a declarative identity card the orchestration layer reads to validate the host environment (environmentNeeds) and register the adapter (plugin).
import { Cache } from '@roastery-adapters/cache';
const manifest = new Cache();
// The orchestrator validates `manifest.environmentNeeds` against the
// environment, then registers `manifest.plugin` on the host app.
app.use(manifest.plugin);| Field | Value |
|-------|-------|
| name | @roastery-adapters/cache |
| environmentNeeds | CacheEnvDependenciesDTO (see Environment variables) |
| plugin | The cache plugin |
| dependencies | None — standalone capsule |
cache plugin
cache is a Barista plugin: it receives the host app, reads CACHE_PROVIDER/REDIS_URL from the app's typed env decorator, and decorates the app with a cache instance.
import { barista } from '@roastery/barista';
import { cache } from '@roastery-adapters/cache/plugins';
import { CacheEnvDependenciesDTO } from '@roastery-adapters/cache/dtos';
const app = barista({ environmentDTOs: [CacheEnvDependenciesDTO] }).use(cache);
await app.decorator.cache.set('greeting', 'hello');Backend selection:
CACHE_PROVIDER: "REDIS"— Bun's nativeRedisClient, created withconnectionTimeout: 1000. The client is lazy: it only connects when the first command is issued. RequiresREDIS_URL.CACHE_PROVIDER: "MEMORY"— anioredis-mockinstance; no Redis server required.
If the app already carries a cache decorator, the plugin reuses that instance instead of creating a new client, making it idempotent across repeated registrations.
The decorated instance is typed as BaristaCacheInstance (from @roastery-adapters/cache/types): Bun's RedisClient intersected with an optional flushall. When the MEMORY provider is used, the underlying ioredis-mock instance really implements flushall(): Promise<"OK">, which is handy for clearing all keys (e.g. between test cases). With the REDIS provider, the instance is Bun's native RedisClient, which doesn't have flushall (only send), so the field is optional and should be accessed as cacheInstance.flushall?.().
SafeCache decorator
SafeCache is a method decorator that catches Redis connection errors and re-throws them as CacheUnavailableException from @roastery/terroir.
import { SafeCache } from '@roastery-adapters/cache/decorators';
class UserCacheRepository {
@SafeCache('UserCacheRepository')
async get(key: string) {
return this.cache.get(key);
}
}The optional layerName parameter sets the context name included in the exception. If omitted, it defaults to the class name (target.constructor.name).
Handled error codes:
| Code | Cause |
|------|-------|
| ERR_REDIS_CONNECTION_CLOSED | Redis connection was closed |
| ERR_REDIS_AUTHENTICATION_FAILED | Authentication to Redis failed |
| ERR_REDIS_INVALID_RESPONSE | Redis returned an unexpected response |
All matched errors are re-thrown as CacheUnavailableException. Unrecognized errors are also wrapped.
Environment variables
| Variable | Type | Required | Description |
|----------|------|----------|-------------|
| CACHE_PROVIDER | "REDIS" \| "MEMORY" | Yes | Selects the cache backend |
| REDIS_URL | string (URL) | Only when CACHE_PROVIDER is "REDIS" | Redis connection URL |
A missing CACHE_PROVIDER — or a missing REDIS_URL when the provider is "REDIS" — makes the plugin throw InvalidEnvironmentException.
Exports reference
import { Cache } from '@roastery-adapters/cache'; // Blend capsule manifest
import { cache } from '@roastery-adapters/cache/plugins'; // Barista plugin
import type { BaristaCacheInstance } from '@roastery-adapters/cache/types'; // RedisClient type + optional flushall (MEMORY provider)
import { SafeCache } from '@roastery-adapters/cache/decorators'; // safe method decorator
import { CacheEnvDependenciesDTO } from '@roastery-adapters/cache/dtos'; // env schema + type
import { CacheProviderDTO } from '@roastery-adapters/cache/dtos'; // "REDIS" | "MEMORY" schema + typeDevelopment
# Run tests
bun run test:unit
# Run tests with coverage
bun run test:coverage
# Build for distribution
bun run build
# Check for unused exports and dependencies
bun run knip
# Full setup (knip + build + bun link)
bun run setupUnit tests use the MEMORY provider and need no external services. To exercise the REDIS provider against a real server, start the bundled Redis container:
docker compose up -d # redis:alpine on localhost:6379License
MIT
