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

@claudewerk/bridge-substrate

v0.2.2

Published

The L2 substrate: named, durable coordination objects that live on both sides of a bun-reliable-ws connection. Composes bun-mq + bun-reliable-ws + envelope-codec + mutual-key-auth.

Readme

bridge-substrate -- the L2 named-object engine (F1 CORE)

The keystone. It composes four proven, independently-built packages into the substrate: named, durable coordination objects that live on both sides of a connection -- "named queues that live on both sides." It is claudewerk- agnostic; it knows nothing about any broker. It is the engine a larger system (or a GATE) drives.

A Substrate sits on each side of one authenticated connection, owns named objects (F1: session + queue), and speaks the substrate verbs over the wire -- with per-object strict-FIFO sequencing, two acknowledgment levels, a durable outbox + dedupe for exactly-once, and the futility / authorized-finality laws.

bun install     # links the four packages via workspace:* (run at the monorepo root)
bun test        # 19 tests, all green (254 across the workspace)

# two-node CLI demo (see examples/README.md):
bun examples/client.ts --name C1 --port 7801
bun examples/client.ts --name C2 --port 7802 --connect 7801

There is a runnable, drive-it-by-hand two-node CLI (open / send / req / close / disconnect / connect) that visualizes the object lifecycle, the two ack levels, and exactly-once across a disconnect.

The four packages it composes

| Package | Role in the substrate | |---|---| | bun-reliable-ws | the L1 carrier: binary wire, staged handshake, reconnect, WS delivery-acks, AuthContext with the full Negotiated tuple + connId + transcriptHash | | envelope-codec | the frame codec (cborCodec): {env, body} -> CBOR bytes, the opaque body rides binary with zero base64 | | mutual-key-auth | the AuthProvider (asAuthProvider): mutual challenge/response, role-absolute, cross-layer downgrade binding -- now zero-glue (see FINDINGS) | | bun-mq | the durable per-object outbox + the dedupe ledger (receipt-fenced ack, StaleReceiptError, 7-day dedupe window) |

Wiring

   SUBSTRATE A  (dialer)                               SUBSTRATE B  (listener)
   ─────────────────────                               ───────────────────────
   app: open / req / send / close                    open? -> accept/reject; req -> res
        │  ▲                                                        ▲   │
        ▼  │ answer (accept/res/end/err)   end-to-end, keyed to id  │   ▼
   ┌──────────┐  per-object durable outbox                  dedupe ledger  ┌──────────┐
   │  Outbox  │  (bun-mq, one seq authority)   (bun-mq (peer,id) >= 7d)    │ Sequencer│
   └──────────┘  at-most-one-un-acked-in-flight        in-seq delivery ────└──────────┘
        │  ▲                                                        ▲   │
        │  │  ◀──────────── ack (hop delivery-ack, receipt-fenced) ─┘   │
        ▼  │  ──────────── open/send/req/res/accept/reject/close ──────▶│

   Link:  bun-reliable-ws  ═══ ws (binary), may drop + reconnect ═══
   Codec: envelope-codec   frame = { env{v,kind,obj,objKind,id,seq,receipt}, body } -> CBOR
   Auth:  mutual-key-auth  challenge/response, bound to connId + full cross-layer tuple

The same stack + composition as a flow:

flowchart LR
  subgraph A["Substrate A · dialer"]
    AApp["app: open / req / send / close"]
    AOut["Outbox<br/>durable bun-mq · 1 seq authority<br/>at-most-1-unacked-in-flight"]
    ASeq["Sequencer<br/>in-seq · gap buffer · resync"]
    AApp --> AOut
    ASeq --> AApp
  end

  subgraph B["Substrate B · listener"]
    BApp["app: accept/reject · req→res"]
    BOut["Outbox"]
    BDed["Dedupe ledger<br/>(peer,id) ≥ 7d"]
    BSeq["Sequencer"]
    BApp --> BOut
    BDed --> BSeq
    BSeq --> BApp
  end

  AOut ==>|"open/send/req/res/accept/reject/close"| BDed
  BOut -.->|"ack · hop delivery-ack · receipt-fenced"| AOut
  BOut ==>|"answer · accept/res/end/err · keyed to id"| ASeq

  AOut <--> L
  BOut <--> L
  L["LINK<br/>bun-reliable-ws — binary ws, drop+reconnect<br/>envelope-codec — {env,body} → CBOR, zero base64<br/>mutual-key-auth — bound to connId + full cross-layer tuple"]

