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/relay-chronicle

v1.0.0

Published

Chronicle relay history and state from NIP-66 Kind 1066 delta events

Downloads

13

Readme

@nostrwatch/relay-chronicle

Historical relay event management from NIP-66 delta events.

npm version License Status Runtime

Overview

@nostrwatch/relay-chronicle stores, aggregates, and queries relay (a WebSocket server that stores and forwards Nostr events) monitoring history. It reconstructs relay state by replaying NIP-66 kind 1066 delta events (signed JSON objects that record relay state changes) in chronological order. Each delta captures a transition — a relay coming online, going offline, changing its software version, or shifting its network location. The library is storage-agnostic: callers implement the EventStorage interface against any backend (Nostr relays, REST API, SQLite, browser IndexedDB). All functions are pure and stateless. No dependencies.

Prerequisites

Node.js >=18 and pnpm >=9. Node.js is required only for server-side use — the browser build has no Node.js requirements.

Installation

pnpm add @nostrwatch/relay-chronicle

Or with npm:

npm install @nostrwatch/relay-chronicle

Quick Start

import {composeState, type EventStorage} from '@nostrwatch/relay-chronicle'
import {SimplePool} from 'nostr-tools/pool'

const pool = new SimplePool()

const storage: EventStorage = {
  async query(options) {
    const events = await pool.querySync(
      ['wss://relay.damus.io'],
      {kinds: [1066], '#r': [options.relay], since: options.since}
    )
    return events.sort((a, b) => a.created_at - b.created_at)
  }
}

const result = await composeState({
  storage,
  relay: 'wss://nos.lol',
  since: Date.now() / 1000 - 86400  // last 24 hours
})

console.log(result.state.online)       // true
console.log(result.state.info.name)    // relay's NIP-11 name
console.log(result.state.geo.city)     // relay's city

API

composeState(options)

async function composeState(options: ComposeOptions): Promise<ComposedState | ComposedSnapshots>

Reconstructs complete relay state by applying delta events sequentially. Returns the final state snapshot, or an array of per-event snapshots when snapshots: true.

| Option | Type | Default | Description | |--------|------|---------|-------------| | storage | EventStorage | — | Storage implementation for querying kind 1066 events | | relay | string | — | Relay WebSocket URL | | since | number | — | Start Unix timestamp | | until | number | — | End Unix timestamp | | statusOnly | boolean | false | Include only state-transition events (O tag) | | snapshots | boolean | false | Return per-event state snapshots instead of final state |

calculateUptime(options)

async function calculateUptime(options: TimeSeriesOptions): Promise<UptimeStats>

Calculates uptime statistics over the given time range.

const stats = await calculateUptime({
  storage,
  relay: 'wss://nos.lol',
  since: Date.now() / 1000 - 2592000  // last 30 days
})

console.log(stats.uptimePercent)   // 99.5
console.log(stats.outageCount)     // 2
console.log(stats.currentStatus)   // 'online'

getLatestState(storage, relay)

async function getLatestState(storage: EventStorage, relay: string): Promise<RelayState | null>

Returns the most recent state snapshot for a relay, or null if no events are found.

liveness(storage, relay)

async function liveness(storage: EventStorage, relay: string): Promise<LivenessInfo | null>

Returns current liveness status: whether the relay is live, when it was first detected, and when it was last checked.

lastDowntime(storage, relay)

async function lastDowntime(storage: EventStorage, relay: string): Promise<DowntimeInfo | null>

Returns information about the most recent downtime period. down_now: true indicates the relay is currently offline.

uptimeHistory(storage, relay, options?)

async function uptimeHistory(
  storage: EventStorage,
  relay: string,
  options?: {since?: number; until?: number}
): Promise<Period[]>

Returns the full sequence of uptime and downtime periods over the requested time range. Each Period has type ('up' or 'down'), duration in milliseconds, and ongoing flag.

whenInit(storage, relay)

async function whenInit(storage: EventStorage, relay: string): Promise<InitInfo | null>

Returns the timestamp and event ID of the first time the relay was detected.

lastChange(storage, relay, field)

async function lastChange(
  storage: EventStorage,
  relay: string,
  field: string
): Promise<ChangeInfo | null>

Returns when a specific relay field last changed. Field names support dot notation: 'version', 'dns.asn', 'geo.city'.

changeHistory(storage, relay, field, options?)

async function changeHistory(
  storage: EventStorage,
  relay: string,
  field: string,
  options?: {since?: number; until?: number}
): Promise<ChangeInfo[]>

Returns the complete change history for a field. Each entry contains oldValue, newValue, timestamp, and date.

Time series functions

Generate time series data for use with @nostrwatch/relay-charts or custom visualizations:

| Function | Returns | Description | |----------|---------|-------------| | generateUptimeSeries(options) | Promise<UptimePoint[]> | Uptime/downtime series for availability charts | | generateRttSeries(options) | Promise<TimeSeriesPoint<number>[]> | RTT latency series | | generateChangeTimeline(options) | Promise<ChangeEvent[]> | Significant relay changes (software, infrastructure) | | generateAggregatedSeries(options) | Promise<AggregatedStats[]> | Bucketed statistics (hourly, daily) | | generateFieldSeries(options, field) | Promise<TimeSeriesPoint<any>[]> | Track any field value over time |

const series = await generateRttSeries({
  storage,
  relay: 'wss://nos.lol',
  since: Date.now() / 1000 - 86400
})
// [{timestamp: 1699000000, value: 145}, ...]

EventStorage interface

Implement this interface to connect any storage backend:

interface EventStorage {
  query(options: QueryOptions): Promise<DeltaEvent[]>
}

interface QueryOptions {
  relay: string
  since?: number
  until?: number
  limit?: number
  statusOnly?: boolean
  periods?: string[]
}

Results must be sorted by created_at ascending. The library replays events in the order returned.

Core types

interface RelayState {
  url: string
  operationalStatus?: 'init' | 'down' | 'up'
  online: boolean
  rttOpen?: number
  retryCount?: number
  info: Record<string, any>    // NIP-11 relay document fields
  dns: Record<string, any>     // DNS data: asn, address, etc.
  geo: Record<string, any>     // Geo data: city, country, geohash, etc.
  timestamp: number
  eventId: string
}

interface UptimeStats {
  uptimeMs: number
  downtimeMs: number
  uptimePercent: number
  outageCount: number
  avgOutageDurationMs?: number
  maxOutageDurationMs?: number
  currentStatus: 'online' | 'offline' | 'unknown'
}

Known Limitations

No known limitations at this time.

Agent Skills

No agent skills defined yet for this package.

Related Packages

  • @nostrwatch/relay-charts — visualization layer; consumes UptimePeriod[] and ChangeInfo[] from relay-chronicle to render charts
  • @nostrwatch/route66 — persistent relay state management; ChronicleService wraps relay-chronicle for use in the monitoring pipeline
  • @nostrwatch/nip66 — NIP-66 event kind definitions and type helpers; relay-chronicle consumes kind 1066 events
  • apps/gui — primary consumer; displays relay history and uptime charts sourced from relay-chronicle

License

MIT