From 4dbd896a02fc99ff622601b8dadc7f18f8ca0cbc Mon Sep 17 00:00:00 2001 From: bailey Date: Wed, 28 Jan 2026 15:23:53 -0700 Subject: [PATCH 01/13] 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 02/13] 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 03/13] 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 04/13] 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 05/13] 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 06/13] 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 07/13] 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 08/13] 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; +} From 7f2bd3458305ba154ea1f03e8b308da325c97530 Mon Sep 17 00:00:00 2001 From: Pavel Safronov Date: Thu, 19 Feb 2026 12:48:22 -0800 Subject: [PATCH 09/13] got some things working, now figuring out the general types in tests --- .gitignore | 1 + etc/build-runtime-barrel.mjs | 21 +++++++ etc/bundle-driver.mjs | 75 +++++++++++++----------- package.json | 10 +++- test/integration/crud/crud_api.test.ts | 81 +++++++++++++++----------- test/mongodb.ts | 16 ++--- test/mongodb_bundled.ts | 72 +++++++++++++++++++++++ test/mongodb_runtime-testing.ts | 4 ++ test/tools/runner/config.ts | 70 ++++++++-------------- test/tools/runner/vm_context_helper.ts | 4 +- test/tools/utils.ts | 5 ++ tsconfig.json | 2 +- 12 files changed, 234 insertions(+), 127 deletions(-) create mode 100644 etc/build-runtime-barrel.mjs create mode 100644 test/mongodb_bundled.ts create mode 100644 test/mongodb_runtime-testing.ts diff --git a/.gitignore b/.gitignore index 9ba7a0df0e..ff5518925b 100644 --- a/.gitignore +++ b/.gitignore @@ -109,3 +109,4 @@ crypt_shared.sh *keytab driver.bundle.js +test/tools/runner/bundle/ diff --git a/etc/build-runtime-barrel.mjs b/etc/build-runtime-barrel.mjs new file mode 100644 index 0000000000..afb34360a9 --- /dev/null +++ b/etc/build-runtime-barrel.mjs @@ -0,0 +1,21 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +// eslint-disable-next-line no-restricted-globals +const useBundled = process.env.MONGODB_BUNDLED === 'true'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const rootDir = path.join(__dirname, '..'); +const outputBarrelFile = path.join(rootDir, 'test/mongodb_runtime-testing.ts'); +const source = useBundled ? './mongodb_bundled' : './mongodb'; + +const contents = + `// This file is auto-generated. Do not edit.\n` + + `// Run 'npm run build:runtime-barrel' to regenerate.\n` + + `export const runNodelessTests = ${useBundled};\n` + + `export * from '${source}';\n`; +await fs.writeFile(outputBarrelFile, contents); + +// eslint-disable-next-line no-console +console.log(`✓ ${outputBarrelFile} now re-exports from ${source}`); diff --git a/etc/bundle-driver.mjs b/etc/bundle-driver.mjs index e12420ba2a..f007d00f06 100755 --- a/etc/bundle-driver.mjs +++ b/etc/bundle-driver.mjs @@ -1,43 +1,52 @@ #!/usr/bin/env node -import * as esbuild from 'esbuild'; -import { fileURLToPath } from 'node:url'; +import fs from 'node:fs/promises'; import { isBuiltin } from 'node:module'; import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import * as esbuild from 'esbuild'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const rootDir = path.join(__dirname, '..'); +const outdir = path.join(rootDir, 'test/tools/runner/bundle/'); +await fs.rm(outdir, { recursive: true, force: true }); + +const outputBundleFile = path.join(outdir, 'driver-bundle.js'); 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' + entryPoints: [path.join(rootDir, 'test/mongodb.ts')], + bundle: true, + outfile: outputBundleFile, + 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 +// eslint-disable-next-line no-console +console.log(`✓ Driver bundle created at ${outputBundleFile}`); diff --git a/package.json b/package.json index 846303354f..8e19cb9e85 100644 --- a/package.json +++ b/package.json @@ -142,6 +142,7 @@ "check:search-indexes": "nyc mocha --config test/mocha_mongodb.js test/manual/search-index-management.prose.test.ts", "check:test": "mocha --config test/mocha_mongodb.js test/integration", "check:unit": "nyc mocha test/unit", + "check:unit-bundled": "MONGODB_BUNDLED=true npm run build:bundle && npm run check:unit", "check:ts": "node ./node_modules/typescript/bin/tsc -v && node ./node_modules/typescript/bin/tsc --noEmit", "check:atlas": "nyc mocha --config test/manual/mocharc.js test/manual/atlas_connectivity.test.ts", "check:drivers-atlas-testing": "nyc mocha --config test/mocha_mongodb.js test/atlas/drivers_atlas_testing.test.ts", @@ -155,9 +156,12 @@ "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", + "check:runtime-independence": "ts-node test/tools/runner/vm_context_helper.ts test/integration/change-streams/change_stream.test.ts", + "check:test-bundled": "MONGODB_BUNDLED=true npm run build:bundle && npm run check:test", + "build:bundle": "npm run bundle:driver && npm run bundle:types && npm run build:runtime-barrel", + "build:runtime-barrel": "node etc/build-runtime-barrel.mjs", "bundle:driver": "node etc/bundle-driver.mjs", - "test:bundled": "npm run bundle:driver && npm run check:test", + "bundle:types": "npx tsc --project ./tsconfig.json --declaration --emitDeclarationOnly --declarationDir test/tools/runner/bundle/types", "fix:eslint": "npm run check:eslint -- --fix", "prepare": "node etc/prepare.js", "preview:docs": "ts-node etc/docs/preview.ts", @@ -174,4 +178,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 d6029553c2..fced0597ac 100644 --- a/test/integration/crud/crud_api.test.ts +++ b/test/integration/crud/crud_api.test.ts @@ -3,28 +3,21 @@ import { finished } from 'node:stream/promises'; import { expect } from 'chai'; import * as sinon from 'sinon'; -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 { +import { Collection, CommandFailedEvent, + type CommandStartedEvent, CommandSucceededEvent, + type Db, MongoBulkWriteError, - MongoClient, + type MongoClient, MongoServerError, ObjectId, - ReturnDocument -} = mongodb; - -type MongoClient = typeof mongodb.MongoClient.prototype; -type Db = typeof mongodb.Db.prototype; -type CommandStartedEvent = typeof mongodb.CommandStartedEvent.prototype; + ReturnDocument, + runNodelessTests +} from '../../mongodb_runtime-testing'; +import { ensureTypeByName, type FailCommandFailPoint } from '../../tools/utils'; +import { assert as test } from '../shared'; const DB_NAME = 'crud_api_tests'; @@ -32,8 +25,6 @@ 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 @@ -113,9 +104,13 @@ describe.only('CRUD API', function () { const spy = sinon.spy(Collection.prototype, 'find'); const result = await collection.findOne({}); expect(result).to.deep.equal({ _id: 1 }); - expect(events.at(0)).to.be.instanceOf(CommandSucceededEvent); - expect(spy.returnValues.at(0)).to.have.property('closed', true); - expect(spy.returnValues.at(0)).to.have.nested.property('session.hasEnded', true); + if (runNodelessTests) { + expect(ensureTypeByName(events.at(0), 'CommandSucceededEvent')).to.be.true; + } else { + expect(events.at(0)).to.be.instanceOf(CommandSucceededEvent); + expect(spy.returnValues.at(0)).to.have.property('closed', true); + expect(spy.returnValues.at(0)).to.have.nested.property('session.hasEnded', true); + } }); }); @@ -145,10 +140,14 @@ describe.only('CRUD API', function () { it('the cursor for findOne is closed', async function () { const spy = sinon.spy(Collection.prototype, 'find'); const error = await collection.findOne({}).catch(error => error); - expect(error).to.be.instanceOf(MongoServerError); - expect(events.at(0)).to.be.instanceOf(CommandFailedEvent); - expect(spy.returnValues.at(0)).to.have.property('closed', true); - expect(spy.returnValues.at(0)).to.have.nested.property('session.hasEnded', true); + if (runNodelessTests) { + expect(ensureTypeByName(error, 'MongoServerError')).to.be.true; + } else { + expect(error).to.be.instanceOf(MongoServerError); + expect(events.at(0)).to.be.instanceOf(CommandFailedEvent); + expect(spy.returnValues.at(0)).to.have.property('closed', true); + expect(spy.returnValues.at(0)).to.have.nested.property('session.hasEnded', true); + } }); }); }); @@ -183,9 +182,13 @@ describe.only('CRUD API', function () { const spy = sinon.spy(Collection.prototype, 'aggregate'); const result = await collection.countDocuments({}); expect(result).to.deep.equal(2); - expect(events[0]).to.be.instanceOf(CommandSucceededEvent); - expect(spy.returnValues[0]).to.have.property('closed', true); - expect(spy.returnValues[0]).to.have.nested.property('session.hasEnded', true); + if (runNodelessTests) { + expect(ensureTypeByName(events[0], 'CommandSucceededEvent')).to.be.true; + } else { + expect(events[0]).to.be.instanceOf(CommandSucceededEvent); + expect(spy.returnValues[0]).to.have.property('closed', true); + expect(spy.returnValues[0]).to.have.nested.property('session.hasEnded', true); + } }); }); @@ -215,10 +218,14 @@ describe.only('CRUD API', function () { it('the cursor for countDocuments is closed', async function () { const spy = sinon.spy(Collection.prototype, 'aggregate'); const error = await collection.countDocuments({}).catch(error => error); - expect(error).to.be.instanceOf(MongoServerError); - expect(events.at(0)).to.be.instanceOf(CommandFailedEvent); - expect(spy.returnValues.at(0)).to.have.property('closed', true); - expect(spy.returnValues.at(0)).to.have.nested.property('session.hasEnded', true); + if (runNodelessTests) { + expect(ensureTypeByName(error, 'MongoServerError')).to.be.true; + } else { + expect(error).to.be.instanceOf(MongoServerError); + expect(events.at(0)).to.be.instanceOf(CommandFailedEvent); + expect(spy.returnValues.at(0)).to.have.property('closed', true); + expect(spy.returnValues.at(0)).to.have.nested.property('session.hasEnded', true); + } }); }); }); @@ -795,7 +802,11 @@ describe.only('CRUD API', function () { .bulkWrite(ops, { ordered: false, writeConcern: { w: 1 } }) .catch(error => error); - expect(error).to.be.instanceOf(MongoBulkWriteError); + if (runNodelessTests) { + expect(ensureTypeByName(error, 'MongoBulkWriteError')).to.be.true; + } else { + expect(error).to.be.instanceOf(MongoBulkWriteError); + } // 1004 because one of them is duplicate key // but since it is unordered we continued to write expect(error).to.have.property('insertedCount', 1004); @@ -818,7 +829,8 @@ describe.only('CRUD API', function () { .collection('t20_1') .bulkWrite(ops, { ordered: true, writeConcern: { w: 1 } }) .catch(err => err); - expect(err).to.be.instanceOf(MongoBulkWriteError); + // expect(err).to.be.instanceOf(MongoBulkWriteError); + expect(ensureTypeByName(err, 'MongoBulkWriteError')).to.be.true; }); describe('sort support', function () { @@ -827,7 +839,6 @@ describe.only('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/mongodb.ts b/test/mongodb.ts index dfc50e2d0d..9c3234acd7 100644 --- a/test/mongodb.ts +++ b/test/mongodb.ts @@ -58,13 +58,13 @@ 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/client_bulk_write_cursor'; export * from '../src/cursor/explainable_cursor'; export * from '../src/cursor/find_cursor'; +export * from '../src/cursor/find_cursor'; export * from '../src/cursor/list_collections_cursor'; +export * from '../src/cursor/list_collections_cursor'; +export * from '../src/cursor/list_indexes_cursor'; export * from '../src/cursor/list_indexes_cursor'; export * from '../src/cursor/list_search_indexes_cursor'; export * from '../src/cursor/run_command_cursor'; @@ -77,17 +77,17 @@ 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/mongo_client_auth_providers'; export * from '../src/mongo_logger'; +export * from '../src/mongo_logger'; export * from '../src/mongo_types'; +export * from '../src/mongo_types'; +export * from '../src/operations/aggregate'; export * from '../src/operations/aggregate'; export * from '../src/operations/client_bulk_write/client_bulk_write'; export * from '../src/operations/client_bulk_write/command_builder'; +export * from '../src/operations/client_bulk_write/command_builder'; +export * from '../src/operations/client_bulk_write/common'; export * from '../src/operations/client_bulk_write/common'; export * from '../src/operations/client_bulk_write/executor'; export * from '../src/operations/client_bulk_write/results_merger'; diff --git a/test/mongodb_bundled.ts b/test/mongodb_bundled.ts new file mode 100644 index 0000000000..6394d50892 --- /dev/null +++ b/test/mongodb_bundled.ts @@ -0,0 +1,72 @@ +import { loadContextifiedMongoDBModule } from './tools/runner/vm_context_helper'; + +type all = typeof import('./mongodb'); +let exportSource: all; +try { + exportSource = loadContextifiedMongoDBModule() as all; +} catch (error) { + throw new Error( + `Failed to load contextified MongoDB module: ${error instanceof Error ? error.message : String(error)}` + ); +} + +// Export public API from the contextified module +export const { + aws4Sign, + Collection, + CommandFailedEvent, + CommandStartedEvent, + CommandSucceededEvent, + Db, + Double, + HostAddress, + isHello, + Long, + MongoAPIError, + MongoBulkWriteError, + MongoClient, + MongoCredentials, + MongoInvalidArgumentError, + MongoLoggableComponent, + MongoLogger, + MongoParseError, + MongoServerError, + ObjectId, + parseOptions, + ReadConcern, + ReadPreference, + resolveSRVRecord, + ReturnDocument, + ServerApiVersion, + SeverityLevel, + TopologyType, + WriteConcern +} = exportSource; + +// Export types from the contextified module +export type { + AuthMechanism, + CompressorName, + MongoClientOptions, + ServerApi, + WriteConcernSettings +} from './tools/runner/bundle/types/index'; + +// Export "clashing" types from the contextified module. +// These are types that clash with the objects of the same name (eg Collection), so we need to export them separately to avoid type errors. +import type { + Collection as _CollectionType, + CommandFailedEvent as _CommandFailedEventType, + CommandStartedEvent as _CommandStartedEventType, + CommandSucceededEvent as _CommandSucceededEventType, + HostAddress as _HostAddressType, + MongoClient as _MongoClientType, + TopologyType as _TopologyType +} from './tools/runner/bundle/types/index'; +export type Collection = _CollectionType; +export type CommandFailedEvent = _CommandFailedEventType; +export type CommandStartedEvent = _CommandStartedEventType; +export type CommandSucceededEvent = _CommandSucceededEventType; +export type HostAddress = _HostAddressType; +export type MongoClient = _MongoClientType; +export type TopologyType = _TopologyType; diff --git a/test/mongodb_runtime-testing.ts b/test/mongodb_runtime-testing.ts new file mode 100644 index 0000000000..c69c911ce1 --- /dev/null +++ b/test/mongodb_runtime-testing.ts @@ -0,0 +1,4 @@ +// This file is auto-generated. Do not edit. +// Run 'npm run build:runtime-barrel' to regenerate. +export const runNodelessTests = true; +export * from './mongodb_bundled'; diff --git a/test/tools/runner/config.ts b/test/tools/runner/config.ts index a04ea89747..265be7a926 100644 --- a/test/tools/runner/config.ts +++ b/test/tools/runner/config.ts @@ -17,10 +17,11 @@ import { MongoClient, type MongoClientOptions, ObjectId, + runNodelessTests, type ServerApi, TopologyType, type WriteConcernSettings -} from '../../mongodb'; +} from '../../mongodb_runtime-testing'; import { getEnvironmentalOptions } from '../utils'; import { type Filter } from './filters/filter'; import { flakyTests } from './flaky'; @@ -114,32 +115,20 @@ 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, - mongodb?: any // Optional contextified mongodb module + private context: Record ) { - this.mongodb = mongodb; - const url = new ConnectionString(uri); const { hosts } = url; - const hostAddresses = hosts.map( - this.mongodb ? this.mongodb.HostAddress.fromString : HostAddress.fromString - ); + const hostAddresses = hosts.map(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 - ? this.mongodb - ? this.mongodb.TopologyType.LoadBalanced - : TopologyType.LoadBalanced - : context.topologyType; + this.topologyType = this.isLoadBalanced ? TopologyType.LoadBalanced : context.topologyType; this.buildInfo = context.buildInfo; this.serverApi = context.serverApi; this.isSrv = uri.indexOf('mongodb+srv') > -1; @@ -232,7 +221,10 @@ export class TestConfiguration { return uri.indexOf('MONGODB-OIDC') > -1 && uri.indexOf(`ENVIRONMENT:${env}`) > -1; } - newClient(urlOrQueryOptions?: string | Record, serverOptions?: MongoClientOptions) { + newClient( + urlOrQueryOptions?: string | Record, + serverOptions?: MongoClientOptions + ): MongoClient { const baseOptions: MongoClientOptions = this.compressor ? { compressors: this.compressor @@ -240,10 +232,10 @@ export class TestConfiguration { : {}; serverOptions = Object.assign(baseOptions, getEnvironmentalOptions(), serverOptions); - // If using contextified mongodb, inject Node.js runtime adapters - if (this.mongodb) { + if (runNodelessTests) { serverOptions.runtimeAdapters = { + // eslint-disable-next-line @typescript-eslint/no-require-imports os: require('os'), ...serverOptions.runtimeAdapters }; @@ -259,8 +251,8 @@ export class TestConfiguration { throw new Error(`Cannot use options to specify host/port, must be in ${urlOrQueryOptions}`); } - const ClientConstructor = this.mongodb ? this.mongodb.MongoClient : MongoClient; - return new ClientConstructor(urlOrQueryOptions, serverOptions); + const newClient: MongoClient = new MongoClient(urlOrQueryOptions, serverOptions); + return newClient; } const queryOptions = urlOrQueryOptions ?? {}; @@ -304,10 +296,7 @@ export class TestConfiguration { delete queryOptions.writeConcern; } - const LoadBalancedType = this.mongodb - ? this.mongodb.TopologyType.LoadBalanced - : TopologyType.LoadBalanced; - if (this.topologyType === LoadBalancedType) { + if (this.topologyType === TopologyType.LoadBalanced) { queryOptions.loadBalanced = true; } @@ -341,8 +330,7 @@ export class TestConfiguration { const connectionString = url.format(urlOptions); - const ClientConstructor = this.mongodb ? this.mongodb.MongoClient : MongoClient; - return new ClientConstructor(connectionString, serverOptions); + return new MongoClient(connectionString, serverOptions); } /** @@ -464,8 +452,7 @@ export class TestConfiguration { } writeConcernMax(): { writeConcern: WriteConcernSettings } { - const SingleType = this.mongodb ? this.mongodb.TopologyType.Single : TopologyType.Single; - if (this.topologyType !== SingleType) { + if (this.topologyType !== TopologyType.Single) { return { writeConcern: { w: 'majority', wtimeoutMS: 30000 } }; } @@ -477,7 +464,7 @@ export class TestConfiguration { } makeAtlasTestConfiguration(): AtlasTestConfiguration { - return new AtlasTestConfiguration(this.uri, this.context, this.mongodb); + return new AtlasTestConfiguration(this.uri, this.context); } loggingEnabled = false; @@ -489,8 +476,7 @@ export class TestConfiguration { testsToEnableLogging = flakyTests; setupLogging(options: MongoClientOptions, id?: string) { - const ObjectIdConstructor = this.mongodb ? this.mongodb.ObjectId : ObjectId; - id ??= new ObjectIdConstructor().toString(); + id ??= new ObjectId().toString(); this.logs = []; const write = log => this.logs.push({ t: log.t, id, ...log }); options.mongodbLogPath = { write }; @@ -505,9 +491,6 @@ 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( @@ -516,13 +499,12 @@ 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 LongConstructor(value).toExtendedJSON() }; + if (typeof value === 'bigint') return { bigint: new Long(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 DoubleConstructor(value).toExtendedJSON() }; + return { number: new Double(value).toExtendedJSON() }; } if (Buffer.isBuffer(value)) return { [value.constructor.name]: Buffer.prototype.base64Slice.call(value) }; @@ -546,10 +528,8 @@ export class TestConfiguration { */ export class AtlasTestConfiguration extends TestConfiguration { override newClient(): MongoClient { - const ClientConstructor = (this as any).mongodb - ? (this as any).mongodb.MongoClient - : MongoClient; - return new ClientConstructor(process.env.MONGODB_URI!); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return new MongoClient(process.env.MONGODB_URI!); } override url(): string { @@ -563,10 +543,8 @@ export class AtlasTestConfiguration extends TestConfiguration { */ export class AstrolabeTestConfiguration extends TestConfiguration { override newClient(): MongoClient { - const ClientConstructor = (this as any).mongodb - ? (this as any).mongodb.MongoClient - : MongoClient; - return new ClientConstructor(process.env.DRIVERS_ATLAS_TESTING_URI!); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return new MongoClient(process.env.DRIVERS_ATLAS_TESTING_URI!); } override url(): string { diff --git a/test/tools/runner/vm_context_helper.ts b/test/tools/runner/vm_context_helper.ts index fbb142828f..fa3ac2ccad 100644 --- a/test/tools/runner/vm_context_helper.ts +++ b/test/tools/runner/vm_context_helper.ts @@ -47,6 +47,7 @@ const sandbox = vm.createContext({ // Global objects needed for runtime Buffer: Buffer, + Headers: global.Headers, Promise: Promise, Map: Map, Set: Set, @@ -85,6 +86,7 @@ const sandbox = vm.createContext({ Math: Math, JSON: JSON, Intl: global.Intl, + crypto: global.crypto, // Custom require that blocks core modules require: createRestrictedRequire(), @@ -103,7 +105,7 @@ sandbox.globalThis = sandbox; * This allows us to control the globals that the driver has access to */ export function loadContextifiedMongoDBModule() { - const bundlePath = path.join(__dirname, 'driver.bundle.js'); + const bundlePath = path.join(__dirname, 'bundle/driver-bundle.js'); if (!fs.existsSync(bundlePath)) { throw new Error(`Driver bundle not found at ${bundlePath}. Run 'npm run bundle:driver' first.`); diff --git a/test/tools/utils.ts b/test/tools/utils.ts index d587e9864d..cb26f844ad 100644 --- a/test/tools/utils.ts +++ b/test/tools/utils.ts @@ -32,6 +32,11 @@ export function ensureCalledWith(stub: any, args: any[]) { args.forEach((m: any) => expect(stub).to.have.been.calledWith(m)); } +export function ensureTypeByName(obj: any, typeName: string) { + const isType = obj != null && obj.constructor != null && obj.constructor.name === typeName; + return isType; +} + export class EventCollector { private _events: Record; private _timeout: number; diff --git a/tsconfig.json b/tsconfig.json index 74fb09c6ff..6604ef2871 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -43,4 +43,4 @@ "include": [ "src/**/*" ] -} \ No newline at end of file +} From 875174b680316fa9596ab9772a73665485cb9ec9 Mon Sep 17 00:00:00 2001 From: Pavel Safronov Date: Thu, 19 Feb 2026 13:09:49 -0800 Subject: [PATCH 10/13] improve tooling and migrate timeout.test.ts to new format --- package.json | 6 ++++-- test/mongodb_bundled.ts | 8 ++++++++ test/mongodb_runtime-testing.ts | 4 ++-- test/unit/timeout.test.ts | 2 +- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 8e19cb9e85..ca6aa9a3f7 100644 --- a/package.json +++ b/package.json @@ -142,7 +142,7 @@ "check:search-indexes": "nyc mocha --config test/mocha_mongodb.js test/manual/search-index-management.prose.test.ts", "check:test": "mocha --config test/mocha_mongodb.js test/integration", "check:unit": "nyc mocha test/unit", - "check:unit-bundled": "MONGODB_BUNDLED=true npm run build:bundle && npm run check:unit", + "check:unit-bundled": "npm run switch:to-bundled && npm run check:unit || npm run switch:to-unbundled", "check:ts": "node ./node_modules/typescript/bin/tsc -v && node ./node_modules/typescript/bin/tsc --noEmit", "check:atlas": "nyc mocha --config test/manual/mocharc.js test/manual/atlas_connectivity.test.ts", "check:drivers-atlas-testing": "nyc mocha --config test/mocha_mongodb.js test/atlas/drivers_atlas_testing.test.ts", @@ -157,7 +157,7 @@ "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_context_helper.ts test/integration/change-streams/change_stream.test.ts", - "check:test-bundled": "MONGODB_BUNDLED=true npm run build:bundle && npm run check:test", + "check:test-bundled": "npm run switch:to-bundled && npm run check:test || npm run switch:to-unbundled", "build:bundle": "npm run bundle:driver && npm run bundle:types && npm run build:runtime-barrel", "build:runtime-barrel": "node etc/build-runtime-barrel.mjs", "bundle:driver": "node etc/bundle-driver.mjs", @@ -165,6 +165,8 @@ "fix:eslint": "npm run check:eslint -- --fix", "prepare": "node etc/prepare.js", "preview:docs": "ts-node etc/docs/preview.ts", + "switch:to-bundled": "MONGODB_BUNDLED=true npm run build:runtime-barrel", + "switch:to-unbundled": "MONGODB_BUNDLED=false npm run build:runtime-barrel", "test": "npm run check:lint && npm run test:all", "test:all": "npm run check:unit && npm run check:test", "update:docs": "npm run build:docs -- --yes" diff --git a/test/mongodb_bundled.ts b/test/mongodb_bundled.ts index 6394d50892..5d8c0f260c 100644 --- a/test/mongodb_bundled.ts +++ b/test/mongodb_bundled.ts @@ -17,10 +17,12 @@ export const { CommandFailedEvent, CommandStartedEvent, CommandSucceededEvent, + CSOTTimeoutContext, Db, Double, HostAddress, isHello, + LegacyTimeoutContext, Long, MongoAPIError, MongoBulkWriteError, @@ -31,6 +33,7 @@ export const { MongoLogger, MongoParseError, MongoServerError, + MongoRuntimeError, ObjectId, parseOptions, ReadConcern, @@ -39,6 +42,9 @@ export const { ReturnDocument, ServerApiVersion, SeverityLevel, + Timeout, + TimeoutContext, + TimeoutError, TopologyType, WriteConcern } = exportSource; @@ -61,6 +67,7 @@ import type { CommandSucceededEvent as _CommandSucceededEventType, HostAddress as _HostAddressType, MongoClient as _MongoClientType, + Timeout as _TimeoutType, TopologyType as _TopologyType } from './tools/runner/bundle/types/index'; export type Collection = _CollectionType; @@ -69,4 +76,5 @@ export type CommandStartedEvent = _CommandStartedEventType; export type CommandSucceededEvent = _CommandSucceededEventType; export type HostAddress = _HostAddressType; export type MongoClient = _MongoClientType; +export type Timeout = _TimeoutType; export type TopologyType = _TopologyType; diff --git a/test/mongodb_runtime-testing.ts b/test/mongodb_runtime-testing.ts index c69c911ce1..d0b07fef47 100644 --- a/test/mongodb_runtime-testing.ts +++ b/test/mongodb_runtime-testing.ts @@ -1,4 +1,4 @@ // This file is auto-generated. Do not edit. // Run 'npm run build:runtime-barrel' to regenerate. -export const runNodelessTests = true; -export * from './mongodb_bundled'; +export const runNodelessTests = false; +export * from './mongodb'; diff --git a/test/unit/timeout.test.ts b/test/unit/timeout.test.ts index 1dd7e83feb..5fbdf0c7a5 100644 --- a/test/unit/timeout.test.ts +++ b/test/unit/timeout.test.ts @@ -8,7 +8,7 @@ import { Timeout, TimeoutContext, TimeoutError -} from '../mongodb'; +} from '../mongodb_runtime-testing'; describe('Timeout', function () { let timeout: Timeout; From 7d9cf78b3c350f042de57f3bff82b812fbda7313 Mon Sep 17 00:00:00 2001 From: Pavel Safronov Date: Thu, 19 Feb 2026 16:51:00 -0800 Subject: [PATCH 11/13] added better message to restricted require error, including the source location --- package.json | 6 +++--- test/tools/runner/vm_context_helper.ts | 6 +++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index ca6aa9a3f7..44cffac996 100644 --- a/package.json +++ b/package.json @@ -142,7 +142,7 @@ "check:search-indexes": "nyc mocha --config test/mocha_mongodb.js test/manual/search-index-management.prose.test.ts", "check:test": "mocha --config test/mocha_mongodb.js test/integration", "check:unit": "nyc mocha test/unit", - "check:unit-bundled": "npm run switch:to-bundled && npm run check:unit || npm run switch:to-unbundled", + "check:unit-bundled": "npm run build:bundle && npm run switch:to-bundled && npm run check:unit ; npm run switch:to-unbundled", "check:ts": "node ./node_modules/typescript/bin/tsc -v && node ./node_modules/typescript/bin/tsc --noEmit", "check:atlas": "nyc mocha --config test/manual/mocharc.js test/manual/atlas_connectivity.test.ts", "check:drivers-atlas-testing": "nyc mocha --config test/mocha_mongodb.js test/atlas/drivers_atlas_testing.test.ts", @@ -157,8 +157,8 @@ "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_context_helper.ts test/integration/change-streams/change_stream.test.ts", - "check:test-bundled": "npm run switch:to-bundled && npm run check:test || npm run switch:to-unbundled", - "build:bundle": "npm run bundle:driver && npm run bundle:types && npm run build:runtime-barrel", + "check:test-bundled": "npm run build:bundle && npm run switch:to-bundled&& npm run check:test ; npm run switch:to-unbundled", + "build:bundle": "npm run bundle:driver && npm run bundle:types", "build:runtime-barrel": "node etc/build-runtime-barrel.mjs", "bundle:driver": "node etc/bundle-driver.mjs", "bundle:types": "npx tsc --project ./tsconfig.json --declaration --emitDeclarationOnly --declarationDir test/tools/runner/bundle/types", diff --git a/test/tools/runner/vm_context_helper.ts b/test/tools/runner/vm_context_helper.ts index fa3ac2ccad..93b0390b3a 100644 --- a/test/tools/runner/vm_context_helper.ts +++ b/test/tools/runner/vm_context_helper.ts @@ -14,7 +14,11 @@ function createRestrictedRequire() { 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`); + const sourceFile = new Error().stack.split('\n')[2]?.replace('at', '').trim(); + const source = sourceFile ? `from ${sourceFile}` : 'from an unknown source'; + throw new Error( + `Access to core module '${moduleName}' (${source}) is restricted in this context` + ); } return require(moduleName); From b17b6656e716e5826606f2f422f8c08b3ec51147 Mon Sep 17 00:00:00 2001 From: Pavel Safronov Date: Thu, 19 Feb 2026 16:57:22 -0800 Subject: [PATCH 12/13] keep wrapper on the same line as the bundle code, so we have accurate line references --- test/tools/runner/vm_context_helper.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/tools/runner/vm_context_helper.ts b/test/tools/runner/vm_context_helper.ts index 93b0390b3a..c19b7cd16c 100644 --- a/test/tools/runner/vm_context_helper.ts +++ b/test/tools/runner/vm_context_helper.ts @@ -121,9 +121,7 @@ export function loadContextifiedMongoDBModule() { const moduleContainer = { exports: exportsContainer }; // Wrap the bundle in a CommonJS-style wrapper - const wrapper = `(function(exports, module, require) { - ${bundleCode} - })`; + const wrapper = `(function(exports, module, require) {${bundleCode}})`; const script = new vm.Script(wrapper, { filename: bundlePath }); const fn = script.runInContext(sandbox); From d23ee71a53a7b43785812eb423659208796eed8a Mon Sep 17 00:00:00 2001 From: Pavel Safronov Date: Fri, 20 Feb 2026 08:20:45 -0800 Subject: [PATCH 13/13] added readme info about the current approach --- package.json | 2 +- test/readme.md | 66 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 44cffac996..8fac3b1665 100644 --- a/package.json +++ b/package.json @@ -157,7 +157,7 @@ "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_context_helper.ts test/integration/change-streams/change_stream.test.ts", - "check:test-bundled": "npm run build:bundle && npm run switch:to-bundled&& npm run check:test ; npm run switch:to-unbundled", + "check:test-bundled": "npm run build:bundle && npm run switch:to-bundled && npm run check:test ; npm run switch:to-unbundled", "build:bundle": "npm run bundle:driver && npm run bundle:types", "build:runtime-barrel": "node etc/build-runtime-barrel.mjs", "bundle:driver": "node etc/bundle-driver.mjs", diff --git a/test/readme.md b/test/readme.md index 9baec61585..6085f4029a 100644 --- a/test/readme.md +++ b/test/readme.md @@ -43,9 +43,15 @@ about the types of tests and how to run them. - [AWS Authentication tests](#aws-authentication-tests) - [Running AWS tests](#running-aws-tests) - [Container Tests](#container-tests) + - [Node-less Runtime Testing](#node-less-runtime-testing) + - [Design](#design) + - [Adding tests to be tested with Node-less Runtime](#adding-tests-to-be-tested-with-node-less-runtime) + - [Running tests in Node-less Runtime](#running-tests-in-node-less-runtime) + - [Running tests manually in Node-less Runtime](#running-tests-manually-in-node-less-runtime) - [GCP](#gcp) - [Azure](#azure) - [AWS](#aws) + - [Running tests with TLS](#running-tests-with-tls) - [TODO Special Env Sections](#todo-special-env-sections) - [Testing driver changes with mongosh](#testing-driver-changes-with-mongosh) - [Point mongosh to the driver](#point-mongosh-to-the-driver) @@ -715,6 +721,64 @@ our existing integration test suite and run Evergreen patches against a single i _Note that in cases where the tests need to run longer than one hour to ensure that tokens expire that the mocha timeout must be increased in order for the test not to timeout._ +### Node-less Runtime Testing + +We are starting to remove explicit Node requirements, and are making it possible for users to provide us with the required functionality. + +To that end, need to run our tests in a Node-less environment. These tests need to fail if our code erroneously uses Node. + +#### Design + +The approach we are taking is to modify our unit and integration tests to run against a patched version of the driver, where any illegal `require` calls are blocked. + +Here are a few of the relevant components of this system: + +1. [test/mongodb.ts](test/mongodb.ts) + - Test entrypoint that exports Driver and all internal types. +2. [etc/bundle-driver.mjs](etc/bundle-driver.mjs) + - Creates a CommonJS bundle (Driver and all internal types) from `test/mongodb.ts`. +3. [test/tools/runner/vm_context_helper.ts](./tools/runner/vm_context_helper.ts) + - Special VM that blocks specific `require` calls. +4. [test/mongodb_bundled.ts](./mongodb_bundled.ts) + - Exports MongoDB from CommonJS bundle created by `etc/bundle-driver.mjs`, using `vm_context_helper.ts` to detect usages of blocked `require` calls. + - This file is currently maintained by hand and needs to export types explicitly. We may want to generate this file as well. +5. [test/mongodb_runtime-testing.ts](./mongodb_runtime-testing.ts) + - Generated "barrel file". It exports either `test/mongodb.ts` (Driver + all internal types) or `test/mongodb_bundled.ts` (Driver + all internal types, loaded from bundle, and using `vm_context_helper.ts` to block `require` calls.) +6. [etc/build-runtime-barrel.mjs](./etc/build-runtime-barrel.mjs) + - Generates the barrel file `test/mongodb_runtime-testing.ts` based on `MONGODB_BUNDLED` env var. + +#### Adding tests to be tested with Node-less Runtime + +Change the test's import from + `} from '../../mongodb';` +to + `} from '../../mongodb_runtime-testing';` + +#### Running tests in Node-less Runtime + +To run opted-in unit or integration tests in a Node-less runtime, run: + + `npm run check:unit-bundled` +or + `npm run check:test-bundled` + +Either command will: + +1. Create a bundle from `test/mongodb.ts` +2. Regenerate the barrel file to export from the generated bundle +3. Run unit or integ tests +4. Regenerate the barrel file to export from the test entry point + +#### Running tests manually in Node-less Runtime + +Call the following command to regenerate the barrel file to import from the bundle: + `npm run switch:to-bundled` + +Call the following command to rebuild the bundle: + `npm run build:bundle` + +You can now run the unit or integ tests locally and they will be importing from the patched bundle. + ## GCP 1. Add a new GCP prose test to `test/integration/auth/mongodb_oidc_gcp.prose.06.test.ts` that mimics the behaviour that @@ -748,7 +812,7 @@ We tests TLS in two suites in CI: Setting up a local environment is the same for both suites. -First, configure a server with TLS enabled by following the steps in [Runing the Tests Locally](#Running-the-Tests-Locally) with the environment +First, configure a server with TLS enabled by following the steps in [Runing the Tests Locally](#Running-the-Tests-Locally) with the environment variable `SSL=SSL` set. Then, in addition to setting the `MONGODB_URI` environment varialbe, set the following environment variables: