monastery 3.0.5 → 3.0.7

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 CHANGED
@@ -9,6 +9,7 @@
9
9
  ],
10
10
  "globals": {
11
11
  // jest globals
12
+ "jest": true,
12
13
  "test": true,
13
14
  "expect": true,
14
15
  "afterAll": true,
package/changelog.md CHANGED
@@ -2,6 +2,10 @@
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
+ ### [3.0.7](https://github.com/boycce/monastery/compare/3.0.5...3.0.7) (2024-04-29)
6
+
7
+ ### [3.0.6](https://github.com/boycce/monastery/compare/3.0.5...3.0.6) (2024-04-29)
8
+
5
9
  ### [3.0.5](https://github.com/boycce/monastery/compare/3.0.4...3.0.5) (2024-04-29)
6
10
 
7
11
  ### [3.0.4](https://github.com/boycce/monastery/compare/3.0.3...3.0.4) (2024-04-29)
package/docs/readme.md CHANGED
@@ -87,9 +87,12 @@ You can view MongoDB's [compatibility table here](https://www.mongodb.com/docs/d
87
87
  - Removed callback functions on all model methods, you can use the returned promise instead
88
88
  - model.update() now returns the following _update property: `{ acknowledged: true, matchedCount: 1, modifiedCount: 1, upsertedCount: 0, upsertedId: null }` instead of `{ n: 1, nModified: 1, ok: 1 }`
89
89
  - model.remove() now returns `{ acknowledged: true, deletedCount: 1 }`, instead of `{ results: {n:1, ok:1} }`
90
- - Models are now added to db.models instead of db.model, e.g. db.models.user
91
- - MongoDB connection can be found here db.db changed from db._db
92
90
  - model._indexes() now returns collection._indexes() not collection._indexInformation()
91
+ - db.model.* moved to db.models.*
92
+ - db._client moved to db.client
93
+ - db._db moved to db.db
94
+ - db.catch/then() moved to db.onError/db.onOpen()
95
+ - next() is now redundant when returning promises from hooks, e.g. `afterFind: [async (data) => {...}]`
93
96
 
94
97
  ## Roadmap
95
98
 
package/lib/model-crud.js CHANGED
@@ -48,9 +48,9 @@ Model.prototype.insert = async function (opts) {
48
48
  let data = await this.validate(opts.data || {}, opts) // was { ...opts }
49
49
 
50
50
  // Insert
51
- await util.runSeries(this.beforeInsert.map(f => f.bind(opts, data)))
51
+ await util.runSeries(this.beforeInsert.map(f => f.bind(opts, data)), `${this.name}.beforeInsert`)
52
52
  let response = await this._insert(data, util.omit(opts, this._queryOptions))
53
- await util.runSeries(this.afterInsert.map(f => f.bind(opts, response)))
53
+ await util.runSeries(this.afterInsert.map(f => f.bind(opts, response)), `${this.name}.afterInsert`)
54
54
 
55
55
  // Success/error
56
56
  if (opts.req && opts.respond) opts.req.res.json(response)
@@ -267,7 +267,7 @@ Model.prototype.update = async function (opts, type='update') {
267
267
  }
268
268
 
269
269
  // Hook: beforeUpdate (has access to original, non-validated opts.data)
270
- await util.runSeries(this.beforeUpdate.map(f => f.bind(opts, data||{})))
270
+ await util.runSeries(this.beforeUpdate.map(f => f.bind(opts, data||{})), `${this.name}.beforeUpdate`)
271
271
 
272
272
  if (data && operators['$set']) {
273
273
  this.info(`'$set' fields take precedence over the data fields for \`${this.name}.${type}()\``)
@@ -297,7 +297,9 @@ Model.prototype.update = async function (opts, type='update') {
297
297
  }
298
298
 
299
299
  // Hook: afterUpdate (doesn't have access to validated data)
300
- if (response) await util.runSeries(this.afterUpdate.map(f => f.bind(opts, response)))
300
+ if (response) {
301
+ await util.runSeries(this.afterUpdate.map(f => f.bind(opts, response)), `${this.name}.afterUpdate`)
302
+ }
301
303
 
302
304
  // Hook: afterFind if findOneAndUpdate
303
305
  if (response && type == 'findOneAndUpdate') {
@@ -329,9 +331,9 @@ Model.prototype.remove = async function (opts) {
329
331
  opts = await this._queryObject(opts, 'remove')
330
332
 
331
333
  // Remove
332
- await util.runSeries(this.beforeRemove.map(f => f.bind(opts)))
334
+ await util.runSeries(this.beforeRemove.map(f => f.bind(opts)), `${this.name}.beforeRemove`)
333
335
  let response = await this._remove(opts.query, util.omit(opts, this._queryOptions))
334
- await util.runSeries(this.afterRemove.map(f => f.bind(response)))
336
+ await util.runSeries(this.afterRemove.map(f => f.bind(response)), `${this.name}.afterRemove`)
335
337
 
336
338
  // Success
337
339
  if (opts.req && opts.respond) opts.req.res.json(response)
@@ -589,7 +591,7 @@ Model.prototype._processAfterFind = function (data, projection={}, afterFindCont
589
591
  callbackSeries.push(fn.bind(afterFindContext, item.dataRef))
590
592
  }
591
593
  }
592
- return util.runSeries(callbackSeries).then(() => data)
594
+ return util.runSeries(callbackSeries, 'afterFind').then(() => data)
593
595
  }
594
596
 
595
597
  Model.prototype._recurseAndFindModels = function (parentPath, schemaFields, dataArr) {
@@ -29,7 +29,8 @@ Model.prototype.validate = async function (data, opts) {
29
29
  else opts.projectionValidate = this._getProjectionFromBlacklist(opts.update ? 'update' : 'insert', opts.blacklist)
30
30
 
31
31
  // Hook: beforeValidate
32
- await util.runSeries(this.beforeValidate.map(f => f.bind(opts, data)))
32
+
33
+ await util.runSeries(this.beforeValidate.map(f => f.bind(opts, data)), `${this.name}.beforeValidate`)
33
34
 
34
35
  // Recurse and validate fields
35
36
  let response = util.toArray(data).map(item => {
package/lib/util.js CHANGED
@@ -309,10 +309,11 @@ module.exports = {
309
309
  return variable
310
310
  },
311
311
 
312
- runSeries: function(tasks, cb) {
312
+ runSeries: function(tasks, info, cb) {
313
313
  /*
314
314
  * Runs functions in series and calls the cb when done
315
315
  * @param {function(err, result)[]} tasks - array of functions
316
+ * @param {object} <info> - data to pass to the error
316
317
  * @param {function(err, results[])} <cb>
317
318
  * @return promise
318
319
  * @source https://github.com/feross/run-series
@@ -322,12 +323,14 @@ module.exports = {
322
323
  let isSync = true
323
324
 
324
325
  return new Promise((res, rej) => {
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()')
326
+ function next(i, err, result) { // aka next(err, data)
327
+ if (i !== current) {
328
+ console.error(`Monastery ${info} error: you cannot return a promise AND call next()`)
329
+ return
328
330
  }
331
+ current++
329
332
  results.push(result)
330
- if (!err && ++current < tasks.length) callTask(current)
333
+ if (!err && current < tasks.length) callTask(current)
331
334
  else done(err)
332
335
  }
333
336
  function done(err) {
@@ -340,10 +343,10 @@ module.exports = {
340
343
  else res(results)
341
344
  }
342
345
  function callTask(i) {
343
- const each2 = each.bind(null, i)
344
- const res = tasks[i](each2)
346
+ const next2 = next.bind(null, i)
347
+ const res = tasks[i](next2)
345
348
  if (res instanceof Promise) {
346
- res.then((result) => each2(null, result)).catch(each2)
349
+ res.then((result) => next2(null, result)).catch((e) => next2(e))
347
350
  }
348
351
  }
349
352
 
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.5",
5
+ "version": "3.0.7",
6
6
  "license": "MIT",
7
7
  "repository": "github:boycce/monastery",
8
8
  "homepage": "https://boycce.github.io/monastery/",
package/test/crud.js CHANGED
@@ -856,4 +856,115 @@ test('hooks > async', async () => {
856
856
  // Third async hook throws an error
857
857
  await expect(user.find({ query: user3Doc._id }))
858
858
  .rejects.toThrow('An async error occurred with Martin3')
859
+ })
860
+
861
+ test('hooks > async and next conflict', async () => {
862
+ let user1 = db.model('user', {
863
+ fields: { age: { type: 'number'} },
864
+ afterFind: [
865
+ async (data, next) => {
866
+ const promise = await new Promise((resolve, reject) => {
867
+ if (data.age === 0) {
868
+ setTimeout(() => {
869
+ data.age = data.age + 1
870
+ resolve(data)
871
+ }, 100)
872
+ } else {
873
+ resolve(data)
874
+ }
875
+ })
876
+ next() // should console an error after waiting for the promise to finish
877
+ return promise
878
+ },
879
+ async (data, next) => {
880
+ data.age = data.age + 1
881
+ },
882
+ ],
883
+ })
884
+ let user2 = db.model('user2', {
885
+ fields: { age: { type: 'number'} },
886
+ afterFind: [
887
+ async (data, next) => {
888
+ await new Promise((resolve, reject) => {
889
+ setTimeout(() => {
890
+ data.age = data.age + 1
891
+ resolve(data)
892
+ }, 100)
893
+ })
894
+ next() // should console an error after waiting for the promise to finish, without returning a promise
895
+ },
896
+ async (data, next) => {
897
+ data.age = data.age + 1
898
+ },
899
+ ],
900
+ })
901
+ let user3 = db.model('user3', {
902
+ fields: { age: { type: 'number'} },
903
+ afterFind: [
904
+ async (data, next) => {
905
+ data.age = data.age + 1
906
+ next() // should console an error for empty promise
907
+ },
908
+ async (data, next) => {
909
+ data.age = data.age + 1
910
+ },
911
+ ],
912
+ })
913
+ let user4 = db.model('user4', {
914
+ fields: { age: { type: 'number'} },
915
+ afterFind: [
916
+ async (data, next) => {
917
+ return await new Promise((resolve, reject) => {
918
+ setTimeout(() => {
919
+ data.age = data.age + 1
920
+ next() // should console an error
921
+ resolve(data)
922
+ }, 100)
923
+ })
924
+ },
925
+ async (data, next) => {
926
+ data.age = data.age + 1
927
+ },
928
+ ],
929
+ })
930
+
931
+ let user5 = db.model('user5', {
932
+ fields: { age: { type: 'number'} },
933
+ afterFind: [
934
+ async (data, next) => {
935
+ const promise = Promise.reject(new Error('An async error occurred with Martin3'))
936
+ next(new Error('An async error occurred with Martin3'))
937
+ return promise
938
+ },
939
+ async (data, next) => {
940
+ data.age = data.age + 1 // shouldn't be reached
941
+ },
942
+ ],
943
+ })
944
+
945
+
946
+ let user1Doc = await user1.insert({ data: { age: 0 } })
947
+ let user2Doc = await user2.insert({ data: { age: 0 } })
948
+ let user3Doc = await user3.insert({ data: { age: 0 } })
949
+ let user4Doc = await user4.insert({ data: { age: 0 } })
950
+ let user5Doc = await user5.insert({ data: { age: 0 } })
951
+
952
+ const logSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
953
+
954
+ // Only increment twice
955
+ await expect(user1.find({ query: user1Doc._id })).resolves.toEqual({ _id: expect.any(Object), age: 2 })
956
+ expect(logSpy).toHaveBeenCalledWith('Monastery afterFind error: you cannot return a promise AND call next()')
957
+
958
+ await expect(user2.find({ query: user2Doc._id })).resolves.toEqual({ _id: expect.any(Object), age: 2 })
959
+ expect(logSpy).toHaveBeenCalledWith('Monastery afterFind error: you cannot return a promise AND call next()')
960
+
961
+ await expect(user3.find({ query: user3Doc._id })).resolves.toEqual({ _id: expect.any(Object), age: 2 })
962
+ expect(logSpy).toHaveBeenCalledWith('Monastery afterFind error: you cannot return a promise AND call next()')
963
+
964
+ await expect(user4.find({ query: user4Doc._id })).resolves.toEqual({ _id: expect.any(Object), age: 2 })
965
+ expect(logSpy).toHaveBeenCalledWith('Monastery afterFind error: you cannot return a promise AND call next()')
966
+
967
+ await expect(user5.find({ query: user5Doc._id })).rejects.toThrow('An async error occurred with Martin3')
968
+ expect(logSpy).toHaveBeenCalledWith('Monastery afterFind error: you cannot return a promise AND call next()')
969
+
859
970
  })