bson 7.2.0 → 7.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": "7.2.0",
17
+ "version": "7.3.0",
18
18
  "author": {
19
19
  "name": "The MongoDB NodeJS Team",
20
20
  "email": "dbx-node@mongodb.com"
package/src/binary.ts CHANGED
@@ -134,7 +134,7 @@ export class Binary extends BSONValue {
134
134
  throw new BSONError('Binary can only be constructed from Uint8Array or number[]');
135
135
  }
136
136
 
137
- this.sub_type = subType ?? Binary.BSON_BINARY_SUBTYPE_DEFAULT;
137
+ this.sub_type = (subType ?? Binary.BSON_BINARY_SUBTYPE_DEFAULT) & 0xff;
138
138
 
139
139
  if (buffer == null) {
140
140
  // create an empty binary buffer
@@ -279,7 +279,7 @@ export class Binary extends BSONValue {
279
279
  }
280
280
 
281
281
  throw new BSONError(
282
- `Binary sub_type "${this.sub_type}" is not supported for converting to UUID. Only "${Binary.SUBTYPE_UUID}" is currently supported.`
282
+ `Binary sub_type "${this.sub_type}" (${typeof this.sub_type}) is not supported for converting to UUID. Only 0x${Binary.SUBTYPE_UUID.toString(16).padStart(2, '0')} is currently supported.`
283
283
  );
284
284
  }
285
285
 
package/src/bson.ts CHANGED
@@ -117,7 +117,6 @@ export function serialize(object: Document, options: SerializeOptions = {}): Uin
117
117
  object,
118
118
  checkKeys,
119
119
  0,
120
- 0,
121
120
  serializeFunctions,
122
121
  ignoreUndefined,
123
122
  null
@@ -161,7 +160,6 @@ export function serializeWithBufferAndIndex(
161
160
  object,
162
161
  checkKeys,
163
162
  0,
164
- 0,
165
163
  serializeFunctions,
166
164
  ignoreUndefined,
167
165
  null
@@ -443,6 +443,7 @@ function parse(text: string, options?: EJSONParseOptions): any {
443
443
  });
444
444
  }
445
445
 
446
+ /* eslint-disable @typescript-eslint/no-explicit-any */
446
447
  /**
447
448
  * Converts a BSON document to an Extended JSON string, optionally replacing values if a replacer
448
449
  * function is specified or optionally including only the specified properties if a replacer array
@@ -464,34 +465,54 @@ function parse(text: string, options?: EJSONParseOptions): any {
464
465
  *
465
466
  * // prints '{"int32":10}'
466
467
  * console.log(EJSON.stringify(doc));
468
+ *
469
+ * // prints '{"int32":{"$numberInt":"10"}}' with 2 space indentation
470
+ * console.log(EJSON.stringify(doc, { relaxed: false }, 2));
467
471
  * ```
468
472
  */
