Support zero-config gateway startup
This commit is contained in:
parent
fbd175918f
commit
4b17d6eab4
@ -5,6 +5,16 @@ const { Pool } = pg;
|
||||
|
||||
let pool: pg.Pool | null = null;
|
||||
|
||||
export function isDatabaseConfigured(): boolean {
|
||||
return Boolean(
|
||||
process.env['DATABASE_URL']
|
||||
|| process.env['DB_HOST']
|
||||
|| process.env['DB_NAME']
|
||||
|| process.env['DB_USER']
|
||||
|| process.env['DB_PASSWORD'],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build pool config from DATABASE_URL (preferred) or individual DB_* env vars.
|
||||
* DATABASE_URL should be supplied by the runtime environment or a secret manager.
|
||||
@ -32,6 +42,9 @@ function buildPoolConfig(): pg.PoolConfig {
|
||||
}
|
||||
|
||||
export function getPool(): pg.Pool {
|
||||
if (!isDatabaseConfigured()) {
|
||||
throw new Error('Database is not configured');
|
||||
}
|
||||
if (!pool) {
|
||||
pool = new Pool(buildPoolConfig());
|
||||
|
||||
@ -42,6 +55,13 @@ export function getPool(): pg.Pool {
|
||||
return pool;
|
||||
}
|
||||
|
||||
export async function closePool(): Promise<void> {
|
||||
if (pool) {
|
||||
await pool.end();
|
||||
pool = null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function query<T extends pg.QueryResultRow = pg.QueryResultRow>(
|
||||
sql: string,
|
||||
params?: unknown[],
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import PgBoss from 'pg-boss';
|
||||
import { logger } from '../observability/logger.js';
|
||||
import { costStream } from '../observability/cost-stream.js';
|
||||
import { isDatabaseConfigured } from '../db/client.js';
|
||||
|
||||
const QUEUE_NAME = 'llm-batch';
|
||||
const CONCURRENCY = 4;
|
||||
@ -32,6 +33,10 @@ interface TaskResult {
|
||||
|
||||
export async function initPgBoss(): Promise<void> {
|
||||
if (boss) return;
|
||||
if (!isDatabaseConfigured()) {
|
||||
logger.info('DATABASE_URL/DB_* not configured - queue disabled');
|
||||
return;
|
||||
}
|
||||
|
||||
const connectionString =
|
||||
process.env['DATABASE_URL'] ??
|
||||
|
||||
@ -4,7 +4,7 @@ import { dirname, join } from 'path';
|
||||
import { readFileSync, existsSync } from 'fs';
|
||||
import { getOllamaBaseUrl } from '../pipeline/router.js';
|
||||
import { getAllBreakerStates } from '../circuit-breaker/ollama-breaker.js';
|
||||
import { query } from '../db/client.js';
|
||||
import { isDatabaseConfigured, query } from '../db/client.js';
|
||||
import { getPgBoss } from '../queue/pg-boss-client.js';
|
||||
import { logger } from '../observability/logger.js';
|
||||
|
||||
@ -13,8 +13,8 @@ interface HealthStatus {
|
||||
timestamp: string;
|
||||
checks: {
|
||||
ollama: { status: 'ok' | 'down'; latency_ms?: number; error?: string };
|
||||
database: { status: 'ok' | 'down'; error?: string };
|
||||
queue: { status: 'ok' | 'down' | 'unknown'; depth?: number; error?: string };
|
||||
database: { status: 'ok' | 'down' | 'disabled'; error?: string };
|
||||
queue: { status: 'ok' | 'down' | 'unknown' | 'disabled'; depth?: number; error?: string };
|
||||
review_queue: { unreviewed_count: number };
|
||||
circuit_breakers: Record<string, 'closed' | 'open' | 'half-open'>;
|
||||
};
|
||||
@ -36,7 +36,10 @@ async function checkOllama(baseUrl: string): Promise<{ status: 'ok' | 'down'; la
|
||||
}
|
||||
}
|
||||
|
||||
async function checkDatabase(): Promise<{ status: 'ok' | 'down'; error?: string }> {
|
||||
async function checkDatabase(): Promise<{ status: 'ok' | 'down' | 'disabled'; error?: string }> {
|
||||
if (!isDatabaseConfigured()) {
|
||||
return { status: 'disabled' };
|
||||
}
|
||||
try {
|
||||
await withTimeout(query('SELECT 1'), 2500, 'database check timed out');
|
||||
return { status: 'ok' };
|
||||
@ -59,7 +62,8 @@ async function withTimeout<T>(promise: Promise<T>, timeoutMs: number, message: s
|
||||
}
|
||||
}
|
||||
|
||||
async function checkQueue(): Promise<{ status: 'ok' | 'down' | 'unknown'; depth?: number; error?: string }> {
|
||||
async function checkQueue(): Promise<{ status: 'ok' | 'down' | 'unknown' | 'disabled'; depth?: number; error?: string }> {
|
||||
if (!isDatabaseConfigured()) return { status: 'disabled' };
|
||||
const boss = getPgBoss();
|
||||
if (!boss) return { status: 'unknown' };
|
||||
|
||||
@ -79,6 +83,7 @@ async function checkQueue(): Promise<{ status: 'ok' | 'down' | 'unknown'; depth?
|
||||
}
|
||||
|
||||
async function getReviewQueueCount(): Promise<number> {
|
||||
if (!isDatabaseConfigured()) return 0;
|
||||
try {
|
||||
const result = await withTimeout(
|
||||
query<{ count: string }>('SELECT COUNT(*) as count FROM review_queue WHERE decision IS NULL'),
|
||||
|
||||
@ -16,7 +16,7 @@ import { learningInsightsRoute } from './routes/learning-insights.js';
|
||||
import { staticRoute } from './routes/static.js';
|
||||
import tenantAuth from './security/tenant-auth.js';
|
||||
import { internalRoute } from './routes/internal.js';
|
||||
import { getPool } from './db/client.js';
|
||||
import { closePool, getPool, isDatabaseConfigured } from './db/client.js';
|
||||
import { runMigrations } from './db/migrate.js';
|
||||
import { initPgBoss } from './queue/pg-boss-client.js';
|
||||
import { logger } from './observability/logger.js';
|
||||
@ -190,8 +190,7 @@ async function main() {
|
||||
logger.info({ signal }, 'Shutdown signal received');
|
||||
try {
|
||||
await server.close();
|
||||
const pool = getPool();
|
||||
await pool.end();
|
||||
await closePool();
|
||||
logger.info('Server and DB connections closed');
|
||||
process.exit(0);
|
||||
} catch (err) {
|
||||
@ -207,6 +206,7 @@ async function main() {
|
||||
const host = process.env['HOST'] ?? 'localhost';
|
||||
|
||||
try {
|
||||
if (isDatabaseConfigured()) {
|
||||
try {
|
||||
await runMigrations();
|
||||
} catch (migErr) {
|
||||
@ -217,6 +217,9 @@ async function main() {
|
||||
} catch (pgErr) {
|
||||
logger.warn({ pgErr }, 'PgBoss init failed - continuing without queue');
|
||||
}
|
||||
} else {
|
||||
logger.info('DATABASE_URL/DB_* not configured - persistence and queue disabled');
|
||||
}
|
||||
// Workspace preset (apply env defaults from workspace.yaml if present)
|
||||
try {
|
||||
const preset = await loadWorkspacePreset();
|
||||
@ -251,8 +254,10 @@ async function main() {
|
||||
|
||||
// Adaptive routing learner (opt-in via ADAPTIVE_ROUTING_ENABLED=1)
|
||||
try {
|
||||
if (isDatabaseConfigured()) {
|
||||
const pool = getPool();
|
||||
scheduleAdaptiveLearner(pool as never);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.warn({ err }, 'Adaptive learner scheduling failed');
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user