npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@hyvmind/drpc

v1.0.0

Published

A faithful Node/TypeScript implementation of the dRPC wire protocol.

Downloads

456

Readme

@hyvmind/drpc

A complete Node/TypeScript port of Storj's dRPC — the wire protocol, the client and server, streaming, connection pooling, code generation from .proto, and the HTTP/Twirp/grpc-web gateway. Every package in storj.io/drpc has a counterpart here, and the port is verified 1:1 against the real Go implementation: byte-identical wire framing and a live TypeScript-client ↔ Go-server round trip.

Licence Status Tests Dependencies

This is unrelated to mjpitz/drpc-node, the Node implementation Storj's own README lists under "Other Languages" as Incomplete. Different author, different code, no shared history.

Why dRPC

dRPC is Storj's replacement for gRPC — a small, dependency-light RPC protocol over any bidirectional byte stream. This port exists because the browser and L7 ingresses cannot pass raw TCP, but they do pass WebSocket upgrades and HTTP, and dRPC's framing rides over any of them unchanged. The Go implementation is roughly 3,600 lines with a documented wire format, which is what makes a faithful, verifiable port tractable rather than a research problem.

Install

Not yet published to npm. Until it is, consume it from the repository — pin a commit, since this is pre-1.0 and every revision is a breaking-change channel:

npm install github:hyvmind-io/drpc#<commit>

Requires Node 26 or newer. The library has zero runtime dependencies.

Quickstart

A real unary call and a bidirectional stream, client to server, over an in-memory pipe (swap the pipe for TCP or WebSocket and nothing else changes). This is lifted from the test suite — it runs as written.

import {
  Conn, Server, Mux, createMemoryPipe,
  type Encoding, type DRPCDescription, type DRPCReceiver, type MethodInfo,
} from "@hyvmind/drpc";

const te = new TextEncoder();
const td = new TextDecoder();

// An Encoding is per-message-type: marshal a value to bytes, unmarshal bytes back. Bring your
// own protobuf runtime, or — as here — a trivial identity byte codec.
const bytes: Encoding<Uint8Array> = { marshal: (m) => m, unmarshal: (b) => b };

// A service description maps RPC names to their encoding + handler. `protoc-gen-drpc-ts`
// generates these; by hand it is a small array.
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 };
}

// --- server -------------------------------------------------------------------------------
const mux = new Mux();
mux.register({}, description([
  method("/echo.Service/Echo", async (_srv, _signal, input) => input), // unary echo
]));

const [clientEnd, serverEnd] = createMemoryPipe();
const server = new Server(mux);
const serving = server.serveOne(serverEnd);

// --- client -------------------------------------------------------------------------------
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(() => {}); // serveOne drains one transport; it ends when the client disconnects

serveOne handles a single already-accepted transport. To accept many connections off a listener, use server.serve(acceptor, signal?) — the port of Go's Serve(ctx, net.Listener), where an Acceptor is anything with accept(): Promise<Transport | undefined> and close() (drpcmigrate's routed listeners satisfy it). It retries temporary accept errors, logs per-connection failures via ServerOptions.log, and shuts down cleanly when signal aborts.

Streaming uses conn.newStream and the stream's send/recv:

const stream = await conn.newStream("/echo.Service/EchoStream");
await stream.send(te.encode("one"), bytes);
await stream.send(te.encode("two"), bytes);
await stream.closeSend();
for (;;) {
  const msg = await stream.recv(bytes).catch(() => undefined);
  if (msg === undefined) break;
  console.log(td.decode(msg));
}

What's here

Every storj.io/drpc package, ported and tested.

| Area | Modules | Ports | | --- | --- | --- | | Wire | frame, varint, reader, writer, split | drpcwire | | RPC core | conn, stream, manager, mux, server, pool | drpcconn, drpcstream, drpcmanager, drpcmux, drpcserver, drpcpool | | Contracts | types, errors, encoding, metadata | drpc.go, drpcerr, drpcenc, drpcmetadata | | Primitives | signal, channel, mutex, tracker | drpcsignal, drpcctx.Tracker | | Aux | cache, stats, debug | drpccache, drpcstats, drpcdebug | | Transports | in-memory pipe, TCP, WebSocket client + server | (net.Conn adapters) | | HTTP gateway | http — Twirp + grpc-web over node:http | drpchttp | | Listener mux | migrate — one port, many protocols by byte prefix | drpcmigrate | | Codegen | protoc-gen-drpc-ts.proto → TS service stubs | cmd/protoc-gen-go-drpc |

