From 4dbd896a02fc99ff622601b8dadc7f18f8ca0cbc Mon Sep 17 00:00:00 2001 From: bailey Date: Wed, 28 Jan 2026 15:23:53 -0700 Subject: [PATCH 1/8] initial POC --- .eslintrc.json | 5 +- src/cmap/auth/gssapi.ts | 11 +-- src/cmap/connection.ts | 21 +++--- src/cmap/handshake/client_metadata.ts | 6 +- src/connection_string.ts | 11 +++ src/index.ts | 1 + src/mongo_client.ts | 72 ++++++++++--------- test/unit/assorted/optional_require.test.ts | 4 +- test/unit/cmap/connect.test.ts | 17 +++-- .../cmap/handshake/client_metadata.test.ts | 60 +++++++++------- test/unit/sdam/topology.test.ts | 8 ++- 11 files changed, 133 insertions(+), 83 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index d009780f37..95c6e99a81 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -276,7 +276,8 @@ "patterns": [ "**/../lib/**", "mongodb-mock-server", - "node:*" + "node:*", + "os" ], "paths": [ { @@ -327,4 +328,4 @@ } } ] -} +} \ No newline at end of file diff --git a/src/cmap/auth/gssapi.ts b/src/cmap/auth/gssapi.ts index d18cb6b360..0154057919 100644 --- a/src/cmap/auth/gssapi.ts +++ b/src/cmap/auth/gssapi.ts @@ -1,5 +1,4 @@ import * as dns from 'dns'; -import * as os from 'os'; import { getKerberos, type Kerberos, type KerberosClient } from '../../deps'; import { MongoInvalidArgumentError, MongoMissingCredentialsError } from '../../error'; @@ -69,9 +68,13 @@ export class GSSAPI extends AuthProvider { } } -async function makeKerberosClient(authContext: AuthContext): Promise { - const { hostAddress } = authContext.options; - const { credentials } = authContext; +async function makeKerberosClient({ + options: { + hostAddress, + runtime: { os } + }, + credentials +}: AuthContext): Promise { if (!hostAddress || typeof hostAddress.host !== 'string' || !credentials) { throw new MongoInvalidArgumentError( 'Connection must have host and port and credentials defined.' diff --git a/src/cmap/connection.ts b/src/cmap/connection.ts index 9652e3a5e4..c53c86d7ed 100644 --- a/src/cmap/connection.ts +++ b/src/cmap/connection.ts @@ -35,6 +35,7 @@ import { type MongoClientAuthProviders } from '../mongo_client_auth_providers'; import { MongoLoggableComponent, type MongoLogger, SeverityLevel } from '../mongo_logger'; import { type Abortable, type CancellationToken, TypedEventEmitter } from '../mongo_types'; import { ReadPreference, type ReadPreferenceLike } from '../read_preference'; +import { type Runtime } from '../runtime_adapters'; import { ServerType } from '../sdam/common'; import { applySession, type ClientSession, updateSessionFromResponse } from '../sessions'; import { type TimeoutContext, TimeoutError } from '../timeout'; @@ -118,8 +119,8 @@ export interface ProxyOptions { /** @public */ export interface ConnectionOptions extends SupportedNodeConnectionOptions, - StreamDescriptionOptions, - ProxyOptions { + StreamDescriptionOptions, + ProxyOptions { // Internal creation info id: number | ''; generation: number; @@ -143,6 +144,8 @@ export interface ConnectionOptions metadata: Promise; /** @internal */ mongoLogger?: MongoLogger | undefined; + /** @internal */ + runtime: Runtime; } /** @public */ @@ -526,10 +529,10 @@ export class Connection extends TypedEventEmitter { options.documentsReturnedIn == null || !options.raw ? options : { - ...options, - raw: false, - fieldsAsRaw: { [options.documentsReturnedIn]: true } - }; + ...options, + raw: false, + fieldsAsRaw: { [options.documentsReturnedIn]: true } + }; /** MongoDBResponse instance or subclass */ let document: MongoDBResponse | undefined = undefined; @@ -692,9 +695,9 @@ export class Connection extends TypedEventEmitter { options.agreedCompressor === 'none' || !OpCompressedRequest.canCompress(command) ? command : new OpCompressedRequest(command, { - agreedCompressor: options.agreedCompressor ?? 'none', - zlibCompressionLevel: options.zlibCompressionLevel ?? 0 - }); + agreedCompressor: options.agreedCompressor ?? 'none', + zlibCompressionLevel: options.zlibCompressionLevel ?? 0 + }); const buffer = Buffer.concat(await finalCommand.toBin()); diff --git a/src/cmap/handshake/client_metadata.ts b/src/cmap/handshake/client_metadata.ts index 48cb6a4735..3b79e1df48 100644 --- a/src/cmap/handshake/client_metadata.ts +++ b/src/cmap/handshake/client_metadata.ts @@ -1,4 +1,3 @@ -import * as os from 'os'; import * as process from 'process'; import { BSON, type Document, Int32, NumberUtils } from '../../bson'; @@ -96,7 +95,8 @@ export class LimitedSizeDocument { } } -type MakeClientMetadataOptions = Pick; +type MakeClientMetadataOptions = Pick; + /** * From the specs: * Implementors SHOULD cumulatively update fields in the following order until the document is under the size limit: @@ -107,7 +107,7 @@ type MakeClientMetadataOptions = Pick; */ export async function makeClientMetadata( driverInfoList: DriverInfo[], - { appName = '' }: MakeClientMetadataOptions + { appName = '', runtime: { os } }: MakeClientMetadataOptions ): Promise { const metadataDocument = new LimitedSizeDocument(512); diff --git a/src/connection_string.ts b/src/connection_string.ts index df6dfc607a..ce63e99ad9 100644 --- a/src/connection_string.ts +++ b/src/connection_string.ts @@ -20,6 +20,7 @@ import { import { MongoLoggableComponent, MongoLogger, SeverityLevel } from './mongo_logger'; import { ReadConcern, type ReadConcernLevel } from './read_concern'; import { ReadPreference, type ReadPreferenceMode } from './read_preference'; +import { type Runtime } from './runtime_adapters'; import { ServerMonitoringMode } from './sdam/monitor'; import type { TagSet } from './sdam/server_description'; import { @@ -538,6 +539,13 @@ export function parseOptions( } ); + const runtime: Runtime = { + // eslint-disable-next-line @typescript-eslint/no-require-imports + os: options.runtimeAdapters?.os ?? require('os') + }; + + mongoOptions.runtime = runtime; + return mongoOptions; } @@ -1061,6 +1069,9 @@ export const OPTIONS = { default: true, type: 'boolean' }, + runtimeAdapters: { + type: 'record' + }, serializeFunctions: { type: 'boolean' }, diff --git a/src/index.ts b/src/index.ts index 8f5c4cfa60..74803dfa2a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -562,6 +562,7 @@ export type { ReadPreferenceLikeOptions, ReadPreferenceOptions } from './read_preference'; +export type { OsAdapter, Runtime, RuntimeAdapters } from './runtime_adapters'; export type { ClusterTime } from './sdam/common'; export type { Monitor, diff --git a/src/mongo_client.ts b/src/mongo_client.ts index 970f0f8806..1ac02e93c6 100644 --- a/src/mongo_client.ts +++ b/src/mongo_client.ts @@ -46,6 +46,7 @@ import { EndSessionsOperation } from './operations/end_sessions'; import { executeOperation } from './operations/execute_operation'; import type { ReadConcern, ReadConcernLevel, ReadConcernLike } from './read_concern'; import { ReadPreference, type ReadPreferenceMode } from './read_preference'; +import { type Runtime, type RuntimeAdapters } from './runtime_adapters'; import type { ServerMonitoringMode } from './sdam/monitor'; import type { TagSet } from './sdam/server_description'; import { DeprioritizedServers, readPreferenceServerSelector } from './sdam/server_selection'; @@ -318,6 +319,8 @@ export interface MongoClientOptions extends BSONSerializeOptions, SupportedNodeC connectionType?: typeof Connection; /** @internal */ __skipPingOnConnect?: boolean; + /** @experimental */ + runtimeAdapters?: RuntimeAdapters; } /** @public */ @@ -1032,39 +1035,39 @@ export class MongoClient extends TypedEventEmitter implements */ export interface MongoOptions extends Required< - Pick< - MongoClientOptions, - | 'autoEncryption' - | 'connectTimeoutMS' - | 'directConnection' - | 'driverInfo' - | 'forceServerObjectId' - | 'minHeartbeatFrequencyMS' - | 'heartbeatFrequencyMS' - | 'localThresholdMS' - | 'maxConnecting' - | 'maxIdleTimeMS' - | 'maxPoolSize' - | 'minPoolSize' - | 'monitorCommands' - | 'noDelay' - | 'pkFactory' - | 'raw' - | 'replicaSet' - | 'retryReads' - | 'retryWrites' - | 'serverSelectionTimeoutMS' - | 'socketTimeoutMS' - | 'srvMaxHosts' - | 'srvServiceName' - | 'tlsAllowInvalidCertificates' - | 'tlsAllowInvalidHostnames' - | 'tlsInsecure' - | 'waitQueueTimeoutMS' - | 'zlibCompressionLevel' - > - >, - SupportedNodeConnectionOptions { + Pick< + MongoClientOptions, + | 'autoEncryption' + | 'connectTimeoutMS' + | 'directConnection' + | 'driverInfo' + | 'forceServerObjectId' + | 'minHeartbeatFrequencyMS' + | 'heartbeatFrequencyMS' + | 'localThresholdMS' + | 'maxConnecting' + | 'maxIdleTimeMS' + | 'maxPoolSize' + | 'minPoolSize' + | 'monitorCommands' + | 'noDelay' + | 'pkFactory' + | 'raw' + | 'replicaSet' + | 'retryReads' + | 'retryWrites' + | 'serverSelectionTimeoutMS' + | 'socketTimeoutMS' + | 'srvMaxHosts' + | 'srvServiceName' + | 'tlsAllowInvalidCertificates' + | 'tlsAllowInvalidHostnames' + | 'tlsInsecure' + | 'waitQueueTimeoutMS' + | 'zlibCompressionLevel' + > + >, + SupportedNodeConnectionOptions { appName?: string; hosts: HostAddress[]; srvHost?: string; @@ -1152,4 +1155,7 @@ export interface MongoOptions timeoutMS?: number; /** @internal */ __skipPingOnConnect?: boolean; + + /** @internal */ + runtime: Runtime; } diff --git a/test/unit/assorted/optional_require.test.ts b/test/unit/assorted/optional_require.test.ts index 5dc579ee30..f6772baf2d 100644 --- a/test/unit/assorted/optional_require.test.ts +++ b/test/unit/assorted/optional_require.test.ts @@ -41,7 +41,9 @@ describe('optionalRequire', function () { const gssapi = new GSSAPI(); const error = await gssapi - .auth(new AuthContext(null, true, { hostAddress: new HostAddress('a'), credentials: true })) + .auth(new AuthContext(null, true, { + hostAddress: new HostAddress('a'), credentials: true, runtime: { os: require('os') } + })) .then( () => null, e => e diff --git a/test/unit/cmap/connect.test.ts b/test/unit/cmap/connect.test.ts index a97cb7194a..145d9f0ff3 100644 --- a/test/unit/cmap/connect.test.ts +++ b/test/unit/cmap/connect.test.ts @@ -210,7 +210,9 @@ describe('Connect Tests', function () { connection: {}, options: { ...CONNECT_DEFAULTS, - metadata: makeClientMetadata([], {}) + metadata: makeClientMetadata([], { + runtime: { os: require('os') } + }) } }; }); @@ -239,7 +241,9 @@ describe('Connect Tests', function () { name: 's'.repeat(128) } ], - { appName: longAppName } + { + appName: longAppName, runtime: { os: require('os') } + } ); const longAuthContext = { connection: {}, @@ -267,7 +271,9 @@ describe('Connect Tests', function () { connection: {}, options: { ...CONNECT_DEFAULTS, - metadata: makeClientMetadata([], {}) + metadata: makeClientMetadata([], { + runtime: { os: require('os') } + }) } }; }); @@ -296,7 +302,10 @@ describe('Connect Tests', function () { name: 's'.repeat(128) } ], - { appName: longAppName } + { + appName: longAppName, + runtime: { os: require('os') } + } ); const longAuthContext = { connection: {}, diff --git a/test/unit/cmap/handshake/client_metadata.test.ts b/test/unit/cmap/handshake/client_metadata.test.ts index 6128838448..b4b19b1720 100644 --- a/test/unit/cmap/handshake/client_metadata.test.ts +++ b/test/unit/cmap/handshake/client_metadata.test.ts @@ -12,6 +12,11 @@ import { makeClientMetadata } from '../../../../src/cmap/handshake/client_metadata'; import { MongoInvalidArgumentError } from '../../../../src/error'; +import { Runtime } from '../../../../src'; + +const runtime: Runtime = { + os: require('os') +}; describe('client metadata module', () => { afterEach(() => sinon.restore()); @@ -141,7 +146,7 @@ describe('client metadata module', () => { describe('makeClientMetadata()', () => { context('when no FAAS environment is detected', () => { it('does not append FAAS metadata', async () => { - const metadata = await makeClientMetadata([], {}); + const metadata = await makeClientMetadata([], { runtime }); expect(metadata).not.to.have.property( 'env', 'faas metadata applied in a non-faas environment' @@ -164,14 +169,14 @@ describe('client metadata module', () => { context('when driverInfo.platform is provided', () => { it('throws an error if driverInfo.platform is too large', async () => { - const error = await makeClientMetadata([{ platform: 'a'.repeat(512) }], {}).catch(e => e); + const error = await makeClientMetadata([{ platform: 'a'.repeat(512) }], { runtime }).catch(e => e); expect(error) .to.be.instanceOf(MongoInvalidArgumentError) .to.match(/platform/); }); it('appends driverInfo.platform to the platform field', async () => { - const metadata = await makeClientMetadata([{ platform: 'myPlatform' }], {}); + const metadata = await makeClientMetadata([{ platform: 'myPlatform' }], { runtime }); expect(metadata).to.deep.equal({ driver: { name: 'nodejs', @@ -190,12 +195,12 @@ describe('client metadata module', () => { context('when driverInfo.name is provided', () => { it('throws an error if driverInfo.name is too large', async () => { - const error = await makeClientMetadata([{ name: 'a'.repeat(512) }], {}).catch(e => e); + const error = await makeClientMetadata([{ name: 'a'.repeat(512) }], { runtime }).catch(e => e); expect(error).to.be.instanceOf(MongoInvalidArgumentError).to.match(/name/); }); it('appends driverInfo.name to the driver.name field', async () => { - const metadata = await makeClientMetadata([{ name: 'myName' }], {}); + const metadata = await makeClientMetadata([{ name: 'myName' }], { runtime }); expect(metadata).to.deep.equal({ driver: { name: 'nodejs|myName', @@ -214,14 +219,14 @@ describe('client metadata module', () => { context('when driverInfo.version is provided', () => { it('throws an error if driverInfo.version is too large', async () => { - const error = await makeClientMetadata([{ version: 'a'.repeat(512) }], {}).catch(e => e); + const error = await makeClientMetadata([{ version: 'a'.repeat(512) }], { runtime }).catch(e => e); expect(error) .to.be.instanceOf(MongoInvalidArgumentError) .to.match(/version/); }); it('appends driverInfo.version to the version field', async () => { - const metadata = await makeClientMetadata([{ version: 'myVersion' }], {}); + const metadata = await makeClientMetadata([{ version: 'myVersion' }], { runtime }); expect(metadata).to.deep.equal({ driver: { name: 'nodejs', @@ -240,7 +245,7 @@ describe('client metadata module', () => { context('when no custom driverInto is provided', () => { it('does not append the driver info to the metadata', async () => { - const metadata = await makeClientMetadata([], {}); + const metadata = await makeClientMetadata([], { runtime }); expect(metadata).to.deep.equal({ driver: { name: 'nodejs', @@ -257,7 +262,7 @@ describe('client metadata module', () => { }); it('does not set the application field', async () => { - const metadata = await makeClientMetadata([], {}); + const metadata = await makeClientMetadata([], { runtime }); expect(metadata).not.to.have.property('application'); }); }); @@ -267,6 +272,7 @@ describe('client metadata module', () => { it('truncates the application name to <=128 bytes', async () => { const longString = 'a'.repeat(300); const metadata = await makeClientMetadata([], { + runtime, appName: longString }); expect(metadata.application?.name).to.be.a('string'); @@ -283,6 +289,7 @@ describe('client metadata module', () => { it('truncates the application name to 129 bytes', async () => { const longString = '€'.repeat(300); const metadata = await makeClientMetadata([], { + runtime, appName: longString }); @@ -298,6 +305,7 @@ describe('client metadata module', () => { context('when the app name is under 128 bytes', () => { it('sets the application name to the value', async () => { const metadata = await makeClientMetadata([], { + runtime, appName: 'myApplication' }); expect(metadata.application?.name).to.equal('myApplication'); @@ -313,37 +321,37 @@ describe('client metadata module', () => { it('sets platform to Deno', async () => { globalThis.Deno = { version: { deno: '1.2.3' } }; - const metadata = await makeClientMetadata([], {}); + const metadata = await makeClientMetadata([], { runtime }); expect(metadata.platform).to.equal('Deno v1.2.3, LE'); }); it('sets platform to Deno with driverInfo.platform', async () => { globalThis.Deno = { version: { deno: '1.2.3' } }; - const metadata = await makeClientMetadata([{ platform: 'myPlatform' }], {}); + const metadata = await makeClientMetadata([{ platform: 'myPlatform' }], { runtime }); expect(metadata.platform).to.equal('Deno v1.2.3, LE|myPlatform'); }); it('ignores version if Deno.version.deno is not a string', async () => { globalThis.Deno = { version: { deno: 1 } }; - const metadata = await makeClientMetadata([], {}); + const metadata = await makeClientMetadata([], { runtime }); expect(metadata.platform).to.equal('Deno v0.0.0-unknown, LE'); }); it('ignores version if Deno.version does not have a deno property', async () => { globalThis.Deno = { version: { somethingElse: '1.2.3' } }; - const metadata = await makeClientMetadata([], {}); + const metadata = await makeClientMetadata([], { runtime }); expect(metadata.platform).to.equal('Deno v0.0.0-unknown, LE'); }); it('ignores version if Deno.version is null', async () => { globalThis.Deno = { version: null }; - const metadata = await makeClientMetadata([], {}); + const metadata = await makeClientMetadata([], { runtime }); expect(metadata.platform).to.equal('Deno v0.0.0-unknown, LE'); }); it('ignores version if Deno is nullish', async () => { globalThis.Deno = null; - const metadata = await makeClientMetadata([], {}); + const metadata = await makeClientMetadata([], { runtime }); expect(metadata.platform).to.equal('Deno v0.0.0-unknown, LE'); }); }); @@ -357,7 +365,7 @@ describe('client metadata module', () => { globalThis.Bun = class { static version = '1.2.3'; }; - const metadata = await makeClientMetadata([], {}); + const metadata = await makeClientMetadata([], { runtime }); expect(metadata.platform).to.equal('Bun v1.2.3, LE'); }); @@ -365,7 +373,7 @@ describe('client metadata module', () => { globalThis.Bun = class { static version = '1.2.3'; }; - const metadata = await makeClientMetadata([{ platform: 'myPlatform' }], {}); + const metadata = await makeClientMetadata([{ platform: 'myPlatform' }], { runtime }); expect(metadata.platform).to.equal('Bun v1.2.3, LE|myPlatform'); }); @@ -373,7 +381,7 @@ describe('client metadata module', () => { globalThis.Bun = class { static version = 1; }; - const metadata = await makeClientMetadata([], {}); + const metadata = await makeClientMetadata([], { runtime }); expect(metadata.platform).to.equal('Bun v0.0.0-unknown, LE'); }); @@ -381,13 +389,13 @@ describe('client metadata module', () => { globalThis.Bun = class { static version = 1; }; - const metadata = await makeClientMetadata([{ platform: 'myPlatform' }], {}); + const metadata = await makeClientMetadata([{ platform: 'myPlatform' }], { runtime }); expect(metadata.platform).to.equal('Bun v0.0.0-unknown, LE|myPlatform'); }); it('ignores version if Bun is nullish', async () => { globalThis.Bun = null; - const metadata = await makeClientMetadata([{ platform: 'myPlatform' }], {}); + const metadata = await makeClientMetadata([{ platform: 'myPlatform' }], { runtime }); expect(metadata.platform).to.equal('Bun v0.0.0-unknown, LE|myPlatform'); }); }); @@ -508,7 +516,7 @@ describe('client metadata module', () => { }); it(`returns ${inspect(outcome)} under env property`, async () => { - const { env } = await makeClientMetadata([], {}); + const { env } = await makeClientMetadata([], { runtime }); expect(env).to.deep.equal(outcome); }); @@ -532,7 +540,7 @@ describe('client metadata module', () => { }); it('does not attach it to the metadata', async () => { - expect(await makeClientMetadata([], {})).not.to.have.nested.property('aws.memory_mb'); + expect(await makeClientMetadata([], { runtime })).not.to.have.nested.property('aws.memory_mb'); }); }); }); @@ -547,7 +555,7 @@ describe('client metadata module', () => { }); it('only includes env.name', async () => { - const metadata = await makeClientMetadata([], {}); + const metadata = await makeClientMetadata([], { runtime }); expect(metadata).to.not.have.nested.property('env.region'); expect(metadata).to.have.nested.property('env.name', 'aws.lambda'); expect(metadata.env).to.have.all.keys('name'); @@ -565,7 +573,7 @@ describe('client metadata module', () => { }); it('only includes env.name', async () => { - const metadata = await makeClientMetadata([], {}); + const metadata = await makeClientMetadata([], { runtime }); expect(metadata).to.have.property('env'); expect(metadata).to.have.nested.property('env.region', 'abc'); expect(metadata.os).to.have.all.keys('type'); @@ -582,7 +590,7 @@ describe('client metadata module', () => { }); it('omits os information', async () => { - const metadata = await makeClientMetadata([], {}); + const metadata = await makeClientMetadata([], { runtime }); expect(metadata).to.not.have.property('os'); }); }); @@ -598,7 +606,7 @@ describe('client metadata module', () => { }); it('omits the faas env', async () => { - const metadata = await makeClientMetadata([{ name: 'a'.repeat(350) }], {}); + const metadata = await makeClientMetadata([{ name: 'a'.repeat(350) }], { runtime }); expect(metadata).to.not.have.property('env'); }); }); diff --git a/test/unit/sdam/topology.test.ts b/test/unit/sdam/topology.test.ts index 1444e1a40c..1dab4f6a93 100644 --- a/test/unit/sdam/topology.test.ts +++ b/test/unit/sdam/topology.test.ts @@ -29,6 +29,11 @@ import { TimeoutContext } from '../../../src/timeout'; import { isHello, ns } from '../../../src/utils'; import * as mock from '../../tools/mongodb-mock/index'; import { topologyWithPlaceholderClient } from '../../tools/utils'; +import { Runtime } from '../../../src'; + +const runtime: Runtime = { + os: require('os') +}; describe('Topology (unit)', function () { let client, topology; @@ -56,6 +61,7 @@ describe('Topology (unit)', function () { it('should correctly pass appname', async function () { const topology: Topology = topologyWithPlaceholderClient([`localhost:27017`], { metadata: makeClientMetadata([], { + runtime, appName: 'My application name' }) }); @@ -120,7 +126,7 @@ describe('Topology (unit)', function () { }); const server = await topology.selectServer('primary', { timeoutContext: ctx, - operationName: 'none' + operationName: 'none', }); const err = await server .command(new RunCursorCommandOperation(ns('admin.$cmd'), { ping: 1 }, {}), ctx) From b790827671322da3d67e0ae7c23841c93b8a32dd Mon Sep 17 00:00:00 2001 From: bailey Date: Wed, 28 Jan 2026 15:28:13 -0700 Subject: [PATCH 2/8] lint --- src/cmap/connection.ts | 18 ++--- src/mongo_client.ts | 66 +++++++++---------- src/runtime_adapters.ts | 25 +++++++ test/tools/utils.ts | 6 ++ test/unit/assorted/optional_require.test.ts | 11 +++- test/unit/cmap/connect.test.ts | 10 +-- .../cmap/handshake/client_metadata.test.ts | 21 +++--- test/unit/sdam/topology.test.ts | 9 +-- 8 files changed, 101 insertions(+), 65 deletions(-) create mode 100644 src/runtime_adapters.ts diff --git a/src/cmap/connection.ts b/src/cmap/connection.ts index c53c86d7ed..dfffb15dae 100644 --- a/src/cmap/connection.ts +++ b/src/cmap/connection.ts @@ -119,8 +119,8 @@ export interface ProxyOptions { /** @public */ export interface ConnectionOptions extends SupportedNodeConnectionOptions, - StreamDescriptionOptions, - ProxyOptions { + StreamDescriptionOptions, + ProxyOptions { // Internal creation info id: number | ''; generation: number; @@ -529,10 +529,10 @@ export class Connection extends TypedEventEmitter { options.documentsReturnedIn == null || !options.raw ? options : { - ...options, - raw: false, - fieldsAsRaw: { [options.documentsReturnedIn]: true } - }; + ...options, + raw: false, + fieldsAsRaw: { [options.documentsReturnedIn]: true } + }; /** MongoDBResponse instance or subclass */ let document: MongoDBResponse | undefined = undefined; @@ -695,9 +695,9 @@ export class Connection extends TypedEventEmitter { options.agreedCompressor === 'none' || !OpCompressedRequest.canCompress(command) ? command : new OpCompressedRequest(command, { - agreedCompressor: options.agreedCompressor ?? 'none', - zlibCompressionLevel: options.zlibCompressionLevel ?? 0 - }); + agreedCompressor: options.agreedCompressor ?? 'none', + zlibCompressionLevel: options.zlibCompressionLevel ?? 0 + }); const buffer = Buffer.concat(await finalCommand.toBin()); diff --git a/src/mongo_client.ts b/src/mongo_client.ts index 1ac02e93c6..75a2bc9fd2 100644 --- a/src/mongo_client.ts +++ b/src/mongo_client.ts @@ -1035,39 +1035,39 @@ export class MongoClient extends TypedEventEmitter implements */ export interface MongoOptions extends Required< - Pick< - MongoClientOptions, - | 'autoEncryption' - | 'connectTimeoutMS' - | 'directConnection' - | 'driverInfo' - | 'forceServerObjectId' - | 'minHeartbeatFrequencyMS' - | 'heartbeatFrequencyMS' - | 'localThresholdMS' - | 'maxConnecting' - | 'maxIdleTimeMS' - | 'maxPoolSize' - | 'minPoolSize' - | 'monitorCommands' - | 'noDelay' - | 'pkFactory' - | 'raw' - | 'replicaSet' - | 'retryReads' - | 'retryWrites' - | 'serverSelectionTimeoutMS' - | 'socketTimeoutMS' - | 'srvMaxHosts' - | 'srvServiceName' - | 'tlsAllowInvalidCertificates' - | 'tlsAllowInvalidHostnames' - | 'tlsInsecure' - | 'waitQueueTimeoutMS' - | 'zlibCompressionLevel' - > - >, - SupportedNodeConnectionOptions { + Pick< + MongoClientOptions, + | 'autoEncryption' + | 'connectTimeoutMS' + | 'directConnection' + | 'driverInfo' + | 'forceServerObjectId' + | 'minHeartbeatFrequencyMS' + | 'heartbeatFrequencyMS' + | 'localThresholdMS' + | 'maxConnecting' + | 'maxIdleTimeMS' + | 'maxPoolSize' + | 'minPoolSize' + | 'monitorCommands' + | 'noDelay' + | 'pkFactory' + | 'raw' + | 'replicaSet' + | 'retryReads' + | 'retryWrites' + | 'serverSelectionTimeoutMS' + | 'socketTimeoutMS' + | 'srvMaxHosts' + | 'srvServiceName' + | 'tlsAllowInvalidCertificates' + | 'tlsAllowInvalidHostnames' + | 'tlsInsecure' + | 'waitQueueTimeoutMS' + | 'zlibCompressionLevel' + > + >, + SupportedNodeConnectionOptions { appName?: string; hosts: HostAddress[]; srvHost?: string; diff --git a/src/runtime_adapters.ts b/src/runtime_adapters.ts new file mode 100644 index 0000000000..3ad5e8751c --- /dev/null +++ b/src/runtime_adapters.ts @@ -0,0 +1,25 @@ +/** + * @public + * @experimental + */ +export type OsAdapter = Pick; + +/** + * @public + * @experimental + * + * This type represents the interface that the driver needs from the runtime in order to function. + */ +export interface RuntimeAdapters { + os?: OsAdapter; +} + +/** + * @internal + * + * Represents a complete, parsed set of runtime adapters. After options parsing, all adapters + * are always present (either using the user's provided adapter, or defaulting to Nodejs' module). + */ +export interface Runtime { + os: OsAdapter; +} diff --git a/test/tools/utils.ts b/test/tools/utils.ts index 5a23fdd970..97def9d179 100644 --- a/test/tools/utils.ts +++ b/test/tools/utils.ts @@ -7,6 +7,7 @@ import * as path from 'node:path'; import { EJSON } from 'bson'; import * as BSON from 'bson'; import { expect } from 'chai'; +import * as os from 'os'; import * as process from 'process'; import { Readable } from 'stream'; import { setTimeout } from 'timers'; @@ -18,6 +19,7 @@ import { type HostAddress, MongoClient, type MongoClientOptions, + type Runtime, type ServerApiVersion, type TopologyOptions } from '../../src'; @@ -604,3 +606,7 @@ export function configureMongocryptdSpawnHooks( port }; } + +export const runtime: Runtime = { + os +}; diff --git a/test/unit/assorted/optional_require.test.ts b/test/unit/assorted/optional_require.test.ts index f6772baf2d..463f7f95ff 100644 --- a/test/unit/assorted/optional_require.test.ts +++ b/test/unit/assorted/optional_require.test.ts @@ -7,6 +7,7 @@ import { GSSAPI } from '../../../src/cmap/auth/gssapi'; import { compress } from '../../../src/cmap/wire_protocol/compression'; import { MongoMissingDependencyError } from '../../../src/error'; import { HostAddress } from '../../../src/utils'; +import { runtime } from '../../tools/utils'; function moduleExistsSync(moduleName) { return existsSync(resolve(__dirname, `../../../node_modules/${moduleName}`)); @@ -41,9 +42,13 @@ describe('optionalRequire', function () { const gssapi = new GSSAPI(); const error = await gssapi - .auth(new AuthContext(null, true, { - hostAddress: new HostAddress('a'), credentials: true, runtime: { os: require('os') } - })) + .auth( + new AuthContext(null, true, { + hostAddress: new HostAddress('a'), + credentials: true, + runtime + }) + ) .then( () => null, e => e diff --git a/test/unit/cmap/connect.test.ts b/test/unit/cmap/connect.test.ts index 145d9f0ff3..cd205de976 100644 --- a/test/unit/cmap/connect.test.ts +++ b/test/unit/cmap/connect.test.ts @@ -15,6 +15,7 @@ import { CancellationToken } from '../../../src/mongo_types'; import { HostAddress, isHello } from '../../../src/utils'; import { genClusterTime } from '../../tools/common'; import * as mock from '../../tools/mongodb-mock/index'; +import { runtime } from '../../tools/utils'; const CONNECT_DEFAULTS = { id: 1, @@ -211,7 +212,7 @@ describe('Connect Tests', function () { options: { ...CONNECT_DEFAULTS, metadata: makeClientMetadata([], { - runtime: { os: require('os') } + runtime }) } }; @@ -242,7 +243,8 @@ describe('Connect Tests', function () { } ], { - appName: longAppName, runtime: { os: require('os') } + appName: longAppName, + runtime } ); const longAuthContext = { @@ -272,7 +274,7 @@ describe('Connect Tests', function () { options: { ...CONNECT_DEFAULTS, metadata: makeClientMetadata([], { - runtime: { os: require('os') } + runtime }) } }; @@ -304,7 +306,7 @@ describe('Connect Tests', function () { ], { appName: longAppName, - runtime: { os: require('os') } + runtime } ); const longAuthContext = { diff --git a/test/unit/cmap/handshake/client_metadata.test.ts b/test/unit/cmap/handshake/client_metadata.test.ts index b4b19b1720..87fd3efcfb 100644 --- a/test/unit/cmap/handshake/client_metadata.test.ts +++ b/test/unit/cmap/handshake/client_metadata.test.ts @@ -12,11 +12,6 @@ import { makeClientMetadata } from '../../../../src/cmap/handshake/client_metadata'; import { MongoInvalidArgumentError } from '../../../../src/error'; -import { Runtime } from '../../../../src'; - -const runtime: Runtime = { - os: require('os') -}; describe('client metadata module', () => { afterEach(() => sinon.restore()); @@ -169,7 +164,9 @@ describe('client metadata module', () => { context('when driverInfo.platform is provided', () => { it('throws an error if driverInfo.platform is too large', async () => { - const error = await makeClientMetadata([{ platform: 'a'.repeat(512) }], { runtime }).catch(e => e); + const error = await makeClientMetadata([{ platform: 'a'.repeat(512) }], { runtime }).catch( + e => e + ); expect(error) .to.be.instanceOf(MongoInvalidArgumentError) .to.match(/platform/); @@ -195,7 +192,9 @@ describe('client metadata module', () => { context('when driverInfo.name is provided', () => { it('throws an error if driverInfo.name is too large', async () => { - const error = await makeClientMetadata([{ name: 'a'.repeat(512) }], { runtime }).catch(e => e); + const error = await makeClientMetadata([{ name: 'a'.repeat(512) }], { runtime }).catch( + e => e + ); expect(error).to.be.instanceOf(MongoInvalidArgumentError).to.match(/name/); }); @@ -219,7 +218,9 @@ describe('client metadata module', () => { context('when driverInfo.version is provided', () => { it('throws an error if driverInfo.version is too large', async () => { - const error = await makeClientMetadata([{ version: 'a'.repeat(512) }], { runtime }).catch(e => e); + const error = await makeClientMetadata([{ version: 'a'.repeat(512) }], { runtime }).catch( + e => e + ); expect(error) .to.be.instanceOf(MongoInvalidArgumentError) .to.match(/version/); @@ -540,7 +541,9 @@ describe('client metadata module', () => { }); it('does not attach it to the metadata', async () => { - expect(await makeClientMetadata([], { runtime })).not.to.have.nested.property('aws.memory_mb'); + expect(await makeClientMetadata([], { runtime })).not.to.have.nested.property( + 'aws.memory_mb' + ); }); }); }); diff --git a/test/unit/sdam/topology.test.ts b/test/unit/sdam/topology.test.ts index 1dab4f6a93..8db64288bd 100644 --- a/test/unit/sdam/topology.test.ts +++ b/test/unit/sdam/topology.test.ts @@ -28,12 +28,7 @@ import { TopologyDescription } from '../../../src/sdam/topology_description'; import { TimeoutContext } from '../../../src/timeout'; import { isHello, ns } from '../../../src/utils'; import * as mock from '../../tools/mongodb-mock/index'; -import { topologyWithPlaceholderClient } from '../../tools/utils'; -import { Runtime } from '../../../src'; - -const runtime: Runtime = { - os: require('os') -}; +import { runtime, topologyWithPlaceholderClient } from '../../tools/utils'; describe('Topology (unit)', function () { let client, topology; @@ -126,7 +121,7 @@ describe('Topology (unit)', function () { }); const server = await topology.selectServer('primary', { timeoutContext: ctx, - operationName: 'none', + operationName: 'none' }); const err = await server .command(new RunCursorCommandOperation(ns('admin.$cmd'), { ping: 1 }, {}), ctx) From 1d1c89ec58a6a4b5fa2ef4c866d82503a108bdd1 Mon Sep 17 00:00:00 2001 From: bailey Date: Wed, 28 Jan 2026 15:33:38 -0700 Subject: [PATCH 3/8] add adapter unit test --- test/unit/runtime_adapters.test.ts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 test/unit/runtime_adapters.test.ts diff --git a/test/unit/runtime_adapters.test.ts b/test/unit/runtime_adapters.test.ts new file mode 100644 index 0000000000..d7ee11a70f --- /dev/null +++ b/test/unit/runtime_adapters.test.ts @@ -0,0 +1,30 @@ +import { expect } from "chai" +import { MongoClient, OsAdapter } from "../../src" +import * as os from 'os'; + +describe('Runtime Adapters tests', function () { + describe('`os`', function () { + describe('when no os adapter is provided', function () { + it(`defaults to Node's os module`, function () { + const client = new MongoClient('mongodb://localhost:27017'); + + expect(client.options.runtime.os).to.equal(os); + }) + }) + + describe('when an os adapter is provided', function () { + it(`uses the user provided adapter`, function () { + const osAdapter: OsAdapter = { + ...os + }; + const client = new MongoClient('mongodb://localhost:27017', { + runtimeAdapters: { + os: osAdapter + } + }); + + expect(client.options.runtime.os).to.equal(osAdapter); + }) + }) + }) +}) \ No newline at end of file From 58d8976ce7f3d0b7580859c9ded15871e77c6fd1 Mon Sep 17 00:00:00 2001 From: bailey Date: Wed, 28 Jan 2026 15:46:30 -0700 Subject: [PATCH 4/8] unit tests and integration tests --- .../connection.test.ts | 15 ++++-- .../cmap/handshake/client_metadata.test.ts | 1 + test/unit/runtime_adapters.test.ts | 49 ++++++++++--------- 3 files changed, 37 insertions(+), 28 deletions(-) diff --git a/test/integration/connection-monitoring-and-pooling/connection.test.ts b/test/integration/connection-monitoring-and-pooling/connection.test.ts index a20f36c7b6..b6aaf75969 100644 --- a/test/integration/connection-monitoring-and-pooling/connection.test.ts +++ b/test/integration/connection-monitoring-and-pooling/connection.test.ts @@ -22,7 +22,7 @@ import { LEGACY_HELLO_COMMAND } from '../../../src/constants'; import { Topology } from '../../../src/sdam/topology'; import { HostAddress, ns } from '../../../src/utils'; import * as mock from '../../tools/mongodb-mock/index'; -import { processTick, sleep } from '../../tools/utils'; +import { processTick, runtime, sleep } from '../../tools/utils'; import { assert as test, setupDatabase } from '../shared'; const commonConnectOptions = { @@ -49,7 +49,10 @@ describe('Connection', function () { ...commonConnectOptions, connectionType: Connection, ...this.configuration.options, - metadata: makeClientMetadata([], {}) + metadata: makeClientMetadata([], { + runtime + }), + runtime }; let conn; @@ -71,7 +74,8 @@ describe('Connection', function () { connectionType: Connection, ...this.configuration.options, monitorCommands: true, - metadata: makeClientMetadata([], {}) + runtime, + metadata: makeClientMetadata([], { runtime }) }; let conn; @@ -102,7 +106,10 @@ describe('Connection', function () { connectionType: Connection, ...this.configuration.options, monitorCommands: true, - metadata: makeClientMetadata([], {}) + runtime, + metadata: makeClientMetadata([], { + runtime + }) }; let conn; diff --git a/test/unit/cmap/handshake/client_metadata.test.ts b/test/unit/cmap/handshake/client_metadata.test.ts index 87fd3efcfb..a7c5e864d7 100644 --- a/test/unit/cmap/handshake/client_metadata.test.ts +++ b/test/unit/cmap/handshake/client_metadata.test.ts @@ -12,6 +12,7 @@ import { makeClientMetadata } from '../../../../src/cmap/handshake/client_metadata'; import { MongoInvalidArgumentError } from '../../../../src/error'; +import { runtime } from '../../../tools/utils'; describe('client metadata module', () => { afterEach(() => sinon.restore()); diff --git a/test/unit/runtime_adapters.test.ts b/test/unit/runtime_adapters.test.ts index d7ee11a70f..3980f9f1d7 100644 --- a/test/unit/runtime_adapters.test.ts +++ b/test/unit/runtime_adapters.test.ts @@ -1,30 +1,31 @@ -import { expect } from "chai" -import { MongoClient, OsAdapter } from "../../src" +import { expect } from 'chai'; import * as os from 'os'; +import { MongoClient, type OsAdapter } from '../../src'; + describe('Runtime Adapters tests', function () { - describe('`os`', function () { - describe('when no os adapter is provided', function () { - it(`defaults to Node's os module`, function () { - const client = new MongoClient('mongodb://localhost:27017'); + describe('`os`', function () { + describe('when no os adapter is provided', function () { + it(`defaults to Node's os module`, function () { + const client = new MongoClient('mongodb://localhost:27017'); - expect(client.options.runtime.os).to.equal(os); - }) - }) + expect(client.options.runtime.os).to.equal(os); + }); + }); - describe('when an os adapter is provided', function () { - it(`uses the user provided adapter`, function () { - const osAdapter: OsAdapter = { - ...os - }; - const client = new MongoClient('mongodb://localhost:27017', { - runtimeAdapters: { - os: osAdapter - } - }); + describe('when an os adapter is provided', function () { + it(`uses the user provided adapter`, function () { + const osAdapter: OsAdapter = { + ...os + }; + const client = new MongoClient('mongodb://localhost:27017', { + runtimeAdapters: { + os: osAdapter + } + }); - expect(client.options.runtime.os).to.equal(osAdapter); - }) - }) - }) -}) \ No newline at end of file + expect(client.options.runtime.os).to.equal(osAdapter); + }); + }); + }); +}); From 9e0e0ff1ca9fbdad1ccfe49d6dfcabea10738787 Mon Sep 17 00:00:00 2001 From: bailey Date: Mon, 2 Feb 2026 11:21:42 -0700 Subject: [PATCH 5/8] cleanup implementation, fix tests --- src/connection_string.ts | 9 ++------- src/mongo_client.ts | 6 +++++- src/runtime_adapters.ts | 29 ++++++++++++++++++++++++++--- test/tools/utils.ts | 9 +++++---- 4 files changed, 38 insertions(+), 15 deletions(-) diff --git a/src/connection_string.ts b/src/connection_string.ts index ce63e99ad9..06315a9686 100644 --- a/src/connection_string.ts +++ b/src/connection_string.ts @@ -20,7 +20,7 @@ import { import { MongoLoggableComponent, MongoLogger, SeverityLevel } from './mongo_logger'; import { ReadConcern, type ReadConcernLevel } from './read_concern'; import { ReadPreference, type ReadPreferenceMode } from './read_preference'; -import { type Runtime } from './runtime_adapters'; +import { resolveRuntimeAdapters } from './runtime_adapters'; import { ServerMonitoringMode } from './sdam/monitor'; import type { TagSet } from './sdam/server_description'; import { @@ -539,12 +539,7 @@ export function parseOptions( } ); - const runtime: Runtime = { - // eslint-disable-next-line @typescript-eslint/no-require-imports - os: options.runtimeAdapters?.os ?? require('os') - }; - - mongoOptions.runtime = runtime; + mongoOptions.runtime = resolveRuntimeAdapters(options); return mongoOptions; } diff --git a/src/mongo_client.ts b/src/mongo_client.ts index 75a2bc9fd2..87d969fee9 100644 --- a/src/mongo_client.ts +++ b/src/mongo_client.ts @@ -319,7 +319,11 @@ export interface MongoClientOptions extends BSONSerializeOptions, SupportedNodeC connectionType?: typeof Connection; /** @internal */ __skipPingOnConnect?: boolean; - /** @experimental */ + /** + * @experimental + * + * If provided, any adapters provided will be used in place of the corresponding Node.js module. + */ runtimeAdapters?: RuntimeAdapters; } diff --git a/src/runtime_adapters.ts b/src/runtime_adapters.ts index 3ad5e8751c..43f48f3934 100644 --- a/src/runtime_adapters.ts +++ b/src/runtime_adapters.ts @@ -1,14 +1,24 @@ +/* eslint-disable no-restricted-imports */ +// We squash the restricted import errors here because we are using type-only imports, which +// do not impact the driver's actual runtime dependencies. + +import type * as os from 'os'; + +import { type MongoClientOptions } from './mongo_client'; + /** * @public * @experimental + * + * Represents the set of dependencies that the driver uses from the [Node.js OS module](https://nodejs.org/api/os.html). */ -export type OsAdapter = Pick; +export type OsAdapter = Pick; /** * @public * @experimental * - * This type represents the interface that the driver needs from the runtime in order to function. + * This type represents the set of dependencies that the driver needs from the Javascript runtime in order to function. */ export interface RuntimeAdapters { os?: OsAdapter; @@ -18,8 +28,21 @@ export interface RuntimeAdapters { * @internal * * Represents a complete, parsed set of runtime adapters. After options parsing, all adapters - * are always present (either using the user's provided adapter, or defaulting to Nodejs' module). + * are always present (either using the user's provided adapter, or defaulting to the Node.js module). */ export interface Runtime { os: OsAdapter; } + +/** + * @internal + * + * Given a MongoClientOptions, this function resolves the set of runtime options, providing Nodejs implementations if + * not provided by in `options`, and returns a `Runtime`. + */ +export function resolveRuntimeAdapters(options: MongoClientOptions): Runtime { + return { + // eslint-disable-next-line @typescript-eslint/no-require-imports + os: options.runtimeAdapters?.os ?? require('os') + }; +} diff --git a/test/tools/utils.ts b/test/tools/utils.ts index 97def9d179..d3948d773c 100644 --- a/test/tools/utils.ts +++ b/test/tools/utils.ts @@ -7,7 +7,6 @@ import * as path from 'node:path'; import { EJSON } from 'bson'; import * as BSON from 'bson'; import { expect } from 'chai'; -import * as os from 'os'; import * as process from 'process'; import { Readable } from 'stream'; import { setTimeout } from 'timers'; @@ -24,6 +23,7 @@ import { type TopologyOptions } from '../../src'; import { OP_MSG } from '../../src/cmap/wire_protocol/constants'; +import { resolveRuntimeAdapters } from '../../src/runtime_adapters'; import { Topology } from '../../src/sdam/topology'; import { processTimeMS } from '../../src/utils'; import { type TestConfiguration } from './runner/config'; @@ -607,6 +607,7 @@ export function configureMongocryptdSpawnHooks( }; } -export const runtime: Runtime = { - os -}; +/** + * A `Runtime` that resolves to entirely Nodejs modules, useful when tests must provide a default `runtime` object to an API. + */ +export const runtime: Runtime = resolveRuntimeAdapters({}); From 0738ef88f3a7d519d049f7c88cec7093b975d9e9 Mon Sep 17 00:00:00 2001 From: bailey Date: Mon, 2 Feb 2026 13:30:18 -0700 Subject: [PATCH 6/8] comments --- src/runtime_adapters.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/runtime_adapters.ts b/src/runtime_adapters.ts index 43f48f3934..bb998d4bd0 100644 --- a/src/runtime_adapters.ts +++ b/src/runtime_adapters.ts @@ -1,6 +1,8 @@ -/* eslint-disable no-restricted-imports */ +/* eslint-disable no-restricted-imports, @typescript-eslint/no-require-imports */ + // We squash the restricted import errors here because we are using type-only imports, which // do not impact the driver's actual runtime dependencies. +// We also allow restricted imports in this file, because we expect this file to be the only place actually importing restricted Node APIs. import type * as os from 'os'; @@ -42,7 +44,6 @@ export interface Runtime { */ export function resolveRuntimeAdapters(options: MongoClientOptions): Runtime { return { - // eslint-disable-next-line @typescript-eslint/no-require-imports os: options.runtimeAdapters?.os ?? require('os') }; } From 358ede2cc24b0fab4ed5cb9272fbfa36465c270d Mon Sep 17 00:00:00 2001 From: bailey Date: Mon, 2 Feb 2026 16:46:34 -0700 Subject: [PATCH 7/8] working POC bundle + context --- .gitignore | 1 + package-lock.json | 502 ++++++++++++++++++++++++- package.json | 6 +- test/integration/crud/crud_api.test.ts | 27 +- test/tools/runner/config.ts | 65 +++- 5 files changed, 576 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index ffc4acb3e9..9ba7a0df0e 100644 --- a/.gitignore +++ b/.gitignore @@ -108,3 +108,4 @@ uri.txt crypt_shared.sh *keytab +driver.bundle.js diff --git a/package-lock.json b/package-lock.json index cbf0eb13e7..27fbd15270 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,6 +37,7 @@ "chai": "^4.4.1", "chai-subset": "^1.6.0", "chalk": "^4.1.2", + "esbuild": "^0.27.2", "eslint": "^9.39.1", "eslint-config-prettier": "^10.1.8", "eslint-plugin-mocha": "^10.4.1", @@ -847,6 +848,7 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -1141,6 +1143,448 @@ "tslib": "^2.4.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", @@ -3025,7 +3469,8 @@ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.20.tgz", "integrity": "sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/chai-subset": { "version": "1.3.6", @@ -3127,6 +3572,7 @@ "integrity": "sha512-BICHQ67iqxQGFSzfCFTT7MRQ5XcBjG5aeKh5Ok38UBbPe5fxTyE+aHFxwVrGyr8GNlqFMLKD1D3P2K/1ks8tog==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -3258,6 +3704,7 @@ "integrity": "sha512-sbaQ27XBUopBkRiuY/P9sWGOWUW4rl8fDoHIUmLpZd8uldsTyB4/Zg6bWTegPoTLnKj9Hqgn3QD6cjPNB32Odw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.46.3", @@ -3288,6 +3735,7 @@ "integrity": "sha512-6m1I5RmHBGTnUGS113G04DMu3CpSdxCAU/UvtjNWL4Nuf3MW9tQhiJqRlHzChIkhy6kZSAQmc+I1bcGjE3yNKg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.3", "@typescript-eslint/types": "8.46.3", @@ -3533,6 +3981,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3593,6 +4042,7 @@ "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -3908,6 +4358,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", @@ -4085,6 +4536,7 @@ "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "assertion-error": "^1.1.0", "check-error": "^1.0.3", @@ -4655,6 +5107,48 @@ "dev": true, "license": "MIT" }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -4691,6 +5185,7 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -4751,6 +5246,7 @@ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -7325,6 +7821,7 @@ "integrity": "sha512-U42vQ4czpKa0QdI1hu950XuNhYqgoM+ZF1HT+VuUHL9hPfDPVvNQyltmMqdE9bUHMVa+8yNbc3QKTj8zQhlVxQ==", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "@istanbuljs/load-nyc-config": "^1.0.0", "@istanbuljs/schema": "^0.1.2", @@ -8009,6 +8506,7 @@ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -8864,6 +9362,7 @@ "integrity": "sha512-a2N2TDY1uGviajJ6r4D1CyRAkzE9NNVlYOV1wX5xQDuAk0ONgzgRl0EjCQuRCPxOwp13ghsMwt9Gdldujs39qw==", "dev": true, "license": "BSD-3-Clause", + "peer": true, "dependencies": { "@sinonjs/commons": "^3.0.1", "@sinonjs/fake-timers": "11.2.2", @@ -9646,6 +10145,7 @@ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/package.json b/package.json index 6daf61d6d5..00c194d4fb 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "chai": "^4.4.1", "chai-subset": "^1.6.0", "chalk": "^4.1.2", + "esbuild": "^0.27.2", "eslint": "^9.39.1", "eslint-config-prettier": "^10.1.8", "eslint-plugin-mocha": "^10.4.1", @@ -154,6 +155,9 @@ "check:csfle": "nyc mocha --config test/mocha_mongodb.js test/integration/client-side-encryption", "check:snappy": "nyc mocha test/unit/assorted/snappy.test.js", "check:x509": "nyc mocha test/manual/x509_auth.test.ts", + "check:runtime-independence": "ts-node test/tools/runner/vm_runner.ts test/integration/change-streams/change_stream.test.ts", + "bundle:driver": "node etc/bundle-driver.mjs", + "test:bundled": "npm run bundle:driver && npm run check:test", "fix:eslint": "npm run check:eslint -- --fix", "prepare": "node etc/prepare.js", "preview:docs": "ts-node etc/docs/preview.ts", @@ -170,4 +174,4 @@ "moduleResolution": "node" } } -} +} \ No newline at end of file diff --git a/test/integration/crud/crud_api.test.ts b/test/integration/crud/crud_api.test.ts index cedea9d867..d6029553c2 100644 --- a/test/integration/crud/crud_api.test.ts +++ b/test/integration/crud/crud_api.test.ts @@ -3,27 +3,37 @@ import { finished } from 'node:stream/promises'; import { expect } from 'chai'; import * as sinon from 'sinon'; -import { +import { loadContextifiedMongoDBModule } from '../../tools/runner/vm_context_helper'; +import { type FailCommandFailPoint } from '../../tools/utils'; +import { assert as test } from '../shared'; + +// Load MongoDB module in VM context +const mongodb = loadContextifiedMongoDBModule(); + +// Extract the exports we need from the contextified module +const { Collection, CommandFailedEvent, - type CommandStartedEvent, CommandSucceededEvent, - type Db, MongoBulkWriteError, - type MongoClient, + MongoClient, MongoServerError, ObjectId, ReturnDocument -} from '../../../src'; -import { type FailCommandFailPoint } from '../../tools/utils'; -import { assert as test } from '../shared'; +} = mongodb; + +type MongoClient = typeof mongodb.MongoClient.prototype; +type Db = typeof mongodb.Db.prototype; +type CommandStartedEvent = typeof mongodb.CommandStartedEvent.prototype; const DB_NAME = 'crud_api_tests'; -describe('CRUD API', function () { +describe.only('CRUD API', function () { let client: MongoClient; beforeEach(async function () { + this.configuration.mongodb = mongodb; + client = this.configuration.newClient(); client.s.options.dbName = DB_NAME; // setup the default db @@ -817,6 +827,7 @@ describe('CRUD API', function () { let collection: Collection; beforeEach(async function () { + this.configuration.mongodb = mongodb; client = this.configuration.newClient({ monitorCommands: true }); events = []; client.on('commandStarted', commandStarted => diff --git a/test/tools/runner/config.ts b/test/tools/runner/config.ts index adcb31674a..0e57a18a3e 100644 --- a/test/tools/runner/config.ts +++ b/test/tools/runner/config.ts @@ -114,20 +114,32 @@ export class TestConfiguration { filters: Record; compressor: CompressorName | null; + // Optional: contextified MongoDB module exports for VM-based testing + private mongodb?: any; + constructor( private uri: string, - private context: Record + private context: Record, + mongodb?: any // Optional contextified mongodb module ) { + this.mongodb = mongodb; + const url = new ConnectionString(uri); const { hosts } = url; - const hostAddresses = hosts.map(HostAddress.fromString); + const hostAddresses = hosts.map( + this.mongodb ? this.mongodb.HostAddress.fromString : HostAddress.fromString + ); this.version = context.version; this.clientSideEncryption = context.clientSideEncryption; this.cryptSharedVersion = context.cryptShared; this.parameters = { ...context.parameters }; this.singleMongosLoadBalancerUri = context.singleMongosLoadBalancerUri; this.multiMongosLoadBalancerUri = context.multiMongosLoadBalancerUri; - this.topologyType = this.isLoadBalanced ? TopologyType.LoadBalanced : context.topologyType; + this.topologyType = this.isLoadBalanced + ? this.mongodb + ? this.mongodb.TopologyType.LoadBalanced + : TopologyType.LoadBalanced + : context.topologyType; this.buildInfo = context.buildInfo; this.serverApi = context.serverApi; this.isSrv = uri.indexOf('mongodb+srv') > -1; @@ -229,6 +241,14 @@ export class TestConfiguration { serverOptions = Object.assign(baseOptions, getEnvironmentalOptions(), serverOptions); + // If using contextified mongodb, inject Node.js runtime adapters + if (this.mongodb) { + serverOptions.runtimeAdapters = { + os: require('os'), + ...serverOptions.runtimeAdapters + }; + } + if (this.loggingEnabled && !Object.hasOwn(serverOptions, 'mongodbLogPath')) { serverOptions = this.setupLogging(serverOptions); } @@ -239,7 +259,8 @@ export class TestConfiguration { throw new Error(`Cannot use options to specify host/port, must be in ${urlOrQueryOptions}`); } - return new MongoClient(urlOrQueryOptions, serverOptions); + const ClientConstructor = this.mongodb ? this.mongodb.MongoClient : MongoClient; + return new ClientConstructor(urlOrQueryOptions, serverOptions); } const queryOptions = urlOrQueryOptions ?? {}; @@ -283,7 +304,10 @@ export class TestConfiguration { delete queryOptions.writeConcern; } - if (this.topologyType === TopologyType.LoadBalanced) { + const LoadBalancedType = this.mongodb + ? this.mongodb.TopologyType.LoadBalanced + : TopologyType.LoadBalanced; + if (this.topologyType === LoadBalancedType) { queryOptions.loadBalanced = true; } @@ -317,7 +341,8 @@ export class TestConfiguration { const connectionString = url.format(urlOptions); - return new MongoClient(connectionString, serverOptions); + const ClientConstructor = this.mongodb ? this.mongodb.MongoClient : MongoClient; + return new ClientConstructor(connectionString, serverOptions); } /** @@ -439,7 +464,8 @@ export class TestConfiguration { } writeConcernMax(): { writeConcern: WriteConcernSettings } { - if (this.topologyType !== TopologyType.Single) { + const SingleType = this.mongodb ? this.mongodb.TopologyType.Single : TopologyType.Single; + if (this.topologyType !== SingleType) { return { writeConcern: { w: 'majority', wtimeoutMS: 30000 } }; } @@ -451,7 +477,7 @@ export class TestConfiguration { } makeAtlasTestConfiguration(): AtlasTestConfiguration { - return new AtlasTestConfiguration(this.uri, this.context); + return new AtlasTestConfiguration(this.uri, this.context, this.mongodb); } loggingEnabled = false; @@ -463,7 +489,8 @@ export class TestConfiguration { testsToEnableLogging = flakyTests; setupLogging(options: MongoClientOptions, id?: string) { - id ??= new ObjectId().toString(); + const ObjectIdConstructor = this.mongodb ? this.mongodb.ObjectId : ObjectId; + id ??= new ObjectIdConstructor().toString(); this.logs = []; const write = log => this.logs.push({ t: log.t, id, ...log }); options.mongodbLogPath = { write }; @@ -478,6 +505,9 @@ export class TestConfiguration { afterEachLogging(ctx: Context) { if (this.loggingEnabled && ctx.currentTest.state === 'failed') { + const LongConstructor = this.mongodb ? this.mongodb.Long : Long; + const DoubleConstructor = this.mongodb ? this.mongodb.Double : Double; + for (const log of this.logs) { console.error( JSON.stringify( @@ -486,12 +516,13 @@ export class TestConfiguration { if (types.isMap(value)) return { Map: Array.from(value.entries()) }; if (types.isSet(value)) return { Set: Array.from(value.values()) }; if (types.isNativeError(value)) return { [value.name]: util.inspect(value) }; - if (typeof value === 'bigint') return { bigint: new Long(value).toExtendedJSON() }; + if (typeof value === 'bigint') + return { bigint: new LongConstructor(value).toExtendedJSON() }; if (typeof value === 'symbol') return `Symbol(${value.description})`; if (typeof value === 'number') { if (Number.isNaN(value) || !Number.isFinite(value) || Object.is(value, -0)) // @ts-expect-error: toExtendedJSON internal on double but not on long - return { number: new Double(value).toExtendedJSON() }; + return { number: new DoubleConstructor(value).toExtendedJSON() }; } if (Buffer.isBuffer(value)) return { [value.constructor.name]: Buffer.prototype.base64Slice.call(value) }; @@ -515,8 +546,10 @@ export class TestConfiguration { */ export class AtlasTestConfiguration extends TestConfiguration { override newClient(): MongoClient { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return new MongoClient(process.env.MONGODB_URI!); + const ClientConstructor = (this as any).mongodb + ? (this as any).mongodb.MongoClient + : MongoClient; + return new ClientConstructor(process.env.MONGODB_URI!); } override url(): string { @@ -530,8 +563,10 @@ export class AtlasTestConfiguration extends TestConfiguration { */ export class AstrolabeTestConfiguration extends TestConfiguration { override newClient(): MongoClient { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return new MongoClient(process.env.DRIVERS_ATLAS_TESTING_URI!); + const ClientConstructor = (this as any).mongodb + ? (this as any).mongodb.MongoClient + : MongoClient; + return new ClientConstructor(process.env.DRIVERS_ATLAS_TESTING_URI!); } override url(): string { From c8e5888468835861e5c36e60cc25a37780687e3b Mon Sep 17 00:00:00 2001 From: bailey Date: Mon, 2 Feb 2026 16:46:45 -0700 Subject: [PATCH 8/8] working POC bundle + context --- etc/bundle-driver.mjs | 43 ++++++++ test/mongodb.ts | 140 +++++++++++++++++++++++++ test/tools/runner/vm_context_helper.ts | 129 +++++++++++++++++++++++ 3 files changed, 312 insertions(+) create mode 100755 etc/bundle-driver.mjs create mode 100644 test/mongodb.ts create mode 100644 test/tools/runner/vm_context_helper.ts diff --git a/etc/bundle-driver.mjs b/etc/bundle-driver.mjs new file mode 100755 index 0000000000..e12420ba2a --- /dev/null +++ b/etc/bundle-driver.mjs @@ -0,0 +1,43 @@ +#!/usr/bin/env node +import * as esbuild from 'esbuild'; +import { fileURLToPath } from 'node:url'; +import { isBuiltin } from 'node:module'; +import path from 'node:path'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const rootDir = path.join(__dirname, '..'); + +await esbuild.build({ + entryPoints: [path.join(rootDir, 'src/index.ts')], + bundle: true, + outfile: path.join(rootDir, 'test/tools/runner/driver.bundle.js'), + platform: 'node', + format: 'cjs', + target: 'node20', + external: [ + 'bson', + 'mongodb-connection-string-url', + '@mongodb-js/saslprep', + '@mongodb-js/zstd', + 'mongodb-client-encryption', + 'snappy', + '@napi-rs/snappy*', + 'kerberos', + 'gcp-metadata', + '@aws-sdk/credential-providers' + ], + plugins: [{ + name: 'externalize-node-builtins', + setup(build) { + build.onResolve({ filter: /.*/ }, args => { + if (isBuiltin(args.path)) { + return { path: args.path, external: true }; + } + }); + } + }], + sourcemap: 'inline', + logLevel: 'info' +}); + +console.log('✓ Driver bundle created at test/tools/runner/driver.bundle.js'); \ No newline at end of file diff --git a/test/mongodb.ts b/test/mongodb.ts new file mode 100644 index 0000000000..b289249eb6 --- /dev/null +++ b/test/mongodb.ts @@ -0,0 +1,140 @@ +import * as fs from 'node:fs'; +import * as path from 'node:path'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function printExports() { + function* walk(root: string): Generator { + const directoryContents = fs.readdirSync(root); + for (const filepath of directoryContents) { + const fullPath = path.join(root, filepath); + const stat = fs.statSync(fullPath); + if (stat.isDirectory()) { + yield* walk(fullPath); + } else if (stat.isFile()) { + yield fullPath; + } + } + } + const driverSourceFiles = Array.from(walk(path.resolve(__dirname, '..', 'src'))); + + for (const srcFile of driverSourceFiles) { + console.log(`export * from '${path.relative(__dirname, srcFile)}';`); + } +} + +export * from '../src/admin'; +export * from '../src/bson'; +export * from '../src/bulk/common'; +export * from '../src/bulk/ordered'; +export * from '../src/bulk/unordered'; +export * from '../src/change_stream'; +export * from '../src/client-side-encryption/auto_encrypter'; +export * from '../src/client-side-encryption/client_encryption'; +export * from '../src/client-side-encryption/errors'; +export * from '../src/client-side-encryption/mongocryptd_manager'; +export * from '../src/client-side-encryption/providers/aws'; +export * from '../src/client-side-encryption/providers/azure'; +export * from '../src/client-side-encryption/providers/gcp'; +export * from '../src/client-side-encryption/providers/index'; +export * from '../src/client-side-encryption/state_machine'; +export * from '../src/cmap/auth/auth_provider'; +export * from '../src/cmap/auth/aws_temporary_credentials'; +export * from '../src/cmap/auth/gssapi'; +export * from '../src/cmap/auth/mongo_credentials'; +export * from '../src/cmap/auth/mongodb_aws'; +export * from '../src/cmap/auth/mongodb_oidc'; +export * from '../src/cmap/auth/mongodb_oidc/automated_callback_workflow'; +export * from '../src/cmap/auth/mongodb_oidc/azure_machine_workflow'; +export * from '../src/cmap/auth/mongodb_oidc/callback_workflow'; +export * from '../src/cmap/auth/plain'; +export * from '../src/cmap/auth/providers'; +export * from '../src/cmap/auth/scram'; +export * from '../src/cmap/auth/x509'; +export * from '../src/cmap/command_monitoring_events'; +export * from '../src/cmap/commands'; +export * from '../src/cmap/connect'; +export * from '../src/cmap/connection'; +export * from '../src/cmap/connection_pool'; +export * from '../src/cmap/connection_pool_events'; +export * from '../src/cmap/errors'; +export * from '../src/cmap/handshake/client_metadata'; +export * from '../src/cmap/metrics'; +export * from '../src/cmap/stream_description'; +export * from '../src/cmap/wire_protocol/compression'; +export * from '../src/cmap/wire_protocol/constants'; +export * from '../src/cmap/wire_protocol/on_demand/document'; +export * from '../src/cmap/wire_protocol/responses'; +export * from '../src/cmap/wire_protocol/shared'; +export * from '../src/collection'; +export * from '../src/connection_string'; +export * from '../src/constants'; +export * from '../src/cursor/abstract_cursor'; +export * from '../src/cursor/aggregation_cursor'; +export * from '../src/cursor/change_stream_cursor'; +export * from '../src/cursor/find_cursor'; +export * from '../src/cursor/list_collections_cursor'; +export * from '../src/cursor/list_indexes_cursor'; +export * from '../src/cursor/run_command_cursor'; +export * from '../src/db'; +export * from '../src/deps'; +export * from '../src/encrypter'; +export * from '../src/error'; +export * from '../src/explain'; +export * from '../src/gridfs/download'; +export * from '../src/gridfs/index'; +export * from '../src/gridfs/upload'; +export * from '../src/mongo_client'; +export * from '../src/mongo_logger'; +export * from '../src/mongo_types'; +export * from '../src/operations/aggregate'; +export * from '../src/operations/client_bulk_write/command_builder'; +export * from '../src/operations/client_bulk_write/common'; +export * from '../src/operations/client_bulk_write/results_merger'; +export * from '../src/operations/command'; +export * from '../src/operations/count'; +export * from '../src/operations/create_collection'; +export * from '../src/operations/delete'; +export * from '../src/operations/distinct'; +export * from '../src/operations/drop'; +export * from '../src/operations/estimated_document_count'; +export * from '../src/operations/execute_operation'; +export * from '../src/operations/find'; +export * from '../src/operations/find_and_modify'; +export * from '../src/operations/get_more'; +export * from '../src/operations/indexes'; +export * from '../src/operations/insert'; +export * from '../src/operations/kill_cursors'; +export * from '../src/operations/list_collections'; +export * from '../src/operations/list_databases'; +export * from '../src/operations/operation'; +export * from '../src/operations/profiling_level'; +export * from '../src/operations/remove_user'; +export * from '../src/operations/rename'; +export * from '../src/operations/run_command'; +export * from '../src/operations/search_indexes/create'; +export * from '../src/operations/search_indexes/drop'; +export * from '../src/operations/search_indexes/update'; +export * from '../src/operations/set_profiling_level'; +export * from '../src/operations/stats'; +export * from '../src/operations/update'; +export * from '../src/operations/validate_collection'; +export * from '../src/read_concern'; +export * from '../src/read_preference'; +export * from '../src/sdam/common'; +export * from '../src/sdam/events'; +export * from '../src/sdam/monitor'; +export * from '../src/sdam/server'; +export * from '../src/sdam/server_description'; +export * from '../src/sdam/server_selection'; +export * from '../src/sdam/srv_polling'; +export * from '../src/sdam/topology'; +export * from '../src/sdam/topology_description'; +export * from '../src/sessions'; +export * from '../src/sort'; +export * from '../src/timeout'; +export * from '../src/transactions'; +export * from '../src/utils'; +export * from '../src/write_concern'; + +// Must be last for precedence +export * from '../src/index'; diff --git a/test/tools/runner/vm_context_helper.ts b/test/tools/runner/vm_context_helper.ts new file mode 100644 index 0000000000..fbb142828f --- /dev/null +++ b/test/tools/runner/vm_context_helper.ts @@ -0,0 +1,129 @@ +/* eslint-disable no-restricted-globals, @typescript-eslint/no-require-imports */ + +import * as fs from 'node:fs'; +import { isBuiltin } from 'node:module'; +import * as path from 'node:path'; +import * as vm from 'node:vm'; + +/** + * Creates a require function that blocks access to specified core modules + */ +function createRestrictedRequire() { + const blockedModules = new Set(['os']); + + return function restrictedRequire(moduleName: string) { + // Block core modules + if (isBuiltin(moduleName) && blockedModules.has(moduleName)) { + throw new Error(`Access to core module '${moduleName}' is restricted in this context`); + } + + return require(moduleName); + } as NodeRequire; +} + +// Create a sandbox context with necessary globals +const sandbox = vm.createContext({ + __proto__: null, + + // Console and timing + console: console, + AbortController: AbortController, + AbortSignal: AbortSignal, + Date: global.Date, + Error: global.Error, + URL: global.URL, + URLSearchParams: global.URLSearchParams, + queueMicrotask: queueMicrotask, + performance: global.performance, + setTimeout: global.setTimeout, + clearTimeout: global.clearTimeout, + setInterval: global.setInterval, + clearInterval: global.clearInterval, + setImmediate: global.setImmediate, + clearImmediate: global.clearImmediate, + + // Process + process: process, + + // Global objects needed for runtime + Buffer: Buffer, + Promise: Promise, + Map: Map, + Set: Set, + WeakMap: WeakMap, + WeakSet: WeakSet, + ArrayBuffer: ArrayBuffer, + SharedArrayBuffer: SharedArrayBuffer, + Atomics: Atomics, + DataView: DataView, + Int8Array: Int8Array, + Uint8Array: Uint8Array, + Uint8ClampedArray: Uint8ClampedArray, + Int16Array: Int16Array, + Uint16Array: Uint16Array, + Int32Array: Int32Array, + Uint32Array: Uint32Array, + Float32Array: Float32Array, + Float64Array: Float64Array, + BigInt64Array: BigInt64Array, + BigUint64Array: BigUint64Array, + + // Other necessary globals + TextEncoder: global.TextEncoder, + TextDecoder: global.TextDecoder, + BigInt: global.BigInt, + Symbol: Symbol, + Proxy: Proxy, + Reflect: Reflect, + Object: Object, + Array: Array, + Function: Function, + String: String, + Number: Number, + Boolean: Boolean, + RegExp: RegExp, + Math: Math, + JSON: JSON, + Intl: global.Intl, + + // Custom require that blocks core modules + require: createRestrictedRequire(), + + // Needed for some modules + global: undefined as any, + globalThis: undefined as any +}); + +// Make global and globalThis point to the sandbox +sandbox.global = sandbox; +sandbox.globalThis = sandbox; + +/** + * Load the bundled MongoDB driver module in a VM context + * This allows us to control the globals that the driver has access to + */ +export function loadContextifiedMongoDBModule() { + const bundlePath = path.join(__dirname, 'driver.bundle.js'); + + if (!fs.existsSync(bundlePath)) { + throw new Error(`Driver bundle not found at ${bundlePath}. Run 'npm run bundle:driver' first.`); + } + + const bundleCode = fs.readFileSync(bundlePath, 'utf8'); + + const exportsContainer = {}; + const moduleContainer = { exports: exportsContainer }; + + // Wrap the bundle in a CommonJS-style wrapper + const wrapper = `(function(exports, module, require) { + ${bundleCode} + })`; + + const script = new vm.Script(wrapper, { filename: bundlePath }); + const fn = script.runInContext(sandbox); + + // Execute the bundle with the restricted require from the sandbox + fn(moduleContainer.exports, moduleContainer, sandbox.require); + + return moduleContainer.exports; +}