nostr-wot-sdk 0.6.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,28 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.7.0] - 2026-02-23
9
+
10
+ ### Added
11
+
12
+ - **Relay utilities subpackage** (`nostr-wot-sdk/relay`) — Reusable relay infrastructure extracted from nostr-wot-feed
13
+ - `QueryBatcher` — Debounced query batching with progressive relay queries and automatic filter merging
14
+ - `RelayPool` — Pool lifecycle management, subscriptions, publishing, status tracking, and NIP-65 relay list fetching
15
+ - `RelayStats` — Per-relay performance tracking (latency, success rate, exponential backoff) with optional persistence adapter
16
+ - All classes are pool-agnostic via a `PoolLike` interface that duck-types against `SimplePool` from nostr-tools
17
+ - All timing constants are configurable via options with sensible defaults
18
+
19
+ - **Relay React integration** (`nostr-wot-sdk/relay/react`)
20
+ - `RelayProvider` — Context provider that manages RelayPool, QueryBatcher, and RelayStats lifecycle
21
+ - `useRelayPool()` — Access the RelayPool instance
22
+ - `useQueryBatcher()` — Access the QueryBatcher instance
23
+ - `useRelayStats()` — Access the RelayStats instance
24
+ - `useRelayStatuses()` — Reactive relay connection statuses and connected count
25
+
26
+ - `nostr-tools` as optional peer dependency (`>=2.0.0`)
27
+
28
+ - New types: `PoolLike`, `SubCloser`, `NostrEvent`, `NostrFilter`, `QueryBatcherOptions`, `QueryOptions`, `RelayPoolOptions`, `RelayStatus`, `RelayStatsOptions`, `RelayMetrics`, `RelayStatsData`, `RelayStatsPersistence`
29
+
8
30
  ## [0.6.2] - 2026-02-22
9
31
 
10
32
  ### Fixed
