OmniRoute Database Migrations and Domain Module Structure: Complete Developer Guide

OmniRoute manages database schema evolution through 84 version-controlled SQLite migrations in src/lib/db/migrations/ and organizes business logic into clean-architecture domain modules under src/domain/.

This guide examines how OmniRoute's migration system and domain layer work together to provide a reliable foundation for LLM routing, quota management, and provider resilience. We'll analyze the actual source code from the release/v3.8.50 branch to understand the schema design patterns and domain module implementation.

How OmniRoute Database Migrations Work

The migration system in OmniRoute follows a strict lexical ordering convention that ensures deterministic schema evolution across all deployment nodes.

Migration File Naming and Location

All migrations reside in src/lib/db/migrations/ and use the pattern ###_description.sql:

src/lib/db/migrations/
├── 001_initial_schema.sql
├── 002_mcp_a2a_tables.sql
├── 003_provider_node_custom_paths.sql
...
├── 088_quota_groups.sql
├── 093_proxy_enable_toggles.sql
└── 126_reasoning_routing_rules.sql

The three-digit numeric prefix controls execution order. Gaps in numbering are intentional, allowing future migrations to insert between existing versions without renaming files.

Key Migrations by Feature Area

From the 84 migration files in the current release, these core migrations establish OmniRoute's data model:

Migration Tables/Features Created
001_initial_schema.sql providers, api_keys, model_capability_overrides, foundational routing tables
002_mcp_a2a_tables.sql MCP/A2A messaging framework tables
003_provider_node_custom_paths.sql Provider-specific path routing configuration
010_model_combo_mappings.sql combo_call_log_targets, combo_sort_order for multi-model routing
014_unified_log_artifacts.sql Consolidated observability schema
025_call_logs_summary_storage.sql Optimized call log aggregation
040_oneproxy_proxy_fields.sql One-proxy abstraction layer
058_command_code_auth_sessions.sql Command-code session authentication
070_webhooks_kind_metadata.sql Webhook kind and metadata extensions
079_provider_plans.sql Provider plan configuration tables
088_quota_groups.sql quota_pools, quota_groups, quota_consumption tables
093_proxy_enable_toggles.sql Per-provider proxy activation flags
126_reasoning_routing_rules.sql Latest reasoning engine routing rules

Migration Runner Implementation

The src/lib/db/migrationRunner.ts file implements transactional, idempotent schema upgrades:

import { runMigrations } from '@/lib/db/migrationRunner';

async function initDatabase() {
  await runMigrations();
}
initDatabase();

The runner executes each migration within a SQLite transaction and records progress in the schema_migrations table. This design guarantees:

  • Atomicity: Failed migrations roll back completely
  • Idempotency: Applied migrations are skipped on subsequent runs
  • Cluster convergence: All nodes reach identical schema states

OmniRoute Domain Module Architecture

Domain modules in src/domain/ implement clean architecture principles, isolating business logic from infrastructure concerns. Each module operates on tables defined by the migration layer.

Core Domain Files and Responsibilities

src/domain/types.ts — Shared TypeScript Interfaces

Defines the contracts used across all domain modules, including request/response shapes and internal model types. This file establishes type safety between the database layer and business logic.

src/domain/policyEngine.ts — Request Validation

Evaluates cost rules, lock-out policies, and fallback strategies before dispatch:

import { evaluatePolicy } from '@/domain/policyEngine';
import { getDbInstance } from '@/lib/db/core';

async function canProceed(request) {
  const db = getDbInstance();
  const result = await evaluatePolicy(db, request);
  return result.allowed;
}

The policy engine queries tables established by migrations 001, 058, and 126 to make routing decisions.

src/domain/comboResolver.ts — Multi-Model Routing

Resolves "combo" requests into ordered target model lists using combo_call_log_targets and combo_sort_order tables (migration 010).

src/domain/quotaCache.ts — Rate Limit Enforcement

Reads from migration 088's quota tables to enforce limits:

  • quota_pools — Aggregate capacity allocation
  • quota_groups — Per-tenant or per-key groupings
  • quota_consumption — Real-time usage tracking

src/domain/modelAvailability.ts — Capability Filtering

Checks whether models can handle specific requests by querying:

  • model_capability_overrides (migration 001)
  • model_intelligence (migration 001)
import { getModelOverrides } from '@/domain/modelAvailability';
import { getDbInstance } from '@/lib/db/core';

async function listOverrides(modelId: string) {
  const db = getDbInstance();
  return await getModelOverrides(db, modelId);
}

Resilience Domain Modules

OmniRoute implements a three-layer resilience mechanism across dedicated domain modules:

Module Purpose Migration Dependencies
fallbackPolicy.ts Provider fallback chain selection provider_breaker_state
lockoutPolicy.ts Model-level lockout enforcement connection_runtime_state

These modules operate on circuit breaker and cooldown tables introduced in earlier migrations.

Assessment and Self-Healing Framework

The src/domain/assessment/ subdirectory contains:

  • selfHealer.ts — Automated recovery from routing failures
  • Categorization logic for audit table analysis

These utilities operate on audit schema generated by assessment_migration.sql.

How Migrations and Domain Modules Integrate

The architecture enforces a unidirectional dependency: domain modules import from src/lib/db/ but migrations never reference domain code.

Schema-to-Code Mapping Example

Migration 088_quota_groups.sql creates quota tables → quotaCache.ts provides typed access:


Migration Layer (DDL)          Domain Layer (Business Logic)
─────────────────────          ─────────────────────────────
quota_groups  ───────────────>  quotaCache.ts
quota_pools   ───────────────>    ├── getPoolCapacity()
quota_consumption ───────────>    ├── consumeQuota()
                                  └── checkGroupLimits()

Adding New Features: Developer Workflow

When extending OmniRoute, the typical pattern is:

  1. Create migration in src/lib/db/migrations/###_feature_name.sql
  2. Update domain types in src/domain/types.ts if new entities are introduced
  3. Implement domain module or extend existing module in src/domain/
  4. Export public API from domain module for use by handlers and executors

Migration Best Practices from OmniRoute Source

The release/v3.8.50 codebase demonstrates several schema evolution patterns:

  • Additive changes only: No migrations drop columns or tables; deprecation happens in domain logic
  • Index creation: Separate from table creation, often in dedicated migrations for large tables
  • Data transformations: Some migrations include INSERT ... SELECT statements to populate new columns from existing data
  • Feature flags: Migrations 093 and later introduce toggles that domain modules check at runtime

Summary

Frequently Asked Questions

Where are OmniRoute database migrations stored?

All migrations live in src/lib/db/migrations/ and follow the ###_description.sql naming pattern. The three-digit prefix ensures lexical execution order. The current release contains 84 migration files ranging from 001_initial_schema.sql to 126_reasoning_routing_rules.sql.

How does the migration runner ensure schema consistency across nodes?

The migrationRunner.ts executes each migration within a SQLite transaction and records the applied version in the schema_migrations table. On startup, nodes compare their recorded version against available migrations and apply only pending changes. This guarantees all cluster members converge on identical schema states.

What tables support OmniRoute's quota and rate limiting system?

Migration 088_quota_groups.sql introduced quota_pools, quota_groups, and quota_consumption tables. The quotaCache.ts domain module provides typed access to these tables, implementing per-key and per-provider limit enforcement with configurable pool capacities.

How do domain modules access database tables without tight coupling?

Domain modules import database connections through src/lib/db/core.ts and use TypeScript interfaces from src/domain/types.ts. The architecture enforces clean separation: migrations define schema (DDL), domain modules implement business logic (DML), and neither layer imports from the other.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →