bson 7.2.0 → 7.3.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.
@@ -1,8 +1,8 @@
1
1
  import { Binary, validateBinaryVector } from '../binary';
2
2
  import type { BSONSymbol, DBRef, Document, MaxKey } from '../bson';
3
+ import { bsonType } from '../bson_value';
3
4
  import type { Code } from '../code';
4
5
  import * as constants from '../constants';
5
- import type { DBRefLike } from '../db_ref';
6
6
  import type { Decimal128 } from '../decimal128';
7
7
  import type { Double } from '../double';
8
8
  import { BSONError, BSONVersionError } from '../error';
@@ -45,12 +45,6 @@ export interface SerializeOptions {
45
45
  const regexp = /\x00/; // eslint-disable-line no-control-regex
46
46
  const ignoreKeys = new Set(['$db', '$ref', '$id', '$clusterTime']);
47
47
 
48
- /*
49
- * isArray indicates if we are writing to a BSON array (type 0x04)
50
- * which forces the "key" which really an array index as a string to be written as ascii
51
- * This will catch any errors in index as a string generation
52
- */
53
-
54
48
  function serializeString(buffer: Uint8Array, key: string, value: string, index: number) {
55
49
  // Encode String type
56
50
  buffer[index++] = constants.BSON_DATA_STRING;
@@ -213,7 +207,7 @@ function serializeMinMax(buffer: Uint8Array, key: string, value: MinKey | MaxKey
213
207
  // Write the type of either min or max key
214
208
  if (value === null) {
215
209
  buffer[index++] = constants.BSON_DATA_NULL;
216
- } else if (value._bsontype === 'MinKey') {
210
+ } else if (value[bsonType] === 'MinKey') {
217
211
  buffer[index++] = constants.BSON_DATA_MIN_KEY;
218
212
  } else {
219
213
  buffer[index++] = constants.BSON_DATA_MAX_KEY;
@@ -268,46 +262,6 @@ function serializeBuffer(buffer: Uint8Array, key: string, value: Uint8Array, ind
268
262
  return index;
269
263
  }
270
264
 
271
- function serializeObject(
272
- buffer: Uint8Array,
273
- key: string,
274
- value: Document,
275
- index: number,
276
- checkKeys: boolean,
277
- depth: number,
278
- serializeFunctions: boolean,
279
- ignoreUndefined: boolean,
280
- path: Set<Document>
281
- ) {
282
- if (path.has(value)) {
283
- throw new BSONError('Cannot convert circular structure to BSON');
284
- }
285
-
286
- path.add(value);
287
-
288
- // Write the type
289
- buffer[index++] = Array.isArray(value) ? constants.BSON_DATA_ARRAY : constants.BSON_DATA_OBJECT;
290
- // Number of written bytes
291
- const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index);
292
- // Encode the name
293
- index = index + numberOfWrittenBytes;
294
- buffer[index++] = 0;
295
- const endIndex = serializeInto(
296
- buffer,
297
- value,
298
- checkKeys,
299
- index,
300
- depth + 1,
301
- serializeFunctions,
302
- ignoreUndefined,
303
- path
304
- );
305
-
306
- path.delete(value);
307
-
308
- return endIndex;
309
- }
310
-
311
265
  function serializeDecimal128(buffer: Uint8Array, key: string, value: Decimal128, index: number) {
312
266
  buffer[index++] = constants.BSON_DATA_DECIMAL128;
313
267
  // Number of written bytes
@@ -323,7 +277,7 @@ function serializeDecimal128(buffer: Uint8Array, key: string, value: Decimal128,
323
277
  function serializeLong(buffer: Uint8Array, key: string, value: Long, index: number) {
324
278
  // Write the type
325
279
  buffer[index++] =
326
- value._bsontype === 'Long' ? constants.BSON_DATA_LONG : constants.BSON_DATA_TIMESTAMP;
280
+ value[bsonType] === 'Long' ? constants.BSON_DATA_LONG : constants.BSON_DATA_TIMESTAMP;
327
281
  // Number of written bytes
328
282
  const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index);
329
283
  // Encode the name
@@ -391,85 +345,6 @@ function serializeFunction(buffer: Uint8Array, key: string, value: Function, ind
391
345
  return index;
392
346
  }
393
347
 
394
- function serializeCode(
395
- buffer: Uint8Array,
396
- key: string,
397
- value: Code,
398
- index: number,
399
- checkKeys = false,
400
- depth = 0,
401
- serializeFunctions = false,
402
- ignoreUndefined = true,
403
- path: Set<Document>
404
- ) {
405
- if (value.scope && typeof value.scope === 'object') {
406
- // Write the type
407
- buffer[index++] = constants.BSON_DATA_CODE_W_SCOPE;
408
- // Number of written bytes
409
- const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index);
410
- // Encode the name
411
- index = index + numberOfWrittenBytes;
412
- buffer[index++] = 0;
413
-
414
- // Starting index
415
- let startIndex = index;
416
-
417
- // Serialize the function
418
- // Get the function string
419
- const functionString = value.code;
420
- // Index adjustment
421
- index = index + 4;
422
- // Write string into buffer
423
- const codeSize = ByteUtils.encodeUTF8Into(buffer, functionString, index + 4) + 1;
424
- // Write the size of the string to buffer
425
- NumberUtils.setInt32LE(buffer, index, codeSize);
426
- // Write end 0
427
- buffer[index + 4 + codeSize - 1] = 0;
428
- // Write the
429
- index = index + codeSize + 4;
430
-
431
- // Serialize the scope value
432
- const endIndex = serializeInto(
433
- buffer,
434
- value.scope,
435
- checkKeys,
436
- index,
437
- depth + 1,
438
- serializeFunctions,
439
- ignoreUndefined,
440
- path
441
- );
442
- index = endIndex - 1;
443
-
444
- // Writ the total
445
- const totalSize = endIndex - startIndex;
446
-
447
- // Write the total size of the object
448
- startIndex += NumberUtils.setInt32LE(buffer, startIndex, totalSize);
449
- // Write trailing zero
450
- buffer[index++] = 0;
451
- } else {
452
- buffer[index++] = constants.BSON_DATA_CODE;
453
- // Number of written bytes
454
- const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index);
455
- // Encode the name
456
- index = index + numberOfWrittenBytes;
457
- buffer[index++] = 0;
458
- // Function string
459
- const functionString = value.code.toString();
460
- // Write the string
461
- const size = ByteUtils.encodeUTF8Into(buffer, functionString, index + 4) + 1;
462
- // Write the size of the string to buffer
463
- NumberUtils.setInt32LE(buffer, index, size);
464
- // Update index
465
- index = index + 4 + size - 1;
466
- // Write zero
467
- buffer[index++] = 0;
468
- }
469
-
470
- return index;
471
- }
472
-
473
348
  function serializeBinary(buffer: Uint8Array, key: string, value: Binary, index: number) {
474
349
  // Write the type
475
350
  buffer[index++] = constants.BSON_DATA_BINARY;
@@ -528,52 +403,98 @@ function serializeSymbol(buffer: Uint8Array, key: string, value: BSONSymbol, ind
528
403
  return index;
529
404
  }
530
405
 
531
- function serializeDBRef(
532
- buffer: Uint8Array,
533
- key: string,
534
- value: DBRef,
535
- index: number,
536
- depth: number,
537
- serializeFunctions: boolean,
538
- path: Set<Document>
539
- ) {
540
- // Write the type
541
- buffer[index++] = constants.BSON_DATA_OBJECT;
542
- // Number of written bytes
543
- const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index);
544
-
545
- // Encode the name
546
- index = index + numberOfWrittenBytes;
547
- buffer[index++] = 0;
548
-
549
- let startIndex = index;
550
- let output: DBRefLike = {
551
- $ref: value.collection || value.namespace, // "namespace" was what library 1.x called "collection"
552
- $id: value.oid
553
- };
406
+ interface SerializationFrame {
407
+ /** Original object passed to this frame — used for cycle-detection (path.delete). */
408
+ sourceObject: Document;
409
+ /** True when serializing a BSON array; affects key format and type byte. */
410
+ isArray: boolean;
411
+ /** Buffer offset where this object's 4-byte size field will be written. */
412
+ objectSizeIndex: number;
413
+ /** Buffer offset for code-with-scope wrapper size, or null if not applicable. */
414
+ codeSizeIndex: number | null;
415
+ /**
416
+ * Object being iterated. For plain objects this may be the toBSON() result of
417
+ * sourceObject; for arrays and Maps it equals sourceObject.
418
+ */
419
+ iterTarget: Document;
420
+ /**
421
+ * Pre-computed Object.keys() of iterTarget for plain objects.
422
+ * Null for arrays (use keyIndex as numeric index) and Maps (use mapIterator).
423
+ */
424
+ keys: string[] | null;
425
+ /** Next index into keys[] for plain objects, or next array index for arrays. */
426
+ keyIndex: number;
427
+ /** Active iterator for Map objects; null for arrays and plain objects. */
428
+ mapIterator: IterableIterator<[unknown, unknown]> | null;
429
+ /** The enclosing frame, or null at the root. The stack is a linked list via this field. */
430
+ prev: SerializationFrame | null;
431
+ /** Whether to validate keys for this frame's document (may differ from caller, e.g. DBRef uses false). */
432
+ checkKeys: boolean;
433
+ /** Whether undefined values are skipped (may differ from caller, e.g. DBRef uses true). */
434
+ ignoreUndefined: boolean;
435
+ }
554
436
 
555
- if (value.db != null) {
556
- output.$db = value.db;
437
+ function makeFrame(
438
+ sourceObject: Document,
439
+ objectSizeIndex: number,
440
+ codeSizeIndex: number | null,
441
+ prev: SerializationFrame | null,
442
+ checkKeys: boolean,
443
+ ignoreUndefined: boolean
444
+ ): SerializationFrame {
445
+ if (Array.isArray(sourceObject)) {
446
+ return {
447
+ sourceObject,
448
+ isArray: true,
449
+ objectSizeIndex,
450
+ codeSizeIndex,
451
+ iterTarget: sourceObject,
452
+ keys: null,
453
+ keyIndex: 0,
454
+ mapIterator: null,
455
+ prev,
456
+ checkKeys,
457
+ ignoreUndefined
458
+ };
557
459
  }
558
-
559
- output = Object.assign(output, value.fields);
560
- const endIndex = serializeInto(
561
- buffer,
562
- output,
563
- false,
564
- index,
565
- depth + 1,
566
- serializeFunctions,
567
- true,
568
- path
569
- );
570
-
571
- // Calculate object size
572
- const size = endIndex - startIndex;
573
- // Write the size
574
- startIndex += NumberUtils.setInt32LE(buffer, index, size);
575
- // Set index
576
- return endIndex;
460
+ if (sourceObject instanceof Map || isMap(sourceObject)) {
461
+ return {
462
+ sourceObject,
463
+ isArray: false,
464
+ objectSizeIndex,
465
+ codeSizeIndex,
466
+ iterTarget: sourceObject,
467
+ keys: null,
468
+ keyIndex: 0,
469
+ mapIterator: (sourceObject as Map<unknown, unknown>).entries(),
470
+ prev,
471
+ checkKeys,
472
+ ignoreUndefined
473
+ };
474
+ }
475
+ // Plain object: call toBSON() if defined to obtain the object to iterate.
476
+ let target: Document = sourceObject;
477
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
478
+ if (typeof (target as any)?.toBSON === 'function') {
479
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
480
+ target = (target as any).toBSON() as Document;
481
+ if (target != null && typeof target !== 'object') {
482
+ throw new BSONError('toBSON function did not return an object');
483
+ }
484
+ }
485
+ return {
486
+ sourceObject,
487
+ isArray: false,
488
+ objectSizeIndex,
489
+ codeSizeIndex,
490
+ iterTarget: target,
491
+ keys: Object.keys(target as object),
492
+ keyIndex: 0,
493
+ mapIterator: null,
494
+ prev,
495
+ checkKeys,
496
+ ignoreUndefined
497
+ };
577
498
  }
578
499
 
579
500
  export function serializeInto(
@@ -581,7 +502,6 @@ export function serializeInto(
581
502
  object: Document,
582
503
  checkKeys: boolean,
583
504
  startingIndex: number,
584
- depth: number,
585
505
  serializeFunctions: boolean,
586
506
  ignoreUndefined: boolean,
587
507
  path: Set<Document> | null
@@ -619,336 +539,210 @@ export function serializeInto(
619
539
  path = new Set();
620
540
  }
621
541
 
622
- // Push the object to the path
623
542
  path.add(object);
624
543
 
625
- // Start place to serialize into
544
+ let currentFrame: SerializationFrame | null = makeFrame(
545
+ object,
546
+ startingIndex,
547
+ null,
548
+ null,
549
+ checkKeys,
550
+ ignoreUndefined
551
+ );
626
552
  let index = startingIndex + 4;
627
553
 
628
- // Special case isArray
629
- if (Array.isArray(object)) {
630
- // Get object keys
631
- for (let i = 0; i < object.length; i++) {
632
- const key = `${i}`;
633
- let value = object[i];
634
-
635
- // Is there an override value
636
- if (typeof value?.toBSON === 'function') {
637
- value = value.toBSON();
638
- }
639
-
640
- // Check the type of the value
641
- const type = typeof value;
642
-
643
- if (value === undefined) {
644
- index = serializeNull(buffer, key, value, index);
645
- } else if (value === null) {
646
- index = serializeNull(buffer, key, value, index);
647
- } else if (type === 'string') {
648
- index = serializeString(buffer, key, value, index);
649
- } else if (type === 'number') {
650
- index = serializeNumber(buffer, key, value, index);
651
- } else if (type === 'bigint') {
652
- index = serializeBigInt(buffer, key, value, index);
653
- } else if (type === 'boolean') {
654
- index = serializeBoolean(buffer, key, value, index);
655
- } else if (type === 'object' && value._bsontype == null) {
656
- if (value instanceof Date || isDate(value)) {
657
- index = serializeDate(buffer, key, value, index);
658
- } else if (value instanceof Uint8Array || isUint8Array(value)) {
659
- index = serializeBuffer(buffer, key, value, index);
660
- } else if (value instanceof RegExp || isRegExp(value)) {
661
- index = serializeRegExp(buffer, key, value, index);
662
- } else {
663
- index = serializeObject(
664
- buffer,
665
- key,
666
- value,
667
- index,
668
- checkKeys,
669
- depth,
670
- serializeFunctions,
671
- ignoreUndefined,
672
- path
673
- );
554
+ while (currentFrame !== null) {
555
+ const frame: SerializationFrame = currentFrame;
556
+
557
+ // Advance to the next key-value pair, or finalize the frame if exhausted.
558
+ let key: string;
559
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
560
+ let value: any;
561
+ if (frame.mapIterator !== null) {
562
+ const next = frame.mapIterator.next();
563
+ if (next.done) {
564
+ buffer[index++] = 0x00;
565
+ NumberUtils.setInt32LE(buffer, frame.objectSizeIndex, index - frame.objectSizeIndex);
566
+ if (frame.codeSizeIndex !== null) {
567
+ NumberUtils.setInt32LE(buffer, frame.codeSizeIndex, index - frame.codeSizeIndex);
674
568
  }
675
- } else if (type === 'object') {
676
- if (value[constants.BSON_VERSION_SYMBOL] !== constants.BSON_MAJOR_VERSION) {
677
- throw new BSONVersionError();
678
- } else if (value._bsontype === 'ObjectId') {
679
- index = serializeObjectId(buffer, key, value, index);
680
- } else if (value._bsontype === 'Decimal128') {
681
- index = serializeDecimal128(buffer, key, value, index);
682
- } else if (value._bsontype === 'Long' || value._bsontype === 'Timestamp') {
683
- index = serializeLong(buffer, key, value, index);
684
- } else if (value._bsontype === 'Double') {
685
- index = serializeDouble(buffer, key, value, index);
686
- } else if (value._bsontype === 'Code') {
687
- index = serializeCode(
688
- buffer,
689
- key,
690
- value,
691
- index,
692
- checkKeys,
693
- depth,
694
- serializeFunctions,
695
- ignoreUndefined,
696
- path
697
- );
698
- } else if (value._bsontype === 'Binary') {
699
- index = serializeBinary(buffer, key, value, index);
700
- } else if (value._bsontype === 'BSONSymbol') {
701
- index = serializeSymbol(buffer, key, value, index);
702
- } else if (value._bsontype === 'DBRef') {
703
- index = serializeDBRef(buffer, key, value, index, depth, serializeFunctions, path);
704
- } else if (value._bsontype === 'BSONRegExp') {
705
- index = serializeBSONRegExp(buffer, key, value, index);
706
- } else if (value._bsontype === 'Int32') {
707
- index = serializeInt32(buffer, key, value, index);
708
- } else if (value._bsontype === 'MinKey' || value._bsontype === 'MaxKey') {
709
- index = serializeMinMax(buffer, key, value, index);
710
- } else if (typeof value._bsontype !== 'undefined') {
711
- throw new BSONError(`Unrecognized or invalid _bsontype: ${String(value._bsontype)}`);
712
- }
713
- } else if (type === 'function' && serializeFunctions) {
714
- index = serializeFunction(buffer, key, value, index);
569
+ path.delete(frame.sourceObject);
570
+ currentFrame = frame.prev;
571
+ continue;
715
572
  }
716
- }
717
- } else if (object instanceof Map || isMap(object)) {
718
- const iterator = object.entries();
719
- let done = false;
720
-
721
- while (!done) {
722
- // Unpack the next entry
723
- const entry = iterator.next();
724
- done = !!entry.done;
725
- // Are we done, then skip and terminate
726
- if (done) continue;
727
-
728
- // Get the entry values
729
- const key = entry.value ? entry.value[0] : undefined;
730
- let value = entry.value ? entry.value[1] : undefined;
731
-
732
- if (typeof value?.toBSON === 'function') {
733
- value = value.toBSON();
734
- }
735
-
736
- // Check the type of the value
737
- const type = typeof value;
738
-
739
- // Check the key and throw error if it's illegal
740
- if (typeof key === 'string' && !ignoreKeys.has(key)) {
741
- if (key.match(regexp) != null) {
742
- // The BSON spec doesn't allow keys with null bytes because keys are
743
- // null-terminated.
744
- throw new BSONError('key ' + key + ' must not contain null bytes');
745
- }
746
-
747
- if (checkKeys) {
748
- if ('$' === key[0]) {
749
- throw new BSONError('key ' + key + " must not start with '$'");
750
- } else if (key.includes('.')) {
751
- throw new BSONError('key ' + key + " must not contain '.'");
752
- }
573
+ key = next.value[0] as string;
574
+ value = next.value[1];
575
+ } else if (frame.keys !== null) {
576
+ if (frame.keyIndex >= frame.keys.length) {
577
+ buffer[index++] = 0x00;
578
+ NumberUtils.setInt32LE(buffer, frame.objectSizeIndex, index - frame.objectSizeIndex);
579
+ if (frame.codeSizeIndex !== null) {
580
+ NumberUtils.setInt32LE(buffer, frame.codeSizeIndex, index - frame.codeSizeIndex);
753
581
  }
582
+ path.delete(frame.sourceObject);
583
+ currentFrame = frame.prev;
584
+ continue;
754
585
  }
755
-
756
- if (value === undefined) {
757
- if (ignoreUndefined === false) index = serializeNull(buffer, key, value, index);
758
- } else if (value === null) {
759
- index = serializeNull(buffer, key, value, index);
760
- } else if (type === 'string') {
761
- index = serializeString(buffer, key, value, index);
762
- } else if (type === 'number') {
763
- index = serializeNumber(buffer, key, value, index);
764
- } else if (type === 'bigint') {
765
- index = serializeBigInt(buffer, key, value, index);
766
- } else if (type === 'boolean') {
767
- index = serializeBoolean(buffer, key, value, index);
768
- } else if (type === 'object' && value._bsontype == null) {
769
- if (value instanceof Date || isDate(value)) {
770
- index = serializeDate(buffer, key, value, index);
771
- } else if (value instanceof Uint8Array || isUint8Array(value)) {
772
- index = serializeBuffer(buffer, key, value, index);
773
- } else if (value instanceof RegExp || isRegExp(value)) {
774
- index = serializeRegExp(buffer, key, value, index);
775
- } else {
776
- index = serializeObject(
777
- buffer,
778
- key,
779
- value,
780
- index,
781
- checkKeys,
782
- depth,
783
- serializeFunctions,
784
- ignoreUndefined,
785
- path
786
- );
586
+ key = frame.keys[frame.keyIndex++];
587
+ value = (frame.iterTarget as Record<string, unknown>)[key];
588
+ } else {
589
+ // Array: use keyIndex as the numeric index.
590
+ const arr = frame.iterTarget as unknown[];
591
+ if (frame.keyIndex >= arr.length) {
592
+ buffer[index++] = 0x00;
593
+ NumberUtils.setInt32LE(buffer, frame.objectSizeIndex, index - frame.objectSizeIndex);
594
+ if (frame.codeSizeIndex !== null) {
595
+ NumberUtils.setInt32LE(buffer, frame.codeSizeIndex, index - frame.codeSizeIndex);
787
596
  }
788
- } else if (type === 'object') {
789
- if (value[constants.BSON_VERSION_SYMBOL] !== constants.BSON_MAJOR_VERSION) {
790
- throw new BSONVersionError();
791
- } else if (value._bsontype === 'ObjectId') {
792
- index = serializeObjectId(buffer, key, value, index);
793
- } else if (value._bsontype === 'Decimal128') {
794
- index = serializeDecimal128(buffer, key, value, index);
795
- } else if (value._bsontype === 'Long' || value._bsontype === 'Timestamp') {
796
- index = serializeLong(buffer, key, value, index);
797
- } else if (value._bsontype === 'Double') {
798
- index = serializeDouble(buffer, key, value, index);
799
- } else if (value._bsontype === 'Code') {
800
- index = serializeCode(
801
- buffer,
802
- key,
803
- value,
804
- index,
805
- checkKeys,
806
- depth,
807
- serializeFunctions,
808
- ignoreUndefined,
809
- path
810
- );
811
- } else if (value._bsontype === 'Binary') {
812
- index = serializeBinary(buffer, key, value, index);
813
- } else if (value._bsontype === 'BSONSymbol') {
814
- index = serializeSymbol(buffer, key, value, index);
815
- } else if (value._bsontype === 'DBRef') {
816
- index = serializeDBRef(buffer, key, value, index, depth, serializeFunctions, path);
817
- } else if (value._bsontype === 'BSONRegExp') {
818
- index = serializeBSONRegExp(buffer, key, value, index);
819
- } else if (value._bsontype === 'Int32') {
820
- index = serializeInt32(buffer, key, value, index);
821
- } else if (value._bsontype === 'MinKey' || value._bsontype === 'MaxKey') {
822
- index = serializeMinMax(buffer, key, value, index);
823
- } else if (typeof value._bsontype !== 'undefined') {
824
- throw new BSONError(`Unrecognized or invalid _bsontype: ${String(value._bsontype)}`);
825
- }
826
- } else if (type === 'function' && serializeFunctions) {
827
- index = serializeFunction(buffer, key, value, index);
597
+ path.delete(frame.sourceObject);
598
+ currentFrame = frame.prev;
599
+ continue;
828
600
  }
601
+ const i = frame.keyIndex++;
602
+ key = String(i);
603
+ value = arr[i];
829
604
  }
830
- } else {
831
- if (typeof object?.toBSON === 'function') {
832
- // Provided a custom serialization method
833
- object = object.toBSON();
834
- if (object != null && typeof object !== 'object') {
835
- throw new BSONError('toBSON function did not return an object');
836
- }
605
+
606
+ if (typeof value?.toBSON === 'function') {
607
+ value = value.toBSON();
837
608
  }
838
609
 
839
- // Iterate over all the keys
840
- for (const key of Object.keys(object)) {
841
- let value = object[key];
842
- // Is there an override value
843
- if (typeof value?.toBSON === 'function') {
844
- value = value.toBSON();
610
+ if (!frame.isArray && typeof key === 'string' && !(key[0] === '$' && ignoreKeys.has(key))) {
611
+ if (regexp.test(key)) {
612
+ throw new BSONError('key ' + key + ' must not contain null bytes');
845
613
  }
846
-
847
- // Check the type of the value
848
- const type = typeof value;
849
-
850
- // Check the key and throw error if it's illegal
851
- if (typeof key === 'string' && !ignoreKeys.has(key)) {
852
- if (key.match(regexp) != null) {
853
- // The BSON spec doesn't allow keys with null bytes because keys are
854
- // null-terminated.
855
- throw new BSONError('key ' + key + ' must not contain null bytes');
856
- }
857
-
858
- if (checkKeys) {
859
- if ('$' === key[0]) {
860
- throw new BSONError('key ' + key + " must not start with '$'");
861
- } else if (key.includes('.')) {
862
- throw new BSONError('key ' + key + " must not contain '.'");
863
- }
614
+ if (frame.checkKeys) {
615
+ if ('$' === key[0]) {
616
+ throw new BSONError('key ' + key + " must not start with '$'");
617
+ } else if (key.includes('.')) {
618
+ throw new BSONError('key ' + key + " must not contain '.'");
864
619
  }
865
620
  }
621
+ }
622
+
623
+ const type = typeof value;
866
624
 
867
- if (value === undefined) {
868
- if (ignoreUndefined === false) index = serializeNull(buffer, key, value, index);
869
- } else if (value === null) {
625
+ if (value === undefined) {
626
+ if (frame.isArray || frame.ignoreUndefined === false) {
870
627
  index = serializeNull(buffer, key, value, index);
871
- } else if (type === 'string') {
872
- index = serializeString(buffer, key, value, index);
873
- } else if (type === 'number') {
874
- index = serializeNumber(buffer, key, value, index);
875
- } else if (type === 'bigint') {
876
- index = serializeBigInt(buffer, key, value, index);
877
- } else if (type === 'boolean') {
878
- index = serializeBoolean(buffer, key, value, index);
879
- } else if (type === 'object' && value._bsontype == null) {
880
- if (value instanceof Date || isDate(value)) {
881
- index = serializeDate(buffer, key, value, index);
882
- } else if (value instanceof Uint8Array || isUint8Array(value)) {
883
- index = serializeBuffer(buffer, key, value, index);
884
- } else if (value instanceof RegExp || isRegExp(value)) {
885
- index = serializeRegExp(buffer, key, value, index);
886
- } else {
887
- index = serializeObject(
888
- buffer,
889
- key,
890
- value,
891
- index,
892
- checkKeys,
893
- depth,
894
- serializeFunctions,
895
- ignoreUndefined,
896
- path
897
- );
628
+ }
629
+ } else if (value === null) {
630
+ index = serializeNull(buffer, key, value, index);
631
+ } else if (type === 'string') {
632
+ index = serializeString(buffer, key, value, index);
633
+ } else if (type === 'number') {
634
+ index = serializeNumber(buffer, key, value, index);
635
+ } else if (type === 'bigint') {
636
+ index = serializeBigInt(buffer, key, value, index);
637
+ } else if (type === 'boolean') {
638
+ index = serializeBoolean(buffer, key, value, index);
639
+ } else if (type === 'object' && value._bsontype == null) {
640
+ if (value instanceof Date || isDate(value)) {
641
+ index = serializeDate(buffer, key, value, index);
642
+ } else if (value instanceof Uint8Array || isUint8Array(value)) {
643
+ index = serializeBuffer(buffer, key, value, index);
644
+ } else if (value instanceof RegExp || isRegExp(value)) {
645
+ index = serializeRegExp(buffer, key, value, index);
646
+ } else {
647
+ if (path.has(value)) {
648
+ throw new BSONError('Cannot convert circular structure to BSON');
898
649
  }
899
- } else if (type === 'object') {
900
- if (value[constants.BSON_VERSION_SYMBOL] !== constants.BSON_MAJOR_VERSION) {
901
- throw new BSONVersionError();
902
- } else if (value._bsontype === 'ObjectId') {
903
- index = serializeObjectId(buffer, key, value, index);
904
- } else if (value._bsontype === 'Decimal128') {
905
- index = serializeDecimal128(buffer, key, value, index);
906
- } else if (value._bsontype === 'Long' || value._bsontype === 'Timestamp') {
907
- index = serializeLong(buffer, key, value, index);
908
- } else if (value._bsontype === 'Double') {
909
- index = serializeDouble(buffer, key, value, index);
910
- } else if (value._bsontype === 'Code') {
911
- index = serializeCode(
912
- buffer,
913
- key,
914
- value,
650
+ const nestedIsArray = Array.isArray(value);
651
+ buffer[index++] = nestedIsArray ? constants.BSON_DATA_ARRAY : constants.BSON_DATA_OBJECT;
652
+ index += ByteUtils.encodeUTF8Into(buffer, key, index);
653
+ buffer[index++] = 0x00;
654
+ const nestedStartIndex = index;
655
+ path.add(value);
656
+ currentFrame = makeFrame(
657
+ value,
658
+ nestedStartIndex,
659
+ null,
660
+ frame,
661
+ frame.checkKeys,
662
+ frame.ignoreUndefined
663
+ );
664
+ index += 4;
665
+ }
666
+ } else if (type === 'object') {
667
+ if (value[constants.BSON_VERSION_SYMBOL] !== constants.BSON_MAJOR_VERSION) {
668
+ throw new BSONVersionError();
669
+ }
670
+ const tag = value[bsonType];
671
+ if (tag === 'ObjectId') {
672
+ index = serializeObjectId(buffer, key, value, index);
673
+ } else if (tag === 'Decimal128') {
674
+ index = serializeDecimal128(buffer, key, value, index);
675
+ } else if (tag === 'Long' || tag === 'Timestamp') {
676
+ index = serializeLong(buffer, key, value, index);
677
+ } else if (tag === 'Double') {
678
+ index = serializeDouble(buffer, key, value, index);
679
+ } else if (tag === 'Code') {
680
+ const codeValue = value as Code;
681
+ if (codeValue.scope && typeof codeValue.scope === 'object') {
682
+ buffer[index++] = constants.BSON_DATA_CODE_W_SCOPE;
683
+ index += ByteUtils.encodeUTF8Into(buffer, key, index);
684
+ buffer[index++] = 0x00;
685
+ const codeTotalSizeIndex = index;
686
+ index += 4;
687
+ const functionString = codeValue.code;
688
+ const codeSize = ByteUtils.encodeUTF8Into(buffer, functionString, index + 4) + 1;
689
+ NumberUtils.setInt32LE(buffer, index, codeSize);
690
+ buffer[index + 4 + codeSize - 1] = 0;
691
+ index = index + codeSize + 4;
692
+ const scope = codeValue.scope;
693
+ if (path.has(scope)) {
694
+ throw new BSONError('Cannot convert circular structure to BSON');
695
+ }
696
+ path.add(scope);
697
+ currentFrame = makeFrame(
698
+ scope,
915
699
  index,
916
- checkKeys,
917
- depth,
918
- serializeFunctions,
919
- ignoreUndefined,
920
- path
700
+ codeTotalSizeIndex,
701
+ frame,
702
+ frame.checkKeys,
703
+ frame.ignoreUndefined
921
704
  );
922
- } else if (value._bsontype === 'Binary') {
923
- index = serializeBinary(buffer, key, value, index);
924
- } else if (value._bsontype === 'BSONSymbol') {
925
- index = serializeSymbol(buffer, key, value, index);
926
- } else if (value._bsontype === 'DBRef') {
927
- index = serializeDBRef(buffer, key, value, index, depth, serializeFunctions, path);
928
- } else if (value._bsontype === 'BSONRegExp') {
929
- index = serializeBSONRegExp(buffer, key, value, index);
930
- } else if (value._bsontype === 'Int32') {
931
- index = serializeInt32(buffer, key, value, index);
932
- } else if (value._bsontype === 'MinKey' || value._bsontype === 'MaxKey') {
933
- index = serializeMinMax(buffer, key, value, index);
934
- } else if (typeof value._bsontype !== 'undefined') {
935
- throw new BSONError(`Unrecognized or invalid _bsontype: ${String(value._bsontype)}`);
705
+ index += 4;
706
+ } else {
707
+ buffer[index++] = constants.BSON_DATA_CODE;
708
+ index += ByteUtils.encodeUTF8Into(buffer, key, index);
709
+ buffer[index++] = 0x00;
710
+ const functionString = codeValue.code.toString();
711
+ const size = ByteUtils.encodeUTF8Into(buffer, functionString, index + 4) + 1;
712
+ NumberUtils.setInt32LE(buffer, index, size);
713
+ index = index + 4 + size - 1;
714
+ buffer[index++] = 0;
936
715
  }
937
- } else if (type === 'function' && serializeFunctions) {
938
- index = serializeFunction(buffer, key, value, index);
716
+ } else if (tag === 'Binary') {
717
+ index = serializeBinary(buffer, key, value, index);
718
+ } else if (tag === 'BSONSymbol') {
719
+ index = serializeSymbol(buffer, key, value, index);
720
+ } else if (tag === 'DBRef') {
721
+ const dbref = value as DBRef;
722
+ const orderedValues: Document = Object.assign(
723
+ { $ref: dbref.collection, $id: dbref.oid },
724
+ dbref.db != null ? { $db: dbref.db } : null,
725
+ dbref.fields
726
+ );
727
+ buffer[index++] = constants.BSON_DATA_OBJECT;
728
+ index += ByteUtils.encodeUTF8Into(buffer, key, index);
729
+ buffer[index++] = 0x00;
730
+ path.add(orderedValues);
731
+ currentFrame = makeFrame(orderedValues, index, null, frame, false, true);
732
+ index += 4;
733
+ } else if (tag === 'BSONRegExp') {
734
+ index = serializeBSONRegExp(buffer, key, value, index);
735
+ } else if (tag === 'Int32') {
736
+ index = serializeInt32(buffer, key, value, index);
737
+ } else if (tag === 'MinKey' || tag === 'MaxKey') {
738
+ index = serializeMinMax(buffer, key, value, index);
739
+ } else if (typeof value._bsontype !== 'undefined') {
740
+ throw new BSONError(`Unrecognized or invalid _bsontype: ${String(value._bsontype)}`);
939
741
  }
742
+ } else if (type === 'function' && serializeFunctions) {
743
+ index = serializeFunction(buffer, key, value, index);
940
744
  }
941
745
  }
942
746
 
943
- // Remove the path
944
- path.delete(object);
945
-
946
- // Final padding byte for object
947
- buffer[index++] = 0x00;
948
-
949
- // Final size
950
- const size = index - startingIndex;
951
- // Write the size of the object
952
- startingIndex += NumberUtils.setInt32LE(buffer, startingIndex, size);
953
747
  return index;
954
748
  }