monastery 2.2.3 → 3.0.1

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/index.js CHANGED
@@ -1,86 +1,191 @@
1
- let fs = require('fs')
2
- let debug = require('debug')
3
- let monk = require('monk')
4
- let path = require('path')
5
- let rules = require('./rules.js')
6
- let util = require('./util.js')
7
-
8
- // Apply monk monkey patches
9
- monk.manager.prototype.open = require('./monk-monkey-patches.js').open
10
- monk.manager.prototype.then = require('./monk-monkey-patches.js').then
11
- monk.Collection.prototype.findOneAndUpdate = require('./monk-monkey-patches.js').findOneAndUpdate
12
-
13
- module.exports = function(uri, opts, fn) {
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
6
+ 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
+ const rules = require('./rules.js')
12
+ const util = require('./util.js')
13
+
14
+ function Manager(uri, opts) {
14
15
  /**
15
- * Constructs and augments a new monk manager
16
- * @param {String|Array|Manager|false} uri:
17
- * {string|array} - monk connection URI string / replica sets (see monk)
18
- * {object} - reuse a monk manager instance
19
- * {false} - no open database connection, used in testing
20
- * @param {object} opts
21
- * @return monk manager
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)
22
26
  */
23
27
  if (!opts) opts = {}
24
- let monasteryOpts = [
25
- 'defaultObjects', 'hideWarnings', 'hideErrors', 'imagePlugin', 'limit', 'nullObjects',
26
- 'timestamps', 'useMilliseconds',
27
- ]
28
-
29
- // Note: Debug doesn't allow debuggers to be enabled by default
30
- let info = debug('monastery:info')
31
- let warn = debug('monastery:warn' + (opts.hideWarnings ? '' : '*'))
32
- let error = debug('monastery:error' + (opts.hideErrors ? '' : '*'))
33
-
34
- if (util.isDefined(opts.defaultFields)) {
35
- warn('opts.defaultFields has been depreciated in favour of opts.timestamps')
36
- opts.timestamps = opts.defaultFields
37
- delete opts.defaultFields
38
- }
39
- if (!util.isDefined(opts.timestamps)) {
40
- opts.timestamps = true
28
+ if (!(this instanceof Manager)) return new Manager(uri, opts)
29
+
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: separate out monastery options
35
+ const mongoOpts = Object.keys(opts||{}).reduce((acc, key) => {
36
+ if (
37
+ ![
38
+ 'databaseName', 'defaultObjects', 'hideWarnings', 'hideErrors', 'imagePlugin', 'limit', 'nullObjects',
39
+ 'promise', 'timestamps', 'useMilliseconds',
40
+ ].includes(key)) {
41
+ acc[key] = opts[key]
42
+ }
43
+ return acc
44
+ }, {})
45
+
46
+ // Add properties
47
+ this.info = debug('monastery:info') // debug doesn't allow debuggers to be enabled by default
48
+ this.warn = debug('monastery:warn' + (opts.hideWarnings ? '' : '*'))
49
+ this.error = debug('monastery:error' + (opts.hideErrors ? '' : '*'))
50
+ this.opts = opts
51
+ this.beforeModel = []
52
+ this.collections = {}
53
+ this.models = {}
54
+ this._openQueue = []
55
+
56
+ // Create a new MongoDB Client
57
+ if (typeof uri == 'string' || Array.isArray(uri)) {
58
+ uri = this.connectionString(uri, opts.databaseName)
59
+ this.client = new MongoClient(uri, mongoOpts)
60
+ } else {
61
+ this.client = uri
41
62
  }
42
63
 
43
- // Monk manager instance or manager mock
44
- // Monk manager instances have manager._db defined which is the raw mongodb connection
45
- if (typeof uri === 'object') var manager = uri
46
- else if (uri) manager = monk(uri, { useUnifiedTopology: true, ...util.omit(opts, monasteryOpts) }, fn)
47
- else manager = { id: monk.id }
48
-
49
- // Add monastery properties
50
- manager.arrayWithSchema = arrayWithSchema
51
- manager.beforeModel = []
52
- manager.imagePluginFile = require('../plugins/images/index.js')
53
- manager.isId = util.isId.bind(util)
54
- manager.model = require('./model.js')
55
- manager.models = models
56
- manager.parseData = util.parseData.bind(util)
57
- manager.info = info
58
- manager.warn = warn
59
- manager.error = error
60
-
61
- // Add opts onto manager
62
- for (let key of monasteryOpts) {
63
- manager[key] = opts[key]
64
+ // Listen to MongoDB events
65
+ for (let eventName of ['close', 'error', 'timeout']) {
66
+ this.client.on(eventName, (e) => this.emit(eventName, e))
64
67
  }
65
68
 
69
+ // Listen to custom open event
70
+ this.on('open', () => {
71
+ // wait for all the on('open') events to get called first
72
+ setTimeout(() => {
73
+ for (let item of this._openQueue) {
74
+ item()
75
+ }
76
+ })
77
+ })
78
+
66
79
  // Initiate any plugins
67
- if (manager.imagePlugin) {
68
- manager.imagePluginFile.setup(manager, util.isObject(manager.imagePlugin)? manager.imagePlugin : {})
80
+ if (opts.imagePlugin) {
81
+ imagePluginFile.setup(this, util.isObject(opts.imagePlugin) ? opts.imagePlugin : {})
69
82
  }
70
83
 
71
- // Catch mongodb connectivity errors
72
- if (uri) manager.catch(err => manager.error(err))
73
- return manager
74
- }
84
+ // Expose the last manager
85
+ module.exports.manager = this
75
86
 
76
- module.exports.rules = rules
87
+ // Connect to the database
88
+ if (opts.promise) return this.open()
89
+ else this.open()
90
+ }
77
91
 
78
- let arrayWithSchema = function(array, schema) {
92
+ Manager.prototype.arrayWithSchema = function(array, schema) {
79
93
  array.schema = schema
80
94
  return array
81
95
  }
82
96
 
83
- let models = async function(pathname, waitForIndexes) {
97
+ Manager.prototype.catch = function(fn) {
98
+ /**
99
+ * Catch any errors that occur during the opening of the database, and still return the manager
100
+ * @param {Function} fn
101
+ * @return {Manager}
102
+ */
103
+ this.catching = true
104
+ this.on('error', fn)
105
+ return this
106
+ }
107
+
108
+ Manager.prototype.close = async function() {
109
+ /**
110
+ * Close the database connection, gracefully
111
+ */
112
+ const _close = async () => {
113
+ this.emit(this._state = 'closing')
114
+ await this.client.close()
115
+ this.emit(this._state = 'close')
116
+ }
117
+ if (this._state == 'open') {
118
+ return await _close()
119
+
120
+ } else if (this._state == 'opening') {
121
+ return new Promise((resolve) => {
122
+ this._openQueue.push(() => _close().then(resolve))
123
+ })
124
+
125
+ } else if (this._state == 'close' || this._state == 'closing') {
126
+ return
127
+ }
128
+ }
129
+
130
+ Manager.prototype.command = function(...args) {
131
+ /**
132
+ * Run a raw MongoDB command
133
+ */
134
+ return this.db.command(...args)
135
+ }
136
+
137
+ Manager.prototype.connectionString = function(uri, databaseName) {
138
+ /**
139
+ * get the connection string
140
+ * @param {string|array} uri
141
+ * @param {string} <databaseName>
142
+ * @return {string}
143
+ */
144
+ if (!uri) {
145
+ throw Error('No connection URI provided.')
146
+ }
147
+ // uri: array, sort out the connection string
148
+ if (util.isArray(uri)) {
149
+ if (!databaseName) {
150
+ for (var i=0, l=uri.length; i<l; i++) {
151
+ if (!databaseName) databaseName = uri[i].replace(/([^\/])+\/?/, '') // eslint-disable-line
152
+ uri[i] = uri[i].replace(/\/.*/, '')
153
+ }
154
+ }
155
+ uri = uri.join(',') + '/' + databaseName
156
+ }
157
+ // uri: string, sort out the connection string
158
+ if (typeof uri === 'string') {
159
+ if (!/^mongodb(\+srv)?:\/\//.test(uri)) {
160
+ uri = 'mongodb://' + uri
161
+ }
162
+ }
163
+ return uri
164
+ }
165
+
166
+ Manager.prototype.get = function(name, options) {
167
+ /**
168
+ * Returns a MongoDB collection
169
+ * @param {String} name
170
+ * @param {Object} [options] - options to pass to the collection
171
+ * @return {MongoDB Collection}
172
+ */
173
+ if (!this.collections[name] || options?.cache === false) {
174
+ delete options?.cache
175
+ this.collections[name] = new Collection(this, name, options)
176
+ }
177
+ return this.collections[name]
178
+ }
179
+
180
+ Manager.prototype.id = function(str) {
181
+ return util.id(str)
182
+ }
183
+
184
+ Manager.prototype.isId = function(value) {
185
+ return util.isId(value)
186
+ }
187
+
188
+ Manager.prototype.models = async function(pathname, waitForIndexes) {
84
189
  /**
85
190
  * Setup model definitions from a folder location
86
191
  * @param {string} pathname
@@ -109,3 +214,38 @@ let models = async function(pathname, waitForIndexes) {
109
214
  }
110
215
  return out
111
216
  }
217
+
218
+ Manager.prototype.open = async function() {
219
+ /**
220
+ * Connect to the database
221
+ * @return {Promise}
222
+ */
223
+ try {
224
+ this._state = 'opening'
225
+ this.db = this.client.db()
226
+ await this.client.connect() // now optional since db().command() will auto-connect
227
+ this.emit(this._state = 'open', this)
228
+ return this
229
+
230
+ } catch (err) {
231
+ this._state = 'closed'
232
+ // Only emit the error if there are listeners, since it will throw an unhandled error
233
+ if (this.listeners('error').length > 0) {
234
+ this.emit('error', err)
235
+ }
236
+ if (!this.catching) {
237
+ throw err
238
+ }
239
+ }
240
+ }
241
+
242
+ Manager.prototype.parseData = function(obj) {
243
+ return util.parseData(obj)
244
+ }
245
+
246
+ Manager.prototype.model = Model
247
+
248
+ inherits(Manager, EventEmitter)
249
+ module.exports = Manager
250
+ module.exports.manager = null
251
+ module.exports.rules = rules