Skip to content

A stdio adapter for oRPC that enables type-safe RPC communication over standard input/output streams.

Notifications You must be signed in to change notification settings

lott-ai/orpc-adapter-stdio

Repository files navigation

orpc-adapter-stdio

A stdio adapter for oRPC that enables type-safe RPC communication over standard input/output streams. Perfect for spawning subprocess servers in Electron, Tauri, or any environment where you need IPC with a child process.

Installation

# bun
bun add orpc-adapter-stdio @orpc/client @orpc/server

# yarn
yarn add orpc-adapter-stdio @orpc/client @orpc/server

# pnpm
pnpm add orpc-adapter-stdio @orpc/client @orpc/server

Features

  • 🔌 Subprocess communication — Run your oRPC server in a child process and communicate via stdin/stdout
  • 🎯 Type-safe — Full end-to-end type safety with your oRPC router
  • 🪟 Framework agnostic — Works with Tauri, Electron, plain Node.js/Bun, or any environment with stdio access
  • 🔀 Isolated messaging — Uses message prefixes to separate RPC traffic from regular console output

Quick Start

1. Define your router

// router.ts
import { os } from "@orpc/server";
import * as z from "zod";

export const router = {
  ping: os
    .input(z.object({ message: z.string() }))
    .output(z.object({ message: z.string() }))
    .handler(async ({ input }) => {
      return { message: `Pong: ${input.message}` };
    }),
};

export type Router = typeof router;

2. Create the server (child process)

// server.ts
import { RPCHandler } from "orpc-adapter-stdio/server";
import { router } from "./router";

const handler = new RPCHandler(router);

handler.upgrade({
  onMessage: async (callback) => {
    // Bun example
    for await (const chunk of Bun.stdin.stream()) {
      callback(Buffer.from(chunk).toString());
    }
  },
  write: (chunk) => {
    process.stdout.write(`${chunk}\n`);
  },
});

3. Create the client (parent process)

// client.ts
import { createORPCClient } from "@orpc/client";
import type { RouterClient } from "@orpc/server";
import { RPCLink } from "orpc-adapter-stdio/client";
import type { Router } from "./router";

// Spawn the server process
const child = Bun.spawn(["bun", "run", "server.ts"], {
  stdin: "pipe",
  stdout: "pipe",
});

const link = new RPCLink({
  stdio: {
    write: (message) => child.stdin.write(message),
    onMessage: async (callback) => {
      for await (const chunk of child.stdout) {
        callback(Buffer.from(chunk).toString());
      }
    },
  },
});

const client: RouterClient<Router> = createORPCClient(link);

// Make type-safe RPC calls
const response = await client.ping({ message: "Hello!" });
console.log(response); // { message: "Pong: Hello!" }

Tauri Example

Use this adapter to run an oRPC server as a Tauri sidecar:

// In your Tauri frontend
import { createORPCClient } from "@orpc/client";
import { Command } from "@tauri-apps/plugin-shell";
import { RPCLink } from "orpc-adapter-stdio/client";
import type { Router } from "./router";

const command = Command.sidecar("binaries/my-sidecar");
const childPromise = command.spawn();

const link = new RPCLink({
  stdio: {
    onMessage: (callback) => command.stdout.on("data", callback),
    write: async (message) => {
      const child = await childPromise;
      await child.write(message);
    },
  },
});

const client = createORPCClient<Router>(link);

API Reference

Server

RPCHandler

Creates an RPC handler that wraps your oRPC router.

import { RPCHandler } from "orpc-adapter-stdio/server";

const handler = new RPCHandler(router, options?);

handler.upgrade(stdio)

Upgrades a stdio interface to handle RPC messages.

handler.upgrade({
  write: (chunk: string) => void,
  onMessage: (callback: (chunk: string) => void) => void,
  onClose?: (callback: () => void) => void,
});

Client

RPCLink

Creates an RPC link for the oRPC client.

import { RPCLink } from "orpc-adapter-stdio/client";

const link = new RPCLink({
  stdio: {
    write: (chunk: string) => void,
    onMessage: (callback: (chunk: string) => void) => void,
    onClose?: (callback: () => void) => void,
  },
  // ... other StandardRPCLinkOptions
});

Utilities

The package exports utilities for working with the message protocol:

import {
  isORPCMessage,
  parseORPCMessage,
  ORPC_MESSAGE_PREFIX,
} from "orpc-adapter-stdio/client"; // or /server

// Check if a message is an oRPC message
isORPCMessage(someString); // boolean

// Parse the oRPC message content
parseORPCMessage(orpcMessage); // string | null

How It Works

The adapter uses a special prefix (\x00__ORPC__\x00) to identify RPC messages in the stdio stream. This allows your subprocess to use regular console.log statements without interfering with RPC communication—only prefixed messages are processed as RPC traffic.

License

MIT

About

A stdio adapter for oRPC that enables type-safe RPC communication over standard input/output streams.

Resources

Stars

Watchers

Forks

Releases

No releases published