monastery 3.0.21 → 3.0.23

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/lib/model.js CHANGED
@@ -109,6 +109,7 @@ function Model(name, opts, manager) {
109
109
  // Extend default fields with passed in fields and check for invalid fields
110
110
  this._setupFields(this.fields = Object.assign({}, this._defaultFields, this.fields))
111
111
  this.fieldsFlattened = this._getFieldsFlattened(this.fields, '') // test output?
112
+ this.modelFieldsArray = this._getModelFieldsArray()
112
113
 
113
114
  // Get collection, and extend model with collection methods
114
115
  this.collection = this.manager.get(name, { castIds: false })
@@ -138,27 +139,40 @@ function Model(name, opts, manager) {
138
139
 
139
140
  Model.prototype._getFieldsFlattened = function(fields, path) {
140
141
  /**
141
- * Flatten fields
142
+ * Get flattened fields
142
143
  * @param {object|array} fields - can be a nested subdocument or array
143
144
  * @param {string} path
144
145
  * @return {object} e.g. {'name': Schema, 'pets.dog': Schema}
145
146
  */
146
147
  let obj = {}
147
148
  util.forEach(fields, function(field, fieldName) {
148
- let newPath = /*util.isArray(fields)? path : */path + fieldName + '.'
149
- if (fieldName == 'schema') {
150
- return
151
- } else if (util.isArray(field)) {
149
+ const schema = field.schema
150
+ const newPath = /*util.isArray(fields)? path : */path + fieldName + '.'
151
+ if (fieldName == 'schema') return
152
+ if (schema.isArray) {
152
153
  Object.assign(obj, this._getFieldsFlattened(field, newPath))
153
- } else if (util.isSubdocument(field)) {
154
+ } else if (schema.isObject) {
154
155
  Object.assign(obj, this._getFieldsFlattened(field, newPath))
155
156
  } else {
156
- obj[newPath.replace(/\.$/, '')] = field
157
+ obj[newPath.replace(/\.$/, '')] = schema
157
158
  }
158
159
  }, this)
159
160
  return obj
160
161
  }
161
162
 
163
+ Model.prototype._getModelFieldsArray = function() {
164
+ /**
165
+ * Get all the model fields in an array
166
+ * @return {array} e.g. [{ path: 'pets.0.dog', 'path2': 'pets.dog'}, ...]
167
+ */
168
+ return Object.keys(this.fieldsFlattened).reduce((acc, path) => {
169
+ if (this.fieldsFlattened[path].model) {
170
+ acc.push({ path: path, path2: path.replace(/\.[0-9]+(\.|$)/g, '$1') })
171
+ }
172
+ return acc
173
+ }, [])
174
+ },
175
+
162
176
  Model.prototype._setupFields = function(fields) {
163
177
  /**
164
178
  * Check for invalid rules on a field object, and set field.isType
@@ -166,76 +180,88 @@ Model.prototype._setupFields = function(fields) {
166
180
  */
167
181
  util.forEach(fields, function(field, fieldName) {
168
182
  // Schema field
183
+ if (fieldName == 'schema') return
169
184
  if (util.isSchema(field)) {
170
- // No image schema pre-processing done yet by a plugin
171
- if (field.type == 'image' && !field.image) field.image = true, field.type = 'any'
172
- if (field.model) field.type = 'id'
173
- let isType = 'is' + util.ucFirst(field.type)
185
+ const schema = field
186
+ fields[fieldName] = field = { schema }
187
+
188
+ // Type 'model'
189
+ if (schema.model) {
190
+ schema.type = 'id'
174
191
 
175
- // No type defined
176
- if (!field.type) {
192
+ // Type 'image', but no image plugin schema processing done, e.g. image plugin not setup
193
+ } else if (schema.type == 'image' && !schema.image) {
194
+ schema.image = true
195
+ schema.type = 'any'
196
+
197
+ // No type
198
+ } else if (!schema.type) {
177
199
  this.error(`No type defined on "${this.name}" for field "${fieldName}". Defaulting to string.`)
178
- field.type = 'string'
200
+ schema.type = 'string'
201
+ }
179
202
 
180
- // Type doesn't exist
181
- } else if (!this.rules[isType] && !rules[isType]) {
182
- this.error(`Not a valid type "${field.type}" defined on "${this.name}" for field "${fieldName}".
203
+ // Type isn't a rule
204
+ const isType = schema.isType = 'is' + util.ucFirst(schema.type)
205
+ if (!this.rules[isType] && !rules[isType]) {
206
+ this.error(`Not a valid type "${schema.type}" defined on "${this.name}" for field "${fieldName}".
183
207
  Defaulting to string.`)
184
- field.type = 'string'
208
+ schema.type = 'string'
185
209
  }
186
210
 
187
- // Convert type into a is{type} rule
188
- field[isType] = true
211
+ field.schema = {
212
+ ...field.schema,
213
+ [isType]: true, // e.g. isString rule
214
+ isSchema: true,
215
+ }
189
216
 
190
217
  // Rule doesn't exist
191
- util.forEach(field, (rule, ruleName) => {
218
+ for (let ruleName in field.schema) {
192
219
  if ((this.rules[ruleName] || rules[ruleName]) && this._ignoredRules.indexOf(ruleName) != -1) {
193
220
  this.error(`The rule name "${ruleName}" for the model "${this.name}" is a reserved keyword, ignoring rule.`)
194
221
  }
195
222
  if (!this.rules[ruleName] && !rules[ruleName] && this._ignoredRules.indexOf(ruleName) == -1) {
196
- // console.log(field)
223
+ // console.log(field.schema)
197
224
  this.error(`No rule "${ruleName}" exists for model "${this.name}", ignoring rule.`)
198
- delete field[ruleName]
225
+ delete field.schema[ruleName]
199
226
  }
200
- }, this)
227
+ }
201
228
 
202
229
  // Misused schema property
203
- } else if (fieldName == 'schema') {
204
- this.error(`Invalid schema on model "${this.name}", remember 'schema' is a reserverd property, ignoring field.`)
230
+ } else if (fieldName == 'schema' || fieldName == 'isSchema') {
231
+ this.error(`Invalid field '${fieldName}' on model '${this.name}', this is a reserved property, ignoring field.`)
205
232
  delete fields[fieldName]
206
233
 
207
234
  // Fields be an array
208
235
  } else if (util.isArray(field)) {
209
- let arrayDefault = this.manager.opts.defaultObjects? () => [] : undefined
210
- let nullObject = this.manager.opts.nullObjects
211
- let virtual = field.length == 1 && (field[0]||{}).virtual ? true : undefined
212
- field.schema = {
236
+ field.schema = util.removeUndefined({
213
237
  type: 'array',
214
238
  isArray: true,
215
- default: arrayDefault,
216
- nullObject: nullObject,
217
- virtual: virtual,
239
+ isSchema: true,
240
+ isType: 'isArray',
241
+ default: this.manager.opts.defaultObjects? () => [] : undefined,
242
+ nullObject: this.manager.opts.nullObjects,
243
+ virtual: field.length == 1 && (field[0] || {}).virtual ? true : undefined,
218
244
  ...(field.schema || {}),
219
- }
245
+ })
220
246
  this._setupFields(field)
221
247
 
222
248
  // Fields can be a subdocument, e.g. user.pet = { name: {}, ..}
223
249
  } else if (util.isSubdocument(field)) {
224
- let objectDefault = this.manager.opts.defaultObjects? () => ({}) : undefined
225
- let nullObject = this.manager.opts.nullObjects
226
250
  let index2dsphere = util.isSubdocument2dsphere(field)
227
251
  field.schema = field.schema || {}
228
252
  if (index2dsphere) {
229
253
  field.schema.index = index2dsphere
230
254
  delete field.index
231
255
  }
232
- field.schema = {
256
+ field.schema = util.removeUndefined({
233
257
  type: 'object',
234
258
  isObject: true,
235
- default: objectDefault,
236
- nullObject: nullObject,
259
+ isSchema: true,
260
+ isType: 'isObject',
261
+ default: this.manager.opts.defaultObjects? () => ({}) : undefined,
262
+ nullObject: this.manager.opts.nullObjects,
237
263
  ...field.schema,
238
- }
264
+ })
239
265
  this._setupFields(field)
240
266
  }
241
267
  }, this)
@@ -246,7 +272,7 @@ Model.prototype._setupIndexes = async function(fields, opts={}) {
246
272
  * Creates indexes for the model (multikey, and sub-document supported)
247
273
  * Note: the collection be created beforehand???
248
274
  * Note: only one text index per model(collection) is allowed due to mongodb limitations
249
- * @param {object} <fields>
275
+ * @param {object} <fields> - processed or unprocessed fields, e.g. {schema: {name: {index}}} or {name: {index}}
250
276
  * @return Promise( {array} indexes ensured ) || error
251
277
  *
252
278
  * MongoDB index structures = [
@@ -267,8 +293,16 @@ Model.prototype._setupIndexes = async function(fields, opts={}) {
267
293
  throw new Error(`Skipping createIndex on the '${model.name||''}' model, no mongodb connection found.`)
268
294
  }
269
295
 
296
+ // Process custom 'unprocessed' fields
297
+ if (fields && !fields[Object.keys(fields)[0]].schema) {
298
+ fields = util.deepCopy(fields)
299
+ this._setupFields(fields)
300
+ // console.dir(fields, { depth: null })
301
+ }
302
+
270
303
  // Find all indexes
271
304
  recurseFields(fields || model.fields, '')
305
+
272
306
  // console.log(2, indexes, fields)
273
307
  if (hasTextIndex) indexes.push(textIndex)
274
308
  if (opts.dryRun) return indexes || []
@@ -313,14 +347,19 @@ Model.prototype._setupIndexes = async function(fields, opts={}) {
313
347
  model.info('db index(s) created for ' + model.name)
314
348
  return indexes
315
349
 
316
- function recurseFields(fields, parentPath) {
317
- util.forEach(fields, (field, name) => {
318
- let index = field.index
350
+ function recurseFields(schemaFields, parentPath) {
351
+ /**
352
+ * Recursively find fields with an index property
353
+ * @param {object} schemaFields
354
+ */
355
+ util.forEach(schemaFields, (field, fieldName) => {
356
+ const index = (field.schema||{}).index
357
+ if (fieldName == 'schema') return
319
358
  if (index) {
320
359
  let options = util.isObject(index)? util.omit(index, ['type']) : {}
321
360
  let type = util.isObject(index)? index.type : index
322
- let path = name == 'schema'? parentPath.slice(0, -1) : parentPath + name
323
- let path2 = path.replace(/(^|\.)[0-9]+(\.|$)/g, '$2') // no numirical keys, e.g. pets.1.name
361
+ let path = parentPath + fieldName
362
+ let path2 = path.replace(/(^|\.)[0-9]+(\.|$)/g, '$2') // no numirical keys, e.g. pets.1.fieldName
324
363
  if (type === true) type = 1
325
364
  if (type == 'text') {
326
365
  hasTextIndex = textIndex.key[path2] = 'text'
@@ -331,10 +370,11 @@ Model.prototype._setupIndexes = async function(fields, opts={}) {
331
370
  indexes.push({ name: `${path2}_1`, key: { [path2]: 1 }, unique: true, ...options })
332
371
  }
333
372
  }
334
- if (util.isSubdocument(field)) {
335
- recurseFields(field, parentPath + name + '.')
336
- } else if (util.isArray(field)) {
337
- recurseFields(field, parentPath + name + '.')
373
+ if (field.schema.isObject) {
374
+ recurseFields(field, parentPath + fieldName + '.')
375
+
376
+ } else if (field.schema.isArray) {
377
+ recurseFields(field, parentPath + fieldName + '.')
338
378
  }
339
379
  })
340
380
  }
package/lib/util.js CHANGED
@@ -35,8 +35,7 @@ module.exports = {
35
35
  let obj2 = Array.isArray(obj)? [] : {}
36
36
  for (let key in obj) {
37
37
  let v = obj[key]
38
- if (this.isId(v)) obj2[key] = v//.toString()
39
- else obj2[key] = (typeof v === 'object')? this.deepCopy(v) : v
38
+ obj2[key] = (typeof v === 'object' && !this.isIdFast(v))? this.deepCopy(v) : v
40
39
  }
41
40
  return obj2
42
41
  },
@@ -114,6 +113,17 @@ module.exports = {
114
113
  else return false
115
114
  },
116
115
 
116
+ isIdFast: function(value) {
117
+ // Check quickly if the value is an ObjectId. We can use db.isId() but this may be slower
118
+ // console.log(isId('66333b1b3343d7e3b200005b')) = true
119
+ // console.log(isId(db.id())) = true
120
+ // console.log(isId(null)) = undefined
121
+ // console.log(isId('qwefqwefqwef')) = undefined
122
+ // console.log(isId({})) = undefined
123
+ // console.log(isId(['66333b1b3343d7e3b200005b'])) = undefined
124
+ if ((value||'').toString()?.match(/^[0-9a-fA-F]{24}$/) && !this.isArray(value)) return true
125
+ },
126
+
117
127
  isNumber: function(value) {
118
128
  return !isNaN(parseFloat(value)) && isFinite(value)
119
129
  },
@@ -311,20 +321,21 @@ module.exports = {
311
321
  return variable
312
322
  },
313
323
 
314
- runSeries: function(tasks, hookName, cb) {
324
+ runSeries: function(tasks, hookName, data) {
315
325
  /*
316
- * Runs functions in series and calls the cb when done
326
+ * Runs functions in series
317
327
  * @param {function(err, result)[]} tasks - array of functions
318
328
  * @param {string} <hookName> - e.g. 'afterFind'
329
+ * @param {any} data - data to pass to the first function
319
330
  * @param {function(err, results[])} <cb>
320
331
  * @return promise
321
332
  * @this Model
322
333
  * @source https://github.com/feross/run-series
323
334
  */
324
335
  let current = 0
325
- let results = []
326
336
  let isSync = true
327
- let caller = hookName == 'afterFind' ? 'afterFind' : this.name + '.' + hookName
337
+ let caller = (this.afterFindName || this.name) + '.' + hookName
338
+ let lastDefinedResult = data
328
339
 
329
340
  return new Promise((res, rej) => {
330
341
  const next = (i, err, result) => { // aka next(err, data)
@@ -333,8 +344,8 @@ module.exports = {
333
344
  return
334
345
  }
335
346
  current++
336
- results.push(result)
337
- if (!err && current < tasks.length) callTask(current)
347
+ if (!err && typeof result !== 'undefined') lastDefinedResult = result
348
+ if (!err && current < tasks.length) callTask(current, lastDefinedResult)
338
349
  else done(err)
339
350
  }
340
351
  const done = (err) => {
@@ -342,13 +353,13 @@ module.exports = {
342
353
  else end(err)
343
354
  }
344
355
  const end = (err) => {
345
- if (cb) cb(err, results)
346
356
  if (err) rej(err)
347
- else res(results)
357
+ else res(lastDefinedResult)
348
358
  }
349
- const callTask = (i) => {
359
+ const callTask = (i, data) => {
350
360
  const next2 = next.bind(null, i)
351
- const res = tasks[i](next2)
361
+ const args = hookName.match(/remove/i)? [next2] : [data, next2]
362
+ const res = tasks[i](...args)
352
363
  if (res instanceof Promise) {
353
364
  res.then((result) => next2(null, result)).catch((e) => next2(e))
354
365
  }
@@ -356,7 +367,7 @@ module.exports = {
356
367
 
357
368
  // Start
358
369
  if (!tasks.length) done(null)
359
- else callTask(current)
370
+ else callTask(current, lastDefinedResult)
360
371
 
361
372
  isSync = false
362
373
  })
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "monastery",
3
3
  "description": "⛪ A simple, straightforward MongoDB ODM",
4
4
  "author": "Ricky Boyce",
5
- "version": "3.0.21",
5
+ "version": "3.0.23",
6
6
  "license": "MIT",
7
7
  "repository": "github:boycce/monastery",
8
8
  "homepage": "https://boycce.github.io/monastery/",
@@ -482,27 +482,27 @@ let plugin = module.exports = {
482
482
  })).then(() => validFiles)
483
483
  },
484
484
 
485
- _findAndTransformImageFields: function(fields, path) {
485
+ _findAndTransformImageFields: function(unprocessedFields, path) {
486
486
  /**
487
- * Returns a list of valid image fields
488
- * @param {object|array} fields
487
+ * Returns a list of valid image field schemas
488
+ * @param {object|array} unprocessedFields - fields not yet setup
489
489
  * @param {string} path
490
490
  * @return [{}, ...]
491
491
  * @this plugin
492
492
  */
493
493
  let list = []
494
494
  let that = this
495
- util.forEach(fields, (field, fieldName) => {
495
+ util.forEach(unprocessedFields, (field, fieldName) => {
496
496
  let path2 = `${path}.${fieldName}`.replace(/^\./, '')
497
- // let schema = field.schema || {}
497
+ if (fieldName == 'schema') return
498
498
 
499
499
  // Subdocument field
500
- if (util.isSubdocument(field)) {//schema.isObject
500
+ if (util.isSubdocument(field)) {
501
501
  // log(`Recurse 1: ${path2}`)
502
502
  list = list.concat(plugin._findAndTransformImageFields(field, path2))
503
503
 
504
504
  // Array field
505
- } else if (util.isArray(field)) {//schema.isArray
505
+ } else if (util.isArray(field)) {
506
506
  // log(`Recurse 2: ${path2}`)
507
507
  list = list.concat(plugin._findAndTransformImageFields(field, path2))
508
508
 
@@ -532,15 +532,15 @@ let plugin = module.exports = {
532
532
  params: field.params ? util.deepCopy(field.params) : undefined,
533
533
  })
534
534
  // Convert image field to subdocument
535
- fields[fieldName] = {
535
+ unprocessedFields[fieldName] = {
536
536
  bucket: { type: 'string' },
537
537
  date: { type: 'number' },
538
538
  filename: { type: 'string' },
539
539
  filesize: { type: 'number' },
540
540
  metadata: { type: 'any' },
541
541
  path: { type: 'string' },
542
- schema: { image: true, nullObject: true, isImageObject: true },
543
542
  uid: { type: 'string' },
543
+ schema: { image: true, isImageObject: true, nullObject: true },
544
544
  }
545
545
  }
546
546
  })
@@ -328,7 +328,8 @@ test('find project population', async () => {
328
328
  expect(find3).toEqual(customProject)
329
329
  })
330
330
 
331
- test('insert blacklisting (validate)', async () => {
331
+ test('insert blacklisting validate', async () => {
332
+ // todo: isolated model._pathBlacklisted test
332
333
  let user = db.model('user', {
333
334
  fields: {
334
335
  list: [{ type: 'number' }],
package/test/crud.js CHANGED
@@ -571,6 +571,68 @@ test('update mixing formData', async() => {
571
571
  })
572
572
  })
573
573
 
574
+ test('update large document', async () => {
575
+ // todo: sereach util.deepCopy
576
+ // todo: check castIds and any other recursive functions
577
+ // todo: move default fields to before validate
578
+ db.model('a', { fields: {} })
579
+ db.model('b', { fields: {} })
580
+ db.model('c', { fields: {} })
581
+ db.model('d', { fields: {} })
582
+ db.model('e', { fields: {} })
583
+ try {
584
+ var large = db.model('large', require('../resources/fixtures/large-definition.js'))
585
+ var largePayload = require('../resources/fixtures/large-payload.json')
586
+ } catch (e) {
587
+ // ignore publicly for now
588
+ return
589
+ }
590
+ // Insert
591
+ let inserted = await large._insert({})
592
+ // Update
593
+ // console.time('update large document')
594
+ let update = await large.update({
595
+ query: inserted._id,
596
+ data: largePayload,
597
+ })
598
+ // console.timeEnd('update large document')
599
+ // Check
600
+ await expect(update).toEqual(removePrunedProperties(largePayload))
601
+ // Find
602
+ // console.time('find large document')
603
+ // await large.findOne({
604
+ // query: inserted._id,
605
+ // })
606
+ // console.timeEnd('find large document')
607
+
608
+ function removePrunedProperties(entity) {
609
+ for (let entitiesKey of [
610
+ 'components', 'connections', 'bridges', 'openings', 'spaces', 'elements', 'elementTypes',
611
+ 'categories', 'typologies',
612
+ ]) {
613
+ if (entity[entitiesKey]) {
614
+ for (let i=0, l=entity[entitiesKey].length; i<l; i++) {
615
+ entity[entitiesKey][i] = removePrunedProperties(entity[entitiesKey][i])
616
+ }
617
+ }
618
+ }
619
+ // remove actually keys
620
+ if (entity.metrics) {
621
+ for (let key in entity.metrics) {
622
+ delete entity.metrics[key].actually
623
+ }
624
+ }
625
+ if (entity.code?.match(/^(ELEM|CAT)/)) {
626
+ delete entity.name
627
+ }
628
+ // // convert _id to ObjectId
629
+ // if (entity._id) {
630
+ // entity._id = db.id(entity._id)
631
+ // }
632
+ return entity
633
+ }
634
+ })
635
+
574
636
  test('findOneAndUpdate general', async () => {
575
637
  // todo: test all findOneAndUpdate options (e.g. array population)
576
638
  // todo: test find & update hooks
@@ -687,7 +749,7 @@ test('count defaults', async () => {
687
749
  .resolves.toEqual(2)
688
750
  })
689
751
 
690
- test('hooks', async () => {
752
+ test('hooks > basic', async () => {
691
753
  let user = db.model('user', {
692
754
  fields: {
693
755
  first: { type: 'string'},
@@ -774,6 +836,123 @@ test('hooks', async () => {
774
836
  await expect(user2.update({ query: userDoc2._id, data: { first: 'M', bad: true } })).rejects.toThrow('error2')
775
837
  })
776
838
 
839
+ test('hooks > chained values', async () => {
840
+ let bookCount = 0
841
+ const afterInsertAsync = [
842
+ async (data) => {
843
+ return // ignored
844
+ },
845
+ async (data) => {
846
+ data.first = 'Martin11'
847
+ },
848
+ async (data) => {
849
+ expect(data.first).toEqual('Martin11')
850
+ return { ...data, first: 'Martin' }
851
+ },
852
+ async (data) => {
853
+ expect(data.first).toEqual('Martin')
854
+ return // ignored
855
+ },
856
+ ]
857
+ const afterFindAsync = [
858
+ async (data) => {
859
+ return // ignored
860
+ },
861
+ async (data) => {
862
+ bookCount++
863
+ if (data.bookNumber) data.bookNumber += (1 + bookCount)
864
+ else data.first = 'Martin2'
865
+ },
866
+ async (data) => {
867
+ if (data.bookNumber) {
868
+ expect(data).toEqual({ _id: expect.any(Object), bookNumber: 11 + bookCount })
869
+ return { _id: 1, bookNumber: 11 + bookCount }
870
+ } else {
871
+ expect(data).toEqual({ _id: expect.any(Object), first: 'Martin2', books: data.books })
872
+ return { _id: 2, books: data.books }
873
+ }
874
+ },
875
+ async (data) => {
876
+ if (data._id == 1) expect(data).toEqual({ _id: 1, bookNumber: 11 + bookCount })
877
+ else expect(data).toEqual({ _id: 2, books: data.books })
878
+ return // ignored
879
+ },
880
+ ]
881
+ const afterFindCallback = [
882
+ (data, next) => {
883
+ next() // ignored
884
+ },
885
+ (data, next) => {
886
+ bookCount++
887
+ if (data.bookNumber) data.bookNumber += (1 + bookCount)
888
+ else data.first = 'Martin2'
889
+ next()
890
+ },
891
+ (data, next) => {
892
+ if (data.bookNumber) {
893
+ expect(data).toEqual({ _id: expect.any(Object), bookNumber: 11 + bookCount })
894
+ next(null, { _id: 1, bookNumber: 11 + bookCount })
895
+ } else {
896
+ expect(data).toEqual({ _id: expect.any(Object), first: 'Martin2', books: data.books })
897
+ next(null, { _id: 2, books: data.books })
898
+ }
899
+ },
900
+ (data, next) => {
901
+ if (data._id == 1) expect(data).toEqual({ _id: 1, bookNumber: 11 + bookCount })
902
+ else expect(data).toEqual({ _id: 2, books: data.books })
903
+ next() // ignored
904
+ },
905
+ ]
906
+
907
+ // Async
908
+ db.model('book', {
909
+ fields: { bookNumber: { type: 'number'} },
910
+ afterFind: afterFindAsync,
911
+ })
912
+ db.model('user', {
913
+ fields: { first: { type: 'string'}, books: [{ model: 'book' }] },
914
+ afterInsert: afterInsertAsync,
915
+ afterFind: afterFindAsync,
916
+ })
917
+ let bookDoc = await db.book.insert({ data: { bookNumber: 10 }})
918
+ let bookDoc2 = await db.book.insert({ data: { bookNumber: 10 }})
919
+ let userDoc = await db.user.insert({ data: { first: 'Martin0', books: [bookDoc._id, bookDoc2._id]}})
920
+
921
+ // AfterInsert async
922
+ expect(userDoc).toEqual({
923
+ _id: expect.any(Object),
924
+ first: 'Martin',
925
+ books: [bookDoc._id, bookDoc2._id],
926
+ })
927
+
928
+ // AfterFind async
929
+ await expect(db.user.find({ query: userDoc._id, populate: ['books'] })).resolves.toEqual({
930
+ _id: 2,
931
+ books: [
932
+ { _id: 1, bookNumber: 12 },
933
+ { _id: 1, bookNumber: 13 },
934
+ ],
935
+ })
936
+
937
+ // AfterFind callback/next
938
+ db.model('book', {
939
+ fields: { bookNumber: { type: 'number'} },
940
+ afterFind: afterFindCallback,
941
+ })
942
+ db.model('user', {
943
+ fields: { first: { type: 'string'}, books: [{ model: 'book' }] },
944
+ afterFind: afterFindCallback,
945
+ })
946
+ await expect(db.user.find({ query: userDoc._id, populate: ['books'] })).resolves.toEqual({
947
+ _id: 2,
948
+ books: [
949
+ { _id: 1, bookNumber: 15 },
950
+ { _id: 1, bookNumber: 16 },
951
+ ],
952
+ })
953
+
954
+ })
955
+
777
956
  test('hooks > async', async () => {
778
957
  let user = db.model('user', {
779
958
  fields: {
@@ -954,19 +1133,19 @@ test('hooks > async and next conflict', async () => {
954
1133
 
955
1134
  // Only increment twice
956
1135
  await expect(user1.find({ query: user1Doc._id })).resolves.toEqual({ _id: expect.any(Object), age: 2 })
957
- expect(logSpy).toHaveBeenCalledWith('Monastery afterFind error: you cannot return a promise AND call next()')
1136
+ expect(logSpy).toHaveBeenCalledWith('Monastery user.afterFind error: you cannot return a promise AND call next()')
958
1137
 
959
1138
  await expect(user2.find({ query: user2Doc._id })).resolves.toEqual({ _id: expect.any(Object), age: 2 })
960
- expect(logSpy).toHaveBeenCalledWith('Monastery afterFind error: you cannot return a promise AND call next()')
1139
+ expect(logSpy).toHaveBeenCalledWith('Monastery user.afterFind error: you cannot return a promise AND call next()')
961
1140
 
962
1141
  await expect(user3.find({ query: user3Doc._id })).resolves.toEqual({ _id: expect.any(Object), age: 2 })
963
- expect(logSpy).toHaveBeenCalledWith('Monastery afterFind error: you cannot return a promise AND call next()')
1142
+ expect(logSpy).toHaveBeenCalledWith('Monastery user.afterFind error: you cannot return a promise AND call next()')
964
1143
 
965
1144
  await expect(user4.find({ query: user4Doc._id })).resolves.toEqual({ _id: expect.any(Object), age: 2 })
966
- expect(logSpy).toHaveBeenCalledWith('Monastery afterFind error: you cannot return a promise AND call next()')
1145
+ expect(logSpy).toHaveBeenCalledWith('Monastery user.afterFind error: you cannot return a promise AND call next()')
967
1146
 
968
1147
  await expect(user5.find({ query: user5Doc._id })).rejects.toThrow('An async error occurred with Martin3')
969
- expect(logSpy).toHaveBeenCalledWith('Monastery afterFind error: you cannot return a promise AND call next()')
1148
+ expect(logSpy).toHaveBeenCalledWith('Monastery user.afterFind error: you cannot return a promise AND call next()')
970
1149
 
971
1150
  db2.close()
972
1151
  })
package/test/manager.js CHANGED
@@ -8,7 +8,7 @@ test('manager > basics', async () => {
8
8
  // Basic find command
9
9
  expect(await manager.db.collection('non-collection').findOne({})).toEqual(null)
10
10
  // Raw MongoDB ping command
11
- expect(await manager.command({ ping: 1 })).toEqual({ ok: 1 })
11
+ expect(await manager.command({ ping: 1 })).toMatchObject({ ok: 1 }) // cluster connections return extra fields
12
12
  manager.close()
13
13
  })
14
14