monastery 2.2.3 → 3.0.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 +10 -1
- package/changelog.md +1 -1
- package/docs/_config.yml +2 -2
- package/docs/assets/imgs/monastery.jpg +0 -0
- package/docs/definition/index.md +1 -2
- package/docs/manager/index.md +19 -11
- package/docs/manager/model.md +1 -1
- package/docs/manager/models.md +2 -3
- package/docs/model/...rawMethods.md +289 -0
- package/docs/model/count.md +25 -0
- package/docs/model/find.md +5 -9
- package/docs/model/findOne.md +1 -1
- package/docs/model/findOneAndUpdate.md +1 -1
- package/docs/model/index.md +5 -30
- package/docs/model/insert.md +4 -6
- package/docs/model/remove.md +4 -6
- package/docs/model/update.md +4 -6
- package/docs/readme.md +80 -47
- package/lib/collection.js +324 -0
- package/lib/index.js +207 -67
- package/lib/model-crud.js +605 -619
- package/lib/model-validate.js +227 -245
- package/lib/model.js +70 -91
- package/lib/rules.js +36 -35
- package/lib/util.js +69 -15
- package/package.json +12 -12
- package/plugins/images/index.js +15 -26
- package/test/blacklisting.js +506 -537
- package/test/collection.js +445 -0
- package/test/crud.js +810 -730
- package/test/index.test.js +26 -0
- package/test/manager.js +77 -0
- package/test/mock/blacklisting.js +23 -23
- package/test/model.js +611 -572
- package/test/plugin-images.js +880 -965
- package/test/populate.js +249 -262
- package/test/util.js +126 -45
- package/test/validate.js +1074 -1121
- package/test/virtuals.js +222 -227
- package/lib/monk-monkey-patches.js +0 -90
- package/test/monk.js +0 -53
- package/test/test.js +0 -38
package/lib/model.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
let util = require('./util.js')
|
|
4
|
-
let validate = require('./model-validate.js')
|
|
1
|
+
const rules = require('./rules.js')
|
|
2
|
+
const util = require('./util.js')
|
|
5
3
|
|
|
6
|
-
|
|
4
|
+
function Model(name, opts, manager) {
|
|
7
5
|
/**
|
|
8
|
-
* Setup a model
|
|
6
|
+
* Setup a model
|
|
9
7
|
* @param {string} name
|
|
10
|
-
* @param {object} opts - see mongodb
|
|
8
|
+
* @param {object} opts - see mongodb collection documentation
|
|
11
9
|
* @param {boolean} opts.waitForIndexes
|
|
12
10
|
* @return Promise(model) | this
|
|
13
11
|
* @this model
|
|
@@ -112,26 +110,22 @@ let Model = module.exports = function(name, opts, manager) {
|
|
|
112
110
|
this._setupFields(this.fields = Object.assign({}, this._defaultFields, this.fields))
|
|
113
111
|
this.fieldsFlattened = this._getFieldsFlattened(this.fields, '') // test output?
|
|
114
112
|
|
|
115
|
-
//
|
|
116
|
-
this.
|
|
117
|
-
|
|
118
|
-
this
|
|
119
|
-
}
|
|
120
|
-
for (let key in (this._collection || {})) {
|
|
121
|
-
if (key.match(/^manager$|^options$|^_|^middlewares$|^name$/)) continue
|
|
122
|
-
this['_' + key] = this._collection[key].bind(this._collection)
|
|
113
|
+
// Get collection, and extend model with collection methods
|
|
114
|
+
this.collection = this.manager.get(name, { castIds: false })
|
|
115
|
+
for (let key in Object.getPrototypeOf(this.collection||{})) {
|
|
116
|
+
this['_' + key] = this.collection[key].bind(this.collection)
|
|
123
117
|
}
|
|
124
118
|
|
|
125
119
|
// Add model to manager
|
|
126
|
-
if (typeof this.manager[name]
|
|
127
|
-
this.manager[name] = this
|
|
128
|
-
} else {
|
|
120
|
+
if (typeof this.manager[name] !== 'undefined' && typeof this.manager.models[name] === 'undefined') {
|
|
129
121
|
this.warn(`Your model name '${name}' is conflicting with an builtin manager property, you are only able to
|
|
130
|
-
access this model via \`db.
|
|
122
|
+
access this model via \`db.models.${name}\``)
|
|
123
|
+
} else {
|
|
124
|
+
this.manager[name] = this
|
|
131
125
|
}
|
|
132
126
|
|
|
133
|
-
// Add model to manager.
|
|
134
|
-
this.manager.
|
|
127
|
+
// Add model to manager.models
|
|
128
|
+
this.manager.models[name] = this
|
|
135
129
|
|
|
136
130
|
// Setup/Ensure field indexes exist in MongoDB
|
|
137
131
|
let errHandler = err => {
|
|
@@ -212,8 +206,8 @@ Model.prototype._setupFields = function(fields) {
|
|
|
212
206
|
|
|
213
207
|
// Fields be an array
|
|
214
208
|
} else if (util.isArray(field)) {
|
|
215
|
-
let arrayDefault = this.manager.defaultObjects? () => [] : undefined
|
|
216
|
-
let nullObject = this.manager.nullObjects
|
|
209
|
+
let arrayDefault = this.manager.opts.defaultObjects? () => [] : undefined
|
|
210
|
+
let nullObject = this.manager.opts.nullObjects
|
|
217
211
|
let virtual = field.length == 1 && (field[0]||{}).virtual ? true : undefined
|
|
218
212
|
field.schema = {
|
|
219
213
|
type: 'array',
|
|
@@ -221,14 +215,14 @@ Model.prototype._setupFields = function(fields) {
|
|
|
221
215
|
default: arrayDefault,
|
|
222
216
|
nullObject: nullObject,
|
|
223
217
|
virtual: virtual,
|
|
224
|
-
...(field.schema || {})
|
|
218
|
+
...(field.schema || {}),
|
|
225
219
|
}
|
|
226
220
|
this._setupFields(field)
|
|
227
221
|
|
|
228
222
|
// Fields can be a subdocument, e.g. user.pet = { name: {}, ..}
|
|
229
223
|
} else if (util.isSubdocument(field)) {
|
|
230
|
-
let objectDefault = this.manager.defaultObjects? () => ({}) : undefined
|
|
231
|
-
let nullObject = this.manager.nullObjects
|
|
224
|
+
let objectDefault = this.manager.opts.defaultObjects? () => ({}) : undefined
|
|
225
|
+
let nullObject = this.manager.opts.nullObjects
|
|
232
226
|
let index2dsphere = util.isSubdocument2dsphere(field)
|
|
233
227
|
field.schema = field.schema || {}
|
|
234
228
|
if (index2dsphere) {
|
|
@@ -240,21 +234,20 @@ Model.prototype._setupFields = function(fields) {
|
|
|
240
234
|
isObject: true,
|
|
241
235
|
default: objectDefault,
|
|
242
236
|
nullObject: nullObject,
|
|
243
|
-
...field.schema
|
|
237
|
+
...field.schema,
|
|
244
238
|
}
|
|
245
239
|
this._setupFields(field)
|
|
246
240
|
}
|
|
247
241
|
}, this)
|
|
248
242
|
},
|
|
249
243
|
|
|
250
|
-
Model.prototype._setupIndexes = function(fields, opts={}) {
|
|
244
|
+
Model.prototype._setupIndexes = async function(fields, opts={}) {
|
|
251
245
|
/**
|
|
252
246
|
* Creates indexes for the model (multikey, and sub-document supported)
|
|
247
|
+
* Note: the collection be created beforehand???
|
|
253
248
|
* Note: only one text index per model(collection) is allowed due to mongodb limitations
|
|
254
|
-
* @link https://docs.mongodb.com/manual/reference/command/createIndexes/
|
|
255
|
-
* @link https://mongodb.github.io/node-mongodb-native/2.1/api/Collection.html#createIndexes
|
|
256
249
|
* @param {object} <fields>
|
|
257
|
-
* @return Promise( {array} indexes ensured
|
|
250
|
+
* @return Promise( {array} indexes ensured ) || error
|
|
258
251
|
*
|
|
259
252
|
* MongoDB index structures = [
|
|
260
253
|
* true = { name: 'name_1', key: { name: 1 } },
|
|
@@ -264,73 +257,61 @@ Model.prototype._setupIndexes = function(fields, opts={}) {
|
|
|
264
257
|
* ..
|
|
265
258
|
* ]
|
|
266
259
|
*/
|
|
267
|
-
let collection
|
|
268
260
|
let hasTextIndex = false
|
|
269
261
|
let indexes = []
|
|
270
262
|
let model = this
|
|
271
263
|
let textIndex = { name: 'text', key: {} }
|
|
272
264
|
|
|
273
265
|
// No db defined
|
|
274
|
-
if (!
|
|
275
|
-
|
|
276
|
-
type: 'info',
|
|
277
|
-
detail: `Skipping createIndex on the '${model.name}' model, no mongodb connection found.`
|
|
278
|
-
}
|
|
279
|
-
return Promise.reject(error)
|
|
266
|
+
if (!model.manager?._state?.match(/^open/)) {
|
|
267
|
+
throw new Error(`Skipping createIndex on the '${model.name||''}' model, no mongodb connection found.`)
|
|
280
268
|
}
|
|
281
269
|
|
|
282
270
|
// Find all indexes
|
|
283
271
|
recurseFields(fields || model.fields, '')
|
|
284
272
|
// console.log(2, indexes, fields)
|
|
285
273
|
if (hasTextIndex) indexes.push(textIndex)
|
|
286
|
-
if (opts.dryRun) return
|
|
287
|
-
if (!indexes.length) return
|
|
274
|
+
if (opts.dryRun) return indexes || []
|
|
275
|
+
if (!indexes.length) return [] // No indexes defined
|
|
288
276
|
|
|
289
277
|
// Create indexes
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
if (collections.length > 0) return collection.indexes()
|
|
299
|
-
else return Promise.resolve([])
|
|
300
|
-
})
|
|
301
|
-
.then(existingIndexes => {
|
|
302
|
-
// Remove any existing text index that has different options as createIndexes will throws error about this
|
|
303
|
-
let indexNames = []
|
|
304
|
-
if (!existingIndexes.length) return new Promise(res => res())
|
|
305
|
-
// console.log(0, textIndex)
|
|
306
|
-
// console.log(1, existingIndexes, indexes)
|
|
307
|
-
// Todo: Remove unused index names
|
|
278
|
+
// As of MongoDB 5 we no longer need to wait for the connection to be open
|
|
279
|
+
// await (model.manager._state == 'open' ? new Promise(res => res()) : model.manager).then()....
|
|
280
|
+
|
|
281
|
+
// Get collection
|
|
282
|
+
const collection = this.collection
|
|
283
|
+
|
|
284
|
+
// Get the collections indexes
|
|
285
|
+
const existingIndexes = await collection.indexes() // returns [] if collection doesn't exist
|
|
308
286
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
287
|
+
// Remove any existing text index that has different options as createIndexes will throws error about this
|
|
288
|
+
if (existingIndexes.length) {
|
|
289
|
+
// console.log(0, textIndex)
|
|
290
|
+
// console.log(1, existingIndexes, indexes)
|
|
291
|
+
// Todo: Remove unused index names
|
|
292
|
+
const textIndexNames = []
|
|
293
|
+
for (let existingIndex of existingIndexes) {
|
|
294
|
+
if (!existingIndex.textIndexVersion) continue
|
|
295
|
+
for (let index of indexes) {
|
|
296
|
+
let fieldsInTextIndex1 = Object.keys(existingIndex.weights).sort().join()
|
|
297
|
+
let fieldsInTextIndex2 = Object.keys(index.key).sort().join()
|
|
298
|
+
if (existingIndex.name == index.name && fieldsInTextIndex1 !== fieldsInTextIndex2) {
|
|
299
|
+
model.info(`Text index options are different for '${existingIndex.name}', removing old text index`)
|
|
300
|
+
if (!textIndexNames.includes(existingIndex.name)) {
|
|
301
|
+
textIndexNames.push(existingIndex.name)
|
|
318
302
|
}
|
|
319
303
|
}
|
|
320
304
|
}
|
|
305
|
+
}
|
|
306
|
+
for (let name of textIndexNames) {
|
|
307
|
+
await collection.dropIndex(name)
|
|
308
|
+
}
|
|
309
|
+
}
|
|
321
310
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
.then(() => {
|
|
327
|
-
// Ensure/create indexes
|
|
328
|
-
return collection.createIndexes(indexes)
|
|
329
|
-
})
|
|
330
|
-
.then(response => {
|
|
331
|
-
model.info('db index(s) created for ' + model.name)
|
|
332
|
-
return indexes
|
|
333
|
-
})
|
|
311
|
+
// create indexes
|
|
312
|
+
await collection.createIndexes(indexes)
|
|
313
|
+
model.info('db index(s) created for ' + model.name)
|
|
314
|
+
return indexes
|
|
334
315
|
|
|
335
316
|
function recurseFields(fields, parentPath) {
|
|
336
317
|
util.forEach(fields, (field, name) => {
|
|
@@ -362,29 +343,27 @@ Model.prototype._setupIndexes = function(fields, opts={}) {
|
|
|
362
343
|
Model.prototype._defaultFields = {
|
|
363
344
|
_id: {
|
|
364
345
|
insertOnly: true,
|
|
365
|
-
type: 'id'
|
|
346
|
+
type: 'id',
|
|
366
347
|
},
|
|
367
348
|
createdAt: {
|
|
368
349
|
default: function(fieldName, model) {
|
|
369
|
-
return model.manager.useMilliseconds? Date.now() : Math.floor(Date.now() / 1000)
|
|
350
|
+
return model.manager.opts.useMilliseconds? Date.now() : Math.floor(Date.now() / 1000)
|
|
370
351
|
},
|
|
371
352
|
insertOnly: true,
|
|
372
353
|
timestampField: true,
|
|
373
|
-
type: 'integer'
|
|
354
|
+
type: 'integer',
|
|
374
355
|
},
|
|
375
356
|
updatedAt: {
|
|
376
357
|
default: function(fieldName, model) {
|
|
377
|
-
return model.manager.useMilliseconds? Date.now() : Math.floor(Date.now() / 1000)
|
|
358
|
+
return model.manager.opts.useMilliseconds? Date.now() : Math.floor(Date.now() / 1000)
|
|
378
359
|
},
|
|
379
360
|
timestampField: true,
|
|
380
|
-
type: 'integer'
|
|
381
|
-
}
|
|
361
|
+
type: 'integer',
|
|
362
|
+
},
|
|
382
363
|
}
|
|
383
364
|
|
|
384
|
-
|
|
385
|
-
Model.prototype[key] = crud[key]
|
|
386
|
-
}
|
|
365
|
+
module.exports = Model
|
|
387
366
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
367
|
+
// Extend Model prototype
|
|
368
|
+
require('./model-crud.js')
|
|
369
|
+
require('./model-validate.js')
|
package/lib/rules.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Todo: remove stringnums in date/number/integer rules
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
const ObjectId = require('mongodb').ObjectId
|
|
3
|
+
const util = require('./util.js')
|
|
4
|
+
const validator = require('validator')
|
|
4
5
|
|
|
5
6
|
module.exports = {
|
|
6
7
|
|
|
@@ -12,7 +13,7 @@ module.exports = {
|
|
|
12
13
|
fn: function(x) {
|
|
13
14
|
if (util.isArray(x) && !x.length) return false
|
|
14
15
|
return x || x === 0 || x === false? true : false
|
|
15
|
-
}
|
|
16
|
+
},
|
|
16
17
|
},
|
|
17
18
|
|
|
18
19
|
// "Type" rules below ignore undefined and null
|
|
@@ -28,7 +29,7 @@ module.exports = {
|
|
|
28
29
|
},
|
|
29
30
|
fn: function(x) {
|
|
30
31
|
return typeof x === 'boolean'
|
|
31
|
-
}
|
|
32
|
+
},
|
|
32
33
|
},
|
|
33
34
|
isArray: {
|
|
34
35
|
validateEmptyString: true,
|
|
@@ -41,7 +42,7 @@ module.exports = {
|
|
|
41
42
|
},
|
|
42
43
|
fn: function(x) {
|
|
43
44
|
return Array.isArray(x)
|
|
44
|
-
}
|
|
45
|
+
},
|
|
45
46
|
},
|
|
46
47
|
isDate: {
|
|
47
48
|
validateEmptyString: true,
|
|
@@ -54,7 +55,7 @@ module.exports = {
|
|
|
54
55
|
fn: function(x) {
|
|
55
56
|
if (util.isString(x) && x.match(/^[+-][0-9]+$/)) return true
|
|
56
57
|
return typeof x === 'number'
|
|
57
|
-
}
|
|
58
|
+
},
|
|
58
59
|
},
|
|
59
60
|
isImageObject: {
|
|
60
61
|
validateEmptyString: true,
|
|
@@ -70,7 +71,7 @@ module.exports = {
|
|
|
70
71
|
fn: function(x) {
|
|
71
72
|
let isObject = x !== null && typeof x === 'object' && !(x instanceof Array)
|
|
72
73
|
if (isObject && x.bucket && x.date && x.filename && typeof x.filesize != 'undefined' && x.path && x.uid) return true
|
|
73
|
-
}
|
|
74
|
+
},
|
|
74
75
|
},
|
|
75
76
|
isInteger: {
|
|
76
77
|
validateEmptyString: true,
|
|
@@ -83,7 +84,7 @@ module.exports = {
|
|
|
83
84
|
fn: function(x) {
|
|
84
85
|
if (util.isString(x) && x.match(/^[+-][0-9]+$/)) return true
|
|
85
86
|
return typeof x === 'number' && (parseInt(x) === x)
|
|
86
|
-
}
|
|
87
|
+
},
|
|
87
88
|
},
|
|
88
89
|
isNumber: {
|
|
89
90
|
validateEmptyString: true,
|
|
@@ -96,7 +97,7 @@ module.exports = {
|
|
|
96
97
|
fn: function(x) {
|
|
97
98
|
if (util.isString(x) && x.match(/^[+-][0-9]+$/)) return true
|
|
98
99
|
return typeof x === 'number'
|
|
99
|
-
}
|
|
100
|
+
},
|
|
100
101
|
},
|
|
101
102
|
isObject: {
|
|
102
103
|
validateEmptyString: true,
|
|
@@ -109,7 +110,7 @@ module.exports = {
|
|
|
109
110
|
},
|
|
110
111
|
fn: function(x) {
|
|
111
112
|
return x !== null && typeof x === 'object' && !(x instanceof Array)
|
|
112
|
-
}
|
|
113
|
+
},
|
|
113
114
|
},
|
|
114
115
|
isString: {
|
|
115
116
|
validateEmptyString: true,
|
|
@@ -120,14 +121,14 @@ module.exports = {
|
|
|
120
121
|
},
|
|
121
122
|
fn: function(x) {
|
|
122
123
|
return typeof x === 'string'
|
|
123
|
-
}
|
|
124
|
+
},
|
|
124
125
|
},
|
|
125
126
|
isAny: {
|
|
126
127
|
validateEmptyString: true,
|
|
127
128
|
message: '',
|
|
128
129
|
fn: function(x) {
|
|
129
130
|
return true
|
|
130
|
-
}
|
|
131
|
+
},
|
|
131
132
|
},
|
|
132
133
|
isId: {
|
|
133
134
|
validateEmptyString: true,
|
|
@@ -135,14 +136,14 @@ module.exports = {
|
|
|
135
136
|
tryParse: function(x) {
|
|
136
137
|
// Try and parse value to a mongodb ObjectId
|
|
137
138
|
if (x === '') return null
|
|
138
|
-
if (util.isString(x) && ObjectId.isValid(x)) return ObjectId(x)
|
|
139
|
+
if (util.isString(x) && ObjectId.isValid(x)) return new ObjectId(x)
|
|
139
140
|
else return x
|
|
140
141
|
},
|
|
141
142
|
fn: function(x) {
|
|
142
143
|
// Must be a valid mongodb ObjectId
|
|
143
144
|
if (x === null) return true
|
|
144
145
|
return util.isObject(x) && ObjectId.isValid(x)/*x.get_inc*/? true : false
|
|
145
|
-
}
|
|
146
|
+
},
|
|
146
147
|
},
|
|
147
148
|
|
|
148
149
|
/* "Number" rules below ignore undefined and null */
|
|
@@ -152,14 +153,14 @@ module.exports = {
|
|
|
152
153
|
fn: function(x, arg) {
|
|
153
154
|
if (typeof x !== 'number') { throw new Error ('Value was not a number.') }
|
|
154
155
|
return x <= arg
|
|
155
|
-
}
|
|
156
|
+
},
|
|
156
157
|
},
|
|
157
158
|
min: {
|
|
158
159
|
message: (x, arg) => 'Value was less than the configured minimum (' + arg + ')',
|
|
159
160
|
fn: function(x, arg) {
|
|
160
161
|
if (typeof x !== 'number') { throw new Error ('Value was not a number.') }
|
|
161
162
|
return x >= arg
|
|
162
|
-
}
|
|
163
|
+
},
|
|
163
164
|
},
|
|
164
165
|
|
|
165
166
|
/* "String" rules below ignore undefined, null, and empty strings */
|
|
@@ -170,54 +171,54 @@ module.exports = {
|
|
|
170
171
|
for (let item of arg) {
|
|
171
172
|
if (x === item + '') return true
|
|
172
173
|
}
|
|
173
|
-
}
|
|
174
|
+
},
|
|
174
175
|
},
|
|
175
176
|
isAfter: {
|
|
176
177
|
message: (x, arg) => 'Value was before the configured time (' + arg + ')',
|
|
177
|
-
fn: function(x, arg) { return
|
|
178
|
+
fn: function(x, arg) { return validator.isAfter(x, arg) },
|
|
178
179
|
},
|
|
179
180
|
isBefore: {
|
|
180
181
|
message: (x, arg) => 'Value was after the configured time (' + arg + ')',
|
|
181
|
-
fn: function(x, arg) { return
|
|
182
|
+
fn: function(x, arg) { return validator.isBefore(x, arg) },
|
|
182
183
|
},
|
|
183
184
|
isCreditCard: {
|
|
184
185
|
message: 'Value was not a valid credit card.',
|
|
185
|
-
fn: function(x, arg) { return
|
|
186
|
+
fn: function(x, arg) { return validator.isCreditCard(x, arg) },
|
|
186
187
|
},
|
|
187
188
|
isEmail: {
|
|
188
189
|
message: 'Please enter a valid email address.',
|
|
189
|
-
fn: function(x, arg) { return
|
|
190
|
+
fn: function(x, arg) { return validator.isEmail(x, arg) },
|
|
190
191
|
},
|
|
191
192
|
isHexColor: {
|
|
192
193
|
message: 'Value was not a valid hex color.',
|
|
193
|
-
fn: function(x, arg) { return
|
|
194
|
+
fn: function(x, arg) { return validator.isHexColor(x, arg) },
|
|
194
195
|
},
|
|
195
196
|
isIn: {
|
|
196
197
|
message: (x, arg) => 'Value was not in the configured whitelist (' + arg.join(', ') + ')',
|
|
197
|
-
fn: function(x, arg) { return
|
|
198
|
+
fn: function(x, arg) { return validator.isIn(x, arg) },
|
|
198
199
|
},
|
|
199
200
|
isIP: {
|
|
200
201
|
message: 'Value was not a valid IP address.',
|
|
201
|
-
fn: function(x, arg) { return
|
|
202
|
+
fn: function(x, arg) { return validator.isIP(x, arg) },
|
|
202
203
|
},
|
|
203
204
|
isNotEmptyString: {
|
|
204
205
|
validateEmptyString: true,
|
|
205
206
|
message: 'Value was an empty string.',
|
|
206
207
|
fn: function(x) {
|
|
207
208
|
return x !== ''
|
|
208
|
-
}
|
|
209
|
+
},
|
|
209
210
|
},
|
|
210
211
|
isNotIn: {
|
|
211
212
|
message: (x, arg) => 'Value was in the configured blacklist (' + arg.join(', ') + ')',
|
|
212
|
-
fn: function(x, arg) { return !
|
|
213
|
+
fn: function(x, arg) { return !validator.isIn(x, arg) },
|
|
213
214
|
},
|
|
214
215
|
isURL: {
|
|
215
216
|
message: 'Value was not a valid URL.',
|
|
216
|
-
fn: function(x, arg) { return
|
|
217
|
+
fn: function(x, arg) { return validator.isURL(x, arg === true? undefined : arg) },
|
|
217
218
|
},
|
|
218
219
|
isUUID: {
|
|
219
220
|
message: 'Value was not a valid UUID.',
|
|
220
|
-
fn: function(x, arg) { return
|
|
221
|
+
fn: function(x, arg) { return validator.isUUID(x) },
|
|
221
222
|
},
|
|
222
223
|
minLength: {
|
|
223
224
|
message: function(x, arg) {
|
|
@@ -226,9 +227,9 @@ module.exports = {
|
|
|
226
227
|
},
|
|
227
228
|
fn: function(x, arg) {
|
|
228
229
|
if (typeof x !== 'string' && !util.isArray(x)) throw new Error ('Value was not a string or an array.')
|
|
229
|
-
else if (typeof x === 'string') return
|
|
230
|
+
else if (typeof x === 'string') return validator.isLength(x, arg)
|
|
230
231
|
else return x.length >= arg
|
|
231
|
-
}
|
|
232
|
+
},
|
|
232
233
|
},
|
|
233
234
|
maxLength: {
|
|
234
235
|
message: function(x, arg) {
|
|
@@ -237,16 +238,16 @@ module.exports = {
|
|
|
237
238
|
},
|
|
238
239
|
fn: function(x, arg) {
|
|
239
240
|
if (typeof x !== 'string' && !util.isArray(x)) throw new Error ('Value was not a string or an array.')
|
|
240
|
-
else if (typeof x === 'string') return
|
|
241
|
+
else if (typeof x === 'string') return validator.isLength(x, 0, arg)
|
|
241
242
|
else return x.length <= arg
|
|
242
|
-
}
|
|
243
|
+
},
|
|
243
244
|
},
|
|
244
245
|
regex: {
|
|
245
246
|
message: (x, arg) => 'Value did not match the configured regular expression (' + arg + ')',
|
|
246
247
|
fn: function(x, arg) {
|
|
247
|
-
if (util.isRegex(arg)) return
|
|
248
|
+
if (util.isRegex(arg)) return validator.matches(x, arg)
|
|
248
249
|
throw new Error('This rule expects a regular expression to be configured, but instead got the '
|
|
249
250
|
+ typeof arg + ' `' + arg + '`.')
|
|
250
|
-
}
|
|
251
|
-
}
|
|
251
|
+
},
|
|
252
|
+
},
|
|
252
253
|
}
|
package/lib/util.js
CHANGED
|
@@ -1,14 +1,41 @@
|
|
|
1
|
-
|
|
1
|
+
const { ObjectId } = require('mongodb')
|
|
2
2
|
|
|
3
3
|
module.exports = {
|
|
4
4
|
|
|
5
|
+
cast: function(obj) {
|
|
6
|
+
/**
|
|
7
|
+
* Applies ObjectId casting to _id fields.
|
|
8
|
+
* @param {Object} optional, query
|
|
9
|
+
* @return {Object} query
|
|
10
|
+
* @private
|
|
11
|
+
*/
|
|
12
|
+
if (this.isArray(obj)) {
|
|
13
|
+
return obj.map(this.cast.bind(this))
|
|
14
|
+
}
|
|
15
|
+
if (obj && typeof obj === 'object') {
|
|
16
|
+
for (let k of Object.keys(obj)) {
|
|
17
|
+
if (k == '_id' && obj._id) {
|
|
18
|
+
if (obj._id.$in) obj._id.$in = obj._id.$in.map(this.id.bind(this))
|
|
19
|
+
else if (obj._id.$nin) obj._id.$nin = obj._id.$nin.map(this.id.bind(this))
|
|
20
|
+
else if (obj._id.$ne) obj._id.$ne = this.id(obj._id.$ne)
|
|
21
|
+
else obj._id = this.id(obj._id)
|
|
22
|
+
} else {
|
|
23
|
+
obj[k] = this.cast(obj[k])
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return obj
|
|
29
|
+
},
|
|
30
|
+
|
|
5
31
|
deepCopy: function(obj) {
|
|
6
32
|
// Deep clones an object
|
|
33
|
+
// v3.0.0 - MongoIds now remain as objects, not strings.
|
|
7
34
|
if (!obj) return obj
|
|
8
35
|
let obj2 = Array.isArray(obj)? [] : {}
|
|
9
36
|
for (let key in obj) {
|
|
10
37
|
let v = obj[key]
|
|
11
|
-
if (this.isId(v)) obj2[key] = v
|
|
38
|
+
if (this.isId(v)) obj2[key] = v//.toString()
|
|
12
39
|
else obj2[key] = (typeof v === 'object')? this.deepCopy(v) : v
|
|
13
40
|
}
|
|
14
41
|
return obj2
|
|
@@ -31,6 +58,16 @@ module.exports = {
|
|
|
31
58
|
forceArray: function(value) {
|
|
32
59
|
return this.isArray(value)? value : [value]
|
|
33
60
|
},
|
|
61
|
+
|
|
62
|
+
id: function(str) {
|
|
63
|
+
/**
|
|
64
|
+
* Casts to ObjectId
|
|
65
|
+
* @param {string|ObjectId} str - string = hex string
|
|
66
|
+
* @return {ObjectId}
|
|
67
|
+
*/
|
|
68
|
+
if (str == null) return new ObjectId()
|
|
69
|
+
return typeof str === 'string' ? ObjectId.createFromHexString(str) : str
|
|
70
|
+
},
|
|
34
71
|
|
|
35
72
|
inArray: (array, key, value) => {
|
|
36
73
|
/**
|
|
@@ -72,8 +109,8 @@ module.exports = {
|
|
|
72
109
|
|
|
73
110
|
isId: function(value) {
|
|
74
111
|
// Called from db.isId
|
|
75
|
-
// True if value is a MongoDB
|
|
76
|
-
if (this.isObject(value) && value
|
|
112
|
+
// True if value is a MongoDB ObjectId() or a valid id string
|
|
113
|
+
if (this.isObject(value) && ObjectId.isValid(value)) return true
|
|
77
114
|
else if (value && this.isString(value) && ObjectId.isValid(value)) return true
|
|
78
115
|
else return false
|
|
79
116
|
},
|
|
@@ -91,8 +128,8 @@ module.exports = {
|
|
|
91
128
|
},
|
|
92
129
|
|
|
93
130
|
isObjectAndNotID: function(value) {
|
|
94
|
-
// A true object that is not an id, i.e. mongodb
|
|
95
|
-
return this.isObject(value) && !value.get_inc
|
|
131
|
+
// A true object that is not an id, i.e. mongodb ObjectId()
|
|
132
|
+
return this.isObject(value) && !ObjectId.isValid(value) // was value.get_inc
|
|
96
133
|
},
|
|
97
134
|
|
|
98
135
|
isRegex: function(value) {
|
|
@@ -280,15 +317,18 @@ module.exports = {
|
|
|
280
317
|
* @return promise
|
|
281
318
|
* @source https://github.com/feross/run-series
|
|
282
319
|
*/
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
320
|
+
let current = 0
|
|
321
|
+
let results = []
|
|
322
|
+
let isSync = true
|
|
286
323
|
|
|
287
324
|
return new Promise((res, rej) => {
|
|
288
|
-
function each(err, result) {
|
|
325
|
+
function each(i, err, result) { // aka next(err, data)
|
|
326
|
+
if (i == current + 1) {
|
|
327
|
+
throw new Error('Hook error: you cannot return a promise and call next()')
|
|
328
|
+
}
|
|
289
329
|
results.push(result)
|
|
290
|
-
if (++current
|
|
291
|
-
else
|
|
330
|
+
if (!err && ++current < tasks.length) callTask(current)
|
|
331
|
+
else done(err)
|
|
292
332
|
}
|
|
293
333
|
function done(err) {
|
|
294
334
|
if (isSync) process.nextTick(() => end(err))
|
|
@@ -299,8 +339,18 @@ module.exports = {
|
|
|
299
339
|
if (err) rej(err)
|
|
300
340
|
else res(results)
|
|
301
341
|
}
|
|
302
|
-
|
|
303
|
-
|
|
342
|
+
function callTask(i) {
|
|
343
|
+
const each2 = each.bind(null, i)
|
|
344
|
+
const res = tasks[i](each2)
|
|
345
|
+
if (res instanceof Promise) {
|
|
346
|
+
res.then((result) => each2(null, result)).catch(each2)
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Start
|
|
351
|
+
if (!tasks.length) done(null)
|
|
352
|
+
else callTask(current)
|
|
353
|
+
|
|
304
354
|
isSync = false
|
|
305
355
|
})
|
|
306
356
|
},
|
|
@@ -356,6 +406,10 @@ module.exports = {
|
|
|
356
406
|
ucFirst: function(string) {
|
|
357
407
|
if (!string) return ''
|
|
358
408
|
return string.charAt(0).toUpperCase() + string.slice(1)
|
|
359
|
-
}
|
|
409
|
+
},
|
|
360
410
|
|
|
411
|
+
wait: async function(ms) {
|
|
412
|
+
return new Promise(res => setTimeout(res, ms))
|
|
413
|
+
},
|
|
414
|
+
|
|
361
415
|
}
|