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 +4 -0
- package/docs/definition/index.md +1 -1
- package/docs/manager/index.md +9 -8
- package/docs/manager/model.md +3 -1
- package/docs/manager/models.md +2 -2
- package/docs/readme.md +3 -2
- package/lib/index.js +17 -286
- package/lib/manager.js +289 -0
- package/lib/model.js +32 -31
- package/package.json +1 -1
- package/test/blacklisting.js +1 -1
- package/test/collection.js +1 -1
- package/test/comparison.js +2 -2
- package/test/crud.js +14 -13
- package/test/manager.js +17 -17
- package/test/model.js +7 -10
- package/test/plugin-images.js +23 -24
- package/test/populate.js +1 -1
- package/test/util.js +3 -3
- package/test/validate.js +5 -5
- package/test/virtuals.js +1 -1
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)
|
package/docs/definition/index.md
CHANGED
|
@@ -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
|
|
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
|
{
|
package/docs/manager/index.md
CHANGED
|
@@ -6,7 +6,7 @@ has_children: true
|
|
|
6
6
|
|
|
7
7
|
# Manager
|
|
8
8
|
|
|
9
|
-
|
|
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
|
|
32
|
+
import db from 'monastery'
|
|
33
33
|
|
|
34
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
44
|
+
manager.onOpen((manager) => {
|
|
44
45
|
// manager.client is connected...
|
|
45
46
|
})
|
|
46
|
-
|
|
47
|
+
manager.onError((err) => {
|
|
47
48
|
// connection error
|
|
48
49
|
})
|
|
49
50
|
```
|
package/docs/manager/model.md
CHANGED
|
@@ -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
|
-
|
|
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
|
|
package/docs/manager/models.md
CHANGED
|
@@ -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)*:
|
|
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 +
|
|
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
|
|
38
|
+
import db from 'monastery'
|
|
39
39
|
|
|
40
40
|
// Initialize a monastery manager
|
|
41
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
19
|
+
// Inherit Manager prototypes onto Monastery
|
|
20
|
+
inherits(Monastery, Manager)
|
|
283
21
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|