Skip to content

Commit bb2e062

Browse files
authored
Merge branch 'main' into NODE-4871/support_bigint_deserialization
2 parents da5d338 + e9e40a2 commit bb2e062

36 files changed

+6106
-8615
lines changed

.evergreen/install-dependencies.sh

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,21 +43,8 @@ nvm use --lts
4343

4444
set -o xtrace
4545

46-
47-
48-
# setup npm cache in a local directory
49-
cat <<EOT > .npmrc
50-
devdir=${NPM_CACHE_DIR}/.node-gyp
51-
init-module=${NPM_CACHE_DIR}/.npm-init.js
52-
cache=${NPM_CACHE_DIR}
53-
tmp=${NPM_TMP_DIR}
54-
registry=https://registry.npmjs.org
55-
EOT
56-
5746
# install node dependencies
58-
# npm prepare runs after install and will compile the library
59-
# TODO(NODE-3555): rollup dependencies for node polyfills have broken peerDeps. We can remove this flag once we've removed them.
60-
npm install --legacy-peer-deps
47+
npm install
6148

6249
set +o xtrace
6350
echo "Running: nvm use ${NODE_VERSION}"

.evergreen/run-typescript.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env bash
22
set -o errexit # Exit the script with error if any of the commands fail
33

4-
# source "${PROJECT_DIRECTORY}/.evergreen/init-nvm.sh"
4+
source "${PROJECT_DIRECTORY}/.evergreen/init-nvm.sh"
55

66
set -o xtrace
77

.mocharc.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,8 @@
1212
"timeout": 10000,
1313
"failZero": true,
1414
"sort": true,
15-
"color": true
15+
"color": true,
16+
"node-option": [
17+
"experimental-vm-modules"
18+
]
1619
}

README.md

Lines changed: 49 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
# BSON parser
22

