Skip to content

sathvikc/omni-db

Repository files navigation

OmniDB Logo

OmniDB

Thin database orchestration library for Node.js — manage multiple connections with health monitoring and failover.

License: MIT Node.js Tests Coverage CI

Features

  • 🔌 Multi-Database Support — Bring your own clients (Prisma, Drizzle, Redis, MongoDB, etc.)
  • 🏥 Health Monitoring — Configurable periodic health checks with timeouts
  • 🔄 Automatic Failover — Route to backups when primary is unhealthy
  • 📡 Event System — Subscribe to connection, health, and failover events
  • 📦 Minimal Footprint — Zero runtime dependencies, <10KB bundle
  • 🔷 TypeScript Ready — Full type definitions with generics

Before & After

❌ Without OmniDB — 80+ lines of boilerplate
// DIY: Managing multiple databases with health checks and failover

const primaryPool = new Pool({ connectionString: PRIMARY_URL });
const replicaPool = new Pool({ connectionString: REPLICA_URL });
const redis = createClient({ url: REDIS_URL });

let primaryHealthy = true;
let replicaHealthy = true;
let redisHealthy = true;

// Manual health check loop
setInterval(async () => {
  try {
    await primaryPool.query('SELECT 1');
    primaryHealthy = true;
  } catch {
    primaryHealthy = false;
    console.log('[HEALTH] Primary unhealthy');
  }
  
  try {
    await replicaPool.query('SELECT 1');
    replicaHealthy = true;
  } catch {
    replicaHealthy = false;
  }
  
  try {
    await redis.ping();
    redisHealthy = true;
  } catch {
    redisHealthy = false;
  }
}, 30000);

// Manual failover logic
function getDatabase() {
  if (primaryHealthy) return primaryPool;
  if (replicaHealthy) {
    console.log('[FAILOVER] Using replica');
    return replicaPool;
  }
  throw new Error('All databases unavailable');
}

// Manual graceful shutdown
process.on('SIGTERM', async () => {
  clearInterval(healthCheckInterval);
  await Promise.all([
    primaryPool.end(),
    replicaPool.end(),
    redis.quit(),
  ]);
  process.exit(0);
});

// Usage
app.get('/users', async (req, res) => {
  const db = getDatabase();
  const { rows } = await db.query('SELECT * FROM users');
  res.json(rows);
});
✅ With OmniDB — 20 lines, zero boilerplate
import { Orchestrator } from 'omni-db';

const db = new Orchestrator({
  connections: { primary: primaryPool, replica: replicaPool, cache: redis },
  failover: { primary: 'replica' },
  healthCheck: {
    interval: '30s',
    checks: {
      primary: async (c) => { await c.query('SELECT 1'); return true; },
      replica: async (c) => { await c.query('SELECT 1'); return true; },
      cache: async (c) => (await c.ping()) === 'PONG',
    },
  },
});

await db.connect();
db.shutdownOnSignal();

// Usage — identical, but with automatic failover
app.get('/users', async (req, res) => {
  const pg = db.get('primary');  // Auto-routes to replica if primary is down
  const { rows } = await pg.query('SELECT * FROM users');
  res.json(rows);
});

What you get: Health monitoring, automatic failover, event system, graceful shutdown, TypeScript types — all in <10KB with zero dependencies.

Installation

npm install omni-db

Quick Start

import { Orchestrator } from 'omni-db';

const db = new Orchestrator({
  connections: {
    primary: prismaClient,
    replica: replicaClient,
    cache: redisClient,
  },
  failover: { primary: 'replica' },
  healthCheck: {
    interval: '30s',
    timeout: '5s',
    checks: {
      primary: async (client) => {
        await client.$queryRaw`SELECT 1`;
        return true;
      },
      cache: async (client) => {
        const pong = await client.ping();
        return pong === 'PONG';
      },
    },
  },
});

// Connect and start health monitoring
await db.connect();

// Get clients (auto-routes to replica if primary is unhealthy)
const client = db.get('primary');

// Check health status
console.log(db.health());
// { primary: { status: 'healthy' }, replica: { status: 'healthy' }, cache: { status: 'healthy' } }

// Disconnect when done
await db.disconnect();

API Reference

new Orchestrator(config)

Creates a new orchestrator instance.

Option Type Description
connections Record<string, any> Named database client instances
failover Record<string, string> Map primary → backup names
healthCheck.interval string Check interval (e.g., '30s', '1m')
healthCheck.timeout string Timeout per check
healthCheck.retry.retries number Failed attempts before marking unhealthy
healthCheck.retry.delay string Waiting time between retries
healthCheck.checks Record<string, Function> Custom health check functions
circuitBreaker.threshold number Failures before opening circuit (default: 5)
circuitBreaker.resetTimeout string Time before half-open (default: '30s')
circuitBreaker.use object External circuit breaker (opossum, cockatiel)

Methods

Method Returns Description
connect() Promise<void> Connect and start health monitoring
disconnect() Promise<void> Disconnect and stop monitoring
get(name) T Get client (with failover routing)
execute(name, fn) Promise<T> Execute with automatic circuit breaker
getStats() Record<string, object> Get health + circuit breaker stats
list() string[] Get all connection names
has(name) boolean Check if connection exists
health() Record<string, ConnectionHealth> Get health status
recordSuccess(name) void Record success for circuit breaker
recordFailure(name) void Record failure for circuit breaker
shutdownOnSignal(opts?) () => void Register graceful shutdown handlers
isConnected boolean Connection state
size number Number of connections

Events

db.on('connected', ({ name }) => console.log(`${name} connected`));
db.on('disconnected', ({ name }) => console.log(`${name} disconnected`));
db.on('health:changed', ({ name, previous, current }) => {
  console.log(`${name}: ${previous}${current}`);
});
db.on('failover', ({ primary, backup }) => {
  console.log(`Switched from ${primary} to ${backup}`);
});
db.on('recovery', ({ primary, backup }) => {
  console.log(`Recovered ${primary}, was using ${backup}`);
});

TypeScript

Full type inference with generic connections:

import { Orchestrator } from 'omni-db';
import { PrismaClient } from '@prisma/client';
import { Redis } from 'ioredis';

const db = new Orchestrator({
  connections: {
    postgres: new PrismaClient(),
    redis: new Redis(),
  },
});

const prisma = db.get('postgres'); // Type: PrismaClient
const redis = db.get('redis');     // Type: Redis

Documentation

For detailed guides, see the docs/ folder:

Guide Description
Getting Started Installation and basic usage
Configuration All configuration options
Architecture How components work together
Health Monitoring Health checks and status
Failover Automatic failover routing
Circuit Breaker Prevent cascading failures
Events Event system reference
Observability Prometheus, logging, metrics
Middleware Express, Fastify, Koa, Hono, NestJS
TypeScript Type definitions and generics
Examples Real-world usage patterns
Best Practices Patterns, anti-patterns, and tips
Error Reference All errors with causes and solutions

Philosophy

OmniDB is a thin orchestration layer. It doesn't abstract your databases — it orchestrates them.

You bring:

  • Your database clients (Prisma, Drizzle, pg, Redis, MongoDB...)
  • Your health check logic
  • Your routing decisions

OmniDB provides:

  • Connection registry with O(1) lookup
  • Periodic health monitoring
  • Automatic failover routing
  • Event-driven notifications

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.

License

MIT © Sathvik C

About

Thin database orchestration library for Node.js - manage multiple connections with health monitoring and failover

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors