bson 6.2.0 → 6.3.0

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.2.0",
17
+ "version": "6.3.0",
18
18
  "author": {
19
19
  "name": "The MongoDB NodeJS Team",
20
20
  "email": "dbx-node@mongodb.com"
@@ -27,41 +27,42 @@
27
27
  },
28
28
  "devDependencies": {
29
29
  "@istanbuljs/nyc-config-typescript": "^1.0.2",
30
- "@microsoft/api-extractor": "^7.36.4",
30
+ "@microsoft/api-extractor": "^7.38.4",
31
31
  "@octokit/core": "^4.2.4",
32
- "@rollup/plugin-node-resolve": "^15.1.0",
33
- "@rollup/plugin-typescript": "^11.1.2",
34
- "@types/chai": "^4.3.5",
35
- "@types/mocha": "^10.0.1",
36
- "@types/node": "^18.17.3",
37
- "@types/sinon": "^10.0.16",
38
- "@types/sinon-chai": "^3.2.9",
32
+ "@rollup/plugin-node-resolve": "^15.2.3",
33
+ "@rollup/plugin-typescript": "^11.1.5",
34
+ "@types/chai": "^4.3.11",
35
+ "@types/mocha": "^10.0.6",
36
+ "@types/node": "^18.19.2",
37
+ "@types/sinon": "^10.0.20",
38
+ "@types/sinon-chai": "^3.2.12",
39
39
  "@typescript-eslint/eslint-plugin": "^5.62.0",
40
40
  "@typescript-eslint/parser": "^5.62.0",
41
41
  "benchmark": "^2.1.4",
42
- "chai": "^4.3.7",
42
+ "chai": "^4.3.10",
43
43
  "chalk": "^5.3.0",
44
- "eslint": "^8.46.0",
44
+ "dbx-js-tools": "github:mongodb-js/dbx-js-tools",
45
+ "eslint": "^8.55.0",
45
46
  "eslint-config-prettier": "^8.10.0",
46
47
  "eslint-plugin-no-bigint-usage": "file:etc/eslint/no-bigint-usage",
47
48
  "eslint-plugin-prettier": "^4.2.1",
48
49
  "eslint-plugin-tsdoc": "^0.2.17",
49
- "magic-string": "^0.30.2",
50
+ "magic-string": "^0.30.5",
50
51
  "mocha": "10.2.0",
51
52
  "node-fetch": "^3.3.2",
52
53
  "nyc": "^15.1.0",
53
54
  "prettier": "^2.8.8",
54
- "rollup": "^3.27.2",
55
+ "rollup": "^3.29.4",
55
56
  "sinon": "^15.2.0",
56
57
  "sinon-chai": "^3.7.0",
57
58
  "source-map-support": "^0.5.21",
58
59
  "standard-version": "^9.5.0",
59
- "tar": "^6.1.15",
60
+ "tar": "^6.2.0",
60
61
  "ts-node": "^10.9.1",
61
62
  "tsd": "^0.28.1",
62
63
  "typescript": "^5.0.4",
63
64
  "typescript-cached-transpile": "0.0.6",
64
- "uuid": "^9.0.0"
65
+ "uuid": "^9.0.1"
65
66
  },
