Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/sandwichfarm/nostr-watch/llms.txt

Use this file to discover all available pages before exploring further.

@nostrwatch/route66 sits between raw relay checks (performed by @nostrwatch/nocap) and the application layer — GUIs and REST APIs consume route66, not raw check events. It aggregates relay status data from NIP-66 monitors and exposes it through a consistent querying API. Both the cache layer and WebSocket connection layer are independently swappable, allowing route66 to work in browser, Node.js, and worker thread environments.

Installation

npm install @nostrwatch/route66

Quick start

import {Route66} from '@nostrwatch/route66'
import {NostrSqliteAdapter} from '@nostrwatch/route66-cacheadapter-nostrsqlite'
import {NostrToolsAdapter} from '@nostrwatch/route66-wsadapter-nostrtools'

const cacheAdapter = new NostrSqliteAdapter()
const websocketAdapter = new NostrToolsAdapter()

const r66 = new Route66({cacheAdapter, websocketAdapter})
await r66.boot()
await r66.ready()

const relays = await r66.relays?.getRelays({network: 'clearnet'})
console.log(relays)
// { 'wss://relay.damus.io': { record: { url: '...', network: 'clearnet', ... } }, ... }

Route66 class

The main entry point. Wraps the cache and WebSocket adapters and exposes RelayService, MonitorService, and StateManager through a unified interface.
class Route66 {
  constructor(adapters: IAdaptersArgument)
  boot(): Promise<void>
  ready(): Promise<Route66>
  shutdown(): Promise<void>
  restart(): Promise<void>
  on(event: string, listener: (...args: any[]) => void): void
  once(event: string, listener: (...args: any[]) => void): void
  off(event: string, listener: (...args: any[]) => void): void
  destroy(): void
  get relays(): RelayService | undefined
  get monitors(): MonitorService | undefined
  get cache(): ICacheAdapter | undefined
  get websocket(): IWebsocketAdapter | undefined
  get state(): typeof StateManager
  get initialized(): boolean
}

Constructor

adapters
IAdaptersArgument
required
An object containing both adapter instances.

Methods

boot() Initializes workers, services, and waits for adapters to signal readiness. Call once after construction. ready() Resolves when initialization is complete. Safe to call multiple times; resolves immediately if already initialized. shutdown() Gracefully shuts down all adapters and workers. restart() Equivalent to calling shutdown() followed by boot().

Getters

relays — Returns the RelayService instance for relay state queries. Available after boot(). monitors — Returns the MonitorService instance for managing active monitor subscriptions. Available after boot(). state — Returns a reference to the StateManager singleton. initializedtrue once boot() has completed successfully.

StateManager

A static singleton that provides shared state, event emission, and local storage access across all route66 services.
class StateManager {
  static get abortController(): AbortController
  static get abortSignal(): AbortSignal
  static abort(): void
  static aborted(): boolean
  static resetAbort(): void
  static emit(event: string, ...args: any[]): void
  static on(event: string, listener: (...args: any[]) => void): void
  static once(event: string, listener: (...args: any[]) => void): void
  static off(event: string, listener?: (...args: any[]) => void): void
  static set(key: string, value: any): void
  static get(key: string): any
  static remove(key: string): void
  static clear(): void
  static size(): number
}
abort() — Cancels all in-flight async operations that respect StateManager.abortSignal. Emits an 'abort' event. resetAbort() — Creates a new AbortController, re-enabling operations after a previous abort() call. emit(event, ...args) — Emits a named event to all registered listeners. set(key, value) / get(key) / remove(key) / clear() — Local storage operations namespaced to the 'state' prefix. Used internally by services to persist lightweight state across page reloads.

ChronicleService

Provides relay history and time series data from Kind 1066 delta events (NIP-66 append-only history log).
class ChronicleService {
  constructor(adapters: IAdaptersArgument, options?: ChronicleServiceOptions)
  syncRelay(relay: string): Promise<void>
  getTimeSeriesData(options: TimeSeriesOptions): Promise<TimeSeriesPoint[]>
  getUptimePeriods(relay: string, since?: number, until?: number): Promise<UptimePeriod[]>
}

ChronicleServiceOptions

autoSync
boolean
default:"false"
Automatically subscribe to Kind 1066 events for queried relays.
syncRelays
string[]
default:"[]"
Relay URLs to fetch Kind 1066 events from.

TimeSeriesOptions

relay
string
required
The relay URL to query.
since
number
Start of time range as a Unix timestamp in seconds.
until
number
End of time range as a Unix timestamp in seconds.
type
string
required
Type of time series data: 'rtt', 'uptime', or 'changes'.
aggregate.bucketSize
number
Bucket size in seconds for time-series aggregation.
aggregate.fn
string
Aggregation function: 'avg', 'min', 'max', or 'sum'.

