orpc-adapter-stdio
v0.0.1
Published
A stdio adapter for [oRPC](https://orpc.unnoq.com/) 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.
Readme
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/serverFeatures
- 🔌 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 | nullHow 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
