diff --git a/packages/browser-repl/src/components/utils/inspect.ts b/packages/browser-repl/src/components/utils/inspect.ts index 0bc0e56b48..2f4734bbe0 100644 --- a/packages/browser-repl/src/components/utils/inspect.ts +++ b/packages/browser-repl/src/components/utils/inspect.ts @@ -1,3 +1,4 @@ +import type { CustomInspectFunction } from 'util'; import { inspect as utilInspect } from 'util'; import { bsonStringifiers } from '@mongosh/service-provider-core'; @@ -14,18 +15,16 @@ import { bsonStringifiers } from '@mongosh/service-provider-core'; const customInspect = utilInspect.custom || 'inspect'; const visitedObjects = new WeakSet(); -function tryAddInspect( - obj: any, - stringifier: (this: any, depth: any, options: any) => string -): void { +function tryAddInspect(obj: unknown, stringifier: CustomInspectFunction): void { try { Object.defineProperty(obj, customInspect, { writable: true, configurable: true, enumerable: false, - value: function (...args: [any, any]) { + value: function (...args: Parameters): string { try { return stringifier.call(this, ...args); + // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (err: any) { // eslint-disable-next-line no-console console.warn('Could not inspect bson object', { obj: this, err }); diff --git a/packages/service-provider-core/src/printable-bson.spec.ts b/packages/service-provider-core/src/printable-bson.spec.ts index d52059bcfa..911fd34888 100644 --- a/packages/service-provider-core/src/printable-bson.spec.ts +++ b/packages/service-provider-core/src/printable-bson.spec.ts @@ -95,6 +95,34 @@ describe('BSON printers', function () { ).to.equal("MD5('0123456789abcdef0123456789abcdef')"); }); + describe('with Vector types', function () { + it('formats Int8Array correctly', function () { + expect( + inspect(bson.Binary.fromInt8Array(new Int8Array([1, 2, 3]))) + ).to.equal('Binary.fromInt8Array(new Int8Array([ 1, 2, 3 ]))'); + }); + + it('formats PackedBits correctly', function () { + expect( + inspect(bson.Binary.fromPackedBits(new Uint8Array([1, 2, 3]))) + ).to.equal('Binary.fromPackedBits(new Uint8Array([ 1, 2, 3 ]))'); + }); + + it('formats Float32Array correctly', function () { + expect( + inspect( + bson.Binary.fromFloat32Array( + new Float32Array([1.1111, 2.2222, 3.3333]) + ), + { compact: true } + ) + ).matches( + // Precision is lost because of float handling, so we use regex to match + /Binary.fromFloat32Array\(new Float32Array\(\[ 1\.1\d*, 2\.2\d*, 3\.3\d* \]\)\)/ + ); + }); + }); + it('formats any other value with the new format using createfromBase64', function () { expect( inspect(bson.Binary.createFromBase64('SGVsbG8sIFdvcmxkIQo=')) diff --git a/packages/service-provider-core/src/printable-bson.ts b/packages/service-provider-core/src/printable-bson.ts index d402ce53d9..874c1de871 100644 --- a/packages/service-provider-core/src/printable-bson.ts +++ b/packages/service-provider-core/src/printable-bson.ts @@ -1,4 +1,6 @@ import { bson as BSON } from './bson-export'; +import type { InspectOptionsStylized, CustomInspectFunction } from 'util'; +import { inspect as utilInspect } from 'util'; const inspectCustom = Symbol.for('nodejs.util.inspect.custom'); type BSONClassKey = (typeof BSON)[Exclude< keyof typeof BSON, @@ -7,26 +9,73 @@ type BSONClassKey = (typeof BSON)[Exclude< // Turn e.g. 'new Double(...)' into 'Double(...)' but preserve possible leading whitespace function removeNewFromInspectResult(str: string): string { - return String(str).replace(/^(\s*)(new )/, '$1'); + return str.replace(/^(\s*)(new )/, '$1'); +} + +/** Typed array such as Int8Array have a format like 'Int8Array(3) [1, 2, 3]' + * and we want to remove the prefix and keep just the array contents. */ +function removeTypedArrayPrefixFromInspectResult(str: string): string { + return str.replace(/^\s*\S+\s*\(\d+\)\s*/, ''); } // Create a Node.js-util-inspect() style custom inspect function that // strips 'new ' from inspect results but otherwise uses the Node.js // driver's bson library's inspect functions. -function makeClasslessInspect(className: K) { +function makeClasslessInspect( + className: K +): CustomInspectFunction { const originalInspect = BSON[className].prototype.inspect; return function ( this: (typeof BSON)[typeof className]['prototype'], - ...args: any + ...args: Parameters ) { - return removeNewFromInspectResult(originalInspect.apply(this, args as any)); - }; + return removeNewFromInspectResult(originalInspect.apply(this, args)); + } satisfies CustomInspectFunction; } const binaryInspect = makeClasslessInspect('Binary'); + +const binaryVectorInspect = function ( + this: typeof BSON.Binary.prototype, + depth: number, + options: InspectOptionsStylized +): string { + switch (this.buffer[0]) { + case BSON.Binary.VECTOR_TYPE.Int8: + return `Binary.fromInt8Array(new Int8Array(${removeTypedArrayPrefixFromInspectResult( + utilInspect(this.toInt8Array(), { + depth, + ...options, + // These arrays can be very large, so would prefer to use the default options instead. + maxArrayLength: utilInspect.defaultOptions.maxArrayLength, + }) + )}))`; + case BSON.Binary.VECTOR_TYPE.Float32: + return `Binary.fromFloat32Array(new Float32Array(${removeTypedArrayPrefixFromInspectResult( + utilInspect(this.toFloat32Array(), { + depth, + ...options, + // These arrays can be very large, so would prefer to use the default options instead. + maxArrayLength: utilInspect.defaultOptions.maxArrayLength, + }) + )}))`; + case BSON.Binary.VECTOR_TYPE.PackedBit: + return `Binary.fromPackedBits(new Uint8Array(${removeTypedArrayPrefixFromInspectResult( + utilInspect(this.toPackedBits(), { + depth, + ...options, + // These arrays can be very large, so would prefer to use the default options instead. + maxArrayLength: utilInspect.defaultOptions.maxArrayLength, + }) + )}))`; + default: + return binaryInspect.call(this, depth, options); + } +} satisfies CustomInspectFunction; + export const bsonStringifiers: Record< BSONClassKey | 'ObjectID', - (this: any, depth: any, options: any) => string + CustomInspectFunction > = { ObjectId: makeClasslessInspect('ObjectId'), ObjectID: makeClasslessInspect('ObjectId'), @@ -43,10 +92,13 @@ export const bsonStringifiers: Record< BSONRegExp: makeClasslessInspect('BSONRegExp'), Binary: function ( this: typeof BSON.Binary.prototype, - ...args: any[] + ...args: Parameters ): string { const hexString = this.toString('hex'); + switch (this.sub_type) { + case BSON.Binary.SUBTYPE_VECTOR: + return binaryVectorInspect.apply(this, args); case BSON.Binary.SUBTYPE_MD5: return `MD5('${hexString}')`; case BSON.Binary.SUBTYPE_UUID: @@ -64,7 +116,7 @@ export const bsonStringifiers: Record< default: return binaryInspect.apply(this, args); } - }, + } satisfies CustomInspectFunction, }; /**