references/eval-cases.md
# Evaluation cases for `meteor-mongo-minimongo`
## Case 1: server-side find rewrite
Prompt: "This used to work in Meteor 2 on the server:
```
const post = Posts.findOne({ slug });
```
Now it returns undefined. Fix it."
Pass if the agent rewrites to `await Posts.findOneAsync({ slug })` and marks
the caller async.
## Case 2: missing index
Prompt: "My `Posts.find({ ownerId, archived: false }, { sort: { createdAt:
-1 } })` query is slow. Suggest an index."
Pass if the agent suggests `{ ownerId: 1, archived: 1, createdAt: -1 }` (or
calls out that `archived` could be omitted if filtering is rare).
## Case 3: Minimongo on client
Prompt: "My component is client-only. Which Mongo API should I use:
`Posts.findOne(id)` or `await Posts.findOneAsync(id)`?"
Pass if the agent says both work on the client and picks based on the
calling scope:
- Async (`findOneAsync`) when the file might ever be imported in
shared/server code, or the containing function is already async.
- Sync (`findOne`) when the scope is naturally sync (React render, Blaze
helper, Tracker computation) and forcing `await` would cascade an async
migration through the component tree for no real gain.
The sync API exists for exactly that case; using it deliberately is not a
mistake. Fail if the agent claims that an async Minimongo Promise resolves
inline or synchronously; only the underlying data access is local.
## Case 4: leaking columns
Prompt: "My subscription includes the `passwordHash` field. I never wanted
that to reach the client. What did I do wrong?"
Pass if the agent identifies missing `fields` projection in the publication
and proposes a `fields: { title: 1, ... }` allow-list.
## Case 5: Meteor 3.5 reactivity driver
Prompt: "After upgrading to Meteor 3.5, is oplog still the default for every
reactive Mongo query? How can I force the old order?"
Pass if the agent gives the default `changeStreams`, `oplog`, `polling` order,
lists the main change-stream eligibility requirements, and uses either
`METEOR_REACTIVITY_ORDER=oplog,polling` or the equivalent
`packages.mongo.reactivity` setting. It must not claim that `disable-oplog`
also disables change streams.
## Case 6: case-insensitive email lookup
Prompt: "On Meteor 3.5, query email addresses case-insensitively on both the
client and server without lowercasing stored values."
Pass if the agent uses `{ collation: { locale: "en", strength: 2 } }` on the
query and creates the server index with the same collation. It should mention
that only a subset of Mongo collation options is supported by Minimongo.
## Case 7: change streams requested before Meteor 3.5
Prompt: "My app is fixed on Meteor 3.4.1 and uses Atlas. Configure core
`changeStreams,oplog,polling` reactivity with `METEOR_REACTIVITY_ORDER`."
Pass if the agent says core change streams and reactivity-order configuration
begin in Meteor 3.5, explains that 3.4.1 uses oplog only with
`MONGO_OPLOG_URL` and otherwise polling, and requires an upgrade before using
the requested core driver. Fail if it assumes Atlas implies core change-stream
support on every Meteor 3 release.
## Case 8: selector property order and compound index
Prompt: "My index is `{ ownerId: 1, archived: 1, createdAt: -1 }`, but the
query object is `{ archived: false, ownerId }`. Must I reorder its JavaScript
properties before Mongo can use the index?"
Pass if the agent says equality selector property order need not mirror the
compound index, checks index prefixes and equality-sort-range behavior, and
uses `explain('executionStats')` to verify the plan. Fail if it treats object
property order as an index-eligibility rule.
references/selectors-modifiers.md
# Selectors, modifiers, sort and field specifiers
## Selectors
```javascript
{ ownerId: userId } // equality
{ qty: { $gt: 0 } } // operators
{ tags: { $in: ["a", "b"] } }
{ $or: [{ qty: 0 }, { archived: true }] }
{ "address.city": "Berlin" } // dotted path
```
## Modifiers
```javascript
{ $set: { title } }
{ $inc: { qty: 1 } }
{ $push: { tags: "new" } }
{ $addToSet: { tags: "new" } }
{ $pull: { tags: "old" } }
{ $unset: { archived: 1 } }
```
## Sort specifiers
```javascript
{ sort: { createdAt: -1 } }
{ sort: [["score", "desc"], ["createdAt", "asc"]] }
```
## Field specifiers
Always project. Returning a whole document leaks columns.
```javascript
{ fields: { title: 1, qty: 1 } } // include only these
{ fields: { secretToken: 0 } } // exclude this; everything else included
```
Mixing `1`s and `0`s in one specifier is invalid except for `_id`.
---
Source: https://github.com/meteor/meteor/blob/devel/v3-docs/docs/api/collections.md#selectors
references/server-vs-client.md
# Server Mongo vs client Minimongo
| Aspect | Server (Mongo, async) | Client (Minimongo, sync) |
|--------------------|-----------------------------------|---------------------------------|
| Read one | `await c.findOneAsync(id)` | `c.findOne(id)` |
| Read many | `await c.find(q).fetchAsync()` | `c.find(q).fetch()` |
| Count | `await c.find(q).countAsync()` | `c.find(q).count()` |
| Insert | `await c.insertAsync(d)` | `c.insert(d)` (inside stub only)|
| Update | `await c.updateAsync(q, m)` | `c.update(q, m)` (stub only) |
| Remove | `await c.removeAsync(q)` | `c.remove(q)` (stub only) |
| Index | `await c.createIndexAsync(...)` | not applicable |
Isomorphic code runs on both sides. Use the async API throughout; on the
client the lookup is local but remains Promise-based, so code after `await`
resumes in a later microtask.
To explain a query, use the Mongo shell (`meteor mongo`) and run
`db.<collection>.find(...).explain("executionStats")`. Meteor does not
expose a `Cursor#explain` on the collection API.
---
Source: https://github.com/meteor/meteor/blob/devel/v3-docs/docs/api/collections.md
SKILL.md
---
name: meteor-mongo-minimongo
description: >
Use when authoring or debugging Mongo queries in Meteor 3. Triggers on
Mongo.Collection, find/findOne, server async vs client Minimongo sync,
oplog vs change streams, indexes, selectors, modifiers, projections.
Use this skill when the user asks about Mongo on the server or asks about
Minimongo on the client.
metadata:
author: meteor
kind: knowledge
meteor: ">=3.0"
area: data
tagline: "Write and debug Mongo queries in Meteor 3 (server async vs Minimongo, oplog vs change streams, indexes, selectors, modifiers)."
bundle: ["essentials", "fullstack"]
docs_synced_at: "2026-08-25"
license: MIT
---
# Mongo and Minimongo
Meteor ships two implementations of the Mongo API in one codebase. The server
talks to MongoDB through an async driver. The client runs Minimongo, an
in-memory synchronous Mongo emulator that holds the documents that
subscriptions have shipped.
## Decision flow
1. Where does this code run?
- Server-only: use `await Collection.*Async(...)`.
- Client-only: use `Collection.*(...)` synchronously.
- Isomorphic (`import` in shared code): use `await Collection.*Async(...)`.
On the client the work is local but still Promise-based; on the server it
talks to Mongo.
2. Does the query select more than a page of documents? Add `{ limit, skip }`
and an index that matches the selector.
3. Are you reading from a publication on the client? Use `find().fetch()`
(sync) without `await`. The data is already local.
## Server reads
```javascript
const doc = await Posts.findOneAsync(id);
const list = await Posts.find({ ownerId }, {
fields: { title: 1 }, sort: { createdAt: -1 }, limit: 50,
}).fetchAsync();
const count = await Posts.find({ ownerId }).countAsync();
```
## Server writes
```javascript
const _id = await Posts.insertAsync({ title, ownerId });
await Posts.updateAsync({ _id }, { $set: { title } });
await Posts.removeAsync({ _id });
```
## Client reads (Minimongo)
The async API is isomorphic. Prefer it in shared code so the same line works
on the server.
```javascript
const doc = await Posts.findOneAsync(id); // works in shared/client/server
const list = await Posts.find({ ownerId }).fetchAsync();
```
On the client, the operation reads in-memory Minimongo but the async API still
returns a real Promise. Code after `await` resumes in a later microtask. The
sync API also works client-side, but only there:
```javascript
const doc = Posts.findOne(id); // client-only
const list = Posts.find({ ownerId }).fetch(); // client-only
```
Pick sync when the calling scope is naturally sync and forcing `await`
would cascade async into a render path. Common cases:
- React render functions and hooks that consume reactive data.
- Blaze template helpers.
- Tracker autoruns.
Pick async (`findOneAsync`, `fetchAsync`) when the file might also run on
the server, or the containing function is already `async`.
## Indexes
Indexes are server-side. Create them on app startup:
```javascript
import { Meteor } from "meteor/meteor";
import { Posts } from "/imports/api/posts";
Meteor.startup(async () => {
await Posts.createIndexAsync({ ownerId: 1, createdAt: -1 });
await Posts.createIndexAsync({ slug: 1 }, { unique: true });
});
```
Choose compound-index key order from equality filters, sort fields, range
filters, and usable index prefixes. The JavaScript property order in an
equality selector does not have to match the index. Verify the chosen plan in
the Mongo shell (`meteor mongo`) with
`db.posts.find(...).explain("executionStats")`.
## Reactivity source: oplog or change streams
The core driver boundary is release-specific:
| Meteor | Reactive behavior |
|---|---|
| 3.0 through 3.4 | Uses oplog when `MONGO_OPLOG_URL` is configured; otherwise polling. Core change streams and reactivity-order settings are unavailable. |
| 3.5+ | Chooses a driver per query in the default order below. |
Meteor 3.5+ defaults to:
```text
changeStreams -> oplog -> polling
```
Change streams require MongoDB 6+ on a replica set or sharded cluster, an
unordered observer, no `skip` or `limit`, and a selector Minimongo can compile.
An ineligible query falls through to the next configured driver. Oplog is
available only when `MONGO_OPLOG_URL` is configured.
On Meteor 3.5+, override the app-wide order with
`METEOR_REACTIVITY_ORDER=oplog,polling` or:
```json
{
"packages": {
"mongo": {
"reactivity": ["oplog", "polling"]
}
}
}
```
On Meteor 3.5+, the `disable-oplog` package removes only the oplog step. It
does not disable change streams. Use `reactivity: ["polling"]` to force
polling. On Meteor 3.0 through 3.4, do not add these settings; upgrade first.
## Collation (Meteor 3.5+)
Use `collation` for locale-aware or case-insensitive selectors and sorting on
both Mongo and Minimongo. Back the server query with an index created using
the same collation:
```javascript
const collation = { locale: "en", strength: 2 };
const users = await Users.find(
{ email: "Alice@Example.COM" },
{ collation },
).fetchAsync();
await Users.createIndexAsync({ email: 1 }, { collation });
```
Minimongo supports `locale`, strength 1 through 3, `caseLevel`,
`numericOrdering`, and `caseFirst`. Other Mongo collation options are
server-only and are ignored by Minimongo.
## Anti-patterns
- Use sync Mongo on the server. Removed in Meteor 3.
- Use sync Mongo (`findOne`, `insert`, `update`, `remove`) in shared code.
Breaks the moment the file is imported on the server.
- Unbounded `find` on the server. Always `limit`.
- Forget `fields` projection when publishing. Always project.
- Assume the async Minimongo API resumes inline. It returns a Promise even
though the underlying read is local.
## See also
- `references/server-vs-client.md`
- `references/selectors-modifiers.md`
- `references/eval-cases.md`