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

@nostrwatch/route66

v0.0.2

Published

A NIP-66 implementation written in typescript.

Downloads

20

Readme

@nostrwatch/route66

Relay aggregation and state management system for NIP-66 monitor data.

npm version License Status Runtime

Overview

@nostrwatch/route66 aggregates relay (a WebSocket server that stores and forwards Nostr events) status data from NIP-66 (the Nostr Implementation Possibility for relay monitoring) monitors and exposes it through a consistent querying API. It sits between raw relay checks (performed by @nostrwatch/nocap) and the application layer — the GUI and REST API consume route66, not raw check events.

route66 uses a pluggable adapter architecture for both data caching and WebSocket connections. A cache adapter stores and queries relay state locally; a WebSocket adapter handles connections to monitoring relays that publish NIP-66 events. Both dimensions are independently swappable, allowing route66 to work in browser, Node.js, or worker thread environments.

StateManager provides shared singleton state, lifecycle events, and local storage access. ChronicleService provides time-series relay history from Kind 1066 delta events. MonitorService tracks which monitors are active and manages subscription filters.

Installation

pnpm add @nostrwatch/route66

Or with npm:

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', ... } }, ... }

API

Route66

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)

Creates a Route66 instance. adapters must implement IAdaptersArgument:

| Field | Type | Description | |-------|------|-------------| | cacheAdapter | ICacheAdapter | Stores and queries relay state locally | | websocketAdapter | IWebsocketAdapter | Handles WebSocket connections to monitoring relays |

boot()

Initializes workers, services, and waits for adapters to signal readiness. Call once after construction. Equivalent to calling init() followed by ready().

ready()

Resolves when initialization is complete. Safe to call multiple times; resolves immediately if already initialized.

shutdown()

Gracefully shuts down all adapters and workers.

relays

Returns the RelayService instance, which provides relay state queries. Available after boot().

monitors

Returns the MonitorService instance, which manages active monitor subscriptions. Available after boot().


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 listeners registered with on() and once().

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

| Option | Type | Default | Description | |--------|------|---------|-------------| | autoSync | boolean | false | Automatically subscribe to Kind 1066 events for queried relays | | syncRelays | string[] | [] | Relay URLs to fetch Kind 1066 events from |

TimeSeriesOptions

| Option | Type | Description | |--------|------|-------------| | relay | string | Relay URL to query | | since | number | Start of time range (unix timestamp in seconds) | | until | number | End of time range (unix timestamp in seconds) | | type | 'rtt' \| 'uptime' \| 'changes' | Type of time series data | | aggregate.bucketSize | number | Bucket size in seconds for aggregation | | aggregate.fn | 'avg' \| 'min' \| 'max' \| 'sum' | Aggregation function |

UptimePeriod

| Field | Type | Description | |-------|------|-------------| | start | number | Period start timestamp | | end | number \| null | Period end timestamp (null if ongoing) | | online | boolean | Whether relay was online during this period | | rtt | number (optional) | Round-trip time in milliseconds |

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.

class MonitorService {
  get array(): Monitor[]
  get map(): Map<string, Monitor>
  get enabledMonitors(): Monitor[]
  get activeEnabledMonitors(): Monitor[]
  get disabledMonitors(): Monitor[]
  ready(): Promise<void>
}

MonitorService is created automatically when Route66 is initialized with both a cache and WebSocket adapter. Access it via r66.monitors.

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 via IAdaptersArgument.

Cache Adapters

Cache adapters implement ICacheAdapter and extend the CacheAdapter base class. They store NIP-66 relay check events locally and answer queries from RelayService.

Interface to implement:

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> {
    // Insert a single event; no-op if already present
  }

  async addEvents(events: IEvent[]): Promise<void> {
    // Batch insert events
  }

  async putEvent(event: IEvent): Promise<void> {
    // Upsert a single event (insert or replace)
  }
}

Registration:

import {Route66} from '@nostrwatch/route66'

const cacheAdapter = new MyCacheAdapter()
const r66 = new Route66({cacheAdapter, websocketAdapter})

Existing cache adapters:

WebSocket Adapters

WebSocket adapters implement IWebsocketAdapter and extend the WebsocketAdapter base class. They open connections to Nostr monitoring relays and deliver incoming NIP-66 events to the rest of route66.

Registration:

import {Route66} from '@nostrwatch/route66'

const websocketAdapter = new MyWebsocketAdapter()
const r66 = new Route66({cacheAdapter, websocketAdapter})

Existing WebSocket adapters:

Reference implementations for both adapter types are in libraries/route66/adapters/.

Known Limitations

  • Hardcoded filter limit: MonitorService caps subscriptions at 10 filters (MAX_FILTERS = 10) regardless of what the relay actually supports. Relays that allow more filters will not be fully utilized; relays that allow fewer may reject subscriptions silently. Split subscriptions across multiple MonitorService instances as a workaround. See CONCERNS.md — Hardcoded Filter Limits.

  • 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. See CONCERNS.md — Default Relay Configuration.

  • 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. See CONCERNS.md — Incomplete RTT Extraction.

Agent Skills

No agent skills defined yet for this package.

Related Packages

License

MIT