Meteor Publications
A library to make creating powerful publications in Meteor easier to write and understand. This library also enables basic "relational" publications.
Installation
npm install --save @webantic/meteor-publications
Since there is no way to require
meteor/meteor
or meteor/mongo
from npm, you must pass this in to the module at startup:
// publication.config.js
import { Meteor } from 'meteor/meteor'
import { Mongo } from 'meteor/mongo'
import init from '@webantic/meteor-publications'
const { Publication, RelationalPublication } = init(Meteor, Mongo)
export {
Publication,
RelationalPublication
}
Usage
All publications require:
- a
name
which can be any string ornull
- an
action
which is a function that defines what data will be sent to the client.
A publication can also control access by setting one or more authenticate
hooks. Access can be further controlled by setting one or more authorize
hooks.
Finally, publications have built-in support for the simple:rest
package - options set on the rest
object will be processed and passed to the simple:rest
package.
Examples
A basic publication can be set up by supplying only a name
and action
:
const inkPub = new Publication({
name: 'inks',
action () {
return Inks.find()
}
})
The action
handlers' contexts are equivalent to that of a standard Meteor publication handler's, but with the addition of two publication helpers; this.publish
and this.relationalPublish
:
const inkPub = new Publication({
name: 'inks',
action () {
// All the standard properties and methods are available:
this.userId
this.added(/* collectionName, documentId, documentFields */)
// etc...
// Two additional methods are also available:
this.publish(/* publicationConfig */) // a helper to publish a cursor to a client
this.relationalPublish(/* publicationConfig */) // a helper to publish a cursor and related data to a client
}
})
The Publish helper
Why?
One of the great things about Meteor publications is being able to return
a cursor and have the associated documents be published to the client. When you need to publish more than one cursor from a single publication, things can get complicated quite quickly. The publish
helper abstracts away a lot of this complexity, leaving you with more expressive publications.
How?
The publish
helper takes a single object argument which contains the following properties:
- {Mongo.Collection} collection The collection to query
- {Object} selector The selector to query the collection with
- {Object} [options] An optional set of query options (limit, sort, etc..)
N.B. You must manually call this.ready()
once you have set up all your observers.
const inkPub = new Publication({
name: 'inks',
action () {
this.publish({
collection: BlueInks,
selector: {},
options: { limit: 5 }
})
this.publish({
collection: BlackInks,
selector: {},
options: { limit: 5 }
})
this.ready()
}
})
The RelationalPublish helper
Why?
Meteor's implementation of Mongo (currently) lacks support for aggregation pipelines, making it challenging to publish related data across multiple collections. This helper abstracts away the implementation, leaving more expressive publications.
How?
The relationalPublish
helper takes a single object argument which contains the same properties as the publish
helper, but with an additional one:
- {Object[]} relations An array of objects with the following keys:
- {Mongo.Collection} collection The collection where the related data resides
- {string} foreignKeyName The name of the key on the main collection which contains the identifier for the related data
- {string} [identifier] An optional identifier name on the related collection. Defaults to '_id'
- {boolean} [reactive] An optional flag to specify whether to update which related data is published when changes occur in the main collection. Defaults to
true
const penPub = new Publication({
name: 'pens',
action () {
this.relationalPublish({
collection: Pens,
selector: { inkColor: 'blue' },
relations: [{
collection: Inks, // publish related data about inks
foreignKeyName: 'inkColor', // use "inkColor" as the foreign key name on the Pens collection (always "blue" in this example)
identifier: 'color' // The field to look for on the "Inks" collection
}]
})
// This acomplishes the following:
// we publish collection.find(selector) --- Pens.find({inkColor: 'blue'})
// we take all the unique values found in the foreignKeyName ("inkColor") field of the resulting documents and publish relation.collection.find({[identifier]: {$in: <those values>}}) --- Inks.find({color: {$in: ['blue']}})
this.ready()
}
})
Authentication and Authorization
You can ensure that users are allowed access to the data a publication provides by setting authenticate
and/or authorize
hook(s):
const protectedPub = new Publication({
name: 'protected',
authenticate () {
// context is the same as `action` handler
return !!this.userId
},
// values can be either functions or arrays of functions
authorize: [authHandler, otherAuthHandler],
action () {
}
})
The context is the same in these hooks as it is in the action
handler. They also receive the same parameters.
You may either return false
or throw an error in an auth hook to prevent the execution of the action
handler.