From 393f9baf78e5dc929965e5a6442ba65cb79cc0eb Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Mon, 5 Oct 2026 12:32:05 +0200 Subject: [PATCH 1/7] module, error and jsdom --- packages/shell-bson-parser/src/index.spec.ts | 16 +++++++++++++--- packages/shell-bson-parser/src/worker-client.ts | 10 +++++++--- .../src/{worker.ts => worker.mts} | 0 .../shell-bson-parser/webpack.worker.config.cjs | 4 ++-- 4 files changed, 22 insertions(+), 8 deletions(-) rename packages/shell-bson-parser/src/{worker.ts => worker.mts} (100%) diff --git a/packages/shell-bson-parser/src/index.spec.ts b/packages/shell-bson-parser/src/index.spec.ts index e9fd0c38..1bd2f725 100644 --- a/packages/shell-bson-parser/src/index.spec.ts +++ b/packages/shell-bson-parser/src/index.spec.ts @@ -17,7 +17,7 @@ import { restrictObjectPrototype, ALLOWED_GLOBALS, DISALLOWED_PROTOTYPE_PROPS, -} from './worker.js'; +} from './worker.mjs'; import { PARSE_TEST_CASES } from './../test/parse-test-cases.js'; const WebWorker = (WebWorkerModule as unknown as { default: typeof Worker }) @@ -27,7 +27,7 @@ describe('shell-bson-parser with webworker processing', function () { const initialWorkerScriptUrl = process.env.TEST_WORKER_SCRIPT_URL; before(function () { - process.env.TEST_WORKER_SCRIPT_URL = '../dist/worker.js'; + process.env.TEST_WORKER_SCRIPT_URL = '../dist/worker.mjs'; }); after(function () { @@ -56,6 +56,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 () { @@ -171,7 +181,7 @@ describe('shell-bson-parser with webworker processing', function () { path.dirname(fileURLToPath(import.meta.url)), '..', 'dist', - 'worker.js', + 'worker.mjs', ); const code = await fs.readFile(workerBundlePath, 'utf8'); diff --git a/packages/shell-bson-parser/src/worker-client.ts b/packages/shell-bson-parser/src/worker-client.ts index 0dbf64f0..74f75cfe 100644 --- a/packages/shell-bson-parser/src/worker-client.ts +++ b/packages/shell-bson-parser/src/worker-client.ts @@ -39,14 +39,18 @@ const isNodeEnv = !!process.versions?.node; async function getWorkerScriptUrl(): Promise { - if (process.env.TEST_WORKER_SCRIPT_URL) { + const testWorkerScriptUrl = + typeof process !== 'undefined' + ? process.env?.TEST_WORKER_SCRIPT_URL + : undefined; + if (testWorkerScriptUrl) { return new URL( - process.env.TEST_WORKER_SCRIPT_URL, + testWorkerScriptUrl, import.meta.url, ).toString(); } if (isNodeEnv) { - return new URL('./worker.js', import.meta.url).toString(); + return new URL('./worker.mjs', import.meta.url).toString(); } // On browser env we want to fetch and blob so that the worker diff --git a/packages/shell-bson-parser/src/worker.ts b/packages/shell-bson-parser/src/worker.mts similarity index 100% rename from packages/shell-bson-parser/src/worker.ts rename to packages/shell-bson-parser/src/worker.mts diff --git a/packages/shell-bson-parser/webpack.worker.config.cjs b/packages/shell-bson-parser/webpack.worker.config.cjs index 0f736a65..044fd153 100644 --- a/packages/shell-bson-parser/webpack.worker.config.cjs +++ b/packages/shell-bson-parser/webpack.worker.config.cjs @@ -4,10 +4,10 @@ const path = require('path'); module.exports = { mode: 'production', target: 'webworker', - entry: path.resolve(__dirname, 'dist', 'worker.js'), + entry: path.resolve(__dirname, 'dist', 'worker.mjs'), output: { path: path.resolve(__dirname, 'dist'), - filename: 'worker.js', + filename: 'worker.mjs', }, module: { parser: { From b0d36932def9cf4562a376cb658e8afaccfba682 Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Mon, 5 Oct 2026 17:18:50 +0200 Subject: [PATCH 2/7] reformat --- packages/shell-bson-parser/src/worker-client.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/shell-bson-parser/src/worker-client.ts b/packages/shell-bson-parser/src/worker-client.ts index 74f75cfe..042516c0 100644 --- a/packages/shell-bson-parser/src/worker-client.ts +++ b/packages/shell-bson-parser/src/worker-client.ts @@ -44,10 +44,7 @@ async function getWorkerScriptUrl(): Promise { ? process.env?.TEST_WORKER_SCRIPT_URL : undefined; if (testWorkerScriptUrl) { - return new URL( - testWorkerScriptUrl, - import.meta.url, - ).toString(); + return new URL(testWorkerScriptUrl, import.meta.url).toString(); } if (isNodeEnv) { return new URL('./worker.mjs', import.meta.url).toString(); From 78bc42e3f4faf2c897a863b1ba69531cd191b653 Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Mon, 5 Oct 2026 17:52:36 +0200 Subject: [PATCH 3/7] bson bump --- package-lock.json | 18 +++++++++--------- packages/mongodb-constants/package.json | 4 ++-- packages/mongodb-log-writer/package.json | 2 +- packages/mongodb-schema/package.json | 4 ++-- packages/mongodb-ts-autocomplete/package.json | 2 +- packages/mql-typescript/package.json | 2 +- packages/shell-bson-parser/package.json | 4 ++-- 7 files changed, 18 insertions(+), 18 deletions(-) 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/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", From 77a81736dc09aef2d7ee9d7fd883924941c212aa Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Mon, 5 Oct 2026 22:33:10 +0200 Subject: [PATCH 4/7] undo ts to mts --- packages/shell-bson-parser/src/index.spec.ts | 6 +++--- packages/shell-bson-parser/src/worker-client.ts | 2 +- packages/shell-bson-parser/src/{worker.mts => worker.ts} | 0 packages/shell-bson-parser/webpack.worker.config.cjs | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) rename packages/shell-bson-parser/src/{worker.mts => worker.ts} (100%) diff --git a/packages/shell-bson-parser/src/index.spec.ts b/packages/shell-bson-parser/src/index.spec.ts index 1bd2f725..7b2798ca 100644 --- a/packages/shell-bson-parser/src/index.spec.ts +++ b/packages/shell-bson-parser/src/index.spec.ts @@ -17,7 +17,7 @@ import { restrictObjectPrototype, ALLOWED_GLOBALS, DISALLOWED_PROTOTYPE_PROPS, -} from './worker.mjs'; +} from './worker.js'; import { PARSE_TEST_CASES } from './../test/parse-test-cases.js'; const WebWorker = (WebWorkerModule as unknown as { default: typeof Worker }) @@ -27,7 +27,7 @@ describe('shell-bson-parser with webworker processing', function () { const initialWorkerScriptUrl = process.env.TEST_WORKER_SCRIPT_URL; before(function () { - process.env.TEST_WORKER_SCRIPT_URL = '../dist/worker.mjs'; + process.env.TEST_WORKER_SCRIPT_URL = '../dist/worker.js'; }); after(function () { @@ -181,7 +181,7 @@ describe('shell-bson-parser with webworker processing', function () { path.dirname(fileURLToPath(import.meta.url)), '..', 'dist', - 'worker.mjs', + 'worker.js', ); const code = await fs.readFile(workerBundlePath, 'utf8'); diff --git a/packages/shell-bson-parser/src/worker-client.ts b/packages/shell-bson-parser/src/worker-client.ts index 042516c0..0c8f6958 100644 --- a/packages/shell-bson-parser/src/worker-client.ts +++ b/packages/shell-bson-parser/src/worker-client.ts @@ -47,7 +47,7 @@ async function getWorkerScriptUrl(): Promise { return new URL(testWorkerScriptUrl, import.meta.url).toString(); } if (isNodeEnv) { - return new URL('./worker.mjs', import.meta.url).toString(); + return new URL('./worker.js', import.meta.url).toString(); } // On browser env we want to fetch and blob so that the worker diff --git a/packages/shell-bson-parser/src/worker.mts b/packages/shell-bson-parser/src/worker.ts similarity index 100% rename from packages/shell-bson-parser/src/worker.mts rename to packages/shell-bson-parser/src/worker.ts diff --git a/packages/shell-bson-parser/webpack.worker.config.cjs b/packages/shell-bson-parser/webpack.worker.config.cjs index 044fd153..0f736a65 100644 --- a/packages/shell-bson-parser/webpack.worker.config.cjs +++ b/packages/shell-bson-parser/webpack.worker.config.cjs @@ -4,10 +4,10 @@ const path = require('path'); module.exports = { mode: 'production', target: 'webworker', - entry: path.resolve(__dirname, 'dist', 'worker.mjs'), + entry: path.resolve(__dirname, 'dist', 'worker.js'), output: { path: path.resolve(__dirname, 'dist'), - filename: 'worker.mjs', + filename: 'worker.js', }, module: { parser: { From a8822fba8b8dbbc42646a68716a69674031c0f2f Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Thu, 8 Oct 2026 10:50:37 +0200 Subject: [PATCH 5/7] wip integration --- .../shell-bson-parser/src/worker-client.ts | 44 ++++++++++++++----- .../shell-bson-parser/webpack.cjs.config.cjs | 16 ++++++- 2 files changed, 49 insertions(+), 11 deletions(-) diff --git a/packages/shell-bson-parser/src/worker-client.ts b/packages/shell-bson-parser/src/worker-client.ts index 0c8f6958..612697c4 100644 --- a/packages/shell-bson-parser/src/worker-client.ts +++ b/packages/shell-bson-parser/src/worker-client.ts @@ -1,6 +1,3 @@ -import * as WebWorkerModule from 'web-worker'; -const WebWorker = (WebWorkerModule as unknown as { default: typeof Worker }) - .default; import { trackBSON, untrackBSON } from './structured-clone-bson.js'; import type { WorkerResponse } from './worker-types.js'; @@ -33,27 +30,53 @@ const pending = new Map< } >(); +// jsdom defines `window` but no `Worker`, so `typeof window` alone cannot tell +// a real browser apart from a Node test environment. const isNodeEnv = - typeof window === 'undefined' && typeof process !== 'undefined' && - !!process.versions?.node; + !!process.versions?.node && + typeof (globalThis as { Worker?: unknown }).Worker === 'undefined'; + +async function loadWebWorker(): Promise { + // web-worker's CJS build mistakes jsdom for a browser and throws when + // constructing a worker; its ESM entry resolves import.meta.url correctly. + // The CJS bundle keeps this a native import() (see webpack.cjs.config.cjs). + const WebWorkerModule = (await import('web-worker')) as unknown as { + default: typeof Worker; + }; + return WebWorkerModule.default; +} async function getWorkerScriptUrl(): Promise { const testWorkerScriptUrl = typeof process !== 'undefined' ? process.env?.TEST_WORKER_SCRIPT_URL : undefined; + + // eslint-disable-next-line no-console + console.log(` + testWorkerScriptUrl: ${String(testWorkerScriptUrl)} + isNodeEnv: ${String(isNodeEnv)} + window: ${String(typeof window !== 'undefined')} + process: ${String(typeof process !== 'undefined')} + cjs or esm: ${String(typeof require !== 'undefined')} + `); + + // Bundlers rewrite `new URL(, import.meta.url)` into an unresolvable + // context module and inline a bare `import.meta.url` as a build-time path, so + // resolve the override against the emitted worker URL instead. + const workerScriptUrl = new URL('./worker.js', import.meta.url); + if (testWorkerScriptUrl) { - return new URL(testWorkerScriptUrl, import.meta.url).toString(); + return new URL(testWorkerScriptUrl, workerScriptUrl).toString(); } if (isNodeEnv) { - return new URL('./worker.js', import.meta.url).toString(); + return workerScriptUrl.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); - const response = await fetch(scriptUrl); + // script can run on atlas-cloud running locally. + const response = await fetch(workerScriptUrl); if (!response.ok) { throw new Error( `Failed to fetch shell-bson-parser worker script: ${response.status} ${response.statusText}`, @@ -74,6 +97,7 @@ function createWorker(): Promise { workerPromise = (async () => { const scriptUrl = await getWorkerScriptUrl(); + const WebWorker = await loadWebWorker(); const newWorker = new WebWorker(scriptUrl, { type: 'module' }); const onMessageHandler = (event: MessageEvent) => { const response = event.data; diff --git a/packages/shell-bson-parser/webpack.cjs.config.cjs b/packages/shell-bson-parser/webpack.cjs.config.cjs index ca44bb9e..b51d4f7c 100644 --- a/packages/shell-bson-parser/webpack.cjs.config.cjs +++ b/packages/shell-bson-parser/webpack.cjs.config.cjs @@ -10,11 +10,25 @@ module.exports = { output: { path: path.resolve(__dirname, 'dist', 'cjs'), filename: 'index.js', + // Keep the emitted worker at a stable `worker.js` so runtime resolution + // next to this bundle finds it instead of a content-hashed name. + assetModuleFilename: '[name][ext]', + // Allow native `import()` for the `web-worker` external below. + environment: { + dynamicImport: true, + }, library: { type: 'commonjs2', }, }, - externals: [/^acorn/, /^javascript-stringify/, /^web-worker/, /^bson/], + // `web-worker` must resolve through its ESM entry: its CJS build mis-detects + // jsdom as a browser and throws when constructing a worker. + externals: [ + { 'web-worker': 'import web-worker' }, + /^acorn/, + /^javascript-stringify/, + /^bson/, + ], module: { parser: { javascript: { From dd3bddea9736c48dd4a7245a20586be3cd008086 Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Wed, 14 Oct 2026 11:51:03 +0200 Subject: [PATCH 6/7] publish worker cjs --- packages/shell-bson-parser/README.md | 2 - packages/shell-bson-parser/src/index.spec.ts | 24 ++++++- .../shell-bson-parser/src/worker-client.ts | 68 ++++++------------- packages/shell-bson-parser/src/worker.ts | 7 +- .../test/fixtures/slow-worker.mjs | 8 +-- .../shell-bson-parser/webpack.cjs.config.cjs | 54 ++++++++++----- 6 files changed, 88 insertions(+), 75 deletions(-) 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/src/index.spec.ts b/packages/shell-bson-parser/src/index.spec.ts index 7b2798ca..bb3f1f06 100644 --- a/packages/shell-bson-parser/src/index.spec.ts +++ b/packages/shell-bson-parser/src/index.spec.ts @@ -186,7 +186,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() {}; @@ -196,6 +209,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'); @@ -209,7 +223,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 612697c4..6c6a1a6e 100644 --- a/packages/shell-bson-parser/src/worker-client.ts +++ b/packages/shell-bson-parser/src/worker-client.ts @@ -1,3 +1,6 @@ +import * as WebWorkerModule from 'web-worker'; +const WebWorker = (WebWorkerModule as unknown as { default: typeof Worker }) + .default; import { trackBSON, untrackBSON } from './structured-clone-bson.js'; import type { WorkerResponse } from './worker-types.js'; @@ -30,60 +33,31 @@ const pending = new Map< } >(); -// jsdom defines `window` but no `Worker`, so `typeof window` alone cannot tell -// a real browser apart from a Node test environment. -const isNodeEnv = - typeof process !== 'undefined' && - !!process.versions?.node && - typeof (globalThis as { Worker?: unknown }).Worker === 'undefined'; - -async function loadWebWorker(): Promise { - // web-worker's CJS build mistakes jsdom for a browser and throws when - // constructing a worker; its ESM entry resolves import.meta.url correctly. - // The CJS bundle keeps this a native import() (see webpack.cjs.config.cjs). - const WebWorkerModule = (await import('web-worker')) as unknown as { - default: typeof Worker; - }; - return WebWorkerModule.default; -} - async function getWorkerScriptUrl(): Promise { - const testWorkerScriptUrl = + const override = typeof process !== 'undefined' - ? process.env?.TEST_WORKER_SCRIPT_URL + ? process.env.TEST_WORKER_SCRIPT_URL : undefined; - - // eslint-disable-next-line no-console - console.log(` - testWorkerScriptUrl: ${String(testWorkerScriptUrl)} - isNodeEnv: ${String(isNodeEnv)} - window: ${String(typeof window !== 'undefined')} - process: ${String(typeof process !== 'undefined')} - cjs or esm: ${String(typeof require !== 'undefined')} - `); - - // Bundlers rewrite `new URL(, import.meta.url)` into an unresolvable - // context module and inline a bare `import.meta.url` as a build-time path, so - // resolve the override against the emitted worker URL instead. - const workerScriptUrl = new URL('./worker.js', import.meta.url); - - if (testWorkerScriptUrl) { - return new URL(testWorkerScriptUrl, workerScriptUrl).toString(); - } - if (isNodeEnv) { - return workerScriptUrl.toString(); + 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 atlas-cloud running locally. - const response = await fetch(workerScriptUrl); + 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( `Failed to fetch shell-bson-parser worker script: ${response.status} ${response.statusText}`, ); } 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; } @@ -97,8 +71,10 @@ function createWorker(): Promise { workerPromise = (async () => { const scriptUrl = await getWorkerScriptUrl(); - const WebWorker = await loadWebWorker(); - 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); @@ -178,7 +154,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 b51d4f7c..821719c2 100644 --- a/packages/shell-bson-parser/webpack.cjs.config.cjs +++ b/packages/shell-bson-parser/webpack.cjs.config.cjs @@ -1,39 +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', - // Keep the emitted worker at a stable `worker.js` so runtime resolution - // next to this bundle finds it instead of a content-hashed name. - assetModuleFilename: '[name][ext]', - // Allow native `import()` for the `web-worker` external below. - environment: { - dynamicImport: true, - }, - library: { - type: 'commonjs2', + entry: { + index: path.join(distDir, 'index.js'), + 'web-worker': { + import: 'web-worker', + library: { type: 'commonjs2', export: 'default' }, }, }, - // `web-worker` must resolve through its ESM entry: its CJS build mis-detects - // jsdom as a browser and throws when constructing a worker. + resolve: { conditionNames: ['import', 'node', 'default'] }, + output: { + path: cjsDir, + clean: true, + filename: '[name].js', + library: { type: 'commonjs2' }, + environment: { dynamicImport: true }, + }, externals: [ - { 'web-worker': 'import web-worker' }, /^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, + ), + }), + ], }; From 75b942a8d2c3dac3d453818deecd65ead76d9dab Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Thu, 15 Oct 2026 11:19:25 +0200 Subject: [PATCH 7/7] add test for file scheme --- packages/shell-bson-parser/src/index.spec.ts | 18 ++++++++++++++++++ .../shell-bson-parser/src/worker-client.ts | 3 ++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/shell-bson-parser/src/index.spec.ts b/packages/shell-bson-parser/src/index.spec.ts index bb3f1f06..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: }'); diff --git a/packages/shell-bson-parser/src/worker-client.ts b/packages/shell-bson-parser/src/worker-client.ts index 6c6a1a6e..dedab114 100644 --- a/packages/shell-bson-parser/src/worker-client.ts +++ b/packages/shell-bson-parser/src/worker-client.ts @@ -33,7 +33,8 @@ const pending = new Map< } >(); -async function getWorkerScriptUrl(): Promise { +/** Exported for tests */ +export async function getWorkerScriptUrl(): Promise { const override = typeof process !== 'undefined' ? process.env.TEST_WORKER_SCRIPT_URL