monastery 1.35.0 → 1.36.2
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 +32 -1
- 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 +8 -7
- package/lib/index.js +36 -34
- package/lib/model-crud.js +89 -25
- package/lib/model-validate.js +28 -22
- package/lib/model.js +2 -3
- package/lib/monk-monkey-patches.js +73 -0
- package/lib/util.js +20 -18
- package/package.json +1 -1
- package/test/blacklisting.js +132 -177
- package/test/crud.js +83 -25
- package/test/mock/blacklisting.js +122 -0
- package/test/monk.js +1 -1
- package/test/test.js +2 -0
- package/test/validate.js +33 -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
|
-
- ~~Blacklist
|
|
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/index.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
let util = require('./util')
|
|
2
|
-
let monk = require('monk')
|
|
3
1
|
let debug = require('debug')
|
|
2
|
+
let monk = require('monk')
|
|
3
|
+
let util = require('./util')
|
|
4
|
+
|
|
5
|
+
// Apply monk monkey patches
|
|
6
|
+
monk.manager.prototype.open = require('./monk-monkey-patches').open
|
|
7
|
+
monk.Collection.prototype.findOneAndUpdate = require('./monk-monkey-patches').findOneAndUpdate
|
|
4
8
|
|
|
5
9
|
module.exports = function(uri, opts, fn) {
|
|
6
10
|
/**
|
|
@@ -12,59 +16,51 @@ module.exports = function(uri, opts, fn) {
|
|
|
12
16
|
* @param {object} opts
|
|
13
17
|
* @return monk manager
|
|
14
18
|
*/
|
|
19
|
+
let monasteryOpts = [
|
|
20
|
+
'defaultObjects', 'imagePlugin', 'limit', 'nullObjects', 'timestamps', 'useMilliseconds'
|
|
21
|
+
]
|
|
22
|
+
|
|
15
23
|
if (!opts) opts = {}
|
|
16
24
|
if (util.isDefined(opts.defaultFields)) {
|
|
17
25
|
var depreciationWarningDefaultField = true
|
|
18
26
|
opts.timestamps = opts.defaultFields
|
|
27
|
+
delete opts.defaultFields
|
|
28
|
+
}
|
|
29
|
+
if (!util.isDefined(opts.timestamps)) {
|
|
30
|
+
opts.timestamps = true
|
|
19
31
|
}
|
|
20
|
-
let defaultObjects = opts.defaultObjects
|
|
21
|
-
let imagePlugin = opts.imagePlugin
|
|
22
|
-
let limit = opts.limit
|
|
23
|
-
let nullObjects = opts.nullObjects
|
|
24
|
-
let timestamps = util.isDefined(opts.timestamps)? opts.timestamps : true
|
|
25
|
-
let useMilliseconds = opts.useMilliseconds
|
|
26
|
-
delete opts.defaultFields
|
|
27
|
-
delete opts.defaultObjects
|
|
28
|
-
delete opts.imagePlugin
|
|
29
|
-
delete opts.limit
|
|
30
|
-
delete opts.nullObjects
|
|
31
|
-
delete opts.timestamps
|
|
32
|
-
delete opts.useMilliseconds
|
|
33
32
|
|
|
34
|
-
// Monk
|
|
33
|
+
// Monk manager instance or manager mock
|
|
35
34
|
// Monk manager instances have manager._db defined which is the raw mongodb connection
|
|
36
35
|
if (typeof uri === 'object') var manager = uri
|
|
37
|
-
else if (uri) manager = monk(uri, { useUnifiedTopology: true, ...opts }, fn)
|
|
36
|
+
else if (uri) manager = monk(uri, { useUnifiedTopology: true, ...util.omit(opts, monasteryOpts) }, fn)
|
|
38
37
|
else manager = { id: monk.id }
|
|
39
38
|
|
|
40
39
|
// Add monastery properties
|
|
41
|
-
manager.
|
|
42
|
-
manager.
|
|
43
|
-
manager.info = debug('monastery:info')
|
|
44
|
-
manager.model = require('./model')
|
|
45
|
-
manager.models = models
|
|
46
|
-
manager.defaultObjects = defaultObjects
|
|
47
|
-
manager.imagePlugin = imagePlugin
|
|
40
|
+
manager.arrayWithSchema = arrayWithSchema
|
|
41
|
+
manager.beforeModel = []
|
|
48
42
|
manager.imagePluginFile = require('../plugins/images')
|
|
49
43
|
manager.isId = util.isId.bind(util)
|
|
50
|
-
manager.
|
|
51
|
-
manager.
|
|
44
|
+
manager.model = require('./model')
|
|
45
|
+
manager.models = models
|
|
52
46
|
manager.parseData = util.parseData.bind(util)
|
|
53
|
-
manager.
|
|
54
|
-
manager.
|
|
55
|
-
manager.
|
|
56
|
-
|
|
57
|
-
|
|
47
|
+
manager.warn = debug('monastery:warn')
|
|
48
|
+
manager.error = debug('monastery:error*')
|
|
49
|
+
manager.info = debug('monastery:info')
|
|
50
|
+
|
|
51
|
+
// Add opts onto manager
|
|
52
|
+
for (let key of monasteryOpts) {
|
|
53
|
+
manager[key] = opts[key]
|
|
58
54
|
}
|
|
59
55
|
|
|
60
56
|
// Depreciation warnings
|
|
61
57
|
if (depreciationWarningDefaultField) {
|
|
62
|
-
manager.error('
|
|
58
|
+
manager.error('opts.defaultFields has been depreciated in favour of opts.timestamps')
|
|
63
59
|
}
|
|
64
60
|
|
|
65
61
|
// Initiate any plugins
|
|
66
62
|
if (manager.imagePlugin) {
|
|
67
|
-
manager.imagePluginFile.setup(manager, util.isObject(imagePlugin)? imagePlugin : {})
|
|
63
|
+
manager.imagePluginFile.setup(manager, util.isObject(manager.imagePlugin)? manager.imagePlugin : {})
|
|
68
64
|
}
|
|
69
65
|
|
|
70
66
|
// Catch mongodb connectivity errors
|
|
@@ -72,11 +68,17 @@ module.exports = function(uri, opts, fn) {
|
|
|
72
68
|
return manager
|
|
73
69
|
}
|
|
74
70
|
|
|
75
|
-
function
|
|
71
|
+
let arrayWithSchema = function(array, schema) {
|
|
72
|
+
array.schema = schema
|
|
73
|
+
return array
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let models = function(path) {
|
|
76
77
|
/**
|
|
77
78
|
* Setup model definitions from a folder location
|
|
78
79
|
* @param {string} pathname
|
|
79
80
|
* @return {object} - e.g. { user: , article: , .. }
|
|
81
|
+
* @this Manager
|
|
80
82
|
*/
|
|
81
83
|
let models = {}
|
|
82
84
|
if (!path || typeof path !== 'string') {
|
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', '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
|
|
@@ -152,11 +150,52 @@ module.exports = {
|
|
|
152
150
|
return this.find(opts, cb, true)
|
|
153
151
|
},
|
|
154
152
|
|
|
155
|
-
findOneAndUpdate: function(opts, cb) {
|
|
156
|
-
|
|
153
|
+
findOneAndUpdate: async function(opts, cb) {
|
|
154
|
+
/**
|
|
155
|
+
* Find and update document(s) with monk, also auto populates
|
|
156
|
+
* @param {object} opts
|
|
157
|
+
* @param {array|string|false} <opts.blacklist> - augment findBL/updateBL, `false` will remove all blacklisting
|
|
158
|
+
* @param {array} <opts.populate> - find population, see docs
|
|
159
|
+
* @param {array|string} <opts.project> - return only these fields, ignores blacklisting
|
|
160
|
+
* @param {object} <opts.query> - mongodb query object
|
|
161
|
+
* @param {boolean} <opts.respond> - automatically call res.json/error (requires opts.req)
|
|
162
|
+
* @param {any} <any mongodb option>
|
|
163
|
+
*
|
|
164
|
+
* Update options:
|
|
165
|
+
* @param {object|array} opts.data - mongodb document update object(s)
|
|
166
|
+
* @param {array|string|true} <opts.skipValidation> - skip validation for this field name(s)
|
|
167
|
+
* @param {boolean} <opts.timestamps> - whether `updatedAt` is automatically updated
|
|
168
|
+
* @param {array|string|false} <opts.validateUndefined> - validates all 'required' undefined fields, true by
|
|
169
|
+
* default, but false on update
|
|
170
|
+
* @param {function} <cb> - execute cb(err, data) instead of responding
|
|
171
|
+
* @return promise
|
|
172
|
+
* @this model
|
|
173
|
+
*/
|
|
174
|
+
|
|
175
|
+
if (opts.populate) {
|
|
176
|
+
try {
|
|
177
|
+
// todo: add transaction flag
|
|
178
|
+
delete opts.multi
|
|
179
|
+
let update = await this.update(opts, null, 'findOneAndUpdate')
|
|
180
|
+
if (update) var response = await this.findOne(opts)
|
|
181
|
+
else response = update
|
|
182
|
+
|
|
183
|
+
// Success
|
|
184
|
+
if (cb) cb(null, response)
|
|
185
|
+
else if (opts.req && opts.respond) opts.req.res.json(response)
|
|
186
|
+
else return Promise.resolve(response)
|
|
187
|
+
|
|
188
|
+
} catch (e) {
|
|
189
|
+
if (cb) cb(e)
|
|
190
|
+
else if (opts && opts.req && opts.respond) opts.req.res.error(e)
|
|
191
|
+
else throw e
|
|
192
|
+
}
|
|
193
|
+
} else {
|
|
194
|
+
return this.update(opts, cb, 'findOneAndUpdate')
|
|
195
|
+
}
|
|
157
196
|
},
|
|
158
197
|
|
|
159
|
-
update: async function(opts, cb) {
|
|
198
|
+
update: async function(opts, cb, type='update') {
|
|
160
199
|
/**
|
|
161
200
|
* Updates document(s) with monk after validating data & before hooks.
|
|
162
201
|
* @param {object} opts
|
|
@@ -171,41 +210,60 @@ module.exports = {
|
|
|
171
210
|
* default, but false on update
|
|
172
211
|
* @param {any} <any mongodb option>
|
|
173
212
|
* @param {function} <cb> - execute cb(err, data) instead of responding
|
|
213
|
+
* @param {function} <type> - 'update', or 'findOneAndUpdate'
|
|
174
214
|
* @return promise(data)
|
|
175
215
|
* @this model
|
|
176
216
|
*/
|
|
177
217
|
if (cb && !util.isFunction(cb)) {
|
|
178
|
-
throw new Error(`The callback passed to ${this.name}
|
|
218
|
+
throw new Error(`The callback passed to ${this.name}.${type}() is not a function`)
|
|
179
219
|
}
|
|
180
220
|
try {
|
|
181
|
-
opts = await this._queryObject(opts,
|
|
221
|
+
opts = await this._queryObject(opts, type)
|
|
182
222
|
let data = opts.data
|
|
183
223
|
let response = null
|
|
184
|
-
let operators = util.
|
|
185
|
-
let custom = ['blacklist', 'data', 'query', 'respond', 'skipValidation', 'validateUndefined']
|
|
224
|
+
let operators = util.pick(opts, [/^\$/])
|
|
186
225
|
|
|
187
226
|
// Validate
|
|
188
|
-
if (util.isDefined(data))
|
|
227
|
+
if (util.isDefined(data)) {
|
|
228
|
+
data = await this.validate(opts.data, opts) // was {...opts}
|
|
229
|
+
}
|
|
189
230
|
if (!util.isDefined(data) && util.isEmpty(operators)) {
|
|
190
|
-
throw new Error(`Please pass an update operator to ${this.name}
|
|
231
|
+
throw new Error(`Please pass an update operator to ${this.name}.${type}(), e.g. data, $unset, etc`)
|
|
191
232
|
}
|
|
192
233
|
if (util.isDefined(data) && (!data || util.isEmpty(data))) {
|
|
193
|
-
throw new Error(`No valid data passed to ${this.name}
|
|
234
|
+
throw new Error(`No valid data passed to ${this.name}.${type}({ data: .. })`)
|
|
194
235
|
}
|
|
236
|
+
|
|
195
237
|
// Hook: beforeUpdate (has access to original, non-validated opts.data)
|
|
196
238
|
await util.runSeries(this.beforeUpdate.map(f => f.bind(opts, data||{})))
|
|
239
|
+
|
|
197
240
|
if (data && operators['$set']) {
|
|
198
|
-
this.warn(`'$set' fields take precedence over the data fields for \`${this.name}
|
|
241
|
+
this.warn(`'$set' fields take precedence over the data fields for \`${this.name}.${type}()\``)
|
|
199
242
|
}
|
|
200
243
|
if (data || operators['$set']) {
|
|
201
244
|
operators['$set'] = { ...data, ...(operators['$set'] || {}) }
|
|
202
245
|
}
|
|
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
|
+
|
|
203
255
|
// Update
|
|
204
|
-
let update = await this
|
|
205
|
-
if (
|
|
256
|
+
let update = await this['_' + type](opts.query, operators, util.omit(opts, this._queryOptions))
|
|
257
|
+
if (type == 'findOneAndUpdate') response = update
|
|
258
|
+
else if (update.n) response = Object.assign(Object.create({ _output: update }), operators['$set']||{})
|
|
206
259
|
|
|
207
260
|
// Hook: afterUpdate (doesn't have access to validated data)
|
|
208
|
-
if (
|
|
261
|
+
if (response) await util.runSeries(this.afterUpdate.map(f => f.bind(opts, response)))
|
|
262
|
+
|
|
263
|
+
// Hook: afterFind if findOneAndUpdate
|
|
264
|
+
if (response && type == 'findOneAndUpdate') {
|
|
265
|
+
response = await this._processAfterFind(response, opts.projection, opts)
|
|
266
|
+
}
|
|
209
267
|
|
|
210
268
|
// Success
|
|
211
269
|
if (cb) cb(null, response)
|
|
@@ -236,12 +294,11 @@ module.exports = {
|
|
|
236
294
|
}
|
|
237
295
|
try {
|
|
238
296
|
opts = await this._queryObject(opts, 'remove')
|
|
239
|
-
let custom = ['query', 'respond']
|
|
240
297
|
if (util.isEmpty(opts.query)) throw new Error('Please specify opts.query')
|
|
241
298
|
|
|
242
299
|
// Remove
|
|
243
300
|
await util.runSeries(this.beforeRemove.map(f => f.bind(opts)))
|
|
244
|
-
let response = await this._remove(opts.query, util.omit(opts,
|
|
301
|
+
let response = await this._remove(opts.query, util.omit(opts, this._queryOptions))
|
|
245
302
|
await util.runSeries(this.afterRemove.map(f => f.bind(response)))
|
|
246
303
|
|
|
247
304
|
// Success
|
|
@@ -368,7 +425,7 @@ module.exports = {
|
|
|
368
425
|
/**
|
|
369
426
|
* Normalise options
|
|
370
427
|
* @param {MongoId|string|object} opts
|
|
371
|
-
* @param {string} type -
|
|
428
|
+
* @param {string} type - insert, update, find, remove, findOneAndUpdate
|
|
372
429
|
* @param {boolean} one - return one document
|
|
373
430
|
* @return {Promise} opts
|
|
374
431
|
* @this model
|
|
@@ -397,7 +454,7 @@ module.exports = {
|
|
|
397
454
|
throw new Error('Please pass an object or MongoId to options.query')
|
|
398
455
|
}
|
|
399
456
|
if (util.isId(opts.query._id)) opts.query._id = this.manager.id(opts.query._id)
|
|
400
|
-
if (isIdType(opts.query._id) || one) opts.one = true
|
|
457
|
+
if (isIdType(opts.query._id) || one || type == 'findOneAndUpdate') opts.one = true
|
|
401
458
|
opts.query = util.removeUndefined(opts.query)
|
|
402
459
|
|
|
403
460
|
// Query options
|
|
@@ -417,7 +474,7 @@ module.exports = {
|
|
|
417
474
|
if (util.isDefined(opts.data)) opts.data = await util.parseData(opts.data)
|
|
418
475
|
|
|
419
476
|
opts.type = type
|
|
420
|
-
opts[type] = true
|
|
477
|
+
opts[type] = true // still being included in the operation options..
|
|
421
478
|
opts.model = this
|
|
422
479
|
return opts
|
|
423
480
|
},
|
|
@@ -574,4 +631,11 @@ module.exports = {
|
|
|
574
631
|
}, this)
|
|
575
632
|
},
|
|
576
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
|
+
|
|
577
641
|
}
|