diff --git a/CHANGELOG.md b/CHANGELOG.md index f0e5de5d..f0913cde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ * Add the `is_system_route` property in `Route` to signify routes designed for non-end-user interaction. * Add `size` property to the `FieldSet` class * When filtering on a translatable field, don't restrict it solely to the presently set locale when the translation functionality is switched off +* Upgrade `mongodb` client from `3.6.6` to `6.1.0`, requiring quite a few fixes +* Minimum node version is now also `v16.20.1` because of the mongodb upgrade ## 1.3.15 (2023-07-03) diff --git a/lib/app/datasource/mongo_datasource.js b/lib/app/datasource/mongo_datasource.js index 6bbbceba..515d7aae 100644 --- a/lib/app/datasource/mongo_datasource.js +++ b/lib/app/datasource/mongo_datasource.js @@ -144,7 +144,7 @@ Mongo.setMethod(function normalizeFindOptions(options) { * * @author Jelle De Loecker * @since 0.2.0 - * @version 1.1.0 + * @version 1.3.16 */ Mongo.decorateMethod(Blast.Decorators.memoize({ignore_arguments: true}), function connect() { @@ -160,7 +160,7 @@ Mongo.decorateMethod(Blast.Decorators.memoize({ignore_arguments: true}), functio pledge = new Pledge(); // Create the connection to the database - MongoClient.connect(that.uri, that.mongoOptions, function connected(err, client) { + Pledge.done(MongoClient.connect(that.uri, that.mongoOptions), function connected(err, client) { if (err) { that.connection_error = err; @@ -186,7 +186,7 @@ Mongo.decorateMethod(Blast.Decorators.memoize({ignore_arguments: true}), functio * * @author Jelle De Loecker * @since 0.2.0 - * @version 0.2.0 + * @version 1.3.16 * * @param {Function} callback */ @@ -208,7 +208,7 @@ Mongo.setMethod(function collection(name, callback) { return callback(err); } - that.connection.collection(name, function createdCollection(err, collection) { + Pledge.done(that.connection.collection(name), function createdCollection(err, collection) { if (err) { return callback(err); @@ -228,7 +228,7 @@ Mongo.setMethod(function collection(name, callback) { * * @author Jelle De Loecker * @since 0.2.0 - * @version 1.2.0 + * @version 1.3.16 */ Mongo.setMethod(function _read(model, criteria, callback) { @@ -268,11 +268,6 @@ Mongo.setMethod(function _read(model, criteria, callback) { let aggregate_options = {}; - // Limits can still be set as an option though - if (options.limit) { - aggregate_options.limit = options.limit; - } - Function.parallel({ available: function getAvailable(next) { @@ -286,13 +281,13 @@ Mongo.setMethod(function _read(model, criteria, callback) { pipeline.push({$count: 'available'}); // Expensive aggregate just to get the available count... - collection.aggregate(pipeline, cloned_options, function gotAggregate(err, cursor) { + Pledge.done(collection.aggregate(pipeline, cloned_options), function gotAggregate(err, cursor) { if (err) { return next(err); } - cursor.toArray(function gotAvailableArray(err, items) { + Pledge.done(cursor.toArray(), function gotAvailableArray(err, items) { if (err) { return next(err); @@ -313,13 +308,22 @@ Mongo.setMethod(function _read(model, criteria, callback) { }); }, items: function getItems(next) { - collection.aggregate(compiled.pipeline, aggregate_options, function gotAggregate(err, cursor) { + + let pipeline = JSON.clone(compiled.pipeline); + + // Limits also have to be set in the pipeline now + // (We have to do it here, so the `available` count is correct) + if (options.limit) { + pipeline.push({$limit: options.limit}); + } + + Pledge.done(collection.aggregate(pipeline, aggregate_options), function gotAggregate(err, cursor) { if (err) { return next(err); } - cursor.toArray(next); + Pledge.done(cursor.toArray(), next); }); } }, function done(err, data) { @@ -346,10 +350,10 @@ Mongo.setMethod(function _read(model, criteria, callback) { return next(null, null); } - cursor.count(false, next); + Pledge.done(collection.countDocuments(compiled), next); }, items: function getItems(next) { - cursor.toArray(next); + Pledge.done(cursor.toArray(), next); } }, function done(err, data) { @@ -369,7 +373,7 @@ Mongo.setMethod(function _read(model, criteria, callback) { * * @author Jelle De Loecker * @since 0.2.0 - * @version 1.3.6 + * @version 1.3.16 */ Mongo.setMethod(function _create(model, data, options, callback) { @@ -379,13 +383,7 @@ Mongo.setMethod(function _create(model, data, options, callback) { return callback(err); } - let method = 'insert'; - - if (typeof collection.insertOne == 'function') { - method = 'insertOne'; - } - - collection[method](data, {w: 1, fullResult: true}, function afterInsert(err, result) { + Pledge.done(collection.insertOne(data, {w: 1, fullResult: true}), function afterInsert(err, result) { // Clear the cache model.nukeCache(); @@ -394,6 +392,7 @@ Mongo.setMethod(function _create(model, data, options, callback) { return callback(err, result); } + // @TODO: fix because of mongodb 6 let write_errors = result.message?.documents?.[0]?.writeErrors; if (write_errors) { @@ -424,7 +423,7 @@ Mongo.setMethod(function _create(model, data, options, callback) { * * @author Jelle De Loecker * @since 0.2.0 - * @version 1.1.0 + * @version 1.3.16 */ Mongo.setMethod(function _update(model, data, options, callback) { @@ -507,16 +506,18 @@ Mongo.setMethod(function _update(model, data, options, callback) { console.log('Updating with obj', id, updateObject); } + let promise; + if (collection.findOneAndUpdate) { - collection.findOneAndUpdate({_id: id}, updateObject, {upsert: true}, afterUpdate); + promise = collection.findOneAndUpdate({_id: id}, updateObject, {upsert: true}); } else if (collection.findAndModify) { - collection.findAndModify({_id: id}, [['_id', 1]], updateObject, {upsert: true}, afterUpdate); + promise = collection.findAndModify({_id: id}, [['_id', 1]], updateObject, {upsert: true}); } else { // If it's not available (like nedb) - collection.update({_id: ''+id}, updateObject, {upsert: true}, afterUpdate); + promise = collection.update({_id: ''+id}, updateObject, {upsert: true}); } - function afterUpdate(err, result) { + Pledge.done(promise, function afterUpdate(err, result) { // Clear the cache model.nukeCache(); @@ -526,7 +527,7 @@ Mongo.setMethod(function _update(model, data, options, callback) { } callback(null, Object.assign({}, data)); - } + }); }); }); @@ -536,7 +537,7 @@ Mongo.setMethod(function _update(model, data, options, callback) { * @author Kjell Keisse * @author Jelle De Loecker * @since 0.2.0 - * @version 1.0.3 + * @version 1.3.16 */ Mongo.setMethod(function _remove(model, query, options, callback) { @@ -546,7 +547,7 @@ Mongo.setMethod(function _remove(model, query, options, callback) { return callback(err); } - collection.findOneAndDelete(query, function _deleted(err, result){ + Pledge.done(collection.findOneAndDelete(query), function _deleted(err, result){ //clear cache model.nukeCache(); diff --git a/lib/core/client_alchemy.js b/lib/core/client_alchemy.js index cff78cf6..64640a4b 100644 --- a/lib/core/client_alchemy.js +++ b/lib/core/client_alchemy.js @@ -315,11 +315,11 @@ Alchemy.setMethod(function castObjectId(str) { /** * Is the given parameter an object id (string or instance) * - * @author Jelle De Loecker + * @author Jelle De Loecker * @since 1.0.4 - * @version 1.0.5 + * @version 1.3.16 * - * @param {String|ObjectID} obj + * @param {String|ObjectId} obj * * @return {Boolean} */ @@ -331,9 +331,13 @@ Alchemy.setMethod(function isObjectId(obj) { let type = typeof obj; - if (obj && type === 'object' && obj.constructor && obj.constructor.name === 'ObjectID') { + if (type === 'string' && obj.isObjectId()) { return true; - } else if (type === 'string' && obj.isObjectId()) { + } + + let class_name = obj.constructor?.name; + + if (class_name == 'ObjectID' || class_name == 'ObjectId') { return true; } diff --git a/lib/init/alchemy.js b/lib/init/alchemy.js index a90994cb..706f289e 100644 --- a/lib/init/alchemy.js +++ b/lib/init/alchemy.js @@ -1248,22 +1248,28 @@ Alchemy.setMethod(function shared(name, type, value) { * * @author Jelle De Loecker * @since 0.0.1 - * @version 0.4.0 + * @version 1.3.16 * - * @param {String|ObjectID} obj + * @param {String|ObjectId} obj * - * @return {ObjectID|undefined} + * @return {ObjectId|undefined} */ Alchemy.setMethod(function castObjectId(obj) { - var type = typeof obj; + let type = typeof obj; - if (obj && type === 'object' && obj.constructor && obj.constructor.name === 'ObjectID') { - return obj; - } else if (type === 'string' && obj.isObjectId()) { + if (type === 'string' && obj.isObjectId()) { return alchemy.ObjectId(obj); } + if (obj && type === 'object') { + let class_name = obj.constructor?.name; + + if (class_name == 'ObjectID' || class_name == 'ObjectId') { + return obj; + } + } + return undefined; }); diff --git a/lib/init/functions.js b/lib/init/functions.js index e08b1968..ea9364a2 100644 --- a/lib/init/functions.js +++ b/lib/init/functions.js @@ -496,6 +496,7 @@ Alchemy.setMethod(function getShared(first, second) { /** * Make JSON-Dry handle ObjectIDs when drying + * (Old class name) * * @author Jelle De Loecker * @since 0.2.0 @@ -507,16 +508,52 @@ JSON.registerDrier('ObjectID', function dryOI(holder, key, value) { /** * Correctly un-dry ObjectIDs + * (Old class name) * * @author Jelle De Loecker * @since 0.2.0 * @version 0.2.0 */ JSON.registerUndrier('ObjectID', function undryOI(holder, key, value) { - return mongo.ObjectID(value); + return alchemy.castObjectId(value); }); -alchemy.ObjectId = mongo.ObjectID; +/** + * Make JSON-Dry handle ObjectIDs when drying + * + * @author Jelle De Loecker + * @since 1.3.16 + * @version 1.3.16 + */ +JSON.registerDrier('ObjectId', function dryOI(holder, key, value) { + return ''+value; +}, {add_path: false}); + +/** + * Correctly un-dry ObjectIDs + * + * @author Jelle De Loecker + * @since 1.3.16 + * @version 1.3.16 + */ +JSON.registerUndrier('ObjectId', function undryOI(holder, key, value) { + return alchemy.castObjectId(value); +}); + +/** + * Create a new ObjectId + * + * @author Jelle De Loecker + * @since 1.3.16 + * @version 1.3.16 + * + * @param {*} object_id + * + * @return {ObjectId} + */ +Alchemy.setMethod(function ObjectId(object_id) { + return new mongo.ObjectId(object_id); +}); /** * Get mimetype info of a file diff --git a/package.json b/package.json index e0fae20a..763b5b83 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "minimist" : "~1.2.5", "mkdirp" : "~3.0.1", "mmmagic" : "~0.5.3", - "mongodb" : "~3.6.6", + "mongodb" : "~6.1.0", "ncp" : "~2.0.0", "postcss" : "~8.4.6", "protoblast" : "~0.8.9", @@ -57,7 +57,7 @@ "codecov" : "~3.8.1", "istanbul-lib-instrument" : "~4.0.3", "mocha" : "~10.2.0", - "mongo-unit" : "~3.2.0", + "mongo-unit" : "~3.3.0", "nyc" : "^15.1.0", "puppeteer" : "~19.0.0", "source-map" : "~0.7.3" @@ -71,6 +71,6 @@ "main": "lib/bootstrap.js", "license": "MIT", "engines": { - "node" : ">=14.0.0" + "node" : ">=16.20.1" } } \ No newline at end of file diff --git a/test/03-model.js b/test/03-model.js index 8666b159..74b6189c 100644 --- a/test/03-model.js +++ b/test/03-model.js @@ -331,6 +331,8 @@ describe('Model', function() { await doc.save(); + assert.strictEqual(!!doc._id, true, 'The _id property is missing'); + let refetched = await Model.get('ClonedSchemas').findByPk(doc._id); let comp = refetched.components?.[0]; @@ -358,10 +360,10 @@ describe('Model', function() { assert.strictEqual(out_val.source.anchor_name, 'out-source-1'); assert.strictEqual(out_val.target.anchor_name, 'out-target-1'); - assert.strictEqual(in_val.source.node_uid.constructor.name, 'ObjectID'); - assert.strictEqual(in_val.target.node_uid.constructor.name, 'ObjectID'); - assert.strictEqual(out_val.source.node_uid.constructor.name, 'ObjectID'); - assert.strictEqual(out_val.target.node_uid.constructor.name, 'ObjectID'); + assert.strictEqual(in_val.source.node_uid.constructor.name, 'ObjectId'); + assert.strictEqual(in_val.target.node_uid.constructor.name, 'ObjectId'); + assert.strictEqual(out_val.source.node_uid.constructor.name, 'ObjectId'); + assert.strictEqual(out_val.target.node_uid.constructor.name, 'ObjectId'); done(); } diff --git a/test/06-document.js b/test/06-document.js index e0998f9c..3cf0b9ea 100644 --- a/test/06-document.js +++ b/test/06-document.js @@ -347,7 +347,7 @@ describe('Document', function() { var doc = await Model.get('Person').find('first'); - assert.strictEqual(doc.hasChanged(), false); + assert.strictEqual(doc.hasChanged(), false, 'The document should not have been marked as changed yet'); assert.strictEqual(doc.firstname, 'Griet');