Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/jiftechnify/nostr-fetch
A utility library that allows JS/TS apps to effortlessly fetch past events from Nostr relays.
https://github.com/jiftechnify/nostr-fetch
javascript nostr typescript
Last synced: 27 days ago
JSON representation
A utility library that allows JS/TS apps to effortlessly fetch past events from Nostr relays.
- Host: GitHub
- URL: https://github.com/jiftechnify/nostr-fetch
- Owner: jiftechnify
- License: mit
- Created: 2023-03-09T09:38:43.000Z (almost 2 years ago)
- Default Branch: main
- Last Pushed: 2024-11-04T04:11:06.000Z (about 1 month ago)
- Last Synced: 2024-11-08T23:04:57.724Z (about 1 month ago)
- Topics: javascript, nostr, typescript
- Language: TypeScript
- Homepage:
- Size: 1.49 MB
- Stars: 49
- Watchers: 1
- Forks: 2
- Open Issues: 14
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
- awesome-nostr-japan - nostr-fetch - A utility library that allows JS/TS apps to effortlessly fetch past events from Nostr relays. by [jiftechnify](https://github.com/jiftechnify) (Libraries)
- awesome-nostr - nostr-fetch - fetch.svg?style=social) - A utility library that allows JS/TS apps to effortlessly fetch past events from Nostr relays. (Libraries / Client reviews and/or comparisons)
README
# 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 `` tags, thanks to [jsDelivr](https://www.jsdelivr.com/).```html
<script type="module">
import { NostrFetcher } from "https://cdn.jsdelivr.net/npm/[email protected]/+esm"
// ...```
### 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`](https://github.com/websockets/ws/tree/master) to `NostrFetcher.init()` as an option.```
npm install ws
``````ts
import { NostrFetcher } from "nostr-fetch";
import WebSocket from "ws";const fetcher = NostrFetcher.init({ webSocketConstructor: WebSocket });
```## Usage
### Basics
```ts
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
```ts
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` :```bash
npm install @nostr-fetch/adapter-nostr-tools
```Then, wrap your relay pool instance with the adapter and pass it to the initializer `NostrFetcher.withCustomPool()`.
```ts
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`](https://github.com/nbd-wtf/nostr-tools) (v1) | `SimplePool` | `@nostr-fetch/adapter-nostr-tools` | `simplePoolAdapter` |
| [`nostr-tools`](https://github.com/nbd-wtf/nostr-tools) (v2) | `SimplePool` | `@nostr-fetch/adapter-nostr-tools-v2` | `simplePoolAdapter` |
| [`nostr-relaypool`](https://github.com/adamritter/nostr-relaypool-ts) | `RelayPool` | `@nostr-fetch/adapter-nostr-relaypool` | `relayPoolAdapter` |
| [`@nostr-dev-kit/ndk`](https://github.com/nostr-dev-kit/ndk) | `NDK` | `@nostr-fetch/adapter-ndk` | `ndkAdapter` |
| [`rx-nostr`](https://github.com/penpenpng/rx-nostr) (v1) | `RxNostr` | `@nostr-fetch/adapter-rx-nostr` | `rxNostrAdapter` |### Cancelling by AbortSignal
```ts
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):
```bash
# 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
```## API
- [class `NostrFetcher`](#class-nostrfetcher)
- Initializers and Finilizers
+ [`NostrFetcher.init`](#nostrfetcherinit)
+ [`NostrFetcher.withCustomPool`](#nostrfetcherwithcustompool)
+ [`NostrFetcher#shutdown`](#nostrfetchershutdown)
- Fetch Methods
+ [`allEventsIterator`](#alleventsiterator)
+ [`fetchAllEvents`](#fetchallevents)
+ [`fetchLatestEvents`](#fetchlatestevents)
+ [`fetchLastEvent`](#fetchlastevent)
+ [`fetchLatestEventsPerKey`](#fetchlatesteventsperkey)
+ [`fetchLastEventPerKey`](#fetchlasteventperkey)
+ [`fetchLatestEventsPerAuthor`](#fetchlatesteventsperauthor)
+ [`fetchLastEventPerAuthor`](#fetchlasteventperauthor)### 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](https://github.com/nbd-wtf/nostr-tools)' `SimplePool`. See [here](#working-with-custom-relay-pool-implementations) 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](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#using-declarations-and-explicit-resource-management) 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.
```ts
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`
```ts
public allEventsIterator(
relayUrls: string[],
filter: FetchFilter,
timeRangeFilter: FetchTimeRangeFilter,
options?: AllEventsIterOptions
): AsyncIterable
```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.
```ts
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`
```ts
public async fetchAllEvents(
relayUrls: string[],
filter: FetchFilter,
timeRangeFilter: FetchTimeRangeFilter,
options?: FetchAllOptions
): Promise
```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`
```ts
public async fetchLatestEvents(
relayUrls: string[],
filter: FetchFilter,
limit: number,
options?: FetchLatestOptions
): Promise
```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`
```ts
public async fetchLastEvent(
relayUrls: string[],
filter: FetchFilter,
options?: FetchLatestOptions
): Promise
```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`
```ts
public fetchLatestEventsPerKey(
keyName: KN,
keysAndRelays: KeysAndRelays,
otherFilter: FetchFilter,
limit: number,
options?: FetchLatestOptions
): AsyncIterable>
```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`: 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: , events: }` pairs.
Each array of events in the result are sorted in "newest to oldest" order.
---
#### `fetchLastEventPerKey`
```ts
public fetchLatestEventsPerKey(
keyName: KN,
keysAndRelays: KeysAndRelays,
otherFilter: FetchFilter,
options?: FetchLatestOptions
): AsyncIterable>
```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`: 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: , event: }` pairs.
`event` in result will be `undefined` if no event matching the filter exists in any relay.
---
#### `fetchLatestEventsPerAuthor`
```ts
public fetchLatestEventsPerAuthor(
authorsAndRelays: AuthorsAndRelays,
otherFilter: Omit,
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`
```ts
public fetchLastEventPerAuthor(
authorsAndRelays: AuthorsAndRelays,
otherFilter: Omit,
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](https://pkgzap.albylabs.com/)