import * as plugins from '../plugins.js'; import { logger } from '../logger.js'; import { defaultTsmDbPath } from '../paths.js'; /** * Configuration options for the unified DCRouter database */ export interface IDcRouterDbConfig { /** External MongoDB connection URL. If absent, uses embedded LocalSmartDb. */ mongoDbUrl?: string; /** Storage path for embedded LocalSmartDb data (default: ~/.serve.zone/dcrouter/tsmdb) */ storagePath?: string; /** Database name (default: dcrouter) */ dbName?: string; /** Enable debug logging */ debug?: boolean; } /** * DcRouterDb - Unified database layer for DCRouter * * Replaces both StorageManager (flat-file key-value) and CacheDb (embedded MongoDB). * All data is stored as smartdata document classes in a single database. * * Two modes: * - **Embedded** (default): Spawns a LocalSmartDb (Rust-based MongoDB-compatible engine) * - **External**: Connects to a provided MongoDB URL */ export class DcRouterDb { private static instance: DcRouterDb | null = null; private localSmartDb: plugins.smartdb.LocalSmartDb | null = null; private smartdataDb!: plugins.smartdata.SmartdataDb; private options: Required; private isStarted: boolean = false; constructor(options: IDcRouterDbConfig = {}) { this.options = { mongoDbUrl: options.mongoDbUrl || '', storagePath: options.storagePath || defaultTsmDbPath, dbName: options.dbName || 'dcrouter', debug: options.debug || false, }; } /** * Get or create the singleton instance */ public static getInstance(options?: IDcRouterDbConfig): DcRouterDb { if (!DcRouterDb.instance) { DcRouterDb.instance = new DcRouterDb(options); } return DcRouterDb.instance; } /** * Reset the singleton instance (useful for testing) */ public static resetInstance(): void { DcRouterDb.instance = null; } /** * Start the database * - If mongoDbUrl is provided, connects directly to external MongoDB * - Otherwise, starts an embedded LocalSmartDb instance */ public async start(): Promise { if (this.isStarted) { logger.log('warn', 'DcRouterDb already started'); return; } try { let connectionUri: string; if (this.options.mongoDbUrl) { // External MongoDB mode connectionUri = this.options.mongoDbUrl; logger.log('info', `DcRouterDb connecting to external MongoDB`); } else { // Embedded LocalSmartDb mode await plugins.fsUtils.ensureDir(this.options.storagePath); this.localSmartDb = new plugins.smartdb.LocalSmartDb({ folderPath: this.options.storagePath, }); const connectionInfo = await this.localSmartDb.start(); connectionUri = connectionInfo.connectionUri; if (this.options.debug) { logger.log('debug', `LocalSmartDb started with URI: ${connectionUri}`); } logger.log('info', `DcRouterDb started embedded instance at ${this.options.storagePath}`); } // Initialize smartdata ORM this.smartdataDb = new plugins.smartdata.SmartdataDb({ mongoDbUrl: connectionUri, mongoDbName: this.options.dbName, }); await this.smartdataDb.init(); this.isStarted = true; logger.log('info', `DcRouterDb ready (db: ${this.options.dbName})`); } catch (error: unknown) { logger.log('error', `Failed to start DcRouterDb: ${(error as Error).message}`); throw error; } } /** * Stop the database */ public async stop(): Promise { if (!this.isStarted) { return; } try { // Close smartdata connection if (this.smartdataDb) { await this.smartdataDb.close(); } // Stop embedded LocalSmartDb if running if (this.localSmartDb) { await this.localSmartDb.stop(); this.localSmartDb = null; } this.isStarted = false; logger.log('info', 'DcRouterDb stopped'); } catch (error: unknown) { logger.log('error', `Error stopping DcRouterDb: ${(error as Error).message}`); throw error; } } /** * Get the smartdata database instance for @Collection decorators */ public getDb(): plugins.smartdata.SmartdataDb { if (!this.isStarted) { throw new Error('DcRouterDb not started. Call start() first.'); } return this.smartdataDb; } /** * Check if the database is ready */ public isReady(): boolean { return this.isStarted; } /** * Whether running in embedded mode (LocalSmartDb) vs external MongoDB */ public isEmbedded(): boolean { return !this.options.mongoDbUrl; } /** * Get the storage path (only relevant for embedded mode) */ public getStoragePath(): string { return this.options.storagePath; } /** * Get the database name */ public getDbName(): string { return this.options.dbName; } }