Summary
We build anofox_forecast for WASM, but nothing verifies the built .wasm actually loads and runs in DuckDB-Wasm — CI only proves it compiles and links. Per query.farm's Testing DuckDB-WASM Extensions ("compiling isn't running"), that's exactly the gap where WASM extensions silently break at LOAD or on first call.
The sister repo anofox-statistics just went through this end-to-end (DataZooDE/anofox-statistics#131). This issue ports the approach here.
What's already good here (verified from main)
So the load path should work — but it is unverified. This issue is about proving it and gating it.
Proposed work (mirrors anofox-statistics #131)
- Add a Node harness under
test/wasm/:
- Boots DuckDB-Wasm in Node (
@duckdb/duckdb-wasm/dist/duckdb-node.cjs, eh bundle, web-worker@1.2.0 pinned, db.instantiate(mainModule, null), db.open({ allowUnsignedExtensions: true })).
- Serves the locally-built
anofox_forecast.duckdb_extension.wasm over a localhost HTTP server (version-agnostic: serve the file for any <version>/<platform>/ path) and FORCE INSTALL ... FROM + LOADs it.
- Runs the existing
test/sql/**/*.test via a minimal sqllogictest-subset runner and asserts.
- Gating CI job in
MainDistributionPipeline.yml (needs: the wasm build) that downloads the wasm_eh artifact and runs the harness — fails the build on any WASM load/runtime error.
- A dedicated
WASM workflow + README badge (green/red) so the badge reflects only WASM, not the whole pipeline.
Gotchas we hit in anofox-statistics (save yourself the loop)
- duckdb-wasm engine version must ABI-match the extension's DuckDB version. The npm version ≠ engine version:
@duckdb/duckdb-wasm@1.29.0 → engine v1.1.1, 1.32.0 → v1.4.3, 1.33.1-dev64.0 → v1.5.5. A mismatch fails LOAD with bad export type for '...SupportStatementCache...'. If forecast builds for DuckDB v1.5.5, pin @duckdb/duckdb-wasm@1.33.1-dev64.0 (currently the only published build bundling v1.5.5 — a dev tag; no stable duckdb-wasm ships 1.5.x yet). Verify a candidate's engine with:
url=$(npm view @duckdb/duckdb-wasm@<version> dist.tarball)
curl -sL "$url" | tar xz -C /tmp/dw
strings -n4 /tmp/dw/package/dist/duckdb-eh.wasm | grep -oE 'v1\.[0-9]+\.[0-9]+' | sort -uV | tail -1
- DECIMAL rendering in duckdb-wasm's Arrow-JS is unscaled —
1.0 (a DECIMAL(2,1) literal) reads back as 10 via .toArray().toJSON(), causing spurious test failures. Format results through DuckDB's own ::VARCHAR (SELECT COLUMNS(*)::VARCHAR FROM (<query>)) so the scale is applied and output matches native sqllogictest. (This bit us as a fake MIN(x1) "discrepancy".)
- Per-file catalog isolation: re-open the DB + re-
LOAD per .test file, or CREATE TABLE state leaks across files.
- Node specifics: pin
web-worker@1.2.0 (1.5.x throws module is not defined); pthreadWorker must be null for the eh bundle; use FORCE INSTALL (Node caches extensions on the real FS).
- Optional tidy: make
openssl a "!wasm32" dep in vcpkg.json (it's unused on WASM since telemetry is off there) to avoid building OpenSSL for Emscripten.
Reference implementation
anofox-statistics PR DataZooDE/anofox-statistics#131 — test/wasm/run.mjs, test/wasm/sqllogic.mjs, the wasm-runtime-test job, and WasmTest.yml. It runs the full suite (2095/2095) against the built .wasm.
Acceptance criteria
🤖 Generated with Claude Code
Summary
We build
anofox_forecastfor WASM, but nothing verifies the built.wasmactually loads and runs in DuckDB-Wasm — CI only proves it compiles and links. Per query.farm's Testing DuckDB-WASM Extensions ("compiling isn't running"), that's exactly the gap where WASM extensions silently break atLOADor on first call.The sister repo anofox-statistics just went through this end-to-end (DataZooDE/anofox-statistics#131). This issue ports the approach here.
What's already good here (verified from
main)LINKED_LIBSis set inextension_config.cmake($<TARGET_FILE:anofox_fcst_ffi-static>, fix: link Rust FFI archive into WASM build (#239) #240) — the Rust FFI archive is linked into the.wasm, so we won't hit the "unresolvedanofox_*imports /TypeErrorat load" failure.if(WASM_BUILD) set(TELEMETRY_SUPPORTED FALSE)), so we do not have the raw-HTTPS-at-load bug that anofox-statistics had (its guard was onlyif(NOT MINGW); fixed in feat: Add native streaming implementation for ts_features_by #131). Nothing to change here.So the load path should work — but it is unverified. This issue is about proving it and gating it.
Proposed work (mirrors anofox-statistics #131)
test/wasm/:@duckdb/duckdb-wasm/dist/duckdb-node.cjs,ehbundle,web-worker@1.2.0pinned,db.instantiate(mainModule, null),db.open({ allowUnsignedExtensions: true })).anofox_forecast.duckdb_extension.wasmover a localhost HTTP server (version-agnostic: serve the file for any<version>/<platform>/path) andFORCE INSTALL ... FROM+LOADs it.test/sql/**/*.testvia a minimal sqllogictest-subset runner and asserts.MainDistributionPipeline.yml(needs:the wasm build) that downloads thewasm_ehartifact and runs the harness — fails the build on any WASM load/runtime error.WASMworkflow + README badge (green/red) so the badge reflects only WASM, not the whole pipeline.Gotchas we hit in anofox-statistics (save yourself the loop)
@duckdb/duckdb-wasm@1.29.0→ engine v1.1.1,1.32.0→ v1.4.3,1.33.1-dev64.0→ v1.5.5. A mismatch failsLOADwithbad export type for '...SupportStatementCache...'. If forecast builds for DuckDB v1.5.5, pin@duckdb/duckdb-wasm@1.33.1-dev64.0(currently the only published build bundling v1.5.5 — a dev tag; no stable duckdb-wasm ships 1.5.x yet). Verify a candidate's engine with:1.0(aDECIMAL(2,1)literal) reads back as10via.toArray().toJSON(), causing spurious test failures. Format results through DuckDB's own::VARCHAR(SELECT COLUMNS(*)::VARCHAR FROM (<query>)) so the scale is applied and output matches native sqllogictest. (This bit us as a fakeMIN(x1)"discrepancy".)LOADper.testfile, orCREATE TABLEstate leaks across files.web-worker@1.2.0(1.5.x throwsmodule is not defined);pthreadWorkermust benullfor theehbundle; useFORCE INSTALL(Node caches extensions on the real FS).openssla"!wasm32"dep invcpkg.json(it's unused on WASM since telemetry is off there) to avoid building OpenSSL for Emscripten.Reference implementation
anofox-statistics PR DataZooDE/anofox-statistics#131 —
test/wasm/run.mjs,test/wasm/sqllogic.mjs, thewasm-runtime-testjob, andWasmTest.yml. It runs the full suite (2095/2095) against the built.wasm.Acceptance criteria
anofox_forecast.duckdb_extension.wasmin DuckDB-Wasm and runstest/sql.@duckdb/duckdb-wasmpinned to the engine version matching the built DuckDB version, documented.🤖 Generated with Claude Code