Skip to content

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: number

Common 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

FeatureStatic MethodsInstance Methods
Called onCollectionDocument
First argCollection (auto-injected)Document (auto-injected)
Use caseQueries, aggregationsDocument operations
ExampleUsers.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();
  },
}

Released under the MIT License.