Skip to content

Packages

P2P runtime API

One symmetric library for any app to host and consume permissioned services.

Verified 16 October 2026

Alpha.2 source candidate

This reference describes @unstoppable-stack/p2p 0.6.0-alpha.2 source. Alpha.1 is published; the new /rpc and /db APIs need source or an alpha.2 candidate tarball until publication. Published Personal Node 0.4.0 uses a different API.

Every app can be a node and a client at the same time. createNode owns stable identity, discovery, pairing, grants, capability calls, streaming and shutdown. A Personal Node is optional; two apps can pair and call each other directly.

Connect and serve

  • createNode({ storage, profile, capabilities, pairing, limits, reconnect }): configure identity persistence, app-defined handlers, trust and resource bounds.
  • ready() initializes the runtime; listen() accepts connections by public key. listen({ topic }) additionally announces a discovery topic.
  • pair(inviteCode, { requested }) redeems a scoped invite. connect(publicKey, { requested, reconnect }) dials a selected peer. discover({ topic }) finds a host; the default topic is machine-local.
  • joinNetwork(id, { grants, requested, approve }) discovers multiple app instances; leaveNetwork(id) leaves discovery. A guessable network name is not authorization.
  • close() cancels work and tears down peers, transport and packs. Local application data must remain usable while peers are offline.

Define a service

js
import { createNode } from '@unstoppable-stack/p2p'

const host = createNode({
  storage: './.echo-host',
  capabilities: [{
    name: 'echo', version: 1, defaultGrants: [],
    methods: { say: async ({ text }) => ({ text: String(text).slice(0, 200) }) }
  }]
})
await host.listen()
const invite = await host.createInvite({ grants: { echo: ['say'] }, uses: 1 })
// Share invite.code with the intended peer.
const client = createNode({ storage: './.echo-client' })
try {
  const peer = await client.pair(invite.code)
  console.log(await peer.call('echo', 'say', { text: 'Hello' }))
} finally {
  await client.close()
  await host.close()
}

A capability pack has a name, version, methods, optional defaultGrants and lifecycle hooks. Unary handlers are async functions; streaming handlers are async generators. Handlers receive params and { peer, appId, grants, signal }. Validate domain parameters and use the authenticated peer key for ownership.

Calls, streams and permissions

  • await peer.call(capability, method, params, { timeout, signal }) returns a unary result. for await over peer.stream(capability, method, params, { signal }) consumes incremental output.
  • peer.api is built from advertised methods. Await a handle for unary output or iterate it for streaming. Dotted method names use brackets, such as peer.api.sync['manifest.replace'](...).
  • Each direction has independent grants. Incoming direct pairing is denied without approval; scoped invitations authorize only their methods. Reconnects cannot exceed persisted grant ceilings.
  • setPeerGrants changes access; unpair persists a denial. Grant removal, disconnect and cancellation abort work. Handlers must honor signal; in-process code cannot be forcibly terminated.
  • createInvite, listInvites and revokeInvite manage codes. issueInvite writes an invite from a separate process without starting another node on the daemon's identity.

Limits and operational behavior

Defaults bound control peers (128), JSON frames (256 KiB), incoming requests (8 per peer / 64 per node), host request time (120 seconds), stream output (1 MiB) and consumer buffers (64 chunks / 1 MiB). Handle NodeError codes such as NOT_GRANTED, RESOURCE_LIMIT, REQUEST_TIMEOUT and CANCELLED. Local events expose pairing, disconnect and reconnect state; they are not a remote event subscription service.

The root has no QVAC or wallet dependency and does not load replication. The /sync entry adds replication, though npm installs its storage dependencies with the package. Keep networking in the app worker. Mobile validation, complete restore and managed availability remain separate concerns.

Need help? Report an issue with your CLI version, platform, command and first relevant error.