@nostrify/nostrify 0.49.2 → 0.50.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.
@@ -1,32 +1,32 @@
1
1
 
2
- > @nostrify/nostrify@0.49.1 build /home/alex/Projects/nostrify/packages/nostrify
2
+ > @nostrify/nostrify@0.50.0 build /home/alex/Projects/nostrify/packages/nostrify
3
3
  > npx tsc -p tsconfig.json && node ../../esbuild.config.js --package ./
4
4
 
5
5
  npm warn Unknown env config "verify-deps-before-run". This will stop working in the next major version of npm.
6
6
  npm warn Unknown env config "_jsr-registry". This will stop working in the next major version of npm.
7
7
  Building with esbuild...
8
8
 
9
- dist/NRelay1.js 8.9kb
10
- dist/NSchema.js 5.7kb
11
- dist/NPool.js 5.4kb
12
- dist/test/TestRelayServer.js 4.4kb
13
- dist/NSet.js 4.1kb
14
- dist/NConnectSigner.js 3.9kb
15
- dist/ln/LNURL.js 3.3kb
16
- dist/NIP98.js 2.7kb
17
- dist/NBrowserSigner.js 2.6kb
18
- dist/uploaders/BlossomUploader.js 1.9kb
19
- dist/uploaders/NostrBuildUploader.js 1.9kb
20
- dist/test/MockRelay.js 1.4kb
21
- dist/BunkerURI.js 1.4kb
22
- dist/NKinds.js 1.1kb
23
- dist/NIP05.js 1.1kb
24
- dist/NSecSigner.js 1.1kb
25
- dist/utils/Machina.js 925b
26
- dist/NCache.js 828b
27
- dist/mod.js 815b
28
- dist/test/mod.js 701b
9
+ dist/NRelay1.js 10.7kb
10
+ dist/NSchema.js 8.2kb
11
+ dist/NPool.js 5.4kb
12
+ dist/test/TestRelayServer.js 4.4kb
13
+ dist/NSet.js 4.1kb
14
+ dist/NConnectSigner.js 3.9kb
15
+ dist/ln/LNURL.js 3.3kb
16
+ dist/NIP98.js 2.7kb
17
+ dist/NBrowserSigner.js 2.6kb
18
+ dist/uploaders/BlossomUploader.js 1.9kb
19
+ dist/uploaders/NostrBuildUploader.js 1.9kb
20
+ dist/test/MockRelay.js 1.4kb
21
+ dist/BunkerURI.js 1.4kb
22
+ dist/NKinds.js 1.1kb
23
+ dist/NIP05.js 1.1kb
24
+ dist/NSecSigner.js 1.1kb
25
+ dist/utils/Machina.js 925b
26
+ dist/NCache.js 828b
27
+ dist/mod.js 815b
28
+ dist/test/mod.js 701b
29
29
  ...and 11 more output files...
30
30
 
31
- ⚡ Done in 15ms
31
+ ⚡ Done in 16ms
32
32
  Done!
