monastery 1.36.0 → 1.36.3

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.
@@ -0,0 +1,42 @@
1
+ ---
2
+ title: findOneAndUpdate
3
+ parent: Model
4
+ ---
5
+
6
+ # `model.findOneAndUpdate`
7
+
8
+ Find a document and update it in one atomic operation (unless using `opt.populate`), requires a write lock for the duration of the operation. Calls the following model hooks: `beforeUpdate`, `afterUpdate`, `afterFind`.
9
+
10
+ ### Arguments
11
+
12
+ Same argument signatures as [`model.find`](./find) and [`model.update`](./update).
13
+
14
+ ### Returns
15
+
16
+ A promise if no callback is passed in.
17
+
18
+ ### Example
19
+
20
+ ```js
21
+ await user.findOneAndUpdate({
22
+ query: { name: "Martin" },
23
+ data: { name: "Martin2" },
24
+ })
25
+ // { name: 'Martin2', ... }
26
+
27
+ // You can return a populated model which isn't atomic
28
+ await user.findOneAndUpdate({
29
+ query: { name: "Martin" },
30
+ data: { name: "Martin2" },
31
+ populate: ['pet'],
32
+ })
33
+ // { name: 'Martin2', pet: {...}, ... }
34
+
35
+ // Blacklisting prunes the data and returned document
36
+ await user.findOneAndUpdate({
37
+ query: { name: "Martin" },
38
+ data: { name: "Martin2", age: 100 },
39
+ blacklist: ['age'],
40
+ })
41
+ // { name: 'Martin2', ... }
42
+ ```
@@ -8,9 +8,9 @@ has_children: true
8
8
 
9
9
  Created via [`manager.model`](../manager/model).
10
10
 
11
- #### Monk collection instance methods
11
+ #### Monk collection instance operators
12
12
 
