diff --git a/.gitignore b/.gitignore index 76c1484..cc6c7aa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +node_modules/ !mongodb-legacy.d.ts .vscode/ *.tgz diff --git a/.mocharc.json b/.mocharc.json index 6eec957..7d198c4 100644 --- a/.mocharc.json +++ b/.mocharc.json @@ -2,11 +2,12 @@ "$schema": "https://raw.githubusercontent.com/SchemaStore/schemastore/master/src/schemas/json/mocharc.json", "extension": ["js", "ts"], "recursive": true, - "failZero": false, + "failZero": true, "sort": true, "color": true, "require": [ "source-map-support/register", - "ts-node/register" + "ts-node/register", + "test/hooks/addons.js" ] } diff --git a/src/index.js b/src/index.js index ebab544..0826400 100644 --- a/src/index.js +++ b/src/index.js @@ -1,3 +1,79 @@ 'use strict'; -throw new Error('Work In Progress - Not intended for use at this time'); +const mongodb = require('mongodb'); + +const { makeLegacyMongoClient } = require('./legacy_wrappers/mongo_client'); +const { makeLegacyDb } = require('./legacy_wrappers/db'); +const { makeLegacyCollection } = require('./legacy_wrappers/collection'); +const { makeLegacyAdmin } = require('./legacy_wrappers/admin'); +const { + makeLegacyAggregationCursor, + makeLegacyFindCursor, + makeLegacyListCollectionsCursor, + makeLegacyListIndexesCursor +} = require('./legacy_wrappers/cursors'); +const { + makeLegacyGridFSBucket, + makeLegacyGridFSBucketWriteStream +} = require('./legacy_wrappers/gridfs'); +const { makeLegacyChangeStream } = require('./legacy_wrappers/change_stream'); +const { makeLegacyClientSession } = require('./legacy_wrappers/session'); +const { + makeLegacyUnorderedBulkOperation, + makeLegacyOrderedBulkOperation +} = require('./legacy_wrappers/bulk'); + +/** @type {import('..')} */ +module.exports = Object.create(null); +Object.defineProperty(module.exports, '__esModule', { value: true }); + +const classesWithAsyncAPIs = new Map([ + ['Admin', makeLegacyAdmin], + ['FindCursor', makeLegacyFindCursor], + ['ListCollectionsCursor', makeLegacyListCollectionsCursor], + ['ListIndexesCursor', makeLegacyListIndexesCursor], + ['AggregationCursor', makeLegacyAggregationCursor], + ['ChangeStream', makeLegacyChangeStream], + ['Collection', makeLegacyCollection], + ['Db', makeLegacyDb], + ['GridFSBucket', makeLegacyGridFSBucket], + ['ClientSession', makeLegacyClientSession], + ['MongoClient', makeLegacyMongoClient], + + // Need to be exported top-level still + ['ClientSession', makeLegacyClientSession], + ['GridFSBucketWriteStream', makeLegacyGridFSBucketWriteStream], + ['OrderedBulkOperation', makeLegacyOrderedBulkOperation], + ['UnorderedBulkOperation', makeLegacyUnorderedBulkOperation] +]); + +const TODO_SPECIAL_IMPORTS = new Map([ + ['ClientSession', '/lib/sessions'], + ['GridFSBucketWriteStream', '/lib/gridfs/upload'], + ['OrderedBulkOperation', '/lib/bulk/ordered'], + ['UnorderedBulkOperation', '/lib/bulk/unordered'] +]); + +for (const [missingTopLevelClassName, location] of TODO_SPECIAL_IMPORTS) { + mongodb[missingTopLevelClassName] = require(`mongodb${location}`)[missingTopLevelClassName]; +} + +for (const [mongodbExportName, mongodbExportValue] of Object.entries(mongodb)) { + let makeLegacyClass = classesWithAsyncAPIs.get(mongodbExportName); + if (makeLegacyClass != null) { + const patchedClass = makeLegacyClass(mongodbExportValue); + Object.defineProperty(module.exports, mongodbExportName, { + enumerable: true, + get: function () { + return patchedClass; + } + }); + } else { + Object.defineProperty(module.exports, mongodbExportName, { + enumerable: true, + get: function () { + return mongodbExportValue; + } + }); + } +} diff --git a/src/legacy_wrappers/admin.js b/src/legacy_wrappers/admin.js new file mode 100644 index 0000000..9c792a5 --- /dev/null +++ b/src/legacy_wrappers/admin.js @@ -0,0 +1,136 @@ +'use strict'; + +const { toLegacy, maybeCallback } = require('../utils'); + +module.exports = Object.create(null); +Object.defineProperty(module.exports, '__esModule', { value: true }); + +module.exports.makeLegacyAdmin = function (baseClass) { + class LegacyAdmin extends baseClass { + addUser(username, password, options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : typeof password === 'function' + ? password + : undefined; + options = + options != null && typeof options === 'object' + ? options + : password != null && typeof password === 'object' + ? password + : undefined; + return maybeCallback(super.addUser(username, password, options), callback); + } + + buildInfo(options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.buildInfo(options), callback); + } + + command(command, options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.command(command, options), callback); + } + + listDatabases(options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.listDatabases(options), callback); + } + + ping(options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.ping(options), callback); + } + + removeUser(username, options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.removeUser(username, options), callback); + } + + replSetGetStatus(options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.replSetGetStatus(options), callback); + } + + serverInfo(options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.serverInfo(options), callback); + } + + serverStatus(options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.serverStatus(options), callback); + } + + validateCollection(name, options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.validateCollection(name, options), callback); + } + } + + Object.defineProperty(baseClass.prototype, toLegacy, { + enumerable: false, + value: function () { + return Object.setPrototypeOf(this, LegacyAdmin.prototype); + } + }); + + return LegacyAdmin; +}; diff --git a/src/legacy_wrappers/bulk.js b/src/legacy_wrappers/bulk.js new file mode 100644 index 0000000..4a0d7f8 --- /dev/null +++ b/src/legacy_wrappers/bulk.js @@ -0,0 +1,36 @@ +'use strict'; + +const { maybeCallback } = require('../utils'); + +module.exports = Object.create(null); +Object.defineProperty(module.exports, '__esModule', { value: true }); + +module.exports.makeLegacyOrderedBulkOperation = function (baseClass) { + return class LegacyOrderedBulkOperation extends baseClass { + execute(options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.execute(options), callback); + } + }; +}; + +module.exports.makeLegacyUnorderedBulkOperation = function (baseClass) { + return class LegacyUnorderedBulkOperation extends baseClass { + execute(options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.execute(options), callback); + } + }; +}; diff --git a/src/legacy_wrappers/change_stream.js b/src/legacy_wrappers/change_stream.js new file mode 100644 index 0000000..4ac0094 --- /dev/null +++ b/src/legacy_wrappers/change_stream.js @@ -0,0 +1,32 @@ +'use strict'; + +const { toLegacy, maybeCallback } = require('../utils'); + +module.exports = Object.create(null); +Object.defineProperty(module.exports, '__esModule', { value: true }); + +module.exports.makeLegacyChangeStream = function (baseClass) { + class LegacyChangeStream extends baseClass { + close(callback) { + return maybeCallback(super.close(), callback); + } + hasNext(callback) { + return maybeCallback(super.hasNext(), callback); + } + next(callback) { + return maybeCallback(super.next(), callback); + } + tryNext(callback) { + return maybeCallback(super.tryNext(), callback); + } + } + + Object.defineProperty(baseClass.prototype, toLegacy, { + enumerable: false, + value: function () { + return Object.setPrototypeOf(this, LegacyChangeStream.prototype); + } + }); + + return LegacyChangeStream; +}; diff --git a/src/legacy_wrappers/collection.js b/src/legacy_wrappers/collection.js new file mode 100644 index 0000000..eb454ec --- /dev/null +++ b/src/legacy_wrappers/collection.js @@ -0,0 +1,413 @@ +'use strict'; + +const { toLegacy, maybeCallback } = require('../utils'); + +module.exports = Object.create(null); +Object.defineProperty(module.exports, '__esModule', { value: true }); + +module.exports.makeLegacyCollection = function (baseClass) { + class LegacyCollection extends baseClass { + // async APIs + bulkWrite(operations, options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.bulkWrite(operations, options), callback); + } + + count(filter, options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : typeof filter === 'function' + ? filter + : undefined; + options = typeof options !== 'function' ? options : undefined; + filter = typeof filter !== 'function' ? filter : undefined; + return maybeCallback(super.count(filter, options), callback); + } + + countDocuments(filter, options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : typeof filter === 'function' + ? filter + : undefined; + options = typeof options !== 'function' ? options : undefined; + filter = typeof filter !== 'function' ? filter : undefined; + return maybeCallback(super.countDocuments(filter, options), callback); + } + + estimatedDocumentCount(options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.estimatedDocumentCount(options), callback); + } + + createIndex(indexSpec, options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.createIndex(indexSpec, options), callback); + } + + createIndexes(indexSpecs, options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.createIndexes(indexSpecs, options), callback); + } + + dropIndex(indexName, options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.dropIndex(indexName, options), callback); + } + + dropIndexes(options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.dropIndexes(options), callback); + } + + deleteMany(filter, options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : typeof filter === 'function' + ? filter + : undefined; + options = typeof options !== 'function' ? options : undefined; + filter = typeof filter !== 'function' ? filter : undefined; + return maybeCallback(super.deleteMany(filter, options), callback); + } + + deleteOne(filter, options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : typeof filter === 'function' + ? filter + : undefined; + options = typeof options !== 'function' ? options : undefined; + filter = typeof filter !== 'function' ? filter : undefined; + return maybeCallback(super.deleteOne(filter, options), callback); + } + + distinct(key, filter, options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : typeof filter === 'function' + ? filter + : undefined; + options = typeof options !== 'function' ? options : undefined; + filter = typeof filter !== 'function' ? filter : undefined; + return maybeCallback(super.distinct(key, filter, options), callback); + } + + drop(options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.drop(options), callback); + } + + findOne(filter, options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : typeof filter === 'function' + ? filter + : undefined; + options = typeof options !== 'function' ? options : undefined; + filter = typeof filter !== 'function' ? filter : undefined; + return maybeCallback(super.findOne(filter, options), callback); + } + + findOneAndDelete(filter, options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : typeof filter === 'function' + ? filter + : undefined; + options = typeof options !== 'function' ? options : undefined; + filter = typeof filter !== 'function' ? filter : undefined; + return maybeCallback(super.findOneAndDelete(filter, options), callback); + } + + findOneAndReplace(filter, replacement, options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.findOneAndReplace(filter, replacement, options), callback); + } + + findOneAndUpdate(filter, update, options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.findOneAndUpdate(filter, update, options), callback); + } + + indexExists(indexes, options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.indexExists(indexes, options), callback); + } + + indexInformation(options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.indexInformation(options), callback); + } + + indexes(options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.indexes(options), callback); + } + + insert(docs, options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.insert(docs, options), callback); + } + + insertMany(docs, options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.insertMany(docs, options), callback); + } + + insertOne(doc, options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.insertOne(doc, options), callback); + } + + isCapped(options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.isCapped(options), callback); + } + + mapReduce(map, reduce, options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.mapReduce(map, reduce, options), callback); + } + + options(options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.options(options), callback); + } + + remove(filter, options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.remove(filter, options), callback); + } + + rename(newName, options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback( + super.rename(newName, options).then(collection => collection[toLegacy]()), + callback + ); + } + + replaceOne(filter, replacement, options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.replaceOne(filter, replacement, options), callback); + } + + stats(options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.stats(options), callback); + } + + update(filter, update, options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.update(filter, update, options), callback); + } + + updateMany(filter, update, options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.updateMany(filter, update, options), callback); + } + + updateOne(filter, update, options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.updateOne(filter, update, options), callback); + } + + // conversion APIs + aggregate(pipeline, options) { + return super.aggregate(pipeline, options)[toLegacy](); + } + + find(filter, options) { + return super.find(filter, options)[toLegacy](); + } + + listIndexes(options) { + return super.listIndexes(options)[toLegacy](); + } + + watch(pipeline, options) { + return super.watch(pipeline, options)[toLegacy](); + } + } + + Object.defineProperty(baseClass.prototype, toLegacy, { + enumerable: false, + value: function () { + return Object.setPrototypeOf(this, LegacyCollection.prototype); + } + }); + + return LegacyCollection; +}; diff --git a/src/legacy_wrappers/cursors.js b/src/legacy_wrappers/cursors.js new file mode 100644 index 0000000..417b9fd --- /dev/null +++ b/src/legacy_wrappers/cursors.js @@ -0,0 +1,197 @@ +'use strict'; + +const { maybeCallback } = require('../utils'); +const { toLegacy } = require('../utils'); + +module.exports = Object.create(null); +Object.defineProperty(module.exports, '__esModule', { value: true }); + +module.exports.makeLegacyFindCursor = function (baseClass) { + class LegacyFindCursor extends baseClass { + /** @deprecated Use `collection.estimatedDocumentCount` or `collection.countDocuments` instead */ + count(options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.count(options), callback); + } + + explain(verbosity, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof verbosity === 'function' + ? verbosity + : undefined; + verbosity = typeof verbosity !== 'function' ? verbosity : undefined; + return maybeCallback(super.explain(verbosity), callback); + } + + close(options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.close(options), callback); + } + forEach(iterator, callback) { + return maybeCallback(super.forEach(iterator), callback); + } + hasNext(callback) { + return maybeCallback(super.hasNext(), callback); + } + next(callback) { + return maybeCallback(super.next(), callback); + } + toArray(callback) { + return maybeCallback(super.toArray(), callback); + } + tryNext(callback) { + return maybeCallback(super.tryNext(), callback); + } + } + + Object.defineProperty(baseClass.prototype, toLegacy, { + enumerable: false, + value: function () { + return Object.setPrototypeOf(this, LegacyFindCursor.prototype); + } + }); + + return LegacyFindCursor; +}; + +module.exports.makeLegacyListCollectionsCursor = function (baseClass) { + class LegacyListCollectionsCursor extends baseClass { + close(options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.close(options), callback); + } + forEach(iterator, callback) { + return maybeCallback(super.forEach(iterator), callback); + } + hasNext(callback) { + return maybeCallback(super.hasNext(), callback); + } + next(callback) { + return maybeCallback(super.next(), callback); + } + toArray(callback) { + return maybeCallback(super.toArray(), callback); + } + tryNext(callback) { + return maybeCallback(super.tryNext(), callback); + } + } + + Object.defineProperty(baseClass.prototype, toLegacy, { + enumerable: false, + value: function () { + return Object.setPrototypeOf(this, LegacyListCollectionsCursor.prototype); + } + }); + + return LegacyListCollectionsCursor; +}; + +module.exports.makeLegacyListIndexesCursor = function (baseClass) { + class LegacyListIndexesCursor extends baseClass { + close(options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.close(options), callback); + } + forEach(iterator, callback) { + return maybeCallback(super.forEach(iterator), callback); + } + hasNext(callback) { + return maybeCallback(super.hasNext(), callback); + } + next(callback) { + return maybeCallback(super.next(), callback); + } + toArray(callback) { + return maybeCallback(super.toArray(), callback); + } + tryNext(callback) { + return maybeCallback(super.tryNext(), callback); + } + } + + Object.defineProperty(baseClass.prototype, toLegacy, { + enumerable: false, + value: function () { + return Object.setPrototypeOf(this, LegacyListIndexesCursor.prototype); + } + }); + + return LegacyListIndexesCursor; +}; + +module.exports.makeLegacyAggregationCursor = function (baseClass) { + class LegacyAggregationCursor extends baseClass { + explain(verbosity, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof verbosity === 'function' + ? verbosity + : undefined; + verbosity = typeof verbosity !== 'function' ? verbosity : undefined; + return maybeCallback(super.explain(verbosity), callback); + } + + close(options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.close(options), callback); + } + forEach(iterator, callback) { + return maybeCallback(super.forEach(iterator), callback); + } + hasNext(callback) { + return maybeCallback(super.hasNext(), callback); + } + next(callback) { + return maybeCallback(super.next(), callback); + } + toArray(callback) { + return maybeCallback(super.toArray(), callback); + } + tryNext(callback) { + return maybeCallback(super.tryNext(), callback); + } + } + + Object.defineProperty(baseClass.prototype, toLegacy, { + enumerable: false, + value: function () { + return Object.setPrototypeOf(this, LegacyAggregationCursor.prototype); + } + }); + + return LegacyAggregationCursor; +}; diff --git a/src/legacy_wrappers/db.js b/src/legacy_wrappers/db.js new file mode 100644 index 0000000..7072855 --- /dev/null +++ b/src/legacy_wrappers/db.js @@ -0,0 +1,190 @@ +'use strict'; + +const { toLegacy, maybeCallback } = require('../utils'); + +module.exports = Object.create(null); +Object.defineProperty(module.exports, '__esModule', { value: true }); + +module.exports.makeLegacyDb = function (baseClass) { + class LegacyDb extends baseClass { + command(command, options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.command(command, options), callback); + } + + // Async APIs + addUser(username, password, options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : typeof password === 'function' + ? password + : undefined; + options = + options != null && typeof options === 'object' + ? options + : password != null && typeof password === 'object' + ? password + : undefined; + return maybeCallback(super.addUser(username, password, options), callback); + } + + removeUser(username, options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.removeUser(username, options), callback); + } + + createCollection(name, options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback( + super.createCollection(name, options).then(collection => collection[toLegacy]()), + callback + ); + } + + dropCollection(name, options, callback) { + return maybeCallback(super.dropCollection(name, options), callback); + } + + createIndex(name, indexSpec, options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.createIndex(name, indexSpec, options), callback); + } + + dropDatabase(options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.dropDatabase(options), callback); + } + + indexInformation(name, options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.indexInformation(name, options), callback); + } + + profilingLevel(options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.profilingLevel(options), callback); + } + + setProfilingLevel(level, options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.setProfilingLevel(level, options), callback); + } + + renameCollection(from, to, options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback( + super.renameCollection(from, to, options).then(collection => collection[toLegacy]()), + callback + ); + } + + stats(options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.stats(options), callback); + } + + // Convert Result to legacy + collections(options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback( + super + .collections(options) + .then(collections => collections.map(collection => collection[toLegacy]())), + callback + ); + } + collection(name, options) { + return super.collection(name, options)[toLegacy](); + } + admin() { + return super.admin()[toLegacy](); + } + aggregate(pipeline, options) { + return super.aggregate(pipeline, options)[toLegacy](); + } + listCollections(filter, options) { + return super.listCollections(filter, options)[toLegacy](); + } + watch(pipeline, options) { + return super.watch(pipeline, options)[toLegacy](); + } + } + + Object.defineProperty(baseClass.prototype, toLegacy, { + enumerable: false, + value: function () { + return Object.setPrototypeOf(this, LegacyDb.prototype); + } + }); + + return LegacyDb; +}; diff --git a/src/legacy_wrappers/gridfs.js b/src/legacy_wrappers/gridfs.js new file mode 100644 index 0000000..67b49cc --- /dev/null +++ b/src/legacy_wrappers/gridfs.js @@ -0,0 +1,53 @@ +'use strict'; + +const { toLegacy, maybeCallback } = require('../utils'); + +module.exports = Object.create(null); +Object.defineProperty(module.exports, '__esModule', { value: true }); + +module.exports.makeLegacyGridFSBucket = function (baseClass) { + class LegacyGridFSBucket extends baseClass { + delete(id, callback) { + return maybeCallback(super.delete(id), callback); + } + + rename(id, filename, callback) { + return maybeCallback(super.rename(id, filename), callback); + } + + drop(callback) { + return maybeCallback(super.drop(), callback); + } + + // conversion + find(filter, options) { + return super.find(filter, options)[toLegacy](); + } + } + + Object.defineProperty(baseClass.prototype, toLegacy, { + enumerable: false, + value: function () { + return Object.setPrototypeOf(this, LegacyGridFSBucket.prototype); + } + }); + + return LegacyGridFSBucket; +}; + +module.exports.makeLegacyGridFSBucketWriteStream = function (baseClass) { + class LegacyGridFSBucketWriteStream extends baseClass { + abort(callback) { + maybeCallback(super.abort(), callback); + } + } + + Object.defineProperty(baseClass.prototype, toLegacy, { + enumerable: false, + value: function () { + return Object.setPrototypeOf(this, LegacyGridFSBucketWriteStream.prototype); + } + }); + + return LegacyGridFSBucketWriteStream; +}; diff --git a/src/legacy_wrappers/mongo_client.js b/src/legacy_wrappers/mongo_client.js new file mode 100644 index 0000000..863fe60 --- /dev/null +++ b/src/legacy_wrappers/mongo_client.js @@ -0,0 +1,63 @@ +'use strict'; + +const { toLegacy, maybeCallback } = require('../utils'); + +module.exports = Object.create(null); +Object.defineProperty(module.exports, '__esModule', { value: true }); + +module.exports.makeLegacyMongoClient = function (baseClass) { + class LegacyMongoClient extends baseClass { + static connect(url, options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + try { + const client = new this(url, options); + return maybeCallback(client.connect(callback), callback); + } catch (error) { + return maybeCallback(Promise.reject(error), callback); + } + } + + connect(callback) { + return maybeCallback(super.connect(), callback); + } + + close(force, callback) { + callback = + typeof callback === 'function' ? callback : typeof force === 'function' ? force : undefined; + force = typeof force !== 'function' ? force : undefined; + return maybeCallback(super.close(force), callback); + } + + // Convert to legacy versions of the following: + db(dbName, options) { + return super.db(dbName, options)[toLegacy](); + } + + watch(pipeline, options) { + return super.watch(pipeline, options)[toLegacy](); + } + + startSession(options) { + return super.startSession(options)[toLegacy](); + } + + withSession(options, executeWithSession) { + executeWithSession = + typeof executeWithSession === 'function' + ? executeWithSession + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return super.withSession(options, session => executeWithSession(session[toLegacy]())); + } + } + + return LegacyMongoClient; +}; diff --git a/src/legacy_wrappers/session.js b/src/legacy_wrappers/session.js new file mode 100644 index 0000000..879f12d --- /dev/null +++ b/src/legacy_wrappers/session.js @@ -0,0 +1,42 @@ +'use strict'; + +const { toLegacy, maybeCallback } = require('../utils'); + +module.exports = Object.create(null); +Object.defineProperty(module.exports, '__esModule', { value: true }); + +module.exports.makeLegacyClientSession = function (baseClass) { + class LegacyClientSession extends baseClass { + abortTransaction(callback) { + return maybeCallback(super.abortTransaction(), callback); + } + + commitTransaction(callback) { + return maybeCallback(super.commitTransaction(), callback); + } + + endSession(options, callback) { + callback = + typeof callback === 'function' + ? callback + : typeof options === 'function' + ? options + : undefined; + options = typeof options !== 'function' ? options : undefined; + return maybeCallback(super.endSession(options), callback); + } + + withTransaction(executeWithTransaction, options) { + super.withTransaction(session => executeWithTransaction(session[toLegacy]()), options); + } + } + + Object.defineProperty(baseClass.prototype, toLegacy, { + enumerable: false, + value: function () { + return Object.setPrototypeOf(this, LegacyClientSession.prototype); + } + }); + + return LegacyClientSession; +}; diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..ed3e998 --- /dev/null +++ b/src/utils.js @@ -0,0 +1,25 @@ +'use strict'; + +module.exports = Object.create(null); +Object.defineProperty(module.exports, '__esModule', { value: true }); + +module.exports.toLegacy = Symbol.for('@@mdb.callbacks.toLegacy'); + +/** + * + * @template T + * @param {Promise} promise - promise intended for optional wrapping in callback style invocation + * @param {(error?: Error, result?: T) => void} [callback] - optional callback argument to handle in case the API was invoked with a callback + * @returns {Promise | void} + */ +module.exports.maybeCallback = (promise, callback) => { + if (callback != null) { + promise.then( + result => callback(undefined, result), + error => callback(error) + ); + return; + } + + return promise; +}; diff --git a/test/hooks/addons.js b/test/hooks/addons.js new file mode 100644 index 0000000..67010d3 --- /dev/null +++ b/test/hooks/addons.js @@ -0,0 +1,6 @@ +'use strict'; + +const chai = require('chai'); +chai.use(require('sinon-chai')); + +module.exports = {}; diff --git a/test/tools/api.js b/test/tools/api.js index 68c9644..c171a02 100644 --- a/test/tools/api.js +++ b/test/tools/api.js @@ -40,6 +40,7 @@ const asyncApi = [ { className: 'AggregationCursor', method: 'explain', returnType: 'Promise' }, // Super class of Unordered/Ordered Bulk operations + // This is listed here as a reference for completeness, but it is tested by the subclass overrides of execute // { className: 'BulkOperationBase', method: 'execute', returnType: 'Promise' }, { className: 'OrderedBulkOperation', method: 'execute', returnType: 'Promise' }, { className: 'UnorderedBulkOperation', method: 'execute', returnType: 'Promise' }, @@ -111,6 +112,11 @@ const asyncApi = [ { className: 'MongoClient', method: 'close', returnType: 'Promise' }, { className: 'MongoClient', method: 'connect', returnType: 'Promise' }, + // Manually test the static version of connect + // This is listed here as a reference for completeness, but it is tested manually + // it is checked to exist in index.test.js + // its functionally tested in maybe_callback.test.js + // { className: 'MongoClient', method: 'static connect', returnType: 'Promise' }, ]; const transformMethods = [ @@ -144,6 +150,8 @@ const transformMethods = [ module.exports.asyncApi = asyncApi; module.exports.transformMethods = transformMethods; module.exports.asyncApiClasses = new Set(asyncApi.map(({className}) => className)) -module.exports.classesToMethods = new Map([...asyncApi, ...transformMethods].map((api, _, array) => - [api.className, new Set(array.filter(v => v.className === api.className))] -)); +module.exports.classNameToMethodList = new Map([...asyncApi, ...transformMethods].map((api, _, array) => { + const methodNames = Array.from(new Set(Array.from(array.filter(v => v.className === api.className), ({ method }) => method))) + methodNames.sort((a, b) => a.localeCompare(b)) + return [api.className, methodNames] +})); diff --git a/test/unit/index.test.js b/test/unit/index.test.js new file mode 100644 index 0000000..bc95473 --- /dev/null +++ b/test/unit/index.test.js @@ -0,0 +1,45 @@ +'use strict'; + +const { expect } = require('chai'); +const mdbLegacy = require('../..'); +const mdbDriver = require('mongodb'); +const { asyncApiClasses, classNameToMethodList } = require('../tools/api'); + +describe('index.js', () => { + it('should export everything mongodb does', () => { + expect(mdbLegacy).to.have.all.keys(Object.keys(mdbDriver)); + }); + + for (const className of asyncApiClasses) { + it(`should export ${className} as a subclass of mongodb.${className}`, () => { + expect(mdbLegacy[className]).to.have.property('prototype'); + expect(mdbLegacy[className].prototype).to.be.instanceOf(mdbDriver[className]); + }); + } + + describe('subclass for legacy callback support', () => { + for (const [className, methodNames] of classNameToMethodList) { + describe(`class ${className}`, () => { + for (const method of methodNames) { + it(`should define override ${method}()`, () => { + expect(mdbLegacy[className].prototype) + .to.have.own.property(method) + .that.is.a('function'); + }); + } + + it(`should only define methods declared in api table for ${className}`, () => { + let names = Object.getOwnPropertyNames(mdbLegacy[className].prototype).filter( + name => name !== 'constructor' + ); + names.sort((a, b) => String.prototype.localeCompare.call(a, b)); + expect(names).to.be.deep.equal(methodNames); + }); + }); + } + + it('class MongoClient should define static override connect()', () => { + expect(mdbLegacy.MongoClient).to.have.own.property('connect').that.is.a('function'); + }); + }); +}); diff --git a/test/unit/test.test.js b/test/unit/test.test.js deleted file mode 100644 index e69de29..0000000 diff --git a/test/unit/utils.test.js b/test/unit/utils.test.js new file mode 100644 index 0000000..3840d65 --- /dev/null +++ b/test/unit/utils.test.js @@ -0,0 +1,94 @@ +'use strict'; + +const { expect } = require('chai'); +const utils = require('../../src/utils'); + +describe('utils.js', () => { + describe('exports', () => { + it('should have toLegacy symbol', () => { + expect(utils).to.have.property('toLegacy').that.is.a('symbol'); + }); + + it('should have maybeCallback helper', () => { + expect(utils).to.have.property('maybeCallback').that.is.a('function'); + }); + }); + + describe('maybeCallback', () => { + const maybeCallback = utils.maybeCallback; + it('should accept to two arguments', () => { + expect(maybeCallback).to.have.lengthOf(2); + }); + + describe('when handling an error case', () => { + it('should pass the error to the callback provided', done => { + const superPromiseRejection = Promise.reject(new Error('fail')); + const result = maybeCallback(superPromiseRejection, (error, result) => { + try { + expect(result).to.not.exist; + expect(error).to.be.instanceOf(Error); + return done(); + } catch (assertionError) { + return done(assertionError); + } + }); + expect(result).to.be.undefined; + }); + + it('should return the rejected promise to the caller when no callback is provided', async () => { + const superPromiseRejection = Promise.reject(new Error('fail')); + const returnedPromise = maybeCallback(superPromiseRejection, null); + expect(returnedPromise).to.equal(superPromiseRejection); + const thrownError = await returnedPromise.catch(error => error); + expect(thrownError).to.be.instanceOf(Error); + }); + + it('should not modify a rejection error promise', async () => { + class MyError extends Error {} + const driverError = Object.freeze(new MyError()); + const rejection = Promise.reject(driverError); + const thrownError = await maybeCallback(rejection, null).catch(error => error); + expect(thrownError).to.be.equal(driverError); + }); + + it('should not modify a rejection error when passed to callback', done => { + class MyError extends Error {} + const driverError = Object.freeze(new MyError()); + const rejection = Promise.reject(driverError); + maybeCallback(rejection, error => { + try { + expect(error).to.exist; + expect(error).to.equal(driverError); + done(); + } catch (assertionError) { + done(assertionError); + } + }); + }); + }); + + describe('when handling a success case', () => { + it('should pass the result and undefined error to the callback provided', done => { + const superPromiseSuccess = Promise.resolve(2); + + const result = maybeCallback(superPromiseSuccess, (error, result) => { + try { + expect(error).to.be.undefined; + expect(result).to.equal(2); + done(); + } catch (assertionError) { + done(assertionError); + } + }); + expect(result).to.be.undefined; + }); + + it('should return the resolved promise to the caller when no callback is provided', async () => { + const superPromiseSuccess = Promise.resolve(2); + const result = maybeCallback(superPromiseSuccess); + expect(result).to.equal(superPromiseSuccess); + expect(await result).to.equal(2); + }); + }); + }); +});