JS → Native
This is the primary direction for host / platform capabilities: a native provider registers an implementation, and React Native consumes it through a typed proxy. Both Android (Kotlin) and iOS (Swift) are fully supported.
Methods
Section titled “Methods”The JS proxy mirrors the contract’s methods. query becomes an async call; fire returns
nothing; querySync reads synchronously.
const connect = useBridge(ConnectHost);
await connect.installEsim({ url, iccId }); // query → Promise<'success' | ...>connect.showLogin(); // fire → voidconst info = connect.getDeviceInfo(); // querySync → value, synchronouslyNative implements the generated interface:
class ConnectHostProvider : ConnectHost { override suspend fun isLoggedIn(): Boolean = session.isLoggedIn() override suspend fun installEsim(params: InstallEsimParams): InstallEsimResult = /* ... */ override fun showLogin() { /* ... */ }}
BridgeKit.default.provide(ConnectHostContract) { ConnectHostProvider() }final class ConnectHostProvider: ConnectHost { func isLoggedIn() throws -> Bool { session.isLoggedIn() } func installEsim(_ params: InstallEsimParams) async throws -> InstallEsimResult { /* ... */ } func showLogin() { /* ... */ }}
let binding = BridgeKitRuntime.default.provide(ConnectHostContract()) { ConnectHostProvider() }Streams (native → JS)
Section titled “Streams (native → JS)”The provider exposes a platform stream type; BridgeKit collects it once per (binding, stream, params) and multiplexes to every JS subscriber.
override fun otpCodes(): Flow<String> = smsRetriever.codes()func otpCodes() -> AsyncStream<String> { AsyncStream { continuation in let task = Task { for await code in smsRetriever.codes() { continuation.yield(code) } } continuation.onTermination = { _ in task.cancel() } }}const codes = connect.otpCodes();const off = codes.subscribe((code) => setOtp(code)); // Unsubscribe// or: for await (const code of codes) { ... }Closing the JS subscription cancels the underlying collection. The default backpressure is a lossless bounded buffer (drop-oldest + logged diagnostic on overflow).
State (native → JS)
Section titled “State (native → JS)”Native owns a mutable reactive value; the router subscribes to it and feeds updates into the
StateStore. On the JS side a StateMirror subscribes via BridgeState.observe() and
serves useBridgeState through useSyncExternalStore.
override val connectivity = MutableStateFlow(Connectivity(online = true))// Expose state as an AsyncStream — the runtime subscribes in a background Task.var connectivity: AsyncStream<Connectivity> { get }const { value, status } = useBridgeState(ConnectHost, 'connectivity');// status: 'initial' | 'available' | 'unprovided'Two properties make this cheap and total:
get()is always local — it reads the cached mirror, never crossing the bridge.- Hydration on connect —
connectDispatcherreturns a snapshot of all native-provided state, so JS mirrors are populated the instant the runtime connects.BridgeKitReadyimplies hydrated mirrors.
Per-hook subscriptions never cross the bridge: there is exactly one multiplexed native
observation per (contract, state key), released via unobserve when the JS subscriber
count reaches zero.
The mirror image of this page — native calling into JS — is Native → JS (reverse).