bson 5.0.0-alpha.1 → 5.0.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -13,7 +13,7 @@
13
13
  "etc/prepare.js"
14
14
  ],
15
15
  "types": "bson.d.ts",
16
- "version": "5.0.0-alpha.1",
16
+ "version": "5.0.0-alpha.2",
17
17
  "author": {
18
18
  "name": "The MongoDB NodeJS Team",
19
19
  "email": "dbx-node@mongodb.com"
@@ -75,11 +75,11 @@
75
75
  },
76
76
  "main": "./lib/bson.cjs",
77
77
  "module": "./lib/bson.mjs",
78
- "browser": "./lib/bson.mjs",
79
78
  "exports": {
80
- "browser": "./lib/bson.mjs",
81
79
  "import": "./lib/bson.mjs",
82
- "require": "./lib/bson.cjs"
80
+ "require": "./lib/bson.cjs",
81
+ "react-native": "./lib/bson.cjs",
82
+ "browser": "./lib/bson.mjs"
83
83
  },
84
84
  "engines": {
85
85
  "node": ">=14.20.1"
package/src/binary.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { bufferToUuidHexString, uuidHexStringToBuffer, uuidValidateString } from './uuid_utils';
2
2
  import { isUint8Array } from './parser/utils';
3
3
  import type { EJSONOptions } from './extended_json';
4
- import { BSONError, BSONTypeError } from './error';
4
+ import { BSONError } from './error';
5
5
  import { BSON_BINARY_SUBTYPE_UUID_NEW, BSON_MAJOR_VERSION } from './constants';
6
6
  import { ByteUtils } from './utils/byte_utils';
7
7
 
@@ -86,7 +86,7 @@ export class Binary {
86
86
  !(buffer instanceof ArrayBuffer) &&
87
87
  !Array.isArray(buffer)
88
88
  ) {
89
- throw new BSONTypeError(
89
+ throw new BSONError(
90
90
  'Binary can only be constructed from string, Buffer, TypedArray, or Array<number>'
91
91
  );
92
92
  }
@@ -121,9 +121,9 @@ export class Binary {
121
121
  put(byteValue: string | number | Uint8Array | number[]): void {
122
122
  // If it's a string and a has more than one character throw an error
123
123
  if (typeof byteValue === 'string' && byteValue.length !== 1) {
124
- throw new BSONTypeError('only accepts single character String');
124
+ throw new BSONError('only accepts single character String');
125
125
  } else if (typeof byteValue !== 'number' && byteValue.length !== 1)
126
- throw new BSONTypeError('only accepts single character Uint8Array or Array');
126
+ throw new BSONError('only accepts single character Uint8Array or Array');
127
127
 
128
128
  // Decode the byte value once
129
129
  let decodedByte: number;
@@ -136,7 +136,7 @@ export class Binary {
136
136
  }
137
137
 
138
138
  if (decodedByte < 0 || decodedByte > 255) {
139
- throw new BSONTypeError('only accepts number in a valid unsigned byte range 0-255');
139
+ throw new BSONError('only accepts number in a valid unsigned byte range 0-255');
140
140
  }
141
141
 
142
142
  if (this.buffer.byteLength > this.position) {
@@ -283,7 +283,7 @@ export class Binary {
283
283
  data = uuidHexStringToBuffer(doc.$uuid);
284
284
  }
285
285
  if (!data) {
286
- throw new BSONTypeError(`Unexpected Binary Extended JSON format ${JSON.stringify(doc)}`);
286
+ throw new BSONError(`Unexpected Binary Extended JSON format ${JSON.stringify(doc)}`);
287
287
  }
288
288
  return type === BSON_BINARY_SUBTYPE_UUID_NEW ? new UUID(data) : new Binary(data, type);
289
289
  }
@@ -337,7 +337,7 @@ export class UUID extends Binary {
337
337
  } else if (typeof input === 'string') {
338
338
  bytes = uuidHexStringToBuffer(input);
339
339
  } else {
340
- throw new BSONTypeError(
340
+ throw new BSONError(
341
341
  'Argument passed in UUID constructor must be a UUID, a 16 byte Buffer or a 32/36 character hex string (dashes excluded/included, format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).'
342
342
  );
343
343
  }
package/src/bson.ts CHANGED
@@ -49,7 +49,7 @@ export {
49
49
  BSONRegExp,
50
50
  Decimal128
51
51
  };
52
- export { BSONError, BSONTypeError } from './error';
52
+ export { BSONError } from './error';
53
53
  export { BSONType } from './constants';
54
54
  export { EJSON } from './extended_json';
55
55
 
package/src/decimal128.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { BSON_MAJOR_VERSION } from './constants';
2
- import { BSONTypeError } from './error';
2
+ import { BSONError } from './error';
3
3
  import { Long } from './long';
4
4
  import { isUint8Array } from './parser/utils';
5
5
  import { ByteUtils } from './utils/byte_utils';
@@ -114,7 +114,7 @@ function lessThan(left: Long, right: Long): boolean {
114
114
  }
115
115
 
116
116
  function invalidErr(string: string, message: string) {
117
- throw new BSONTypeError(`"${string}" is not a valid Decimal128 string - ${message}`);
117
+ throw new BSONError(`"${string}" is not a valid Decimal128 string - ${message}`);
118
118
  }
119
119
 
120
120
  /** @public */
@@ -147,11 +147,11 @@ export class Decimal128 {
147
147
  this.bytes = Decimal128.fromString(bytes).bytes;
148
148
  } else if (isUint8Array(bytes)) {
149
149
  if (bytes.byteLength !== 16) {
150
- throw new BSONTypeError('Decimal128 must take a Buffer of 16 bytes');
150
+ throw new BSONError('Decimal128 must take a Buffer of 16 bytes');
151
151
  }
152
152
  this.bytes = bytes;
153
153
  } else {
154
- throw new BSONTypeError('Decimal128 must take a Buffer or string');
154
+ throw new BSONError('Decimal128 must take a Buffer or string');
155
155
  }
156
156
  }
157
157
 
@@ -206,7 +206,7 @@ export class Decimal128 {
206
206
  // TODO: implementing a custom parsing for this, or refactoring the regex would yield
207
207
  // further gains.
208
208
  if (representation.length >= 7000) {
209
- throw new BSONTypeError('' + representation + ' not a valid Decimal128 string');
209
+ throw new BSONError('' + representation + ' not a valid Decimal128 string');
210
210
  }
211
211
 
212
212
  // Results
@@ -216,7 +216,7 @@ export class Decimal128 {
216
216
 
217
217
  // Validate the string
218
218
  if ((!stringMatch && !infMatch && !nanMatch) || representation.length === 0) {
219
- throw new BSONTypeError('' + representation + ' not a valid Decimal128 string');
219
+ throw new BSONError('' + representation + ' not a valid Decimal128 string');
220
220
  }
221
221
 
222
222
  if (stringMatch) {
@@ -288,7 +288,7 @@ export class Decimal128 {
288
288
  }
289
289
 
290
290
  if (sawRadix && !nDigitsRead)
291
- throw new BSONTypeError('' + representation + ' not a valid Decimal128 string');
291
+ throw new BSONError('' + representation + ' not a valid Decimal128 string');
292
292
 
293
293
  // Read exponent if exists
294
294
  if (representation[index] === 'e' || representation[index] === 'E') {
package/src/double.ts CHANGED
@@ -57,23 +57,15 @@ export class Double {
57
57
  return this.value;
58
58
  }
59
59
 
60
- // NOTE: JavaScript has +0 and -0, apparently to model limit calculations. If a user
61
- // explicitly provided `-0` then we need to ensure the sign makes it into the output
62
60
  if (Object.is(Math.sign(this.value), -0)) {
63
- return { $numberDouble: `-${this.value.toFixed(1)}` };
61
+ // NOTE: JavaScript has +0 and -0, apparently to model limit calculations. If a user
62
+ // explicitly provided `-0` then we need to ensure the sign makes it into the output
63
+ return { $numberDouble: '-0.0' };
64
64
  }
65
65
 
66
- let $numberDouble: string;
67
- if (Number.isInteger(this.value)) {
68
- $numberDouble = this.value.toFixed(1);
69
- if ($numberDouble.length >= 13) {
70
- $numberDouble = this.value.toExponential(13).toUpperCase();
71
- }
72
- } else {
73
- $numberDouble = this.value.toString();
74
- }
75
-
76
- return { $numberDouble };
66
+ return {
67
+ $numberDouble: Number.isInteger(this.value) ? this.value.toFixed(1) : this.value.toString()
68
+ };
77
69
  }
78
70
 
79
71
  /** @internal */
package/src/error.ts CHANGED
@@ -1,21 +1,45 @@
1
- /** @public */
1
+ /**
2
+ * @public
3
+ * `BSONError` objects are thrown when runtime errors occur.
4
+ */
2
5
  export class BSONError extends Error {
3
- constructor(message: string) {
4
- super(message);
6
+ /**
7
+ * @internal
8
+ * The underlying algorithm for isBSONError may change to improve how strict it is
9
+ * about determining if an input is a BSONError. But it must remain backwards compatible
10
+ * with previous minors & patches of the current major version.
11
+ */
12
+ protected get bsonError(): true {
13
+ return true;
5
14
  }
6
15
 
7
- get name(): string {
16
+ override get name(): string {
8
17
  return 'BSONError';
9
18
  }
10
- }
11
19
 
12
- /** @public */
13
- export class BSONTypeError extends TypeError {
14
20
  constructor(message: string) {
15
21
  super(message);
16
22
  }
17
23
 
18
- get name(): string {
19
- return 'BSONTypeError';
24
+ /**
25
+ * @public
26
+ *
27
+ * All errors thrown from the BSON library inherit from `BSONError`.
28
+ * This method can assist with determining if an error originates from the BSON library
29
+ * even if it does not pass an `instanceof` check against this class' constructor.
30
+ *
31
+ * @param value - any javascript value that needs type checking
32
+ */
33
+ public static isBSONError(value: unknown): value is BSONError {
34
+ return (
35
+ value != null &&
36
+ typeof value === 'object' &&
37
+ 'bsonError' in value &&
38
+ value.bsonError === true &&
39
+ // Do not access the following properties, just check existence
40
+ 'name' in value &&
41
+ 'message' in value &&
42
+ 'stack' in value
43
+ );
20
44
  }
21
45
  }
@@ -1,11 +1,17 @@
1
1
  import { Binary } from './binary';
2
2
  import type { Document } from './bson';
3
3
  import { Code } from './code';
4
- import { BSON_INT32_MAX, BSON_INT32_MIN, BSON_INT64_MAX, BSON_INT64_MIN } from './constants';
4
+ import {
5
+ BSON_INT32_MAX,
6
+ BSON_INT32_MIN,
7
+ BSON_INT64_MAX,
8
+ BSON_INT64_MIN,
9
+ BSON_MAJOR_VERSION
10
+ } from './constants';
5
11
  import { DBRef, isDBRefLike } from './db_ref';
6
12
  import { Decimal128 } from './decimal128';
7
13
  import { Double } from './double';
8
- import { BSONError, BSONTypeError } from './error';
14
+ import { BSONError } from './error';
9
15
  import { Int32 } from './int_32';
10
16
  import { Long } from './long';
11
17
  import { MaxKey } from './max_key';
@@ -192,7 +198,7 @@ function serializeValue(value: any, options: EJSONSerializeOptions): any {
192
198
  circularPart.length + (alreadySeen.length + current.length) / 2 - 1
193
199
  );
194
200
 
195
- throw new BSONTypeError(
201
+ throw new BSONError(
196
202
  'Converting circular structure to EJSON:\n' +
197
203
  ` ${leadingPart}${alreadySeen}${circularPart}${current}\n` +
198
204
  ` ${leadingSpace}\\${dashes}/`
@@ -310,7 +316,7 @@ function serializeDocument(doc: any, options: EJSONSerializeOptions) {
310
316
  doc != null &&
311
317
  typeof doc === 'object' &&
312
318
  typeof doc._bsontype === 'string' &&
313
- doc[Symbol.for('@@mdb.bson.version')] == null
319
+ doc[Symbol.for('@@mdb.bson.version')] !== BSON_MAJOR_VERSION
314
320
  ) {
315
321
  throw new BSONError('Unsupported BSON version, bson types must be from bson 5.0 or later');
316
322
  } else if (isBSONType(doc)) {
@@ -324,7 +330,7 @@ function serializeDocument(doc: any, options: EJSONSerializeOptions) {
324
330
  // Copy the object into this library's version of that type.
325
331
  const mapper = BSON_TYPE_MAPPINGS[doc._bsontype];
326
332
  if (!mapper) {
327
- throw new BSONTypeError('Unrecognized or invalid _bsontype: ' + doc._bsontype);
333
+ throw new BSONError('Unrecognized or invalid _bsontype: ' + doc._bsontype);
328
334
  }
329
335
  outDoc = mapper(outDoc);
330
336
  }
package/src/long.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { BSON_MAJOR_VERSION } from './constants';
2
+ import { BSONError } from './error';
2
3
  import type { EJSONOptions } from './extended_json';
3
4
  import type { Timestamp } from './timestamp';
4
5
 
@@ -250,7 +251,7 @@ export class Long {
250
251
  * @returns The corresponding Long value
251
252
  */
252
253
  static fromString(str: string, unsigned?: boolean, radix?: number): Long {
253
- if (str.length === 0) throw Error('empty string');
254
+ if (str.length === 0) throw new BSONError('empty string');
254
255
  if (str === 'NaN' || str === 'Infinity' || str === '+Infinity' || str === '-Infinity')
255
256
  return Long.ZERO;
256
257
  if (typeof unsigned === 'number') {
@@ -260,10 +261,10 @@ export class Long {
260
261
  unsigned = !!unsigned;
261
262
  }
262
263
  radix = radix || 10;
263
- if (radix < 2 || 36 < radix) throw RangeError('radix');
264
+ if (radix < 2 || 36 < radix) throw new BSONError('radix');
264
265
 
265
266
  let p;
266
- if ((p = str.indexOf('-')) > 0) throw Error('interior hyphen');
267
+ if ((p = str.indexOf('-')) > 0) throw new BSONError('interior hyphen');
267
268
  else if (p === 0) {
268
269
  return Long.fromString(str.substring(1), unsigned, radix).neg();
269
270
  }
@@ -431,7 +432,7 @@ export class Long {
431
432
  */
432
433
  divide(divisor: string | number | Long | Timestamp): Long {
433
434
  if (!Long.isLong(divisor)) divisor = Long.fromValue(divisor);
434
- if (divisor.isZero()) throw Error('division by zero');
435
+ if (divisor.isZero()) throw new BSONError('division by zero');
435
436
 
436
437
  // use wasm support if present
437
438
  if (wasm) {
@@ -959,7 +960,7 @@ export class Long {
959
960
  */
960
961
  toString(radix?: number): string {
961
962
  radix = radix || 10;
962
- if (radix < 2 || 36 < radix) throw RangeError('radix');
963
+ if (radix < 2 || 36 < radix) throw new BSONError('radix');
963
964
  if (this.isZero()) return '0';
964
965
  if (this.isNegative()) {
965
966
  // Unsigned Longs are never negative
package/src/objectid.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { BSON_MAJOR_VERSION } from './constants';
2
- import { BSONTypeError } from './error';
2
+ import { BSONError } from './error';
3
3
  import { isUint8Array } from './parser/utils';
4
4
  import { BSONDataView, ByteUtils } from './utils/byte_utils';
5
5
 
@@ -57,9 +57,7 @@ export class ObjectId {
57
57
  let workingId;
58
58
  if (typeof inputId === 'object' && inputId && 'id' in inputId) {
59
59
  if (typeof inputId.id !== 'string' && !ArrayBuffer.isView(inputId.id)) {
60
- throw new BSONTypeError(
61
- 'Argument passed in must have an id that is of type string or Buffer'
62
- );
60
+ throw new BSONError('Argument passed in must have an id that is of type string or Buffer');
63
61
  }
64
62
  if ('toHexString' in inputId && typeof inputId.toHexString === 'function') {
65
63
  workingId = ByteUtils.fromHex(inputId.toHexString());
@@ -85,17 +83,17 @@ export class ObjectId {
85
83
  if (bytes.byteLength === 12) {
86
84
  this[kId] = bytes;
87
85
  } else {
88
- throw new BSONTypeError('Argument passed in must be a string of 12 bytes');
86
+ throw new BSONError('Argument passed in must be a string of 12 bytes');
89
87
  }
90
88
  } else if (workingId.length === 24 && checkForHexRegExp.test(workingId)) {
91
89
  this[kId] = ByteUtils.fromHex(workingId);
92
90
  } else {
93
- throw new BSONTypeError(
91
+ throw new BSONError(
94
92
  'Argument passed in must be a string of 12 bytes or a string of 24 hex characters or an integer'
95
93
  );
96
94
  }
97
95
  } else {
98
- throw new BSONTypeError('Argument passed in does not match the accepted types');
96
+ throw new BSONError('Argument passed in does not match the accepted types');
99
97
  }
100
98
  // If we are caching the hex string
101
99
  if (ObjectId.cacheHexString) {
@@ -271,7 +269,7 @@ export class ObjectId {
271
269
  static createFromHexString(hexString: string): ObjectId {
272
270
  // Throw an error if it's not a valid setup
273
271
  if (typeof hexString === 'undefined' || (hexString != null && hexString.length !== 24)) {
274
- throw new BSONTypeError(
272
+ throw new BSONError(
275
273
  'Argument passed in must be a single String of 12 bytes or a string of 24 hex characters'
276
274
  );
277
275
  }
@@ -1,5 +1,6 @@
1
1
  import { Binary } from '../binary';
2
2
  import type { Document } from '../bson';
3
+ import { BSONError } from '../error';
3
4
  import * as constants from '../constants';
4
5
  import { ByteUtils } from '../utils/byte_utils';
5
6
  import { isAnyArrayBuffer, isDate, isRegExp } from './utils';
@@ -77,7 +78,17 @@ function calculateElement(
77
78
  case 'boolean':
78
79
  return (name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) + (1 + 1);
79
80
  case 'object':
80
- if (value == null || value['_bsontype'] === 'MinKey' || value['_bsontype'] === 'MaxKey') {
81
+ if (
82
+ value != null &&
83
+ typeof value._bsontype === 'string' &&
84
+ value[Symbol.for('@@mdb.bson.version')] !== constants.BSON_MAJOR_VERSION
85
+ ) {
86
+ throw new BSONError('Unsupported BSON version, bson types must be from bson 5.0 or later');
87
+ } else if (
88
+ value == null ||
89
+ value['_bsontype'] === 'MinKey' ||
90
+ value['_bsontype'] === 'MaxKey'
91
+ ) {
81
92
  return (name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) + 1;
82
93
  } else if (value['_bsontype'] === 'ObjectId') {
83
94
  return (name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) + (12 + 1);
@@ -14,12 +14,14 @@ import { ObjectId } from '../objectid';
14
14
  import { BSONRegExp } from '../regexp';
15
15
  import { BSONSymbol } from '../symbol';
16
16
  import { Timestamp } from '../timestamp';
17
- import { ByteUtils } from '../utils/byte_utils';
17
+ import { BSONDataView, ByteUtils } from '../utils/byte_utils';
18
18
  import { validateUtf8 } from '../validate_utf8';
19
19
 
20
20
  /** @public */
21
21
  export interface DeserializeOptions {
22
- /** when deserializing a Long will fit it into a Number if it's smaller than 53 bits */
22
+ /** when deserializing a Long will return as a BigInt. */
23
+ useBigInt64?: boolean;
24
+ /** when deserializing a Long will fit it into a Number if it's smaller than 53 bits. */
23
25
  promoteLongs?: boolean;
24
26
  /** when deserializing a Binary will return it as a node.js Buffer instance. */
25
27
  promoteBuffers?: boolean;
@@ -29,7 +31,7 @@ export interface DeserializeOptions {
29
31
  fieldsAsRaw?: Document;
30
32
  /** return BSON regular expressions as BSONRegExp instances. */
31
33
  bsonRegExp?: boolean;
32
- /** allows the buffer to be larger than the parsed BSON object */
34
+ /** allows the buffer to be larger than the parsed BSON object. */
33
35
  allowObjectSmallerThanBufferSize?: boolean;
34
36
  /** Offset into buffer to begin reading document from */
35
37
  index?: number;
@@ -96,7 +98,7 @@ export function internalDeserialize(
96
98
  );
97
99
  }
98
100
 
99
- // Start deserializtion
101
+ // Start deserialization
100
102
  return deserializeObject(buffer, index, options, isArray);
101
103
  }
102
104
 
@@ -117,9 +119,18 @@ function deserializeObject(
117
119
  const bsonRegExp = typeof options['bsonRegExp'] === 'boolean' ? options['bsonRegExp'] : false;
118
120
 
119
121
  // Controls the promotion of values vs wrapper classes
120
- const promoteBuffers = options['promoteBuffers'] == null ? false : options['promoteBuffers'];
121
- const promoteLongs = options['promoteLongs'] == null ? true : options['promoteLongs'];
122
- const promoteValues = options['promoteValues'] == null ? true : options['promoteValues'];
122
+ const promoteBuffers = options.promoteBuffers ?? false;
123
+ const promoteLongs = options.promoteLongs ?? true;
124
+ const promoteValues = options.promoteValues ?? true;
125
+ const useBigInt64 = options.useBigInt64 ?? false;
126
+
127
+ if (useBigInt64 && !promoteValues) {
128
+ throw new BSONError('Must either request bigint or Long for int64 deserialization');
129
+ }
130
+
131
+ if (useBigInt64 && !promoteLongs) {
132
+ throw new BSONError('Must either request bigint or Long for int64 deserialization');
133
+ }
123
134
 
124
135
  // Ensures default validation option if none given
125
136
  const validation = options.validation == null ? { utf8: true } : options.validation;
@@ -323,6 +334,8 @@ function deserializeObject(
323
334
  value = null;
324
335
  } else if (elementType === constants.BSON_DATA_LONG) {
325
336
  // Unpack the low and high bits
337
+ const dataview = BSONDataView.fromUint8Array(buffer.subarray(index, index + 8));
338
+
326
339
  const lowBits =
327
340
  buffer[index++] |
328
341
  (buffer[index++] << 8) |
@@ -334,8 +347,10 @@ function deserializeObject(
334
347
  (buffer[index++] << 16) |
335
348
  (buffer[index++] << 24);
336
349
  const long = new Long(lowBits, highBits);
337
- // Promote the long if possible
338
- if (promoteLongs && promoteValues === true) {
350
+ if (useBigInt64) {
351
+ value = dataview.getBigInt64(0, true);
352
+ } else if (promoteLongs && promoteValues === true) {
353
+ // Promote the long if possible
339
354
  value =
340
355
  long.lessThanOrEqual(JS_INT_MAX_LONG) && long.greaterThanOrEqual(JS_INT_MIN_LONG)
341
356
  ? long.toNumber()