monastery 3.5.0 → 3.5.2

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.5.2](https://github.com/boycce/monastery/compare/3.5.1...3.5.2) (2025-01-22)
6
+
7
+ ### [3.5.1](https://github.com/boycce/monastery/compare/3.5.0...3.5.1) (2024-12-23)
8
+
5
9
  ## [3.5.0](https://github.com/boycce/monastery/compare/3.4.3...3.5.0) (2024-12-20)
6
10
 
7
11
  ### [3.4.3](https://github.com/boycce/monastery/compare/3.4.2...3.4.3) (2024-08-27)
package/lib/index.js CHANGED
@@ -1,26 +1,8 @@
1
1
  const Manager = require('./manager')
2
- const inherits = require('util').inherits
3
2
  const rules = require('./rules.js')
4
3
 
5
- function Monastery() {
6
- let hasDefaultManager = false
7
-
8
- this.manager = function(uri, opts) {
9
- const manager = new Manager(uri, opts)
10
- // Inherit the default manager onto Monastery once
11
- if (!hasDefaultManager) {
12
- hasDefaultManager = true
13
- Object.assign(this, manager)
14
- }
15
- return manager
16
- }
17
- }
18
-
19
- // Inherit Manager prototypes onto Monastery
20
- inherits(Monastery, Manager)
21
-
22
4
  // Exports
23
- module.exports = new Monastery()
5
+ module.exports = new Manager('init')
24
6
  module.exports.arrayWithSchema = Manager.prototype.arrayWithSchema
25
7
  module.exports.getSignedUrl = Manager.prototype.getSignedUrl
26
8
  module.exports.id = Manager.prototype.id
package/lib/manager.js CHANGED
@@ -2,14 +2,15 @@ const fs = require('fs')
2
2
  const debug = require('debug')
3
3
  const path = require('path')
4
4
  const EventEmitter = require('events').EventEmitter
5
- const inherits = require('util').inherits
6
5
  const { MongoClient } = require('mongodb')
7
6
  const Collection = require('./collection.js')
8
7
  const imagePluginFile = require('../plugins/images/index.js')
9
8
  const Model = require('./model.js')
10
9
  const util = require('./util.js')
11
10
 
12
- function Manager(uri, opts) {
11
+ let hasDefaultManager = false
12
+
13
+ function Manager(uri, opts, parent) {
13
14
  /**
14
15
  * Constructs a new manager which contains a new MongoDB client
15
16
  *
@@ -22,8 +23,11 @@ function Manager(uri, opts) {
22
23
  * @link https://www.mongodb.com/docs/drivers/node/v5.9/quick-reference/
23
24
  * @link https://mongodb.github.io/node-mongodb-native/ (all versions)
24
25
  */
26
+ const that = (!hasDefaultManager && parent) ? parent : this
27
+
25
28
  if (!opts) opts = {}
26
29
  if (!(this instanceof Manager)) return new Manager(uri, opts)
30
+ if (uri == 'init') return
27
31
 
28
32
  // opts: timestamps
29
33
  if (typeof opts.defaultFields != 'undefined') throw Error('opts.defaultFields has been depreciated, use opts.timestamps')
@@ -34,51 +38,51 @@ function Manager(uri, opts) {
34
38
 
35
39
  // opts: separate out monastery options
36
40
  const mongoOpts = Object.keys(opts||{}).reduce((acc, key) => {
37
- if (
38
- ![
39
- 'databaseName', 'defaultObjects', 'logLevel', 'imagePlugin', 'limit', 'noDefaults', 'nullObjects',
40
- 'promise', 'timestamps', 'useMilliseconds',
41
- ].includes(key)) {
42
- acc[key] = opts[key]
41
+ if (![
42
+ 'databaseName', 'defaultObjects', 'logLevel', 'imagePlugin', 'limit', 'noDefaults', 'nullObjects',
43
+ 'promise', 'timestamps', 'useMilliseconds',
44
+ ].includes(key)) {
45
+ acc[key] = opts[key]
43
46
  }
44
47
  return acc
45
48
  }, {})
46
49
 
47
50
  // Add properties
48
- this.uri = uri
49
- this.error = debug('monastery:error' + (opts.logLevel > 0 ? '*' : ''))
50
- this.warn = debug('monastery:warn' + (opts.logLevel > 1 ? '*' : ''))
51
- this.info = debug('monastery:info' + (opts.logLevel > 2 ? '*' : ''))
52
- this.opts = opts
53
- this.beforeModel = []
54
- this.collections = {}
55
- this._openQueue = []
51
+ that.uri = uri
52
+ that.error = debug('monastery:error' + (opts.logLevel >= 1 ? '*' : ''))
53
+ that.warn = debug('monastery:warn' + (opts.logLevel >= 2 ? '*' : ''))
54
+ that.info = debug('monastery:info' + (opts.logLevel >= 3 ? '*' : ''))
55
+ that.opts = opts
56
+ that.beforeModel = []
57
+ that.collections = {}
58
+ that._openQueue = []
59
+ that.emitter = new EventEmitter()
56
60
 
57
61
  // If there is no DEBUG= environment variable, we will need to force the debugs on (due to bug on debug@4.3.4)
58
62
  if (!debug.namespaces) {
59
- if (opts.logLevel > 0) this.error.enabled = true
60
- if (opts.logLevel > 1) this.warn.enabled = true
61
- if (opts.logLevel > 2) this.info.enabled = true
63
+ if (opts.logLevel >= 1) that.error.enabled = true
64
+ if (opts.logLevel >= 2) that.warn.enabled = true
65
+ if (opts.logLevel >= 3) that.info.enabled = true
62
66
  }
63
67
 
64
68
  // Create a new MongoDB Client
65
- if (typeof this.uri == 'string' || Array.isArray(this.uri)) {
66
- this.uri = this.connectionString(this.uri, opts.databaseName)
67
- this.client = new MongoClient(this.uri, mongoOpts)
69
+ if (typeof that.uri == 'string' || Array.isArray(that.uri)) {
70
+ that.uri = that.connectionString(that.uri, opts.databaseName)
71
+ that.client = new MongoClient(that.uri, mongoOpts)
68
72
  } else {
69
- this.client = this.uri
73
+ that.client = that.uri
70
74
  }
71
75
 
72
76
  // Listen to MongoDB events
73
77
  for (let eventName of ['close', 'error', 'timeout']) {
74
- this.client.on(eventName, (e) => this.emit(eventName, e))
78
+ that.client.on(eventName, (e) => that.emitter.emit(eventName, e))
75
79
  }
76
80
 
77
81
  // Listen to custom open event
78
- this.on('open', () => {
82
+ that.emitter.on('open', () => {
79
83
  // wait for all the on('open') events to get called first
80
84
  setTimeout(() => {
81
- for (let item of this._openQueue) {
85
+ for (let item of that._openQueue) {
82
86
  item()
83
87
  }
84
88
  })
@@ -86,12 +90,17 @@ function Manager(uri, opts) {
86
90
 
87
91
  // Initiate any plugins
88
92
  if (opts.imagePlugin) {
89
- imagePluginFile.setup(this, util.isObject(opts.imagePlugin) ? opts.imagePlugin : {})
93
+ imagePluginFile.setup(that, util.isObject(opts.imagePlugin) ? opts.imagePlugin : {})
90
94
  }
91
95
 
92
- // Connect to the database
93
- if (opts.promise) return this.open()
94
- else this.open()
96
+ // Update the parent manager with the new manager
97
+ if (!hasDefaultManager && parent) hasDefaultManager = true
98
+ if (opts.promise) return that.open()
99
+ else that.open()
100
+ }
101
+
102
+ Manager.prototype.manager = function(uri, opts) {
103
+ return new Manager(uri, opts, this)
95
104
  }
96
105
 
97
106
  Manager.prototype.arrayWithSchema = function(array, schema) {
@@ -104,9 +113,9 @@ Manager.prototype.close = async function() {
104
113
  * Close the database connection, gracefully
105
114
  */
106
115
  const _close = async () => {
107
- this.emit(this._state = 'closing')
116
+ this.emitter.emit(this._state = 'closing')
108
117
  await this.client.close()
109
- this.emit(this._state = 'close')
118
+ this.emitter.emit(this._state = 'close')
110
119
  }
111
120
  if (this._state == 'open') {
112
121
  return await _close()
@@ -179,26 +188,33 @@ Manager.prototype.isId = function(value) {
179
188
  return util.isId(value)
180
189
  }
181
190
 
182
- Manager.prototype.models = async function(pathname, opts) {
191
+ Manager.prototype.models = async function(pathname, opts={}) {
183
192
  /**
184
193
  * Setup model definitions from a folder location
185
194
  * @param {string} pathname
186
195
  * @param {object} opts:
187
196
  * @param {boolean} opts.waitForIndexes - Wait for indexes to be created?
197
+ * @param {boolean} opts.skipIfExists - Skip if the model already exists?
188
198
  * @return {Promise(object)} - e.g. { user: , article: , .. }
189
199
  * @this Manager
190
200
  */
191
201
  let out = {}
192
- let { waitForIndexes } = opts || {}
202
+ let { waitForIndexes } = opts
193
203
  if (!pathname || typeof pathname !== 'string') {
194
204
  throw 'The path must be a valid pathname'
195
205
  }
206
+ if (!fs.existsSync(pathname)) {
207
+ this.warn(`The models path ${pathname} does not exist`)
208
+ return out
209
+ }
196
210
  let filenames = fs.readdirSync(pathname)
197
211
  for (let filename of filenames) {
198
212
  // Ignore folders
199
213
  if (!filename.match(/\.[cm]?js$/)) continue
200
214
  let name = filename.replace('.js', '')
201
215
  let filepath = path.join(pathname, filename)
216
+ // Skip
217
+ if (opts.skipIfExists && this.models[name]) continue
202
218
  // We can't use require() here since we need to be able to import both CJS and ES6 modules
203
219
  let definition = ((await import(filepath))||{}).default
204
220
  // When a commonJS project uses babel (e.g. `nodemon -r @babel/register`, similar to `-r esm`), import()
@@ -219,7 +235,11 @@ Manager.prototype.onError = function(fn) {
219
235
  * @return {Promise}
220
236
  */
221
237
  return new Promise((resolve, reject) => {
222
- this.on('error', (err) => {
238
+ if (!this.emitter) {
239
+ reject(new Error('Emitter not found! This can happen if two seperate monastery packages are imported into the ' +
240
+ 'same project. E.g. the first package initializes the manager, and the second package tries to use it.'))
241
+ }
242
+ this.emitter.on('error', (err) => {
223
243
  resolve(err)
224
244
  })
225
245
  }).then((err) => {
@@ -234,13 +254,17 @@ Manager.prototype.onOpen = function(fn) {
234
254
  * @return {Promise(manager)}
235
255
  */
236
256
  return new Promise((resolve, reject) => {
257
+ if (!this.emitter) {
258
+ reject(new Error('Emitter not found! This can happen if two seperate monastery packages are imported into the ' +
259
+ 'same project. E.g. the first package initializes the manager, and the second package tries to use it.'))
260
+ }
237
261
  if (this._state == 'open') {
238
262
  resolve(this)
239
263
  }
240
- this.on('open', () => {
264
+ this.emitter.on('open', () => {
241
265
  resolve(this)
242
266
  })
243
- this.on('error', (err) => {
267
+ this.emitter.on('error', (err) => {
244
268
  reject(err)
245
269
  })
246
270
  }).then((err) => { // If `then` is not chained, the error will be thrown, detached!
@@ -257,16 +281,16 @@ Manager.prototype.open = async function() {
257
281
  this._state = 'opening'
258
282
  this.db = this.client.db()
259
283
  await this.client.connect() // now optional since db().command() will auto-connect
260
- this.emit(this._state = 'open', this)
284
+ this.emitter.emit(this._state = 'open', this)
261
285
  return this
262
286
 
263
287
  } catch (err) {
264
288
  this._state = 'closed'
265
289
  // Only emit the error if there are listeners, since it will throw an unhandled error
266
- if (this.listeners('error').length > 0) {
267
- this.emit('error', err)
290
+ if (this.emitter.listeners('error').length > 0) {
291
+ this.emitter.emit('error', err)
268
292
  }
269
- if (this.opts.promise || this.listeners('error').length == 0) {
293
+ if (this.opts.promise || this.emitter.listeners('error').length == 0) {
270
294
  throw new Error(err)
271
295
  }
272
296
  }
@@ -284,6 +308,4 @@ Manager.prototype._getSignedUrl = () => {
284
308
  throw new Error('monastery._getSignedUrl() has been moved to monastery.getSignedUrl()')
285
309
  }
286
310
 
287
- inherits(Manager, EventEmitter)
288
-
289
311
  module.exports = Manager
package/lib/model.js CHANGED
@@ -9,7 +9,7 @@ function Model(name, definition, waitForIndexes, manager) {
9
9
  * @param {boolean} waitForIndexes - wait for indexes to be created before returning (returns a promise)
10
10
  *
11
11
  * @return model or Promise(model)
12
- * @this manager
12
+ * @this Manager | Model
13
13
  */
14
14
  if (!(this instanceof Model)) {
15
15
  return new Model(name, definition, waitForIndexes, this)
@@ -27,7 +27,11 @@ function Model(name, definition, waitForIndexes, manager) {
27
27
  ', e.g. { schema: { strict: false }}'
28
28
  } else if (!util.isSubdocument(definition.fields) && !util.isEmpty(definition.fields)) {
29
29
  throw `The ${name}.fields object should be a valid document, e.g. { name: { type: 'string' }}`
30
- }
30
+ }
31
+ // else if (manager.models[name]) {
32
+ // manager.warn(`The model '${name}' already exists, skipping model setup.`)
33
+ // return manager.models[name]
34
+ // }
31
35
 
32
36
  // Add schema options
33
37
  Object.assign(this, {
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.5.0",
5
+ "version": "3.5.2",
6
6
  "license": "MIT",
7
7
  "repository": "github:boycce/monastery",
8
8
  "homepage": "https://boycce.github.io/monastery/",
package/test/manager.js CHANGED
@@ -83,7 +83,7 @@ test('manager > events', async () => {
83
83
  // note: jests tests only wait for 5s
84
84
  function eventTester(db, eventName) {
85
85
  return new Promise((resolve) => {
86
- db.on(eventName, () => resolve(true))
86
+ db.emitter.on(eventName, () => resolve(true))
87
87
  })
88
88
  }
89
89
  // Triggers on opening/open
@@ -108,3 +108,17 @@ test('Manager > get collection', async () => {
108
108
  expect(await db.get('non-collection').find({})).toEqual([])
109
109
  db.close()
110
110
  })
111
+
112
+ test('Manager > multiple managers', async () => {
113
+ const db1 = monastery.manager('localhost/monastery', { logLevel: 5 })
114
+ const db2 = monastery.manager('localhost/monastery', { logLevel: 6 })
115
+ const db3 = monastery.manager('localhost/monastery', { logLevel: 7 })
116
+
117
+ expect(monastery.opts.logLevel).not.toEqual(6) // default manager
118
+ expect(db2.opts.logLevel).not.toEqual(db1.opts.logLevel)
119
+ expect(db3.opts.logLevel).not.toEqual(db2.opts.logLevel)
120
+
121
+ db1.close()
122
+ db2.close()
123
+ db3.close()
124
+ })