@@ -270,6 +292,7 @@ function Profile({ pubkey }) {
270
292
  - TypeScript support with full type definitions
271
293
  - Error classes: `WoTError`, `NetworkError`, `NotFoundError`, `TimeoutError`, `ValidationError`
272
294
 
295
+ [0.7.0]: https://github.com/nostr-wot/nostr-wot-sdk/compare/v0.6.2...v0.7.0
273
296
  [0.6.2]: https://github.com/nostr-wot/nostr-wot-sdk/compare/v0.6.1...v0.6.2
274
297
  [0.6.1]: https://github.com/nostr-wot/nostr-wot-sdk/compare/v0.6.0...v0.6.1
275
298
  [0.6.0]: https://github.com/nostr-wot/nostr-wot-sdk/compare/v0.5.3...v0.6.0
package/README.md CHANGED
@@ -59,6 +59,7 @@ const hops = await wot.getDistance('def456...');
59
59
  - **Cross-Site Trust** — Extension provides same WoT data on all websites
60
60
  - **Offline Support** — Extension caches data locally for offline queries
61
61
  - **Batch Queries** — Check multiple pubkeys efficiently
62
+ - **Relay Utilities** — Reusable relay pool, query batching, and stats tracking
62
63
  - **TypeScript** — Full type definitions included
63
64
 
64
65
  ## API Reference
@@ -443,6 +444,202 @@ ext.isChecked() // Check complete
443
444
  ext.refresh() // Function to re-check extension availability
444
445
  ```
445
446
 
447
+ ## Relay Utilities
448
+
449
+ The SDK includes a standalone relay subpackage for managing Nostr relay connections, batching queries, and tracking relay performance. These utilities are pool-agnostic and work with any `PoolLike` implementation (e.g. `SimplePool` from nostr-tools).
450
+
451
+ ```bash
452
+ npm install nostr-wot-sdk nostr-tools
453
+ ```
454
+
455
+ ### Basic Usage
456
+
457
+ ```javascript
458
+ import { QueryBatcher, RelayPool, RelayStats } from 'nostr-wot-sdk/relay';
459
+ import { SimplePool } from 'nostr-tools';
460
+
461
+ // Optional: track relay performance
462
+ const stats = new RelayStats();
463
+ await stats.init(); // works in-memory, or pass a persistence adapter
464
+
465
+ // Create a relay pool
466
+ const pool = new RelayPool({
467
+ urls: ['wss://relay.damus.io', 'wss://nos.lol'],
468
+ prioritizeUrls: (urls) => stats.getPrioritizedUrls(urls),
469
+ onStatusChange: (statuses) => console.log('Relay statuses:', statuses),
470
+ });
471
+
472
+ // Initialize with SimplePool
473
+ pool.ensurePool(() => new SimplePool());
474
+
475
+ // Query with automatic batching + progressive results
476
+ const events = await pool.query(
477
+ { kinds: [1], limit: 50 },
478
+ { onUpdate: (partial) => renderNotes(partial) }
479
+ );
480
+
481
+ // Subscribe to live events
482
+ const sub = pool.subscribe({ kinds: [1], since: Math.floor(Date.now() / 1000) }, {
483
+ onEvent: (event) => console.log('New note:', event.content),
484
+ onEose: () => console.log('Caught up'),
485
+ });
486
+
487
+ // Clean up
488
+ sub.close();
489
+ pool.destroy();
490
+ stats.destroy();
491
+ ```
492
+
493
+ ### QueryBatcher
494
+
495
+ Debounces and merges concurrent relay queries for efficiency. Queries made within the debounce window are batched together, compatible filters are merged, and results stream progressively.
496
+
497
+ ```javascript
498
+ import { QueryBatcher } from 'nostr-wot-sdk/relay';
499
+ import { SimplePool } from 'nostr-tools';
500
+
501
+ const batcher = new QueryBatcher(new SimplePool(), {
502
+ debounceMs: 100, // batch window (default: 100)
503
+ collectionWindowMs: 200, // wait after first event (default: 200)
504
+ maxWaitMs: 5000, // hard timeout (default: 5000)
505
+ });
506
+
507
+ // These concurrent queries get merged into fewer relay requests
508
+ const [profiles, contacts] = await Promise.all([
509
+ batcher.query(['wss://relay.damus.io'], { kinds: [0], authors: pubkeys }),
510
+ batcher.query(['wss://relay.damus.io'], { kinds: [3], authors: pubkeys }),
511
+ ]);
512
+
513
+ // For user-initiated actions, bypass the debounce
514
+ const notes = await batcher.queryImmediate(urls, { kinds: [1], limit: 20 });
515
+
516
+ batcher.destroy();
517
+ ```
518
+
519
+ ### RelayStats
520
+
521
+ Tracks per-relay latency, success rate, and applies exponential backoff to failing relays.
522
+
523
+ ```javascript
524
+ import { RelayStats } from 'nostr-wot-sdk/relay';
525
+
526
+ const stats = new RelayStats({ maxBackoffMs: 30000 });
527
+
528
+ // Optional: load persisted stats (e.g. from IndexedDB)
529
+ await stats.init({
530
+ load: async () => db.getAll('relayStats'),
531
+ save: async (data) => db.putAll('relayStats', data),
532
+ });
533
+
534
+ // Record relay performance
535
+ stats.recordSuccess('wss://relay.damus.io', 150); // 150ms latency
536
+ stats.recordFailure('wss://slow.relay.com', 'timeout');
537
+
538
+ // Get URLs sorted by reliability + speed
539
+ const prioritized = stats.getPrioritizedUrls([
540
+ 'wss://relay.damus.io',
541
+ 'wss://slow.relay.com',
542
+ 'wss://nos.lol',
543
+ ]);
544
+
545
+ // Check backoff status
546
+ stats.isBackedOff('wss://slow.relay.com'); // true
547
+
548
+ stats.destroy();
549
+ ```
550
+
551
+ ### RelayPool
552
+
553
+ Manages pool lifecycle, subscriptions, publishing, and NIP-65 relay list fetching.
554
+
555
+ ```javascript
556
+ import { RelayPool } from 'nostr-wot-sdk/relay';
557
+ import { SimplePool } from 'nostr-tools';
558
+
559
+ const pool = new RelayPool({
560
+ urls: ['wss://relay.damus.io', 'wss://nos.lol'],
561
+ authorChunkSize: 150, // chunk large author lists
562
+ onRelaysChanged: (urls) => saveToSettings(urls),
563
+ });
564
+
565
+ pool.ensurePool(() => new SimplePool());
566
+
567
+ // Subscribe to notes from followed authors (auto-chunks large lists)
568
+ const sub = pool.subscribeAuthors(followedPubkeys, { kinds: [1], since, limit: 200 },
569
+ (event) => addToFeed(event),
570
+ () => console.log('Caught up')
571
+ );
572
+
573
+ // Publish events
574
+ await pool.publish(signedEvent);
575
+
576
+ // Fetch a user's NIP-65 relay list
577
+ const userRelays = await pool.fetchUserRelays(pubkey);
578
+
579
+ // Manage relays
580
+ pool.addRelay('wss://new.relay.com');
581
+ pool.removeRelay('wss://old.relay.com');
582
+
583
+ pool.destroy();
584
+ ```
585
+
586
+ ### React Integration
587
+
588
+ ```javascript
589
+ import { RelayProvider, useRelayPool, useRelayStatuses } from 'nostr-wot-sdk/relay/react';
590
+ import { SimplePool } from 'nostr-tools';
591
+
592
+ function App() {
593
+ return (
594
+ <RelayProvider
595
+ urls={['wss://relay.damus.io', 'wss://nos.lol']}
596
+ createPool={() => new SimplePool()}
597
+ enableStats={true}
598
+ >
599
+ <Feed />
600
+ </RelayProvider>
601
+ );
602
+ }
603
+
604
+ function Feed() {
605
+ const pool = useRelayPool();
606
+ const { statuses, connectedCount } = useRelayStatuses();
607
+
608
+ useEffect(() => {
609
+ pool.query({ kinds: [1], limit: 30 }).then(setNotes);
610
+ }, [pool]);
611
+
612
+ return (
613
+ <div>
614
+ <span>{connectedCount} relays connected</span>
615
+ {/* render notes */}
616
+ </div>
617
+ );
618
+ }
619
+ ```
620
+
621
+ #### Provider Props
622
+
623
+ | Prop | Type | Default | Description |
624
+ |------|------|---------|-------------|
625
+ | `urls` | `string[]` | required | Relay WebSocket URLs |
626
+ | `createPool` | `() => PoolLike` | required | Factory to create pool instance |
627
+ | `poolOptions` | `Partial<RelayPoolOptions>` | — | Options for RelayPool |
628
+ | `batcherOptions` | `QueryBatcherOptions` | — | Options for QueryBatcher |
629
+ | `statsPersistence` | `RelayStatsPersistence` | — | Persistence adapter for stats |
630
+ | `statsOptions` | `RelayStatsOptions` | — | Options for RelayStats |
631
+ | `enableStats` | `boolean` | `true` | Enable relay performance tracking |
632
+
633
+ #### Available Hooks
634
+
635
+ | Hook | Returns | Description |
636
+ |------|---------|-------------|
637
+ | `useRelayPool()` | `RelayPool` | Access the RelayPool instance |
638
+ | `useQueryBatcher()` | `QueryBatcher` | Access the QueryBatcher instance |
639
+ | `useRelayStats()` | `RelayStats \| null` | Access RelayStats (null if disabled) |
640
+ | `useRelayStatuses()` | `{ statuses, connectedCount }` | Reactive connection statuses |
641
+ | `useRelayContext()` | `RelayContextValue` | Full context with all instances |
642
+
446
643
  ## TypeScript
447
644
 
448
645
  Full type definitions included: