monastery 2.2.2 → 3.0.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/.eslintrc.json +10 -1
- package/changelog.md +2 -0
- 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/...rawMethods.md +289 -0
- 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/remove.md +4 -6
- package/docs/model/update.md +4 -6
- package/docs/readme.md +78 -45
- 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 -40
- package/test/test.js +0 -38
package/docs/readme.md
CHANGED
|
@@ -2,16 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/monastery) [](https://app.travis-ci.com/github/boycce/monastery)
|
|
4
4
|
|
|
5
|
+
> [!IMPORTANT]
|
|
6
|
+
> v3.0 has been released 🎉 refer to [breaking changes](#v3.0BreakingChanges) below when upgrading from v2.x.
|
|
7
|
+
|
|
5
8
|
## Features
|
|
6
9
|
|
|
7
|
-
* User friendly API design,
|
|
8
|
-
* Simple CRUD operations with model population
|
|
9
|
-
* Model validation
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* User friendly API design, *inspired by SailsJS*
|
|
11
|
+
* Simple CRUD operations, with simple but fast model population
|
|
12
|
+
* Model validation controlled by your model definitions
|
|
13
|
+
* Normalized error responses objects ready for client consumption
|
|
14
|
+
* Custom error messages can be defined in your model definitions
|
|
15
|
+
* Blacklist sensitive fields once in your model definition, or per operation
|
|
16
|
+
* Model methods can accept bracket (multipart/form-data) and dot notation data formats, you can also mix these together
|
|
17
|
+
* Automatic Mongo index creation
|
|
18
|
+
|
|
19
|
+
#### Why Monastery over Mongoose?
|
|
14
20
|
|
|
21
|
+
* User friendly API designed for busy agencies, allowing you to quickly build projects without distractions
|
|
22
|
+
* Model schema and configurations are all defined within a single object (model definition)
|
|
23
|
+
* You can blacklist/exclude sensitive model fields in the model definition for each CRUD operation
|
|
24
|
+
* Model population uses a single aggregation call instead of multiple queries for faster responses
|
|
25
|
+
* Errors throw normalized error objects that contain the model and field name, error message etc, handy in the client
|
|
15
26
|
|
|
16
27
|
## Install
|
|
17
28
|
|
|
@@ -26,9 +37,8 @@ $ npm install --save monastery
|
|
|
26
37
|
```javascript
|
|
27
38
|
import monastery from 'monastery'
|
|
28
39
|
|
|
29
|
-
//
|
|
40
|
+
// Initialize a monastery manager
|
|
30
41
|
const db = monastery('localhost/mydb')
|
|
31
|
-
// const db = monastery('user:pass@localhost:port/mydb')
|
|
32
42
|
|
|
33
43
|
// Define a model
|
|
34
44
|
db.model('user', {
|
|
@@ -41,18 +51,16 @@ db.model('user', {
|
|
|
41
51
|
})
|
|
42
52
|
|
|
43
53
|
// Insert some data
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}).catch(errs => {
|
|
54
|
+
try {
|
|
55
|
+
const newUser = await db.user.insert({
|
|
56
|
+
data: {
|
|
57
|
+
name: 'Martin Luther',
|
|
58
|
+
pets: ['sparky', 'tiny'],
|
|
59
|
+
address: { city: 'Eisleben' },
|
|
60
|
+
points: [[1, 5], [3, 1]]
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
} catch (errs) {
|
|
56
64
|
// [{
|
|
57
65
|
// detail: "Value needs to be at least 10 characters long.",
|
|
58
66
|
// status: "400",
|
|
@@ -63,25 +71,28 @@ db.user.insert({
|
|
|
63
71
|
// rule: "minLength"
|
|
64
72
|
// }
|
|
65
73
|
// }]
|
|
66
|
-
}
|
|
74
|
+
}
|
|
67
75
|
```
|
|
68
|
-
##
|
|
69
|
-
|
|
70
|
-
This package uses [debug](https://github.com/visionmedia/debug) which allows you to set different levels of output via the `DEBUG` environment variable. Due to known limations `monastery:warning` and `monastery:error` are forced on, you can however disable these via [manager settings](./manager).
|
|
76
|
+
## Version Compatibility
|
|
71
77
|
|
|
72
|
-
|
|
73
|
-
$ DEBUG=monastery:info # shows operation information
|
|
74
|
-
```
|
|
78
|
+
You can view MongoDB's [compatibility table here](https://www.mongodb.com/docs/drivers/node/current/compatibility/), and see all of MongoDB NodeJS Driver [releases here](https://mongodb.github.io/node-mongodb-native/)
|
|
75
79
|
|
|
76
|
-
|
|
80
|
+
| Monastery | Mongo NodeJS Driver | MongoDB Server | Node |
|
|
81
|
+
| :------------------- | :-----------------: | :---------------: | ------------------: |
|
|
82
|
+
| `3.x` | [`5.9.x`](https://mongodb.github.io/node-mongodb-native/5.9/) | `>=3.6 <=7.x` | `>=14.x <=latest` |
|
|
83
|
+
| `2.x` | [`3.7.x`](https://mongodb.github.io/node-mongodb-native/3.7/api/) | `>=2.6 <=6.x` | `>=4.x <=14.x` |
|
|
77
84
|
|
|
78
|
-
```bash
|
|
79
|
-
npm run dev -- -t 'Model indexes'
|
|
80
|
-
```
|
|
81
85
|
|
|
82
|
-
##
|
|
86
|
+
## v3.0 Breaking Changes
|
|
83
87
|
|
|
84
|
-
|
|
88
|
+
- Removed callback functions on all model methods, you can use the returned promise instead
|
|
89
|
+
- `model.update()` now returns the following _update property:
|
|
90
|
+
- `{ acknowledged: true, matchedCount: 1, modifiedCount: 1, upsertedCount: 0, upsertedId: null }`, instead of
|
|
91
|
+
- `{ n: 1, nModified: 1, ok: 1 }`
|
|
92
|
+
- `model.remove()` now returns `{ acknowledged: true, deletedCount: 1 }`, instead of `{ results: { n: 1, ok: 1} }`
|
|
93
|
+
- Models are now added to `db.models` instead of `db.model`, e.g. `db.models.user`
|
|
94
|
+
- MongoDB connection can be found here `db.db` changed from `db._db`
|
|
95
|
+
- `model._indexes()` now returns `collection._indexes()` not `collection._indexInformation()`
|
|
85
96
|
|
|
86
97
|
## Roadmap
|
|
87
98
|
|
|
@@ -97,25 +108,38 @@ Coming soon...
|
|
|
97
108
|
- ~~Ability to change ACL default on the manager~~
|
|
98
109
|
- ~~Public db.arrayWithSchema method~~
|
|
99
110
|
- ~~Added support for array population~~
|
|
111
|
+
- ~~MongoClient instances can now be reused when initializing the manager, e.g. `monastery(mongoClient)`, handy for migrate-mongo~~
|
|
100
112
|
- Change population warnings into errors
|
|
101
113
|
- Global after/before hooks
|
|
102
|
-
-
|
|
103
|
-
-
|
|
104
|
-
- Split away from Monk
|
|
114
|
+
- Before hooks can receive a data array, remove this
|
|
115
|
+
- Docs: Make the implicit ID query conversion more apparent
|
|
116
|
+
- ~~Split away from Monk so we can update the MongoDB NodeJS Driver version~~
|
|
105
117
|
- Add a warning if an invalid model is referenced in jthe schema
|
|
106
118
|
- Remove leading forward slashes from custom image paths (AWS adds this as a seperate folder)
|
|
107
|
-
-
|
|
108
|
-
- ~~
|
|
119
|
+
- Double check await db.model.remove({ query: idfromparam }) doesnt cause issues for null, undefined or '', but continue to allow {}
|
|
120
|
+
- ~~Can't insert/update model id (maybe we can allow this and add _id to default insert/update blacklists)~~
|
|
109
121
|
- timstamps are blacklisted by default (instead of the `timestamps` opt), and can be switched off via blacklisting
|
|
110
122
|
- Allow rules on image types, e.g. `required`
|
|
111
|
-
-
|
|
123
|
+
- Test importing of models
|
|
112
124
|
- Docs: model.methods
|
|
125
|
+
- ~~Convert hooks to promises~~
|
|
126
|
+
- ~~added `model.count()` ~~
|
|
113
127
|
|
|
114
|
-
##
|
|
128
|
+
## Debugging
|
|
115
129
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
130
|
+
This package uses [debug](https://github.com/visionmedia/debug) which allows you to set different levels of output via the `DEBUG` environment variable. Due to known limations `monastery:warning` and `monastery:error` are forced on, you can however disable these via [manager settings](./manager).
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
$ DEBUG=monastery:info # shows operation information
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Contributing
|
|
137
|
+
|
|
138
|
+
All pull requests are welcome. To run isolated tests with Jest:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
npm run dev -- -t 'Model indexes'
|
|
142
|
+
```
|
|
119
143
|
|
|
120
144
|
## Special Thanks
|
|
121
145
|
|
|
@@ -123,4 +147,13 @@ Coming soon...
|
|
|
123
147
|
|
|
124
148
|
## License
|
|
125
149
|
|
|
126
|
-
Copyright
|
|
150
|
+
Copyright 2024 Ricky Boyce. Code released under the MIT license.
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
///////////////////////////////
|
|
158
|
+
/////3. add 'Collection' to monastery docs sidebar ( also add model.count)
|
|
159
|
+
//// docs sapcing.... +4px
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
const util = require('./util.js')
|
|
2
|
+
|
|
3
|
+
function Collection (manager, name, options) {
|
|
4
|
+
this.col = null
|
|
5
|
+
this.manager = manager
|
|
6
|
+
this.name = name
|
|
7
|
+
this.options = options
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
Collection.prototype._middleware = async function (args) {
|
|
11
|
+
/**
|
|
12
|
+
* Modfy the arguments before passing them to the MongoDB driver collection operations
|
|
13
|
+
* @return {Object} args
|
|
14
|
+
*/
|
|
15
|
+
const objectsToCast = ['operations', 'query', 'data', 'update']
|
|
16
|
+
let { fields, opts, query } = args
|
|
17
|
+
if (!opts) args.opts = opts = {}
|
|
18
|
+
|
|
19
|
+
// Get the collection
|
|
20
|
+
this.col = this.col || this.manager.db.collection(this.name)
|
|
21
|
+
|
|
22
|
+
// Query: convert strings to ObjectIds
|
|
23
|
+
if (query) {
|
|
24
|
+
if (typeof query === 'string' || typeof query.toHexString === 'function') {
|
|
25
|
+
args.query = {_id: args.query}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Options: setup
|
|
30
|
+
if (typeof opts === 'string' || Array.isArray(opts)) {
|
|
31
|
+
throw new Error('You can no longer pass an array or string `projection` to find()')
|
|
32
|
+
} else {
|
|
33
|
+
// MongoDB 5.0 docs seem to be a little off, projection is still included in `opts`...
|
|
34
|
+
if (opts.fields) opts.projection = _fields(opts.fields, 0)
|
|
35
|
+
if (opts.sort) opts.sort = _fields(opts.sort, -1)
|
|
36
|
+
delete opts.fields
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Options: use collection defaults
|
|
40
|
+
for (let key in this.options) {
|
|
41
|
+
if (typeof opts[key] === 'undefined') {
|
|
42
|
+
opts[key] = this.options[key]
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Options: castIds (defaults on)
|
|
47
|
+
if (opts.castIds !== false) {
|
|
48
|
+
for (const key of objectsToCast) {
|
|
49
|
+
if (args[key]) args[key] = util.cast(args[key])
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Fields: convert strings/arrays to objects, e.g. 'name _id' -> {name: 1, _id: 1}
|
|
54
|
+
if (fields && !util.isObject(fields)) {
|
|
55
|
+
let fieldsArray = typeof fields === 'string' ? fields.split(' ') : (fields || [])
|
|
56
|
+
args.fields = fieldsArray.reduce((acc, fieldName) => {
|
|
57
|
+
acc[fieldName] = 1
|
|
58
|
+
return acc
|
|
59
|
+
}, {})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function _fields (obj, numberWhenMinus) {
|
|
63
|
+
if (!Array.isArray(obj) && typeof obj === 'object') return obj
|
|
64
|
+
obj = typeof obj === 'string' ? obj.split(' ') : (obj || [])
|
|
65
|
+
let fields = {}
|
|
66
|
+
for (let i = 0, l = obj.length; i < l; i++) {
|
|
67
|
+
if (obj[i][0] === '-') fields[obj[i].substr(1)] = numberWhenMinus
|
|
68
|
+
else fields[obj[i]] = 1
|
|
69
|
+
}
|
|
70
|
+
return fields
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return args
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
Collection.prototype.aggregate = async function (stages, opts) {
|
|
77
|
+
const args = await this._middleware({ stages, opts })
|
|
78
|
+
return this.col.aggregate(args.stages, args.opts).toArray()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
Collection.prototype.bulkWrite = async function (operations, opts) {
|
|
82
|
+
const args = await this._middleware({ operations, opts })
|
|
83
|
+
return this.col.bulkWrite(args.operations, args.opts)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
Collection.prototype.count = async function (query, opts) {
|
|
87
|
+
const args = await this._middleware({ query, opts })
|
|
88
|
+
const { estimate, ..._opts } = args.opts
|
|
89
|
+
if (estimate) {
|
|
90
|
+
return this.col.estimatedDocumentCount(_opts)
|
|
91
|
+
} else {
|
|
92
|
+
return this.col.countDocuments(args.query, _opts)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
Collection.prototype.createIndex = async function (indexSpec, opts) {
|
|
97
|
+
const args = await this._middleware({ indexSpec, opts })
|
|
98
|
+
return await this.col.createIndex(args.indexSpec, args.opts)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
Collection.prototype.createIndexes = async function (indexSpecs, opts) {
|
|
102
|
+
const args = await this._middleware({ indexSpecs, opts }) // doesn't currently accept string or array parsing.
|
|
103
|
+
return await this.col.createIndexes(args.indexSpecs, args.opts)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
Collection.prototype.distinct = async function (field, query, opts) {
|
|
107
|
+
const args = await this._middleware({ field, query, opts })
|
|
108
|
+
return this.col.distinct(args.field, args.query, args.opts)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
Collection.prototype.drop = async function () {
|
|
112
|
+
try {
|
|
113
|
+
await this._middleware({})
|
|
114
|
+
await this.col.drop()
|
|
115
|
+
// this.col = null
|
|
116
|
+
} catch (err) {
|
|
117
|
+
if (err?.message == 'ns not found') return 'ns not found'
|
|
118
|
+
throw err
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
Collection.prototype.dropIndex = async function (name, opts) {
|
|
123
|
+
const args = await this._middleware({ name, opts })
|
|
124
|
+
return await this.col.dropIndex(args.name, args.opts)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
Collection.prototype.dropIndexes = async function () {
|
|
128
|
+
await this._middleware({})
|
|
129
|
+
return this.col.dropIndexes()
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
Collection.prototype.find = async function (query, opts) {
|
|
133
|
+
// v3.0 - removed the abillity to pass an array or string to opts as `opts.projection`
|
|
134
|
+
// v3.0 - find().each() is removed, use `opts.stream` instead
|
|
135
|
+
const args = await this._middleware({ query, opts })
|
|
136
|
+
query = args.query
|
|
137
|
+
opts = args.opts
|
|
138
|
+
const rawCursor = opts.rawCursor
|
|
139
|
+
|
|
140
|
+
// Get the raw cursor
|
|
141
|
+
const cursor = this.col.find(query, opts)
|
|
142
|
+
|
|
143
|
+
// If a raw cursor is requested, return it now
|
|
144
|
+
if (rawCursor) return cursor
|
|
145
|
+
|
|
146
|
+
// If no stream is requested, return now the array of results
|
|
147
|
+
if (!opts.stream) return cursor.toArray()
|
|
148
|
+
|
|
149
|
+
if (typeof opts.stream !== 'function') {
|
|
150
|
+
throw new Error('opts.stream must be a function')
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const stream = cursor.stream()
|
|
154
|
+
return new Promise((resolve, reject) => {
|
|
155
|
+
let closed = false
|
|
156
|
+
let finished = false
|
|
157
|
+
let processing = 0
|
|
158
|
+
function close () {
|
|
159
|
+
closed = true
|
|
160
|
+
processing -= 1
|
|
161
|
+
cursor.close()
|
|
162
|
+
}
|
|
163
|
+
function pause () {
|
|
164
|
+
processing += 1
|
|
165
|
+
stream.pause()
|
|
166
|
+
}
|
|
167
|
+
function resume () {
|
|
168
|
+
processing -= 1
|
|
169
|
+
stream.resume()
|
|
170
|
+
if (processing === 0 && finished) done()
|
|
171
|
+
}
|
|
172
|
+
function done () {
|
|
173
|
+
finished = true
|
|
174
|
+
if (processing <= 0) resolve()
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
stream.on('close', done)
|
|
178
|
+
stream.on('end', done)
|
|
179
|
+
stream.on('error', (err) => reject(err))
|
|
180
|
+
stream.on('data', (doc) => {
|
|
181
|
+
if (!closed) opts.stream(doc, { close, pause, resume })
|
|
182
|
+
})
|
|
183
|
+
})
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
Collection.prototype.findOne = async function (query, opts) {
|
|
187
|
+
const args = await this._middleware({ query, opts })
|
|
188
|
+
const docs = await this.col.find(args.query, args.opts).limit(1).toArray()
|
|
189
|
+
return docs?.[0] || null
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
Collection.prototype.findOneAndDelete = async function (query, opts) {
|
|
193
|
+
const args = await this._middleware({ query, opts })
|
|
194
|
+
const doc = await this.col.findOneAndDelete(args.query, args.opts)
|
|
195
|
+
|
|
196
|
+
if (doc && typeof doc.value !== 'undefined') return doc.value
|
|
197
|
+
if (doc.ok && doc.lastErrorObject && doc.lastErrorObject.n === 0) return null
|
|
198
|
+
return doc
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
Collection.prototype.findOneAndUpdate = async function (query, update, opts) {
|
|
202
|
+
const args = await this._middleware({ query, update, opts })
|
|
203
|
+
let method = 'findOneAndUpdate'
|
|
204
|
+
|
|
205
|
+
if (typeof args.opts?.returnDocument === 'undefined') {
|
|
206
|
+
args.opts.returnDocument = 'after'
|
|
207
|
+
}
|
|
208
|
+
if (typeof args.opts?.returnOriginal !== 'undefined') {
|
|
209
|
+
this.manager.warn('The `returnOriginal` option is deprecated, use `returnDocument` instead.')
|
|
210
|
+
args.opts.returnDocument = args.opts.returnOriginal ? 'before' : 'after'
|
|
211
|
+
}
|
|
212
|
+
if (args.opts.replaceOne || args.opts.replace) {
|
|
213
|
+
method = 'findOneAndReplace'
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const doc = await this.col[method](args.query, args.update, args.opts)
|
|
217
|
+
if (doc && typeof doc.value !== 'undefined') return doc.value
|
|
218
|
+
if (doc.ok && doc.lastErrorObject && doc.lastErrorObject.n === 0) return null
|
|
219
|
+
return doc
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
Collection.prototype.geoHaystackSearch = async function (x, y, opts) {
|
|
223
|
+
// https://www.mongodb.com/docs/manual/geospatial-queries/
|
|
224
|
+
throw new Error('geoHaystackSearch is depreciated in MongoDB 4.0, use geospatial queries instead, e.g. $geoWithin')
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
Collection.prototype.indexInformation = async function (opts) {
|
|
228
|
+
try {
|
|
229
|
+
const args = await this._middleware({ opts })
|
|
230
|
+
return await this.col.indexInformation(args.opts)
|
|
231
|
+
} catch (e) {
|
|
232
|
+
// col.indexInformation() throws an error if the collection is created yet...
|
|
233
|
+
if (e?.message.match(/ns does not exist/)) return {}
|
|
234
|
+
else throw new Error(e)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
Collection.prototype.indexes = async function (opts) {
|
|
239
|
+
try {
|
|
240
|
+
const args = await this._middleware({ opts })
|
|
241
|
+
return await this.col.indexes(args.opts)
|
|
242
|
+
} catch (e) {
|
|
243
|
+
// col.indexes() throws an error if the collection is created yet...
|
|
244
|
+
if (e?.message.match(/ns does not exist/)) return []
|
|
245
|
+
else throw new Error(e)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
Collection.prototype.insert = async function (data, opts) {
|
|
250
|
+
const args = await this._middleware({ data, opts })
|
|
251
|
+
const arrayInsert = Array.isArray(args.data)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
if (arrayInsert && args.data.length === 0) {
|
|
255
|
+
return Promise.resolve([])
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const doc = await this.col[`insert${arrayInsert ? 'Many' : 'One'}`](args.data, args.opts)
|
|
259
|
+
if (!doc) return
|
|
260
|
+
|
|
261
|
+
// Starting MongoDB 4 the `insert` method only returns the _id, rather than the whole doc.
|
|
262
|
+
// We need to return the whole doc for consistency with previous versions.
|
|
263
|
+
const output = util.deepCopy(args.data)
|
|
264
|
+
if (arrayInsert) {
|
|
265
|
+
for (let i=output.length; i--;) {
|
|
266
|
+
output[i]._id = doc.insertedIds[i]
|
|
267
|
+
}
|
|
268
|
+
} else {
|
|
269
|
+
output._id = doc.insertedId
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return output
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
Collection.prototype.mapReduce = async function (map, reduce, opts) {
|
|
276
|
+
// https://www.mongodb.com/docs/manual/reference/method/db.collection.mapReduce/
|
|
277
|
+
throw new Error('mapReduce is depreciated in MongoDB 5.0, use aggregation pipeline instead')
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
Collection.prototype.remove = async function (query, opts) {
|
|
281
|
+
const args = await this._middleware({ query, opts })
|
|
282
|
+
const method = args.opts.single || args.opts.multi === false ? 'deleteOne' : 'deleteMany'
|
|
283
|
+
return this.col[method](args.query, args.opts)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
Collection.prototype.stats = async function (opts) {
|
|
287
|
+
const args = await this._middleware({ opts })
|
|
288
|
+
return this.col.stats(args.opts)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
Collection.prototype.update = async function (query, update, opts) {
|
|
292
|
+
// v3.0 - returns now an object with the following properties:
|
|
293
|
+
// {
|
|
294
|
+
// acknowledged: true,
|
|
295
|
+
// matchedCount: 0,
|
|
296
|
+
// modifiedCount: 0,
|
|
297
|
+
// upsertedCount: 1,
|
|
298
|
+
// upsertedId: expect.any(ObjectId),
|
|
299
|
+
// }
|
|
300
|
+
// was:
|
|
301
|
+
// {
|
|
302
|
+
// result: {
|
|
303
|
+
// ok: 1,
|
|
304
|
+
// n: 1,
|
|
305
|
+
// nModified: 1,
|
|
306
|
+
// upserted: [{ _id: expect.any(ObjectId) }],
|
|
307
|
+
// }
|
|
308
|
+
// }
|
|
309
|
+
const args = await this._middleware({ query, update, opts })
|
|
310
|
+
let method = args.opts.multi || args.opts.single === false ? 'updateMany' : 'updateOne'
|
|
311
|
+
|
|
312
|
+
if (args.opts.replace || args.opts.replaceOne) {
|
|
313
|
+
if (args.opts.multi || args.opts.single === false) {
|
|
314
|
+
throw new Error('The `replace` option is only available for single updates.')
|
|
315
|
+
}
|
|
316
|
+
method = 'replaceOne'
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const doc = await this.col[method](args.query, args.update, args.opts)
|
|
320
|
+
// return doc?.result || doc
|
|
321
|
+
return doc
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
module.exports = Collection
|