monastery 1.34.0 → 1.36.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,88 +3,63 @@ let rules = require('./rules')
3
3
 
4
4
  module.exports = {
5
5
 
6
- validate: function(data, opts, cb) {
6
+ validate: async function(data, opts, cb) {
7
7
  /**
8
8
  * Validates a model
9
- * @param {instance} model
10
9
  * @param {object} data
11
10
  * @param {object} <opts>
12
- * @param {boolean(false)} update - are we validating for insert or update?
13
- * @param {array|string|false} blacklist - augment schema blacklist, `false` will remove all blacklisting
14
- * @param {array|string} projection - only return these fields, ignores blacklist
15
- * @param {array|string|false} validateUndefined - validates all 'required' undefined fields, true by
16
- * default, but false on update
17
- * @param {array|string|true} skipValidation - skip validation on these fields
18
- * @param {boolean} timestamps - whether `createdAt` and `updatedAt` are inserted, or `updatedAt` is
11
+ * @param {array|string|false} <opts.blacklist> - augment insertBL/updateBL, `false` will remove blacklisting
12
+ * @param {array|string} <opts.project> - return only these fields, ignores blacklisting
13
+ * @param {array|string|true} <opts.skipValidation> - skip validation on these fields
14
+ * @param {boolean} <opts.timestamps> - whether `createdAt` and `updatedAt` are inserted, or `updatedAt` is
19
15
  * updated, depending on the `options.update` value
16
+ * @param {boolean(false)} <opts.update> - are we validating for insert or update? todo: change to `type`
17
+ * @param {array|string|false} <opts.validateUndefined> - validates all 'required' undefined fields, true by
18
+ * default, but false on update
20
19
  * @param {function} <cb> - instead of returning a promise
21
- * @this model
22
-
23
20
  * @return promise(errors[] || pruned data{})
21
+ * @this model
24
22
  */
25
23
 
26
24
  // Optional cb and opts
27
- if (util.isFunction(opts)) { cb = opts; opts = undefined }
25
+ if (util.isFunction(opts)) {
26
+ cb = opts; opts = undefined
27
+ }
28
28
  if (cb && !util.isFunction(cb)) {
29
29
  throw new Error(`The callback passed to ${this.name}.validate() is not a function`)
30
30
  }
31
- data = util.deepCopy(data)
32
- opts = opts || {}
33
- opts.insert = !opts.update
34
- opts.action = opts.update? 'update' : 'insert'
35
- opts.skipValidation = opts.skipValidation === true? true : util.toArray(opts.skipValidation||[])
31
+ try {
32
+ data = util.deepCopy(data)
33
+ opts = opts || {}
34
+ opts.update = opts.update || opts.findOneAndUpdate
35
+ opts.insert = !opts.update
36
+ opts.skipValidation = opts.skipValidation === true ? true : util.toArray(opts.skipValidation||[])
36
37
 
37
- // Blacklist
38
- if (opts.blacklist) {
39
- let whitelist = []
40
- let blacklist = [ ...this[`${opts.action}BL`] ]
41
- if (typeof opts.blacklist === 'string') {
42
- opts.blacklist = opts.blacklist.trim().split(/\s+/)
43
- }
44
- // Auguemnt the schema blacklist
45
- for (let _path of opts.blacklist) {
46
- let path = _path.replace(/^-/, '')
47
- if (_path.match(/^-/)) whitelist.push(path)
48
- else blacklist.push(path)
49
- }
50
- // Remove whitelisted/negated fields
51
- blacklist = blacklist.filter(o => !whitelist.includes(o))
52
- // Remove any deep blacklisted fields that have a whitelisted parent specified.
53
- // E.g remove ['deep.deep2.deep3'] if ['deep'] exists in the whitelist
54
- for (let i=blacklist.length; i--;) {
55
- let split = blacklist[i].split('.')
56
- for (let j=split.length; j--;) {
57
- if (split.length > 1) split.pop()
58
- else continue
59
- if (whitelist.includes(split.join())) {
60
- blacklist.splice(i, 1)
61
- break
62
- }
63
- }
64
- }
65
- opts.blacklist = blacklist
66
- } else {
67
- opts.blacklist = [ ...this[`${opts.action}BL`] ]
68
- }
38
+ // Get projection
39
+ if (opts.project) opts.projectionValidate = this._getProjectionFromProject(opts.project)
40
+ else opts.projectionValidate = this._getProjectionFromBlacklist(opts.update ? 'update' : 'insert', opts.blacklist)
41
+
42
+ // Hook: beforeValidate
43
+ await util.runSeries(this.beforeValidate.map(f => f.bind(opts, data)))
69
44
 
70
- // Run before hook, then recurse through the model's fields
71
- return util.runSeries(this.beforeValidate.map(f => f.bind(opts, data))).then(() => {
72
- return util.toArray(data).map(item => {
45
+ // Recurse and validate fields
46
+ let response = util.toArray(data).map(item => {
73
47
  let validated = this._validateFields(item, this.fields, item, opts, '')
74
48
  if (validated[0].length) throw validated[0]
75
49
  else return validated[1]
76
50
  })
77
51
 
78
- // Success/error
79
- }).then(data2 => {
80
- let response = util.isArray(data)? data2 : data2[0]
52
+ // Single document?
53
+ response = util.isArray(data)? response : response[0]
54
+
55
+ // Success/error
81
56
  if (cb) cb(null, response)
82
57
  else return Promise.resolve(response)
83
58
 
84
- }).catch(errs => {
85
- if (cb) cb(errs)
86
- else throw errs
87
- })
59
+ } catch (e) {
60
+ if (cb) cb(e)
61
+ else throw e
62
+ }
88
63
  },
89
64
 
90
65
  _getMostSpecificKeyMatchingPath: function(object, path) {
@@ -140,7 +115,7 @@ module.exports = {
140
115
  let value = util.isArray(fields)? data : (data||{})[fieldName]
141
116
  let indexOrFieldName = util.isArray(fields)? i : fieldName
142
117
  let path2 = `${path}.${indexOrFieldName}`.replace(/^\./, '')
143
- let path3 = path2.replace(/(^|\.)[0-9]+(\.|$)/, '$2') // no numerical keys, e.g. pets.1.name
118
+ let path3 = path2.replace(/(^|\.)[0-9]+(\.|$)/, '$2') // no numerical keys, e.g. pets.1.name = pets.name
144
119
  let isType = 'is' + util.ucFirst(schema.type)
145
120
  let isTypeRule = this.rules[isType] || rules[isType]
146
121
 
@@ -157,7 +132,7 @@ module.exports = {
157
132
  }
158
133
 
159
134
  // Ignore blacklisted
160
- if (opts.blacklist.indexOf(path3) >= 0 && !schema.defaultOverride) return
135
+ if (this._pathBlacklisted(path3, opts.projectionValidate) && !schema.defaultOverride) return
161
136
  // Ignore insert only
162
137
  if (opts.update && schema.insertOnly) return
163
138
  // Ignore virtual fields
@@ -218,8 +193,8 @@ module.exports = {
218
193
  * @param {object} field - field schema
219
194
  * @param {string} path - full field path
220
195
  * @param {object} opts - original validate() options
221
- * @this model
222
196
  * @return {array} errors
197
+ * @this model
223
198
  */
224
199
  let errors = []
225
200
  if (opts.skipValidation === true) return []
package/lib/model.js CHANGED
@@ -9,8 +9,8 @@ let Model = module.exports = function(name, opts, manager) {
9
9
  * @param {string} name
10
10
  * @param {object} opts - see mongodb colleciton documentation
11
11
  * @param {boolean} opts.waitForIndexes
12
- * @this model
13
12
  * @return Promise(model) | this
13
+ * @this model
14
14
  */
15
15
  if (!(this instanceof Model)) {
16
16
  return new Model(name, opts, this)
@@ -72,7 +72,7 @@ let Model = module.exports = function(name, opts, manager) {
72
72
  this._setupFields(this.fields = Object.assign({}, this._timestampFields, this.fields))
73
73
  this.fieldsFlattened = this._getFieldsFlattened(this.fields, '') // test output?
74
74
 
75
- // Extend model with monk collection actions
75
+ // Extend model with monk collection queries
76
76
  this._collection = manager.get? manager.get(name, { castIds: false }) : null
77
77
  if (!this._collection) {
78
78
  this.info('There is no mongodb connection, a lot of the monk/monastery methods will be unavailable')
@@ -83,8 +83,7 @@ let Model = module.exports = function(name, opts, manager) {
83
83
  }
84
84
 
85
85
  // Add model to manager
86
- if (typeof this.manager[name] === 'undefined'
87
- || typeof this.manager.model[name] !== 'undefined') {
86
+ if (typeof this.manager[name] === 'undefined' || typeof this.manager.model[name] !== 'undefined') {
88
87
  this.manager[name] = this
89
88
  } else {
90
89
  this.warn(`Your model name '${name}' is conflicting, you are only able to
@@ -0,0 +1,73 @@
1
+ let MongoClient = require('mongodb').MongoClient
2
+
3
+ module.exports.open = function(uri, opts, fn) {
4
+ /*
5
+ * Monkey patch to remove db event listener warnings
6
+ * @todo remove when monk is removed
7
+ * @see https://www.mongodb.com/community/forums/t/node-44612-deprecationwarning-listening-to-events-on-
8
+ the-db-class-has-been-deprecated-and-will-be-removed-in-the-next-major-version/15849/4
9
+ */
10
+ var STATE = {
11
+ CLOSED: 'closed',
12
+ OPENING: 'opening',
13
+ OPEN: 'open'
14
+ }
15
+ MongoClient.connect(uri, opts, function (err, client) {
16
+ // this = Manager
17
+ if (err) {
18
+ this._state = STATE.CLOSED
19
+ this.emit('error-opening', err)
20
+ } else {
21
+ this._state = STATE.OPEN
22
+
23
+ this._client = client
24
+ this._db = client.db()
25
+
26
+ // set up events
27
+ var self = this
28
+ ;['authenticated', 'close', 'error', 'parseError', 'timeout'].forEach(function (eventName) {
29
+ self._client.on(eventName, function (e) {
30
+ self.emit(eventName, e)
31
+ })
32
+ })
33
+
34
+ this.emit('open', this._db)
35
+ }
36
+ if (fn) {
37
+ fn(err, this)
38
+ }
39
+ }.bind(this))
40
+ }
41
+
42
+ module.exports.findOneAndUpdate = function(query, update, opts, fn) {
43
+ /*
44
+ * Monkey patch to use returnDocument
45
+ * @todo remove when monk is removed
46
+ * @see https://github.com/Automattic/monk/blob/master/lib/collection.js#L265
47
+ */
48
+ // this = model
49
+ if (typeof opts === 'function') {
50
+ fn = opts
51
+ opts = {}
52
+ }
53
+ return this._dispatch(function findOneAndUpdate(args) {
54
+ var method = 'findOneAndUpdate'
55
+ if (typeof (args.options || {}).returnDocument === 'undefined') {
56
+ args.options.returnDocument = 'after'
57
+ }
58
+ if (args.options.replaceOne | args.options.replace) {
59
+ method = 'findOneAndReplace'
60
+ }
61
+ return args.col[method](args.query, args.update, args.options)
62
+ .then(function (doc) {
63
+ if (doc && typeof doc.value !== 'undefined') {
64
+ return doc.value
65
+ }
66
+ if (doc.ok && doc.lastErrorObject && doc.lastErrorObject.n === 0) {
67
+ return null
68
+ }
69
+ return doc
70
+ })
71
+ })({options: opts, query: query, update: update, callback: fn}, 'findOneAndUpdate')
72
+ }
73
+
package/lib/util.js CHANGED
@@ -140,14 +140,13 @@ module.exports = {
140
140
  return typeof value === 'undefined'
141
141
  },
142
142
 
143
- omit: function(obj, keys) {
144
- let target = {}
145
- for (let i in obj) {
146
- if (keys.indexOf(i) >= 0) continue
147
- if (!Object.prototype.hasOwnProperty.call(obj, i)) continue
148
- target[i] = obj[i]
143
+ omit: function(obj, fields) {
144
+ const shallowCopy = Object.assign({}, obj)
145
+ for (let i=0; i<fields.length; i+=1) {
146
+ const key = fields[i]
147
+ delete shallowCopy[key]
149
148
  }
150
- return target
149
+ return shallowCopy
151
150
  },
152
151
 
153
152
  parseData: function(obj) {
@@ -228,21 +227,24 @@ module.exports = {
228
227
  }
229
228
  },
230
229
 
231
- pluck: function(obj, keys) {
232
- let target = {}
233
- for (let prop in obj) {
234
- let match
235
- if (!Object.prototype.hasOwnProperty.call(obj, prop)) continue
236
- for (let key of keys) {
237
- if (this.isString(key) && key == prop) match = true
238
- else if (this.isRegex(key) && prop.match(key)) match = true
230
+ pick: function(obj, keys) {
231
+ // Similiar to underscore.pick
232
+ // @param {string[] | regex[]} keys
233
+ if (!this.isObject(obj) && !this.isFunction(obj)) return {}
234
+ keys = this.toArray(keys)
235
+ let res = {}
236
+ for (let key of keys) {
237
+ if (this.isString(key) && obj.hasOwnProperty(key)) res[key] = obj[key]
238
+ if (this.isRegex(key)) {
239
+ for (let key2 in obj) {
240
+ if (obj.hasOwnProperty(key2) && key2.match(key)) res[key2] = obj[key2]
241
+ }
239
242
  }
240
- if (match) target[prop] = obj[prop]
241
243
  }
242
- return target
244
+ return res
243
245
  },
244
246
 
245
- removeUndefined: (variable) => {
247
+ removeUndefined: function(variable) {
246
248
  // takes an array or object
247
249
  if (Array.isArray(variable)) {
248
250
  for (let i=variable.length; i--;) {
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "monastery",
3
3
  "description": "⛪ A straight forward MongoDB ODM built around Monk",
4
4
  "author": "Ricky Boyce",
5
- "version": "1.34.0",
5
+ "version": "1.36.1",
6
6
  "license": "MIT",
7
7
  "repository": "github:boycce/monastery",
8
8
  "homepage": "https://boycce.github.io/monastery/",