// SPDX-License-Identifier: Apache-2.0 import { createPostgresStorageRepositories, getSharedPostgresPool, SERVER_BETA_POSTGRES_SCHEMA_VERSION } from '../../storage/postgres/index.js'; import { bootstrapServerBetaPostgresSchema } from '../../storage/postgres/schema.js'; import type { PostgresPool } from '../../storage/postgres/pool.js'; import { getRedisQueueConfig } from '../queue/redis-config.js'; import { ActiveServerBetaQueueManager } from './ActiveServerBetaQueueManager.js'; import { ServerBetaService } from './ServerBetaService.js'; import { DisabledServerBetaEventBroadcaster, DisabledServerBetaGenerationWorkerManager, DisabledServerBetaProviderRegistry, DisabledServerBetaQueueManager, type ServerBetaAuthMode, type ServerBetaBootstrapStatus, type ServerBetaQueueManager, type ServerBetaServiceGraph, } from './types.js'; export interface CreateServerBetaServiceOptions { pool?: PostgresPool; authMode?: ServerBetaAuthMode; bootstrapSchema?: boolean; queueManager?: ServerBetaQueueManager; } export async function createServerBetaService( options: CreateServerBetaServiceOptions = {}, ): Promise { const pool = options.pool ?? getSharedPostgresPool({ requireDatabaseUrl: true }); const bootstrap = await initializePostgres(pool, options.bootstrapSchema ?? true); const graph: ServerBetaServiceGraph = { runtime: 'server-beta', postgres: { pool, bootstrap, }, authMode: options.authMode ?? parseAuthMode(process.env.CLAUDE_MEM_AUTH_MODE), queueManager: options.queueManager ?? buildQueueManager(), generationWorkerManager: new DisabledServerBetaGenerationWorkerManager('Phase 2 boundary only; generation workers are not wired.'), providerRegistry: new DisabledServerBetaProviderRegistry('Phase 2 boundary only; provider-backed generation is not wired.'), eventBroadcaster: new DisabledServerBetaEventBroadcaster('Phase 2 boundary only; SSE/event broadcasting is not wired.'), storage: createPostgresStorageRepositories(pool), }; return new ServerBetaService({ graph }); } // Queue manager selection is fail-fast on misconfiguration. If the user // explicitly opts into BullMQ via CLAUDE_MEM_QUEUE_ENGINE=bullmq we build // the active manager; any error there throws so the runtime does not // silently fall back to a disabled queue. Default behavior (sqlite engine // or no opt-in) keeps the disabled boundary so worker-era runtimes stay // compatible. function buildQueueManager(): ServerBetaQueueManager { const config = getRedisQueueConfig(); if (config.engine !== 'bullmq') { return new DisabledServerBetaQueueManager( `Queue engine is "${config.engine}"; set CLAUDE_MEM_QUEUE_ENGINE=bullmq to activate the server-beta queue manager.`, ); } return new ActiveServerBetaQueueManager(config); } async function initializePostgres(pool: PostgresPool, bootstrapSchema: boolean): Promise { if (!bootstrapSchema) { return { initialized: false, schemaVersion: null, appliedAt: null }; } await bootstrapServerBetaPostgresSchema(pool); const result = await pool.query( ` SELECT version, applied_at FROM server_beta_schema_migrations WHERE version = $1 `, [SERVER_BETA_POSTGRES_SCHEMA_VERSION], ); const row = result.rows[0] as { version?: number; applied_at?: Date | string } | undefined; return { initialized: row?.version === SERVER_BETA_POSTGRES_SCHEMA_VERSION, schemaVersion: typeof row?.version === 'number' ? row.version : null, appliedAt: row?.applied_at ? new Date(row.applied_at).toISOString() : null, }; } function parseAuthMode(value: string | undefined): ServerBetaAuthMode { if (value === 'local-dev' || value === 'disabled') { return value; } return 'api-key'; }