3-
BSON is short for "Binary JSON," and is the binary-encoded serialization of JSON-like documents. You can learn more about it in [the specification](http://bsonspec.org).
4-
5-
This browser version of the BSON parser is compiled using [rollup](https://rollupjs.org/) and the current version is pre-compiled in the `dist` directory.
6-
7-
This is the default BSON parser, however, there is a C++ Node.js addon version as well that does not support the browser. It can be found at [mongod-js/bson-ext](https://github.com/mongodb-js/bson-ext).
3+
BSON is short for "Binary JSON," and is the binary-encoded serialization of JSON-like documents.
4+
You can learn more about it in [the specification](http://bsonspec.org).
85

96
### Table of Contents
107
- [Usage](#usage)
@@ -32,106 +29,43 @@ npm install
3229
npm run build
3330
```
3431

35-
### Node (no bundling)
36-
A simple example of how to use BSON in `Node.js`:
32+
### Node.js or Bundling Usage
3733

38-
```js
39-
const BSON = require('bson');
40-
const Long = BSON.Long;
34+
When using a bundler or Node.js you can import bson using the package name:
4135

42-
// Serialize a document
43-
const doc = { long: Long.fromNumber(100) };
44-
const data = BSON.serialize(doc);
45-
console.log('data:', data);
36+
```js
37+
import { BSON, EJSON, ObjectId } from 'bson';
38+
// or:
39+
// const { BSON, EJSON, ObjectId } = require('bson');
4640

47-
// Deserialize the resulting Buffer
48-
const doc_2 = BSON.deserialize(data);
49-
console.log('doc_2:', doc_2);
41+
const bytes = BSON.serialize({ _id: new ObjectId() });
42+
console.log(bytes);
43+
const doc = BSON.deserialize(bytes);
44+
console.log(EJSON.stringify(doc));
45+
// {"_id":{"$oid":"..."}}
5046
```
5147

52-
### Browser (no bundling)
48+
### Browser Usage
5349

54-
If you are not using a bundler like webpack, you can include `dist/bson.bundle.js` using a script tag. It includes polyfills for built-in node types like `Buffer`.
50+
If you are working directly in the browser without a bundler please use the `.mjs` bundle like so:
5551

5652
```html
57-
<script src="./dist/bson.bundle.js"></script>
58-
59-
<script>
60-
function start() {
61-
// Get the Long type
62-
const Long = BSON.Long;
63-
64-
// Serialize a document
65-
const doc = { long: Long.fromNumber(100) }
66-
const data = BSON.serialize(doc);
67-
console.log('data:', data);
68-
69-
// De serialize it again
70-
const doc_2 = BSON.deserialize(data);
71-
console.log('doc_2:', doc_2);
72-
}
53+
<script type="module">
54+
import { BSON, EJSON, ObjectId } from './lib/bson.mjs';
55+
56+
const bytes = BSON.serialize({ _id: new ObjectId() });
57+
console.log(bytes);
58+
const doc = BSON.deserialize(bytes);
59+
console.log(EJSON.stringify(doc));
60+
// {"_id":{"$oid":"..."}}
7361
</script>
7462
```
7563

76-
### Using webpack
77-
78-
If using webpack, you can use your normal import/require syntax of your project to pull in the `bson` library.
79-
80-
ES6 Example:
81-
82-
```js
83-
import { Long, serialize, deserialize } from 'bson';
84-
85-
// Serialize a document
86-
const doc = { long: Long.fromNumber(100) };
87-
const data = serialize(doc);
88-
console.log('data:', data);
89-
90-
// De serialize it again
91-
const doc_2 = deserialize(data);
92-
console.log('doc_2:', doc_2);
93-
```
94-
95-
ES5 Example:
96-
97-
```js
98-
const BSON = require('bson');
99-
const Long = BSON.Long;
100-
101-
// Serialize a document
102-
const doc = { long: Long.fromNumber(100) };
103-
const data = BSON.serialize(doc);
104-
console.log('data:', data);
105-
106-
// Deserialize the resulting Buffer
107-
const doc_2 = BSON.deserialize(data);
108-
console.log('doc_2:', doc_2);
109-
```
110-
111-
Depending on your settings, webpack will under the hood resolve to one of the following:
112-
113-
- `dist/bson.browser.esm.js` If your project is in the browser and using ES6 modules (Default for `webworker` and `web` targets)
114-
- `dist/bson.browser.umd.js` If your project is in the browser and not using ES6 modules
115-
- `dist/bson.esm.js` If your project is in Node.js and using ES6 modules (Default for `node` targets)
116-
- `lib/bson.js` (the normal include path) If your project is in Node.js and not using ES6 modules
117-
118-
For more information, see [this page on webpack's `resolve.mainFields`](https://webpack.js.org/configuration/resolve/#resolvemainfields) and [the `package.json` for this project](./package.json#L52)
119-
120-
### Usage with Angular
121-
122-
Starting with Angular 6, Angular CLI removed the shim for `global` and other node built-in variables (original comment [here](https://github.com/angular/angular-cli/issues/9827#issuecomment-386154063)). If you are using BSON with Angular, you may need to add the following shim to your `polyfills.ts` file:
123-
124-
```js
125-
// In polyfills.ts
126-
(window as any).global = window;
127-
```
128-
129-
- [Original Comment by Angular CLI](https://github.com/angular/angular-cli/issues/9827#issuecomment-386154063)
130-
- [Original Source for Solution](https://stackoverflow.com/a/50488337/4930088)
131-
13264
## Installation
13365

134-
`npm install bson`
66+
```sh
67+
npm install bson
68+
```
13569

13670
## Documentation
13771

@@ -352,6 +286,29 @@ Deserialize stream data as BSON documents.
352286

353287
**Returns**: <code>Number</code> - returns the next index in the buffer after deserialization **x** numbers of documents.
354288

289+
## Error Handling
290+
291+
It is our recommendation to use `BSONError.isBSONError()` checks on errors and to avoid relying on parsing `error.message` and `error.name` strings in your code. We guarantee `BSONError.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.
292+
293+
Any new errors we add to the driver will directly extend an existing error class and no existing error will be moved to a different parent class outside of a major release.
294+
This means `BSONError.isBSONError()` will always be able to accurately capture the errors that our BSON library throws.
295+
296+
Hypothetical example: A collection in our Db has an issue with UTF-8 data:
297+
298+
```ts
299+
let documentCount = 0;
300+
const cursor = collection.find({}, { utf8Validation: true });
301+
try {
302+
for await (const doc of cursor) documentCount += 1;
303+
} catch (error) {
304+
if (BSONError.isBSONError(error)) {
305+
console.log(`Found the troublemaker UTF-8!: ${documentCount} ${error.message}`);
306+
return documentCount;
307+
}
308+
throw error;
309+
}
310+
```
311+
355312
## FAQ
356313

357314
#### Why does `undefined` get converted to `null`?

docs/upgrade-to-v5.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,3 +238,53 @@ const result = BSON.serialize(Object.fromEntries([1, true, 'blue'].entries()))
238238
BSON.deserialize(result)
239239
// { '0': 1, '1': true, '2': 'blue' }
240240
```
241+
242+
### Exports and available bundles
243+
244+
Most users should be unaffected by these changes, Node.js `require()` / Node.js `import` will fetch the corresponding BSON library as expected.
245+
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.
246+
247+
Our package.json defines the following `"exports"` settings.
248+
```json
249+
{
250+
"main": "./lib/bson.cjs",
251+
"module": "./lib/bson.mjs",
252+
"browser": "./lib/bson.mjs",
253+
"exports": {
254+
"browser": "./lib/bson.mjs",
255+
"import": "./lib/bson.mjs",
256+
"require": "./lib/bson.cjs"
257+
}
258+
}
259+
```
260+
261+
You can now find compiled bundles of the BSON library in 3 common formats in the `lib` directory.
262+
263+
- CommonJS - `lib/bson.cjs`
264+
- ES Module - `lib/bson.mjs`
265+
- Immediate Invoked Function Expression (IIFE) - `lib/bson.bundle.js`
266+
- 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.
267+
268+
### `BSONTypeError` removed and `BSONError` offers filtering functionality with `static isBSONError()`
269+
270+
`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 driver's readme](https://github.com/mongodb/node-mongodb-native/tree/main#error-handling).
271+
272+
273+
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.
274+
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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)).
275+
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.
276+
277+
Hypothetical example: A collection in our Db has an issue with UTF-8 data:
278+
```ts
279+
let documentCount = 0;
280+
const cursor = collection.find({}, { utf8Validation: true });
281+
try {
282+
for await (const doc of cursor) documentCount += 1;
283+
} catch (error) {
284+
if (BSONError.isBSONError(error)) {
285+
console.log(`Found the troublemaker UTF-8!: ${documentCount} ${error.message}`);
286+
return documentCount;
287+
}
288+
throw error;
289+
}
290+
```
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import MagicString from 'magic-string';
2+
3+
const CRYPTO_IMPORT_ESM_SRC = `const nodejsRandomBytes = await (async () => {
4+
try {
5+
return (await import('node:crypto')).randomBytes;`;
6+
7+
export class RequireRewriter {
8+
/**
9+
* Take the compiled source code input; types are expected to already have been removed
10+
* Look for the function that depends on crypto, replace it with a top-level await
11+
* and dynamic import for the node:crypto module.
12+
*
13+
* @param {string} code - source code of the module being transformed
14+
* @param {string} id - module id (usually the source file name)
15+
* @returns {{ code: string; map: import('magic-string').SourceMap }}
16+
*/
17+
transform(code, id) {
18+
if (!id.includes('node_byte_utils')) {
19+
return;
20+
}
21+
if (!code.includes('const nodejsRandomBytes')) {
22+
throw new Error(`Unexpected! 'const nodejsRandomBytes' is missing from ${id}`);
23+
}
24+
25+
const start = code.indexOf('const nodejsRandomBytes');
26+
const endString = `return require('node:crypto').randomBytes;`;
27+
const end = code.indexOf(endString) + endString.length;
28+
29+
if (start < 0 || end < 0) {
30+
throw new Error(
31+
`Unexpected! 'const nodejsRandomBytes' or 'return require('node:crypto').randomBytes;' not found`
32+
);
33+
}
34+
35+
// MagicString lets us edit the source code and still generate an accurate source map
36+
const magicString = new MagicString(code);
37+
magicString.overwrite(start, end, CRYPTO_IMPORT_ESM_SRC);
38+
39+
return {
40+
code: magicString.toString(),
41+
map: magicString.generateMap({ hires: true })
42+
};
43+
}
44+
}

0 commit comments

Comments
 (0)