-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathpublication-factory.js
102 lines (84 loc) · 3.9 KB
/
publication-factory.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import { Meteor } from 'meteor/meteor'
import { check, Match } from 'meteor/check'
const isConnection = Match.Where(c => typeof c === 'object' && typeof c.publish === 'function')
const isMaybeMongoCursor = Match.Where(c => !c || c.constructor.name === 'Cursor')
const defaultErrorHandler = e => e
export const createPublicationFactory = ({ schemaFactory, mixins, onError, connection = Meteor } = {}) => {
check(connection, isConnection)
check(onError, Match.Maybe(Function))
const abstractFactoryLevelConnection = connection
const abstractFactoryLevelMixins = mixins || []
const abstractFactoryLevelOnError = onError
const isRequiredSchema = schemaFactory ? Object : Match.Maybe(Object)
return ({ name, schema, validate, run, mixins = [], onError, connection = Meteor, ...factoryArgs }) => {
check(name, String)
check(schema, isRequiredSchema)
check(validate, Match.Maybe(Function))
check(run, Function)
check(mixins, [Function])
check(connection, isConnection)
check(onError, Match.Maybe(Function))
// first we apply all mixins and create our internal "options"
const localMixins = [].concat(mixins, abstractFactoryLevelMixins)
const allArgs = Object.assign({}, { name, validate, run, mixins: localMixins, connection }, factoryArgs)
const options = applyMixins(allArgs, localMixins)
const errorHandler = onError || abstractFactoryLevelOnError || defaultErrorHandler
// connection can be overridden on factory level
const factoryLevelConnection = options.connection !== Meteor
? options.connection
: abstractFactoryLevelConnection
// ensure there is no pub with the same name and fail early
if (typeof factoryLevelConnection.server.publish_handlers[options.name] === 'function') {
throw new Error(`Publication "${options.name}" already exists`)
}
let validateFn = options.validate || (() => {})
if (!options.validate && schemaFactory) {
const validationSchema = schemaFactory(schema)
// we fallback to a plain object to support Meteor.subscribe(name)
// for schemas that contain no property: { schema: {} }
validateFn = function validate (document = {}) {
validationSchema.validate(document)
}
}
/**
* The final publication to run with arbitrary args
* @param args arbitrary number and form of arguments
* @return {Mongo.Cursor} a Mongo Cursor instance
*/
const publication = async function (...args) {
check(args, Match.Any) // make audit-all-arguments happy
const self = this
try {
validateFn(...args)
const cursor = await options.run.apply(self, args)
check(cursor, isMaybeMongoCursor)
return cursor ?? self.ready()
} catch (publicationRuntimeError) {
// if we catched an error, we need to allow to log the error or transform the error
// for example to a sanitized / client-safe version to be passed to the client
// therefore we pass the error to the errorHandler and return the result (if any) to the client
const maybeTransformedError = await errorHandler.call(self , publicationRuntimeError) || publicationRuntimeError
return self.error(maybeTransformedError)
}
}
factoryLevelConnection.publish(options.name, publication)
check(factoryLevelConnection.server.publish_handlers[options.name], Function)
return publication
}
}
function applyMixins (args, mixins) {
// Save name of the method here, so we can attach it to potential error messages
const { name } = args
mixins.forEach((mixin) => {
args = mixin(args)
if (!Match.test(args, Object)) {
const functionName = mixin.toString().match(/function\s(\w+)/)
let msg = 'One of the mixins'
if (functionName) {
msg = `The function '${functionName[1]}'`
}
throw new Error(`Error in ${name} publication: ${msg} didn't return the options object.`)
}
})
return args
}