|
|
|
|
@@ -1,6 +1,6 @@
|
|
|
|
|
# @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
|
|
|
|
|
|
|
|
|
|
@@ -16,21 +16,79 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
|
|
|
|
|
|
|
|
|
|
## 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 |
|
|
|
|
|
|---------|------------|---------|
|
|
|
|
|
| **Type** | Real MongoDB (memory server) | Pure TypeScript wire protocol server |
|
|
|
|
|
| **Speed** | ~2-5s startup | ⚡ Instant startup (~5ms) |
|
|
|
|
|
| **Compatibility** | 100% MongoDB | MongoDB driver compatible |
|
|
|
|
|
| **Dependencies** | Downloads MongoDB binary | Zero external dependencies |
|
|
|
|
|
| **Replication** | ✅ Full replica set support | Single node emulation |
|
|
|
|
|
| **Use Case** | Integration testing | Unit testing, CI/CD |
|
|
|
|
|
| **Persistence** | Dump to directory | Optional file/memory persistence |
|
|
|
|
|
| Feature | SmartMongo | TsmDB | LocalTsmDb |
|
|
|
|
|
|---------|------------|-------|------------|
|
|
|
|
|
| **Type** | Real MongoDB (memory server) | Wire protocol server | Zero-config local DB |
|
|
|
|
|
| **Speed** | ~2-5s startup | ⚡ Instant (~5ms) | ⚡ Instant + auto-connect |
|
|
|
|
|
| **Compatibility** | 100% MongoDB | MongoDB driver compatible | MongoDB driver compatible |
|
|
|
|
|
| **Dependencies** | Downloads MongoDB binary | Zero external deps | Zero external deps |
|
|
|
|
|
| **Replication** | ✅ Full replica set | Single node | Single node |
|
|
|
|
|
| **Persistence** | Dump to directory | Memory or file | File-based (automatic) |
|
|
|
|
|
| **Use Case** | Integration testing | Unit testing, CI/CD | Quick prototyping, local dev |
|
|
|
|
|
|
|
|
|
|
## 🚀 Quick Start
|
|
|
|
|
|
|
|
|
|
### Option 1: SmartMongo (Real MongoDB)
|
|
|
|
|
### Option 1: LocalTsmDb (Zero-Config Local Database) ⭐ NEW
|
|
|
|
|
|
|
|
|
|
The easiest way to get started — just point it at a folder and you have a persistent MongoDB-compatible database with automatic port discovery!
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
import { LocalTsmDb } from '@push.rocks/smartmongo';
|
|
|
|
|
|
|
|
|
|
// Create a local database backed by files
|
|
|
|
|
const db = new LocalTsmDb({ folderPath: './my-data' });
|
|
|
|
|
|
|
|
|
|
// Start and get a connected MongoDB client
|
|
|
|
|
const client = await db.start();
|
|
|
|
|
|
|
|
|
|
// 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 db.stop();
|
|
|
|
|
|
|
|
|
|
// Later... data is still there
|
|
|
|
|
const db2 = new LocalTsmDb({ folderPath: './my-data' });
|
|
|
|
|
const client2 = await db2.start();
|
|
|
|
|
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
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
@@ -51,34 +109,42 @@ console.log(descriptor.mongoDbUrl); // mongodb://127.0.0.1:xxxxx/...
|
|
|
|
|
await mongo.stop();
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Option 2: TsmDB (Wire Protocol Server)
|
|
|
|
|
## 📖 LocalTsmDb API
|
|
|
|
|
|
|
|
|
|
A lightweight, pure TypeScript MongoDB-compatible server that speaks the wire protocol — use the official `mongodb` driver directly!
|
|
|
|
|
The simplest option for local development and prototyping — zero config, auto port discovery, and automatic persistence.
|
|
|
|
|
|
|
|
|
|
### Basic Usage
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
import { tsmdb } from '@push.rocks/smartmongo';
|
|
|
|
|
import { MongoClient } from 'mongodb';
|
|
|
|
|
import { LocalTsmDb } from '@push.rocks/smartmongo';
|
|
|
|
|
|
|
|
|
|
// Start TsmDB server
|
|
|
|
|
const server = new tsmdb.TsmdbServer({ port: 27017 });
|
|
|
|
|
await server.start();
|
|
|
|
|
const db = new LocalTsmDb({
|
|
|
|
|
folderPath: './data', // Required: where to store data
|
|
|
|
|
port: 27017, // Optional: defaults to auto-discovery
|
|
|
|
|
host: '127.0.0.1', // Optional: bind address
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Connect with the official MongoDB driver!
|
|
|
|
|
const client = new MongoClient('mongodb://127.0.0.1:27017');
|
|
|
|
|
await client.connect();
|
|
|
|
|
// Start and get connected client
|
|
|
|
|
const client = await db.start();
|
|
|
|
|
|
|
|
|
|
// Use exactly like real MongoDB
|
|
|
|
|
const db = client.db('myapp');
|
|
|
|
|
await db.collection('users').insertOne({ name: 'Alice', age: 30 });
|
|
|
|
|
// Access the underlying server if needed
|
|
|
|
|
const server = db.getServer();
|
|
|
|
|
const uri = db.getConnectionUri();
|
|
|
|
|
|
|
|
|
|
const user = await db.collection('users').findOne({ name: 'Alice' });
|
|
|
|
|
console.log(user); // { _id: ObjectId(...), name: 'Alice', age: 30 }
|
|
|
|
|
// Check status
|
|
|
|
|
console.log(db.running); // true
|
|
|
|
|
|
|
|
|
|
// Clean up
|
|
|
|
|
await client.close();
|
|
|
|
|
await server.stop();
|
|
|
|
|
// Stop when done
|
|
|
|
|
await db.stop();
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Features
|
|
|
|
|
|
|
|
|
|
- 🔍 **Auto Port Discovery** — Automatically finds an available port if 27017 is in use
|
|
|
|
|
- 💾 **Automatic Persistence** — Data saved to files, survives restarts
|
|
|
|
|
- 🔌 **Pre-connected Client** — `start()` returns a ready-to-use MongoDB client
|
|
|
|
|
- 🎯 **Zero Config** — Just specify a folder path and you're good to go
|
|
|
|
|
|
|
|
|
|
## 📖 SmartMongo API
|
|
|
|
|
|
|
|
|
|
### Creating an Instance
|
|
|
|
|
@@ -293,11 +359,11 @@ const server = new tsmdb.TsmdbServer({
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// File-based - persistent storage with optional checksums
|
|
|
|
|
import { FileStorageAdapter } from '@push.rocks/smartmongo/tsmdb';
|
|
|
|
|
import { tsmdb } from '@push.rocks/smartmongo';
|
|
|
|
|
|
|
|
|
|
const adapter = new FileStorageAdapter('./data/tsmdb', {
|
|
|
|
|
enableChecksums: true, // CRC32 checksums for data integrity
|
|
|
|
|
strictChecksums: false // Log warnings vs throw on mismatch
|
|
|
|
|
const server = new tsmdb.TsmdbServer({
|
|
|
|
|
storage: 'file',
|
|
|
|
|
storagePath: './data/tsmdb'
|
|
|
|
|
});
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
@@ -331,14 +397,14 @@ import { tsmdb } from '@push.rocks/smartmongo';
|
|
|
|
|
|
|
|
|
|
// For debugging, you can access the query planner
|
|
|
|
|
const planner = new tsmdb.QueryPlanner(indexEngine);
|
|
|
|
|
const plan = planner.createPlan(filter);
|
|
|
|
|
const plan = await planner.plan(filter);
|
|
|
|
|
|
|
|
|
|
console.log(plan);
|
|
|
|
|
// {
|
|
|
|
|
// type: 'IXSCAN', // or 'IXSCAN_RANGE', 'COLLSCAN'
|
|
|
|
|
// indexName: 'email_1',
|
|
|
|
|
// estimatedCost: 1,
|
|
|
|
|
// selectivity: 0.001
|
|
|
|
|
// selectivity: 0.01,
|
|
|
|
|
// indexCovering: true
|
|
|
|
|
// }
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
@@ -357,10 +423,10 @@ await wal.initialize();
|
|
|
|
|
// - Timestamp
|
|
|
|
|
// - Operation type (insert, update, delete, checkpoint)
|
|
|
|
|
// - Document data (BSON serialized)
|
|
|
|
|
// - CRC32 checksum
|
|
|
|
|
// - CRC32 checksum for integrity
|
|
|
|
|
|
|
|
|
|
// Recovery support
|
|
|
|
|
const entries = await wal.getEntriesAfter(lastCheckpointLsn);
|
|
|
|
|
const entries = wal.getEntriesAfter(lastCheckpointLsn);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 🔐 Session Management
|
|
|
|
|
@@ -393,15 +459,10 @@ try {
|
|
|
|
|
File-based storage supports CRC32 checksums to detect corruption:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
import { FileStorageAdapter } from '@push.rocks/smartmongo/tsmdb';
|
|
|
|
|
|
|
|
|
|
const adapter = new FileStorageAdapter('./data', {
|
|
|
|
|
enableChecksums: true,
|
|
|
|
|
strictChecksums: true // Throw error on corruption (vs warning)
|
|
|
|
|
});
|
|
|
|
|
import { tsmdb } from '@push.rocks/smartmongo';
|
|
|
|
|
|
|
|
|
|
// Checksums are used internally for WAL and data integrity
|
|
|
|
|
// Documents are checksummed on write, verified on read
|
|
|
|
|
// Checksums are automatically stripped before returning to client
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 📋 Supported Wire Protocol Commands
|
|
|
|
|
@@ -420,6 +481,38 @@ TsmDB supports MongoDB wire protocol versions 0-21, compatible with MongoDB 3.6
|
|
|
|
|
|
|
|
|
|
## 🧪 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' });
|
|
|
|
|
client = await db.start();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
afterAll(async () => {
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
@@ -428,7 +521,7 @@ import { MongoClient, Db } from 'mongodb';
|
|
|
|
|
|
|
|
|
|
let server: tsmdb.TsmdbServer;
|
|
|
|
|
let client: MongoClient;
|
|
|
|
|
let db: Db;
|
|
|
|
|
let testDb: Db;
|
|
|
|
|
|
|
|
|
|
beforeAll(async () => {
|
|
|
|
|
server = new tsmdb.TsmdbServer({ port: 27117 });
|
|
|
|
|
@@ -436,7 +529,7 @@ beforeAll(async () => {
|
|
|
|
|
|
|
|
|
|
client = new MongoClient('mongodb://127.0.0.1:27117');
|
|
|
|
|
await client.connect();
|
|
|
|
|
db = client.db('test');
|
|
|
|
|
testDb = client.db('test');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
afterAll(async () => {
|
|
|
|
|
@@ -445,12 +538,11 @@ afterAll(async () => {
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
|
// Clean slate for each test
|
|
|
|
|
await db.dropDatabase();
|
|
|
|
|
await testDb.dropDatabase();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
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' });
|
|
|
|
|
|
|
|
|
|
const user = await users.findOne({ name: 'Alice' });
|
|
|
|
|
@@ -462,22 +554,18 @@ test('should insert and find user', async () => {
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
|
|
|
|
import { tsmdb } from '@push.rocks/smartmongo';
|
|
|
|
|
import { MongoClient } from 'mongodb';
|
|
|
|
|
import { LocalTsmDb } from '@push.rocks/smartmongo';
|
|
|
|
|
|
|
|
|
|
let server: tsmdb.TsmdbServer;
|
|
|
|
|
let client: MongoClient;
|
|
|
|
|
let db: LocalTsmDb;
|
|
|
|
|
|
|
|
|
|
tap.test('setup', async () => {
|
|
|
|
|
server = new tsmdb.TsmdbServer({ port: 27117 });
|
|
|
|
|
await server.start();
|
|
|
|
|
client = new MongoClient('mongodb://127.0.0.1:27117');
|
|
|
|
|
await client.connect();
|
|
|
|
|
db = new LocalTsmDb({ folderPath: './test-data' });
|
|
|
|
|
await db.start();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
tap.test('should perform CRUD operations', async () => {
|
|
|
|
|
const db = client.db('test');
|
|
|
|
|
const col = db.collection('items');
|
|
|
|
|
const client = db.getClient();
|
|
|
|
|
const col = client.db('test').collection('items');
|
|
|
|
|
|
|
|
|
|
// Create
|
|
|
|
|
const result = await col.insertOne({ name: 'Widget', price: 9.99 });
|
|
|
|
|
@@ -499,8 +587,7 @@ tap.test('should perform CRUD operations', async () => {
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
tap.test('teardown', async () => {
|
|
|
|
|
await client.close();
|
|
|
|
|
await server.stop();
|
|
|
|
|
await db.stop();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export default tap.start();
|
|
|
|
|
@@ -508,6 +595,15 @@ export default tap.start();
|
|
|
|
|
|
|
|
|
|
## 🏗️ Architecture
|
|
|
|
|
|
|
|
|
|
### Module Structure
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
@push.rocks/smartmongo
|
|
|
|
|
├── SmartMongo → Real MongoDB memory server (mongodb-memory-server wrapper)
|
|
|
|
|
├── tsmdb → Wire protocol server with full engine stack
|
|
|
|
|
└── LocalTsmDb → Zero-config local database (convenience wrapper)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### TsmDB Wire Protocol Stack
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|