Aviary

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 tests

Distributed adapters

Each adapter is its own package with the engine driver as a peer dependency. Pick one:

PackageEngineConstructSchema
@dudousxd/nestjs-resilience-store-redisRedis (ioredis)new RedisResilienceStore(redis, opts?)none (atomic Lua)
@dudousxd/nestjs-resilience-store-typeormPostgres (TypeORM)new TypeOrmResilienceStore(dataSource, opts?)ensureSchema()
@dudousxd/nestjs-resilience-store-prismaPostgres (Prisma)new PrismaResilienceStore(prisma, opts?)ensureSchema()
@dudousxd/nestjs-resilience-store-mikro-ormPostgres (MikroORM)new MikroOrmResilienceStore(orm, opts?)ensureSchema()
@dudousxd/nestjs-resilience-store-drizzleSQLite (better-sqlite3)new DrizzleResilienceStore(db, opts?)exported schema
pnpm add @dudousxd/nestjs-resilience-store-redis ioredis
app.module.ts
import 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 typeorm
app.module.ts
import { 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/client
app.module.ts
import { 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/postgresql
app.module.ts
import { 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-sqlite3
app.module.ts
import { 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.

On this page