diff --git a/package-lock.json b/package-lock.json index fc295c22..b9f0da75 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1088,21 +1088,22 @@ "dev": true }, "@microsoft/api-extractor": { - "version": "7.9.13", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.9.13.tgz", - "integrity": "sha512-hWpNLeHv4zwEn0UBhgSDN3Fn8YVVU8Ti8IMTy6Ik/AC7AEwU6QzGRnH5GSxbkfig+K22dmnwxljyxy6FAkam+g==", + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.11.2.tgz", + "integrity": "sha512-iZPv22j9K02cbwIDblOgF1MxZG+KWovp3CQpWCD6UC/+YYO4DfLxX5uZYVNzfgT4vU8fN0rugJmGm85rHX6Ouw==", "dev": true, "requires": { - "@microsoft/api-extractor-model": "7.8.21", + "@microsoft/api-extractor-model": "7.10.8", "@microsoft/tsdoc": "0.12.19", - "@rushstack/node-core-library": "3.31.0", - "@rushstack/ts-command-line": "4.6.4", + "@rushstack/node-core-library": "3.34.7", + "@rushstack/rig-package": "0.2.7", + "@rushstack/ts-command-line": "4.7.6", "colors": "~1.2.1", "lodash": "~4.17.15", "resolve": "~1.17.0", "semver": "~7.3.0", "source-map": "~0.6.1", - "typescript": "~3.9.5" + "typescript": "~4.0.5" }, "dependencies": { "semver": { @@ -1118,21 +1119,21 @@ "dev": true }, "typescript": { - "version": "3.9.7", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", - "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.5.tgz", + "integrity": "sha512-ywmr/VrTVCmNTJ6iV2LwIrfG1P+lv6luD8sUJs+2eI9NLGigaN+nUQc13iHqisq7bra9lnmUSYqbJvegraBOPQ==", "dev": true } } }, "@microsoft/api-extractor-model": { - "version": "7.8.21", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.8.21.tgz", - "integrity": "sha512-gpg92BaN1mlxvSvyFMTPkcIZOqC9Q5pX/cKqkF+rtH0RXVQXL7ck9LWyM6gSrd/ricz/ARlsyTlLPNq2vJZg7A==", + "version": "7.10.8", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.10.8.tgz", + "integrity": "sha512-9TfiCTPnkUeLaYywZeg9rYbVPX9Tj6AAkO6ThnjSE0tTPLjMcL3RiHkqn0BJ4+aGcl56APwo32zj5+kG+NqxYA==", "dev": true, "requires": { "@microsoft/tsdoc": "0.12.19", - "@rushstack/node-core-library": "3.31.0" + "@rushstack/node-core-library": "3.34.7" } }, "@microsoft/tsdoc": { @@ -1248,9 +1249,9 @@ } }, "@rushstack/node-core-library": { - "version": "3.31.0", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.31.0.tgz", - "integrity": "sha512-GN/nKEszlKvW4Efhmicbk/s+mY6cshXexINl+W1OBtq2AaTae1QI0XjGjCp6cCygW7PltilJ5EOKGEtTQg2mew==", + "version": "3.34.7", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.34.7.tgz", + "integrity": "sha512-7FwJ0jmZsh7bDIZ1IqDNphY9Kc6aAi1D2K8jiq+da4flMyL84HNeq2KxvwFLzjLwu3eMr88X+oBpgxCTD5Y57Q==", "dev": true, "requires": { "@types/node": "10.17.13", @@ -1272,10 +1273,21 @@ } } }, + "@rushstack/rig-package": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.2.7.tgz", + "integrity": "sha512-hI1L0IIzCHqH/uW64mKqEQ0/MANA/IklVId3jGpj1kt9RJcBdeNUIlzDtHl437LZRAuEA8CyotRHzG6YDgWlTw==", + "dev": true, + "requires": { + "@types/node": "10.17.13", + "resolve": "~1.17.0", + "strip-json-comments": "~3.1.1" + } + }, "@rushstack/ts-command-line": { - "version": "4.6.4", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.6.4.tgz", - "integrity": "sha512-ubIANZimyU07+ChU56LfiD36NJ8gvw1txlvUP20GYNQi4lf5N0xEnev4r+AtKkOdnowpGy60ObGmYxSUpSacpw==", + "version": "4.7.6", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.7.6.tgz", + "integrity": "sha512-falJVNfpJtsL3gJaY77JXXycfzhzB9VkKhqEfjRWD69/f6ezMUorPR6Nc90MnIaWgePTcdTJPZibxOQrNpu1Uw==", "dev": true, "requires": { "@types/argparse": "1.0.38", @@ -5415,6 +5427,12 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "dev": true + }, "node-preload": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", diff --git a/package.json b/package.json index a8a148eb..d31205d8 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "@babel/plugin-external-helpers": "^7.10.4", "@babel/preset-env": "^7.11.0", "@istanbuljs/nyc-config-typescript": "^1.0.1", - "@microsoft/api-extractor": "^7.9.10", + "@microsoft/api-extractor": "^7.11.2", "@rollup/plugin-babel": "^5.2.0", "@rollup/plugin-commonjs": "^15.0.0", "@rollup/plugin-json": "^4.1.0", @@ -49,6 +49,7 @@ "karma-mocha-reporter": "^2.2.5", "karma-rollup-preprocessor": "^7.0.5", "mocha": "5.2.0", + "node-fetch": "^2.6.1", "nyc": "^15.1.0", "prettier": "^2.1.1", "rimraf": "^3.0.2", @@ -78,7 +79,7 @@ "scripts": { "docs": "typedoc", "test": "npm run build && npm run test-node && npm run test-browser", - "test-node": "mocha test/node", + "test-node": "mocha test/node test/*_tests.js", "test-browser": "karma start karma.conf.js", "build:ts": "tsc", "build:dts": "npm run build:ts && api-extractor run --typescript-compiler-folder node_modules/typescript --local && rimraf 'lib/**/*.d.ts*'", diff --git a/src/bson.ts b/src/bson.ts index 09cf5a98..22776fc9 100644 --- a/src/bson.ts +++ b/src/bson.ts @@ -5,6 +5,7 @@ import { DBRef } from './db_ref'; import { Decimal128 } from './decimal128'; import { Double } from './double'; import { ensureBuffer } from './ensure_buffer'; +import { EJSON } from './extended_json'; import { Int32 } from './int_32'; import { Long } from './long'; import { Map } from './map'; @@ -20,42 +21,7 @@ import { BSONSymbol } from './symbol'; import { Timestamp } from './timestamp'; export { BinaryExtended, BinaryExtendedLegacy, BinarySequence } from './binary'; export { CodeExtended } from './code'; -export { - BSON_BINARY_SUBTYPE_BYTE_ARRAY, - BSON_BINARY_SUBTYPE_DEFAULT, - BSON_BINARY_SUBTYPE_FUNCTION, - BSON_BINARY_SUBTYPE_MD5, - BSON_BINARY_SUBTYPE_USER_DEFINED, - BSON_BINARY_SUBTYPE_UUID, - BSON_BINARY_SUBTYPE_UUID_NEW, - BSON_DATA_ARRAY, - BSON_DATA_BINARY, - BSON_DATA_BOOLEAN, - BSON_DATA_CODE, - BSON_DATA_CODE_W_SCOPE, - BSON_DATA_DATE, - BSON_DATA_DBPOINTER, - BSON_DATA_DECIMAL128, - BSON_DATA_INT, - BSON_DATA_LONG, - BSON_DATA_MAX_KEY, - BSON_DATA_MIN_KEY, - BSON_DATA_NULL, - BSON_DATA_NUMBER, - BSON_DATA_OBJECT, - BSON_DATA_OID, - BSON_DATA_REGEXP, - BSON_DATA_STRING, - BSON_DATA_SYMBOL, - BSON_DATA_TIMESTAMP, - BSON_DATA_UNDEFINED, - BSON_INT32_MAX, - BSON_INT32_MIN, - BSON_INT64_MAX, - BSON_INT64_MIN, - JS_INT_MAX, - JS_INT_MIN -} from './constants'; +export * from './constants'; export { DBRefLike } from './db_ref'; export { Decimal128Extended } from './decimal128'; export { DoubleExtended } from './double'; @@ -291,3 +257,37 @@ export function deserializeStream( // Return object containing end index of parsing and list of documents return index; } + +/** + * BSON default export + * @deprecated Please use named exports + * @privateRemarks + * We want to someday deprecate the default export, + * so none of the new TS types are being exported on the default + * @public + */ +const BSON = { + Binary, + Code, + DBRef, + Decimal128, + Double, + Int32, + Long, + Map, + MaxKey, + MinKey, + ObjectId, + ObjectID: ObjectId, + BSONRegExp, + BSONSymbol, + Timestamp, + EJSON, + setInternalBufferSize, + serialize, + serializeWithBufferAndIndex, + deserialize, + calculateObjectSize, + deserializeStream +}; +export default BSON; diff --git a/src/ensure_buffer.ts b/src/ensure_buffer.ts index 0c9a897e..436aefe2 100644 --- a/src/ensure_buffer.ts +++ b/src/ensure_buffer.ts @@ -1,4 +1,5 @@ import { Buffer } from 'buffer'; +import { isBuffer } from './parser/utils'; /** * Makes sure that, if a Uint8Array is passed in, it is wrapped in a Buffer. @@ -9,7 +10,7 @@ import { Buffer } from 'buffer'; * @throws TypeError If anything other than a Buffer or Uint8Array is passed in */ export function ensureBuffer(potentialBuffer: Buffer | ArrayBufferView | ArrayBuffer): Buffer { - if (Buffer.isBuffer(potentialBuffer)) { + if (isBuffer(potentialBuffer)) { return potentialBuffer; } diff --git a/src/parser/serializer.ts b/src/parser/serializer.ts index a99ac470..e53fa8da 100644 --- a/src/parser/serializer.ts +++ b/src/parser/serializer.ts @@ -1,4 +1,4 @@ -import { Buffer } from 'buffer'; +import type { Buffer } from 'buffer'; import { Binary } from '../binary'; import type { BSONSymbol, DBRef, Document, MaxKey } from '../bson'; import type { Code } from '../code'; @@ -18,6 +18,7 @@ import type { BSONRegExp } from '../regexp'; import { isBigInt64Array, isBigUInt64Array, + isBuffer, isDate, isUint8Array, normalizedFunctionString @@ -785,7 +786,7 @@ export function serializeInto( index = serializeNull(buffer, key, value, index, true); } else if (value['_bsontype'] === 'ObjectId' || value['_bsontype'] === 'ObjectID') { index = serializeObjectId(buffer, key, value, index, true); - } else if (Buffer.isBuffer(value) || isUint8Array(value)) { + } else if (isBuffer(value) || isUint8Array(value)) { index = serializeBuffer(buffer, key, value, index, true); } else if (value instanceof RegExp || isRegExp(value)) { index = serializeRegExp(buffer, key, value, index, true); @@ -891,7 +892,7 @@ export function serializeInto( index = serializeNull(buffer, key, value, index); } else if (value['_bsontype'] === 'ObjectId' || value['_bsontype'] === 'ObjectID') { index = serializeObjectId(buffer, key, value, index); - } else if (Buffer.isBuffer(value) || isUint8Array(value)) { + } else if (isBuffer(value) || isUint8Array(value)) { index = serializeBuffer(buffer, key, value, index); } else if (value instanceof RegExp || isRegExp(value)) { index = serializeRegExp(buffer, key, value, index); @@ -997,7 +998,7 @@ export function serializeInto( index = serializeNull(buffer, key, value, index); } else if (value['_bsontype'] === 'ObjectId' || value['_bsontype'] === 'ObjectID') { index = serializeObjectId(buffer, key, value, index); - } else if (Buffer.isBuffer(value) || isUint8Array(value)) { + } else if (isBuffer(value) || isUint8Array(value)) { index = serializeBuffer(buffer, key, value, index); } else if (value instanceof RegExp || isRegExp(value)) { index = serializeRegExp(buffer, key, value, index); diff --git a/src/parser/utils.ts b/src/parser/utils.ts index b7a6a3d9..905e8aa6 100644 --- a/src/parser/utils.ts +++ b/src/parser/utils.ts @@ -58,7 +58,7 @@ export function haveBuffer(): boolean { /** Callable in any environment to check if value is a Buffer */ export function isBuffer(value: unknown): value is Buffer { - return haveBuffer() && Buffer.isBuffer(value); + return typeof value === 'object' && value?.constructor?.name === 'Buffer'; } // To ensure that 0.4 of node works correctly diff --git a/test/bson_older_versions_tests.js b/test/bson_older_versions_tests.js new file mode 100644 index 00000000..79035b92 --- /dev/null +++ b/test/bson_older_versions_tests.js @@ -0,0 +1,89 @@ +'use strict'; + +const newBSON = require('./register-bson'); +const fs = require('fs'); +const fetch = require('node-fetch').default; +const rimraf = require('rimraf'); +const cp = require('child_process'); + +/* + * This file tests that previous versions of BSON + * serialize and deserialize correctly in the most recent version of BSON + * + * This is an unusual situation to run into as users should be using one BSON lib version + * but it does arise with sub deps etc. and we wish to protect against unexpected behavior + * + * If backwards compatibility breaks there should be clear warnings/failures + * rather than empty or zero-ed values. + */ + +const OLD_VERSIONS = ['v1.1.5', 'v1.1.4']; +const getZipUrl = ver => `https://github.com/mongodb/js-bson/archive/${ver}.zip`; +const getImportPath = ver => `../bson-${ver}/js-bson-${ver.substring(1)}`; + +function downloadZip(version, done) { + // downloads a zip of previous BSON version + fetch(getZipUrl(version)) + .then(r => { + return r.arrayBuffer(); + }) + .then(r => { + fs.writeFileSync(`bson-${version}.zip`, new Uint8Array(r)); + try { + // unzips the code, right now these test won't handle versions written in TS + cp.execSync(`unzip bson-${version}.zip -d bson-${version}`); + } catch (err) { + return done(err); + } + done(); + }); +} + +describe('Current version', function () { + OLD_VERSIONS.forEach(version => { + before(function (done) { + if (Number(process.version.split('.')[0].substring(1)) < 8) { + // WHATWG fetch doesn't download correctly prior to node 8 + // but we should be safe by testing on node 8 + + this.skip(); + } + if (fs.existsSync(`bson-${version}.zip`)) { + fs.unlinkSync(`bson-${version}.zip`); + rimraf(`./bson-${version}`, err => { + if (err) done(err); + + // download old versions + downloadZip(version, done); + }); + } else { + // download old versions + downloadZip(version, done); + } + }); + + after(function (done) { + try { + fs.unlinkSync(`bson-${version}.zip`); + } catch (e) { + // ignore + } + rimraf(`./bson-${version}`, err => { + if (err) done(err); + done(); + }); + }); + + it(`serializes correctly against ${version} Binary class`, function () { + const oldBSON = require(getImportPath(version)); + const binFromNew = { + binary: new newBSON.Binary('aaaa') + }; + const binFromOld = { + binary: new oldBSON.Binary('aaaa') + }; + expect(oldBSON.prototype.serialize(binFromNew).toString('hex')).to.equal( + newBSON.serialize(binFromOld).toString('hex') + ); + }); + }); +});