Skip to content

fix(web): load SQLite worker in VS Code Web (vscode.dev) (#418)#419

Merged
zknpr merged 4 commits into
mainfrom
fix/418-vscode-dev-worker-load
May 31, 2026
Merged

fix(web): load SQLite worker in VS Code Web (vscode.dev) (#418)#419
zknpr merged 4 commits into
mainfrom
fix/418-vscode-dev-worker-load

Conversation

@zknpr
Copy link
Copy Markdown
Owner

@zknpr zknpr commented May 31, 2026

Summary

Fixes #418 — in VS Code for Web (vscode.dev / github.dev) every database file (.db, .sqlite, .gpkg, …) hangs forever on the loading screen. Desktop VS Code and the website demo are unaffected because they use different worker paths.

Root cause (reproduced live in a real browser, not theorized)

The browser SQLite worker (out/worker-browser.js, spawned via new Worker(blobUrl) in workerFactory.ts) boots but immediately throws, so it never wires up its message handler — every host→worker RPC then times out silently and the viewer stays on "loading".

Live console from the unfixed build:

[DatabaseWorker] Starting...
Uncaught TypeError: <fn>.on is not a function

src/databaseWorker.ts calls Node's parentPort.on('message', …). In a browser Web Worker the global scope (DedicatedWorkerGlobalScope) exposes addEventListener/postMessage, not Node's .on(). threadPool.ts had set parentPort = globalThis, so .on(...) is undefinedTypeError → handler never registered → RPC hangs.

Fix

  1. src/platform/threadPool.ts — provide a Node-style parentPort adapter for the browser runtime via an exported, unit-testable createBrowserParentPort(scope) that maps .on('message', cb)addEventListener('message', e => cb(e.data)) (and passes other events, e.g. error, through). databaseWorker.ts is unchanged.
  2. scripts/build.mjs — emit the browser worker as IIFE instead of ESM. This is defensive hardening, not an independent cause of [Bug]: Cannot load gpkg in vscode.dev #418: databaseWorker.ts has no exports today, so the ESM output had no top-level export{} and still parsed as a classic worker. But loading an ESM bundle via classic new Worker(blobUrl) is wrong-in-principle and would break the moment any top-level export is introduced. IIFE matches how the bundle is actually loaded.

Tests (new regressions, both proven to fail when their fix is reverted)

  • tests/unit/worker_browser_bundle.test.ts — parses the built out/worker-browser.js with node:vm as a classic script; fails if the bundle ever reverts to an ESM/module format.
  • tests/unit/threadPool_browser_parentport.test.ts — covers the adapter: message-data delivery, error passthrough, postMessage/transfer forwarding, and off().

Verification

  • node scripts/build.mjs ✓ · npx tsc --noEmit -p tsconfig.json ✓ · npm test332/332 pass
  • End-to-end in a real browser: built the worker exactly as workerFactory.ts does (classic blob-URL worker) → worker reaches Ready for connections, initializeDatabase + runQuery succeed (rows returned), zero worker errors. Reverting either fix reproduces the hang / fails the corresponding test.
  • Also launched the actual VS Code Web build via @vscode/test-web with this build sideloaded (the same runtime vscode.dev serves) for manual click-through.

Release prep (in the second commit)

Since this is the first change to cut a release after v1.3.5, the 1.3.6 CHANGELOG section covers all 46 commits merged since the v1.3.5 tag (this fix + perf, refactors, tests, CI, website, deps), package.json is bumped to 1.3.6, and release.yml now skips hyphenated tags so a GitHub pre-release (v1.3.6-rc.1) won't trigger the stable-release job.

Closes #418.

Summary by CodeRabbit

  • New Features

    • Enhanced browser worker compatibility with improved message handling.
  • Bug Fixes

    • Fixed potential hanging issues in VS Code web with corrected worker script format.
    • Multiple bug fixes documented in changelog.
  • Improvements

    • Performance enhancements.
    • Documentation and CI tooling updates.
  • Chores

    • Version bumped to 1.3.6.

zknpr and others added 2 commits May 31, 2026 21:37
Every database hung on the loading screen in vscode.dev (works in the web
demo and on desktop). The browser worker never booted, due to two stacked
bugs, both confirmed live in a real browser:

1. out/worker-browser.js was bundled as ESM (format: 'esm'), but
   workerFactory.ts loads it as a CLASSIC worker via `new Worker(blobUrl)`.
   An ESM bundle emits a top-level `export{...}`, which is a SyntaxError in
   a classic worker ("Unexpected token 'export'"); the worker never started.

2. threadPool.ts set the browser worker's `parentPort = globalThis`, but
   databaseWorker.ts calls `parentPort.on('message', ...)`. The worker
   global scope exposes addEventListener, not Node's `.on()`, so even once
   the worker loaded it threw `TypeError: parentPort.on is not a function`
   and never wired up its message handler -> host RPC hangs forever.

Fix:
- Build the browser worker as IIFE (classic-worker compatible; matches
  extension-browser.js and the emscripten/sql.js classic-worker env).
- Add a Node-style {on, off, postMessage} adapter over the worker global
  scope in threadPool.ts so databaseWorker.ts works unchanged.

Verified end-to-end: a classic blob-URL worker built from this config
loads, initializes sql.js, and runs queries. tsc clean, build green,
330 unit tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… CI guard

Builds on the #418 fix (browser worker parentPort adapter) committed earlier
on this branch.

- threadPool.ts: extract the browser parentPort adapter into an exported,
  unit-testable createBrowserParentPort() factory (behavior unchanged).
- tests: add two regressions for #418 —
  - worker_browser_bundle.test.ts: parse out/worker-browser.js with node:vm
    as a classic script; fails if the bundle ever reverts to an ESM/module
    format (the classic-vs-module mismatch hardened in build.mjs).
  - threadPool_browser_parentport.test.ts: cover the adapter (message data
    delivery, error passthrough, postMessage forwarding, off()).
- CHANGELOG.md: add the 1.3.6 section covering all changes since v1.3.5
  (46 commits: #418 fix, perf, refactors, tests, CI, website, deps).
- package.json: 1.3.5 -> 1.3.6.
- release.yml: skip hyphenated tags (e.g. v1.3.6-rc.1) so GitHub pre-releases
  don't trigger the stable-release job.
- .gitignore: ignore local browser/extension test-harness artifacts.

Verified: node scripts/build.mjs OK; tsc --noEmit -p tsconfig.json clean;
npm test 332/332 pass; browser e2e repro of the fixed worker passes
(worker boots, initializeDatabase + runQuery succeed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 31, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
sq-lite-explorer Ready Ready Preview, Comment May 31, 2026 9:05pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 31, 2026

Review Change Stack

Warning

Review limit reached

@zknpr, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 41 minutes and 54 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e3755bdc-dacf-4f57-a180-5e7a7e0a8988

📥 Commits

Reviewing files that changed from the base of the PR and between d79cb47 and a4440ec.

📒 Files selected for processing (3)
  • src/platform/threadPool.ts
  • tests/unit/threadPool_browser_parentport.test.ts
  • tests/unit/worker_browser_bundle.test.ts
📝 Walkthrough

Walkthrough

Version 1.3.6 fixes a hang in vscode.dev when loading gpkg files by switching the browser worker bundle from ESM to IIFE format and adapting the browser parentPort to handle worker messages correctly. The release includes comprehensive changelog entries, a version bump, and updated deployment configuration to publish only stable tags.

Changes

v1.3.6 Release - Browser Worker Compatibility Fix

Layer / File(s) Summary
Browser worker bundle IIFE format configuration
scripts/build.mjs, tests/unit/worker_browser_bundle.test.ts
The esbuild configuration for the browser worker now emits IIFE format instead of ESM, because the classic web worker loader cannot parse ESM top-level exports. A regression test validates the bundle is emitted in IIFE form and can be parsed without module syntax errors.
Browser parentPort adapter for worker message semantics
src/platform/threadPool.ts, tests/unit/threadPool_browser_parentport.test.ts
A new createBrowserParentPort adapter bridges browser worker global scope to Node.js parentPort semantics, unwrapping MessageEvent.data for message events and preserving other events. Browser initialization now creates the adapter, and comprehensive tests verify event listener adaptation and message forwarding with transferables.
Release version and deployment configuration
package.json, CHANGELOG.md, .github/workflows/release.yml, .gitignore
Extension version bumped to 1.3.6; detailed release notes added for bug fixes, performance, improvements, tests, CI, docs, website, and dependencies; release workflow updated to skip pre-release tags and only publish stable versions; .gitignore extended to exclude browser test artifacts.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A worker spoke in browser's tongue,
But classic scripts had rules to follow,
IIFE wraps the bundle, done,
And messages flow through the adapter's hollow.
Now vscode.dev can load and sing!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR comprehensively addresses #418 (worker loading in vscode.dev) through IIFE emission and parentPort adapter, but does not address #39 (iterator optimization in native-worker.js). Verify whether #39 should be included in this PR or handled separately; if included, implement the iterator optimization in natives/native-worker.js and update package-lock.json.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main fix: enabling SQLite worker to load in VS Code Web, matching the changes in build config and thread pool adapter.
Out of Scope Changes check ✅ Passed All changes directly support the #418 fix: build config for IIFE emission, parentPort adapter, tests for validation, and version bump for release.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/418-vscode-dev-worker-load

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the extension to version 1.3.6, resolving a critical issue where VS Code for Web databases hang on the loading screen. This is fixed by switching the browser worker bundle format to IIFE and introducing a Node-style parentPort adapter (createBrowserParentPort) for the browser runtime, supported by new unit and regression tests. A review comment identifies a bug in the createBrowserParentPort implementation where registering the same handler for multiple event types will overwrite its entry in the WeakMap, leading to unregistration failures and memory leaks. The reviewer provides a code suggestion to use a nested map structure to resolve this issue.

Comment thread src/platform/threadPool.ts Outdated
Comment on lines +60 to +79
const browserListeners = new WeakMap<EventListenerOrEventListenerObject, EventListener>();

return {
postMessage: (data: unknown, transfer?: Transferable[]) =>
transfer ? scope.postMessage(data, transfer) : scope.postMessage(data),
on: (event: string, handler: EventListenerOrEventListenerObject) => {
// Node's parentPort.on('message', cb) passes the message DATA directly; the
// DOM 'message' event wraps it in event.data. For other events (e.g.
// 'error') pass the event through so consumers can read `.message`.
const cb = handler as (payload: unknown) => void;
const wrapped: EventListener = (e: Event) =>
cb(event === 'message' ? (e as MessageEvent).data : e);
browserListeners.set(handler, wrapped);
scope.addEventListener(event, wrapped);
},
off: (event: string, handler: EventListenerOrEventListenerObject) => {
const wrapped = browserListeners.get(handler);
if (wrapped) scope.removeEventListener(event, wrapped);
},
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current implementation of createBrowserParentPort uses a single WeakMap keyed by the handler to store the wrapped listener. If the same handler is registered for multiple event types (e.g., both 'message' and 'error'), the second registration will overwrite the first one in the WeakMap.

This leads to two issues:

  1. Calling off for the first event type will retrieve the wrapped listener of the second event type and attempt to remove it, which is a no-op and fails to unregister the listener.
  2. The original wrapped listener is leaked and can never be unregistered.

To fix this, we should store a nested Map of event names to wrapped listeners inside the WeakMap.

  const browserListeners = new WeakMap<EventListenerOrEventListenerObject, Map<string, EventListener>>();

  return {
    postMessage: (data: unknown, transfer?: Transferable[]) =>
      transfer ? scope.postMessage(data, transfer) : scope.postMessage(data),
    on: (event: string, handler: EventListenerOrEventListenerObject) => {
      // Node's parentPort.on('message', cb) passes the message DATA directly; the
      // DOM 'message' event wraps it in event.data. For other events (e.g.
      // 'error') pass the event through so consumers can read `.message`.
      const cb = handler as (payload: unknown) => void;
      const wrapped: EventListener = (e: Event) =>
        cb(event === 'message' ? (e as MessageEvent).data : e);

      let eventsMap = browserListeners.get(handler);
      if (!eventsMap) {
        eventsMap = new Map();
        browserListeners.set(handler, eventsMap);
      }
      eventsMap.set(event, wrapped);
      scope.addEventListener(event, wrapped);
    },
    off: (event: string, handler: EventListenerOrEventListenerObject) => {
      const eventsMap = browserListeners.get(handler);
      if (eventsMap) {
        const wrapped = eventsMap.get(event);
        if (wrapped) {
          scope.removeEventListener(event, wrapped);
          eventsMap.delete(event);
          if (eventsMap.size === 0) {
            browserListeners.delete(handler);
          }
        }
      }
    },
  };

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d79cb470d1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +19 to +21
if (!existsSync(bundlePath)) {
assert.fail(
'Missing out/worker-browser.js. Run `node scripts/build.mjs` before running this regression test.'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid requiring ignored build output in unit tests

When npm test is run from a clean checkout, out/worker-browser.js will not exist because out/ is ignored and the test script does not build first; this new assertion makes the whole unit suite fail unless a previous node scripts/build.mjs happened to leave artifacts behind. Either have the test build to a temporary output or skip/structure this as a build-validation test so the documented standalone npm test workflow remains reliable.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/platform/threadPool.ts`:
- Around line 60-78: The current browserListeners WeakMap (used by on/off) is
keyed only by handler so registering the same handler for different events
overwrites earlier wrapped listeners and prevents proper removal; change
browserListeners to map by event then handler (for example: Map<string,
WeakMap<EventListenerOrEventListenerObject, EventListener>>) and update on() to
get/create the per-event WeakMap and store wrapped there, and update off() to
look up the per-event WeakMap and remove the specific wrapped listener before
calling scope.removeEventListener(event, wrapped); keep using the existing
symbols browserListeners, on, off, scope.addEventListener and
scope.removeEventListener.

In `@tests/unit/threadPool_browser_parentport.test.ts`:
- Around line 87-91: Add a new test that registers the same handler function
twice (e.g., reuse messageHandler) for two different events via parentPort.on
and then calls parentPort.off for each event, asserting that removedListeners
contains two entries with event names matching each call and listener equal to
the originally registered wrapped listener (use registeredListeners to compare).
This verifies that off() correctly removes the wrapped listener when the same
handler is reused (exercise the WeakMap keying behavior referenced in
threadPool.ts) by checking removedListeners.length increments and
removedListeners[n].listener === registeredListeners[n].listener for both
events.
- Around line 9-11: Add an import for the VS Code mock setup at the very top of
tests/unit/threadPool_browser_parentport.test.ts so mocks are initialized before
any other imports; specifically, insert "import
'tests/unit/vscode_mock_setup.ts';" as the first line above the existing imports
(which reference createBrowserParentPort) to ensure the VS Code mocks are loaded
prior to running the tests that import createBrowserParentPort.

In `@tests/unit/worker_browser_bundle.test.ts`:
- Around line 9-13: The test file is missing the VS Code mock initializer; add
the import './vscode_mock_setup'; as the very first import in
tests/unit/worker_browser_bundle.test.ts so mocks are registered before any
other imports (place it above the existing imports of describe/it, assert,
existsSync, readFileSync, path, vm) to ensure VS Code mocks are initialized
prior to module loading.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: b8eb7fd4-0e4c-4260-846e-85186ba957f8

📥 Commits

Reviewing files that changed from the base of the PR and between cb011bf and d79cb47.

📒 Files selected for processing (8)
  • .github/workflows/release.yml
  • .gitignore
  • CHANGELOG.md
  • package.json
  • scripts/build.mjs
  • src/platform/threadPool.ts
  • tests/unit/threadPool_browser_parentport.test.ts
  • tests/unit/worker_browser_bundle.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build, typecheck & test
🧰 Additional context used
📓 Path-based instructions (3)
tests/unit/*.test.ts

📄 CodeRabbit inference engine (CLAUDE.md)

tests/unit/*.test.ts: Use Object.defineProperty(obj, prop, { value, writable: true, configurable: true }) for setting readonly VS Code API fields like vscode.env.uiKind in unit tests
Import tests/unit/vscode_mock_setup.ts at the beginning of unit test files to ensure VS Code mocks are initialized before running tests

Files:

  • tests/unit/threadPool_browser_parentport.test.ts
  • tests/unit/worker_browser_bundle.test.ts
src/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.ts: Use prepared statements with ? placeholders for all query parameter values to prevent SQL injection
Always use escapeIdentifier() function for table and column names in SQL queries to prevent identifier-based SQL injection
Use validateSqlType() for all user-provided SQL types in DDL statements to prevent type-based SQL injection
Validate PRAGMA string values with the regex /^[a-zA-Z0-9_-]+$/ whitelist and check numeric PRAGMA values with Number.isFinite()
Use escapeLikePattern() for user input in LIKE queries with the ESCAPE '\\' clause to prevent LIKE wildcard injection
Use zero-copy transfer for large binary data (ArrayBuffers) in RPC communication by wrapping with the Transfer wrapper
Use SAVEPOINT/RELEASE/ROLLBACK TO instead of BEGIN TRANSACTION in updateCellBatch to safely handle nested transactions
Use the safeRollback(context) helper when handling transaction errors to log failures instead of throwing, preventing secondary rollback errors
Check for SQLite json_patch() availability at engine construction time and use it in UPDATE statements when available, falling back to JS-side applyMergePatch() when unavailable
Use getNodeFs() from sqlite-db.ts to safely require the Node.js fs module, which returns undefined in browser environments
Check import.meta.env.VSCODE_BROWSER_EXT to conditionally handle environment-specific code paths for browser vs Node.js platforms
Use the Core RPC protocol defined in src/core/rpc.ts for all Worker communication and when the Extension invokes Webview methods
Use buildMethodProxy() from src/core/rpc.ts to create proxy objects that automatically serialize RPC calls to workers or the webview
Record database modifications in ModificationTracker via recordModification() before committing changes to track undo/redo history
Write all executed SQL (both read and write operations) to the 'SQLite Explorer' output channel via GlobalOutputChannel?.appendLine() for debugging...

Files:

  • src/platform/threadPool.ts
{src/**/*.ts,core/ui/modules/*.js}

📄 CodeRabbit inference engine (CLAUDE.md)

Serialize Uint8Array using the marker format { __type: 'Uint8Array', base64: '...' } with exactly 2 keys to prevent collisions with user data

Files:

  • src/platform/threadPool.ts
🔇 Additional comments (7)
package.json (1)

6-6: LGTM!

CHANGELOG.md (1)

3-52: LGTM!

.github/workflows/release.yml (1)

16-19: LGTM!

.gitignore (1)

36-40: LGTM!

scripts/build.mjs (1)

181-186: LGTM!

tests/unit/worker_browser_bundle.test.ts (1)

16-39: LGTM!

src/platform/threadPool.ts (1)

92-104: LGTM!

Comment thread src/platform/threadPool.ts Outdated
Comment thread tests/unit/threadPool_browser_parentport.test.ts
Comment thread tests/unit/threadPool_browser_parentport.test.ts
Comment thread tests/unit/worker_browser_bundle.test.ts
…review)

Addresses the WeakMap reuse bug flagged independently by Gemini and CodeRabbit
on PR #419.

createBrowserParentPort keyed wrapped listeners by handler alone, so registering
the same handler for two events (e.g. one callback for both 'message' and
'error') would overwrite the first wrapper — leaking that DOM listener and making
off() remove the wrong one. Key by (handler, event) via a per-handler inner Map;
off() now removes the exact wrapper for that event and prunes empty maps.

Not triggered by the current worker (databaseWorker.ts uses distinct handlers and
never calls off()), but createBrowserParentPort is exported and unit-tested as a
reusable primitive, so the latent bug is worth fixing.

Added a regression test that registers one handler for two events and asserts
off() removes the correct per-event wrapper — verified to FAIL against the old
flat-key adapter and pass with this fix.

Verified: tsc --noEmit clean; npm test 333/333 pass; browser e2e of the rebuilt
worker still boots and runs queries.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@zknpr
Copy link
Copy Markdown
Owner Author

zknpr commented May 31, 2026

Review feedback addressed (pushed in 8d8a2e9)

Thanks @coderabbitai, @gemini-code-assist, and Codex. Verdicts:

✅ Fixed — createBrowserParentPort WeakMap reuse bug (Gemini + CodeRabbit)

Confirmed real: the listener map was keyed by handler alone, so registering one handler for two events would overwrite the first wrapper (leak + wrong-listener removal on off()). Now keyed by (handler, event) via a per-handler inner Map; off() removes the exact per-event wrapper and prunes empty maps.

Not actually reachable by the current worker (databaseWorker.ts uses distinct handlers and never calls off()), but createBrowserParentPort is exported and unit-tested as a reusable primitive, so it's worth fixing properly.

✅ Added — off() reuse-handler test (CodeRabbit nitpick)

Added a regression test that registers one handler for message + error and asserts off() removes the correct per-event wrapper. Verified it fails against the old flat-key adapter and passes with the fix.

⏭️ Skipped — import './vscode_mock_setup' in the two new test files (CodeRabbit, "Major")

Respectfully skipping, against the repo's own established precedent:

  • 35 unit-test files; 13 do not import the mock — exactly the pure-logic tests that don't touch vscode (e.g. threadPool.test.ts, rpc-protocol.test.ts, json-utils.test.ts, sql-utils.test.ts).
  • The direct precedent is threadPool.test.ts: it tests the same module and omits the mock.
  • threadPool.ts has no vscode import; the bundle test uses only node:vm/fs. Both new files pass without the mock (333/333).

Adding a no-op import would imply a dependency that doesn't exist. The guideline applies to tests that load vscode-touching modules; these don't.

Verification after changes

npx tsc --noEmit -p tsconfig.json clean · npm test333/333 pass · browser e2e of the rebuilt worker-browser.js still boots and runs queries.

@gemini-code-assist
Copy link
Copy Markdown

Thank you for the update and for confirming the fix for the createBrowserParentPort WeakMap reuse issue. Your reasoning regarding the exclusion of vscode_mock_setup in the new test files is sound, especially given the existing project structure and the fact that these tests are focused on pure-logic primitives that do not interact with the VS Code API. It is good to see that the regression tests are robust and passing.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 31, 2026

@zknpr — great follow-through on both items, and thanks for the thorough breakdown.

WeakMap fix + regression test — the per-handler inner Map keyed by event name is exactly the right approach. The regression test that fails on the old adapter and passes on the fix is the ideal way to lock this in. Both addressed cleanly. ✅

vscode_mock_setup skip — accepted. The evidence is clear: threadPool.test.ts is a direct precedent for the same module, 13/35 tests omit the mock, and neither new file touches a vscode-importing code path. Adding a no-op import would be misleading rather than defensive. I'll update my long-term memory accordingly.

🐇 ✨


✏️ Learnings added
Learnt from: zknpr
URL: https://github.com/zknpr/SQLite-Explorer/pull/419

Timestamp: 2026-05-31T20:57:58.176Z
Learning: In the zknpr/SQLite-Explorer repository, unit tests in `tests/unit/` only import `./vscode_mock_setup` when they directly or transitively touch a `vscode`-importing module. Pure-logic tests (e.g., `threadPool.test.ts`, `rpc-protocol.test.ts`, `json-utils.test.ts`) intentionally omit the mock import. As of 2026-05, 13 of 35 unit test files do not import the mock. New test files that do not load any `vscode`-importing modules should NOT import `./vscode_mock_setup`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

✅ Actions performed

Comments resolved. Approval is disabled; enable reviews.request_changes_workflow to allow explicit top-level @coderabbitai resolve or @coderabbitai approve commands.

…review)

Addresses Codex's P2 on PR #419.

- worker_browser_bundle.test.ts validates a build artifact (out/worker-browser.js),
  which is gitignored and not produced by `npm test`. It previously assert.fail()ed
  when the bundle was absent, breaking a clean `npm ci && npm test`. It now skips
  (with a hint) when the bundle is missing; CI builds before tests, so the real
  assertions still run there.
- threadPool.ts: remove the now-unused BrowserMessagePort interface and the
  MessageReceiver alias it depended on (long-standing dead scaffolding surfaced
  after the createBrowserParentPort refactor started using NodeMessagePort).

Verified: tsc --noEmit clean; `npm test` 334/334 with bundle built, and 1 skipped
(0 failed) on a clean tree with no build; browser e2e of the rebuilt worker passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@zknpr
Copy link
Copy Markdown
Owner Author

zknpr commented May 31, 2026

Round 2 — Codex feedback addressed (pushed)

Thanks @codex.

✅ Fixed — worker_browser_bundle.test.ts required a gitignored build artifact (Codex, P2)

Correct catch: the test read out/worker-browser.js (gitignored, not built by npm test) and assert.fail()ed when absent, breaking a clean npm ci && npm test. It now skips with a hint when the bundle isn't built, and runs the real assertions when it is. CI builds before tests, so the ESM-regression guard still executes there.

Verified: clean tree, no build → # skipped 1 / # fail 0; with build → assertions run and pass.

🧹 Also removed dead types in threadPool.ts

While in the file: removed the now-unused BrowserMessagePort interface and the MessageReceiver alias it depended on (long-standing dead scaffolding the editor flagged once the createBrowserParentPort refactor started using NodeMessagePort).

Verification

tsc --noEmit -p tsconfig.json clean · npm test334/334 with bundle built (1 skipped / 0 failed on a no-build tree) · browser e2e of the rebuilt worker-browser.js still boots + runs queries.

@chatgpt-codex-connector
Copy link
Copy Markdown

To use Codex here, create an environment for this repo.

@zknpr zknpr merged commit f94489d into main May 31, 2026
7 checks passed
@zknpr zknpr deleted the fix/418-vscode-dev-worker-load branch May 31, 2026 21:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Cannot load gpkg in vscode.dev

1 participant