Instance Methods
Add custom methods to documents returned from queries.
Defining Methods
typescript
const Users = collection({
name: 'users',
schema: {
_id: field.id(),
firstName: field.string(),
lastName: field.string(),
email: field.email(),
role: field.enum(['user', 'admin']).default('user'),
},
methods: {
// First argument (doc) is auto-injected
fullName: (doc) => `${doc.firstName} ${doc.lastName}`,
isAdmin: (doc) => doc.role === 'admin',
// Methods can accept additional arguments
greet: (doc, greeting = 'Hello') => `${greeting}, ${doc.firstName}!`,
// Methods can be async
sendEmail: async (doc, subject: string, body: string) => {
await emailService.send({
to: doc.email,
subject,
body,
});
},
},
});Using Methods
Methods are available on documents returned from queries:
typescript
const user = await Users.findOne({ email: 'alice@example.com' });
if (user) {
console.log(user.fullName()); // 'Alice Smith'
console.log(user.isAdmin()); // false
console.log(user.greet('Hi')); // 'Hi, Alice!'
await user.sendEmail('Welcome!', 'Thanks for signing up.');
}Method Availability
Methods are attached to documents returned from:
findOne()findById()find().toArray()(each document)insertOne()insertMany()(each document)updateOne()findOneAndUpdate()findOneAndDelete()findOneAndReplace()
Type Safety
Methods are fully typed:
typescript
const Users = collection({
name: 'users',
schema: {
_id: field.id(),
points: field.number().default(0),
level: field.enum(['bronze', 'silver', 'gold']).default('bronze'),
},
methods: {
getLevel: (doc): 'bronze' | 'silver' | 'gold' => {
if (doc.points >= 1000) return 'gold';
if (doc.points >= 500) return 'silver';
return 'bronze';
},
addPoints: async (doc, amount: number) => {
return await Users.updateOne(
{ _id: doc._id },
{ $inc: { points: amount } }
);
},
},
});
const user = await Users.findById(id);
if (user) {
const level = user.getLevel(); // Type: 'bronze' | 'silver' | 'gold'
await user.addPoints(100); // Type-checked argument
}Common Patterns
Computed Properties
typescript
methods: {
fullName: (doc) => `${doc.firstName} ${doc.lastName}`,
age: (doc) => {
const today = new Date();
const birth = doc.birthDate;
let age = today.getFullYear() - birth.getFullYear();
if (today < new Date(today.getFullYear(), birth.getMonth(), birth.getDate())) {
age--;
}
return age;
},
displayPrice: (doc) => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: doc.price.currency,
}).format(doc.price.amount / 100);
},
}Self-Updating Methods
typescript
methods: {
markAsRead: async (doc) => {
return await Notifications.updateOne(
{ _id: doc._id },
{ $set: { readAt: new Date() } }
);
},
incrementViews: async (doc) => {
return await Posts.updateOne(
{ _id: doc._id },
{ $inc: { views: 1 } }
);
},
softDelete: async (doc) => {
return await Users.updateOne(
{ _id: doc._id },
{ $set: { deletedAt: new Date(), status: 'deleted' } }
);
},
}Relationship Loading
typescript
methods: {
getAuthor: async (doc) => {
return await Users.findById(doc.authorId);
},
getComments: async (doc) => {
return await Comments.find({ postId: doc._id }).toArray();
},
getCommentsCount: async (doc) => {
return await Comments.count({ postId: doc._id });
},
}Validation Methods
typescript
methods: {
canEdit: (doc, userId: string) => {
return doc.authorId.equals(userId) || doc.editors.includes(userId);
},
isExpired: (doc) => {
return doc.expiresAt && doc.expiresAt < new Date();
},
hasPermission: (doc, permission: string) => {
return doc.permissions.includes(permission) || doc.role === 'admin';
},
}Methods vs Statics
| Feature | Instance Methods | Static Methods |
|---|---|---|
| Access | On documents | On collection |
| First arg | Document (auto-injected) | Collection |
| Use case | Document operations | Collection operations |
| Example | user.fullName() | Users.findByEmail(email) |
Use instance methods when the operation is specific to a single document. Use static methods for operations that work with the collection as a whole.
Serialization Note
Methods are not included when you serialize a document:
typescript
const user = await Users.findOne({ email: 'alice@example.com' });
// Before serialization - methods available
user.fullName(); // Works
// After serialization - plain object, no methods
const plain = user.serialize();
plain.fullName; // undefined - methods are not serializedIf you need computed values in serialized output, compute them before serializing:
typescript
const user = await Users.findOne({ email: 'alice@example.com' });
const serialized = {
...user.serialize(),
fullName: user.fullName(),
isAdmin: user.isAdmin(),
};