10 Commits
v4.1.1 ... main

Author SHA1 Message Date
c65c285296 v5.1.0
Some checks failed
Default (tags) / security (push) Successful in 38s
Default (tags) / test (push) Failing after 4m1s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-02-03 16:48:50 +00:00
b72174ca7b feat(localtsmdb): export ILocalTsmDbConnectionInfo and expand LocalTsmDb/TsmDB documentation and examples 2026-02-03 16:48:50 +00:00
1ff4d219af v5.0.0
Some checks failed
Default (tags) / security (push) Successful in 43s
Default (tags) / test (push) Failing after 4m2s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-02-03 16:42:49 +00:00
09f60de56f BREAKING CHANGE(localtsmdb): add Unix socket support and change LocalTsmDb API to return connection info instead of a MongoClient 2026-02-03 16:42:49 +00:00
e6a36ecb5f v4.3.0
Some checks failed
Default (tags) / security (push) Successful in 41s
Default (tags) / test (push) Failing after 4m0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-02-03 08:14:58 +00:00
6a37a773ea feat(docs): add LocalTsmDb documentation and examples; update README code samples and imports; correct examples and variable names; update package author 2026-02-03 08:14:58 +00:00
1fff277698 v4.2.1
Some checks failed
Default (tags) / security (push) Successful in 44s
Default (tags) / test (push) Failing after 4m1s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-02-03 07:29:20 +00:00
0ad7f316c4 fix(package.json): replace main and typings with exports field pointing to ./dist_ts/index.js 2026-02-03 07:29:20 +00:00
0d450e7d4e v4.2.0
Some checks failed
Default (tags) / security (push) Successful in 43s
Default (tags) / test (push) Failing after 4m2s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-02-01 23:33:35 +00:00
fff77fbd8e feat(tsmdb): implement TsmDB Mongo-wire-compatible server, add storage/engine modules and reorganize exports 2026-02-01 23:33:35 +00:00
42 changed files with 658 additions and 185 deletions

View File

@@ -1,5 +1,50 @@
# 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)
add LocalTsmDb documentation and examples; update README code samples and imports; correct examples and variable names; update package author
- Introduce LocalTsmDb: zero-config local database with automatic persistence, auto port discovery, and pre-connected client (added Quick Start, API, Features, and testing examples).
- Expand comparison table to include LocalTsmDb alongside SmartMongo and TsmDB.
- Update README examples: new LocalTsmDb usage, reorder options (LocalTsmDb, TsmDB, SmartMongo), rename test DB variable (db -> testDb), and adjust test snippets for Jest/Mocha and tap.
- Adjust code snippets and API notes: switch some example imports to use tsmdb, replace FileStorageAdapter references, change planner.createPlan to await planner.plan, and use wal.getEntriesAfter(...) without awaiting.
- Update package.json author from 'Lossless GmbH' to 'Task Venture Capital GmbH'.
## 2026-02-03 - 4.2.1 - fix(package.json)
replace main and typings with exports field pointing to ./dist_ts/index.js
- Added package.json exports field mapping "." to ./dist_ts/index.js to declare the package entrypoint.
- Removed main (dist_ts/index.js) and typings (dist_ts/index.d.ts) entries.
- Note: switching to exports improves Node resolution but removing the typings entry may affect TypeScript consumers expecting index.d.ts.
## 2026-02-01 - 4.2.0 - feat(tsmdb)
implement TsmDB Mongo-wire-compatible server, add storage/engine modules and reorganize exports
- Add full TsmDB implementation under ts/ts_tsmdb: wire protocol, server, command router, handlers, engines (Query, Update, Aggregation, Index, Transaction, Session), storage adapters (Memory, File), OpLog, WAL, utils and types.
- Remove legacy ts/tsmdb implementation and replace with new ts_tsmdb module exports.
- Introduce ts/ts_mongotools module and move SmartMongo class there; update top-level exports in ts/index.ts to export SmartMongo, tsmdb (from ts_tsmdb) and LocalTsmDb.
- Add LocalTsmDb convenience class (ts/ts_local) to start a file-backed TsmDB and return a connected MongoClient.
- Refactor plugin imports into per-module plugins files and add utilities (checksum, persistence, query planner, index engine).
## 2026-02-01 - 4.1.1 - fix(tsmdb) ## 2026-02-01 - 4.1.1 - fix(tsmdb)
add comprehensive unit tests for tsmdb components: checksum, query planner, index engine, session, and WAL add comprehensive unit tests for tsmdb components: checksum, query planner, index engine, session, and WAL

View File

