diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml
new file mode 100644
index 0000000000..c9e5bae077
--- /dev/null
+++ b/.github/workflows/wasm.yml
@@ -0,0 +1,38 @@
+name: CI
+
+on:
+ push:
+ branches: [ "develop" ]
+ pull_request: { }
+ workflow_dispatch: { }
+
+permissions:
+ actions: read
+ contents: read
+ checks: write # audit-check creates checks
+ issues: write # audit-check creates issues
+
+env:
+ CARGO_TERM_COLOR: always
+ RUST_BACKTRACE: 1
+
+jobs:
+ build:
+ name: "build for wasm32-wasip1"
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: ./.github/actions/cleanup
+ - uses: rui314/setup-mold@v1
+ - uses: ./.github/actions/setup-rust
+ - uses: jetli/wasm-pack-action@v0.4.0
+ with:
+ # Optional version of wasm-pack to install(eg. 'v0.9.1', 'latest')
+ version: 'latest'
+ - name: build for wasm32-wasip1 target
+ working-directory: ./vortex-wasm
+ run: |
+ echo 'building Rust + TS, making sure it completes with success'
+ npm run build-dev
+
+
diff --git a/Cargo.lock b/Cargo.lock
index 4cf54131bb..42e092b653 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -966,6 +966,16 @@ dependencies = [
"windows-sys 0.52.0",
]
+[[package]]
+name = "console_error_panic_hook"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen",
+]
+
[[package]]
name = "const-random"
version = "0.1.18"
@@ -5107,6 +5117,21 @@ dependencies = [
"vortex-proto",
]
+[[package]]
+name = "vortex-wasm"
+version = "0.21.0"
+dependencies = [
+ "bytes",
+ "console_error_panic_hook",
+ "futures-channel",
+ "futures-util",
+ "js-sys",
+ "vortex",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
[[package]]
name = "vortex-zigzag"
version = "0.21.0"
diff --git a/Cargo.toml b/Cargo.toml
index f306a62f33..68d85ec3d5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,6 +19,7 @@ members = [
"vortex-proto",
"vortex-sampling-compressor",
"vortex-scalar",
+ "vortex-wasm",
"xtask",
]
resolver = "2"
@@ -81,6 +82,7 @@ flexbuffers = "2.0.0"
flume = "0.11"
fsst-rs = "0.4.1"
futures = { version = "0.3", default-features = false }
+futures-channel = "0.3"
futures-executor = "0.3"
futures-util = "0.3"
getrandom = "0.2.14"
@@ -91,6 +93,7 @@ humansize = "2.1.3"
indicatif = "0.17.8"
itertools = "0.13.0"
jiff = "0.1.8"
+js-sys = "0.3"
libfuzzer-sys = "0.4"
log = "0.4.21"
mimalloc = "0.1.42"
@@ -125,7 +128,9 @@ tokio = "1.37.0"
tracing = "0.1"
url = "2"
uuid = "1.8.0"
+wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
+web-sys = { version = "0.3" }
# BEGIN crates published by this project
vortex = { version = "0.21.0", path = "./vortex" }
diff --git a/vortex-io/src/dispatcher/mod.rs b/vortex-io/src/dispatcher/mod.rs
index fb518c157e..2f948d08e3 100644
--- a/vortex-io/src/dispatcher/mod.rs
+++ b/vortex-io/src/dispatcher/mod.rs
@@ -8,7 +8,7 @@ mod wasm;
use std::future::Future;
use futures::channel::oneshot;
-#[cfg(not(any(feature = "compio", feature = "tokio")))]
+#[cfg(not(any(feature = "compio", feature = "tokio", target_arch = "wasm32")))]
use vortex_error::vortex_panic;
use vortex_error::VortexResult;
diff --git a/vortex-wasm/.appveyor.yml b/vortex-wasm/.appveyor.yml
new file mode 100644
index 0000000000..50910bd6f3
--- /dev/null
+++ b/vortex-wasm/.appveyor.yml
@@ -0,0 +1,11 @@
+install:
+ - appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
+ - if not defined RUSTFLAGS rustup-init.exe -y --default-host x86_64-pc-windows-msvc --default-toolchain nightly
+ - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
+ - rustc -V
+ - cargo -V
+
+build: false
+
+test_script:
+ - cargo test --locked
diff --git a/vortex-wasm/.gitignore b/vortex-wasm/.gitignore
new file mode 100644
index 0000000000..82a9f352f1
--- /dev/null
+++ b/vortex-wasm/.gitignore
@@ -0,0 +1,11 @@
+/target
+/.idea
+**/*.rs.bk
+Cargo.lock
+bin/
+pkg/
+wasm-pack.log
+/vortex.js
+/vortex.d.ts
+tsconfig.tsbuildinfo
+/node_modules
diff --git a/vortex-wasm/Cargo.toml b/vortex-wasm/Cargo.toml
new file mode 100644
index 0000000000..b25e752029
--- /dev/null
+++ b/vortex-wasm/Cargo.toml
@@ -0,0 +1,53 @@
+[package]
+name = "vortex-wasm"
+description = "Vortex WebAssembly bindings"
+publish = false
+version = { workspace = true }
+homepage = { workspace = true }
+repository = { workspace = true }
+authors = { workspace = true }
+license = { workspace = true }
+keywords = { workspace = true }
+include = { workspace = true }
+edition = { workspace = true }
+rust-version = { workspace = true }
+categories = { workspace = true }
+readme = { workspace = true }
+
+[lib]
+crate-type = ["cdylib", "rlib"]
+
+[features]
+default = ["console_error_panic_hook"]
+
+[dependencies]
+
+# The `console_error_panic_hook` crate provides better debugging of panics by
+# logging them with `console.error`. This is great for development, but requires
+# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
+# code size when deploying.
+console_error_panic_hook = { version = "0.1.7", optional = true }
+bytes = { workspace = true }
+vortex = { workspace = true }
+js-sys = { workspace = true }
+futures-channel = { workspace = true }
+futures-util = { workspace = true }
+wasm-bindgen = { workspace = true }
+wasm-bindgen-futures = { workspace = true }
+web-sys = { workspace = true, features = [
+ "console",
+ "Blob",
+ "File",
+ "FileReader",
+ 'ReadableStream',
+ "ReadableStreamByobReader",
+ "ReadableStreamByobRequest",
+ "ReadableStreamDefaultReader",
+ "ReadableStreamReadResult",
+ "ReadableStreamReaderMode",
+ "ReadableStreamGetReaderOptions",
+] }
+
+[profile.release]
+# Tell `rustc` to optimize for small code size.
+opt-level = "s"
diff --git a/vortex-wasm/index.html b/vortex-wasm/index.html
new file mode 100644
index 0000000000..fae08cc406
--- /dev/null
+++ b/vortex-wasm/index.html
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+Upload a Vortex file
+
+
+
+
+
diff --git a/vortex-wasm/package-lock.json b/vortex-wasm/package-lock.json
new file mode 100644
index 0000000000..92f1f5cb6d
--- /dev/null
+++ b/vortex-wasm/package-lock.json
@@ -0,0 +1,30 @@
+{
+ "name": "@vortex-dev/vortex",
+ "version": "0.2.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "@vortex-dev/vortex",
+ "version": "0.2.0",
+ "license": "ISC",
+ "devDependencies": {
+ "typescript": "~5.6.2"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.6.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
+ "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ }
+ }
+}
diff --git a/vortex-wasm/package.json b/vortex-wasm/package.json
new file mode 100644
index 0000000000..26a9285dcb
--- /dev/null
+++ b/vortex-wasm/package.json
@@ -0,0 +1,28 @@
+{
+ "name": "@vortex-dev/vortex",
+ "version": "0.2.0",
+ "description": "Vortex WASM bindings (WIP)",
+ "type": "module",
+ "module": "./vortex.js",
+ "types": "./vortex.d.ts",
+ "directories": {
+ "test": "tests"
+ },
+ "scripts": {
+ "build": "wasm-pack build --target web && tsc",
+ "build-dev": "wasm-pack build --dev --target web && tsc"
+ },
+ "author": "Vortex Developers ",
+ "license": "ISC",
+ "files": [
+ "vortex.ts",
+ "vortex.js",
+ "vortex.d.ts",
+ "pkg/vortex_wasm.js",
+ "pkg/vortex_wasm_bg.wasm",
+ "pkg/vortex_wasm_bg.wasm.d.ts"
+ ],
+ "devDependencies": {
+ "typescript": "~5.6.2"
+ }
+}
diff --git a/vortex-wasm/src/blob.rs b/vortex-wasm/src/blob.rs
new file mode 100644
index 0000000000..71345ca93e
--- /dev/null
+++ b/vortex-wasm/src/blob.rs
@@ -0,0 +1,69 @@
+use std::cell::RefCell;
+use std::future::Future;
+use std::rc::Rc;
+
+use bytes::{Bytes, BytesMut};
+use futures_channel::oneshot;
+use futures_util::FutureExt;
+use js_sys::Uint8Array;
+use vortex::io::VortexReadAt;
+use wasm_bindgen::closure::Closure;
+use wasm_bindgen::JsCast;
+use web_sys::{Blob, FileReader};
+
+#[derive(Clone)]
+pub struct BlobReader(pub Rc>);
+
+// (•_•) it's time to get
+// ( •_•)>⌐■-■ ...
+// (⌐■_■) Send + Sync
+//
+// This is safe because the browser runs single-threaded.
+// TODO(aduffy): is this actually safe?
+unsafe impl Send for BlobReader {}
+unsafe impl Sync for BlobReader {}
+
+impl VortexReadAt for BlobReader {
+ fn read_byte_range(
+ &self,
+ pos: u64,
+ len: u64,
+ ) -> impl Future