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 CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2021 Losant IoT, Inc.
3
+ Copyright (c) 2022 Losant IoT, Inc.
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
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 (data, builder, options) {
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.definition ? options.definition.items || {} : {};
17
+ const itemDefinition = options?.definition?.items || {};
17
18
  const itemOptions = { definition: itemDefinition, clone: false };
18
19
 
19
-
20
- ['forEach', 'map', 'filter', 'some', 'every'].forEach(function (name) {
21
- builder.defineMethod(name, function (cb, thisArg) {
22
- var immuArr = Immutable.create(data, options);
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 (name) {
30
- builder.defineMethod(name, function (cb, initialValue) {
31
- var immuArr = Immutable.create(data, options);
32
- return Immutable.create(Array.prototype[name].call(data, function (prev, cur, index) {
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
- 'toString', 'toLocaleString'].forEach(function (name) {
40
- builder.defineMethod(name, function () {
41
- return Immutable.create(Array.prototype[name].apply(data, arguments), options);
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, arguments), options);
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
- var args = Array(arguments.length);
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 (cb) {
58
- var newArr = clone(data);
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 (a, b) {
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
- var args = Array(arguments.length);
69
- for (var i = 0, l = arguments.length; i < l; i++) {
70
- args[i] = arguments[i];
71
- }
72
- var start = args[0];
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
- var newArr = clone(data);
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
- var Immutable = require('./immutable');
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 (data, builder, options) {
11
+ module.exports = function(data, builder, options) {
11
12
  builder.type = 'Date';
12
13
 
13
- builder.defineMethod('mutate', function (cb) {
14
- var newDate = new Date(data.valueOf());
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
- 'toLocaleString', 'toLocaleDateString', 'toLocaleTimeString', 'valueOf',
21
- 'getTime', 'getFullYear', 'getUTCFullYear', 'toGMTString', 'getMonth',
22
- 'getUTCMonth', 'getDate', 'getUTCDate', 'getDay', 'getUTCDay', 'getHours',
23
- 'getUTCHours', 'getMinutes', 'getUTCMinutes', 'getSeconds', 'getUTCSeconds',
24
- 'getMilliseconds', 'getUTCMilliseconds', 'getTimezoneOffset', 'getYear',
25
- 'toJSON'].forEach(function (name) {
26
- builder.defineMethod(name, function () {
27
- var args = Array(arguments.length);
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
- 'setUTCSeconds', 'setMinutes', 'setUTCMinutes', 'setHours', 'setUTCHours',
37
- 'setDate', 'setUTCDate', 'setMonth', 'setUTCMonth', 'setFullYear',
38
- 'setUTCFullYear', 'setYear'].forEach(function (name) {
39
- builder.defineMethod(name, function () {
40
- var args = Array(arguments.length);
41
- for (var i = 0, l = args.length; i < l; i++) {
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, isEmpty, clamp, isPlainObject
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
- let opts;
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.com/mongodb/node-mongodb-native/blob/357cbf689735c2447bfb05d73c142f1a5b88ca91/lib/read_preference.js#L69}
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
- const countOptions = { limit: options.limit, skip: options.skip };
205
- debug(`db.${collection.collectionName}.countDocuments`, query, countOptions);
206
- if (!query || isEmpty(query)) {
207
- const count = await collection.estimatedDocumentCount();
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
- return collection.countDocuments(query, options);
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, options);
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 (toArray)`, query, findOptions);
231
- return collection.find(query, options);
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 conern options https://mongodb.github.io/node-mongodb-native/4.0/classes/writeconcern.html
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, options) {
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
- let insertOpts;
250
- if (options?.writeConcern) {
251
- insertOpts = { writeConcern: options.writeConcern };
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 conern options https://mongodb.github.io/node-mongodb-native/4.0/classes/writeconcern.html
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
- const updateOptions = { upsert: options.upsert };
290
- if (options.writeConcern) {
291
- updateOptions.writeConcern = options.writeConcern;
292
- }
293
- return updateOneOrMany(collection, options.multiple, query, update, updateOptions);
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 conern options https://mongodb.github.io/node-mongodb-native/4.0/classes/writeconcern.html
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
- let removeOpts;
315
- if (options.writeConcern) {
316
- removeOpts = { writeConcern: options.writeConcern };
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 {boolean} [options.explain=false] - Should should the execution plan be returned.
327
- * @param {object} [options.readPreference] - the read preference for the query
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.com/mongodb/node-mongodb-native/blob/357cbf689735c2447bfb05d73c142f1a5b88ca91/lib/read_preference.js#L69}
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, options);
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.com/mongodb/node-mongodb-native/blob/357cbf689735c2447bfb05d73c142f1a5b88ca91/lib/read_preference.js#L69}
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, options);
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 conern options https://mongodb.github.io/node-mongodb-native/4.0/classes/writeconcern.html
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, options) {
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
- let replaceOpts;
375
- if (options?.writeConcern) {
376
- replaceOpts = { writeConcern: options.writeConcern };
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();
@@ -1,54 +1,55 @@
1
- var Immutable = require('./immutable');
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 (data, builder, options) {
16
+ module.exports = function(data, builder, options) {
16
17
 
17
18
  if (options.definition) {
18
- var definition = options.definition;
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 (prop) {
25
- var propertyDef = definition.properties[prop];
26
- var defaultVal = propertyDef.default;
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
- var value = data[prop];
31
- var getter = function () {
31
+ let value = data[prop];
32
+ const getter = function() {
32
33
  return (value = Immutable.create(value, { definition: propertyDef, clone: false }));
33
34
  };
34
- var options = {};
35
+ const getterOptions = {};
35
36
  if (propertyDef && 'undefined' !== typeof propertyDef.enumerable) {
36
- options.enumerable = propertyDef.enumerable;
37
+ getterOptions.enumerable = propertyDef.enumerable;
37
38
  }
38
- builder.defineProperty(prop, getter, options);
39
+ builder.defineProperty(prop, getter, getterOptions);
39
40
  });
40
41
  }
41
42
  if (definition.virtuals) {
42
- Object.keys(definition.virtuals).forEach(function (virtual) {
43
- var virtualFunc = definition.virtuals[virtual];
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 (method) {
51
- var methodFunc = definition.methods[method];
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
- var defaults = require('defaults');
2
- var assign = require('object-assign');
3
- var clone = require('clone');
1
+ const clone = require('clone');
4
2
 
5
- var IMMUTABLE_TYPES = [ 'function', 'string', 'boolean', 'number', 'undefined' ];
3
+ const IMMUTABLE_TYPES = [ 'function', 'string', 'boolean', 'number', 'undefined' ];
6
4
 
7
- var typeHandlers = [];
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
- var Immutable = {
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 (type, handler) {
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 (value) {
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 (value, type) {
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 (data, options) {
81
+ create: function(data, options = {}) {
59
82
  if (Immutable.isImmutable(data)) {
60
83
  return data;
61
84
  }
62
85
 
63
- options = defaults(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
- var builder = new Builder('object', {});
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 (cb) {
79
- var obj = clone(data);
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, newOptions);
102
+ return Immutable.create(obj, { ...options, clone: false });
83
103
  });
84
104
 
85
- typeHandlers.forEach(function (typeHandler) {
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 ('function' === typeof newOrUpdatedModel.afterSave) {
174
- // should not be awaited
175
- const afterSave = newOrUpdatedModel.afterSave(original || null, options);
176
- if (options.waitAfterSave) { await afterSave; }
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 = defaults(value, { silent: false });
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.silent) {
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(options) {
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 (options.virtuals) {
252
+ if (virtuals) {
259
253
  addVirtuals(definition, json);
260
254
  }
261
- if (options.extended) {
255
+ if (extended) {
262
256
  json = EJSON.serialize(json);
263
257
  }
264
258
  json = _serialize(json);
265
- if (options.exclude) {
266
- json = omit(options.exclude, json);
267
- } else if (options.include) {
268
- json = pick(options.include, json);
259
+ if (exclude) {
260
+ json = omit(exclude, json);
261
+ } else if (include) {
262
+ json = pick(include, json);
269
263
  }
270
- if (options.transform) {
271
- json = options.transform.call(null, 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, options = {}) {
281
- const joiOptions = defaults(onlyJoiOptions(options), defaultJoiOptions);
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, joiOptions);
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
- if ('function' === typeof this.afterRemove) {
314
- const afterRemove = this.afterRemove(options);
315
- if (options.waitAfterRemove) { await afterRemove; }
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 = defaults(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 : assign({}, definition.where, 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 (options.autoSet) {
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 = defaults(newDef.properties, superDef.properties || {});
478
- newDef.methods = defaults(newDef.methods, superDef.methods || {});
479
- newDef.virtuals = defaults(newDef.virtuals, superDef.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 (data, builder) {
20
- for (let p in data) {
21
- addProp(p, builder, data);
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.0.0",
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.5.1",
38
+ "bson": "~4.6.4",
39
39
  "clone": "^2.1.2",
40
- "debug": "^4.3.2",
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.1.1",
46
- "object-assign": "^4.1.1",
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": "^7.0.2",
53
- "lint-staged": "^11.1.2",
54
- "chai": "^4.3.4",
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": "^9.1.1",
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",