Not yet on npm. Consume from the repository, pinned to a commit (pre-1.0, every revision is a breaking-change channel):
npm install github:hyvmind-io/drpc#<commit>Requires Node 26+. Zero runtime dependencies.
dRPC has four moving parts you touch directly:
| Type | Role |
|---|---|
Transport |
a bidirectional byte stream: read()/write()/close(). Any carrier. |
Encoding<M> |
marshals a message type to bytes and back. One per message type. |
Conn |
the client. invoke for unary, newStream for streaming. |
Server + Mux |
the server. A Mux dispatches RPC names to handlers; a Server runs it over a transport. |
import {
Conn, Server, Mux, createMemoryPipe,
type Encoding, type DRPCDescription, type DRPCReceiver, type MethodInfo,
} from "@hyvmind/drpc";
const te = new TextEncoder();
const td = new TextDecoder();
// Identity byte codec — replace with a protobuf-backed Encoding in real use.
const bytes: Encoding<Uint8Array> = { marshal: (m) => m, unmarshal: (b) => b };
function description(methods: MethodInfo[]): DRPCDescription {
return { numMethods: () => methods.length, method: (n) => methods[n] };
}
function method(rpc: string, receiver: DRPCReceiver): MethodInfo {
return { rpc, encoding: bytes, receiver, method: undefined };
}
const mux = new Mux();
mux.register({}, description([
method("/echo.Service/Echo", async (_srv, _signal, input) => input),
]));
const [clientEnd, serverEnd] = createMemoryPipe();
const server = new Server(mux);
const serving = server.serveOne(serverEnd);
const conn = new Conn(clientEnd);
const response = await conn.invoke("/echo.Service/Echo", bytes, te.encode("hello dRPC"), bytes);
console.log(td.decode(response)); // "hello dRPC"
await conn.close();
await serving.catch(() => {});invoke takes two encodings — one for the request, one for the response — because
Encoding<M> is per-message-type. For a real service, protoc-gen-drpc-ts generates a typed
client so you call client.echo(req) instead of naming the RPC and codecs by hand; see
Code generation.
Swap the in-memory pipe for a real transport — the RPC code above is unchanged:
import { createServer } from "node:net";
import { Server, fromNodeSocket, connectNodeSocket, Conn } from "@hyvmind/drpc";
// server
createServer((socket) => { void new Server(mux).serveOne(fromNodeSocket(socket)); }).listen(9000);
// client
const conn = new Conn(await connectNodeSocket({ port: 9000, host: "127.0.0.1" }));See Transports for TCP, WebSocket, and the in-memory pipe.