469
473
  function stringify(
470
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
471
474
  value: any,
472
- replacer?:
475
+ replacer?: (number | string)[] | ((this: any, key: string, value: any) => any) | null,
476
+ space?: string | number,
477
+ options?: EJSONSerializeOptions
478
+ ): string;
479
+ function stringify(
480
+ value: any,
481
+ replacer?: (number | string)[] | ((this: any, key: string, value: any) => any) | null,
482
+ options?: EJSONSerializeOptions
483
+ ): string;
484
+ function stringify(value: any, options?: EJSONSerializeOptions, space?: string | number): string;
485
+ function stringify(
486
+ value: any,
487
+ replacerOrOptions?:
473
488
  | (number | string)[]
474
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
475
489
  | ((this: any, key: string, value: any) => any)
490
+ | null
476
491
  | EJSONSerializeOptions,
477
- space?: string | number,
492
+ spaceOrOptions?: string | number | EJSONSerializeOptions,
478
493
  options?: EJSONSerializeOptions
479
494
  ): string {
480
- if (space != null && typeof space === 'object') {
481
- options = space;
482
- space = 0;
495
+ /* eslint-enable @typescript-eslint/no-explicit-any */
496
+
497
+ if (spaceOrOptions != null && typeof spaceOrOptions === 'object') {
498
+ options = spaceOrOptions;
499
+ spaceOrOptions = undefined;
483
500
  }
484
- if (replacer != null && typeof replacer === 'object' && !Array.isArray(replacer)) {
485
- options = replacer;
486
- replacer = undefined;
487
- space = 0;
501
+ if (
502
+ replacerOrOptions != null &&
503
+ typeof replacerOrOptions === 'object' &&
504
+ !Array.isArray(replacerOrOptions)
505
+ ) {
506
+ options = replacerOrOptions;
507
+ replacerOrOptions = undefined;
488
508
  }
509
+
489
510
  const serializeOptions = Object.assign({ relaxed: true, legacy: false }, options, {
490
511
  seenObjects: [{ propertyName: '(root)', obj: null }]
491
512
  });
492
513
 
493
514
  const doc = serializeValue(value, serializeOptions);
494
- return JSON.stringify(doc, replacer as Parameters<JSON['stringify']>[1], space);
515
+ return JSON.stringify(doc, replacerOrOptions as Parameters<JSON['stringify']>[1], spaceOrOptions);
495
516
  }
496
517
 
497
518
  /**
package/src/objectid.ts CHANGED
@@ -4,9 +4,6 @@ import { type InspectFn, defaultInspect } from './parser/utils';
4
4
  import { ByteUtils } from './utils/byte_utils';
5
5
  import { NumberUtils } from './utils/number_utils';
6
6
 
7
- // Unique sequence for the current process (initialized on first use)
8
- let PROCESS_UNIQUE: Uint8Array | null = null;
9
-
10
7
  /** ObjectId hexString cache @internal */
11
8
  const __idCache = new WeakMap(); // TODO(NODE-6549): convert this to #__id private field when target updated to ES2022
12
9
 
@@ -33,7 +30,28 @@ export class ObjectId extends BSONValue {
33
30
  }
34
31
 
35
32
  /** @internal */
36
- private static index = Math.floor(Math.random() * 0xffffff);
33
+ private static index = 0;
34
+
35
+ /** Unique sequence for the current process (initialized on first use)
36
+ * @internal
37
+ */
38
+ private static PROCESS_UNIQUE: Uint8Array | null = null;
39
+
40
+ /** @internal */
41
+ private static resetState = (): void => {
42
+ this.index = Math.floor(Math.random() * 0x1000000);
43
+ this.PROCESS_UNIQUE = ByteUtils.randomBytes(5);
44
+ };
45
+
46
+ static {
47
+ this.resetState();
48
+ // https://nodejs.org/api/v8.html#startup-snapshot-api
49
+ // @ts-expect-error Node.js types not present since this is an optional API
50
+ const { startupSnapshot } = globalThis?.process?.getBuiltinModule('v8') ?? {};
51
+ if (startupSnapshot?.isBuildingSnapshot()) {
52
+ startupSnapshot?.addDeserializeCallback(this.resetState);
53
+ }
54
+ }
37
55
 
38
56
  static cacheHexString: boolean;
39
57
 
@@ -178,7 +196,7 @@ export class ObjectId extends BSONValue {
178
196
  * @internal
179
197
  */
180
198
  private static getInc(): number {
181
- return (ObjectId.index = (ObjectId.index + 1) % 0xffffff);
199
+ return (ObjectId.index = (ObjectId.index + 1) % 0x1000000);
182
200
  }
183
201
 
184
202
  /**
@@ -197,12 +215,8 @@ export class ObjectId extends BSONValue {
197
215
  // 4-byte timestamp
198
216
  NumberUtils.setInt32BE(buffer, 0, time);
199
217
 
200
- // set PROCESS_UNIQUE if yet not initialized
201
- if (PROCESS_UNIQUE === null) {
202
- PROCESS_UNIQUE = ByteUtils.randomBytes(5);
203
- }
204
-
205
218
  // 5-byte process unique
219
+ const PROCESS_UNIQUE = this.PROCESS_UNIQUE!;
206
220
  buffer[4] = PROCESS_UNIQUE[0];
207
221
  buffer[5] = PROCESS_UNIQUE[1];
208
222
  buffer[6] = PROCESS_UNIQUE[2];
@@ -10,43 +10,64 @@ export function internalCalculateObjectSize(
10
10
  serializeFunctions?: boolean,
11
11
  ignoreUndefined?: boolean
12
12
  ): number {
13
- let totalLength = 4 + 1;
13
+ // Each stack entry carries its own ignoreUndefined so DBRef fields can force ignoreUndefined=true
14
+ // regardless of the caller's setting, matching the behavior of serializeInto.
15
+ const objectStack: Array<{ obj: Document; ignoreUndefined: boolean }> = [
16
+ { obj: object, ignoreUndefined: ignoreUndefined ?? false }
17
+ ];
18
+ let total = 0;
14
19
 
15
- if (Array.isArray(object)) {
16
- for (let i = 0; i < object.length; i++) {
17
- totalLength += calculateElement(
18
- i.toString(),
19
- object[i],
20
- serializeFunctions,
21
- true,
22
- ignoreUndefined
23
- );
24
- }
25
- } else {
26
- // If we have toBSON defined, override the current object
20
+ while (objectStack.length > 0) {
21
+ const { obj, ignoreUndefined: frameIgnoreUndefined } = objectStack.pop()!;
22
+ total += 5; // 4-byte size field + null terminator
27
23
 
28
- if (typeof object?.toBSON === 'function') {
29
- object = object.toBSON();
24
+ const isObjArray = Array.isArray(obj);
25
+ let target = obj;
26
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
27
+ if (!isObjArray && typeof (obj as any)?.toBSON === 'function') {
28
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
+ target = (obj as any).toBSON();
30
30
  }
31
31
 
32
- // Calculate size
33
- for (const key of Object.keys(object)) {
34
- totalLength += calculateElement(key, object[key], serializeFunctions, false, ignoreUndefined);
32
+ if (isObjArray) {
33
+ const array = target as unknown[];
34
+ for (let i = 0; i < array.length; i++) {
35
+ total += calculateElementSize(
36
+ i.toString(),
37
+ array[i],
38
+ serializeFunctions,
39
+ true,
40
+ frameIgnoreUndefined,
41
+ objectStack
42
+ );
43
+ }
44
+ } else {
45
+ for (const key of Object.keys(target)) {
46
+ total += calculateElementSize(
47
+ key,
48
+ target[key],
49
+ serializeFunctions,
50
+ false,
51
+ frameIgnoreUndefined,
52
+ objectStack
53
+ );
54
+ }
35
55
  }
36
56
  }
37
57
 
38
- return totalLength;
58
+ return total;
39
59
  }
40
60
 
41
61
  /** @internal */
42
- function calculateElement(
62
+ function calculateElementSize(
43
63
  name: string,
44
64
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
45
65
  value: any,
46
66
  serializeFunctions = false,
47
67
  isArray = false,
48
- ignoreUndefined = false
49
- ) {
68
+ ignoreUndefined = false,
69
+ objectStack: Array<{ obj: Document; ignoreUndefined: boolean }>
70
+ ): number {
50
71
  // If we have toBSON defined, override the current object
51
72
  if (typeof value?.toBSON === 'function') {
52
73
  value = value.toBSON();
@@ -63,20 +84,19 @@ function calculateElement(
63
84
  ) {
64
85
  if (value >= constants.BSON_INT32_MIN && value <= constants.BSON_INT32_MAX) {
65
86
  // 32 bit
66
- return (name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) + (4 + 1);
87
+ return ByteUtils.utf8ByteLength(name) + 1 + (4 + 1);
67
88
  } else {
68
- return (name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) + (8 + 1);
89
+ return ByteUtils.utf8ByteLength(name) + 1 + (8 + 1);
69
90
  }
70
91
  } else {
71
92
  // 64 bit
72
- return (name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) + (8 + 1);
93
+ return ByteUtils.utf8ByteLength(name) + 1 + (8 + 1);
73
94
  }
74
95
  case 'undefined':
75
- if (isArray || !ignoreUndefined)
76
- return (name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) + 1;
96
+ if (isArray || !ignoreUndefined) return ByteUtils.utf8ByteLength(name) + 1 + 1;
77
97
  return 0;
78
98
  case 'boolean':
79
- return (name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) + (1 + 1);
99
+ return ByteUtils.utf8ByteLength(name) + 1 + (1 + 1);
80
100
  case 'object':
81
101
  if (
82
102
  value != null &&
@@ -85,42 +105,42 @@ function calculateElement(
85
105
  ) {
86
106
  throw new BSONVersionError();
87
107
  } else if (value == null || value._bsontype === 'MinKey' || value._bsontype === 'MaxKey') {
88
- return (name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) + 1;
108
+ return ByteUtils.utf8ByteLength(name) + 1 + 1;
89
109
  } else if (value._bsontype === 'ObjectId') {
90
- return (name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) + (12 + 1);
110
+ return ByteUtils.utf8ByteLength(name) + 1 + (12 + 1);
91
111
  } else if (value instanceof Date || isDate(value)) {
92
- return (name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) + (8 + 1);
112
+ return ByteUtils.utf8ByteLength(name) + 1 + (8 + 1);
93
113
  } else if (
94
114
  ArrayBuffer.isView(value) ||
95
115
  value instanceof ArrayBuffer ||
96
116
  isAnyArrayBuffer(value)
97
117
  ) {
98
- return (
99
- (name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) + (1 + 4 + 1) + value.byteLength
100
- );
118
+ return ByteUtils.utf8ByteLength(name) + 1 + (1 + 4 + 1) + value.byteLength;
101
119
  } else if (
102
120
  value._bsontype === 'Long' ||
103
121
  value._bsontype === 'Double' ||
104
122
  value._bsontype === 'Timestamp'
105
123
  ) {
106
- return (name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) + (8 + 1);
124
+ return ByteUtils.utf8ByteLength(name) + 1 + (8 + 1);
107
125
  } else if (value._bsontype === 'Decimal128') {
108
- return (name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) + (16 + 1);
126
+ return ByteUtils.utf8ByteLength(name) + 1 + (16 + 1);
109
127
  } else if (value._bsontype === 'Code') {
110
128
  // Calculate size depending on the availability of a scope
111
129
  if (value.scope != null && Object.keys(value.scope).length > 0) {
130
+ objectStack.push({ obj: value.scope, ignoreUndefined });
112
131
  return (
113
- (name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) +
132
+ ByteUtils.utf8ByteLength(name) +
133
+ 1 +
114
134
  1 +
115
135
  4 +
116
136
  4 +
117
137
  ByteUtils.utf8ByteLength(value.code.toString()) +
118
- 1 +
119
- internalCalculateObjectSize(value.scope, serializeFunctions, ignoreUndefined)
138
+ 1
120
139
  );
121
140
  } else {
122
141
  return (
123
- (name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) +
142
+ ByteUtils.utf8ByteLength(name) +
143
+ 1 +
124
144
  1 +
125
145
  4 +
126
146
  ByteUtils.utf8ByteLength(value.code.toString()) +
@@ -131,22 +151,13 @@ function calculateElement(
131
151
  const binary: Binary = value;
132
152
  // Check what kind of subtype we have
133
153
  if (binary.sub_type === Binary.SUBTYPE_BYTE_ARRAY) {
134
- return (
135
- (name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) +
136
- (binary.position + 1 + 4 + 1 + 4)
137
- );
154
+ return ByteUtils.utf8ByteLength(name) + 1 + (binary.position + 1 + 4 + 1 + 4);
138
155
  } else {
139
- return (
140
- (name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) + (binary.position + 1 + 4 + 1)
141
- );
156
+ return ByteUtils.utf8ByteLength(name) + 1 + (binary.position + 1 + 4 + 1);
142
157
  }
143
158
  } else if (value._bsontype === 'Symbol') {
144
159
  return (
145
- (name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) +
146
- ByteUtils.utf8ByteLength(value.value) +
147
- 4 +
148
- 1 +
149
- 1
160
+ ByteUtils.utf8ByteLength(name) + 1 + ByteUtils.utf8ByteLength(value.value) + 4 + 1 + 1
150
161
  );
151
162
  } else if (value._bsontype === 'DBRef') {
152
163
  // Set up correct object for serialization
@@ -163,14 +174,13 @@ function calculateElement(
163
174
  ordered_values['$db'] = value.db;
164
175
  }
165
176
 
166
- return (
167
- (name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) +
168
- 1 +
169
- internalCalculateObjectSize(ordered_values, serializeFunctions, ignoreUndefined)
170
- );
177
+ // DBRef fields always use ignoreUndefined=true to match serializeInto behavior.
178
+ objectStack.push({ obj: ordered_values, ignoreUndefined: true });
179
+ return ByteUtils.utf8ByteLength(name) + 1 + 1;
171
180
  } else if (value instanceof RegExp || isRegExp(value)) {
172
181
  return (
173
- (name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) +
182
+ ByteUtils.utf8ByteLength(name) +
183
+ 1 +
174
184
  1 +
175
185
  ByteUtils.utf8ByteLength(value.source) +
176
186
  1 +
@@ -181,7 +191,8 @@ function calculateElement(
181
191
  );
182
192
  } else if (value._bsontype === 'BSONRegExp') {
183
193
  return (
184
- (name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) +
194
+ ByteUtils.utf8ByteLength(name) +
195
+ 1 +
185
196
  1 +
186
197
  ByteUtils.utf8ByteLength(value.pattern) +
187
198
  1 +
@@ -189,16 +200,14 @@ function calculateElement(
189
200
  1
190
201
  );
191
202
  } else {
192
- return (
193
- (name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) +
194
- internalCalculateObjectSize(value, serializeFunctions, ignoreUndefined) +
195
- 1
196
- );
203
+ objectStack.push({ obj: value, ignoreUndefined });
204
+ return ByteUtils.utf8ByteLength(name) + 1 + 1;
197
205
  }
198
206
  case 'function':
199
207
  if (serializeFunctions) {
200
208
  return (
201
- (name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) +
209
+ ByteUtils.utf8ByteLength(name) +
210
+ 1 +
202
211
  1 +
203
212
  4 +
204
213
  ByteUtils.utf8ByteLength(value.toString()) +
@@ -207,12 +216,10 @@ function calculateElement(
207
216
  }
208
217
  return 0;
209
218
  case 'bigint':
210
- return (name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) + (8 + 1);
219
+ return ByteUtils.utf8ByteLength(name) + 1 + (8 + 1);
211
220
  case 'symbol':
212
221
  return 0;
213
222
  default:
214
223
  throw new BSONError(`Unrecognized JS type: ${typeof value}`);
215
224
  }
216
-
217
- return 0;
218
225
  }