2026-02-01 23:33:35 +00:00
|
|
|
import * as plugins from './plugins.js';
|
2026-02-03 16:42:49 +00:00
|
|
|
import * as crypto from 'crypto';
|
|
|
|
|
import * as path from 'path';
|
|
|
|
|
import * as os from 'os';
|
2026-02-01 23:33:35 +00:00
|
|
|
import { TsmdbServer } from '../ts_tsmdb/index.js';
|
2026-02-03 16:42:49 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Connection information returned by LocalTsmDb.start()
|
|
|
|
|
*/
|
|
|
|
|
export interface ILocalTsmDbConnectionInfo {
|
|
|
|
|
/** The Unix socket file path */
|
|
|
|
|
socketPath: string;
|
|
|
|
|
/** MongoDB connection URI ready for MongoClient */
|
|
|
|
|
connectionUri: string;
|
|
|
|
|
}
|
2026-02-01 23:33:35 +00:00
|
|
|
|
|
|
|
|
export interface ILocalTsmDbOptions {
|
2026-02-03 16:42:49 +00:00
|
|
|
/** Required: where to store data */
|
2026-02-01 23:33:35 +00:00
|
|
|
folderPath: string;
|
2026-02-03 16:42:49 +00:00
|
|
|
/** Optional: custom socket path (default: auto-generated in /tmp) */
|
|
|
|
|
socketPath?: string;
|
2026-02-01 23:33:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-02-03 16:42:49 +00:00
|
|
|
* LocalTsmDb - Lightweight local MongoDB-compatible database using Unix sockets
|
2026-02-01 23:33:35 +00:00
|
|
|
*
|
|
|
|
|
* This class wraps TsmdbServer and provides a simple interface for
|
2026-02-03 16:42:49 +00:00
|
|
|
* starting a local file-based MongoDB-compatible server. Returns connection
|
|
|
|
|
* info that you can use with your own MongoDB driver instance.
|
2026-02-01 23:33:35 +00:00
|
|
|
*
|
|
|
|
|
* @example
|
|
|
|
|
* ```typescript
|
|
|
|
|
* import { LocalTsmDb } from '@push.rocks/smartmongo';
|
2026-02-03 16:42:49 +00:00
|
|
|
* import { MongoClient } from 'mongodb';
|
2026-02-01 23:33:35 +00:00
|
|
|
*
|
|
|
|
|
* const db = new LocalTsmDb({ folderPath: './data' });
|
2026-02-03 16:42:49 +00:00
|
|
|
* const { connectionUri } = await db.start();
|
|
|
|
|
*
|
|
|
|
|
* // Connect with your own MongoDB client
|
|
|
|
|
* const client = new MongoClient(connectionUri, { directConnection: true });
|
|
|
|
|
* await client.connect();
|
2026-02-01 23:33:35 +00:00
|
|
|
*
|
|
|
|
|
* // Use the MongoDB client
|
|
|
|
|
* const collection = client.db('mydb').collection('users');
|
|
|
|
|
* await collection.insertOne({ name: 'Alice' });
|
|
|
|
|
*
|
|
|
|
|
* // When done
|
2026-02-03 16:42:49 +00:00
|
|
|
* await client.close();
|
2026-02-01 23:33:35 +00:00
|
|
|
* await db.stop();
|
|
|
|
|
* ```
|
|
|
|
|
*/
|
|
|
|
|
export class LocalTsmDb {
|
|
|
|
|
private options: ILocalTsmDbOptions;
|
|
|
|
|
private server: TsmdbServer | null = null;
|
2026-02-03 16:42:49 +00:00
|
|
|
private generatedSocketPath: string | null = null;
|
2026-02-01 23:33:35 +00:00
|
|
|
|
|
|
|
|
constructor(options: ILocalTsmDbOptions) {
|
|
|
|
|
this.options = options;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-02-03 16:42:49 +00:00
|
|
|
* Generate a unique socket path in /tmp
|
2026-02-01 23:33:35 +00:00
|
|
|
*/
|
2026-02-03 16:42:49 +00:00
|
|
|
private generateSocketPath(): string {
|
|
|
|
|
const randomId = crypto.randomBytes(8).toString('hex');
|
|
|
|
|
return path.join(os.tmpdir(), `smartmongo-${randomId}.sock`);
|
2026-02-01 23:33:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-02-03 16:42:49 +00:00
|
|
|
* Start the local TsmDB server and return connection info
|
2026-02-01 23:33:35 +00:00
|
|
|
*/
|
2026-02-03 16:42:49 +00:00
|
|
|
async start(): Promise<ILocalTsmDbConnectionInfo> {
|
|
|
|
|
if (this.server) {
|
2026-02-01 23:33:35 +00:00
|
|
|
throw new Error('LocalTsmDb is already running');
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-03 16:42:49 +00:00
|
|
|
// Use provided socket path or generate one
|
|
|
|
|
this.generatedSocketPath = this.options.socketPath ?? this.generateSocketPath();
|
2026-02-01 23:33:35 +00:00
|
|
|
|
|
|
|
|
this.server = new TsmdbServer({
|
2026-02-03 16:42:49 +00:00
|
|
|
socketPath: this.generatedSocketPath,
|
2026-02-01 23:33:35 +00:00
|
|
|
storage: 'file',
|
|
|
|
|
storagePath: this.options.folderPath,
|
|
|
|
|
});
|
|
|
|
|
await this.server.start();
|
|
|
|
|
|
2026-02-03 16:42:49 +00:00
|
|
|
return {
|
|
|
|
|
socketPath: this.generatedSocketPath,
|
|
|
|
|
connectionUri: this.server.getConnectionUri(),
|
|
|
|
|
};
|
2026-02-01 23:33:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-02-03 16:42:49 +00:00
|
|
|
* Get connection info (throws if not started)
|
2026-02-01 23:33:35 +00:00
|
|
|
*/
|
2026-02-03 16:42:49 +00:00
|
|
|
getConnectionInfo(): ILocalTsmDbConnectionInfo {
|
|
|
|
|
if (!this.server || !this.generatedSocketPath) {
|
2026-02-01 23:33:35 +00:00
|
|
|
throw new Error('LocalTsmDb is not running. Call start() first.');
|
|
|
|
|
}
|
2026-02-03 16:42:49 +00:00
|
|
|
return {
|
|
|
|
|
socketPath: this.generatedSocketPath,
|
|
|
|
|
connectionUri: this.server.getConnectionUri(),
|
|
|
|
|
};
|
2026-02-01 23:33:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the underlying TsmdbServer instance (throws if not started)
|
|
|
|
|
*/
|
|
|
|
|
getServer(): TsmdbServer {
|
|
|
|
|
if (!this.server) {
|
|
|
|
|
throw new Error('LocalTsmDb is not running. Call start() first.');
|
|
|
|
|
}
|
|
|
|
|
return this.server;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the connection URI
|
|
|
|
|
*/
|
|
|
|
|
getConnectionUri(): string {
|
|
|
|
|
if (!this.server) {
|
|
|
|
|
throw new Error('LocalTsmDb is not running. Call start() first.');
|
|
|
|
|
}
|
|
|
|
|
return this.server.getConnectionUri();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check if the server is running
|
|
|
|
|
*/
|
|
|
|
|
get running(): boolean {
|
|
|
|
|
return this.server !== null && this.server.running;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-02-03 16:42:49 +00:00
|
|
|
* Stop the local TsmDB server
|
2026-02-01 23:33:35 +00:00
|
|
|
*/
|
|
|
|
|
async stop(): Promise<void> {
|
|
|
|
|
if (this.server) {
|
|
|
|
|
await this.server.stop();
|
|
|
|
|
this.server = null;
|
2026-02-03 16:42:49 +00:00
|
|
|
this.generatedSocketPath = null;
|
2026-02-01 23:33:35 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|