No description
Find a file
autocommit 6b63c64340
Some checks failed
Build and Publish / build-and-publish (push) Failing after 47s
docs(docs): 📝 Update registry URL in README.md to reflect infrastructure changes from forge.black.local to forge.black.lan
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-10 21:17:47 -07:00
.forgejo/workflows feat: add checkPortInUse for runtime port validation 2026-01-20 23:38:04 -08:00
.turbo feat: add checkPortInUse for runtime port validation 2026-01-20 23:38:04 -08:00
node_modules deps-upgrade: ⬆️ Update @nestjs/common and @nestjs/config to latest version with bug fixes, performance improvements, and new configuration options 2026-02-06 14:17:53 -08:00
src test(validation): Update validation test suite to cover edge cases and enforce stricter input rules 2026-02-26 22:08:45 -08:00
.gitignore chore(gitignore): Add missing patterns 2026-01-21 12:39:45 -08:00
CHANGELOG.md feat: add checkPortInUse for runtime port validation 2026-01-20 23:38:04 -08:00
eslint.config.js feat: add checkPortInUse for runtime port validation 2026-01-20 23:38:04 -08:00
package.json deps-upgrade(deps): ⬆️ Update dependencies to latest stable versions for security and compatibility 2026-06-10 21:17:47 -07:00
README.md docs(docs): 📝 Update registry URL in README.md to reflect infrastructure changes from forge.black.local to forge.black.lan 2026-06-10 21:17:47 -07:00
tsconfig.json chore(config): 🔧 Update TypeScript compiler settings in tsconfig.json to enforce stricter type-checking and add new flags for improved code quality 2026-01-25 10:42:20 -08:00
tsup.config.ts chore(config): 🔧 Update deployment-related files (deployment.ts, index.ts) in core package 2026-02-17 15:35:57 -08:00

@lilith/service-addresses

Version: 3.3.1 Registry: forge.black.lan

Service address resolution with flexible configuration and port conflict detection.

Features

  • Flexible Configuration - No hardcoded paths, configure exactly where your service configs are
  • Environment-Aware Ports - Dotenv-inspired pattern for dev/staging/prod port overrides
  • Port Conflict Detection - Automatic validation ensures no two services use the same port
  • Dependency Validation - Validates all service dependencies exist
  • Type-Safe - Full TypeScript support with comprehensive interfaces
  • Multiple APIs - Choose between singleton helpers or class-based API
  • Database/Redis Helpers - TypeORM and ioredis compatible configuration generation
  • Service Discovery - Query by type, dependencies, GPU requirements, criticality

Installation

pnpm add @lilith/service-addresses

Quick Start

import {
  initServiceRegistry,
  getServiceUrl,
  getServicePort,
  getDatabaseConfig
} from '@lilith/service-addresses';

// Initialize once at startup with flexible paths
initServiceRegistry({
  servicesPath: './config/services',  // Directory with *.yaml files
  portsPath: './config/ports.yaml',   // Optional: master ports file
  strict: true                        // Enable validation (default)
});

// Use anywhere in your app
const apiUrl = getServiceUrl('platform-admin', 'api');
// → http://localhost:3011

const port = getServicePort('analytics', 'api');
// → 3012

// TypeORM database configuration
const dbConfig = getDatabaseConfig('analytics');
// → { type: 'postgres', host: 'localhost', port: 5434, ... }

Option 2: Environment-Aware Configuration (v3.3.0+)

Use separate helper for environment-specific port overrides:

import {
  initServiceRegistry,
  loadPortsConfig,
  withEnvironmentOverrides
} from '@lilith/service-addresses';

// Load base ports
const basePorts = loadPortsConfig('./config/ports.yaml');

// Merge with environment-specific overrides
const envPorts = withEnvironmentOverrides(
  basePorts,
  './config/ports.yaml',
  'production'  // or 'staging', 'development'
);

// Initialize with merged config
initServiceRegistry({
  servicesPath: './config/services',
  portsConfig: envPorts,  // Use merged ports
  strict: true
});

YAML files:

# ports.yaml (development base)
features:
  merchant:
    api: 3020
    postgresql: 5445