13
- Additionally models inherit most of the [monk collection](https://automattic.github.io/monk/docs/collection/) instance methods which are available under `model`.
13
+ Additionally models inherit most of the [monk collection](https://automattic.github.io/monk/docs/collection/) instance operators which are available under `model`.
14
14
 
15
15
  * model.[_aggregate](https://automattic.github.io/monk/docs/collection/aggregate.html)
16
16
  * model.[_bulkWrite](https://automattic.github.io/monk/docs/collection/bulkWrite.html)
@@ -5,16 +5,17 @@ parent: Model
5
5
 
6
6
  # `model.insert`
7
7
 
8
- Validate and insert document(s) in a collection and call related hooks: `schema.beforeInsert`, `schema.afterInsert`
8
+ Validate and insert document(s) in a collection and calls model hooks: `beforeInsert`, `afterInsert`
9
9
 
10
10
  ### Arguments
11
11
 
12
12
  `options` *(object)*
13
13
 
14
- - `options.data` *(object\|array)* - Data that is validated against the model schema. Key names can be in dot or bracket notation which is handy for HTML FormData.
15
- - [`options.skipValidation`] (string\|array): skip validation for this field name(s)
16
- - [`options.timestamps`] *(boolean)*: whether `createdAt` and `updatedAt` are automatically inserted, defaults to `manager.timestamps`
17
- - [`options.blacklist`] *(array\|string\|false)*: augment `schema.insertBL`. `false` will remove all blacklisting
14
+ - `data` *(object\|array)* - Data that is validated against the model fields. Key names can be in dot or bracket notation which is handy for HTML FormData.
15
+ - [[`blacklist`](#blacklisting)] *(array\|string\|false)*: augment `definition.insertBL`. `false` will remove all blacklisting
16
+ - [`project`] *(string\|array\|object)*: project these fields, ignores blacklisting
17
+ - [`skipValidation`] (string\|array): skip validation for this field name(s)
18
+ - [`timestamps`] *(boolean)*: whether `createdAt` and `updatedAt` are automatically inserted, defaults to `manager.timestamps`
18
19
  - [[`any mongodb option`](http://mongodb.github.io/node-mongodb-native/3.2/api/Collection.html#insert)] *(any)*
19
20
 
20
21
  [`callback`] *(function)*: pass instead of return a promise
@@ -32,32 +33,33 @@ user.insert({ data: [{ name: 'Martin Luther' }, { name: 'Bruce Lee' }]})
32
33
 
33
34
  ### Blacklisting
34
35
 
35
- You can augment the model's `schema.insertBL` blacklist by passing a custom `blacklist`:
36
+ You can augment the model's blacklist (`definition.insertBL`) by passing a custom `blacklist`:
36
37
 
37
38
  ```js
38
39
  // Prevents `name` and `pets.$.name` (array) from being returned.
39
40
  user.insert({ data: {}, blacklist: ['name', 'pets.name'] })
40
- // You can also whitelist any blacklisted fields found in schema.insertBL
41
+ // You can also whitelist any blacklisted fields found in definition.insertBL
41
42
  user.insert({ data: {}, blacklist: ['-name', '-pet'] })
42
43
  ```
43
44
 
44
45
  ### Defaults example
45
46
 
46
- When defaultObjects is enabled, undefined subdocuments and arrays will default to `{}` `[]` respectively when inserting. You can enable `defaultObjects` via the [manager options](../manager#arguments).
47
+ When defaultObjects is enabled, undefined embedded documents and arrays will default to `{}` `[]` respectively when inserting. You can enable `defaultObjects` via the [manager options](../manager#arguments).
47
48
 
48
49
  ```js
49
- db.model({ fields: {
50
- names: [{ type: 'string' }],
51
- pets: {
52
- name: { type: 'string' },
53
- colors: [{ type: 'string' }]
50
+ db.model({
51
+ fields: {
52
+ names: [{ type: 'string' }],
53
+ pets: {
54
+ name: { type: 'string' },
55
+ colors: [{ type: 'string' }]
56
+ }
54
57
  }
55
- }})
56
-
57
- user.insert({ data: {} }).then(data => {
58
- // data = {
59
- // names: [],
60
- // pets: { colors: [] }
61
- // }
62
58
  })
59
+
60
+ await user.insert({ data: {} })
61
+ // data = {
62
+ // names: [],
63
+ // pets: { colors: [] }
64
+ // }
63
65
  ```
@@ -5,14 +5,14 @@ parent: Model
5
5
 
6
6
  # `model.remove`
7
7
 
8
- Remove document(s) in a collection and call related hooks: `schema.beforeRemove`, `schema.afterRemove`
8
+ Remove document(s) in a collection and calls model hooks: `beforeRemove`, `afterRemove`
9
9
 
10
10
  ### Arguments
11
11
 
12
12
  `options` *(object)*
13
13
 
14
- - `options.query` *(object\|id)*
15
- - [`options.sort`] *(string\|object\|array)*: same as the mongodb option, but allows for string parsing e.g. 'name', 'name:1'
14
+ - `query` *(object\|id)*
15
+ - [`sort`] *(string\|object\|array)*: same as the mongodb option, but allows for string parsing e.g. 'name', 'name:1'
16
16
  - [[`any mongodb option`](http://mongodb.github.io/node-mongodb-native/3.2/api/Collection.html#remove)] *(any)*
17
17
 
18
18
  [`callback`] *(function)*: pass instead of return a promise
@@ -5,18 +5,19 @@ parent: Model
5
5
 
6
6
  # `model.update`
7
7
 
8
- Update document(s) in a collection and call related hooks: `schema.beforeUpdate`, `schema.afterUpdate`. By default this method method updates a single document. Set the `multi` mongodb option to update all documents that match the query criteria.
8
+ Update document(s) in a collection and calls model hooks: `beforeUpdate`, `afterUpdate`. By default this method method updates a single document. Set the `multi` mongodb option to update all documents that match the query criteria.
9
9
 
10
10
  ### Arguments
11
11
 
12
12
  `options` *(object)*
13
13
 
14
- - `options.query` *(object\|id)*
15
- - `options.data` *(object)* - data that's validated against the model schema and then wrapped in `{ $set: .. }`, [`more below`](#data)
16
- - [`options.skipValidation`] (string\|array): skip validation for this field name(s)
17
- - [`options.sort`] *(string\|object\|array)*: same as the mongodb option, but allows for string parsing e.g. 'name', 'name:1'
18
- - [`options.timestamps`] *(boolean)*: whether `updatedAt` is automatically updated, defaults to the `manager.timestamps` value
19
- - [`options.blacklist`] *(array\|string\|false)*: augment `schema.updateBL`. `false` will remove all blacklisting
14
+ - `query` *(object\|id)*
15
+ - [`data`](#data) *(object)* - data that's validated against the model fields (always wrapped in `{ $set: .. }`)
16
+ - [[`blacklist`](#blacklisting)]*(array\|string\|false)*: augment `definition.updateBL`. `false` will remove all blacklisting
17
+ - [`project`] *(string\|array\|object)*: project these fields, ignores blacklisting
18
+ - [`skipValidation`] (string\|array): skip validation for this field name(s)
19
+ - [`sort`] *(string\|object\|array)*: same as the mongodb option, but allows for string parsing e.g. 'name', 'name:1'
20
+ - [`timestamps`] *(boolean)*: whether `updatedAt` is automatically updated, defaults to the `manager.timestamps` value
20
21
  - [[`any mongodb option`](http://mongodb.github.io/node-mongodb-native/3.2/api/Collection.html#update)] *(any)*
21
22
 
22
23
  [`callback`] *(function)*: pass instead of return a promise
@@ -33,7 +34,7 @@ user.update({ query: { name: 'foo' }, data: { name: 'bar' }})
33
34
 
34
35
  ### Data
35
36
 
36
- Data that is validated against the model schema and then wrapped in `{ $set: .. }`. Key names can be in dot or bracket notation which is handy for HTML FormData.
37
+ Data that's validated against the model fields (always wrapped in `{ $set: .. }`). Key names can be in dot or bracket notation which is handy for HTML FormData.
37
38
 
38
39
  You can also pass `options.$set` or any other mongodb update operation instead of `options.data`, which bypasses validation, e.g.
39
40
 
@@ -48,10 +49,10 @@ user.update({ query: {}, $pull: { name: 'Martin', badField: 1 }})
48
49
 
49
50
  ### Blacklisting
50
51
 
51
- You can augment the model's `schema.updateBL` blacklist by passing a custom `blacklist`:
52
+ You can augment the model's blacklist (`updateBL`) by passing a custom `blacklist`:
52
53
 
53
54
  ```js
54
55
  // Prevents `name` and `pets.$.name` (array) from being returned.
55
56
  user.update({ query: {}, data: {}, blacklist: ['name', 'pets.name'] })
56
- // You can also whitelist any blacklisted fields found in schema.updateBL
57
+ // You can also whitelist any blacklisted fields found in updateBL
57
58
  user.update({ query: {}, data: {}, blacklist: ['-name', '-pet'] })
@@ -5,7 +5,7 @@ parent: Model
5
5
 
6
6
  # `model.validate`
7
7
 
8
- Validate a model and call related hook: `schema.beforeValidate`
8
+ Validate a model and calls the model hook: `beforeValidate`
9
9
 
10
10
 
11
11
  ### Arguments
@@ -14,10 +14,11 @@ Validate a model and call related hook: `schema.beforeValidate`
14
14
 
15
15
  [`options`] *(object)*
16
16
 
17
- - [`options.skipValidation`] (string\|array): skip validation for this field name(s)
18
- - [`options.blacklist`] *(array\|string\|false)*: augment the model's blacklist. `false` will remove all blacklisting
19
- - [`options.timestamps`] *(boolean)*: whether `createdAt` and `updatedAt` are inserted, or `updatedAt` is updated, depending on the `options.update` value. Defaults to the `manager.timestamps` value
20
- - [`options.update`] *(boolean)*: If true, required rules will be skipped, defaults to false
17
+ - [`skipValidation`] (string\|array): skip validation for this field name(s)
18
+ - [[`blacklist`](#blacklisting)] *(array\|string\|false)*: augment the model's blacklist. `false` will remove all blacklisting
19
+ - [`project`] *(string\|array\|object)*: project these fields, ignores blacklisting
20
+ - [`timestamps`] *(boolean)*: whether `createdAt` and `updatedAt` are inserted, or `updatedAt` is updated, depending on the `update` value. Defaults to the `manager.timestamps` value
21
+ - [`update`] *(boolean)*: If true, required rules will be skipped, defaults to false
21
22
 
22
23
  ### Returns
23
24
 
@@ -34,39 +35,35 @@ db.model('user', {
34
35
  }
35
36
  })
36
37
 
37
- db.user.validate({
38
+ await db.user.validate({
38
39
  name: 'Martin Luther',
39
40
  unknownField: 'Some data'
40
-
41
- }).then(data => {
42
- // { name: "Martin Luther" }
43
41
  })
42
+ // { name: "Martin Luther" }
44
43
 
45
- db.user.validate({
44
+ await db.user.validate({
46
45
  name: 'Martin Luther'
47
46
  address: { city: 'Eisleben' }
48
-
49
- }).catch(errs => {
50
- // [{
51
- // detail: "Value needs to be at least 10 characters long.",
52
- // status: "400",
53
- // title: "address.city",
54
- // meta: {
55
- // field: "city",
56
- // model: "user",
57
- // rule: "minLength"
58
- // }
59
- // }]
60
47
  })
61
-
48
+ // Error [{
49
+ // detail: "Value needs to be at least 10 characters long.",
50
+ // status: "400",
51
+ // title: "address.city",
52
+ // meta: {
53
+ // field: "city",
54
+ // model: "user",
55
+ // rule: "minLength"
56
+ // }
57
+ // }]
62
58
  ```
59
+
63
60
  ### Blacklisting
64
61
 
65
- Depending on the `update` option, you can augment the model's `schema.insertBL` or `schema.updateBL` by passing a custom `blacklist`:
62
+ Depending on the `update` option, you can augment the model's `insertBL` or `updateBL` by passing a custom `blacklist`:
66
63
 
67
64
  ```js
68
65
  // Prevents `name` and `pets.$.name` (array) from being returned.
69
66
  user.validate({}, { blacklist: ['name', 'pets.name'] })
70
- // You can also whitelist any blacklisted fields found in schema's blacklist
67
+ // You can also whitelist any blacklisted fields found in insertBL/updateBL
71
68
  user.validate({}, { blacklist: ['-name', '-pet'] })
72
69
  ```
package/docs/readme.md CHANGED
@@ -6,8 +6,8 @@
6
6
 
7
7
  * User friendly API design, built around the awesome [Monk](https://automattic.github.io/monk/)
8
8
  * Simple CRUD operations with model population
9
- * Model validation deriving from your schema
10
- * Custom error messages can be defined in your schema
9
+ * Model validation deriving from your model definitions
10
+ * Custom error messages can be defined in your model definition
11
11
  * Normalised error responses ready for client consumption
12
12
  * Automatic mongodb index setup
13
13
 
@@ -83,20 +83,21 @@ Coming soon...
83
83
 
84
84
  ## Roadmap
85
85
 
86
- - Add FindOneAndUpdate
87
- - Add before/afterInsertUpdate
86
+ - Add Aggregate
87
+ - ~~Add FindOneAndUpdate~~
88
+ - ~~Add beforeInsertUpdate / afterInsertUpdate~~
88
89
  - Bug: Setting an object literal on an ID field ('model') saves successfully
89
90
  - Population within array items
90
91
  - ~~Blacklist false removes all blacklisting~~
91
92
  - ~~Add project to insert/update/validate~~
92
93
  - ~~Whitelisting a parent will remove any previously blacklisted children~~
93
94
  - ~~Blacklist/project works the same across find/insert/update/validate~~
94
- - Automatic subdocument ids
95
+ - Automatic embedded document ids/createdAt/updatedAt fields
95
96
  - Remove ACL default 'public read'
96
97
  - ~~Public db.arrayWithSchema method~~
97
98
  - Global after/before hooks
98
- - Split away from Monk (unless updated)
99
99
  - docs: Make the implicit ID query conversion more apparent
100
+ - Split away from Monk (unless updated)
100
101
 
101
102
  ## Special Thanks
102
103
 
package/lib/model-crud.js CHANGED
@@ -24,14 +24,13 @@ module.exports = {
24
24
  }
25
25
  try {
26
26
  opts = await this._queryObject(opts, 'insert')
27
- let custom = ['blacklist', 'data', 'insert', 'model', 'respond', 'skipValidation', 'validateUndefined']
28
27
 
29
28
  // Validate
30
- let data = await this.validate(opts.data || {}, { ...opts })
29
+ let data = await this.validate(opts.data || {}, opts) // was { ...opts }
31
30
 
32
31
  // Insert
33
32
  await util.runSeries(this.beforeInsert.map(f => f.bind(opts, data)))
34
- let response = await this._insert(data, util.omit(opts, custom))
33
+ let response = await this._insert(data, util.omit(opts, this._queryOptions))
35
34
  await util.runSeries(this.afterInsert.map(f => f.bind(opts, response)))
36
35
 
37
36
  // Success/error
@@ -64,9 +63,8 @@ module.exports = {
64
63
  throw new Error(`The callback passed to ${this.name}.find() is not a function`)
65
64
  }
66
65
  try {
67
- opts = await this._queryObject(opts, 'find', one)
68
- let custom = ['blacklist', 'model', 'one', 'populate', 'project', 'query', 'respond']
69
66
  let lookups = []
67
+ opts = await this._queryObject(opts, 'find', one)
70
68
 
71
69
  // Get projection
72
70
  if (opts.project) opts.projection = this._getProjectionFromProject(opts.project)
@@ -80,7 +78,7 @@ module.exports = {
80
78
 
81
79
  // Wanting to populate?
82
80
  if (!opts.populate) {
83
- var response = await this[`_find${opts.one? 'One' : ''}`](opts.query, util.omit(opts, custom))
81
+ var response = await this[`_find${opts.one? 'One' : ''}`](opts.query, util.omit(opts, this._queryOptions))
84
82
  } else {
85
83
  loop: for (let item of opts.populate) {
86
84
  let path = util.isObject(item)? item.as : item
@@ -157,18 +155,14 @@ module.exports = {
157
155
  * Find and update document(s) with monk, also auto populates
158
156
  * @param {object} opts
159
157
  * @param {array|string|false} <opts.blacklist> - augment findBL/updateBL, `false` will remove all blacklisting
160
- * @param {array|string|false} <opts.blacklistFind> - augment findBL, `false` will remove all blacklisting
161
158
  * @param {array} <opts.populate> - find population, see docs
162
159
  * @param {array|string} <opts.project> - return only these fields, ignores blacklisting
163
- * @param {array|string} <opts.projectFind> - return only these fields, ignores blacklisting
164
160
  * @param {object} <opts.query> - mongodb query object
165
161
  * @param {boolean} <opts.respond> - automatically call res.json/error (requires opts.req)
166
162
  * @param {any} <any mongodb option>
167
163
  *
168
164
  * Update options:
169
165
  * @param {object|array} opts.data - mongodb document update object(s)
170
- * @param {array|string|false} <opts.blacklistUpdate> - augment updateBL, `false` will remove all blacklisting
171
- * @param {array|string} <opts.projectUpdate> - return only these fields, ignores blacklisting
172
166
  * @param {array|string|true} <opts.skipValidation> - skip validation for this field name(s)
173
167
  * @param {boolean} <opts.timestamps> - whether `updatedAt` is automatically updated
174
168
  * @param {array|string|false} <opts.validateUndefined> - validates all 'required' undefined fields, true by
@@ -228,37 +222,49 @@ module.exports = {
228
222
  let data = opts.data
229
223
  let response = null
230
224
  let operators = util.pick(opts, [/^\$/])
231
- let custom = [
232
- 'blacklist', 'data', 'model', 'one', 'populate', 'project', 'query', 'respond', 'skipValidation',
233
- 'validateUndefined'
234
- ]
235
225
 
236
226
  // Validate
237
- if (util.isDefined(data)) data = await this.validate(opts.data, { ...opts })
227
+ if (util.isDefined(data)) {
228
+ data = await this.validate(opts.data, opts) // was {...opts}
229
+ }
238
230
  if (!util.isDefined(data) && util.isEmpty(operators)) {
239
231
  throw new Error(`Please pass an update operator to ${this.name}.${type}(), e.g. data, $unset, etc`)
240
232
  }
241
233
  if (util.isDefined(data) && (!data || util.isEmpty(data))) {
242
234
  throw new Error(`No valid data passed to ${this.name}.${type}({ data: .. })`)
243
235
  }
236
+
244
237
  // Hook: beforeUpdate (has access to original, non-validated opts.data)
245
238
  await util.runSeries(this.beforeUpdate.map(f => f.bind(opts, data||{})))
239
+
246
240
  if (data && operators['$set']) {
247
241
  this.warn(`'$set' fields take precedence over the data fields for \`${this.name}.${type}()\``)
248
242
  }
249
243
  if (data || operators['$set']) {
250
244
  operators['$set'] = { ...data, ...(operators['$set'] || {}) }
251
245
  }
252
- // Just peform a normal update if we need to populate a findOneAndUpdate
253
- if (opts.populate && type == 'findOneAndUpdate') type ='update'
246
+
247
+ // findOneAndUpdate, get 'find' projection
248
+ if (type == 'findOneAndUpdate') {
249
+ if (opts.project) opts.projection = this._getProjectionFromProject(opts.project)
250
+ else opts.projection = this._getProjectionFromBlacklist('find', opts.blacklist)
251
+ // Just peform a normal update if we need to populate a findOneAndUpdate
252
+ if (opts.populate) type = 'update'
253
+ }
254
+
254
255
  // Update
255
- let update = await this['_' + type](opts.query, operators, util.omit(opts, custom))
256
+ let update = await this['_' + type](opts.query, operators, util.omit(opts, this._queryOptions))
256
257
  if (type == 'findOneAndUpdate') response = update
257
258
  else if (update.n) response = Object.assign(Object.create({ _output: update }), operators['$set']||{})
258
259
 
259
260
  // Hook: afterUpdate (doesn't have access to validated data)
260
261
  if (response) await util.runSeries(this.afterUpdate.map(f => f.bind(opts, response)))
261
262
 
263
+ // Hook: afterFind if findOneAndUpdate
264
+ if (response && type == 'findOneAndUpdate') {
265
+ response = await this._processAfterFind(response, opts.projection, opts)
266
+ }
267
+
262
268
  // Success
263
269
  if (cb) cb(null, response)
264
270
  else if (opts.req && opts.respond) opts.req.res.json(response)
@@ -288,12 +294,11 @@ module.exports = {
288
294
  }
289
295
  try {
290
296
  opts = await this._queryObject(opts, 'remove')
291
- let custom = ['model', 'query', 'respond']
292
297
  if (util.isEmpty(opts.query)) throw new Error('Please specify opts.query')
293
298
 
294
299
  // Remove
295
300
  await util.runSeries(this.beforeRemove.map(f => f.bind(opts)))
296
- let response = await this._remove(opts.query, util.omit(opts, custom))
301
+ let response = await this._remove(opts.query, util.omit(opts, this._queryOptions))
297
302
  await util.runSeries(this.afterRemove.map(f => f.bind(response)))
298
303
 
299
304
  // Success
@@ -626,4 +631,11 @@ module.exports = {
626
631
  }, this)
627
632
  },
628
633
 
634
+ _queryOptions: [
635
+ // todo: remove type properties
636
+ 'blacklist', 'data', 'find', 'findOneAndUpdate', 'insert', 'model', 'one', 'populate', 'project',
637
+ 'projectionValidate', 'query', 'remove', 'req', 'respond', 'skipValidation', 'type', 'update',
638
+ 'validateUndefined',
639
+ ],
640
+
629
641
  }
@@ -36,8 +36,8 @@ module.exports = {
36
36
  opts.skipValidation = opts.skipValidation === true ? true : util.toArray(opts.skipValidation||[])
37
37
 
38
38
  // Get projection
39
- if (opts.project) opts.projection = this._getProjectionFromProject(opts.project)
40
- else opts.projection = this._getProjectionFromBlacklist(opts.update ? 'update' : 'insert', opts.blacklist)
39
+ if (opts.project) opts.projectionValidate = this._getProjectionFromProject(opts.project)
40
+ else opts.projectionValidate = this._getProjectionFromBlacklist(opts.update ? 'update' : 'insert', opts.blacklist)
41
41
 
42
42
  // Hook: beforeValidate
43
43
  await util.runSeries(this.beforeValidate.map(f => f.bind(opts, data)))
@@ -132,7 +132,7 @@ module.exports = {
132
132
  }
133
133
 
134
134
  // Ignore blacklisted
135
- if (this._pathBlacklisted(path3, opts.projection) && !schema.defaultOverride) return
135
+ if (this._pathBlacklisted(path3, opts.projectionValidate) && !schema.defaultOverride) return
136
136
  // Ignore insert only
137
137
  if (opts.update && schema.insertOnly) return
138
138
  // Ignore virtual fields
@@ -245,7 +245,7 @@ module.exports = {
245
245
  if (typeof value === 'undefined' && (!validateUndefined || !rule.validateUndefined)) return
246
246
 
247
247
  // Ignore null (if nullObject is set on objects or arrays)
248
- if (value === null && (field.isObject || field.isArray) && field.nullObject) return
248
+ if (value === null && (field.isObject || field.isArray) && field.nullObject && !rule.validateNull) return
249
249
 
250
250
  // Ignore null
251
251
  if (value === null && !(field.isObject || field.isArray) && !rule.validateNull) return
package/lib/util.js CHANGED
@@ -163,8 +163,15 @@ module.exports = {
163
163
  * Mutates dot notation objects into a deep object
164
164
  * @param {object}
165
165
  */
166
+ let original = this.deepCopy(obj)
166
167
  for (let key in obj) {
167
- if (key.indexOf('.') !== -1) recurse(key, obj[key], obj)
168
+ if (key.indexOf('.') !== -1) {
169
+ recurse(key, obj[key], obj)
170
+ } else {
171
+ // Ordinary values may of been updated by the bracket notation values, we are
172
+ // reassigning, trying to preserve the order of keys (not always guaranteed in for loops)
173
+ obj[key] = original[key]
174
+ }
168
175
  }
169
176
  return obj
170
177
  function recurse(str, val, obj) {
@@ -188,6 +195,7 @@ module.exports = {
188
195
  },
189
196
 
190
197
  parseFormData: function(obj) {
198
+ let original = this.deepCopy(obj)
191
199
  /**
192
200
  * Mutates FormData (bracket notation) objects into a deep object
193
201
  * @param {object}
@@ -203,6 +211,10 @@ module.exports = {
203
211
  }
204
212
  if (key.indexOf('[') !== -1) {
205
213
  setup(key)
214
+ } else {
215
+ // Ordinary values may of been updated by the bracket notation values, we are
216
+ // reassigning, trying to preserve the order of keys (not always guaranteed in for loops)
217
+ obj[key] = original[key]
206
218
  }
207
219
  }
208
220
  res(obj)
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.36.0",
5
+ "version": "1.36.3",
6
6
  "license": "MIT",
7
7
  "repository": "github:boycce/monastery",
8
8
  "homepage": "https://boycce.github.io/monastery/",