cosa 6.0.0 → 6.2.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/LICENSE +1 -1
- package/lib/array.js +32 -40
- package/lib/date.js +22 -29
- package/lib/db.js +99 -80
- package/lib/defined-object.js +22 -21
- package/lib/immutable.js +49 -53
- package/lib/model.js +84 -62
- package/lib/object.js +10 -7
- package/lib/session.js +58 -0
- package/package.json +9 -18
package/LICENSE
CHANGED
package/lib/array.js
CHANGED
|
@@ -4,81 +4,73 @@ const Immutable = require('./immutable');
|
|
|
4
4
|
/**
|
|
5
5
|
* Immutable handler used to wrap a javascript array.
|
|
6
6
|
* @name ImmutableArray
|
|
7
|
-
* @param {object} data
|
|
8
|
-
* @param {object} builder
|
|
9
|
-
* @param {object} options
|
|
7
|
+
* @param {object} data - Underlying array
|
|
8
|
+
* @param {object} builder - Builder instance
|
|
9
|
+
* @param {object} options - Optional settings
|
|
10
|
+
* @returns {undefined} no return value
|
|
10
11
|
*/
|
|
11
|
-
module.exports = function
|
|
12
|
+
module.exports = function(data, builder, options) {
|
|
12
13
|
builder.type = 'Array';
|
|
13
14
|
|
|
14
15
|
builder.defineProperty('length', data.length);
|
|
15
16
|
|
|
16
|
-
const itemDefinition = options
|
|
17
|
+
const itemDefinition = options?.definition?.items || {};
|
|
17
18
|
const itemOptions = { definition: itemDefinition, clone: false };
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return Immutable.create(Array.prototype[name].call(data, function (val, index) {
|
|
20
|
+
['forEach', 'map', 'filter', 'some', 'every'].forEach(function(name) {
|
|
21
|
+
builder.defineMethod(name, function(cb, thisArg) {
|
|
22
|
+
const immuArr = Immutable.create(data, options);
|
|
23
|
+
return Immutable.create(Array.prototype[name].call(data, function(val, index) {
|
|
24
24
|
return cb.call(thisArg, Immutable.create(val, itemOptions), index, immuArr);
|
|
25
25
|
}));
|
|
26
26
|
});
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
-
['reduce', 'reduceRight'].forEach(function
|
|
30
|
-
builder.defineMethod(name, function
|
|
31
|
-
|
|
32
|
-
return Immutable.create(Array.prototype[name].call(data, function
|
|
29
|
+
['reduce', 'reduceRight'].forEach(function(name) {
|
|
30
|
+
builder.defineMethod(name, function(cb, initialValue) {
|
|
31
|
+
const immuArr = Immutable.create(data, options);
|
|
32
|
+
return Immutable.create(Array.prototype[name].call(data, function(prev, cur, index) {
|
|
33
33
|
return cb(Immutable.create(prev), Immutable.create(cur, itemOptions), index, immuArr);
|
|
34
34
|
}), initialValue);
|
|
35
35
|
});
|
|
36
36
|
});
|
|
37
37
|
|
|
38
38
|
['concat', 'join', 'slice', 'indexOf', 'lastIndexOf', 'reverse',
|
|
39
|
-
|
|
40
|
-
builder.defineMethod(name, function
|
|
41
|
-
return Immutable.create(Array.prototype[name].apply(data,
|
|
39
|
+
'toString', 'toLocaleString'].forEach(function(name) {
|
|
40
|
+
builder.defineMethod(name, function(...rest) {
|
|
41
|
+
return Immutable.create(Array.prototype[name].apply(data, rest), options);
|
|
42
42
|
});
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
-
builder.defineMethod('push', function
|
|
46
|
-
return Immutable.create(Array.prototype.concat.apply(data,
|
|
45
|
+
builder.defineMethod('push', function(...rest) {
|
|
46
|
+
return Immutable.create(Array.prototype.concat.apply(data, rest), options);
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
-
builder.defineMethod('unshift', function
|
|
50
|
-
|
|
51
|
-
for (var i = 0, l = arguments.length; i < l; i++) {
|
|
52
|
-
args[i] = arguments[i];
|
|
53
|
-
}
|
|
54
|
-
return Immutable.create(args.concat(data), options);
|
|
49
|
+
builder.defineMethod('unshift', function(...rest) {
|
|
50
|
+
return Immutable.create(rest.concat(data), options);
|
|
55
51
|
});
|
|
56
52
|
|
|
57
|
-
builder.defineMethod('sort', function
|
|
58
|
-
|
|
53
|
+
builder.defineMethod('sort', function(cb) {
|
|
54
|
+
const newArr = clone(data);
|
|
59
55
|
if (!cb) {
|
|
60
56
|
return Immutable.create(newArr.sort(), options);
|
|
61
57
|
}
|
|
62
|
-
return Immutable.create(newArr.sort(function
|
|
58
|
+
return Immutable.create(newArr.sort(function(a, b) {
|
|
63
59
|
return cb(Immutable.create(a, itemOptions), Immutable.create(b, itemOptions));
|
|
64
60
|
}));
|
|
65
61
|
});
|
|
66
62
|
|
|
67
|
-
builder.defineMethod('splice', function
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
var deleteCount = args[1];
|
|
74
|
-
var items = args.slice(2) || [];
|
|
75
|
-
var front = data.slice(0, start);
|
|
76
|
-
var back = data.slice(start + deleteCount);
|
|
63
|
+
builder.defineMethod('splice', function(...rest) {
|
|
64
|
+
const start = rest[0];
|
|
65
|
+
const deleteCount = rest[1];
|
|
66
|
+
const items = rest.slice(2) || [];
|
|
67
|
+
const front = data.slice(0, start);
|
|
68
|
+
const back = data.slice(start + deleteCount);
|
|
77
69
|
return Immutable.create(front.concat(items, back), options);
|
|
78
70
|
});
|
|
79
71
|
|
|
80
|
-
builder.defineMethod('reverse', function
|
|
81
|
-
|
|
72
|
+
builder.defineMethod('reverse', function() {
|
|
73
|
+
const newArr = clone(data);
|
|
82
74
|
return Immutable.create(newArr.reverse(), options);
|
|
83
75
|
});
|
|
84
76
|
};
|
package/lib/date.js
CHANGED
|
@@ -1,48 +1,41 @@
|
|
|
1
|
-
|
|
1
|
+
const Immutable = require('./immutable');
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Immutable handler used to wrap javascript Dates.
|
|
5
5
|
* @name ImmutableDate
|
|
6
|
-
* @param {object} data
|
|
7
|
-
* @param {object} builder
|
|
8
|
-
* @param {object} options
|
|
6
|
+
* @param {object} data - Underlying date
|
|
7
|
+
* @param {object} builder - Builder instance
|
|
8
|
+
* @param {object} options - Optional settings
|
|
9
|
+
* @returns {undefined} no return value
|
|
9
10
|
*/
|
|
10
|
-
module.exports = function
|
|
11
|
+
module.exports = function(data, builder, options) {
|
|
11
12
|
builder.type = 'Date';
|
|
12
13
|
|
|
13
|
-
builder.defineMethod('mutate', function
|
|
14
|
-
|
|
14
|
+
builder.defineMethod('mutate', function(cb) {
|
|
15
|
+
const newDate = new Date(data.valueOf());
|
|
15
16
|
cb.apply(newDate);
|
|
16
17
|
return Immutable.create(newDate, { clone: false });
|
|
17
18
|
});
|
|
18
19
|
|
|
19
20
|
['toString', 'toISOString', 'toUTCString', 'toDateString', 'toTimeString',
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
builder.defineMethod(name, function
|
|
27
|
-
|
|
28
|
-
for (var i = 0, l = args.length; i < l; i++) {
|
|
29
|
-
args[i] = arguments[i];
|
|
30
|
-
}
|
|
31
|
-
return Date.prototype[name].apply(data, args);
|
|
21
|
+
'toLocaleString', 'toLocaleDateString', 'toLocaleTimeString', 'valueOf',
|
|
22
|
+
'getTime', 'getFullYear', 'getUTCFullYear', 'toGMTString', 'getMonth',
|
|
23
|
+
'getUTCMonth', 'getDate', 'getUTCDate', 'getDay', 'getUTCDay', 'getHours',
|
|
24
|
+
'getUTCHours', 'getMinutes', 'getUTCMinutes', 'getSeconds', 'getUTCSeconds',
|
|
25
|
+
'getMilliseconds', 'getUTCMilliseconds', 'getTimezoneOffset', 'getYear',
|
|
26
|
+
'toJSON'].forEach(function(name) {
|
|
27
|
+
builder.defineMethod(name, function(...rest) {
|
|
28
|
+
return Date.prototype[name].apply(data, rest);
|
|
32
29
|
});
|
|
33
30
|
});
|
|
34
31
|
|
|
35
32
|
['setTime', 'setMilliseconds', 'setUTCMilliseconds', 'setSeconds',
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
builder.defineMethod(name, function
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
args[i] = arguments[i];
|
|
43
|
-
}
|
|
44
|
-
var newDate = new Date(data.valueOf());
|
|
45
|
-
newDate[name].apply(newDate, args);
|
|
33
|
+
'setUTCSeconds', 'setMinutes', 'setUTCMinutes', 'setHours', 'setUTCHours',
|
|
34
|
+
'setDate', 'setUTCDate', 'setMonth', 'setUTCMonth', 'setFullYear',
|
|
35
|
+
'setUTCFullYear', 'setYear'].forEach(function(name) {
|
|
36
|
+
builder.defineMethod(name, function(...rest) {
|
|
37
|
+
const newDate = new Date(data.valueOf());
|
|
38
|
+
newDate[name].apply(newDate, rest);
|
|
46
39
|
return Immutable.create(newDate, options);
|
|
47
40
|
});
|
|
48
41
|
});
|
package/lib/db.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
const debug = require('debug')('cosa:db');
|
|
2
|
-
const defaults = require('defaults');
|
|
3
2
|
const { ObjectId } = require('bson');
|
|
4
3
|
const { EventEmitter } = require('events');
|
|
5
4
|
const { MongoClient } = require('mongodb');
|
|
6
5
|
const {
|
|
7
|
-
curry, split, last, pipe,
|
|
6
|
+
curry, split, last, pipe, clamp, isPlainObject, isNilOrEmpty
|
|
8
7
|
} = require('omnibelt');
|
|
9
8
|
const Immutable = require('./immutable');
|
|
10
9
|
const RESULT_FIELDS = [ 'insertedId', 'insertedCount', 'insertedIds', 'matchedCount', 'modifiedCount', 'upsertedCount', 'upsertedId', 'deletedCount', 'upsertedIds' ];
|
|
@@ -111,7 +110,7 @@ class Database extends EventEmitter {
|
|
|
111
110
|
try {
|
|
112
111
|
this._client = new MongoClient(this._uri);
|
|
113
112
|
this._client = await this._client.connect();
|
|
114
|
-
|
|
113
|
+
const opts = {};
|
|
115
114
|
if (process.env.COSA_DB_READ_PREFERENCE) {
|
|
116
115
|
opts.readPreference = process.env.COSA_DB_READ_PREFERENCE;
|
|
117
116
|
}
|
|
@@ -180,55 +179,70 @@ class Database extends EventEmitter {
|
|
|
180
179
|
* @param {object} [options.count=false] - get a count of the items, instead of the items themselves.
|
|
181
180
|
* @param {object} [options.findOne=false] - Should a single item be returned.
|
|
182
181
|
* @param {object} [options.readPreference] - the read preference for the query with one of the read constants
|
|
182
|
+
* @param {number} [options.batchSize] - number of items per batch (default in mongo driver is 1000)
|
|
183
|
+
* @param {boolean} [options.noCursorTimeout] - boolan, if the cursor can time out after being idle, mongo driver default is false
|
|
184
|
+
* @param {number} [options.maxTimeMS] - maximum amount of time (in ms) this cursor is allowed to live
|
|
185
|
+
* @param {object} [options.session] - Mongo session or Cosa Mongo session wrapper
|
|
183
186
|
* @returns {Cursor} returns Cursor object
|
|
184
187
|
* @see {@link https://mongodb.github.io/node-mongodb-native/4.0/classes/findcursor.html}
|
|
185
188
|
* @see {@link https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#find}
|
|
186
189
|
* @see {@link https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#findOne}
|
|
187
190
|
* @see {@link https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#count}
|
|
188
|
-
* @see {@link https://github.
|
|
191
|
+
* @see {@link https://mongodb.github.io/node-mongodb-native/4.0/interfaces/findoptions.html#readpreference}
|
|
192
|
+
* @see {@link https://mongodb.github.io/node-mongodb-native/4.0/interfaces/findoptions.html#batchsize}
|
|
193
|
+
* @see {@link https://mongodb.github.io/node-mongodb-native/4.0/interfaces/findoptions.html#nocursortimeout}
|
|
194
|
+
* @see {@link https://mongodb.github.io/node-mongodb-native/4.0/interfaces/findoptions.html#maxtimems}
|
|
189
195
|
*/
|
|
190
|
-
async find(collectionName, query, options) {
|
|
191
|
-
options = defaults(options, {
|
|
192
|
-
projection: undefined,
|
|
193
|
-
sort: undefined,
|
|
194
|
-
skip: undefined,
|
|
195
|
-
limit: options && options.count ? null : 1000,
|
|
196
|
-
count: false,
|
|
197
|
-
findOne: false,
|
|
198
|
-
readPreference: undefined
|
|
199
|
-
});
|
|
196
|
+
async find(collectionName, query, options = {}) {
|
|
200
197
|
query = deserialize(query);
|
|
201
|
-
debug('getting db collection');
|
|
202
198
|
const collection = await this.collection(collectionName);
|
|
199
|
+
const session = options.session?.mongoSession || options.session;
|
|
203
200
|
if (options.count) {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
201
|
+
if (isNilOrEmpty(query) && !session) {
|
|
202
|
+
const countOptions = {
|
|
203
|
+
readPreference: options.readPreference,
|
|
204
|
+
maxTimeMS: options.maxTimeMS
|
|
205
|
+
};
|
|
206
|
+
debug(`db.${collection.collectionName}.estimatedDocumentCount`, query, countOptions);
|
|
207
|
+
const count = await collection.estimatedDocumentCount(countOptions);
|
|
208
208
|
return clamp(0, options.limit || Infinity, count - (options.skip || 0));
|
|
209
209
|
} else {
|
|
210
|
-
|
|
210
|
+
const countOptions = {
|
|
211
|
+
limit: options.limit,
|
|
212
|
+
skip: options.skip,
|
|
213
|
+
readPreference: options.readPreference,
|
|
214
|
+
maxTimeMS: options.maxTimeMS,
|
|
215
|
+
session
|
|
216
|
+
};
|
|
217
|
+
debug(`db.${collection.collectionName}.countDocuments`, query, countOptions);
|
|
218
|
+
return collection.countDocuments(query, countOptions);
|
|
211
219
|
}
|
|
212
220
|
} else if (options.findOne) {
|
|
213
221
|
const findOptions = {
|
|
214
222
|
projection: options.projection || options.fields,
|
|
215
|
-
limit: options.limit,
|
|
216
223
|
skip: options.skip,
|
|
217
224
|
sort: options.sort,
|
|
218
|
-
readPreference: options.readPreference
|
|
225
|
+
readPreference: options.readPreference,
|
|
226
|
+
noCursorTimeout: options.noCursorTimeout,
|
|
227
|
+
maxTimeMS: options.maxTimeMS,
|
|
228
|
+
session
|
|
219
229
|
};
|
|
220
230
|
debug(`db.${collection.collectionName}.findOne`, query, findOptions);
|
|
221
|
-
return collection.findOne(query,
|
|
231
|
+
return collection.findOne(query, findOptions);
|
|
222
232
|
} else {
|
|
223
233
|
const findOptions = {
|
|
224
234
|
projection: options.projection || options.fields,
|
|
225
|
-
limit: options.limit,
|
|
235
|
+
limit: options.limit === undefined ? 1000 : options.limit,
|
|
226
236
|
skip: options.skip,
|
|
227
237
|
sort: options.sort,
|
|
228
|
-
readPreference: options.readPreference
|
|
238
|
+
readPreference: options.readPreference,
|
|
239
|
+
batchSize: options.batchSize,
|
|
240
|
+
noCursorTimeout: options.noCursorTimeout,
|
|
241
|
+
maxTimeMS: options.maxTimeMS,
|
|
242
|
+
session
|
|
229
243
|
};
|
|
230
|
-
debug(`db.${collection.collectionName}.find
|
|
231
|
-
return collection.find(query,
|
|
244
|
+
debug(`db.${collection.collectionName}.find`, query, findOptions);
|
|
245
|
+
return collection.find(query, findOptions);
|
|
232
246
|
}
|
|
233
247
|
}
|
|
234
248
|
|
|
@@ -237,20 +251,20 @@ class Database extends EventEmitter {
|
|
|
237
251
|
* @param {string} collectionName - Name of the collection.
|
|
238
252
|
* @param {(object|Array)} docs - Documents objects to insert.
|
|
239
253
|
* @param {object} options - options on insert
|
|
240
|
-
* @param {object} [options.writeConcern] - the write
|
|
254
|
+
* @param {object} [options.writeConcern] - the write concern
|
|
255
|
+
* @param {object} [options.session] - Mongo session or Cosa Mongo session wrapper
|
|
241
256
|
* @returns {Promise} resolves with an object with results, and ops as keys
|
|
242
257
|
* @see {@link https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#insertmany}
|
|
243
258
|
* @see {@link https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#insertOne}
|
|
259
|
+
* @see {@link https://mongodb.github.io/node-mongodb-native/4.0/interfaces/insertoneoptions.html#writeconcern}
|
|
244
260
|
*/
|
|
245
|
-
async insert(collectionName, docs,
|
|
246
|
-
debug('getting db collection');
|
|
261
|
+
async insert(collectionName, docs, { writeConcern, session } = {}) {
|
|
247
262
|
docs = deserialize(docs);
|
|
248
263
|
const collection = await this.collection(collectionName);
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
}
|
|
253
|
-
const results = await insertOneOrMany(collection, Array.isArray(docs), docs, insertOpts);
|
|
264
|
+
const results = await insertOneOrMany(collection, Array.isArray(docs), docs, {
|
|
265
|
+
writeConcern,
|
|
266
|
+
session: session?.mongoSession || session
|
|
267
|
+
});
|
|
254
268
|
|
|
255
269
|
if (Array.isArray(docs)) {
|
|
256
270
|
docs.forEach((doc, i) => {
|
|
@@ -271,26 +285,23 @@ class Database extends EventEmitter {
|
|
|
271
285
|
* @param {object} update - Document properties to update.
|
|
272
286
|
* @param {object} [options] - Optional settings see mongo documentation
|
|
273
287
|
* @param {boolean} [options.multiple=false] - Should multiple documents be updated.
|
|
274
|
-
* @param {boolean} [options.upsert=false] - Should documents be inserted if they don't already exist
|
|
275
|
-
* @param {object} [options.writeConcern] - the write
|
|
288
|
+
* @param {boolean} [options.upsert=false] - Should documents be inserted if they don't already exist.
|
|
289
|
+
* @param {object} [options.writeConcern] - the write concern options
|
|
290
|
+
* @param {object} [options.session] - Mongo session or Cosa Mongo session wrapper
|
|
276
291
|
* @returns {Promise} resolves with an object with results, and ops as keys
|
|
277
292
|
* @see {@link https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#updateMany}
|
|
278
293
|
* @see {@link https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#updateOne}
|
|
294
|
+
* @see {@link https://mongodb.github.io/node-mongodb-native/4.0/interfaces/updateoptions.html#writeconcern}
|
|
279
295
|
*/
|
|
280
|
-
async update(collectionName, query, update, options) {
|
|
281
|
-
options = defaults(options, {
|
|
282
|
-
multiple: false,
|
|
283
|
-
upsert: false
|
|
284
|
-
});
|
|
285
|
-
debug('getting db collection');
|
|
296
|
+
async update(collectionName, query, update, options = {}) {
|
|
286
297
|
query = deserialize(query);
|
|
287
298
|
update = deserialize(update);
|
|
288
299
|
const collection = await this.collection(collectionName);
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
300
|
+
return updateOneOrMany(collection, options.multiple, query, update, {
|
|
301
|
+
upsert: options.upsert,
|
|
302
|
+
writeConcern: options.writeConcern,
|
|
303
|
+
session: options.session?.mongoSession || options.session
|
|
304
|
+
});
|
|
294
305
|
}
|
|
295
306
|
|
|
296
307
|
/**
|
|
@@ -299,23 +310,20 @@ class Database extends EventEmitter {
|
|
|
299
310
|
* @param {object} query - Query to find which documents to remove.
|
|
300
311
|
* @param {object} [options] - Optional settings see mongo documentation
|
|
301
312
|
* @param {boolean} [options.multiple=false] - Should multiple documents be removed.
|
|
302
|
-
* @param {object} [options.writeConcern] - the write
|
|
313
|
+
* @param {object} [options.writeConcern] - the write concern options
|
|
314
|
+
* @param {object} [options.session] - Mongo session or Cosa Mongo session wrapper
|
|
303
315
|
* @returns {Promise} resolves with an object with results, and ops as keys
|
|
304
316
|
* @see {@link https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#deleteMany}
|
|
305
317
|
* @see {@link https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#deleteOne}
|
|
318
|
+
* @see {@link https://mongodb.github.io/node-mongodb-native/4.0/interfaces/deleteoptions.html#writeconcern}
|
|
306
319
|
*/
|
|
307
|
-
async remove(collectionName, query, options) {
|
|
308
|
-
options = defaults(options, {
|
|
309
|
-
multiple: false
|
|
310
|
-
});
|
|
311
|
-
debug('getting db collection');
|
|
320
|
+
async remove(collectionName, query, options = {}) {
|
|
312
321
|
query = deserialize(query);
|
|
313
322
|
const collection = await this.collection(collectionName);
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
}
|
|
318
|
-
return deleteOneOrMany(collection, options.multiple, query, removeOpts);
|
|
323
|
+
return deleteOneOrMany(collection, options.multiple, query, {
|
|
324
|
+
writeConcern: options.writeConcern,
|
|
325
|
+
session: options.session?.mongoSession || options.session
|
|
326
|
+
});
|
|
319
327
|
}
|
|
320
328
|
|
|
321
329
|
/**
|
|
@@ -323,19 +331,24 @@ class Database extends EventEmitter {
|
|
|
323
331
|
* @param {string} collectionName - Name of the collection.
|
|
324
332
|
* @param {object} pipeline - Aggregation pipeline.
|
|
325
333
|
* @param {object} [options] - Optional settings see mongo documentation
|
|
326
|
-
* @param {
|
|
327
|
-
* @param {
|
|
334
|
+
* @param {object} [options.readPreference] - the read preference for the query with one of the read constants
|
|
335
|
+
* @param {number} [options.batchSize] - number of items per batch (default in mongo driver is 1000)
|
|
336
|
+
* @param {number} [options.maxTimeMS] - maximum amount of time (in ms) this cursor is allowed to live
|
|
337
|
+
* @param {object} [options.session] - Mongo session or Cosa Mongo session wrapper
|
|
328
338
|
* @returns {Promise} resolves with the result of the aggregation from mongo
|
|
329
339
|
* @see {@link https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#aggregate}
|
|
330
|
-
* @see {@link https://github.
|
|
340
|
+
* @see {@link https://mongodb.github.io/node-mongodb-native/4.0/interfaces/aggregateoptions.html#readpreference}
|
|
341
|
+
* @see {@link https://mongodb.github.io/node-mongodb-native/4.0/interfaces/aggregateoptions.html#batchsize}
|
|
342
|
+
* @see {@link https://mongodb.github.io/node-mongodb-native/4.0/interfaces/aggregateoptions.html#maxtimems}
|
|
331
343
|
*/
|
|
332
|
-
async aggregate(collectionName, pipeline, options) {
|
|
333
|
-
options = defaults(options, {
|
|
334
|
-
explain: undefined // do not use false that will actually cause an explain. https://github.com/mongodb/node-mongodb-native/pull/2626/files#diff-17118eb51bf767027b48c4456850f1b0b9efcd4be4322b5f26898a42731e4621R28
|
|
335
|
-
});
|
|
336
|
-
debug('getting db collection');
|
|
344
|
+
async aggregate(collectionName, pipeline, options = {}) {
|
|
337
345
|
const collection = await this.collection(collectionName);
|
|
338
|
-
return collection.aggregate(pipeline,
|
|
346
|
+
return collection.aggregate(pipeline, {
|
|
347
|
+
readPreference: options.readPreference,
|
|
348
|
+
batchSize: options.batchSize,
|
|
349
|
+
maxTimeMS: options.maxTimeMS,
|
|
350
|
+
session: options.session?.mongoSession || options.session
|
|
351
|
+
});
|
|
339
352
|
}
|
|
340
353
|
|
|
341
354
|
/**
|
|
@@ -345,15 +358,21 @@ class Database extends EventEmitter {
|
|
|
345
358
|
* @param {object} query - Query to find which documents evaluate.
|
|
346
359
|
* @param {object} [options] - Optional settings see mongo documentation
|
|
347
360
|
* @param {string} [options.readPreference] - the read preference for the query
|
|
361
|
+
* @param {number} [options.maxTimeMS] - maximum amount of time (in ms) this cursor is allowed to live
|
|
362
|
+
* @param {object} [options.session] - Mongo session or Cosa Mongo session wrapper
|
|
348
363
|
* @returns {Promise} resolves with the result of the distinct query from mongo
|
|
349
364
|
* @see {@link https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#distinct}
|
|
350
|
-
* @see {@link https://github.
|
|
365
|
+
* @see {@link https://mongodb.github.io/node-mongodb-native/4.0/interfaces/commandoperationoptions.html#readpreference}
|
|
366
|
+
* @see {@link https://mongodb.github.io/node-mongodb-native/4.0/interfaces/commandoperationoptions.html#maxtimems}
|
|
351
367
|
*/
|
|
352
|
-
async distinct(collectionName, key, query, options) {
|
|
353
|
-
debug('getting db collection');
|
|
368
|
+
async distinct(collectionName, key, query, options = {}) {
|
|
354
369
|
query = deserialize(query);
|
|
355
370
|
const collection = await this.collection(collectionName);
|
|
356
|
-
return collection.distinct(key, query,
|
|
371
|
+
return collection.distinct(key, query, {
|
|
372
|
+
readPreference: options.readPreference,
|
|
373
|
+
maxTimeMS: options.maxTimeMS,
|
|
374
|
+
session: options.session?.mongoSession || options.session
|
|
375
|
+
});
|
|
357
376
|
}
|
|
358
377
|
|
|
359
378
|
/**
|
|
@@ -362,25 +381,25 @@ class Database extends EventEmitter {
|
|
|
362
381
|
* @param {object} query - Query to find which documents evaluate.
|
|
363
382
|
* @param {object} replace - doc to save on the collection
|
|
364
383
|
* @param {object} [options] - Optional settings see mongo documentation
|
|
365
|
-
* @param {object} [options.writeConcern] - the write
|
|
384
|
+
* @param {object} [options.writeConcern] - the write concern options
|
|
385
|
+
* @param {object} [options.session] - Mongo session or Cosa Mongo session wrapper
|
|
366
386
|
* @returns {Promise} resolves with the result of the distinct query from mongo
|
|
367
387
|
* @see {@link http://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html#replace}
|
|
388
|
+
* @see {@link https://mongodb.github.io/node-mongodb-native/4.0/interfaces/replaceoptions.html#writeconcern}
|
|
368
389
|
*/
|
|
369
|
-
async replace(collectionName, query, replace,
|
|
390
|
+
async replace(collectionName, query, replace, { writeConcern, session } = {}) {
|
|
370
391
|
const collection = await this.collection(collectionName);
|
|
371
392
|
query = deserialize(query);
|
|
372
393
|
replace = deserialize(replace);
|
|
373
394
|
debug(`db.${collection.collectionName}.replaceOne`, query, replace);
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
}
|
|
378
|
-
const r = await collection.replaceOne(query, replace, replaceOpts);
|
|
395
|
+
const r = await collection.replaceOne(query, replace, {
|
|
396
|
+
writeConcern,
|
|
397
|
+
session: session?.mongoSession || session
|
|
398
|
+
});
|
|
379
399
|
r.ops = replace;
|
|
380
400
|
return normalizeResult(r);
|
|
381
401
|
}
|
|
382
402
|
|
|
383
|
-
|
|
384
403
|
}
|
|
385
404
|
|
|
386
405
|
module.exports = new Database();
|
package/lib/defined-object.js
CHANGED
|
@@ -1,54 +1,55 @@
|
|
|
1
|
-
|
|
1
|
+
const Immutable = require('./immutable');
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Immutable handler used to create an immutable object based on definition
|
|
4
|
+
* Immutable handler used to create an immutable object based on definition
|
|
5
5
|
* describing the object properties and methods.
|
|
6
6
|
* @name ImmutableDefinedObject
|
|
7
|
-
* @param {object} data
|
|
8
|
-
* @param {object} builder
|
|
9
|
-
* @param {object} options
|
|
10
|
-
* @param {object} options.definition
|
|
7
|
+
* @param {object} data - Underlying defined object
|
|
8
|
+
* @param {object} builder - Builder instance
|
|
9
|
+
* @param {object} options - Optional settings
|
|
10
|
+
* @param {object} options.definition - Defintion settings
|
|
11
11
|
* @param {object} [options.definition.properties] - Describes the properties of the immutable object.
|
|
12
12
|
* @param {object} [options.definition.virtuals] - Describes the virtual properties of the immutable object.
|
|
13
13
|
* @param {object} [options.definition.methods] - Describes the methods of the immutable object.
|
|
14
|
+
* @returns {undefined} no return value
|
|
14
15
|
*/
|
|
15
|
-
module.exports = function
|
|
16
|
+
module.exports = function(data, builder, options) {
|
|
16
17
|
|
|
17
18
|
if (options.definition) {
|
|
18
|
-
|
|
19
|
+
const definition = options.definition;
|
|
19
20
|
if (definition.name) {
|
|
20
21
|
builder.type = definition.name;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
if (definition.properties) {
|
|
24
|
-
Object.keys(definition.properties).forEach(function
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
Object.keys(definition.properties).forEach(function(prop) {
|
|
26
|
+
const propertyDef = definition.properties[prop];
|
|
27
|
+
const defaultVal = propertyDef.default;
|
|
27
28
|
if ('undefined' === typeof data[prop] && 'undefined' !== typeof defaultVal) {
|
|
28
29
|
data[prop] = 'function' === typeof defaultVal ? defaultVal.apply(data) : defaultVal;
|
|
29
30
|
}
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
let value = data[prop];
|
|
32
|
+
const getter = function() {
|
|
32
33
|
return (value = Immutable.create(value, { definition: propertyDef, clone: false }));
|
|
33
34
|
};
|
|
34
|
-
|
|
35
|
+
const getterOptions = {};
|
|
35
36
|
if (propertyDef && 'undefined' !== typeof propertyDef.enumerable) {
|
|
36
|
-
|
|
37
|
+
getterOptions.enumerable = propertyDef.enumerable;
|
|
37
38
|
}
|
|
38
|
-
builder.defineProperty(prop, getter,
|
|
39
|
+
builder.defineProperty(prop, getter, getterOptions);
|
|
39
40
|
});
|
|
40
41
|
}
|
|
41
42
|
if (definition.virtuals) {
|
|
42
|
-
Object.keys(definition.virtuals).forEach(function
|
|
43
|
-
|
|
44
|
-
builder.defineProperty(virtual, function
|
|
43
|
+
Object.keys(definition.virtuals).forEach(function(virtual) {
|
|
44
|
+
const virtualFunc = definition.virtuals[virtual];
|
|
45
|
+
builder.defineProperty(virtual, function() {
|
|
45
46
|
return Immutable.create(virtualFunc.call(data));
|
|
46
47
|
});
|
|
47
48
|
});
|
|
48
49
|
}
|
|
49
50
|
if (definition.methods) {
|
|
50
|
-
Object.keys(definition.methods).forEach(function
|
|
51
|
-
|
|
51
|
+
Object.keys(definition.methods).forEach(function(method) {
|
|
52
|
+
const methodFunc = definition.methods[method];
|
|
52
53
|
builder.defineMethod(method, methodFunc);
|
|
53
54
|
});
|
|
54
55
|
}
|
package/lib/immutable.js
CHANGED
|
@@ -1,24 +1,47 @@
|
|
|
1
|
-
|
|
2
|
-
var assign = require('object-assign');
|
|
3
|
-
var clone = require('clone');
|
|
1
|
+
const clone = require('clone');
|
|
4
2
|
|
|
5
|
-
|
|
3
|
+
const IMMUTABLE_TYPES = [ 'function', 'string', 'boolean', 'number', 'undefined' ];
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
const typeHandlers = [];
|
|
6
|
+
|
|
7
|
+
let Immutable;
|
|
8
|
+
const Builder = function(type, props) {
|
|
9
|
+
this.type = type || 'object';
|
|
10
|
+
this.props = props || {};
|
|
11
|
+
|
|
12
|
+
this.defineProperty = function(name, getterOrValue, options = {}) {
|
|
13
|
+
options = { enumerable: true, ...options };
|
|
14
|
+
options.get = ('function' === typeof getterOrValue) ?
|
|
15
|
+
getterOrValue : function() { return Immutable.create(getterOrValue); };
|
|
16
|
+
options.set = function() {
|
|
17
|
+
throw new Error(`Cannot modify ${name} of immutable ${this.type}`);
|
|
18
|
+
};
|
|
19
|
+
this.props[name] = options;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
this.defineMethod = function(name, func, options = {}) {
|
|
23
|
+
this.props[name] = {
|
|
24
|
+
enumerable: false,
|
|
25
|
+
...options,
|
|
26
|
+
value: func
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
};
|
|
8
30
|
|
|
9
31
|
/**
|
|
10
32
|
* Static class for working with immutable data types.
|
|
11
33
|
* @static Immutable
|
|
12
34
|
*/
|
|
13
|
-
|
|
35
|
+
Immutable = {
|
|
14
36
|
|
|
15
37
|
/**
|
|
16
|
-
* Registers an immutable handler for the given data type. Handlers are used
|
|
38
|
+
* Registers an immutable handler for the given data type. Handlers are used
|
|
17
39
|
* to wrap specific data types. For example BSON ObjectIds or Dates.
|
|
18
|
-
* @param {string} [type=*]
|
|
19
|
-
* @param {function} handler
|
|
40
|
+
* @param {string} [type=*] - the name of the type
|
|
41
|
+
* @param {function} handler - the handler for the type
|
|
42
|
+
* @returns {undefined} - no return value
|
|
20
43
|
*/
|
|
21
|
-
use: function
|
|
44
|
+
use: function(type, handler) {
|
|
22
45
|
if (!handler) {
|
|
23
46
|
handler = type;
|
|
24
47
|
type = '*';
|
|
@@ -31,21 +54,21 @@ var Immutable = {
|
|
|
31
54
|
|
|
32
55
|
/**
|
|
33
56
|
* Returns `true` if the given value is an Immutable.
|
|
34
|
-
* @param {object} value
|
|
35
|
-
* @returns {boolean}
|
|
57
|
+
* @param {object} value - a value to check
|
|
58
|
+
* @returns {boolean} - if it is an immutable instance
|
|
36
59
|
*/
|
|
37
|
-
isImmutable: function
|
|
60
|
+
isImmutable: function(value) {
|
|
38
61
|
if ('undefined' === typeof value || value === null) { return true; }
|
|
39
62
|
return (IMMUTABLE_TYPES.indexOf(typeof value) > -1 || !!value.__immutable);
|
|
40
63
|
},
|
|
41
64
|
|
|
42
65
|
/**
|
|
43
66
|
* Returns `true` if the given value is an Immutable of the given type.
|
|
44
|
-
* @param {object} value
|
|
45
|
-
* @param {string} type
|
|
46
|
-
* @returns {boolean}
|
|
67
|
+
* @param {object} value - a value to check
|
|
68
|
+
* @param {string} type - type to check against
|
|
69
|
+
* @returns {boolean} - if the value is an immutable instance of that type
|
|
47
70
|
*/
|
|
48
|
-
isImmutableType: function
|
|
71
|
+
isImmutableType: function(value, type) {
|
|
49
72
|
return value && value.__immutable && value.__type.toLowerCase() === type.toLowerCase();
|
|
50
73
|
},
|
|
51
74
|
|
|
@@ -53,36 +76,33 @@ var Immutable = {
|
|
|
53
76
|
* Creates an immutable object based on the given data.
|
|
54
77
|
* @param {object} data - Object to make immutable.
|
|
55
78
|
* @param {object} [options] - Options passed to the immutable handler for the given data type.
|
|
56
|
-
* @return {object}
|
|
79
|
+
* @return {object} - the immutable wrapped object
|
|
57
80
|
*/
|
|
58
|
-
create: function
|
|
81
|
+
create: function(data, options = {}) {
|
|
59
82
|
if (Immutable.isImmutable(data)) {
|
|
60
83
|
return data;
|
|
61
84
|
}
|
|
62
85
|
|
|
63
|
-
options =
|
|
64
|
-
clone: true
|
|
65
|
-
});
|
|
86
|
+
options = { clone: true, ...options };
|
|
66
87
|
|
|
67
88
|
if (options.clone) {
|
|
68
89
|
data = clone(data);
|
|
69
90
|
}
|
|
70
91
|
|
|
71
|
-
|
|
92
|
+
const builder = new Builder('object', {});
|
|
72
93
|
builder.defineProperty('__immutable', true, { enumerable: false });
|
|
73
94
|
|
|
74
|
-
builder.defineMethod('toObject', function
|
|
95
|
+
builder.defineMethod('toObject', function() {
|
|
75
96
|
return data;
|
|
76
97
|
});
|
|
77
98
|
|
|
78
|
-
builder.defineMethod('mutate', function
|
|
79
|
-
|
|
80
|
-
var newOptions = assign({}, options, { clone: false });
|
|
99
|
+
builder.defineMethod('mutate', function(cb) {
|
|
100
|
+
const obj = clone(data);
|
|
81
101
|
cb.apply(obj);
|
|
82
|
-
return Immutable.create(obj,
|
|
102
|
+
return Immutable.create(obj, { ...options, clone: false });
|
|
83
103
|
});
|
|
84
104
|
|
|
85
|
-
typeHandlers.forEach(function
|
|
105
|
+
typeHandlers.forEach(function(typeHandler) {
|
|
86
106
|
if ('string' === typeof typeHandler.type) {
|
|
87
107
|
if (typeHandler.type === '*' || typeof data === typeHandler.type) {
|
|
88
108
|
return typeHandler.handler(data, builder, options);
|
|
@@ -102,31 +122,7 @@ var Immutable = {
|
|
|
102
122
|
return Object.freeze(
|
|
103
123
|
Object.create(Object.prototype, builder.props)
|
|
104
124
|
);
|
|
105
|
-
|
|
106
125
|
}
|
|
107
|
-
|
|
108
126
|
};
|
|
109
127
|
|
|
110
128
|
module.exports = Immutable;
|
|
111
|
-
|
|
112
|
-
var Builder = function (type, props) {
|
|
113
|
-
this.type = type || 'object';
|
|
114
|
-
this.props = props || {};
|
|
115
|
-
|
|
116
|
-
this.defineProperty = function (name, getterOrValue, options) {
|
|
117
|
-
options = defaults(options, { enumerable: true });
|
|
118
|
-
options.get = ('function' === typeof getterOrValue) ?
|
|
119
|
-
getterOrValue : function () { return Immutable.create(getterOrValue); };
|
|
120
|
-
options.set = function () {
|
|
121
|
-
throw new Error('Cannot modify ' + name + ' of immutable ' + this.type);
|
|
122
|
-
};
|
|
123
|
-
this.props[name] = options;
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
this.defineMethod = function (name, func, options) {
|
|
127
|
-
options = defaults(options, { enumerable: false });
|
|
128
|
-
options.value = func;
|
|
129
|
-
this.props[name] = options;
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
};
|
package/lib/model.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
const assign = require('object-assign');
|
|
2
1
|
const joi = require('@hapi/joi');
|
|
3
2
|
const etag = require('etag');
|
|
4
3
|
const debug = require('debug')('cosa:model');
|
|
5
4
|
const objectPath = require('object-path');
|
|
6
|
-
const defaults = require('defaults');
|
|
7
5
|
const clone = require('clone');
|
|
8
6
|
const { EJSON } = require('bson');
|
|
9
7
|
const Cursor = require('./cursor');
|
|
@@ -20,14 +18,6 @@ Immutable.use(require('./object'));
|
|
|
20
18
|
|
|
21
19
|
const isNotVirtual = complement(pathEq([ 'type' ], 'virtual'));
|
|
22
20
|
const removeMeta = omit(['__modified', '__original']);
|
|
23
|
-
const onlyJoiOptions = pick(['abortEarly', 'convert', 'allowUnknown', 'skipFunctions', 'stripUnknown']);
|
|
24
|
-
const defaultJoiOptions = {
|
|
25
|
-
abortEarly: false,
|
|
26
|
-
convert: false,
|
|
27
|
-
allowUnknown: false,
|
|
28
|
-
skipFunctions: true,
|
|
29
|
-
stripUnknown: false
|
|
30
|
-
};
|
|
31
21
|
|
|
32
22
|
const db = require('./db');
|
|
33
23
|
// functions that are defined later on.
|
|
@@ -118,7 +108,6 @@ const _create = (data, definition) => {
|
|
|
118
108
|
};
|
|
119
109
|
|
|
120
110
|
const _saveHelper = async function(context, options = {}, explicitId) {
|
|
121
|
-
options = defaults(options, { waitAfterSave: false });
|
|
122
111
|
if (!context.isNew() && !context.isModified()) {
|
|
123
112
|
return context;
|
|
124
113
|
}
|
|
@@ -127,7 +116,7 @@ const _create = (data, definition) => {
|
|
|
127
116
|
await context.beforeSave.apply(obj, [options]);
|
|
128
117
|
}
|
|
129
118
|
obj = await context._validate(obj, options);
|
|
130
|
-
const original = obj.__original;
|
|
119
|
+
const original = obj.__original || null;
|
|
131
120
|
obj = removeMeta(obj);
|
|
132
121
|
const newEtag = etag(JSON.stringify(obj));
|
|
133
122
|
const collection = definition.collection;
|
|
@@ -170,10 +159,27 @@ const _create = (data, definition) => {
|
|
|
170
159
|
obj = Array.isArray(result.ops) ? result.ops[0] : result.ops;
|
|
171
160
|
newOrUpdatedModel = _create(obj, definition);
|
|
172
161
|
}
|
|
173
|
-
if (
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
162
|
+
if (options.session) {
|
|
163
|
+
if ('function' === typeof newOrUpdatedModel.afterSave) {
|
|
164
|
+
await newOrUpdatedModel.afterSave(original, options);
|
|
165
|
+
}
|
|
166
|
+
if ('function' === typeof newOrUpdatedModel.afterSaveCommit) {
|
|
167
|
+
options.session.afterCommits.push(() => newOrUpdatedModel.afterSaveCommit(original, options));
|
|
168
|
+
}
|
|
169
|
+
if ('function' === typeof newOrUpdatedModel.afterSaveAbort) {
|
|
170
|
+
options.session.afterAborts.push(() => newOrUpdatedModel.afterSaveAbort(original, options));
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
let chain = Promise.resolve();
|
|
174
|
+
if ('function' === typeof newOrUpdatedModel.afterSave) {
|
|
175
|
+
chain = chain.then(() => newOrUpdatedModel.afterSave(original, options));
|
|
176
|
+
}
|
|
177
|
+
if ('function' === typeof newOrUpdatedModel.afterSaveCommit) {
|
|
178
|
+
chain = chain.then(() => newOrUpdatedModel.afterSaveCommit(original, options));
|
|
179
|
+
}
|
|
180
|
+
if (options.waitAfterSave) {
|
|
181
|
+
await chain;
|
|
182
|
+
}
|
|
177
183
|
}
|
|
178
184
|
return newOrUpdatedModel;
|
|
179
185
|
};
|
|
@@ -213,16 +219,15 @@ const _create = (data, definition) => {
|
|
|
213
219
|
return this.mutate(function() {
|
|
214
220
|
let paths;
|
|
215
221
|
if ('object' === typeof pathOrObj && !Array.isArray(pathOrObj)) {
|
|
216
|
-
options =
|
|
217
|
-
assign(this, pathOrObj);
|
|
222
|
+
options = value;
|
|
223
|
+
Object.assign(this, pathOrObj);
|
|
218
224
|
paths = Object.keys(pathOrObj);
|
|
219
225
|
} else {
|
|
220
|
-
options = defaults(options, { silent: false });
|
|
221
226
|
objectPath.set(this, pathOrObj, value);
|
|
222
227
|
paths = [pathOrObj];
|
|
223
228
|
}
|
|
224
229
|
|
|
225
|
-
if (!options
|
|
230
|
+
if (!options?.silent) {
|
|
226
231
|
_markAsModified(this, paths, original);
|
|
227
232
|
}
|
|
228
233
|
});
|
|
@@ -240,35 +245,24 @@ const _create = (data, definition) => {
|
|
|
240
245
|
return objectPath.has(this, path);
|
|
241
246
|
};
|
|
242
247
|
|
|
243
|
-
definition.methods.toJSON = function(
|
|
244
|
-
// can only have include or exclude not both, should there be an error thrown if both are given?
|
|
245
|
-
// probably not
|
|
246
|
-
options = defaults(options, {
|
|
247
|
-
virtuals: true,
|
|
248
|
-
extended: true,
|
|
249
|
-
exclude: undefined, // array of properties to exclude from the json object
|
|
250
|
-
include: undefined, // array of white listed properties to include in the json object
|
|
251
|
-
transform: undefined // function that accepts json object and returns a transformed version
|
|
252
|
-
});
|
|
253
|
-
// TODO move this out of here, there are no references to this, it's just mutation...recursive
|
|
254
|
-
|
|
248
|
+
definition.methods.toJSON = function({ virtuals = true, extended = true, exclude, include, transform } = {}) {
|
|
255
249
|
let json = removeMeta(this.toObject());
|
|
256
250
|
json = clone(json); // not 100% sure we need to clone
|
|
257
251
|
|
|
258
|
-
if (
|
|
252
|
+
if (virtuals) {
|
|
259
253
|
addVirtuals(definition, json);
|
|
260
254
|
}
|
|
261
|
-
if (
|
|
255
|
+
if (extended) {
|
|
262
256
|
json = EJSON.serialize(json);
|
|
263
257
|
}
|
|
264
258
|
json = _serialize(json);
|
|
265
|
-
if (
|
|
266
|
-
json = omit(
|
|
267
|
-
} else if (
|
|
268
|
-
json = pick(
|
|
259
|
+
if (exclude) {
|
|
260
|
+
json = omit(exclude, json);
|
|
261
|
+
} else if (include) {
|
|
262
|
+
json = pick(include, json);
|
|
269
263
|
}
|
|
270
|
-
if (
|
|
271
|
-
json =
|
|
264
|
+
if (transform) {
|
|
265
|
+
json = transform(json);
|
|
272
266
|
}
|
|
273
267
|
return json;
|
|
274
268
|
};
|
|
@@ -277,10 +271,21 @@ const _create = (data, definition) => {
|
|
|
277
271
|
return this._validate(this.toObject(), options);
|
|
278
272
|
};
|
|
279
273
|
|
|
280
|
-
definition.methods._validate = async function(obj,
|
|
281
|
-
|
|
274
|
+
definition.methods._validate = async function(obj, {
|
|
275
|
+
abortEarly = false,
|
|
276
|
+
convert = false,
|
|
277
|
+
allowUnknown = false,
|
|
278
|
+
skipFunctions = true,
|
|
279
|
+
stripUnknown = false
|
|
280
|
+
} = {} ) {
|
|
282
281
|
try {
|
|
283
|
-
return await definition._schema.validateAsync(obj,
|
|
282
|
+
return await definition._schema.validateAsync(obj, {
|
|
283
|
+
abortEarly,
|
|
284
|
+
convert,
|
|
285
|
+
allowUnknown,
|
|
286
|
+
skipFunctions,
|
|
287
|
+
stripUnknown
|
|
288
|
+
});
|
|
284
289
|
} catch (err) {
|
|
285
290
|
throw errors.Validation(err);
|
|
286
291
|
}
|
|
@@ -297,8 +302,7 @@ const _create = (data, definition) => {
|
|
|
297
302
|
return _saveHelper(this, options, new ObjectId(id));
|
|
298
303
|
};
|
|
299
304
|
|
|
300
|
-
definition.methods.remove = async function(options) {
|
|
301
|
-
options = defaults(options, { waitAfterRemove: false });
|
|
305
|
+
definition.methods.remove = async function(options = {}) {
|
|
302
306
|
const collection = definition.collection;
|
|
303
307
|
const query = { _id: this._id, _etag: this._etag };
|
|
304
308
|
if ('function' === typeof this.beforeRemove) {
|
|
@@ -310,10 +314,30 @@ const _create = (data, definition) => {
|
|
|
310
314
|
throw errors.Conflict({ message: 'Document remove conflict' });
|
|
311
315
|
}
|
|
312
316
|
debug(`remove from ${collection} successful`);
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
if (
|
|
317
|
+
|
|
318
|
+
if (options.session) {
|
|
319
|
+
if ('function' === typeof this.afterRemove) {
|
|
320
|
+
await this.afterRemove(options);
|
|
321
|
+
}
|
|
322
|
+
if ('function' === typeof this.afterRemoveCommit) {
|
|
323
|
+
options.session.afterCommits.push(() => this.afterRemoveCommit(options));
|
|
324
|
+
}
|
|
325
|
+
if ('function' === typeof this.afterRemoveAbort) {
|
|
326
|
+
options.session.afterAborts.push(() => this.afterRemoveAbort(options));
|
|
327
|
+
}
|
|
328
|
+
} else {
|
|
329
|
+
let chain = Promise.resolve();
|
|
330
|
+
if ('function' === typeof this.afterRemove) {
|
|
331
|
+
chain = chain.then(() => this.afterRemove(options));
|
|
332
|
+
}
|
|
333
|
+
if ('function' === typeof this.afterRemoveCommit) {
|
|
334
|
+
chain = chain.then(() => this.afterRemoveCommit(options));
|
|
335
|
+
}
|
|
336
|
+
if (options.waitAfterRemove) {
|
|
337
|
+
await chain;
|
|
338
|
+
}
|
|
316
339
|
}
|
|
340
|
+
|
|
317
341
|
return result;
|
|
318
342
|
};
|
|
319
343
|
|
|
@@ -342,11 +366,12 @@ const _create = (data, definition) => {
|
|
|
342
366
|
*/
|
|
343
367
|
const define = (definition) => {
|
|
344
368
|
|
|
345
|
-
definition =
|
|
369
|
+
definition = {
|
|
346
370
|
properties: {},
|
|
347
371
|
methods: {},
|
|
348
|
-
virtuals: {}
|
|
349
|
-
|
|
372
|
+
virtuals: {},
|
|
373
|
+
...definition
|
|
374
|
+
};
|
|
350
375
|
|
|
351
376
|
if (!definition.collection && !definition.abstract) {
|
|
352
377
|
throw new Error('A model must have a collection unless defined as abstract');
|
|
@@ -376,7 +401,7 @@ const define = (definition) => {
|
|
|
376
401
|
|
|
377
402
|
const applyGlobalWhere = (query, options) => {
|
|
378
403
|
return options.bypassGlobalWhere || !definition.where ?
|
|
379
|
-
query :
|
|
404
|
+
query : { ...definition.where, ...(query || {}) };
|
|
380
405
|
};
|
|
381
406
|
|
|
382
407
|
return {
|
|
@@ -398,11 +423,10 @@ const define = (definition) => {
|
|
|
398
423
|
},
|
|
399
424
|
|
|
400
425
|
count: (query, options = {}) => {
|
|
401
|
-
options = assign({}, options, { count: true });
|
|
402
426
|
query = applyGlobalWhere(query, options);
|
|
403
427
|
const collection = definition.collection;
|
|
404
428
|
debug(`counting ${JSON.stringify(query)} in ${collection}`);
|
|
405
|
-
return db.find(collection, query, options);
|
|
429
|
+
return db.find(collection, query, { ...options, count: true });
|
|
406
430
|
},
|
|
407
431
|
|
|
408
432
|
find: async (query, options = {}) => {
|
|
@@ -417,19 +441,17 @@ const define = (definition) => {
|
|
|
417
441
|
},
|
|
418
442
|
|
|
419
443
|
findOne: async (query, options = {}) => {
|
|
420
|
-
options = assign({}, options, { findOne: true });
|
|
421
444
|
query = applyGlobalWhere(query, options);
|
|
422
445
|
const collection = definition.collection;
|
|
423
446
|
debug(`finding one${JSON.stringify(query)} in ${collection}`);
|
|
424
|
-
const result = await db.find(collection, query, options);
|
|
447
|
+
const result = await db.find(collection, query, { ...options, findOne: true });
|
|
425
448
|
return !result ? null : _create(result, definition);
|
|
426
449
|
},
|
|
427
450
|
|
|
428
|
-
update: (query, update, options = {}) => {
|
|
429
|
-
options = defaults(options, { autoSet: true });
|
|
451
|
+
update: (query, update, { autoSet = true, ...options } = {}) => {
|
|
430
452
|
query = applyGlobalWhere(query, options);
|
|
431
453
|
const collection = definition.collection;
|
|
432
|
-
if (
|
|
454
|
+
if (autoSet) {
|
|
433
455
|
update = { $set: update };
|
|
434
456
|
}
|
|
435
457
|
debug(`updating ${JSON.stringify(query)} from ${collection}`);
|
|
@@ -474,9 +496,9 @@ _extend = (definition, superDef) => {
|
|
|
474
496
|
if (!newDef.collection) {
|
|
475
497
|
newDef.collection = superDef.collection;
|
|
476
498
|
}
|
|
477
|
-
newDef.properties =
|
|
478
|
-
newDef.methods =
|
|
479
|
-
newDef.virtuals =
|
|
499
|
+
newDef.properties = { ...(superDef.properties || {}), ...(newDef.properties || {}) };
|
|
500
|
+
newDef.methods = { ...(superDef.methods || {}), ...(newDef.methods || {}) };
|
|
501
|
+
newDef.virtuals = { ...(superDef.virtuals || {}), ...(newDef.virtuals || {}) };
|
|
480
502
|
return define(newDef);
|
|
481
503
|
};
|
|
482
504
|
|
package/lib/object.js
CHANGED
|
@@ -4,20 +4,23 @@ const addProp = function(name, builder, data) {
|
|
|
4
4
|
if (builder.props[name]) { return; }
|
|
5
5
|
let value = data[name];
|
|
6
6
|
if ('function' === typeof value) { return; }
|
|
7
|
-
const getter = function
|
|
7
|
+
const getter = function() {
|
|
8
8
|
return (value = Immutable.create(value, { clone: false }));
|
|
9
9
|
};
|
|
10
10
|
builder.defineProperty(name, getter);
|
|
11
|
-
}
|
|
11
|
+
};
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Immutable handler used to wrap a simple javascript object.
|
|
15
15
|
* @name ImmutableObject
|
|
16
|
-
* @param {object} data
|
|
17
|
-
* @param {object} builder
|
|
16
|
+
* @param {object} data - Underlying array
|
|
17
|
+
* @param {object} builder - Builder instance
|
|
18
|
+
* @returns {undefined} no return value
|
|
18
19
|
*/
|
|
19
|
-
module.exports = function
|
|
20
|
-
for (
|
|
21
|
-
|
|
20
|
+
module.exports = function(data, builder) {
|
|
21
|
+
for (const p in data) {
|
|
22
|
+
if (Object.prototype.hasOwnProperty.call(data, p)) {
|
|
23
|
+
addProp(p, builder, data);
|
|
24
|
+
}
|
|
22
25
|
}
|
|
23
26
|
};
|
package/lib/session.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const db = require('./db');
|
|
2
|
+
const { forEachSerialP } = require('omnibelt');
|
|
3
|
+
|
|
4
|
+
const createSession = async () => {
|
|
5
|
+
const afterCommits = [];
|
|
6
|
+
const afterAborts = [];
|
|
7
|
+
if (!db._client) {
|
|
8
|
+
await db.init();
|
|
9
|
+
}
|
|
10
|
+
const session = await db._client.startSession();
|
|
11
|
+
|
|
12
|
+
const abortTransaction = async () => {
|
|
13
|
+
let error;
|
|
14
|
+
await session.abortTransaction();
|
|
15
|
+
await session.endSession();
|
|
16
|
+
await forEachSerialP(async (afterAborted) => {
|
|
17
|
+
try {
|
|
18
|
+
await afterAborted(error);
|
|
19
|
+
} catch (err) {
|
|
20
|
+
if (!err) {
|
|
21
|
+
error = err;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}, afterAborts);
|
|
25
|
+
if (error) { throw error; }
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
get mongoSession() {
|
|
30
|
+
return session;
|
|
31
|
+
},
|
|
32
|
+
startTransaction: () => {
|
|
33
|
+
return session.startTransaction();
|
|
34
|
+
},
|
|
35
|
+
commitTransaction: async () => {
|
|
36
|
+
let error;
|
|
37
|
+
await session.commitTransaction();
|
|
38
|
+
await session.endSession();
|
|
39
|
+
await forEachSerialP(async (afterCommitFunc) => {
|
|
40
|
+
try {
|
|
41
|
+
await afterCommitFunc();
|
|
42
|
+
} catch (err) {
|
|
43
|
+
if (!error) {
|
|
44
|
+
error = err;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}, afterCommits);
|
|
48
|
+
if (error) { throw error; }
|
|
49
|
+
},
|
|
50
|
+
abortTransaction,
|
|
51
|
+
afterCommits,
|
|
52
|
+
afterAborts
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
module.exports = {
|
|
57
|
+
createSession
|
|
58
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cosa",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.2.0",
|
|
4
4
|
"description": "Cosa Models for MongoDB",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"engines": {
|
|
@@ -35,39 +35,30 @@
|
|
|
35
35
|
"lib"
|
|
36
36
|
],
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"bson": "~4.
|
|
38
|
+
"bson": "~4.6.4",
|
|
39
39
|
"clone": "^2.1.2",
|
|
40
|
-
"debug": "^4.3.
|
|
41
|
-
"defaults": "^1.0.3",
|
|
40
|
+
"debug": "^4.3.4",
|
|
42
41
|
"error": "^7.0.2",
|
|
43
42
|
"etag": "^1.8.1",
|
|
44
43
|
"@hapi/joi": "^17.1.1",
|
|
45
|
-
"mongodb": "~4.
|
|
46
|
-
"object-
|
|
47
|
-
"object-path": "^0.11.7",
|
|
44
|
+
"mongodb": "~4.7.0",
|
|
45
|
+
"object-path": "^0.11.8",
|
|
48
46
|
"omnibelt": "^2.1.0"
|
|
49
47
|
},
|
|
50
48
|
"devDependencies": {
|
|
51
49
|
"@losant/eslint-config-losant": "^1.4.3",
|
|
52
|
-
"husky": "^
|
|
53
|
-
"lint-staged": "^
|
|
54
|
-
"chai": "^4.3.
|
|
50
|
+
"husky": "^8.0.1",
|
|
51
|
+
"lint-staged": "^13.0.2",
|
|
52
|
+
"chai": "^4.3.6",
|
|
55
53
|
"chai-as-promised": "^7.1.1",
|
|
56
54
|
"chai-datetime": "^1.8.0",
|
|
57
55
|
"documentation": "^13.2.5",
|
|
58
|
-
"mocha": "^
|
|
56
|
+
"mocha": "^10.0.0",
|
|
59
57
|
"string-template": "^1.0.0"
|
|
60
58
|
},
|
|
61
59
|
"eslintConfig": {
|
|
62
60
|
"extends": "@losant/eslint-config-losant/env/node"
|
|
63
61
|
},
|
|
64
|
-
"eslintIgnore": [
|
|
65
|
-
"lib/array.js",
|
|
66
|
-
"lib/date.js",
|
|
67
|
-
"lib/defined-object.js",
|
|
68
|
-
"lib/immutable.js",
|
|
69
|
-
"lib/object.js"
|
|
70
|
-
],
|
|
71
62
|
"mocha": {
|
|
72
63
|
"require": "chai",
|
|
73
64
|
"reporter": "spec",
|