LogoPear Docs

Add typed RPC to a Pear app

Replace hand-checked message types on a Pear app's worker IPC stream with generated, typed request/response methods using HRPC.

If your host and worker are exchanging plain strings or a hand-rolled { type: '...' } object over Bare.IPC, this guide replaces that with HRPC: a schema defines each method once, and both sides get generated methods to call instead of parsing messages by hand. See Structured RPC over IPC for why this is the recommended default once you have more than a message or two.

The worked example below adds one request/response method (getStatus, host asks the worker for its peer count) and one send-only event (log, worker to host, no reply expected)—between them, the two shapes cover most of what a Pear app's worker needs to expose.

Install

npm i hrpc hyperschema

Define a schema

Register the request/response shapes on a hyperschema namespace and write it to disk. Schemas are append-only and versioned—add fields later, never renumber or remove one:

// spec/build.js
const Hyperschema = require('hyperschema')

const schema = Hyperschema.from('./spec/hyperschema')
const ns = schema.namespace('worker')

ns.register({
  name: 'status-response',
  fields: [
    { name: 'peers', type: 'uint' },
    { name: 'uptimeSeconds', type: 'uint' }
  ]
})

ns.register({
  name: 'log-event',
  fields: [
    { name: 'message', type: 'string' }
  ]
})

Hyperschema.toDisk(schema)

getStatus takes no arguments, so it needs no request type—only status-response. The log event needs a request type (log-event) and no response, since it never expects a reply.

Register RPC methods and generate

Point HRPCBuilder at the same schema directory and register each method. getStatus is a plain request/response call; log sets send: true on its request and omits response entirely, marking it fire-and-forget:

// spec/build.js, continued
const HRPCBuilder = require('hrpc')

const hrpc = HRPCBuilder.from('./spec/hyperschema', './spec/hrpc')
const ns = hrpc.namespace('worker')

ns.register({
  name: 'get-status',
  response: { name: '@worker/status-response', stream: false }
})

ns.register({
  name: 'log',
  request: { name: '@worker/log-event', send: true }
})

HRPCBuilder.toDisk(hrpc)

Run the build script (node spec/build.js) to write spec/hyperschema/ and spec/hrpc/ to disk. Re-run it whenever the schema changes—commit the generated spec/ directory, don't gitignore it, so both sides of the IPC stream always compile from the same generated code.

Handle requests in the worker

Construct HRPC over Bare.IPC and register a handler for getStatus. Call rpc.log(...) whenever there's something to report—it returns immediately, no response awaited:

// workers/main.js
const HRPC = require('../spec/hrpc')

const rpc = new HRPC(Bare.IPC)
const startedAt = Date.now()

rpc.onGetStatus(() => ({
  peers: swarm.connections.size,
  uptimeSeconds: Math.floor((Date.now() - startedAt) / 1000)
}))

rpc.log({ message: 'worker ready' })

(swarm here is whatever Hyperswarm instance the worker already holds—this guide only adds the RPC layer on top of it.)

Call it from the host

The host side constructs HRPC over the same IPC stream returned by pear.run, calls getStatus() like any async function, and listens for log events:

// main process
const HRPC = require('./spec/hrpc')

const IPC = pear.run('./workers/main.js', [pear.storage])
const rpc = new HRPC(IPC)

rpc.onLog(({ message }) => console.log('[worker]', message))

const status = await rpc.getStatus()
console.log(`${status.peers} peer(s), up ${status.uptimeSeconds}s`)

Method names and encodings can't drift out of sync between the two files, because both compile from the one schema in spec/.

Alternatives

If a schema and a build step are more than a given protocol needs, two lighter options cover the same duplex stream without codegen:

See also

On this page