Skip to content

Relay Pool ​

The RelayPool class in applesauce-relay manages multiple relay connections as one event source. Its high-level request and subscription methods emit deduplicated events and report total active-cohort failure as RelayGroupError.

Features ​

  • Connect to multiple relays
  • Create and manage groups of relays
  • Send requests and events to multiple relays simultaneously
  • Maintain a blacklist of relays to avoid

Relay Management ​

The RelayPool provides methods to create and manage relay connections:

typescript
// Get or create a relay connection
const relay = pool.relay("wss://relay.example.com");

// Create a group of relays
const group = pool.group(["wss://relay1.example.com", "wss://relay2.example.com"]);

Making Requests ​

The RelayPool provides several methods to interact with relays. These methods mirror those found in the Relay class, allowing you to use familiar patterns while working with multiple relays simultaneously.

REQ Method ​

The req method sends a subscription request to multiple relays:

typescript
// Send a REQ to multiple relays
pool
  .req(relays, {
    kinds: [1],
    limit: 10,
  })
  .subscribe({
    next: (response) => {
      if (response === "EOSE") {
        console.log("End of stored events from all relays");
      } else {
        console.log("Event", response);
      }
    },
    error: (error) => {
      console.error("Subscription error:", error);
    },
  });

Note: The req method does not deduplicate events by default. If you need deduplication, use the request or subscription methods instead, which automatically deduplicate events using an event store.

Event Method ​

The event method sends an EVENT message to multiple relays and returns an observable of the responses from each relay.

typescript
const event = {
  kind: 1,
  content: "Hello from RelayPool!",
  created_at: Math.floor(Date.now() / 1000),
  tags: [],
  // ... other required fields
};

// Subscribe to a stream of responses
pool.event(relays, event).subscribe({
  next: (response) => {
    console.log(`Published to ${response.from}:`, response.ok);
    if (!response.ok) console.log(`Error message: ${response.message}`);
  },
  complete: () => {
    console.log("Publishing complete");
  },
});

Publish Method ​

The publish method is a wrapper around the event method that returns a Promise<PublishResponse[]> and automatically handles retrying:

typescript
// Publish with retries (defaults to 3 retries)
const responses = await pool.publish(relays, event);
for (const response of responses) {
  console.log(`Published to ${response.from}:`, response.ok, response.message);
}

Request Method ​

The request method allows you to make one-off requests with automatic retries for connection errors:

typescript
pool.request(relays, { kinds: [1], limit: 50 }, { timeout: 5000 }).subscribe({
  next: (event) => console.log(event.id),
  error(error) {
    if (error instanceof RelayGroupError) {
      for (const [url, outcome] of Object.entries(error.outcomes)) console.error(url, outcome.error);
    }
  },
  complete: () => console.log("Request complete"),
});

request() has one 30-second whole-Observable timeout by default. Events, EOSE, retries, and reconnections do not reset it. A request with no active relays completes empty; one EOSE plus any failures is also a successful request.

By default, request() completes once relays go quiet after the first EOSE (RelayGroup.completeAfterIdle()). It waits 3 seconds for relays that haven't responded, keeps waiting while events arrive, and stops after 15 seconds. Duplicate-only traffic can extend the wait for at most 5 seconds without a new event ID (progress); increase this option when useful events arrive between long duplicate bursts. A relay that sends nothing within firstResponseTimeout (default 10 seconds) is treated as failed. Pass complete to choose a different condition; the completion conditions example compares them side by side.

Subscription Method ​

The subscription method creates persistent subscriptions that retry connection errors and can resubscribe after clean relay CLOSED messages:

typescript
// Create persistent subscription
const subscription = pool
  .subscription(
    relays,
    {
      kinds: [1, 7],
      "#t": ["nostr"],
    },
    {
      id: "custom-sub-id", // optional custom subscription ID
      reconnect: Infinity, // retry connection errors forever
      resubscribe: true, // resubscribe after clean relay CLOSED messages
    },
  )
  .subscribe({
    next: (event) => console.log("Subscription update:", event),
  });

// Later, you can unsubscribe
subscription.unsubscribe();

Subscriptions have no built-in duration or inactivity timeout. Compose the lifetime at the call site when needed:

ts
import { takeUntil, timer } from "rxjs";

pool.subscription(relays, filters).pipe(takeUntil(timer(30_000)));

All of these methods accept the same parameters as their counterparts in the Relay class, making it easy to transition between working with individual relays and relay pools.

Subscription Map Method ​

The subscriptionMap method allows you to subscribe to different filters on different relays:

typescript
import { createFilterMap } from "applesauce-core/helpers";

// Create a map of relay URLs to filters
const filterMap = {
  "wss://relay1.example.com": { kinds: [1], authors: ["pubkey1"] },
  "wss://relay2.example.com": { kinds: [1], authors: ["pubkey2"] },
};

pool.subscriptionMap(filterMap).subscribe({
  next: (event) => console.log("Event:", event),
});

Outbox Subscription Method ​

The outboxSubscription method subscribes to each user's events on their own outbox relays; see Outbox.

Counting and Sync ​

pool.count() and pool.sync() send COUNT and negentropy requests to several relays and report each relay's result separately. See Counting and Negentropy Sync.

Relay Groups ​

The RelayGroup class is used internally by RelayPool to manage collections of relays. You can access relay groups directly through the pool:

typescript
// Create a group of relays
const group = pool.group(["wss://relay1.example.com", "wss://relay2.example.com"]);

// Make requests to the group
group.req({ kinds: [1] }).subscribe((response) => console.log(response));

// Send events to the group
group.event(event).subscribe((response) => console.log(response));

// Use other group methods
const responses = await group.publish(event);
group.request({ kinds: [1] }).subscribe((event) => console.log(event));
group.subscription({ kinds: [1] }).subscribe((response) => console.log(response));

High-level Group and Pool methods preserve the same RelayGroupError instance. Outcome keys are normalized relay URLs and causes retain identity; raw req() keeps exposing lifecycle messages instead of this aggregate error contract.

Integration ​

Dynamic subscriptionMap() and outboxSubscription() replace the active relay cohort as their maps change. An empty dynamic subscription stays open for future relays, while an empty finite request completes immediately.

Pool and group methods forward auth options to every relay; see Authentication.

Best Practices ​

  • Handle RelayGroupError at the returned Observable's error callback.
  • Use outcomes for normalized URL lookup and native errors for aggregate tooling.
  • Let subscriptions run until the consumer unsubscribes.
  • Compose timeout or takeUntil(timer(...)) when a caller needs its own bound.

Observable Properties ​

The RelayPool provides observables for tracking relays:

status$ emits a record of the latest status for every relay, keyed by relay URL. Adding or removing a relay emits one complete snapshot while retaining subscriptions to unchanged relays; each relay's subsequent status changes still emit updated snapshots. An empty pool emits {}.

typescript
pool.status$.subscribe((statuses) => {
  console.log("Connected relays:", Object.values(statuses).filter((status) => status.connected).length);
});

Migration: membership changes no longer emit intermediate empty or partially rebuilt status records. Handle the complete snapshot instead of relying on those intermediate emissions.

typescript
// Subscribe to changes in the relays map
pool.relays$.subscribe((relaysMap) => {
  console.log("Relays updated:", Array.from(relaysMap.keys()));
});

// Listen for relay additions
pool.add$.subscribe((relay) => {
  console.log("Relay added:", relay.url);
});

// Listen for relay removals
pool.remove$.subscribe((relay) => {
  console.log("Relay removed:", relay.url);
});