cosa 6.1.0 → 6.3.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/db.js +38 -15
- package/lib/model.js +45 -8
- package/lib/session.js +54 -0
- package/package.json +10 -10
package/LICENSE
CHANGED
package/lib/db.js
CHANGED
|
@@ -3,7 +3,7 @@ const { ObjectId } = require('bson');
|
|
|
3
3
|
const { EventEmitter } = require('events');
|
|
4
4
|
const { MongoClient } = require('mongodb');
|
|
5
5
|
const {
|
|
6
|
-
curry, split, last, pipe,
|
|
6
|
+
curry, split, last, pipe, clamp, isPlainObject, isNilOrEmpty
|
|
7
7
|
} = require('omnibelt');
|
|
8
8
|
const Immutable = require('./immutable');
|
|
9
9
|
const RESULT_FIELDS = [ 'insertedId', 'insertedCount', 'insertedIds', 'matchedCount', 'modifiedCount', 'upsertedCount', 'upsertedId', 'deletedCount', 'upsertedIds' ];
|
|
@@ -182,6 +182,7 @@ class Database extends EventEmitter {
|
|
|
182
182
|
* @param {number} [options.batchSize] - number of items per batch (default in mongo driver is 1000)
|
|
183
183
|
* @param {boolean} [options.noCursorTimeout] - boolan, if the cursor can time out after being idle, mongo driver default is false
|
|
184
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
|
|
185
186
|
* @returns {Cursor} returns Cursor object
|
|
186
187
|
* @see {@link https://mongodb.github.io/node-mongodb-native/4.0/classes/findcursor.html}
|
|
187
188
|
* @see {@link https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#find}
|
|
@@ -195,8 +196,9 @@ class Database extends EventEmitter {
|
|
|
195
196
|
async find(collectionName, query, options = {}) {
|
|
196
197
|
query = deserialize(query);
|
|
197
198
|
const collection = await this.collection(collectionName);
|
|
199
|
+
const session = options.session?.mongoSession || options.session;
|
|
198
200
|
if (options.count) {
|
|
199
|
-
if (
|
|
201
|
+
if (isNilOrEmpty(query) && !session) {
|
|
200
202
|
const countOptions = {
|
|
201
203
|
readPreference: options.readPreference,
|
|
202
204
|
maxTimeMS: options.maxTimeMS
|
|
@@ -209,7 +211,8 @@ class Database extends EventEmitter {
|
|
|
209
211
|
limit: options.limit,
|
|
210
212
|
skip: options.skip,
|
|
211
213
|
readPreference: options.readPreference,
|
|
212
|
-
maxTimeMS: options.maxTimeMS
|
|
214
|
+
maxTimeMS: options.maxTimeMS,
|
|
215
|
+
session
|
|
213
216
|
};
|
|
214
217
|
debug(`db.${collection.collectionName}.countDocuments`, query, countOptions);
|
|
215
218
|
return collection.countDocuments(query, countOptions);
|
|
@@ -221,10 +224,11 @@ class Database extends EventEmitter {
|
|
|
221
224
|
sort: options.sort,
|
|
222
225
|
readPreference: options.readPreference,
|
|
223
226
|
noCursorTimeout: options.noCursorTimeout,
|
|
224
|
-
maxTimeMS: options.maxTimeMS
|
|
227
|
+
maxTimeMS: options.maxTimeMS,
|
|
228
|
+
session
|
|
225
229
|
};
|
|
226
230
|
debug(`db.${collection.collectionName}.findOne`, query, findOptions);
|
|
227
|
-
return collection.findOne(query,
|
|
231
|
+
return collection.findOne(query, findOptions);
|
|
228
232
|
} else {
|
|
229
233
|
const findOptions = {
|
|
230
234
|
projection: options.projection || options.fields,
|
|
@@ -234,10 +238,11 @@ class Database extends EventEmitter {
|
|
|
234
238
|
readPreference: options.readPreference,
|
|
235
239
|
batchSize: options.batchSize,
|
|
236
240
|
noCursorTimeout: options.noCursorTimeout,
|
|
237
|
-
maxTimeMS: options.maxTimeMS
|
|
241
|
+
maxTimeMS: options.maxTimeMS,
|
|
242
|
+
session
|
|
238
243
|
};
|
|
239
244
|
debug(`db.${collection.collectionName}.find`, query, findOptions);
|
|
240
|
-
return collection.find(query,
|
|
245
|
+
return collection.find(query, findOptions);
|
|
241
246
|
}
|
|
242
247
|
}
|
|
243
248
|
|
|
@@ -247,15 +252,19 @@ class Database extends EventEmitter {
|
|
|
247
252
|
* @param {(object|Array)} docs - Documents objects to insert.
|
|
248
253
|
* @param {object} options - options on insert
|
|
249
254
|
* @param {object} [options.writeConcern] - the write concern
|
|
255
|
+
* @param {object} [options.session] - Mongo session or Cosa Mongo session wrapper
|
|
250
256
|
* @returns {Promise} resolves with an object with results, and ops as keys
|
|
251
257
|
* @see {@link https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#insertmany}
|
|
252
258
|
* @see {@link https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#insertOne}
|
|
253
259
|
* @see {@link https://mongodb.github.io/node-mongodb-native/4.0/interfaces/insertoneoptions.html#writeconcern}
|
|
254
260
|
*/
|
|
255
|
-
async insert(collectionName, docs, { writeConcern } = {}) {
|
|
261
|
+
async insert(collectionName, docs, { writeConcern, session } = {}) {
|
|
256
262
|
docs = deserialize(docs);
|
|
257
263
|
const collection = await this.collection(collectionName);
|
|
258
|
-
const results = await insertOneOrMany(collection, Array.isArray(docs), docs, {
|
|
264
|
+
const results = await insertOneOrMany(collection, Array.isArray(docs), docs, {
|
|
265
|
+
writeConcern,
|
|
266
|
+
session: session?.mongoSession || session
|
|
267
|
+
});
|
|
259
268
|
|
|
260
269
|
if (Array.isArray(docs)) {
|
|
261
270
|
docs.forEach((doc, i) => {
|
|
@@ -278,6 +287,7 @@ class Database extends EventEmitter {
|
|
|
278
287
|
* @param {boolean} [options.multiple=false] - Should multiple documents be updated.
|
|
279
288
|
* @param {boolean} [options.upsert=false] - Should documents be inserted if they don't already exist.
|
|
280
289
|
* @param {object} [options.writeConcern] - the write concern options
|
|
290
|
+
* @param {object} [options.session] - Mongo session or Cosa Mongo session wrapper
|
|
281
291
|
* @returns {Promise} resolves with an object with results, and ops as keys
|
|
282
292
|
* @see {@link https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#updateMany}
|
|
283
293
|
* @see {@link https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#updateOne}
|
|
@@ -289,7 +299,8 @@ class Database extends EventEmitter {
|
|
|
289
299
|
const collection = await this.collection(collectionName);
|
|
290
300
|
return updateOneOrMany(collection, options.multiple, query, update, {
|
|
291
301
|
upsert: options.upsert,
|
|
292
|
-
writeConcern: options.writeConcern
|
|
302
|
+
writeConcern: options.writeConcern,
|
|
303
|
+
session: options.session?.mongoSession || options.session
|
|
293
304
|
});
|
|
294
305
|
}
|
|
295
306
|
|
|
@@ -300,6 +311,7 @@ class Database extends EventEmitter {
|
|
|
300
311
|
* @param {object} [options] - Optional settings see mongo documentation
|
|
301
312
|
* @param {boolean} [options.multiple=false] - Should multiple documents be removed.
|
|
302
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}
|
|
@@ -308,7 +320,10 @@ class Database extends EventEmitter {
|
|
|
308
320
|
async remove(collectionName, query, options = {}) {
|
|
309
321
|
query = deserialize(query);
|
|
310
322
|
const collection = await this.collection(collectionName);
|
|
311
|
-
return deleteOneOrMany(collection, options.multiple, query, {
|
|
323
|
+
return deleteOneOrMany(collection, options.multiple, query, {
|
|
324
|
+
writeConcern: options.writeConcern,
|
|
325
|
+
session: options.session?.mongoSession || options.session
|
|
326
|
+
});
|
|
312
327
|
}
|
|
313
328
|
|
|
314
329
|
/**
|
|
@@ -319,6 +334,7 @@ class Database extends EventEmitter {
|
|
|
319
334
|
* @param {object} [options.readPreference] - the read preference for the query with one of the read constants
|
|
320
335
|
* @param {number} [options.batchSize] - number of items per batch (default in mongo driver is 1000)
|
|
321
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
|
|
322
338
|
* @returns {Promise} resolves with the result of the aggregation from mongo
|
|
323
339
|
* @see {@link https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#aggregate}
|
|
324
340
|
* @see {@link https://mongodb.github.io/node-mongodb-native/4.0/interfaces/aggregateoptions.html#readpreference}
|
|
@@ -330,7 +346,8 @@ class Database extends EventEmitter {
|
|
|
330
346
|
return collection.aggregate(pipeline, {
|
|
331
347
|
readPreference: options.readPreference,
|
|
332
348
|
batchSize: options.batchSize,
|
|
333
|
-
maxTimeMS: options.maxTimeMS
|
|
349
|
+
maxTimeMS: options.maxTimeMS,
|
|
350
|
+
session: options.session?.mongoSession || options.session
|
|
334
351
|
});
|
|
335
352
|
}
|
|
336
353
|
|
|
@@ -342,6 +359,7 @@ class Database extends EventEmitter {
|
|
|
342
359
|
* @param {object} [options] - Optional settings see mongo documentation
|
|
343
360
|
* @param {string} [options.readPreference] - the read preference for the query
|
|
344
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
|
|
345
363
|
* @returns {Promise} resolves with the result of the distinct query from mongo
|
|
346
364
|
* @see {@link https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#distinct}
|
|
347
365
|
* @see {@link https://mongodb.github.io/node-mongodb-native/4.0/interfaces/commandoperationoptions.html#readpreference}
|
|
@@ -352,7 +370,8 @@ class Database extends EventEmitter {
|
|
|
352
370
|
const collection = await this.collection(collectionName);
|
|
353
371
|
return collection.distinct(key, query, {
|
|
354
372
|
readPreference: options.readPreference,
|
|
355
|
-
maxTimeMS: options.maxTimeMS
|
|
373
|
+
maxTimeMS: options.maxTimeMS,
|
|
374
|
+
session: options.session?.mongoSession || options.session
|
|
356
375
|
});
|
|
357
376
|
}
|
|
358
377
|
|
|
@@ -363,16 +382,20 @@ class Database extends EventEmitter {
|
|
|
363
382
|
* @param {object} replace - doc to save on the collection
|
|
364
383
|
* @param {object} [options] - Optional settings see mongo documentation
|
|
365
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}
|
|
368
388
|
* @see {@link https://mongodb.github.io/node-mongodb-native/4.0/interfaces/replaceoptions.html#writeconcern}
|
|
369
389
|
*/
|
|
370
|
-
async replace(collectionName, query, replace, { writeConcern } = {}) {
|
|
390
|
+
async replace(collectionName, query, replace, { writeConcern, session } = {}) {
|
|
371
391
|
const collection = await this.collection(collectionName);
|
|
372
392
|
query = deserialize(query);
|
|
373
393
|
replace = deserialize(replace);
|
|
374
394
|
debug(`db.${collection.collectionName}.replaceOne`, query, replace);
|
|
375
|
-
const r = await collection.replaceOne(query, replace, {
|
|
395
|
+
const r = await collection.replaceOne(query, replace, {
|
|
396
|
+
writeConcern,
|
|
397
|
+
session: session?.mongoSession || session
|
|
398
|
+
});
|
|
376
399
|
r.ops = replace;
|
|
377
400
|
return normalizeResult(r);
|
|
378
401
|
}
|
package/lib/model.js
CHANGED
|
@@ -116,7 +116,7 @@ const _create = (data, definition) => {
|
|
|
116
116
|
await context.beforeSave.apply(obj, [options]);
|
|
117
117
|
}
|
|
118
118
|
obj = await context._validate(obj, options);
|
|
119
|
-
const original = obj.__original;
|
|
119
|
+
const original = obj.__original || null;
|
|
120
120
|
obj = removeMeta(obj);
|
|
121
121
|
const newEtag = etag(JSON.stringify(obj));
|
|
122
122
|
const collection = definition.collection;
|
|
@@ -159,10 +159,27 @@ const _create = (data, definition) => {
|
|
|
159
159
|
obj = Array.isArray(result.ops) ? result.ops[0] : result.ops;
|
|
160
160
|
newOrUpdatedModel = _create(obj, definition);
|
|
161
161
|
}
|
|
162
|
-
if (
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
+
}
|
|
166
183
|
}
|
|
167
184
|
return newOrUpdatedModel;
|
|
168
185
|
};
|
|
@@ -297,10 +314,30 @@ const _create = (data, definition) => {
|
|
|
297
314
|
throw errors.Conflict({ message: 'Document remove conflict' });
|
|
298
315
|
}
|
|
299
316
|
debug(`remove from ${collection} successful`);
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
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
|
+
}
|
|
303
339
|
}
|
|
340
|
+
|
|
304
341
|
return result;
|
|
305
342
|
};
|
|
306
343
|
|
package/lib/session.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
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
|
+
const errors = [];
|
|
14
|
+
await session.abortTransaction();
|
|
15
|
+
await session.endSession();
|
|
16
|
+
await forEachSerialP(async (afterAborted) => {
|
|
17
|
+
try {
|
|
18
|
+
await afterAborted();
|
|
19
|
+
} catch (err) {
|
|
20
|
+
errors.push(err);
|
|
21
|
+
}
|
|
22
|
+
}, afterAborts);
|
|
23
|
+
return { errors };
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
get mongoSession() {
|
|
28
|
+
return session;
|
|
29
|
+
},
|
|
30
|
+
startTransaction: () => {
|
|
31
|
+
return session.startTransaction();
|
|
32
|
+
},
|
|
33
|
+
commitTransaction: async () => {
|
|
34
|
+
const errors = [];
|
|
35
|
+
await session.commitTransaction();
|
|
36
|
+
await session.endSession();
|
|
37
|
+
await forEachSerialP(async (afterCommitFunc) => {
|
|
38
|
+
try {
|
|
39
|
+
await afterCommitFunc();
|
|
40
|
+
} catch (err) {
|
|
41
|
+
errors.push(err);
|
|
42
|
+
}
|
|
43
|
+
}, afterCommits);
|
|
44
|
+
return { errors };
|
|
45
|
+
},
|
|
46
|
+
abortTransaction,
|
|
47
|
+
afterCommits,
|
|
48
|
+
afterAborts
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
module.exports = {
|
|
53
|
+
createSession
|
|
54
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cosa",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.3.0",
|
|
4
4
|
"description": "Cosa Models for MongoDB",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"engines": {
|
|
@@ -35,25 +35,25 @@
|
|
|
35
35
|
"lib"
|
|
36
36
|
],
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"bson": "~4.
|
|
38
|
+
"bson": "~4.7.0",
|
|
39
39
|
"clone": "^2.1.2",
|
|
40
|
-
"debug": "^4.3.
|
|
40
|
+
"debug": "^4.3.4",
|
|
41
41
|
"error": "^7.0.2",
|
|
42
42
|
"etag": "^1.8.1",
|
|
43
43
|
"@hapi/joi": "^17.1.1",
|
|
44
|
-
"mongodb": "~4.
|
|
44
|
+
"mongodb": "~4.10.0",
|
|
45
45
|
"object-path": "^0.11.8",
|
|
46
46
|
"omnibelt": "^2.1.0"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
|
-
"@losant/eslint-config-losant": "^1.4.
|
|
50
|
-
"husky": "^
|
|
51
|
-
"lint-staged": "^
|
|
52
|
-
"chai": "^4.3.
|
|
49
|
+
"@losant/eslint-config-losant": "^1.4.4",
|
|
50
|
+
"husky": "^8.0.1",
|
|
51
|
+
"lint-staged": "^13.0.3",
|
|
52
|
+
"chai": "^4.3.6",
|
|
53
53
|
"chai-as-promised": "^7.1.1",
|
|
54
54
|
"chai-datetime": "^1.8.0",
|
|
55
|
-
"documentation": "^
|
|
56
|
-
"mocha": "^
|
|
55
|
+
"documentation": "^14.0.0",
|
|
56
|
+
"mocha": "^10.0.0",
|
|
57
57
|
"string-template": "^1.0.0"
|
|
58
58
|
},
|
|
59
59
|
"eslintConfig": {
|