Storage is pluggable (where durable state lives)

Every peer's outbox, inbox, dedupe ledger and object registry go into one Store (bun-mq's persistence contract). Defaults need no configuration; a host that wants the state in ITS OWN database plugs a store in:

| Option | Where state lives | |---|---| | nothing | memory, not durable | | dbPath / acceptor dbPathFor(peerId) | a SQLite file the substrate opens and closes itself | | storage / acceptor storageFor(peerId) | a Store the host built; the substrate never closes it |

Precedence on an acceptor: storageFor, then storage, then dbPathFor, then dbPath. Every durable key is scoped by peer id, so ONE store can serve every peer. To keep bridge state in the host's database, next to the host's tables and in the host's transactions:

import { createPeerAcceptor, sqliteStore } from "@claudewerk/bridge-substrate";

const bridgeStore = sqliteStore(appDb, { tablePrefix: "bridge_" }); // appDb opened { strict: true }
const peers = createPeerAcceptor({ peerId: "broker", auth, authorize, storageFor: () => bridgeStore });

A queue object accepted with acceptQueue() then keeps its durable inbox in the host's database: the host can count it, inspect it and manage it like the rest of its data. Releasing a peer leaves its rows in place for the host to keep or delete.

Exactly-once across a forced disconnect (the money flow)

sequenceDiagram
  participant A as Substrate A (Outbox)
  participant Wire as ws link
  participant B as Substrate B (Dedupe→app)

  A->>A: enqueue send(id=M-1, seq=n) — durable, BEFORE the wire
  A->>Wire: send{id:M-1, receipt:r1}
  Wire->>B: send{id:M-1, receipt:r1}
  B->>B: admit(peer,M-1)=fresh → commit, deliver to app once
  Note over Wire: 💥 connection drops — ack lost
  B--xWire: ack{M-1, r1}  (never arrives)
  Wire-->>A: reconnect (new connId, same peer → rebind)
  A->>Wire: RESEND send{id:M-1, receipt:r1}  (same producer-stable id)
  Wire->>B: send{id:M-1}
  B->>B: admit(peer,M-1)=DUP → skip app (dedupe wins)
  B->>Wire: ack{M-1, r1}  (branchless re-ack)
  Wire->>A: ack{M-1, r1}
  A->>A: q.ack(r1) receipt-fenced → outbox row freed
  Note over A,B: delivered exactly once · FIFO intact

Object model

An object is a named coordination endpoint that exists on both sides:

  • Address -- an opaque token the substrate MATCHES, never parses. Charset [a-z0-9._/-]{1,128}; an @ is routing_not_supported. objKind (session | queue) is an explicit envelope field, not parsed from the name.
  • Lifecycle -- open -> accept / reject, then live, then close (or a terminal err). Both endpoints track the object's participants; a killing verb is honoured only from a verified participant.
  • Session -- supports req/res (end-to-end answer) and one-way send.
  • Queue -- the same machinery, send-oriented (one-way send + ack).

Verbs (env.kind)

| Verb | Direction | Meaning | |---|---|---| | open / accept / reject | opener ⇄ acceptor | establish or refuse an object | | close | either participant | terminate an object (authorized finality) | | send + ack | producer -> peer | one-way message; ack is the hop delivery-ack | | req / res | caller ⇄ callee | request + end-to-end answer | | err | callee / substrate | terminal error (class derived by the receiver from the code) | | end | callee | terminal end-of-answer | | chunk / resync | -- | chunk is an F2 seam; resync an internal control seam |

The two acknowledgment levels

  1. Hop delivery-ack (ack, QoS-1, this connection). Sent by the receiver the moment an op is durably committed to the dedupe ledger. It frees the sender's outbox row, receipt-fenced via the echoed env.receipt. Lost to a dropped connection -> the sender resends the same frame on reconnect -> the receiver dedupes the redelivery and re-acks. This is what makes delivery exactly-once and what enforces strict FIFO (at most one un-acked op in flight per object).
  2. End-to-end answer (accept/reject/res/end/err), keyed to the producer-stable id, connection-agnostic -- it resolves the caller's promise whenever it can be delivered, across any number of reconnects.

Per-object sequencing + strict FIFO

Each object has a single seq authority per direction (the Outbox). Frames go out strictly one-un-acked-at-a-time, so with an ordered carrier the receiver's Sequencer sees them in order and passes through; a reorder/redelivery is buffered within [nextExpected, +window], a below-frontier seq is a duplicate (re-ack only), and a gap older than 30s asks for a bounded resync.

