monastery 1.34.0 → 1.35.0
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 +1 -1
- package/changelog.md +7 -0
- package/docs/readme.md +4 -2
- package/docs/schema/index.md +3 -3
- package/lib/model-crud.js +125 -101
- package/lib/model-validate.js +16 -47
- package/lib/model.js +1 -1
- package/package.json +1 -1
- package/test/blacklisting.js +83 -26
package/.eslintrc.json
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"plugins": [],
|
|
22
22
|
"rules": {
|
|
23
23
|
"brace-style": ["error", "1tbs", { "allowSingleLine": true }],
|
|
24
|
-
"max-len": ["error", { "code":
|
|
24
|
+
"max-len": ["error", { "code": 125, "ignorePattern": "^\\s*<(rect|path|line)\\s" }],
|
|
25
25
|
"no-prototype-builtins": "off",
|
|
26
26
|
"no-unused-vars": ["error", { "args": "none" }],
|
|
27
27
|
"object-shorthand": ["error", "consistent"],
|
package/changelog.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
## [1.35.0](https://github.com/boycce/monastery/compare/1.34.0...1.35.0) (2022-04-08)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* `false` removes blacklist, added tests for blacklisting/project stirng ([5999859](https://github.com/boycce/monastery/commit/599985972cc14b980148db26c03108feabf23756))
|
|
11
|
+
|
|
5
12
|
## [1.34.0](https://github.com/boycce/monastery/compare/1.33.0...1.34.0) (2022-04-05)
|
|
6
13
|
|
|
7
14
|
|
package/docs/readme.md
CHANGED
|
@@ -87,11 +87,13 @@ Coming soon...
|
|
|
87
87
|
- Add before/afterInsertUpdate
|
|
88
88
|
- Bug: Setting an object literal on an ID field ('model') saves successfully
|
|
89
89
|
- Population within array items
|
|
90
|
-
- ~~
|
|
90
|
+
- ~~Blacklist `false` removes all blacklisting~~
|
|
91
|
+
- ~~Add project to insert/update/validate~~
|
|
91
92
|
- ~~Whitelisting a parent will remove any previously blacklisted children~~
|
|
93
|
+
- ~~Blacklist/project works the same across find/insert/update/validate~~
|
|
92
94
|
- Automatic subdocument ids
|
|
93
95
|
- Remove ACL default 'public read'
|
|
94
|
-
- Public db.arrayWithSchema method
|
|
96
|
+
- ~~Public db.arrayWithSchema method~~
|
|
95
97
|
- Global after/before hooks
|
|
96
98
|
- Split away from Monk (unless updated)
|
|
97
99
|
- docs: Make the implicit ID query conversion more apparent
|
package/docs/schema/index.md
CHANGED
|
@@ -248,9 +248,9 @@ schema.messages = {
|
|
|
248
248
|
}
|
|
249
249
|
// You can also target any rules set on the array or sub arrays
|
|
250
250
|
// e.g.
|
|
251
|
-
// let arrayWithSchema = (array, schema) => { array.schema = schema; return array }
|
|
252
|
-
// petGroups = arrayWithSchema(
|
|
253
|
-
// [arrayWithSchema(
|
|
251
|
+
// // let arrayWithSchema = (array, schema) => { array.schema = schema; return array }, OR you can use db.arrayWithSchema
|
|
252
|
+
// petGroups = db.arrayWithSchema(
|
|
253
|
+
// [db.arrayWithSchema(
|
|
254
254
|
// [{ name: { type: 'string' }}],
|
|
255
255
|
// { minLength: 1 }
|
|
256
256
|
// )],
|
package/lib/model-crud.js
CHANGED
|
@@ -6,33 +6,32 @@ module.exports = {
|
|
|
6
6
|
/**
|
|
7
7
|
* Inserts document(s) with monk after validating data & before hooks.
|
|
8
8
|
* @param {object} opts
|
|
9
|
-
* @param {object|array}
|
|
10
|
-
* @param {array|string|false} <opts.blacklist> - augment schema.insertBL, `false` will remove
|
|
9
|
+
* @param {object|array} opts.data - documents to insert
|
|
10
|
+
* @param {array|string|false} <opts.blacklist> - augment schema.insertBL, `false` will remove blacklisting
|
|
11
|
+
* @param {array|string} <opts.project> - return only these fields, ignores blacklisting
|
|
11
12
|
* @param {boolean} <opts.respond> - automatically call res.json/error (requires opts.req)
|
|
12
|
-
* @param {array|string|false} validateUndefined - validates all 'required' undefined fields, true by
|
|
13
|
-
* default, but false on update
|
|
14
13
|
* @param {array|string|true} <opts.skipValidation> - skip validation for this field name(s)
|
|
15
14
|
* @param {boolean} <opts.timestamps> - whether `createdAt` and `updatedAt` are automatically inserted
|
|
16
|
-
* @param {
|
|
15
|
+
* @param {array|string|false} <opts.validateUndefined> - validates all 'required' undefined fields, true by
|
|
16
|
+
* default, but false on update
|
|
17
|
+
* @param {any} <any mongodb option>
|
|
17
18
|
* @param {function} <cb> - execute cb(err, data) instead of responding
|
|
18
|
-
* @this model
|
|
19
19
|
* @return promise
|
|
20
|
+
* @this model
|
|
20
21
|
*/
|
|
21
22
|
if (cb && !util.isFunction(cb)) {
|
|
22
23
|
throw new Error(`The callback passed to ${this.name}.insert() is not a function`)
|
|
23
24
|
}
|
|
24
25
|
try {
|
|
25
26
|
opts = await this._queryObject(opts, 'insert')
|
|
26
|
-
let
|
|
27
|
-
'data', 'insert', 'model', 'respond', 'validateUndefined', 'skipValidation', 'blacklist'
|
|
28
|
-
])
|
|
27
|
+
let custom = ['blacklist', 'data', 'insert', 'model', 'respond', 'skipValidation', 'validateUndefined']
|
|
29
28
|
|
|
30
29
|
// Validate
|
|
31
|
-
let data = await this.validate(opts.data||{}, { ...opts })
|
|
30
|
+
let data = await this.validate(opts.data || {}, { ...opts })
|
|
32
31
|
|
|
33
32
|
// Insert
|
|
34
33
|
await util.runSeries(this.beforeInsert.map(f => f.bind(opts, data)))
|
|
35
|
-
let response = await this._insert(data,
|
|
34
|
+
let response = await this._insert(data, util.omit(opts, custom))
|
|
36
35
|
await util.runSeries(this.afterInsert.map(f => f.bind(opts, response)))
|
|
37
36
|
|
|
38
37
|
// Success/error
|
|
@@ -51,53 +50,42 @@ module.exports = {
|
|
|
51
50
|
/**
|
|
52
51
|
* Finds document(s) with monk, also auto populates
|
|
53
52
|
* @param {object} opts
|
|
54
|
-
* @param {object} <opts.query> - mongodb query object
|
|
55
|
-
* @param {boolean} <opts.respond> - automatically call res.json/error (requires opts.req)
|
|
56
|
-
* @param {array} <opts.populate> - population, see docs
|
|
57
53
|
* @param {array|string|false} <opts.blacklist> - augment schema.findBL, `false` will remove all blacklisting
|
|
54
|
+
* @param {array} <opts.populate> - population, see docs
|
|
58
55
|
* @param {array|string} <opts.project> - return only these fields, ignores blacklisting
|
|
59
|
-
* @param {
|
|
56
|
+
* @param {object} <opts.query> - mongodb query object
|
|
57
|
+
* @param {boolean} <opts.respond> - automatically call res.json/error (requires opts.req)
|
|
58
|
+
* @param {any} <any mongodb option>
|
|
60
59
|
* @param {function} <cb> - execute cb(err, data) instead of responding
|
|
61
|
-
* @this model
|
|
62
60
|
* @return promise
|
|
61
|
+
* @this model
|
|
63
62
|
*/
|
|
64
63
|
if (cb && !util.isFunction(cb)) {
|
|
65
64
|
throw new Error(`The callback passed to ${this.name}.find() is not a function`)
|
|
66
65
|
}
|
|
67
66
|
try {
|
|
68
67
|
opts = await this._queryObject(opts, 'find', one)
|
|
68
|
+
let custom = ['blacklist', 'one', 'populate', 'project', 'query', 'respond']
|
|
69
69
|
let lookups = []
|
|
70
|
-
let options = util.omit(opts, ['blacklist', 'one', 'populate', 'project', 'query', 'respond'])
|
|
71
|
-
options.addFields = options.addFields || {}
|
|
72
70
|
|
|
73
|
-
//
|
|
74
|
-
if (opts.project)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
if (util.isArray(opts.project)) {
|
|
79
|
-
options.projection = opts.project.reduce((o, v) => {
|
|
80
|
-
o[v.replace(/^-/, '')] = v.match(/^-/)? 0 : 1
|
|
81
|
-
return o
|
|
82
|
-
}, {})
|
|
83
|
-
}
|
|
84
|
-
// Or blacklisting
|
|
85
|
-
} else {
|
|
86
|
-
options.projection = this._getBlacklistProjection('find', opts.blacklist)
|
|
87
|
-
}
|
|
71
|
+
// Get projection
|
|
72
|
+
if (opts.project) opts.projection = this._getProjectionFromProject(opts.project)
|
|
73
|
+
else opts.projection = this._getProjectionFromBlacklist(opts.type, opts.blacklist)
|
|
74
|
+
|
|
88
75
|
// Has text search?
|
|
89
76
|
// if (opts.query.$text) {
|
|
90
|
-
//
|
|
91
|
-
//
|
|
77
|
+
// opts.projection.score = { $meta: 'textScore' }
|
|
78
|
+
// opts.sort = { score: { $meta: 'textScore' }}
|
|
92
79
|
// }
|
|
80
|
+
|
|
93
81
|
// Wanting to populate?
|
|
94
82
|
if (!opts.populate) {
|
|
95
|
-
var response = await this[`_find${opts.one? 'One' : ''}`](opts.query,
|
|
83
|
+
var response = await this[`_find${opts.one? 'One' : ''}`](opts.query, util.omit(opts, custom))
|
|
96
84
|
} else {
|
|
97
85
|
loop: for (let item of opts.populate) {
|
|
98
86
|
let path = util.isObject(item)? item.as : item
|
|
99
87
|
// Blacklisted?
|
|
100
|
-
if (
|
|
88
|
+
if (this._pathBlacklisted(path, opts.projection)) continue loop
|
|
101
89
|
// Custom $lookup definition
|
|
102
90
|
// https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-36-lookup-expr
|
|
103
91
|
if (util.isObject(item)) {
|
|
@@ -118,7 +106,7 @@ module.exports = {
|
|
|
118
106
|
continue
|
|
119
107
|
}
|
|
120
108
|
// Populate model (convert array into document & create lookup)
|
|
121
|
-
|
|
109
|
+
(opts.addFields = opts.addFields || {})[path] = { '$arrayElemAt': [ '$' + path, 0 ] }
|
|
122
110
|
lookups.push({ $lookup: {
|
|
123
111
|
from: modelName,
|
|
124
112
|
localField: path,
|
|
@@ -127,23 +115,26 @@ module.exports = {
|
|
|
127
115
|
}})
|
|
128
116
|
}
|
|
129
117
|
}
|
|
130
|
-
// console.log(1,
|
|
118
|
+
// console.log(1, opts.projection)
|
|
131
119
|
// console.log(2, lookups)
|
|
132
120
|
let aggregate = [
|
|
133
121
|
{ $match: opts.query },
|
|
134
|
-
{ $sort:
|
|
135
|
-
{ $skip:
|
|
136
|
-
...(
|
|
122
|
+
{ $sort: opts.sort },
|
|
123
|
+
{ $skip: opts.skip },
|
|
124
|
+
...(opts.limit? [{ $limit: opts.limit }] : []),
|
|
137
125
|
...lookups,
|
|
138
|
-
...
|
|
139
|
-
{ $project:
|
|
126
|
+
...(opts.addFields? [{ $addFields: opts.addFields }] : []),
|
|
127
|
+
...(opts.projection? [{ $project: opts.projection }] : []),
|
|
140
128
|
]
|
|
141
129
|
response = await this._aggregate(aggregate)
|
|
142
130
|
this.info('aggregate', JSON.stringify(aggregate))
|
|
143
131
|
}
|
|
144
132
|
|
|
133
|
+
// Returning one?
|
|
145
134
|
if (opts.one && util.isArray(response)) response = response[0]
|
|
146
|
-
|
|
135
|
+
|
|
136
|
+
// Process afterFind hooks
|
|
137
|
+
response = await this._processAfterFind(response, opts.projection, opts)
|
|
147
138
|
|
|
148
139
|
// Success
|
|
149
140
|
if (cb) cb(null, response)
|
|
@@ -161,26 +152,27 @@ module.exports = {
|
|
|
161
152
|
return this.find(opts, cb, true)
|
|
162
153
|
},
|
|
163
154
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
155
|
+
findOneAndUpdate: function(opts, cb) {
|
|
156
|
+
return this._findOneAndUpdate(opts, cb)
|
|
157
|
+
},
|
|
167
158
|
|
|
168
159
|
update: async function(opts, cb) {
|
|
169
160
|
/**
|
|
170
161
|
* Updates document(s) with monk after validating data & before hooks.
|
|
171
162
|
* @param {object} opts
|
|
163
|
+
* @param {object|array} opts.data - mongodb document update object(s)
|
|
164
|
+
* @param {array|string|false} <opts.blacklist> - augment schema.updateBL, `false` will remove blacklisting
|
|
165
|
+
* @param {array|string} <opts.project> - return only these fields, ignores blacklisting
|
|
172
166
|
* @param {object} <opts.query> - mongodb query object
|
|
173
|
-
* @param {object|array} <opts.data> - mongodb document update object(s)
|
|
174
167
|
* @param {boolean} <opts.respond> - automatically call res.json/error (requires opts.req)
|
|
175
|
-
* @param {array|string|false} validateUndefined - validates all 'required' undefined fields, true by
|
|
176
|
-
* default, but false on update
|
|
177
168
|
* @param {array|string|true} <opts.skipValidation> - skip validation for this field name(s)
|
|
178
169
|
* @param {boolean} <opts.timestamps> - whether `updatedAt` is automatically updated
|
|
179
|
-
* @param {array|string|false} <opts.
|
|
180
|
-
*
|
|
170
|
+
* @param {array|string|false} <opts.validateUndefined> - validates all 'required' undefined fields, true by
|
|
171
|
+
* default, but false on update
|
|
172
|
+
* @param {any} <any mongodb option>
|
|
181
173
|
* @param {function} <cb> - execute cb(err, data) instead of responding
|
|
182
|
-
* @this model
|
|
183
174
|
* @return promise(data)
|
|
175
|
+
* @this model
|
|
184
176
|
*/
|
|
185
177
|
if (cb && !util.isFunction(cb)) {
|
|
186
178
|
throw new Error(`The callback passed to ${this.name}.update() is not a function`)
|
|
@@ -190,7 +182,7 @@ module.exports = {
|
|
|
190
182
|
let data = opts.data
|
|
191
183
|
let response = null
|
|
192
184
|
let operators = util.pluck(opts, [/^\$/])
|
|
193
|
-
let
|
|
185
|
+
let custom = ['blacklist', 'data', 'query', 'respond', 'skipValidation', 'validateUndefined']
|
|
194
186
|
|
|
195
187
|
// Validate
|
|
196
188
|
if (util.isDefined(data)) data = await this.validate(opts.data, { ...opts })
|
|
@@ -209,7 +201,7 @@ module.exports = {
|
|
|
209
201
|
operators['$set'] = { ...data, ...(operators['$set'] || {}) }
|
|
210
202
|
}
|
|
211
203
|
// Update
|
|
212
|
-
let update = await this._update(opts.query, operators,
|
|
204
|
+
let update = await this._update(opts.query, operators, util.omit(opts, custom))
|
|
213
205
|
if (update.n) response = Object.assign(Object.create({ _output: update }), operators['$set']||{})
|
|
214
206
|
|
|
215
207
|
// Hook: afterUpdate (doesn't have access to validated data)
|
|
@@ -234,22 +226,22 @@ module.exports = {
|
|
|
234
226
|
* @param {object} <opts.query> - mongodb query object
|
|
235
227
|
* @param {boolean} <opts.respond> - automatically call res.json/error (requires opts.req)
|
|
236
228
|
* @param {boolean=true} <opts.multi> - set to false to limit the deletion to just one document
|
|
237
|
-
* @param {any} <
|
|
229
|
+
* @param {any} <any mongodb option>
|
|
238
230
|
* @param {function} <cb> - execute cb(err, data) instead of responding
|
|
239
|
-
* @this model
|
|
240
231
|
* @return promise
|
|
232
|
+
* @this model
|
|
241
233
|
*/
|
|
242
234
|
if (cb && !util.isFunction(cb)) {
|
|
243
235
|
throw new Error(`The callback passed to ${this.name}.remove() is not a function`)
|
|
244
236
|
}
|
|
245
237
|
try {
|
|
246
238
|
opts = await this._queryObject(opts, 'remove')
|
|
247
|
-
let
|
|
239
|
+
let custom = ['query', 'respond']
|
|
248
240
|
if (util.isEmpty(opts.query)) throw new Error('Please specify opts.query')
|
|
249
241
|
|
|
250
242
|
// Remove
|
|
251
243
|
await util.runSeries(this.beforeRemove.map(f => f.bind(opts)))
|
|
252
|
-
let response = await this._remove(opts.query,
|
|
244
|
+
let response = await this._remove(opts.query, util.omit(opts, custom))
|
|
253
245
|
await util.runSeries(this.afterRemove.map(f => f.bind(response)))
|
|
254
246
|
|
|
255
247
|
// Success
|
|
@@ -264,13 +256,16 @@ module.exports = {
|
|
|
264
256
|
}
|
|
265
257
|
},
|
|
266
258
|
|
|
267
|
-
|
|
259
|
+
_getProjectionFromBlacklist: function(type, customBlacklist) {
|
|
268
260
|
/**
|
|
269
261
|
* Returns an exclusion projection
|
|
270
262
|
*
|
|
263
|
+
* Path collisions are removed
|
|
264
|
+
* E.g. ['pets.dogs', 'pets.dogs.name', '-cat', 'pets.dogs.age'] = { 'pets.dog': 0 }
|
|
265
|
+
*
|
|
271
266
|
* @param {string} type - find, insert, or update
|
|
272
|
-
* @param {array} customBlacklist - normally passed through options
|
|
273
|
-
* @return {array} exclusion
|
|
267
|
+
* @param {array|string|false} customBlacklist - normally passed through options
|
|
268
|
+
* @return {array|undefined} exclusion $project {'pets.name': 0}
|
|
274
269
|
* @this model
|
|
275
270
|
*
|
|
276
271
|
* 1. collate deep-blacklists
|
|
@@ -281,16 +276,24 @@ module.exports = {
|
|
|
281
276
|
let list = []
|
|
282
277
|
let manager = this.manager
|
|
283
278
|
let projection = {}
|
|
279
|
+
if (customBlacklist === false) return
|
|
280
|
+
|
|
281
|
+
// String?
|
|
282
|
+
if (typeof customBlacklist === 'string') {
|
|
283
|
+
customBlacklist = customBlacklist.trim().split(/\s+/)
|
|
284
|
+
}
|
|
284
285
|
|
|
285
286
|
// Concat deep blacklists
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
287
|
+
if (type == 'find') {
|
|
288
|
+
util.forEach(this.fieldsFlattened, (schema, path) => {
|
|
289
|
+
if (!schema.model) return
|
|
290
|
+
let deepBL = manager.model[schema.model][`${type}BL`] || []
|
|
291
|
+
let pathWithoutArrays = path.replace(/\.0(\.|$)/, '$1')
|
|
292
|
+
list = list.concat(deepBL.map(o => {
|
|
293
|
+
return `${o.charAt(0) == '-'? '-' : ''}${pathWithoutArrays}.${o.replace(/^-/, '')}`
|
|
294
|
+
}))
|
|
295
|
+
})
|
|
296
|
+
}
|
|
294
297
|
|
|
295
298
|
// Concat model, and custom blacklists
|
|
296
299
|
list = list.concat([...this[`${type}BL`]]).concat(customBlacklist || [])
|
|
@@ -302,7 +305,8 @@ module.exports = {
|
|
|
302
305
|
|
|
303
306
|
// Remove any child fields. E.g remove { user.token: 0 } = key2 if iterating { user: 0 } = key
|
|
304
307
|
for (let key2 in projection) {
|
|
305
|
-
|
|
308
|
+
// todo: need to write a test, testing that this is scoped to \.
|
|
309
|
+
if (key2.match(new RegExp('^' + key.replace(/\./g, '\\.') + '(\\.|$)'))) {
|
|
306
310
|
delete projection[key2]
|
|
307
311
|
}
|
|
308
312
|
}
|
|
@@ -335,6 +339,28 @@ module.exports = {
|
|
|
335
339
|
if (projection[key]) delete projection[key]
|
|
336
340
|
}
|
|
337
341
|
|
|
342
|
+
return util.isEmpty(projection) ? undefined : projection
|
|
343
|
+
},
|
|
344
|
+
|
|
345
|
+
_getProjectionFromProject: function(customProject) {
|
|
346
|
+
/**
|
|
347
|
+
* Returns an in/exclusion projection
|
|
348
|
+
* todo: tests
|
|
349
|
+
*
|
|
350
|
+
* @param {object|array|string} customProject - normally passed through options
|
|
351
|
+
* @return {array|undefined} in/exclusion projection {'pets.name': 0}
|
|
352
|
+
* @this model
|
|
353
|
+
*/
|
|
354
|
+
let projection
|
|
355
|
+
if (util.isString(customProject)) {
|
|
356
|
+
customProject = customProject.trim().split(/\s+/)
|
|
357
|
+
}
|
|
358
|
+
if (util.isArray(customProject)) {
|
|
359
|
+
projection = customProject.reduce((o, v) => {
|
|
360
|
+
o[v.replace(/^-/, '')] = v.match(/^-/)? 0 : 1
|
|
361
|
+
return o
|
|
362
|
+
}, {})
|
|
363
|
+
}
|
|
338
364
|
return projection
|
|
339
365
|
},
|
|
340
366
|
|
|
@@ -344,9 +370,10 @@ module.exports = {
|
|
|
344
370
|
* @param {MongoId|string|object} opts
|
|
345
371
|
* @param {string} type - operation type
|
|
346
372
|
* @param {boolean} one - return one document
|
|
347
|
-
* @this model
|
|
348
373
|
* @return {Promise} opts
|
|
374
|
+
* @this model
|
|
349
375
|
*
|
|
376
|
+
* Query parsing logic:
|
|
350
377
|
* opts == string|MongodId - treated as an id
|
|
351
378
|
* opts == undefined|null|false - throw error
|
|
352
379
|
* opts.query == string|MongodID - treated as an id
|
|
@@ -389,6 +416,7 @@ module.exports = {
|
|
|
389
416
|
if (!util.isDefined(opts.data) && util.isDefined((opts.req||{}).body)) opts.data = opts.req.body
|
|
390
417
|
if (util.isDefined(opts.data)) opts.data = await util.parseData(opts.data)
|
|
391
418
|
|
|
419
|
+
opts.type = type
|
|
392
420
|
opts[type] = true
|
|
393
421
|
opts.model = this
|
|
394
422
|
return opts
|
|
@@ -400,11 +428,12 @@ module.exports = {
|
|
|
400
428
|
* Recurses through fields that are models and populates missing default-fields and calls model.afterFind([fn,..])
|
|
401
429
|
* Be sure to add any virtual fields to the schema that your populating on,
|
|
402
430
|
* e.g. "nurses": [{ model: 'user' }]
|
|
431
|
+
*
|
|
403
432
|
* @param {object|array|null} data
|
|
404
433
|
* @param {object} projection - $project object
|
|
405
434
|
* @param {object} afterFindContext - handy context object given to schema.afterFind
|
|
406
|
-
* @this model
|
|
407
435
|
* @return Promise(data)
|
|
436
|
+
* @this model
|
|
408
437
|
*/
|
|
409
438
|
// Recurse down from the parent model, ending with the parent model as the parent afterFind hook may
|
|
410
439
|
// want to manipulate any populated models
|
|
@@ -422,8 +451,8 @@ module.exports = {
|
|
|
422
451
|
if (!util.isDefined(schema.default) || path.match(/^\.?(createdAt|updatedAt)$/)) return
|
|
423
452
|
let parentPath = item.fieldName? item.fieldName + '.' : ''
|
|
424
453
|
let pathWithoutArrays = (parentPath + path).replace(/\.0(\.|$)/, '$1')
|
|
425
|
-
// Ignore default fields that are
|
|
426
|
-
if (
|
|
454
|
+
// Ignore default fields that are blacklisted
|
|
455
|
+
if (this._pathBlacklisted(pathWithoutArrays, projection)) return
|
|
427
456
|
// console.log(pathWithoutArrays, path, projection)
|
|
428
457
|
// Set value
|
|
429
458
|
let value = util.isFunction(schema.default)? schema.default(this.manager) : schema.default
|
|
@@ -438,39 +467,34 @@ module.exports = {
|
|
|
438
467
|
return util.runSeries(callbackSeries).then(() => data)
|
|
439
468
|
},
|
|
440
469
|
|
|
441
|
-
|
|
470
|
+
_pathBlacklisted: function(path, projection, matchDeepWhitelistedKeys=true) {
|
|
442
471
|
/**
|
|
443
|
-
* Checks if the path is
|
|
472
|
+
* Checks if the path is blacklisted within a inclusion/exclusion projection
|
|
444
473
|
* @param {string} path - path without array brackets e.g. '.[]'
|
|
445
|
-
* @param {object} projection
|
|
446
|
-
* @param {boolean}
|
|
474
|
+
* @param {object} projection - inclusion/exclusion projection, not mixed
|
|
475
|
+
* @param {boolean} matchDeepWhitelistedKeys - match deep whitelisted keys containing path
|
|
476
|
+
* E.g. pets.color == pets.color.age
|
|
447
477
|
* @return {boolean}
|
|
448
478
|
*/
|
|
449
|
-
let inc
|
|
450
|
-
// console.log(path, projection)
|
|
451
479
|
for (let key in projection) {
|
|
452
|
-
if (projection[key]
|
|
453
|
-
// Inclusion
|
|
454
|
-
// E.g.
|
|
455
|
-
// E.g.
|
|
456
|
-
|
|
457
|
-
inc = true
|
|
458
|
-
if (key.match(new RegExp('^' + path.replace(/\./g, '\\.') + '(\\.|$)'))) return true
|
|
459
|
-
if (path.match(new RegExp('^' + key.replace(/\./g, '\\.') + '(\\.|$)'))) return true
|
|
460
|
-
} else if (projection[key]) {
|
|
461
|
-
// Inclusion (equal to key, or key included in path)
|
|
462
|
-
// E.g. 'pets.color.age'.match(/^pets.color.age(.|$)/) = match
|
|
463
|
-
// E.g. 'pets.color.age'.match(/^pets.color(.|$)/) = match
|
|
464
|
-
// E.g. 'pets.color'.match(/^pets.color.age(.|$)/) = no match
|
|
465
|
-
inc = true
|
|
466
|
-
if (path.match(new RegExp('^' + key.replace(/\./g, '\\.') + '(\\.|$)'))) return true
|
|
467
|
-
} else {
|
|
468
|
-
// Exclusion
|
|
469
|
-
// E.g. 'pets.color.age'.match(/^pets.color(.|$)/) = match
|
|
480
|
+
if (projection[key]) {
|
|
481
|
+
// Inclusion (whitelisted)
|
|
482
|
+
// E.g. pets.color.age == pets.color.age (exact match)
|
|
483
|
+
// E.g. pets.color.age == pets.color (path contains key)
|
|
484
|
+
var inclusion = true
|
|
470
485
|
if (path.match(new RegExp('^' + key.replace(/\./g, '\\.') + '(\\.|$)'))) return false
|
|
486
|
+
if (matchDeepWhitelistedKeys) {
|
|
487
|
+
// E.g. pets.color == pets.color.age (key contains path)
|
|
488
|
+
if (key.match(new RegExp('^' + path.replace(/\./g, '\\.') + '\\.'))) return false
|
|
489
|
+
}
|
|
490
|
+
} else {
|
|
491
|
+
// Exclusion (blacklisted)
|
|
492
|
+
// E.g. pets.color.age == pets.color.age (exact match)
|
|
493
|
+
// E.g. pets.color.age == pets.color (path contains key)
|
|
494
|
+
if (path.match(new RegExp('^' + key.replace(/\./g, '\\.') + '(\\.|$)'))) return true
|
|
471
495
|
}
|
|
472
496
|
}
|
|
473
|
-
return
|
|
497
|
+
return inclusion? true : false
|
|
474
498
|
},
|
|
475
499
|
|
|
476
500
|
_recurseAndFindModels: function(fields, dataArr) {
|
package/lib/model-validate.js
CHANGED
|
@@ -6,21 +6,19 @@ module.exports = {
|
|
|
6
6
|
validate: 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 {
|
|
13
|
-
* @param {array|string
|
|
14
|
-
* @param {array|string}
|
|
15
|
-
* @param {
|
|
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
|
|
@@ -31,41 +29,12 @@ module.exports = {
|
|
|
31
29
|
data = util.deepCopy(data)
|
|
32
30
|
opts = opts || {}
|
|
33
31
|
opts.insert = !opts.update
|
|
34
|
-
opts.action = opts.update? 'update' : 'insert'
|
|
35
|
-
opts.skipValidation = opts.skipValidation === true? true : util.toArray(opts.skipValidation||[])
|
|
32
|
+
opts.action = opts.update ? 'update' : 'insert'
|
|
33
|
+
opts.skipValidation = opts.skipValidation === true ? true : util.toArray(opts.skipValidation||[])
|
|
36
34
|
|
|
37
|
-
//
|
|
38
|
-
if (opts.
|
|
39
|
-
|
|
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
|
-
}
|
|
35
|
+
// Get projection
|
|
36
|
+
if (opts.project) opts.projection = this._getProjectionFromProject(opts.project)
|
|
37
|
+
else opts.projection = this._getProjectionFromBlacklist(opts.action, opts.blacklist)
|
|
69
38
|
|
|
70
39
|
// Run before hook, then recurse through the model's fields
|
|
71
40
|
return util.runSeries(this.beforeValidate.map(f => f.bind(opts, data))).then(() => {
|
|
@@ -140,7 +109,7 @@ module.exports = {
|
|
|
140
109
|
let value = util.isArray(fields)? data : (data||{})[fieldName]
|
|
141
110
|
let indexOrFieldName = util.isArray(fields)? i : fieldName
|
|
142
111
|
let path2 = `${path}.${indexOrFieldName}`.replace(/^\./, '')
|
|
143
|
-
let path3 = path2.replace(/(^|\.)[0-9]+(\.|$)/, '$2') // no numerical keys, e.g. pets.1.name
|
|
112
|
+
let path3 = path2.replace(/(^|\.)[0-9]+(\.|$)/, '$2') // no numerical keys, e.g. pets.1.name = pets.name
|
|
144
113
|
let isType = 'is' + util.ucFirst(schema.type)
|
|
145
114
|
let isTypeRule = this.rules[isType] || rules[isType]
|
|
146
115
|
|
|
@@ -157,7 +126,7 @@ module.exports = {
|
|
|
157
126
|
}
|
|
158
127
|
|
|
159
128
|
// Ignore blacklisted
|
|
160
|
-
if (
|
|
129
|
+
if (this._pathBlacklisted(path3, opts.projection) && !schema.defaultOverride) return
|
|
161
130
|
// Ignore insert only
|
|
162
131
|
if (opts.update && schema.insertOnly) return
|
|
163
132
|
// Ignore virtual fields
|
|
@@ -218,8 +187,8 @@ module.exports = {
|
|
|
218
187
|
* @param {object} field - field schema
|
|
219
188
|
* @param {string} path - full field path
|
|
220
189
|
* @param {object} opts - original validate() options
|
|
221
|
-
* @this model
|
|
222
190
|
* @return {array} errors
|
|
191
|
+
* @this model
|
|
223
192
|
*/
|
|
224
193
|
let errors = []
|
|
225
194
|
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)
|
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.
|
|
5
|
+
"version": "1.35.0",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": "github:boycce/monastery",
|
|
8
8
|
"homepage": "https://boycce.github.io/monastery/",
|
package/test/blacklisting.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module.exports = function(monastery, opendb) {
|
|
2
2
|
|
|
3
|
-
test('find blacklisting', async () => {
|
|
3
|
+
test('find blacklisting basic', async () => {
|
|
4
4
|
// Setup
|
|
5
5
|
let db = (await opendb(null)).db
|
|
6
6
|
let bird = db.model('bird', {
|
|
@@ -93,7 +93,7 @@ module.exports = function(monastery, opendb) {
|
|
|
93
93
|
}
|
|
94
94
|
}})
|
|
95
95
|
|
|
96
|
-
//
|
|
96
|
+
// initial blacklist
|
|
97
97
|
let find1 = await user.findOne({
|
|
98
98
|
query: user1._id
|
|
99
99
|
})
|
|
@@ -107,18 +107,30 @@ module.exports = function(monastery, opendb) {
|
|
|
107
107
|
deepModel: { myBird: bird1._id }
|
|
108
108
|
})
|
|
109
109
|
|
|
110
|
-
//
|
|
110
|
+
// augmented blacklist
|
|
111
111
|
let find2 = await user.findOne({
|
|
112
112
|
query: user1._id,
|
|
113
113
|
blacklist: ['pet', 'pet', 'deep', 'deepModel', '-dog', '-animals.cat']
|
|
114
114
|
})
|
|
115
|
-
|
|
115
|
+
let customBlacklist
|
|
116
|
+
expect(find2).toEqual((customBlacklist = {
|
|
116
117
|
_id: user1._id,
|
|
117
118
|
dog: 'Bruce',
|
|
118
119
|
list: [44, 54],
|
|
119
120
|
pets: [{ name: 'Pluto' }, { name: 'Milo' }],
|
|
120
121
|
animals: { dog: 'Max', cat: 'Ginger' }
|
|
122
|
+
}))
|
|
123
|
+
|
|
124
|
+
// blacklist string
|
|
125
|
+
let find3 = await user.findOne({
|
|
126
|
+
query: user1._id,
|
|
127
|
+
blacklist: 'pet pet deep deepModel -dog -animals.cat'
|
|
121
128
|
})
|
|
129
|
+
expect(find3).toEqual(customBlacklist)
|
|
130
|
+
|
|
131
|
+
// blacklist removal
|
|
132
|
+
let find4 = await user.findOne({ query: user1._id, blacklist: false })
|
|
133
|
+
expect(find4).toEqual(user1)
|
|
122
134
|
|
|
123
135
|
db.close()
|
|
124
136
|
})
|
|
@@ -219,6 +231,11 @@ module.exports = function(monastery, opendb) {
|
|
|
219
231
|
_id: user1._id,
|
|
220
232
|
bird5: { ...bird1Base, name: 'ponyo', height: 40 },
|
|
221
233
|
})
|
|
234
|
+
// blacklist removal
|
|
235
|
+
expect(await user.findOne({ query: user1._id, blacklist: false, populate: ['bird1'] })).toEqual({
|
|
236
|
+
...user1,
|
|
237
|
+
bird1: { ...bird1Base, height: 40, name: 'ponyo', wing: { size: 1, sizes: { one: 1, two: 1 }} },
|
|
238
|
+
})
|
|
222
239
|
|
|
223
240
|
db.close()
|
|
224
241
|
})
|
|
@@ -243,13 +260,13 @@ module.exports = function(monastery, opendb) {
|
|
|
243
260
|
},
|
|
244
261
|
})
|
|
245
262
|
// default
|
|
246
|
-
expect(db.user.
|
|
263
|
+
expect(db.user._getProjectionFromBlacklist('find')).toEqual({
|
|
247
264
|
'bird1.wing': 0,
|
|
248
265
|
'bird1.age': 0,
|
|
249
266
|
'password': 0,
|
|
250
267
|
})
|
|
251
268
|
// blacklist /w invalid field (which goes through)
|
|
252
|
-
expect(db.user.
|
|
269
|
+
expect(db.user._getProjectionFromBlacklist('find', ['name', 'invalidfield'])).toEqual({
|
|
253
270
|
'bird1.wing': 0,
|
|
254
271
|
'bird1.age': 0,
|
|
255
272
|
'invalidfield': 0,
|
|
@@ -257,38 +274,38 @@ module.exports = function(monastery, opendb) {
|
|
|
257
274
|
'password': 0,
|
|
258
275
|
})
|
|
259
276
|
// whitelist
|
|
260
|
-
expect(db.user.
|
|
277
|
+
expect(db.user._getProjectionFromBlacklist('find', ['-password', '-bird1.age'])).toEqual({
|
|
261
278
|
'bird1.wing': 0,
|
|
262
279
|
})
|
|
263
280
|
// whitelist parent
|
|
264
|
-
expect(db.user.
|
|
281
|
+
expect(db.user._getProjectionFromBlacklist('find', ['-bird1'])).toEqual({
|
|
265
282
|
'password': 0,
|
|
266
283
|
})
|
|
267
284
|
// whitelist parent, then blacklist child
|
|
268
|
-
expect(db.user.
|
|
285
|
+
expect(db.user._getProjectionFromBlacklist('find', ['-bird1', 'bird1.name'])).toEqual({
|
|
269
286
|
'password': 0,
|
|
270
287
|
'bird1.name': 0,
|
|
271
288
|
})
|
|
272
289
|
// the model's blacklists are applied after deep model's
|
|
273
290
|
db.user.findBL = ['-bird1.age']
|
|
274
|
-
expect(db.user.
|
|
291
|
+
expect(db.user._getProjectionFromBlacklist('find')).toEqual({
|
|
275
292
|
'bird1.wing': 0,
|
|
276
293
|
})
|
|
277
294
|
// custom blacklists are applied after the model's, which are after deep model's
|
|
278
295
|
db.user.findBL = ['-bird1.age']
|
|
279
|
-
expect(db.user.
|
|
296
|
+
expect(db.user._getProjectionFromBlacklist('find', ['bird1'])).toEqual({
|
|
280
297
|
'bird1': 0,
|
|
281
298
|
})
|
|
282
299
|
// blacklisted parent with a blacklisted child
|
|
283
|
-
expect(db.user.
|
|
300
|
+
expect(db.user._getProjectionFromBlacklist('find', ['bird1', 'bird1.wing'])).toEqual({
|
|
284
301
|
'bird1': 0,
|
|
285
302
|
})
|
|
286
303
|
// A mess of things
|
|
287
|
-
expect(db.user.
|
|
304
|
+
expect(db.user._getProjectionFromBlacklist('find', ['-bird1', 'bird1.wing', '-bird1.wing','bird1.wing.size'])).toEqual({
|
|
288
305
|
'bird1.wing.size': 0,
|
|
289
306
|
})
|
|
290
307
|
// blacklisted parent with a whitelisted child (expect blacklist expansion in future version?)
|
|
291
|
-
// expect(db.user.
|
|
308
|
+
// expect(db.user._getProjectionFromBlacklist('find', ['bird1', '-bird1.wing'])).toEqual({
|
|
292
309
|
// 'bird1.age': 0,
|
|
293
310
|
// 'bird1.name': 0,
|
|
294
311
|
// })
|
|
@@ -296,7 +313,7 @@ module.exports = function(monastery, opendb) {
|
|
|
296
313
|
db.close()
|
|
297
314
|
})
|
|
298
315
|
|
|
299
|
-
test('find project', async () => {
|
|
316
|
+
test('find project basic', async () => {
|
|
300
317
|
// Test mongodb native project option
|
|
301
318
|
// Setup
|
|
302
319
|
let db = (await opendb(null)).db
|
|
@@ -370,14 +387,14 @@ module.exports = function(monastery, opendb) {
|
|
|
370
387
|
color: { type: 'string', default: 'red' },
|
|
371
388
|
}
|
|
372
389
|
},
|
|
373
|
-
findBL: ['age']
|
|
390
|
+
findBL: ['age'],
|
|
374
391
|
})
|
|
375
392
|
let user = db.model('user', {
|
|
376
393
|
fields: {
|
|
377
394
|
dog: { type: 'string' },
|
|
378
395
|
bird: { model: 'bird' },
|
|
379
396
|
bird2: { model: 'bird' },
|
|
380
|
-
bird3: { model: 'bird' }
|
|
397
|
+
bird3: { model: 'bird' },
|
|
381
398
|
},
|
|
382
399
|
findBL: [
|
|
383
400
|
// allll these should be ignored.....?/////
|
|
@@ -390,38 +407,47 @@ module.exports = function(monastery, opendb) {
|
|
|
390
407
|
name: 'ponyo',
|
|
391
408
|
age: 3,
|
|
392
409
|
height: 40,
|
|
393
|
-
sub: {}
|
|
410
|
+
sub: {},
|
|
394
411
|
}})
|
|
395
412
|
let user1 = await user.insert({ data: {
|
|
396
413
|
dog: 'Bruce',
|
|
397
414
|
bird: bird1._id,
|
|
398
415
|
bird2: bird1._id,
|
|
399
|
-
bird3: bird1._id
|
|
416
|
+
bird3: bird1._id,
|
|
400
417
|
}})
|
|
401
418
|
|
|
402
|
-
//
|
|
419
|
+
// project
|
|
403
420
|
let find1 = await user.findOne({
|
|
404
421
|
query: user1._id,
|
|
405
422
|
populate: ['bird', 'bird2'],
|
|
406
|
-
project: ['bird.age', 'bird2']
|
|
423
|
+
project: ['bird.age', 'bird2'],
|
|
407
424
|
})
|
|
408
425
|
expect(find1).toEqual({
|
|
409
426
|
_id: user1._id,
|
|
410
427
|
bird: { age: 3 },
|
|
411
|
-
bird2: { _id: bird1._id, age: 3, name: 'ponyo', height: 40, color: 'red', sub: { color: 'red' }}
|
|
428
|
+
bird2: { _id: bird1._id, age: 3, name: 'ponyo', height: 40, color: 'red', sub: { color: 'red' }},
|
|
412
429
|
})
|
|
413
430
|
|
|
414
|
-
//
|
|
431
|
+
// project (different project details)
|
|
415
432
|
let find2 = await user.findOne({
|
|
416
433
|
query: user1._id,
|
|
417
434
|
populate: ['bird', 'bird2'],
|
|
418
|
-
project: ['bird', 'bird2.height']
|
|
435
|
+
project: ['bird', 'bird2.height'],
|
|
419
436
|
})
|
|
420
|
-
|
|
437
|
+
let customProject
|
|
438
|
+
expect(find2).toEqual((customProject={
|
|
421
439
|
_id: user1._id,
|
|
422
440
|
bird: { _id: bird1._id, age: 3, name: 'ponyo', height: 40, color: 'red', sub: { color: 'red' }},
|
|
423
441
|
bird2: { height: 40 },
|
|
442
|
+
}))
|
|
443
|
+
|
|
444
|
+
// project string
|
|
445
|
+
let find3 = await user.findOne({
|
|
446
|
+
query: user1._id,
|
|
447
|
+
populate: ['bird', 'bird2'],
|
|
448
|
+
project: 'bird bird2.height',
|
|
424
449
|
})
|
|
450
|
+
expect(find3).toEqual(customProject)
|
|
425
451
|
|
|
426
452
|
db.close()
|
|
427
453
|
})
|
|
@@ -506,7 +532,8 @@ module.exports = function(monastery, opendb) {
|
|
|
506
532
|
'-deep' // blacklist a parent
|
|
507
533
|
],
|
|
508
534
|
})
|
|
509
|
-
|
|
535
|
+
let customBlacklist
|
|
536
|
+
expect(user2).toEqual((customBlacklist = {
|
|
510
537
|
list: [44, 54],
|
|
511
538
|
dog: 'Bruce',
|
|
512
539
|
pet: 'Freddy',
|
|
@@ -522,6 +549,36 @@ module.exports = function(monastery, opendb) {
|
|
|
522
549
|
}
|
|
523
550
|
}
|
|
524
551
|
}
|
|
552
|
+
}))
|
|
553
|
+
|
|
554
|
+
// Blacklist string
|
|
555
|
+
let user3 = await user.validate(doc1, {
|
|
556
|
+
blacklist: '-dog -animals.dog pets.name -hiddenList -deep'
|
|
557
|
+
})
|
|
558
|
+
expect(user3).toEqual(customBlacklist)
|
|
559
|
+
|
|
560
|
+
// Blacklist removal
|
|
561
|
+
let user4 = await user.validate(doc1, { blacklist: false })
|
|
562
|
+
expect(user4).toEqual(doc1)
|
|
563
|
+
|
|
564
|
+
// Project whitelist
|
|
565
|
+
let user5 = await user.validate(doc1, {
|
|
566
|
+
project: [
|
|
567
|
+
'dog',
|
|
568
|
+
'pets.name',
|
|
569
|
+
'deep'
|
|
570
|
+
],
|
|
571
|
+
})
|
|
572
|
+
expect(user5).toEqual({
|
|
573
|
+
dog: 'Bruce',
|
|
574
|
+
pets: [ {name: 'Pluto'}, {name: 'Milo'} ],
|
|
575
|
+
deep: {
|
|
576
|
+
deep2: {
|
|
577
|
+
deep3: {
|
|
578
|
+
deep4: 'hideme'
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
525
582
|
})
|
|
526
583
|
db.close()
|
|
527
584
|
})
|