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

@minip2p/core

v0.11.9

Published

Platform-neutral TypeScript SDK for minip2p

Readme

@minip2p/core

Platform-neutral TypeScript SDK for minip2p.

Applications normally install a platform package such as @minip2p/react-native, which provides the native backend and re-exports these public types. Adapter authors can implement the small contract from @minip2p/core/backend.

The API provides typed events, cancellable Promise operations, and Stream handles:

const path = await endpoint.connect(remoteAddress);
// Optional: wait for Identify when you need advertised protocols.
await endpoint.waitPeerReady(path.peerId);
const rttMs = await endpoint.ping(path.peerId);

const stream = await endpoint.openStream(path.peerId, "/example/files/1");
await stream.write(new Uint8Array([1, 2, 3]));
stream.closeWrite();

Reading applies backpressure: a chunk counts as consumed when read() returns it or every data handler has returned from it, and each stream delivers at most one receive window that has not been consumed, so a reader that stops reading stalls its sender instead of losing data. An async data handler is consumed when it returns its Promise, not when the Promise settles; use read() to hold the sender back while asynchronous work runs. Unconsumed bytes keep holding native resources after their connection ends (ADR 0012): native keeps the ended connection's slot until they are consumed or the stream is abandoned, so the SDK still acknowledges bytes it discards or reads after a close. An endpoint whose application never consumes what it received can have native refuse new connections.

The endpoint's event queue keeps stream and connection-end events. A peer-opened stream, or a connection, that ends without carrying data before any of its events was dispatched leaves the queue whole, so peer churn cannot grow it; an unseen connection that is replaced folds into its successor.

Streams are async iterables. Iteration preserves chunk order, drains bytes buffered before a remote write half-close, and then ends quietly. Local shutdown, reset, peer disconnect, connection replacement, or driver failure rejects the iterator with its terminal error:

for await (const chunk of stream) {
  process(chunk);
}

endpoint.events() returns an async iterable of endpoint events. Each iterator has a bounded, drop-oldest buffer. It reports dropped events in-band as queueOverflow events. The default cap is 4096; pass bufferCap to change it or signal to end iteration on abort.

for await (const event of endpoint.events({ signal })) {
  if (event.type === "peerReady") {
    console.log(event.peerId);
  }
}

A peer holds one connection at a time. connectionEstablished and connectionClosed mark the peer connecting and disconnecting, and connectionClosed carries a reason (ConnectionCloseReason: local, remote with the peer's code, timedOut, stalled, or error with a message); when a newer connection takes the peer's slot, a single connectionReplaced ({ peerId, oldConnId, newConnId }) stands in for both, the peer stays connected, and streams and pending opens on oldConnId end with StreamClosedError. peerReady carries the connId that completed Identify and fires once per connection. waitPeerReady follows the peer's current connection, so a stale peerReady for a replaced connection never resolves it; it resolves at once when connectionInfo(peerId).readyProtocols shows the current connection is already ready, and rejects with PeerDisconnectedError when that connection closes. When the connection's first Identify failed, the rejection is IdentifyFailedError, a PeerDisconnectedError with connId and kind (the IdentifyFailure). identifyFailed ({ peerId, connId, cause }, cause from IdentifyFailure: TimedOut, OpenFailed, Rejected, ClosedEarly, or Malformed) reports that failure; the connection never becomes ready, its connectionClosed follows, and Identify is not retried, so redial to try again.

connect accepts a Connection target: a peer ID, one complete peer multiaddress, or a list of complete addresses naming one peer. For a Connection attempt, a timeout ends only the local wait (cancelOnTimeout: true opts into cancelling), an aborted signal cancels the attempt (the wait settles from its terminal), and a terminal lost to event overflow rejects with ConnectResultLostError. Waits never stop unrelated events from reaching other subscribers.

Use endpoint.on("stream", handler) to claim inbound streams. Operations accept { timeoutMs, signal }; the default timeout is 65 seconds and timeoutMs: 0 disables it. Streams implement Symbol.dispose. endpoint.close(timeoutMs = 500) rejects outstanding operations at once, then resolves with an EndpointCloseReport ({ reason, outcome?, unfinished }) once native has disconnected peers and drained for up to timeoutMs (0 tears down at once); onClose observers receive the same report then. The deadline bounds teardown time, not delivery of accepted writes. Endpoints implement Symbol.asyncDispose (awaits close()) and Symbol.dispose (starts it).

limits overrides native transport limits: quic, tcp (with its own yamux), circuit (relay circuits' Yamux limits, independent of tcp.yamux), and maxCircuits. Every field is optional, and the SDK passes on only what you set, so the rest keep their native defaults. quic.connectionReceiveWindow takes "unlimited", quic.congestionController takes "cubic" (the default), "newReno" or "bbr", quic.packetThreshold (default 3) must be at least 3, quic.timeThreshold (a multiple of the RTT, default 1.125) must be between 1.0 and 10.0, and tcp.handshakeTimeoutMs and tcp.sendStallTimeoutMs take "never". Limits for a transport the endpoint does not bind are ignored. Native validates the rest: an invalid limit throws InvalidConfigError with capability Transport. @minip2p/web rejects limits.

const endpoint = Minip2p.create({
  secretKey,
  // TCP limits apply only when a TCP listen address binds TCP.
  listen: ["/ip4/0.0.0.0/udp/0/quic-v1", "/ip4/0.0.0.0/tcp/0"],
  limits: {
    quic: {
      streamReceiveWindow: 4_000_000,
      connectionReceiveWindow: "unlimited",
    },
    tcp: { handshakeTimeoutMs: 10_000, yamux: { receiveWindow: 1 << 20 } },
  },
});

timeouts overrides the protocol deadlines, in milliseconds: negotiationMs (one outbound stream's protocol negotiation), identifyMs (the time a connection's first Identify may take, its negotiation included; the connection is closed if it fails or runs out) and pingMs (one ping, from opening its stream to the echo). negotiationMs and identifyMs default to 30 s and pingMs to 10 s; omitted fields keep their default. On a slow or loaded link, size them from how long each exchange actually takes there, plus slack for a retransmission. waitPeerReady's default timeout allows a connect plus identifyMs. A zero throws InvalidConfigError with capability Options. Unlike limits, timeouts applies in @minip2p/web too. The minip2p-rs README's Deadlines table lists every deadline and how they relate.

const endpoint = Minip2p.create({
  secretKey,
  timeouts: { negotiationMs: 45_000, identifyMs: 45_000 },
});

create throws InvalidConfigError when native rejects the configuration. Its capability (Gossipsub, Mdns, Discovery, Transport, or Options, from ConfigCapability) names the part to change, and detail carries the native diagnostic. A malformed listen address and a socket the platform refused are not config errors; they keep their native error codes.

With mDNS enabled, mdnsFailed ({ kind, detail }, kind from MdnsFailureKind) reports a hard mDNS failure once per change in failure state. mDNS keeps running and retries, and mdnsRecovered follows once a retry succeeds; a goodbye that failed during shutdown is final.

Raw native unions, the Minip2pBackend adapter contract, resolveEndpointConfig (shared config defaults and validation), and typedFfiError (native FfiError variant to typed SDK error) are available from @minip2p/core/backend.