monastery 3.0.13 → 3.0.15

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/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.15](https://github.com/boycce/monastery/compare/3.0.14...3.0.15) (2024-05-01)
6
+
7
+ ### [3.0.14](https://github.com/boycce/monastery/compare/3.0.13...3.0.14) (2024-05-01)
8
+
5
9
  ### [3.0.13](https://github.com/boycce/monastery/compare/3.0.12...3.0.13) (2024-05-01)
6
10
 
7
11
  ### [3.0.12](https://github.com/boycce/monastery/compare/3.0.11...3.0.12) (2024-05-01)
@@ -60,6 +60,7 @@ db.onError((err) => {
60
60
  - `manager.models()`: [see models](./models.html)
61
61
  - `manager.onError(Function)`: Catches connection errors
62
62
  - `manager.onOpen(Function)`: Triggers on successful connection
63
+ - `manager.getSignedUrl(path, expires, bucket)`: You can sign AWS S3 paths using this image plugin helper
63
64
 
64
65
  ### Dates
65
66
 
package/lib/index.js CHANGED
@@ -282,7 +282,7 @@ Manager.prototype.parseData = function(obj) {
282
282
  }
283
283
 
284
284
  Manager.prototype.model = Model
285
- Manager.prototype._getSignedUrl = imagePluginFile._getSignedUrl
285
+ Manager.prototype.getSignedUrl = Manager.prototype._getSignedUrl = imagePluginFile.getSignedUrl
286
286
 
287
287
  inherits(Manager, EventEmitter)
288
288
  module.exports = Manager
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)), `${this.name}.beforeInsert`)
51
+ await util.runSeries.call(this, this.beforeInsert.map(f => f.bind(opts, data)), '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)), `${this.name}.afterInsert`)
53
+ await util.runSeries.call(this, this.afterInsert.map(f => f.bind(opts, response)), '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||{})), `${this.name}.beforeUpdate`)
270
+ await util.runSeries.call(this, this.beforeUpdate.map(f => f.bind(opts, data||{})), '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}()\``)
@@ -298,7 +298,7 @@ Model.prototype.update = async function (opts, type='update') {
298
298
 
299
299
  // Hook: afterUpdate (doesn't have access to validated data)
300
300
  if (response) {
301
- await util.runSeries(this.afterUpdate.map(f => f.bind(opts, response)), `${this.name}.afterUpdate`)
301
+ await util.runSeries.call(this, this.afterUpdate.map(f => f.bind(opts, response)), 'afterUpdate')
302
302
  }
303
303
 
304
304
  // Hook: afterFind if findOneAndUpdate
@@ -331,9 +331,9 @@ Model.prototype.remove = async function (opts) {
331
331
  opts = await this._queryObject(opts, 'remove')
332
332
 
333
333
  // Remove
334
- await util.runSeries(this.beforeRemove.map(f => f.bind(opts)), `${this.name}.beforeRemove`)
334
+ await util.runSeries.call(this, this.beforeRemove.map(f => f.bind(opts)), 'beforeRemove')
335
335
  let response = await this._remove(opts.query, util.omit(opts, this._queryOptions))
336
- await util.runSeries(this.afterRemove.map(f => f.bind(response)), `${this.name}.afterRemove`)
336
+ await util.runSeries.call(this, this.afterRemove.map(f => f.bind(response)), 'afterRemove')
337
337
 
338
338
  // Success
339
339
  if (opts.req && opts.respond) opts.req.res.json(response)
@@ -591,7 +591,7 @@ Model.prototype._processAfterFind = function (data, projection={}, afterFindCont
591
591
  callbackSeries.push(fn.bind(afterFindContext, item.dataRef))
592
592
  }
593
593
  }
594
- return util.runSeries(callbackSeries, 'afterFind').then(() => data)
594
+ return util.runSeries.call(this, callbackSeries, 'afterFind').then(() => data)
595
595
  }
596
596
 
597
597
  Model.prototype._recurseAndFindModels = function (parentPath, schemaFields, dataArr) {
@@ -30,7 +30,7 @@ Model.prototype.validate = async function (data, opts) {
30
30
 
31
31
  // Hook: beforeValidate
32
32
 
33
- await util.runSeries(this.beforeValidate.map(f => f.bind(opts, data)), `${this.name}.beforeValidate`)
33
+ await util.runSeries.call(this, this.beforeValidate.map(f => f.bind(opts, data)), 'beforeValidate')
34
34
 
35
35
  // Recurse and validate fields
36
36
  let response = util.toArray(data).map(item => {
package/lib/util.js CHANGED
@@ -309,23 +309,25 @@ module.exports = {
309
309
  return variable
310
310
  },
311
311
 
312
- runSeries: function(tasks, info, cb) {
312
+ runSeries: function(tasks, hookName, 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
+ * @param {string} <hookName> - e.g. 'afterFind'
317
317
  * @param {function(err, results[])} <cb>
318
318
  * @return promise
319
+ * @this Model
319
320
  * @source https://github.com/feross/run-series
320
321
  */
321
322
  let current = 0
322
323
  let results = []
323
324
  let isSync = true
325
+ let caller = hookName == 'afterFind' ? 'afterFind' : this.name + '.' + hookName
324
326
 
325
327
  return new Promise((res, rej) => {
326
- function next(i, err, result) { // aka next(err, data)
328
+ const next = (i, err, result) => { // aka next(err, data)
327
329
  if (i !== current) {
328
- console.error(`Monastery ${info} error: you cannot return a promise AND call next()`)
330
+ this.manager.error(`Monastery ${caller} error: you cannot return a promise AND call next()`)
329
331
  return
330
332
  }
331
333
  current++
@@ -333,16 +335,16 @@ module.exports = {
333
335
  if (!err && current < tasks.length) callTask(current)
334
336
  else done(err)
335
337
  }
336
- function done(err) {
338
+ const done = (err) => {
337
339
  if (isSync) process.nextTick(() => end(err))
338
340
  else end(err)
339
341
  }
340
- function end(err) {
342
+ const end = (err) => {
341
343
  if (cb) cb(err, results)
342
344
  if (err) rej(err)
343
345
  else res(results)
344
346
  }
345
- function callTask(i) {
347
+ const callTask = (i) => {
346
348
  const next2 = next.bind(null, i)
347
349
  const res = tasks[i](next2)
348
350
  if (res instanceof Promise) {
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.13",
5
+ "version": "3.0.15",
6
6
  "license": "MIT",
7
7
  "repository": "github:boycce/monastery",
8
8
  "homepage": "https://boycce.github.io/monastery/",
@@ -31,7 +31,7 @@ let plugin = module.exports = {
31
31
  this.bucketDir = options.bucketDir || 'full' // depreciated > 1.36.2
32
32
  this.filesize = options.filesize
33
33
  this.formats = options.formats || ['bmp', 'gif', 'jpg', 'jpeg', 'png', 'tiff']
34
- this.getSignedUrl = options.getSignedUrl
34
+ this.getSignedUrlOption = options.getSignedUrl
35
35
  this.manager = manager
36
36
  this.metadata = options.metadata ? util.deepCopy(options.metadata) : undefined,
37
37
  this.params = options.params ? util.deepCopy(options.params) : {},
@@ -41,17 +41,18 @@ let plugin = module.exports = {
41
41
  throw new Error('Monastery imagePlugin: awsRegion, awsBucket, awsAccessKeyId, or awsSecretAccessKey is not defined')
42
42
  }
43
43
  if (!options.awsRegion) {
44
- throw new Error('Monastery imagePlugin: v3 requires awsRegion to be defined for signing urls, e.g. "ap-southeast-2"')
44
+ throw new Error('Monastery imagePlugin: v3 requires awsRegion to be defined for signing urls, e.g. \'ap-southeast-2\'')
45
45
  }
46
46
 
47
47
  // Create s3 'service' instance (defer require since it takes 120ms to load)
48
48
  // v2: https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#constructor-property
49
49
  // v3: https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/s3/
50
50
  // v3 examples: https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/javascript_s3_code_examples.html
51
- this.getS3Client = () => {
51
+ this.getS3Client = (useRegion) => {
52
52
  const { S3 } = require('@aws-sdk/client-s3')
53
- return this._s3Client || (this._s3Client = new S3({
54
- region: this.awsRegion,
53
+ const key = useRegion ? '_s3ClientRegional' : '_s3Client'
54
+ return this[key] || (this[key] = new S3({
55
+ region: useRegion ? this.awsRegion : undefined,
55
56
  credentials: {
56
57
  accessKeyId: this.awsAccessKeyId,
57
58
  secretAccessKey: this.awsSecretAccessKey,
@@ -230,11 +231,11 @@ let plugin = module.exports = {
230
231
  for (let doc of util.toArray(data)) {
231
232
  for (let imageField of this.imageFields) {
232
233
  if (options.getSignedUrls
233
- || (util.isDefined(imageField.getSignedUrl) ? imageField.getSignedUrl : plugin.getSignedUrl)) {
234
+ || (util.isDefined(imageField.getSignedUrl) ? imageField.getSignedUrl : plugin.getSignedUrlOption)) {
234
235
  let images = plugin._findImagesInData(doc, imageField, 0, '').filter(o => o.image)
235
236
  // todo: we could do this in parallel
236
237
  for (let image of images) {
237
- image.image.signedUrl = await plugin._getSignedUrl(image.image.path, 3600, imageField.awsBucket)
238
+ image.image.signedUrl = await plugin.getSignedUrl(image.image.path, 3600, imageField.awsBucket)
238
239
  }
239
240
  }
240
241
  }
@@ -589,24 +590,24 @@ let plugin = module.exports = {
589
590
  return list
590
591
  },
591
592
 
592
- _getSignedUrl: async (path, expires=3600, bucket) => {
593
+ getSignedUrl: async (path, expires=3600, bucket) => {
593
594
  /**
594
595
  * @param {string} path - aws file path
595
596
  * @param {number} <expires> - seconds
596
- * @param {number} <bucket>
597
+ * @param {string} <bucket>
597
598
  * @return {promise} signedUrl
598
599
  * @see v2: https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#getSignedUrl-property
599
600
  * @see v3: https://github.com/aws/aws-sdk-js-v3/blob/main/UPGRADING.md#s3-presigned-url
600
601
  */
601
602
  if (!plugin.getS3Client) {
602
603
  throw new Error(
603
- 'To use db._getSignedUrl(), the imagePlugin manager option must be defined, e.g. `monastery(..., { imagePlugin })`'
604
+ 'To use db.getSignedUrl(), the imagePlugin manager option must be defined, e.g. `monastery(..., { imagePlugin })`'
604
605
  )
605
606
  }
606
607
  const { GetObjectCommand } = require('@aws-sdk/client-s3')
607
608
  const params = { Bucket: bucket || plugin.awsBucket, Key: path }
608
609
  const command = new GetObjectCommand(params)
609
- let signedUrl = await getSignedUrl(plugin.getS3Client(), command, { expiresIn: expires })
610
+ let signedUrl = await getSignedUrl(plugin.getS3Client(true), command, { expiresIn: expires })
610
611
  // console.log(signedUrl)
611
612
  return signedUrl
612
613
  },
package/test/crud.js CHANGED
@@ -859,7 +859,8 @@ test('hooks > async', async () => {
859
859
  })
860
860
 
861
861
  test('hooks > async and next conflict', async () => {
862
- let user1 = db.model('user', {
862
+ const db2 = monastery('127.0.0.1/monastery', { timestamps: false })
863
+ let user1 = db2.model('user', {
863
864
  fields: { age: { type: 'number'} },
864
865
  afterFind: [
865
866
  async (data, next) => {
@@ -881,7 +882,7 @@ test('hooks > async and next conflict', async () => {
881
882
  },
882
883
  ],
883
884
  })
884
- let user2 = db.model('user2', {
885
+ let user2 = db2.model('user2', {
885
886
  fields: { age: { type: 'number'} },
886
887
  afterFind: [
887
888
  async (data, next) => {
@@ -898,7 +899,7 @@ test('hooks > async and next conflict', async () => {
898
899
  },
899
900
  ],
900
901
  })
901
- let user3 = db.model('user3', {
902
+ let user3 = db2.model('user3', {
902
903
  fields: { age: { type: 'number'} },
903
904
  afterFind: [
904
905
  async (data, next) => {
@@ -910,7 +911,7 @@ test('hooks > async and next conflict', async () => {
910
911
  },
911
912
  ],
912
913
  })
913
- let user4 = db.model('user4', {
914
+ let user4 = db2.model('user4', {
914
915
  fields: { age: { type: 'number'} },
915
916
  afterFind: [
916
917
  async (data, next) => {
@@ -928,7 +929,7 @@ test('hooks > async and next conflict', async () => {
928
929
  ],
929
930
  })
930
931
 
931
- let user5 = db.model('user5', {
932
+ let user5 = db2.model('user5', {
932
933
  fields: { age: { type: 'number'} },
933
934
  afterFind: [
934
935
  async (data, next) => {
@@ -949,7 +950,7 @@ test('hooks > async and next conflict', async () => {
949
950
  let user4Doc = await user4.insert({ data: { age: 0 } })
950
951
  let user5Doc = await user5.insert({ data: { age: 0 } })
951
952
 
952
- const logSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
953
+ const logSpy = jest.spyOn(db2, 'error').mockImplementation(() => {})
953
954
 
954
955
  // Only increment twice
955
956
  await expect(user1.find({ query: user1Doc._id })).resolves.toEqual({ _id: expect.any(Object), age: 2 })
@@ -967,4 +968,5 @@ test('hooks > async and next conflict', async () => {
967
968
  await expect(user5.find({ query: user5Doc._id })).rejects.toThrow('An async error occurred with Martin3')
968
969
  expect(logSpy).toHaveBeenCalledWith('Monastery afterFind error: you cannot return a promise AND call next()')
969
970
 
971
+ db2.close()
970
972
  })
@@ -573,7 +573,7 @@ test('images option defaults', async () => {
573
573
  expect(imagePluginFile.awsAcl).toEqual('public-read')
574
574
  expect(imagePluginFile.filesize).toEqual(undefined)
575
575
  expect(imagePluginFile.formats).toEqual(['bmp', 'gif', 'jpg', 'jpeg', 'png', 'tiff'])
576
- expect(imagePluginFile.getSignedUrl).toEqual(undefined)
576
+ expect(imagePluginFile.getSignedUrlOption).toEqual(undefined)
577
577
  expect(imagePluginFile.metadata).toEqual(undefined)
578
578
  expect(imagePluginFile.path).toEqual(expect.any(Function))
579
579
  expect(imagePluginFile.params).toEqual({})
@@ -722,7 +722,7 @@ test('images option getSignedUrls', async () => {
722
722
  timestamps: false,
723
723
  imagePlugin: {
724
724
  ...imagePluginFakeOpts,
725
- awsRegion: 's3-ap-southeast-2',
725
+ awsRegion: 'ap-southeast-2',
726
726
  getSignedUrl: true,
727
727
  },
728
728
  })