- Add the following code to your Meteor app:
import { Meteor } from 'meteor/meteor'; import { NpmModuleMongodb } from 'meteor/npm-mongo'; const { publish } = Meteor; Meteor.publish = function publishWithDDPRouter(name, fn) { Meteor.methods({ async [`__subscription__${name}`]() { const context = { ...this, ready() {}, unblock() {} }; const maybeCursorOrCursors = await fn.apply(context, arguments); const cursors = Array.isArray(maybeCursorOrCursors) ? maybeCursorOrCursors : maybeCursorOrCursors ? [maybeCursorOrCursors] : []; const cursorDescriptions = cursors.map(cursor => { const cursorDescription = cursor._cursorDescription; if (!cursorDescription) { console.error('Expected a cursor, got:', cursor); throw new Error('CursorExpectedError'); } return cursorDescription; }); // Use BSON's EJSON instead of Meteor's one and return a string to make // sure the latter won't interfere. return NpmModuleMongodb.BSON.EJSON.stringify(cursorDescriptions); }, }); return publish.apply(this, arguments); };
- Note that it has to run before any publication is registered.
- Make sure to exclude all low-level publications (i.e., ones that use
this.addedand similar APIs directly) and other publish-like packages (e.g., publish composite).
- Start Meteor with two additional flags:
DISABLE_SOCKJS=trueto disable the SockJS communication format and additional handshakes.DDP_DEFAULT_CONNECTION_URL=127.0.0.1:4000to make the browser connect through the DDP Router.
- Start DDP Router:
- Provide the required configuration in
config.tomlor in environmental variables. cargo runstarts it in a debug mode (add--releasefor release mode).- Alternatively, build it with
cargo buildand run manually.
- Provide the required configuration in
- Use the Meteor app as normal.
When the client connects to the DDP Router, the DDP Router connects to the Meteor server and forwards all DDP messages both ways. That's the base the following flows rely on.
- Client starts a subscription (
subDDP message). - DDP Router intercepts it and tries to execute
__subscription__${subscriptionName}method using the same arguments. - If it fails, DDP Router forwards the
submessage as follows.- This is needed for incompatible publications, e.g., ones that were not registered in the patched
Meteor.publish, or ones that threw an error (e.g., they used low-level publication APIs).
- This is needed for incompatible publications, e.g., ones that were not registered in the patched
- If it succeeds, DDP Router registers the subscription.
- DDP Router periodically reruns the subscriptions, updates the Mergebox, and sends corresponding DDP messages.
- It also sends the
readyDDP message after the first subscription run.
- It also sends the
- When the client stops a subscription (
unsubDDP message), DDP Router checks if it manages it and if so, unregisters the subscription and stops it (nosubDDP message).
- Server may send all sorts of live-data messages to the client (e.g.,
added) for a couple of reasons: global publications, not registered and low-level ones, and just because it wants to (some packages do that). - DDP Router intercepts those and applies them to Mergebox to make sure the client receives only the relevant messages.
- It's especially important when the same collection is published both from the DDP Router and the Meteor server.
- No resumption handling. When an error occurs either on the client or server connection, both connections are closed.
- Different
$regexdialect. PCRE2 is not feasible in Rust, so we useregex. It's mostly compatible, though. - A limited support for real-time database updates. If DDP Router can fully understand the query (including its projection, sorting, etc.) then it'll runt a Change Stream. If not, it'll fall back to pooling instead.
- Missing query operators:
$bitsAllClear,$bitsAllSet,$bitsAnyClear,$bitsAnySet,$elemMatch, and$where(not possible). - No projection operators.
- No sorting on parallel dotted paths (e.g.,
{'a.x': 1, 'a.y': 1}). - No
skip. We could support it, but we'll need to storelimit + skipdocuments in memory anyway. Maybe make a configurable limit for it?
- Missing query operators:
- Nondeterministic synchronization. In cases of multiple cursors publishing from the same collection, it may happen that instead of one
Changedmessage, DDP Router will sendRemoved+Addedpair. - Collections with
ObjectIdin the_idfield. It looks like Meteor does not useEJSONfor serializing the_idfield, but DDP Router does. Instead of patching the DDP Router, patch the Meteor app using the following code:import { MongoID } from 'meteor/mongo-id'; const idParse = MongoID.idParse; MongoID.idParse = id => (id?.$type === 'oid' ? idParse(id.$value) : idParse(id));
We use cargo-about and cargo-deny to handle 3rd party licenses.
cargo about generate -o licenses.html about.hbsgenerates a HTML summary of 3rd party licenses.cargo deny check licenseschecks all 3rd party licenses (i.e., whether they are on the allowed list).