Skip to content

Commit a2e5a93

Browse files
committed
fix: compile wasm vs native features with custom cfg to prevent dupe features
1 parent b9eff14 commit a2e5a93

16 files changed

Lines changed: 159 additions & 122 deletions

File tree

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ docker-compose up -d
9595
- `rivet-envoy-client` transport features are mutually exclusive; native builds use the default `native-transport`, while wasm builds must set `default-features = false` and enable `wasm-transport`.
9696
- `rivet-envoy-client` wasm WebSocket code lives behind `target_arch = "wasm32"` with a native-host `wasm-transport` stub so feature checks do not compile browser APIs on developer machines.
9797
- `rivetkit-core` wasm builds use `--no-default-features --features wasm-runtime,sqlite-remote`; keep native process and runner-config HTTP code behind `native-runtime`.
98+
- Do not treat wasm feature flags alone as browser builds; pair wasm runtime/transport behavior with the `wasm32-unknown-unknown` target, and keep host feature checks native-safe.
9899
- Core-owned lifecycle tasks in `rivetkit-core` should spawn through `RuntimeSpawner` so native builds use Send-capable tasks and wasm builds use local tasks.
99100
- `rivet-envoy-client::async_counter::AsyncCounter` is the shared HTTP request counter type consumed by core sleep logic; do not pull `rivet-util` into core for that counter.
100101
- For `wasm32-unknown-unknown` Rust checks, use target-specific minimal Tokio plus `getrandom/js` and `uuid/js`; scan production dependencies with `cargo tree -e normal` so dev-dependencies do not create false native-dependency hits.

Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,17 @@ members = [
6969
"rivetkit-typescript/packages/rivetkit-napi",
7070
"rivetkit-typescript/packages/rivetkit-wasm"
7171
]
72+
# Keep default root checks on the native workspace graph. `rivetkit-wasm`
73+
# enables wasm runtime features that conflict with NAPI's native runtime
74+
# features when Cargo unifies the full workspace feature graph.
75+
default-members = [
76+
"engine/packages/*",
77+
"engine/sdks/rust/*",
78+
"rivetkit-rust/packages/actor-persist",
79+
"rivetkit-rust/packages/rivetkit-core",
80+
"rivetkit-rust/packages/shared-types",
81+
"rivetkit-typescript/packages/rivetkit-napi"
82+
]
7283

7384
[workspace.package]
7485
version = "2.3.0-rc.5"
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
use std::env;
2+
3+
fn main() {
4+
println!("cargo:rustc-check-cfg=cfg(rivet_envoy_native_transport)");
5+
println!("cargo:rustc-check-cfg=cfg(rivet_envoy_wasm_transport)");
6+
7+
let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default();
8+
let native_transport = env::var_os("CARGO_FEATURE_NATIVE_TRANSPORT").is_some();
9+
let wasm_transport = env::var_os("CARGO_FEATURE_WASM_TRANSPORT").is_some();
10+
let is_wasm = target_arch == "wasm32";
11+
12+
// Use custom cfgs instead of raw feature flags because Cargo features are
13+
// additive, so native and wasm transport features can be enabled together.
14+
// These cfgs collapse features plus target_arch into one effective transport.
15+
if native_transport && !is_wasm {
16+
println!("cargo:rustc-cfg=rivet_envoy_native_transport");
17+
} else if wasm_transport {
18+
println!("cargo:rustc-cfg=rivet_envoy_wasm_transport");
19+
}
20+
}

engine/sdks/rust/envoy-client/src/connection/mod.rs

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,36 @@
11
use rivet_envoy_protocol as protocol;
22
#[cfg(any(
3-
feature = "native-transport",
4-
all(feature = "wasm-transport", target_arch = "wasm32")
3+
rivet_envoy_native_transport,
4+
all(rivet_envoy_wasm_transport, target_arch = "wasm32")
55
))]
66
use rivet_util_serde::HashableMap;
77
use vbare::OwnedVersionedData;
88

