bson 7.1.0 → 7.2.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.1.0",
17
+ "version": "7.2.0",
18
18
  "author": {
19
19
  "name": "The MongoDB NodeJS Team",
20
20
  "email": "dbx-node@mongodb.com"
package/src/bson.ts CHANGED
@@ -22,7 +22,12 @@ export type { CodeExtended } from './code';
22
22
  export type { DBRefLike } from './db_ref';
23
23
  export type { Decimal128Extended } from './decimal128';
24
24
  export type { DoubleExtended } from './double';
25
- export type { EJSONOptions } from './extended_json';
25
+ export type {
26
+ EJSONOptions,
27
+ EJSONOptionsBase,
28
+ EJSONSerializeOptions,
29
+ EJSONParseOptions
30
+ } from './extended_json';
26
31
  export type { Int32Extended } from './int_32';
27
32
  export type { LongExtended } from './long';
28
33
  export type { MaxKeyExtended } from './max_key';
@@ -24,7 +24,7 @@ import { BSONSymbol } from './symbol';
24
24
  import { Timestamp } from './timestamp';
25
25
 
26
26
  /** @public */
27
- export type EJSONOptions = {
27
+ export type EJSONOptionsBase = {
28
28
  /**
29
29
  * Output using the Extended JSON v1 spec
30
30
  * @defaultValue `false`
@@ -32,8 +32,22 @@ export type EJSONOptions = {
32
32
  legacy?: boolean;
33
33
  /**
34
34
  * Enable Extended JSON's `relaxed` mode, which attempts to return native JS types where possible, rather than BSON types
35
- * @defaultValue `false` */
35
+ * @defaultValue `false`
36
+ */
36
37
  relaxed?: boolean;
38
+ };
39
+
40
+ /** @public */
41
+ export type EJSONSerializeOptions = EJSONOptionsBase & {
42
+ /**
43
+ * Omits undefined values from the output instead of converting them to null
44
+ * @defaultValue `false`
45
+ */
46
+ ignoreUndefined?: boolean;
47
+ };
48
+
49
+ /** @public */
50
+ export type EJSONParseOptions = EJSONOptionsBase & {
37
51
  /**
38
52
  * Enable native bigint support
39
53
  * @defaultValue `false`
@@ -41,6 +55,9 @@ export type EJSONOptions = {
41
55
  useBigInt64?: boolean;
42
56
  };
43
57
 
58
+ /** @public */
59
+ export type EJSONOptions = EJSONSerializeOptions & EJSONParseOptions;
60
+
44
61
  /** @internal */
45
62
  type BSONType =
46
63
  | Binary
@@ -174,12 +191,12 @@ function deserializeValue(value: any, options: EJSONOptions = {}) {
174
191
  return value;
175
192
  }
176
193
 
177
- type EJSONSerializeOptions = EJSONOptions & {
194
+ type EJSONSerializeInternalOptions = EJSONSerializeOptions & {
178
195
  seenObjects: { obj: unknown; propertyName: string }[];
179
196
  };
180
197
 
181
198
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
182
- function serializeArray(array: any[], options: EJSONSerializeOptions): any[] {
199
+ function serializeArray(array: any[], options: EJSONSerializeInternalOptions): any[] {
183
200
  return array.map((v: unknown, index: number) => {
184
201
  options.seenObjects.push({ propertyName: `index ${index}`, obj: null });
185
202
  try {
@@ -197,7 +214,7 @@ function getISOString(date: Date) {
197
214
  }
198
215
 
199
216
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
200
- function serializeValue(value: any, options: EJSONSerializeOptions): any {
217
+ function serializeValue(value: any, options: EJSONSerializeInternalOptions): any {
201
218
  if (value instanceof Map || isMap(value)) {
202
219
  const obj: Record<string, unknown> = Object.create(null);
203
220
  for (const [k, v] of value) {
@@ -242,7 +259,7 @@ function serializeValue(value: any, options: EJSONSerializeOptions): any {
242
259
 
243
260
  if (Array.isArray(value)) return serializeArray(value, options);
244
261
 
245
- if (value === undefined) return null;
262
+ if (value === undefined) return options.ignoreUndefined ? undefined : null;
246
263
 
247
264
  if (value instanceof Date || isDate(value)) {
248
265
  const dateNum = value.getTime(),
@@ -326,7 +343,7 @@ const BSON_TYPE_MAPPINGS = {
326
343
  } as const;
327
344
 
328
345
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
329
- function serializeDocument(doc: any, options: EJSONSerializeOptions) {
346
+ function serializeDocument(doc: any, options: EJSONSerializeInternalOptions) {
330
347
  if (doc == null || typeof doc !== 'object') throw new BSONError('not an object instance');
331
348
 
332
349
  const bsontype: BSONType['_bsontype'] = doc._bsontype;
@@ -410,7 +427,7 @@ function serializeDocument(doc: any, options: EJSONSerializeOptions) {
410
427
  * ```
411
428
  */
412
429
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
413
- function parse(text: string, options?: EJSONOptions): any {
430
+ function parse(text: string, options?: EJSONParseOptions): any {
414
431
  const ejsonOptions = {
415
432
  useBigInt64: options?.useBigInt64 ?? false,
416
433
  relaxed: options?.relaxed ?? true,
@@ -452,10 +469,13 @@ function parse(text: string, options?: EJSONOptions): any {
452
469
  function stringify(
453
470
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
454
471
  value: any,
455
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
456
- replacer?: (number | string)[] | ((this: any, key: string, value: any) => any) | EJSONOptions,
472
+ replacer?:
473
+ | (number | string)[]
474
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
475
+ | ((this: any, key: string, value: any) => any)
476
+ | EJSONSerializeOptions,
457
477
  space?: string | number,
458
- options?: EJSONOptions
478
+ options?: EJSONSerializeOptions
459
479
  ): string {
460
480
  if (space != null && typeof space === 'object') {
461
481
  options = space;
@@ -481,7 +501,7 @@ function stringify(
481
501
  * @param options - Optional settings passed to the `stringify` function
482
502
  */
483
503
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
484
- function EJSONserialize(value: any, options?: EJSONOptions): Document {
504
+ function EJSONserialize(value: any, options?: EJSONSerializeOptions): Document {
485
505
  options = options || {};
486
506
  return JSON.parse(stringify(value, options));
487
507
  }
@@ -493,7 +513,7 @@ function EJSONserialize(value: any, options?: EJSONOptions): Document {
493
513
  * @param options - Optional settings passed to the parse method
494
514
  */
495
515
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
496
- function EJSONdeserialize(ejson: Document, options?: EJSONOptions): any {
516
+ function EJSONdeserialize(ejson: Document, options?: EJSONParseOptions): any {
497
517
  options = options || {};
498
518
  return parse(JSON.stringify(ejson), options);
499
519
  }
@@ -1,3 +1,5 @@
1
+ import { ByteUtils } from '../../utils/byte_utils';
2
+ import { NumberUtils } from '../../utils/number_utils';
1
3
  import { type BSONElement, parseToElements } from './parse_to_elements';
2
4
  /**
3
5
  * @experimental
@@ -9,6 +11,10 @@ export type OnDemand = {
9
11
  parseToElements: (this: void, bytes: Uint8Array, startOffset?: number) => Iterable<BSONElement>;
10
12
  // Types
11
13
  BSONElement: BSONElement;
14
+
15
+ // Utils
16
+ ByteUtils: ByteUtils;
17
+ NumberUtils: NumberUtils;
12
18
  };
13
19
 
14
20
  /**
@@ -18,6 +24,8 @@ export type OnDemand = {
18
24
  const onDemand: OnDemand = Object.create(null);
19
25
 
20
26
  onDemand.parseToElements = parseToElements;
27
+ onDemand.ByteUtils = ByteUtils;
28
+ onDemand.NumberUtils = NumberUtils;
21
29
 
22
30
  Object.freeze(onDemand);
23
31
 
@@ -21,6 +21,14 @@ export type ByteUtils = {
21
21
  compare: (buffer1: Uint8Array, buffer2: Uint8Array) => -1 | 0 | 1;
22
22
  /** Concatenating all the Uint8Arrays in new Uint8Array. */
23
23
  concat: (list: Uint8Array[]) => Uint8Array;
24
+ /** Copy bytes from source Uint8Array to target Uint8Array */
25
+ copy: (
26
+ source: Uint8Array,
27
+ target: Uint8Array,
28
+ targetStart?: number,
29
+ sourceStart?: number,
30
+ sourceEnd?: number
31
+ ) => number;
24
32
  /** Check if two Uint8Arrays are deep equal */
25
33
  equals: (a: Uint8Array, b: Uint8Array) => boolean;
26
34
  /** Create a Uint8Array from an array of numbers */
@@ -104,6 +104,18 @@ export const nodeJsByteUtils = {
104
104
  return Buffer.concat(list);
105
105
  },
106
106
 
107
+ copy(
108
+ source: Uint8Array,
109
+ target: Uint8Array,
110
+ targetStart?: number,
111
+ sourceStart?: number,
112
+ sourceEnd?: number
113
+ ): number {
114
+ return nodeJsByteUtils
115
+ .toLocalBufferType(source)
116
+ .copy(target, targetStart ?? 0, sourceStart ?? 0, sourceEnd ?? source.length);
117
+ },
118
+
107
119
  equals(a: Uint8Array, b: Uint8Array): boolean {
108
120
  return nodeJsByteUtils.toLocalBufferType(a).equals(b);
109
121
  },
@@ -155,6 +155,49 @@ export const webByteUtils = {
155
155
  return result;
156
156
  },
157
157
 
158
+ copy(
159
+ source: Uint8Array,
160
+ target: Uint8Array,
161
+ targetStart?: number,
162
+ sourceStart?: number,
163
+ sourceEnd?: number
164
+ ): number {
165
+ // validate and standardize passed-in sourceEnd
166
+ if (sourceEnd !== undefined && sourceEnd < 0) {
167
+ throw new RangeError(
168
+ `The value of "sourceEnd" is out of range. It must be >= 0. Received ${sourceEnd}`
169
+ );
170
+ }
171
+ sourceEnd = sourceEnd ?? source.length;
172
+
173
+ // validate and standardize passed-in sourceStart
174
+ if (sourceStart !== undefined && (sourceStart < 0 || sourceStart > sourceEnd)) {
175
+ throw new RangeError(
176
+ `The value of "sourceStart" is out of range. It must be >= 0 and <= ${sourceEnd}. Received ${sourceStart}`
177
+ );
178
+ }
179
+ sourceStart = sourceStart ?? 0;
180
+
181
+ // validate and standardize passed-in targetStart
182
+ if (targetStart !== undefined && targetStart < 0) {
183
+ throw new RangeError(
184
+ `The value of "targetStart" is out of range. It must be >= 0. Received ${targetStart}`
185
+ );
186
+ }
187
+ targetStart = targetStart ?? 0;
188
+
189
+ // figure out how many bytes we can copy
190
+ const srcSlice = source.subarray(sourceStart, sourceEnd);
191
+ const maxLen = Math.min(srcSlice.length, target.length - targetStart);
192
+ if (maxLen <= 0) {
193
+ return 0;
194
+ }
195
+
196
+ // perform the copy
197
+ target.set(srcSlice.subarray(0, maxLen), targetStart);
198
+ return maxLen;
199
+ },
200
+
158
201
  equals(uint8Array: Uint8Array, otherUint8Array: Uint8Array): boolean {
159
202
  if (uint8Array.byteLength !== otherUint8Array.byteLength) {
160
203
  return false;