No description
Find a file
autocommit e140bed22b
Some checks failed
Build and Publish / build-and-publish (push) Failing after 42s
deps-upgrade(deps): ⬆️ Update all dependencies to latest stable versions
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-10 04:34:10 -07:00
.forgejo/workflows fix(ci): fix backslash-bang syntax error in workflow 2026-01-30 15:49:48 -08:00
src chore: initial commit with publish config 2026-01-21 12:30:21 -08:00
.gitignore chore: initial commit with publish config 2026-01-21 12:30:21 -08:00
.npmignore chore: initial commit with publish config 2026-01-21 12:30:21 -08:00
example-usage.ts chore: initial commit with publish config 2026-01-21 12:30:21 -08:00
IMPLEMENTATION_SUMMARY.md chore: initial commit with publish config 2026-01-21 12:30:21 -08:00
package.json deps-upgrade(deps): ⬆️ Update all dependencies to latest stable versions 2026-06-10 04:34:10 -07:00
README.md chore: trigger CI publish 2026-01-30 11:56:23 -08:00
tsconfig.json chore: initial commit with publish config 2026-01-21 12:30:21 -08:00
tsup.config.ts chore(build): Optimize bundling with minification in tsup config to reduce bundle size 2026-01-21 15:33:55 -08:00

@lilith/service-discovery

Redis-backed dynamic service discovery with health tracking, load balancing, and real-time event notifications.

Features

  • Dynamic Service Registration: Auto-register services with metadata and health status
  • Service Discovery: Query services by name, feature, type, tags, or health status
  • Load Balancing: Multiple strategies (round-robin, random, least-response-time, least-connections)
  • Health Tracking: Automatic heartbeat and health status updates
  • Real-time Events: Watch for service registration, deregistration, and health changes
  • Multi-index Support: Fast queries by service name, feature, type, and tags
  • Atomic Operations: Lua scripts for race-condition-free updates
  • NestJS Integration: First-class support for NestJS applications

Installation

pnpm add @lilith/service-discovery

Quick Start

Basic Usage

import { createServiceDiscovery } from '@lilith/service-discovery'

const client = createServiceDiscovery({
  redis: 'redis://localhost:6379',
  heartbeatIntervalMs: 10_000,
  unhealthyThresholdMs: 30_000,
})

// Register service
const registration = client.getRegistration()
await registration.register(
  'platform-admin-api',
  'platform-admin',
  'api',
  'localhost',
  3011,
  'http',
  {
    tags: ['public', 'rest-api'],
    metadata: { version: '1.0.0' },
  }
)

// Discover services
const discovery = client.getDiscovery()
const services = await discovery.discoverAll({
  featureName: 'platform-admin',
  onlyHealthy: true,
})

// Get single instance with load balancing
const service = await discovery.discoverOne({
  serviceName: 'platform-admin-api',
  loadBalanceStrategy: 'round-robin',
})

NestJS Integration

import { Module } from '@nestjs/common'
import { ServiceDiscoveryModule, SERVICE_DISCOVERY_CLIENT } from '@lilith/service-discovery/nestjs'
import type { ServiceDiscoveryClient } from '@lilith/service-discovery'

@Module({
  imports: [
    ServiceDiscoveryModule.register({
      redis: process.env.REDIS_URL || 'redis://localhost:6379',
      heartbeatIntervalMs: 10_000,
    }),
  ],
})
export class AppModule {}

// Inject in service
@Injectable()
export class MyService {
  constructor(
    @Inject(SERVICE_DISCOVERY_CLIENT)
    private readonly discovery: ServiceDiscoveryClient,
  ) {}

  async findService() {
    const service = await this.discovery.getDiscovery().discoverOne({
      serviceName: 'my-service',
    })
    return service?.url
  }
}

API Reference

ServiceDiscoveryClient

Main client combining discovery and registration.

const client = createServiceDiscovery(config)
client.getDiscovery()      // ServiceDiscovery interface
client.getRegistration()   // ServiceRegistration interface
client.getLoadBalancer()   // LoadBalancer interface
await client.cleanup()     // Cleanup resources

ServiceRegistration

Register and manage service instances.

const registration = client.getRegistration()

// Register service
await registration.register(serviceName, featureName, serviceType, host, port, protocol, options)

// Send heartbeat
await registration.heartbeat()

// Update metadata
await registration.updateMetadata({ responseTimeMs: 120, activeConnections: 5 })

// Deregister
await registration.deregister()

ServiceDiscovery

Query and watch for services.

const discovery = client.getDiscovery()

// Find all matching services
const services = await discovery.discoverAll({
  featureName: 'analytics',
  serviceType: 'api',
  tags: ['public'],
  onlyHealthy: true,
})

// Find single instance with load balancing
const service = await discovery.discoverOne({
  serviceName: 'analytics-api',
  loadBalanceStrategy: 'least-response-time',
})

// Watch for changes
const subscription = await discovery.watch(
  (event) => {
    console.log(`Service ${event.type}:`, event.service)
  },
  { featureName: 'analytics' }
)

// Unsubscribe
await subscription.unsubscribe()

Load Balancing Strategies

  • round-robin: Cycle through instances sequentially
  • random: Random selection from healthy instances
  • least-response-time: Prefer instances with lowest response time
  • least-connections: Prefer instances with fewest active connections

Configuration

interface DiscoveryConfig {
  redis: string | Redis                          // Redis connection
  keyPrefix?: string                             // Redis key prefix (default: 'service:discovery')
  heartbeatIntervalMs?: number                   // Heartbeat interval (default: 10000)
  unhealthyThresholdMs?: number                  // Mark unhealthy after (default: 30000)
  expirationMs?: number                          // Remove after (default: 60000)
  defaultLoadBalanceStrategy?: LoadBalanceStrategy // Default strategy (default: 'round-robin')
  debug?: boolean                                // Debug logging (default: false)
}

Redis Data Structure

service:discovery:instance:{instanceId}     → Service data (Hash with TTL)
service:discovery:instances:all             → Set of all instance IDs
service:discovery:index:service:{name}      → Set of instances for service
service:discovery:index:feature:{name}      → Set of instances in feature
service:discovery:index:type:{type}         → Set of instances by type
service:discovery:index:tag:{tag}           → Set of instances with tag
service:discovery:events                    → Pub/Sub channel for events

Architecture

See ADR-009: Redis-backed Service Discovery for design decisions and implementation details.

Status

Wave 1: Scaffold and type definitions (CURRENT) Wave 2: Core implementation (registration, discovery, load balancing) Wave 3: Production hardening (error recovery, monitoring, benchmarks)

License

UNLICENSED - Internal Lilith Platform package