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/.eslintrc.json +10 -1
- package/changelog.md +3 -1
- package/docs/_config.yml +2 -2
- package/docs/assets/imgs/monastery.jpg +0 -0
- package/docs/definition/index.md +1 -2
- package/docs/manager/index.md +19 -11
- package/docs/manager/model.md +1 -1
- package/docs/manager/models.md +2 -3
- package/docs/model/count.md +25 -0
- package/docs/model/find.md +5 -9
- package/docs/model/findOne.md +1 -1
- package/docs/model/findOneAndUpdate.md +1 -1
- package/docs/model/index.md +5 -30
- package/docs/model/insert.md +4 -6
- package/docs/model/rawMethods.md +290 -0
- package/docs/model/remove.md +4 -6
- package/docs/model/update.md +4 -6
- package/docs/readme.md +69 -48
- package/lib/collection.js +324 -0
- package/lib/index.js +207 -67
- package/lib/model-crud.js +605 -619
- package/lib/model-validate.js +227 -245
- package/lib/model.js +70 -91
- package/lib/rules.js +36 -35
- package/lib/util.js +69 -15
- package/package.json +12 -11
- package/plugins/images/index.js +11 -11
- package/test/blacklisting.js +506 -537
- package/test/collection.js +445 -0
- package/test/crud.js +810 -730
- package/test/index.test.js +26 -0
- package/test/manager.js +77 -0
- package/test/mock/blacklisting.js +23 -23
- package/test/model.js +611 -572
- package/test/plugin-images.js +880 -965
- package/test/populate.js +249 -262
- package/test/util.js +126 -45
- package/test/validate.js +1074 -1121
- package/test/virtuals.js +222 -227
- package/lib/monk-monkey-patches.js +0 -90
- package/test/monk.js +0 -53
- package/test/test.js +0 -38
package/lib/index.js
CHANGED
|
@@ -1,86 +1,191 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* {
|
|
20
|
-
*
|
|
21
|
-
*
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
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 (
|
|
68
|
-
|
|
80
|
+
if (opts.imagePlugin) {
|
|
81
|
+
imagePluginFile.setup(this, util.isObject(opts.imagePlugin) ? opts.imagePlugin : {})
|
|
69
82
|
}
|
|
70
83
|
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
return manager
|
|
74
|
-
}
|
|
84
|
+
// Expose the last manager
|
|
85
|
+
module.exports.manager = this
|
|
75
86
|
|
|
76
|
-
|
|
87
|
+
// Connect to the database
|
|
88
|
+
if (opts.promise) return this.open()
|
|
89
|
+
else this.open()
|
|
90
|
+
}
|
|
77
91
|
|
|
78
|
-
|
|
92
|
+
Manager.prototype.arrayWithSchema = function(array, schema) {
|
|
79
93
|
array.schema = schema
|
|
80
94
|
return array
|
|
81
95
|
}
|
|
82
96
|
|
|
83
|
-
|
|
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
|