UptimePeriod fields

start
number
required
Period start timestamp (Unix seconds).
end
number | null
required
Period end timestamp. null if the period is still ongoing.
online
boolean
required
Whether the relay was online during this period.
rtt
number
Round-trip time in milliseconds (optional; may be absent — see known limitations).

Example

import {ChronicleService} from '@nostrwatch/route66'

const chronicle = new ChronicleService(r66.adapters, {
  autoSync: true,
  syncRelays: ['wss://relay.nostr.watch']
})

await chronicle.syncRelay('wss://relay.damus.io')

const rttData = await chronicle.getTimeSeriesData({
  relay: 'wss://relay.damus.io',
  type: 'rtt',
  since: Math.floor(Date.now() / 1000) - 86400
})
console.log(rttData)
// [{ timestamp: 1709000000, value: 120 }, ...]

MonitorService

Manages subscriptions to NIP-66 monitors (Kind 10166 announcements) and tracks which monitors are currently active. Created automatically when Route66 is initialized. Access it via r66.monitors.
class MonitorService {
  get array(): Monitor[]
  get map(): Map<string, Monitor>
  get enabledMonitors(): Monitor[]
  get activeEnabledMonitors(): Monitor[]
  get disabledMonitors(): Monitor[]
  ready(): Promise<void>
}
array — All known monitors as an array. map — All known monitors as a Map keyed by monitor pubkey. enabledMonitors — Monitors that are enabled in the local configuration. activeEnabledMonitors — Enabled monitors that are currently publishing NIP-66 events. disabledMonitors — Monitors that have been disabled. ready() — Resolves when the initial monitor list has been loaded.

Adapter pattern

route66 has two independent adapter dimensions: cache adapters store and query relay state data, and WebSocket adapters connect to monitoring relays over Nostr. Both are provided at construction time.

Cache adapter interface

Cache adapters extend CacheAdapter and implement ICacheAdapter:
import {CacheAdapter, type ICacheAdapter} from '@nostrwatch/route66'
import type {IEvent} from '@nostrwatch/route66'

export class MyCacheAdapter extends CacheAdapter implements ICacheAdapter {
  readonly slug = 'MyCacheAdapter'

  async ready(): Promise<void> {
    // Signal that the adapter is initialized and ready to serve queries
  }

  async REQ(filters: any[]): Promise<IEvent[]> {
    // Execute a Nostr REQ query and return matching events
    return []
  }

  async COUNT(filters: any[]): Promise<number> {
    // Return the count of events matching the given filters
    return 0
  }

  async DELETE(filters: any[]): Promise<string[]> {
    // Delete events matching the given filters; return deleted event IDs
    return []
  }

  async DUMP(): Promise<Uint8Array> {
    // Export the full event store as a binary blob
    return new Uint8Array()
  }

  async CLOSE(subId: string): Promise<boolean> {
    // Close a subscription by ID; return true if closed successfully
    return true
  }

  async WIPE(): Promise<boolean> {
    // Clear all stored events; return true if successful
    return true
  }

  async addEvent(event: IEvent): Promise<void> {}
  async addEvents(events: IEvent[]): Promise<void> {}
  async putEvent(event: IEvent): Promise<void> {}
}

Existing adapters

NostrSqliteAdapter

SQLite-backed cache storage using @nostrwatch/worker-relay in a web worker. Works in both browser and Node.js environments.Package: @nostrwatch/route66-cacheadapter-nostrsqlite

NostrToolsAdapter

WebSocket implementation using nostr-tools and nostr-fetch. Used for connecting to NIP-66 monitoring relays.Package: @nostrwatch/route66-wsadapter-nostrtools

Known limitations

Hardcoded filter limit. MonitorService caps subscriptions at 10 filters (MAX_FILTERS = 10) regardless of relay support. Relays that allow more filters will not be fully utilized. As a workaround, split subscriptions across multiple MonitorService instances.
Default relays hardcoded in source. Default relay URLs in RelayService and MonitorService are set in the constructor rather than loaded from configuration. Changing the default relay list requires a code change. Pass an explicit relay list to the respective service constructor as a workaround.
Incomplete RTT extraction. RTT values are not extracted from chronicle period metadata in ChronicleService. Uptime and downtime records may be missing RTT data. No workaround is available at this time.

@nostrwatch/nocap

Performs the raw relay capability checks that produce the NIP-66 events route66 aggregates.

@nostrwatch/auditor

NIP conformance testing; audit results can be published as NIP-66 events.

@nostrwatch/nostrings

Relay URL sanitization; route66 passes relay URLs through nostrings before storage.

Build docs developers (and LLMs) love