├── .gitignore ├── test ├── logger.js ├── mongo-client.js ├── find-cursor.js ├── db.js └── collection.js ├── logger.js ├── .eslintrc ├── utils.js ├── CHANGELOG.md ├── find-cursor.js ├── package.json ├── index.js ├── .github └── workflows │ └── main.yml ├── mongo-client.js ├── README.md ├── db.js └── collection.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /test/logger.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert/strict'); 2 | const mongo = require('../index.js'); 3 | 4 | describe('logger', function() { 5 | it('logger.setLevel', function() { 6 | assert.doesNotThrow( 7 | () => { 8 | mongo.Logger.setLevel('info'); 9 | } 10 | ); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /logger.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | // https://github.com/mongodb/node-mongodb-native/blob/v5.0.0/etc/notes/CHANGES_5.0.0.md#mongoclientoptionslogger-and-mongoclientoptionsloglevel-removed 3 | class EmulateLogger { 4 | static setLevel() { 5 | // Do nothing 6 | } 7 | } 8 | 9 | return EmulateLogger; 10 | }; 11 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "apostrophe" 4 | ], 5 | "globals": { 6 | "apos": true 7 | }, 8 | "overrides": [ 9 | { 10 | "files": [ 11 | "test/**/*.js" 12 | ], 13 | "rules": { 14 | "max-len": [ 15 | "error", 16 | { 17 | "code": 120 18 | } 19 | ] 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /utils.js: -------------------------------------------------------------------------------- 1 | const toEmulate = Symbol.for('@@mdb.callbacks.toEmulate'); 2 | 3 | const wrapMaybeCallback = (promise, callback, wrap = (result) => result) => { 4 | if (callback) { 5 | promise.then( 6 | result => callback(undefined, wrap(result)), 7 | error => callback(error) 8 | ); 9 | 10 | return; 11 | } 12 | 13 | return promise.then(result => wrap(result)); 14 | }; 15 | 16 | module.exports = { 17 | toEmulate, 18 | wrapMaybeCallback 19 | }; 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## 1.0.6 (2024-07-19) 4 | 5 | ### Changes 6 | 7 | * Reset sort when using `find-cursor.count` due to internal code using projection with `{ _id: 1 }`. 8 | 9 | ## 1.0.5 (2024-07-09) 10 | 11 | ### Add 12 | 13 | * Add integration test for `collection.count`, `find-cursor.count` and `find-cursor.sort`. 14 | * Add support for the `mongodb@6.8.0` driver and up. 15 | 16 | ### Changes 17 | 18 | * Use a projection to count documents. 19 | 20 | ## 1.0.4 (2024-07-04) 21 | 22 | ### Fix 23 | 24 | * Update package-lock.json to reflect package.json content. 25 | 26 | ## 1.0.3 (2024-06-28) 27 | 28 | ### Fix 29 | 30 | * Lock to `mongodb@6.7.0` to prevent issues with `cursor.count` not using the current query filter. 31 | 32 | ## 1.0.2 33 | 34 | ### Fix 35 | 36 | * Discard connection options not permitted or required by newer MongoDB drivers. Important for `emulate-mongo-2-driver` which depends on this module. 37 | 38 | ## 1.0.1 39 | 40 | ### Fix 41 | 42 | * Allow `FindCursor.sort` with `false` as sort key. 43 | 44 | ## 1.0.0 45 | 46 | Initial release. 47 | -------------------------------------------------------------------------------- /find-cursor.js: -------------------------------------------------------------------------------- 1 | const { toEmulate, wrapMaybeCallback } = require('./utils.js'); 2 | 3 | module.exports = function (baseClass) { 4 | class EmulateFindCursor extends baseClass { 5 | count(options, callback) { 6 | callback = 7 | typeof callback === 'function' 8 | ? callback 9 | : typeof options === 'function' 10 | ? options 11 | : undefined; 12 | options = typeof options !== 'function' ? options : undefined; 13 | 14 | return wrapMaybeCallback( 15 | this 16 | .clone() 17 | .project({ _id: 1 }) 18 | .sort(null) 19 | .toArray() 20 | .then(count => count.length), 21 | callback 22 | ); 23 | } 24 | 25 | sort(sort, direction) { 26 | return super.sort(sort || {}, direction); 27 | } 28 | } 29 | 30 | Object.defineProperty( 31 | baseClass.prototype, 32 | toEmulate, 33 | { 34 | enumerable: false, 35 | value: function () { 36 | Object.setPrototypeOf(this, EmulateFindCursor.prototype); 37 | return this; 38 | } 39 | } 40 | ); 41 | 42 | return EmulateFindCursor; 43 | }; 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@apostrophecms/emulate-mongo-3-driver", 3 | "version": "1.0.6", 4 | "description": "Emulate the Mongo 3.x nodejs driver on top of the Mongo 6.x nodejs driver, for bc", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "npx eslint . && mocha" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/apostrophecms/emulate-mongo-3-driver.git" 12 | }, 13 | "keywords": [ 14 | "mongodb", 15 | "mongo", 16 | "apostrophecms" 17 | ], 18 | "author": "Apostrophe Technologies", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/apostrophecms/emulate-mongo-3-driver/issues" 22 | }, 23 | "homepage": "https://github.com/apostrophecms/emulate-mongo-3-driver#readme", 24 | "dependencies": { 25 | "mongodb": "^6.8.0", 26 | "mongodb-legacy": "^6.0.1" 27 | }, 28 | "devDependencies": { 29 | "eslint": "^8.57.0", 30 | "eslint-config-apostrophe": "^4.3.0", 31 | "eslint-config-standard": "^17.1.0", 32 | "eslint-plugin-import": "^2.18.2", 33 | "eslint-plugin-node": "^11.1.0", 34 | "eslint-plugin-promise": "^6.4.0", 35 | "mocha": "^10.6.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const mongodb = require('mongodb-legacy'); 2 | const collection = require('./collection.js'); 3 | const findCursor = require('./find-cursor.js'); 4 | const db = require('./db.js'); 5 | const logger = require('./logger.js'); 6 | const mongoClient = require('./mongo-client.js'); 7 | 8 | const emulateClasses = new Map([ 9 | [ 'FindCursor', findCursor ], 10 | [ 'Collection', collection ], 11 | [ 'Db', db ], 12 | [ 'Logger', logger ], 13 | [ 'MongoClient', mongoClient ] 14 | ]); 15 | 16 | const entries = Object.entries(mongodb).concat([ [ 'Logger', null ] ]); 17 | for (const [ mongodbExportName, mongodbExportValue ] of entries) { 18 | const emulateClass = emulateClasses.get(mongodbExportName); 19 | if (emulateClass != null) { 20 | const patchedClass = emulateClass(mongodbExportValue); 21 | Object.defineProperty( 22 | module.exports, 23 | mongodbExportName, 24 | { 25 | enumerable: true, 26 | get: function () { 27 | return patchedClass; 28 | } 29 | } 30 | ); 31 | } else { 32 | Object.defineProperty( 33 | module.exports, 34 | mongodbExportName, 35 | { 36 | enumerable: true, 37 | get: function () { 38 | return mongodbExportValue; 39 | } 40 | } 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: tests 4 | 5 | # Controls when the action will run. 6 | on: 7 | push: 8 | branches: [ '*' ] 9 | pull_request: 10 | branches: [ '*' ] 11 | 12 | # Allows you to run this workflow manually from the Actions tab 13 | workflow_dispatch: 14 | 15 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 16 | jobs: 17 | # This workflow contains a single job called "build" 18 | build: 19 | # The type of runner that the job will run on 20 | runs-on: ubuntu-latest 21 | strategy: 22 | matrix: 23 | node-version: [16, 18, 20] 24 | mongodb-version: [4.4, 5.0, 6.0] 25 | 26 | # Steps represent a sequence of tasks that will be executed as part of the job 27 | steps: 28 | - name: Git checkout 29 | uses: actions/checkout@v2 30 | 31 | - name: Use Node.js ${{ matrix.node-version }} 32 | uses: actions/setup-node@v1 33 | with: 34 | node-version: ${{ matrix.node-version }} 35 | 36 | - name: Start MongoDB 37 | uses: supercharge/mongodb-github-action@1.3.0 38 | with: 39 | mongodb-version: ${{ matrix.mongodb-version }} 40 | 41 | - run: npm ci 42 | 43 | - run: npm test 44 | env: 45 | CI: true 46 | -------------------------------------------------------------------------------- /mongo-client.js: -------------------------------------------------------------------------------- 1 | const { toEmulate, wrapMaybeCallback } = require('./utils.js'); 2 | 3 | module.exports = function (baseClass) { 4 | class EmulateMongoClient extends baseClass { 5 | constructor(connectionString, options) { 6 | const { 7 | useUnifiedTopology, 8 | useNewUrlParser, 9 | autoReconnect, 10 | reconnectTries, 11 | reconnectInterval, 12 | ...v6Options 13 | } = options || {}; 14 | 15 | super(connectionString, v6Options); 16 | } 17 | 18 | static connect(url, options, callback) { 19 | callback = 20 | typeof callback === 'function' 21 | ? callback 22 | : typeof options === 'function' 23 | ? options 24 | : undefined; 25 | options = typeof options !== 'function' ? options : undefined; 26 | 27 | try { 28 | const client = new this(url, options); 29 | 30 | return client.connect(callback); 31 | } catch (error) { 32 | return wrapMaybeCallback(Promise.reject(error), callback); 33 | } 34 | } 35 | 36 | connect(callback) { 37 | return wrapMaybeCallback( 38 | super.connect().then(() => this), 39 | callback 40 | ); 41 | } 42 | 43 | db(dbName, options) { 44 | return super.db(dbName, options)[toEmulate](); 45 | } 46 | } 47 | 48 | return EmulateMongoClient; 49 | }; 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @apostrophecms/emulate-mongo-3-driver 2 | 3 | ## Purpose 4 | 5 | You have legacy code that depends on the 3.x version of the MongoDB Node.js driver. 6 | You don't want to upgrade to the 6.x driver because of backwards compability problems in 7 | [v4](https://github.com/mongodb/node-mongodb-native/blob/v6.0.0/etc/notes/CHANGES_4.0.0.md), 8 | [v5](https://github.com/mongodb/node-mongodb-native/blob/v6.0.0/etc/notes/CHANGES_5.0.0.md), 9 | [v6](https://github.com/mongodb/node-mongodb-native/blob/v6.0.0/etc/notes/CHANGES_6.0.0.md) 10 | but you don't have a choice because of reported vulnerabilities 11 | such as those detected by `npm audit`. 12 | 13 | `@apostrophecms/emulate-mongo-3-driver` aims to be a compatible emulation 14 | of the 3.x version of the MongoDB Node.js driver, 15 | implemented as a wrapper for the 6.x driver. 16 | 17 | It was created for long term support of [ApostropheCMS](https://apostrophecms.com). 18 | Of course, ApostropheCMS 3.x and 4.x will use the MongoDB 6.x driver directly. 19 | 20 | ## Usage 21 | 22 | If you are using ApostropheCMS, this is **standard** beginning 23 | with versions 3.64.0+ and 4.2.0+. You don't have to do anything. 24 | 25 | The example below is for those who wish to use this driver in non-ApostropheCMS projects. 26 | 27 | ```sh 28 | npm install @apostrophecms/emulate-mongo-3-driver 29 | ``` 30 | 31 | ```javascript 32 | const mongo = require('@apostrophecms/emulate-mongo-3-driver'); 33 | 34 | // Use it here as if it were the 3.x driver 35 | ``` 36 | 37 | ## Goals 38 | 39 | This module aims for partial compatibility with the features mentioned 40 | as obsolete or changed in 41 | [v4](https://github.com/mongodb/node-mongodb-native/blob/v6.0.0/etc/notes/CHANGES_4.0.0.md), 42 | [v5](https://github.com/mongodb/node-mongodb-native/blob/v6.0.0/etc/notes/CHANGES_5.0.0.md), 43 | [v6](https://github.com/mongodb/node-mongodb-native/blob/v6.0.0/etc/notes/CHANGES_6.0.0.md) 44 | but there are omissions. 45 | 46 | An emphasis has been placed on features used by ApostropheCMS 47 | but PRs for further compatibility are welcome. 48 | 49 | ## What about those warnings? 50 | 51 | "What about the warnings re: insert, update and ensureIndex operations being obsolete?" 52 | 53 | Although deprecated, some of these operations are still supported by the 6.x driver 54 | and work just fine. 55 | 56 | However, since the preferred newer operations were also supported by the 3.x driver, 57 | the path forward is clear. 58 | 59 | We will migrate away from using them gradually, and you should do the same. 60 | 61 | It doesn't make sense to provide "deprecation-free" wrappers 62 | when doing the right thing is in easy reach. 63 | -------------------------------------------------------------------------------- /db.js: -------------------------------------------------------------------------------- 1 | const { toEmulate, wrapMaybeCallback } = require('./utils.js'); 2 | 3 | module.exports = function (baseClass) { 4 | class EmulateDb extends baseClass { 5 | collections(options, callback) { 6 | callback = 7 | typeof callback === 'function' 8 | ? callback 9 | : typeof options === 'function' 10 | ? options 11 | : undefined; 12 | options = typeof options !== 'function' ? options : undefined; 13 | 14 | return wrapMaybeCallback( 15 | super 16 | .collections(options) 17 | .then(collections => collections.map(collection => collection[toEmulate]())), 18 | callback 19 | ); 20 | } 21 | 22 | collection(name, options, callback) { 23 | callback = 24 | typeof callback === 'function' 25 | ? callback 26 | : typeof options === 'function' 27 | ? options 28 | : undefined; 29 | options = typeof options !== 'function' ? options : undefined; 30 | 31 | const collection = super.collection(name, options)[toEmulate](); 32 | if (callback) { 33 | callback(null, collection); 34 | } 35 | 36 | return collection; 37 | } 38 | 39 | createCollection(name, options, callback) { 40 | callback = 41 | typeof callback === 'function' 42 | ? callback 43 | : typeof options === 'function' 44 | ? options 45 | : undefined; 46 | options = typeof options !== 'function' ? options : undefined; 47 | 48 | return wrapMaybeCallback( 49 | super.createCollection(name, options).then(collection => collection[toEmulate]()), 50 | callback 51 | ); 52 | } 53 | 54 | ensureIndex(name, indexSpec, options, callback) { 55 | callback = 56 | typeof callback === 'function' 57 | ? callback 58 | : typeof options === 'function' 59 | ? options 60 | : undefined; 61 | options = typeof options !== 'function' ? options : undefined; 62 | 63 | return wrapMaybeCallback(super.createIndex(name, indexSpec, options), callback); 64 | } 65 | 66 | renameCollection(from, to, options, callback) { 67 | callback = 68 | typeof callback === 'function' 69 | ? callback 70 | : typeof options === 'function' 71 | ? options 72 | : undefined; 73 | options = typeof options !== 'function' ? options : undefined; 74 | 75 | return wrapMaybeCallback( 76 | super.renameCollection(from, to, options) 77 | .then(collection => collection[toEmulate]()), 78 | callback 79 | ); 80 | } 81 | } 82 | 83 | Object.defineProperty( 84 | baseClass.prototype, 85 | toEmulate, 86 | { 87 | enumerable: false, 88 | value: function () { 89 | return Object.setPrototypeOf(this, EmulateDb.prototype); 90 | } 91 | } 92 | ); 93 | 94 | return EmulateDb; 95 | }; 96 | -------------------------------------------------------------------------------- /test/mongo-client.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert/strict'); 2 | const mongo = require('../index.js'); 3 | 4 | describe('mongo-client', function() { 5 | let client; 6 | let db; 7 | 8 | afterEach(async function() { 9 | const collections = await db.collections(); 10 | await Promise.all(collections.map(collection => db.dropCollection(collection.collectionName))); 11 | await client.close(); 12 | }); 13 | 14 | beforeEach(async function() { 15 | client = new mongo.MongoClient('mongodb://localhost:27017/testdb', {}); 16 | await client.connect(); 17 | db = client.db(); 18 | }); 19 | 20 | it('mongoClient.constructor removes warnings', function() { 21 | const warningListener = warning => { 22 | if (warning.includes('[MONGODB DRIVER] Warning:')) { 23 | assert.fail('should not throws warning for deprecated options'); 24 | } 25 | }; 26 | process.on('warning', warningListener); 27 | 28 | try { 29 | const options = { 30 | useUnifiedTopology: true, 31 | useNewUrlParser: true 32 | }; 33 | const currentClient = new mongo.MongoClient('mongodb://localhost:27017/testdb', options); 34 | currentClient.close(); 35 | } finally { 36 | process.removeListener('warning', warningListener); 37 | } 38 | }); 39 | 40 | it('mongoClient.connect static (callback)', function(done) { 41 | mongo.MongoClient.connect( 42 | 'mongodb://localhost:27017/testdb', 43 | {}, 44 | (err, currentClient) => { 45 | try { 46 | assert.ifError(err); 47 | const actual = currentClient.constructor; 48 | const expected = mongo.MongoClient; 49 | 50 | assert.deepEqual(actual, expected); 51 | done(); 52 | } catch (error) { 53 | done(error); 54 | } finally { 55 | currentClient.close(); 56 | } 57 | } 58 | ); 59 | }); 60 | it('mongoClient.connect static (promise)', async function() { 61 | const currentClient = await mongo.MongoClient.connect('mongodb://localhost:27017/testdb', {}); 62 | 63 | try { 64 | const actual = currentClient.constructor; 65 | const expected = mongo.MongoClient; 66 | 67 | assert.deepEqual(actual, expected); 68 | } finally { 69 | currentClient.close(); 70 | } 71 | }); 72 | 73 | it('mongoClient.connect (callback)', function(done) { 74 | const newClient = new mongo.MongoClient('mongodb://localhost:27017/testdb', {}); 75 | newClient.connect( 76 | (err, currentClient) => { 77 | try { 78 | assert.ifError(err); 79 | const actual = currentClient.constructor; 80 | const expected = mongo.MongoClient; 81 | 82 | assert.deepEqual(actual, expected); 83 | done(); 84 | } catch (error) { 85 | done(error); 86 | } finally { 87 | currentClient.close(); 88 | } 89 | } 90 | ); 91 | }); 92 | it('mongoClient.connect (promise)', async function() { 93 | const newClient = new mongo.MongoClient('mongodb://localhost:27017/testdb', {}); 94 | const currentClient = await newClient.connect(); 95 | 96 | try { 97 | const actual = currentClient.constructor; 98 | const expected = mongo.MongoClient; 99 | 100 | assert.deepEqual(actual, expected); 101 | } finally { 102 | currentClient.close(); 103 | } 104 | }); 105 | 106 | it('mongoClient.db', function() { 107 | const currentDb = client.db('testdb', {}); 108 | 109 | const actual = currentDb.constructor; 110 | const expected = mongo.Db; 111 | 112 | assert.deepEqual(actual, expected); 113 | }); 114 | }); 115 | -------------------------------------------------------------------------------- /test/find-cursor.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert/strict'); 2 | const mongo = require('../index.js'); 3 | 4 | describe('find-cursor', function() { 5 | let client; 6 | let db; 7 | 8 | afterEach(async function() { 9 | const collections = await db.collections(); 10 | await Promise.all(collections.map(collection => db.dropCollection(collection.collectionName))); 11 | await client.close(); 12 | }); 13 | 14 | beforeEach(async function() { 15 | client = new mongo.MongoClient('mongodb://localhost:27017/testdb', {}); 16 | await client.connect(); 17 | db = client.db(); 18 | }); 19 | 20 | it('findCursor.count (callback)', function(done) { 21 | const trees = db.collection('trees'); 22 | trees.insert( 23 | [ 24 | { 25 | title: 'birch', 26 | type: 'tree' 27 | }, 28 | { 29 | title: 'oak', 30 | type: 'tree' 31 | }, 32 | { 33 | title: 'rhododendron ', 34 | type: 'shrub' 35 | } 36 | ], 37 | {}, 38 | (insertError) => { 39 | if (insertError) { 40 | done(insertError); 41 | 42 | return; 43 | } 44 | 45 | trees.find({ type: 'shrub' }).count( 46 | (err, result) => { 47 | try { 48 | assert.ifError(err); 49 | const actual = result; 50 | const expected = 1; 51 | 52 | assert.equal(actual, expected); 53 | done(); 54 | } catch (error) { 55 | done(error); 56 | } 57 | } 58 | ); 59 | } 60 | ); 61 | }); 62 | it('findCursor.count (promise)', async function() { 63 | const trees = db.collection('trees'); 64 | await trees.insert( 65 | [ 66 | { 67 | title: 'birch', 68 | type: 'tree' 69 | }, 70 | { 71 | title: 'oak', 72 | type: 'tree' 73 | }, 74 | { 75 | title: 'rhododendron ', 76 | type: 'shrub' 77 | } 78 | ] 79 | ); 80 | 81 | const actual = { 82 | tree: await trees.find({ type: 'tree' }).count(), 83 | shrub: await trees.find({ type: 'shrub' }).count(), 84 | all: await trees.find().count() 85 | }; 86 | const expected = { 87 | tree: 2, 88 | shrub: 1, 89 | all: 3 90 | }; 91 | 92 | assert.deepEqual(actual, expected); 93 | }); 94 | 95 | it('findCursor.sort (callback)', function(done) { 96 | const trees = db.collection('trees'); 97 | trees.insert( 98 | [ 99 | { 100 | title: 'birch', 101 | type: 'tree' 102 | }, 103 | { 104 | title: 'oak', 105 | type: 'tree' 106 | }, 107 | { 108 | title: 'rhododendron ', 109 | type: 'shrub' 110 | } 111 | ], 112 | {}, 113 | (insertError) => { 114 | if (insertError) { 115 | done(insertError); 116 | 117 | return; 118 | } 119 | 120 | trees 121 | .find({ type: 'tree' }) 122 | .sort('title', -1) 123 | .toArray( 124 | (err, result) => { 125 | try { 126 | assert.ifError(err); 127 | const actual = result; 128 | const expected = [ 129 | { 130 | _id: actual.at(0)._id, 131 | title: 'oak', 132 | type: 'tree' 133 | }, 134 | { 135 | _id: actual.at(1)._id, 136 | title: 'birch', 137 | type: 'tree' 138 | } 139 | ]; 140 | 141 | assert.deepEqual(actual, expected); 142 | done(); 143 | } catch (error) { 144 | done(error); 145 | } 146 | } 147 | ); 148 | } 149 | ); 150 | }); 151 | it('findCursor.sort (promise)', async function() { 152 | const trees = db.collection('trees'); 153 | await trees.insert( 154 | [ 155 | { 156 | title: 'birch', 157 | type: 'tree' 158 | }, 159 | { 160 | title: 'oak', 161 | type: 'tree' 162 | }, 163 | { 164 | title: 'rhododendron ', 165 | type: 'shrub' 166 | } 167 | ] 168 | ); 169 | 170 | const actual = await trees 171 | .find({ type: 'tree' }, { title: 1 }) 172 | .sort('title', -1) 173 | .toArray(); 174 | const expected = [ 175 | { 176 | _id: actual.at(0)._id, 177 | title: 'oak', 178 | type: 'tree' 179 | }, 180 | { 181 | _id: actual.at(1)._id, 182 | title: 'birch', 183 | type: 'tree' 184 | } 185 | ]; 186 | 187 | assert.deepEqual(actual, expected); 188 | }); 189 | }); 190 | -------------------------------------------------------------------------------- /test/db.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert/strict'); 2 | const mongo = require('../index.js'); 3 | 4 | describe('db', function() { 5 | let client; 6 | let db; 7 | 8 | afterEach(async function() { 9 | const collections = await db.collections(); 10 | await Promise.all(collections.map(collection => db.dropCollection(collection.collectionName))); 11 | await client.close(); 12 | }); 13 | 14 | beforeEach(async function() { 15 | client = new mongo.MongoClient('mongodb://localhost:27017/testdb', {}); 16 | await client.connect(); 17 | db = client.db(); 18 | }); 19 | 20 | it('db.collections (callback)', function(done) { 21 | db.collections( 22 | {}, 23 | function(err, collections) { 24 | try { 25 | assert.ifError(err); 26 | const actual = collections.map(collection => collection.constructor); 27 | const expected = Array(collections.length).fill(mongo.Collection); 28 | 29 | assert.deepEqual(actual, expected); 30 | done(); 31 | } catch (error) { 32 | done(error); 33 | } 34 | } 35 | ); 36 | }); 37 | it('db.collections (promise)', async function() { 38 | const collections = await db.collections(); 39 | 40 | const actual = collections.map(collection => collection.constructor); 41 | const expected = Array(collections.length).fill(mongo.Collection); 42 | 43 | assert.deepEqual(actual, expected); 44 | }); 45 | 46 | it('db.collection (callback)', function(done) { 47 | db.collection( 48 | 'trees', 49 | {}, 50 | function(err, collection) { 51 | try { 52 | assert.ifError(err); 53 | const actual = collection.constructor; 54 | const expected = mongo.Collection; 55 | 56 | assert.deepEqual(actual, expected); 57 | done(); 58 | } catch (error) { 59 | done(error); 60 | } 61 | } 62 | ); 63 | }); 64 | it('db.collection (promise)', async function() { 65 | const collection = await db.collection('trees', {}); 66 | 67 | const actual = collection.constructor; 68 | const expected = mongo.Collection; 69 | 70 | assert.deepEqual(actual, expected); 71 | }); 72 | 73 | it('db.createCollection (callback)', function(done) { 74 | db.createCollection( 75 | 'leaves', 76 | {}, 77 | function(err, collection) { 78 | try { 79 | assert.ifError(err); 80 | const actual = collection.constructor; 81 | const expected = mongo.Collection; 82 | 83 | assert.deepEqual(actual, expected); 84 | done(); 85 | } catch (error) { 86 | done(error); 87 | } 88 | } 89 | ); 90 | }); 91 | it('db.createCollection (promise)', async function() { 92 | const collection = await db.createCollection('leaves', {}); 93 | 94 | const actual = collection.constructor; 95 | const expected = mongo.Collection; 96 | 97 | assert.deepEqual(actual, expected); 98 | }); 99 | 100 | it('db.ensureIndex (callback)', function(done) { 101 | db.ensureIndex( 102 | 'location_2dsphere', 103 | { 104 | location: '2dsphere' 105 | }, 106 | {}, 107 | (err, index) => { 108 | try { 109 | assert.ifError(err); 110 | const actual = index; 111 | const expected = 'location_2dsphere'; 112 | 113 | assert.equal(actual, expected); 114 | done(); 115 | } catch (error) { 116 | done(error); 117 | } 118 | } 119 | ); 120 | }); 121 | it('db.ensureIndex (promise)', async function() { 122 | const actual = await db.ensureIndex( 123 | 'location_2dsphere', 124 | { 125 | location: '2dsphere' 126 | } 127 | ); 128 | const expected = 'location_2dsphere'; 129 | 130 | assert.equal(actual, expected); 131 | }); 132 | 133 | it('db.renameCollection (callback)', function(done) { 134 | db.createCollection('leaves') 135 | .then(() => { 136 | db.renameCollection( 137 | 'leaves', 138 | 'branches', 139 | {}, 140 | function(err, collection) { 141 | try { 142 | assert.ifError(err); 143 | const actual = collection.constructor; 144 | const expected = mongo.Collection; 145 | 146 | assert.deepEqual(actual, expected); 147 | done(); 148 | } catch (error) { 149 | done(error); 150 | } 151 | } 152 | ); 153 | }); 154 | }); 155 | it('db.renameCollection (promise)', async function() { 156 | await db.createCollection('leaves'); 157 | 158 | const collection = await db.renameCollection('leaves', 'branches', {}); 159 | 160 | const actual = collection.constructor; 161 | const expected = mongo.Collection; 162 | 163 | assert.deepEqual(actual, expected); 164 | }); 165 | }); 166 | -------------------------------------------------------------------------------- /collection.js: -------------------------------------------------------------------------------- 1 | const { toEmulate, wrapMaybeCallback } = require('./utils.js'); 2 | 3 | module.exports = function (baseClass) { 4 | // - `BulkWriteResult.nInserted` -> `BulkWriteResult.insertedCount` 5 | // - `BulkWriteResult.nUpserted` -> `BulkWriteResult.upsertedCount` 6 | // - `BulkWriteResult.nMatched` -> `BulkWriteResult.matchedCount` 7 | // - `BulkWriteResult.nModified` -> `BulkWriteResult.modifiedCount` 8 | // - `BulkWriteResult.nRemoved` -> `BulkWriteResult.deletedCount` 9 | // - `BulkWriteResult.getUpsertedIds` -> `BulkWriteResult.upsertedIds` 10 | // `BulkWriteResult.getUpsertedIdAt(index: number)` 11 | // - `BulkWriteResult.getInsertedIds` -> `BulkWriteResult.insertedIds` 12 | const enrichWithResult = function (response) { 13 | const result = { 14 | nInserted: response.insertedCount, 15 | nUpserted: response.upsertedCount, 16 | nMatched: response.matchedCount, 17 | nModified: response.modifiedCount, 18 | nRemoved: response.deletedCount, 19 | getUpsertedIds: response.upsertedIds, 20 | getInsertedIds: response.insertedIds, 21 | ok: 1, 22 | n: response.insertedCount !== undefined 23 | ? response.insertedCount 24 | : response.modifiedCount !== undefined 25 | ? response.matchedCount 26 | : response.deletedCount !== undefined 27 | ? response.deletedCount 28 | : response.insertedId !== undefined 29 | ? 1 30 | : undefined 31 | }; 32 | 33 | const additional = response.insertedId !== undefined && 34 | response.insertedCount === undefined 35 | ? { 36 | insertedCount: 1, 37 | ops: [ { _id: response.insertedId } ] 38 | } 39 | : {}; 40 | 41 | return { 42 | result, 43 | ...additional, 44 | ...response 45 | }; 46 | }; 47 | 48 | class EmulateCollection extends baseClass { 49 | bulkWrite(operations, options, callback) { 50 | callback = 51 | typeof callback === 'function' 52 | ? callback 53 | : typeof options === 'function' 54 | ? options 55 | : undefined; 56 | options = typeof options !== 'function' ? options : undefined; 57 | 58 | return wrapMaybeCallback( 59 | super.bulkWrite(operations, options), 60 | callback, 61 | enrichWithResult 62 | ); 63 | } 64 | 65 | count(filter, options, callback) { 66 | return super.countDocuments(filter, options, callback); 67 | } 68 | 69 | ensureIndex(indexSpec, options, callback) { 70 | callback = 71 | typeof callback === 'function' 72 | ? callback 73 | : typeof options === 'function' 74 | ? options 75 | : undefined; 76 | options = typeof options !== 'function' ? options : undefined; 77 | 78 | return wrapMaybeCallback( 79 | super.createIndex(indexSpec, options), 80 | callback 81 | ); 82 | } 83 | 84 | insert(docs, options, callback) { 85 | if (Array.isArray(docs)) { 86 | return this.insertMany(docs, options, callback); 87 | } 88 | 89 | return this.insertOne(docs, options, callback); 90 | } 91 | 92 | insertMany(docs, options, callback) { 93 | callback = 94 | typeof callback === 'function' 95 | ? callback 96 | : typeof options === 'function' 97 | ? options 98 | : undefined; 99 | options = typeof options !== 'function' ? options : undefined; 100 | 101 | return wrapMaybeCallback( 102 | super.insertMany(docs, options), 103 | callback, 104 | enrichWithResult 105 | ); 106 | } 107 | 108 | insertOne(doc, options, callback) { 109 | callback = 110 | typeof callback === 'function' 111 | ? callback 112 | : typeof options === 'function' 113 | ? options 114 | : undefined; 115 | options = typeof options !== 'function' ? options : undefined; 116 | 117 | return wrapMaybeCallback( 118 | super.insertOne(doc, options), 119 | callback, 120 | enrichWithResult 121 | ); 122 | } 123 | 124 | deleteMany(filter, options, callback) { 125 | callback = 126 | typeof callback === 'function' 127 | ? callback 128 | : typeof options === 'function' 129 | ? options 130 | : typeof filter === 'function' 131 | ? filter 132 | : undefined; 133 | options = typeof options !== 'function' ? options : undefined; 134 | filter = typeof filter !== 'function' ? filter : undefined; 135 | 136 | return wrapMaybeCallback( 137 | super.deleteMany(filter, options), 138 | callback, 139 | enrichWithResult 140 | ); 141 | } 142 | 143 | deleteOne(filter, options, callback) { 144 | callback = 145 | typeof callback === 'function' 146 | ? callback 147 | : typeof options === 'function' 148 | ? options 149 | : typeof filter === 'function' 150 | ? filter 151 | : undefined; 152 | options = typeof options !== 'function' ? options : undefined; 153 | filter = typeof filter !== 'function' ? filter : undefined; 154 | 155 | return wrapMaybeCallback( 156 | super.deleteOne(filter, options), 157 | callback, 158 | enrichWithResult 159 | ); 160 | } 161 | 162 | remove(filter, options, callback) { 163 | if (options?.single) { 164 | const { single, ...newOptions } = options; 165 | 166 | return this.deleteOne(filter, newOptions, callback); 167 | } 168 | 169 | return this.deleteMany(filter, options, callback); 170 | } 171 | 172 | removeMany(filter, options, callback) { 173 | return this.deleteMany(filter, options, callback); 174 | } 175 | 176 | removeOne(filter, options, callback) { 177 | return this.deleteOne(filter, options, callback); 178 | } 179 | 180 | rename(newName, options, callback) { 181 | callback = 182 | typeof callback === 'function' 183 | ? callback 184 | : typeof options === 'function' 185 | ? options 186 | : undefined; 187 | options = typeof options !== 'function' ? options : undefined; 188 | 189 | return wrapMaybeCallback( 190 | super.rename(newName, options).then(collection => collection[toEmulate]()), 191 | callback 192 | ); 193 | } 194 | 195 | replaceOne(filter, replacement, options, callback) { 196 | callback = 197 | typeof callback === 'function' 198 | ? callback 199 | : typeof options === 'function' 200 | ? options 201 | : undefined; 202 | options = typeof options !== 'function' ? options : undefined; 203 | 204 | return wrapMaybeCallback( 205 | super.replaceOne(filter, replacement, options), 206 | callback, 207 | enrichWithResult 208 | ); 209 | } 210 | 211 | update(filter, update, options, callback) { 212 | if (options?.multi) { 213 | const { multi, ...newOptions } = options; 214 | 215 | return this.updateMany(filter, update, newOptions, callback); 216 | } 217 | 218 | return this.updateOne(filter, update, options, callback); 219 | } 220 | 221 | updateMany(filter, update, options, callback) { 222 | callback = 223 | typeof callback === 'function' 224 | ? callback 225 | : typeof options === 'function' 226 | ? options 227 | : undefined; 228 | options = typeof options !== 'function' ? options : undefined; 229 | 230 | return wrapMaybeCallback( 231 | super.updateMany(filter, update, options), 232 | callback, 233 | enrichWithResult 234 | ); 235 | } 236 | 237 | updateOne(filter, update, options, callback) { 238 | callback = 239 | typeof callback === 'function' 240 | ? callback 241 | : typeof options === 'function' 242 | ? options 243 | : undefined; 244 | options = typeof options !== 'function' ? options : undefined; 245 | 246 | return wrapMaybeCallback( 247 | super.updateOne(filter, update, options), 248 | callback, 249 | enrichWithResult 250 | ); 251 | } 252 | 253 | // conversion APIs 254 | // aggregate(pipeline, options) { 255 | // return super.aggregate(pipeline, options)[toEmulate](); 256 | // } 257 | // 258 | // initializeUnorderedBulkOp(options) { 259 | // return super.initializeUnorderedBulkOp(options)[toEmulate](); 260 | // } 261 | // 262 | // initializeOrderedBulkOp(options) { 263 | // return super.initializeOrderedBulkOp(options)[toEmulate](); 264 | // } 265 | 266 | find(filter, options) { 267 | return super.find(filter, options)[toEmulate](); 268 | } 269 | 270 | // listIndexes(options) { 271 | // return super.listIndexes(options)[toEmulate](); 272 | // } 273 | 274 | // watch(pipeline, options) { 275 | // return super.watch(pipeline, options)[toEmulate](); 276 | // } 277 | } 278 | 279 | Object.defineProperty( 280 | baseClass.prototype, 281 | toEmulate, 282 | { 283 | enumerable: false, 284 | value: function () { 285 | return Object.setPrototypeOf(this, EmulateCollection.prototype); 286 | } 287 | } 288 | ); 289 | 290 | return EmulateCollection; 291 | }; 292 | -------------------------------------------------------------------------------- /test/collection.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert/strict'); 2 | const mongo = require('../index.js'); 3 | 4 | describe('collection', function() { 5 | let client; 6 | let db; 7 | 8 | afterEach(async function() { 9 | const collections = await db.collections(); 10 | await Promise.all(collections.map(collection => db.dropCollection(collection.collectionName))); 11 | await client.close(); 12 | }); 13 | 14 | beforeEach(async function() { 15 | client = new mongo.MongoClient('mongodb://localhost:27017/testdb', {}); 16 | await client.connect(); 17 | db = client.db(); 18 | }); 19 | 20 | // [ ] bulkWrite 21 | // [x] count 22 | // [x] ensureIndex 23 | // [x] insert 24 | // [x] insertMany 25 | // [x] insertOne 26 | // [x] remove 27 | // [x] deleteMany 28 | // [x] deleteOne 29 | // [x] removeMany 30 | // [x] removeOne 31 | // [x] rename 32 | // [x] replaceOne 33 | // [x] update 34 | // [x] updateMany 35 | // [x] updateOne 36 | 37 | it('collection.count (callback)', function(done) { 38 | const trees = db.collection('trees'); 39 | 40 | trees.insert( 41 | [ 42 | { 43 | title: 'birch', 44 | type: 'tree' 45 | }, 46 | { 47 | title: 'oak', 48 | type: 'tree' 49 | }, 50 | { 51 | title: 'rhododendron ', 52 | type: 'shrub' 53 | } 54 | ], 55 | {}, 56 | (insertError) => { 57 | if (insertError) { 58 | done(insertError); 59 | 60 | return; 61 | } 62 | 63 | trees.count( 64 | { type: 'shrub' }, 65 | {}, 66 | (err, result) => { 67 | try { 68 | assert.ifError(err); 69 | const actual = result; 70 | const expected = 1; 71 | 72 | assert.equal(actual, expected); 73 | done(); 74 | } catch (error) { 75 | done(error); 76 | } 77 | } 78 | ); 79 | } 80 | ); 81 | }); 82 | it('collection.count (promise)', async function() { 83 | const trees = db.collection('trees'); 84 | 85 | await trees.insert( 86 | [ 87 | { 88 | title: 'birch', 89 | type: 'tree' 90 | }, 91 | { 92 | title: 'oak', 93 | type: 'tree' 94 | }, 95 | { 96 | title: 'rhododendron ', 97 | type: 'shrub' 98 | } 99 | ] 100 | ); 101 | const actual = { 102 | tree: await trees.count({ type: 'tree' }), 103 | shrub: await trees.count({ type: 'shrub' }), 104 | all: await trees.count() 105 | }; 106 | const expected = { 107 | tree: 2, 108 | shrub: 1, 109 | all: 3 110 | }; 111 | 112 | assert.deepEqual(actual, expected); 113 | }); 114 | 115 | it('collection.ensureIndex (callback)', function(done) { 116 | const trees = db.collection('trees'); 117 | 118 | trees.ensureIndex( 119 | { 120 | location: '2dsphere' 121 | }, 122 | {}, 123 | (err, index) => { 124 | assert.ifError(err); 125 | const actual = index; 126 | const expected = 'location_2dsphere'; 127 | 128 | assert.equal(actual, expected); 129 | done(); 130 | } 131 | ); 132 | }); 133 | it('collection.ensureIndex (promise)', async function() { 134 | const trees = db.collection('trees'); 135 | 136 | const actual = await trees.ensureIndex({ 137 | location: '2dsphere' 138 | }); 139 | const expected = 'location_2dsphere'; 140 | 141 | assert.equal(actual, expected); 142 | }); 143 | 144 | it('collection.insert array (callback)', function(done) { 145 | const trees = db.collection('trees'); 146 | 147 | trees.insert( 148 | [ 149 | { 150 | title: 'birch' 151 | }, 152 | { 153 | title: 'oak' 154 | } 155 | ], 156 | {}, 157 | (err, result) => { 158 | try { 159 | assert.ifError(err); 160 | const actual = result; 161 | const expected = { 162 | acknowledged: true, 163 | insertedCount: 2, 164 | insertedIds: actual.insertedIds, 165 | result: { 166 | getInsertedIds: actual.insertedIds, 167 | getUpsertedIds: undefined, 168 | n: 2, 169 | nInserted: 2, 170 | nMatched: undefined, 171 | nModified: undefined, 172 | nRemoved: undefined, 173 | nUpserted: undefined, 174 | ok: 1 175 | } 176 | }; 177 | 178 | assert.deepEqual(actual, expected); 179 | done(); 180 | } catch (error) { 181 | done(error); 182 | } 183 | } 184 | ); 185 | }); 186 | it('collection.insert array (promise)', async function() { 187 | const trees = db.collection('trees'); 188 | 189 | const actual = await trees.insert( 190 | [ 191 | { 192 | title: 'birch' 193 | }, 194 | { 195 | title: 'oak' 196 | } 197 | ] 198 | ); 199 | const expected = { 200 | acknowledged: true, 201 | insertedCount: 2, 202 | insertedIds: actual.insertedIds, 203 | result: { 204 | getInsertedIds: actual.insertedIds, 205 | getUpsertedIds: undefined, 206 | n: 2, 207 | nInserted: 2, 208 | nMatched: undefined, 209 | nModified: undefined, 210 | nRemoved: undefined, 211 | nUpserted: undefined, 212 | ok: 1 213 | } 214 | }; 215 | 216 | assert.deepEqual(actual, expected); 217 | }); 218 | 219 | it('collection.insert (callback)', function(done) { 220 | const trees = db.collection('trees'); 221 | 222 | trees.insert( 223 | { 224 | title: 'birch' 225 | }, 226 | {}, 227 | (err, result) => { 228 | try { 229 | assert.ifError(err); 230 | const actual = result; 231 | const expected = { 232 | acknowledged: true, 233 | insertedCount: 1, 234 | insertedId: actual.insertedId, 235 | ops: [ { _id: actual.insertedId } ], 236 | result: { 237 | getInsertedIds: undefined, 238 | getUpsertedIds: undefined, 239 | n: 1, 240 | nInserted: undefined, 241 | nMatched: undefined, 242 | nModified: undefined, 243 | nRemoved: undefined, 244 | nUpserted: undefined, 245 | ok: 1 246 | } 247 | }; 248 | 249 | assert.deepEqual(actual, expected); 250 | done(); 251 | } catch (error) { 252 | done(error); 253 | } 254 | } 255 | ); 256 | }); 257 | it('collection.insert (promise)', async function() { 258 | const trees = db.collection('trees'); 259 | 260 | const actual = await trees.insert({ 261 | title: 'birch' 262 | }); 263 | const expected = { 264 | acknowledged: true, 265 | insertedCount: 1, 266 | insertedId: actual.insertedId, 267 | ops: [ { _id: actual.insertedId } ], 268 | result: { 269 | getInsertedIds: undefined, 270 | getUpsertedIds: undefined, 271 | n: 1, 272 | nInserted: undefined, 273 | nMatched: undefined, 274 | nModified: undefined, 275 | nRemoved: undefined, 276 | nUpserted: undefined, 277 | ok: 1 278 | } 279 | }; 280 | 281 | assert.deepEqual(actual, expected); 282 | }); 283 | 284 | it('collection.remove (callback)', function(done) { 285 | const trees = db.collection('trees'); 286 | Promise.all([ 287 | trees.insert({ title: 'birch' }), 288 | trees.insert({ title: 'oak' }) 289 | ]) 290 | .then(() => { 291 | trees.remove( 292 | { 293 | title: { 294 | $in: [ 'birch', 'oak' ] 295 | } 296 | }, 297 | {}, 298 | (err, result) => { 299 | try { 300 | assert.ifError(err); 301 | const actual = result; 302 | const expected = { 303 | acknowledged: true, 304 | deletedCount: 2, 305 | result: { 306 | getInsertedIds: undefined, 307 | getUpsertedIds: undefined, 308 | n: 2, 309 | nInserted: undefined, 310 | nMatched: undefined, 311 | nModified: undefined, 312 | nRemoved: 2, 313 | nUpserted: undefined, 314 | ok: 1 315 | } 316 | }; 317 | 318 | assert.deepEqual(actual, expected); 319 | done(); 320 | } catch (error) { 321 | done(error); 322 | } 323 | } 324 | ); 325 | }); 326 | }); 327 | it('collection.remove (promise)', async function() { 328 | const trees = db.collection('trees'); 329 | await trees.insert({ title: 'birch' }); 330 | await trees.insert({ title: 'oak' }); 331 | 332 | const actual = await trees.remove({ 333 | title: { 334 | $in: [ 'birch', 'oak' ] 335 | } 336 | }); 337 | const expected = { 338 | acknowledged: true, 339 | deletedCount: 2, 340 | result: { 341 | getInsertedIds: undefined, 342 | getUpsertedIds: undefined, 343 | n: 2, 344 | nInserted: undefined, 345 | nMatched: undefined, 346 | nModified: undefined, 347 | nRemoved: 2, 348 | nUpserted: undefined, 349 | ok: 1 350 | } 351 | }; 352 | 353 | assert.deepEqual(actual, expected); 354 | }); 355 | 356 | it('collection.remove single (callback)', function(done) { 357 | const trees = db.collection('trees'); 358 | Promise.all([ 359 | trees.insert({ title: 'birch' }), 360 | trees.insert({ title: 'oak' }) 361 | ]) 362 | .then(() => { 363 | trees.remove( 364 | { 365 | title: { 366 | $in: [ 'birch', 'oak' ] 367 | } 368 | }, 369 | { 370 | single: true 371 | }, 372 | (err, result) => { 373 | try { 374 | assert.ifError(err); 375 | const actual = result; 376 | const expected = { 377 | acknowledged: true, 378 | deletedCount: 1, 379 | result: { 380 | getInsertedIds: undefined, 381 | getUpsertedIds: undefined, 382 | n: 1, 383 | nInserted: undefined, 384 | nMatched: undefined, 385 | nModified: undefined, 386 | nRemoved: 1, 387 | nUpserted: undefined, 388 | ok: 1 389 | } 390 | }; 391 | 392 | assert.deepEqual(actual, expected); 393 | done(); 394 | } catch (error) { 395 | done(error); 396 | } 397 | } 398 | ); 399 | }); 400 | }); 401 | it('collection.remove single (promise)', async function() { 402 | const trees = db.collection('trees'); 403 | await trees.insert({ title: 'birch' }); 404 | await trees.insert({ title: 'oak' }); 405 | 406 | const actual = await trees.remove( 407 | { 408 | title: { 409 | $in: [ 'birch', 'oak' ] 410 | } 411 | }, 412 | { 413 | single: true 414 | } 415 | ); 416 | const expected = { 417 | acknowledged: true, 418 | deletedCount: 1, 419 | result: { 420 | getInsertedIds: undefined, 421 | getUpsertedIds: undefined, 422 | n: 1, 423 | nInserted: undefined, 424 | nMatched: undefined, 425 | nModified: undefined, 426 | nRemoved: 1, 427 | nUpserted: undefined, 428 | ok: 1 429 | } 430 | }; 431 | 432 | assert.deepEqual(actual, expected); 433 | }); 434 | 435 | it('collection.rename (callback)', function(done) { 436 | db.createCollection('leaves') 437 | .then((leaves) => { 438 | leaves.rename( 439 | 'branches', 440 | {}, 441 | function(err, collection) { 442 | try { 443 | assert.ifError(err); 444 | const actual = collection.constructor; 445 | const expected = mongo.Collection; 446 | 447 | assert.deepEqual(actual, expected); 448 | done(); 449 | } catch (error) { 450 | done(error); 451 | } 452 | } 453 | ); 454 | }); 455 | }); 456 | it('collection.rename (promise)', async function() { 457 | const leaves = await db.createCollection('leaves'); 458 | 459 | const collection = await leaves.rename('branches', {}); 460 | 461 | const actual = collection.constructor; 462 | const expected = mongo.Collection; 463 | 464 | assert.deepEqual(actual, expected); 465 | }); 466 | 467 | it('collection.replaceOne (callback)', function(done) { 468 | const trees = db.collection('trees'); 469 | trees.insert({ title: 'birch' }) 470 | .then(() => { 471 | trees.replaceOne( 472 | { 473 | title: 'birch' 474 | }, 475 | {}, 476 | (err, result) => { 477 | try { 478 | assert.ifError(err); 479 | const actual = result; 480 | const expected = { 481 | acknowledged: true, 482 | matchedCount: 1, 483 | modifiedCount: 1, 484 | result: { 485 | getInsertedIds: undefined, 486 | getUpsertedIds: undefined, 487 | n: 1, 488 | nInserted: undefined, 489 | nMatched: 1, 490 | nModified: 1, 491 | nRemoved: undefined, 492 | nUpserted: 0, 493 | ok: 1 494 | }, 495 | upsertedCount: 0, 496 | upsertedId: null 497 | }; 498 | 499 | assert.deepEqual(actual, expected); 500 | done(); 501 | } catch (error) { 502 | done(error); 503 | } 504 | } 505 | ); 506 | }); 507 | }); 508 | it('collection.replaceOne (promise)', async function() { 509 | const trees = db.collection('trees'); 510 | await trees.insert({ title: 'birch' }); 511 | 512 | const actual = await trees.replaceOne( 513 | { 514 | title: 'birch' 515 | }, 516 | { 517 | title: 'oak', 518 | age: 37 519 | }, 520 | {} 521 | ); 522 | const expected = { 523 | acknowledged: true, 524 | matchedCount: 1, 525 | modifiedCount: 1, 526 | result: { 527 | getInsertedIds: undefined, 528 | getUpsertedIds: undefined, 529 | n: 1, 530 | nInserted: undefined, 531 | nMatched: 1, 532 | nModified: 1, 533 | nRemoved: undefined, 534 | nUpserted: 0, 535 | ok: 1 536 | }, 537 | upsertedCount: 0, 538 | upsertedId: null 539 | }; 540 | 541 | assert.deepEqual(actual, expected); 542 | }); 543 | 544 | it('collection.update multi (callback)', function(done) { 545 | const trees = db.collection('trees'); 546 | Promise.all([ 547 | trees.insert({ title: 'birch' }), 548 | trees.insert({ title: 'oak' }) 549 | ]) 550 | .then(() => { 551 | trees.update( 552 | { 553 | title: { 554 | $in: [ 'birch', 'oak' ] 555 | } 556 | }, 557 | { 558 | $set: { 559 | age: 37 560 | } 561 | }, 562 | { 563 | multi: true 564 | }, 565 | (err, result) => { 566 | try { 567 | assert.ifError(err); 568 | const actual = result; 569 | const expected = { 570 | acknowledged: true, 571 | matchedCount: 2, 572 | modifiedCount: 2, 573 | result: { 574 | getInsertedIds: undefined, 575 | getUpsertedIds: undefined, 576 | n: 2, 577 | nInserted: undefined, 578 | nMatched: 2, 579 | nModified: 2, 580 | nRemoved: undefined, 581 | nUpserted: 0, 582 | ok: 1 583 | }, 584 | upsertedCount: 0, 585 | upsertedId: null 586 | }; 587 | 588 | assert.deepEqual(actual, expected); 589 | done(); 590 | } catch (error) { 591 | done(error); 592 | } 593 | } 594 | ); 595 | }); 596 | }); 597 | it('collection.update multi (promise)', async function() { 598 | const trees = db.collection('trees'); 599 | await trees.insert({ title: 'birch' }); 600 | await trees.insert({ title: 'oak' }); 601 | 602 | const actual = await trees.update( 603 | { 604 | title: { 605 | $in: [ 'birch', 'oak' ] 606 | } 607 | }, 608 | { 609 | $set: { 610 | age: 37 611 | } 612 | }, 613 | { 614 | multi: true 615 | } 616 | ); 617 | const expected = { 618 | acknowledged: true, 619 | matchedCount: 2, 620 | modifiedCount: 2, 621 | result: { 622 | getInsertedIds: undefined, 623 | getUpsertedIds: undefined, 624 | nInserted: undefined, 625 | n: 2, 626 | nMatched: 2, 627 | nModified: 2, 628 | nRemoved: undefined, 629 | nUpserted: 0, 630 | ok: 1 631 | }, 632 | upsertedCount: 0, 633 | upsertedId: null 634 | }; 635 | 636 | assert.deepEqual(actual, expected); 637 | }); 638 | 639 | it('collection.update (callback)', function(done) { 640 | const trees = db.collection('trees'); 641 | Promise.all([ 642 | trees.insert({ title: 'birch' }), 643 | trees.insert({ title: 'oak' }) 644 | ]) 645 | .then(() => { 646 | trees.update( 647 | { 648 | title: { 649 | $in: [ 'birch', 'oak' ] 650 | } 651 | }, 652 | { 653 | $set: { 654 | age: 37 655 | } 656 | }, 657 | {}, 658 | (err, result) => { 659 | try { 660 | assert.ifError(err); 661 | const actual = result; 662 | const expected = { 663 | acknowledged: true, 664 | matchedCount: 1, 665 | modifiedCount: 1, 666 | result: { 667 | getInsertedIds: undefined, 668 | getUpsertedIds: undefined, 669 | n: 1, 670 | nInserted: undefined, 671 | nMatched: 1, 672 | nModified: 1, 673 | nRemoved: undefined, 674 | nUpserted: 0, 675 | ok: 1 676 | }, 677 | upsertedCount: 0, 678 | upsertedId: null 679 | }; 680 | 681 | assert.deepEqual(actual, expected); 682 | done(); 683 | } catch (error) { 684 | done(error); 685 | } 686 | } 687 | ); 688 | }); 689 | }); 690 | it('collection.update (promise)', async function() { 691 | const trees = db.collection('trees'); 692 | await trees.insert({ title: 'birch' }); 693 | await trees.insert({ title: 'oak' }); 694 | 695 | const actual = await trees.update( 696 | { 697 | title: { 698 | $in: [ 'birch', 'oak' ] 699 | } 700 | }, 701 | { 702 | $set: { 703 | age: 37 704 | } 705 | }, 706 | {} 707 | ); 708 | const expected = { 709 | acknowledged: true, 710 | matchedCount: 1, 711 | modifiedCount: 1, 712 | result: { 713 | getInsertedIds: undefined, 714 | getUpsertedIds: undefined, 715 | n: 1, 716 | nInserted: undefined, 717 | nMatched: 1, 718 | nModified: 1, 719 | nRemoved: undefined, 720 | nUpserted: 0, 721 | ok: 1 722 | }, 723 | upsertedCount: 0, 724 | upsertedId: null 725 | }; 726 | 727 | assert.deepEqual(actual, expected); 728 | }); 729 | }); 730 | --------------------------------------------------------------------------------