MCPcopy Index your code
hub / github.com/aperturerobotics/starpc

github.com/aperturerobotics/starpc @v0.49.18

Chat with this repo
repository ↗ · DeepWiki ↗ · release v0.49.18 ↗ · + Follow
2,150 symbols 5,025 edges 206 files 636 documented · 30% updated 3d agov0.49.18 · 2026-06-05★ 74
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

starpc

npm crates.io Build status GoDoc Widget Go Report Card Widget

Streaming Protobuf RPC with bidirectional streaming over any multiplexed transport.

Table of Contents

Features

Documentation

Installation

# Clone the template project
git clone -b starpc https://github.com/aperturerobotics/protobuf-project
cd protobuf-project

# Install dependencies (any of these work)
bun install
npm install
pnpm install

# Generate TypeScript and Go code
bun run gen

Quick Start

  1. Start with the protobuf-project template repository (starpc branch)
  2. Add your .proto files to the project
  3. Run bun run gen to generate TypeScript and Go code
  4. Implement your services using the examples below

Protobuf

The following examples use the echo protobuf sample.

syntax = "proto3";
package echo;

// Echoer service returns the given message.
service Echoer {
  // Echo returns the given message.
  rpc Echo(EchoMsg) returns (EchoMsg);
  // EchoServerStream is an example of a server -> client one-way stream.
  rpc EchoServerStream(EchoMsg) returns (stream EchoMsg);
  // EchoClientStream is an example of client->server one-way stream.
  rpc EchoClientStream(stream EchoMsg) returns (EchoMsg);
  // EchoBidiStream is an example of a two-way stream.
  rpc EchoBidiStream(stream EchoMsg) returns (stream EchoMsg);
}

// EchoMsg is the message body for Echo.
message EchoMsg {
  string body = 1;
}

Go

This example demonstrates both the server and client:

// construct the server
echoServer := &echo.EchoServer{}
mux := srpc.NewMux()
if err := echo.SRPCRegisterEchoer(mux, echoServer); err != nil {
    t.Fatal(err.Error())
}
server := srpc.NewServer(mux)

// create an in-memory connection to the server
openStream := srpc.NewServerPipe(server)

// construct the client
client := srpc.NewClient(openStream)

// construct the client rpc interface
clientEcho := echo.NewSRPCEchoerClient(client)
ctx := context.Background()
bodyTxt := "hello world"
out, err := clientEcho.Echo(ctx, &echo.EchoMsg{
    Body: bodyTxt,
})
if err != nil {
    t.Fatal(err.Error())
}
if out.GetBody() != bodyTxt {
    t.Fatalf("expected %q got %q", bodyTxt, out.GetBody())
}

TypeScript

For Go <-> TypeScript interop, see the integration test. For TypeScript <-> TypeScript, see the e2e test.

Rust

Add the dependencies to your Cargo.toml:

[dependencies]
starpc = "0.49"
prost = "0.14"
tokio = { version = "1", features = ["rt", "macros"] }

[build-dependencies]
starpc = { version = "0.49", features = ["build"] }
prost-build = "0.14"

Create a build.rs to generate code from your proto files:

fn main() -> Result<(), Box<dyn std::error::Error>> {
    starpc::build::configure()
        .compile_protos(&["proto/echo.proto"], &["proto"])?;
    Ok(())
}

Implement and use your service:

use starpc::{Client, Mux, Server, SrpcClient};
use std::sync::Arc;

// Include generated code
mod proto {
    include!(concat!(env!("OUT_DIR"), "/echo.rs"));
}

use proto::*;

// Implement the server trait
struct EchoServer;

