Type a native RPC bridge
Replace raw IPC bytes between a native shell and a Bare worklet with a typed, schema-generated RPC seam using hyperschema and bare-rpc.
Once a worklet is exchanging bytes with its host, the next problem is structure: framing messages and parsing them by hand on both sides is error-prone, and the two sides drift apart over time. This guide puts a typed RPC seam on the channel—methods generated from a single schema—as described in One core, many platforms.
The pieces: hyperschema defines the data structures and generates compact-encoding codecs; bare-rpc frames requests and replies over the IPC stream.
Install the tools
npm i hyperschema bare-rpc compact-encodingDefine a schema
Register your structures on a namespace. Because schemas are versioned and append-only, you can add optional fields later without breaking older peers:
// build-schema.js
const Hyperschema = require('hyperschema')
const schema = Hyperschema.from('./schema')
const ns = schema.namespace('app')
ns.register({
name: 'message',
fields: [
{ name: 'id', type: 'uint', required: true },
{ name: 'text', type: 'string' }
]
})
Hyperschema.toDisk(schema)Running this writes a schema.json (for versioning) and a generated index.js of compact-encoding definitions you resolve by name and version:
const c = require('compact-encoding')
const { resolveStruct } = require('./schema')
const message = resolveStruct('@app/message', 1)
const bytes = c.encode(message, { id: 1, text: 'hello' })To generate wire-compatible Swift types for an iOS shell, run the Swift toolchain (hyperschema-swift, compact-encoding-swift) against the same schema; for the C and Kotlin paths, see One core, many platforms.
Frame calls with bare-rpc
Give each method a unique command number. In the worklet (the core), construct an RPC over the BareKit.IPC stream and handle incoming requests:
// inside the worklet
import RPC from 'bare-rpc'
import c from 'compact-encoding'
import { resolveStruct } from './schema'
const SEND_MESSAGE = 1
const message = resolveStruct('@app/message', 1)
const { IPC } = BareKit
const rpc = new RPC(IPC, (req) => {
if (req.command === SEND_MESSAGE) {
const { text } = c.decode(message, req.data)
console.log('received:', text)
req.reply('ok')
}
})On the host side, construct an RPC over the worklet's IPC and send a request:
// in the React Native host
import RPC from 'bare-rpc'
import c from 'compact-encoding'
const rpc = new RPC(worklet.IPC)
const req = rpc.request(SEND_MESSAGE)
req.send(c.encode(message, { id: 1, text: 'hello' }))
const reply = await req.reply()
console.log(reply.toString()) // okA Swift shell does the same through the generated HRPC class and a small transport delegate that forwards bytes to and from the worklet—no hand-written parsing.
Stream when one message isn't enough
For more than request/response, bare-rpc exposes streams on a request—req.createRequestStream() and req.createResponseStream()—covering the five patterns the seam supports: unary, send-only events, response-stream, request-stream, and duplex.
const req = rpc.request(SUBSCRIBE)
const updates = req.createResponseStream()
for await (const chunk of updates) {
// handle each streamed update
}See also
- One core, many platforms—the architecture and the Swift/Android codegen story.
bare-kitreference—the IPC channel this RPC rides on.- Compact encoding—the codec
hyperschemagenerates. - Embed Bare in a React Native app—the worklet this seam connects to.