gnutella-bun-client: GBun: A Modern Gnutella Servent Built Like a Library, Not a Fossil

A zero-dependency Bun and TypeScript implementation that turns a legacy peer-to-peer protocol into a testable CLI and an embeddable library, with real support for compression, QRP, and defensive parsing.

10-12 min read • View on GitHub • More from RickCarlino

A workstation hosts a small mechanical system that faces both a terminal prompt and a typed library interface. A network line feeds into compression chambers and routing parts, showing one codebase serving two roles. The image explains that GBun is both a CLI client and a reusable library.
GBun treats the same protocol engine as both an interactive client and a composable module.
Key Takeaways

The Weird Part Is the Point

GBun is not a nostalgia project wearing a modern coat of paint. It is a servent, a Gnutella node that can both serve and consume traffic, built as a clean TypeScript system that can also be embedded in other apps. That dual identity changes everything about the architecture.

The README puts it plainly: "GBun is a small Gnutella client you can run from the terminal or embed in a TypeScript app." That is the unusual promise here. The code is not just a client. It is a protocol engine with a CLI wrapped around it.

GBun GBun is a small Gnutella client you can run from the terminal or embed in a TypeScript app.

Project README, Repository documentation · RickCarlino/gnutella-bun-client README

Zero Dependencies, Real Protocol Work

The stack is stripped down on purpose. Bun provides the runtime, TypeScript provides the types, and the project leans on built-ins like node:net, node:zlib, and node:crypto instead of pulling in production dependencies.

That matters because every extra abstraction would blur the protocol story. Here, the important work is visible: binary parsing, socket management, compression, and peer routing. The repository does not outsource the hard parts.

DimensionLegacy Gnutella clientsGBun
Runtime styleDesktop-heavy, often monolithicBun-native, scriptable, embeddable
Dependency footprintLarge and historically tangledZero production dependencies
Primary goalFull client feature setProtocol clarity and reuse
TestabilityOften coupled to live socketsCollaborator injection and mockable network seams
Protocol fidelityVaries by client and eraExplicit support for Gnutella 0.6 features
Developer ergonomicsHard to inspect or reuseSmall surface area with strict TypeScript

The same protocol core sits underneath both the terminal client and the embeddable API.

The Servent as a Collaborator Machine

The heart of the project is GnutellaServent, and the design choice worth noticing is collaborator injection. Instead of hardwiring time, scheduling, and sockets, the code passes in a clock, a scheduler, and a netFactory.

That is not just neatness. It makes the protocol layer testable in isolation. You can swap the network layer for an in-memory fake and exercise the same code paths without needing a real TCP peer on the other end.

const collaborators = buildCollaborators({
  clock,
  scheduler,
  netFactory,
});

const servent = new GnutellaServent(collaborators);
await servent.start();

How a Socket Becomes a Gnutella Conversation

Once a peer connects, the runtime attaches the socket, negotiates capabilities, and can enable compression in either direction. Raw bytes are not treated as a blob. They are fed through codecs that turn descriptor frames into typed events.

That pipeline is the project’s real engineering discipline. The code has to survive malformed peers, partial frames, and legacy protocol quirks while still keeping the state machine comprehensible. The defensive limits in the repo exist for a reason: old network code is hostile code.

attachPeer(socket, peerState);

if (capabilities.compressIn) {
  socket.pipe(inflater).pipe(peerParser);
}

for (const event of consumePeerBuffer(buffer)) {
  handleDescriptor(event);
}
A query token reaches a dense filter wall and splits into two outcomes. Some paths light up and travel onward into a peer mesh, while others stop cold. The image explains how QRP reduces broadcast storms by forwarding only likely matches.
QRP narrows query traffic before it fans out across the network.

QRP Is the Real Trick

Query Routing Protocol is the part that makes Gnutella feel like a system, not just a swarm. Without a filter, a query can become a broadcast storm. With QRP, peers use a compact routing table to decide whether a search is worth forwarding.

That is the sharpest sign that GBun is doing serious protocol work. It is not merely parsing old packets. It is reconstructing the logic that kept a decentralized network from drowning itself.

const hit = matchQuery(qrpTable, queryTerms);
if (hit) {
  forwardQuery(peer, query);
} else {
  dropQuery(peer, query);
}

Why This Still Matters

GBun sits in a niche that modern P2P systems usually skip. It is useful for legacy interoperability, protocol study, and embedded experimentation. It is not trying to outgrow IPFS or WebTorrent. It is trying to make an old network dialect accessible in a modern development environment.

Project classStrengthTrade-offBest fit
GBunProtocol fidelity with clean architectureNot a broad consumer platformDevelopers, researchers, and embedded tooling
Legacy Gnutella clientsHistorical completeness and network familiarityHeavy, dated, and harder to reuseUsers of the original ecosystem
IPFS and WebTorrentModern decentralized distribution modelsDifferent assumptions and APIsGeneral-purpose distributed apps

That is why the repository feels unusually durable. The value is not in novelty. It is in preservation through clarity, the kind of engineering that keeps a hard protocol understandable after its first wave of users has moved on.