99
use crate::context::SharedContext;
1010
use crate::context::WsTxMessage;
1111
#[cfg(any(
12-
feature = "native-transport",
13-
all(feature = "wasm-transport", target_arch = "wasm32")
12+
rivet_envoy_native_transport,
13+
all(rivet_envoy_wasm_transport, target_arch = "wasm32")
1414
))]
1515
use crate::envoy::ToEnvoyMessage;
1616
#[cfg(any(
17-
feature = "native-transport",
18-
all(feature = "wasm-transport", target_arch = "wasm32")
17+
rivet_envoy_native_transport,
18+
all(rivet_envoy_wasm_transport, target_arch = "wasm32")
1919
))]
2020
use crate::stringify::stringify_to_envoy;
2121
use crate::stringify::stringify_to_rivet;
2222

23-
#[cfg(all(feature = "native-transport", feature = "wasm-transport"))]
24-
compile_error!(
25-
"`native-transport` and `wasm-transport` are mutually exclusive. Enable exactly one envoy-client transport."
26-
);
27-
28-
#[cfg(not(any(feature = "native-transport", feature = "wasm-transport")))]
29-
compile_error!(
30-
"rivet-envoy-client requires a WebSocket transport. Enable `native-transport` or `wasm-transport`."
31-
);
32-
33-
#[cfg(feature = "native-transport")]
23+
#[cfg(rivet_envoy_native_transport)]
3424
mod native;
35-
#[cfg(feature = "wasm-transport")]
25+
mod transport;
26+
#[cfg(rivet_envoy_wasm_transport)]
3627
mod wasm;
3728

38-
#[cfg(feature = "native-transport")]
39-
pub use native::start_connection;
40-
#[cfg(feature = "wasm-transport")]
41-
pub use wasm::start_connection;
29+
pub use transport::start_connection;
4230

4331
#[cfg(any(
44-
feature = "native-transport",
45-
all(feature = "wasm-transport", target_arch = "wasm32")
32+
rivet_envoy_native_transport,
33+
all(rivet_envoy_wasm_transport, target_arch = "wasm32")
4634
))]
4735
async fn send_initial_metadata(shared: &SharedContext) {
4836
let mut prepopulate_map = HashableMap::new();
@@ -73,8 +61,8 @@ async fn send_initial_metadata(shared: &SharedContext) {
7361
}
7462

7563
#[cfg(any(
76-
feature = "native-transport",
77-
all(feature = "wasm-transport", target_arch = "wasm32")
64+
rivet_envoy_native_transport,
65+
all(rivet_envoy_wasm_transport, target_arch = "wasm32")
7866
))]
7967
async fn forward_to_envoy(shared: &SharedContext, message: protocol::ToEnvoy) {
8068
if tracing::enabled!(tracing::Level::DEBUG) {
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//! Selects the public connection entrypoint from the effective transport cfg.
2+
//!
3+
//! Raw feature cfgs are not enough here because Cargo features are additive
4+
//! and transport selection also depends on the build target.
5+
6+
#[cfg(all(
7+
target_arch = "wasm32",
8+
feature = "native-transport",
9+
feature = "wasm-transport"
10+
))]
11+
compile_error!(
12+
"`native-transport` and `wasm-transport` are mutually exclusive. Enable exactly one envoy-client transport."
13+
);
14+
15+
#[cfg(not(any(feature = "native-transport", feature = "wasm-transport")))]
16+
compile_error!(
17+
"rivet-envoy-client requires a WebSocket transport. Enable `native-transport` or `wasm-transport`."
18+
);
19+
20+
#[cfg(rivet_envoy_native_transport)]
21+
pub use super::native::start_connection;
22+
23+
#[cfg(rivet_envoy_wasm_transport)]
24+
pub use super::wasm::start_connection;

rivetkit-rust/packages/rivetkit-core/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ native-runtime = [
1414
"dep:reqwest",
1515
"rivet-envoy-client/native-transport",
1616
]
17-
wasm-runtime = ["rivet-envoy-client/wasm-transport"]
17+
wasm-runtime = ["dep:wasm-bindgen-futures", "rivet-envoy-client/wasm-transport"]
1818
sqlite = ["sqlite-local"]
1919
sqlite-local = ["native-runtime", "dep:async-trait", "dep:depot-client"]
2020
sqlite-remote = []
@@ -49,6 +49,7 @@ tokio-util.workspace = true
4949
tracing.workspace = true
5050
url.workspace = true
5151
vbare.workspace = true
52+
wasm-bindgen-futures = { version = "0.4", optional = true }
5253

