1.0.12 โ€ข Published 6 months ago

mongo-patterns v1.0.12

Weekly downloads
-
License
MIT
Repository
-
Last release
6 months ago

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 value
  • UNSET - Remove a field
  • INCREMENT - Increment a numeric field
  • MULTIPLY - Multiply a numeric field
  • PUSH - Add elements to an array
  • PULL - Remove elements from an array
  • ADD_TO_SET - Add unique elements to an array
  • POP - Remove first or last element from an array
  • MIN - Update if new value is less than current
  • MAX - Update if new value is greater than current
  • CURRENT_DATE - Set field to current date
  • RENAME - 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

  1. 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
    )
]);
  1. 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)
]);
  1. 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

  1. Keep transactions as short as possible
  2. Avoid mixing read and write operations when possible
  3. Use appropriate read/write concerns for your use case
  4. Handle transaction results appropriately
  5. Monitor retry counts and execution modes for optimization
1.0.12

6 months ago

1.0.11

6 months ago

1.0.10

6 months ago

1.0.9

6 months ago

1.0.8

7 months ago

1.0.7

7 months ago

1.0.6

7 months ago

1.0.5

8 months ago

1.0.4

8 months ago

1.0.3

8 months ago

1.0.2

8 months ago

1.0.1

8 months ago

1.0.0

8 months ago