npm package: @penname/mongoose-mongo-builder
Description: A standalone package for shaping Mongoose models with enhanced functionality
Latest version: 2.2.0

# Mongoose Model Builder

A powerful TypeScript library for shaping Mongoose models with enhanced functionality, custom static methods, and advanced aggregation capabilities.

## Overview

`@penname/mongoose-mongo-builder` provides a flexible way to extend Mongoose models with custom static methods, schema extensions, and advanced pagination/aggregation features. It uses a container-based pattern to organize model-related logic while maintaining full compatibility with Mongoose's native API.

## Features

- **Custom Static Methods**: Easily add custom static methods to your Mongoose models using a class-based container pattern
- **Schema Extensions**: Apply custom schema modifications before model creation
- **Advanced Pagination**: Built-in support for complex pagination with grouping, sorting, and population
- **Type-Safe**: Full TypeScript support with comprehensive type definitions
- **Flexible Configuration**: Optional schema extenders and custom aggregation functions
- **Proxy-Based Access**: Seamless access to both container methods and native Mongoose model methods
- **Dual Module Support**: Works with both ES modules and CommonJS

## Installation

```bash
npm install @penname/mongoose-mongo-builder mongoose
```

## Quick Start

### Basic Usage

```typescript
import { shapeModel } from '@penname/mongoose-mongo-builder';
import mongoose from 'mongoose';

// Define your schema
const UserSchema = new mongoose.Schema({
  name: { type: String, required: true },
  email: { type: String, required: true, unique: true },
  createdAt: { type: Date, default: Date.now }
});

// Create a container class with custom static methods
class UserContainer extends shapeModel.ModelContainer {
  async findByEmail(email: string) {
    return (this as any).findOne({ email }).lean().exec();
  }

  async getAllUsers() {
    return (this as any).find({}).lean().exec();
  }
}

// Shape the model
const User = shapeModel(
  {
    ModelSchema: UserSchema,
    collectionName: 'users',
    shouldRunUpdateValidators: true
  },
  UserContainer
);

// Use your shaped model
const user = await User.findByEmail('john@example.com');
const allUsers = await User.getAllUsers();
```

## API Reference

### `shapeModel(config, Container, customConfig?)`

Creates a shaped Mongoose model with custom static methods and optional enhancements.

#### Parameters

- **config** (required)
  - `ModelSchema: mongoose.Schema` - The Mongoose schema to use
  - `collectionName: string` - The MongoDB collection name
  - `shouldRunUpdateValidators?: boolean` - Enable validators on `findOneAndUpdate` operations (default: false)

- **Container** (required)
  - A class extending `shapeModel.ModelContainer` that contains your custom static methods

- **customConfig?** (optional)
  - `schemaExtender?: { extend: (schema: mongoose.Schema) => void }` - Function to extend the schema before model creation
  - `pageAggregation?: (config: PageAggregationConfig) => Promise<PageAggregationResult>` - Custom pagination/aggregation function

#### Returns

An instance of your Container class with:
- All custom methods from your Container class
- Access to the Mongoose Model via the `Model` property
- Access to the Schema via the `Schema` property
- Full proxy-based access to native Mongoose model methods

### `ModelContainer`

Base class for creating custom model containers.

#### Properties

- `Model: mongoose.Model<any>` - The underlying Mongoose model (read-only after initialization)
- `Schema: mongoose.Schema` - The Mongoose schema (read-only after initialization)
- `collection` - The MongoDB collection object
- `collectionName` - The name of the MongoDB collection
- `pageAggregation` - Custom pagination function (if provided in config)

### Types

#### `ShapeModelConfig`

```typescript
interface ShapeModelConfig {
  schemaExtender?: {
    extend: (schema: mongoose.Schema) => void;
  };
  pageAggregation?: (
    config: PageAggregationConfig
  ) => Promise<PageAggregationResult>;
}
```

#### `PageAggregationConfig`

Configuration for advanced pagination and aggregation:

