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.
- package/.eslintrc.json +2 -1
- package/.travis.yml +1 -1
- package/changelog.md +23 -0
- package/docs/{schema/rules.md → definition/field-rules.md} +6 -4
- package/docs/definition/index.md +338 -0
- package/docs/image-plugin.md +16 -16
- package/docs/manager/index.md +8 -8
- package/docs/manager/model.md +4 -3
- package/docs/manager/models.md +1 -1
- package/docs/model/find.md +31 -29
- package/docs/model/findOne.md +3 -4
- package/docs/model/findOneAndUpdate.md +42 -0
- package/docs/model/index.md +2 -2
- package/docs/model/insert.md +22 -20
- package/docs/model/remove.md +3 -3
- package/docs/model/update.md +11 -10
- package/docs/model/validate.md +22 -25
- package/docs/readme.md +7 -6
- package/lib/model-crud.js +32 -20
- package/lib/model-validate.js +4 -4
- package/lib/util.js +13 -1
- package/package.json +1 -1
- package/test/blacklisting.js +132 -177
- package/test/crud.js +98 -27
- package/test/mock/blacklisting.js +122 -0
- package/test/test.js +2 -0
- package/test/validate.js +35 -3
- package/docs/schema/index.md +0 -313
|
@@ -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
|
+
```
|
package/docs/model/index.md
CHANGED
|
@@ -8,9 +8,9 @@ has_children: true
|
|
|
8
8
|
|
|
9
9
|
Created via [`manager.model`](../manager/model).
|
|
10
10
|
|
|
11
|
-
#### Monk collection instance
|
|
11
|
+
#### Monk collection instance operators
|
|
12
12
|
|
|
13
|
-
Additionally models inherit most of the [monk collection](https://automattic.github.io/monk/docs/collection/) instance
|
|
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)
|
package/docs/model/insert.md
CHANGED
|
@@ -5,16 +5,17 @@ parent: Model
|
|
|
5
5
|
|
|
6
6
|
# `model.insert`
|
|
7
7
|
|
|
8
|
-
Validate and insert document(s) in a collection and
|
|
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
|
-
- `
|
|
15
|
-
- [`
|
|
16
|
-
- [`
|
|
17
|
-
- [`
|
|
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 `
|
|
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
|
|
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
|
|
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({
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
```
|
package/docs/model/remove.md
CHANGED
|
@@ -5,14 +5,14 @@ parent: Model
|
|
|
5
5
|
|
|
6
6
|
# `model.remove`
|
|
7
7
|
|
|
8
|
-
Remove document(s) in a collection and
|
|
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
|
-
- `
|
|
15
|
-
- [`
|
|
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
|
package/docs/model/update.md
CHANGED
|
@@ -5,18 +5,19 @@ parent: Model
|
|
|
5
5
|
|
|
6
6
|
# `model.update`
|
|
7
7
|
|
|
8
|
-
Update document(s) in a collection and
|
|
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
|
-
- `
|
|
15
|
-
- `
|
|
16
|
-
- [`
|
|
17
|
-
- [`
|
|
18
|
-
- [`
|
|
19
|
-
- [`
|
|
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
|
|
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 `
|
|
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
|
|
57
|
+
// You can also whitelist any blacklisted fields found in updateBL
|
|
57
58
|
user.update({ query: {}, data: {}, blacklist: ['-name', '-pet'] })
|
package/docs/model/validate.md
CHANGED
|
@@ -5,7 +5,7 @@ parent: Model
|
|
|
5
5
|
|
|
6
6
|
# `model.validate`
|
|
7
7
|
|
|
8
|
-
Validate a model and
|
|
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
|
-
- [`
|
|
18
|
-
- [`
|
|
19
|
-
- [`
|
|
20
|
-
- [`
|
|
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 `
|
|
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
|
|
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
|
|
10
|
-
* Custom error messages can be defined in your
|
|
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
|
|
87
|
-
- Add
|
|
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
|
|
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,
|
|
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,
|
|
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))
|
|
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
|
-
|
|
253
|
-
|
|
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,
|
|
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,
|
|
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
|
}
|
package/lib/model-validate.js
CHANGED
|
@@ -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.
|
|
40
|
-
else opts.
|
|
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.
|
|
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)
|
|
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.
|
|
5
|
+
"version": "1.36.3",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": "github:boycce/monastery",
|
|
8
8
|
"homepage": "https://boycce.github.io/monastery/",
|