Skip to content

A utility library that allows JS/TS apps to effortlessly fetch past events from Nostr relays.

License

Notifications You must be signed in to change notification settings

jiftechnify/nostr-fetch

Repository files navigation

nostr-fetch

A utility library that allows JS/TS apps to effortlessly fetch past events from Nostr relays.

Installation

for npm Project

npm install nostr-fetch

for Deno Project

deno add npm:nostr-fetch

for Browser Apps, without Bundlers

You can also use nostr-fetch in your HTML via <script> tags, thanks to jsDelivr.

<script type="module">
  import { NostrFetcher } from "https://cdn.jsdelivr.net/npm/[email protected]/+esm"
  // ...
</script>

Note for Users on Node.js < v22

Node.js < v22 doesn't have native WebSocket implementation. On Such a environment you may want to pass a custom WebSocket constructor from an external package like ws to NostrFetcher.init() as an option.

npm install ws
import { NostrFetcher } from "nostr-fetch";
import WebSocket from "ws";

const fetcher = NostrFetcher.init({ webSocketConstructor: WebSocket });

Usage

Basics

import { NostrFetcher } from "nostr-fetch";

const nHoursAgo = (hrs: number): number =>
  Math.floor((Date.now() - hrs * 60 * 60 * 1000) / 1000);

const fetcher = NostrFetcher.init();
const relayUrls = [/* relay URLs */];

// fetches all text events since 24 hr ago in streaming manner
const postIter = fetcher.allEventsIterator(
    relayUrls, 
    /* filter (kinds, authors, ids, tags) */
    { kinds: [ 1 ] },
    /* time range filter (since, until) */
    { since: nHoursAgo(24) },
    /* fetch options (optional) */
    { skipFilterMatching: true }
);
for await (const ev of postIter) {
    console.log(ev.content);
}

// fetches all text events since 24 hr ago, as a single array
const allPosts = await fetcher.fetchAllEvents(
    relayUrls,
    /* filter */
    { kinds: [ 1 ] },
    /* time range filter */
    { since: nHoursAgo(24) },
    /* fetch options (optional) */
    { sort: true }
)

Various Fetch Methods

import { NostrFetcher } from "nostr-fetch";

const fetcher = NostrFetcher.init();
const relayUrls = [/* relay URLs */];

// fetches latest 100 text posts
// internally: 
// 1. fetch latest 100 events from each relay
// 2. merge lists of events
// 3. take latest 100 events
const latestPosts: NostrEvent[] = await fetcher.fetchLatestEvents(
    relayUrls,
    /* filter */
    { kinds: [ 1 ] },
    /* number of events to fetch */
    100,
);

// fetches the last metadata event published by pubkey "deadbeef..."
// internally:
// 1. fetch the last event from each relay
// 2. take the latest one
const lastMetadata: NostrEvent | undefined = await fetcher.fetchLastEvent(
    relayUrls,
    /* filter */
    { kinds: [ 0 ], authors: [ "deadbeef..." ] },
);

// fetches latest 10 text posts from each author in `authors`
const postsPerAuthor = fetcher.fetchLatestEventsPerAuthor(
    /* authors and relay set */
    // you can also pass a `Map` which has mappings from authors (pubkey) to reley sets,
    // to specify a relay set for each author
    { 
        authors: ["deadbeef...", "abcdef01...", ...],
        relayUrls,
    },
    /* filter */
    { kinds: [ 1 ] },
    /* number of events to fetch for each author */
    10,
);
for await (const { author, events } of postsPerAuthor) {
    console.log(`posts from ${author}:`);
    for (const ev of events) {
        console.log(ev.content);
    }
}

// fetches the last metadata event from each author in `authors`
const metadataPerAuthor = fetcher.fetchLastEventPerAuthor(
    /* authors and relay set */
    // you can also pass a `Map` which has mappings from authors (pubkey) to reley sets,
    // to specify a relay set for each author
    {
        authors: ["deadbeef...", "abcdef01...", ...],
        relayUrls,
    }
    /* filter */
    { kinds: [ 0 ] },
);
for await (const { author, event } of metadataPerAuthor ) {
    console.log(`${author}: ${event?.content ?? "not found"}`);
}

Working with custom relay pool implementations

