{"id":13763478,"url":"https://github.com/jiftechnify/nostr-fetch","last_synced_at":"2026-01-12T10:37:00.439Z","repository":{"id":131579759,"uuid":"611663157","full_name":"jiftechnify/nostr-fetch","owner":"jiftechnify","description":"A utility library that allows JS/TS apps to effortlessly fetch past events from Nostr relays.","archived":false,"fork":false,"pushed_at":"2025-01-06T05:01:26.000Z","size":1860,"stargazers_count":51,"open_issues_count":16,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-26T07:12:38.924Z","etag":null,"topics":["javascript","nostr","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jiftechnify.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-03-09T09:38:43.000Z","updated_at":"2025-09-01T06:49:01.000Z","dependencies_parsed_at":"2024-02-04T06:19:24.137Z","dependency_job_id":"f5928283-3380-4e5c-9bb5-06d473f46329","html_url":"https://github.com/jiftechnify/nostr-fetch","commit_stats":{"total_commits":475,"total_committers":3,"mean_commits":"158.33333333333334","dds":"0.21894736842105267","last_synced_commit":"7fb94b89783580997902bd76aee50a8295615c95"},"previous_names":[],"tags_count":35,"template":false,"template_full_name":null,"purl":"pkg:github/jiftechnify/nostr-fetch","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jiftechnify%2Fnostr-fetch","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jiftechnify%2Fnostr-fetch/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jiftechnify%2Fnostr-fetch/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jiftechnify%2Fnostr-fetch/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jiftechnify","download_url":"https://codeload.github.com/jiftechnify/nostr-fetch/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jiftechnify%2Fnostr-fetch/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28338700,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-12T06:09:07.588Z","status":"ssl_error","status_checked_at":"2026-01-12T06:05:18.301Z","response_time":98,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["javascript","nostr","typescript"],"created_at":"2024-08-03T15:00:48.228Z","updated_at":"2026-01-12T10:37:00.417Z","avatar_url":"https://github.com/jiftechnify.png","language":"TypeScript","funding_links":[],"categories":["Libraries"],"sub_categories":["Client reviews and/or comparisons"],"readme":"# nostr-fetch\nA utility library that allows JS/TS apps to effortlessly fetch *past* events from Nostr relays.\n\n## Installation\n\n### for npm Project\n\n```\nnpm install nostr-fetch\n```\n\n### for Deno Project\n\n```\ndeno add npm:nostr-fetch\n```\n\n### for Browser Apps, without Bundlers\nYou can also use nostr-fetch in your HTML via `\u003cscript\u003e` tags, thanks to [jsDelivr](https://www.jsdelivr.com/).\n\n```html\n\u003cscript type=\"module\"\u003e\n  import { NostrFetcher } from \"https://cdn.jsdelivr.net/npm/nostr-fetch@0.16.0/+esm\"\n  // ...\n\u003c/script\u003e\n```\n\n### Note for Users on Node.js \u003c v22\nNode.js \u003c v22 doesn't have native WebSocket implementation. \nOn 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.\n\n```\nnpm install ws\n```\n\n```ts\nimport { NostrFetcher } from \"nostr-fetch\";\nimport WebSocket from \"ws\";\n\nconst fetcher = NostrFetcher.init({ webSocketConstructor: WebSocket });\n```\n\n## Usage\n\n### Basics\n\n```ts\nimport { NostrFetcher } from \"nostr-fetch\";\n\nconst nHoursAgo = (hrs: number): number =\u003e\n  Math.floor((Date.now() - hrs * 60 * 60 * 1000) / 1000);\n\nconst fetcher = NostrFetcher.init();\nconst relayUrls = [/* relay URLs */];\n\n// fetches all text events since 24 hr ago in streaming manner\nconst postIter = fetcher.allEventsIterator(\n    relayUrls, \n    /* filter (kinds, authors, ids, tags) */\n    { kinds: [ 1 ] },\n    /* time range filter (since, until) */\n    { since: nHoursAgo(24) },\n    /* fetch options (optional) */\n    { skipFilterMatching: true }\n);\nfor await (const ev of postIter) {\n    console.log(ev.content);\n}\n\n// fetches all text events since 24 hr ago, as a single array\nconst allPosts = await fetcher.fetchAllEvents(\n    relayUrls,\n    /* filter */\n    { kinds: [ 1 ] },\n    /* time range filter */\n    { since: nHoursAgo(24) },\n    /* fetch options (optional) */\n    { sort: true }\n)\n```\n\n### Various Fetch Methods\n\n```ts\nimport { NostrFetcher } from \"nostr-fetch\";\n\nconst fetcher = NostrFetcher.init();\nconst relayUrls = [/* relay URLs */];\n\n// fetches latest 100 text posts\n// internally: \n// 1. fetch latest 100 events from each relay\n// 2. merge lists of events\n// 3. take latest 100 events\nconst latestPosts: NostrEvent[] = await fetcher.fetchLatestEvents(\n    relayUrls,\n    /* filter */\n    { kinds: [ 1 ] },\n    /* number of events to fetch */\n    100,\n);\n\n// fetches the last metadata event published by pubkey \"deadbeef...\"\n// internally:\n// 1. fetch the last event from each relay\n// 2. take the latest one\nconst lastMetadata: NostrEvent | undefined = await fetcher.fetchLastEvent(\n    relayUrls,\n    /* filter */\n    { kinds: [ 0 ], authors: [ \"deadbeef...\" ] },\n);\n\n// fetches latest 10 text posts from each author in `authors`\nconst postsPerAuthor = fetcher.fetchLatestEventsPerAuthor(\n    /* authors and relay set */\n    // you can also pass a `Map` which has mappings from authors (pubkey) to reley sets,\n    // to specify a relay set for each author\n    { \n        authors: [\"deadbeef...\", \"abcdef01...\", ...],\n        relayUrls,\n    },\n    /* filter */\n    { kinds: [ 1 ] },\n    /* number of events to fetch for each author */\n    10,\n);\nfor await (const { author, events } of postsPerAuthor) {\n    console.log(`posts from ${author}:`);\n    for (const ev of events) {\n        console.log(ev.content);\n    }\n}\n\n// fetches the last metadata event from each author in `authors`\nconst metadataPerAuthor = fetcher.fetchLastEventPerAuthor(\n    /* authors and relay set */\n    // you can also pass a `Map` which has mappings from authors (pubkey) to reley sets,\n    // to specify a relay set for each author\n    {\n        authors: [\"deadbeef...\", \"abcdef01...\", ...],\n        relayUrls,\n    }\n    /* filter */\n    { kinds: [ 0 ] },\n);\nfor await (const { author, event } of metadataPerAuthor ) {\n    console.log(`${author}: ${event?.content ?? \"not found\"}`);\n}\n```\n\n### Working with custom relay pool implementations\n\nFirst, install the adapter package for the relay pool implementation you want to use.\nFor example, if you want to use nostr-fetch with nostr-tools' `SimplePool` :\n\n```bash\nnpm install @nostr-fetch/adapter-nostr-tools\n```\n\nThen, wrap your relay pool instance with the adapter and pass it to the initializer `NostrFetcher.withCustomPool()`.\n```ts\nimport { NostrFetcher } from \"nostr-fetch\";\nimport { simplePoolAdapter } from \"@nostr-fetch/adapter-nostr-tools\";\nimport { SimplePool } from \"nostr-tools\";\n\nconst pool = new SimplePool();\n\n// wrap SimplePool with simplePoolAdapter to make it interoperable with nostr-fetch\nconst fetcher = NostrFetcher.withCustomPool(simplePoolAdapter(pool));\n\n// now, you can use any fetch methods described above!\n```\n\n#### Table of Available Adapters\n\n| Package                                                               | Relay Pool Impl. | Adapter Package                        | Adapter             |\n|-----------------------------------------------------------------------|------------------|----------------------------------------|---------------------|\n| [`nostr-tools`](https://github.com/nbd-wtf/nostr-tools) (v1)          | `SimplePool`     | `@nostr-fetch/adapter-nostr-tools`     | `simplePoolAdapter` |\n| [`nostr-tools`](https://github.com/nbd-wtf/nostr-tools) (v2)          | `SimplePool`     | `@nostr-fetch/adapter-nostr-tools-v2`  | `simplePoolAdapter` |\n| [`nostr-relaypool`](https://github.com/adamritter/nostr-relaypool-ts) | `RelayPool`      | `@nostr-fetch/adapter-nostr-relaypool` | `relayPoolAdapter`  |\n| [`@nostr-dev-kit/ndk`](https://github.com/nostr-dev-kit/ndk)          | `NDK`            | `@nostr-fetch/adapter-ndk`             | `ndkAdapter`        |\n| [`rx-nostr`](https://github.com/penpenpng/rx-nostr) (v1)              | `RxNostr`        | `@nostr-fetch/adapter-rx-nostr`        | `rxNostrAdapter`    |\n\n### Cancelling by AbortSignal\n\n```ts\nimport { NostrFecher } from \"nostr-fetch\"\n\nconst fetcher = NostrFetcher.init();\nconst relayUrls = [/* relay URLs */];\n\nconst evIter = fetcher.allEventsIterator(\n    relayUrls,\n    {/* filter */},\n    {/* time range */},\n    /* pass an `AbortSignal` here to enable cancellation! */\n    { signal: AbortSignal.timeout(1000) },\n);\n\nfor await (const ev of evIter) {\n    // ...\n}\n```\n\n## Examples\nYou can find example codes under `packages/examples` directory.\n\nTo run examples, follow the steps (using `npm` for example):\n\n```bash\n# first time only: install dependencies \u0026 build subpackages\nnpm install \u0026\u0026 npm run build\n\n\n# then, execute example\n# the command executes packages/examples/src/fetchAll.ts\nnpm run example fetchAll\n\n# some examples takes a hex pubkey as an argument\nnpm run example fetchLastPerAuthor \u003cyour hex pubkey\u003e\n```\n\n## API\n\n- [class `NostrFetcher`](#class-nostrfetcher)\n- Initializers and Finilizers\n    + [`NostrFetcher.init`](#nostrfetcherinit)\n    + [`NostrFetcher.withCustomPool`](#nostrfetcherwithcustompool)\n    + [`NostrFetcher#shutdown`](#nostrfetchershutdown)\n- Fetch Methods\n    + [`allEventsIterator`](#alleventsiterator)\n    + [`fetchAllEvents`](#fetchallevents)\n    + [`fetchLatestEvents`](#fetchlatestevents)\n    + [`fetchLastEvent`](#fetchlastevent)\n    + [`fetchLatestEventsPerKey`](#fetchlatesteventsperkey)\n    + [`fetchLastEventPerKey`](#fetchlasteventperkey)\n    + [`fetchLatestEventsPerAuthor`](#fetchlatesteventsperauthor)\n    + [`fetchLastEventPerAuthor`](#fetchlasteventperauthor)\n\n\n### class `NostrFetcher`\n\nThe entry point of Nostr events fetching. \n\nIt manages connections to Nostr relays under the hood. It is recommended to reuse single `NostrFetcher` instance in entire app.\n\nYou should instantiate it with following initializers instead of the constructor.\n\n---\n\n### Initializers and Finalizers\n\n#### `NostrFetcher.init`\n\nInitializes a `NostrFetcher` instance based on the default relay pool implementation.\n\n\n#### `NostrFetcher.withCustomPool`\n\nInitializes a `NostrFetcher` instance based on a custom relay pool implementation passed as an argument.\n\nThis 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.\n\n#### `NostrFetcher#shutdown`\n\nCleans up the internal relay pool.\n\nIf you use a fetcher instance initialized via `NostrFetcher.init`, calling this method closes connections to all the connected relays.\n\nYou 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.\n\n```ts\nasync function withFinally() {\n    const fetcher = NostrFetcher.init();\n    try {\n        // do some work with the fetcher...\n    } finally {\n        fetcher.shutdown();\n    }\n}\n\nasync function withUsing() {\n    using fetcher = NostrFetcher.init();\n    // do some work with the fetcher...\n\n    // the fetcher will be automatically shutdown here!\n}\n```\n\n---\n\n### Fetch Methods\n\nAll methods are instance methods of `NostrFetcher`.\n\n#### `allEventsIterator`\n\n```ts\npublic allEventsIterator(\n    relayUrls: string[],\n    filter: FetchFilter,\n    timeRangeFilter: FetchTimeRangeFilter,\n    options?: AllEventsIterOptions\n): AsyncIterable\u003cNostrEvent\u003e\n```\n\nReturns an async iterable of all events matching the filter from Nostr relays specified by the array of URLs.\n\nYou can iterate over events using for-await-of loop.\n\n```ts\nconst fetcher = NostrFetcher.init();\nconst events = fetcher.allEventsIterator([/* relays */], {/* filter */}, {/* time range */});\nfor await (const ev of events) {\n    // process events\n}\n```\n\nSpecifying `enableBackpressure: true` in `options` enables \"backpressure mode\", where the fetcher is backpressured by the consumer of the iterator.\n\n\u003e **Note**\n\u003e\n\u003e There are no guarantees about the order of returned events. Especially, events are not necessarily ordered in \"newest to oldest\" order.\n\n---\n\n#### `fetchAllEvents`\n\n```ts\npublic async fetchAllEvents(\n    relayUrls: string[],\n    filter: FetchFilter,\n    timeRangeFilter: FetchTimeRangeFilter,\n    options?: FetchAllOptions\n): Promise\u003cNostrEvent[]\u003e\n```\n\nFetches all events matching the filter from Nostr relays specified by the array of URLs, and collect them into an array.\n\nIf `sort: true` is specified in `options`, events in the resulting array will be sorted in \"newest to oldest\" order.\n\n\u003e **Note**\n\u003e\n\u003e There are no guarantees about the order of returned events if `sort` options is not specified.\n\n---\n\n#### `fetchLatestEvents`\n\n```ts\npublic async fetchLatestEvents(\n    relayUrls: string[],\n    filter: FetchFilter,\n    limit: number,\n    options?: FetchLatestOptions\n): Promise\u003cNostrEvent[]\u003e\n```\n\nFetches latest up to `limit` events matching the filter from Nostr relays specified by the array of URLs. \n\nEvents in the result will be sorted in \"newest to oldest\" order.\n\n---\n\n#### `fetchLastEvent`\n\n```ts\npublic async fetchLastEvent(\n    relayUrls: string[],\n    filter: FetchFilter,\n    options?: FetchLatestOptions\n): Promise\u003cNostrEvent | undefined\u003e\n```\n\nFetches the last event matching the filter from Nostr relays specified by the array of URLs.\n\nReturns `undefined` if no event matching the filter exists in any relay.\n\n---\n\n#### `fetchLatestEventsPerKey`\n\n```ts\npublic fetchLatestEventsPerKey\u003cKN extends FetchFilterKeyName\u003e(\n    keyName: KN,\n    keysAndRelays: KeysAndRelays\u003cKN\u003e,\n    otherFilter: FetchFilter,\n    limit: number,\n    options?: FetchLatestOptions\n): AsyncIterable\u003cNostrEventListWithKey\u003cKN\u003e\u003e\n```\n\nFetches latest up to `limit` events **for each key specified by `keyName` and `keysAndRelays`**.\n\n`keysAndRelays` can be either of two types:\n\n- `{ keys: K[], relayUrls: string[] }`: The fetcher will use the same relay set (`relayUrls`) for all `keys` to fetch events.\n- `Map\u003cK, string[]\u003e`: 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.\n\n\u003e **Note**\n\u003e\n\u003e The type `K` is `number` if `keyName` is `\"kinds\"`. Otherwise, `K` is `string`.\n\nResult is an async iterable of `{ key: \u003ckey of events\u003e, events: \u003cevents which have that key\u003e }` pairs.\n\nEach array of events in the result are sorted in \"newest to oldest\" order.\n\n---\n\n#### `fetchLastEventPerKey`\n\n```ts\npublic fetchLatestEventsPerKey\u003cKN extends FetchFilterKeyName\u003e(\n    keyName: KN,\n    keysAndRelays: KeysAndRelays\u003cKN\u003e,\n    otherFilter: FetchFilter,\n    options?: FetchLatestOptions\n): AsyncIterable\u003cNostrEventWithKey\u003cKN\u003e\u003e\n```\n\nFetches the last event **for each key specified by `keysAndRelays`**.\n\n`keysAndRelays` can be either of two types:\n\n- `{ keys: K[], relayUrls: string[] }`: The fetcher will use the same relay set (`relayUrls`) for all `keys` to fetch events.\n- `Map\u003cK, string[]\u003e`: 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.\n\n\u003e **Note**\n\u003e\n\u003e The type `K` is `number` if `keyName` is `\"kinds\"`. Otherwise, `K` is `string`.\n\nResult is an async iterable of `{ key: \u003ckey of events\u003e, event: \u003cthe latest event which have that key\u003e }` pairs.\n\n`event` in result will be `undefined` if no event matching the filter exists in any relay.\n\n---\n\n#### `fetchLatestEventsPerAuthor`\n\n```ts\npublic fetchLatestEventsPerAuthor(\n    authorsAndRelays: AuthorsAndRelays,\n    otherFilter: Omit\u003cFetchFilter, \"authors\"\u003e,\n    limit: number,\n    options: FetchLatestOptions = {}\n): AsyncIterable\u003c{ author: string; events: NostrEvent[] }\u003e\n```\nFetches latest up to `limit` events **for each author specified by `authorsAndRelays`**.\n\nIt is just a wrapper of `fetchLatestEventsPerKey` specialized to `\"authors\"` key.\n\n---\n\n#### `fetchLastEventPerAuthor`\n\n```ts\npublic fetchLastEventPerAuthor(\n    authorsAndRelays: AuthorsAndRelays,\n    otherFilter: Omit\u003cFetchFilter, \"authors\"\u003e,\n    options: FetchLatestOptions = {}\n): AsyncIterable\u003c{ author: string; event: NostrEvent | undefined }\u003e\n```\n\nFetches the last event **for each author specified by `authorsAndRelays`**.\n\nIt is just a wrapper of `fetchLastEventPerKey` specialized to `\"authors\"` key.\n\n\n## Support me!\nYou can support this project by:\n\n- ⭐ Starring the repo\n- ⚡️ Sending some sats to my lightning address: jiftechnify@eclair.c-stellar.net\n- 🐝 Sending funds via [PkgZap](https://pkgzap.albylabs.com/)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjiftechnify%2Fnostr-fetch","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjiftechnify%2Fnostr-fetch","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjiftechnify%2Fnostr-fetch/lists"}