bson 6.4.0 → 6.6.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.4.0",
17
+ "version": "6.6.0",
18
18
  "author": {
19
19
  "name": "The MongoDB NodeJS Team",
20
20
  "email": "dbx-node@mongodb.com"
package/src/binary.ts CHANGED
@@ -56,6 +56,8 @@ export class Binary extends BSONValue {
56
56
  static readonly SUBTYPE_ENCRYPTED = 6;
57
57
  /** Column BSON type */
58
58
  static readonly SUBTYPE_COLUMN = 7;
59
+ /** Sensitive BSON type */
60
+ static readonly SUBTYPE_SENSITIVE = 8;
59
61
  /** User BSON type */
60
62
  static readonly SUBTYPE_USER_DEFINED = 128;
61
63
 
@@ -184,15 +186,15 @@ export class Binary extends BSONValue {
184
186
  }
185
187
 
186
188
  toJSON(): string {
187
- return ByteUtils.toBase64(this.buffer);
189
+ return ByteUtils.toBase64(this.buffer.subarray(0, this.position));
188
190
  }
189
191
 
190
192
  toString(encoding?: 'hex' | 'base64' | 'utf8' | 'utf-8'): string {
191
- if (encoding === 'hex') return ByteUtils.toHex(this.buffer);
192
- if (encoding === 'base64') return ByteUtils.toBase64(this.buffer);
193
+ if (encoding === 'hex') return ByteUtils.toHex(this.buffer.subarray(0, this.position));
194
+ if (encoding === 'base64') return ByteUtils.toBase64(this.buffer.subarray(0, this.position));
193
195
  if (encoding === 'utf8' || encoding === 'utf-8')
194
- return ByteUtils.toUTF8(this.buffer, 0, this.buffer.byteLength, false);
195
- return ByteUtils.toUTF8(this.buffer, 0, this.buffer.byteLength, false);
196
+ return ByteUtils.toUTF8(this.buffer, 0, this.position, false);
197
+ return ByteUtils.toUTF8(this.buffer, 0, this.position, false);
196
198
  }
197
199
 
198
200
  /** @internal */
package/src/bson.ts CHANGED
@@ -51,9 +51,10 @@ export {
51
51
  Decimal128
52
52
  };
53
53
  export { BSONValue } from './bson_value';
54
- export { BSONError, BSONVersionError, BSONRuntimeError } from './error';
54
+ export { BSONError, BSONVersionError, BSONRuntimeError, BSONOffsetError } from './error';
55
55
  export { BSONType } from './constants';
56
56
  export { EJSON } from './extended_json';
57
+ export { onDemand, type OnDemand } from './parser/on_demand/index';
57
58
 
58
59
  /** @public */
59
60
  export interface Document {
package/src/constants.ts CHANGED
@@ -109,6 +109,9 @@ export const BSON_BINARY_SUBTYPE_ENCRYPTED = 6;
109
109
  /** Column BSON type @internal */
110
110
  export const BSON_BINARY_SUBTYPE_COLUMN = 7;
111
111
 
112
+ /** Sensitive BSON type @internal */
113
+ export const BSON_BINARY_SUBTYPE_SENSITIVE = 8;
114
+
112
115
  /** Binary User Defined Type @internal */
113
116
  export const BSON_BINARY_SUBTYPE_USER_DEFINED = 128;
114
117
 
package/src/error.ts CHANGED
@@ -81,3 +81,25 @@ export class BSONRuntimeError extends BSONError {
81
81
  super(message);
82
82
  }
83
83
  }
84
+
85
+ /**
86
+ * @public
87
+ * @category Error
88
+ *
89
+ * @experimental
90
+ *
91
+ * An error generated when BSON bytes are invalid.
92
+ * Reports the offset the parser was able to reach before encountering the error.
93
+ */
94
+ export class BSONOffsetError extends BSONError {
95
+ public get name(): 'BSONOffsetError' {
96
+ return 'BSONOffsetError';
97
+ }
98
+
99
+ public offset: number;
100
+
101
+ constructor(message: string, offset: number, options?: { cause?: unknown }) {
102
+ super(`${message}. offset: ${offset}`, options);
103
+ this.offset = offset;
104
+ }
105
+ }
@@ -0,0 +1,32 @@
1
+ import { ByteUtils } from '../../utils/byte_utils';
2
+ import { NumberUtils } from '../../utils/number_utils';
3
+ import { type BSONElement, parseToElements } from './parse_to_elements';
4
+ /**
5
+ * @experimental
6
+ * @public
7
+ *
8
+ * A new set of BSON APIs that are currently experimental and not intended for production use.
9
+ */
10
+ export type OnDemand = {
11
+ parseToElements: (this: void, bytes: Uint8Array, startOffset?: number) => Iterable<BSONElement>;
12
+ // Types
13
+ BSONElement: BSONElement;
14
+
15
+ // Utils
16
+ ByteUtils: ByteUtils;
17
+ NumberUtils: NumberUtils;
18
+ };
19
+
20
+ /**
21
+ * @experimental
22
+ * @public
23
+ */
24
+ const onDemand: OnDemand = Object.create(null);
25
+
26
+ onDemand.parseToElements = parseToElements;
27
+ onDemand.ByteUtils = ByteUtils;
28
+ onDemand.NumberUtils = NumberUtils;
29
+
30
+ Object.freeze(onDemand);
31
+
32
+ export { onDemand };
@@ -0,0 +1,188 @@
1
+ import { BSONOffsetError } from '../../error';
2
+ import { NumberUtils } from '../../utils/number_utils';
3
+
4
+ /**
5
+ * @internal
6
+ *
7
+ * @remarks
8
+ * - This enum is const so the code we produce will inline the numbers
9
+ * - `minKey` is set to 255 so unsigned comparisons succeed
10
+ * - Modify with caution, double check the bundle contains literals
11
+ */
12
+ const enum BSONElementType {
13
+ double = 1,
14
+ string = 2,
15
+ object = 3,
16
+ array = 4,
17
+ binData = 5,
18
+ undefined = 6,
19
+ objectId = 7,
20
+ bool = 8,
21
+ date = 9,
22
+ null = 10,
23
+ regex = 11,
24
+ dbPointer = 12,
25
+ javascript = 13,
26
+ symbol = 14,
27
+ javascriptWithScope = 15,
28
+ int = 16,
29
+ timestamp = 17,
30
+ long = 18,
31
+ decimal = 19,
32
+ minKey = 255,
33
+ maxKey = 127
34
+ }
35
+
36
+ /**
37
+ * @public
38
+ * @experimental
39
+ */
40
+ export type BSONElement = [
41
+ type: number,
42
+ nameOffset: number,
43
+ nameLength: number,
44
+ offset: number,
45
+ length: number
46
+ ];
47
+
48
+ function getSize(source: Uint8Array, offset: number) {
49
+ try {
50
+ return NumberUtils.getNonnegativeInt32LE(source, offset);
51
+ } catch (cause) {
52
+ throw new BSONOffsetError('BSON size cannot be negative', offset, { cause });
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Searches for null terminator of a BSON element's value (Never the document null terminator)
58
+ * **Does not** bounds check since this should **ONLY** be used within parseToElements which has asserted that `bytes` ends with a `0x00`.
59
+ * So this will at most iterate to the document's terminator and error if that is the offset reached.
60
+ */
61
+ function findNull(bytes: Uint8Array, offset: number): number {
62
+ let nullTerminatorOffset = offset;
63
+
64
+ for (; bytes[nullTerminatorOffset] !== 0x00; nullTerminatorOffset++);
65
+
66
+ if (nullTerminatorOffset === bytes.length - 1) {
67
+ // We reached the null terminator of the document, not a value's
68
+ throw new BSONOffsetError('Null terminator not found', offset);
69
+ }
70
+
71
+ return nullTerminatorOffset;
72
+ }
73
+
74
+ /**
75
+ * @public
76
+ * @experimental
77
+ */
78
+ export function parseToElements(
79
+ bytes: Uint8Array,
80
+ startOffset: number | null = 0
81
+ ): Iterable<BSONElement> {
82
+ startOffset ??= 0;
83
+
84
+ if (bytes.length < 5) {
85
+ throw new BSONOffsetError(
86
+ `Input must be at least 5 bytes, got ${bytes.length} bytes`,
87
+ startOffset
88
+ );
89
+ }
90
+
91
+ const documentSize = getSize(bytes, startOffset);
92
+
93
+ if (documentSize > bytes.length - startOffset) {
94
+ throw new BSONOffsetError(
95
+ `Parsed documentSize (${documentSize} bytes) does not match input length (${bytes.length} bytes)`,
96
+ startOffset
97
+ );
98
+ }
99
+
100
+ if (bytes[startOffset + documentSize - 1] !== 0x00) {
101
+ throw new BSONOffsetError('BSON documents must end in 0x00', startOffset + documentSize);
102
+ }
103
+
104
+ const elements: BSONElement[] = [];
105
+ let offset = startOffset + 4;
106
+
107
+ while (offset <= documentSize + startOffset) {
108
+ const type = bytes[offset];
109
+ offset += 1;
110
+
111
+ if (type === 0) {
112
+ if (offset - startOffset !== documentSize) {
113
+ throw new BSONOffsetError(`Invalid 0x00 type byte`, offset);
114
+ }
115
+ break;
116
+ }
117
+
118
+ const nameOffset = offset;
119
+ const nameLength = findNull(bytes, offset) - nameOffset;
120
+ offset += nameLength + 1;
121
+
122
+ let length: number;
123
+
124
+ if (
125
+ type === BSONElementType.double ||
126
+ type === BSONElementType.long ||
127
+ type === BSONElementType.date ||
128
+ type === BSONElementType.timestamp
129
+ ) {
130
+ length = 8;
131
+ } else if (type === BSONElementType.int) {
132
+ length = 4;
133
+ } else if (type === BSONElementType.objectId) {
134
+ length = 12;
135
+ } else if (type === BSONElementType.decimal) {
136
+ length = 16;
137
+ } else if (type === BSONElementType.bool) {
138
+ length = 1;
139
+ } else if (
140
+ type === BSONElementType.null ||
141
+ type === BSONElementType.undefined ||
142
+ type === BSONElementType.maxKey ||
143
+ type === BSONElementType.minKey
144
+ ) {
145
+ length = 0;
146
+ }
147
+ // Needs a size calculation
148
+ else if (type === BSONElementType.regex) {
149
+ length = findNull(bytes, findNull(bytes, offset) + 1) + 1 - offset;
150
+ } else if (
151
+ type === BSONElementType.object ||
152
+ type === BSONElementType.array ||
153
+ type === BSONElementType.javascriptWithScope
154
+ ) {
155
+ length = getSize(bytes, offset);
156
+ } else if (
157
+ type === BSONElementType.string ||
158
+ type === BSONElementType.binData ||
159
+ type === BSONElementType.dbPointer ||
160
+ type === BSONElementType.javascript ||
161
+ type === BSONElementType.symbol
162
+ ) {
163
+ length = getSize(bytes, offset) + 4;
164
+ if (type === BSONElementType.binData) {
165
+ // binary subtype
166
+ length += 1;
167
+ }
168
+ if (type === BSONElementType.dbPointer) {
169
+ // dbPointer's objectId
170
+ length += 12;
171
+ }
172
+ } else {
173
+ throw new BSONOffsetError(
174
+ `Invalid 0x${type.toString(16).padStart(2, '0')} type byte`,
175
+ offset
176
+ );
177
+ }
178
+
179
+ if (length > documentSize) {
180
+ throw new BSONOffsetError('value reports length larger than document', offset);
181
+ }
182
+
183
+ elements.push([type, nameOffset, nameLength, offset, length]);
184
+ offset += length;
185
+ }
186
+
187
+ return elements;
188
+ }
@@ -1,10 +1,16 @@
1
1
  import { nodeJsByteUtils } from './node_byte_utils';
2
2
  import { webByteUtils } from './web_byte_utils';
3
3
 
4
- /** @internal */
4
+ /**
5
+ * @public
6
+ * @experimental
7
+ *
8
+ * A collection of functions that help work with data in a Uint8Array.
9
+ * ByteUtils is configured at load time to use Node.js or Web based APIs for the internal implementations.
10
+ */
5
11
  export type ByteUtils = {
6
12
  /** Transforms the input to an instance of Buffer if running on node, otherwise Uint8Array */
7
- toLocalBufferType(buffer: Uint8Array | ArrayBufferView | ArrayBuffer): Uint8Array;
13
+ toLocalBufferType: (buffer: Uint8Array | ArrayBufferView | ArrayBuffer) => Uint8Array;
8
14
  /** Create empty space of size */
9
15
  allocate: (size: number) => Uint8Array;
10
16
  /** Create empty space of size, use pooled memory when available */
@@ -30,9 +36,9 @@ export type ByteUtils = {
30
36
  /** Get the utf8 code unit count from a string if it were to be transformed to utf8 */
31
37
  utf8ByteLength: (input: string) => number;
32
38
  /** Encode UTF8 bytes generated from `source` string into `destination` at byteOffset. Returns the number of bytes encoded. */
33
- encodeUTF8Into(destination: Uint8Array, source: string, byteOffset: number): number;
39
+ encodeUTF8Into: (destination: Uint8Array, source: string, byteOffset: number) => number;
34
40
  /** Generate a Uint8Array filled with random bytes with byteLength */
35
- randomBytes(byteLength: number): Uint8Array;
41
+ randomBytes: (byteLength: number) => Uint8Array;
36
42
  };
37
43
 
38
44
  declare const Buffer: { new (): unknown; prototype?: { _isBuffer?: boolean } } | undefined;
@@ -1,12 +1,52 @@
1
1
  const FLOAT = new Float64Array(1);
2
2
  const FLOAT_BYTES = new Uint8Array(FLOAT.buffer, 0, 8);
3
3
 
4
+ FLOAT[0] = -1;
5
+ // Little endian [0, 0, 0, 0, 0, 0, 240, 191]
6
+ // Big endian [191, 240, 0, 0, 0, 0, 0, 0]
7
+ const isBigEndian = FLOAT_BYTES[7] === 0;
8
+
9
+ /**
10
+ * @experimental
11
+ * @public
12
+ *
13
+ * A collection of functions that get or set various numeric types and bit widths from a Uint8Array.
14
+ */
15
+ export type NumberUtils = {
16
+ /**
17
+ * Parses a signed int32 at offset. Throws a `RangeError` if value is negative.
18
+ */
19
+ getNonnegativeInt32LE: (source: Uint8Array, offset: number) => number;
20
+ getInt32LE: (source: Uint8Array, offset: number) => number;
21
+ getUint32LE: (source: Uint8Array, offset: number) => number;
22
+ getUint32BE: (source: Uint8Array, offset: number) => number;
23
+ getBigInt64LE: (source: Uint8Array, offset: number) => bigint;
24
+ getFloat64LE: (source: Uint8Array, offset: number) => number;
25
+ setInt32BE: (destination: Uint8Array, offset: number, value: number) => 4;
26
+ setInt32LE: (destination: Uint8Array, offset: number, value: number) => 4;
27
+ setBigInt64LE: (destination: Uint8Array, offset: number, value: bigint) => 8;
28
+ setFloat64LE: (destination: Uint8Array, offset: number, value: number) => 8;
29
+ };
30
+
4
31
  /**
5
32
  * Number parsing and serializing utilities.
6
33
  *
7
- * @internal
34
+ * @experimental
35
+ * @public
8
36
  */
9
- export const NumberUtils = {
37
+ export const NumberUtils: NumberUtils = {
38
+ getNonnegativeInt32LE(source: Uint8Array, offset: number): number {
39
+ if (source[offset + 3] > 127) {
40
+ throw new RangeError(`Size cannot be negative at offset: ${offset}`);
41
+ }
42
+ return (
43
+ source[offset] |
44
+ (source[offset + 1] << 8) |
45
+ (source[offset + 2] << 16) |
46
+ (source[offset + 3] << 24)
47
+ );
48
+ },
49
+
10
50
  /** Reads a little-endian 32-bit integer from source */
11
51
  getInt32LE(source: Uint8Array, offset: number): number {
12
52
  return (
@@ -50,17 +90,29 @@ export const NumberUtils = {
50
90
  },
51
91
 
52
92
  /** Reads a little-endian 64-bit float from source */
53
- getFloat64LE(source: Uint8Array, offset: number): number {
54
- FLOAT_BYTES[0] = source[offset];
55
- FLOAT_BYTES[1] = source[offset + 1];
56
- FLOAT_BYTES[2] = source[offset + 2];
57
- FLOAT_BYTES[3] = source[offset + 3];
58
- FLOAT_BYTES[4] = source[offset + 4];
59
- FLOAT_BYTES[5] = source[offset + 5];
60
- FLOAT_BYTES[6] = source[offset + 6];
61
- FLOAT_BYTES[7] = source[offset + 7];
62
- return FLOAT[0];
63
- },
93
+ getFloat64LE: isBigEndian
94
+ ? (source: Uint8Array, offset: number) => {
95
+ FLOAT_BYTES[7] = source[offset];
96
+ FLOAT_BYTES[6] = source[offset + 1];
97
+ FLOAT_BYTES[5] = source[offset + 2];
98
+ FLOAT_BYTES[4] = source[offset + 3];
99
+ FLOAT_BYTES[3] = source[offset + 4];
100
+ FLOAT_BYTES[2] = source[offset + 5];
101
+ FLOAT_BYTES[1] = source[offset + 6];
102
+ FLOAT_BYTES[0] = source[offset + 7];
103
+ return FLOAT[0];
104
+ }
105
+ : (source: Uint8Array, offset: number) => {
106
+ FLOAT_BYTES[0] = source[offset];
107
+ FLOAT_BYTES[1] = source[offset + 1];
108
+ FLOAT_BYTES[2] = source[offset + 2];
109
+ FLOAT_BYTES[3] = source[offset + 3];
110
+ FLOAT_BYTES[4] = source[offset + 4];
111
+ FLOAT_BYTES[5] = source[offset + 5];
112
+ FLOAT_BYTES[6] = source[offset + 6];
113
+ FLOAT_BYTES[7] = source[offset + 7];
114
+ return FLOAT[0];
115
+ },
64
116
 
65
117
  /** Writes a big-endian 32-bit integer to destination, can be signed or unsigned */
66
118
  setInt32BE(destination: Uint8Array, offset: number, value: number): 4 {
@@ -120,16 +172,29 @@ export const NumberUtils = {
120
172
  },
121
173
 
122
174
  /** Writes a little-endian 64-bit float to destination */
123
- setFloat64LE(destination: Uint8Array, offset: number, value: number): 8 {
124
- FLOAT[0] = value;
125
- destination[offset] = FLOAT_BYTES[0];
126
- destination[offset + 1] = FLOAT_BYTES[1];
127
- destination[offset + 2] = FLOAT_BYTES[2];
128
- destination[offset + 3] = FLOAT_BYTES[3];
129
- destination[offset + 4] = FLOAT_BYTES[4];
130
- destination[offset + 5] = FLOAT_BYTES[5];
131
- destination[offset + 6] = FLOAT_BYTES[6];
132
- destination[offset + 7] = FLOAT_BYTES[7];
133
- return 8;
134
- }
175
+ setFloat64LE: isBigEndian
176
+ ? (destination: Uint8Array, offset: number, value: number) => {
177
+ FLOAT[0] = value;
178
+ destination[offset] = FLOAT_BYTES[7];
179
+ destination[offset + 1] = FLOAT_BYTES[6];
180
+ destination[offset + 2] = FLOAT_BYTES[5];
181
+ destination[offset + 3] = FLOAT_BYTES[4];
182
+ destination[offset + 4] = FLOAT_BYTES[3];
183
+ destination[offset + 5] = FLOAT_BYTES[2];
184
+ destination[offset + 6] = FLOAT_BYTES[1];
185
+ destination[offset + 7] = FLOAT_BYTES[0];
186
+ return 8;
187
+ }
188
+ : (destination: Uint8Array, offset: number, value: number) => {
189
+ FLOAT[0] = value;
190
+ destination[offset] = FLOAT_BYTES[0];
191
+ destination[offset + 1] = FLOAT_BYTES[1];
192
+ destination[offset + 2] = FLOAT_BYTES[2];
193
+ destination[offset + 3] = FLOAT_BYTES[3];
194
+ destination[offset + 4] = FLOAT_BYTES[4];
195
+ destination[offset + 5] = FLOAT_BYTES[5];
196
+ destination[offset + 6] = FLOAT_BYTES[6];
197
+ destination[offset + 7] = FLOAT_BYTES[7];
198
+ return 8;
199
+ }
135
200
  };