First, install the adapter package for the relay pool implementation you want to use. For example, if you want to use nostr-fetch with nostr-tools' SimplePool :

npm install @nostr-fetch/adapter-nostr-tools

Then, wrap your relay pool instance with the adapter and pass it to the initializer NostrFetcher.withCustomPool().

import { NostrFetcher } from "nostr-fetch";
import { simplePoolAdapter } from "@nostr-fetch/adapter-nostr-tools";
import { SimplePool } from "nostr-tools";

const pool = new SimplePool();

// wrap SimplePool with simplePoolAdapter to make it interoperable with nostr-fetch
const fetcher = NostrFetcher.withCustomPool(simplePoolAdapter(pool));

// now, you can use any fetch methods described above!

Table of Available Adapters

Package Relay Pool Impl. Adapter Package Adapter
nostr-tools (v1) SimplePool @nostr-fetch/adapter-nostr-tools simplePoolAdapter
nostr-tools (v2) SimplePool @nostr-fetch/adapter-nostr-tools-v2 simplePoolAdapter
nostr-relaypool RelayPool @nostr-fetch/adapter-nostr-relaypool relayPoolAdapter
@nostr-dev-kit/ndk NDK @nostr-fetch/adapter-ndk ndkAdapter
rx-nostr (v1) RxNostr @nostr-fetch/adapter-rx-nostr rxNostrAdapter

Cancelling by AbortSignal

import { NostrFecher } from "nostr-fetch"

const fetcher = NostrFetcher.init();
const relayUrls = [/* relay URLs */];

const evIter = fetcher.allEventsIterator(
    relayUrls,
    {/* filter */},
    {/* time range */},
    /* pass an `AbortSignal` here to enable cancellation! */
    { signal: AbortSignal.timeout(1000) },
);

for await (const ev of evIter) {
    // ...
}

Examples

You can find example codes under packages/examples directory.

To run examples, follow the steps (using npm for example):

# first time only: install dependencies & build subpackages
npm install && npm run build


# then, execute example
# the command executes packages/examples/src/fetchAll.ts
npm run example fetchAll

# some examples takes a hex pubkey as an argument
npm run example fetchLastPerAuthor <your hex pubkey>

API

class NostrFetcher

The entry point of Nostr events fetching.

It manages connections to Nostr relays under the hood. It is recommended to reuse single NostrFetcher instance in entire app.

You should instantiate it with following initializers instead of the constructor.


Initializers and Finalizers

NostrFetcher.init

Initializes a NostrFetcher instance based on the default relay pool implementation.

NostrFetcher.withCustomPool

Initializes a NostrFetcher instance based on a custom relay pool implementation passed as an argument.

This opens up interoperability with other relay pool implementations such as nostr-tools' SimplePool. See here for details.

NostrFetcher#shutdown

Cleans up the internal relay pool.

If you use a fetcher instance initialized via NostrFetcher.init, calling this method closes connections to all the connected relays.

You can use a variable with using keyword to automatically shutdown a fetcher instance when the scope of the using variable ends. Roughly speaking, withFinally() and withUsing() in the code bellow are the same.

async function withFinally() {
    const fetcher = NostrFetcher.init();
    try {
        // do some work with the fetcher...
    } finally {
        fetcher.shutdown();
    }
}

async function withUsing() {
    using fetcher = NostrFetcher.init();
    // do some work with the fetcher...

    // the fetcher will be automatically shutdown here!
}

Fetch Methods

All methods are instance methods of NostrFetcher.

allEventsIterator

public allEventsIterator(
    relayUrls: string[],
    filter: FetchFilter,
    timeRangeFilter: FetchTimeRangeFilter,
    options?: AllEventsIterOptions
): AsyncIterable<NostrEvent>

Returns an async iterable of all events matching the filter from Nostr relays specified by the array of URLs.

You can iterate over events using for-await-of loop.

const fetcher = NostrFetcher.init();
const events = fetcher.allEventsIterator([/* relays */], {/* filter */}, {/* time range */});
for await (const ev of events) {
    // process events
}

Specifying enableBackpressure: true in options enables "backpressure mode", where the fetcher is backpressured by the consumer of the iterator.

Note

There are no guarantees about the order of returned events. Especially, events are not necessarily ordered in "newest to oldest" order.


fetchAllEvents

