bson 6.6.1 → 6.7.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.
package/bson.d.ts CHANGED
@@ -601,6 +601,20 @@ export declare class Double extends BSONValue {
601
601
  * @param value - the number we want to represent as a double.
602
602
  */
603
603
  constructor(value: number);
604
+ /**
605
+ * Attempt to create an double type from string.
606
+ *
607
+ * This method will throw a BSONError on any string input that is not representable as a IEEE-754 64-bit double.
608
+ * Notably, this method will also throw on the following string formats:
609
+ * - Strings in non-decimal and non-exponential formats (binary, hex, or octal digits)
610
+ * - Strings with characters other than numeric, floating point, or leading sign characters (Note: 'Infinity', '-Infinity', and 'NaN' input strings are still allowed)
611
+ * - Strings with leading and/or trailing whitespace
612
+ *
613
+ * Strings with leading zeros, however, are also allowed
614
+ *
615
+ * @param value - the string we want to represent as a double.
616
+ */
617
+ static fromString(value: string): Double;
604
618
  /**
605
619
  * Access the number value.
606
620
  *
@@ -677,6 +691,20 @@ export declare class Int32 extends BSONValue {
677
691
  * @param value - the number we want to represent as an int32.
678
692
  */
679
693
  constructor(value: number | string);
694
+ /**
695
+ * Attempt to create an Int32 type from string.
696
+ *
697
+ * This method will throw a BSONError on any string input that is not representable as an Int32.
698
+ * Notably, this method will also throw on the following string formats:
699
+ * - Strings in non-decimal formats (exponent notation, binary, hex, or octal digits)
700
+ * - Strings non-numeric and non-leading sign characters (ex: '2.0', '24,000')
701
+ * - Strings with leading and/or trailing whitespace
702
+ *
703
+ * Strings with leading zeros, however, are allowed.
704
+ *
705
+ * @param value - the string we want to represent as an int32.
706
+ */
707
+ static fromString(value: string): Int32;
680
708
  /**
681
709
  * Access the number value.
682
710
  *
@@ -791,8 +819,113 @@ export declare class Long extends BSONValue {
791
819
  * @returns The corresponding Long value
792
820
  */
793
821
  static fromBigInt(value: bigint, unsigned?: boolean): Long;
822
+ /* Excluded from this release type: _fromString */
823
+ /**
824
+ * Returns a signed Long representation of the given string, written using radix 10.
825
+ * Will throw an error if the given text is not exactly representable as a Long.
826
+ * Throws an error if any of the following conditions are true:
827
+ * - the string contains invalid characters for the radix 10
828
+ * - the string contains whitespace
829
+ * - the value the string represents is too large or too small to be a Long
830
+ * Unlike Long.fromString, this method does not coerce '+/-Infinity' and 'NaN' to Long.Zero
831
+ * @param str - The textual representation of the Long
832
+ * @returns The corresponding Long value
833
+ */
834
+ static fromStringStrict(str: string): Long;
835
+ /**
836
+ * Returns a Long representation of the given string, written using the radix 10.
837
+ * Will throw an error if the given parameters are not exactly representable as a Long.
838
+ * Throws an error if any of the following conditions are true:
839
+ * - the string contains invalid characters for the given radix
840
+ * - the string contains whitespace
841
+ * - the value the string represents is too large or too small to be a Long
842
+ * Unlike Long.fromString, this method does not coerce '+/-Infinity' and 'NaN' to Long.Zero
843
+ * @param str - The textual representation of the Long
844
+ * @param unsigned - Whether unsigned or not, defaults to signed
845
+ * @returns The corresponding Long value
846
+ */
847
+ static fromStringStrict(str: string, unsigned?: boolean): Long;
848
+ /**
849
+ * Returns a signed Long representation of the given string, written using the specified radix.
850
+ * Will throw an error if the given parameters are not exactly representable as a Long.
851
+ * Throws an error if any of the following conditions are true:
852
+ * - the string contains invalid characters for the given radix
853
+ * - the string contains whitespace
854
+ * - the value the string represents is too large or too small to be a Long
855
+ * Unlike Long.fromString, this method does not coerce '+/-Infinity' and 'NaN' to Long.Zero
856
+ * @param str - The textual representation of the Long
857
+ * @param radix - The radix in which the text is written (2-36), defaults to 10
858
+ * @returns The corresponding Long value
859
+ */
860
+ static fromStringStrict(str: string, radix?: boolean): Long;
794
861
  /**
795
862
  * Returns a Long representation of the given string, written using the specified radix.
863
+ * Will throw an error if the given parameters are not exactly representable as a Long.
864
+ * Throws an error if any of the following conditions are true:
865
+ * - the string contains invalid characters for the given radix
866
+ * - the string contains whitespace
867
+ * - the value the string represents is too large or too small to be a Long
868
+ * Unlike Long.fromString, this method does not coerce '+/-Infinity' and 'NaN' to Long.Zero
869
+ * @param str - The textual representation of the Long
870
+ * @param unsigned - Whether unsigned or not, defaults to signed
871
+ * @param radix - The radix in which the text is written (2-36), defaults to 10
872
+ * @returns The corresponding Long value
873
+ */
874
+ static fromStringStrict(str: string, unsigned?: boolean, radix?: number): Long;
875
+ /**
876
+ * Returns a signed Long representation of the given string, written using radix 10.
877
+ *
878
+ * If the input string is empty, this function will throw a BSONError.
879
+ *
880
+ * If input string does not have valid signed 64-bit Long representation, this method will return a coerced value:
881
+ * - inputs that overflow 64-bit signed long will be coerced to Long.MAX_VALUE and Long.MIN_VALUE respectively
882
+ * - 'NaN' or '+/-Infinity' are coerced to Long.ZERO
883
+ * - other invalid characters sequences have variable behavior
884
+ *
885
+ * @param str - The textual representation of the Long
886
+ * @returns The corresponding Long value
887
+ */
888
+ static fromString(str: string): Long;
889
+ /**
890
+ * Returns a signed Long representation of the given string, written using the provided radix.
891
+ *
892
+ * If the input string is empty or a provided radix is not within (2-36), this function will throw a BSONError.
893
+ *
894
+ * If input parameters do not have valid signed 64-bit Long representation, this method will return a coerced value:
895
+ * - inputs that overflow 64-bit signed long will be coerced to Long.MAX_VALUE and Long.MIN_VALUE respectively
896
+ * - if the radix is less than 24, 'NaN' is coerced to Long.ZERO
897
+ * - if the radix is less than 35, '+/-Infinity' inputs are coerced to Long.ZERO
898
+ * - other invalid characters sequences have variable behavior
899
+ * @param str - The textual representation of the Long
900
+ * @param radix - The radix in which the text is written (2-36), defaults to 10
901
+ * @returns The corresponding Long value
902
+ */
903
+ static fromString(str: string, radix?: number): Long;
904
+ /**
905
+ * Returns a Long representation of the given string, written using radix 10.
906
+ *
907
+ * If the input string is empty, this function will throw a BSONError.
908
+ *
909
+ * If input parameters do not have a valid 64-bit Long representation, this method will return a coerced value:
910
+ * - inputs that overflow 64-bit long will be coerced to max or min (if signed) values
911
+ * - if the radix is less than 24, 'NaN' is coerced to Long.ZERO
912
+ * - if the radix is less than 35, '+/-Infinity' inputs are coerced to Long.ZERO
913
+ * - other invalid characters sequences have variable behavior
914
+ * @param str - The textual representation of the Long
915
+ * @param unsigned - Whether unsigned or not, defaults to signed
916
+ * @returns The corresponding Long value
917
+ */
918
+ static fromString(str: string, unsigned?: boolean): Long;
919
+ /**
920
+ * Returns a Long representation of the given string, written using the specified radix.
921
+ *
922
+ * If the input string is empty or a provided radix is not within (2-36), this function will throw a BSONError.
923
+ *
924
+ * If input parameters do not have a valid 64-bit Long representation, this method will return a coerced value:
925
+ * - inputs that overflow 64-bit long will be coerced to max or min (if signed) values
926
+ * - if the radix is less than 24, 'NaN' is coerced to Long.ZERO
927
+ * - if the radix is less than 35, '+/-Infinity' inputs are coerced to Long.ZERO
928
+ * - other invalid characters sequences have variable behavior
796
929
  * @param str - The textual representation of the Long
797
930
  * @param unsigned - Whether unsigned or not, defaults to signed
798
931
  * @param radix - The radix in which the text is written (2-36), defaults to 10
@@ -137,41 +137,20 @@ class BSONOffsetError extends BSONError {
137
137
  }
138
138
  }
139
139
 
140
- const FIRST_BIT = 0x80;
141
- const FIRST_TWO_BITS = 0xc0;
142
- const FIRST_THREE_BITS = 0xe0;
143
- const FIRST_FOUR_BITS = 0xf0;
144
- const FIRST_FIVE_BITS = 0xf8;
145
- const TWO_BIT_CHAR = 0xc0;
146
- const THREE_BIT_CHAR = 0xe0;
147
- const FOUR_BIT_CHAR = 0xf0;
148
- const CONTINUING_CHAR = 0x80;
149
- function validateUtf8(bytes, start, end) {
150
- let continuation = 0;
151
- for (let i = start; i < end; i += 1) {
152
- const byte = bytes[i];
153
- if (continuation) {
154
- if ((byte & FIRST_TWO_BITS) !== CONTINUING_CHAR) {
155
- return false;
156
- }
157
- continuation -= 1;
140
+ let TextDecoderFatal;
141
+ let TextDecoderNonFatal;
142
+ function parseUtf8(buffer, start, end, fatal) {
143
+ if (fatal) {
144
+ TextDecoderFatal ??= new TextDecoder('utf8', { fatal: true });
145
+ try {
146
+ return TextDecoderFatal.decode(buffer.subarray(start, end));
158
147
  }
159
- else if (byte & FIRST_BIT) {
160
- if ((byte & FIRST_THREE_BITS) === TWO_BIT_CHAR) {
161
- continuation = 1;
162
- }
163
- else if ((byte & FIRST_FOUR_BITS) === THREE_BIT_CHAR) {
164
- continuation = 2;
165
- }
166
- else if ((byte & FIRST_FIVE_BITS) === FOUR_BIT_CHAR) {
167
- continuation = 3;
168
- }
169
- else {
170
- return false;
171
- }
148
+ catch (cause) {
149
+ throw new BSONError('Invalid UTF-8 string in BSON document', { cause });
172
150
  }
173
151
  }
174
- return !continuation;
152
+ TextDecoderNonFatal ??= new TextDecoder('utf8', { fatal: false });
153
+ return TextDecoderNonFatal.decode(buffer.subarray(start, end));
175
154
  }
176
155
 
177
156
  function tryReadBasicLatin(uint8array, start, end) {
@@ -292,9 +271,7 @@ const nodeJsByteUtils = {
292
271
  if (fatal) {
293
272
  for (let i = 0; i < string.length; i++) {
294
273
  if (string.charCodeAt(i) === 0xfffd) {
295
- if (!validateUtf8(buffer, start, end)) {
296
- throw new BSONError('Invalid UTF-8 string in BSON document');
297
- }
274
+ parseUtf8(buffer, start, end, true);
298
275
  break;
299
276
  }
300
277
  }
@@ -418,15 +395,7 @@ const webByteUtils = {
418
395
  if (basicLatin != null) {
419
396
  return basicLatin;
420
397
  }
421
- if (fatal) {
422
- try {
423
- return new TextDecoder('utf8', { fatal }).decode(uint8array.slice(start, end));
424
- }
425
- catch (cause) {
426
- throw new BSONError('Invalid UTF-8 string in BSON document', { cause });
427
- }
428
- }
429
- return new TextDecoder('utf8', { fatal }).decode(uint8array.slice(start, end));
398
+ return parseUtf8(uint8array, start, end, fatal);
430
399
  },
431
400
  utf8ByteLength(input) {
432
401
  return new TextEncoder().encode(input).byteLength;
@@ -839,6 +808,32 @@ class DBRef extends BSONValue {
839
808
  }
840
809
  }
841
810
 
811
+ function removeLeadingZerosAndExplicitPlus(str) {
812
+ if (str === '') {
813
+ return str;
814
+ }
815
+ let startIndex = 0;
816
+ const isNegative = str[startIndex] === '-';
817
+ const isExplicitlyPositive = str[startIndex] === '+';
818
+ if (isExplicitlyPositive || isNegative) {
819
+ startIndex += 1;
820
+ }
821
+ let foundInsignificantZero = false;
822
+ for (; startIndex < str.length && str[startIndex] === '0'; ++startIndex) {
823
+ foundInsignificantZero = true;
824
+ }
825
+ if (!foundInsignificantZero) {
826
+ return isExplicitlyPositive ? str.slice(1) : str;
827
+ }
828
+ return `${isNegative ? '-' : ''}${str.length === startIndex ? '0' : str.slice(startIndex)}`;
829
+ }
830
+ function validateStringCharacters(str, radix) {
831
+ radix = radix ?? 10;
832
+ const validCharacters = '0123456789abcdefghijklmnopqrstuvwxyz'.slice(0, radix);
833
+ const regex = new RegExp(`[^-+${validCharacters}]`, 'i');
834
+ return regex.test(str) ? false : str;
835
+ }
836
+
842
837
  let wasm = undefined;
843
838
  try {
844
839
  wasm = new WebAssembly.Instance(new WebAssembly.Module(new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 13, 2, 96, 0, 1, 127, 96, 4, 127, 127, 127, 127, 1, 127, 3, 7, 6, 0, 1, 1, 1, 1, 1, 6, 6, 1, 127, 1, 65, 0, 11, 7, 50, 6, 3, 109, 117, 108, 0, 1, 5, 100, 105, 118, 95, 115, 0, 2, 5, 100, 105, 118, 95, 117, 0, 3, 5, 114, 101, 109, 95, 115, 0, 4, 5, 114, 101, 109, 95, 117, 0, 5, 8, 103, 101, 116, 95, 104, 105, 103, 104, 0, 0, 10, 191, 1, 6, 4, 0, 35, 0, 11, 36, 1, 1, 126, 32, 0, 173, 32, 1, 173, 66, 32, 134, 132, 32, 2, 173, 32, 3, 173, 66, 32, 134, 132, 126, 34, 4, 66, 32, 135, 167, 36, 0, 32, 4, 167, 11, 36, 1, 1, 126, 32, 0, 173, 32, 1, 173, 66, 32, 134, 132, 32, 2, 173, 32, 3, 173, 66, 32, 134, 132, 127, 34, 4, 66, 32, 135, 167, 36, 0, 32, 4, 167, 11, 36, 1, 1, 126, 32, 0, 173, 32, 1, 173, 66, 32, 134, 132, 32, 2, 173, 32, 3, 173, 66, 32, 134, 132, 128, 34, 4, 66, 32, 135, 167, 36, 0, 32, 4, 167, 11, 36, 1, 1, 126, 32, 0, 173, 32, 1, 173, 66, 32, 134, 132, 32, 2, 173, 32, 3, 173, 66, 32, 134, 132, 129, 34, 4, 66, 32, 135, 167, 36, 0, 32, 4, 167, 11, 36, 1, 1, 126, 32, 0, 173, 32, 1, 173, 66, 32, 134, 132, 32, 2, 173, 32, 3, 173, 66, 32, 134, 132, 130, 34, 4, 66, 32, 135, 167, 36, 0, 32, 4, 167, 11])), {}).exports;
@@ -927,25 +922,16 @@ class Long extends BSONValue {
927
922
  static fromBigInt(value, unsigned) {
928
923
  return Long.fromString(value.toString(), unsigned);
929
924
  }
930
- static fromString(str, unsigned, radix) {
925
+ static _fromString(str, unsigned, radix) {
931
926
  if (str.length === 0)
932
927
  throw new BSONError('empty string');
933
- if (str === 'NaN' || str === 'Infinity' || str === '+Infinity' || str === '-Infinity')
934
- return Long.ZERO;
935
- if (typeof unsigned === 'number') {
936
- (radix = unsigned), (unsigned = false);
937
- }
938
- else {
939
- unsigned = !!unsigned;
940
- }
941
- radix = radix || 10;
942
928
  if (radix < 2 || 36 < radix)
943
929
  throw new BSONError('radix');
944
930
  let p;
945
931
  if ((p = str.indexOf('-')) > 0)
946
932
  throw new BSONError('interior hyphen');
947
933
  else if (p === 0) {
948
- return Long.fromString(str.substring(1), unsigned, radix).neg();
934
+ return Long._fromString(str.substring(1), unsigned, radix).neg();
949
935
  }
950
936
  const radixToPower = Long.fromNumber(Math.pow(radix, 8));
951
937
  let result = Long.ZERO;
@@ -963,6 +949,45 @@ class Long extends BSONValue {
963
949
  result.unsigned = unsigned;
964
950
  return result;
965
951
  }
952
+ static fromStringStrict(str, unsignedOrRadix, radix) {
953
+ let unsigned = false;
954
+ if (typeof unsignedOrRadix === 'number') {
955
+ (radix = unsignedOrRadix), (unsignedOrRadix = false);
956
+ }
957
+ else {
958
+ unsigned = !!unsignedOrRadix;
959
+ }
960
+ radix ??= 10;
961
+ if (str.trim() !== str) {
962
+ throw new BSONError(`Input: '${str}' contains leading and/or trailing whitespace`);
963
+ }
964
+ if (!validateStringCharacters(str, radix)) {
965
+ throw new BSONError(`Input: '${str}' contains invalid characters for radix: ${radix}`);
966
+ }
967
+ const cleanedStr = removeLeadingZerosAndExplicitPlus(str);
968
+ const result = Long._fromString(cleanedStr, unsigned, radix);
969
+ if (result.toString(radix).toLowerCase() !== cleanedStr.toLowerCase()) {
970
+ throw new BSONError(`Input: ${str} is not representable as ${result.unsigned ? 'an unsigned' : 'a signed'} 64-bit Long ${radix != null ? `with radix: ${radix}` : ''}`);
971
+ }
972
+ return result;
973
+ }
974
+ static fromString(str, unsignedOrRadix, radix) {
975
+ let unsigned = false;
976
+ if (typeof unsignedOrRadix === 'number') {
977
+ (radix = unsignedOrRadix), (unsignedOrRadix = false);
978
+ }
979
+ else {
980
+ unsigned = !!unsignedOrRadix;
981
+ }
982
+ radix ??= 10;
983
+ if (str === 'NaN' && radix < 24) {
984
+ return Long.ZERO;
985
+ }
986
+ else if ((str === 'Infinity' || str === '+Infinity' || str === '-Infinity') && radix < 35) {
987
+ return Long.ZERO;
988
+ }
989
+ return Long._fromString(str, unsigned, radix);
990
+ }
966
991
  static fromBytes(bytes, unsigned, le) {
967
992
  return le ? Long.fromBytesLE(bytes, unsigned) : Long.fromBytesBE(bytes, unsigned);
968
993
  }
@@ -2040,6 +2065,28 @@ class Double extends BSONValue {
2040
2065
  }
2041
2066
  this.value = +value;
2042
2067
  }
2068
+ static fromString(value) {
2069
+ const coercedValue = Number(value);
2070
+ if (value === 'NaN')
2071
+ return new Double(NaN);
2072
+ if (value === 'Infinity')
2073
+ return new Double(Infinity);
2074
+ if (value === '-Infinity')
2075
+ return new Double(-Infinity);
2076
+ if (!Number.isFinite(coercedValue)) {
2077
+ throw new BSONError(`Input: ${value} is not representable as a Double`);
2078
+ }
2079
+ if (value.trim() !== value) {
2080
+ throw new BSONError(`Input: '${value}' contains whitespace`);
2081
+ }
2082
+ if (value === '') {
2083
+ throw new BSONError(`Input is an empty string`);
2084
+ }
2085
+ if (/[^-0-9.+eE]/.test(value)) {
2086
+ throw new BSONError(`Input: '${value}' is not in decimal or exponential notation`);
2087
+ }
2088
+ return new Double(coercedValue);
2089
+ }
2043
2090
  valueOf() {
2044
2091
  return this.value;
2045
2092
  }
@@ -2081,6 +2128,23 @@ class Int32 extends BSONValue {
2081
2128
  }
2082
2129
  this.value = +value | 0;
2083
2130
  }
2131
+ static fromString(value) {
2132
+ const cleanedValue = removeLeadingZerosAndExplicitPlus(value);
2133
+ const coercedValue = Number(value);
2134
+ if (BSON_INT32_MAX < coercedValue) {
2135
+ throw new BSONError(`Input: '${value}' is larger than the maximum value for Int32`);
2136
+ }
2137
+ else if (BSON_INT32_MIN > coercedValue) {
2138
+ throw new BSONError(`Input: '${value}' is smaller than the minimum value for Int32`);
2139
+ }
2140
+ else if (!Number.isSafeInteger(coercedValue)) {
2141
+ throw new BSONError(`Input: '${value}' is not a safe integer`);
2142
+ }
2143
+ else if (coercedValue.toString() !== cleanedValue) {
2144
+ throw new BSONError(`Input: '${value}' is not a valid Int32 string`);
2145
+ }
2146
+ return new Int32(coercedValue);
2147
+ }
2084
2148
  valueOf() {
2085
2149
  return this.value;
2086
2150
  }
@@ -3174,12 +3238,7 @@ function deserializeObject(buffer, index, options, isArray = false) {
3174
3238
  stringSize > buffer.length - index ||
3175
3239
  buffer[index + stringSize - 1] !== 0)
3176
3240
  throw new BSONError('bad string length in bson');
3177
- if (validation != null && validation.utf8) {
3178
- if (!validateUtf8(buffer, index, index + stringSize - 1)) {
3179
- throw new BSONError('Invalid UTF-8 string in BSON document');
3180
- }
3181
- }
3182
- const namespace = ByteUtils.toUTF8(buffer, index, index + stringSize - 1, false);
3241
+ const namespace = ByteUtils.toUTF8(buffer, index, index + stringSize - 1, shouldValidateKey);
3183
3242
  index = index + stringSize;
3184
3243
  const oidBuffer = ByteUtils.allocateUnsafe(12);
3185
3244
  for (let i = 0; i < 12; i++)