import { readFileSync, existsSync } from 'node:fs'; import { resolve } from 'node:path'; import type { StorageConfig } from '@mosaic/storage'; import type { QueueAdapterConfig as QueueConfig } from '@mosaic/queue'; /* ------------------------------------------------------------------ */ /* Types */ /* ------------------------------------------------------------------ */ export type StorageTier = 'local' | 'team'; export interface MemoryConfigRef { type: 'pgvector' | 'sqlite-vec' | 'keyword'; } export interface MosaicConfig { tier: StorageTier; storage: StorageConfig; queue: QueueConfig; memory: MemoryConfigRef; } /* ------------------------------------------------------------------ */ /* Defaults */ /* ------------------------------------------------------------------ */ export const DEFAULT_LOCAL_CONFIG: MosaicConfig = { tier: 'local', storage: { type: 'pglite', dataDir: '.mosaic/storage-pglite' }, queue: { type: 'local', dataDir: '.mosaic/queue' }, memory: { type: 'keyword' }, }; export const DEFAULT_TEAM_CONFIG: MosaicConfig = { tier: 'team', storage: { type: 'postgres', url: 'postgresql://mosaic:mosaic@localhost:5432/mosaic' }, queue: { type: 'bullmq' }, memory: { type: 'pgvector' }, }; /* ------------------------------------------------------------------ */ /* Validation */ /* ------------------------------------------------------------------ */ const VALID_TIERS = new Set(['local', 'team']); const VALID_STORAGE_TYPES = new Set(['postgres', 'pglite', 'files']); const VALID_QUEUE_TYPES = new Set(['bullmq', 'local']); const VALID_MEMORY_TYPES = new Set(['pgvector', 'sqlite-vec', 'keyword']); export function validateConfig(raw: unknown): MosaicConfig { if (typeof raw !== 'object' || raw === null) { throw new Error('MosaicConfig must be a non-null object'); } const obj = raw as Record; // tier const tier = obj['tier']; if (typeof tier !== 'string' || !VALID_TIERS.has(tier)) { throw new Error(`Invalid tier "${String(tier)}" — expected "local" or "team"`); } // storage const storage = obj['storage']; if (typeof storage !== 'object' || storage === null) { throw new Error('config.storage must be a non-null object'); } const storageType = (storage as Record)['type']; if (typeof storageType !== 'string' || !VALID_STORAGE_TYPES.has(storageType)) { throw new Error(`Invalid storage.type "${String(storageType)}"`); } // queue const queue = obj['queue']; if (typeof queue !== 'object' || queue === null) { throw new Error('config.queue must be a non-null object'); } const queueType = (queue as Record)['type']; if (typeof queueType !== 'string' || !VALID_QUEUE_TYPES.has(queueType)) { throw new Error(`Invalid queue.type "${String(queueType)}"`); } // memory const memory = obj['memory']; if (typeof memory !== 'object' || memory === null) { throw new Error('config.memory must be a non-null object'); } const memoryType = (memory as Record)['type']; if (typeof memoryType !== 'string' || !VALID_MEMORY_TYPES.has(memoryType)) { throw new Error(`Invalid memory.type "${String(memoryType)}"`); } return { tier: tier as StorageTier, storage: storage as StorageConfig, queue: queue as QueueConfig, memory: memory as MemoryConfigRef, }; } /* ------------------------------------------------------------------ */ /* Loader */ /* ------------------------------------------------------------------ */ function detectFromEnv(): MosaicConfig { if (process.env['DATABASE_URL']) { return { ...DEFAULT_TEAM_CONFIG, storage: { type: 'postgres', url: process.env['DATABASE_URL'], }, queue: { type: 'bullmq', url: process.env['VALKEY_URL'], }, }; } return DEFAULT_LOCAL_CONFIG; } export function loadConfig(configPath?: string): MosaicConfig { // 1. Explicit path or default location const paths = configPath ? [resolve(configPath)] : [ resolve(process.cwd(), 'mosaic.config.json'), resolve(process.cwd(), '../../mosaic.config.json'), // monorepo root when cwd is apps/gateway ]; for (const p of paths) { if (existsSync(p)) { const raw: unknown = JSON.parse(readFileSync(p, 'utf-8')); return validateConfig(raw); } } // 2. Fall back to env-var detection return detectFromEnv(); }