Skip to content

Commit

Permalink
Merge branch 'main' into NODE-4873/support_ejson_fromBigInt_to_number…
Browse files Browse the repository at this point in the history
…Long
  • Loading branch information
W-A-James authored Jan 6, 2023
2 parents a5411c9 + 3b4b61e commit 78c0513
Show file tree
Hide file tree
Showing 12 changed files with 413 additions and 206 deletions.
37 changes: 36 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,12 +238,13 @@ Serialize a Javascript object using a predefined Buffer and index into the buffe
| buffer | <code>Buffer</code> | | the buffer containing the serialized set of BSON documents. |
| [options.evalFunctions] | <code>Object</code> | <code>false</code> | evaluate functions in the BSON document scoped to the object deserialized. |
| [options.cacheFunctions] | <code>Object</code> | <code>false</code> | cache evaluated functions for reuse. |
| [options.useBigInt64] | <code>Object</code> | <code>false</code> | when deserializing a Long will return a BigInt. |
| [options.promoteLongs] | <code>Object</code> | <code>true</code> | when deserializing a Long will fit it into a Number if it's smaller than 53 bits |
| [options.promoteBuffers] | <code>Object</code> | <code>false</code> | when deserializing a Binary will return it as a node.js Buffer instance. |
| [options.promoteValues] | <code>Object</code> | <code>false</code> | when deserializing will promote BSON values to their Node.js closest equivalent types. |
| [options.fieldsAsRaw] | <code>Object</code> | <code></code> | allow to specify if there what fields we wish to return as unserialized raw buffer. |
| [options.bsonRegExp] | <code>Object</code> | <code>false</code> | return BSON regular expressions as BSONRegExp instances. |
| [options.allowObjectSmallerThanBufferSize] | <code>boolean</code> | <code>false</code> | allows the buffer to be larger than the parsed BSON object |
| [options.allowObjectSmallerThanBufferSize] | <code>boolean</code> | <code>false</code> | allows the buffer to be larger than the parsed BSON object. |

Deserialize data as BSON.

