The following is a detailed collection of the changes in the major v5 release of the BSON package for Node.js and web platforms.
TL;DR: Impacted APIs now return
Uint8Array
in web environments; Node.js environments are unaffected
For those that use the BSON library on Node.js, there is no change - the BSON APIs will still return and accept instances of Node.js Buffer
. Since we no longer depend on the Buffer
web shim for compatibility with browsers, in non-Node.js environments a Uint8Array
will be returned instead.
This allows the BSON library to be more platform independent while keeping its behavior consistent cross platform.
The following APIs now return Uint8Arrays
when the library is loaded in an environment that does not define a global Node.js Buffer
.
Binary.prototype.buffer
Binary.prototype.read()
Binary.prototype.value()
Decimal128.prototype.bytes
ObjectId.prototype.id
ObjectId.generate()
serialize()
UUID.prototype.id
UUID.generate()
TL;DR: The only supported encodings are:
'hex' | 'base64' | 'utf8'
The methods: ObjectId.toString
, UUID.toString
, and Binary.toString
took encodings that were passed through to the Node.js Buffer
API. As a result of no longer relying on the presence of Buffer
we can no longer support every encoding that Node.js does. We continue to support 'hex'
and 'base64'
on all three methods and additionally 'utf-8' | 'utf8'
on Binary.toString
. If any of the other encodings are desired the underlying buffer for all these classes are publicly accessible and while in Node.js will be stored as a Node.js buffer:
// Given Binary constructed from one of the encodings (using 'utf16le' as an example here)
// no longer supported directly by the Binary.toString method
const bin = new Binary(Buffer.from('abc', 'utf16le'), 0);
// To obtain the original translation of bytes to string
// We can access the underlying buffer and on Node.js it will be an instanceof Buffer
// so it will support the translation to the specified encoding.
bin.value(true).toString('utf16le');
// In web environments (and Node.js) the same can be accomplished with TextDecoder
new TextDecoder('utf-16le').decode(bin.value(true));
We have set our TypeScript compilation target to es2020
which aligns with our minimum supported Node.js version 14+. The following is from the TypeScript release notes on es2020
support, so it's some of the syntax that can be expected to be preserved after compilation:
This will preserve newer ECMAScript 2020 features like optional chaining, nullish coalescing, export * as ns, and dynamic import(...) syntax. It also means bigint literals now have a stable target below esnext.
Previously, our compilation target was set such that each BSON class
became a function in the shipped code, allowing callers to omit the new
keyword. Because of the "target"
upgrade, the library now ships javascript class
syntax which requires callers to use the new
keyword.
If serializeFunctions
was enabled and the functions being serialized had a name that is outside of Controls and Basic Latin character ranges (a.k.a utf8 bytes: 0x00-0x7F
) they would be incorrectly serialized.
This library no longer polyfills ES Map and the export "Map
" was removed. Users should migrate to using the global Map
constructor available in all supported JS environments.
Decimal128
can no longer have a toObject()
method added on to its prototype for mapping to a custom value. This feature was undocumented and inconsistent with the rest of our BSON types.
At this time there is no direct migration. Cursors in the driver support transformations via .map
, otherwise the Decimal128
instances will require manual transformation. There is a plan to provide a better mechanism for consistently transforming BSON values tracked in NODE-4680. Please feel free to add a vote or comment with a use case to help us land the feature in the most useful form.
The following deprecated methods have been removed:
ObjectId.prototype.generate
- Instead, generate a new
ObjectId
with the constructor:new ObjectId()
or using thestatic generate(time?: number)
method.
- Instead, generate a new
ObjectId.prototype.generationTime
- Instead, use
static createFromTime()
andgetTimestamp()
to set and inspect these values on anObjectId()
- Instead, use
ObjectId.prototype.getInc
ObjectId.prototype.get_inc
ObjectId.get_inc
- The
static getInc()
is private since invoking it increments the nextObjectId
index, so invoking would impact the creation of subsequentObjectId
s.
- The
BSON.serialize
, EJSON.stringify
and BSON.calculateObjectSize
now only inspect own properties and do not consider properties defined on the prototype of the input.
const object = { a: 1 };
Object.setPrototypeOf(object, { b: 2 });
BSON.deserialize(BSON.serialize(object));
// now returns { a: 1 } in v5.0
// would have returned { a: 1, b: 2 } in v4.x
BSON serialize will now preserve negative zero values as a floating point number.
Previously it was required to use the Double
type class to preserve -0
:
BSON.deserialize(BSON.serialize({ d: -0 }))
// no type preservation, returns { d: 0 }
BSON.deserialize(BSON.serialize({ d: new Double(-0) }))
// type preservation, returns { d: -0 }
Now -0
can be used directly
BSON.deserialize(BSON.serialize({ d: -0 }))
// type preservation, returns { d: -0 }
For clarity the deprecated and duplicate export ObjectID
has been removed. ObjectId
matches the class name and is equal in every way to the capital "D" export.
The Timestamp
type no longer accepts two number arguments for the low and high bits of the int64
value.
Supported constructors are as follows:
class Timestamp {
constructor(int: bigint);
constructor(long: Long);
constructor(value: { t: number; i: number });
}
Any code that uses the two number argument style of constructing a Timestamp
will need to be migrated to one of the supported constructors. We recommend using the { t: number; i: number }
style input, representing the timestamp and increment respectively.
// in 4.x BSON
new Timestamp(1, 2); // as an int64: 8589934593
// in 5.x BSON
new Timestamp({ t: 2, i: 1 }); // as an int64: 8589934593
Additionally, the t
and i
fields of { t: number; i: number }
are now validated more strictly to ensure your timestamps are being constructed as expected.
For example:
new Timestamp({ t: -2, i: 1 });
// Will throw, both fields need to be positive
new Timestamp({ t: 2, i: 0xFFFF_FFFF + 1 });
// Will throw, both fields need to be less than or equal to the unsigned int32 max value
Extended JSON parse
and stringify
APIs no longer support the strict
option, please use the relaxed
option instead.
Note that the relaxed
setting is the inverse of strict
. See the following migration example:
// parse
EJSON.parse("...", { strict: true }); /* migrate to */ EJSON.parse("...", { relaxed: false });
EJSON.parse("...", { strict: false }); /* migrate to */ EJSON.parse("...", { relaxed: true });
// stringify
EJSON.stringify({}, { strict: true }); /* migrate to */ EJSON.stringify({}, { relaxed: false });
EJSON.stringify({}, { strict: false }); /* migrate to */ EJSON.stringify({}, { relaxed: true });
- If you import BSON
commonjs
styleconst BSON = require('bson')
then theBSON.default
property is no longer present. - If you import BSON
esmodule
styleimport BSON from 'bson'
then this code will crash upon loading.- This error will throw:
SyntaxError: The requested module 'bson' does not provide an export named 'default'
.
- This error will throw:
The Code
class still supports the same constructor arguments as before.
It will now convert the first argument to a string before saving it to the code property, see the following:
const myCode = new Code(function iLoveJavascript() { console.log('I love javascript') });
// myCode.code === "function iLoveJavascript() { console.log('I love javascript') }"
// typeof myCode.code === 'string'
The deserialize options: evalFunctions
, cacheFunctions
, and cacheFunctionsCrc32
have been removed.
The evalFunctions
option, when enabled, would return BSON Code
typed values as eval-ed JavaScript functions, now it will always return Code
instances.
See the following snippet for how to migrate:
const bsonBytes = BSON.serialize(
{ iLoveJavascript: function () { console.log('I love javascript') } },
{ serializeFunctions: true } // serializeFunctions still works!
);
const result = BSON.deserialize(bsonBytes)
// result.iLoveJavascript instanceof Code
// result.iLoveJavascript.code === "function () { console.log('I love javascript') }"
const iLoveJavascript = new Function(`return ${result.iLoveJavascript.code}`)();
iLoveJavascript();
// prints "I love javascript"
// iLoveJavascript.name === "iLoveJavascript"
The BSON format does not support encoding arrays as the root object.
However, in JavaScript arrays are just objects where the keys are numeric (and a magic length
property), so round tripping an array (ex. [1, 2]
) though BSON would return { '0': 1, '1': 2 }
.
BSON.serialize()
now validates input types, the input to serialize must be an object or a Map
, arrays will now cause an error.
BSON.serialize([1, 2, 3])
// BSONError: serialize does not support an array as the root input
If the functionality of turning arrays into an object with numeric keys is useful, see the following example:
// Migration example:
const result = BSON.serialize(Object.fromEntries([1, true, 'blue'].entries()))
BSON.deserialize(result)
// { '0': 1, '1': true, '2': 'blue' }
Most users should be unaffected by these changes, Node.js require()
/ Node.js import
will fetch the corresponding BSON library as expected.
And for folks using bundlers like webpack or rollup, a tree shakable ES module bundle will be pulled in because of the settings in our package.json
.
Our package.json
defines the following "exports"
settings.
{
"main": "./lib/bson.cjs",
"module": "./lib/bson.mjs",
"exports": {
"import": "./lib/bson.mjs",
"require": "./lib/bson.cjs",
"react-native": "./lib/bson.cjs",
"browser": "./lib/bson.mjs"
},
}
You can now find compiled bundles of the BSON library in 3 common formats in the lib
directory.
- CommonJS -
lib/bson.cjs
- ES Module -
lib/bson.mjs
- Immediate Invoked Function Expression (IIFE) -
lib/bson.bundle.js
- Typically used when trying to import JS on the web CDN style, but the ES Module (
.mjs
) bundle is fully browser compatible and should be preferred if it works in your use case.
- Typically used when trying to import JS on the web CDN style, but the ES Module (
BSONTypeError
has been removed because it was not a subclass of BSONError so would not return true for an instanceof
check against BSONError
. To learn more about our expectations of error handling see this section of the MongoDB Node.js Driver's README.
A BSONError
can be thrown from deep within a library that relies on BSON, having one error super class for the library helps with programmatic filtering of an error's origin.
Since BSON can be used in environments where instances may originate from across realms, BSONError
has a static isBSONError()
method that helps with determining if an object is a BSONError
instance (much like Array.isArray).
It is our recommendation to use isBSONError()
checks on errors and to avoid relying on parsing error.message
and error.name
strings in your code. We guarantee isBSONError()
checks will pass according to semver guidelines, but errors may be sub-classed or their messages may change at any time, even patch releases, as we see fit to increase the helpfulness of the errors.
Hypothetical example: A collection in our database contains invalid UTF-8 data:
let documentCount = 0;
const cursor = collection.find({}, { utf8Validation: true });
try {
for await (const doc of cursor) documentCount += 1;
} catch (error) {
if (BSONError.isBSONError(error)) {
console.log(`Found the troublemaker UTF-8!: ${documentCount} ${error.message}`);
return documentCount;
}
throw error;
}
Starting with v5.0.0 of the BSON library instances of types from previous versions will throw an error when passed to the serializer. This is to ensure that types are always serialized correctly and that there is no unexpected silent BSON serialization mistakes that could occur when mixing versions.
It's unexpected for any applications to have more than one version of the BSON library but with nested dependencies and re-exporting, this new error will illuminate those incorrect combinations.
// npm install bson4@npm:bson@4
// npm install bson5@npm:bson@5
import { ObjectId } from 'bson4';
import { serialize } from 'bson5';
serialize({ _id: new ObjectId() });
// Uncaught BSONVersionError: Unsupported BSON version, bson types must be from bson 5.0 or later
export type JSONPrimitive = string | number | boolean | null;
export type SerializableTypes = Document | Array<JSONPrimitive | Document> | JSONPrimitive;
SerializableTypes
is removed in v5 due to its inaccuracy and inconvenience when working with return type of EJSON.parse()
.
This type does not contain all possible outputs from this function and it cannot be conveniently related to a custom declared type.
EJSON.parse
and EJSON.stringify
now accept any
in alignment with JSON
's corresponding APIs.
For users that desire type strictness it is recommended to wrap these APIs with type annotations that take/return unknown
since that generally forces better narrowing logic than SerializableTypes
would have prompted.