diff --git a/.travis.yml b/.travis.yml index 4564cc1b6..67641bd6b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,10 +14,9 @@ dist: xenial language: node_js node_js: - - "12" - - "14" - "16" - + # - "18" + # - "20" env: global: - WATERLINE_ADAPTER_TESTS_URL=localhost/testdb diff --git a/appveyor.yml b/appveyor.yml index c68b2724d..8628af7fd 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -12,22 +12,20 @@ # Test against these versions of Node.js. environment: - WATERLINE_ADAPTER_TESTS_URL: localhost/testdb - WATERLINE_ADAPTER_TESTS_HOST: localhost + WATERLINE_ADAPTER_TESTS_URL: 127.0.0.1/testdb + WATERLINE_ADAPTER_TESTS_HOST: 127.0.0.1 WATERLINE_ADAPTER_TESTS_DATABASE: sails-mongo NODE_ENV: test matrix: - - nodejs_version: "10" - - nodejs_version: "12" - - nodejs_version: "14" + - nodejs_version: "16" + - nodejs_version: "18" + - nodejs_version: "20" # Install scripts. (runs after repo cloning) install: # Get the latest stable version of Node.js # (Not sure what this is for, it's just in Appveyor's example.) - ps: Install-Product node $env:nodejs_version - # Don't let npm send metrics as it creates a file in the .npm folder invalidating the cache every time - - npm config set send-metrics false # Install declared dependencies - npm install --no-audit diff --git a/docker-compose.yml b/docker-compose.yml index f4a773433..6f7ac00da 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,22 +1,24 @@ -adapter: - image: node:12 - volumes: - - $PWD:/home/node/sails-mongo - links: - - mongo - environment: - - WATERLINE_ADAPTER_TESTS_DATABASE=sails-mongo - - WATERLINE_ADAPTER_TESTS_URL=mongo/testdb - - WATERLINE_ADAPTER_TESTS_HOST=mongo - - NODE_ENV=test - user: node - working_dir: /home/node/sails-mongo - command: - - bash -c "npm test" +version: '3' +services: + adapter: + image: node:20 + volumes: + - $PWD:/home/node/sails-mongo + links: + - mongo + environment: + - WATERLINE_ADAPTER_TESTS_DATABASE=sails-mongo + - WATERLINE_ADAPTER_TESTS_URL=mongo/testdb + - WATERLINE_ADAPTER_TESTS_HOST=mongo + - NODE_ENV=test + user: node + working_dir: /home/node/sails-mongo + command: + - bash -c "npm test" -mongo: - image: mongo:4.2 - restart: always - command: "--logpath=/dev/null" - ports: - - "27017:27017" + mongo: + image: mongo:7 + restart: always + command: "--logpath=/dev/null" + ports: + - "27017:27017" diff --git a/lib/index.js b/lib/index.js index 531c779fb..5a45a280f 100644 --- a/lib/index.js +++ b/lib/index.js @@ -810,7 +810,7 @@ module.exports = { // Create the index on the Mongo collection. // (https://docs.mongodb.com/manual/reference/method/db.collection.createIndex) - mongoCollection.createIndex(mongoSingleFieldIdxKeys, { unique: true }, function (err) { + (function(iifeDone) { mongoCollection.createIndex(mongoSingleFieldIdxKeys, { unique: true }).then(function() { iifeDone(); }).catch(function(err) {iifeDone(err);});})(function (err) { if (err && !_.isError(err)) { err = flaverr({raw: err}, new Error('Consistency violation: Expecting Error instance, but instead got: '+util.inspect(err))); return next(err); @@ -859,7 +859,7 @@ module.exports = { // Drop the physical model (e.g. table/etc.) var db = dsEntry.manager; - db.collection(tableName).drop(function (err) { + (function(iifeDone) { db.collection(tableName).drop().then(function() {iifeDone();}).catch(function(err) {iifeDone(err);});})(function (err) { try { if (err) { diff --git a/lib/private/machines/avg-records.js b/lib/private/machines/avg-records.js index d48ec9f4c..377b722c7 100644 --- a/lib/private/machines/avg-records.js +++ b/lib/private/machines/avg-records.js @@ -76,7 +76,7 @@ module.exports = { } ], { cursor: {} }); - cursor.toArray(function aggregateCb(err, nativeResult) { + (function(iifeDone) { cursor.toArray().then(function(nativeResult) { iifeDone(undefined, nativeResult); }).catch(function (err){iifeDone(err);}); })(function aggregateCb(err, nativeResult) { if (err) { return exits.error(err); } var mean = 0; diff --git a/lib/private/machines/count-records.js b/lib/private/machines/count-records.js index 5d0e7156b..9a4d82ca6 100644 --- a/lib/private/machines/count-records.js +++ b/lib/private/machines/count-records.js @@ -61,7 +61,7 @@ module.exports = { // ╚═╝╚═╝╩ ╩╩ ╩╚═╝╝╚╝╩╚═╝╩ ╩ ╩ ╚═╝ └┴┘┴ ┴ ┴ ┴ ─┴┘└─┘ var db = inputs.connection; var mongoCollection = db.collection(tableName); - mongoCollection.find(mongoWhere).count(function countCb(err, nativeResult) { + (function(iifeDone) { mongoCollection.countDocuments(mongoWhere).then(function(nativeResult) { iifeDone(undefined, nativeResult);}).catch(function(err) { iifeDone(err);});})(function countCb(err, nativeResult) { if (err) { return exits.error(err); } return exits.success(nativeResult); diff --git a/lib/private/machines/create-each-record.js b/lib/private/machines/create-each-record.js index 57ad7cd83..6c3c49357 100644 --- a/lib/private/machines/create-each-record.js +++ b/lib/private/machines/create-each-record.js @@ -29,6 +29,7 @@ module.exports = { fn: function (inputs, exits) { // Dependencies + var util = require('util'); var _ = require('@sailshq/lodash'); var processNativeRecord = require('./private/process-native-record'); var processNativeError = require('./private/process-native-error'); @@ -82,7 +83,7 @@ module.exports = { // if (s3q.meta && s3q.meta.logMongoS3Qs) { // console.log('- - - - - - - - - -CREATE EACH: s3q.newRecords:',require('util').inspect(s3q.newRecords,{depth:5}),'\n'); // } - mongoCollection.insertMany(s3q.newRecords, function (err, nativeResult) { + (function(iifeDone){ mongoCollection.insertMany(s3q.newRecords).then(function(nativeResult) { iifeDone(undefined, nativeResult);}).catch(function(err) { iifeDone(err);});})(function(err, nativeResult) { if (err) { err = processNativeError(err); if (err.footprint && err.footprint.identity === 'notUnique') { @@ -97,6 +98,12 @@ module.exports = { return exits.success(); }//-• + // Sanity check: Verify that IDs were sent back. + if (_.isUndefined(nativeResult.insertedIds) || !_.isObject(nativeResult.insertedIds)) { + return exits.error(new Error('Consistency violation: Unable to retrieve valid insertedIds from the result. This might indicate a consistency violation. Native result details:\n```\n' + util.inspect(nativeResult, {depth: 5}) + '\n```')); + } + + var insertedIds = Object.values(nativeResult.insertedIds); // Otherwise, IWMIH we'll be sending back records: // ============================================ @@ -104,16 +111,20 @@ module.exports = { // ╔═╗╦═╗╔═╗╔═╗╔═╗╔═╗╔═╗ ┌┐┌┌─┐┌┬┐┬┬ ┬┌─┐ ┬─┐┌─┐┌─┐┌─┐┬─┐┌┬┐┌─┌─┐─┐ // ╠═╝╠╦╝║ ║║ ║╣ ╚═╗╚═╗ │││├─┤ │ │└┐┌┘├┤ ├┬┘├┤ │ │ │├┬┘ │││ └─┐ │ // ╩ ╩╚═╚═╝╚═╝╚═╝╚═╝╚═╝ ┘└┘┴ ┴ ┴ ┴ └┘ └─┘ ┴└─└─┘└─┘└─┘┴└──┴┘└─└─┘─┘ - // Process record(s) (mutate in-place) to wash away adapter-specific eccentricities. - var phRecords = nativeResult.ops; - try { - _.each(phRecords, function (phRecord){ - processNativeRecord(phRecord, WLModel, s3q.meta); - }); - } catch (e) { return exits.error(e); } - - return exits.success(phRecords); - + // Use the new record ID(s) to find the record(s) that were created. + (function(iifeDone){ mongoCollection.find({ _id: { $in: insertedIds } }).toArray().then(function(phRecords) { iifeDone(undefined, phRecords); }).catch(function(err) { iifeDone(err);});})(function(err, phRecords) { + if (err) { + return exits.error(err); + } + try { + _.each(phRecords, function (phRecord){ + // Process record(s) (mutate in-place) to wash away adapter-specific eccentricities. + processNativeRecord(phRecord, WLModel, s3q.meta); + }); + } catch (e) { return exits.error(e); } + + return exits.success(phRecords); + }); }); // } diff --git a/lib/private/machines/create-manager.js b/lib/private/machines/create-manager.js index bd64f0d4d..0c8447109 100644 --- a/lib/private/machines/create-manager.js +++ b/lib/private/machines/create-manager.js @@ -125,16 +125,8 @@ module.exports = { var mongoUrl = _clientConfig.url; _clientConfig = _.omit(_clientConfig, ['url', 'user', 'password', 'host', 'port', 'database']); - // Use unified topology. MongoDB node maintainers recommends this to be enabled - // https://github.com/mongodb/node-mongodb-native/releases/tag/v3.2.1 - // Use new url parser to remove warnings - _clientConfig = Object.assign({ - useNewUrlParser: true, - useUnifiedTopology: true - }, _clientConfig); - - // http://mongodb.github.io/node-mongodb-native/3.5/api/MongoClient.html#.connect - NodeMongoDBNativeLib.MongoClient.connect(mongoUrl, _clientConfig, function connectCb(err, client) { + // https://mongodb.github.io/node-mongodb-native/6.3/classes/MongoClient.html#connect + (function(iifeDone){ NodeMongoDBNativeLib.MongoClient.connect(mongoUrl, _clientConfig).then(function(client){ iifeDone(undefined, client);}).catch(function(err) { iifeDone(err);});})(function(err, client){ if (err) { return exits.error(err); } diff --git a/lib/private/machines/create-record.js b/lib/private/machines/create-record.js index 22d390657..830e37897 100644 --- a/lib/private/machines/create-record.js +++ b/lib/private/machines/create-record.js @@ -76,7 +76,7 @@ module.exports = { // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - var db = inputs.connection; var mongoCollection = db.collection(tableName); - mongoCollection.insertOne(s3q.newRecord, function (err, nativeResult) { + (function(iifeDone){ mongoCollection.insertOne(s3q.newRecord).then(function(nativeResult) { iifeDone(undefined, nativeResult);}).catch(function(err){ iifeDone(err);});})(function(err, nativeResult) { if (err) { err = processNativeError(err); if (err.footprint && err.footprint.identity === 'notUnique') { @@ -102,23 +102,28 @@ module.exports = { // Otherwise, IWMIH we'll be sending back a record: // ============================================ - - // Sanity check: Verify that there is only one record. - if (nativeResult.ops.length !== 1) { - return exits.error(new Error('Consistency violation: Unexpected # of records returned from Mongo (in `.ops`). Native result:\n```\n'+util.inspect(nativeResult, {depth: 5})+'\n```')); + // Sanity check: Verify that an ID was sent back. + if (_.isUndefined(nativeResult.insertedId)) { + return exits.error(new Error('Consistency violation: Unable to retrieve insertedId from the result. This might indicate a consistency violation. Native result details:\n```\n' + util.inspect(nativeResult, {depth: 5}) + '\n```')); } + // ╔═╗╦═╗╔═╗╔═╗╔═╗╔═╗╔═╗ ┌┐┌┌─┐┌┬┐┬┬ ┬┌─┐ ┬─┐┌─┐┌─┐┌─┐┬─┐┌┬┐ // ╠═╝╠╦╝║ ║║ ║╣ ╚═╗╚═╗ │││├─┤ │ │└┐┌┘├┤ ├┬┘├┤ │ │ │├┬┘ ││ // ╩ ╩╚═╚═╝╚═╝╚═╝╚═╝╚═╝ ┘└┘┴ ┴ ┴ ┴ └┘ └─┘ ┴└─└─┘└─┘└─┘┴└──┴┘ - // Process record (mutate in-place) to wash away adapter-specific eccentricities. - var phRecord = nativeResult.ops[0]; - try { - processNativeRecord(phRecord, WLModel, s3q.meta); - } catch (e) { return exits.error(e); } - - // Then send it back. - return exits.success(phRecord); + // Use the new record ID to find the record that was created. + (function(iifeDone) { mongoCollection.findOne({ _id: nativeResult.insertedId }).then(function(phRecord) { iifeDone(undefined, phRecord);}).catch(function(err) { iifeDone(err);});})(function(err, phRecord) { + if (err) { + return exits.error(err); + } + try { + // Process record (mutate in-place) to wash away adapter-specific eccentricities. + processNativeRecord(phRecord, WLModel, s3q.meta); + } catch (e) { return exits.error(e); } + + // Then send it back. + return exits.success(phRecord); + }); }); // } diff --git a/lib/private/machines/destroy-records.js b/lib/private/machines/destroy-records.js index 38833766b..5ea9c5e93 100644 --- a/lib/private/machines/destroy-records.js +++ b/lib/private/machines/destroy-records.js @@ -87,7 +87,7 @@ module.exports = { } // Find matching records. - mongoCollection.find(mongoWhere).toArray(function findCb(err, nativeResult) { + (function(iifeDone) {mongoCollection.find(mongoWhere).toArray().then(function findCb(nativeResult) { iifeDone(undefined, nativeResult);}).catch(function(err) { iifeDone(err);});})(function findCb(err, nativeResult) { if (err) { return proceed(err); } return proceed(undefined, nativeResult); }); @@ -104,7 +104,7 @@ module.exports = { secondaryMongoWhere = {}; secondaryMongoWhere[pkColumnName] = { '$in': _.pluck(phRecords, pkColumnName) }; } - mongoCollection.deleteMany(secondaryMongoWhere, function deleteCb(err) { + (function(iifeDone) { mongoCollection.deleteMany(secondaryMongoWhere).then(function() { iifeDone();}).catch(function(err) { iifeDone(err);});})(function deleteCb(err) { if (err) { return exits.error(err); } if (!isFetchEnabled) { diff --git a/lib/private/machines/find-records.js b/lib/private/machines/find-records.js index e5b53bfc1..3473ba23f 100644 --- a/lib/private/machines/find-records.js +++ b/lib/private/machines/find-records.js @@ -117,7 +117,7 @@ module.exports = { // ║ ║ ║║║║║║║║ ║║║║║║ ╠═╣ ║ ║╣ ││││ │ ├─┤ ││├┴┐ // ╚═╝╚═╝╩ ╩╩ ╩╚═╝╝╚╝╩╚═╝╩ ╩ ╩ ╚═╝ └┴┘┴ ┴ ┴ ┴ ─┴┘└─┘ // Find the documents in the db. - mongoDeferred.toArray(function findCb(err, nativeResult) { + (function(iifeDone) { mongoDeferred.toArray().then(function(nativeResult) { iifeDone(undefined, nativeResult);}).catch(function(err) { iifeDone(err);});})(function findCb(err, nativeResult) { if (err) { return exits.error(err); } // ╔═╗╦═╗╔═╗╔═╗╔═╗╔═╗╔═╗ ┌┐┌┌─┐┌┬┐┬┬ ┬┌─┐ ┬─┐┌─┐┌─┐┌─┐┬─┐┌┬┐┌─┌─┐─┐ diff --git a/lib/private/machines/sum-records.js b/lib/private/machines/sum-records.js index 490077da3..742c63ce8 100644 --- a/lib/private/machines/sum-records.js +++ b/lib/private/machines/sum-records.js @@ -76,7 +76,7 @@ module.exports = { } ], { cursor: {} }); - cursor.toArray(function aggregateCb(err, nativeResult) { + (function(iifeDone) { cursor.toArray().then(function(nativeResult) { iifeDone(null, nativeResult);}).catch(function(err) { iifeDone(err, null);});})(function aggregateCb(err, nativeResult) { if (err) { return exits.error(err); } var sum = 0; diff --git a/lib/private/machines/update-records.js b/lib/private/machines/update-records.js index d885a96b7..8b723ffc0 100644 --- a/lib/private/machines/update-records.js +++ b/lib/private/machines/update-records.js @@ -103,7 +103,7 @@ module.exports = { // console.log('mongoWhere:',mongoWhere); // console.log('typeof mongoWhere._id.$in[0]:',typeof mongoWhere._id.$in[0]); // console.log('projection:',projection); - mongoCollection.find(mongoWhere, projection).toArray(function findCb(err, nativeResult) { + (function(iifeDone) { mongoCollection.find(mongoWhere, projection).toArray().then(function(nativeResult) { iifeDone(undefined, nativeResult);}).catch(function(err) { iifeDone(err);});})(function findCb(err, nativeResult) { if (err) { return proceed(err); } return proceed(undefined, _.pluck(nativeResult, pkColumnName)); }); @@ -126,7 +126,7 @@ module.exports = { // console.log('- - - - - - - - - -UPDATE: secondaryMongoWhere:',secondaryMongoWhere, { '$set': s3q.valuesToSet }); // } - mongoCollection.updateMany(secondaryMongoWhere, { '$set': s3q.valuesToSet }, function updateManyCb(err) { + (function(iifeDone) { mongoCollection.updateMany(secondaryMongoWhere, { '$set': s3q.valuesToSet }).then(function() { iifeDone();}).catch(function(err) { iifeDone(err);});})(function updateManyCb(err) { if (err) { err = processNativeError(err); if (err.footprint && err.footprint.identity === 'notUnique') { @@ -158,7 +158,7 @@ module.exports = { // Now re-fetch the now-updated records. - mongoCollection.find(secondaryMongoWhere).toArray(function (err, phRecords) { + (function(iifeDone) { mongoCollection.find(secondaryMongoWhere).toArray().then(function(phRecords) { iifeDone(undefined, phRecords);}).catch(function(err) { iifeDone(err);});})(function(err, phRecords) { if (err) { return exits.error(err); } // ╔═╗╦═╗╔═╗╔═╗╔═╗╔═╗╔═╗ ┌┐┌┌─┐┌┬┐┬┬ ┬┌─┐ ┬─┐┌─┐┌─┐┌─┐┬─┐┌┬┐┌─┌─┐─┐ diff --git a/package.json b/package.json index 112875582..13df7b50a 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "async": "3.2.4", "flaverr": "^1.10.0", "machine": "^15.2.2", - "mongodb": "3.7.3", + "mongodb": "6.3.0", "qs": "6.9.7" }, "devDependencies": { diff --git a/test/run-adapter-specific-tests.js b/test/run-adapter-specific-tests.js index 557fb7d33..9133c356f 100644 --- a/test/run-adapter-specific-tests.js +++ b/test/run-adapter-specific-tests.js @@ -145,7 +145,7 @@ describe('dontUseObjectIds', function() { describe('Updating a single record', function() { it('should update the record correctly', function(done) { - models.user._adapter.datastores.test.manager.collection('user').insertOne({_id: 123, name: 'bob'}, function(err) { + (function(iifeDone) { models.user._adapter.datastores.test.manager.collection('user').insertOne({_id: 123, name: 'bob'}).then(function(){ iifeDone();}).catch(function(err){ iifeDone(err);});})(function(err) { if (err) {return done(err);} models.user.updateOne({id: 123}, {name: 'joe'}).exec(function(err, record) { if (err) {return done(err);} @@ -164,7 +164,7 @@ describe('dontUseObjectIds', function() { it('should update the records correctly', function(done) { - models.user._adapter.datastores.test.manager.collection('user').insertMany([{_id: 123, name: 'sid'}, {_id: 555, name: 'nancy'}], function(err) { + (function(iifeDone) {models.user._adapter.datastores.test.manager.collection('user').insertMany([{_id: 123, name: 'sid'}, {_id: 555, name: 'nancy'}]).then(function() { iifeDone();}).catch(function(err) { iifeDone(err);});})(function(err) { if (err) {return done(err);} models.user.update({id: {'>': 0}}, {name: 'joe'}).exec(function(err, records) { if (err) {return done(err);} @@ -185,7 +185,7 @@ describe('dontUseObjectIds', function() { it('should find a record w/ a numeric ID', function(done) { - models.user._adapter.datastores.test.manager.collection('user').insertOne({_id: 123, name: 'bob'}, function(err) { + (function(iifeDone) {models.user._adapter.datastores.test.manager.collection('user').insertOne({_id: 123, name: 'bob'}).then(function(){ iifeDone();}).catch(function(err) { iifeDone(err);});})(function(err) { if (err) {return done(err);} models.user.findOne({id: 123}).exec(function(err, record) { if (err) {return done(err);} @@ -203,7 +203,7 @@ describe('dontUseObjectIds', function() { it('should find the records correctly', function(done) { - models.user._adapter.datastores.test.manager.collection('user').insertMany([{_id: 123, name: 'sid'}, {_id: 555, name: 'nancy'}], function(err) { + (function(iifeDone) {models.user._adapter.datastores.test.manager.collection('user').insertMany([{_id: 123, name: 'sid'}, {_id: 555, name: 'nancy'}]).then(function() { iifeDone();}).catch(function(err) { iifeDone(err);});})(function(err) { if (err) {return done(err);} models.user.find({id: {'>': 0}}).exec(function(err, records) { if (err) {return done(err);} @@ -222,11 +222,11 @@ describe('dontUseObjectIds', function() { describe('Deleting a single record', function() { it('should delete the record correctly', function(done) { - models.user._adapter.datastores.test.manager.collection('user').insertOne({_id: 123, name: 'bob'}, function(err) { + (function(iifeDone) {models.user._adapter.datastores.test.manager.collection('user').insertOne({_id: 123, name: 'bob'}).then(function() { iifeDone();}).catch(function(err) { iifeDone(err);});})(function(err) { if (err) {return done(err);} models.user.destroy({id: 123}).exec(function(err) { if (err) {return done(err);} - models.user._adapter.datastores.test.manager.collection('user').find({}).toArray(function(err, records) { + (function(iifeDone) { models.user._adapter.datastores.test.manager.collection('user').find({}).toArray().then(function(records) { iifeDone(undefined, records);}).catch(function(err) { iifeDone(err);});})(function(err, records) { if (err) {return done(err);} assert.equal(records.length, 0); return done(); @@ -244,11 +244,11 @@ describe('dontUseObjectIds', function() { it('should delete the records correctly', function(done) { - models.user._adapter.datastores.test.manager.collection('user').insertMany([{_id: 123, name: 'sid'}, {_id: 555, name: 'nancy'}], function(err) { + (function(iifeDone) { models.user._adapter.datastores.test.manager.collection('user').insertMany([{_id: 123, name: 'sid'}, {_id: 555, name: 'nancy'}]).then(function() { iifeDone();}).catch(function(err) { iifeDone(err);});})(function(err) { if (err) {return done(err);} models.user.destroy({id: {'>': 0}}).exec(function(err) { if (err) {return done(err);} - models.user._adapter.datastores.test.manager.collection('user').find({}).toArray(function(err, records) { + (function(iifeDone) { models.user._adapter.datastores.test.manager.collection('user').find({}).toArray().then(function(records) { iifeDone(undefined, records);}).catch(function(err) { iifeDone(err);});})(function(err, records) { if (err) {return done(err);} assert.equal(records.length, 0); return done();