Skip to content

feat: accept the .wasm bytes or a compiled module in loadModule() - #192

Closed
pyramation wants to merge 2 commits into
devin/1792902893-combined-hardeningfrom
devin/1793296430-load-module-wasm
Closed

pyramation wants to merge 2 commits into
devin/1792902893-combined-hardeningfrom
devin/1793296430-load-module-wasm

Conversation

@pyramation

@pyramation pyramation commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Some hosts can't read libpg-query.wasm themselves. One example is an Electron custom protocol registered without supportFetchAPI, which is the Beekeeper Studio plugin case from #191. On those hosts there is currently no way to load the module, in any version. This PR lets the host pass the file in once, in v13–v18:

await loadModule({ wasm }); // ArrayBuffer | ArrayBufferView | WebAssembly.Module

The input is compiled once and stored as compiled. Every instance is then created from it, including the synchronous replacement after a failed call (replaceInstance) and the retry after the out-of-memory cap. The wrapper never reads the file after that. If the default loader is still in flight from import time, its result is discarded once wasm has been provided.

The same change goes into both templates: templates/full/index.ts for v18 and templates/index.ts for v13–v17. The generated versions/*/src/index.ts are regenerated with pnpm run copy:templates.

This is stacked on #190 because it reuses that PR's compiled and replaceInstance path. It does the same job as #191 (thanks @Gldywn) without an async per-reload hook. It doesn't change import-time loading, so parseSync behaves as before.

Other details:

  • loadModule takes a rest parameter, so loadModule.length stays 0. That keeps before(loadModule) working.
  • The option's type is derived from globalThis.WebAssembly, so the .d.ts type-checks with lib: ["es2022"] and no DOM or Node types.

Tests

test/load-module.test.js is in every version. It runs a copy of the wrapper that has no libpg-query.wasm next to it, and covers:

  • loading from bytes
  • loading from a WebAssembly.Module and from a typed array
  • recovery after stack overflows, sync and async
  • the out-of-memory cap
  • rejection of input that isn't WebAssembly

Results:

Electron 37, with the page served from plugin:// without supportFetchAPI:

  • Published 18.2.1: Aborted(both async and sync fetching of the wasm failed)
  • This branch with loadModule({ wasm: bytes }): loads, a deep query throws, and the next parse succeeds.

Link to Devin session: https://app.devin.ai/sessions/9bbf3e7a34e9477ea812446ccf8a5fb0
Open in Devin Desktop: https://app.devin.ai/desktop/session/9bbf3e7a34e9477ea812446ccf8a5fb0?variant=devin
Requested by: @pyramation

Co-authored-by: Gldywn <14254051+Gldywn@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

@tenki-reviewer

tenki-reviewer Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Review complete. No blocking issues — approved ✅; 2 nitpicks below.

🧹 Nitpicks (2) — 🟢 2 low
  • 🟢 Race between default and provided loaders corrupts compiled (index.ts:196) — instantiate() re-checks wasmProvided before compiling (versions/18/src/index.ts:196), but loadModule({ wasm }) sets the flag and compiled only after its own await WebAssembly.compile completes (versions/18/src/index.ts:310-311).
  • 🟢 LOADING_WASM.md documents a v18-only loadModule option (LOADING_WASM.md:27) — LOADING_WASM.md tells users of hosts that cannot fetch the .wasm file to call await loadModule({ wasm: bytes }), but the option exists only in the full template generating v18 (versions/18/src/index.ts:304); the standard template that generates versions 13-17 still exports loadModule() with no options (templates/index.ts:251).

This PR lets callers bypass the default file-based WASM loader by passing pre-compiled bytes to loadModule, which compiles them and swaps in a new instance; the change is generated into v18 from templates/full/ and documented in LOADING_WASM.md. A new test spawns a child Node process with a lowered WASM heap cap to exercise the OOM instance-replacement path.

Files Change
templates/full/index.ts, versions/18/src/index.ts Add loadModule({ wasm }) support: accept provided bytes, compile via WebAssembly.compile, and replace the running instance.
versions/18/test/load-module.test.js New child-process test that spawns Node with a reduced WASM memory cap and asserts initialization and query execution succeed.
LOADING_WASM.md, versions/18/README.md, versions/18/package.json Document the new option, update the v18 package description/version.
versions/18/test/load-module.test.js Test-only additions for the provided-bytes loading path.

Reviewed commit: 1c4fdae

…ule()

Co-authored-by: Gldywn <14254051+Gldywn@users.noreply.github.com>
@devin-ai-integration devin-ai-integration Bot changed the title feat(v18): accept the .wasm bytes or a compiled module in loadModule() feat: accept the .wasm bytes or a compiled module in loadModule() Oct 29, 2026
@devin-ai-integration

Copy link
Copy Markdown

Superseded by #193, which makes the same loadModule({ wasm }) change directly on main, without the #190 base.

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.

1 participant