Skip to content

Commit 716eee0

Browse files
committed
chore(rivetkit): build wasm package in publish workflow
1 parent 7a3b577 commit 716eee0

15 files changed

Lines changed: 114 additions & 16 deletions

File tree

.agent/specs/rivetkit-core-wasm-support.md

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,26 @@ Support a WebAssembly build of RivetKit core while keeping the existing native N
99

1010
This proposal is intentionally gate-oriented: implementation should not start until the parity, rollout, and failure-mode criteria below are accepted.
1111

12+
## Implemented Invariants
13+
14+
- Remote SQL is an envoy protocol v4-only capability. Older protocol targets reject remote SQL request and response serialization with `ProtocolCompatibilityError { feature: RemoteSqliteExecution, required_version: 4, ... }`, and runtime callers map unsupported remote SQL to `sqlite.remote_unavailable` instead of a BARE decode failure.
15+
- Wasm uses remote SQLite only. The valid SQLite driver cells are native/local/all encodings, native/remote/all encodings, and wasm/remote/all encodings; wasm/local is invalid and covered by a targeted unavailable assertion.
16+
- Shared SQLite bind, value, result, and route types live in `rivetkit-sqlite-types`. Native and remote execution both route through the shared SQLite execution layer so statement classification, writer stickiness, migrations, `execute_write`, and manual transactions stay aligned.
17+
- `pegboard-envoy` creates at most one remote SQL executor per active `(actor_id, sqlite_generation)`. The executor is created lazily on the first accepted SQL request, reused for that generation, and removed when the actor closes or the connection shuts down.
18+
- Remote SQL work runs outside the pegboard-envoy WebSocket read loop in bounded workers. Accepted SQL is tracked per `(actor_id, sqlite_generation)`; actor stop rejects new SQL after stopping begins and waits for already accepted SQL before closing storage.
19+
- Sent remote SQL requests fail with `sqlite.remote_indeterminate_result` if the WebSocket disconnects before the response arrives. Only unsent requests may be sent after reconnect.
20+
- `rivet-envoy-client` owns native versus wasm WebSocket transport selection through mutually exclusive `native-transport` and `wasm-transport` features. `rivetkit-core` selects transport by enabling `native-runtime` or `wasm-runtime`.
21+
- The wasm binding strategy is direct `wasm-bindgen` in `@rivetkit/rivetkit-wasm`. Native NAPI and wasm bindings both adapt into the shared TypeScript `CoreRuntime` interface; raw binding imports stay inside approved runtime adapter files.
22+
- `scripts/cargo/check-rivetkit-core-wasm.sh` is the canonical wasm dependency gate for `rivetkit-core`.
23+
1224
## Current State
1325

1426
- `rivetkit-core` owns `ActorContext::sql()` and currently routes `exec`, `query`, `run`, `execute`, and `execute_write` through `SqliteDb`.
15-
- With the `sqlite` feature enabled, `SqliteDb` opens `rivetkit-sqlite::NativeDatabaseHandle`, which uses native `libsqlite3-sys` plus a custom VFS.
16-
- With the `sqlite` feature disabled, `SqliteDb` keeps the public API but returns `sqlite.unavailable` for SQL execution.
17-
- The existing envoy SQLite protocol is page/storage oriented: `get_pages`, `get_page_range`, `commit`, staged commit, and preload hints.
18-
- `pegboard-envoy` already validates SQLite requests and owns an `Arc<sqlite_storage::SqliteEngine>`, but it does not execute SQL text today.
19-
- The first wasm compile probe fails before core code due to native Tokio networking: `cargo check -p rivetkit-core --target wasm32-unknown-unknown --no-default-features` hits `mio`'s wasm unsupported error. The dependency path is primarily `rivetkit-core -> rivet-envoy-client -> tokio-tungstenite -> tokio/mio`, plus `rivet-pools`, `reqwest`, and `nix`.
27+
- With `sqlite-local`, `SqliteDb` opens `rivetkit-sqlite::NativeDatabaseHandle`, which uses native `libsqlite3-sys` plus a custom VFS.
28+
- With `sqlite-remote`, `SqliteDb` sends SQL through envoy remote execution. With no usable backend, the public API returns explicit structured unavailable errors.
29+
- The envoy SQLite protocol now includes both the page/storage path and v4 SQL execution requests for `exec`, `execute`, and `execute_write`.
30+
- `pegboard-envoy` validates remote SQL namespace, actor, generation, request size, and response size before executing through the shared SQLite execution layer.
31+
- The canonical wasm compile and dependency check is `scripts/cargo/check-rivetkit-core-wasm.sh`.
2032

