🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@nostr-wot/data

Package Overview
Dependencies
Maintainers
1
Versions
11
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@nostr-wot/data

Pure-function Nostr data layer: profiles, notes, threads, engagement, NIP-65 outbox. Optional SWR cache + React hooks.

latest
Source
npmnpm
Version
0.5.1
Version published
Maintainers
1
Created
Source

@nostr-wot/data

Pure-function Nostr data layer. Profiles, notes, threads, follows, follower lists, engagement (reactions / reposts / zaps), NIP-65 outbox-model relay discovery, and a streaming subscription coalescer. Optional SWR cache + React hooks ship as separate entrypoints.

Three entrypoints

Import pathWhat's in itDepends on
@nostr-wot/dataPure fetchers, parsers, outbox helper, getPool(), sharedCoalescernostr-tools (peer)
@nostr-wot/data/cacheSWR cache wrapping the fetchers; observable primitive; localStorage persistence+ the above
@nostr-wot/data/reactuseProfile, useNote, useThread, <NostrDataProvider>, plus the shared session context (<NostrSessionProvider>, useSession, useKEKSigner, …)+ react (peer)

Use only what you need.

Install

npm i @nostr-wot/data nostr-tools

Vanilla fetchers (no cache, no React)

import {
  fetchProfile,
  fetchNote,
  fetchNotesByAuthor,
  fetchThread,
  fetchFollows,
  fetchEngagement,
  fetchRelayList,
  relaysForAuthor,
  setDefaultRelays,
  setProfileAggregators,
} from "@nostr-wot/data";

setDefaultRelays(["wss://relay.damus.io", "wss://nos.lol"]);
setProfileAggregators(["wss://purplepag.es"]);

const profile = await fetchProfile("hex-pubkey");
// → { displayName, name, picture, banner, about, nip05, lud16, fetchedAt }

const note = await fetchNote("hex-event-id");
const notes = await fetchNotesByAuthor("hex-pubkey", { limit: 50 });
const replies = await fetchThread("hex-event-id");

const follows = await fetchFollows("hex-pubkey");
// → { event, pubkeys, fetchedAt }

const engagement = await fetchEngagement(["id1", "id2"]);
// → Map<id, { reactionCount, repostCount, zapTotalSats }>

// Outbox: union of defaults + the author's NIP-65 write relays
const relays = await relaysForAuthor("hex-pubkey");
const relayList = await fetchRelayList("hex-pubkey");
// → { read: string[], write: string[], event } | null

Streaming variants

streamProfile yields entry updates as relay events arrive (instead of resolving on EOSE).

import { streamProfile } from "@nostr-wot/data";

for await (const entry of streamProfile("hex-pubkey")) {
  // each yield is an updated ProfileEntry — useful for showing partial results
}

Subscription coalescer

sharedCoalescer merges concurrent reads from across your app — DM cache, profile cache, follower lists, etc. — into a single REQ per relay-set within a 50ms window. When the last consumer unsubscribes, the underlying subscription tears down.

import { sharedCoalescer } from "@nostr-wot/data";

// Live subscription
const stop = sharedCoalescer.enqueue({
  filters: [{ kinds: [1], authors: ["hex"], limit: 50 }],
  relays: ["wss://relay.damus.io"],
  onEvent: (e) => { /* ... */ },
  onEose: (relay) => { /* ... */ },
});

// One-shot
const events = await sharedCoalescer.querySync(
  [{ kinds: [10002], authors: ["hex"], limit: 1 }],
  { relays, timeoutMs: 5000 },
);

SWR cache layer

import {
  getProfile,
  getNote,
  getThread,
  getFollows,
  getRelayList,
  fetchEngagementBatch,
  configurePersistence,
  createKeyedObservable,
} from "@nostr-wot/data/cache";

configurePersistence({ namespace: "myapp", ttlMs: 24 * 3600_000 });

const profile = await getProfile("hex-pubkey");
// Cold-loads from localStorage if cached, refreshes in the background
// from relays as newer kind-0 events arrive.

The cache exposes its own primitive — createKeyedObservable<K, V>() — used internally by every cached fetcher and by @nostr-wot/dm/cache. Useful if you're building your own SWR-style stream:

