231 lines
6.0 KiB
TypeScript
231 lines
6.0 KiB
TypeScript
import { RustDbBridge } from '../rust-db-bridge.js';
|
|
import { StorageMigrator } from '../../ts_migration/index.js';
|
|
import type {
|
|
IOpLogEntry,
|
|
IOpLogResult,
|
|
IOpLogStats,
|
|
IRevertResult,
|
|
ICollectionInfo,
|
|
IDocumentsResult,
|
|
ISmartDbMetrics,
|
|
} from '../rust-db-bridge.js';
|
|
|
|
/**
|
|
* Server configuration options
|
|
*/
|
|
export interface ISmartdbServerOptions {
|
|
/** Port to listen on (default: 27017) - ignored if socketPath is set */
|
|
port?: number;
|
|
/** Host to bind to (default: 127.0.0.1) - ignored if socketPath is set */
|
|
host?: string;
|
|
/** Unix socket path - if set, server listens on socket instead of TCP */
|
|
socketPath?: string;
|
|
/** Storage type: 'memory' or 'file' (default: 'memory') */
|
|
storage?: 'memory' | 'file';
|
|
/** Path for file storage (required if storage is 'file') */
|
|
storagePath?: string;
|
|
/** Enable persistence for memory storage */
|
|
persistPath?: string;
|
|
/** Persistence interval in ms (default: 60000) */
|
|
persistIntervalMs?: number;
|
|
}
|
|
|
|
/**
|
|
* SmartdbServer - Wire protocol compatible database server backed by Rust
|
|
*
|
|
* This server implements the wire protocol to allow official drivers to
|
|
* connect and perform operations. The core engine runs as a Rust sidecar
|
|
* binary managed via @push.rocks/smartrust IPC.
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* import { SmartdbServer } from '@push.rocks/smartdb';
|
|
* import { MongoClient } from 'mongodb';
|
|
*
|
|
* const server = new SmartdbServer({ port: 27017 });
|
|
* await server.start();
|
|
*
|
|
* const client = new MongoClient(server.getConnectionUri());
|
|
* await client.connect();
|
|
* ```
|
|
*/
|
|
export class SmartdbServer {
|
|
private options: ISmartdbServerOptions;
|
|
private bridge: RustDbBridge;
|
|
private isRunning = false;
|
|
private resolvedConnectionUri = '';
|
|
|
|
constructor(options: ISmartdbServerOptions = {}) {
|
|
this.options = {
|
|
port: options.port ?? 27017,
|
|
host: options.host ?? '127.0.0.1',
|
|
socketPath: options.socketPath,
|
|
storage: options.storage ?? 'memory',
|
|
storagePath: options.storagePath ?? './data',
|
|
persistPath: options.persistPath,
|
|
persistIntervalMs: options.persistIntervalMs ?? 60000,
|
|
};
|
|
this.bridge = new RustDbBridge();
|
|
}
|
|
|
|
/**
|
|
* Start the server
|
|
*/
|
|
async start(): Promise<void> {
|
|
if (this.isRunning) {
|
|
throw new Error('Server is already running');
|
|
}
|
|
|
|
// Run storage migration for file-based storage before starting Rust engine
|
|
if (this.options.storage === 'file' && this.options.storagePath) {
|
|
const migrator = new StorageMigrator(this.options.storagePath);
|
|
await migrator.run();
|
|
}
|
|
|
|
const spawned = await this.bridge.spawn();
|
|
if (!spawned) {
|
|
throw new Error(
|
|
'smartdb Rust binary not found. Set SMARTDB_RUST_BINARY env var, ' +
|
|
'install the platform package, or build locally with `tsrust`.'
|
|
);
|
|
}
|
|
|
|
// Forward unexpected exit
|
|
this.bridge.on('exit', (code: number | null, signal: string | null) => {
|
|
if (this.isRunning) {
|
|
console.error(`smartdb Rust process exited unexpectedly (code=${code}, signal=${signal})`);
|
|
}
|
|
});
|
|
|
|
// Send config, get back connectionUri
|
|
const result = await this.bridge.startDb({
|
|
port: this.options.port,
|
|
host: this.options.host,
|
|
socketPath: this.options.socketPath,
|
|
storage: this.options.storage ?? 'memory',
|
|
storagePath: this.options.storagePath,
|
|
persistPath: this.options.persistPath,
|
|
persistIntervalMs: this.options.persistIntervalMs,
|
|
});
|
|
|
|
this.resolvedConnectionUri = result.connectionUri;
|
|
this.isRunning = true;
|
|
}
|
|
|
|
/**
|
|
* Stop the server
|
|
*/
|
|
async stop(): Promise<void> {
|
|
if (!this.isRunning) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await this.bridge.stopDb();
|
|
} catch {
|
|
// Bridge may already be dead
|
|
}
|
|
|
|
this.bridge.kill();
|
|
this.isRunning = false;
|
|
}
|
|
|
|
/**
|
|
* Get the connection URI for this server
|
|
*/
|
|
getConnectionUri(): string {
|
|
if (this.resolvedConnectionUri) {
|
|
return this.resolvedConnectionUri;
|
|
}
|
|
// Fallback: compute from options
|
|
if (this.options.socketPath) {
|
|
const encodedPath = encodeURIComponent(this.options.socketPath);
|
|
return `mongodb://${encodedPath}`;
|
|
}
|
|
return `mongodb://${this.options.host ?? '127.0.0.1'}:${this.options.port ?? 27017}`;
|
|
}
|
|
|
|
/**
|
|
* Get the socket path (if using Unix socket mode)
|
|
*/
|
|
get socketPath(): string | undefined {
|
|
return this.options.socketPath;
|
|
}
|
|
|
|
/**
|
|
* Check if the server is running
|
|
*/
|
|
get running(): boolean {
|
|
return this.isRunning;
|
|
}
|
|
|
|
/**
|
|
* Get the port the server is listening on
|
|
*/
|
|
get port(): number {
|
|
return this.options.port ?? 27017;
|
|
}
|
|
|
|
/**
|
|
* Get the host the server is bound to
|
|
*/
|
|
get host(): string {
|
|
return this.options.host ?? '127.0.0.1';
|
|
}
|
|
|
|
// --- OpLog / Debug API ---
|
|
|
|
/**
|
|
* Get oplog entries, optionally filtered.
|
|
*/
|
|
async getOpLog(params: {
|
|
sinceSeq?: number;
|
|
limit?: number;
|
|
db?: string;
|
|
collection?: string;
|
|
} = {}): Promise<IOpLogResult> {
|
|
return this.bridge.getOpLog(params);
|
|
}
|
|
|
|
/**
|
|
* Get aggregate oplog statistics.
|
|
*/
|
|
async getOpLogStats(): Promise<IOpLogStats> {
|
|
return this.bridge.getOpLogStats();
|
|
}
|
|
|
|
/**
|
|
* Revert database state to a specific oplog sequence number.
|
|
* Use dryRun=true to preview which entries would be reverted.
|
|
*/
|
|
async revertToSeq(seq: number, dryRun = false): Promise<IRevertResult> {
|
|
return this.bridge.revertToSeq(seq, dryRun);
|
|
}
|
|
|
|
/**
|
|
* List all collections across all databases, with document counts.
|
|
*/
|
|
async getCollections(db?: string): Promise<ICollectionInfo[]> {
|
|
return this.bridge.getCollections(db);
|
|
}
|
|
|
|
/**
|
|
* Get documents from a collection with pagination.
|
|
*/
|
|
async getDocuments(
|
|
db: string,
|
|
collection: string,
|
|
limit = 50,
|
|
skip = 0,
|
|
): Promise<IDocumentsResult> {
|
|
return this.bridge.getDocuments(db, collection, limit, skip);
|
|
}
|
|
|
|
/**
|
|
* Get server metrics including database/collection counts and oplog info.
|
|
*/
|
|
async getMetrics(): Promise<ISmartDbMetrics> {
|
|
return this.bridge.getMetrics();
|
|
}
|
|
}
|