2133
## Phase 1: Remote SQLite SQL Execution
2234

@@ -621,7 +633,7 @@ Run `scripts/cargo/check-rivetkit-core-wasm.sh` before claiming wasm dependency
621633
- Decision: direct wasm-bindgen on `wasm32-unknown-unknown` is the wasm binding path for Supabase and Cloudflare.
622634
- Decision: NAPI-RS wasm is not viable for the mainline edge-host binding because the spike showed async/callback surfaces fail in workerd when Rust tries to spawn a thread.
623635
- Open: exact numeric defaults for SQL text, bind bytes, row count, cell bytes, response bytes, and execution timeout.
624-
- Open: whether remote writes use durable request IDs and server-side deduplication or fail with an indeterminate-result error on lost responses.
636+
- Decision: sent remote SQL requests fail with `sqlite.remote_indeterminate_result` on lost responses; durable request ID deduplication is a future enhancement.
625637
- Decision: remote SQL calls already accepted before actor stopping may finish, but new calls after stopping begins are rejected.
626638
- Open: whether workflow/agent-os are in scope for the first wasm package or deferred as explicit non-goals.
627639
- Decision: Node wasm and WASI are follow-up targets. They do not replace Supabase and Cloudflare acceptance.
@@ -646,4 +658,4 @@ Run `scripts/cargo/check-rivetkit-core-wasm.sh` before claiming wasm dependency
646658
- Supabase Edge Functions WebAssembly guide: https://supabase.com/docs/guides/functions/wasm
647659
- Cloudflare Workers WebAssembly API docs: https://developers.cloudflare.com/workers/runtime-apis/webassembly/
648660
- reqwest crate docs for WebAssembly support: https://docs.rs/reqwest/latest/reqwest/
649-
- Local compile probe: `cargo check -p rivetkit-core --target wasm32-unknown-unknown --no-default-features` currently fails in `mio` because native Tokio networking is still included.
661+
- Local compile gate: `scripts/cargo/check-rivetkit-core-wasm.sh`.

.github/workflows/publish.yaml

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ jobs:
7979
fi
8080
8181
# ---------------------------------------------------------------------------
82-
# build — matrix of 10 native/engine artifacts (11 on release for Windows)
82+
# build — matrix of native/engine artifacts
8383
# ---------------------------------------------------------------------------
8484
build:
8585
needs: [context]
@@ -232,6 +232,45 @@ jobs:
232232
path: artifacts/${{ matrix.artifact }}
233233
if-no-files-found: error
234234