@@ -1,113 +1,113 @@
1
-
2
-
3
- > @nostrify/nostrify@0.49.1 test /home/alex/Projects/nostrify/packages/nostrify
4
- > node --test "**/*.test.ts"
5
-
6
- ✔ BunkerURI (2.855929ms)
7
- BunkerURI.fromJSON (0.647302ms)
8
- ✔ NBrowserSigner - without extension (5.614283ms)
9
- ✔ NBrowserSigner - with extension polyfill (162.64428ms)
10
- ✔ NBrowserSigner.nip44 - with extension polyfill (35.751206ms)
11
- ✔ NBrowserSigner.nip04 - with extension polyfill (24.897688ms)
12
- ✔ NBrowserSigner.getRelays - with extension polyfill (1.639964ms)
13
- ✔ NBrowserSigner - missing nip44 support (0.681486ms)
14
- ✔ NBrowserSigner - missing nip04 support (0.334181ms)
15
- NBrowserSigner - feature detection (2.345656ms)
16
- NCache (4.846063ms)
17
- ✔ NConnectSigner.signEvent with nip04 encryption (284.998411ms)
18
- NConnectSigner.signEvent with nip44 encryption (79.574293ms)
19
- ✔ NIP05.lookup (94.266524ms)
20
- ✔ NIP05.lookup with invalid values but valid profile pointer (6.109077ms)
21
- NIP05.lookup with invalid document (7.362813ms)
22
- ✔ NIP50.parseInput (6.692668ms)
23
- NIP50.parseInput with negated token (0.459908ms)
24
- ✔ NIP98.template (7.800118ms)
25
- ✔ NIP98.template with payload (57.504461ms)
26
- ✔ NIP98.verify (167.402989ms)
27
- ✔ NIP98.verify fails with missing header (1.462579ms)
28
- ✔ NIP98.verify fails with missing token (1.026357ms)
29
- ✔ NIP98.verify fails with invalid token (1.318398ms)
30
- ✔ NIP98.verify fails with invalid event (15.517318ms)
31
- ✔ NIP98.verify fails with wrong event kind (15.461533ms)
32
- ✔ NIP98.verify fails with wrong request URL (12.774894ms)
33
- ✔ NIP98.verify fails with wrong request method (15.755518ms)
34
- ✔ NIP98.verify fails with expired event (11.549622ms)
35
- NIP98.verify fails with invalid payload (25.108065ms)
36
- ✔ NIP98Client.fetch - basic GET request (152.34065ms)
37
- ✔ NIP98Client.fetch - POST request with body (48.046766ms)
38
- ✔ NIP98Client.fetch - with Request object input (22.539248ms)
39
- ✔ NIP98Client.fetch - with URL object input (14.907818ms)
40
- ✔ NIP98Client.fetch - uses default fetch when not provided (11.87691ms)
41
- ✔ NIP98Client.fetch - preserves existing headers (16.091842ms)
42
- ✔ NIP98Client.fetch - event can be verified with NIP98.verify (20.434596ms)
43
- NIP98Client.fetch - handles different HTTP methods (54.082414ms)
44
- NKinds (5.032997ms)
45
- ✔ NPool.query (378.702333ms)
46
- ✔ NPool.req (77.556426ms)
47
- ✔ NPool.event (32.572729ms)
48
- ✔ NPool.query with eoseTimeout (559.204759ms)
49
- NPool.query with eoseTimeout disabled (519.68897ms)
50
- ✔ NRelay1.query (226.319484ms)
51
- ✔ NRelay1.query mismatched filter (27.633319ms)
52
- ✔ NRelay1.req (66.241496ms)
53
- ✔ NRelay1.event (12.452346ms)
54
- ﹣ NRelay1 backoff (0.21217ms) # SKIP
55
- ▶ NRelay1 idleTimeout
56
- ✔ websocket opens (2.256778ms)
57
- ✔ websocket closes after idleTimeout (150.482082ms)
58
- ✔ websocket wakes up during activity (17.379211ms)
59
- ✔ NRelay1 idleTimeout (173.173788ms)
60
- ✔ NRelay1.count rejects when the server sends CLOSED (7.752588ms)
61
- ✔ NRelay1 closes when it receives a binary message (5.304398ms)
62
- ✔ n.id (11.977479ms)
63
- ✔ n.bech32 (4.31372ms)
64
- ✔ n.filter (83.609146ms)
65
- ✔ n.event (39.732538ms)
66
- ✔ n.metadata (67.78575ms)
67
- ✔ NSecSigner (134.833466ms)
68
- ✔ NSecSigner.nip44 (39.725124ms)
69
- ✔ NSet (26.167433ms)
70
- ✔ NSet.add (replaceable) (0.555688ms)
71
- ✔ NSet.add (parameterized) (0.859632ms)
72
- ✔ NSet.add (deletion) (0.659384ms)
73
- ✔ Construct a RelayError from the reason message (2.48014ms)
74
- ✔ Throw a new RelayError if the OK message is false (0.480627ms)
75
- ✔ LNURL.fromString (7.853168ms)
76
- ✔ LNURL.fromLightningAddress (1.565174ms)
77
- ✔ LNURL.toString (1.207389ms)
78
- ✔ LNURL.getDetails (61.890516ms)
79
- ✔ LNURL.getInvoice (12.594574ms)
80
- ✔ ErrorRelay (89.704386ms)
81
- ✔ MockRelay (5.643569ms)
82
- ✔ BlossomUploader.upload (685.760126ms)
83
- ✖ NostrBuildUploader.upload (25.389164ms)
84
- ✔ CircularSet (2.759166ms)
85
- ✔ push, iterate, & close (103.744176ms)
86
- ✔ close & reopen (0.695512ms)
87
- ✔ aborts with signal (51.384223ms)
88
- ✔ already aborted signal in constructor (0.622785ms)
89
- ✔ push after abort (0.654174ms)
90
- ✔ multiple messages in queue (0.316858ms)
91
- ✔ N64 (18.170805ms)
92
- ✔ N64.encodeEvent (0.414843ms)
93
- ✔ N64.decodeEvent (4.505341ms)
94
- ℹ tests 87
95
- ℹ suites 0
96
- ℹ pass 85
97
- ℹ fail 1
98
- ℹ cancelled 0
99
- ℹ skipped 1
100
- ℹ todo 0
101
- ℹ duration_ms 3425.548766
102
-
103
- ✖ failing tests:
104
-
105
- test at uploaders/NostrBuildUploader.test.ts:12:7
106
- ✖ NostrBuildUploader.upload (25.389164ms)
107
- Error: nostr.build with default uploader requires a secret key to be configured
108
- at TestContext.<anonymous> (file:///home/alex/Projects/nostrify/packages/nostrify/uploaders/NostrBuildUploader.test.ts:25:13)
109
-  at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
110
-  at async Test.run (node:internal/test_runner/test:1102:7)
111
-  at async startSubtestAfterBootstrap (node:internal/test_runner/harness:358:3)
112
- at async file:///home/alex/Projects/nostrify/packages/nostrify/uploaders/NostrBuildUploader.test.ts:12:1
113
-  ELIFECYCLE  Test failed. See above for more details.
1
+
2
+ > @nostrify/nostrify@0.49.2 test /home/alex/Projects/nostrify/packages/nostrify
3
+ > node --test "**/*.test.ts"
4
+
5
+ ✔ BunkerURI (3.395047ms)
6
+ ✔ BunkerURI.fromJSON (0.74187ms)
7
+ NBrowserSigner - without extension (6.198276ms)
8
+ ✔ NBrowserSigner - with extension polyfill (190.009587ms)
9
+ ✔ NBrowserSigner.nip44 - with extension polyfill (37.935911ms)
10
+ ✔ NBrowserSigner.nip04 - with extension polyfill (25.129052ms)
11
+ ✔ NBrowserSigner.getRelays - with extension polyfill (2.053194ms)
12
+ ✔ NBrowserSigner - missing nip44 support (0.917462ms)
13
+ ✔ NBrowserSigner - missing nip04 support (0.459447ms)
14
+ ✔ NBrowserSigner - feature detection (0.765374ms)
15
+ NCache (12.840903ms)
16
+ NConnectSigner.signEvent with nip04 encryption (271.624777ms)
17
+ ✔ NConnectSigner.signEvent with nip44 encryption (71.743781ms)
18
+ NIP05.lookup (136.606498ms)
19
+ ✔ NIP05.lookup with invalid values but valid profile pointer (26.791359ms)
20
+ ✔ NIP05.lookup with invalid document (12.007099ms)
21
+ NIP50.parseInput (3.937501ms)
22
+ ✔ NIP50.parseInput with negated token (0.560007ms)
23
+ NIP98.template (6.37427ms)
24
+ ✔ NIP98.template with payload (26.999101ms)
25
+ ✔ NIP98.verify (179.4453ms)
26
+ ✔ NIP98.verify fails with missing header (3.242699ms)
27
+ ✔ NIP98.verify fails with missing token (1.904625ms)
28
+ ✔ NIP98.verify fails with invalid token (2.465312ms)
29
+ ✔ NIP98.verify fails with invalid event (22.614687ms)
30
+ ✔ NIP98.verify fails with wrong event kind (32.449507ms)
31
+ ✔ NIP98.verify fails with wrong request URL (21.092385ms)
32
+ ✔ NIP98.verify fails with wrong request method (29.362892ms)
33
+ ✔ NIP98.verify fails with expired event (18.052067ms)
34
+ ✔ NIP98.verify fails with invalid payload (31.823746ms)
35
+ NIP98Client.fetch - basic GET request (203.916982ms)
36
+ ✔ NIP98Client.fetch - POST request with body (53.60961ms)
37
+ ✔ NIP98Client.fetch - with Request object input (20.893399ms)
38
+ ✔ NIP98Client.fetch - with URL object input (11.890279ms)
39
+ ✔ NIP98Client.fetch - uses default fetch when not provided (9.503144ms)
40
+ ✔ NIP98Client.fetch - preserves existing headers (14.925967ms)
41
+ ✔ NIP98Client.fetch - event can be verified with NIP98.verify (24.239093ms)
42
+ ✔ NIP98Client.fetch - handles different HTTP methods (62.95163ms)
43
+ NKinds (2.673706ms)
44
+ NPool.query (340.485108ms)
45
+ ✔ NPool.req (83.109821ms)
46
+ ✔ NPool.event (39.765323ms)
47
+ ✔ NPool.query with eoseTimeout (589.053675ms)
48
+ ✔ NPool.query with eoseTimeout disabled (517.784379ms)
49
+ NRelay1.query (327.416455ms)
50
+ ✔ NRelay1.query with NIP-50 search preserves relay order (54.456828ms)
51
+ ✔ NRelay1.query mismatched filter (24.4706ms)
52
+ ✔ NRelay1.req (70.307922ms)
53
+ ✔ NRelay1.event (25.854061ms)
54
+ ﹣ NRelay1 backoff (0.278776ms) # SKIP
55
+ ▶ NRelay1 idleTimeout
56
+ ✔ websocket opens (2.814902ms)
57
+ ✔ websocket closes after idleTimeout (150.342419ms)
58
+ ✔ websocket wakes up during activity (16.192178ms)
59
+ ✔ NRelay1 idleTimeout (172.377473ms)
60
+ ✔ NRelay1.count rejects when the server sends CLOSED (6.414255ms)
61
+ ✔ NRelay1 closes when it receives a binary message (4.879718ms)
62
+ ✔ n.id (13.224767ms)
63
+ ✔ n.bech32 (4.952717ms)
64
+ ✔ n.filter (59.599323ms)
65
+ ✔ n.event (50.481827ms)
66
+ ✔ n.metadata (79.798873ms)
67
+ ✔ NSecSigner (174.438973ms)
68
+ ✔ NSecSigner.nip44 (40.880438ms)
69
+ ✔ NSet (32.904085ms)
70
+ ✔ NSet.add (replaceable) (0.611303ms)
71
+ ✔ NSet.add (parameterized) (0.78409ms)
72
+ ✔ NSet.add (deletion) (0.528828ms)
73
+ ✔ Construct a RelayError from the reason message (2.522901ms)
74
+ ✔ Throw a new RelayError if the OK message is false (0.555108ms)
75
+ ✔ LNURL.fromString (9.215922ms)
76
+ ✔ LNURL.fromLightningAddress (2.004964ms)
77
+ ✔ LNURL.toString (0.956956ms)
78
+ ✔ LNURL.getDetails (77.433128ms)
79
+ ✔ LNURL.getInvoice (11.77437ms)
80
+ ✔ ErrorRelay (108.988137ms)
81
+ ✔ MockRelay (4.248918ms)
82
+ ✔ BlossomUploader.upload (667.499053ms)
83
+ ✖ NostrBuildUploader.upload (15.830003ms)
84
+ ✔ CircularSet (3.14234ms)
85
+ ✔ push, iterate, & close (103.279174ms)
86
+ ✔ close & reopen (0.623095ms)
87
+ ✔ aborts with signal (51.133076ms)
88
+ ✔ already aborted signal in constructor (0.404423ms)
89
+ ✔ push after abort (0.54578ms)
90
+ ✔ multiple messages in queue (0.272634ms)
91
+ ✔ N64 (11.53621ms)
92
+ ✔ N64.encodeEvent (0.261664ms)
93
+ ✔ N64.decodeEvent (2.938024ms)
94
+ ℹ tests 88
95
+ ℹ suites 0
96
+ ℹ pass 86
97
+ ℹ fail 1
98
+ ℹ cancelled 0
99
+ ℹ skipped 1
100
+ ℹ todo 0
101
+ ℹ duration_ms 3268.962193
102
+
103
+ ✖ failing tests:
104
+
105
+ test at uploaders/NostrBuildUploader.test.ts:12:7
106
+ ✖ NostrBuildUploader.upload (15.830003ms)
107
+ Error: nostr.build with default uploader requires a secret key to be configured
108
+ at TestContext.<anonymous> (file:///home/alex/Projects/nostrify/packages/nostrify/uploaders/NostrBuildUploader.test.ts:25:13)
109
+ at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
110
+ at async Test.run (node:internal/test_runner/test:1102:7)
111
+ at async startSubtestAfterBootstrap (node:internal/test_runner/harness:358:3)
112
+ at async file:///home/alex/Projects/nostrify/packages/nostrify/uploaders/NostrBuildUploader.test.ts:12:1
113
+  ELIFECYCLE  Test failed. See above for more details.
@@ -1,5 +1,5 @@
1
1
 
2
- > @nostrify/nostrify@0.49.1 typecheck /home/alex/Projects/nostrify/packages/nostrify
2
+ > @nostrify/nostrify@0.50.0 typecheck /home/alex/Projects/nostrify/packages/nostrify
3
3
  > npx tsc --noEmit
4
4
 
5
5
  npm warn Unknown env config "verify-deps-before-run". This will stop working in the next major version of npm.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.50.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Add NIP-11 relay information document support to NRelay1 with `getRelayInfo()` method and `NSchema.relayInfo()` validation schema. Fix NRelay1.query to preserve relay-provided ordering for NIP-50 search filters instead of sorting by created_at.
8
+
3
9
  ## 0.49.2
4
10
 
5
11
  ### Patch Changes
package/NRelay1.test.ts CHANGED
@@ -36,6 +36,38 @@ await test("NRelay1.query", async () => {
36
36
  clearTimeout(tid);
37
37
  });
38
38
 
39
+ await test("NRelay1.query with NIP-50 search preserves relay order", async () => {
40
+ // Create events with different timestamps. The relay will send them
41
+ // in "relevance" order (oldest first), not chronological order.
42
+ const sk = generateSecretKey();
43
+ const oldest = genEvent({ kind: 1, content: "most relevant", created_at: 1000 }, sk);
44
+ const middle = genEvent({ kind: 1, content: "somewhat relevant", created_at: 2000 }, sk);
45
+ const newest = genEvent({ kind: 1, content: "least relevant", created_at: 3000 }, sk);
46
+
47
+ // Relay sends in relevance order: oldest, middle, newest.
48
+ // Without the fix, NSet would sort them newest-first by created_at.
49
+ await using server = await TestRelayServer.create({
50
+ handleMessage(socket, msg) {
51
+ if (msg[0] === "REQ") {
52
+ const [, subId] = msg;
53
+ socket.send(JSON.stringify(["EVENT", subId, oldest]));
54
+ socket.send(JSON.stringify(["EVENT", subId, middle]));
55
+ socket.send(JSON.stringify(["EVENT", subId, newest]));
56
+ socket.send(JSON.stringify(["EOSE", subId]));
57
+ }
58
+ },
59
+ });
60
+
61
+ await using relay = new NRelay1(server.url);
62
+ const events = await relay.query([{ kinds: [1], search: "relevant" }]);
63
+
64
+ deepStrictEqual(events.length, 3);
65
+ // Results should preserve relay order (relevance), not be sorted by created_at.
66
+ deepStrictEqual(events[0].id, oldest.id);
67
+ deepStrictEqual(events[1].id, middle.id);
68
+ deepStrictEqual(events[2].id, newest.id);
69
+ });
70
+
39
71
  await test("NRelay1.query mismatched filter", async () => {
40
72
  await using server = await TestRelayServer.create({
41
73
  handleMessage(socket, msg) {
package/NRelay1.ts CHANGED
@@ -10,6 +10,7 @@ import type {
10
10
  NostrRelayMsg,
11
11
  NostrRelayNOTICE,
12
12
  NostrRelayOK,
13
+ NostrRelayInfo,
13
14
  NRelay,
14
15
  } from '@nostrify/types';
15
16
  import { getFilterLimit, matchFilters, verifyEvent as _verifyEvent } from 'nostr-tools';
@@ -28,7 +29,7 @@ type EventMap = {
28
29
  notice: NostrRelayNOTICE;
29
30
  };
30
31
 
31
- /** Options used for constructing an `NRelay1` instance. */
32
+ /** Options used for constructing an `NRelay1` instance. */
32
33
  export interface NRelay1Opts {
33
34
  /** Respond to `AUTH` challenges by producing a signed kind `22242` event. */
34
35
  auth?(challenge: string): Promise<NostrEvent>;
@@ -40,6 +41,8 @@ export interface NRelay1Opts {
40
41
  verifyEvent?(event: NostrEvent): boolean;
41
42
  /** Logger callback. */
42
43
  log?(log: NRelay1Log): void;
44
+ /** Custom fetch function for retrieving NIP-11 relay information. Default: `globalThis.fetch`. */
45
+ fetch?: typeof globalThis.fetch;
43
46
  }
44
47
 
45
48
  export interface NRelay1Log {
@@ -58,6 +61,7 @@ export class NRelay1 implements NRelay {
58
61
  private controller = new AbortController();
59
62
  private url: string;
60
63
  private opts: NRelay1Opts;
64
+ private relayInfoPromise?: Promise<NostrRelayInfo | undefined>;
61
65
 
62
66
  private ee = new EventTarget();
63
67
 
@@ -76,6 +80,69 @@ export class NRelay1 implements NRelay {
76
80
  this.maybeStartIdleTimer();
77
81
  }
78
82
 
83
+ /** Fetch the NIP-11 relay information document. */
84
+ private async fetchRelayInfo(opts?: { signal?: AbortSignal }): Promise<NostrRelayInfo | undefined> {
85
+ try {
86
+ const { fetch: fetchFn = globalThis.fetch } = this.opts;
87
+ const { signal } = opts || {};
88
+
89
+ const httpUrl = this.url.replace(/^wss?:\/\//, (match) =>
90
+ match === 'ws://' ? 'http://' : 'https://'
91
+ );
92
+
93
+ const response = await fetchFn(httpUrl, {
94
+ headers: { Accept: 'application/nostr+json' },
95
+ signal,
96
+ });
97
+
98
+ if (!response.ok) {
99
+ this.log({
100
+ level: 'warn',
101
+ ns: 'relay.nip11',
102
+ status: response.status,
103
+ message: 'Failed to fetch relay info',
104
+ });
105
+ return undefined;
106
+ }
107
+
108
+ const data = await response.json();
109
+ const result = n.relayInfo().safeParse(data);
110
+
111
+ if (!result.success) {
112
+ this.log({
113
+ level: 'warn',
114
+ ns: 'relay.nip11',
115
+ error: result.error,
116
+ message: 'Invalid relay info format',
117
+ });
118
+ return undefined;
119
+ }
120
+
121
+ this.log({
122
+ level: 'debug',
123
+ ns: 'relay.nip11',
124
+ message: 'Successfully fetched relay info',
125
+ });
126
+ return result.data as NostrRelayInfo;
127
+ } catch (error) {
128
+ this.log({
129
+ level: 'warn',
130
+ ns: 'relay.nip11',
131
+ error: error instanceof Error ? error : new Error(String(error)),
132
+ message: 'Error fetching relay info',
133
+ });
134
+ return undefined;
135
+ }
136
+ }
137
+
138
+ /** Get the NIP-11 relay information document. Fetches on first call and caches the result. */
139
+ async getRelayInfo(opts?: { signal?: AbortSignal }): Promise<NostrRelayInfo | undefined> {
140
+ if (!this.relayInfoPromise) {
141
+ this.relayInfoPromise = this.fetchRelayInfo(opts);
142
+ }
143
+ return this.relayInfoPromise;
144
+ }
145
+
79
146
  /** Create (and open) a WebSocket connection with automatic reconnect. */
80
147
  private createSocket(): Websocket {
81
148
  const { backoff = new ExponentialBackoff(1000) } = this.opts;
@@ -255,7 +322,8 @@ export class NRelay1 implements NRelay {
255
322
  filters: NostrFilter[],
256
323
  opts?: { signal?: AbortSignal },
257
324
  ): Promise<NostrEvent[]> {
258
- const events = new NSet();
325
+ const map = new Map<string, NostrEvent>();
326
+ const events = new NSet(map);
259
327
 
260
328
  const limit = filters.reduce(
261
329
  (result, filter) => result + getFilterLimit(filter),
@@ -273,7 +341,12 @@ export class NRelay1 implements NRelay {
273
341
  }
274
342
  }
275
343
 
276
- return [...events];
344
+ // Don't sort results of search filters.
345
+ if (filters.some((filter) => typeof filter.search === 'string')) {
346
+ return [...map.values()];
347
+ } else {
348
+ return [...events];
349
+ }
277
350
  }
278
351
 
279
352
  async event(
package/NSchema.ts CHANGED
@@ -217,6 +217,51 @@ class NSchema {
217
217
  });
218
218
  }
219
219
 
220
+ /** NIP-11 Relay Information Document schema. */
221
+ static relayInfo() {
222
+ return z.looseObject({
223
+ name: z.string().optional().catch(undefined),
224
+ description: z.string().optional().catch(undefined),
225
+ pubkey: NSchema.id().optional().catch(undefined),
226
+ contact: z.string().optional().catch(undefined),
227
+ supported_nips: z.number().int().nonnegative().array().optional().catch(undefined),
228
+ software: z.string().optional().catch(undefined),
229
+ version: z.string().optional().catch(undefined),
230
+ limitation: z.looseObject({
231
+ max_message_length: z.number().int().nonnegative().optional().catch(undefined),
232
+ max_subscriptions: z.number().int().nonnegative().optional().catch(undefined),
233
+ max_filters: z.number().int().nonnegative().optional().catch(undefined),
234
+ max_limit: z.number().int().nonnegative().optional().catch(undefined),
235
+ max_subid_length: z.number().int().nonnegative().optional().catch(undefined),
236
+ max_event_tags: z.number().int().nonnegative().optional().catch(undefined),
237
+ max_content_length: z.number().int().nonnegative().optional().catch(undefined),
238
+ min_pow_difficulty: z.number().int().nonnegative().optional().catch(undefined),
239
+ auth_required: z.boolean().optional().catch(undefined),
240
+ payment_required: z.boolean().optional().catch(undefined),
241
+ restricted_writes: z.boolean().optional().catch(undefined),
242
+ created_at_lower_limit: z.number().int().nonnegative().optional().catch(undefined),
243
+ created_at_upper_limit: z.number().int().nonnegative().optional().catch(undefined),
244
+ }).optional().catch(undefined),
245
+ retention: z.array(z.object({
246
+ time: z.number().int().nullable(),
247
+ count: z.number().int().nonnegative().optional(),
248
+ kinds: z.number().int().nonnegative().array().optional(),
249
+ })).optional().catch(undefined),
250
+ relay_countries: z.string().array().optional().catch(undefined),
251
+ language_tags: z.string().array().optional().catch(undefined),
252
+ tags: z.string().array().optional().catch(undefined),
253
+ posting_policy: z.string().optional().catch(undefined),
254
+ payments_url: z.string().optional().catch(undefined),
255
+ fees: z.record(z.string(), z.array(z.object({
256
+ amount: z.number(),
257
+ unit: z.string(),
258
+ period: z.number().int().nonnegative().optional(),
259
+ kinds: z.number().int().nonnegative().array().optional(),
260
+ }))).optional().catch(undefined),
261
+ icon: z.string().optional().catch(undefined),
262
+ });
263
+ }
264
+
220
265
  /** NIP-46 request content schema. */
221
266
  static connectRequest() {
222
267
  return z.object({
package/dist/NRelay1.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { NostrClientMsg, NostrClientREQ, NostrEvent, NostrFilter, NostrRelayCLOSED, NostrRelayEOSE, NostrRelayEVENT, NostrRelayMsg, NRelay } from '@nostrify/types';
1
+ import type { NostrClientMsg, NostrClientREQ, NostrEvent, NostrFilter, NostrRelayCLOSED, NostrRelayEOSE, NostrRelayEVENT, NostrRelayMsg, NostrRelayInfo, NRelay } from '@nostrify/types';
2
2
  import { Websocket } from 'websocket-ts';
3
3
  import type { Backoff } from 'websocket-ts';
4
4
  /** Options used for constructing an `NRelay1` instance. */
@@ -13,6 +13,8 @@ export interface NRelay1Opts {
13
13
  verifyEvent?(event: NostrEvent): boolean;
14
14
  /** Logger callback. */
15
15
  log?(log: NRelay1Log): void;
16
+ /** Custom fetch function for retrieving NIP-11 relay information. Default: `globalThis.fetch`. */
17
+ fetch?: typeof globalThis.fetch;
16
18
  }
17
19
  export interface NRelay1Log {
18
20
  level: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal' | 'critical';
@@ -30,10 +32,17 @@ export declare class NRelay1 implements NRelay {
30
32
  private controller;
31
33
  private url;
32
34
  private opts;
35
+ private relayInfoPromise?;
33
36
  private ee;
34
37
  get subscriptions(): readonly NostrClientREQ[];
35
38
  private log;
36
39
  constructor(url: string, opts?: NRelay1Opts);
40
+ /** Fetch the NIP-11 relay information document. */
41
+ private fetchRelayInfo;
42
+ /** Get the NIP-11 relay information document. Fetches on first call and caches the result. */
43
+ getRelayInfo(opts?: {
44
+ signal?: AbortSignal;
45
+ }): Promise<NostrRelayInfo | undefined>;
37
46
  /** Create (and open) a WebSocket connection with automatic reconnect. */
38
47
  private createSocket;
39
48
  /** Handle a NIP-01 relay message. */
@@ -1 +1 @@
1
- {"version":3,"file":"NRelay1.d.ts","sourceRoot":"","sources":["../NRelay1.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"NRelay1.d.ts","sourceRoot":"","sources":["../NRelay1.ts"],"names":[],"mappings":"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"}