Durable outbox + dedupe (exactly-once)

Every open/send/req (and every answer) is durably queued before it hits the wire. id is a producer-stable idempotency key -- minted once, reused on every retry, never per-attempt. The receiver dedupes by (peer, id) for >= 7 days via bun-mq's persistent dedupe table (which outlives consume + restart), so dedupe wins over tombstone and a redelivery collapses even after the original was delivered.

Futility / bounce laws

  • (a) Receiver-derived error class -- there is no wire final flag; the receiver classifies a code as terminal or retryable (finality.ts).
  • (b) Authorized finality -- close/err that kills an object is honoured ONLY from a verified participant; an unauthorized finality frame is rejected before it can even perturb the seq frontier.
  • (c) Exactly one bounce -- a frame to an unknown object (or a bad address) yields exactly one terminal err, tombstoned per-op-id (not per name), so a later legit open of the same name still works.
  • (d) An err never begets an err -- an unroutable err is never bounced.
  • (e) Purge on terminal -- a terminal answer/bounce purges the sender's op and emits a bridge/futile event.

Handshake (the full cross-layer tuple)

The L1 stands up via bun-reliable-ws + cborCodec + asAuthProvider. The auth transcript binds the complete negotiated tuple -- version, features, the chosen name in every slot (auth, e2eSeal, e2eSign, codec), dedupeScope (carried as a negotiated feature token), and connId -- so downgrade binding is complete across every layer.

The auth choice is one value, and it is required

createDialerSubstrate / createListenerSubstrate take auth: () => AuthProvider or auth: "none". There is no default and no third option, so a deployment cannot arrive at an unauthenticated link by leaving something out; writing auth: "none" is as deliberate as SSH's Ciphers none, and it is logged at warn when it resolves. Both the advertised auth name-list and the wired provider are derived from that one value, so they cannot disagree.

The authorize gate is required too, and for the same reason

auth settles WHO the peer is. authorize settles WHAT it may do, and it is required on the same factories with no default, so authorization cannot end up off through an omission. It takes an interceptor: (ctx) => Denial | void, where returning nothing allows and deny(code) refuses.

import { createListenerSubstrate, scopeAuthorizer, compositeInterceptor, deny, ALLOW_ALL } from "@claudewerk/bridge-substrate";

createListenerSubstrate({
  peerId: "gate",
  // One key record per peer, chosen by the id the peer claimed in its hello.
  auth: substrateAuth({ myKey, peerRecordFor: (peer) => keyRecords.get(peer), method: AUTH_METHOD }),
  authorize: scopeAuthorizer({
    scopesOf: (peer) => keyRecords.get(peer)?.scopes,  // unknown peer -> refused
    matrix: { producer: ["open", "send", "close"], admin: ["*"] },
  }),
});

Where the gate sits is the whole value of it. Every inbound operation is offered to the chain after address and verb validation and before object lookup, the sequencer, the durable commit and the hop-ack -- so a denied peer creates no object here, does not move the receive frontier, and is never told its operation was accepted. An inbound stream establish passes the same chain and is refused with establish_fail{unauthorized} before a label is allocated; objKind: "stream" is not a way around the gate.

It is a CHAIN because observing is the common case: an interceptor that returns nothing allows, so tracing and audit are members alongside the policy, and the first denial stops the rest.

authorize: compositeInterceptor([
  (ctx) => void log.info("inbound", { peer: ctx.peer, verb: ctx.verb, obj: ctx.obj }),
  (ctx) => (ctx.obj.startsWith("admin/") ? deny("unauthorized") : undefined),
  scopeAuthorizer({ scopesOf, matrix }),
]);

The shipped policy is deny-by-default and calls mutual-key-auth's scopeAllows rather than restating the rule -- one definition, two callers. ALLOW_ALL is the named opt-out and gives back the pre-A1 posture: any authenticated peer may do anything on this connection. ctx.cap carries an opaque capability token the opener presented (open(kind, addr, { cap }) / openStream(addr, { cap })); the substrate ferries it and never parses it.

The end-to-end crypto seam

src/e2e.ts is what the e2eSeal and e2eSign slot names MEAN. One registry per slot holds the suites this build implements; a host advertises exactly those names and resolves a negotiated name back through the same map, so the list and the wiring cannot drift. A negotiated name with no implementation is refused at ready, never skipped.