Transports

dRPC runs over any bidirectional byte stream. A Transport is deliberately as small as Go's io.Reader + io.Writer + io.Closer:

interface Transport {
  read(): Promise<Uint8Array | undefined>; // undefined = clean EOF
  write(data: Uint8Array): Promise<void>;
  close(): Promise<void>;
}

Four ship in the box:

  • createMemoryPipe() — a connected pair, with configurable chunking and fault injection. The test fixture that lets the whole stack run with no network.
  • fromNodeSocket(socket) / connectNodeSocket(opts) — TCP (and TLS) over node:net.
  • connectWebSocket(url) — client, on Node 26's built-in WebSocket, zero dependency.
  • upgradeToWebSocket(req, socket, head) / attachWebSocketServer(server) — a hand-rolled RFC 6455 server, no ws dependency.

HTTP, Twirp, and grpc-web

createHttpHandler turns a dRPC handler into a node:http request listener, so a browser, Twirp, or grpc-web client can call your service over plain HTTP — the reason this port targets an L7-friendly carrier at all.

import { createServer } from "node:http";
import { createHttpHandler } from "@hyvmind/drpc";

createServer(createHttpHandler(mux)).listen(8080);

The protocol is chosen by Content-Type: application/proto and application/json are Twirp (unary); application/grpc-web+proto, +json, and the -text base64 variants are grpc-web (unary + server-streaming). Errors become a Twirp JSON {code, msg} with a mapped HTTP status, or a grpc-web grpc-status trailer. See docs/http-gateway.md.

One port, many protocols

ListenMux serves dRPC alongside gRPC or HTTP on a single TCP port, routing each connection on its first bytes — a client that writes DRPC_HEADER reaches the dRPC route, everything else falls through to a default. See docs/listener-mux.md.

Code generation

protoc-gen-drpc-ts (under tools/) is a protoc/buf plugin that generates a typed client, a server interface, and a service description from a .proto. Generated code takes its Encoding by injection, so it imports no protobuf runtime — the library stays zero-dependency even with codegen in play. See docs/codegen.md.

Design

  • Zero runtime dependencies. Node built-ins only. protobuf-es is a dependency of the code generator tool, never of the library or its generated output.
  • bigint for wire integers. Stream IDs, message IDs, lengths, and error codes are uint64 on the wire. A JS number loses precision above 2^53, and the value that exposes it — a long-lived connection's message counter — is exactly the one nobody tests.
  • AbortSignal is the context.Context equivalent. Cancellation and deadlines map directly; Go's context values are threaded explicitly instead of ambiently.
  • CSP channels. Go's goroutines and chan T are ported to async/await over a hand-rolled Channel<T> with a Go-faithful select.

Full architecture: docs/architecture.md.

Verification

Correctness means agreeing with Go, not with itself. Three checks prove it, all run against the real vendored storj.io/drpc:

  1. Byte parity. A Go program using the real drpcwire emits canonical bytes for varints, frames, splits, and error bodies; the TS encoders reproduce all 35 vectors byte-for-byte.
  2. Live interop. A real Go dRPC server, called by this port's Conn over TCP — actual bytes through actual drpcwire framing on both peers.
  3. Generated stubs. Code generated from a .proto drives a real unary and streaming RPC.

Plus 515 unit and end-to-end tests over an in-memory pipe, real TCP, real WebSocket, and real HTTP. The interop harness lives in interop/ (Go, test-only, never packaged); its byte fixtures are committed so the parity tier runs without a Go toolchain.

npm test              # typecheck + the full suite (Node 26)
npm run build         # emit dist/

Status

The runtime and codegen are complete and verified 1:1 against Go. Pre-1.0: the API may still change, and it has not been published to npm. See docs/parity.md for the package-by-package parity map and the two deliberate divergences (Go's ambient context values, and non-UTF-8 metadata in a UTF-16 string).

Documentation

Built as a site with mkdocs serve (see mkdocs.yml).

Licence

MIT, matching upstream storj/drpc. See LICENSE. The licence file carries two copyright lines: this is a port, so Storj's notice is retained alongside mine, as MIT's notice clause requires.