@@ -1,12 +1,13 @@
{ {
"name": "@push.rocks/smartmongo", "name": "@push.rocks/smartmongo",
"version": "4.1.1", "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.",
"main": "dist_ts/index.js", "exports": {
"typings": "dist_ts/index.d.ts", ".": "./dist_ts/index.js"
},
"type": "module", "type": "module",
"author": "Lossless GmbH", "author": "Task Venture Capital GmbH",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"test": "(tstest test/. --verbose --logfile --timeout 60)", "test": "(tstest test/. --verbose --logfile --timeout 60)",

367
readme.md
View File

@@ -1,6 +1,6 @@
# @push.rocks/smartmongo # @push.rocks/smartmongo
A powerful MongoDB toolkit for testing and development — featuring both a real MongoDB memory server (**SmartMongo**) and an ultra-fast, lightweight wire-protocol-compatible in-memory database server (**TsmDB**). 🚀 A powerful MongoDB toolkit for testing and development — featuring a real MongoDB memory server (**SmartMongo**), an ultra-fast wire-protocol-compatible in-memory database server (**TsmDB**), and a zero-config local database (**LocalTsmDb**). 🚀
## Install ## Install
@@ -16,21 +16,88 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
## Overview ## Overview
`@push.rocks/smartmongo` provides two powerful approaches for MongoDB in testing and development: `@push.rocks/smartmongo` provides three powerful approaches for MongoDB in testing and development:
| Feature | SmartMongo | TsmDB | | Feature | SmartMongo | TsmDB | LocalTsmDb |
|---------|------------|---------| |---------|------------|-------|------------|
| **Type** | Real MongoDB (memory server) | Pure TypeScript wire protocol server | | **Type** | Real MongoDB (memory server) | Wire protocol server | Zero-config local DB |
| **Speed** | ~2-5s startup | ⚡ Instant startup (~5ms) | | **Speed** | ~2-5s startup | ⚡ Instant (~5ms) | ⚡ Instant (Unix socket) |
| **Compatibility** | 100% MongoDB | MongoDB driver compatible | | **Compatibility** | 100% MongoDB | MongoDB driver compatible | MongoDB driver compatible |
| **Dependencies** | Downloads MongoDB binary | Zero external dependencies | | **Dependencies** | Downloads MongoDB binary | Zero external deps | Zero external deps (no MongoDB driver!) |
| **Replication** | ✅ Full replica set support | Single node emulation | | **Connection** | TCP | TCP or Unix socket | Unix socket (default) |
| **Use Case** | Integration testing | Unit testing, CI/CD | | **Replication** | ✅ Full replica set | Single node | Single node |
| **Persistence** | Dump to directory | Optional file/memory persistence | | **Persistence** | Dump to directory | Memory or file | File-based (automatic) |
| **Use Case** | Integration testing | Unit testing, CI/CD | Quick prototyping, local dev |
## 🚀 Quick Start ## 🚀 Quick Start
### Option 1: SmartMongo (Real MongoDB) ### 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 using Unix sockets. No port conflicts, no MongoDB driver dependency in LocalTsmDb!
```typescript
import { LocalTsmDb } from '@push.rocks/smartmongo';
import { MongoClient } from 'mongodb';
// Create a local database backed by files
const db = new LocalTsmDb({ folderPath: './my-data' });
// Start and get connection info (Unix socket path + connection URI)
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
const users = client.db('myapp').collection('users');
await users.insertOne({ name: 'Alice', email: 'alice@example.com' });
const user = await users.findOne({ name: 'Alice' });
console.log(user); // { _id: ObjectId(...), name: 'Alice', email: 'alice@example.com' }
// Data persists to disk automatically!
await client.close();
await db.stop();
// Later... data is still there
const db2 = new LocalTsmDb({ folderPath: './my-data' });
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' });
// savedUser exists!
```
### Option 2: TsmDB (Wire Protocol Server)
A lightweight, pure TypeScript MongoDB-compatible server — use the official `mongodb` driver directly!
```typescript
import { tsmdb } from '@push.rocks/smartmongo';
import { MongoClient } from 'mongodb';
// Start TsmDB server (TCP mode)
const server = new tsmdb.TsmdbServer({ port: 27017 });
await server.start();
// Connect with the official MongoDB driver
const client = new MongoClient('mongodb://127.0.0.1:27017');
await client.connect();
// Use exactly like real MongoDB
const db = client.db('myapp');
await db.collection('users').insertOne({ name: 'Alice', age: 30 });
const user = await db.collection('users').findOne({ name: 'Alice' });
console.log(user); // { _id: ObjectId(...), name: 'Alice', age: 30 }
// Clean up
await client.close();
await server.stop();
```
### Option 3: SmartMongo (Real MongoDB)
Spin up a real MongoDB replica set in memory — perfect for integration tests that need full MongoDB compatibility. Spin up a real MongoDB replica set in memory — perfect for integration tests that need full MongoDB compatibility.
@@ -51,36 +118,91 @@ console.log(descriptor.mongoDbUrl); // mongodb://127.0.0.1:xxxxx/...
await mongo.stop(); await mongo.stop();
``` ```
### Option 2: TsmDB (Wire Protocol Server) ---
A lightweight, pure TypeScript MongoDB-compatible server that speaks the wire protocol — use the official `mongodb` driver directly! ## 📖 LocalTsmDb API
The simplest option for local development and prototyping — lightweight, Unix socket-based, and automatic persistence.
### Configuration
```typescript ```typescript
import { tsmdb } from '@push.rocks/smartmongo'; import { LocalTsmDb } from '@push.rocks/smartmongo';
import { MongoClient } from 'mongodb'; import type { ILocalTsmDbOptions, ILocalTsmDbConnectionInfo } from '@push.rocks/smartmongo';
// Start TsmDB server const options: ILocalTsmDbOptions = {
const server = new tsmdb.TsmdbServer({ port: 27017 }); folderPath: './data', // Required: where to store data
await server.start(); socketPath: '/tmp/my.sock', // Optional: custom socket path (default: auto-generated)
};
// Connect with the official MongoDB driver! const db = new LocalTsmDb(options);
const client = new MongoClient('mongodb://127.0.0.1:27017');
await client.connect();
// Use exactly like real MongoDB
const db = client.db('myapp');
await db.collection('users').insertOne({ name: 'Alice', age: 30 });
const user = await db.collection('users').findOne({ name: 'Alice' });
console.log(user); // { _id: ObjectId(...), name: 'Alice', age: 30 }
// Clean up
await client.close();
await server.stop();
``` ```
### 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
```typescript
import { LocalTsmDb } from '@push.rocks/smartmongo';
import { MongoClient } from 'mongodb';
const db = new LocalTsmDb({ folderPath: './data' });
// Start and get connection info
const { socketPath, connectionUri } = await db.start();
console.log(socketPath); // /tmp/smartmongo-abc123.sock (auto-generated)
console.log(connectionUri); // mongodb://%2Ftmp%2Fsmartmongo-abc123.sock
// Connect with your own MongoDB client
const client = new MongoClient(connectionUri, { directConnection: true });
await client.connect();
// Use the client
const users = client.db('mydb').collection('users');
await users.insertOne({ name: 'Alice' });
// Check status
console.log(db.running); // true
// Stop when done (close your client first!)
await client.close();
await db.stop();
```
### Features
- 🔌 **Unix Sockets** — No port conflicts, faster IPC than TCP
- 💾 **Automatic Persistence** — Data saved to files, survives restarts
- 🪶 **Lightweight** — No MongoDB driver dependency in LocalTsmDb itself
- 🎯 **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
@@ -116,27 +238,44 @@ 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
storage: 'memory', // 'memory' or 'file' storage: 'memory', // 'memory' or 'file'
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();
``` ```
@@ -146,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' });
@@ -175,6 +315,7 @@ const result = await collection.findOneAndUpdate(
``` ```
#### 🔹 Query Operators #### 🔹 Query Operators
```typescript ```typescript
// Comparison // Comparison
{ age: { $eq: 25 } } { age: { $eq: 25 } }
@@ -197,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: '' } }
@@ -212,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' } },
@@ -225,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();
@@ -249,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({});
@@ -264,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' } } },
@@ -292,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 { FileStorageAdapter } from '@push.rocks/smartmongo/tsmdb'; const server = new tsmdb.TsmdbServer({
storage: 'file',
const adapter = new FileStorageAdapter('./data/tsmdb', { storagePath: './data/tsmdb'
enableChecksums: true, // CRC32 checksums for data integrity
strictChecksums: false // Log warnings vs throw on mismatch
}); });
``` ```
---
## ⚡ 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`)
@@ -331,14 +496,14 @@ import { tsmdb } from '@push.rocks/smartmongo';
// For debugging, you can access the query planner // For debugging, you can access the query planner
const planner = new tsmdb.QueryPlanner(indexEngine); const planner = new tsmdb.QueryPlanner(indexEngine);
const plan = planner.createPlan(filter); const plan = await planner.plan(filter);
console.log(plan); console.log(plan);
// { // {
// type: 'IXSCAN', // or 'IXSCAN_RANGE', 'COLLSCAN' // type: 'IXSCAN', // or 'IXSCAN_RANGE', 'COLLSCAN'
// indexName: 'email_1', // indexName: 'email_1',
// estimatedCost: 1, // selectivity: 0.01,
// selectivity: 0.001 // indexCovering: true
// } // }
``` ```
@@ -357,10 +522,10 @@ await wal.initialize();
// - Timestamp // - Timestamp
// - Operation type (insert, update, delete, checkpoint) // - Operation type (insert, update, delete, checkpoint)
// - Document data (BSON serialized) // - Document data (BSON serialized)
// - CRC32 checksum // - CRC32 checksum for integrity
// Recovery support // Recovery support
const entries = await wal.getEntriesAfter(lastCheckpointLsn); const entries = wal.getEntriesAfter(lastCheckpointLsn);
``` ```
### 🔐 Session Management ### 🔐 Session Management
@@ -393,33 +558,67 @@ try {
File-based storage supports CRC32 checksums to detect corruption: File-based storage supports CRC32 checksums to detect corruption:
```typescript ```typescript
import { FileStorageAdapter } from '@push.rocks/smartmongo/tsmdb'; import { tsmdb } from '@push.rocks/smartmongo';
const adapter = new FileStorageAdapter('./data', {
enableChecksums: true,
strictChecksums: true // Throw error on corruption (vs warning)
});
// 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
// Checksums are automatically stripped before returning to client 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
```typescript
import { LocalTsmDb } from '@push.rocks/smartmongo';
import { MongoClient, Db } from 'mongodb';
let db: LocalTsmDb;
let client: MongoClient;
beforeAll(async () => {
db = new LocalTsmDb({ folderPath: './test-data' });
const { connectionUri } = await db.start();
client = new MongoClient(connectionUri, { directConnection: true });
await client.connect();
});
afterAll(async () => {
await client.close();
await db.stop();
});
beforeEach(async () => {
// Clean slate for each test
await client.db('test').dropDatabase();
});
test('should insert and find user', async () => {
const users = client.db('test').collection('users');
await users.insertOne({ name: 'Alice', email: 'alice@example.com' });
const user = await users.findOne({ name: 'Alice' });
expect(user?.email).toBe('alice@example.com');
});
```
### Jest/Mocha with TsmDB ### Jest/Mocha with TsmDB
```typescript ```typescript
@@ -428,7 +627,7 @@ import { MongoClient, Db } from 'mongodb';
let server: tsmdb.TsmdbServer; let server: tsmdb.TsmdbServer;
let client: MongoClient; let client: MongoClient;
let db: Db; let testDb: Db;
beforeAll(async () => { beforeAll(async () => {
server = new tsmdb.TsmdbServer({ port: 27117 }); server = new tsmdb.TsmdbServer({ port: 27117 });
@@ -436,7 +635,7 @@ beforeAll(async () => {
client = new MongoClient('mongodb://127.0.0.1:27117'); client = new MongoClient('mongodb://127.0.0.1:27117');
await client.connect(); await client.connect();
db = client.db('test'); testDb = client.db('test');
}); });
afterAll(async () => { afterAll(async () => {
@@ -445,12 +644,11 @@ afterAll(async () => {
}); });
beforeEach(async () => { beforeEach(async () => {
// Clean slate for each test await testDb.dropDatabase();
await db.dropDatabase();
}); });
test('should insert and find user', async () => { test('should insert and find user', async () => {
const users = db.collection('users'); const users = testDb.collection('users');
await users.insertOne({ name: 'Alice', email: 'alice@example.com' }); await users.insertOne({ name: 'Alice', email: 'alice@example.com' });
const user = await users.findOne({ name: 'Alice' }); const user = await users.findOne({ name: 'Alice' });
@@ -462,22 +660,21 @@ 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 { tsmdb } from '@push.rocks/smartmongo'; import { LocalTsmDb } from '@push.rocks/smartmongo';
import { MongoClient } from 'mongodb'; import { MongoClient } from 'mongodb';
let server: tsmdb.TsmdbServer; let db: LocalTsmDb;
let client: MongoClient; let client: MongoClient;
tap.test('setup', async () => { tap.test('setup', async () => {
server = new tsmdb.TsmdbServer({ port: 27117 }); db = new LocalTsmDb({ folderPath: './test-data' });
await server.start(); const { connectionUri } = await db.start();
client = new MongoClient('mongodb://127.0.0.1:27117'); client = new MongoClient(connectionUri, { directConnection: true });
await client.connect(); await client.connect();
}); });
tap.test('should perform CRUD operations', async () => { tap.test('should perform CRUD operations', async () => {
const db = client.db('test'); const col = client.db('test').collection('items');
const col = db.collection('items');
// Create // Create
const result = await col.insertOne({ name: 'Widget', price: 9.99 }); const result = await col.insertOne({ name: 'Widget', price: 9.99 });
@@ -500,14 +697,25 @@ tap.test('should perform CRUD operations', async () => {
tap.test('teardown', async () => { tap.test('teardown', async () => {
await client.close(); await client.close();
await server.stop(); await db.stop();
}); });
export default tap.start(); export default tap.start();
``` ```
---
## 🏗️ Architecture ## 🏗️ Architecture
### Module Structure
```
@push.rocks/smartmongo
├── SmartMongo → Real MongoDB memory server (mongodb-memory-server wrapper)
├── tsmdb → Wire protocol server with full engine stack
└── LocalTsmDb → Lightweight Unix socket wrapper (no MongoDB driver dependency)
```
### TsmDB Wire Protocol Stack ### TsmDB Wire Protocol Stack
``` ```
@@ -515,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 │
@@ -554,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.

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@push.rocks/smartmongo', name: '@push.rocks/smartmongo',
version: '4.1.1', 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.'
} }

View File

@@ -1,74 +1,14 @@
import { commitinfo } from './00_commitinfo_data.js'; import { commitinfo } from './00_commitinfo_data.js';
import * as plugins from './smartmongo.plugins.js';
// Export SmartMongo from ts_mongotools
export { SmartMongo } from './ts_mongotools/index.js';
// Export TsmDB module // Export TsmDB module
export * as tsmdb from './tsmdb/index.js'; export * as tsmdb from './ts_tsmdb/index.js';
export class SmartMongo { // Export LocalTsmDb from ts_local
// STATIC export { LocalTsmDb } from './ts_local/index.js';
public static async createAndStart(replCountArg: number = 1) { export type { ILocalTsmDbOptions, ILocalTsmDbConnectionInfo } from './ts_local/index.js';
const smartMongoInstance = new SmartMongo();
await smartMongoInstance.start(replCountArg);
return smartMongoInstance;
}
// INSTANCE // Export commitinfo
private _readyDeferred = plugins.smartpromise.defer(); export { commitinfo };
public readyPromise = this._readyDeferred.promise;
public mongoReplicaSet: plugins.mongoPlugin.MongoMemoryReplSet;
constructor() {}
public async start(countArg: number = 1) {
this.mongoReplicaSet = await plugins.mongoPlugin.MongoMemoryReplSet.create({
replSet: { count: countArg },
instanceOpts: [
{
storageEngine: 'wiredTiger',
},
],
});
this._readyDeferred.resolve();
console.log(`mongoReplicaSet with ${countArg} replicas started.`);
console.log(`@pushrocks/smartmongo version ${commitinfo.version}`);
}
/**
* returns a mongo descriptor for modules like
* @pushrocks/smartfile.
*/
public async getMongoDescriptor(): Promise<plugins.smartdata.IMongoDescriptor> {
await this.readyPromise;
return {
mongoDbName: `smartmongo_testdatabase`,
mongoDbUrl: this.mongoReplicaSet.getUri(),
};
}
/**
* stops the smartmongo instance
* and cleans up after itself
*/
public async stop() {
await this.mongoReplicaSet.stop();
await this.mongoReplicaSet.cleanup();
}
/**
* like stop() but allows you to actually store
* the database on disk
*/
public async stopAndDumpToDir(
dirArg: string,
nameFunctionArg?: (doc: any) => string,
emptyDirArg = true,
) {
const mongodumpInstance = new plugins.mongodump.MongoDump();
const mongodumpTarget = await mongodumpInstance.addMongoTargetByMongoDescriptor(
await this.getMongoDescriptor(),
);
await mongodumpTarget.dumpAllCollectionsToDir(dirArg, nameFunctionArg, emptyDirArg);
await mongodumpInstance.stop();
await this.stop();
}
}

View File

@@ -0,0 +1,143 @@
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';
/**
* 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;
/** Optional: custom socket path (default: auto-generated in /tmp) */
socketPath?: string;
}
/**
* 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. 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 { 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 generatedSocketPath: string | null = null;
constructor(options: ILocalTsmDbOptions) {
this.options = options;
}
/**
* Generate a unique socket path in /tmp
*/
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 connection info
*/
async start(): Promise<ILocalTsmDbConnectionInfo> {
if (this.server) {
throw new Error('LocalTsmDb is already running');
}
// Use provided socket path or generate one
this.generatedSocketPath = this.options.socketPath ?? this.generateSocketPath();
this.server = new TsmdbServer({
socketPath: this.generatedSocketPath,
storage: 'file',
storagePath: this.options.folderPath,
});
await this.server.start();
return {
socketPath: this.generatedSocketPath,
connectionUri: this.server.getConnectionUri(),
};
}
/**
* Get connection info (throws if not started)
*/
getConnectionInfo(): ILocalTsmDbConnectionInfo {
if (!this.server || !this.generatedSocketPath) {
throw new Error('LocalTsmDb is not running. Call start() first.');
}
return {
socketPath: this.generatedSocketPath,
connectionUri: this.server.getConnectionUri(),
};
}
/**
* 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;
}
/**
* Stop the local TsmDB server
*/
async stop(): Promise<void> {
if (this.server) {
await this.server.stop();
this.server = null;
this.generatedSocketPath = null;
}
}
}

2
ts/ts_local/index.ts Normal file
View File

@@ -0,0 +1,2 @@
export { LocalTsmDb } from './classes.localtsmdb.js';
export type { ILocalTsmDbOptions, ILocalTsmDbConnectionInfo } from './classes.localtsmdb.js';

3
ts/ts_local/plugins.ts Normal file
View File

@@ -0,0 +1,3 @@
import * as smartpromise from '@push.rocks/smartpromise';
export { smartpromise };

View File

@@ -0,0 +1,71 @@
import { commitinfo } from '../00_commitinfo_data.js';
import * as plugins from './plugins.js';
export class SmartMongo {
// STATIC
public static async createAndStart(replCountArg: number = 1) {
const smartMongoInstance = new SmartMongo();
await smartMongoInstance.start(replCountArg);
return smartMongoInstance;
}
// INSTANCE
private _readyDeferred = plugins.smartpromise.defer();
public readyPromise = this._readyDeferred.promise;
public mongoReplicaSet: plugins.mongoPlugin.MongoMemoryReplSet;
constructor() {}
public async start(countArg: number = 1) {
this.mongoReplicaSet = await plugins.mongoPlugin.MongoMemoryReplSet.create({
replSet: { count: countArg },
instanceOpts: [
{
storageEngine: 'wiredTiger',
},
],
});
this._readyDeferred.resolve();
console.log(`mongoReplicaSet with ${countArg} replicas started.`);
console.log(`@pushrocks/smartmongo version ${commitinfo.version}`);
}
/**
* returns a mongo descriptor for modules like
* @pushrocks/smartfile.
*/
public async getMongoDescriptor(): Promise<plugins.smartdata.IMongoDescriptor> {
await this.readyPromise;
return {
mongoDbName: `smartmongo_testdatabase`,
mongoDbUrl: this.mongoReplicaSet.getUri(),
};
}
/**
* stops the smartmongo instance
* and cleans up after itself
*/
public async stop() {
await this.mongoReplicaSet.stop();
await this.mongoReplicaSet.cleanup();
}
/**
* like stop() but allows you to actually store
* the database on disk
*/
public async stopAndDumpToDir(
dirArg: string,
nameFunctionArg?: (doc: any) => string,
emptyDirArg = true,
) {
const mongodumpInstance = new plugins.mongodump.MongoDump();
const mongodumpTarget = await mongodumpInstance.addMongoTargetByMongoDescriptor(
await this.getMongoDescriptor(),
);
await mongodumpTarget.dumpAllCollectionsToDir(dirArg, nameFunctionArg, emptyDirArg);
await mongodumpInstance.stop();
await this.stop();
}
}

View File

@@ -0,0 +1,2 @@
export * from './plugins.js';
export { SmartMongo } from './classes.smartmongo.js';

View File

@@ -1,4 +1,4 @@
import * as plugins from '../tsmdb.plugins.js'; import * as plugins from '../plugins.js';
import type { Document, IStoredDocument, IAggregateOptions } from '../types/interfaces.js'; import type { Document, IStoredDocument, IAggregateOptions } from '../types/interfaces.js';
// Import mingo Aggregator // Import mingo Aggregator

View File

@@ -1,4 +1,4 @@
import * as plugins from '../tsmdb.plugins.js'; import * as plugins from '../plugins.js';
import type { IStorageAdapter } from '../storage/IStorageAdapter.js'; import type { IStorageAdapter } from '../storage/IStorageAdapter.js';
// Simple B-Tree implementation for range queries // Simple B-Tree implementation for range queries

View File

@@ -1,4 +1,4 @@
import * as plugins from '../tsmdb.plugins.js'; import * as plugins from '../plugins.js';
import type { Document, IStoredDocument, ISortSpecification, ISortDirection } from '../types/interfaces.js'; import type { Document, IStoredDocument, ISortSpecification, ISortDirection } from '../types/interfaces.js';
// Import mingo Query class // Import mingo Query class

View File

@@ -1,4 +1,4 @@
import * as plugins from '../tsmdb.plugins.js'; import * as plugins from '../plugins.js';
import type { Document, IStoredDocument } from '../types/interfaces.js'; import type { Document, IStoredDocument } from '../types/interfaces.js';
import { IndexEngine } from './IndexEngine.js'; import { IndexEngine } from './IndexEngine.js';

View File

@@ -1,4 +1,4 @@
import * as plugins from '../tsmdb.plugins.js'; import * as plugins from '../plugins.js';
import type { TransactionEngine } from './TransactionEngine.js'; import type { TransactionEngine } from './TransactionEngine.js';
/** /**

View File

@@ -1,4 +1,4 @@
import * as plugins from '../tsmdb.plugins.js'; import * as plugins from '../plugins.js';
import type { IStorageAdapter } from '../storage/IStorageAdapter.js'; import type { IStorageAdapter } from '../storage/IStorageAdapter.js';
import type { Document, IStoredDocument, ITransactionOptions } from '../types/interfaces.js'; import type { Document, IStoredDocument, ITransactionOptions } from '../types/interfaces.js';
import { TsmdbTransactionError, TsmdbWriteConflictError } from '../errors/TsmdbErrors.js'; import { TsmdbTransactionError, TsmdbWriteConflictError } from '../errors/TsmdbErrors.js';

View File

@@ -1,4 +1,4 @@
import * as plugins from '../tsmdb.plugins.js'; import * as plugins from '../plugins.js';
import type { Document, IStoredDocument } from '../types/interfaces.js'; import type { Document, IStoredDocument } from '../types/interfaces.js';
import { QueryEngine } from './QueryEngine.js'; import { QueryEngine } from './QueryEngine.js';

View File

@@ -2,7 +2,7 @@
// Use the official MongoDB driver to connect to TsmdbServer // Use the official MongoDB driver to connect to TsmdbServer
// Re-export plugins for external use // Re-export plugins for external use
import * as plugins from './tsmdb.plugins.js'; import * as plugins from './plugins.js';
export { plugins }; export { plugins };
// Export BSON types for convenience // Export BSON types for convenience

View File

@@ -1,4 +1,4 @@
import * as plugins from '../tsmdb.plugins.js'; import * as plugins from '../plugins.js';
import type { IStorageAdapter } from '../storage/IStorageAdapter.js'; import type { IStorageAdapter } from '../storage/IStorageAdapter.js';
import type { IParsedCommand } from './WireProtocol.js'; import type { IParsedCommand } from './WireProtocol.js';
import type { TsmdbServer } from './TsmdbServer.js'; import type { TsmdbServer } from './TsmdbServer.js';

View File

@@ -1,5 +1,6 @@
import * as net from 'net'; import * as net from 'net';
import * as plugins from '../tsmdb.plugins.js'; import * as fs from 'fs/promises';
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';
import { MemoryStorageAdapter } from '../storage/MemoryStorageAdapter.js'; import { MemoryStorageAdapter } from '../storage/MemoryStorageAdapter.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 {
} }
}); });
this.server.listen(this.options.port, this.options.host, () => { if (this.useSocket && this.options.socketPath) {
this.isRunning = true; // Listen on Unix socket
this.startTime = new Date(); this.server.listen(this.options.socketPath, () => {
resolve(); this.isRunning = true;
}); this.startTime = new Date();
resolve();
});
} else {
// Listen on TCP
this.server.listen(this.options.port, this.options.host, () => {
this.isRunning = true;
this.startTime = new Date();
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
*/ */

View File

@@ -1,4 +1,4 @@
import * as plugins from '../tsmdb.plugins.js'; import * as plugins from '../plugins.js';
/** /**
* MongoDB Wire Protocol Implementation * MongoDB Wire Protocol Implementation

View File

@@ -1,4 +1,4 @@
import * as plugins from '../../tsmdb.plugins.js'; import * as plugins from '../../plugins.js';
import type { ICommandHandler, IHandlerContext } from '../CommandRouter.js'; import type { ICommandHandler, IHandlerContext } from '../CommandRouter.js';
import { SessionEngine } from '../../engine/SessionEngine.js'; import { SessionEngine } from '../../engine/SessionEngine.js';

View File

@@ -1,4 +1,4 @@
import * as plugins from '../../tsmdb.plugins.js'; import * as plugins from '../../plugins.js';
import type { ICommandHandler, IHandlerContext, ICursorState } from '../CommandRouter.js'; import type { ICommandHandler, IHandlerContext, ICursorState } from '../CommandRouter.js';
import { AggregationEngine } from '../../engine/AggregationEngine.js'; import { AggregationEngine } from '../../engine/AggregationEngine.js';

View File

@@ -1,4 +1,4 @@
import * as plugins from '../../tsmdb.plugins.js'; import * as plugins from '../../plugins.js';
import type { ICommandHandler, IHandlerContext } from '../CommandRouter.js'; import type { ICommandHandler, IHandlerContext } from '../CommandRouter.js';
import type { IStoredDocument } from '../../types/interfaces.js'; import type { IStoredDocument } from '../../types/interfaces.js';
import { QueryEngine } from '../../engine/QueryEngine.js'; import { QueryEngine } from '../../engine/QueryEngine.js';

View File

@@ -1,4 +1,4 @@
import * as plugins from '../../tsmdb.plugins.js'; import * as plugins from '../../plugins.js';
import type { ICommandHandler, IHandlerContext, ICursorState } from '../CommandRouter.js'; import type { ICommandHandler, IHandlerContext, ICursorState } from '../CommandRouter.js';
import type { IStoredDocument } from '../../types/interfaces.js'; import type { IStoredDocument } from '../../types/interfaces.js';
import { QueryEngine } from '../../engine/QueryEngine.js'; import { QueryEngine } from '../../engine/QueryEngine.js';

View File

@@ -1,4 +1,4 @@
import * as plugins from '../../tsmdb.plugins.js'; import * as plugins from '../../plugins.js';
import type { ICommandHandler, IHandlerContext } from '../CommandRouter.js'; import type { ICommandHandler, IHandlerContext } from '../CommandRouter.js';
/** /**

View File

@@ -1,4 +1,4 @@
import * as plugins from '../../tsmdb.plugins.js'; import * as plugins from '../../plugins.js';
import type { ICommandHandler, IHandlerContext } from '../CommandRouter.js'; import type { ICommandHandler, IHandlerContext } from '../CommandRouter.js';
import { IndexEngine } from '../../engine/IndexEngine.js'; import { IndexEngine } from '../../engine/IndexEngine.js';

View File

@@ -1,4 +1,4 @@
import * as plugins from '../../tsmdb.plugins.js'; import * as plugins from '../../plugins.js';
import type { ICommandHandler, IHandlerContext } from '../CommandRouter.js'; import type { ICommandHandler, IHandlerContext } from '../CommandRouter.js';
import type { IStoredDocument } from '../../types/interfaces.js'; import type { IStoredDocument } from '../../types/interfaces.js';

View File

@@ -1,4 +1,4 @@
import * as plugins from '../../tsmdb.plugins.js'; import * as plugins from '../../plugins.js';
import type { ICommandHandler, IHandlerContext } from '../CommandRouter.js'; import type { ICommandHandler, IHandlerContext } from '../CommandRouter.js';
import type { IStoredDocument } from '../../types/interfaces.js'; import type { IStoredDocument } from '../../types/interfaces.js';
import { QueryEngine } from '../../engine/QueryEngine.js'; import { QueryEngine } from '../../engine/QueryEngine.js';

View File

@@ -1,4 +1,4 @@
import * as plugins from '../tsmdb.plugins.js'; import * as plugins from '../plugins.js';
import type { IStorageAdapter } from './IStorageAdapter.js'; import type { IStorageAdapter } from './IStorageAdapter.js';
import type { IStoredDocument, IOpLogEntry, Document } from '../types/interfaces.js'; import type { IStoredDocument, IOpLogEntry, Document } from '../types/interfaces.js';
import { calculateDocumentChecksum, verifyChecksum } from '../utils/checksum.js'; import { calculateDocumentChecksum, verifyChecksum } from '../utils/checksum.js';

View File

@@ -1,4 +1,4 @@
import type * as plugins from '../tsmdb.plugins.js'; import type * as plugins from '../plugins.js';
import type { IStoredDocument, IOpLogEntry, Document } from '../types/interfaces.js'; import type { IStoredDocument, IOpLogEntry, Document } from '../types/interfaces.js';
/** /**

View File

@@ -1,4 +1,4 @@
import * as plugins from '../tsmdb.plugins.js'; import * as plugins from '../plugins.js';
import type { IStorageAdapter } from './IStorageAdapter.js'; import type { IStorageAdapter } from './IStorageAdapter.js';
import type { IStoredDocument, IOpLogEntry, Document } from '../types/interfaces.js'; import type { IStoredDocument, IOpLogEntry, Document } from '../types/interfaces.js';

View File

@@ -1,4 +1,4 @@
import * as plugins from '../tsmdb.plugins.js'; import * as plugins from '../plugins.js';
import type { IStorageAdapter } from './IStorageAdapter.js'; import type { IStorageAdapter } from './IStorageAdapter.js';
import type { IOpLogEntry, Document, IResumeToken, ChangeStreamOperationType } from '../types/interfaces.js'; import type { IOpLogEntry, Document, IResumeToken, ChangeStreamOperationType } from '../types/interfaces.js';

View File

@@ -1,4 +1,4 @@
import * as plugins from '../tsmdb.plugins.js'; import * as plugins from '../plugins.js';
import type { Document, IStoredDocument } from '../types/interfaces.js'; import type { Document, IStoredDocument } from '../types/interfaces.js';
/** /**

View File

@@ -1,4 +1,4 @@
import type * as plugins from '../tsmdb.plugins.js'; import type * as plugins from '../plugins.js';
// ============================================================================ // ============================================================================
// Document Types // Document Types