# ports.prod.yaml (production overrides only)
features:
  merchant:
    api: 443  # Different in prod
    # postgresql inherits 5445 from base

Option 3: Class-Based API (Advanced Usage)

import { loadServiceRegistry, ServiceAddresses } from '@lilith/service-addresses';

const registry = loadServiceRegistry({
  servicesPath: './services',
  strict: true,
  host: 'localhost'
});

const addresses = new ServiceAddresses(registry);

// Query services
const seoApi = addresses.getService('seo.api');
const allDatabases = addresses.getDatabases();
const gpuServices = addresses.getGpuServices();
const dependencies = addresses.getDependencies('seo.api');

Configuration Format

Service Registry Config

interface ServiceRegistryConfig {
  /**
   * Directory containing service YAML files
   * All .yaml files (except _*.yaml) will be loaded
   */
  servicesPath?: string;

  /**
   * Pre-loaded feature configurations
   * Use this if loading from custom sources
   */
  featureConfigs?: FeatureConfig[];

  /**
   * Path to master ports file (optional)
   */
  portsPath?: string;

  /**
   * Pre-loaded ports configuration (v3.3.0+)
   * Use this to apply environment overrides via withEnvironmentOverrides()
   * Alternative to portsPath for explicit control
   */
  portsConfig?: PortsConfig;

  /**
   * Host for URL generation (default: 'localhost')
   */
  host?: string;

  /**
   * Enable strict validation (default: true)
   * - Port conflict detection
   * - Dependency validation
   */
  strict?: boolean;
}

Service YAML Format

Place service definition files in your servicesPath directory:

# services/analytics.yaml
feature:
  id: analytics
  name: Analytics Service
  description: User analytics and metrics tracking
  owner: platform-team

ports:
  api: 3012
  postgresql: 5434
  redis: 6381

services:
  - id: api
    name: Analytics API
    type: api
    port: 3012
    entrypoint: dist/apps/analytics/src/main.js
    critical: true
    healthCheck:
      type: http
      path: /health
    dependencies:
      - platform-admin.api
      - auth.api

  - id: postgresql
    name: Analytics Database
    type: postgresql
    port: 5434
    critical: true

  - id: redis
    name: Analytics Cache
    type: redis
    port: 6381

API Reference

Singleton Functions

initServiceRegistry(config)

Initialize the singleton registry.

// Simple configuration (single ports.yaml)
initServiceRegistry({
  servicesPath: './config/services',
  portsPath: './config/ports.yaml',
  strict: true
});

// With environment overrides (v3.3.0+)
const basePorts = loadPortsConfig('./config/ports.yaml');
const envPorts = withEnvironmentOverrides(basePorts, './config/ports.yaml', 'production');

initServiceRegistry({
  servicesPath: './config/services',
  portsConfig: envPorts,  // Use merged config
  strict: true
});

// Or pass pre-loaded configs
initServiceRegistry({
  featureConfigs: [...],
  strict: true
});

loadPortsConfig(portsYamlPath) (v3.3.0+)

Load and parse a single ports YAML file.

import { loadPortsConfig } from '@lilith/service-addresses';

const ports = loadPortsConfig('./config/ports.yaml');
// Returns parsed PortsConfig object

withEnvironmentOverrides(basePorts, baseYamlPath, environment) (v3.3.0+)

Merge environment-specific port overrides (dotenv-inspired pattern).

import { loadPortsConfig, withEnvironmentOverrides } from '@lilith/service-addresses';

const basePorts = loadPortsConfig('./config/ports.yaml');
const prodPorts = withEnvironmentOverrides(basePorts, './config/ports.yaml', 'production');
// Merges ports.yaml + ports.prod.yaml

const stagingPorts = withEnvironmentOverrides(basePorts, './config/ports.yaml', 'staging');
// Merges ports.yaml + ports.staging.yaml

Pattern:

  • ports.yaml = Base configuration (development)
  • ports.prod.yaml = Production overrides
  • ports.staging.yaml = Staging overrides (optional)

Single Responsibility: loadPortsConfig() loads, withEnvironmentOverrides() merges.

