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 app = useBridge(AppHost);
await app.saveFile({ url, name }); // query → Promise<'success' | ...>app.showLogin(); // fire → voidconst info = app.getDeviceInfo(); // querySync → value, synchronouslyNative implements the generated interface:
class AppHostProvider : AppHost { override suspend fun isLoggedIn(): Boolean = session.isLoggedIn() override suspend fun saveFile(params: SaveFileParams): SaveFileResult = /* ... */ override fun showLogin() { /* ... */ }}
BridgeKit.default.provide(AppHostContract) { AppHostProvider() }final class AppHostProvider: AppHost { func isLoggedIn() throws -> Bool { session.isLoggedIn() } func saveFile(_ params: SaveFileParams) async throws -> SaveFileResult { /* ... */ } func showLogin() { /* ... */ }}
let binding = BridgeKitRuntime.default.provide(AppHostContract()) { AppHostProvider() }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 notifications(): Flow<String> = pushService.messages()func notifications() -> AsyncStream<String> { AsyncStream { continuation in let task = Task { for await code in pushService.messages() { continuation.yield(code) } } continuation.onTermination = { _ in task.cancel() } }}const notes = app.notifications();const off = notes.subscribe((note) => addNotification(note)); // Unsubscribe// or: for await (const note of notes) { ... }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(AppHost, '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).