soapbox-pub/ditto · Archived

nostr-queries

Query Nostr events efficiently with useNostr + TanStack Query. Covers the standard useQuery pattern, combining related kinds into a single request to avoid rate limiting, and validating events with required tags or strict schemas.

First seen Jun 26, 2026

Installation

$ npx skills add soapbox-pub/ditto --skill nostr-queries

Stronger alternatives

This repository is archived — consider an actively maintained alternative.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from soapbox-pub/ditto.

npx skills add soapbox-pub/ditto

Browse all from soapbox-pub/ditto

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 61
License LICENSE
Default branch main
Status Archived

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 4,300 B
  • docs SUMMARY.md 251 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 1 installs

SKILL.md

Querying Nostr Events

Use this skill when building a hook that fetches Nostr events. Covers the standard useNostr + useQuery pattern, efficient query design (combining kinds to avoid relay round-trips), and event validation for kinds with required tags.

The Standard Pattern

Combine useNostr with TanStack Query in a custom hook. Pass the abort signal from c.signal into nostr.query so cancelled queries free relay resources:

import { useNostr } from '@nostrify/react';
import { useQuery } from '@tanstack/react-query';

function usePosts() {
  const { nostr } = useNostr();

  return useQuery({
    queryKey: ['posts'],
    queryFn: async (c) => {
      const events = await nostr.query(
        [{ kinds: [1], limit: 20 }],
        { signal: c.signal },
      );
      return events;
    },
  });
}

Transform events into a domain model inside the queryFn if needed — callers should rarely see raw NostrEvents. Multiple calls to nostr.query() inside one queryFn are fine for compound queries that can't be expressed as a single filter.

Efficient Query Design

Always minimize the number of separate round-trips to relays. Each query consumes relay capacity and may count against rate limits.

✅ Efficient — single query with multiple kinds:

// Query repost variants in one request
const events = await nostr.query([{
  kinds: [1, 6, 16],
  '#e': [eventId],
  limit: 150,
}]);

// Separate by kind in JavaScript
const notes = events.filter((e) => e.kind === 1);
const reposts = events.filter((e) => e.kind === 6);
const genericReposts = events.filter((e) => e.kind === 16);

❌ Inefficient — three separate round-trips:

const [notes, reposts, genericReposts] = await Promise.all([
  nostr.query([{ kinds: [1], '#e': [eventId] }]),
  nostr.query([{ kinds: [6], '#e': [eventId] }]),
  nostr.query([{ kinds: [16], '#e': [eventId] }]),
]);

Optimization rules

  1. Combine kinds into one filter: kinds: [1, 6, 16].
  2. Use multiple filter objects in a single nostr.query() call when different tag filters are needed simultaneously.
  3. Raise the limit when combining kinds so you still receive enough of each type.
  4. Split by kind in JavaScript, not by making separate requests.
  5. Respect relay capacity — heavy parallel queries can trigger rate limits even when each individually would be fine.

Event Validation

For kinds with required tags or strict schemas (most custom kinds, anything beyond kind 1), filter query results through a validator before returning them. Loose kinds (kind 1 text notes) rarely need validation — all tags are optional and content is freeform.

import type { NostrEvent } from '@nostrify/nostrify';

// Example validator for NIP-52 calendar events
function validateCalendarEvent(event: NostrEvent): boolean {
  if (![31922, 31923].includes(event.kind)) return false;

  const d = event.tags.find(([n]) => n === 'd')?.[1];
  const title = event.tags.find(([n]) => n === 'title')?.[1];
  const start = event.tags.find(([n]) => n === 'start')?.[1];
  if (!d || !title || !start) return false;

  // Date-based events require YYYY-MM-DD
  if (event.kind === 31922 && !/^\d{4}-\d{2}-\d{2}$/.test(start)) return false;

  // Time-based events require a unix timestamp
  if (event.kind === 31923) {
    const ts = parseInt(start);
    if (isNaN(ts) || ts <= 0) return false;
  }

  return true;
}

function useCalendarEvents() {
  const { nostr } = useNostr();
  return useQuery({
    queryKey: ['calendar-events'],
    queryFn: async (c) => {
      const events = await nostr.query(
        [{ kinds: [31922, 31923], limit: 20 }],
        { signal: c.signal },
      );
      return events.filter(validateCalendarEvent);
    },
  });
}

Validation is a correctness layer, not a security layer. For trust-sensitive queries (admin actions, addressable events, moderator approvals), also constrain authors — see the nostr-security skill.