BREAKING CHANGE(db): replace StorageManager and CacheDb with a unified smartdata-backed database layer
This commit is contained in:
179
ts/db/classes.dcrouter-db.ts
Normal file
179
ts/db/classes.dcrouter-db.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
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<IDcRouterDbConfig>;
|
||||
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<void> {
|
||||
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<void> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user