getServiceUrl(featureId, serviceId)

Get full URL for a service.

const url = getServiceUrl('analytics', 'api');
// → http://localhost:3012

getServicePort(featureId, serviceId)

Get port number for a service.

const port = getServicePort('analytics', 'api');
// → 3012

getDatabaseConfig(featureId, options?)

Get TypeORM-compatible database configuration.

const config = getDatabaseConfig('analytics', {
  username: 'myuser',    // Optional overrides
  password: 'mypass',
  database: 'analytics_db',
  synchronize: false,
  logging: true
});

// Use in TypeORM
TypeOrmModule.forRoot(config);

getRedisConfig(featureId, options?)

Get ioredis-compatible Redis configuration.

const config = getRedisConfig('analytics', {
  password: 'redis-pass',
  db: 0
});

// Use with ioredis
const redis = new Redis(config);

getConsumedApiUrls(featureId)

Get all API URLs a feature depends on.

const apis = getConsumedApiUrls('analytics');
// → { 'platform-admin': 'http://localhost:3011', 'auth': 'http://localhost:3013' }

ServiceAddresses Class

Service Queries

// Get service by full ID
const service = addresses.getService('analytics.api');

// Get service by feature + service ID
const service = addresses.getServiceByParts('analytics', 'api');

// Get all services
const all = addresses.getServices();

// Get all features
const features = addresses.getFeatures();

Type-Based Queries

// Get all APIs
const apis = addresses.getApis();

// Get all databases
const dbs = addresses.getDatabases();

// Get all Redis services
const redis = addresses.getRedisServices();

// Get all ML services
const ml = addresses.getMlServices();

// Get critical services
const critical = addresses.getCriticalServices();

// Get GPU-requiring services
const gpu = addresses.getGpuServices();

Dependency Graph

// Get dependencies of a service
const deps = addresses.getDependencies('analytics.api');

// Get dependents (what depends on this service)
const dependents = addresses.getDependents('auth.api');

// Get all edges
const edges = addresses.getEdges();
// → [{ from: 'analytics.api', to: 'auth.api' }, ...]

URL Helpers

// Get API URL
const apiUrl = addresses.getApiUrl('analytics');

// Get health check URL
const healthUrl = addresses.getHealthUrl('analytics.api');

// Get PostgreSQL connection URL
const dbUrl = addresses.getPostgresUrl('analytics', {
  user: 'myuser',
  password: 'mypass',
  database: 'analytics_db'
});

// Get Redis URL
const redisUrl = addresses.getRedisUrl('analytics');

Service Types

Supported service types:

  • api - REST/GraphQL API services
  • frontend - React/Vue frontend applications
  • ml - Machine learning services
  • redis - Redis cache services
  • postgresql - PostgreSQL databases
  • worker - Background job workers
  • websocket - WebSocket servers

Validation

Port Conflict Detection

The package automatically detects port conflicts when strict: true:

// This will throw if two services use port 3000:
loadServiceRegistry({
  servicesPath: './services',
  strict: true  // default
});

// Error:
// Port conflicts detected:
//   Port 3000 conflict: analytics.api, platform-admin.api
//
// Each service must have a unique port. Update your services.yaml files.

Dependency Validation

Missing dependencies are detected:

// If analytics.api depends on 'auth.api' but auth.api doesn't exist:

// Error:
// Missing service dependencies:
//   analytics.api → auth.api (not found)
//
// All service dependencies must reference valid service IDs (format: feature.service)

Disable validation for development:

loadServiceRegistry({
  servicesPath: './services',
  strict: false  // Skip validation
});

Environment Variables

Auto-initialization from environment:

# Service registry configuration
export LILITH_SERVICES_PATH=./config/services
export LILITH_PORTS_PATH=./config/ports.yaml
export LILITH_STRICT_VALIDATION=true  # default: true

Then use without explicit init:

// Auto-initializes from env vars on first use
const url = getServiceUrl('analytics', 'api');

NestJS Integration

import { ServiceConfigModule } from '@lilith/service-addresses/nestjs';

@Module({
  imports: [
    ServiceConfigModule.register({
      servicesPath: './config/services',
      strict: true
    }),
  ],
})
export class AppModule {}

