mongo-patterns v1.0.12
MongoDB Repository Pattern
A flexible and type-safe MongoDB repository pattern implementation for TypeScript applications. This package provides a robust abstraction layer for MongoDB operations with a powerful criteria builder for querying and mutation builder for updates.
Features
- ๐ฏ Type-safe MongoDB operations
- ๐ Powerful criteria builder for complex queries
- ๐ Flexible mutation builder for updates and upserts
- ๐ฆ Connection management for multiple databases
- ๐ ๏ธ Repository pattern implementation
- โก Optimized for performance
- ๐งช Well tested with unit and integration tests
- ๐ Support for transaction-like operations
- ๐จ Clean and maintainable code structure
Installation
npm install mongo-patterns
Quick Start
import { MongoDBRepository, Criteria, Mutation, MongoDBConnectionManager } from 'mongo-patterns';
// Define your entity type
interface User {
id: string;
name: string;
email: string;
age: number;
}
// Create your repository
class UserRepository extends MongoDBRepository<User> {
constructor(db: Db) {
super(db, 'users');
}
}
// Connect to MongoDB
const db = await MongoDBConnectionManager.connect({
uri: 'mongodb://localhost:27017',
dbName: 'myapp'
});
// Initialize repository
const userRepo = new UserRepository(db);
// Create a user
const user = await userRepo.create({
id:"1",
name: 'John Doe',
email: 'john@example.com',
age: 30
});
// Find users using criteria
const users = await userRepo.findMany(
Criteria.create<User>()
.where('age', 'GREATER_THAN', 25)
.andWhere('name', 'LIKE', 'John')
.orderBy('name', 'ASC')
.take(10)
.skip(0)
);
Criteria Builder
The Criteria Builder provides a fluent interface for building complex queries:
const criteria = Criteria.create<User>()
.where('age', 'GREATER_THAN', 18)
.andWhere('status', 'IN', ['active', 'pending'])
.orderBy('createdAt', 'DESC')
.take(10)
.skip(0);
Available operators:
EQUAL
GREATER_THAN
LESS_THAN
GREATER_THAN_OR_EQUAL
LESS_THAN_OR_EQUAL
NOT_EQUAL
IN
NOT_IN
LIKE
BETWEEN
Mutation Builder
The Mutation Builder provides a powerful interface for building update operations:
// Simple update
const updateMutation = Mutation.update<User>()
.set('name', 'John Doe')
.increment('age', 1)
.push('tags', 'new-tag');
// Upsert operation
const upsertMutation = Mutation.upsert<User>()
.set('email', 'john@example.com')
.set('status', 'active');
// Array operations
const arrayMutation = Mutation.update<User>()
.push('roles', ['admin'], { $position: 0 })
.addToSet('permissions', ['read', 'write'])
.pull('removedRoles', 'guest');
Available mutation operators:
SET
- Set a field valueUNSET
- Remove a fieldINCREMENT
- Increment a numeric fieldMULTIPLY
- Multiply a numeric fieldPUSH
- Add elements to an arrayPULL
- Remove elements from an arrayADD_TO_SET
- Add unique elements to an arrayPOP
- Remove first or last element from an arrayMIN
- Update if new value is less than currentMAX
- Update if new value is greater than currentCURRENT_DATE
- Set field to current dateRENAME
- Rename a field
Array modifiers for PUSH operations:
$each
- Add multiple elements$position
- Insert at specific position$slice
- Limit array size after operation$sort
- Sort array after operation
Connection Management
The package includes a robust connection management system:
// Connect to multiple databases
const db1 = await MongoDBConnectionManager.connect({
uri: 'mongodb://localhost:27017',
dbName: 'db1'
}, 'client1');
const db2 = await MongoDBConnectionManager.connect({
uri: 'mongodb://localhost:27017',
dbName: 'db2'
}, 'client2');
// Get database instances
const db1Instance = MongoDBConnectionManager.getDb('client1');
const db2Instance = MongoDBConnectionManager.getDb('client2');
// Disconnect
await MongoDBConnectionManager.disconnect('client1');
await MongoDBConnectionManager.disconnectAll();
Repository Operations
The repository pattern provides standard CRUD operations:
// Create
const created = await repo.create(document);
const manyCreated = await repo.createMany([doc1, doc2]);
// Read
const one = await repo.findOne(criteria);
const many = await repo.findMany(criteria);
// Update with Mutation Builder
const updateMutation = Mutation.update<User>()
.set('status', 'active')
.increment('loginCount', 1);
const updated = await repo.updateOne(criteria, updateMutation);
// Upsert with Mutation Builder
const upsertMutation = Mutation.upsert<User>()
.set('email', 'john@example.com')
.set('createdAt', new Date());
const upserted = await repo.updateOne(criteria, upsertMutation);
// Delete
const deleted = await repo.deleteOne(criteria);
const deletedMany = await repo.deleteMany(criteria);
// Check existence
const exists = await repo.exists(criteria);
Project Structure
.
โโโ src/
โ โโโ core/ # Core functionality
โ โโโ domain/ # Domain interfaces
โ โโโ infrastructure/ # Implementation
โ โ โโโ mongodb/
โ โโโ types/ # Type definitions
โโโ tests/
โ โโโ integration/ # Integration tests
โ โโโ unit/ # Unit tests
Running Tests
# Install dependencies
npm install
# Run all tests
npm test
# Run tests with coverage
npm run test:coverage
# Run tests in watch mode
npm run test:watch
Configuration
The package supports various MongoDB connection options:
interface MongoDBConfig {
uri: string;
dbName: string;
options?: {
maxPoolSize?: number;
minPoolSize?: number;
retryWrites?: boolean;
connectTimeoutMS?: number;
socketTimeoutMS?: number;
ssl?: boolean;
replicaSet?: string;
authSource?: string;
};
}
Development
# Build the project
npm run build
# Run tests
npm test
MongoDB Transactions
The library provides robust transaction support through the MongoDBTransactionManager
class, which implements advanced retry strategies and error handling for MongoDB transactions.
Features
- Parallel and Sequential transaction execution strategies
- Exponential backoff retry mechanism
- Automatic handling of transient errors and deadlocks
- Configurable transaction options
- Support for mixed operation types within transactions
Basic Usage
const transactionManager = new MongoDBTransactionManager(client, db);
// Execute multiple operations in a transaction
const result = await transactionManager.executeTransaction([
(session) => repository.create(entity1, { session }),
(session) => repository.updateOne(criteria, mutation, session),
(session) => repository.deleteOne(criteria, { session })
]);
if (result.success) {
console.log('Transaction completed successfully');
} else {
console.error('Transaction failed:', result.error);
}
Configuration Options
const customTransactionManager = new MongoDBTransactionManager(client, db, {
maxRetries: 3,
initialRetryDelay: 100,
maxRetryDelay: 1000,
readPreference: 'primary',
readConcern: 'snapshot',
writeConcern: {
w: 'majority',
j: true,
wtimeout: 5000
}
});
Example Scenarios
- Handling Concurrent Updates
const result = await transactionManager.executeTransaction([
(session) => repository.updateOne(
Criteria.create<Entity>().where('name', 'EQUAL', 'example'),
Mutation.update<Entity>().set('value', newValue),
session
)
]);
- Multiple Operations Across Collections
const result = await transactionManager.executeTransaction([
(session) => repository1.create(entity1, { session }),
(session) => repository2.create(entity2, { session }),
(session) => repository1.updateOne(criteria, mutation, session)
]);
- Handling Deadlocks The transaction manager automatically handles deadlocks by retrying in sequential mode:
const result = await transactionManager.executeTransaction([
(session) => repository.create(entity1, { session }),
(session) => repository.updateOne(criteria, mutation, session)
]);
// If a deadlock occurs, the transaction will:
// 1. Retry in sequential mode
// 2. Apply exponential backoff
// 3. Return success/failure status with retry information
Error Handling
The transaction manager provides detailed error information:
const result = await transactionManager.executeTransaction([/*...*/]);
console.log({
success: result.success,
executionMode: result.executionMode, // 'parallel' or 'sequential'
retries: result.retries, // number of retry attempts
error: result.error // error details if failed
});
Best Practices
- Keep transactions as short as possible
- Avoid mixing read and write operations when possible
- Use appropriate read/write concerns for your use case
- Handle transaction results appropriately
- Monitor retry counts and execution modes for optimization