Expand Down Expand Up @@ -308,6 +309,40 @@ try {
}
```

## React Native

BSON requires that `TextEncoder`, `TextDecoder`, `atob`, `btoa`, and `crypto.getRandomValues` are available globally. These are present in most Javascript runtimes but require polyfilling in React Native. Polyfills for the missing functionality can be installed with the following command:
```sh
npm install --save react-native-get-random-values text-encoding-polyfill base-64
```

The following snippet should be placed at the top of the entrypoint (by default this is the root `index.js` file) for React Native projects using the BSON library. These lines must be placed for any code that imports `BSON`.

```typescript
// Required Polyfills For ReactNative
import {encode, decode} from 'base-64';
if (global.btoa == null) {
global.btoa = encode;
}
if (global.atob == null) {
global.atob = decode;
}
import 'text-encoding-polyfill';
import 'react-native-get-random-values';
```

Finally, import the `BSON` library like so:

```typescript
import { BSON, EJSON } from 'bson';
```

This will cause React Native to import the `node_modules/bson/lib/bson.cjs` bundle (see the `"react-native"` setting we have in the `"exports"` section of our [package.json](./package.json).)

### Technical Note about React Native module import

The `"exports"` definition in our `package.json` will result in BSON's CommonJS bundle being imported in a React Native project instead of the ES module bundle. Importing the CommonJS bundle is necessary because BSON's ES module bundle of BSON uses top-level await, which is not supported syntax in [React Native's runtime hermes](https://hermesengine.dev/).

## FAQ

#### Why does `undefined` get converted to `null`?
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,11 @@
},
"main": "./lib/bson.cjs",
"module": "./lib/bson.mjs",
"browser": "./lib/bson.mjs",
"exports": {
"browser": "./lib/bson.mjs",
"import": "./lib/bson.mjs",
"require": "./lib/bson.cjs"
"require": "./lib/bson.cjs",
"react-native": "./lib/bson.cjs",
"browser": "./lib/bson.mjs"
},
"engines": {
"node": ">=14.20.1"
Expand Down
16 changes: 5 additions & 11 deletions src/double.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,23 +52,17 @@ export class Double {
return this.value;
}

// NOTE: JavaScript has +0 and -0, apparently to model limit calculations. If a user
// explicitly provided `-0` then we need to ensure the sign makes it into the output
if (Object.is(Math.sign(this.value), -0)) {
return { $numberDouble: `-${this.value.toFixed(1)}` };
// NOTE: JavaScript has +0 and -0, apparently to model limit calculations. If a user
// explicitly provided `-0` then we need to ensure the sign makes it into the output
return { $numberDouble: '-0.0' };
}

let $numberDouble: string;
if (Number.isInteger(this.value)) {
$numberDouble = this.value.toFixed(1);
if ($numberDouble.length >= 13) {
$numberDouble = this.value.toExponential(13).toUpperCase();
}
return { $numberDouble: `${this.value}.0` };
} else {
$numberDouble = this.value.toString();
return { $numberDouble: `${this.value}` };
}

return { $numberDouble };
}

/** @internal */
Expand Down
33 changes: 24 additions & 9 deletions src/parser/deserializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ import { ObjectId } from '../objectid';
import { BSONRegExp } from '../regexp';
import { BSONSymbol } from '../symbol';
import { Timestamp } from '../timestamp';
import { ByteUtils } from '../utils/byte_utils';
import { BSONDataView, ByteUtils } from '../utils/byte_utils';
import { validateUtf8 } from '../validate_utf8';

/** @public */
export interface DeserializeOptions {
/** when deserializing a Long will fit it into a Number if it's smaller than 53 bits */
/** when deserializing a Long will return as a BigInt. */
useBigInt64?: boolean;
/** when deserializing a Long will fit it into a Number if it's smaller than 53 bits. */
promoteLongs?: boolean;
/** when deserializing a Binary will return it as a node.js Buffer instance. */
promoteBuffers?: boolean;
Expand All @@ -29,7 +31,7 @@ export interface DeserializeOptions {
fieldsAsRaw?: Document;
/** return BSON regular expressions as BSONRegExp instances. */
bsonRegExp?: boolean;
/** allows the buffer to be larger than the parsed BSON object */
/** allows the buffer to be larger than the parsed BSON object. */
allowObjectSmallerThanBufferSize?: boolean;
/** Offset into buffer to begin reading document from */
index?: number;
Expand Down Expand Up @@ -96,7 +98,7 @@ export function internalDeserialize(
);
}

// Start deserializtion
// Start deserialization
return deserializeObject(buffer, index, options, isArray);
}

Expand All @@ -117,9 +119,18 @@ function deserializeObject(
const bsonRegExp = typeof options['bsonRegExp'] === 'boolean' ? options['bsonRegExp'] : false;

// Controls the promotion of values vs wrapper classes
const promoteBuffers = options['promoteBuffers'] == null ? false : options['promoteBuffers'];
const promoteLongs = options['promoteLongs'] == null ? true : options['promoteLongs'];
const promoteValues = options['promoteValues'] == null ? true : options['promoteValues'];
const promoteBuffers = options.promoteBuffers ?? false;
const promoteLongs = options.promoteLongs ?? true;
const promoteValues = options.promoteValues ?? true;
const useBigInt64 = options.useBigInt64 ?? false;

if (useBigInt64 && !promoteValues) {
throw new BSONError('Must either request bigint or Long for int64 deserialization');
}

if (useBigInt64 && !promoteLongs) {
throw new BSONError('Must either request bigint or Long for int64 deserialization');
}

// Ensures default validation option if none given
const validation = options.validation == null ? { utf8: true } : options.validation;
Expand Down Expand Up @@ -323,6 +334,8 @@ function deserializeObject(
value = null;
} else if (elementType === constants.BSON_DATA_LONG) {
// Unpack the low and high bits
const dataview = BSONDataView.fromUint8Array(buffer.subarray(index, index + 8));

const lowBits =
buffer[index++] |
(buffer[index++] << 8) |
Expand All @@ -334,8 +347,10 @@ function deserializeObject(
(buffer[index++] << 16) |
(buffer[index++] << 24);
const long = new Long(lowBits, highBits);
// Promote the long if possible
if (promoteLongs && promoteValues === true) {
if (useBigInt64) {
value = dataview.getBigInt64(0, true);
} else if (promoteLongs && promoteValues === true) {
// Promote the long if possible
value =
long.lessThanOrEqual(JS_INT_MAX_LONG) && long.greaterThanOrEqual(JS_INT_MIN_LONG)
? long.toNumber()
Expand Down
Loading

0 comments on commit 78c0513

Please sign in to comment.