5354
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
5455
tokio.workspace = true
@@ -60,7 +61,6 @@ js-sys = "0.3"
6061
tokio = { version = "1.44.0", default-features = false, features = ["macros", "rt", "sync", "time"] }
6162
uuid = { version = "1.11.0", features = ["v4", "serde", "js"] }
6263
wasm-bindgen = "0.2"
63-
wasm-bindgen-futures = "0.4"
6464
web-time = "1.1"
6565

6666
[dev-dependencies]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
use std::env;
2+
3+
fn main() {
4+
println!("cargo:rustc-check-cfg=cfg(rivetkit_native_runtime)");
5+
println!("cargo:rustc-check-cfg=cfg(rivetkit_wasm_runtime)");
6+
7+
let native_runtime = env::var_os("CARGO_FEATURE_NATIVE_RUNTIME").is_some();
8+
let wasm_runtime = env::var_os("CARGO_FEATURE_WASM_RUNTIME").is_some();
9+
10+
// Use custom cfgs instead of raw feature flags because Cargo features are
11+
// additive, so native and wasm features can be enabled at the same time.
12+
// These cfgs collapse that feature set into exactly one effective runtime.
13+
if wasm_runtime && !native_runtime {
14+
println!("cargo:rustc-cfg=rivetkit_wasm_runtime");
15+
} else {
16+
println!("cargo:rustc-cfg=rivetkit_native_runtime");
17+
}
18+
}

