/** * Extensible Configuration System * Allows augmentations to register new storage types, presets, and configurations */ import { StorageOption, PresetName, PresetConfig, ModelPrecision, DistributedRole, PresetCategory } from './distributedPresets.js' import { StorageConfigResult } from './storageAutoConfig.js' /** * Storage provider registration interface */ export interface StorageProvider { type: string // e.g., 'redis', 'mongodb', 'postgres' name: string description: string // Detection function - returns true if this storage should be auto-selected detect: () => Promise // Configuration builder getConfig: () => Promise // Priority for auto-detection (higher = checked first) priority?: number // Required environment variables or packages requirements?: { env?: string[] packages?: string[] } } /** * Preset extension interface */ export interface PresetExtension { name: string config: PresetConfig override?: boolean // Override existing preset } /** * Global registry for extensions */ class ConfigurationRegistry { private static instance: ConfigurationRegistry // Registered storage providers private storageProviders: Map = new Map() // Registered preset extensions private presetExtensions: Map = new Map() // Custom auto-detection hooks private autoDetectHooks: Array<() => Promise> = [] private constructor() { // Initialize with built-in providers this.registerBuiltInProviders() } static getInstance(): ConfigurationRegistry { if (!ConfigurationRegistry.instance) { ConfigurationRegistry.instance = new ConfigurationRegistry() } return ConfigurationRegistry.instance } /** * Register a new storage provider * This is how augmentations add new storage types */ registerStorageProvider(provider: StorageProvider): void { console.log(`📦 Registering storage provider: ${provider.type} (${provider.name})`) this.storageProviders.set(provider.type, provider) } /** * Register a new preset */ registerPreset(name: string, extension: PresetExtension): void { console.log(`🎨 Registering preset: ${name}`) this.presetExtensions.set(name, extension) } /** * Register an auto-detection hook */ registerAutoDetectHook(hook: () => Promise): void { this.autoDetectHooks.push(hook) } /** * Get all registered storage providers */ getStorageProviders(): StorageProvider[] { return Array.from(this.storageProviders.values()) .sort((a, b) => (b.priority || 0) - (a.priority || 0)) } /** * Get all registered presets (built-in + extensions) */ getAllPresets(): Map { // Start with built-in presets const allPresets = new Map() // Note: Would import from distributedPresets-new.ts // Add extended presets for (const [name, extension] of this.presetExtensions) { if (extension.override || !allPresets.has(name)) { allPresets.set(name, extension.config) } } return allPresets } /** * Auto-detect storage including extensions */ async autoDetectStorage(): Promise { // Check registered providers first (in priority order) for (const provider of this.getStorageProviders()) { try { if (await provider.detect()) { const config = await provider.getConfig() return { type: provider.type as any, config, reason: `Auto-detected ${provider.name}`, autoSelected: true } } } catch (error) { console.warn(`Failed to detect ${provider.type}:`, error) } } // Fallback to built-in detection const { autoDetectStorage } = await import('./storageAutoConfig.js') return autoDetectStorage() } /** * Register built-in providers */ private registerBuiltInProviders(): void { // These would be the built-in ones, but could be overridden } } /** * Example: Redis storage provider registration * This would be in the redis augmentation package */ export const redisStorageProvider: StorageProvider = { type: 'redis', name: 'Redis Storage', description: 'High-performance in-memory data store', priority: 10, // Check before filesystem requirements: { env: ['REDIS_URL', 'REDIS_HOST'], packages: ['redis', 'ioredis'] }, async detect(): Promise { // Check for Redis connection info if (process.env.REDIS_URL || process.env.REDIS_HOST) { try { // Try to connect to Redis (dynamic import for optional dependency) const redis: any = await new Function('return import("ioredis")')().catch(() => null) if (!redis) return false const client = new redis.default(process.env.REDIS_URL) await client.ping() await client.quit() return true } catch { // Redis not available } } return false }, async getConfig(): Promise { return { type: 'redis', redisStorage: { url: process.env.REDIS_URL || `redis://${process.env.REDIS_HOST}:${process.env.REDIS_PORT || 6379}`, prefix: process.env.REDIS_PREFIX || 'brainy:', ttl: process.env.REDIS_TTL ? parseInt(process.env.REDIS_TTL) : undefined } } } } /** * Example: MongoDB storage provider */ export const mongoStorageProvider: StorageProvider = { type: 'mongodb', name: 'MongoDB Storage', description: 'Document database for complex data', priority: 8, requirements: { env: ['MONGODB_URI', 'MONGO_URL'], packages: ['mongodb'] }, async detect(): Promise { if (process.env.MONGODB_URI || process.env.MONGO_URL) { try { const mongodb: any = await new Function('return import("mongodb")')().catch(() => null) if (!mongodb) return false const client = new mongodb.MongoClient(process.env.MONGODB_URI || process.env.MONGO_URL!) await client.connect() await client.close() return true } catch { // MongoDB not available } } return false }, async getConfig(): Promise { return { type: 'mongodb', mongoStorage: { uri: process.env.MONGODB_URI || process.env.MONGO_URL, database: process.env.MONGO_DATABASE || 'brainy', collection: process.env.MONGO_COLLECTION || 'vectors' } } } } /** * Example: PostgreSQL with pgvector extension */ export const postgresStorageProvider: StorageProvider = { type: 'postgres', name: 'PostgreSQL Storage', description: 'PostgreSQL with pgvector for scalable vector search', priority: 9, requirements: { env: ['DATABASE_URL', 'POSTGRES_URL'], packages: ['pg', 'pgvector'] }, async detect(): Promise { const url = process.env.DATABASE_URL || process.env.POSTGRES_URL if (url && url.includes('postgres')) { try { const pg: any = await new Function('return import("pg")')().catch(() => null) if (!pg) return false const client = new pg.Client({ connectionString: url }) await client.connect() // Check for pgvector extension const result = await client.query( "SELECT * FROM pg_extension WHERE extname = 'vector'" ) await client.end() return result.rows.length > 0 } catch { // PostgreSQL not available or pgvector not installed } } return false }, async getConfig(): Promise { return { type: 'postgres', postgresStorage: { connectionString: process.env.DATABASE_URL || process.env.POSTGRES_URL, table: process.env.POSTGRES_TABLE || 'brainy_vectors', schema: process.env.POSTGRES_SCHEMA || 'public' } } } } /** * How an augmentation would register its storage provider */ export function registerStorageAugmentation(provider: StorageProvider): void { const registry = ConfigurationRegistry.getInstance() registry.registerStorageProvider(provider) } /** * How to register a new preset */ export function registerPresetAugmentation(name: string, config: PresetConfig): void { const registry = ConfigurationRegistry.getInstance() registry.registerPreset(name, { name, config, override: false }) } /** * Example preset for Redis-based caching service */ export const redisCachePreset: PresetConfig = { storage: 'redis' as any, // Extended storage type model: ModelPrecision.Q8, features: ['core', 'cache', 'search'], distributed: true, role: DistributedRole.READER, readOnly: true, cache: { hotCacheMaxSize: 50000, // Large Redis cache autoTune: true }, description: 'Redis-backed caching layer', category: PresetCategory.SERVICE } /** * Get the configuration registry */ export function getConfigRegistry(): ConfigurationRegistry { return ConfigurationRegistry.getInstance() }