diff --git a/src/deps.ts b/src/deps.ts index fc92beda8bd..95d9b8737cd 100644 --- a/src/deps.ts +++ b/src/deps.ts @@ -1,3 +1,5 @@ +import * as zlib from 'zlib'; + import { type Stream } from './cmap/connect'; import { MongoMissingDependencyError } from './error'; import type { Callback } from './utils'; @@ -60,7 +62,41 @@ type ZStandardLib = { export type ZStandard = ZStandardLib | { kModuleError: MongoMissingDependencyError }; +function getBuiltInZstdLibrary(): ZStandardLib | null { + if (typeof zlib.zstdCompress !== 'function' || typeof zlib.zstdDecompress !== 'function') { + return null; + } + + return { + compress(buf: Uint8Array, level?: number): Promise { + return new Promise((resolve, reject) => { + zlib.zstdCompress( + buf, + level == null ? {} : { params: { [zlib.constants.ZSTD_c_compressionLevel]: level } }, + (error, result) => { + if (error) return reject(error); + resolve(result); + } + ); + }); + }, + decompress(buf: Uint8Array): Promise { + return new Promise((resolve, reject) => { + zlib.zstdDecompress(buf, (error, result) => { + if (error) return reject(error); + resolve(result); + }); + }); + } + }; +} + export function getZstdLibrary(): ZStandardLib | { kModuleError: MongoMissingDependencyError } { + const builtInZstdLibrary = getBuiltInZstdLibrary(); + if (builtInZstdLibrary != null) { + return builtInZstdLibrary; + } + let ZStandard: ZStandardLib | { kModuleError: MongoMissingDependencyError }; try { // eslint-disable-next-line @typescript-eslint/no-require-imports @@ -68,7 +104,7 @@ export function getZstdLibrary(): ZStandardLib | { kModuleError: MongoMissingDep } catch (error) { ZStandard = makeErrorModule( new MongoMissingDependencyError( - 'Optional module `@mongodb-js/zstd` not found. Please install it to enable zstd compression', + 'Built-in zstd support is unavailable and optional module `@mongodb-js/zstd` not found. Please use Node.js 22.15.0+ or install `@mongodb-js/zstd` to enable zstd compression', { cause: error, dependencyName: 'zstd' } ) ); diff --git a/test/unit/assorted/optional_require.test.ts b/test/unit/assorted/optional_require.test.ts index 95145391186..0a0150badf3 100644 --- a/test/unit/assorted/optional_require.test.ts +++ b/test/unit/assorted/optional_require.test.ts @@ -1,6 +1,7 @@ import { expect } from 'chai'; import { existsSync } from 'fs'; import { resolve } from 'path'; +import * as zlib from 'zlib'; import { AuthContext, @@ -16,6 +17,32 @@ function moduleExistsSync(moduleName) { } describe('optionalRequire', function () { + describe('Zstandard', function () { + it('supports built-in zstd when the addon is not installed', async function () { + const moduleName = '@mongodb-js/zstd'; + if (moduleExistsSync(moduleName)) { + return this.skip(); + } + + const error = await compress( + { zlibCompressionLevel: 0, agreedCompressor: 'zstd' }, + Buffer.from('test', 'utf8') + ).then( + () => null, + e => e + ); + + const hasBuiltInZstd = + typeof zlib.zstdCompress === 'function' && typeof zlib.zstdDecompress === 'function'; + + if (hasBuiltInZstd) { + expect(error).to.equal(null); + } else { + expect(error).to.be.instanceOf(MongoMissingDependencyError); + } + }); + }); + describe('Snappy', function () { it('should error if not installed', async function () { const moduleName = 'snappy'; diff --git a/test/unit/cmap/wire_protocol/compression.test.ts b/test/unit/cmap/wire_protocol/compression.test.ts index e67fa0afb7b..aefe4777386 100644 --- a/test/unit/cmap/wire_protocol/compression.test.ts +++ b/test/unit/cmap/wire_protocol/compression.test.ts @@ -1,4 +1,3 @@ -import * as zstd from '@mongodb-js/zstd'; import { expect } from 'chai'; import { compress, Compressor, decompress } from '../../../mongodb'; @@ -13,8 +12,8 @@ describe('compression', function () { it('compresses the data', async function () { const data = await compress(options, buffer); - // decompress throws if the message is not zstd compresed - expect(await zstd.decompress(data)).to.deep.equal(buffer); + expect(data).to.not.deep.equal(buffer); + expect(await decompress(Compressor.zstd, data)).to.deep.equal(buffer); }); }); });