Stores
Share circuit-breaker state across instances with a pluggable ResilienceStore — in-memory by default, or Redis / Postgres / SQLite adapters that coordinate the half-open probe atomically.
The circuit breaker keeps its state — failure counts, open-until timestamps, the half-open probe — behind a small ResilienceStore interface. Swap the implementation to move that state from one process to your whole fleet.
interface ResilienceStore {
admit(key: string, cfg: BreakerConfig): Promise<Admission>; // may I run? (and am I the probe?)
record(key: string, cfg: BreakerConfig, ok: boolean, probe: boolean): Promise<CircuitStatus>;
snapshot(key: string): Promise<CircuitSnapshot>; // read-only inspect
}admit and record must be atomic — the whole point of a distributed store is that, under concurrent load, exactly one instance gets the half-open probe. Every adapter below enforces that against its real engine (Lua, SELECT … FOR UPDATE, a SQLite transaction) and is verified by a shared contract suite.
In-memory (default)
InMemoryResilienceStore ships in the core package and needs nothing. State lives in the process, so each instance trips its own breaker:
import { InMemoryResilienceStore } from '@dudousxd/nestjs-resilience';
const store = new InMemoryResilienceStore(); // accepts an optional Clock for testsDistributed adapters
Each adapter is its own package with the engine driver as a peer dependency. Pick one:
| Package | Engine | Construct | Schema |
|---|---|---|---|
@dudousxd/nestjs-resilience-store-redis | Redis (ioredis) | new RedisResilienceStore(redis, opts?) | none (atomic Lua) |
@dudousxd/nestjs-resilience-store-typeorm | Postgres (TypeORM) | new TypeOrmResilienceStore(dataSource, opts?) | ensureSchema() |
@dudousxd/nestjs-resilience-store-prisma | Postgres (Prisma) | new PrismaResilienceStore(prisma, opts?) | ensureSchema() |
@dudousxd/nestjs-resilience-store-mikro-orm | Postgres (MikroORM) | new MikroOrmResilienceStore(orm, opts?) | ensureSchema() |
@dudousxd/nestjs-resilience-store-drizzle | SQLite (better-sqlite3) | new DrizzleResilienceStore(db, opts?) | exported schema |
pnpm add @dudousxd/nestjs-resilience-store-redis ioredisimport Redis from 'ioredis';
import { ResilienceModule } from '@dudousxd/nestjs-resilience';
import { RedisResilienceStore } from '@dudousxd/nestjs-resilience-store-redis';
ResilienceModule.forRootAsync({
inject: [Redis],
useFactory: (redis: Redis) => ({
store: new RedisResilienceStore(redis, {
keyPrefix: 'resilience:cb:', // default
}),
}),
});admit/record each run as a single atomic Lua script — no schema, no migration.
pnpm add @dudousxd/nestjs-resilience-store-typeorm typeormimport { DataSource } from 'typeorm';
import { ResilienceModule } from '@dudousxd/nestjs-resilience';
import { TypeOrmResilienceStore } from '@dudousxd/nestjs-resilience-store-typeorm';
ResilienceModule.forRootAsync({
inject: [DataSource],
useFactory: async (dataSource: DataSource) => {
const store = new TypeOrmResilienceStore(dataSource);
await store.ensureSchema(); // create the circuits table once on boot
return { store };
},
});Atomicity uses a transaction with SELECT … FOR UPDATE. Prefer migrations? Import the CIRCUITS_DDL string instead of calling ensureSchema().
pnpm add @dudousxd/nestjs-resilience-store-prisma @prisma/clientimport { PrismaService } from './prisma.service';
import { ResilienceModule } from '@dudousxd/nestjs-resilience';
import { PrismaResilienceStore } from '@dudousxd/nestjs-resilience-store-prisma';
ResilienceModule.forRootAsync({
inject: [PrismaService],
useFactory: async (prisma: PrismaService) => {
const store = new PrismaResilienceStore(prisma);
await store.ensureSchema();
return { store };
},
});The store duck-types the client ($transaction / $executeRawUnsafe / $queryRawUnsafe), so it works across Prisma 5–7 without importing your generated client.
pnpm add @dudousxd/nestjs-resilience-store-mikro-orm @mikro-orm/core @mikro-orm/postgresqlimport { MikroORM } from '@mikro-orm/core';
import { ResilienceModule } from '@dudousxd/nestjs-resilience';
import { MikroOrmResilienceStore } from '@dudousxd/nestjs-resilience-store-mikro-orm';
ResilienceModule.forRootAsync({
inject: [MikroORM],
useFactory: async (orm: MikroORM) => {
const store = new MikroOrmResilienceStore(orm);
await store.ensureSchema();
return { store };
},
});Uses em.transactional() — make sure transactions aren't disabled on your config.
pnpm add @dudousxd/nestjs-resilience-store-drizzle drizzle-orm better-sqlite3import { ResilienceModule } from '@dudousxd/nestjs-resilience';
import { DrizzleResilienceStore, resilienceSchema } from '@dudousxd/nestjs-resilience-store-drizzle';
import { drizzle } from 'drizzle-orm/better-sqlite3';
import Database from 'better-sqlite3';
ResilienceModule.forRootAsync({
useFactory: () => {
const db = drizzle(new Database('app.db'), { schema: resilienceSchema });
return { store: new DrizzleResilienceStore(db) };
},
});SQLite-backed with synchronous transactions. The package exports circuits, resilienceSchema, and CIRCUITS_DDL for your migrations.
Wiring a store
Pass the store to the module — every breaker and decorator then shares it:
ResilienceModule.forRootAsync({
inject: [Redis],
useFactory: (redis: Redis) => ({ store: new RedisResilienceStore(redis) }),
});…or to an individual circuitBreaker({ store, … }) / failover({ policy: () => circuitBreaker({ store, … }) }).
All adapters share one type, BreakerConfig (threshold, cooldownMs, halfOpenMax?) — the same options you pass to circuitBreaker. Bring your own engine by implementing the three methods and validating it against the contract suite.
Decorators & Module
Wrap provider methods with @Timeout / @Retry / @CircuitBreaker, register named policies through ResilienceModule, and run them via ResilienceService.
Integrations
Emit state transitions over nestjs-diagnostics, mirror them onto @nestjs/event-emitter, and make breaker keys tenant-aware through nestjs-context — all soft-detected and optional.