bson 6.9.0 → 6.10.1

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
@@ -14,7 +14,7 @@
14
14
  "vendor"
15
15
  ],
16
16
  "types": "bson.d.ts",
17
- "version": "6.9.0",
17
+ "version": "6.10.1",
18
18
  "author": {
19
19
  "name": "The MongoDB NodeJS Team",
20
20
  "email": "dbx-node@mongodb.com"
@@ -55,7 +55,6 @@
55
55
  "sinon": "^18.0.0",
56
56
  "sinon-chai": "^3.7.0",
57
57
  "source-map-support": "^0.5.21",
58
- "standard-version": "^9.5.0",
59
58
  "tar": "^7.4.3",
60
59
  "ts-node": "^10.9.2",
61
60
  "tsd": "^0.31.1",
package/src/binary.ts CHANGED
@@ -4,6 +4,7 @@ import { BSONError } from './error';
4
4
  import { BSON_BINARY_SUBTYPE_UUID_NEW } from './constants';
5
5
  import { ByteUtils } from './utils/byte_utils';
6
6
  import { BSONValue } from './bson_value';
7
+ import { NumberUtils } from './utils/number_utils';
7
8
 
8
9
  /** @public */
9
10
  export type BinarySequence = Uint8Array | number[];
@@ -58,12 +59,60 @@ export class Binary extends BSONValue {
58
59
  static readonly SUBTYPE_COLUMN = 7;
59
60
  /** Sensitive BSON type */
60
61
  static readonly SUBTYPE_SENSITIVE = 8;
62
+ /** Vector BSON type */
63
+ static readonly SUBTYPE_VECTOR = 9;
61
64
  /** User BSON type */
62
65
  static readonly SUBTYPE_USER_DEFINED = 128;
63
66
 
64
- buffer!: Uint8Array;
65
- sub_type!: number;
66
- position!: number;
67
+ /** datatype of a Binary Vector (subtype: 9) */
68
+ static readonly VECTOR_TYPE = Object.freeze({
69
+ Int8: 0x03,
70
+ Float32: 0x27,
71
+ PackedBit: 0x10
72
+ } as const);
73
+
74
+ /**
75
+ * The bytes of the Binary value.
76
+ *
77
+ * The format of a Binary value in BSON is defined as:
78
+ * ```txt
79
+ * binary ::= int32 subtype (byte*)
80
+ * ```
81
+ *
82
+ * This `buffer` is the "(byte*)" segment.
83
+ *
84
+ * Unless the value is subtype 2, then deserialize will read the first 4 bytes as an int32 and set this to the remaining bytes.
85
+ *
86
+ * ```txt
87
+ * binary ::= int32 unsigned_byte(2) int32 (byte*)
88
+ * ```
89
+ *
90
+ * @see https://bsonspec.org/spec.html
91
+ */
92
+ public buffer: Uint8Array;
93
+ /**
94
+ * The binary subtype.
95
+ *
96
+ * Current defined values are:
97
+ *
98
+ * - `unsigned_byte(0)` Generic binary subtype
99
+ * - `unsigned_byte(1)` Function
100
+ * - `unsigned_byte(2)` Binary (Deprecated)
101
+ * - `unsigned_byte(3)` UUID (Deprecated)
102
+ * - `unsigned_byte(4)` UUID
103
+ * - `unsigned_byte(5)` MD5
104
+ * - `unsigned_byte(6)` Encrypted BSON value
105
+ * - `unsigned_byte(7)` Compressed BSON column
106
+ * - `unsigned_byte(8)` Sensitive
107
+ * - `unsigned_byte(9)` Vector
108
+ * - `unsigned_byte(128)` - `unsigned_byte(255)` User defined
109
+ */
110
+ public sub_type: number;
111
+ /**
112
+ * The Binary's `buffer` can be larger than the Binary's content.
113
+ * This property is used to determine where the content ends in the buffer.
114
+ */
115
+ public position: number;
67
116
 
68
117
  /**
69
118
  * Create a new Binary instance.
@@ -160,16 +209,15 @@ export class Binary extends BSONValue {
160
209
  }
161
210
 
162
211
  /**
163
- * Reads **length** bytes starting at **position**.
212
+ * Returns a view of **length** bytes starting at **position**.
164
213
  *
165
214
  * @param position - read from the given position in the Binary.
166
215
  * @param length - the number of bytes to read.
167
216
  */
168
- read(position: number, length: number): BinarySequence {
217
+ read(position: number, length: number): Uint8Array {
169
218
  length = length && length > 0 ? length : this.position;
170
-
171
- // Let's return the data based on the type we have
172
- return this.buffer.slice(position, position + length);
219
+ const end = position + length;
220
+ return this.buffer.subarray(position, end > this.position ? this.position : end);
173
221
  }
174
222
 
175
223
  /** returns a view of the binary value as a Uint8Array */
@@ -200,6 +248,11 @@ export class Binary extends BSONValue {
200
248
  /** @internal */
201
249
  toExtendedJSON(options?: EJSONOptions): BinaryExtendedLegacy | BinaryExtended {
202
250
  options = options || {};
251
+
252
+ if (this.sub_type === Binary.SUBTYPE_VECTOR) {
253
+ validateBinaryVector(this);
254
+ }
255
+
203
256
  const base64String = ByteUtils.toBase64(this.buffer);
204
257
 
205
258
  const subType = Number(this.sub_type).toString(16);
@@ -219,7 +272,7 @@ export class Binary extends BSONValue {
219
272
 
220
273
  toUUID(): UUID {
221
274
  if (this.sub_type === Binary.SUBTYPE_UUID) {
222
- return new UUID(this.buffer.slice(0, this.position));
275
+ return new UUID(this.buffer.subarray(0, this.position));
223
276
  }
224
277
 
225
278
  throw new BSONError(
@@ -272,6 +325,209 @@ export class Binary extends BSONValue {
272
325
  const subTypeArg = inspect(this.sub_type, options);
273
326
  return `Binary.createFromBase64(${base64Arg}, ${subTypeArg})`;
274
327
  }
328
+
329
+ /**
330
+ * If this Binary represents a Int8 Vector (`binary.buffer[0] === Binary.VECTOR_TYPE.Int8`),
331
+ * returns a copy of the bytes in a new Int8Array.
332
+ *
333
+ * If the Binary is not a Vector, or the datatype is not Int8, an error is thrown.
334
+ */
335
+ public toInt8Array(): Int8Array {
336
+ if (this.sub_type !== Binary.SUBTYPE_VECTOR) {
337
+ throw new BSONError('Binary sub_type is not Vector');
338
+ }
339
+
340
+ if (this.buffer[0] !== Binary.VECTOR_TYPE.Int8) {
341
+ throw new BSONError('Binary datatype field is not Int8');
342
+ }
343
+
344
+ return new Int8Array(
345
+ this.buffer.buffer.slice(this.buffer.byteOffset + 2, this.buffer.byteOffset + this.position)
346
+ );
347
+ }
348
+
349
+ /**
350
+ * If this Binary represents a Float32 Vector (`binary.buffer[0] === Binary.VECTOR_TYPE.Float32`),
351
+ * returns a copy of the bytes in a new Float32Array.
352
+ *
353
+ * If the Binary is not a Vector, or the datatype is not Float32, an error is thrown.
354
+ */
355
+ public toFloat32Array(): Float32Array {
356
+ if (this.sub_type !== Binary.SUBTYPE_VECTOR) {
357
+ throw new BSONError('Binary sub_type is not Vector');
358
+ }
359
+
360
+ if (this.buffer[0] !== Binary.VECTOR_TYPE.Float32) {
361
+ throw new BSONError('Binary datatype field is not Float32');
362
+ }
363
+
364
+ const floatBytes = new Uint8Array(
365
+ this.buffer.buffer.slice(this.buffer.byteOffset + 2, this.buffer.byteOffset + this.position)
366
+ );
367
+
368
+ if (NumberUtils.isBigEndian) ByteUtils.swap32(floatBytes);
369
+
370
+ return new Float32Array(floatBytes.buffer);
371
+ }
372
+
373
+ /**
374
+ * If this Binary represents packed bit Vector (`binary.buffer[0] === Binary.VECTOR_TYPE.PackedBit`),
375
+ * returns a copy of the bytes that are packed bits.
376
+ *
377
+ * Use `toBits` to get the unpacked bits.
378
+ *
379
+ * If the Binary is not a Vector, or the datatype is not PackedBit, an error is thrown.
380
+ */
381
+ public toPackedBits(): Uint8Array {
382
+ if (this.sub_type !== Binary.SUBTYPE_VECTOR) {
383
+ throw new BSONError('Binary sub_type is not Vector');
384
+ }
385
+
386
+ if (this.buffer[0] !== Binary.VECTOR_TYPE.PackedBit) {
387
+ throw new BSONError('Binary datatype field is not packed bit');
388
+ }
389
+
390
+ return new Uint8Array(
391
+ this.buffer.buffer.slice(this.buffer.byteOffset + 2, this.buffer.byteOffset + this.position)
392
+ );
393
+ }
394
+
395
+ /**
396
+ * If this Binary represents a Packed bit Vector (`binary.buffer[0] === Binary.VECTOR_TYPE.PackedBit`),
397
+ * returns a copy of the bit unpacked into a new Int8Array.
398
+ *
399
+ * Use `toPackedBits` to get the bits still in packed form.
400
+ *
401
+ * If the Binary is not a Vector, or the datatype is not PackedBit, an error is thrown.
402
+ */
403
+ public toBits(): Int8Array {
404
+ if (this.sub_type !== Binary.SUBTYPE_VECTOR) {
405
+ throw new BSONError('Binary sub_type is not Vector');
406
+ }
407
+
408
+ if (this.buffer[0] !== Binary.VECTOR_TYPE.PackedBit) {
409
+ throw new BSONError('Binary datatype field is not packed bit');
410
+ }
411
+
412
+ const byteCount = this.length() - 2;
413
+ const bitCount = byteCount * 8 - this.buffer[1];
414
+ const bits = new Int8Array(bitCount);
415
+
416
+ for (let bitOffset = 0; bitOffset < bits.length; bitOffset++) {
417
+ const byteOffset = (bitOffset / 8) | 0;
418
+ const byte = this.buffer[byteOffset + 2];
419
+ const shift = 7 - (bitOffset % 8);
420
+ const bit = (byte >> shift) & 1;
421
+ bits[bitOffset] = bit;
422
+ }
423
+
424
+ return bits;
425
+ }
426
+
427
+ /**
428
+ * Constructs a Binary representing an Int8 Vector.
429
+ * @param array - The array to store as a view on the Binary class
430
+ */
431
+ public static fromInt8Array(array: Int8Array): Binary {
432
+ const buffer = ByteUtils.allocate(array.byteLength + 2);
433
+ buffer[0] = Binary.VECTOR_TYPE.Int8;
434
+ buffer[1] = 0;
435
+ const intBytes = new Uint8Array(array.buffer, array.byteOffset, array.byteLength);
436
+ buffer.set(intBytes, 2);
437
+ return new this(buffer, this.SUBTYPE_VECTOR);
438
+ }
439
+
440
+ /** Constructs a Binary representing an Float32 Vector. */
441
+ public static fromFloat32Array(array: Float32Array): Binary {
442
+ const binaryBytes = ByteUtils.allocate(array.byteLength + 2);
443
+ binaryBytes[0] = Binary.VECTOR_TYPE.Float32;
444
+ binaryBytes[1] = 0;
445
+
446
+ const floatBytes = new Uint8Array(array.buffer, array.byteOffset, array.byteLength);
447
+ binaryBytes.set(floatBytes, 2);
448
+
449
+ if (NumberUtils.isBigEndian) ByteUtils.swap32(new Uint8Array(binaryBytes.buffer, 2));
450
+
451
+ return new this(binaryBytes, this.SUBTYPE_VECTOR);
452
+ }
453
+
454
+ /**
455
+ * Constructs a Binary representing a packed bit Vector.
456
+ *
457
+ * Use `fromBits` to pack an array of 1s and 0s.
458
+ */
459
+ public static fromPackedBits(array: Uint8Array, padding = 0): Binary {
460
+ const buffer = ByteUtils.allocate(array.byteLength + 2);
461
+ buffer[0] = Binary.VECTOR_TYPE.PackedBit;
462
+ buffer[1] = padding;
463
+ buffer.set(array, 2);
464
+ return new this(buffer, this.SUBTYPE_VECTOR);
465
+ }
466
+
467
+ /**
468
+ * Constructs a Binary representing an Packed Bit Vector.
469
+ * @param array - The array of 1s and 0s to pack into the Binary instance
470
+ */
471
+ public static fromBits(bits: ArrayLike<number>): Binary {
472
+ const byteLength = (bits.length + 7) >>> 3; // ceil(bits.length / 8)
473
+ const bytes = new Uint8Array(byteLength + 2);
474
+ bytes[0] = Binary.VECTOR_TYPE.PackedBit;
475
+
476
+ const remainder = bits.length % 8;
477
+ bytes[1] = remainder === 0 ? 0 : 8 - remainder;
478
+
479
+ for (let bitOffset = 0; bitOffset < bits.length; bitOffset++) {
480
+ const byteOffset = bitOffset >>> 3; // floor(bitOffset / 8)
481
+ const bit = bits[bitOffset];
482
+
483
+ if (bit !== 0 && bit !== 1) {
484
+ throw new BSONError(
485
+ `Invalid bit value at ${bitOffset}: must be 0 or 1, found ${bits[bitOffset]}`
486
+ );
487
+ }
488
+
489
+ if (bit === 0) continue;
490
+
491
+ const shift = 7 - (bitOffset % 8);
492
+ bytes[byteOffset + 2] |= bit << shift;
493
+ }
494
+
495
+ return new this(bytes, Binary.SUBTYPE_VECTOR);
496
+ }
497
+ }
498
+
499
+ export function validateBinaryVector(vector: Binary): void {
500
+ if (vector.sub_type !== Binary.SUBTYPE_VECTOR) return;
501
+
502
+ const size = vector.position;
503
+
504
+ // NOTE: Validation is only applied to **KNOWN** vector types
505
+ // If a new datatype is introduced, a future version of the library will need to add validation
506
+ const datatype = vector.buffer[0];
507
+
508
+ // NOTE: We do not enable noUncheckedIndexedAccess so TS believes this is always number
509
+ // a Binary vector may be empty, in which case the padding is undefined
510
+ // this possible value is tolerable for our validation checks
511
+ const padding: number | undefined = vector.buffer[1];
512
+
513
+ if (
514
+ (datatype === Binary.VECTOR_TYPE.Float32 || datatype === Binary.VECTOR_TYPE.Int8) &&
515
+ padding !== 0
516
+ ) {
517
+ throw new BSONError('Invalid Vector: padding must be zero for int8 and float32 vectors');
518
+ }
519
+
520
+ if (datatype === Binary.VECTOR_TYPE.PackedBit && padding !== 0 && size === 2) {
521
+ throw new BSONError(
522
+ 'Invalid Vector: padding must be zero for packed bit vectors that are empty'
523
+ );
524
+ }
525
+
526
+ if (datatype === Binary.VECTOR_TYPE.PackedBit && padding > 7) {
527
+ throw new BSONError(
528
+ `Invalid Vector: padding must be a value between 0 and 7. found: ${padding}`
529
+ );
530
+ }
275
531
  }
276
532
 
277
533
  /** @public */
package/src/objectid.ts CHANGED
@@ -7,6 +7,9 @@ import { NumberUtils } from './utils/number_utils';
7
7
  // Unique sequence for the current process (initialized on first use)
8
8
  let PROCESS_UNIQUE: Uint8Array | null = null;
9
9
 
10
+ /** ObjectId hexString cache @internal */
11
+ const __idCache = new WeakMap(); // TODO(NODE-6549): convert this to #__id private field when target updated to ES2022
12
+
10
13
  /** @public */
11
14
  export interface ObjectIdLike {
12
15
  id: string | Uint8Array;
@@ -36,8 +39,6 @@ export class ObjectId extends BSONValue {
36
39
 
37
40
  /** ObjectId Bytes @internal */
38
41
  private buffer!: Uint8Array;
39
- /** ObjectId hexString cache @internal */
40
- private __id?: string;
41
42
 
42
43
  /**
43
44
  * Create ObjectId from a number.
@@ -111,6 +112,10 @@ export class ObjectId extends BSONValue {
111
112
  } else if (typeof workingId === 'string') {
112
113
  if (ObjectId.validateHexString(workingId)) {
113
114
  this.buffer = ByteUtils.fromHex(workingId);
115
+ // If we are caching the hex string
116
+ if (ObjectId.cacheHexString) {
117
+ __idCache.set(this, workingId);
118
+ }
114
119
  } else {
115
120
  throw new BSONError(
116
121
  'input must be a 24 character hex string, 12 byte Uint8Array, or an integer'
@@ -119,10 +124,6 @@ export class ObjectId extends BSONValue {
119
124
  } else {
120
125
  throw new BSONError('Argument passed in does not match the accepted types');
121
126
  }
122
- // If we are caching the hex string
123
- if (ObjectId.cacheHexString) {
124
- this.__id = ByteUtils.toHex(this.id);
125
- }
126
127
  }
127
128
 
128
129
  /**
@@ -136,7 +137,7 @@ export class ObjectId extends BSONValue {
136
137
  set id(value: Uint8Array) {
137
138
  this.buffer = value;
138
139
  if (ObjectId.cacheHexString) {
139
- this.__id = ByteUtils.toHex(value);
140
+ __idCache.set(this, ByteUtils.toHex(value));
140
141
  }
141
142
  }
142
143
 
@@ -165,14 +166,15 @@ export class ObjectId extends BSONValue {
165
166
 
166
167
  /** Returns the ObjectId id as a 24 lowercase character hex string representation */
167
168
  toHexString(): string {
168
- if (ObjectId.cacheHexString && this.__id) {
169
- return this.__id;
169
+ if (ObjectId.cacheHexString) {
170
+ const __id = __idCache.get(this);
171
+ if (__id) return __id;
170
172
  }
171
173
 
172
174
  const hexString = ByteUtils.toHex(this.id);
173
175
 
174
- if (ObjectId.cacheHexString && !this.__id) {
175
- this.__id = hexString;
176
+ if (ObjectId.cacheHexString) {
177
+ __idCache.set(this, hexString);
176
178
  }
177
179
 
178
180
  return hexString;
@@ -370,6 +372,11 @@ export class ObjectId extends BSONValue {
370
372
  return new ObjectId(doc.$oid);
371
373
  }
372
374
 
375
+ /** @internal */
376
+ private isCached(): boolean {
377
+ return ObjectId.cacheHexString && __idCache.has(this);
378
+ }
379
+
373
380
  /**
374
381
  * Converts to a string representation of this Id.
375
382
  *
@@ -296,7 +296,7 @@ function deserializeObject(
296
296
 
297
297
  // We have a raw value
298
298
  if (raw) {
299
- value = buffer.slice(index, index + objectSize);
299
+ value = buffer.subarray(index, index + objectSize);
300
300
  } else {
301
301
  let objectOptions = options;
302
302
  if (!globalUTFValidation) {
@@ -374,52 +374,24 @@ function deserializeObject(
374
374
  if (binarySize > buffer.byteLength)
375
375
  throw new BSONError('Binary type size larger than document size');
376
376
 
377
- // Decode as raw Buffer object if options specifies it
378
- if (buffer['slice'] != null) {
379
- // If we have subtype 2 skip the 4 bytes for the size
380
- if (subType === Binary.SUBTYPE_BYTE_ARRAY) {
381
- binarySize = NumberUtils.getInt32LE(buffer, index);
382
- index += 4;
383
- if (binarySize < 0)
384
- throw new BSONError('Negative binary type element size found for subtype 0x02');
385
- if (binarySize > totalBinarySize - 4)
386
- throw new BSONError('Binary type with subtype 0x02 contains too long binary size');
387
- if (binarySize < totalBinarySize - 4)
388
- throw new BSONError('Binary type with subtype 0x02 contains too short binary size');
389
- }
377
+ // If we have subtype 2 skip the 4 bytes for the size
378
+ if (subType === Binary.SUBTYPE_BYTE_ARRAY) {
379
+ binarySize = NumberUtils.getInt32LE(buffer, index);
380
+ index += 4;
381
+ if (binarySize < 0)
382
+ throw new BSONError('Negative binary type element size found for subtype 0x02');
383
+ if (binarySize > totalBinarySize - 4)
384
+ throw new BSONError('Binary type with subtype 0x02 contains too long binary size');
385
+ if (binarySize < totalBinarySize - 4)
386
+ throw new BSONError('Binary type with subtype 0x02 contains too short binary size');
387
+ }
390
388
 
391
- if (promoteBuffers && promoteValues) {
392
- value = ByteUtils.toLocalBufferType(buffer.slice(index, index + binarySize));
393
- } else {
394
- value = new Binary(buffer.slice(index, index + binarySize), subType);
395
- if (subType === constants.BSON_BINARY_SUBTYPE_UUID_NEW && UUID.isValid(value)) {
396
- value = value.toUUID();
397
- }
398
- }
389
+ if (promoteBuffers && promoteValues) {
390
+ value = ByteUtils.toLocalBufferType(buffer.subarray(index, index + binarySize));
399
391
  } else {
400
- // If we have subtype 2 skip the 4 bytes for the size
401
- if (subType === Binary.SUBTYPE_BYTE_ARRAY) {
402
- binarySize = NumberUtils.getInt32LE(buffer, index);
403
- index += 4;
404
- if (binarySize < 0)
405
- throw new BSONError('Negative binary type element size found for subtype 0x02');
406
- if (binarySize > totalBinarySize - 4)
407
- throw new BSONError('Binary type with subtype 0x02 contains too long binary size');
408
- if (binarySize < totalBinarySize - 4)
409
- throw new BSONError('Binary type with subtype 0x02 contains too short binary size');
410
- }
411
-
412
- if (promoteBuffers && promoteValues) {
413
- value = ByteUtils.allocateUnsafe(binarySize);
414
- // Copy the data
415
- for (i = 0; i < binarySize; i++) {
416
- value[i] = buffer[index + i];
417
- }
418
- } else {
419
- value = new Binary(buffer.slice(index, index + binarySize), subType);
420
- if (subType === constants.BSON_BINARY_SUBTYPE_UUID_NEW && UUID.isValid(value)) {
421
- value = value.toUUID();
422
- }
392
+ value = new Binary(buffer.subarray(index, index + binarySize), subType);
393
+ if (subType === constants.BSON_BINARY_SUBTYPE_UUID_NEW && UUID.isValid(value)) {
394
+ value = value.toUUID();
423
395
  }
424
396
  }
425
397
 
@@ -1,4 +1,4 @@
1
- import { Binary } from '../binary';
1
+ import { Binary, validateBinaryVector } from '../binary';
2
2
  import type { BSONSymbol, DBRef, Document, MaxKey } from '../bson';
3
3
  import type { Code } from '../code';
4
4
  import * as constants from '../constants';
@@ -495,6 +495,10 @@ function serializeBinary(buffer: Uint8Array, key: string, value: Binary, index:
495
495
  index += NumberUtils.setInt32LE(buffer, index, size);
496
496
  }
497
497
 
498
+ if (value.sub_type === Binary.SUBTYPE_VECTOR) {
499
+ validateBinaryVector(value);
500
+ }
501
+
498
502
  if (size <= 16) {
499
503
  for (let i = 0; i < size; i++) buffer[index + i] = data[i];
500
504
  } else {
@@ -1,65 +1,44 @@
1
- const map = new WeakMap<object, string>();
1
+ const TypedArrayPrototypeGetSymbolToStringTag = (() => {
2
+ // Type check system lovingly referenced from:
3
+ // https://github.com/nodejs/node/blob/7450332339ed40481f470df2a3014e2ec355d8d8/lib/internal/util/types.js#L13-L15
4
+ // eslint-disable-next-line @typescript-eslint/unbound-method -- the intention is to call this method with a bound value
5
+ const g = Object.getOwnPropertyDescriptor(
6
+ Object.getPrototypeOf(Uint8Array.prototype),
7
+ Symbol.toStringTag
8
+ )!.get!;
2
9
 
3
- const TYPES = {
4
- ArrayBuffer: '[object ArrayBuffer]',
5
- SharedArrayBuffer: '[object SharedArrayBuffer]',
6
- Uint8Array: '[object Uint8Array]',
7
- BigInt64Array: '[object BigInt64Array]',
8
- BigUint64Array: '[object BigUint64Array]',
9
- RegExp: '[object RegExp]',
10
- Map: '[object Map]',
11
- Date: '[object Date]'
12
- };
13
-
14
- /**
15
- * Retrieves the prototype.toString() of a value.
16
- * If the value is an object, it will cache the result in a WeakMap for future use.
17
- */
18
- function getPrototypeString(value: unknown): string {
19
- let str = map.get(value as object);
20
-
21
- if (!str) {
22
- str = Object.prototype.toString.call(value);
23
- if (value !== null && typeof value === 'object') {
24
- map.set(value, str);
25
- }
26
- }
27
- return str;
28
- }
29
-
30
- export function isAnyArrayBuffer(value: unknown): value is ArrayBuffer {
31
- const type = getPrototypeString(value);
32
- return type === TYPES.ArrayBuffer || type === TYPES.SharedArrayBuffer;
33
- }
10
+ return (value: unknown) => g.call(value);
11
+ })();
34
12
 
35
13
  export function isUint8Array(value: unknown): value is Uint8Array {
36
- const type = getPrototypeString(value);
37
- return type === TYPES.Uint8Array;
14
+ return TypedArrayPrototypeGetSymbolToStringTag(value) === 'Uint8Array';
38
15
  }
39
16
 
40
- export function isBigInt64Array(value: unknown): value is BigInt64Array {
41
- const type = getPrototypeString(value);
42
- return type === TYPES.BigInt64Array;
43
- }
44
-
45
- export function isBigUInt64Array(value: unknown): value is BigUint64Array {
46
- const type = getPrototypeString(value);
47
- return type === TYPES.BigUint64Array;
17
+ export function isAnyArrayBuffer(value: unknown): value is ArrayBuffer {
18
+ return (
19
+ typeof value === 'object' &&
20
+ value != null &&
21
+ Symbol.toStringTag in value &&
22
+ (value[Symbol.toStringTag] === 'ArrayBuffer' ||
23
+ value[Symbol.toStringTag] === 'SharedArrayBuffer')
24
+ );
48
25
  }
49
26
 
50
- export function isRegExp(d: unknown): d is RegExp {
51
- const type = getPrototypeString(d);
52
- return type === TYPES.RegExp;
27
+ export function isRegExp(regexp: unknown): regexp is RegExp {
28
+ return regexp instanceof RegExp || Object.prototype.toString.call(regexp) === '[object RegExp]';
53
29
  }
54
30
 
55
- export function isMap(d: unknown): d is Map<unknown, unknown> {
56
- const type = getPrototypeString(d);
57
- return type === TYPES.Map;
31
+ export function isMap(value: unknown): value is Map<unknown, unknown> {
32
+ return (
33
+ typeof value === 'object' &&
34
+ value != null &&
35
+ Symbol.toStringTag in value &&
36
+ value[Symbol.toStringTag] === 'Map'
37
+ );
58
38
  }
59
39
 
60
- export function isDate(d: unknown): d is Date {
61
- const type = getPrototypeString(d);
62
- return type === TYPES.Date;
40
+ export function isDate(date: unknown): date is Date {
41
+ return date instanceof Date || Object.prototype.toString.call(date) === '[object Date]';
63
42
  }
64
43
 
65
44
  export type InspectFn = (x: unknown, options?: unknown) => string;
@@ -39,6 +39,8 @@ export type ByteUtils = {
39
39
  encodeUTF8Into: (destination: Uint8Array, source: string, byteOffset: number) => number;
40
40
  /** Generate a Uint8Array filled with random bytes with byteLength */
41
41
  randomBytes: (byteLength: number) => Uint8Array;
42
+ /** Interprets `buffer` as an array of 32-bit values and swaps the byte order in-place. */
43
+ swap32: (buffer: Uint8Array) => Uint8Array;
42
44
  };
43
45
 
44
46
  declare const Buffer: { new (): unknown; prototype?: { _isBuffer?: boolean } } | undefined;
@@ -9,6 +9,7 @@ type NodeJsBuffer = ArrayBufferView &
9
9
  copy(target: Uint8Array, targetStart: number, sourceStart: number, sourceEnd: number): number;
10
10
  toString: (this: Uint8Array, encoding: NodeJsEncoding, start?: number, end?: number) => string;
11
11
  equals: (this: Uint8Array, other: Uint8Array) => boolean;
12
+ swap32: (this: NodeJsBuffer) => NodeJsBuffer;
12
13
  };
13
14
  type NodeJsBufferConstructor = Omit<Uint8ArrayConstructor, 'from'> & {
14
15
  alloc: (size: number) => NodeJsBuffer;
@@ -83,7 +84,7 @@ export const nodeJsByteUtils = {
83
84
  return Buffer.from(potentialBuffer);
84
85
  }
85
86
 
86
- throw new BSONError(`Cannot create Buffer from ${String(potentialBuffer)}`);
87
+ throw new BSONError(`Cannot create Buffer from the passed potentialBuffer.`);
87
88
  },
88
89
 
89
90
  allocate(size: number): NodeJsBuffer {
@@ -159,5 +160,9 @@ export const nodeJsByteUtils = {
159
160
  return nodeJsByteUtils.toLocalBufferType(buffer).write(source, byteOffset, undefined, 'utf8');
160
161
  },
161
162
 
162
- randomBytes: nodejsRandomBytes
163
+ randomBytes: nodejsRandomBytes,
164
+
165
+ swap32(buffer: Uint8Array): NodeJsBuffer {
166
+ return nodeJsByteUtils.toLocalBufferType(buffer).swap32();
167
+ }
163
168
  };
@@ -13,6 +13,8 @@ const isBigEndian = FLOAT_BYTES[7] === 0;
13
13
  * A collection of functions that get or set various numeric types and bit widths from a Uint8Array.
14
14
  */
15
15
  export type NumberUtils = {
16
+ /** Is true if the current system is big endian. */
17
+ isBigEndian: boolean;
16
18
  /**
17
19
  * Parses a signed int32 at offset. Throws a `RangeError` if value is negative.
18
20
  */
@@ -35,6 +37,8 @@ export type NumberUtils = {
35
37
  * @public
36
38
  */
37
39
  export const NumberUtils: NumberUtils = {
40
+ isBigEndian,
41
+
38
42
  getNonnegativeInt32LE(source: Uint8Array, offset: number): number {
39
43
  if (source[offset + 3] > 127) {
40
44
  throw new RangeError(`Size cannot be negative at offset: ${offset}`);