Skip to content

Commit c2da5e3

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

6 files changed

Lines changed: 102 additions & 12 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: 49 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,44 @@ 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+
- uses: Swatinem/rust-cache@v2
259+
with:
260+
shared-key: "rivetkit-wasm-publish"
261+
cache-on-failure: true
262+
- name: Install wasm package dependencies
263+
run: pnpm install --frozen-lockfile --filter=@rivetkit/rivetkit-wasm
264+
- name: Build wasm package
265+
run: pnpm --filter=@rivetkit/rivetkit-wasm build
266+
- name: Upload wasm package artifact
267+
uses: actions/upload-artifact@v4
268+
with:
269+
name: wasm-package
270+
path: rivetkit-typescript/packages/rivetkit-wasm/pkg
271+
if-no-files-found: error
272+
235273
# ---------------------------------------------------------------------------
236274
# docker-images — per-arch runtime images pushed to Docker Hub
237275
# ---------------------------------------------------------------------------
@@ -298,12 +336,13 @@ jobs:
298336
# publish — npm publish + R2 upload + Docker manifest + release tail
299337
# ---------------------------------------------------------------------------
300338
publish:
301-
needs: [context, build, docker-images]
339+
needs: [context, build, build-wasm, docker-images]
302340
name: "Publish"
303341
if: |
304342
!cancelled() &&
305343
needs.context.outputs.is_fork != 'true' &&
306344
needs.build.result == 'success' &&
345+
needs.build-wasm.result == 'success' &&
307346
needs.docker-images.result == 'success'
308347
runs-on: depot-ubuntu-24.04-8
309348
permissions:
@@ -343,6 +382,11 @@ jobs:
343382
path: engine-artifacts
344383
pattern: engine-*
345384
merge-multiple: true
385+
- name: Download wasm package artifact
386+
uses: actions/download-artifact@v4
387+
with:
388+
name: wasm-package
389+
path: rivetkit-typescript/packages/rivetkit-wasm/pkg
346390

347391
- name: Place native binaries in platform packages
348392
run: |
@@ -397,7 +441,9 @@ jobs:
397441
398442
# ---- build TypeScript packages (turbo dep graph picks up native) ----
399443
- 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'
444+
env:
445+
SKIP_WASM_BUILD: "1"
446+
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'
401447

402448
# ---- shared publish (runs for all triggers) ----
403449
- name: Finalize package versions for publish

rivetkit-typescript/packages/rivetkit-wasm/scripts/build.mjs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,27 @@
11
#!/usr/bin/env node
22
import { execFileSync } from "node:child_process";
3+
import { existsSync } from "node:fs";
4+
import { dirname, join } from "node:path";
5+
import { fileURLToPath } from "node:url";
6+
7+
const packageDir = dirname(dirname(fileURLToPath(import.meta.url)));
8+
const pkgDir = join(packageDir, "pkg");
9+
10+
if (["1", "true"].includes(process.env.SKIP_WASM_BUILD ?? "")) {
11+
const requiredFiles = [
12+
"rivetkit_wasm.js",
13+
"rivetkit_wasm.d.ts",
14+
"rivetkit_wasm_bg.wasm",
15+
];
16+
const missingFiles = requiredFiles.filter((file) => !existsSync(join(pkgDir, file)));
17+
if (missingFiles.length > 0) {
18+
throw new Error(
19+
`SKIP_WASM_BUILD is set but pkg is incomplete: ${missingFiles.join(", ")}`,
20+
);
21+
}
22+
console.log("[rivetkit-wasm/build] using existing pkg artifact");
23+
process.exit(0);
24+
}
325

426
const args = process.argv.slice(2);
527
const targetIndex = args.indexOf("--target");

scripts/ralph/prd.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@
398398
"Typecheck passes"
399399
],
400400
"priority": 23,
401-
"passes": false,
401+
"passes": true,
402402
"notes": ""
403403
}
404404
]

scripts/ralph/progress.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,3 +276,13 @@ Started: Wed Apr 29 08:03:50 PM PDT 2026
276276
- Public `c.sql` write forcing goes through `writeMode(() => c.sql.execute(...))`; the lower runtime adapter maps that to `executeWrite`.
277277
- `@rivetkit/rivetkit-wasm/pkg/` is generated, so host smoke tests should not require importing the real package until the wasm-pack output exists in the test environment.
278278
---
279+
## 2026-04-29 23:26:43 PDT - US-023
280+
- Documented the implemented remote SQLite and wasm runtime invariants in `.agent/specs/rivetkit-core-wasm-support.md`.
281+
- Refreshed stale current-state notes so the spec records v4-only remote SQL rollout behavior, wasm remote-only SQLite, lazy pegboard-envoy SQL executors, envoy-client transport ownership, lost-response behavior, and the direct wasm-bindgen binding strategy.
282+
- Files changed: `.agent/specs/rivetkit-core-wasm-support.md`, `scripts/ralph/prd.json`, `scripts/ralph/progress.txt`.
283+
- Quality checks: `scripts/cargo/check-rivetkit-core-wasm.sh`.
284+
- **Learnings for future iterations:**
285+
- Keep high-level wasm and remote SQLite decisions in the spec's implemented invariants section so future changes do not have to reconstruct them from individual story logs.
286+
- The wasm support spec should reflect the current gate command instead of stale one-off compile probes.
287+
- Remote SQL lost-response behavior is now a decided invariant: sent requests fail with `sqlite.remote_indeterminate_result`, while only unsent requests can be sent after reconnect.
288+
---

turbo.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"package.json"
1414
],
1515
"outputs": ["dist/**"],
16-
"env": ["FAST_BUILD", "SKIP_NAPI_BUILD"]
16+
"env": ["FAST_BUILD", "SKIP_NAPI_BUILD", "SKIP_WASM_BUILD"]
1717
},
1818
"build:ladle": {
1919
"dependsOn": ["^build"],

0 commit comments

Comments
 (0)