monastery 3.4.2 → 3.5.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/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.0](https://github.com/boycce/monastery/compare/3.4.3...3.5.0) (2024-12-20)
6
+
7
+ ### [3.4.3](https://github.com/boycce/monastery/compare/3.4.2...3.4.3) (2024-08-27)
8
+
5
9
  ### [3.4.2](https://github.com/boycce/monastery/compare/3.4.1...3.4.2) (2024-08-24)
6
10
 
7
11
  ### [3.4.1](https://github.com/boycce/monastery/compare/3.4.0...3.4.1) (2024-08-09)
@@ -304,7 +304,7 @@ You are able to define custom error messages for each field rule.
304
304
 
305
305
  ### Operation hooks
306
306
 
307
- You are able provide an array of callbacks to these model operation hooks. If you need to throw an error asynchronously, please pass an error as the first argument to `next()`, e.g. `next(new Error('Your error here'))`. You can also access the operation details via `this` in each callback.
307
+ You are able provide an array of promises to the following model operation hooks. `this` is the operation details.
308
308
 
309
309
  ```js
310
310
  {
@@ -6,7 +6,7 @@ has_children: true
6
6
 
7
7
  # Manager
8
8
 
9
- Monastery manager constructor.
9
+ Creates a new manager instance and makes the first instance available globally via the `db`.
10
10
 
11
11
  ### Arguments
12
12
 
@@ -24,26 +24,27 @@ Monastery manager constructor.
24
24
 
25
25
  ### Returns
26
26
 
27
- A manager instance.
27
+ A new manager instance.
28
28
 
29
29
  ### Example
30
30
 
31
31
  ```js
32
- import monastery from 'monastery'
32
+ import db from 'monastery'
33
33
 
34
- const db = monastery('localhost/mydb', options)
34
+ // this manager instance is now available via the `db` object on subsequent imports
35
+ const manager = db.manager('localhost/mydb', options)
35
36
  // replica set
36
- const db = monastery('localhost/mydb,192.168.1.1')
37
+ const manager = db.manager('localhost/mydb,192.168.1.1')
37
38
  // you can wait for the connection (which is not required before calling methods)
38
- const db = await monastery('localhost/mydb,192.168.1.1', { promise: true })
39
+ const manager = await db.manager('localhost/mydb,192.168.1.1', { promise: true })
39
40
  ```
40
41
 
41
42
  You can also listen for errors or successful connection using these hooks
42
43
  ```js
43
- db.onOpen((manager) => {
44
+ manager.onOpen((manager) => {
44
45
  // manager.client is connected...
45
46
  })
46
- db.onError((err) => {
47
+ manager.onError((err) => {
47
48
  // connection error
48
49
  })
49
50
  ```
@@ -11,7 +11,9 @@ Sets up a model, retrieves a collection, and sets up any required indexes
11
11
 
12
12
  `name` *(string)*: name of the mongo collection
13
13
 
14
- [`definition`] *(object)*: [definition](../definition)
14
+ `definition` *(object)*: [definition](../definition)
15
+
16
+ `waitForIndexes` *(boolean)*: wait for indexes to be setup (returns a promise instead of a model)
15
17
 
16
18
  ### Returns
17
19
 
@@ -13,7 +13,7 @@ Setup model definitions from a folder location
13
13
 
14
14
  [`options`] *(object)*:
15
15
  - [`commonJs=false`] *(boolean)*: for old commonjs projects, you will need to set this to `true` which uses `require` instead of `import` (removed in `3.0.0`)
16
- - [`waitForIndexes=false`] *(boolean)*: returns a proimse that waits for the Mongo collection indexes to be setup
16
+ - [`waitForIndexes=false`] *(boolean)*: wait for collection indexes to be setup
17
17
 
18
18
  ### Returns
19
19
 
@@ -39,5 +39,5 @@ export default { // Make sure the model definition is exported as the default
39
39
  ```
40
40
 
41
41
  ```js
42
- await db.models(__dirname + "models")
42
+ await db.models(__dirname + 'models', { waitForIndexes: true })
43
43
  ```
package/docs/readme.md CHANGED
@@ -35,10 +35,10 @@ $ npm install --save monastery
35
35
  ## Usage
36
36
 
37
37
  ```javascript
38
- import monastery from 'monastery'
38
+ import db from 'monastery'
39
39
 
40
40
  // Initialize a monastery manager
41
- const db = monastery('localhost/mydb')
41
+ db.manager('localhost/mydb')
42
42
 
43
43
  // Define a model
44
44
  db.model('user', {
@@ -96,6 +96,7 @@ You can view MongoDB's [compatibility table here](https://www.mongodb.com/docs/d
96
96
  - db.catch/then() moved to db.onError/db.onOpen()
97
97
  - next() is now redundant when returning promises from hooks, e.g. `afterFind: [async (data) => {...}]`
98
98
  - deep paths in data, e.g. `books[].title` are now validated, and don't replace the whole object, e.g. `books`
99
+ - `db()` moved to `db.manager()`
99
100
 
100
101
  ## v2 Breaking Changes
101
102
 
package/lib/index.js CHANGED
@@ -1,301 +1,32 @@
1
- /*eslint-disable max-len*/
2
- const fs = require('fs')
3
- const debug = require('debug')
4
- const path = require('path')
5
- const EventEmitter = require('events').EventEmitter
1
+ const Manager = require('./manager')
6
2
  const inherits = require('util').inherits
7
- const { MongoClient } = require('mongodb')
8
- const Collection = require('./collection.js')
9
- const imagePluginFile = require('../plugins/images/index.js')
10
- const Model = require('./model.js')
11
3
  const rules = require('./rules.js')
12
- const util = require('./util.js')
13
4
 
14
- function Manager(uri, opts) {
15
- /**
16
- * Constructs a new manager which contains a new MongoDB client
17
- *
18
- * @param {String|Array|Object} uri - MongoDB connection URI string / replica set, or reuse a MongoClient instance
19
- * @param {Object} opts -
20
- * {String} <opts.databaseName> - the name of the database
21
- * {Boolean} <opts.promise(manager)> - return a promise
22
- *
23
- * @return {Connection|Promise}
24
- * @link https://www.mongodb.com/docs/drivers/node/v5.9/quick-reference/
25
- * @link https://mongodb.github.io/node-mongodb-native/ (all versions)
26
- */
27
- if (!opts) opts = {}
28
- if (!(this instanceof Manager)) return new Manager(uri, opts)
5
+ function Monastery() {
6
+ let hasDefaultManager = false
29
7
 
30
- // opts: timestamps
31
- if (typeof opts.defaultFields != 'undefined') throw Error('opts.defaultFields has been depreciated, use opts.timestamps')
32
- if (typeof opts.timestamps == 'undefined') opts.timestamps = true
33
-
34
- // opts: logLevel
35
- if (typeof opts.logLevel == 'undefined') opts.logLevel = 2
36
-
37
- // opts: separate out monastery options
38
- const mongoOpts = Object.keys(opts||{}).reduce((acc, key) => {
39
- if (
40
- ![
41
- 'databaseName', 'defaultObjects', 'logLevel', 'imagePlugin', 'limit', 'noDefaults', 'nullObjects',
42
- 'promise', 'timestamps', 'useMilliseconds',
43
- ].includes(key)) {
44
- acc[key] = opts[key]
45
- }
46
- return acc
47
- }, {})
48
-
49
- // Add properties
50
- this.uri = uri
51
- this.error = debug('monastery:error' + (opts.logLevel > 0 ? '*' : ''))
52
- this.warn = debug('monastery:warn' + (opts.logLevel > 1 ? '*' : ''))
53
- this.info = debug('monastery:info' + (opts.logLevel > 2 ? '*' : ''))
54
- this.opts = opts
55
- this.beforeModel = []
56
- this.collections = {}
57
- this._openQueue = []
58
-
59
- // If there is no DEBUG= environment variable, we will need to force the debugs on (due to bug on debug@4.3.4)
60
- if (!debug.namespaces) {
61
- if (opts.logLevel > 0) this.error.enabled = true
62
- if (opts.logLevel > 1) this.warn.enabled = true
63
- if (opts.logLevel > 2) this.info.enabled = true
64
- }
65
-
66
- // Create a new MongoDB Client
67
- if (typeof this.uri == 'string' || Array.isArray(this.uri)) {
68
- this.uri = this.connectionString(this.uri, opts.databaseName)
69
- this.client = new MongoClient(this.uri, mongoOpts)
70
- } else {
71
- this.client = this.uri
72
- }
73
-
74
- // Listen to MongoDB events
75
- for (let eventName of ['close', 'error', 'timeout']) {
76
- this.client.on(eventName, (e) => this.emit(eventName, e))
77
- }
78
-
79
- // Listen to custom open event
80
- this.on('open', () => {
81
- // wait for all the on('open') events to get called first
82
- setTimeout(() => {
83
- for (let item of this._openQueue) {
84
- item()
85
- }
86
- })
87
- })
88
-
89
- // Initiate any plugins
90
- if (opts.imagePlugin) {
91
- imagePluginFile.setup(this, util.isObject(opts.imagePlugin) ? opts.imagePlugin : {})
92
- }
93
-
94
- // Expose the last manager
95
- module.exports.manager = this
96
-
97
- // Connect to the database
98
- if (opts.promise) return this.open()
99
- else this.open()
100
- }
101
-
102
- Manager.prototype.arrayWithSchema = function(array, schema) {
103
- array.schema = schema
104
- return array
105
- }
106
-
107
- Manager.prototype.close = async function() {
108
- /**
109
- * Close the database connection, gracefully
110
- */
111
- const _close = async () => {
112
- this.emit(this._state = 'closing')
113
- await this.client.close()
114
- this.emit(this._state = 'close')
115
- }
116
- if (this._state == 'open') {
117
- return await _close()
118
-
119
- } else if (this._state == 'opening') {
120
- return new Promise((resolve) => {
121
- this._openQueue.push(() => _close().then(resolve))
122
- })
123
-
124
- } else if (this._state == 'close' || this._state == 'closing') {
125
- return
126
- }
127
- }
128
-
129
- Manager.prototype.command = function(...args) {
130
- /**
131
- * Run a raw MongoDB command
132
- */
133
- return this.db.command(...args)
134
- }
135
-
136
- Manager.prototype.connectionString = function(uri, databaseName) {
137
- /**
138
- * get the connection string
139
- * @param {string|array} uri
140
- * @param {string} <databaseName>
141
- * @return {string}
142
- */
143
- if (!uri) {
144
- throw Error('No connection URI provided.')
145
- }
146
- // uri: array, sort out the connection string
147
- if (util.isArray(uri)) {
148
- if (!databaseName) {
149
- for (var i=0, l=uri.length; i<l; i++) {
150
- if (!databaseName) databaseName = uri[i].replace(/([^\/])+\/?/, '') // eslint-disable-line
151
- uri[i] = uri[i].replace(/\/.*/, '')
152
- }
153
- }
154
- uri = uri.join(',') + '/' + databaseName
155
- }
156
- // uri: string, sort out the connection string
157
- if (typeof uri === 'string') {
158
- if (!/^mongodb(\+srv)?:\/\//.test(uri)) {
159
- uri = 'mongodb://' + uri
160
- }
161
- }
162
- return uri
163
- }
164
-
165
- Manager.prototype.get = function(name, options) {
166
- /**
167
- * Returns a MongoDB collection
168
- * @param {String} name
169
- * @param {Object} [options] - options to pass to the collection
170
- * @return {MongoDB Collection}
171
- */
172
- if (!this.collections[name] || (options||{}).cache === false) {
173
- delete (options||{}).cache
174
- this.collections[name] = new Collection(this, name, options)
175
- }
176
- return this.collections[name]
177
- }
178
-
179
- Manager.prototype.id = function(str) {
180
- return util.id(str)
181
- }
182
-
183
- Manager.prototype.isId = function(value) {
184
- return util.isId(value)
185
- }
186
-
187
- Manager.prototype.models = async function(pathname, opts) {
188
- /**
189
- * Setup model definitions from a folder location
190
- * @param {string} pathname
191
- * @param {object} opts:
192
- * @param {boolean} opts.waitForIndexes - Wait for indexes to be created?
193
- * @return {Promise(object)} - e.g. { user: , article: , .. }
194
- * @this Manager
195
- */
196
- let out = {}
197
- let { waitForIndexes } = opts || {}
198
- if (!pathname || typeof pathname !== 'string') {
199
- throw 'The path must be a valid pathname'
200
- }
201
- let filenames = fs.readdirSync(pathname)
202
- for (let filename of filenames) {
203
- // Ignore folders
204
- if (!filename.match(/\.[cm]?js$/)) continue
205
- let name = filename.replace('.js', '')
206
- let filepath = path.join(pathname, filename)
207
- // We can't use require() here since we need to be able to import both CJS and ES6 modules
208
- let definition = ((await import(filepath))||{}).default
209
- // When a commonJS project uses babel (e.g. `nodemon -r @babel/register`, similar to `-r esm`), import()
210
- // will return ES6 model definitions in another nested `default` object
211
- if (definition && definition.default) definition = definition.default
212
- if (!definition) throw new Error(`The model definition for '${name}' must export a default object`)
213
- // Wait for indexes to be created?
214
- if (waitForIndexes) out[name] = await this.model(name, { ...definition, waitForIndexes })
215
- else out[name] = this.model(name, definition)
216
- }
217
- return out
218
- }
219
-
220
- Manager.prototype.onError = function(fn) {
221
- /**
222
- * Called when an error occurs when trying to connect to the MongoClient.
223
- * @param {Function} fn
224
- * @return {Promise}
225
- */
226
- return new Promise((resolve, reject) => {
227
- this.on('error', (err) => {
228
- resolve(err)
229
- })
230
- }).then((err) => {
231
- return fn(err)
232
- })
233
- }
234
-
235
- Manager.prototype.onOpen = function(fn) {
236
- /**
237
- * Called when a successful MongoClient connection has been made.
238
- * @param {Function} fn
239
- * @return {Promise(manager)}
240
- */
241
- return new Promise((resolve, reject) => {
242
- if (this._state == 'open') {
243
- resolve(this)
244
- }
245
- this.on('open', () => {
246
- resolve(this)
247
- })
248
- this.on('error', (err) => {
249
- reject(err)
250
- })
251
- }).then((err) => { // If `then` is not chained, the error will be thrown, detached!
252
- return fn(err)
253
- })
254
- }
255
-
256
- Manager.prototype.open = async function() {
257
- /**
258
- * Connect to the database
259
- * @return {Promise(manager)}
260
- */
261
- try {
262
- this._state = 'opening'
263
- this.db = this.client.db()
264
- await this.client.connect() // now optional since db().command() will auto-connect
265
- this.emit(this._state = 'open', this)
266
- return this
267
-
268
- } catch (err) {
269
- this._state = 'closed'
270
- // Only emit the error if there are listeners, since it will throw an unhandled error
271
- if (this.listeners('error').length > 0) {
272
- this.emit('error', err)
273
- }
274
- if (this.opts.promise || this.listeners('error').length == 0) {
275
- throw new Error(err)
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)
276
14
  }
15
+ return manager
277
16
  }
278
17
  }
279
18
 
280
- Manager.prototype.parseData = function(obj, parseBracketToDotNotation, parseDotNotation) {
281
- return util.parseData(obj, parseBracketToDotNotation, parseDotNotation)
282
- }
19
+ // Inherit Manager prototypes onto Monastery
20
+ inherits(Monastery, Manager)
283
21
 
284
- Manager.prototype.model = Model
285
- Manager.prototype.getSignedUrl = imagePluginFile.getSignedUrl
286
- Manager.prototype._getSignedUrl = () => {
287
- throw new Error('monastery._getSignedUrl() has been moved to monastery.getSignedUrl()')
288
- }
289
-
290
- inherits(Manager, EventEmitter)
291
- module.exports = Manager
22
+ // Exports
23
+ module.exports = new Monastery()
24
+ module.exports.arrayWithSchema = Manager.prototype.arrayWithSchema
25
+ module.exports.getSignedUrl = Manager.prototype.getSignedUrl
292
26
  module.exports.id = Manager.prototype.id
293
27
  module.exports.isId = Manager.prototype.isId
294
- module.exports.arrayWithSchema = Manager.prototype.arrayWithSchema
295
- module.exports.parseData = Manager.prototype.parseData
296
28
  module.exports.parseBracketNotation = Manager.prototype.parseBracketNotation
297
29
  module.exports.parseBracketToDotNotation = Manager.prototype.parseBracketToDotNotation
30
+ module.exports.parseData = Manager.prototype.parseData
298
31
  module.exports.parseDotNotation = Manager.prototype.parseDotNotation
299
- module.exports.getSignedUrl = Manager.prototype.getSignedUrl
300
- module.exports.manager = null
301
32
  module.exports.rules = rules