Primitives & networking
@johnhenry/browsermesh-primitives and @johnhenry/browsermesh-netway are the two packages everything else either depends on or connects through. Both ship zero dependencies and pure ES modules — they run identically in a browser or Node.js.
Primitives
Section titled “Primitives”npm install @johnhenry/browsermesh-primitives<!-- In <head>, before any <script type="module"> or modulepreload. --><script type="importmap"> { "imports": { "@johnhenry/browsermesh-primitives": "https://cdn.jsdelivr.net/npm/@johnhenry/[email protected]/src/index.mjs" } }</script><script type="module"> import * as browsermeshPrimitives from "@johnhenry/browsermesh-primitives";</script>import { PodIdentity, VectorClock, ORSet, CapabilityToken, ACLEngine } from '@johnhenry/browsermesh-primitives'
// Ed25519 identityconst identity = await PodIdentity.generate()console.log(identity.podId) // base64url-encoded SHA-256 of the public key
const data = new TextEncoder().encode('hello mesh')const sig = await identity.sign(data)// Argument order is (publicKey, signature, data) -- the same as// crypto.subtle.verify. Resolves true for a good signature.const ok = await PodIdentity.verify(identity.keyPair.publicKey, sig, data)
// CRDTs merge across peers instead of conflictingconst clockA = new VectorClock()clockA.increment('node-a')const clockB = new VectorClock()clockB.increment('node-b')const merged = clockA.merge(clockB)
const set = new ORSet()set.add('item', identity.podId)set.has('item') // trueWhat’s in here
Section titled “What’s in here”- Identity —
PodIdentity(Ed25519 keypair, sign/verify),derivePodId(publicKey),encodeBase64url(bytes)/decodeBase64url(str) - Wire format —
messageTypeRegistry,encodeMeshMessage(msg)/decodeMeshMessage(bytes) - Capabilities —
CapabilityToken(scoped, with expiry),parseScope/matchScope - Trust —
createTrustEdge,computeTransitiveTrust(edges, source, target)— a weighted trust graph, not just a boolean allow-list - ACL —
ACLEngine,Permission,AccessGrant,matchResourcePattern(glob-style) - CRDTs —
VectorClock,LWWRegister,GCounter,PNCounter,ORSet,RGA(replicated growable array),LWWMap. All supportmerge(),toJSON(),fromJSON(). - Test utilities —
DeterministicRNG,LocalChannel/createLocalChannelPair(),TestMesh— useful if you’re testing code that consumes this package without standing up a real mesh
Breaking in 0.2.0: PodIdentity.verify() takes WebCrypto argument order
Section titled “Breaking in 0.2.0: PodIdentity.verify() takes WebCrypto argument order”PodIdentity.verify(publicKey, signature, data) now matches crypto.subtle.verify('Ed25519', publicKey, signature, data). Before 0.2.0 it took (publicKey, data, signature). Because Ed25519 signatures are always exactly 64 bytes, the new verify() can recognise the old order (a 64-byte data argument with a signature that is not 64 bytes) and throws a TypeError instead of quietly returning false — so a caller that was not updated fails loudly rather than rejecting every signature.
Everything built on primitives follows the same order — core’s MeshIdentityManager#verify() and IdentityWallet#verify() (breaking in core 0.5.0), the verification callbacks you hand to browsermesh-apps (see the application layer), and dialback/browsermesh’s handshake. Any other code you have that calls PodIdentity.verify() positionally needs its last two arguments swapped.
primitives (0.2.0) ships its own TypeScript declarations and declares "engines": { "node": ">=26.0.0" }.
Provenance
Section titled “Provenance”Previously published, unscoped, as [email protected] — a real standalone repo with its own history, not extracted from a private monorepo. Imported into the @johnhenry scope as part of the browsermesh consolidation; version restarts at 0.0.0.
Netway
Section titled “Netway”npm install @johnhenry/browsermesh-netway<!-- In <head>, before any <script type="module"> or modulepreload. --><script type="importmap"> { "imports": { "@johnhenry/browsermesh-netway": "https://cdn.jsdelivr.net/npm/@johnhenry/[email protected]/src/index.mjs" } }</script><script type="module"> import * as browsermeshNetway from "@johnhenry/browsermesh-netway";</script>BSD-socket-like abstractions — StreamSocket (TCP-like), DatagramSocket (UDP-like), Listener — running entirely in-memory, or proxied through a remote gateway.
import { VirtualNetwork, CAPABILITY } from '@johnhenry/browsermesh-netway'
const net = new VirtualNetwork()
const listener = await net.listen('mem://localhost:8080')const client = await net.connect('mem://localhost:8080')const server = await listener.accept()
await client.write(new TextEncoder().encode('hello'))const chunk = await server.read() // Uint8Array: "hello"
// Scoped policy enforcementconst sandbox = net.scope({ capabilities: [CAPABILITY.LOOPBACK] })await sandbox.connect('mem://localhost:8080') // allowed// sandbox.connect('tcp://example.com:80') // throws PolicyDeniedErrorBackends — this is where “virtual” stops meaning “fake”
Section titled “Backends — this is where “virtual” stops meaning “fake””net.listen/net.connect dispatch on URL scheme to a backend, and the backend is what determines whether traffic actually leaves the process:
LoopbackBackend—mem://andloop://, purely in-memoryGatewayBackend— wsh-proxied, for real TCP/UDP/DNS. This is the integration point with@johnhenry/wsh: netway’s socket API stays the same, but traffic goes out over wsh’s remote command/tunnel transport.ServiceBackend—svc://scheme, routes through a service registryFsServiceBackend— filesystem-backed service routingChaosBackendWrapper— wraps any of the above with fault injection (latency, drops, partitions) — useful for testing reconnect/retry logic without a real flaky network
Knowing which backend a given scheme resolves to matters: mem://localhost:8080 and tcp://example.com:80 look like the same kind of address but have completely different trust and reachability implications, and nothing about the connect() call site tells you which backend you’re on unless you check the scheme.
netway (0.0.1) declares "engines": { "node": ">=24.0.0" } — a lower floor than primitives and the mesh-stack packages built on it, which require Node 26.
Provenance
Section titled “Provenance”Previously published, unscoped, as [email protected] — also a real standalone repo. Version restarts at 0.0.0 under the @johnhenry scope.