public async fetchAllEvents(
    relayUrls: string[],
    filter: FetchFilter,
    timeRangeFilter: FetchTimeRangeFilter,
    options?: FetchAllOptions
): Promise<NostrEvent[]>

Fetches all events matching the filter from Nostr relays specified by the array of URLs, and collect them into an array.

If sort: true is specified in options, events in the resulting array will be sorted in "newest to oldest" order.

Note

There are no guarantees about the order of returned events if sort options is not specified.


fetchLatestEvents

public async fetchLatestEvents(
    relayUrls: string[],
    filter: FetchFilter,
    limit: number,
    options?: FetchLatestOptions
): Promise<NostrEvent[]>

Fetches latest up to limit events matching the filter from Nostr relays specified by the array of URLs.

Events in the result will be sorted in "newest to oldest" order.


fetchLastEvent

public async fetchLastEvent(
    relayUrls: string[],
    filter: FetchFilter,
    options?: FetchLatestOptions
): Promise<NostrEvent | undefined>

Fetches the last event matching the filter from Nostr relays specified by the array of URLs.

Returns undefined if no event matching the filter exists in any relay.


fetchLatestEventsPerKey

public fetchLatestEventsPerKey<KN extends FetchFilterKeyName>(
    keyName: KN,
    keysAndRelays: KeysAndRelays<KN>,
    otherFilter: FetchFilter,
    limit: number,
    options?: FetchLatestOptions
): AsyncIterable<NostrEventListWithKey<KN>>

Fetches latest up to limit events for each key specified by keyName and keysAndRelays.

keysAndRelays can be either of two types:

  • { keys: K[], relayUrls: string[] }: The fetcher will use the same relay set (relayUrls) for all keys to fetch events.
  • Map<K, string[]>: Key must be the key of event and value must be relay set for that key. The fetcher will use separate relay set for each key to fetch events.

Note

The type K is number if keyName is "kinds". Otherwise, K is string.

Result is an async iterable of { key: <key of events>, events: <events which have that key> } pairs.

Each array of events in the result are sorted in "newest to oldest" order.


fetchLastEventPerKey

public fetchLatestEventsPerKey<KN extends FetchFilterKeyName>(
    keyName: KN,
    keysAndRelays: KeysAndRelays<KN>,
    otherFilter: FetchFilter,
    options?: FetchLatestOptions
): AsyncIterable<NostrEventWithKey<KN>>

Fetches the last event for each key specified by keysAndRelays.

keysAndRelays can be either of two types:

  • { keys: K[], relayUrls: string[] }: The fetcher will use the same relay set (relayUrls) for all keys to fetch events.
  • Map<K, string[]>: Key must be key of the event and value must be relay set for that key. The fetcher will use separate relay set for each key to fetch events.

Note

The type K is number if keyName is "kinds". Otherwise, K is string.

Result is an async iterable of { key: <key of events>, event: <the latest event which have that key> } pairs.

event in result will be undefined if no event matching the filter exists in any relay.


fetchLatestEventsPerAuthor

public fetchLatestEventsPerAuthor(
    authorsAndRelays: AuthorsAndRelays,
    otherFilter: Omit<FetchFilter, "authors">,
    limit: number,
    options: FetchLatestOptions = {}
): AsyncIterable<{ author: string; events: NostrEvent[] }>

Fetches latest up to limit events for each author specified by authorsAndRelays.

It is just a wrapper of fetchLatestEventsPerKey specialized to "authors" key.


fetchLastEventPerAuthor

public fetchLastEventPerAuthor(
    authorsAndRelays: AuthorsAndRelays,
    otherFilter: Omit<FetchFilter, "authors">,
    options: FetchLatestOptions = {}
): AsyncIterable<{ author: string; event: NostrEvent | undefined }>

Fetches the last event for each author specified by authorsAndRelays.

It is just a wrapper of fetchLastEventPerKey specialized to "authors" key.

Support me!

You can support this project by:

  • ⭐ Starring the repo
  • ⚡️ Sending some sats to my lightning address: [email protected]
  • 🐝 Sending funds via PkgZap

About

A utility library that allows JS/TS apps to effortlessly fetch past events from Nostr relays.

Topics

Resources

License

Stars

Watchers

Forks

Contributors 4

  •  
  •  
  •  
  •