Skip to content

Making Requests ​

The Network plugin (app.use(Network)) wraps the @welshman/net request and publish functions and binds them to the app's net context (its pool and repository). Cleaning up the app aborts its in-flight requests. The Sync plugin (app.use(Sync)) adds negentropy-aware reconciliation.

Loading and requesting ​

typescript
const net = app.use(Network)

// A one-shot request, which resolves with the matching events
const events = await net.load({
  filters: [{kinds: [1], authors: [pubkey]}],
  relays: ["wss://relay.example"],
  timeout: 3000,
})

// Open a subscription
net.request({
  filters: [{kinds: [1]}],
  relays: ["wss://relay.example"],
  onEvent: event => console.log(event),
})

net.load is net.request with autoClose: true. A load resolves once every relay has closed or timed out, so an empty result means every relay answered nothing. The outbox model routes on that answer. A timeout caps how long a caller waits, and threshold: 0.5 resolves once half the relays have closed and closes the rest. A load whose results are read reactively from the repository needs neither. Requests made at the same time share a relay subscription through socketPolicyMultiplex.

net.publish(options) sends a signed event directly. App publishing goes through the Publisher, which adds the optimistic write and status tracking.

The outbox model: loadUsingOutbox ​

Most data plugins load through loadUsingOutbox. Given an author's pubkey, it resolves [...relays(hints), outbox(pubkey)] (the relay hints plus the author's NIP-65 write relays) through the Router, requests the resulting urls once with a 3s timeout, and returns the most recent matching event.

typescript
const latestProfile = await net.loadUsingOutbox(pubkey, {kinds: [0]})

// With relay hints to try first
const note = await net.loadUsingOutbox(pubkey, {kinds: [1], limit: 1}, ["wss://hint.example"])

The filter is always constrained to authors: [pubkey]. Lazy loading in Profiles, FollowLists, MuteLists and the other outbox-backed plugins goes through it.

loadAllUsingOutbox(pubkey, filter?, hints?) routes the same way and returns every matching event instead of the newest one.

Negentropy sync ​

Sync reconciles the repository with relays one relay at a time. A relay whose NIP-11 document advertises negentropy (app.use(Relays).hasNegentropy(url)) gets NIP-77 set reconciliation through @welshman/net's pull and push. Any other relay gets a plain request (pull) or one publish per event (push).

typescript
type AppSyncOpts = {relays: string[]; filters: Filter[]}

const sync = app.use(Sync)

// Pull missing events from relays into the repository
await sync.pull({relays, filters: [{kinds: [3], authors: [pubkey]}]})

// Push local events up to relays
await sync.push({relays, filters: [{authors: [pubkey]}]})

// Query the repository's signed events
const local = await sync.query([{kinds: [1]}])

Querying the repository: Events ​

app.use(Network) fetches events, and app.use(Events) reads the ones already in the repository. Every method binds this app's repository and returns a Projection. Every view but isDeleted is an AsyncProjection, whose load() reads the repository fresh.

typescript
import {Events} from "@welshman/app"

const events = app.use(Events)

events.byId(filters).$          // Map<id, TrustedEvent>
events.all(filters).$           // store order
events.asc(filters).$           // oldest first
events.desc(filters).$          // newest first
events.one(idOrAddress, hints)  // a single event, loaded on first read if missing
events.isDeleted(event).$

Scoped to a relay, via the tracker:

typescript
events.byIdForUrl(url, filters).$
events.forUrl(url, filters).$
events.byIdByUrl(filters).$             // Map<url, Map<id, TrustedEvent>>
events.relaySignedForUrl(url, filters).$ // only events the relay itself signed

relaySignedForUrl drops any event whose pubkey isn't the relay's NIP-11 self, as RelaySignedDerivedPlugin does. Relay-generated kinds (NIP-29 room state, relay membership) mean nothing from another author.