# Type a native RPC bridge





Once a [worklet](/bare/how-to/run-on-native/embed-bare-in-react-native) 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](/bare/explanation/bare-on-native#the-typed-rpc-seam) on the channel—methods generated from a single schema—as described in [One core, many platforms](/bare/explanation/bare-on-native).

The pieces: [`hyperschema`](/p2p/reference/helpers/hyperschema) defines the data structures and generates [`compact-encoding`](/p2p/reference/helpers/compact-encoding) codecs; [`bare-rpc`](/bare/reference/bare/modules) frames requests and replies over the IPC stream.

<Callout type="info">
  This guide shows the `bare-rpc` + `hyperschema` primitives directly, which is what you need when the far side is written in a language [HRPC](/p2p/reference/helpers/hrpc) has no generator for yet—Kotlin today. Before following it, check whether HRPC already covers your case, because it usually does:

  * **Both endpoints JavaScript** (a Pear app's host and its worker, or a React Native view and its Bare worklet)—use HRPC. This is about the two *endpoints*, not whether a native shell sits in the path: an RN app whose two ends are JS still counts.
  * **A Swift or C shell**—also HRPC. [`hrpc-swift`](https://github.com/holepunchto/hrpc-swift) and [`hrpc-c`](https://github.com/holepunchto/hrpc-c) extend the same builder and emit wire-compatible stubs from one schema, so you get generated methods on both sides rather than the hand-written dispatch below.

  See [Structured RPC over IPC](/bare/explanation/rpc-and-schemas#structured-rpc-over-ipc).
</Callout>

<Steps>
  <Step>
    ### Install the tools [#install-the-tools]

    ```sh skip="install-instruction"
    npm i hyperschema bare-rpc compact-encoding
    ```
  </Step>

  <Step>
    ### Define a schema [#define-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:

    ```js skip="snippet-illustration"
    // 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:

    ```js skip="snippet-illustration"
    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](/bare/explanation/bare-on-native).
  </Step>

  <Step>
    ### Frame calls with bare-rpc [#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:

    ```js skip="snippet-illustration"
    // 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:

    ```js skip="snippet-illustration"
    // 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()) // ok
    ```

    A 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.
  </Step>

  <Step>
    ### Stream when one message isn't enough [#stream-when-one-message-isnt-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.

    ```js skip="snippet-illustration"
    const req = rpc.request(SUBSCRIBE)
    const updates = req.createResponseStream()
    for await (const chunk of updates) {
      // handle each streamed update
    }
    ```
  </Step>
</Steps>

## See also [#see-also]

* [One core, many platforms](/bare/explanation/bare-on-native)—the architecture and the Swift/Android codegen story.
* [`bare-kit` reference](/bare/reference/bare/bare-kit)—the IPC channel this RPC rides on.
* [Compact encoding](/p2p/reference/helpers/compact-encoding)—the codec `hyperschema` generates.
* [Embed Bare in a React Native app](/bare/how-to/run-on-native/embed-bare-in-react-native)—the worklet this seam connects to.