235+
# ---------------------------------------------------------------------------
236+
# build-wasm — wasm package artifact built in parallel with native artifacts
237+
# ---------------------------------------------------------------------------
238+
build-wasm:
239+
needs: [context]
240+
name: "Build rivetkit-wasm"
241+
if: needs.context.outputs.is_fork != 'true'
242+
runs-on: depot-ubuntu-24.04-8
243+
permissions:
244+
contents: read
245+
steps:
246+
- uses: actions/checkout@v4
247+
with:
248+
lfs: ${{ needs.context.outputs.trigger == 'release' }}
249+
- run: corepack enable
250+
- uses: actions/setup-node@v4
251+
with:
252+
node-version: "22"
253+
cache: pnpm
254+
- uses: actions-rust-lang/setup-rust-toolchain@v1
255+
with:
256+
toolchain: stable
257+
target: wasm32-unknown-unknown
258+
rustflags: ""
259+
- uses: Swatinem/rust-cache@v2
260+
with:
261+
shared-key: "rivetkit-wasm-publish"
262+
cache-on-failure: true
263+
- name: Install wasm package dependencies
264+
run: pnpm install --frozen-lockfile --filter=@rivetkit/rivetkit-wasm
265+
- name: Build wasm package
266+
run: pnpm --filter=@rivetkit/rivetkit-wasm build
267+
- name: Upload wasm package artifact
268+
uses: actions/upload-artifact@v4
269+
with:
270+
name: wasm-package
271+
path: rivetkit-typescript/packages/rivetkit-wasm/pkg
272+
if-no-files-found: error
273+
235274
# ---------------------------------------------------------------------------
236275
# docker-images — per-arch runtime images pushed to Docker Hub
237276
# ---------------------------------------------------------------------------
@@ -298,12 +337,13 @@ jobs:
298337
# publish — npm publish + R2 upload + Docker manifest + release tail
299338
# ---------------------------------------------------------------------------
300339
publish:
301-
needs: [context, build, docker-images]
340+
needs: [context, build, build-wasm, docker-images]
302341
name: "Publish"
303342
if: |
304343
!cancelled() &&
305344
needs.context.outputs.is_fork != 'true' &&
306345
needs.build.result == 'success' &&
346+
needs.build-wasm.result == 'success' &&
307347
needs.docker-images.result == 'success'
308348
runs-on: depot-ubuntu-24.04-8
309349
permissions:
@@ -343,6 +383,16 @@ jobs:
343383
path: engine-artifacts
344384
pattern: engine-*
345385
merge-multiple: true
386+
- name: Download wasm package artifact
387+
uses: actions/download-artifact@v4
388+
with:
389+
name: wasm-package
390+
path: rivetkit-typescript/packages/rivetkit-wasm/pkg
391+
- name: Validate wasm package artifact
392+
run: |
393+
test -f rivetkit-typescript/packages/rivetkit-wasm/pkg/rivetkit_wasm.js
394+
test -f rivetkit-typescript/packages/rivetkit-wasm/pkg/rivetkit_wasm.d.ts
395+
test -f rivetkit-typescript/packages/rivetkit-wasm/pkg/rivetkit_wasm_bg.wasm
346396
347397
- name: Place native binaries in platform packages
348398
run: |
@@ -397,7 +447,9 @@ jobs:
397447
398448
# ---- build TypeScript packages (turbo dep graph picks up native) ----
399449
- name: Build TypeScript packages
400-
run: pnpm build -F rivetkit -F '@rivetkit/*' -F '!@rivetkit/shared-data' -F '!@rivetkit/engine-frontend' -F '!@rivetkit/mcp-hub' -F '!@rivetkit/rivetkit-napi'
450+
env:
451+
SKIP_WASM_BUILD: "1"
452+
run: pnpm build -F rivetkit -F '@rivetkit/*' -F '!@rivetkit/shared-data' -F '!@rivetkit/engine-frontend' -F '!@rivetkit/mcp-hub' -F '!@rivetkit/rivetkit-napi' -F '!@rivetkit/rivetkit-wasm'
401453

402454
# ---- shared publish (runs for all triggers) ----
403455
- name: Finalize package versions for publish

docker/build/darwin-arm64.Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ COPY . .
3535
RUN if [ "$BUILD_TARGET" = "engine" ] && [ "$BUILD_FRONTEND" = "true" ]; then \
3636
export NODE_OPTIONS="--max-old-space-size=8192" && \
3737
export SKIP_NAPI_BUILD=1 && \
38+
export SKIP_WASM_BUILD=1 && \
3839
pnpm install --ignore-scripts && \
3940
VITE_APP_API_URL="${VITE_APP_API_URL}" VITE_FEATURE_FLAGS="${VITE_FEATURE_FLAGS}" npx turbo build -F @rivetkit/engine-frontend; \
4041
fi

docker/build/darwin-x64.Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ COPY . .
3535
RUN if [ "$BUILD_TARGET" = "engine" ] && [ "$BUILD_FRONTEND" = "true" ]; then \
3636
export NODE_OPTIONS="--max-old-space-size=8192" && \
3737
export SKIP_NAPI_BUILD=1 && \
38+
export SKIP_WASM_BUILD=1 && \
3839
pnpm install --ignore-scripts && \
3940
VITE_APP_API_URL="${VITE_APP_API_URL}" VITE_FEATURE_FLAGS="${VITE_FEATURE_FLAGS}" npx turbo build -F @rivetkit/engine-frontend; \
4041
fi

docker/build/linux-arm64-gnu.Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ COPY . .
2222
RUN if [ "$BUILD_TARGET" = "engine" ] && [ "$BUILD_FRONTEND" = "true" ]; then \
2323
export NODE_OPTIONS="--max-old-space-size=8192" && \
2424
export SKIP_NAPI_BUILD=1 && \
25+
export SKIP_WASM_BUILD=1 && \
2526
pnpm install --ignore-scripts && \
2627
VITE_APP_API_URL="${VITE_APP_API_URL}" VITE_FEATURE_FLAGS="${VITE_FEATURE_FLAGS}" npx turbo build -F @rivetkit/engine-frontend; \
2728
fi

