BREAKING CHANGE(localtsmdb): add Unix socket support and change LocalTsmDb API to return connection info instead of a MongoClient
This commit is contained in:
@@ -1,98 +1,106 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import * as crypto from 'crypto';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import { TsmdbServer } from '../ts_tsmdb/index.js';
|
||||
import type { MongoClient } from 'mongodb';
|
||||
|
||||
/**
|
||||
* Connection information returned by LocalTsmDb.start()
|
||||
*/
|
||||
export interface ILocalTsmDbConnectionInfo {
|
||||
/** The Unix socket file path */
|
||||
socketPath: string;
|
||||
/** MongoDB connection URI ready for MongoClient */
|
||||
connectionUri: string;
|
||||
}
|
||||
|
||||
export interface ILocalTsmDbOptions {
|
||||
/** Required: where to store data */
|
||||
folderPath: string;
|
||||
port?: number;
|
||||
host?: string;
|
||||
/** Optional: custom socket path (default: auto-generated in /tmp) */
|
||||
socketPath?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* LocalTsmDb - Convenience class for local MongoDB-compatible database
|
||||
* LocalTsmDb - Lightweight local MongoDB-compatible database using Unix sockets
|
||||
*
|
||||
* This class wraps TsmdbServer and provides a simple interface for
|
||||
* starting a local file-based MongoDB-compatible server and connecting to it.
|
||||
* starting a local file-based MongoDB-compatible server. Returns connection
|
||||
* info that you can use with your own MongoDB driver instance.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { LocalTsmDb } from '@push.rocks/smartmongo';
|
||||
* import { MongoClient } from 'mongodb';
|
||||
*
|
||||
* const db = new LocalTsmDb({ folderPath: './data' });
|
||||
* const client = await db.start();
|
||||
* const { connectionUri } = await db.start();
|
||||
*
|
||||
* // Connect with your own MongoDB client
|
||||
* const client = new MongoClient(connectionUri, { directConnection: true });
|
||||
* await client.connect();
|
||||
*
|
||||
* // Use the MongoDB client
|
||||
* const collection = client.db('mydb').collection('users');
|
||||
* await collection.insertOne({ name: 'Alice' });
|
||||
*
|
||||
* // When done
|
||||
* await client.close();
|
||||
* await db.stop();
|
||||
* ```
|
||||
*/
|
||||
export class LocalTsmDb {
|
||||
private options: ILocalTsmDbOptions;
|
||||
private server: TsmdbServer | null = null;
|
||||
private client: MongoClient | null = null;
|
||||
private generatedSocketPath: string | null = null;
|
||||
|
||||
constructor(options: ILocalTsmDbOptions) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an available port starting from the given port
|
||||
* Generate a unique socket path in /tmp
|
||||
*/
|
||||
private async findAvailablePort(startPort = 27017): Promise<number> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const server = plugins.net.createServer();
|
||||
server.listen(startPort, '127.0.0.1', () => {
|
||||
const addr = server.address();
|
||||
const port = typeof addr === 'object' && addr ? addr.port : startPort;
|
||||
server.close(() => resolve(port));
|
||||
});
|
||||
server.on('error', () => {
|
||||
this.findAvailablePort(startPort + 1).then(resolve).catch(reject);
|
||||
});
|
||||
});
|
||||
private generateSocketPath(): string {
|
||||
const randomId = crypto.randomBytes(8).toString('hex');
|
||||
return path.join(os.tmpdir(), `smartmongo-${randomId}.sock`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the local TsmDB server and return a connected MongoDB client
|
||||
* Start the local TsmDB server and return connection info
|
||||
*/
|
||||
async start(): Promise<MongoClient> {
|
||||
if (this.server && this.client) {
|
||||
async start(): Promise<ILocalTsmDbConnectionInfo> {
|
||||
if (this.server) {
|
||||
throw new Error('LocalTsmDb is already running');
|
||||
}
|
||||
|
||||
const port = this.options.port ?? await this.findAvailablePort();
|
||||
const host = this.options.host ?? '127.0.0.1';
|
||||
// Use provided socket path or generate one
|
||||
this.generatedSocketPath = this.options.socketPath ?? this.generateSocketPath();
|
||||
|
||||
this.server = new TsmdbServer({
|
||||
port,
|
||||
host,
|
||||
socketPath: this.generatedSocketPath,
|
||||
storage: 'file',
|
||||
storagePath: this.options.folderPath,
|
||||
});
|
||||
await this.server.start();
|
||||
|
||||
// Dynamically import mongodb to avoid requiring it as a hard dependency
|
||||
const mongodb = await import('mongodb');
|
||||
this.client = new mongodb.MongoClient(this.server.getConnectionUri(), {
|
||||
directConnection: true,
|
||||
serverSelectionTimeoutMS: 5000,
|
||||
});
|
||||
await this.client.connect();
|
||||
|
||||
return this.client;
|
||||
return {
|
||||
socketPath: this.generatedSocketPath,
|
||||
connectionUri: this.server.getConnectionUri(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the MongoDB client (throws if not started)
|
||||
* Get connection info (throws if not started)
|
||||
*/
|
||||
getClient(): MongoClient {
|
||||
if (!this.client) {
|
||||
getConnectionInfo(): ILocalTsmDbConnectionInfo {
|
||||
if (!this.server || !this.generatedSocketPath) {
|
||||
throw new Error('LocalTsmDb is not running. Call start() first.');
|
||||
}
|
||||
return this.client;
|
||||
return {
|
||||
socketPath: this.generatedSocketPath,
|
||||
connectionUri: this.server.getConnectionUri(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,16 +131,13 @@ export class LocalTsmDb {
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the local TsmDB server and close the client connection
|
||||
* Stop the local TsmDB server
|
||||
*/
|
||||
async stop(): Promise<void> {
|
||||
if (this.client) {
|
||||
await this.client.close();
|
||||
this.client = null;
|
||||
}
|
||||
if (this.server) {
|
||||
await this.server.stop();
|
||||
this.server = null;
|
||||
this.generatedSocketPath = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user