#[starpc::async_trait]
impl EchoerServer for EchoServer {
    async fn echo(&self, request: EchoMsg) -> starpc::Result<EchoMsg> {
        Ok(request) // Echo back the message
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create server
    let mux = Arc::new(Mux::new());
    mux.register(Arc::new(EchoerHandler::new(EchoServer)))?;
    let server = Server::with_arc(mux);

    // Create an in-memory connection for demonstration
    let (client_stream, server_stream) = tokio::io::duplex(64 * 1024);

    // Spawn server handler
    tokio::spawn(async move {
        let _ = server.handle_stream(server_stream).await;
    });

    // Create client
    let opener = starpc::client::transport::SingleStreamOpener::new(client_stream);
    let client = SrpcClient::new(opener);
    let echoer = EchoerClientImpl::new(client);

    // Make RPC call
    let response = echoer.echo(&EchoMsg { body: "Hello!".into() }).await?;
    println!("Response: {}", response.body);

    Ok(())
}

See the echo example for a complete working example.

WebSocket Example

Connect to a WebSocket server:

import { WebSocketConn } from 'starpc'
import { EchoerClient } from './echo/index.js'

const ws = new WebSocket('ws://localhost:8080/api')
const conn = new WebSocketConn(ws, 'outbound')
const client = conn.buildClient()
const echoer = new EchoerClient(client)

const result = await echoer.Echo({ body: 'Hello world!' })
console.log('result:', result.body)

In-Memory Example

Server and client with an in-memory pipe:

import { pipe } from 'it-pipe'
import { createHandler, createMux, Server, StreamConn } from 'starpc'
import { EchoerDefinition, EchoerServer } from './echo/index.js'

// Create server with registered handlers
const mux = createMux()
const echoer = new EchoerServer()
mux.register(createHandler(EchoerDefinition, echoer))
const server = new Server(mux.lookupMethod)

// Create client and server connections, pipe together
const clientConn = new StreamConn()
const serverConn = new StreamConn(server)
pipe(clientConn, serverConn, clientConn)

// Build client and make RPC calls
const client = clientConn.buildClient()
const echoerClient = new EchoerClient(client)

// Unary call
const result = await echoerClient.Echo({ body: 'Hello world!' })
console.log('result:', result.body)

// Client streaming
import { pushable } from 'it-pushable'
const stream = pushable({ objectMode: true })
stream.push({ body: 'Message 1' })
stream.push({ body: 'Message 2' })
stream.end()
const response = await echoerClient.EchoClientStream(stream)
console.log('response:', response.body)

// Server streaming
for await (const msg of echoerClient.EchoServerStream({ body: 'Hello' })) {
  console.log('server msg:', msg.body)
}

Debugging

Enable debug logging in TypeScript using the DEBUG environment variable:

# Enable all starpc logs
DEBUG=starpc:* node app.js

# Enable specific component logs
DEBUG=starpc:stream-conn node app.js

Attribution

protoc-gen-go-starpc is a heavily modified version of protoc-gen-go-drpc. Check out drpc as well - it's compatible with grpc, twirp, and more.

Uses vtprotobuf to generate Protobuf marshal/unmarshal code for Go.

protoc-gen-es-starpc is a modified version of protoc-gen-connect-es. Uses protobuf-es-lite (fork of protobuf-es) for TypeScript.

Development Setup

MacOS

The aptre CLI handles all protobuf generation without requiring additional homebrew packages. Just run bun run gen after installing bun.

Support

Extension points exported contracts — how you extend this code

RpcStream (Interface)
RpcStream implements a RPC call stream over a RPC call. Used to implement sub-components which have a different set of s [12 …
rpcstream/rpcstream.go
Handler (Interface)
Handler describes a SRPC call handler implementation. [7 implementers]
srpc/handler.go
StreamRecv (Interface)
StreamRecv is a stream that can receive typed messages. T is the response type. [6 implementers]
srpc/stream.go
Invoker (Interface)
Invoker is a function for invoking SRPC service methods. [9 implementers]
srpc/invoker.go
PacketWriter (Interface)
PacketWriter is the interface used to write messages to a PacketStream. [10 implementers]
srpc/writer.go
Client (Interface)
Client implements a SRPC client which can initiate RPC streams. [5 implementers]
srpc/client.go
Echoer (Interface)
(no doc) [9 implementers]
echo/echo_srpc.pb.ts
RpcStream (Interface)
(no doc) [15 implementers]
rpcstream/rpcstream.rs

Core symbols most depended-on inside this repo

P
called by 410
cmd/protoc-gen-starpc-cpp/generator.go
P
called by 178
cmd/protoc-gen-starpc-rust/generator.go
line
called by 172
srpc/build.rs
close
called by 82
srpc/stream-muxer.ts
Close
called by 79
srpc/stream.go
get
called by 50
srpc/rwc-conn.go
register
called by 39
srpc/mux.rs
ErrorString
called by 37
srpc/errors.hpp

Shape

Method 1,385
Function 373
Class 226
Interface 79
Struct 71
Enum 7
FuncType 5
TypeAlias 4

Languages

Go36%
C++30%
Rust19%
TypeScript14%

Modules by API surface

srpc/rpcproto.pb.h110 symbols
echo/echo_srpc.pb.go102 symbols
rpcstream/rpcstream.pb.h96 symbols
srpc/rpcproto.pb.go92 symbols
rpcstream/rpcstream.pb.go86 symbols
echo/echo_srpc.pb.hpp52 symbols
echo/echo_e2e_test.cpp45 symbols
srpc/rpc.rs39 symbols
srpc/rpcproto.pb.cc33 symbols
rpcstream/rpcstream.pb.cc33 symbols
mock/mock.pb.h28 symbols
echo/echo_srpc.pb.rs28 symbols

For agents

$ claude mcp add starpc \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page