diff --git a/src/bson_value.ts b/src/bson_value.ts index 069764d8..10d50141 100644 --- a/src/bson_value.ts +++ b/src/bson_value.ts @@ -1,5 +1,6 @@ import { BSON_MAJOR_VERSION } from './constants'; import { type InspectFn } from './parser/utils'; +import { BSON_VERSION_SYMBOL } from './constants'; /** @public */ export abstract class BSONValue { @@ -7,7 +8,7 @@ export abstract class BSONValue { public abstract get _bsontype(): string; /** @internal */ - get [Symbol.for('@@mdb.bson.version')](): typeof BSON_MAJOR_VERSION { + get [BSON_VERSION_SYMBOL](): typeof BSON_MAJOR_VERSION { return BSON_MAJOR_VERSION; } diff --git a/src/constants.ts b/src/constants.ts index 7f273948..c399fda3 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,6 +1,9 @@ /** @internal */ export const BSON_MAJOR_VERSION = 6; +/** @internal */ +export const BSON_VERSION_SYMBOL = Symbol.for('@@mdb.bson.version'); + /** @internal */ export const BSON_INT32_MAX = 0x7fffffff; /** @internal */ diff --git a/src/decimal128.ts b/src/decimal128.ts index 806938e3..8f491b3c 100644 --- a/src/decimal128.ts +++ b/src/decimal128.ts @@ -142,7 +142,7 @@ export class Decimal128 extends BSONValue { super(); if (typeof bytes === 'string') { this.bytes = Decimal128.fromString(bytes).bytes; - } else if (isUint8Array(bytes)) { + } else if (bytes instanceof Uint8Array || isUint8Array(bytes)) { if (bytes.byteLength !== 16) { throw new BSONError('Decimal128 must take a Buffer of 16 bytes'); } diff --git a/src/extended_json.ts b/src/extended_json.ts index eb08b3c1..7727ce93 100644 --- a/src/extended_json.ts +++ b/src/extended_json.ts @@ -6,7 +6,8 @@ import { BSON_INT32_MIN, BSON_INT64_MAX, BSON_INT64_MIN, - BSON_MAJOR_VERSION + BSON_MAJOR_VERSION, + BSON_VERSION_SYMBOL } from './constants'; import { DBRef, isDBRefLike } from './db_ref'; import { Decimal128 } from './decimal128'; @@ -358,7 +359,7 @@ function serializeDocument(doc: any, options: EJSONSerializeOptions) { doc != null && typeof doc === 'object' && typeof doc._bsontype === 'string' && - doc[Symbol.for('@@mdb.bson.version')] !== BSON_MAJOR_VERSION + doc[BSON_VERSION_SYMBOL] !== BSON_MAJOR_VERSION ) { throw new BSONVersionError(); } else if (isBSONType(doc)) { diff --git a/src/parser/calculate_size.ts b/src/parser/calculate_size.ts index fd1e4a02..923beeb0 100644 --- a/src/parser/calculate_size.ts +++ b/src/parser/calculate_size.ts @@ -81,7 +81,7 @@ function calculateElement( if ( value != null && typeof value._bsontype === 'string' && - value[Symbol.for('@@mdb.bson.version')] !== constants.BSON_MAJOR_VERSION + value[constants.BSON_VERSION_SYMBOL] !== constants.BSON_MAJOR_VERSION ) { throw new BSONVersionError(); } else if (value == null || value._bsontype === 'MinKey' || value._bsontype === 'MaxKey') { diff --git a/src/parser/serializer.ts b/src/parser/serializer.ts index 86490798..fbb47245 100644 --- a/src/parser/serializer.ts +++ b/src/parser/serializer.ts @@ -633,77 +633,81 @@ export function serializeInto( value = value.toBSON(); } - if (typeof value === 'string') { + // Check the type of the value + const type = typeof value; + + if (value === undefined) { + index = serializeNull(buffer, key, value, index); + } else if (value === null) { + index = serializeNull(buffer, key, value, index); + } else if (type === 'string') { index = serializeString(buffer, key, value, index); - } else if (typeof value === 'number') { + } else if (type === 'number') { index = serializeNumber(buffer, key, value, index); - } else if (typeof value === 'bigint') { + } else if (type === 'bigint') { index = serializeBigInt(buffer, key, value, index); - } else if (typeof value === 'boolean') { + } else if (type === 'boolean') { index = serializeBoolean(buffer, key, value, index); - } else if (value instanceof Date || isDate(value)) { - index = serializeDate(buffer, key, value, index); - } else if (value === undefined) { - index = serializeNull(buffer, key, value, index); - } else if (value === null) { - index = serializeNull(buffer, key, value, index); - } else if (isUint8Array(value)) { - index = serializeBuffer(buffer, key, value, index); - } else if (value instanceof RegExp || isRegExp(value)) { - index = serializeRegExp(buffer, key, value, index); - } else if (typeof value === 'object' && value._bsontype == null) { - index = serializeObject( - buffer, - key, - value, - index, - checkKeys, - depth, - serializeFunctions, - ignoreUndefined, - path - ); - } else if ( - typeof value === 'object' && - value[Symbol.for('@@mdb.bson.version')] !== constants.BSON_MAJOR_VERSION - ) { - throw new BSONVersionError(); - } else if (value._bsontype === 'ObjectId') { - index = serializeObjectId(buffer, key, value, index); - } else if (value._bsontype === 'Decimal128') { - index = serializeDecimal128(buffer, key, value, index); - } else if (value._bsontype === 'Long' || value._bsontype === 'Timestamp') { - index = serializeLong(buffer, key, value, index); - } else if (value._bsontype === 'Double') { - index = serializeDouble(buffer, key, value, index); - } else if (typeof value === 'function' && serializeFunctions) { + } else if (type === 'object' && value._bsontype == null) { + if (value instanceof Date || isDate(value)) { + index = serializeDate(buffer, key, value, index); + } else if (value instanceof Uint8Array || isUint8Array(value)) { + index = serializeBuffer(buffer, key, value, index); + } else if (value instanceof RegExp || isRegExp(value)) { + index = serializeRegExp(buffer, key, value, index); + } else { + index = serializeObject( + buffer, + key, + value, + index, + checkKeys, + depth, + serializeFunctions, + ignoreUndefined, + path + ); + } + } else if (type === 'object') { + if (value[constants.BSON_VERSION_SYMBOL] !== constants.BSON_MAJOR_VERSION) { + throw new BSONVersionError(); + } else if (value._bsontype === 'ObjectId') { + index = serializeObjectId(buffer, key, value, index); + } else if (value._bsontype === 'Decimal128') { + index = serializeDecimal128(buffer, key, value, index); + } else if (value._bsontype === 'Long' || value._bsontype === 'Timestamp') { + index = serializeLong(buffer, key, value, index); + } else if (value._bsontype === 'Double') { + index = serializeDouble(buffer, key, value, index); + } else if (value._bsontype === 'Code') { + index = serializeCode( + buffer, + key, + value, + index, + checkKeys, + depth, + serializeFunctions, + ignoreUndefined, + path + ); + } else if (value._bsontype === 'Binary') { + index = serializeBinary(buffer, key, value, index); + } else if (value._bsontype === 'BSONSymbol') { + index = serializeSymbol(buffer, key, value, index); + } else if (value._bsontype === 'DBRef') { + index = serializeDBRef(buffer, key, value, index, depth, serializeFunctions, path); + } else if (value._bsontype === 'BSONRegExp') { + index = serializeBSONRegExp(buffer, key, value, index); + } else if (value._bsontype === 'Int32') { + index = serializeInt32(buffer, key, value, index); + } else if (value._bsontype === 'MinKey' || value._bsontype === 'MaxKey') { + index = serializeMinMax(buffer, key, value, index); + } else if (typeof value._bsontype !== 'undefined') { + throw new BSONError(`Unrecognized or invalid _bsontype: ${String(value._bsontype)}`); + } + } else if (type === 'function' && serializeFunctions) { index = serializeFunction(buffer, key, value, index); - } else if (value._bsontype === 'Code') { - index = serializeCode( - buffer, - key, - value, - index, - checkKeys, - depth, - serializeFunctions, - ignoreUndefined, - path - ); - } else if (value._bsontype === 'Binary') { - index = serializeBinary(buffer, key, value, index); - } else if (value._bsontype === 'BSONSymbol') { - index = serializeSymbol(buffer, key, value, index); - } else if (value._bsontype === 'DBRef') { - index = serializeDBRef(buffer, key, value, index, depth, serializeFunctions, path); - } else if (value._bsontype === 'BSONRegExp') { - index = serializeBSONRegExp(buffer, key, value, index); - } else if (value._bsontype === 'Int32') { - index = serializeInt32(buffer, key, value, index); - } else if (value._bsontype === 'MinKey' || value._bsontype === 'MaxKey') { - index = serializeMinMax(buffer, key, value, index); - } else if (typeof value._bsontype !== 'undefined') { - throw new BSONError(`Unrecognized or invalid _bsontype: ${String(value._bsontype)}`); } } } else if (object instanceof Map || isMap(object)) { @@ -745,7 +749,11 @@ export function serializeInto( } } - if (type === 'string') { + if (value === undefined) { + if (ignoreUndefined === false) index = serializeNull(buffer, key, value, index); + } else if (value === null) { + index = serializeNull(buffer, key, value, index); + } else if (type === 'string') { index = serializeString(buffer, key, value, index); } else if (type === 'number') { index = serializeNumber(buffer, key, value, index); @@ -753,67 +761,66 @@ export function serializeInto( index = serializeBigInt(buffer, key, value, index); } else if (type === 'boolean') { index = serializeBoolean(buffer, key, value, index); - } else if (value instanceof Date || isDate(value)) { - index = serializeDate(buffer, key, value, index); - } else if (value === null || (value === undefined && ignoreUndefined === false)) { - index = serializeNull(buffer, key, value, index); - } else if (isUint8Array(value)) { - index = serializeBuffer(buffer, key, value, index); - } else if (value instanceof RegExp || isRegExp(value)) { - index = serializeRegExp(buffer, key, value, index); } else if (type === 'object' && value._bsontype == null) { - index = serializeObject( - buffer, - key, - value, - index, - checkKeys, - depth, - serializeFunctions, - ignoreUndefined, - path - ); - } else if ( - typeof value === 'object' && - value[Symbol.for('@@mdb.bson.version')] !== constants.BSON_MAJOR_VERSION - ) { - throw new BSONVersionError(); - } else if (value._bsontype === 'ObjectId') { - index = serializeObjectId(buffer, key, value, index); - } else if (type === 'object' && value._bsontype === 'Decimal128') { - index = serializeDecimal128(buffer, key, value, index); - } else if (value._bsontype === 'Long' || value._bsontype === 'Timestamp') { - index = serializeLong(buffer, key, value, index); - } else if (value._bsontype === 'Double') { - index = serializeDouble(buffer, key, value, index); - } else if (value._bsontype === 'Code') { - index = serializeCode( - buffer, - key, - value, - index, - checkKeys, - depth, - serializeFunctions, - ignoreUndefined, - path - ); - } else if (typeof value === 'function' && serializeFunctions) { + if (value instanceof Date || isDate(value)) { + index = serializeDate(buffer, key, value, index); + } else if (value instanceof Uint8Array || isUint8Array(value)) { + index = serializeBuffer(buffer, key, value, index); + } else if (value instanceof RegExp || isRegExp(value)) { + index = serializeRegExp(buffer, key, value, index); + } else { + index = serializeObject( + buffer, + key, + value, + index, + checkKeys, + depth, + serializeFunctions, + ignoreUndefined, + path + ); + } + } else if (type === 'object') { + if (value[constants.BSON_VERSION_SYMBOL] !== constants.BSON_MAJOR_VERSION) { + throw new BSONVersionError(); + } else if (value._bsontype === 'ObjectId') { + index = serializeObjectId(buffer, key, value, index); + } else if (value._bsontype === 'Decimal128') { + index = serializeDecimal128(buffer, key, value, index); + } else if (value._bsontype === 'Long' || value._bsontype === 'Timestamp') { + index = serializeLong(buffer, key, value, index); + } else if (value._bsontype === 'Double') { + index = serializeDouble(buffer, key, value, index); + } else if (value._bsontype === 'Code') { + index = serializeCode( + buffer, + key, + value, + index, + checkKeys, + depth, + serializeFunctions, + ignoreUndefined, + path + ); + } else if (value._bsontype === 'Binary') { + index = serializeBinary(buffer, key, value, index); + } else if (value._bsontype === 'BSONSymbol') { + index = serializeSymbol(buffer, key, value, index); + } else if (value._bsontype === 'DBRef') { + index = serializeDBRef(buffer, key, value, index, depth, serializeFunctions, path); + } else if (value._bsontype === 'BSONRegExp') { + index = serializeBSONRegExp(buffer, key, value, index); + } else if (value._bsontype === 'Int32') { + index = serializeInt32(buffer, key, value, index); + } else if (value._bsontype === 'MinKey' || value._bsontype === 'MaxKey') { + index = serializeMinMax(buffer, key, value, index); + } else if (typeof value._bsontype !== 'undefined') { + throw new BSONError(`Unrecognized or invalid _bsontype: ${String(value._bsontype)}`); + } + } else if (type === 'function' && serializeFunctions) { index = serializeFunction(buffer, key, value, index); - } else if (value._bsontype === 'Binary') { - index = serializeBinary(buffer, key, value, index); - } else if (value._bsontype === 'BSONSymbol') { - index = serializeSymbol(buffer, key, value, index); - } else if (value._bsontype === 'DBRef') { - index = serializeDBRef(buffer, key, value, index, depth, serializeFunctions, path); - } else if (value._bsontype === 'BSONRegExp') { - index = serializeBSONRegExp(buffer, key, value, index); - } else if (value._bsontype === 'Int32') { - index = serializeInt32(buffer, key, value, index); - } else if (value._bsontype === 'MinKey' || value._bsontype === 'MaxKey') { - index = serializeMinMax(buffer, key, value, index); - } else if (typeof value._bsontype !== 'undefined') { - throw new BSONError(`Unrecognized or invalid _bsontype: ${String(value._bsontype)}`); } } } else { @@ -853,7 +860,11 @@ export function serializeInto( } } - if (type === 'string') { + if (value === undefined) { + if (ignoreUndefined === false) index = serializeNull(buffer, key, value, index); + } else if (value === null) { + index = serializeNull(buffer, key, value, index); + } else if (type === 'string') { index = serializeString(buffer, key, value, index); } else if (type === 'number') { index = serializeNumber(buffer, key, value, index); @@ -861,69 +872,66 @@ export function serializeInto( index = serializeBigInt(buffer, key, value, index); } else if (type === 'boolean') { index = serializeBoolean(buffer, key, value, index); - } else if (value instanceof Date || isDate(value)) { - index = serializeDate(buffer, key, value, index); - } else if (value === undefined) { - if (ignoreUndefined === false) index = serializeNull(buffer, key, value, index); - } else if (value === null) { - index = serializeNull(buffer, key, value, index); - } else if (isUint8Array(value)) { - index = serializeBuffer(buffer, key, value, index); - } else if (value instanceof RegExp || isRegExp(value)) { - index = serializeRegExp(buffer, key, value, index); } else if (type === 'object' && value._bsontype == null) { - index = serializeObject( - buffer, - key, - value, - index, - checkKeys, - depth, - serializeFunctions, - ignoreUndefined, - path - ); - } else if ( - typeof value === 'object' && - value[Symbol.for('@@mdb.bson.version')] !== constants.BSON_MAJOR_VERSION - ) { - throw new BSONVersionError(); - } else if (value._bsontype === 'ObjectId') { - index = serializeObjectId(buffer, key, value, index); - } else if (type === 'object' && value._bsontype === 'Decimal128') { - index = serializeDecimal128(buffer, key, value, index); - } else if (value._bsontype === 'Long' || value._bsontype === 'Timestamp') { - index = serializeLong(buffer, key, value, index); - } else if (value._bsontype === 'Double') { - index = serializeDouble(buffer, key, value, index); - } else if (value._bsontype === 'Code') { - index = serializeCode( - buffer, - key, - value, - index, - checkKeys, - depth, - serializeFunctions, - ignoreUndefined, - path - ); - } else if (typeof value === 'function' && serializeFunctions) { + if (value instanceof Date || isDate(value)) { + index = serializeDate(buffer, key, value, index); + } else if (value instanceof Uint8Array || isUint8Array(value)) { + index = serializeBuffer(buffer, key, value, index); + } else if (value instanceof RegExp || isRegExp(value)) { + index = serializeRegExp(buffer, key, value, index); + } else { + index = serializeObject( + buffer, + key, + value, + index, + checkKeys, + depth, + serializeFunctions, + ignoreUndefined, + path + ); + } + } else if (type === 'object') { + if (value[constants.BSON_VERSION_SYMBOL] !== constants.BSON_MAJOR_VERSION) { + throw new BSONVersionError(); + } else if (value._bsontype === 'ObjectId') { + index = serializeObjectId(buffer, key, value, index); + } else if (value._bsontype === 'Decimal128') { + index = serializeDecimal128(buffer, key, value, index); + } else if (value._bsontype === 'Long' || value._bsontype === 'Timestamp') { + index = serializeLong(buffer, key, value, index); + } else if (value._bsontype === 'Double') { + index = serializeDouble(buffer, key, value, index); + } else if (value._bsontype === 'Code') { + index = serializeCode( + buffer, + key, + value, + index, + checkKeys, + depth, + serializeFunctions, + ignoreUndefined, + path + ); + } else if (value._bsontype === 'Binary') { + index = serializeBinary(buffer, key, value, index); + } else if (value._bsontype === 'BSONSymbol') { + index = serializeSymbol(buffer, key, value, index); + } else if (value._bsontype === 'DBRef') { + index = serializeDBRef(buffer, key, value, index, depth, serializeFunctions, path); + } else if (value._bsontype === 'BSONRegExp') { + index = serializeBSONRegExp(buffer, key, value, index); + } else if (value._bsontype === 'Int32') { + index = serializeInt32(buffer, key, value, index); + } else if (value._bsontype === 'MinKey' || value._bsontype === 'MaxKey') { + index = serializeMinMax(buffer, key, value, index); + } else if (typeof value._bsontype !== 'undefined') { + throw new BSONError(`Unrecognized or invalid _bsontype: ${String(value._bsontype)}`); + } + } else if (type === 'function' && serializeFunctions) { index = serializeFunction(buffer, key, value, index); - } else if (value._bsontype === 'Binary') { - index = serializeBinary(buffer, key, value, index); - } else if (value._bsontype === 'BSONSymbol') { - index = serializeSymbol(buffer, key, value, index); - } else if (value._bsontype === 'DBRef') { - index = serializeDBRef(buffer, key, value, index, depth, serializeFunctions, path); - } else if (value._bsontype === 'BSONRegExp') { - index = serializeBSONRegExp(buffer, key, value, index); - } else if (value._bsontype === 'Int32') { - index = serializeInt32(buffer, key, value, index); - } else if (value._bsontype === 'MinKey' || value._bsontype === 'MaxKey') { - index = serializeMinMax(buffer, key, value, index); - } else if (typeof value._bsontype !== 'undefined') { - throw new BSONError(`Unrecognized or invalid _bsontype: ${String(value._bsontype)}`); } } } diff --git a/src/parser/utils.ts b/src/parser/utils.ts index 0b27249e..a404eb4b 100644 --- a/src/parser/utils.ts +++ b/src/parser/utils.ts @@ -1,31 +1,65 @@ +const map = new WeakMap(); + +const TYPES = { + ArrayBuffer: '[object ArrayBuffer]', + SharedArrayBuffer: '[object SharedArrayBuffer]', + Uint8Array: '[object Uint8Array]', + BigInt64Array: '[object BigInt64Array]', + BigUint64Array: '[object BigUint64Array]', + RegExp: '[object RegExp]', + Map: '[object Map]', + Date: '[object Date]' +}; + +/** + * Retrieves the prototype.toString() of a value. + * If the value is an object, it will cache the result in a WeakMap for future use. + */ +function getPrototypeString(value: unknown): string { + let str = map.get(value as object); + + if (!str) { + str = Object.prototype.toString.call(value); + if (value !== null && typeof value === 'object') { + map.set(value, str); + } + } + return str; +} + export function isAnyArrayBuffer(value: unknown): value is ArrayBuffer { - return ['[object ArrayBuffer]', '[object SharedArrayBuffer]'].includes( - Object.prototype.toString.call(value) - ); + const type = getPrototypeString(value); + return type === TYPES.ArrayBuffer || type === TYPES.SharedArrayBuffer; } export function isUint8Array(value: unknown): value is Uint8Array { - return Object.prototype.toString.call(value) === '[object Uint8Array]'; + const type = getPrototypeString(value); + return type === TYPES.Uint8Array; } export function isBigInt64Array(value: unknown): value is BigInt64Array { - return Object.prototype.toString.call(value) === '[object BigInt64Array]'; + const type = getPrototypeString(value); + return type === TYPES.BigInt64Array; } export function isBigUInt64Array(value: unknown): value is BigUint64Array { - return Object.prototype.toString.call(value) === '[object BigUint64Array]'; + const type = getPrototypeString(value); + return type === TYPES.BigUint64Array; } export function isRegExp(d: unknown): d is RegExp { - return Object.prototype.toString.call(d) === '[object RegExp]'; + const type = getPrototypeString(d); + return type === TYPES.RegExp; } export function isMap(d: unknown): d is Map { - return Object.prototype.toString.call(d) === '[object Map]'; + const type = getPrototypeString(d); + return type === TYPES.Map; } export function isDate(d: unknown): d is Date { - return Object.prototype.toString.call(d) === '[object Date]'; + const type = getPrototypeString(d); + return type === TYPES.Date; } export type InspectFn = (x: unknown, options?: unknown) => string;