@penname/mongoose-mongo-builder
v2.2.0
Published
A standalone package for shaping Mongoose models with enhanced functionality
Readme
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
npm install @penname/mongoose-mongo-builder mongooseQuick Start
Basic Usage
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('[email protected]');
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 usecollectionName: string- The MongoDB collection nameshouldRunUpdateValidators?: boolean- Enable validators onfindOneAndUpdateoperations (default: false)
Container (required)
- A class extending
shapeModel.ModelContainerthat contains your custom static methods
- A class extending
customConfig? (optional)
schemaExtender?: { extend: (schema: mongoose.Schema) => void }- Function to extend the schema before model creationpageAggregation?: (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
Modelproperty - Access to the Schema via the
Schemaproperty - 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 objectcollectionName- The name of the MongoDB collectionpageAggregation- Custom pagination function (if provided in config)
Types
ShapeModelConfig
interface ShapeModelConfig {
schemaExtender?: {
extend: (schema: mongoose.Schema) => void;
};
pageAggregation?: (
config: PageAggregationConfig
) => Promise<PageAggregationResult>;
}PageAggregationConfig
Configuration for advanced pagination and aggregation:
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
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
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
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:
// Custom methods from container
const user = await User.findByEmail('[email protected]');
// 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.
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).
import { isFunc } from '@penname/mongoose-mongo-builder';
isFunc(() => {}); // true
isFunc(async () => {}); // true
isFunc('not a function'); // falseScripts
npm run build- Build the library (ESM and CJS)npm run dev- Watch mode development buildnpm test- Run testsnpm run test:watch- Run tests in watch modenpm run test:coverage- Generate coverage reportnpm run test:ci- Run tests in CI modenpm run publish:patch- Publish a patch versionnpm run publish:minor- Publish a minor versionnpm 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:
// ES Module
import { shapeModel } from '@penname/mongoose-mongo-builder';
// CommonJS
const { shapeModel } = require('@penname/mongoose-mongo-builder');License
MIT
Repository
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
