@fluojs/redis
v1.1.0
Published
App-scoped Redis lifecycle management, raw client injection, and RedisService facade for Fluo.
Downloads
301
Maintainers
Readme
@fluojs/redis
Shared Redis connection layer for fluo. It provides a default app-scoped ioredis client plus optional named clients, all managed by the application lifecycle.
Table of Contents
Installation
npm install @fluojs/redis ioredisWhen to Use
- When you need a shared Redis connection across multiple modules (caching, queues, throttlers).
- When you want automatic connection management (connect on bootstrap, quit on shutdown).
- When you need a JSON-aware facade for common key-value operations.
Quick Start
Register the Module
Use RedisModule.forRoot(options) to register the default Redis client and RedisService facade.
RedisModule.forRoot(...) is intentionally synchronous and always creates a new ioredis client from Redis constructor options; it does not adopt an externally constructed client. When migrating from NestJS async dynamic modules such as forRootAsync(...), resolve secrets, environment-specific hosts, and TLS options at the application boundary first, then pass the final Redis options into forRoot(...). Keep externally constructed clients outside this module and close them from the application lifecycle. fluo does not defer Redis module wiring to an async factory hidden inside the module graph.
import { Module } from '@fluojs/core';
import { RedisModule } from '@fluojs/redis';
@Module({
imports: [
RedisModule.forRoot({
host: 'localhost',
port: 6379,
}),
],
})
export class AppModule {}Use the Redis Service
Inject RedisService for high-level operations or REDIS_CLIENT for the raw ioredis instance.
RedisService.get() attempts JSON parsing and falls back to the raw string; missing keys return null. RedisService.set() serializes values with JSON.stringify(): finite positive integer TTLs use Redis EX, while finite positive fractional TTLs use PX with milliseconds rounded up. Omit the TTL or pass a non-positive or non-finite value for a persistent key.
import { Inject } from '@fluojs/core';
import { RedisService } from '@fluojs/redis';
@Inject(RedisService)
export class CacheRepository {
constructor(private readonly redis: RedisService) {}
async saveUser(id: string, user: object) {
await this.redis.set(`user:${id}`, user, 3600);
}
async getUser(id: string) {
return await this.redis.get(`user:${id}`);
}
}Common Patterns
Lifecycle Ownership
Every RedisModule.forRoot(...) registration creates a new client that @fluojs/redis owns, including named clients registered through RedisModule.forRoot({ name, ... }). The module never adopts an existing client instance.
- Fluo always forces
lazyConnect: true, even if callers cast options manually, so sockets open during application bootstrap instead of import time. - During bootstrap, the lifecycle service only calls
connect()while the client is still in iorediswaitstate. - Lifecycle-owned
connect()andquit()calls are bounded by package timeouts (10_000ms by default) so bootstrap and shutdown do not wait forever on a stalled Redis command. Override them withlifecycle.connectTimeoutMsandlifecycle.quitTimeoutMs; pass0only when the host process intentionally owns an unbounded wait. - During shutdown, ready/connecting clients attempt
quit()first for graceful teardown, while monitoring, wait, and closed-transition states usedisconnect()directly. - If
quit()fails, Fluo falls back todisconnect()and only rethrows when the client still remains open afterward.
Named Clients
Use RedisModule.forRoot({ name, ...options }) when one application needs more than one Redis connection. RedisModule.forRoot(options) without name provides the default REDIS_CLIENT and RedisService aliases, and named registrations are resolved with getRedisClientToken(name) and getRedisServiceToken(name).
- Omit
namewhen you want the default aliases:REDIS_CLIENT/RedisService. - Pass
namewhen you want the named helpers:getRedisClientToken(name)/getRedisServiceToken(name). nameidentifies the Fluo registration only. For an ioredis Sentinel master name, passsentinelName; Fluo forwards it to ioredis as its constructornameoption without changing the registration tokens.- Named clients follow the same bootstrap/shutdown contract as the default client; only the default registration exports the
REDIS_CLIENT/RedisServicealiases. - Names are trimmed, and blank or whitespace-only names are rejected by token/component helpers.
import { Module, Inject } from '@fluojs/core';
import type Redis from 'ioredis';
import {
getRedisClientToken,
getRedisServiceToken,
RedisModule,
RedisService,
} from '@fluojs/redis';
const ANALYTICS_REDIS = getRedisServiceToken('analytics');
const ANALYTICS_REDIS_CLIENT = getRedisClientToken('analytics');
@Module({
imports: [
RedisModule.forRoot({ host: 'localhost', port: 6379 }),
RedisModule.forRoot({ name: 'analytics', host: 'localhost', port: 6380 }),
RedisModule.forRoot({ name: 'sentinel-cache', sentinelName: 'mymaster', sentinels: [{ host: 'localhost', port: 26379 }] }),
],
})
export class AppModule {}
@Inject(RedisService, ANALYTICS_REDIS, ANALYTICS_REDIS_CLIENT)
export class AnalyticsStore {
constructor(
private readonly defaultRedis: RedisService,
private readonly analyticsRedis: RedisService,
private readonly analyticsClient: Redis,
) {}
}Raw Client Access
If you need advanced Redis commands (pipelines or Lua scripts), inject the raw client directly.
If you already injected RedisService, call redis.getRawClient() to access the same underlying ioredis instance.
Redis Pub/Sub is the exception to ordinary shared-client reuse: Redis switches subscribed connections into subscribe mode, so do not use the lifecycle-managed REDIS_CLIENT or a RedisService.getRawClient() result as both publisher and subscriber. Create a dedicated subscriber connection with client.duplicate() (or an explicitly named RedisModule.forRoot({ name: 'subscriber', ... }) registration) and let that connection have its own lifecycle owner.
If you use client.duplicate(), the duplicate is application-owned: connect it, subscribe with it, and close it from your own shutdown path. If you want fluo to own subscriber startup/shutdown timeouts, prefer a named registration and inject it with getRedisClientToken(name).
import { Inject } from '@fluojs/core';
import { REDIS_CLIENT } from '@fluojs/redis';
import type Redis from 'ioredis';
@Inject(REDIS_CLIENT)
export class AdvancedService {
constructor(private readonly client: Redis) {}
async executeComplex() {
return await this.client.pipeline().set('foo', 'bar').get('foo').exec();
}
}import { Inject, Module } from '@fluojs/core';
import { getRedisClientToken, RedisModule } from '@fluojs/redis';
import { RedisPubSubMicroserviceTransport } from '@fluojs/microservices';
import type Redis from 'ioredis';
const COMMAND_REDIS = getRedisClientToken();
const SUBSCRIBER_REDIS = getRedisClientToken('subscriber');
@Module({
imports: [
RedisModule.forRoot({ host: 'localhost', port: 6379 }),
RedisModule.forRoot({ name: 'subscriber', host: 'localhost', port: 6379 }),
],
})
export class RedisConnectionsModule {}
@Inject(COMMAND_REDIS, SUBSCRIBER_REDIS)
export class PubSubTransportFactory {
constructor(
private readonly commandClient: Redis,
private readonly subscriberClient: Redis,
) {}
createTransport() {
return new RedisPubSubMicroserviceTransport({
publishClient: this.commandClient,
subscribeClient: this.subscriberClient,
});
}
}Public API Overview
Core
RedisModule: Registers Redis clients and lifecycle hooks.RedisModule.forRoot(options): Registers the default Redis client plusRedisServicefacade whennameis omitted, or an additional named Redis client whennameis provided. The default registration is global by default; named registrations are scoped. Uselifecycle.connectTimeoutMsandlifecycle.quitTimeoutMsto bound Fluo-owned connection startup and shutdown.- Lifecycle hooks are configured only through
RedisModule.forRoot(...).lifecycle; the internal lifecycle service is intentionally not part of the public API. RedisService: Facade with JSON codec support andget/set/delmethods.REDIS_CLIENT: DI token for the underlyingioredisinstance.DEFAULT_REDIS_CLIENT_NAME: Stable default Redis client name.getRedisClientToken(name): DI token helper for a named raw client. Omittingnamereturns the defaultREDIS_CLIENTtoken.getRedisServiceToken(name): DI token helper for a namedRedisServicefacade. Omittingnamereturns the defaultRedisServicetoken.getRedisComponentId(name): Status/dependency id helper used by Redis-consuming packages (redis.default,redis.cache, etc.).createRedisPlatformStatusSnapshot(input): Adapts Redis connection state into Fluo's platform health/readiness snapshot contract.
Types
DefaultRedisModuleOptions: Options accepted by the unnamed default Redis registration, including optional global alias visibility and lifecycle timeout controls.NamedRedisModuleOptions: Options accepted by additional named Redis registrations, including requirednameand scoped lifecycle timeout controls.RedisModuleOptions: Configuration options passed to theioredisconstructor after Fluo removes module-onlyname,global,lifecycle, andsentinelNamefields.sentinelNameis forwarded as the ioredis Sentinel mastername, whilenameremains the Fluo registration identifier.RedisClientOptions: Redis constructor options after Fluo removes module-only fields and before it forceslazyConnect: trueinternally.RedisLifecycleOptions: Optional timeout controls for Fluo-ownedconnect()andquit()lifecycle commands.PersistencePlatformStatusSnapshot,RedisStatusAdapterInput: Status snapshot input/output types.
Related Packages
@fluojs/cache-manager: Uses this package for Redis-backed caching.@fluojs/queue: Uses this package for distributed job processing.@fluojs/throttler: Uses this package for distributed rate limiting.
Example Sources
packages/redis/src/module.test.ts: Module lifecycle and DI wiring.packages/redis/src/public-api.test.ts: Root-barrel export guard for the documented Redis surface.packages/redis/src/redis-service.ts: Facade implementation and codec logic.