rivetkit-rust/packages/rivetkit-core/src/actor/context.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -451,9 +451,9 @@ impl ActorContext {
451451
// so future changes to `mark_destroy_requested` cannot drift.
452452
// `destroy_requested` is already true from the swap above. The redundant
453453
// `store(true)` inside is harmless.
454-
#[cfg(not(feature = "wasm-runtime"))]
454+
#[cfg(not(rivetkit_wasm_runtime))]
455455
self.mark_destroy_requested();
456-
#[cfg(feature = "wasm-runtime")]
456+
#[cfg(rivetkit_wasm_runtime)]
457457
self.mark_destroy_requested_without_spawn();
458458

459459
let ctx = self.clone();
@@ -480,7 +480,7 @@ impl ActorContext {
480480
self.0.destroy_completed.store(false, Ordering::SeqCst);
481481
}
482482

483-
#[cfg(feature = "wasm-runtime")]
483+
#[cfg(rivetkit_wasm_runtime)]
484484
fn mark_destroy_requested_without_spawn(&self) {
485485
self.cancel_sleep_timer();
486486
self.0.destroy_requested.store(true, Ordering::SeqCst);
@@ -530,7 +530,7 @@ impl ActorContext {
530530
self.0.shutdown_deadline.cancel();
531531
}
532532

533-
#[cfg(not(feature = "wasm-runtime"))]
533+
#[cfg(not(rivetkit_wasm_runtime))]
534534
pub fn wait_until(&self, future: impl Future<Output = ()> + Send + 'static) {
535535
if Handle::try_current().is_err() {
536536
tracing::warn!("skipping wait_until without a tokio runtime");
@@ -550,15 +550,15 @@ impl ActorContext {
550550
});
551551
}
552552

553-
#[cfg(not(feature = "wasm-runtime"))]
553+
#[cfg(not(rivetkit_wasm_runtime))]
554554
pub fn register_task(&self, future: impl Future<Output = ()> + Send + 'static) {
555555
let ctx = self.clone();
556556
self.track_shutdown_task(async move {
557557
Self::run_registered_task(ctx, future).await;
558558
});
559559
}
560560

561-
#[cfg(feature = "wasm-runtime")]
561+
#[cfg(rivetkit_wasm_runtime)]
562562
pub fn wait_until(&self, future: impl Future<Output = ()> + 'static) {
563563
let ctx = self.clone();
564564
self.track_shutdown_task(async move {
@@ -570,7 +570,7 @@ impl ActorContext {
570570
});
571571
}
572572

573-
#[cfg(feature = "wasm-runtime")]
573+
#[cfg(rivetkit_wasm_runtime)]
574574
pub fn register_task(&self, future: impl Future<Output = ()> + 'static) {
575575
let ctx = self.clone();
576576
self.track_shutdown_task(async move {
@@ -1286,10 +1286,10 @@ impl ActorContext {
12861286
return;
12871287
}
12881288

1289-
#[cfg(feature = "wasm-runtime")]
1289+
#[cfg(rivetkit_wasm_runtime)]
12901290
return;
12911291

1292-
#[cfg(not(feature = "wasm-runtime"))]
1292+
#[cfg(not(rivetkit_wasm_runtime))]
12931293
self.reset_sleep_timer_state();
12941294
}
12951295

rivetkit-rust/packages/rivetkit-core/src/actor/factory.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ use crate::ActorConfig;
66
use crate::actor::lifecycle_hooks::ActorStart;
77
use crate::runtime::RuntimeBoxFuture;
88

9-
#[cfg(feature = "wasm-runtime")]
9+
#[cfg(rivetkit_wasm_runtime)]
1010
pub type ActorEntryFn = dyn Fn(ActorStart) -> RuntimeBoxFuture<Result<()>>;
1111

12-
#[cfg(not(feature = "wasm-runtime"))]
12+
#[cfg(not(rivetkit_wasm_runtime))]
1313
pub type ActorEntryFn = dyn Fn(ActorStart) -> RuntimeBoxFuture<Result<()>> + Send + Sync;
1414

1515
/// Runtime extension point for building actor receive loops.
@@ -19,10 +19,10 @@ pub struct ActorFactory {
1919
manual_startup_ready: bool,
2020
}
2121

22-
#[cfg(feature = "wasm-runtime")]
22+
#[cfg(rivetkit_wasm_runtime)]
2323
unsafe impl Send for ActorFactory {}
2424

25-
#[cfg(feature = "wasm-runtime")]
25+
#[cfg(rivetkit_wasm_runtime)]
2626
unsafe impl Sync for ActorFactory {}
2727

2828
impl ActorFactory {
@@ -63,19 +63,19 @@ impl ActorFactory {
6363
}
6464
}
6565

66-
#[cfg(feature = "wasm-runtime")]
66+
#[cfg(rivetkit_wasm_runtime)]
6767
pub trait ActorEntry: Fn(ActorStart) -> RuntimeBoxFuture<Result<()>> + 'static {}
6868

69-
#[cfg(feature = "wasm-runtime")]
69+
#[cfg(rivetkit_wasm_runtime)]
7070
impl<F> ActorEntry for F where F: Fn(ActorStart) -> RuntimeBoxFuture<Result<()>> + 'static {}
7171

72-
#[cfg(not(feature = "wasm-runtime"))]
72+
#[cfg(not(rivetkit_wasm_runtime))]
7373
pub trait ActorEntry:
7474
Fn(ActorStart) -> RuntimeBoxFuture<Result<()>> + Send + Sync + 'static
7575
{
7676
}
7777

78-
#[cfg(not(feature = "wasm-runtime"))]
78+
#[cfg(not(rivetkit_wasm_runtime))]
7979
impl<F> ActorEntry for F where
8080
F: Fn(ActorStart) -> RuntimeBoxFuture<Result<()>> + Send + Sync + 'static
8181
{

rivetkit-rust/packages/rivetkit-core/src/actor/schedule.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use uuid::Uuid;
1515
use crate::actor::context::ActorContext;
1616
use crate::actor::state::PersistedScheduleEvent;
1717
use crate::error::ActorRuntime;
18-
#[cfg(feature = "wasm-runtime")]
18+
#[cfg(rivetkit_wasm_runtime)]
1919
use crate::runtime::RuntimeSpawner;
2020

2121
pub(super) type InternalKeepAwakeCallback =
@@ -349,7 +349,7 @@ impl ActorContext {
349349
return;
350350
}
351351

352-
#[cfg(not(feature = "wasm-runtime"))]
352+
#[cfg(not(rivetkit_wasm_runtime))]
353353
let tokio_handle = match Handle::try_current() {
354354
Ok(handle) => handle,
355355
Err(_) => return,
@@ -384,10 +384,10 @@ impl ActorContext {
384384
}
385385
.in_current_span();
386386

387-
#[cfg(not(feature = "wasm-runtime"))]
387+
#[cfg(not(rivetkit_wasm_runtime))]
388388
let handle = tokio_handle.spawn(task);
389389

390-
#[cfg(feature = "wasm-runtime")]
390+
#[cfg(rivetkit_wasm_runtime)]
391391
let handle = RuntimeSpawner::spawn(task);
392392

393393
*self.0.schedule_local_alarm_task.lock() = Some(handle);

0 commit comments

Comments
 (0)