I know this is a catchy name but it’s a name used by the “lord of announcement” himself and it fits this announcement very well
So what’s this about?
After lot’s of rewrites, refactoring and abstractions I came up with some abstract factory packages that should be flexible enough to be used in nearly every project (and from older Meteor versions to the newest):
- Collection factory - create collections
- Method factory - create validated methods
- Publication factory - create publications like validated methods
- Rate-Limit factory - add methods/pubs/builtins to ratelimiter
- HTTP factory - create http routes (connext/WebApp)
- FilesCollection factory - create FilesCollection instances (work in progress!)
A simple example
All of them together can act like this huge factory that allows you to throw on your objects and just creates all you need to run your app within a few steps. Consider this simple Article
Object:
export const Article = {
name: 'article'
}
Article.collection = () => {
throw new Error('not yet created')
}
Article.schema = {
title: String,
text: String
}
Article.methods = {
create: {
name: 'article.methods.create',
schema: Article.schema,
run: function ({ title, text }) {
return Article.collection().insert({ title, text })
}
}
}
Article.publications = {
all: {
name: 'article.publications.all',
run: function () {
return Article.collection().find()
}
}
}
Article.http = {
byTitle: {
path: '/article',
method: 'get',
schema: {
title: String
}
run: function (/* req, res, next */) {
const { title } = this.data()
return Article.collection().findOne({ title })
}
}
}
A simple factory pipeline
Now let’s create our (simplified) factory pipeline:
import { createCollectionFactory } from 'meteor/leaonline:collection-factory'
import { createMethodFactory } from 'meteor/leaonline:method-factory'
import { createPublicationFactory } from 'meteor/leaonline:publication-factory'
import { createHTTPFactory } from 'meteor/leaonline:http-factory'
import { rateLimitMethods, rateLimitPublications } from 'meteor/leaonline:ratelimit-factory'
import SimpleSchema from 'simpl-schema'
// create a simple factory function for schema validation
const simpleSchemaFactory = definitions => new SimpleSchema(definitions)
// alternatively you could use check/Match:
const checkMatchFactory = schema => ({
validate (args) {
check(args, schema)
}
})
// default mixin to check permission on each method / publication
const permissionMixin = function (options) {
const runFct = options.run
options.run = function run (...args) {
if (!this.userId || !Meteor.users.findOne(this.userId))
throw new Meteor.Error('errors.permissionDenied', 'errors.userNotExists', this.userId)
return runFct.apply(this, args)
}
return options
}
// with schema = support for aldeed:collection2
const createCollection = createCollectionFactory({ schemaFactory })
// all methods and publications are now private and validate input by default
const createMethod = createMethodFactory({ schemaFactory, mixins: [permissionMixin] })
const createPublication = createPublicationFactory({ schemaFactory, mixins: [permissionMixin] })
// HTTP params/queryparams are validated via schema
const createHttpRoute = createHTTPFactory({ schemaFactory })
export const factoryPipeline = ({ name, schema, methods, publications, http }) => {
const product = {}
product.collection = createCollection({ name, schema })
const methodsInput = Object.values(methods || [])
product.methods = methods.map(methodsInput)
rateLimitMethods(methodsInput)
const publicationsInput = Object.values(publications || [])
product.publications = publications.map(publicationsInput)
rateLimitPublications(publicationsInput)
const httpInput = Object.values(http || [])
product.http = http.map(createHttpRoute)
return product
}
Note: If you wonder, why you first need to create the factories - it allows to create different factories, where each has a certain set of default configuration (think of one for all public methods and one for all methods for logged-in users), which themselves can then even further be configured by the input.
Putting all together
From here we can now simply import our definition Objects at startup and throw them into the factory pipline:
import { Article } from '/path/to/article'
import { factoryPipeline } from '/path/to/factoryPipeline'
export { runRateLimiter } from 'meteor/leaonline:ratelimit-factory'
const allInputs = [Article]
// here we not only create the products but also
// add the collection to the Objects to make them accessible
// in thier run functions of their methods / publications / http routes
allInputs.forEach(input => {
const product = factoryPipeline(input)
const { collection } = product
input.collection = () => collection
})
// provide a callback that is used when rate-limit has been exceeded
runRateLimiter(function callback (reply, input) {
if (reply.allowed) {
// ...
} else {
// log exceeding, if you like here
console.log('rate limit exceeded')
console.log(reply)
console.log(input)
}
})
Why all this?
I know this has been already invented in similar packages and projects but I always had the issue, that they
- had many dependencies
- were not flexible / extendible enough for edge cases
- contain lots of code
- all have different API designs, making it hard to switch between them
- cannot easily replaced in case of abandonment
I made these packages fix the mentioned issues and I hope they do it for you, too!
What you can do
It would be great to get any feedback on these packages, especially if they are flexible enough to cover your use-cases. Contributions are always welcome and I would be super happy to see others using these packages in their projects.