Skip to content

Redis ​

Utilities for Redis-backed caching, distributed locking, and inter-process communication.

Client Creation ​

typescript
import { createRedis, createRedisOptions } from '@zyno-io/ts-server-foundation';

// Create with default config (REDIS_HOST, REDIS_PORT)
const { client, prefix } = createRedis();

// Create with specific config prefix (uses CACHE_REDIS_* and falls back to REDIS_*)
const { client, prefix } = createRedis('CACHE');

Each call creates a separate connection and registers it with the current application. app.stop() closes those clients automatically, including clients acquired before a failed startup completes. disconnectAllRedis() remains available for callers that need to release every tracked Redis client before application shutdown.

Availability Alerts ​

The generic createAvailabilityMonitor() state machine logs one warning when a dependency becomes unavailable, reports one error if the outage lasts past the configured delay, and rearms after recovery. Call unavailable(error?) and available() from any dependency's lifecycle events.

monitorRedisAvailability() is the Redis-specific adapter. It maps ioredis error and reconnecting events to unavailable and ready to available:

typescript
import { monitorRedisAvailability } from '@zyno-io/ts-server-foundation';

const monitor = monitorRedisAvailability(client, logger, {
    alertAfterMs: config.REDIS_UNAVAILABLE_ALERT_AFTER_MS,
    name: 'SSE Redis',
    warningAfterMs: 2_000
});

The default alert delay is 60 seconds. warningAfterMs is optional and defaults to 0; when set, reconnects that recover before that delay do not produce warning or recovery logs. The sustained-outage alert deadline is still measured from the first failure. These settings affect reporting only and do not slow or limit ioredis reconnection attempts.

Sentinel connections keep one subscription to Sentinel's +switch-master notifications so they reconnect as soon as a failover is announced without opening a detector connection to every discovered Sentinel. A READONLY response also forces master resolution and retries the rejected command, covering promotions where the old master socket remains open after becoming a replica.

Sentinel-mode TCP connections and Sentinel discovery commands each have a one-second timeout by default. This bounds attempts against retired pod addresses and Sentinels that accept connections but stop answering. Application commands retain their existing retry and timeout behavior; the mesh's five-second heartbeat and fifteen-second lease remain unchanged.

When REDIS_SENTINEL_HOST is a highly available endpoint such as a Kubernetes Service backed by all Sentinels, set REDIS_SENTINEL_DISCOVER_PEERS=false. Reconnects then query that endpoint instead of accumulating discovered pod addresses. Keep peer discovery enabled when the configured host is an individual Sentinel whose peers provide redundancy. These settings allow fast recovery during planned handoffs; a genuine outage that outlasts the lease still fences mesh work.

Cache ​

Redis-backed cache with TTL support:

typescript
import { Cache } from '@zyno-io/ts-server-foundation';

// String values
await Cache.set('key', 'value', 3600); // TTL in seconds
const value = await Cache.get('key');

// Object values (auto JSON serialization)
await Cache.setObj('user:123', { name: 'Alice', role: 'admin' }, 3600);
const user = await Cache.getObj<{ name: string; role: string }>('user:123');
MethodDescription
Cache.get(key)Get a string value (returns null if missing)
Cache.set(key, val, ttl)Set a string value with TTL in seconds
Cache.getObj<T>(key)Get and deserialize a JSON value
Cache.setObj(key, val, ttl)Serialize and set a JSON value with TTL

Uses CACHE_REDIS_* environment variables (falls back to REDIS_*).

Distributed Mutex ​

Redis-backed distributed locking for coordinating work across multiple instances:

typescript
import { withMutex, withMutexes, MutexAcquisitionError } from '@zyno-io/ts-server-foundation';

// Single mutex
const result = await withMutex({
    key: ['user', userId],
    fn: async didWait => {
        // Critical section - only one instance executes at a time
        // didWait: true if we had to wait for the lock
        return await processPayment(userId);
    },
    retryCount: 30, // Max retry attempts (default: 30)
    retryDelay: 1000, // Delay between retries in ms (default: 1000)
    renewInterval: 1000 // Lock renewal interval in ms (default: 1000)
});

