Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Document modeling, aggregation pipeline, indexing strategy, change streams, and multi-document transactions.
.claude/skills/mongodb-patterns/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-11 | ✗→✓ | ▲ Improved | — | — |
| case-20 | ✗→✗ | = Same ✗ | — | — |
| case-04 | ✗→✗ | = Same ✗ | — | — |
| case-18 | ✗→✗ | = Same ✗ | — | — |
| case-01 | ✗→✗ | = Same ✗ | — | — |
Document database design and query optimization for MongoDB.
typescript// EMBED when: 1:1 or 1:few, data read together, child has no independent lifecycle interface Order { _id: ObjectId customerId: ObjectId status: 'pending' | 'paid' | 'shipped' items: OrderItem[] // Embedded - always read with order shippingAddress: Address // Embedded - 1:1 createdAt: Date } interface OrderItem { productId: ObjectId name: string // Denormalized - avoid join at read time price: number // Snapshot at purchase time quantity: number } // REFERENCE when: 1:many (unbounded), independent queries, shared across documents interface Product { _id: ObjectId name: string price: number categoryId: ObjectId // Reference - category queried independently reviews: never // DON'T embed - unbounded array } // Bucket pattern: group time-series data into fixed-size documents interface SensorBucket { _id: ObjectId sensorId: string startTime: Date endTime: Date count: number // Track bucket fullness measurements: { // Embed up to 200 per bucket timestamp: Date value: number }[] }
typescript// Compound index: field order matters (ESR rule) // Equality → Sort → Range db.orders.createIndex({ status: 1, // Equality: exact match filter createdAt: -1, // Sort: avoid in-memory sort total: 1 // Range: price > 100 }) // Partial index: only index documents matching filter (smaller index) db.orders.createIndex( { customerId: 1, createdAt: -1 }, { partialFilterExpression: { status: 'pending' } } ) // Text index for search db.products.createIndex({ name: 'text', description: 'text' }) // TTL index for auto-expiration db.sessions.createIndex( { createdAt: 1 }, { expireAfterSeconds: 86400 } // Auto-delete after 24h ) // Wildcard index for dynamic schemas db.events.createIndex({ 'metadata.$**': 1 })
typescript// Sales analytics: top products by revenue per category const pipeline = [ // Stage 1: Filter date range { $match: { createdAt: { $gte: new Date('2025-01-01'), $lt: new Date('2025-02-01') }, status: 'paid' }}, // Stage 2: Unwind embedded items array { $unwind: '$items' }, // Stage 3: Group by product { $group: { _id: '$items.productId', productName: { $first: '$items.name' }, totalRevenue: { $sum: { $multiply: ['$items.price', '$items.quantity'] } }, totalSold: { $sum: '$items.quantity' }, orderCount: { $addToSet: '$_id' } }}, // Stage 4: Add computed fields { $addFields: { orderCount: { $size: '$orderCount' }, avgOrderValue: { $divide: ['$totalRevenue', { $size: '$orderCount' }] } }}, // Stage 5: Sort by revenue descending { $sort: { totalRevenue: -1 } }, // Stage 6: Limit to top 20 { $limit: 20 }, // Stage 7: Lookup category details { $lookup: { from: 'products', localField: '_id', foreignField: '_id', pipeline: [{ $project: { categoryId: 1 } }], as: 'product' }} ] const results = await db.orders.aggregate(pipeline).toArray()
typescriptasync function watchOrderChanges(): Promise<void> { const pipeline = [ { $match: { operationType: { $in: ['insert', 'update'] }, 'fullDocument.status': 'paid' }} ] // resumeAfter enables resuming from last processed change (crash recovery) const changeStream = db.orders.watch(pipeline, { fullDocument: 'updateLookup', // Include full document on updates resumeAfter: await getLastResumeToken() }) changeStream.on('change', async (event) => { try { await processOrderPayment(event.fullDocument!) await saveResumeToken(event._id) // Persist for crash recovery } catch (err) { console.error('Change stream processing failed:', err) } }) changeStream.on('error', (err) => { console.error('Change stream error:', err) // Reconnect with resume token setTimeout(() => watchOrderChanges(), 5000) }) }
typescriptasync function transferFunds( fromAccountId: string, toAccountId: string, amount: number ): Promise<void> { const session = client.startSession() try { await session.withTransaction(async () => { const from = await db.accounts.findOne( { _id: new ObjectId(fromAccountId) }, { session } ) if (!from || from.balance < amount) { throw new Error('Insufficient funds') } await db.accounts.updateOne( { _id: new ObjectId(fromAccountId) }, { $inc: { balance: -amount } }, { session } ) await db.accounts.updateOne( { _id: new ObjectId(toAccountId) }, { $inc: { balance: amount } }, { session } ) await db.transactions.insertOne({ from: fromAccountId, to: toAccountId, amount, createdAt: new Date() }, { session }) }) } finally { await session.endSession() } }
explain() to verify queries use indexesmajority for critical writes (data loss risk)| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-23 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-24 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-25 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 25 cases were attempted. The headline lift of +4 percentage points is the difference between those two pass rates over the 25 comparable cases.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.