leaonline:publication-factory

v2.0.0Published 4 months ago

Meteor Publication Factory

JavaScript Style Guide Project Status: Active – The project has reached a stable, usable state and is being actively developed. GitHub file size in bytes GitHub

Create validated Meteor publications. Lightweight. Simple.

With this package you can define factory functions to create a variety of Meteor publications. Decouples definition from instantiation (also for the schema) and allows different configurations for different types of publications.

Minified size < 2KB!

Why do I want this?

  • Decouple definition from instantiation
  • Validate publication arguments as with mdg:validated-method
  • Just pass in the schema as plain object, instead of manually instantiating SimpleSchema
  • Create mixins (similar to mdg:validated-method) on the abstract factory level, on the factory level, or both (see mixins section)
  • Fail silently in case of errors (uses the publication's error and ready), undefined cursors or unexpected returntypes

Installation

Simply add this package to your meteor packages

$ meteor add leaonline:publication-factory

Usage

Import the createPublicationFactory publication and create the factory function from it:

1import { createPublicationFactory } from 'meteor/leaonline:publication-factory'
2import { MyCollection } from '/path/to/MyCollection'
3
4const createPublication = createPublicationFactory() // no params = use defaults
5const fancyPublication = createPublication({ name: 'fancy', validate: () => {}, run: () => MyCollection.find() }) // minimal required

Under the hood it all runs like a

With schema

We support various ways to validate an input schema. To decouple schema definition from instantiation, we introduced a shemaFactory, which is basically a function that creates your schema for this collection. This also ensures, that publications don't share the same schema instances.

Using SimpleSchema

1import { createPublicationFactory } from 'meteor/leaonline:publication-factory'
2import { MyCollection } from '/path/to/MyCollection'
3import SimpleSchema from 'simpl-schema'
4
5const schemaFactory = definitions => new SimpleSchema(definitions)
6
7const createPublication = createPublicationFactory({ schemaFactory })
8createPublication({
9  name: 'fancy',
10  schema: { author: String },
11  run: function({ author }) {
12    return MyCollection.find({ author })
13  }
14})

and subcribe via

1Meteor.subscribe('fancy', { author: 'Mr.x' }) // leaving author will raise an error

As you can see, there is no need to pass a validate function as it is internally built using the schemaFactory and the given schema.

Overriding validate when using schema

You can also override the internal validate when using schema by passing a validate function. This, however, disables the schema validation and is then your responsibility:

1import { createPublicationFactory } from 'meteor/leaonline:publication-factory'
2import { MyCollection } from '/path/to/MyCollection'
3import SimpleSchema from 'simpl-schema'
4
5const schemaFactory = definitions => new SimpleSchema(definitions)
6
7const createPublication = createPublicationFactory({ schemaFactory })
8createPublication({
9  name: 'fancy',
10  schema: { author: String },
11  validate: () => {},
12  run: function({ author }) {
13    return MyCollection.find({ author })
14  }
15})

and subcribe via

1Meteor.subscribe('fancy', {}) // leaving author will NOT raise an error

If none of these cover your use case, you can still use mixins.

Using check

You can also use Meteor's builtin check and Match for schema validation:

1import { check } from 'meteor/check'
2import { MyCollection } from '/path/to/MyCollection'
3import { createPublicationFactory } from 'meteor/leaonline:publication-factory'
4
5const schemaFactory = schema => ({
6  validate (args) {
7    check(args, schema)
8  }
9})
10
11const createPublication = createPublicationFactory({ schemaFactory })
12createPublication({
13  name: 'fancy',
14  schema: { author: String },
15  run: function({ author }) {
16    return MyCollection.find({ author })
17  }
18})

Note, that some definitions for SimpleSchema and check/Match may differ.

With mixins

There are three ways to define mixins:

  • on the abstract factory function level, all publications created by the factory will contain these mixins
  • on the factory level, you basically pass mixins the a single publication
  • on both levels, where mixins from the abstract factory function are executed first; no overrides

Abstract factory level mixins

If you want a certain mixin to be included for all publications created by the factory just pass them to the createPublicationFactory function:

1import { createPublicationFactory } from 'meteor/leaonline:publication-factory'
2import { ValidatedPublication } from 'meteor/mdg:validated-publication'
3import { myDefaultMixin } from '/path/to/myDefaultMixin'
4import { MyCollection } from '/path/to/MyCollection'
5
6const createPublication = createPublicationFactory({ mixins: [myDefaultMixin] })
7createPublication({ 
8  name: 'publicationWithMixin', 
9  validate: () => {}, 
10  run: () => MyCollection.find(), 
11  foo: 'bar' // assuming your mixin requires foo 
12})

Factory level mixins

You can also define mixins for each publication. This is the same as passing mixins to the ValidatedPublication:

1import { createPublicationFactory } from 'meteor/leaonline:publication-factory'
2import { ValidatedPublication } from 'meteor/mdg:validated-publication'
3import { myDefaultMixin } from '/path/to/myDefaultMixin'
4import { MyCollection } from '/path/to/MyCollection'
5
6const createPublication = createPublicationFactory() // use defaults
7
8createPublication({ 
9  name: 'publicationWithMixin',
10  mixins: [myDefaultMixin],
11  validate: () => {}, 
12  run: () => MyCollection.find(), 
13  foo: 'bar' // assuming your mixin requires foo 
14})
15
16const publicationWithoutMixin = createPublication({
17  name: 'publicationWithoutMixin',
18  validate: () => {}, 
19  run: () => MyCollection.find(), 
20})
Use mixins on both levels

Of course you can define mixins on both levels, so that you have a certain set of default mixins and publication-specific mixins:

1import { createPublicationFactory } from 'meteor/leaonline:publication-factory'
2import { ValidatedPublication } from 'meteor/mdg:validated-publication'
3import { myDefaultMixin } from '/path/to/myDefaultMixin'
4import { someOtherMixin } from '/path/to/someOtherMixin'
5import { MyCollection } from '/path/to/MyCollection'
6
7const createPublication = createPublicationFactory({ mixins: [myDefaultMixin] })
8
9const publicationWithMixin = createPublication({ 
10  name: 'publicationWithMixin', 
11  validate: () => {}, 
12  run: () => MyCollection.find(), 
13  foo: 'bar' // assuming your mixin requires foo 
14})
15
16const publicationWithMixins = createPublication({
17  name: 'publicationWithMixin', 
18  mixins: [someOtherMixin],
19  validate: () => {}, 
20  run: () => MyCollection.find(), 
21  foo: 'bar', // assuming your mixin requires foo
22  bar: 'baz', // assuming the other mixin requires bar 
23})

Error handling

With version 1.1.0 we introduced an onError handler, that allows to log and transform errors. Sometimes errors are not thrown as Meteor.Errors and may be sent as ambiguous or confusing 500 messages to the client.

Therefore it is possible to transform errors on both, abstract factory and publication, levels.

Abstract factory level error handling

Add an onError handler to your createPublicationFactory invocation in order to handle any occurring error, including validation errors and runtime errors:

1const createPublication = createPublicationFactory({
2  onError: function(e) {
3    // if you want to log the error just pass it to your logger
4    myLogger.log(e)
5    
6    // if you want to pass this error to the client
7    // and it's a Meteor.Error, just return it
8    if (e.errorType === 'Meteor.Error') {
9      return e
10    }
11    
12    // if it is not a Meteor.Error but you still want to pass it
13    // (in this case we have a validation-error from SimpleSchema)
14    // just add the isClientSafe attribute to it
15    if (e.errorType === 'ClientError' && e.error === 'validation-error') {
16      e.isClientSafe = true
17    }
18    
19    // if you like to transform if, feel free to do so
20    e.message = 'There was an error but we won\'t tell you about it'
21    
22    // return the error on order to tell the publication to set it
23    // as error to be returned to client, however, you can also omit
24    // this and return nothing in order to let Meteor decide
25    return e
26  }
27})

Publication level error handling

You can also handle errors differently for each publication you create. Also note, that like with mixins, the local definitions override the abstract factory level definitions.

1const createPublication = createPublicationFactory({
2  onError: function(e) {
3    myLogger.logError(e)
4  }
5})
6
7const publicationWithErrorHandler = createPublication({ 
8  name: 'publicationWithMixin', 
9  validate: () => {}, 
10  run: () => MyCollection.find(), 
11  onError: function(e) {
12    // for this pub we log this error with a different priority 
13    myLogger.logFatalError(e)
14  } 
15})

Codestyle

We use standard as code style and for linting.

via npm
$ npm install --global standard snazzy
$ standard | snazzy
via Meteor npm
$ meteor npm install --global standard snazzy
$ standard | snazzy

Test

We use meteortesting:mocha to run our tests on the package.

Watch mode
$ TEST_WATCH=1 TEST_CLIENT=0 meteor test-packages ./ --driver-package meteortesting:mocha
Cli mode

License

MIT, see LICENSE