mcpField
M
meteor-pubsub

meteor-pubsub

github.com/meteorDevelopment10296 installsUpdated 5d ago
Repository

About meteor-pubsub

A guide to Meteor publications and subscriptions for Meteor developers. It explains how to choose reactive data delivery, apply authorization and field projection, and handle asynchronous joins.

Key features

  1. 01Choose methods or publications
  2. 02Filter by user and project fields
  3. 03Use asynchronous publish handlers
  4. 04Handle async joins with the low-level API
  5. 05Configure publication strategies

Use cases

  1. 01Publish data for authenticated users
  2. 02Limit fields sent to clients
  3. 03Implement async lookups or joins
  4. 04Control document cleanup on unsubscribe

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/meteor/agent-skills --skill meteor-pubsub

After installing, type/meteor-pubsubin Claude Code to confirm it loaded.

Bundled files

References and scripts installed along with SKILL.md.

references/
eval-cases.md
low-level-publish-api.md
publication-strategies.md

Details

From the project README

Meteor publications and subscriptions

Publications stream a set of documents to subscribed clients and keep them live. The publication is the only place server-side authorization can filter rows before they reach the client.

Decision flow

  1. Does the client need this data reactively? If no, prefer a method (one-shot read). If yes, use a publication.
  2. Is the data user-specific? Filter by this.userId inside the publish function. Without that filter, documents leak across users.
  3. Can the publication be expressed as a single cursor? Return it directly.
  4. Does it need one async lookup before choosing a cursor? Use an async publish handler, await the lookup, then return the cursor.
  5. Does it need per-document async joins, custom aggregation output, or an external reactive source? Drop to the low-level this.added / this.changed / this.removed API.

Scaffold

import { Meteor } from "meteor/meteor";
import { Items } from "/imports/api/items";

Meteor.publish("items.mine", function () {
  if (!this.userId) {
    return this.ready();
  }
  return Items.find(
    { ownerId: this.userId },
    { fields: { title: 1, qty: 1, updatedAt: 1 }, sort: { updatedAt: -1 } },
  );
});

Project fields whenever the collection contains columns the subscriber must not receive.

Async publish handlers may also return a cursor:

Meteor.publish("items.byTeam", async function (teamId) {
  const member = await Memberships.findOneAsync({
    teamId,
    userId: this.userId,
  });
  if (!member) return this.ready();
  return Items.find({ teamId }, { fields: { title: 1, qty: 1 } });
});

Meteor awaits the handler before processing the returned cursor.

Subscribing

import { Meteor } from "meteor/meteor";

const handle = Meteor.subscribe("items.mine");
// React / Blaze / Svelte hooks observe handle.ready() and the local cursor.
handle.stop();

Publication strategies

Set per-collection with Meteor.server.setPublicationStrategy. Three options:

import { DDPServer } from "meteor/ddp-server";

Meteor.server.setPublicationStrategy(
  "items",
  DDPServer.publicationStrategies.NO_MERGE,
);
StrategyWhen to use
SERVER_MERGEDefault. Tracks merged document fields across publications and sends deltas.
NO_MERGETracks sent document IDs so unsubscribe can remove them. Use when the collection is owned by one publication.
NO_MERGE_NO_HISTORYRemembers nothing and sends no removals on stop. Reserve for send-and-forget queues where stale client documents are intentional.

See references/publication-strategies.md.

Low-level publish API

For joins or async work:

Meteor.publish("feed", async function () {
  const cursor = Posts.find({}, { fields: { title: 1, authorId: 1 } });
  const observer = await cursor.observeChangesAsync({
    added: async (id, doc) => {
      const author = await Users.findOneAsync(doc.authorId, {
        fields: { username: 1 },
      });
      this.added("feed", id, { ...doc, authorName: author?.username });
    },
    changed: (id, changes) => this.changed("feed", id, changes),
    removed: (id) => this.removed("feed", id),
  });

  this.ready();
  this.onStop(() => observer.stop());
});

Note: cursor transform functions remain synchronous. Use a separate publication or the low-level API for per-document async joins.

Live observer delivery does not wait for one async callback before later changes arrive. If ordering or backpressure matters, serialize the work with a per-subscription Promise queue. Attach an error policy and stop the observer from this.onStop; see references/low-level-publish-api.md.

Anti-patterns

  • Publish without a this.userId filter when data is user-specific.
  • Publish sensitive or unnecessary columns. Add a fields projection when the full document is not part of the publication contract.
  • Confuse an async publish handler with an async cursor transform. Meteor awaits the handler Promise, but each transform callback must return a document synchronously.
  • Use cursor transforms for async joins. Transforms are synchronous; the low-level API is the right tool.
  • Subscribe to large unbounded collections. Page the data with limit/skip.

See also

  • references/publication-strategies.md
  • references/low-level-publish-api.md
  • references/eval-cases.md

Comments

Markdown supported