-
Notifications
You must be signed in to change notification settings - Fork 2
Cacher Redis
Redis-backed distributed cache engine.
Runtime note:
RedisCacherneeds a reachable TCP target. That works on Cloudflare Workers — real TCP via@tundralibs/compat/net'scloudflare:socketsbackend, nonodejs_compatflag needed — but not in a plain browser, which has no raw TCP. The class imports fine everywhere — onlyconnect()requires the socket.
RedisCacher uses a Redis server as its backend via @tundralibs/drivers/redis. It supports authentication, TLS, database selection, and all standard AbstractEngine cache operations.
| Feature | Supported |
|---|---|
| No external dependencies | ❌ |
| Shared across processes | ✅ |
| TLS / SSL support | ✅ |
| Sliding (window) expiry | ✅ |
| Per-entry custom TTL | ✅ |
| Namespace isolation | ✅ |
Deno:
deno add @tundralibs/cacherBun:
bunx jsr add @tundralibs/cacherNode.js:
npx jsr add @tundralibs/cachertype RedisCacherOptions = CacherOptions & {
/** Redis server hostname. Required. */
host: string;
/** Redis server port. Default: 6379. */
port?: number;
/** Redis username for authentication (ACL). */
username?: string;
/** Redis password for authentication. */
password?: string;
/** Redis database number. */
db?: number;
/**
* TLS configuration.
* Pass `true` for default TLS (system CA, no client cert),
* or an EngineSSLOptions object for fine-grained control.
*/
ssl?: boolean | EngineSSLOptions;
/** Default TTL in seconds. 0 = no expiry. Default: 300. */
defaultExpiry?: number;
};username and password must be supplied together — providing only one throws CONFIG_MISSING.
Forwarded verbatim to RedisEngine. Pick one presentation style — inline
PEM strings or file paths — per connection.
type EngineSSLOptions = {
ca?: string[]; // CA certificate(s), PEM strings
cert?: string; // Client certificate PEM string
key?: string; // Client private key PEM string
caFile?: string; // Path to CA certificate file
certFile?: string; // Path to client certificate file
keyFile?: string; // Path to client private key file
rejectUnauthorized?: boolean; // Verify server certificate (default: true)
enforce?: boolean; // Enforce TLS even if not required
};Inline and file-path fields are mutually exclusive. Supplying both styles in one
sslobject (e.g.{ ca: [...], certFile: '...' }) is rejected at the type level and again at runtime.cert/key(andcertFile/keyFile) must also be supplied as a pair — providing only one half throws.cais an array of PEM strings, not a single string.
rejectUnauthorized: falseis not honoured on every runtime: it throws on Deno (which has no in-process certificate-skip primitive), and on Cloudflare Workerssslmay only betrue/false/{ enforce }—cert/key/ca/certFile/keyFile/caFile/rejectUnauthorized: falseall throw there, becausecloudflare:socketsaccepts no TLS material and always verifies the peer against its own trust store. Bun and Node acceptrejectUnauthorized: false.
Creates a new Redis-backed cache instance. Throws CacherEngineError with code CONFIG_MISSING if host is absent or only one of username/password is supplied, and CONFIG_INVALID if name contains : — the reserved namespace separator (a colon in the name would let clear()'s ${name}:* pattern wipe a colon-prefixed sibling namespace). The : rule applies to Cacher.create(...) too, but there it is caught earlier by the manager — which throws a base CacherError (no code) — before this constructor's AbstractEngine check runs.
import { RedisCacher } from '@tundralibs/cacher/engines';
const cache = new RedisCacher('session-cache', {
host: 'redis.example.com',
port: 6379,
defaultExpiry: 3600,
});All methods are inherited from AbstractEngine. The connection is established lazily on the first operation.
| Method | Returns | Description |
|---|---|---|
init() |
Promise<void> |
Connect to Redis (lazy — called automatically) |
finalize() |
Promise<void> |
Disconnect from Redis |
set<T>(key, value, options?) |
Promise<void> |
Store a value |
get<T>(key) |
Promise<T | undefined> |
Retrieve a value |
has(key) |
Promise<boolean> |
Check if a key exists |
delete(key) |
Promise<void> |
Remove a single entry |
clear() |
Promise<void> |
Remove all entries in this namespace |
import { RedisCacher } from '@tundralibs/cacher/engines';
const cache = new RedisCacher('app-cache', {
host: 'localhost',
port: 6379,
defaultExpiry: 300,
});
await cache.set('user:1', { name: 'Alice', role: 'admin' });
const user = await cache.get<{ name: string; role: string }>('user:1');
console.log(user?.name); // 'Alice'
await cache.finalize(); // Disconnect on shutdownimport { Cacher } from '@tundralibs/cacher';
const cache = Cacher.create('REDIS', 'sessions', {
host: 'localhost',
port: 6379,
password: 'secret',
defaultExpiry: 1800,
});
await cache.set('session:abc', { userId: 42 });import { RedisCacher } from '@tundralibs/cacher/engines';
const cache = new RedisCacher('secure-cache', {
host: 'redis.example.com',
port: 6379,
username: 'myapp',
password: 'strongpassword',
db: 1,
defaultExpiry: 600,
});import { RedisCacher } from '@tundralibs/cacher/engines';
// Default TLS (system CA, no client cert)
const cache = new RedisCacher('tls-cache', {
host: 'redis.example.com',
port: 6379,
ssl: true,
defaultExpiry: 300,
});
// Custom TLS with file paths
const cacheWithCerts = new RedisCacher('mtls-cache', {
host: 'redis.example.com',
port: 6379,
ssl: {
caFile: '/etc/ssl/redis-ca.pem',
certFile: '/etc/ssl/client.crt',
keyFile: '/etc/ssl/client.key',
rejectUnauthorized: true,
},
defaultExpiry: 300,
});import { RedisCacher } from '@tundralibs/cacher/engines';
const cache = new RedisCacher('sessions', {
host: 'localhost',
port: 6379,
defaultExpiry: 1800,
});
// TTL is reset on every get()
await cache.set('session:xyz', { userId: 1 }, { window: true });
const session = await cache.get('session:xyz'); // Extends TTLimport { Cacher } from '@tundralibs/cacher';
import { CacherEngineError } from '@tundralibs/cacher';
try {
const cache = Cacher.create('REDIS', 'app', {
host: 'redis.example.com',
port: 6379,
});
await cache.set('ping', true);
} catch (err) {
if (err instanceof CacherEngineError) {
if (err.code === 'CONNECTION_FAILED') {
console.error('Could not connect to Redis:', err.message);
}
}
}- The connection is lazy —
init()is called automatically on the first operation. - Keys are namespaced as
{name}:{key}internally. - Values are JSON-serialized on write and deserialized on read.
- Sliding expiry uses Redis
EXPIREto reset the TTL on eachget(). -
usernameandpasswordmust both be provided or both absent; providing only one throwsCacherEngineError('CONFIG_MISSING', ...). -
clear()runsKEYS {name}:*(glob-escaping the namespace first) then one bulkDELof everything it finds.KEYSis O(N) over the whole keyspace and blocks Redis's single-threaded event loop while it runs — fine for development or a namespace with a small number of keys, but a namespace with many thousands of keys on a shared production server can stall other clients for the duration of the scan. There is noSCAN-based option today; if that matters, clear via a separateSCAN+ batchedDELoutside this cacher, or keep namespaces small enough thatKEYSis cheap.