Static Methods
Add custom methods to the collection itself for reusable queries and operations.
Defining Statics
typescript
const Users = collection({
name: 'users',
schema: {
_id: field.id(),
name: field.string(),
email: field.email(),
role: field.enum(['user', 'admin', 'moderator']),
status: field.enum(['active', 'inactive', 'banned']),
},
statics: {
// First argument (col) is auto-injected (the collection instance)
findByEmail: (col, email: string) => {
return col.findOne({ email });
},
findAdmins: (col) => {
return col.find({ role: 'admin' }).toArray();
},
countByRole: async (col) => {
const results = await col.aggregate([
{ $group: { _id: '$role', count: { $sum: 1 } } },
]).toArray();
return Object.fromEntries(
results.map((r) => [r._id, r.count])
);
},
// Complex queries with multiple parameters
search: (col, query: string, options?: { role?: string; limit?: number }) => {
const filter: any = { $text: { $search: query } };
if (options?.role) filter.role = options.role;
return col.find(filter)
.limit(options?.limit ?? 20)
.toArray();
},
},
});Using Statics
Static methods are called directly on the collection:
typescript
// Find by email
const user = await Users.findByEmail('alice@example.com');
// Get all admins
const admins = await Users.findAdmins();
// Count by role
const stats = await Users.countByRole();
// { user: 150, admin: 5, moderator: 10 }
// Search with options
const results = await Users.search('alice', { role: 'user', limit: 10 });Type Safety
Static methods are fully typed:
typescript
const Users = collection({
name: 'users',
schema: {
_id: field.id(),
email: field.email(),
status: field.enum(['active', 'pending', 'banned']),
},
statics: {
findActiveByEmail: async (col, email: string) => {
return col.findOne({ email, status: 'active' });
},
bulkActivate: async (col, ids: string[]) => {
const objectIds = ids.map(id => new ObjectId(id));
return col.updateMany(
{ _id: { $in: objectIds } },
{ $set: { status: 'active' } }
);
},
},
});
// TypeScript knows the return types
const user = await Users.findActiveByEmail('alice@example.com');
// user: User | null
const count = await Users.bulkActivate(['id1', 'id2', 'id3']);
// count: numberCommon Patterns
Query Builders
typescript
statics: {
findActive: (col) => col.find({ status: 'active' }),
findRecent: (col, days = 7) => {
const since = new Date();
since.setDate(since.getDate() - days);
return col.find({ createdAt: { $gte: since } });
},
findByRole: (col, role: string) => col.find({ role }),
}
// Usage - returns cursor for chaining
const users = await Users.findActive()
.sort({ createdAt: -1 })
.limit(10)
.toArray();Aggregation Helpers
typescript
statics: {
getStats: async (col) => {
const [result] = await col.aggregate([
{
$group: {
_id: null,
total: { $sum: 1 },
active: { $sum: { $cond: [{ $eq: ['$status', 'active'] }, 1, 0] } },
avgAge: { $avg: '$age' },
},
},
]).toArray();
return result;
},
getMonthlySignups: async (col) => {
return col.aggregate([
{
$group: {
_id: {
year: { $year: '$createdAt' },
month: { $month: '$createdAt' },
},
count: { $sum: 1 },
},
},
{ $sort: { '_id.year': -1, '_id.month': -1 } },
]).toArray();
},
}Business Logic
typescript
statics: {
createWithDefaults: async (col, data: { name: string; email: string }) => {
return col.insertOne({
...data,
role: 'user',
status: 'pending',
settings: {
notifications: true,
theme: 'light',
},
});
},
deactivateInactiveUsers: async (col, inactiveDays = 90) => {
const cutoff = new Date();
cutoff.setDate(cutoff.getDate() - inactiveDays);
return col.updateMany(
{ lastLoginAt: { $lt: cutoff }, status: 'active' },
{ $set: { status: 'inactive' } }
);
},
mergeAccounts: async (col, primaryId: string, secondaryId: string) => {
return col.transaction(async (session) => {
const secondary = await col.findById(secondaryId);
if (!secondary) throw new Error('Secondary account not found');
// Merge data into primary
await col.updateOne(
{ _id: primaryId },
{
$addToSet: { mergedEmails: secondary.email },
$inc: { points: secondary.points },
},
{ session }
);
// Delete secondary
await col.deleteOne({ _id: secondaryId }, { session });
return col.findById(primaryId);
});
},
}Pagination Helpers
typescript
statics: {
paginated: async (
col,
filter: object,
options: { page?: number; limit?: number; sort?: object } = {}
) => {
const { page = 1, limit = 20, sort = { createdAt: -1 } } = options;
return col.find(filter)
.sort(sort)
.paginate({ page, limit });
},
searchPaginated: async (col, query: string, page = 1) => {
return col.find({ $text: { $search: query } })
.sort({ score: { $meta: 'textScore' } })
.paginate({ page, limit: 20 });
},
}Statics vs Methods
| Feature | Static Methods | Instance Methods |
|---|---|---|
| Called on | Collection | Document |
| First arg | Collection (auto-injected) | Document (auto-injected) |
| Use case | Queries, aggregations | Document operations |
| Example | Users.findByEmail(email) | user.fullName() |
Use static methods for:
- Custom queries and filters
- Aggregation pipelines
- Bulk operations
- Business logic involving multiple documents
Use instance methods for:
- Computed properties
- Single document operations
- Document-specific logic
Accessing Raw Collection
Static methods receive the Monch collection, but you can access the raw MongoDB collection if needed:
typescript
statics: {
rawAggregate: async (col, pipeline: object[]) => {
const raw = await col.collection; // Raw MongoDB collection
return raw.aggregate(pipeline).toArray();
},
}