66
67
  "tsd": {
67
68
  "directory": "test/types",
@@ -103,7 +104,9 @@
103
104
  "check:tsd": "npm run build:dts && tsd",
104
105
  "check:web": "WEB=true mocha test/node",
105
106
  "check:web-no-bigint": "WEB=true NO_BIGINT=true mocha test/node",
106
- "check:bench": "cd test/bench && npx tsc && node ./lib/index.js && mv benchmarks.json ../../.",
107
+ "check:granular-bench": "npm run build:bench && node ./test/bench/etc/run_granular_benchmarks.js",
108
+ "check:spec-bench": "npm run build:bench && node ./test/bench/lib/spec/bsonBench.js",
109
+ "build:bench": "cd test/bench && npx tsc",
107
110
  "build:ts": "node ./node_modules/typescript/bin/tsc",
108
111
  "build:dts": "npm run build:ts && api-extractor run --typescript-compiler-folder node_modules/typescript --local && node etc/clean_definition_files.cjs",
109
112
  "build:bundle": "rollup -c rollup.config.mjs",
package/src/binary.ts CHANGED
@@ -191,8 +191,8 @@ export class Binary extends BSONValue {
191
191
  if (encoding === 'hex') return ByteUtils.toHex(this.buffer);
192
192
  if (encoding === 'base64') return ByteUtils.toBase64(this.buffer);
193
193
  if (encoding === 'utf8' || encoding === 'utf-8')
194
- return ByteUtils.toUTF8(this.buffer, 0, this.buffer.byteLength);
195
- return ByteUtils.toUTF8(this.buffer, 0, this.buffer.byteLength);
194
+ return ByteUtils.toUTF8(this.buffer, 0, this.buffer.byteLength, false);
195
+ return ByteUtils.toUTF8(this.buffer, 0, this.buffer.byteLength, false);
196
196
  }
197
197
 
198
198
  /** @internal */
package/src/error.ts CHANGED
@@ -4,7 +4,7 @@ import { BSON_MAJOR_VERSION } from './constants';
4
4
  * @public
5
5
  * @category Error
6
6
  *
7
- * `BSONError` objects are thrown when BSON ecounters an error.
7
+ * `BSONError` objects are thrown when BSON encounters an error.
8
8
  *
9
9
  * This is the parent class for all the other errors thrown by this library.
10
10
  */
@@ -23,8 +23,8 @@ export class BSONError extends Error {
23
23
  return 'BSONError';
24
24
  }
25
25
 
26
- constructor(message: string) {
27
- super(message);
26
+ constructor(message: string, options?: { cause?: unknown }) {
27
+ super(message, options);
28
28
  }
29
29
 
30
30
  /**
package/src/objectid.ts CHANGED
@@ -44,9 +44,48 @@ export class ObjectId extends BSONValue {
44
44
  private __id?: string;
45
45
 
46
46
  /**
47
- * Create an ObjectId type
47
+ * Create ObjectId from a number.
48
48
  *
49
- * @param inputId - Can be a 24 character hex string, 12 byte binary Buffer, or a number.
49
+ * @param inputId - A number.
50
+ * @deprecated Instead, use `static createFromTime()` to set a numeric value for the new ObjectId.
51
+ */
52
+ constructor(inputId: number);
53
+ /**
54
+ * Create ObjectId from a 24 character hex string.
55
+ *
56
+ * @param inputId - A 24 character hex string.
57
+ */
58
+ constructor(inputId: string);
59
+ /**
60
+ * Create ObjectId from the BSON ObjectId type.
61
+ *
62
+ * @param inputId - The BSON ObjectId type.
63
+ */
64
+ constructor(inputId: ObjectId);
65
+ /**
66
+ * Create ObjectId from the object type that has the toHexString method.
67
+ *
68
+ * @param inputId - The ObjectIdLike type.
69
+ */
70
+ constructor(inputId: ObjectIdLike);
71
+ /**
72
+ * Create ObjectId from a 12 byte binary Buffer.
73
+ *
74
+ * @param inputId - A 12 byte binary Buffer.
75
+ */
76
+ constructor(inputId: Uint8Array);
77
+ /** To generate a new ObjectId, use ObjectId() with no argument. */
78
+ constructor();
79
+ /**
80
+ * Implementation overload.
81
+ *
82
+ * @param inputId - All input types that are used in the constructor implementation.
83
+ */
84
+ constructor(inputId?: string | number | ObjectId | ObjectIdLike | Uint8Array);
85
+ /**
86
+ * Create a new ObjectId.
87
+ *
88
+ * @param inputId - An input value to create a new ObjectId from.
50
89
  */
51
90
  constructor(inputId?: string | number | ObjectId | ObjectIdLike | Uint8Array) {
52
91
  super();
@@ -65,7 +104,7 @@ export class ObjectId extends BSONValue {
65
104
  workingId = inputId;
66
105
  }
67
106
 
68
- // the following cases use workingId to construct an ObjectId
107
+ // The following cases use workingId to construct an ObjectId
69
108
  if (workingId == null || typeof workingId === 'number') {
70
109
  // The most common use case (blank id, new objectId instance)
71
110
  // Generate a new id
@@ -236,7 +236,7 @@ function deserializeObject(
236
236
  if (i >= buffer.byteLength) throw new BSONError('Bad BSON Document: illegal CString');
237
237
 
238
238
  // Represents the key
239
- const name = isArray ? arrayIndex++ : ByteUtils.toUTF8(buffer, index, i);
239
+ const name = isArray ? arrayIndex++ : ByteUtils.toUTF8(buffer, index, i, false);
240
240
 
241
241
  // shouldValidateKey is true if the key should be validated, false otherwise
242
242
  let shouldValidateKey = true;
@@ -266,7 +266,7 @@ function deserializeObject(
266
266
  ) {
267
267
  throw new BSONError('bad string length in bson');
268
268
  }
269
- value = getValidatedString(buffer, index, index + stringSize - 1, shouldValidateKey);
269
+ value = ByteUtils.toUTF8(buffer, index, index + stringSize - 1, shouldValidateKey);
270
270
  index = index + stringSize;
271
271
  } else if (elementType === constants.BSON_DATA_OID) {
272
272
  const oid = ByteUtils.allocate(12);
@@ -476,7 +476,7 @@ function deserializeObject(
476
476
  // If are at the end of the buffer there is a problem with the document
477
477
  if (i >= buffer.length) throw new BSONError('Bad BSON Document: illegal CString');
478
478
  // Return the C string
479
- const source = ByteUtils.toUTF8(buffer, index, i);
479
+ const source = ByteUtils.toUTF8(buffer, index, i, false);
480
480
  // Create the regexp
481
481
  index = i + 1;
482
482
 
@@ -489,7 +489,7 @@ function deserializeObject(
489
489
  // If are at the end of the buffer there is a problem with the document
490
490
  if (i >= buffer.length) throw new BSONError('Bad BSON Document: illegal CString');
491
491
  // Return the C string
492
- const regExpOptions = ByteUtils.toUTF8(buffer, index, i);
492
+ const regExpOptions = ByteUtils.toUTF8(buffer, index, i, false);
493
493
  index = i + 1;
494
494
 
495
495
  // For each option add the corresponding one for javascript
@@ -521,7 +521,7 @@ function deserializeObject(
521
521
  // If are at the end of the buffer there is a problem with the document
522
522
  if (i >= buffer.length) throw new BSONError('Bad BSON Document: illegal CString');
523
523
  // Return the C string
524
- const source = ByteUtils.toUTF8(buffer, index, i);
524
+ const source = ByteUtils.toUTF8(buffer, index, i, false);
525
525
  index = i + 1;
526
526
 
527
527
  // Get the start search index
@@ -533,7 +533,7 @@ function deserializeObject(
533
533
  // If are at the end of the buffer there is a problem with the document
534
534
  if (i >= buffer.length) throw new BSONError('Bad BSON Document: illegal CString');
535
535
  // Return the C string
536
- const regExpOptions = ByteUtils.toUTF8(buffer, index, i);
536
+ const regExpOptions = ByteUtils.toUTF8(buffer, index, i, false);
537
537
  index = i + 1;
538
538
 
539
539
  // Set the object
@@ -551,7 +551,7 @@ function deserializeObject(
551
551
  ) {
552
552
  throw new BSONError('bad string length in bson');
553
553
  }
554
- const symbol = getValidatedString(buffer, index, index + stringSize - 1, shouldValidateKey);
554
+ const symbol = ByteUtils.toUTF8(buffer, index, index + stringSize - 1, shouldValidateKey);
555
555
  value = promoteValues ? symbol : new BSONSymbol(symbol);
556
556
  index = index + stringSize;
557
557
  } else if (elementType === constants.BSON_DATA_TIMESTAMP) {
@@ -587,7 +587,7 @@ function deserializeObject(
587
587
  ) {
588
588
  throw new BSONError('bad string length in bson');
589
589
  }
590
- const functionString = getValidatedString(
590
+ const functionString = ByteUtils.toUTF8(
591
591
  buffer,
592
592
  index,
593
593
  index + stringSize - 1,
@@ -626,7 +626,7 @@ function deserializeObject(
626
626
  }
627
627
 
628
628
  // Javascript function
629
- const functionString = getValidatedString(
629
+ const functionString = ByteUtils.toUTF8(
630
630
  buffer,
631
631
  index,
632
632
  index + stringSize - 1,
@@ -678,7 +678,7 @@ function deserializeObject(
678
678
  throw new BSONError('Invalid UTF-8 string in BSON document');
679
679
  }
680
680
  }
681
- const namespace = ByteUtils.toUTF8(buffer, index, index + stringSize - 1);
681
+ const namespace = ByteUtils.toUTF8(buffer, index, index + stringSize - 1, false);
682
682
  // Update parse index position
683
683
  index = index + stringSize;
684
684
 
@@ -728,24 +728,3 @@ function deserializeObject(
728
728
 
729
729
  return object;
730
730
  }
731
-
732
- function getValidatedString(
733
- buffer: Uint8Array,
734
- start: number,
735
- end: number,
736
- shouldValidateUtf8: boolean
737
- ) {
738
- const value = ByteUtils.toUTF8(buffer, start, end);
739
- // if utf8 validation is on, do the check
740
- if (shouldValidateUtf8) {
741
- for (let i = 0; i < value.length; i++) {
742
- if (value.charCodeAt(i) === 0xfffd) {
743
- if (!validateUtf8(buffer, start, end)) {
744
- throw new BSONError('Invalid UTF-8 string in BSON document');
745
- }
746
- break;
747
- }
748
- }
749
- }
750
- return value;
751
- }
@@ -25,8 +25,8 @@ export type ByteUtils = {
25
25
  toHex: (buffer: Uint8Array) => string;
26
26
  /** Create a Uint8Array containing utf8 code units from a string */
27
27
  fromUTF8: (text: string) => Uint8Array;
28
- /** Create a string from utf8 code units */
29
- toUTF8: (buffer: Uint8Array, start: number, end: number) => string;
28
+ /** Create a string from utf8 code units, fatal=true will throw an error if UTF-8 bytes are invalid, fatal=false will insert replacement characters */
29
+ toUTF8: (buffer: Uint8Array, start: number, end: number, fatal: boolean) => string;
30
30
  /** Get the utf8 code unit count from a string if it were to be transformed to utf8 */
31
31
  utf8ByteLength: (input: string) => number;
32
32
  /** Encode UTF8 bytes generated from `source` string into `destination` at byteOffset. Returns the number of bytes encoded. */
@@ -0,0 +1,61 @@
1
+ /**
2
+ * This function is an optimization for small basic latin strings.
3
+ * @internal
4
+ * @remarks
5
+ * ### Important characteristics:
6
+ * - If the uint8array or distance between start and end is 0 this function returns an empty string
7
+ * - If the byteLength of the string is 1, 2, or 3 we invoke String.fromCharCode and manually offset into the buffer
8
+ * - If the byteLength of the string is less than or equal to 20 an array of bytes is built and `String.fromCharCode.apply` is called with the result
9
+ * - If any byte exceeds 128 this function returns null
10
+ *
11
+ * @param uint8array - A sequence of bytes that may contain basic latin characters
12
+ * @param start - The start index from which to search the uint8array
13
+ * @param end - The index to stop searching the uint8array
14
+ * @returns string if all bytes are within the basic latin range, otherwise null
15
+ */
16
+ export function tryLatin(uint8array: Uint8Array, start: number, end: number): string | null {
17
+ if (uint8array.length === 0) {
18
+ return '';
19
+ }
20
+
21
+ const stringByteLength = end - start;
22
+ if (stringByteLength === 0) {
23
+ return '';
24
+ }
25
+
26
+ if (stringByteLength > 20) {
27
+ return null;
28
+ }
29
+
30
+ if (stringByteLength === 1 && uint8array[start] < 128) {
31
+ return String.fromCharCode(uint8array[start]);
32
+ }
33
+
34
+ if (stringByteLength === 2 && uint8array[start] < 128 && uint8array[start + 1] < 128) {
35
+ return String.fromCharCode(uint8array[start]) + String.fromCharCode(uint8array[start + 1]);
36
+ }
37
+
38
+ if (
39
+ stringByteLength === 3 &&
40
+ uint8array[start] < 128 &&
41
+ uint8array[start + 1] < 128 &&
42
+ uint8array[start + 2] < 128
43
+ ) {
44
+ return (
45
+ String.fromCharCode(uint8array[start]) +
46
+ String.fromCharCode(uint8array[start + 1]) +
47
+ String.fromCharCode(uint8array[start + 2])
48
+ );
49
+ }
50
+
51
+ const latinBytes = [];
52
+ for (let i = start; i < end; i++) {
53
+ const byte = uint8array[i];
54
+ if (byte > 127) {
55
+ return null;
56
+ }
57
+ latinBytes.push(byte);
58
+ }
59
+
60
+ return String.fromCharCode(...latinBytes);
61
+ }
@@ -1,4 +1,6 @@
1
1
  import { BSONError } from '../error';
2
+ import { validateUtf8 } from '../validate_utf8';
3
+ import { tryLatin } from './latin';
2
4
 
3
5
  type NodeJsEncoding = 'base64' | 'hex' | 'utf8' | 'binary';
4
6
  type NodeJsBuffer = ArrayBufferView &
@@ -125,8 +127,25 @@ export const nodeJsByteUtils = {
125
127
  return Buffer.from(text, 'utf8');
126
128
  },
127
129
 
128
- toUTF8(buffer: Uint8Array, start: number, end: number): string {
129
- return nodeJsByteUtils.toLocalBufferType(buffer).toString('utf8', start, end);
130
+ toUTF8(buffer: Uint8Array, start: number, end: number, fatal: boolean): string {
131
+ const basicLatin = end - start <= 20 ? tryLatin(buffer, start, end) : null;
132
+ if (basicLatin != null) {
133
+ return basicLatin;
134
+ }
135
+
136
+ const string = nodeJsByteUtils.toLocalBufferType(buffer).toString('utf8', start, end);
137
+ if (fatal) {
138
+ // TODO(NODE-4930): Insufficiently strict BSON UTF8 validation
139
+ for (let i = 0; i < string.length; i++) {
140
+ if (string.charCodeAt(i) === 0xfffd) {
141
+ if (!validateUtf8(buffer, start, end)) {
142
+ throw new BSONError('Invalid UTF-8 string in BSON document');
143
+ }
144
+ break;
145
+ }
146
+ }
147
+ }
148
+ return string;
130
149
  },
131
150
 
132
151
  utf8ByteLength(input: string): number {
@@ -1,4 +1,5 @@
1
1
  import { BSONError } from '../error';
2
+ import { tryLatin } from './latin';
2
3
 
3
4
  type TextDecoder = {
4
5
  readonly encoding: string;
@@ -172,8 +173,20 @@ export const webByteUtils = {
172
173
  return new TextEncoder().encode(text);
173
174
  },
174
175
 
175
- toUTF8(uint8array: Uint8Array, start: number, end: number): string {
176
- return new TextDecoder('utf8', { fatal: false }).decode(uint8array.slice(start, end));
176
+ toUTF8(uint8array: Uint8Array, start: number, end: number, fatal: boolean): string {
177
+ const basicLatin = end - start <= 20 ? tryLatin(uint8array, start, end) : null;
178
+ if (basicLatin != null) {
179
+ return basicLatin;
180
+ }
181
+
182
+ if (fatal) {
183
+ try {
184
+ return new TextDecoder('utf8', { fatal }).decode(uint8array.slice(start, end));
185
+ } catch (cause) {
186
+ throw new BSONError('Invalid UTF-8 string in BSON document', { cause });
187
+ }
188
+ }
189
+ return new TextDecoder('utf8', { fatal }).decode(uint8array.slice(start, end));
177
190
  },
178
191
 
179
192
  utf8ByteLength(input: string): number {