The only suite shipped today is none, which is the honest answer for a point-to-point deployment: TLS already covers every hop, and the real suites exist for a relay that must carry what it cannot read. Adding one is two registry entries and no change to negotiation.

Observability (pluggable, vendor-neutral)

The substrate stays dependency-free but is instrumented. Four narrow concerns -- Logger / Metrics / Events / Tracer -- each with a null default (zero cost) and a composite (fan-out to N). Pass an observability bag to the host factories; heavy sinks (OTel, Prometheus) are adapters BEHIND the interfaces, never deps.

import { createListenerSubstrate, observe, inMemoryMetrics, ALLOW_ALL } from "@claudewerk/bridge-substrate";

const metrics = inMemoryMetrics();                       // snapshot() feeds a dashboard
createListenerSubstrate({ peerId: "host", authorize: ALLOW_ALL, auth: "none", observability: observe({ metrics }) });
// ... later:
metrics.snapshot().gauges.filter(g => g.name === "substrate.outbox.depth"); // per-object backlog

Default is NO_OBSERVABILITY -- uninstrumented is the norm and free. Emitted signals: substrate.ops.enqueued/acked, substrate.outbox.depth{obj} (per-object backlog), substrate.ack.latency_ms / substrate.answer.latency_ms, substrate.ingress.op{verb}, substrate.dedupe.duplicate, substrate.seq.deferred, substrate.authorize.denied{verb,objKind,code}, substrate.bounce, substrate.futile{code}, substrate.conn.ready/ended. Adapters: consoleLogger/Metrics/Events (debug), inMemoryMetrics (dashboards); an OTel adapter is the reserved next one. See src/observe.ts + src/observe-adapters.ts.

Layout

src/
  wire.ts        Env / Frame types, verbs, objKind, address validation
  observe.ts     pluggable observability: Logger/Metrics/Events/Tracer, null + composite
  observe-adapters.ts  console + in-memory sinks (OTel deferred)
  codec.ts       cborCodec as a WS Codec (zero glue)
  auth.ts        asAuthProvider as a WS AuthProvider (zero glue)
  host.ts        createDialer/ListenerSubstrate; capabilities; per-peer rebind
  link.ts        the structural Link surface (subset of ReliableConnection)
  substrate.ts   the Substrate: object registry, routing, the two ack levels, laws
  object.ts      SubstrateObject: state machine + app-facing verbs
  outbox.ts      durable per-object outbox, strict-FIFO one-in-flight, receipt-fenced
  sequencer.ts   per-object in-seq delivery, gap buffer, out_of_seq, resync
  dedupe.ts      durable (peer,id) dedupe ledger over the bun-mq Store
  finality.ts    error-class derivation + per-op-id tombstones
test/
  handshake.test.ts    four packages compose; connId agrees on both ends
  session.test.ts      open+accept, req->res, send->ack, reject, close
  reliability.test.ts  exactly-once across a forced disconnect; strict FIFO under redelivery
  futility.test.ts     non-participant close rejected; unknown-object bounce+tombstone; bad address
  auth.test.ts         wrong key fails; tampered tuple breaks the proof
  primitives.test.ts   unit tests for outbox / sequencer / dedupe

What this proves

Over a real reliable-ws connection between two Substrate instances:

  • session open A->B (accept), req -> res, one-way send -> ack;
  • exactly-once across a forced disconnect + reconnect (resend + dedupe);
  • strict FIFO per object preserved under a redelivery;
  • a close/err from a non-participant is rejected (object stays alive);
  • a frame to an unknown object yields exactly one terminal err + a per-op-id tombstone (a later legit open of the same name still works);
  • auth/downgrade: a wrong key fails; a tampered negotiated tuple (connId / cross-layer set) breaks the direction proof.

Deferred (clean seams, NOT built)

F1 CORE stops here. Later phases have named seams in the code:

  • F2 bulk transfer -- the chunk verb + sendChunk on the carrier (declared, not wired).
  • F3 peer lifecycle -- active/unreachable/severing/terminated (the substrate is per-peer + rebindable, which is the hook).
  • F4 discovery + propose/consent/elicit -- new verbs on the same router.
  • F5 state@1 (Yjs) -- a third objKind on the same object machinery.

Sequencer state and tombstones are in-memory (the durable outbox + dedupe carry exactly-once across restart); durable sequencer/tombstone persistence is a later-phase seam. See FINDINGS.md for what composing revealed.