```typescript
interface PageAggregationConfig {
  Model: mongoose.Model<any>;
  findQuery: { [key: string]: any };
  pager: {
    field: string;
    groupBy?: {
      fields: string[];
      sortBefore?: true;
      projectFields?: string[];
      countAs?: string;
      fieldSumMap?: Record<string, string>;
    };
    sortAsc?: boolean;
    sortOrder?: 'asc' | 'desc';
    from?: { [key: string]: any };
    limit?: number | string;
  };
  shouldSecordarySortOnId?: boolean;
  populators?: Array<{
    Model: mongoose.Model<any>;
    localField: string;
    targetField?: string;
    as?: string;
  } & (
    | { shouldReplaceRoot: true; retainedFieldMap?: Record<string, string | true> }
    | { shouldReplaceRoot?: never }
  )>;
  postPopulatorFilter?: { [key: string]: any };
  skipCache?: boolean;
}
```

#### `PageAggregationResult`

```typescript
interface PageAggregationResult<D extends DbObject = DbObject> {
  dbObjects: (D & { _retained?: Record<string, any> })[];
  from: { [key: string]: any } | null;
  total: number;
  canLoadMore: boolean;
  sortOrder: 'asc' | 'desc';
  size: number;
  limit: number;
  startedAt: string; // ISO date
  finishedAt: string; // ISO date
}
```

## Advanced Examples

### With Schema Extension

```typescript
import { shapeModel } from '@penname/mongoose-mongo-builder';
import mongoose from 'mongoose';

const UserSchema = new mongoose.Schema({
  name: String,
  email: String
});

class UserContainer extends shapeModel.ModelContainer {
  async findActive() {
    return (this as any).find({ isActive: true }).exec();
  }
}

const User = shapeModel(
  {
    ModelSchema: UserSchema,
    collectionName: 'users'
  },
  UserContainer,
  {
    schemaExtender: {
      extend: (schema) => {
        schema.add({ isActive: { type: Boolean, default: true } });
        schema.add({ lastLogin: Date });
      }
    }
  }
);
```

### With Update Validators

```typescript
const User = shapeModel(
  {
    ModelSchema: UserSchema,
    collectionName: 'users',
    shouldRunUpdateValidators: true // Enables validators on findOneAndUpdate
  },
  UserContainer
);

// Now validators will run on updates
await User.Model.findOneAndUpdate(
  { _id: userId },
  { email: newEmail }
);
```

### Accessing Native Mongoose Methods

The shaped model provides transparent access to all native Mongoose methods through a proxy:

```typescript
// Custom methods from container
const user = await User.findByEmail('john@example.com');

// Native Mongoose methods (via proxy)
const allUsers = await User.find({});
const count = await User.countDocuments();
const updated = await User.findByIdAndUpdate(id, { name: 'Jane' });

// Direct model access
const model = User.Model;
const schema = User.Schema;
const collectionName = User.collectionName;
```

## Utility Functions

### `compute(fn)`

A simple utility function that executes a function and returns its result.

```typescript
import { compute } from '@penname/mongoose-mongo-builder';

const result = compute(() => {
  // Your computation here
  return 42;
});
```

### `isFunc(value)`

Checks if a value is a function (including async functions).

```typescript
import { isFunc } from '@penname/mongoose-mongo-builder';

isFunc(() => {}); // true
isFunc(async () => {}); // true
isFunc('not a function'); // false
```

## Scripts

- `npm run build` - Build the library (ESM and CJS)
- `npm run dev` - Watch mode development build
- `npm test` - Run tests
- `npm run test:watch` - Run tests in watch mode
- `npm run test:coverage` - Generate coverage report
- `npm run test:ci` - Run tests in CI mode
- `npm run publish:patch` - Publish a patch version
- `npm run publish:minor` - Publish a minor version
- `npm run publish:major` - Publish a major version

## Requirements

- **Node.js**: 18+
- **Mongoose**: 8.7.0 or higher
- **TypeScript**: 5.6.2 or higher (for development)

## Module Support

This package supports both ES modules and CommonJS:

```typescript
// ES Module
import { shapeModel } from '@penname/mongoose-mongo-builder';

// CommonJS
const { shapeModel } = require('@penname/mongoose-mongo-builder');
```

## License

MIT

## Repository

[GitHub Repository](https://github.com/PennameHq/mongoose-model-builder)

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.