Migration from v2.x to v3.0.0

Breaking Changes

v3.0.0 removes ALL backward compatibility code for a clean, cruft-free API:

  1. loadFromInfrastructure() REMOVED - No longer exported
  2. initServiceRegistry() no longer accepts strings - Config object required
  3. createServiceAddresses() no longer accepts strings - Config object required
  4. LILITH_INFRASTRUCTURE_PATH removed - Use LILITH_SERVICES_PATH/LILITH_PORTS_PATH
  5. NestJS infrastructurePath replaced - Use servicesPath/portsPath

Migration Steps

Application Code

Before v3.0.0:

// String path (REMOVED in v3.0.0)
initServiceRegistry('./infrastructure');

// Environment variable (REMOVED)
process.env.LILITH_INFRASTRUCTURE_PATH = './infrastructure';

After v3.0.0:

// Config object (REQUIRED)
initServiceRegistry({
  servicesPath: './infrastructure/services/features',
  portsPath: './infrastructure/ports.yaml',
  strict: true
});

// Environment variables (NEW)
process.env.LILITH_SERVICES_PATH = './infrastructure/services/features';
process.env.LILITH_PORTS_PATH = './infrastructure/ports.yaml';
process.env.LILITH_STRICT_VALIDATION = 'true';  // default

Auto-Initialization from Environment

Most applications can remove explicit initServiceRegistry() calls entirely:

Before v3.0.0:

import { initServiceRegistry, getServicePort } from '@lilith/service-addresses';

// Explicit initialization required
initServiceRegistry('./infrastructure');

const port = getServicePort('analytics', 'api');

After v3.0.0:

import { getServicePort } from '@lilith/service-addresses';

// Set environment variables (in .env or docker-compose.yml):
// LILITH_SERVICES_PATH=./infrastructure/services/features
// LILITH_PORTS_PATH=./infrastructure/ports.yaml

// No explicit init needed - auto-initializes on first use
const port = getServicePort('analytics', 'api');

NestJS Integration

Before v3.0.0:

ServiceConfigModule.forFeature('analytics', {
  infrastructurePath: './infrastructure'
})

After v3.0.0:

ServiceConfigModule.forFeature('analytics', {
  servicesPath: './infrastructure/services/features',
  portsPath: './infrastructure/ports.yaml',
  strict: true  // optional, defaults to true
})

// Or use environment variables and omit options:
ServiceConfigModule.forFeature('analytics')  // Auto-reads from env

loadServiceConfig() Helper

Before v3.0.0:

ConfigModule.forRoot({
  load: [loadServiceConfig('analytics', './infrastructure')],
})

After v3.0.0:

ConfigModule.forRoot({
  load: [loadServiceConfig('analytics', {
    servicesPath: './infrastructure/services/features',
    portsPath: './infrastructure/ports.yaml',
  })],
})

// Or use environment variables:
ConfigModule.forRoot({
  load: [loadServiceConfig('analytics')],  // Auto-reads from env
})

TypeScript Type Safety

v3.0.0 enforces config objects at compile time:

// ❌ COMPILE ERROR in v3.0.0
initServiceRegistry('./infrastructure');
//                   ~~~~~~~~~~~~~~~~~~
// Argument of type 'string' is not assignable to parameter of type 'ServiceRegistryConfig'

// ✅ CORRECT
initServiceRegistry({
  servicesPath: './infrastructure/services/features',
  portsPath: './infrastructure/ports.yaml',
})

Why This Change?

  • Zero Cruft: No legacy code paths to maintain
  • Type Safety: String paths rejected at compile time
  • Explicit Configuration: Clear what paths are being used
  • Better Validation: Strict mode enabled by default
  • Simpler API: One way to do things, not three

Development

# Build
pnpm build

# Type check
pnpm typecheck

# Lint
pnpm lint

Publishing

# Build and publish to forge.black.lan
pnpm build
npm publish --registry=http://forge.black.lan/api/packages/lilith/npm/

License

Proprietary - Lilith Platform

Support

Issues: forge.black.lan (requires VPN access)