// Multiple mutexes (acquired in order)
const result = await withMutexes({
    keys: [
        ['resource', resourceA],
        ['resource', resourceB]
    ],
    fn: async didWait => {
        return await updateResources(resourceA, resourceB);
    }
});

Keys ​

Mutex keys can be primitives or arrays: ['resource', 123] becomes resource:123.

Error Handling ​

Throws MutexAcquisitionError if the lock cannot be acquired within the retry window.

Options ​

OptionTypeDefaultDescription
key / keysMutexKey—Lock key(s) — string, number, or array
fn(didWait: boolean) => Promise<T>—Function to execute under lock
retryCountnumber30Max retry attempts
retryDelaynumber1000Delay between retries (ms)
renewIntervalnumber1000Lock renewal interval (ms)

Modes ​

Configure via MUTEX_MODE:

  • local (default) — In-process locking with no Redis dependency. Useful for single-instance or testing scenarios.
  • redis — Uses Redis Lua scripts for atomic acquire/release/renew. Suitable for multi-instance deployments.

Uses MUTEX_REDIS_* environment variables (falls back to REDIS_*).

DevConsole Integration ​

Active mutexes and acquisition history are visible in the DevConsole Mutex view.

Broadcast Channels ​

Redis pub/sub for inter-process communication:

typescript
import { createBroadcastChannel } from '@zyno-io/ts-server-foundation';

// Create a typed broadcast channel
const channel = createBroadcastChannel<{ userId: string; action: string }>('user-events');

channel.subscribe(data => {
    console.log(`${data.userId}: ${data.action}`);
});

channel.publish({ userId: '123', action: 'login' });

Distributed Methods ​

Execute a function locally and broadcast to all instances:

typescript
import { createDistributedMethod } from '@zyno-io/ts-server-foundation';

class CacheManager {
    invalidate = createDistributedMethod<{ key: string }>(
        {
            name: 'invalidate',
            logger: () => this.logger
        },
        async data => {
            this.localCache.delete(data.key);
        }
    );
}

// Calling invalidate() runs locally AND broadcasts to all instances
await cacheManager.invalidate({ key: 'user:123' });

The logger option takes a getter function so it can reference this.logger even when used as a class field initializer. If omitted, a default logger scoped to Distributed:<name> is used.

Uses BROADCAST_REDIS_* environment variables (falls back to REDIS_*).

Configuration ​

All Redis utilities support independent connection configuration via environment variable prefixes:

UtilityEnv PrefixFallback
CacheCACHE_REDIS_*REDIS_*
MutexMUTEX_REDIS_*REDIS_*
BroadcastBROADCAST_REDIS_*REDIS_*
MeshMESH_REDIS_*REDIS_*
BullMQBULL_REDIS_*REDIS_*

Common variables for each prefix:

VariableDescriptionDefault
*_REDIS_HOSTRedis hostunset
*_REDIS_PORTRedis port6379
*_REDIS_PASSWORD_SECRETRedis authentication passwordunset
*_REDIS_PREFIXKey prefixpackage name
*_REDIS_SENTINEL_HOSTRedis Sentinel hostunset
*_REDIS_SENTINEL_PORTRedis Sentinel port26379
*_REDIS_SENTINEL_NAMERedis Sentinel master nameunset
*_REDIS_SENTINEL_CONNECT_TIMEOUT_MSTCP connection timeout in Sentinel mode (including the master connection)1000
*_REDIS_SENTINEL_COMMAND_TIMEOUT_MSPer-command timeout for Sentinel discovery1000
*_REDIS_SENTINEL_DISCOVER_PEERSAdd discovered Sentinels to the reconnect listtrue

For the default shared connection, omit the utility prefix and use REDIS_HOST, REDIS_PORT, REDIS_PASSWORD_SECRET, REDIS_PREFIX, and REDIS_SENTINEL_*.

REDIS_UNAVAILABLE_ALERT_AFTER_MS controls the default outage grace period used with monitorRedisAvailability(). Its default is 60000.

Released under the MIT License.