jkuester:publication-factory

v2.0.0Published 4 years ago

This package has not had recent updates. Please investigate it's current state before committing to using it in your project.

Project Status: Active – The project has reached a stable, usable state and is being actively developed. Build Status GitHub

Super flexible and lightweight factory for creating publications using custom validators.

Principle

First and foremost you want your publications to be created from config objects (similar to mdg:validated-method).

But of course, you want strong security for your publications but also flexibility to choose, which technologies you use to enforce this security.

Therefore, you write custom validators that apply for all publications, that are created using a factory instance. You basically abstract your validations away, so your publications will only need configuration.

At the same time you want to focus your publication logic on the data layer, no checks, validations etc. Therefore, you add a run function to your config, that only cares about which data to query from which collection and how to transform if, finally returning a cursor.

That's it - separation of concerns together with a flexible dependency injection.

Installation

1meteor add jkuester:publication-factory

Changelog

2.0.0

  • Major change using generic validators
  • No binding to external packages

Documentation

The factory is a configurable class, so you can create different factories using different configurations.

Minimal example

You can for example leave the config empty to create a minimal factory:

1const factory = new PublicationFactory()
2factory.create({
3  name: 'allMyDocuments',
4  run: function({ limit }) {
5    const createdBy = this.userId
6    return MyCollection.find({}, { createdBy, limit })
7  }
8})

In this base configuration there is no validation at all. If you want to add custom validations, you need to define custom validators and add them to the factory configuration.

Writing Validators

A validator is a function that receives the create options and returns a validation function based on these options:

1({}) => (...*) => undefined|throw

The validation function should throw an Error if validation fails, otherwise return undefined / no return.

The following example defines a validator for an arguments schema and for user logged-in status:

1const validateArgs = function({ schema }) {
2  if (!schema) throw new Error('expected schema') // makes the field required
3  const schema = new SimpleSchema(schema)
4  return function validate (...args) {
5    schema.validate(...args)
6  }
7}
8
9const validateUser = function({ name, isPublic }) {
10  if (isPublic) return
11  return function validate () {
12    // this-context will be bound to the publication function
13    // so we can access the user as in any normal publication
14    const { userId } = this
15    if (!userId || !Meteor.users.findOne(userId)) {
16      throw new Meteor.Error(403, 'publication.permissionDenied', name)
17    }
18  }
19}
20
21const validators = [ validateArgs, validateUser ]
22const factory = new PublicationFactory({ validators })
23factory.create({
24  name: 'allMyDocuments',
25  isPublic: false,
26  schema: { limit: Number },
27  run: function({ limit }) {
28    const createdBy = this.userId
29    return MyCollection.find({}, { createdBy, limit })
30  }
31})

Full Example

The following example creates a new publication and uses a custom validation for a user to have certain roles. This involves a third party package (alanning:roles) and you are not bound to use this specific package, since the validators are up to you.

First we create the validators:

1const validateArgs = function() {
2  return function validate (...args) {
3    if (args.length > 0) throw new Error('expected no args')
4  }
5}
6
7const validateRoles = function({ roles, group }) {
8  if (!roles) return
9  
10 return function validate () {
11   const { userId } = this
12   if (!userId || !Roles.userIsInRoles(userId)) {
13     throw new Meteor.Error(403, 'publication.permissionDenied', name)
14   }
15 }
16}

Second, we create the factory while injecting the validators:

1const validators = [validateRoles]
2const factory = new PublicationFactory({ validators })

Third, we create a specific publication using the factory:

1const Cars = new Mongo.Collection('cars');
2const prototypeCars = factory.create({
3  name: 'prototypeCars',
4  roles: ['camViewPrototypes'],
5  group: 'researchers',
6  run: function() {
7    return Cars.find({ isPrototype: true })
8  }
9})
10
11// note that we could automatically publish using
12// the `publish` flag in the config for factory.create
13Meteor.publish('prototypeCars', prototypeCars)

Testing

You can run the tests using the test script or using the following bash line:

$ METEOR_PACKAGE_DIRS=../ TEST_WATCH=1 meteor test-packages ./ --driver-package meteortesting:mocha

Contribution

Every contribution is very welcome. Feel free to raise an issue, if there is any problem with this package.

License

MIT, see license file