const obs = createKeyedObservable<string, MyValue>();
obs.set("key", { ... });
obs.subscribe("key", (v) => { /* re-render */ });
const slot = obs.get("key");

React hooks

import {
  NostrDataProvider,
  useProfile,
  useNote,
  useThread,
  useEngagement,
  useFollows,
  useRelayList,
} from "@nostr-wot/data/react";

<NostrDataProvider
  relays={["wss://relay.damus.io", "wss://nos.lol"]}
  profileAggregators={["wss://purplepag.es"]}
  persistence={{ namespace: "myapp", ttlMs: 86400_000 }}
>
  <App />
</NostrDataProvider>;

function ProfileCard({ pubkey }: { pubkey: string }) {
  const profile = useProfile(pubkey); // SWR
  if (!profile) return <Skeleton />;
  return <h1>{profile.displayName ?? profile.name}</h1>;
}

<NostrDataProvider> configures defaults (relays, aggregators, persistence) on mount. If you're using @nostr-wot/wot or nostr-wot-sdk, prefer <NostrSdkProvider> instead — it wraps <NostrDataProvider> and adds opt-in WoT context.

Session context

@nostr-wot/data/react also hosts the shared session context — the single mount point for the active NostrSigner and the user's pubkey. It lives here (not in @nostr-wot/ui) so non-UI packages — DM hooks, blossom uploads, wallet/zap hooks — can read the signer from context without depending on the React UI package.

import {
  NostrSessionProvider,
  useSession,
  useSigner,
  usePubkey,
  useLogin,
  useLogout,
  useKEKSigner,
} from "@nostr-wot/data/react";

<NostrSessionProvider
  initialSigner={someSigner /* optionale.g. constructed at boot */}
  onChange={({ signer, pubkey }) => /* mirror to your state */}
  onLogout={async () => { /* clear app caches */ }}
>
  <App />
</NostrSessionProvider>;
HookReturns
useSession(){ signer, pubkey, isLoading, error, setSigner, logout }
useSigner()The active NostrSigner or null
usePubkey()The active hex pubkey or null
useLogin()(signer) => Promise<void> callback — set the active signer
useLogout()() => Promise<void> callback — drops the signer + runs onLogout
useKEKSigner()A narrow KEKSigner (NIP-44 encrypt + decrypt + pubkey) when the active signer supports NIP-44, else null

useKEKSigner exists because some operations — DM cache-key derivation, wallet local-store encryption — need NIP-44 specifically and must skip signers that don't expose it (e.g. NIP-46 bunkers with restricted perms). The hook returns null instead of throwing, so consumers can branch on capability:

const kek = useKEKSigner();
if (!kek) return <p>Your signer doesn't support NIP-44 — encrypted-at-rest cache disabled.</p>;
const cacheKey = await getOrCreateCacheKey(kek.pubkey, kek);

@nostr-wot/ui mounts and consumes this same context for its login flows, and re-exports the hooks for convenience. If you're only using @nostr-wot/data (no UI), construct a signer yourself (from @nostr-wot/signers) and call useLogin()(signer) to attach it.

Outbox model (NIP-65)

Every fetcher routes through the outbox helper by default. When you ask for an author's content, the SDK resolves their kind-10002 (NIP-65) relay list and queries the union of their declared write relays + your defaults. This is the only reliable way to find content that isn't pinned to popular aggregators.

Override globally:

setDefaultRelays(["wss://relay.damus.io", "wss://nos.lol"]);
setProfileAggregators(["wss://purplepag.es"]);

Or per-call (most fetchers accept an optional relays override):

await fetchProfile(pubkey, ["wss://my-private-relay"]);

Pool sharing

@nostr-wot/data owns a single SimplePool instance, accessible via getPool(). If you have your own pool (e.g. for binary-frame coercion or NDK interop), wire it once at startup:

import { setPool } from "@nostr-wot/data";

const pool = new SimplePool({ websocketImplementation: MyWebSocket });
setPool(pool);

Other @nostr-wot/* packages reuse this same pool — DM subscriptions, blossom uploads, WoT lookups, all share connections.

License

MIT

Keywords

nostr

FAQs

Package last updated on 10 May 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts