diff --git a/src/objectid.ts b/src/objectid.ts index 98daecc8..ce208134 100644 --- a/src/objectid.ts +++ b/src/objectid.ts @@ -2,13 +2,15 @@ import { BSONValue } from './bson_value'; import { BSONError } from './error'; import { type InspectFn, defaultInspect } from './parser/utils'; import { ByteUtils } from './utils/byte_utils'; -import { NumberUtils } from './utils/number_utils'; +import { flattenString } from './utils/string_utils'; // Regular expression that checks for hex value -const checkForHexRegExp = new RegExp('^[0-9a-fA-F]{24}$'); +const checkForHexRegExp = new RegExp('^[0-9a-f]{24}$'); // Unique sequence for the current process (initialized on first use) -let PROCESS_UNIQUE: Uint8Array | null = null; +let PROCESS_UNIQUE: string | null = null; + +const OID_SKIP_VALIDATE = Symbol(); /** @public */ export interface ObjectIdLike { @@ -34,13 +36,24 @@ export class ObjectId extends BSONValue { /** @internal */ private static index = Math.floor(Math.random() * 0xffffff); + /** @internal */ + private static lastTimeGenerate?: number; + /** @internal */ + private static timeHexCache?: string; + /** @deprecated Hex string is always cached */ static cacheHexString: boolean; + /** + * Cache buffer internally + * Uses much more memory but can speed up performance if performing lots of buffer specific tasks + */ + static cacheBuffer: boolean; + /** ObjectId Bytes @internal */ - private buffer!: Uint8Array; + private buffer?: Uint8Array; /** ObjectId hexString cache @internal */ - private __id?: string; + private __id: string; /** * Create ObjectId from a number. @@ -55,6 +68,8 @@ export class ObjectId extends BSONValue { * @param inputId - A 24 character hex string. */ constructor(inputId: string); + /** @internal */ + constructor(inputId: string, _internalFlag?: symbol); /** * Create ObjectId from the BSON ObjectId type. * @@ -72,7 +87,7 @@ export class ObjectId extends BSONValue { * * @param inputId - A 12 byte binary Buffer. */ - constructor(inputId: Uint8Array); + constructor(inputId: Uint8Array, offset?: number); /** To generate a new ObjectId, use ObjectId() with no argument. */ constructor(); /** @@ -86,7 +101,11 @@ export class ObjectId extends BSONValue { * * @param inputId - An input value to create a new ObjectId from. */ - constructor(inputId?: string | number | ObjectId | ObjectIdLike | Uint8Array) { + constructor( + inputId?: string | number | ObjectId | ObjectIdLike | Uint8Array, + option?: symbol | number + ) { + let bufferCache: Uint8Array | undefined; super(); // workingId is set based on type of input and whether valid id exists for the input let workingId; @@ -95,7 +114,8 @@ export class ObjectId extends BSONValue { throw new BSONError('Argument passed in must have an id that is of type string or Buffer'); } if ('toHexString' in inputId && typeof inputId.toHexString === 'function') { - workingId = ByteUtils.fromHex(inputId.toHexString()); + workingId = inputId.toHexString(); + option = OID_SKIP_VALIDATE; } else { workingId = inputId.id; } @@ -104,27 +124,43 @@ export class ObjectId extends BSONValue { } // The following cases use workingId to construct an ObjectId - if (workingId == null || typeof workingId === 'number') { + if (typeof workingId === 'string') { + if (option === OID_SKIP_VALIDATE) { + this.__id = workingId; + } else { + const validString = ObjectId.validateHexString(workingId); + if (validString) { + this.__id = validString; + } else { + throw new BSONError( + 'input must be a 24 character hex string, 12 byte Uint8Array, or an integer' + ); + } + } + } else if (workingId == null || typeof workingId === 'number') { // The most common use case (blank id, new objectId instance) // Generate a new id - this.buffer = ObjectId.generate(typeof workingId === 'number' ? workingId : undefined); - } else if (ArrayBuffer.isView(workingId) && workingId.byteLength === 12) { - // If intstanceof matches we can escape calling ensure buffer in Node.js environments - this.buffer = ByteUtils.toLocalBufferType(workingId); - } else if (typeof workingId === 'string') { - if (workingId.length === 24 && checkForHexRegExp.test(workingId)) { - this.buffer = ByteUtils.fromHex(workingId); - } else { - throw new BSONError( - 'input must be a 24 character hex string, 12 byte Uint8Array, or an integer' - ); + this.__id = ObjectId.generate(typeof workingId === 'number' ? workingId : undefined); + } else if (ArrayBuffer.isView(workingId)) { + if (option == null && workingId.byteLength !== 12) { + throw new BSONError('Buffer length must be 12 or offset must be specified'); } + if ( + option && + (typeof option !== 'number' || option < 0 || workingId.byteLength < option + 12) + ) { + throw new BSONError('Buffer offset must be a non-negative number less than buffer length'); + } + // If intstanceof matches we can escape calling ensure buffer in Node.js environments + bufferCache = ByteUtils.toLocalBufferType(workingId); + const offset = option || 0; + this.__id = ByteUtils.toHex(bufferCache, offset, offset + 12); } else { throw new BSONError('Argument passed in does not match the accepted types'); } - // If we are caching the hex string - if (ObjectId.cacheHexString) { - this.__id = ByteUtils.toHex(this.id); + // If we are caching the buffer + if (ObjectId.cacheBuffer) { + this.buffer = bufferCache || ByteUtils.fromHex(this.__id); } } @@ -133,29 +169,31 @@ export class ObjectId extends BSONValue { * @readonly */ get id(): Uint8Array { - return this.buffer; + return this.buffer || ByteUtils.fromHex(this.__id); } set id(value: Uint8Array) { - this.buffer = value; - if (ObjectId.cacheHexString) { - this.__id = ByteUtils.toHex(value); - } + this.__id = ByteUtils.toHex(value); } /** Returns the ObjectId id as a 24 lowercase character hex string representation */ toHexString(): string { - if (ObjectId.cacheHexString && this.__id) { - return this.__id; - } - - const hexString = ByteUtils.toHex(this.id); - - if (ObjectId.cacheHexString && !this.__id) { - this.__id = hexString; - } + return this.__id; + } - return hexString; + /** + * @internal + * Validates the input string is a valid hex representation of an ObjectId. + * If valid, returns the input string. Otherwise, returns false. + * Returned string is lowercase. + */ + private static validateHexString(input: string): false | string { + if (input == null) return false; + if (input.length !== 24) return false; + if (checkForHexRegExp.test(input)) return input; + const inputLower = input.toLowerCase(); + if (checkForHexRegExp.test(inputLower)) return inputLower; + return false; } /** @@ -167,39 +205,49 @@ export class ObjectId extends BSONValue { } /** - * Generate a 12 byte id buffer used in ObjectId's - * - * @param time - pass in a second based timestamp. + * Generates the hex timestamp from a second based number or the current time. + * @internal */ - static generate(time?: number): Uint8Array { + private static getTimeHex(time?: number): string { if ('number' !== typeof time) { time = Math.floor(Date.now() / 1000); + } else { + time = (time | 0) % 0xffffffff; } + if (!ObjectId.timeHexCache || time !== ObjectId.lastTimeGenerate) { + ObjectId.lastTimeGenerate = time; + // This is moderately expensive so we can cache this for repetitive calls + ObjectId.timeHexCache = time.toString(16); + // Dates before 1978-07-05T00:00:00.000Z can be represented in less than 8 hex digits so we need to padStart + if (ObjectId.timeHexCache.length < 8) { + ObjectId.timeHexCache = ObjectId.timeHexCache.padStart(8, '0'); + } + } + return ObjectId.timeHexCache; + } + + /** + * Generate a 12 byte id buffer used in ObjectId's + * + * @param time - pass in a second based timestamp. + */ + static generate(time?: number): string { const inc = ObjectId.getInc(); - const buffer = ByteUtils.allocateUnsafe(12); // 4-byte timestamp - NumberUtils.setInt32BE(buffer, 0, time); + const timeString = ObjectId.getTimeHex(time); // set PROCESS_UNIQUE if yet not initialized if (PROCESS_UNIQUE === null) { - PROCESS_UNIQUE = ByteUtils.randomBytes(5); + PROCESS_UNIQUE = ByteUtils.toHex(ByteUtils.randomBytes(5)); } - // 5-byte process unique - buffer[4] = PROCESS_UNIQUE[0]; - buffer[5] = PROCESS_UNIQUE[1]; - buffer[6] = PROCESS_UNIQUE[2]; - buffer[7] = PROCESS_UNIQUE[3]; - buffer[8] = PROCESS_UNIQUE[4]; - // 3-byte counter - buffer[11] = inc & 0xff; - buffer[10] = (inc >> 8) & 0xff; - buffer[9] = (inc >> 16) & 0xff; + const incString = inc.toString(16).padStart(6, '0'); - return buffer; + // Flatten concatenated string to save memory + return flattenString(timeString + PROCESS_UNIQUE + incString); } /** @@ -209,13 +257,13 @@ export class ObjectId extends BSONValue { toString(encoding?: 'hex' | 'base64'): string { // Is the id a buffer then use the buffer toString method to return the format if (encoding === 'base64') return ByteUtils.toBase64(this.id); - if (encoding === 'hex') return this.toHexString(); - return this.toHexString(); + if (encoding === 'hex') return this.__id; + return this.__id; } /** Converts to its JSON the 24 character hex string representation. */ toJSON(): string { - return this.toHexString(); + return this.__id; } /** @internal */ @@ -239,18 +287,16 @@ export class ObjectId extends BSONValue { } if (ObjectId.is(otherId)) { - return ( - this.buffer[11] === otherId.buffer[11] && ByteUtils.equals(this.buffer, otherId.buffer) - ); + return this.__id === otherId.__id; } if (typeof otherId === 'string') { - return otherId.toLowerCase() === this.toHexString(); + return otherId === this.__id || otherId.toLowerCase() === this.__id; } if (typeof otherId === 'object' && typeof otherId.toHexString === 'function') { const otherIdString = otherId.toHexString(); - const thisIdString = this.toHexString(); + const thisIdString = this.__id; return typeof otherIdString === 'string' && otherIdString.toLowerCase() === thisIdString; } @@ -259,10 +305,7 @@ export class ObjectId extends BSONValue { /** Returns the generation date (accurate up to the second) that this ID was generated. */ getTimestamp(): Date { - const timestamp = new Date(); - const time = NumberUtils.getUint32BE(this.buffer, 0); - timestamp.setTime(Math.floor(time) * 1000); - return timestamp; + return new Date(parseInt(this.__id.substring(0, 8), 16) * 1000); } /** @internal */ @@ -272,18 +315,26 @@ export class ObjectId extends BSONValue { /** @internal */ serializeInto(uint8array: Uint8Array, index: number): 12 { - uint8array[index] = this.buffer[0]; - uint8array[index + 1] = this.buffer[1]; - uint8array[index + 2] = this.buffer[2]; - uint8array[index + 3] = this.buffer[3]; - uint8array[index + 4] = this.buffer[4]; - uint8array[index + 5] = this.buffer[5]; - uint8array[index + 6] = this.buffer[6]; - uint8array[index + 7] = this.buffer[7]; - uint8array[index + 8] = this.buffer[8]; - uint8array[index + 9] = this.buffer[9]; - uint8array[index + 10] = this.buffer[10]; - uint8array[index + 11] = this.buffer[11]; + let temp = parseInt(this.__id.substring(0, 8), 16); + + uint8array[index + 3] = temp & 0xff; + uint8array[index + 2] = (temp >> 8) & 0xff; + uint8array[index + 1] = (temp >> 16) & 0xff; + uint8array[index] = (temp >> 24) & 0xff; + + temp = parseInt(this.__id.substring(8, 16), 16); + + uint8array[index + 7] = temp & 0xff; + uint8array[index + 6] = (temp >> 8) & 0xff; + uint8array[index + 5] = (temp >> 16) & 0xff; + uint8array[index + 4] = (temp >> 24) & 0xff; + + temp = parseInt(this.__id.substring(16, 24), 16); + + uint8array[index + 11] = temp & 0xff; + uint8array[index + 10] = (temp >> 8) & 0xff; + uint8array[index + 9] = (temp >> 16) & 0xff; + uint8array[index + 8] = (temp >> 24) & 0xff; return 12; } @@ -293,12 +344,8 @@ export class ObjectId extends BSONValue { * @param time - an integer number representing a number of seconds. */ static createFromTime(time: number): ObjectId { - const buffer = ByteUtils.allocate(12); - for (let i = 11; i >= 4; i--) buffer[i] = 0; - // Encode time into first 4 bytes - NumberUtils.setInt32BE(buffer, 0, time); // Return the new objectId - return new ObjectId(buffer); + return new ObjectId(time); } /** @@ -311,7 +358,7 @@ export class ObjectId extends BSONValue { throw new BSONError('hex string must be 24 characters'); } - return new ObjectId(ByteUtils.fromHex(hexString)); + return new ObjectId(hexString); } /** Creates an ObjectId instance from a base64 string */ @@ -329,6 +376,7 @@ export class ObjectId extends BSONValue { */ static isValid(id: string | number | ObjectId | ObjectIdLike | Uint8Array): boolean { if (id == null) return false; + if (typeof id === 'string') return !!ObjectId.validateHexString(id); try { new ObjectId(id); @@ -340,13 +388,12 @@ export class ObjectId extends BSONValue { /** @internal */ toExtendedJSON(): ObjectIdExtended { - if (this.toHexString) return { $oid: this.toHexString() }; - return { $oid: this.toString('hex') }; + return { $oid: this.__id }; } /** @internal */ static fromExtendedJSON(doc: ObjectIdExtended): ObjectId { - return new ObjectId(doc.$oid); + return new ObjectId(doc.$oid, OID_SKIP_VALIDATE); } /** @@ -356,6 +403,6 @@ export class ObjectId extends BSONValue { */ inspect(depth?: number, options?: unknown, inspect?: InspectFn): string { inspect ??= defaultInspect; - return `new ObjectId(${inspect(this.toHexString(), options)})`; + return `new ObjectId(${inspect(this.__id, options)})`; } } diff --git a/src/parser/deserializer.ts b/src/parser/deserializer.ts index 165a529c..4d14859a 100644 --- a/src/parser/deserializer.ts +++ b/src/parser/deserializer.ts @@ -263,9 +263,7 @@ function deserializeObject( value = ByteUtils.toUTF8(buffer, index, index + stringSize - 1, shouldValidateKey); index = index + stringSize; } else if (elementType === constants.BSON_DATA_OID) { - const oid = ByteUtils.allocateUnsafe(12); - for (let i = 0; i < 12; i++) oid[i] = buffer[index + i]; - value = new ObjectId(oid); + value = new ObjectId(buffer, index); index = index + 12; } else if (elementType === constants.BSON_DATA_INT && promoteValues === false) { value = new Int32(NumberUtils.getInt32LE(buffer, index)); @@ -608,9 +606,7 @@ function deserializeObject( index = index + stringSize; // Read the oid - const oidBuffer = ByteUtils.allocateUnsafe(12); - for (let i = 0; i < 12; i++) oidBuffer[i] = buffer[index + i]; - const oid = new ObjectId(oidBuffer); + const oid = new ObjectId(buffer, index); // Update the index index = index + 12; diff --git a/src/parser/serializer.ts b/src/parser/serializer.ts index 86490798..72757d8b 100644 --- a/src/parser/serializer.ts +++ b/src/parser/serializer.ts @@ -231,10 +231,9 @@ function serializeObjectId(buffer: Uint8Array, key: string, value: ObjectId, ind // Write the type buffer[index++] = constants.BSON_DATA_OID; // Number of written bytes - const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index); + index += ByteUtils.encodeUTF8Into(buffer, key, index); // Encode the name - index = index + numberOfWrittenBytes; buffer[index++] = 0; index += value.serializeInto(buffer, index); @@ -647,6 +646,8 @@ export function serializeInto( index = serializeNull(buffer, key, value, index); } else if (value === null) { index = serializeNull(buffer, key, value, index); + } else if (value._bsontype === 'ObjectId') { + index = serializeObjectId(buffer, key, value, index); } else if (isUint8Array(value)) { index = serializeBuffer(buffer, key, value, index); } else if (value instanceof RegExp || isRegExp(value)) { @@ -668,8 +669,6 @@ export function serializeInto( 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') { @@ -757,6 +756,8 @@ export function serializeInto( index = serializeDate(buffer, key, value, index); } else if (value === null || (value === undefined && ignoreUndefined === false)) { index = serializeNull(buffer, key, value, index); + } else if (value._bsontype === 'ObjectId') { + index = serializeObjectId(buffer, key, value, index); } else if (isUint8Array(value)) { index = serializeBuffer(buffer, key, value, index); } else if (value instanceof RegExp || isRegExp(value)) { @@ -778,8 +779,6 @@ export function serializeInto( 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') { @@ -867,6 +866,8 @@ export function serializeInto( if (ignoreUndefined === false) index = serializeNull(buffer, key, value, index); } else if (value === null) { index = serializeNull(buffer, key, value, index); + } else if (value._bsontype === 'ObjectId') { + index = serializeObjectId(buffer, key, value, index); } else if (isUint8Array(value)) { index = serializeBuffer(buffer, key, value, index); } else if (value instanceof RegExp || isRegExp(value)) { @@ -888,8 +889,6 @@ export function serializeInto( 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') { diff --git a/src/utils/byte_utils.ts b/src/utils/byte_utils.ts index f3da53fd..4fb9d033 100644 --- a/src/utils/byte_utils.ts +++ b/src/utils/byte_utils.ts @@ -30,7 +30,7 @@ export type ByteUtils = { /** Create a Uint8Array from a hex string */ fromHex: (hex: string) => Uint8Array; /** Create a lowercase hex string from bytes */ - toHex: (buffer: Uint8Array) => string; + toHex: (buffer: Uint8Array, start?: number, end?: number) => string; /** Create a string from utf8 code units, fatal=true will throw an error if UTF-8 bytes are invalid, fatal=false will insert replacement characters */ toUTF8: (buffer: Uint8Array, start: number, end: number, fatal: boolean) => string; /** Get the utf8 code unit count from a string if it were to be transformed to utf8 */ diff --git a/src/utils/node_byte_utils.ts b/src/utils/node_byte_utils.ts index ca1482ca..24b17b9a 100644 --- a/src/utils/node_byte_utils.ts +++ b/src/utils/node_byte_utils.ts @@ -124,8 +124,8 @@ export const nodeJsByteUtils = { return Buffer.from(hex, 'hex'); }, - toHex(buffer: Uint8Array): string { - return nodeJsByteUtils.toLocalBufferType(buffer).toString('hex'); + toHex(buffer: NodeJsBuffer, start?: number, end?: number): string { + return nodeJsByteUtils.toLocalBufferType(buffer).toString('hex', start, end); }, toUTF8(buffer: Uint8Array, start: number, end: number, fatal: boolean): string { diff --git a/src/utils/string_utils.ts b/src/utils/string_utils.ts index 1ffb118e..671c3f9b 100644 --- a/src/utils/string_utils.ts +++ b/src/utils/string_utils.ts @@ -42,3 +42,17 @@ export function validateStringCharacters(str: string, radix?: number): false | s const regex = new RegExp(`[^-+${validCharacters}]`, 'i'); return regex.test(str) ? false : str; } + +/** + * @internal + * "flattens" a string that was created through concatenation of multiple strings. + * Most engines will try to optimize concatenation of strings using a "rope" graph of the substrings, + * This can lead to increased memory usage with extra pointers and performance issues when operating on these strings. + * `string.charAt(0)` forces the engine to flatten the string before performing the operation. + * See https://en.wikipedia.org/wiki/Rope_(data_structure) + * See https://docs.google.com/document/d/1o-MJPAddpfBfDZCkIHNKbMiM86iDFld7idGbNQLuKIQ + */ +export function flattenString(str: string): string { + str.charAt(0); + return str; +} diff --git a/src/utils/web_byte_utils.ts b/src/utils/web_byte_utils.ts index 0f79f0df..90f39a62 100644 --- a/src/utils/web_byte_utils.ts +++ b/src/utils/web_byte_utils.ts @@ -170,7 +170,7 @@ export const webByteUtils = { return Uint8Array.from(buffer); }, - toHex(uint8array: Uint8Array): string { + toHex(uint8array: Uint8Array, _start?: number, _end?: number): string { return Array.from(uint8array, byte => byte.toString(16).padStart(2, '0')).join(''); }, diff --git a/test/node/bson_test.js b/test/node/bson_test.js index 2f495da7..12a9a810 100644 --- a/test/node/bson_test.js +++ b/test/node/bson_test.js @@ -1661,18 +1661,12 @@ describe('BSON', function () { expect(__id).to.equal(a.toHexString()); // number - var genTime = a.generationTime; - a = new ObjectId(genTime); + a = new ObjectId(Date.now()); __id = a.__id; expect(__id).to.equal(a.toHexString()); - // generationTime - delete a.__id; - a.generationTime = genTime; - expect(__id).to.equal(a.toHexString()); - // createFromTime - a = ObjectId.createFromTime(genTime); + a = ObjectId.createFromTime(Date.now()); __id = a.__id; expect(__id).to.equal(a.toHexString()); ObjectId.cacheHexString = false; diff --git a/test/node/object_id.test.ts b/test/node/object_id.test.ts index 62344c3a..912d9f70 100644 --- a/test/node/object_id.test.ts +++ b/test/node/object_id.test.ts @@ -306,11 +306,31 @@ describe('ObjectId', function () { done(); }); + it('should correctly create ObjectId from valid Buffer and offset', function (done) { + if (!Buffer.from) return done(); + let a = 'AAAAAAAAAAAAAAAAAAAAAAAA'; + let b = new ObjectId(Buffer.from(`aaaa${a}aaaa`, 'hex'), 2); + let c = b.equals(a); // => false + expect(true).to.equal(c); + + a = 'aaaaaaaaaaaaaaaaaaaaaaaa'; + b = new ObjectId(Buffer.from(`AAAA${a}AAAA`, 'hex'), 2); + c = b.equals(a); // => true + expect(a).to.equal(b.toString()); + expect(true).to.equal(c); + done(); + }); + it('should throw an error if invalid Buffer passed in', function () { const a = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]); expect(() => new ObjectId(a)).to.throw(BSONError); }); + it('should throw an error if invalid Buffer offset passed in', function () { + const a = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]); + expect(() => new ObjectId(a, 5)).to.throw(BSONError); + }); + it('should correctly allow for node.js inspect to work with ObjectId', function (done) { const a = 'AAAAAAAAAAAAAAAAAAAAAAAA'; const b = new ObjectId(a); @@ -375,7 +395,7 @@ describe('ObjectId', function () { */ const oidString = '6b61666665656b6c61746368'; const oid = new ObjectId(oidString); - const oidKId = 'buffer'; + const oidKId = '__id'; it('should return false for an undefined otherId', () => { // otherId === undefined || otherId === null expect(oid.equals(null)).to.be.false; @@ -415,33 +435,21 @@ describe('ObjectId', function () { it('should not rely on toString for otherIds that are instanceof ObjectId', () => { // Note: the method access the symbol prop directly instead of the getter - const equalId = { toString: () => oidString + 'wrong', [oidKId]: oid.id }; + const equalId = { toString: () => oidString + 'wrong', [oidKId]: oid.toHexString() }; Object.setPrototypeOf(equalId, ObjectId.prototype); expect(oid.toString()).to.not.equal(equalId.toString()); expect(oid.equals(equalId)).to.be.true; }); - it('should use otherId[kId] Buffer for equality when otherId has _bsontype === ObjectId', () => { - let equalId = { _bsontype: 'ObjectId', [oidKId]: oid.id }; - - const propAccessRecord: string[] = []; - equalId = new Proxy(equalId, { - get(target, prop: string, recv) { - if (prop !== '_bsontype') { - propAccessRecord.push(prop); - } - return Reflect.get(target, prop, recv); - } - }); + it('should use otherId[kId] for equality when otherId has _bsontype === ObjectId', () => { + const equalId = { _bsontype: 'ObjectId', [oidKId]: oid.toHexString() }; expect(oid.equals(equalId)).to.be.true; - // once for the 11th byte shortcut - // once for the total equality - expect(propAccessRecord).to.deep.equal([oidKId, oidKId]); }); }); it('should return the same instance if a buffer is passed in', function () { + ObjectId.cacheBuffer = true; const inBuffer = Buffer.from('00'.repeat(12), 'hex'); const outBuffer = new ObjectId(inBuffer); @@ -452,6 +460,7 @@ describe('ObjectId', function () { expect(inBuffer).to.deep.equal(outBuffer.id); // class method equality expect(Buffer.prototype.equals.call(inBuffer, outBuffer.id)).to.be.true; + ObjectId.cacheBuffer = false; }); context('createFromHexString()', () => {