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/lib/manager.js
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const debug = require('debug')
|
|
3
|
+
const path = require('path')
|
|
4
|
+
const EventEmitter = require('events').EventEmitter
|
|
5
|
+
const inherits = require('util').inherits
|
|
6
|
+
const { MongoClient } = require('mongodb')
|
|
7
|
+
const Collection = require('./collection.js')
|
|
8
|
+
const imagePluginFile = require('../plugins/images/index.js')
|
|
9
|
+
const Model = require('./model.js')
|
|
10
|
+
const util = require('./util.js')
|
|
11
|
+
|
|
12
|
+
function Manager(uri, opts) {
|
|
13
|
+
/**
|
|
14
|
+
* Constructs a new manager which contains a new MongoDB client
|
|
15
|
+
*
|
|
16
|
+
* @param {String|Array|Object} uri - MongoDB connection URI string / replica set, or reuse a MongoClient instance
|
|
17
|
+
* @param {Object} opts -
|
|
18
|
+
* {String} <opts.databaseName> - the name of the database
|
|
19
|
+
* {Boolean} <opts.promise(manager)> - return a promise
|
|
20
|
+
*
|
|
21
|
+
* @return {Connection|Promise}
|
|
22
|
+
* @link https://www.mongodb.com/docs/drivers/node/v5.9/quick-reference/
|
|
23
|
+
* @link https://mongodb.github.io/node-mongodb-native/ (all versions)
|
|
24
|
+
*/
|
|
25
|
+
if (!opts) opts = {}
|
|
26
|
+
if (!(this instanceof Manager)) return new Manager(uri, opts)
|
|
27
|
+
|
|
28
|
+
// opts: timestamps
|
|
29
|
+
if (typeof opts.defaultFields != 'undefined') throw Error('opts.defaultFields has been depreciated, use opts.timestamps')
|
|
30
|
+
if (typeof opts.timestamps == 'undefined') opts.timestamps = true
|
|
31
|
+
|
|
32
|
+
// opts: logLevel
|
|
33
|
+
if (typeof opts.logLevel == 'undefined') opts.logLevel = 2
|
|
34
|
+
|
|
35
|
+
// opts: separate out monastery options
|
|
36
|
+
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]
|
|
43
|
+
}
|
|
44
|
+
return acc
|
|
45
|
+
}, {})
|
|
46
|
+
|
|
47
|
+
// 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 = []
|
|
56
|
+
|
|
57
|
+
// If there is no DEBUG= environment variable, we will need to force the debugs on (due to bug on debug@4.3.4)
|
|
58
|
+
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
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 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)
|
|
68
|
+
} else {
|
|
69
|
+
this.client = this.uri
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Listen to MongoDB events
|
|
73
|
+
for (let eventName of ['close', 'error', 'timeout']) {
|
|
74
|
+
this.client.on(eventName, (e) => this.emit(eventName, e))
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Listen to custom open event
|
|
78
|
+
this.on('open', () => {
|
|
79
|
+
// wait for all the on('open') events to get called first
|
|
80
|
+
setTimeout(() => {
|
|
81
|
+
for (let item of this._openQueue) {
|
|
82
|
+
item()
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
// Initiate any plugins
|
|
88
|
+
if (opts.imagePlugin) {
|
|
89
|
+
imagePluginFile.setup(this, util.isObject(opts.imagePlugin) ? opts.imagePlugin : {})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Connect to the database
|
|
93
|
+
if (opts.promise) return this.open()
|
|
94
|
+
else this.open()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
Manager.prototype.arrayWithSchema = function(array, schema) {
|
|
98
|
+
array.schema = schema
|
|
99
|
+
return array
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
Manager.prototype.close = async function() {
|
|
103
|
+
/**
|
|
104
|
+
* Close the database connection, gracefully
|
|
105
|
+
*/
|
|
106
|
+
const _close = async () => {
|
|
107
|
+
this.emit(this._state = 'closing')
|
|
108
|
+
await this.client.close()
|
|
109
|
+
this.emit(this._state = 'close')
|
|
110
|
+
}
|
|
111
|
+
if (this._state == 'open') {
|
|
112
|
+
return await _close()
|
|
113
|
+
|
|
114
|
+
} else if (this._state == 'opening') {
|
|
115
|
+
return new Promise((resolve) => {
|
|
116
|
+
this._openQueue.push(() => _close().then(resolve))
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
} else if (this._state == 'close' || this._state == 'closing') {
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
Manager.prototype.command = function(...args) {
|
|
125
|
+
/**
|
|
126
|
+
* Run a raw MongoDB command
|
|
127
|
+
*/
|
|
128
|
+
return this.db.command(...args)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
Manager.prototype.connectionString = function(uri, databaseName) {
|
|
132
|
+
/**
|
|
133
|
+
* get the connection string
|
|
134
|
+
* @param {string|array} uri
|
|
135
|
+
* @param {string} <databaseName>
|
|
136
|
+
* @return {string}
|
|
137
|
+
*/
|
|
138
|
+
if (!uri) {
|
|
139
|
+
throw Error('No connection URI provided.')
|
|
140
|
+
}
|
|
141
|
+
// uri: array, sort out the connection string
|
|
142
|
+
if (util.isArray(uri)) {
|
|
143
|
+
if (!databaseName) {
|
|
144
|
+
for (var i=0, l=uri.length; i<l; i++) {
|
|
145
|
+
if (!databaseName) databaseName = uri[i].replace(/([^\/])+\/?/, '') // eslint-disable-line
|
|
146
|
+
uri[i] = uri[i].replace(/\/.*/, '')
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
uri = uri.join(',') + '/' + databaseName
|
|
150
|
+
}
|
|
151
|
+
// uri: string, sort out the connection string
|
|
152
|
+
if (typeof uri === 'string') {
|
|
153
|
+
if (!/^mongodb(\+srv)?:\/\//.test(uri)) {
|
|
154
|
+
uri = 'mongodb://' + uri
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return uri
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
Manager.prototype.get = function(name, options) {
|
|
161
|
+
/**
|
|
162
|
+
* Returns a MongoDB collection
|
|
163
|
+
* @param {String} name
|
|
164
|
+
* @param {Object} [options] - options to pass to the collection
|
|
165
|
+
* @return {MongoDB Collection}
|
|
166
|
+
*/
|
|
167
|
+
if (!this.collections[name] || (options||{}).cache === false) {
|
|
168
|
+
delete (options||{}).cache
|
|
169
|
+
this.collections[name] = new Collection(this, name, options)
|
|
170
|
+
}
|
|
171
|
+
return this.collections[name]
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
Manager.prototype.id = function(str) {
|
|
175
|
+
return util.id(str)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
Manager.prototype.isId = function(value) {
|
|
179
|
+
return util.isId(value)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
Manager.prototype.models = async function(pathname, opts) {
|
|
183
|
+
/**
|
|
184
|
+
* Setup model definitions from a folder location
|
|
185
|
+
* @param {string} pathname
|
|
186
|
+
* @param {object} opts:
|
|
187
|
+
* @param {boolean} opts.waitForIndexes - Wait for indexes to be created?
|
|
188
|
+
* @return {Promise(object)} - e.g. { user: , article: , .. }
|
|
189
|
+
* @this Manager
|
|
190
|
+
*/
|
|
191
|
+
let out = {}
|
|
192
|
+
let { waitForIndexes } = opts || {}
|
|
193
|
+
if (!pathname || typeof pathname !== 'string') {
|
|
194
|
+
throw 'The path must be a valid pathname'
|
|
195
|
+
}
|
|
196
|
+
let filenames = fs.readdirSync(pathname)
|
|
197
|
+
for (let filename of filenames) {
|
|
198
|
+
// Ignore folders
|
|
199
|
+
if (!filename.match(/\.[cm]?js$/)) continue
|
|
200
|
+
let name = filename.replace('.js', '')
|
|
201
|
+
let filepath = path.join(pathname, filename)
|
|
202
|
+
// We can't use require() here since we need to be able to import both CJS and ES6 modules
|
|
203
|
+
let definition = ((await import(filepath))||{}).default
|
|
204
|
+
// When a commonJS project uses babel (e.g. `nodemon -r @babel/register`, similar to `-r esm`), import()
|
|
205
|
+
// will return ES6 model definitions in another nested `default` object
|
|
206
|
+
if (definition && definition.default) definition = definition.default
|
|
207
|
+
if (!definition) throw new Error(`The model definition for '${name}' must export a default object`)
|
|
208
|
+
// Wait for indexes to be created?
|
|
209
|
+
if (waitForIndexes) out[name] = await this.model(name, definition, waitForIndexes)
|
|
210
|
+
else out[name] = this.model(name, definition)
|
|
211
|
+
}
|
|
212
|
+
return out
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
Manager.prototype.onError = function(fn) {
|
|
216
|
+
/**
|
|
217
|
+
* Called when an error occurs when trying to connect to the MongoClient.
|
|
218
|
+
* @param {Function} fn
|
|
219
|
+
* @return {Promise}
|
|
220
|
+
*/
|
|
221
|
+
return new Promise((resolve, reject) => {
|
|
222
|
+
this.on('error', (err) => {
|
|
223
|
+
resolve(err)
|
|
224
|
+
})
|
|
225
|
+
}).then((err) => {
|
|
226
|
+
return fn(err)
|
|
227
|
+
})
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
Manager.prototype.onOpen = function(fn) {
|
|
231
|
+
/**
|
|
232
|
+
* Called when a successful MongoClient connection has been made.
|
|
233
|
+
* @param {Function} fn
|
|
234
|
+
* @return {Promise(manager)}
|
|
235
|
+
*/
|
|
236
|
+
return new Promise((resolve, reject) => {
|
|
237
|
+
if (this._state == 'open') {
|
|
238
|
+
resolve(this)
|
|
239
|
+
}
|
|
240
|
+
this.on('open', () => {
|
|
241
|
+
resolve(this)
|
|
242
|
+
})
|
|
243
|
+
this.on('error', (err) => {
|
|
244
|
+
reject(err)
|
|
245
|
+
})
|
|
246
|
+
}).then((err) => { // If `then` is not chained, the error will be thrown, detached!
|
|
247
|
+
return fn(err)
|
|
248
|
+
})
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
Manager.prototype.open = async function() {
|
|
252
|
+
/**
|
|
253
|
+
* Connect to the database
|
|
254
|
+
* @return {Promise(manager)}
|
|
255
|
+
*/
|
|
256
|
+
try {
|
|
257
|
+
this._state = 'opening'
|
|
258
|
+
this.db = this.client.db()
|
|
259
|
+
await this.client.connect() // now optional since db().command() will auto-connect
|
|
260
|
+
this.emit(this._state = 'open', this)
|
|
261
|
+
return this
|
|
262
|
+
|
|
263
|
+
} catch (err) {
|
|
264
|
+
this._state = 'closed'
|
|
265
|
+
// 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)
|
|
268
|
+
}
|
|
269
|
+
if (this.opts.promise || this.listeners('error').length == 0) {
|
|
270
|
+
throw new Error(err)
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
Manager.prototype.parseData = function(obj, parseBracketToDotNotation, parseDotNotation) {
|
|
276
|
+
return util.parseData(obj, parseBracketToDotNotation, parseDotNotation)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
Manager.prototype.model = Model
|
|
280
|
+
|
|
281
|
+
Manager.prototype.getSignedUrl = imagePluginFile.getSignedUrl
|
|
282
|
+
|
|
283
|
+
Manager.prototype._getSignedUrl = () => {
|
|
284
|
+
throw new Error('monastery._getSignedUrl() has been moved to monastery.getSignedUrl()')
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
inherits(Manager, EventEmitter)
|
|
288
|
+
|
|
289
|
+
module.exports = Manager
|
package/lib/model.js
CHANGED
|
@@ -1,58 +1,59 @@
|
|
|
1
1
|
const rules = require('./rules.js')
|
|
2
2
|
const util = require('./util.js')
|
|
3
3
|
|
|
4
|
-
function Model(name,
|
|
4
|
+
function Model(name, definition, waitForIndexes, manager) {
|
|
5
5
|
/**
|
|
6
6
|
* Setup a model
|
|
7
7
|
* @param {string} name
|
|
8
|
-
* @param {object}
|
|
9
|
-
* @param {boolean}
|
|
10
|
-
*
|
|
11
|
-
* @
|
|
8
|
+
* @param {object} definition - model definition
|
|
9
|
+
* @param {boolean} waitForIndexes - wait for indexes to be created before returning (returns a promise)
|
|
10
|
+
*
|
|
11
|
+
* @return model or Promise(model)
|
|
12
|
+
* @this manager
|
|
12
13
|
*/
|
|
13
14
|
if (!(this instanceof Model)) {
|
|
14
|
-
return new Model(name,
|
|
15
|
+
return new Model(name, definition, waitForIndexes, this)
|
|
15
16
|
} else if (!name) {
|
|
16
17
|
throw 'No model name defined'
|
|
17
18
|
} else if (name.match(/^_/)) {
|
|
18
19
|
throw 'Model names cannot start with an underscore'
|
|
19
|
-
} else if (!
|
|
20
|
+
} else if (!definition) {
|
|
20
21
|
throw `No model definition passed for "${name}"`
|
|
21
|
-
} else if (!
|
|
22
|
+
} else if (!definition.fields) {
|
|
22
23
|
throw `We couldn't find ${name}.fields in the model definition, the model maybe setup `
|
|
23
|
-
+ `or exported incorrectly:\n${JSON.stringify(
|
|
24
|
-
} else if (!util.isSubdocument(
|
|
24
|
+
+ `or exported incorrectly:\n${JSON.stringify(definition, null, 2)}`
|
|
25
|
+
} else if (!util.isSubdocument(definition.fields) && definition.fields.type == 'any') {
|
|
25
26
|
throw `Instead of using { type: 'any' } for ${name}.fields, please use the new 'strict' definition rule` +
|
|
26
27
|
', e.g. { schema: { strict: false }}'
|
|
27
|
-
} else if (!util.isSubdocument(
|
|
28
|
+
} else if (!util.isSubdocument(definition.fields) && !util.isEmpty(definition.fields)) {
|
|
28
29
|
throw `The ${name}.fields object should be a valid document, e.g. { name: { type: 'string' }}`
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
// Add schema options
|
|
32
33
|
Object.assign(this, {
|
|
33
|
-
...(
|
|
34
|
-
afterFind:
|
|
35
|
-
afterInsert: (
|
|
36
|
-
afterUpdate: (
|
|
37
|
-
afterRemove:
|
|
38
|
-
beforeInsert: (
|
|
39
|
-
beforeUpdate: (
|
|
40
|
-
beforeRemove:
|
|
41
|
-
beforeValidate:
|
|
34
|
+
...(definition.methods || {}),
|
|
35
|
+
afterFind: definition.afterFind || [],
|
|
36
|
+
afterInsert: (definition.afterInsert || []).concat(definition.afterInsertUpdate || []),
|
|
37
|
+
afterUpdate: (definition.afterUpdate || []).concat(definition.afterInsertUpdate || []),
|
|
38
|
+
afterRemove: definition.afterRemove || [],
|
|
39
|
+
beforeInsert: (definition.beforeInsert || []).concat(definition.beforeInsertUpdate || []),
|
|
40
|
+
beforeUpdate: (definition.beforeUpdate || []).concat(definition.beforeInsertUpdate || []),
|
|
41
|
+
beforeRemove: definition.beforeRemove || [],
|
|
42
|
+
beforeValidate: definition.beforeValidate || [],
|
|
42
43
|
error: manager.error,
|
|
43
44
|
info: manager.info,
|
|
44
45
|
warn: manager.warn,
|
|
45
|
-
insertBL:
|
|
46
|
-
? !
|
|
47
|
-
|
|
48
|
-
fields: { ...(util.deepCopy(
|
|
49
|
-
findBL:
|
|
46
|
+
insertBL: !definition.insertBL
|
|
47
|
+
? ['_id'] : !definition.insertBL.includes('_id') && !definition.insertBL.includes('-_id')
|
|
48
|
+
? ['_id'].concat(definition.insertBL) : definition.insertBL,
|
|
49
|
+
fields: { ...(util.deepCopy(definition.fields) || {}) },
|
|
50
|
+
findBL: definition.findBL || ['password'], // todo: password should be removed
|
|
50
51
|
manager: manager,
|
|
51
|
-
messages:
|
|
52
|
-
messagesLen: Object.keys(
|
|
52
|
+
messages: definition.messages || {},
|
|
53
|
+
messagesLen: Object.keys(definition.messages || {}).length > 0,
|
|
53
54
|
name: name,
|
|
54
|
-
rules: { ...(
|
|
55
|
-
updateBL:
|
|
55
|
+
rules: { ...(definition.rules || {}) },
|
|
56
|
+
updateBL: definition.updateBL || [],
|
|
56
57
|
})
|
|
57
58
|
|
|
58
59
|
// Sort messages by specifity first, then we can just return the first match
|
|
@@ -140,7 +141,7 @@ function Model(name, opts, manager) {
|
|
|
140
141
|
if (err.type == 'info') this.info(err.detail)
|
|
141
142
|
else this.error(err)
|
|
142
143
|
}
|
|
143
|
-
if (
|
|
144
|
+
if (waitForIndexes) return this._setupIndexes().catch(errHandler).then(() => this)
|
|
144
145
|
else this._setupIndexes().catch(errHandler) // returns this
|
|
145
146
|
}
|
|
146
147
|
|
|
@@ -446,4 +447,4 @@ module.exports = Model
|
|
|
446
447
|
|
|
447
448
|
// Extend Model prototype
|
|
448
449
|
require('./model-crud.js')
|
|
449
|
-
require('./model-validate.js')
|
|
450
|
+
require('./model-validate.js')
|
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
|
+
"version": "3.5.0",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": "github:boycce/monastery",
|
|
8
8
|
"homepage": "https://boycce.github.io/monastery/",
|
package/test/blacklisting.js
CHANGED
|
@@ -4,7 +4,7 @@ const bird = require('./mock/blacklisting.js').bird
|
|
|
4
4
|
const user = require('./mock/blacklisting.js').user
|
|
5
5
|
|
|
6
6
|
let db
|
|
7
|
-
beforeAll(async () => { db = monastery('127.0.0.1/monastery', { timestamps: false }) })
|
|
7
|
+
beforeAll(async () => { db = monastery.manager('127.0.0.1/monastery', { timestamps: false }) })
|
|
8
8
|
afterAll(async () => { db.close() })
|
|
9
9
|
|
|
10
10
|
test('find blacklisting basic', async () => {
|
package/test/collection.js
CHANGED
|
@@ -5,7 +5,7 @@ const util = require('../lib/util.js')
|
|
|
5
5
|
// Setup/destroy
|
|
6
6
|
let db, userCol, indexCol, indexDropCol
|
|
7
7
|
beforeAll(async () => {
|
|
8
|
-
db = monastery('127.0.0.1/monastery')
|
|
8
|
+
db = monastery.manager('127.0.0.1/monastery')
|
|
9
9
|
userCol = db.get('users-' + Date.now())
|
|
10
10
|
indexCol = db.get('index-' + Date.now())
|
|
11
11
|
indexDropCol = db.get('index-' + Date.now())
|
package/test/comparison.js
CHANGED
|
@@ -2,7 +2,7 @@ const monastery = require('../lib/index.js')
|
|
|
2
2
|
const mongoose = require('mongoose') // very slow initialisation
|
|
3
3
|
|
|
4
4
|
test('comparison insert', async () => {
|
|
5
|
-
const db = monastery('127.0.0.1/monastery', { timestamps: false })
|
|
5
|
+
const db = monastery.manager('127.0.0.1/monastery', { timestamps: false })
|
|
6
6
|
const Test1 = db.model('Test1', { fields: {
|
|
7
7
|
raw: {
|
|
8
8
|
words: [{
|
|
@@ -70,7 +70,7 @@ test('comparison validate', async () => {
|
|
|
70
70
|
})),
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
|
-
const db = monastery('127.0.0.1/monastery', { timestamps: false })
|
|
73
|
+
const db = monastery.manager('127.0.0.1/monastery', { timestamps: false })
|
|
74
74
|
const Test1 = db.model('Test1', { fields: {
|
|
75
75
|
raw: {
|
|
76
76
|
words: [{
|
package/test/crud.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
const monastery = require('../lib/index.js')
|
|
3
3
|
|
|
4
4
|
let db
|
|
5
|
-
beforeAll(async () => { db = monastery('127.0.0.1/monastery', { timestamps: false }) })
|
|
5
|
+
beforeAll(async () => { db = monastery.manager('127.0.0.1/monastery', { timestamps: false }) })
|
|
6
6
|
afterAll(async () => { db.close() })
|
|
7
7
|
|
|
8
8
|
test('insert basics', async () => {
|
|
9
|
-
let db2 = monastery('127.0.0.1/monastery', { timestamps: false })
|
|
9
|
+
let db2 = monastery.manager('127.0.0.1/monastery', { timestamps: false })
|
|
10
10
|
let user = db2.model('user', { fields: {
|
|
11
11
|
name: { type: 'string' },
|
|
12
12
|
names: [{ type: 'string' }],
|
|
@@ -52,7 +52,7 @@ test('insert basics', async () => {
|
|
|
52
52
|
})
|
|
53
53
|
|
|
54
54
|
test('insert option defaultObjects', async () => {
|
|
55
|
-
let db2 = monastery('127.0.0.1/monastery', {
|
|
55
|
+
let db2 = monastery.manager('127.0.0.1/monastery', {
|
|
56
56
|
defaultObjects: true,
|
|
57
57
|
timestamps: false,
|
|
58
58
|
imagePlugin: {
|
|
@@ -97,7 +97,7 @@ test('insert option defaultObjects', async () => {
|
|
|
97
97
|
})
|
|
98
98
|
|
|
99
99
|
test('insert option timestamps', async () => {
|
|
100
|
-
let db2 = monastery('127.0.0.1/monastery', { timestamps: true })
|
|
100
|
+
let db2 = monastery.manager('127.0.0.1/monastery', { timestamps: true })
|
|
101
101
|
let schema = {
|
|
102
102
|
fields: {
|
|
103
103
|
name: { type: 'string' },
|
|
@@ -353,12 +353,9 @@ test('find default field blacklisted', async () => {
|
|
|
353
353
|
|
|
354
354
|
test('find default field population with option noDefaults', async () => {
|
|
355
355
|
async function setup(noDefaults) {
|
|
356
|
-
|
|
357
|
-
* Setup
|
|
358
|
-
* @returns {Object} {dog, user, dog1Doc, dog2Doc, user1Doc}
|
|
359
|
-
*/
|
|
356
|
+
// @returns {Object} {dog, user, dog1Doc, dog2Doc, user1Doc}
|
|
360
357
|
// similar to "find default field population"
|
|
361
|
-
const db = monastery('127.0.0.1/monastery', { noDefaults: noDefaults, timestamps: false })
|
|
358
|
+
const db = monastery.manager('127.0.0.1/monastery', { noDefaults: noDefaults, timestamps: false })
|
|
362
359
|
const userDefinition = {
|
|
363
360
|
fields: {
|
|
364
361
|
name: { type: 'string', default: 'Martin Luther' },
|
|
@@ -508,6 +505,9 @@ test('find default field population with option noDefaults', async () => {
|
|
|
508
505
|
},
|
|
509
506
|
dogs: [{ _id: s2.dog1Doc._id, user: s2.user1Doc._id }], // should not have a default name
|
|
510
507
|
})
|
|
508
|
+
|
|
509
|
+
s1.db.close()
|
|
510
|
+
s2.db.close()
|
|
511
511
|
})
|
|
512
512
|
|
|
513
513
|
test('update general', async () => {
|
|
@@ -577,7 +577,7 @@ test('update general', async () => {
|
|
|
577
577
|
})
|
|
578
578
|
|
|
579
579
|
test('update defaults', async () => {
|
|
580
|
-
const db2 = monastery('127.0.0.1/monastery', { useMilliseconds: true })
|
|
580
|
+
const db2 = monastery.manager('127.0.0.1/monastery', { useMilliseconds: true })
|
|
581
581
|
let user = db2.model('user', {
|
|
582
582
|
fields: {
|
|
583
583
|
name: { type: 'string' },
|
|
@@ -1074,7 +1074,6 @@ test('hooks > basic', async () => {
|
|
|
1074
1074
|
await expect(user2.update({ query: userDoc2._id, data: { first: 'MM' } })).resolves.toEqual({ first: 'MM' })
|
|
1075
1075
|
await expect(user2.update({ query: userDoc2._id, data: { first: 'M', bad: true } })).rejects.toThrow('error2')
|
|
1076
1076
|
})
|
|
1077
|
-
|
|
1078
1077
|
test('hooks > chained values', async () => {
|
|
1079
1078
|
let bookCount = 0
|
|
1080
1079
|
const afterInsertAsync = [
|
|
@@ -1277,7 +1276,7 @@ test('hooks > async', async () => {
|
|
|
1277
1276
|
})
|
|
1278
1277
|
|
|
1279
1278
|
test('hooks > async and next conflict', async () => {
|
|
1280
|
-
const db2 = monastery('127.0.0.1/monastery', { timestamps: false })
|
|
1279
|
+
const db2 = monastery.manager('127.0.0.1/monastery', { timestamps: false })
|
|
1281
1280
|
let user1 = db2.model('user', {
|
|
1282
1281
|
fields: { age: { type: 'number'} },
|
|
1283
1282
|
afterFind: [
|
|
@@ -1442,7 +1441,7 @@ test('hooks > option skipHooks', async () => {
|
|
|
1442
1441
|
})
|
|
1443
1442
|
|
|
1444
1443
|
test('update set and unset with option skipValidation', async () => {
|
|
1445
|
-
const db2 = monastery('127.0.0.1/monastery', { timestamps: false })
|
|
1444
|
+
const db2 = monastery.manager('127.0.0.1/monastery', { timestamps: false })
|
|
1446
1445
|
let user = db2.model('user_set_and_unset', {
|
|
1447
1446
|
fields: {
|
|
1448
1447
|
profile: {
|
|
@@ -1514,4 +1513,6 @@ test('update set and unset with option skipValidation', async () => {
|
|
|
1514
1513
|
const u8 = { query: userId, $unset: { 'profile.name': '' }, skipValidation: true }
|
|
1515
1514
|
await expect(user.update(u8)).resolves.toEqual({})
|
|
1516
1515
|
await expect(user.findOne(userId)).resolves.toEqual({ _id: userId, profile: {} })
|
|
1516
|
+
|
|
1517
|
+
db2.close()
|
|
1517
1518
|
})
|
package/test/manager.js
CHANGED
|
@@ -2,23 +2,23 @@ const { MongoClient } = require('mongodb')
|
|
|
2
2
|
const monastery = require('../lib/index.js')
|
|
3
3
|
|
|
4
4
|
test('manager > basics', async () => {
|
|
5
|
-
const
|
|
5
|
+
const db = monastery.manager('localhost/monastery', { serverSelectionTimeoutMS: 2000 })
|
|
6
6
|
// Manager is exposed
|
|
7
|
-
expect(
|
|
7
|
+
expect(monastery.uri).toEqual(expect.any(String))
|
|
8
8
|
// Basic find command
|
|
9
|
-
expect(await
|
|
9
|
+
expect(await db.db.collection('non-collection').findOne({})).toEqual(null)
|
|
10
10
|
// Raw MongoDB ping command
|
|
11
|
-
expect(await
|
|
12
|
-
|
|
11
|
+
expect(await db.command({ ping: 1 })).toMatchObject({ ok: 1 }) // cluster connections return extra fields
|
|
12
|
+
db.close()
|
|
13
13
|
})
|
|
14
14
|
|
|
15
15
|
test('manager > uri error', async () => {
|
|
16
|
-
expect(() => monastery('', {})).toThrow('No connection URI provided.')
|
|
16
|
+
expect(() => monastery.manager('', {})).toThrow('No connection URI provided.')
|
|
17
17
|
})
|
|
18
18
|
|
|
19
19
|
test('manager > onError', async () => {
|
|
20
20
|
// Bad port (thrown by MongoDB)
|
|
21
|
-
const db = monastery('localhost:1234/monastery', { serverSelectionTimeoutMS: 500 })
|
|
21
|
+
const db = monastery.manager('localhost:1234/monastery', { serverSelectionTimeoutMS: 500 })
|
|
22
22
|
let error, isAPromise
|
|
23
23
|
await db.onError((res) => { error = res.message }).then(() => { isAPromise = true })
|
|
24
24
|
expect(error).toEqual('connect ECONNREFUSED 127.0.0.1:1234')
|
|
@@ -27,7 +27,7 @@ test('manager > onError', async () => {
|
|
|
27
27
|
})
|
|
28
28
|
|
|
29
29
|
test('manager > onOpen', async () => {
|
|
30
|
-
const db = monastery('localhost/monastery', { serverSelectionTimeoutMS: 500 })
|
|
30
|
+
const db = monastery.manager('localhost/monastery', { serverSelectionTimeoutMS: 500 })
|
|
31
31
|
|
|
32
32
|
let manager1
|
|
33
33
|
await db.onOpen((res) => { manager1 = res })
|
|
@@ -52,7 +52,7 @@ test('manager > onOpen', async () => {
|
|
|
52
52
|
test('manager > onOpen error', async () => {
|
|
53
53
|
// Bad port (thrown by MongoDB)
|
|
54
54
|
let manager
|
|
55
|
-
const db = monastery('localhost:1234/monastery', { serverSelectionTimeoutMS: 500 })
|
|
55
|
+
const db = monastery.manager('localhost:1234/monastery', { serverSelectionTimeoutMS: 500 })
|
|
56
56
|
await expect(db.onOpen((res) => { manager = res })).rejects.toThrow('connect ECONNREFUSED 127.0.0.1:1234')
|
|
57
57
|
expect(manager).toEqual(undefined)
|
|
58
58
|
expect(db).toEqual(expect.any(Object))
|
|
@@ -60,19 +60,19 @@ test('manager > onOpen error', async () => {
|
|
|
60
60
|
})
|
|
61
61
|
|
|
62
62
|
test('manager > return a promise', async () => {
|
|
63
|
-
const db = await monastery('localhost/monastery', { serverSelectionTimeoutMS: 500, promise: true })
|
|
63
|
+
const db = await monastery.manager('localhost/monastery', { serverSelectionTimeoutMS: 500, promise: true })
|
|
64
64
|
expect(db).toEqual(expect.any(Object))
|
|
65
65
|
db.close()
|
|
66
66
|
})
|
|
67
67
|
|
|
68
68
|
test('manager > return a promise with uri error', async () => {
|
|
69
|
-
await expect(monastery('badlocalhost/monastery', { serverSelectionTimeoutMS: 500, promise: true }))
|
|
69
|
+
await expect(monastery.manager('badlocalhost/monastery', { serverSelectionTimeoutMS: 500, promise: true }))
|
|
70
70
|
.rejects.toThrow('getaddrinfo EAI_AGAIN badlocalhost')
|
|
71
71
|
})
|
|
72
72
|
|
|
73
73
|
test('manager > reuse MongoDB Client', async () => {
|
|
74
74
|
const mongoClient = new MongoClient('mongodb://localhost/monastery', {})
|
|
75
|
-
const db = await monastery(mongoClient, { serverSelectionTimeoutMS: 500, promise: true })
|
|
75
|
+
const db = await monastery.manager(mongoClient, { serverSelectionTimeoutMS: 500, promise: true })
|
|
76
76
|
expect(db).toEqual(expect.any(Object))
|
|
77
77
|
expect(db.client).toEqual(expect.any(Object))
|
|
78
78
|
expect(db.isId).toEqual(expect.any(Function))
|
|
@@ -87,7 +87,7 @@ test('manager > events', async () => {
|
|
|
87
87
|
})
|
|
88
88
|
}
|
|
89
89
|
// Triggers on opening/open
|
|
90
|
-
const db = monastery('localhost/monastery', { serverSelectionTimeoutMS: 2000 })
|
|
90
|
+
const db = monastery.manager('localhost/monastery', { serverSelectionTimeoutMS: 2000 })
|
|
91
91
|
expect(db._state).toEqual('opening')
|
|
92
92
|
// Start waiting for the following events
|
|
93
93
|
const promises = Promise.all([
|
|
@@ -101,10 +101,10 @@ test('manager > events', async () => {
|
|
|
101
101
|
})
|
|
102
102
|
|
|
103
103
|
test('Manager > get collection', async () => {
|
|
104
|
-
const
|
|
104
|
+
const db = monastery.manager('localhost/monastery', { serverSelectionTimeoutMS: 500 })
|
|
105
105
|
// Basic aggregate command
|
|
106
|
-
expect(await
|
|
106
|
+
expect(await db.get('non-collection').aggregate([], {})).toEqual([])
|
|
107
107
|
// Basic find command
|
|
108
|
-
expect(await
|
|
109
|
-
|
|
108
|
+
expect(await db.get('non-collection').find({})).toEqual([])
|
|
109
|
+
db.close()
|
|
110
110
|
})
|