import * as plugins from './plugins.js'; import * as path from 'path'; import * as url from 'url'; import { EventEmitter } from 'events'; /** * Type-safe command definitions for the RustDb IPC protocol. */ type TSmartDbCommands = { start: { params: { config: ISmartDbRustConfig }; result: { connectionUri: string } }; stop: { params: Record; result: void }; getStatus: { params: Record; result: { running: boolean } }; getMetrics: { params: Record; result: any }; }; /** * Configuration sent to the Rust binary on start. */ interface ISmartDbRustConfig { port?: number; host?: string; socketPath?: string; storage: 'memory' | 'file'; storagePath?: string; persistPath?: string; persistIntervalMs?: number; } /** * Get the package root directory using import.meta.url. * This file is at ts/ts_smartdb/, so package root is 2 levels up. */ function getPackageRoot(): string { const thisDir = path.dirname(url.fileURLToPath(import.meta.url)); return path.resolve(thisDir, '..', '..'); } /** * Map Node.js process.platform/process.arch to tsrust's friendly name suffix. * tsrust names cross-compiled binaries as: rustdb_linux_amd64, rustdb_linux_arm64, etc. */ function getTsrustPlatformSuffix(): string | null { const archMap: Record = { x64: 'amd64', arm64: 'arm64' }; const osMap: Record = { linux: 'linux', darwin: 'macos' }; const os = osMap[process.platform]; const arch = archMap[process.arch]; if (os && arch) { return `${os}_${arch}`; } return null; } /** * Build local search paths for the Rust binary, including dist_rust/ candidates * (built by tsrust) and local development build paths. */ function buildLocalPaths(): string[] { const packageRoot = getPackageRoot(); const suffix = getTsrustPlatformSuffix(); const paths: string[] = []; // dist_rust/ candidates (tsrust cross-compiled output) if (suffix) { paths.push(path.join(packageRoot, 'dist_rust', `rustdb_${suffix}`)); } paths.push(path.join(packageRoot, 'dist_rust', 'rustdb')); // Local dev build paths paths.push(path.resolve(process.cwd(), 'rust', 'target', 'release', 'rustdb')); paths.push(path.resolve(process.cwd(), 'rust', 'target', 'debug', 'rustdb')); return paths; } /** * Bridge between TypeScript SmartdbServer and the Rust binary. * Wraps @push.rocks/smartrust's RustBridge with type-safe command definitions. */ export class RustDbBridge extends EventEmitter { private bridge: plugins.smartrust.RustBridge; constructor() { super(); this.bridge = new plugins.smartrust.RustBridge({ binaryName: 'rustdb', envVarName: 'SMARTDB_RUST_BINARY', platformPackagePrefix: '@push.rocks/smartdb', localPaths: buildLocalPaths(), maxPayloadSize: 10 * 1024 * 1024, // 10 MB }); // Forward events from the inner bridge this.bridge.on('exit', (code: number | null, signal: string | null) => { this.emit('exit', code, signal); }); } /** * Spawn the Rust binary in management mode. * Returns true if the binary was found and spawned successfully. */ public async spawn(): Promise { return this.bridge.spawn(); } /** * Kill the Rust process and clean up. */ public kill(): void { this.bridge.kill(); } /** * Whether the bridge is currently running. */ public get running(): boolean { return this.bridge.running; } // --- Convenience methods for each management command --- public async startDb(config: ISmartDbRustConfig): Promise<{ connectionUri: string }> { return await this.bridge.sendCommand('start', { config }) as { connectionUri: string }; } public async stopDb(): Promise { await this.bridge.sendCommand('stop', {} as Record); } public async getStatus(): Promise<{ running: boolean }> { return await this.bridge.sendCommand('getStatus', {} as Record) as { running: boolean }; } public async getMetrics(): Promise { return this.bridge.sendCommand('getMetrics', {} as Record); } }