Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c65c285296 | |||
| b72174ca7b | |||
| 1ff4d219af | |||
| 09f60de56f |
20
changelog.md
20
changelog.md
@@ -1,5 +1,25 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2026-02-03 - 5.1.0 - feat(localtsmdb)
|
||||||
|
export ILocalTsmDbConnectionInfo and expand LocalTsmDb/TsmDB documentation and examples
|
||||||
|
|
||||||
|
- Exported new type ILocalTsmDbConnectionInfo from ts_local (ts/index.ts)
|
||||||
|
- Added LocalTsmDb configuration example, methods table, and ConnectionInfo interface to README
|
||||||
|
- Documented Unix socket vs TCP connection modes and updated usage examples (TCP and socket examples)
|
||||||
|
- Expanded TsmDB docs: additional server properties, aggregation stages, regex examples, index operations, database ops, checksums, and wire protocol commands
|
||||||
|
- Updated architecture notes to include Unix socket support and new engine components (QueryEngine, UpdateEngine, AggregationEngine)
|
||||||
|
|
||||||
|
## 2026-02-03 - 5.0.0 - BREAKING CHANGE(localtsmdb)
|
||||||
|
add Unix socket support and change LocalTsmDb API to return connection info instead of a MongoClient
|
||||||
|
|
||||||
|
- LocalTsmDb.start() now returns ILocalTsmDbConnectionInfo { socketPath, connectionUri } instead of a connected MongoClient
|
||||||
|
- Removed internal MongoClient management: consumers must create/connect/close their own MongoClient using the returned connectionUri (close client before calling db.stop())
|
||||||
|
- Added ILocalTsmDbConnectionInfo type and getConnectionInfo() (replaces getClient())
|
||||||
|
- TsmdbServer: added socketPath option to listen on Unix sockets, cleans up stale socket files on start/stop, and encodes socket paths in getConnectionUri()
|
||||||
|
- LocalTsmDb can auto-generate socket paths in the OS temp dir; LocalTsmDb no longer depends on the mongodb package internally (lightweight Unix socket wrapper)
|
||||||
|
- Updated docs and tests to use MongoClient externally and to demonstrate socketPath/connectionUri workflow
|
||||||
|
- ts_local plugins no longer export net (net usage moved to server implementation)
|
||||||
|
|
||||||
## 2026-02-03 - 4.3.0 - feat(docs)
|
## 2026-02-03 - 4.3.0 - feat(docs)
|
||||||
add LocalTsmDb documentation and examples; update README code samples and imports; correct examples and variable names; update package author
|
add LocalTsmDb documentation and examples; update README code samples and imports; correct examples and variable names; update package author
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@push.rocks/smartmongo",
|
"name": "@push.rocks/smartmongo",
|
||||||
"version": "4.3.0",
|
"version": "5.1.0",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "A module for creating and managing a local MongoDB instance for testing purposes.",
|
"description": "A module for creating and managing a local MongoDB instance for testing purposes.",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
193
readme.md
193
readme.md
@@ -21,27 +21,33 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
|
|||||||
| Feature | SmartMongo | TsmDB | LocalTsmDb |
|
| Feature | SmartMongo | TsmDB | LocalTsmDb |
|
||||||
|---------|------------|-------|------------|
|
|---------|------------|-------|------------|
|
||||||
| **Type** | Real MongoDB (memory server) | Wire protocol server | Zero-config local DB |
|
| **Type** | Real MongoDB (memory server) | Wire protocol server | Zero-config local DB |
|
||||||
| **Speed** | ~2-5s startup | ⚡ Instant (~5ms) | ⚡ Instant + auto-connect |
|
| **Speed** | ~2-5s startup | ⚡ Instant (~5ms) | ⚡ Instant (Unix socket) |
|
||||||
| **Compatibility** | 100% MongoDB | MongoDB driver compatible | MongoDB driver compatible |
|
| **Compatibility** | 100% MongoDB | MongoDB driver compatible | MongoDB driver compatible |
|
||||||
| **Dependencies** | Downloads MongoDB binary | Zero external deps | Zero external deps |
|
| **Dependencies** | Downloads MongoDB binary | Zero external deps | Zero external deps (no MongoDB driver!) |
|
||||||
|
| **Connection** | TCP | TCP or Unix socket | Unix socket (default) |
|
||||||
| **Replication** | ✅ Full replica set | Single node | Single node |
|
| **Replication** | ✅ Full replica set | Single node | Single node |
|
||||||
| **Persistence** | Dump to directory | Memory or file | File-based (automatic) |
|
| **Persistence** | Dump to directory | Memory or file | File-based (automatic) |
|
||||||
| **Use Case** | Integration testing | Unit testing, CI/CD | Quick prototyping, local dev |
|
| **Use Case** | Integration testing | Unit testing, CI/CD | Quick prototyping, local dev |
|
||||||
|
|
||||||
## 🚀 Quick Start
|
## 🚀 Quick Start
|
||||||
|
|
||||||
### Option 1: LocalTsmDb (Zero-Config Local Database) ⭐ NEW
|
### Option 1: LocalTsmDb (Zero-Config Local Database) ⭐
|
||||||
|
|
||||||
The easiest way to get started — just point it at a folder and you have a persistent MongoDB-compatible database with automatic port discovery!
|
The easiest way to get started — just point it at a folder and you have a persistent MongoDB-compatible database using Unix sockets. No port conflicts, no MongoDB driver dependency in LocalTsmDb!
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { LocalTsmDb } from '@push.rocks/smartmongo';
|
import { LocalTsmDb } from '@push.rocks/smartmongo';
|
||||||
|
import { MongoClient } from 'mongodb';
|
||||||
|
|
||||||
// Create a local database backed by files
|
// Create a local database backed by files
|
||||||
const db = new LocalTsmDb({ folderPath: './my-data' });
|
const db = new LocalTsmDb({ folderPath: './my-data' });
|
||||||
|
|
||||||
// Start and get a connected MongoDB client
|
// Start and get connection info (Unix socket path + connection URI)
|
||||||
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 exactly like MongoDB
|
// Use exactly like MongoDB
|
||||||
const users = client.db('myapp').collection('users');
|
const users = client.db('myapp').collection('users');
|
||||||
@@ -51,11 +57,14 @@ const user = await users.findOne({ name: 'Alice' });
|
|||||||
console.log(user); // { _id: ObjectId(...), name: 'Alice', email: 'alice@example.com' }
|
console.log(user); // { _id: ObjectId(...), name: 'Alice', email: 'alice@example.com' }
|
||||||
|
|
||||||
// Data persists to disk automatically!
|
// Data persists to disk automatically!
|
||||||
|
await client.close();
|
||||||
await db.stop();
|
await db.stop();
|
||||||
|
|
||||||
// Later... data is still there
|
// Later... data is still there
|
||||||
const db2 = new LocalTsmDb({ folderPath: './my-data' });
|
const db2 = new LocalTsmDb({ folderPath: './my-data' });
|
||||||
const client2 = await db2.start();
|
const { connectionUri: uri2 } = await db2.start();
|
||||||
|
const client2 = new MongoClient(uri2, { directConnection: true });
|
||||||
|
await client2.connect();
|
||||||
const savedUser = await client2.db('myapp').collection('users').findOne({ name: 'Alice' });
|
const savedUser = await client2.db('myapp').collection('users').findOne({ name: 'Alice' });
|
||||||
// savedUser exists!
|
// savedUser exists!
|
||||||
```
|
```
|
||||||
@@ -68,7 +77,7 @@ A lightweight, pure TypeScript MongoDB-compatible server — use the official `m
|
|||||||
import { tsmdb } from '@push.rocks/smartmongo';
|
import { tsmdb } from '@push.rocks/smartmongo';
|
||||||
import { MongoClient } from 'mongodb';
|
import { MongoClient } from 'mongodb';
|
||||||
|
|
||||||
// Start TsmDB server
|
// Start TsmDB server (TCP mode)
|
||||||
const server = new tsmdb.TsmdbServer({ port: 27017 });
|
const server = new tsmdb.TsmdbServer({ port: 27017 });
|
||||||
await server.start();
|
await server.start();
|
||||||
|
|
||||||
@@ -109,44 +118,91 @@ console.log(descriptor.mongoDbUrl); // mongodb://127.0.0.1:xxxxx/...
|
|||||||
await mongo.stop();
|
await mongo.stop();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 📖 LocalTsmDb API
|
## 📖 LocalTsmDb API
|
||||||
|
|
||||||
The simplest option for local development and prototyping — zero config, auto port discovery, and automatic persistence.
|
The simplest option for local development and prototyping — lightweight, Unix socket-based, and automatic persistence.
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { LocalTsmDb } from '@push.rocks/smartmongo';
|
||||||
|
import type { ILocalTsmDbOptions, ILocalTsmDbConnectionInfo } from '@push.rocks/smartmongo';
|
||||||
|
|
||||||
|
const options: ILocalTsmDbOptions = {
|
||||||
|
folderPath: './data', // Required: where to store data
|
||||||
|
socketPath: '/tmp/my.sock', // Optional: custom socket path (default: auto-generated)
|
||||||
|
};
|
||||||
|
|
||||||
|
const db = new LocalTsmDb(options);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Methods
|
||||||
|
|
||||||
|
| Method | Returns | Description |
|
||||||
|
|--------|---------|-------------|
|
||||||
|
| `start()` | `Promise<ILocalTsmDbConnectionInfo>` | Starts the server and returns connection info |
|
||||||
|
| `stop()` | `Promise<void>` | Stops the server and cleans up the socket |
|
||||||
|
| `getConnectionInfo()` | `ILocalTsmDbConnectionInfo` | Returns current connection info |
|
||||||
|
| `getConnectionUri()` | `string` | Returns the MongoDB connection URI |
|
||||||
|
| `getServer()` | `TsmdbServer` | Returns the underlying TsmDB server instance |
|
||||||
|
| `running` | `boolean` | Property indicating if the server is running |
|
||||||
|
|
||||||
|
### Connection Info
|
||||||
|
|
||||||
|
The `start()` method returns an `ILocalTsmDbConnectionInfo` object:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface ILocalTsmDbConnectionInfo {
|
||||||
|
socketPath: string; // The Unix socket file path, e.g., /tmp/smartmongo-abc123.sock
|
||||||
|
connectionUri: string; // MongoDB URI, e.g., mongodb://%2Ftmp%2Fsmartmongo-abc123.sock
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Basic Usage
|
### Basic Usage
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { LocalTsmDb } from '@push.rocks/smartmongo';
|
import { LocalTsmDb } from '@push.rocks/smartmongo';
|
||||||
|
import { MongoClient } from 'mongodb';
|
||||||
|
|
||||||
const db = new LocalTsmDb({
|
const db = new LocalTsmDb({ folderPath: './data' });
|
||||||
folderPath: './data', // Required: where to store data
|
|
||||||
port: 27017, // Optional: defaults to auto-discovery
|
|
||||||
host: '127.0.0.1', // Optional: bind address
|
|
||||||
});
|
|
||||||
|
|
||||||
// Start and get connected client
|
// Start and get connection info
|
||||||
const client = await db.start();
|
const { socketPath, connectionUri } = await db.start();
|
||||||
|
console.log(socketPath); // /tmp/smartmongo-abc123.sock (auto-generated)
|
||||||
|
console.log(connectionUri); // mongodb://%2Ftmp%2Fsmartmongo-abc123.sock
|
||||||
|
|
||||||
// Access the underlying server if needed
|
// Connect with your own MongoDB client
|
||||||
const server = db.getServer();
|
const client = new MongoClient(connectionUri, { directConnection: true });
|
||||||
const uri = db.getConnectionUri();
|
await client.connect();
|
||||||
|
|
||||||
|
// Use the client
|
||||||
|
const users = client.db('mydb').collection('users');
|
||||||
|
await users.insertOne({ name: 'Alice' });
|
||||||
|
|
||||||
// Check status
|
// Check status
|
||||||
console.log(db.running); // true
|
console.log(db.running); // true
|
||||||
|
|
||||||
// Stop when done
|
// Stop when done (close your client first!)
|
||||||
|
await client.close();
|
||||||
await db.stop();
|
await db.stop();
|
||||||
```
|
```
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
- 🔍 **Auto Port Discovery** — Automatically finds an available port if 27017 is in use
|
- 🔌 **Unix Sockets** — No port conflicts, faster IPC than TCP
|
||||||
- 💾 **Automatic Persistence** — Data saved to files, survives restarts
|
- 💾 **Automatic Persistence** — Data saved to files, survives restarts
|
||||||
- 🔌 **Pre-connected Client** — `start()` returns a ready-to-use MongoDB client
|
- 🪶 **Lightweight** — No MongoDB driver dependency in LocalTsmDb itself
|
||||||
- 🎯 **Zero Config** — Just specify a folder path and you're good to go
|
- 🎯 **Zero Config** — Just specify a folder path and you're good to go
|
||||||
|
- 🔗 **Connection URI** — Ready-to-use URI for your own MongoClient
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 📖 SmartMongo API
|
## 📖 SmartMongo API
|
||||||
|
|
||||||
|
Full MongoDB replica set in memory using `mongodb-memory-server`.
|
||||||
|
|
||||||
### Creating an Instance
|
### Creating an Instance
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
@@ -182,13 +238,18 @@ await mongo.stopAndDumpToDir('./test-data');
|
|||||||
await mongo.stopAndDumpToDir('./test-data', (doc) => `${doc.collection}-${doc._id}.bson`);
|
await mongo.stopAndDumpToDir('./test-data', (doc) => `${doc.collection}-${doc._id}.bson`);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 🔧 TsmDB API
|
## 🔧 TsmDB API
|
||||||
|
|
||||||
|
Pure TypeScript MongoDB wire protocol server. No external dependencies.
|
||||||
|
|
||||||
### Server Configuration
|
### Server Configuration
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { tsmdb } from '@push.rocks/smartmongo';
|
import { tsmdb } from '@push.rocks/smartmongo';
|
||||||
|
|
||||||
|
// TCP mode (default)
|
||||||
const server = new tsmdb.TsmdbServer({
|
const server = new tsmdb.TsmdbServer({
|
||||||
port: 27017, // Default MongoDB port
|
port: 27017, // Default MongoDB port
|
||||||
host: '127.0.0.1', // Bind address
|
host: '127.0.0.1', // Bind address
|
||||||
@@ -196,13 +257,25 @@ const server = new tsmdb.TsmdbServer({
|
|||||||
storagePath: './data', // For file-based storage
|
storagePath: './data', // For file-based storage
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Unix socket mode (no port conflicts!)
|
||||||
|
const server = new tsmdb.TsmdbServer({
|
||||||
|
socketPath: '/tmp/my-tsmdb.sock',
|
||||||
|
storage: 'file',
|
||||||
|
storagePath: './data',
|
||||||
|
});
|
||||||
|
|
||||||
await server.start();
|
await server.start();
|
||||||
console.log(server.getConnectionUri()); // mongodb://127.0.0.1:27017
|
console.log(server.getConnectionUri());
|
||||||
|
// TCP: mongodb://127.0.0.1:27017
|
||||||
|
// Socket: mongodb://%2Ftmp%2Fmy-tsmdb.sock
|
||||||
|
|
||||||
// Server properties
|
// Server properties
|
||||||
console.log(server.running); // true
|
console.log(server.running); // true
|
||||||
console.log(server.getUptime()); // seconds
|
console.log(server.port); // 27017 (TCP mode)
|
||||||
console.log(server.getConnectionCount()); // active connections
|
console.log(server.host); // '127.0.0.1' (TCP mode)
|
||||||
|
console.log(server.socketPath); // '/tmp/my-tsmdb.sock' (socket mode)
|
||||||
|
console.log(server.getUptime()); // seconds since start
|
||||||
|
console.log(server.getConnectionCount()); // active client connections
|
||||||
|
|
||||||
await server.stop();
|
await server.stop();
|
||||||
```
|
```
|
||||||
@@ -212,6 +285,7 @@ await server.stop();
|
|||||||
TsmDB supports the core MongoDB operations via the wire protocol:
|
TsmDB supports the core MongoDB operations via the wire protocol:
|
||||||
|
|
||||||
#### 🔹 CRUD Operations
|
#### 🔹 CRUD Operations
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// Insert
|
// Insert
|
||||||
await collection.insertOne({ name: 'Bob' });
|
await collection.insertOne({ name: 'Bob' });
|
||||||
@@ -241,6 +315,7 @@ const result = await collection.findOneAndUpdate(
|
|||||||
```
|
```
|
||||||
|
|
||||||
#### 🔹 Query Operators
|
#### 🔹 Query Operators
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// Comparison
|
// Comparison
|
||||||
{ age: { $eq: 25 } }
|
{ age: { $eq: 25 } }
|
||||||
@@ -263,9 +338,14 @@ const result = await collection.findOneAndUpdate(
|
|||||||
{ tags: { $all: ['mongodb', 'database'] } }
|
{ tags: { $all: ['mongodb', 'database'] } }
|
||||||
{ scores: { $elemMatch: { $gte: 80, $lt: 90 } } }
|
{ scores: { $elemMatch: { $gte: 80, $lt: 90 } } }
|
||||||
{ tags: { $size: 3 } }
|
{ tags: { $size: 3 } }
|
||||||
|
|
||||||
|
// Regex
|
||||||
|
{ name: { $regex: /^Al/i } }
|
||||||
|
{ email: { $regex: '@example\\.com$' } }
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 🔹 Update Operators
|
#### 🔹 Update Operators
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
{ $set: { name: 'New Name' } }
|
{ $set: { name: 'New Name' } }
|
||||||
{ $unset: { tempField: '' } }
|
{ $unset: { tempField: '' } }
|
||||||
@@ -278,9 +358,12 @@ const result = await collection.findOneAndUpdate(
|
|||||||
{ $addToSet: { tags: 'unique-tag' } }
|
{ $addToSet: { tags: 'unique-tag' } }
|
||||||
{ $pop: { queue: 1 } } // Remove last
|
{ $pop: { queue: 1 } } // Remove last
|
||||||
{ $pop: { queue: -1 } } // Remove first
|
{ $pop: { queue: -1 } } // Remove first
|
||||||
|
{ $rename: { oldField: 'newField' } }
|
||||||
|
{ $currentDate: { lastModified: true } }
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 🔹 Aggregation Pipeline
|
#### 🔹 Aggregation Pipeline
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
const results = await collection.aggregate([
|
const results = await collection.aggregate([
|
||||||
{ $match: { status: 'active' } },
|
{ $match: { status: 'active' } },
|
||||||
@@ -291,17 +374,28 @@ const results = await collection.aggregate([
|
|||||||
]).toArray();
|
]).toArray();
|
||||||
```
|
```
|
||||||
|
|
||||||
Supported stages: `$match`, `$project`, `$group`, `$sort`, `$limit`, `$skip`, `$unwind`, `$lookup`, `$addFields`, `$count`, `$facet`, and more.
|
**Supported stages:** `$match`, `$project`, `$group`, `$sort`, `$limit`, `$skip`, `$unwind`, `$lookup`, `$addFields`, `$count`, `$facet`, `$replaceRoot`, `$set`, `$unset`, and more.
|
||||||
|
|
||||||
|
**Supported group accumulators:** `$sum`, `$avg`, `$min`, `$max`, `$first`, `$last`, `$push`, `$addToSet`, `$count`.
|
||||||
|
|
||||||
#### 🔹 Index Operations
|
#### 🔹 Index Operations
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
|
// Create indexes
|
||||||
await collection.createIndex({ email: 1 }, { unique: true });
|
await collection.createIndex({ email: 1 }, { unique: true });
|
||||||
await collection.createIndex({ name: 1, age: -1 });
|
await collection.createIndex({ name: 1, age: -1 });
|
||||||
|
await collection.createIndex({ location: '2dsphere' }); // Geospatial
|
||||||
|
|
||||||
|
// List indexes
|
||||||
const indexes = await collection.listIndexes().toArray();
|
const indexes = await collection.listIndexes().toArray();
|
||||||
|
|
||||||
|
// Drop indexes
|
||||||
await collection.dropIndex('email_1');
|
await collection.dropIndex('email_1');
|
||||||
|
await collection.dropIndexes(); // Drop all except _id
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 🔹 Database Operations
|
#### 🔹 Database Operations
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// List databases
|
// List databases
|
||||||
const dbs = await client.db().admin().listDatabases();
|
const dbs = await client.db().admin().listDatabases();
|
||||||
@@ -315,9 +409,13 @@ await db.dropCollection('oldcollection');
|
|||||||
|
|
||||||
// Drop database
|
// Drop database
|
||||||
await db.dropDatabase();
|
await db.dropDatabase();
|
||||||
|
|
||||||
|
// Database stats
|
||||||
|
const stats = await db.stats();
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 🔹 Count & Distinct
|
#### 🔹 Count & Distinct
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// Count documents
|
// Count documents
|
||||||
const total = await collection.countDocuments({});
|
const total = await collection.countDocuments({});
|
||||||
@@ -330,6 +428,7 @@ const activeDepts = await collection.distinct('department', { status: 'active' }
|
|||||||
```
|
```
|
||||||
|
|
||||||
#### 🔹 Bulk Operations
|
#### 🔹 Bulk Operations
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
const result = await collection.bulkWrite([
|
const result = await collection.bulkWrite([
|
||||||
{ insertOne: { document: { name: 'Bulk1' } } },
|
{ insertOne: { document: { name: 'Bulk1' } } },
|
||||||
@@ -358,22 +457,22 @@ const server = new tsmdb.TsmdbServer({
|
|||||||
persistIntervalMs: 30000 // Save every 30 seconds
|
persistIntervalMs: 30000 // Save every 30 seconds
|
||||||
});
|
});
|
||||||
|
|
||||||
// File-based - persistent storage with optional checksums
|
// File-based - persistent storage with checksums
|
||||||
import { tsmdb } from '@push.rocks/smartmongo';
|
|
||||||
|
|
||||||
const server = new tsmdb.TsmdbServer({
|
const server = new tsmdb.TsmdbServer({
|
||||||
storage: 'file',
|
storage: 'file',
|
||||||
storagePath: './data/tsmdb'
|
storagePath: './data/tsmdb'
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## ⚡ Performance & Reliability Features
|
## ⚡ Performance & Reliability Features
|
||||||
|
|
||||||
TsmDB includes enterprise-grade features for robustness:
|
TsmDB includes enterprise-grade features for robustness:
|
||||||
|
|
||||||
### 🔍 Index-Accelerated Queries
|
### 🔍 Index-Accelerated Queries
|
||||||
|
|
||||||
Indexes are automatically used to accelerate queries. Instead of scanning all documents, TsmDB uses:
|
Indexes are automatically used to accelerate queries:
|
||||||
|
|
||||||
- **Hash indexes** for equality queries (`$eq`, `$in`)
|
- **Hash indexes** for equality queries (`$eq`, `$in`)
|
||||||
- **B-tree indexes** for range queries (`$gt`, `$gte`, `$lt`, `$lte`)
|
- **B-tree indexes** for range queries (`$gt`, `$gte`, `$lt`, `$lte`)
|
||||||
@@ -463,22 +562,26 @@ import { tsmdb } from '@push.rocks/smartmongo';
|
|||||||
|
|
||||||
// Checksums are used internally for WAL and data integrity
|
// Checksums are used internally for WAL and data integrity
|
||||||
// Documents are checksummed on write, verified on read
|
// Documents are checksummed on write, verified on read
|
||||||
|
const checksum = tsmdb.calculateDocumentChecksum(doc);
|
||||||
|
const isValid = tsmdb.verifyChecksum(docWithChecksum);
|
||||||
```
|
```
|
||||||
|
|
||||||
### 📋 Supported Wire Protocol Commands
|
### 📋 Supported Wire Protocol Commands
|
||||||
|
|
||||||
| Category | Commands |
|
| Category | Commands |
|
||||||
|----------|----------|
|
|----------|----------|
|
||||||
| **Handshake** | `hello`, `isMaster` |
|
| **Handshake** | `hello`, `isMaster`, `ismaster` |
|
||||||
| **CRUD** | `find`, `insert`, `update`, `delete`, `findAndModify`, `getMore`, `killCursors` |
|
| **CRUD** | `find`, `insert`, `update`, `delete`, `findAndModify`, `getMore`, `killCursors` |
|
||||||
| **Aggregation** | `aggregate`, `count`, `distinct` |
|
| **Aggregation** | `aggregate`, `count`, `distinct` |
|
||||||
| **Indexes** | `createIndexes`, `dropIndexes`, `listIndexes` |
|
| **Indexes** | `createIndexes`, `dropIndexes`, `listIndexes` |
|
||||||
| **Transactions** | `startTransaction`, `commitTransaction`, `abortTransaction` |
|
| **Transactions** | `startTransaction`, `commitTransaction`, `abortTransaction` |
|
||||||
| **Sessions** | `startSession`, `endSessions` |
|
| **Sessions** | `startSession`, `endSessions`, `refreshSessions` |
|
||||||
| **Admin** | `ping`, `listDatabases`, `listCollections`, `drop`, `dropDatabase`, `create`, `serverStatus`, `buildInfo`, `dbStats`, `collStats` |
|
| **Admin** | `ping`, `listDatabases`, `listCollections`, `drop`, `dropDatabase`, `create`, `serverStatus`, `buildInfo`, `dbStats`, `collStats`, `connectionStatus` |
|
||||||
|
|
||||||
TsmDB supports MongoDB wire protocol versions 0-21, compatible with MongoDB 3.6 through 7.0 drivers.
|
TsmDB supports MongoDB wire protocol versions 0-21, compatible with MongoDB 3.6 through 7.0 drivers.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 🧪 Testing Examples
|
## 🧪 Testing Examples
|
||||||
|
|
||||||
### Jest/Mocha with LocalTsmDb
|
### Jest/Mocha with LocalTsmDb
|
||||||
@@ -492,10 +595,13 @@ let client: MongoClient;
|
|||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
db = new LocalTsmDb({ folderPath: './test-data' });
|
db = new LocalTsmDb({ folderPath: './test-data' });
|
||||||
client = await db.start();
|
const { connectionUri } = await db.start();
|
||||||
|
client = new MongoClient(connectionUri, { directConnection: true });
|
||||||
|
await client.connect();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
await client.close();
|
||||||
await db.stop();
|
await db.stop();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -555,16 +661,19 @@ test('should insert and find user', async () => {
|
|||||||
```typescript
|
```typescript
|
||||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import { LocalTsmDb } from '@push.rocks/smartmongo';
|
import { LocalTsmDb } from '@push.rocks/smartmongo';
|
||||||
|
import { MongoClient } from 'mongodb';
|
||||||
|
|
||||||
let db: LocalTsmDb;
|
let db: LocalTsmDb;
|
||||||
|
let client: MongoClient;
|
||||||
|
|
||||||
tap.test('setup', async () => {
|
tap.test('setup', async () => {
|
||||||
db = new LocalTsmDb({ folderPath: './test-data' });
|
db = new LocalTsmDb({ folderPath: './test-data' });
|
||||||
await db.start();
|
const { connectionUri } = await db.start();
|
||||||
|
client = new MongoClient(connectionUri, { directConnection: true });
|
||||||
|
await client.connect();
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('should perform CRUD operations', async () => {
|
tap.test('should perform CRUD operations', async () => {
|
||||||
const client = db.getClient();
|
|
||||||
const col = client.db('test').collection('items');
|
const col = client.db('test').collection('items');
|
||||||
|
|
||||||
// Create
|
// Create
|
||||||
@@ -587,12 +696,15 @@ tap.test('should perform CRUD operations', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
tap.test('teardown', async () => {
|
tap.test('teardown', async () => {
|
||||||
|
await client.close();
|
||||||
await db.stop();
|
await db.stop();
|
||||||
});
|
});
|
||||||
|
|
||||||
export default tap.start();
|
export default tap.start();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 🏗️ Architecture
|
## 🏗️ Architecture
|
||||||
|
|
||||||
### Module Structure
|
### Module Structure
|
||||||
@@ -601,7 +713,7 @@ export default tap.start();
|
|||||||
@push.rocks/smartmongo
|
@push.rocks/smartmongo
|
||||||
├── SmartMongo → Real MongoDB memory server (mongodb-memory-server wrapper)
|
├── SmartMongo → Real MongoDB memory server (mongodb-memory-server wrapper)
|
||||||
├── tsmdb → Wire protocol server with full engine stack
|
├── tsmdb → Wire protocol server with full engine stack
|
||||||
└── LocalTsmDb → Zero-config local database (convenience wrapper)
|
└── LocalTsmDb → Lightweight Unix socket wrapper (no MongoDB driver dependency)
|
||||||
```
|
```
|
||||||
|
|
||||||
### TsmDB Wire Protocol Stack
|
### TsmDB Wire Protocol Stack
|
||||||
@@ -611,7 +723,7 @@ export default tap.start();
|
|||||||
│ Official MongoDB Driver │
|
│ Official MongoDB Driver │
|
||||||
│ (mongodb npm) │
|
│ (mongodb npm) │
|
||||||
└─────────────────────────┬───────────────────────────────────┘
|
└─────────────────────────┬───────────────────────────────────┘
|
||||||
│ TCP + OP_MSG/BSON
|
│ TCP/Unix Socket + OP_MSG/BSON
|
||||||
▼
|
▼
|
||||||
┌─────────────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
│ TsmdbServer │
|
│ TsmdbServer │
|
||||||
@@ -650,11 +762,16 @@ export default tap.start();
|
|||||||
| **WireProtocol** | Parses MongoDB OP_MSG binary protocol |
|
| **WireProtocol** | Parses MongoDB OP_MSG binary protocol |
|
||||||
| **CommandRouter** | Routes commands to appropriate handlers |
|
| **CommandRouter** | Routes commands to appropriate handlers |
|
||||||
| **QueryPlanner** | Analyzes queries and selects execution strategy |
|
| **QueryPlanner** | Analyzes queries and selects execution strategy |
|
||||||
|
| **QueryEngine** | Executes queries with filter matching |
|
||||||
|
| **UpdateEngine** | Processes update operators (`$set`, `$inc`, etc.) |
|
||||||
|
| **AggregationEngine** | Executes aggregation pipelines |
|
||||||
| **IndexEngine** | Manages B-tree and hash indexes |
|
| **IndexEngine** | Manages B-tree and hash indexes |
|
||||||
| **SessionEngine** | Tracks client sessions and timeouts |
|
| **SessionEngine** | Tracks client sessions and timeouts |
|
||||||
| **TransactionEngine** | Handles ACID transaction semantics |
|
| **TransactionEngine** | Handles ACID transaction semantics |
|
||||||
| **WAL** | Write-ahead logging for durability |
|
| **WAL** | Write-ahead logging for durability |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## License and Legal Information
|
## License and Legal Information
|
||||||
|
|
||||||
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file.
|
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file.
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartmongo',
|
name: '@push.rocks/smartmongo',
|
||||||
version: '4.3.0',
|
version: '5.1.0',
|
||||||
description: 'A module for creating and managing a local MongoDB instance for testing purposes.'
|
description: 'A module for creating and managing a local MongoDB instance for testing purposes.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export * as tsmdb from './ts_tsmdb/index.js';
|
|||||||
|
|
||||||
// Export LocalTsmDb from ts_local
|
// Export LocalTsmDb from ts_local
|
||||||
export { LocalTsmDb } from './ts_local/index.js';
|
export { LocalTsmDb } from './ts_local/index.js';
|
||||||
export type { ILocalTsmDbOptions } from './ts_local/index.js';
|
export type { ILocalTsmDbOptions, ILocalTsmDbConnectionInfo } from './ts_local/index.js';
|
||||||
|
|
||||||
// Export commitinfo
|
// Export commitinfo
|
||||||
export { commitinfo };
|
export { commitinfo };
|
||||||
|
|||||||
@@ -1,98 +1,106 @@
|
|||||||
import * as plugins from './plugins.js';
|
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 { 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 {
|
export interface ILocalTsmDbOptions {
|
||||||
|
/** Required: where to store data */
|
||||||
folderPath: string;
|
folderPath: string;
|
||||||
port?: number;
|
/** Optional: custom socket path (default: auto-generated in /tmp) */
|
||||||
host?: string;
|
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
|
* 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
|
* @example
|
||||||
* ```typescript
|
* ```typescript
|
||||||
* import { LocalTsmDb } from '@push.rocks/smartmongo';
|
* import { LocalTsmDb } from '@push.rocks/smartmongo';
|
||||||
|
* import { MongoClient } from 'mongodb';
|
||||||
*
|
*
|
||||||
* const db = new LocalTsmDb({ folderPath: './data' });
|
* 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
|
* // Use the MongoDB client
|
||||||
* const collection = client.db('mydb').collection('users');
|
* const collection = client.db('mydb').collection('users');
|
||||||
* await collection.insertOne({ name: 'Alice' });
|
* await collection.insertOne({ name: 'Alice' });
|
||||||
*
|
*
|
||||||
* // When done
|
* // When done
|
||||||
|
* await client.close();
|
||||||
* await db.stop();
|
* await db.stop();
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export class LocalTsmDb {
|
export class LocalTsmDb {
|
||||||
private options: ILocalTsmDbOptions;
|
private options: ILocalTsmDbOptions;
|
||||||
private server: TsmdbServer | null = null;
|
private server: TsmdbServer | null = null;
|
||||||
private client: MongoClient | null = null;
|
private generatedSocketPath: string | null = null;
|
||||||
|
|
||||||
constructor(options: ILocalTsmDbOptions) {
|
constructor(options: ILocalTsmDbOptions) {
|
||||||
this.options = options;
|
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> {
|
private generateSocketPath(): string {
|
||||||
return new Promise((resolve, reject) => {
|
const randomId = crypto.randomBytes(8).toString('hex');
|
||||||
const server = plugins.net.createServer();
|
return path.join(os.tmpdir(), `smartmongo-${randomId}.sock`);
|
||||||
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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start the local TsmDB server and return a connected MongoDB client
|
* Start the local TsmDB server and return connection info
|
||||||
*/
|
*/
|
||||||
async start(): Promise<MongoClient> {
|
async start(): Promise<ILocalTsmDbConnectionInfo> {
|
||||||
if (this.server && this.client) {
|
if (this.server) {
|
||||||
throw new Error('LocalTsmDb is already running');
|
throw new Error('LocalTsmDb is already running');
|
||||||
}
|
}
|
||||||
|
|
||||||
const port = this.options.port ?? await this.findAvailablePort();
|
// Use provided socket path or generate one
|
||||||
const host = this.options.host ?? '127.0.0.1';
|
this.generatedSocketPath = this.options.socketPath ?? this.generateSocketPath();
|
||||||
|
|
||||||
this.server = new TsmdbServer({
|
this.server = new TsmdbServer({
|
||||||
port,
|
socketPath: this.generatedSocketPath,
|
||||||
host,
|
|
||||||
storage: 'file',
|
storage: 'file',
|
||||||
storagePath: this.options.folderPath,
|
storagePath: this.options.folderPath,
|
||||||
});
|
});
|
||||||
await this.server.start();
|
await this.server.start();
|
||||||
|
|
||||||
// Dynamically import mongodb to avoid requiring it as a hard dependency
|
return {
|
||||||
const mongodb = await import('mongodb');
|
socketPath: this.generatedSocketPath,
|
||||||
this.client = new mongodb.MongoClient(this.server.getConnectionUri(), {
|
connectionUri: this.server.getConnectionUri(),
|
||||||
directConnection: true,
|
};
|
||||||
serverSelectionTimeoutMS: 5000,
|
|
||||||
});
|
|
||||||
await this.client.connect();
|
|
||||||
|
|
||||||
return this.client;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the MongoDB client (throws if not started)
|
* Get connection info (throws if not started)
|
||||||
*/
|
*/
|
||||||
getClient(): MongoClient {
|
getConnectionInfo(): ILocalTsmDbConnectionInfo {
|
||||||
if (!this.client) {
|
if (!this.server || !this.generatedSocketPath) {
|
||||||
throw new Error('LocalTsmDb is not running. Call start() first.');
|
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> {
|
async stop(): Promise<void> {
|
||||||
if (this.client) {
|
|
||||||
await this.client.close();
|
|
||||||
this.client = null;
|
|
||||||
}
|
|
||||||
if (this.server) {
|
if (this.server) {
|
||||||
await this.server.stop();
|
await this.server.stop();
|
||||||
this.server = null;
|
this.server = null;
|
||||||
|
this.generatedSocketPath = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
export { LocalTsmDb } from './classes.localtsmdb.js';
|
export { LocalTsmDb } from './classes.localtsmdb.js';
|
||||||
export type { ILocalTsmDbOptions } from './classes.localtsmdb.js';
|
export type { ILocalTsmDbOptions, ILocalTsmDbConnectionInfo } from './classes.localtsmdb.js';
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import * as smartpromise from '@push.rocks/smartpromise';
|
import * as smartpromise from '@push.rocks/smartpromise';
|
||||||
import * as net from 'net';
|
|
||||||
|
|
||||||
export { smartpromise, net };
|
export { smartpromise };
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import * as net from 'net';
|
import * as net from 'net';
|
||||||
|
import * as fs from 'fs/promises';
|
||||||
import * as plugins from '../plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
import { WireProtocol, OP_QUERY } from './WireProtocol.js';
|
import { WireProtocol, OP_QUERY } from './WireProtocol.js';
|
||||||
import { CommandRouter } from './CommandRouter.js';
|
import { CommandRouter } from './CommandRouter.js';
|
||||||
@@ -10,10 +11,12 @@ import type { IStorageAdapter } from '../storage/IStorageAdapter.js';
|
|||||||
* Server configuration options
|
* Server configuration options
|
||||||
*/
|
*/
|
||||||
export interface ITsmdbServerOptions {
|
export interface ITsmdbServerOptions {
|
||||||
/** Port to listen on (default: 27017) */
|
/** Port to listen on (default: 27017) - ignored if socketPath is set */
|
||||||
port?: number;
|
port?: number;
|
||||||
/** Host to bind to (default: 127.0.0.1) */
|
/** Host to bind to (default: 127.0.0.1) - ignored if socketPath is set */
|
||||||
host?: string;
|
host?: string;
|
||||||
|
/** Unix socket path - if set, server listens on socket instead of TCP */
|
||||||
|
socketPath?: string;
|
||||||
/** Storage type: 'memory' or 'file' (default: 'memory') */
|
/** Storage type: 'memory' or 'file' (default: 'memory') */
|
||||||
storage?: 'memory' | 'file';
|
storage?: 'memory' | 'file';
|
||||||
/** Path for file storage (required if storage is 'file') */
|
/** Path for file storage (required if storage is 'file') */
|
||||||
@@ -54,7 +57,7 @@ interface IConnectionState {
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export class TsmdbServer {
|
export class TsmdbServer {
|
||||||
private options: Required<ITsmdbServerOptions>;
|
private options: Required<Omit<ITsmdbServerOptions, 'socketPath'>> & { socketPath: string };
|
||||||
private server: net.Server | null = null;
|
private server: net.Server | null = null;
|
||||||
private storage: IStorageAdapter;
|
private storage: IStorageAdapter;
|
||||||
private commandRouter: CommandRouter;
|
private commandRouter: CommandRouter;
|
||||||
@@ -62,11 +65,14 @@ export class TsmdbServer {
|
|||||||
private connectionIdCounter = 0;
|
private connectionIdCounter = 0;
|
||||||
private isRunning = false;
|
private isRunning = false;
|
||||||
private startTime: Date = new Date();
|
private startTime: Date = new Date();
|
||||||
|
private useSocket: boolean;
|
||||||
|
|
||||||
constructor(options: ITsmdbServerOptions = {}) {
|
constructor(options: ITsmdbServerOptions = {}) {
|
||||||
|
this.useSocket = !!options.socketPath;
|
||||||
this.options = {
|
this.options = {
|
||||||
port: options.port ?? 27017,
|
port: options.port ?? 27017,
|
||||||
host: options.host ?? '127.0.0.1',
|
host: options.host ?? '127.0.0.1',
|
||||||
|
socketPath: options.socketPath ?? '',
|
||||||
storage: options.storage ?? 'memory',
|
storage: options.storage ?? 'memory',
|
||||||
storagePath: options.storagePath ?? './data',
|
storagePath: options.storagePath ?? './data',
|
||||||
persistPath: options.persistPath ?? '',
|
persistPath: options.persistPath ?? '',
|
||||||
@@ -119,6 +125,18 @@ export class TsmdbServer {
|
|||||||
// Initialize storage
|
// Initialize storage
|
||||||
await this.storage.initialize();
|
await this.storage.initialize();
|
||||||
|
|
||||||
|
// Clean up stale socket file if using Unix socket
|
||||||
|
if (this.useSocket && this.options.socketPath) {
|
||||||
|
try {
|
||||||
|
await fs.unlink(this.options.socketPath);
|
||||||
|
} catch (err: any) {
|
||||||
|
// Ignore ENOENT (file doesn't exist)
|
||||||
|
if (err.code !== 'ENOENT') {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.server = net.createServer((socket) => {
|
this.server = net.createServer((socket) => {
|
||||||
this.handleConnection(socket);
|
this.handleConnection(socket);
|
||||||
@@ -132,11 +150,21 @@ export class TsmdbServer {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (this.useSocket && this.options.socketPath) {
|
||||||
|
// Listen on Unix socket
|
||||||
|
this.server.listen(this.options.socketPath, () => {
|
||||||
|
this.isRunning = true;
|
||||||
|
this.startTime = new Date();
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Listen on TCP
|
||||||
this.server.listen(this.options.port, this.options.host, () => {
|
this.server.listen(this.options.port, this.options.host, () => {
|
||||||
this.isRunning = true;
|
this.isRunning = true;
|
||||||
this.startTime = new Date();
|
this.startTime = new Date();
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,9 +189,22 @@ export class TsmdbServer {
|
|||||||
await this.storage.close();
|
await this.storage.close();
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
this.server!.close(() => {
|
this.server!.close(async () => {
|
||||||
this.isRunning = false;
|
this.isRunning = false;
|
||||||
this.server = null;
|
this.server = null;
|
||||||
|
|
||||||
|
// Clean up socket file if using Unix socket
|
||||||
|
if (this.useSocket && this.options.socketPath) {
|
||||||
|
try {
|
||||||
|
await fs.unlink(this.options.socketPath);
|
||||||
|
} catch (err: any) {
|
||||||
|
// Ignore ENOENT (file doesn't exist)
|
||||||
|
if (err.code !== 'ENOENT') {
|
||||||
|
console.error('Failed to remove socket file:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -275,9 +316,21 @@ export class TsmdbServer {
|
|||||||
* Get the connection URI for this server
|
* Get the connection URI for this server
|
||||||
*/
|
*/
|
||||||
getConnectionUri(): string {
|
getConnectionUri(): string {
|
||||||
|
if (this.useSocket && this.options.socketPath) {
|
||||||
|
// URL-encode the socket path (replace / with %2F)
|
||||||
|
const encodedPath = encodeURIComponent(this.options.socketPath);
|
||||||
|
return `mongodb://${encodedPath}`;
|
||||||
|
}
|
||||||
return `mongodb://${this.options.host}:${this.options.port}`;
|
return `mongodb://${this.options.host}:${this.options.port}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the socket path (if using Unix socket mode)
|
||||||
|
*/
|
||||||
|
get socketPath(): string | undefined {
|
||||||
|
return this.useSocket ? this.options.socketPath : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the server is running
|
* Check if the server is running
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user