Loading...
Loading...
Full-stack Meteor 3.x development with React, MongoDB, async APIs, methods, pub/sub, and GraphQL. Use this skill when working on any Meteor project — writing methods, publications, subscriptions, React data containers, collection helpers, ORM patterns, REST APIs with accounts-express, Meteor-to-React integration via useTracker/withTracker, async migration from Fibers, optimistic UI, DDP, or debugging Meteor-specific issues like circular dependencies, method stubs, and simulation errors. Trigger on: Meteor, Meteor.js, Meteor 3, MeteorJS, callAsync, useTracker, withTracker, Meteor methods, Meteor publications, Meteor subscriptions, SubsManager, Minimongo, DDP, Mongo.Collection, Meteor.Error, optimistic UI, Fibers migration, meteor async, accounts-express.
npx skill4agent add kolyasya/skills meteor-fullstack*Async| Operation | Async API (server) | Sync API (client Minimongo only) |
|---|---|---|
| Insert | | |
| Find one | | |
| Update | | |
| Upsert | | |
| Remove | | |
| Count | | |
| Fetch | | |
| forEach | | |
| map | | |
| Observe | | |
| Create index | | — |
| Send email | | |
collection.find(...)Meteor.methods({
async 'todos.create'(text) {
if (!this.userId) throw new Meteor.Error('not-authorized');
return await Todos.insertAsync({ text, createdAt: new Date(), userId: this.userId });
},
});
// Client
const id = await Meteor.callAsync('todos.create', 'Buy milk');references/methods-rpc.mdapplyAsync// Server
Meteor.publish('todos.byUser', function () {
if (!this.userId) return this.ready();
return Todos.find({ userId: this.userId });
});
// Client (React)
const { todos, isLoading } = useTracker(() => {
const handle = Meteor.subscribe('todos.byUser');
return {
isLoading: !handle.ready(),
todos: Todos.find({}, { sort: { createdAt: -1 } }).fetch(),
};
}, []);references/pubsub.mdaccounts-expressimport express from 'express';
import { WebApp } from 'meteor/webapp';
import { createAuthMiddleware } from 'meteor/accounts-express';
const app = express();
app.use('/api', createAuthMiddleware({ required: true }));
app.get('/api/me', async (req, res) => {
const user = await Meteor.userAsync();
res.json({ userId: Meteor.userId(), email: user?.emails?.[0]?.address });
});
WebApp.handlers.use(app);references/architecture.mduseTrackerwithTrackerimport { useTracker } from 'meteor/react-meteor-data';
function TodoList() {
const { todos, user } = useTracker(() => ({
todos: Todos.find().fetch(),
user: Meteor.user(),
}));
return todos.map(t => <TodoItem key={t._id} todo={t} user={user} />);
}references/react-integration.mdwithTrackermy-app/
├── client/ # Client entry, main.jsx, global styles
│ └── main.jsx # Meteor.startup(() => render(<App />))
├── server/ # Server entry, publications, startup
│ ├── main.js # Meteor.startup, indexes, seeds
│ └── publications/ # Pub definitions by domain
├── imports/ # Shared code (lazy-loaded by convention)
│ ├── api/ # Collections, methods, schemas
│ │ ├── todos/
│ │ │ ├── collection.js
│ │ │ ├── methods.js
│ │ │ └── publications.js
│ │ └── users/
│ ├── ui/ # React components
│ │ ├── components/ # Reusable UI
│ │ ├── pages/ # Route-level components
│ │ └── layouts/ # Layout wrappers
│ └── startup/ # Client/server bootstrap
├── public/ # Static assets (served as-is)
├── private/ # Server-only assets (Assets API)
├── .meteor/ # Meteor internals, packages, versions
└── package.jsonimports/client/server/imports/client/server/references/architecture.mdMeteor.Error// Server method
async 'orders.cancel'(orderId) {
const order = await Orders.findOneAsync(orderId);
if (!order) throw new Meteor.Error('not-found', 'Order not found');
if (order.userId !== this.userId) throw new Meteor.Error('not-authorized', 'Not your order');
await Orders.updateAsync(orderId, { $set: { status: 'cancelled' } });
}
// Client
try {
await Meteor.callAsync('orders.cancel', orderId);
} catch (err) {
if (err.error === 'not-found') showToast(err.reason);
}dburles:collection-helpersTodos.helpers({
isOverdue() {
return this.dueDate && this.dueDate < new Date();
},
owner() {
return Meteor.users.findOne(this.userId);
},
});
// Usage — any document from Todos.find/findOne gets these methods
const todo = Todos.findOne(id);
if (todo.isOverdue()) { /* ... */ }this.userIdMeteor.methods({
async 'projects.archive'(projectId) {
if (!this.userId) throw new Meteor.Error('not-authorized');
const project = await Projects.findOneAsync(projectId);
if (project.ownerId !== this.userId) {
throw new Meteor.Error('forbidden', 'Only the owner can archive');
}
return await Projects.updateAsync(projectId, { $set: { archived: true } });
},
});Meteor.userId()Meteor.user()// Server — async required in v3
const user = await Meteor.userAsync();
// Client — sync is fine (reads from Minimongo)
const user = Meteor.user();
const userId = Meteor.userId();
// Reactive in useTracker
const user = useTracker(() => Meteor.user(), []);
// Async client logins
await Meteor.loginWithPasswordAsync(email, password);
await Meteor.loginWithTokenAsync(token);Email.sendAsyncimport { Email } from 'meteor/email';
Meteor.methods({
async 'notifications.send'(to, subject, html) {
if (!this.userId) throw new Meteor.Error('not-authorized');
await Email.sendAsync({ from: 'noreply@example.com', to, subject, html });
},
});withTrackeruseTrackerMeteor.deferif (Meteor.isClient) return;Meteor.callMeteor.callMeteor.callAsyncElement type is invalidundefinedasyncthis.ready()Meteor.publish('items.forTeam', async function (teamId) {
const team = await Teams.findOneAsync(teamId);
if (!team.members.includes(this.userId)) return this.ready();
return Items.find({ teamId }); // sync cursor for reactivity
});find()findOneAsync()fieldsprojection// Bad — fetches every field on every matching document
const names = await Users.find({ active: true }).fetchAsync();
// Good — only pull what you need
const names = await Users.find({ active: true }, { fields: { username: 1, email: 1 } }).fetchAsync();
// In publications — reduces data pushed over DDP
Meteor.publish('todos.titles', function () {
return Todos.find({ userId: this.userId }, { fields: { text: 1, done: 1, createdAt: 1 } });
});references/performance.mdrawCollection()rawCollection()updatedAtreferences/collections-models.mdasyncthis.unblock()references/performance.mdthis.unblock()Meteor.defer()_idfieldstitlebodystatus{ profile: { name: 'Alice' } }{ profile: { age: 30 } }profileundefined// server — two publications, two DDP collection namespaces
Meteor.publish('messages.list', function (channelId) {
// Lightweight: just what the list UI needs
return Messages.find(
{ channelId },
{ fields: { authorId: 1, preview: 1, createdAt: 1 } }
);
// DDP collection name defaults to 'messages'
});
Meteor.publish('messages.full', function (messageId) {
// Use low-level API to push into a DIFFERENT client collection name
const self = this;
const doc = Messages.findOne(messageId); // or use cursor + observe
if (doc) self.added('messagesFull', doc._id, doc);
self.ready();
});// client — two separate Minimongo collections, no merge conflict
export const Messages = new Mongo.Collection('messages'); // list view
export const MessagesFull = new Mongo.Collection('messagesFull'); // detail viewMessagesFullMessagesStrippedMessagesListUse this pattern whenever: (a) you need different field shapes of the same document in the same session, (b) you have a public list projection and a richer authenticated detail projection, or (c) you've seen fields mysteriously disappear when a second subscription activates.
| File | When to read |
|---|---|
| Stubs, optimistic UI, |
| Composite publications, SubsManager, reactive counts, data flow, MergeBox / virtual collection pattern |
| |
| Schemas, helpers, indexes, aggregation, |
| Fibers→async migration, async method/publication patterns |
| Project structure, import rules, circular dependency prevention, REST API ( |
| |
asyncawaitthis.userIdMeteor.Error(errorCode, reason)npm