diff --git a/package-lock.json b/package-lock.json index c03d1746..76137992 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28907,7 +28907,7 @@ "@types/mocha": "^9.1.1", "@types/semver": "^7.8.0", "acorn": "^8.18.0", - "bson": "^4.6.3 || ^5 || ^6.10.3 || ^7.0.0", + "bson": "^4.6.3 || ^5 || ^6.10.3 || ^7.3.2", "chai": "^4.5.0", "depcheck": "^1.4.7", "eslint": "^7.25.0 || ^8.0.0", @@ -28919,7 +28919,7 @@ "typescript": "^5.9.3" }, "peerDependencies": { - "bson": "^4.6.3 || ^5 || ^6.10.3 || ^7.0.0" + "bson": "^4.6.3 || ^5 || ^6.10.3 || ^7.3.2" } }, "packages/mongodb-constants/node_modules/semver": { @@ -28995,7 +28995,7 @@ "typescript": "^5.9.3" }, "optionalDependencies": { - "bson": "^4.6.3 || ^5 || ^6.10.3 || ^7.0.0" + "bson": "^4.6.3 || ^5 || ^6.10.3 || ^7.3.2" } }, "packages/mongodb-ns": { @@ -29303,7 +29303,7 @@ "@types/node": "^25.5.2", "@types/reservoir": "^0.1.0", "ajv": "^8.20.0", - "bson": "^4.6.3 || ^5 || ^6.10.3 || ^7.0.0", + "bson": "^4.6.3 || ^5 || ^6.10.3 || ^7.3.2", "chai": "^4.5.0", "depcheck": "^1.4.7", "eslint": "^7.25.0 || ^8.0.0", @@ -29317,7 +29317,7 @@ "typescript": "^5.9.3" }, "optionalDependencies": { - "bson": "^4.6.3 || ^5 || ^6.10.3 || ^7.0.0", + "bson": "^4.6.3 || ^5 || ^6.10.3 || ^7.3.2", "cli-table": "^0.3.4", "js-yaml": "^5.2.2", "mongodb": "^6.9.0 || ^7.0.0", @@ -29537,7 +29537,7 @@ "@types/chai": "^4.2.21", "@types/mocha": "^9.1.1", "@types/node": "^25.5.2", - "bson": "^4.6.3 || ^5 || ^6.10.3 || ^7.0.0", + "bson": "^4.6.3 || ^5 || ^6.10.3 || ^7.3.2", "chai": "^4.5.0", "depcheck": "^1.4.7", "eslint": "^7.25.0 || ^8.0.0", @@ -29610,7 +29610,7 @@ "version": "0.8.10", "license": "Apache-2.0", "dependencies": { - "bson": "^4.6.3 || ^5 || ^6.10.3 || ^7.0.0", + "bson": "^4.6.3 || ^5 || ^6.10.3 || ^7.3.2", "mongodb": "^7.5.0" }, "bin": { @@ -30220,7 +30220,7 @@ "@types/estree": "^0.0.41", "@types/mocha": "^9.1.1", "benchmark": "^2.1.4", - "bson": "^4.6.3 || ^5 || ^6.10.3 || ^7.0.0", + "bson": "^4.6.3 || ^5 || ^6.10.3 || ^7.3.2", "chai": "^4.5.0", "depcheck": "^1.4.7", "eslint": "^7.25.0 || ^8.0.0", @@ -30232,7 +30232,7 @@ "webpack": "^5.82.0" }, "peerDependencies": { - "bson": "^4.6.3 || ^5 || ^6.10.3 || ^7.0.0" + "bson": "^4.6.3 || ^5 || ^6.10.3 || ^7.3.2" } }, "packages/shell-bson-parser/node_modules/@types/estree": { diff --git a/packages/mongodb-constants/package.json b/packages/mongodb-constants/package.json index 869195b2..c2741dbb 100644 --- a/packages/mongodb-constants/package.json +++ b/packages/mongodb-constants/package.json @@ -52,7 +52,7 @@ "reformat": "npm run prettier -- --write ." }, "peerDependencies": { - "bson": "^4.6.3 || ^5 || ^6.10.3 || ^7.0.0" + "bson": "^4.6.3 || ^5 || ^6.10.3 || ^7.3.2" }, "devDependencies": { "@mongodb-js/eslint-config-devtools": "^0.11.13", @@ -63,7 +63,7 @@ "@types/mocha": "^9.1.1", "@types/semver": "^7.8.0", "acorn": "^8.18.0", - "bson": "^4.6.3 || ^5 || ^6.10.3 || ^7.0.0", + "bson": "^4.6.3 || ^5 || ^6.10.3 || ^7.3.2", "chai": "^4.5.0", "depcheck": "^1.4.7", "eslint": "^7.25.0 || ^8.0.0", diff --git a/packages/mongodb-log-writer/package.json b/packages/mongodb-log-writer/package.json index df4fa14d..488ba9c8 100644 --- a/packages/mongodb-log-writer/package.json +++ b/packages/mongodb-log-writer/package.json @@ -85,6 +85,6 @@ "typescript": "^5.9.3" }, "optionalDependencies": { - "bson": "^4.6.3 || ^5 || ^6.10.3 || ^7.0.0" + "bson": "^4.6.3 || ^5 || ^6.10.3 || ^7.3.2" } } diff --git a/packages/mongodb-schema/package.json b/packages/mongodb-schema/package.json index 90c686e2..7fb68290 100644 --- a/packages/mongodb-schema/package.json +++ b/packages/mongodb-schema/package.json @@ -71,7 +71,7 @@ "reservoir": "^0.1.2" }, "optionalDependencies": { - "bson": "^4.6.3 || ^5 || ^6.10.3 || ^7.0.0", + "bson": "^4.6.3 || ^5 || ^6.10.3 || ^7.3.2", "cli-table": "^0.3.4", "js-yaml": "^5.2.2", "mongodb": "^6.9.0 || ^7.0.0", @@ -93,7 +93,7 @@ "@types/node": "^25.5.2", "@types/reservoir": "^0.1.0", "ajv": "^8.20.0", - "bson": "^4.6.3 || ^5 || ^6.10.3 || ^7.0.0", + "bson": "^4.6.3 || ^5 || ^6.10.3 || ^7.3.2", "chai": "^4.5.0", "depcheck": "^1.4.7", "eslint": "^7.25.0 || ^8.0.0", diff --git a/packages/mongodb-ts-autocomplete/package.json b/packages/mongodb-ts-autocomplete/package.json index 978ad3b0..a1f7a216 100644 --- a/packages/mongodb-ts-autocomplete/package.json +++ b/packages/mongodb-ts-autocomplete/package.json @@ -70,7 +70,7 @@ "@types/chai": "^4.2.21", "@types/mocha": "^9.1.1", "@types/node": "^25.5.2", - "bson": "^4.6.3 || ^5 || ^6.10.3 || ^7.0.0", + "bson": "^4.6.3 || ^5 || ^6.10.3 || ^7.3.2", "chai": "^4.5.0", "depcheck": "^1.4.7", "eslint": "^7.25.0 || ^8.0.0", diff --git a/packages/mql-typescript/package.json b/packages/mql-typescript/package.json index cb525f30..c55f9410 100644 --- a/packages/mql-typescript/package.json +++ b/packages/mql-typescript/package.json @@ -92,7 +92,7 @@ "zod": "^3.24.2" }, "dependencies": { - "bson": "^4.6.3 || ^5 || ^6.10.3 || ^7.0.0", + "bson": "^4.6.3 || ^5 || ^6.10.3 || ^7.3.2", "mongodb": "^7.5.0" } } diff --git a/packages/shell-bson-parser/README.md b/packages/shell-bson-parser/README.md index 3b833c3c..14e25f70 100644 --- a/packages/shell-bson-parser/README.md +++ b/packages/shell-bson-parser/README.md @@ -100,5 +100,3 @@ const query = await parse('{ a: 1 }'); terminateWorker(); ``` - -This package is ESM only. diff --git a/packages/shell-bson-parser/package.json b/packages/shell-bson-parser/package.json index b4d63505..5b3474fb 100644 --- a/packages/shell-bson-parser/package.json +++ b/packages/shell-bson-parser/package.json @@ -57,7 +57,7 @@ "web-worker": "^1.5.0" }, "peerDependencies": { - "bson": "^4.6.3 || ^5 || ^6.10.3 || ^7.0.0" + "bson": "^4.6.3 || ^5 || ^6.10.3 || ^7.3.2" }, "devDependencies": { "@mongodb-js/eslint-config-devtools": "^0.11.13", @@ -68,7 +68,7 @@ "@types/estree": "^0.0.41", "@types/mocha": "^9.1.1", "benchmark": "^2.1.4", - "bson": "^4.6.3 || ^5 || ^6.10.3 || ^7.0.0", + "bson": "^4.6.3 || ^5 || ^6.10.3 || ^7.3.2", "chai": "^4.5.0", "depcheck": "^1.4.7", "eslint": "^7.25.0 || ^8.0.0", diff --git a/packages/shell-bson-parser/src/index.spec.ts b/packages/shell-bson-parser/src/index.spec.ts index e9fd0c38..2584b8db 100644 --- a/packages/shell-bson-parser/src/index.spec.ts +++ b/packages/shell-bson-parser/src/index.spec.ts @@ -11,6 +11,7 @@ import { terminateWorker, callWorker, getExecutionTimeoutMs, + getWorkerScriptUrl, } from './worker-client.js'; import { restrictGlobalScope, @@ -39,6 +40,23 @@ describe('shell-bson-parser with webworker processing', function () { terminateWorker(); }); + it('returns the file URL for the worker script', async function () { + const initialWorkerScriptUrl = process.env.TEST_WORKER_SCRIPT_URL; + delete process.env.TEST_WORKER_SCRIPT_URL; + + try { + expect(await getWorkerScriptUrl()).to.equal( + new URL('./worker.js', import.meta.url).toString(), + ); + } finally { + if (initialWorkerScriptUrl) { + process.env.TEST_WORKER_SCRIPT_URL = initialWorkerScriptUrl; + } else { + delete process.env.TEST_WORKER_SCRIPT_URL; + } + } + }); + it('keeps the error type of a failed parse', async function () { try { await api.parse('{ a: }'); @@ -56,6 +74,16 @@ describe('shell-bson-parser with webworker processing', function () { expect(res).to.deep.equal(expected); }); } + + it('keeps the error type of a failed parse', async function () { + try { + await api.parse('{ a: }'); + expect.fail('Expected parse to throw'); + } catch (err) { + expect(err).to.be.instanceOf(SyntaxError); + expect((err as Error).name).to.equal('SyntaxError'); + } + }); }); describe('restrictGlobalScope', function () { @@ -176,7 +204,20 @@ describe('shell-bson-parser with webworker processing', function () { const code = await fs.readFile(workerBundlePath, 'utf8'); const sandbox: Record = Object.create(null); - sandbox.postMessage = function postMessage() {}; + let messageHandler: ((event: MessageEvent) => void) | undefined; + let response: any; + sandbox.postMessage = function postMessage(value: unknown) { + expect(this).to.equal(workerScope); + response = value; + }; + sandbox.addEventListener = function addEventListener( + name: string, + handler: (event: MessageEvent) => void, + ) { + expect(name).to.equal('message'); + messageHandler = handler; + }; + sandbox.onmessage = null; sandbox.fetch = function fetch() {}; sandbox.require = function require() {}; sandbox.importScripts = function importScripts() {}; @@ -186,6 +227,7 @@ describe('shell-bson-parser with webworker processing', function () { sandbox.globalThis = sandbox; vm.createContext(sandbox); + const workerScope = vm.runInContext('globalThis', sandbox); expect(sandbox).to.have.property('fetch'); expect(sandbox).to.have.property('require'); @@ -199,7 +241,13 @@ describe('shell-bson-parser with webworker processing', function () { expect(sandbox).to.not.have.property('importScripts'); expect(sandbox).to.not.have.property('XMLHttpRequest'); - expect(typeof sandbox.onmessage).to.equal('function'); + expect(sandbox).to.not.have.property('onmessage'); + expect(messageHandler).to.be.a('function'); + messageHandler!({ + data: { id: 7, args: { data: ['{ a: 1 }'], bsonTypes: new Map() } }, + } as MessageEvent); + expect(response).to.include({ id: 7, ok: true }); + expect(response.result.data).to.deep.equal({ a: 1 }); const stillHasProtoAccessor = vm.runInContext( `Object.prototype.hasOwnProperty('__proto__')`, diff --git a/packages/shell-bson-parser/src/worker-client.ts b/packages/shell-bson-parser/src/worker-client.ts index 0dbf64f0..dedab114 100644 --- a/packages/shell-bson-parser/src/worker-client.ts +++ b/packages/shell-bson-parser/src/worker-client.ts @@ -33,25 +33,20 @@ const pending = new Map< } >(); -const isNodeEnv = - typeof window === 'undefined' && - typeof process !== 'undefined' && - !!process.versions?.node; - -async function getWorkerScriptUrl(): Promise { - if (process.env.TEST_WORKER_SCRIPT_URL) { - return new URL( - process.env.TEST_WORKER_SCRIPT_URL, - import.meta.url, - ).toString(); - } - if (isNodeEnv) { - return new URL('./worker.js', import.meta.url).toString(); +/** Exported for tests */ +export async function getWorkerScriptUrl(): Promise { + const override = + typeof process !== 'undefined' + ? process.env.TEST_WORKER_SCRIPT_URL + : undefined; + if (override) { + return new URL(override, import.meta.url).toString(); } - // On browser env we want to fetch and blob so that the worker - // script can run on the web running locally. const scriptUrl = new URL('./worker.js', import.meta.url); + if (scriptUrl.protocol === 'file:') { + return scriptUrl.toString(); + } const response = await fetch(scriptUrl); if (!response.ok) { throw new Error( @@ -59,7 +54,11 @@ async function getWorkerScriptUrl(): Promise { ); } const code = await response.text(); - blobUrl = URL.createObjectURL(new Blob([code], { type: 'text/javascript' })); + // Compass Web injects whatwg-url for bare URL references; it lacks blob APIs. + // Use the native constructor for blob creation and revocation. + blobUrl = globalThis.URL.createObjectURL( + new Blob([code], { type: 'text/javascript' }), + ); return blobUrl; } @@ -73,7 +72,10 @@ function createWorker(): Promise { workerPromise = (async () => { const scriptUrl = await getWorkerScriptUrl(); - const newWorker = new WebWorker(scriptUrl, { type: 'module' }); + const newWorker = new WebWorker( + scriptUrl, + typeof globalThis.Worker === 'function' ? undefined : { type: 'module' }, + ); const onMessageHandler = (event: MessageEvent) => { const response = event.data; const entry = pending.get(response.id); @@ -153,7 +155,7 @@ export function terminateWorker( reason: Error = new Error('Worker terminated'), ): void { if (worker) worker.terminate(); - if (blobUrl) URL.revokeObjectURL(blobUrl); + if (blobUrl) globalThis.URL.revokeObjectURL(blobUrl); worker = null; workerPromise = null; diff --git a/packages/shell-bson-parser/src/worker.ts b/packages/shell-bson-parser/src/worker.ts index 7b45b1c1..8a61c20d 100644 --- a/packages/shell-bson-parser/src/worker.ts +++ b/packages/shell-bson-parser/src/worker.ts @@ -81,9 +81,10 @@ export function handleRequest(request: WorkerRequest): WorkerResponse { } if (typeof self !== 'undefined') { + const sendResponse = postMessage.bind(self); + self.addEventListener('message', (event: MessageEvent) => { + sendResponse(handleRequest(event.data)); + }); restrictObjectPrototype(); restrictGlobalScope(globalThis); - self.onmessage = (event: MessageEvent) => { - postMessage(handleRequest(event.data)); - }; } diff --git a/packages/shell-bson-parser/test/fixtures/slow-worker.mjs b/packages/shell-bson-parser/test/fixtures/slow-worker.mjs index fc78c456..c1c47b89 100644 --- a/packages/shell-bson-parser/test/fixtures/slow-worker.mjs +++ b/packages/shell-bson-parser/test/fixtures/slow-worker.mjs @@ -1,14 +1,14 @@ // Avoiding the import of these two functions, // adding them here to keep this file self-contained -function unmarkBSON(value) { +function untrackBSON(value) { return value.data; } -function markBSON(value) { +function trackBSON(value) { return { data: value, bsonTypes: new Map() }; } self.onmessage = (event) => { const { id, args } = event.data; - const [delayMs] = unmarkBSON(args); + const [delayMs] = untrackBSON(args); const start = Date.now(); while (Date.now() - start < delayMs) { // noop @@ -16,6 +16,6 @@ self.onmessage = (event) => { self.postMessage({ id, ok: true, - result: markBSON('done'), + result: trackBSON('done'), }); }; diff --git a/packages/shell-bson-parser/webpack.cjs.config.cjs b/packages/shell-bson-parser/webpack.cjs.config.cjs index ca44bb9e..821719c2 100644 --- a/packages/shell-bson-parser/webpack.cjs.config.cjs +++ b/packages/shell-bson-parser/webpack.cjs.config.cjs @@ -1,25 +1,57 @@ 'use strict'; const path = require('path'); +const webpack = require('webpack'); + +const distDir = path.resolve(__dirname, 'dist'); +const cjsDir = path.join(distDir, 'cjs'); -// Bundles the already tsc-compiled ESM dist/index.js into a standalone -// CommonJS entry point. The worker file stays same as in the ESM build. module.exports = { mode: 'production', target: 'node', - entry: path.resolve(__dirname, 'dist', 'index.js'), - output: { - path: path.resolve(__dirname, 'dist', 'cjs'), - filename: 'index.js', - library: { - type: 'commonjs2', + entry: { + index: path.join(distDir, 'index.js'), + 'web-worker': { + import: 'web-worker', + library: { type: 'commonjs2', export: 'default' }, }, }, - externals: [/^acorn/, /^javascript-stringify/, /^web-worker/, /^bson/], + resolve: { conditionNames: ['import', 'node', 'default'] }, + output: { + path: cjsDir, + clean: true, + filename: '[name].js', + library: { type: 'commonjs2' }, + environment: { dynamicImport: true }, + }, + externals: [ + /^acorn/, + /^javascript-stringify/, + /^bson/, + ({ request, contextInfo }, callback) => { + if (request === 'web-worker' && contextInfo.issuer) { + return callback(null, 'commonjs ./web-worker.js'); + } + callback(); + }, + ], module: { parser: { javascript: { + url: false, + import: false, importExportsPresence: 'warn', }, }, }, + plugins: [ + new webpack.DefinePlugin({ + 'import.meta.url': webpack.DefinePlugin.runtimeValue( + ({ module }) => + module.resource === path.join(distDir, 'worker-client.js') + ? 'require("node:url").pathToFileURL(require("node:path").join(__dirname, "..", "worker-client.js")).href' + : 'require("node:url").pathToFileURL(__filename).href', + true, + ), + }), + ], };