docker/build/linux-arm64-musl.Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ COPY . .
2828
RUN if [ "$BUILD_TARGET" = "engine" ] && [ "$BUILD_FRONTEND" = "true" ]; then \
2929
export NODE_OPTIONS="--max-old-space-size=8192" && \
3030
export SKIP_NAPI_BUILD=1 && \
31+
export SKIP_WASM_BUILD=1 && \
3132
pnpm install --ignore-scripts && \
3233
VITE_APP_API_URL="${VITE_APP_API_URL}" VITE_FEATURE_FLAGS="${VITE_FEATURE_FLAGS}" npx turbo build -F @rivetkit/engine-frontend; \
3334
fi

docker/build/linux-x64-gnu.Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ COPY . .
3131
RUN if [ "$BUILD_TARGET" = "engine" ] && [ "$BUILD_FRONTEND" = "true" ]; then \
3232
export NODE_OPTIONS="--max-old-space-size=8192" && \
3333
export SKIP_NAPI_BUILD=1 && \
34+
export SKIP_WASM_BUILD=1 && \
3435
pnpm install --ignore-scripts && \
3536
VITE_APP_API_URL="${VITE_APP_API_URL}" VITE_FEATURE_FLAGS="${VITE_FEATURE_FLAGS}" npx turbo build -F @rivetkit/engine-frontend; \
3637
fi

docker/build/linux-x64-musl.Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ COPY . .
2727
RUN if [ "$BUILD_TARGET" = "engine" ] && [ "$BUILD_FRONTEND" = "true" ]; then \
2828
export NODE_OPTIONS="--max-old-space-size=8192" && \
2929
export SKIP_NAPI_BUILD=1 && \
30+
export SKIP_WASM_BUILD=1 && \
3031
pnpm install --ignore-scripts && \
3132
VITE_APP_API_URL="${VITE_APP_API_URL}" VITE_FEATURE_FLAGS="${VITE_FEATURE_FLAGS}" npx turbo build -F @rivetkit/engine-frontend; \
3233
fi

docker/build/windows-x64.Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ COPY . .
3535
RUN if [ "$BUILD_TARGET" = "engine" ] && [ "$BUILD_FRONTEND" = "true" ]; then \
3636
export NODE_OPTIONS="--max-old-space-size=8192" && \
3737
export SKIP_NAPI_BUILD=1 && \
38+
export SKIP_WASM_BUILD=1 && \
3839
pnpm install --ignore-scripts && \
3940
VITE_APP_API_URL="${VITE_APP_API_URL}" VITE_FEATURE_FLAGS="${VITE_FEATURE_FLAGS}" npx turbo build -F @rivetkit/engine-frontend; \
4041
fi

docker/engine/Dockerfile

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,13 @@ COPY . .
2121
# Build frontend. Use --ignore-scripts because the root postinstall runs
2222
# `lefthook install`, which needs a .git directory (excluded by
2323
# .dockerignore). lefthook is a dev-only git hook manager and has no
24-
# place inside the Docker build. SKIP_NAPI_BUILD=1 tells
25-
# @rivetkit/rivetkit-napi to skip its napi build. The frontend only
26-
# consumes the TypeScript surface, not the runtime .node binary.
24+
# place inside the Docker build. SKIP_NAPI_BUILD=1 and SKIP_WASM_BUILD=1 tell
25+
# the runtime binding packages to skip native artifact builds. The frontend only
26+
# consumes their TypeScript surfaces, not the runtime binaries.
2727
RUN if [ "$BUILD_FRONTEND" = "true" ]; then \
2828
export NODE_OPTIONS="--max-old-space-size=8192" && \
2929
export SKIP_NAPI_BUILD=1 && \
30+
export SKIP_WASM_BUILD=1 && \
3031
pnpm install --ignore-scripts && \
3132
VITE_APP_API_URL="${VITE_APP_API_URL}" VITE_FEATURE_FLAGS="${VITE_FEATURE_FLAGS}" VITE_APP_TURNSTILE_SITE_KEY="${VITE_APP_TURNSTILE_SITE_KEY}" npx turbo build -F @rivetkit/engine-frontend; \
3233
fi

0 commit comments

Comments
 (0)