applesauce-core 0.12.0 → 1.0.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.
Files changed (68) hide show
  1. package/README.md +28 -10
  2. package/dist/event-store/__tests__/event-store.test.js +83 -1
  3. package/dist/event-store/database.d.ts +1 -0
  4. package/dist/event-store/database.js +7 -8
  5. package/dist/event-store/event-store.d.ts +4 -0
  6. package/dist/event-store/event-store.js +61 -22
  7. package/dist/event-store/interface.d.ts +11 -5
  8. package/dist/helpers/__tests__/bookmarks.test.d.ts +1 -0
  9. package/dist/helpers/__tests__/bookmarks.test.js +88 -0
  10. package/dist/helpers/__tests__/comment.test.js +14 -0
  11. package/dist/helpers/__tests__/contacts.test.d.ts +1 -0
  12. package/dist/helpers/__tests__/contacts.test.js +34 -0
  13. package/dist/helpers/__tests__/events.test.d.ts +1 -0
  14. package/dist/helpers/__tests__/events.test.js +32 -0
  15. package/dist/helpers/__tests__/mutes.test.d.ts +1 -0
  16. package/dist/helpers/__tests__/mutes.test.js +55 -0
  17. package/dist/helpers/bookmarks.d.ts +6 -1
  18. package/dist/helpers/bookmarks.js +52 -7
  19. package/dist/helpers/comment.d.ts +7 -3
  20. package/dist/helpers/comment.js +6 -1
  21. package/dist/helpers/contacts.d.ts +11 -0
  22. package/dist/helpers/contacts.js +34 -0
  23. package/dist/helpers/event.d.ts +8 -3
  24. package/dist/helpers/event.js +21 -13
  25. package/dist/helpers/lists.d.ts +40 -12
  26. package/dist/helpers/lists.js +62 -23
  27. package/dist/helpers/mutes.d.ts +8 -0
  28. package/dist/helpers/mutes.js +66 -5
  29. package/dist/helpers/nip-19.d.ts +14 -0
  30. package/dist/helpers/nip-19.js +29 -0
  31. package/dist/helpers/pointers.js +6 -6
  32. package/dist/observable/__tests__/listen-latest-updates.test.d.ts +1 -0
  33. package/dist/observable/__tests__/listen-latest-updates.test.js +55 -0
  34. package/dist/observable/defined.d.ts +3 -0
  35. package/dist/observable/defined.js +5 -0
  36. package/dist/observable/get-observable-value.d.ts +4 -1
  37. package/dist/observable/get-observable-value.js +4 -1
  38. package/dist/observable/index.d.ts +3 -1
  39. package/dist/observable/index.js +3 -1
  40. package/dist/observable/listen-latest-updates.d.ts +5 -0
  41. package/dist/observable/listen-latest-updates.js +12 -0
  42. package/dist/queries/blossom.js +1 -6
  43. package/dist/queries/bookmarks.d.ts +5 -5
  44. package/dist/queries/bookmarks.js +18 -17
  45. package/dist/queries/channels.js +41 -53
  46. package/dist/queries/comments.js +6 -9
  47. package/dist/queries/contacts.d.ts +6 -1
  48. package/dist/queries/contacts.js +21 -9
  49. package/dist/queries/index.d.ts +1 -0
  50. package/dist/queries/index.js +1 -0
  51. package/dist/queries/mailboxes.js +4 -7
  52. package/dist/queries/mutes.d.ts +6 -6
  53. package/dist/queries/mutes.js +20 -19
  54. package/dist/queries/pins.d.ts +1 -0
  55. package/dist/queries/pins.js +4 -6
  56. package/dist/queries/profile.js +1 -6
  57. package/dist/queries/reactions.js +11 -14
  58. package/dist/queries/relays.d.ts +27 -0
  59. package/dist/queries/relays.js +44 -0
  60. package/dist/queries/simple.js +5 -22
  61. package/dist/queries/thread.js +45 -51
  62. package/dist/queries/user-status.js +23 -29
  63. package/dist/queries/zaps.js +10 -13
  64. package/dist/query-store/query-store.d.ts +7 -6
  65. package/dist/query-store/query-store.js +13 -8
  66. package/package.json +3 -3
  67. package/dist/observable/share-latest-value.d.ts +0 -6
  68. package/dist/observable/share-latest-value.js +0 -24
@@ -27,6 +27,7 @@ describe("getCommentEventPointer", () => {
27
27
  ["e", "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f"],
28
28
  ];
29
29
  expect(getCommentEventPointer(tags, true)).toEqual({
30
+ type: "event",
30
31
  id: "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f",
31
32
  kind: 1621,
32
33
  pubkey: "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
@@ -45,6 +46,7 @@ describe("getCommentEventPointer", () => {
45
46
  ["P", "bad-pubkey"],
46
47
  ];
47
48
  expect(getCommentEventPointer(tags, true)).toEqual({
49
+ type: "event",
48
50
  id: "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f",
49
51
  kind: 1621,
50
52
  pubkey: "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
@@ -62,6 +64,7 @@ describe("getCommentEventPointer", () => {
62
64
  ["K", "1621"],
63
65
  ];
64
66
  expect(getCommentEventPointer(tags, true)).toEqual({
67
+ type: "event",
65
68
  id: "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f",
66
69
  kind: 1621,
67
70
  pubkey: "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
@@ -75,6 +78,7 @@ describe("getCommentEventPointer", () => {
75
78
  ["P", "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10"],
76
79
  ];
77
80
  expect(getCommentEventPointer(tags, true)).toEqual({
81
+ type: "event",
78
82
  id: "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f",
79
83
  kind: 1621,
80
84
  pubkey: "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
@@ -107,6 +111,7 @@ describe("getCommentAddressPointer", () => {
107
111
  ["E", "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f"],
108
112
  ["K", "30000"],
109
113
  ], true)).toEqual({
114
+ type: "address",
110
115
  id: "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f",
111
116
  kind: 30000,
112
117
  pubkey: "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
@@ -118,6 +123,7 @@ describe("getCommentAddressPointer", () => {
118
123
  ["e", "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f"],
119
124
  ["k", "30000"],
120
125
  ])).toEqual({
126
+ type: "address",
121
127
  id: "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f",
122
128
  kind: 30000,
123
129
  pubkey: "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
@@ -130,6 +136,7 @@ describe("getCommentAddressPointer", () => {
130
136
  ["A", "30000:e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10:list", "wss://relay.io/"],
131
137
  ["K", "30000"],
132
138
  ], true)).toEqual({
139
+ type: "address",
133
140
  kind: 30000,
134
141
  pubkey: "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
135
142
  identifier: "list",
@@ -140,6 +147,7 @@ describe("getCommentAddressPointer", () => {
140
147
  ["a", "30000:e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10:list", "wss://relay.io/"],
141
148
  ["k", "30000"],
142
149
  ])).toEqual({
150
+ type: "address",
143
151
  kind: 30000,
144
152
  pubkey: "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
145
153
  identifier: "list",
@@ -153,6 +161,7 @@ describe("getCommentAddressPointer", () => {
153
161
  ["E", "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f", "wss://relay.io/"],
154
162
  ["K", "30000"],
155
163
  ], true)).toEqual({
164
+ type: "address",
156
165
  id: "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f",
157
166
  kind: 30000,
158
167
  pubkey: "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
@@ -165,6 +174,7 @@ describe("getCommentAddressPointer", () => {
165
174
  ["e", "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f", "wss://relay.io/"],
166
175
  ["k", "30000"],
167
176
  ])).toEqual({
177
+ type: "address",
168
178
  id: "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f",
169
179
  kind: 30000,
170
180
  pubkey: "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
@@ -178,6 +188,7 @@ describe("getCommentAddressPointer", () => {
178
188
  ["A", "30010:e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10:list"],
179
189
  ["K", "30000"],
180
190
  ], true)).toEqual({
191
+ type: "address",
181
192
  kind: 30010,
182
193
  pubkey: "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
183
194
  identifier: "list",
@@ -187,6 +198,7 @@ describe("getCommentAddressPointer", () => {
187
198
  ["a", "30010:e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10:list"],
188
199
  ["k", "30000"],
189
200
  ])).toEqual({
201
+ type: "address",
190
202
  kind: 30010,
191
203
  pubkey: "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
192
204
  identifier: "list",
@@ -214,6 +226,7 @@ describe("getCommentExternalPointer", () => {
214
226
  ["I", "podcast:item:guid:d98d189b-dc7b-45b1-8720-d4b98690f31f"],
215
227
  ["K", "podcast:item:guid"],
216
228
  ], true)).toEqual({
229
+ type: "external",
217
230
  identifier: "podcast:item:guid:d98d189b-dc7b-45b1-8720-d4b98690f31f",
218
231
  kind: "podcast:item:guid",
219
232
  });
@@ -222,6 +235,7 @@ describe("getCommentExternalPointer", () => {
222
235
  ["i", "podcast:item:guid:d98d189b-dc7b-45b1-8720-d4b98690f31f"],
223
236
  ["k", "podcast:item:guid"],
224
237
  ])).toEqual({
238
+ type: "external",
225
239
  identifier: "podcast:item:guid:d98d189b-dc7b-45b1-8720-d4b98690f31f",
226
240
  kind: "podcast:item:guid",
227
241
  });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,34 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { mergeContacts } from "../contacts.js";
3
+ describe("mergeContacts", () => {
4
+ it("should merge contacts and remove duplicates", () => {
5
+ // Create some test profile pointers
6
+ const pointer1 = { pubkey: "pubkey1", relays: ["relay1"] };
7
+ const pointer2 = { pubkey: "pubkey2" }; // No relays
8
+ const pointer3 = { pubkey: "pubkey3", relays: ["relay3"] };
9
+ const pointer4 = { pubkey: "pubkey1" }; // Duplicate pubkey without relays
10
+ // Test merging arrays of pointers
11
+ const result1 = mergeContacts([pointer1, pointer2], [pointer3, pointer4]);
12
+ // Should have 3 unique pubkeys
13
+ expect(result1.length).toBe(3);
14
+ // Check that the duplicate was handled correctly (last one should win)
15
+ const pubkey1Entry = result1.find((p) => p.pubkey === "pubkey1");
16
+ expect(pubkey1Entry).toBeDefined();
17
+ expect(pubkey1Entry?.relays).toBeUndefined();
18
+ // Test with undefined values
19
+ const result2 = mergeContacts([pointer1], undefined, [pointer2, undefined]);
20
+ expect(result2.length).toBe(2);
21
+ // Test with single pointers
22
+ const result3 = mergeContacts(pointer1, pointer2, pointer1);
23
+ expect(result3.length).toBe(2);
24
+ // Test with empty arrays
25
+ const result4 = mergeContacts([], [pointer1], []);
26
+ expect(result4.length).toBe(1);
27
+ // Test with pointers that have and don't have relays
28
+ const pointer5 = { pubkey: "pubkey5", relays: ["relay5"] };
29
+ const pointer6 = { pubkey: "pubkey5" }; // Same pubkey without relays
30
+ const result5 = mergeContacts([pointer5], [pointer6]);
31
+ expect(result5.length).toBe(1);
32
+ expect(result5[0].relays).toBeUndefined();
33
+ });
34
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,32 @@
1
+ import { kinds } from "nostr-tools";
2
+ import { describe, expect, it } from "vitest";
3
+ import { FakeUser } from "../../__tests__/fixtures.js";
4
+ import { getReplaceableAddress } from "../event.js";
5
+ const user = new FakeUser();
6
+ describe("getReplaceableAddress", () => {
7
+ it("should throw an error for non-replaceable events", () => {
8
+ const normalEvent = user.note("Hello world");
9
+ expect(() => {
10
+ getReplaceableAddress(normalEvent);
11
+ }).toThrow("Event is not replaceable or addressable");
12
+ });
13
+ it("should return the correct address for replaceable events", () => {
14
+ const replaceableEvent = user.event({
15
+ kind: kinds.Metadata,
16
+ content: JSON.stringify({ name: "Test User" }),
17
+ tags: [],
18
+ });
19
+ const expectedAddress = `${kinds.Metadata}:${user.pubkey}:`;
20
+ expect(getReplaceableAddress(replaceableEvent)).toBe(expectedAddress);
21
+ });
22
+ it("should include the identifier for addressable events", () => {
23
+ const identifier = "test-profile";
24
+ const addressableEvent = user.event({
25
+ kind: 30000, // Parameterized replaceable event
26
+ content: "Test content",
27
+ tags: [["d", identifier]],
28
+ });
29
+ const expectedAddress = `30000:${user.pubkey}:${identifier}`;
30
+ expect(getReplaceableAddress(addressableEvent)).toBe(expectedAddress);
31
+ });
32
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,55 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { matchMutes } from "../mutes.js";
3
+ import { FakeUser } from "../../__tests__/fixtures.js";
4
+ const mutedUser = new FakeUser();
5
+ const nonMutedUser = new FakeUser();
6
+ const thread = nonMutedUser.note("Hello world");
7
+ // Create a mutes object with a pubkey to mute
8
+ const mutes = {
9
+ pubkeys: new Set([mutedUser.pubkey]),
10
+ threads: new Set([thread.id]),
11
+ hashtags: new Set(["nostr"]),
12
+ words: new Set(["GM"]),
13
+ };
14
+ describe("matchMutes", () => {
15
+ it("should match events with muted pubkeys", () => {
16
+ const mutedEvent = mutedUser.note("Hello world");
17
+ const nonMutedEvent = nonMutedUser.note("Hello world");
18
+ // The event with the muted pubkey should match
19
+ expect(matchMutes(mutes, mutedEvent)).toBe(true);
20
+ // The event with a different pubkey should not match
21
+ expect(matchMutes(mutes, nonMutedEvent)).toBe(false);
22
+ });
23
+ it("should match events with muted hashtags", () => {
24
+ // Create events with and without the muted hashtag
25
+ const eventWithMutedHashtag = nonMutedUser.note("Hello world");
26
+ eventWithMutedHashtag.tags.push(["t", "nostr"]);
27
+ const eventWithDifferentHashtag = nonMutedUser.note("Hello world");
28
+ eventWithDifferentHashtag.tags.push(["t", "bitcoin"]);
29
+ const eventWithNoHashtag = nonMutedUser.note("Hello world");
30
+ // The event with the muted hashtag should match
31
+ expect(matchMutes(mutes, eventWithMutedHashtag)).toBe(true);
32
+ // The events without the muted hashtag should not match
33
+ expect(matchMutes(mutes, eventWithDifferentHashtag)).toBe(false);
34
+ expect(matchMutes(mutes, eventWithNoHashtag)).toBe(false);
35
+ });
36
+ it("should match events within threads", () => {
37
+ // Create a reply to the thread
38
+ const reply = nonMutedUser.note("Hello world");
39
+ reply.tags.push(["e", thread.id, "", "root"]);
40
+ // The reply should match the mute
41
+ expect(matchMutes(mutes, reply)).toBe(true);
42
+ // The thread should not match the mute
43
+ expect(matchMutes(mutes, thread)).toBe(false);
44
+ });
45
+ it("should match events with muted words", () => {
46
+ // The event with the muted word should match
47
+ expect(matchMutes(mutes, nonMutedUser.note("GM"))).toBe(true);
48
+ // Should not match other words that contain the muted word
49
+ expect(matchMutes(mutes, nonMutedUser.note("GMing"))).toBe(false);
50
+ // Should be case-insensitive
51
+ expect(matchMutes(mutes, nonMutedUser.note("gm"))).toBe(true);
52
+ // Should match if the muted word
53
+ expect(matchMutes(mutes, nonMutedUser.note("Hello GM world"))).toBe(true);
54
+ });
55
+ });
@@ -8,8 +8,13 @@ export type Bookmarks = {
8
8
  hashtags: string[];
9
9
  urls: string[];
10
10
  };
11
+ /** Parses an array of tags into a {@link Bookmarks} object */
11
12
  export declare function parseBookmarkTags(tags: string[][]): Bookmarks;
12
- /** Returns the public bookmarks of the event */
13
+ /** Merges any number of {@link Bookmarks} objects */
14
+ export declare function mergeBookmarks(...bookmarks: (Bookmarks | undefined)[]): Bookmarks;
15
+ /** Returns all the bookmarks of the event */
13
16
  export declare function getBookmarks(bookmark: NostrEvent): Bookmarks;
17
+ /** Returns the public bookmarks of the event */
18
+ export declare function getPublicBookmarks(bookmark: NostrEvent): Bookmarks;
14
19
  /** Returns the bookmarks of the event if its unlocked */
15
20
  export declare function getHiddenBookmarks(bookmark: NostrEvent): Bookmarks | undefined;
@@ -1,9 +1,11 @@
1
1
  import { kinds } from "nostr-tools";
2
- import { getAddressPointerFromATag, getEventPointerFromETag } from "./pointers.js";
3
2
  import { getOrComputeCachedValue } from "./cache.js";
4
- import { getHiddenTags } from "./index.js";
3
+ import { getHiddenTags, isHiddenTagsLocked } from "./index.js";
4
+ import { mergeAddressPointers, mergeEventPointers } from "./nip-19.js";
5
+ import { getAddressPointerFromATag, getCoordinateFromAddressPointer, getEventPointerFromETag } from "./pointers.js";
5
6
  export const BookmarkPublicSymbol = Symbol.for("bookmark-public");
6
7
  export const BookmarkHiddenSymbol = Symbol.for("bookmark-hidden");
8
+ /** Parses an array of tags into a {@link Bookmarks} object */
7
9
  export function parseBookmarkTags(tags) {
8
10
  const notes = tags.filter((t) => t[0] === "e" && t[1]).map(getEventPointerFromETag);
9
11
  const articles = tags
@@ -14,14 +16,57 @@ export function parseBookmarkTags(tags) {
14
16
  const urls = tags.filter((t) => t[0] === "r" && t[1]).map((t) => t[1]);
15
17
  return { notes, articles, hashtags, urls };
16
18
  }
17
- /** Returns the public bookmarks of the event */
19
+ /** Merges any number of {@link Bookmarks} objects */
20
+ export function mergeBookmarks(...bookmarks) {
21
+ const notes = new Map();
22
+ const articles = new Map();
23
+ const hashtags = new Set();
24
+ const urls = new Set();
25
+ for (const bookmark of bookmarks) {
26
+ if (!bookmark)
27
+ continue;
28
+ for (const note of bookmark.notes) {
29
+ const existing = notes.get(note.id);
30
+ if (existing)
31
+ notes.set(note.id, mergeEventPointers(existing, note));
32
+ else
33
+ notes.set(note.id, note);
34
+ }
35
+ for (const article of bookmark.articles) {
36
+ const coord = getCoordinateFromAddressPointer(article);
37
+ const existing = articles.get(coord);
38
+ if (existing)
39
+ articles.set(coord, mergeAddressPointers(existing, article));
40
+ else
41
+ articles.set(coord, article);
42
+ }
43
+ for (const hashtag of bookmark.hashtags)
44
+ hashtags.add(hashtag);
45
+ for (const url of bookmark.urls)
46
+ urls.add(url);
47
+ }
48
+ return {
49
+ notes: Array.from(notes.values()),
50
+ articles: Array.from(articles.values()),
51
+ hashtags: Array.from(hashtags),
52
+ urls: Array.from(urls),
53
+ };
54
+ }
55
+ /** Returns all the bookmarks of the event */
18
56
  export function getBookmarks(bookmark) {
57
+ const hidden = getHiddenBookmarks(bookmark);
58
+ if (hidden)
59
+ return mergeBookmarks(hidden, getPublicBookmarks(bookmark));
60
+ else
61
+ return getPublicBookmarks(bookmark);
62
+ }
63
+ /** Returns the public bookmarks of the event */
64
+ export function getPublicBookmarks(bookmark) {
19
65
  return getOrComputeCachedValue(bookmark, BookmarkPublicSymbol, () => parseBookmarkTags(bookmark.tags));
20
66
  }
21
67
  /** Returns the bookmarks of the event if its unlocked */
22
68
  export function getHiddenBookmarks(bookmark) {
23
- return getOrComputeCachedValue(bookmark, BookmarkHiddenSymbol, () => {
24
- const tags = getHiddenTags(bookmark);
25
- return tags && parseBookmarkTags(tags);
26
- });
69
+ if (isHiddenTagsLocked(bookmark))
70
+ return undefined;
71
+ return getOrComputeCachedValue(bookmark, BookmarkHiddenSymbol, () => parseBookmarkTags(getHiddenTags(bookmark)));
27
72
  }
@@ -2,20 +2,24 @@ import { NostrEvent } from "nostr-tools";
2
2
  import { ExternalPointer, ExternalIdentifiers } from "./external-id.js";
3
3
  export declare const COMMENT_KIND = 1111;
4
4
  export type CommentEventPointer = {
5
+ type: "event";
5
6
  id: string;
6
7
  kind: number;
7
8
  pubkey?: string;
8
9
  relay?: string;
9
10
  };
10
11
  export type CommentAddressPointer = {
12
+ type: "address";
11
13
  id?: string;
12
14
  kind: number;
13
15
  pubkey: string;
14
16
  identifier: string;
15
17
  relay?: string;
16
18
  };
17
- export type CommentExternalPointer = ExternalPointer<keyof ExternalIdentifiers>;
18
- export type CommentPointer = CommentEventPointer | CommentAddressPointer | CommentExternalPointer;
19
+ export type CommentExternalPointer<T extends keyof ExternalIdentifiers> = ExternalPointer<T> & {
20
+ type: "external";
21
+ };
22
+ export type CommentPointer = CommentEventPointer | CommentAddressPointer | CommentExternalPointer<keyof ExternalIdentifiers>;
19
23
  export declare const CommentRootPointerSymbol: unique symbol;
20
24
  export declare const CommentReplyPointerSymbol: unique symbol;
21
25
  /**
@@ -32,7 +36,7 @@ export declare function getCommentAddressPointer(tags: string[][], root?: boolea
32
36
  * Gets the ExternalPointer from an array of tags
33
37
  * @throws
34
38
  */
35
- export declare function getCommentExternalPointer(tags: string[][], root?: boolean): CommentExternalPointer | null;
39
+ export declare function getCommentExternalPointer(tags: string[][], root?: boolean): CommentExternalPointer<keyof ExternalIdentifiers> | null;
36
40
  /**
37
41
  * Returns the root pointer for a comment
38
42
  * @throws
@@ -18,6 +18,7 @@ export function getCommentEventPointer(tags, root = false) {
18
18
  // only the root pubkey can be gotten from the tags, since due to quotes and mentions there will be many "p" tags for replies
19
19
  const rootPubkey = root ? tags.find((t) => t[0] === "P")?.[1] : undefined;
20
20
  const pointer = {
21
+ type: "event",
21
22
  id: eTag[1],
22
23
  kind: parseInt(kind),
23
24
  pubkey: eTag[3] || rootPubkey || undefined,
@@ -40,6 +41,7 @@ export function getCommentAddressPointer(tags, root = false) {
40
41
  throw new Error("Missing kind tag");
41
42
  const addressPointer = getAddressPointerFromATag(aTag);
42
43
  const pointer = {
44
+ type: "address",
43
45
  id: eTag?.[1],
44
46
  pubkey: addressPointer.pubkey,
45
47
  identifier: addressPointer.identifier,
@@ -60,7 +62,10 @@ export function getCommentExternalPointer(tags, root = false) {
60
62
  if (iTag) {
61
63
  if (!kind)
62
64
  throw new Error("Missing kind tag");
63
- return getExternalPointerFromTag(iTag);
65
+ return {
66
+ type: "external",
67
+ ...getExternalPointerFromTag(iTag),
68
+ };
64
69
  }
65
70
  return null;
66
71
  }
@@ -1,3 +1,14 @@
1
1
  import { NostrEvent } from "nostr-tools";
2
+ import { ProfilePointer } from "nostr-tools/nip19";
2
3
  export declare const ContactsRelaysSymbol: unique symbol;
4
+ export declare const PublicContactsSymbol: unique symbol;
5
+ export declare const HiddenContactsSymbol: unique symbol;
3
6
  export declare function getRelaysFromContactsEvent(event: NostrEvent): Map<string, "all" | "inbox" | "outbox"> | null;
7
+ /** Merges any number of contact lists into a single list */
8
+ export declare function mergeContacts(...pointers: (ProfilePointer | undefined | (ProfilePointer | undefined)[])[]): ProfilePointer[];
9
+ /** Returns all public and hidden contacts from a contacts list event */
10
+ export declare function getContacts(event: NostrEvent): ProfilePointer[];
11
+ /** Returns only the public contacts from a contacts list event */
12
+ export declare function getPublicContacts(event: NostrEvent): ProfilePointer[];
13
+ /** Returns only the hidden contacts from a contacts list event */
14
+ export declare function getHiddenContacts(event: NostrEvent): ProfilePointer[] | undefined;
@@ -1,6 +1,11 @@
1
1
  import { getOrComputeCachedValue } from "./cache.js";
2
2
  import { isSafeRelayURL } from "./relays.js";
3
+ import { isPTag, processTags } from "./tags.js";
4
+ import { getProfilePointerFromPTag } from "./pointers.js";
5
+ import { getHiddenTags, isHiddenTagsLocked } from "./hidden-tags.js";
3
6
  export const ContactsRelaysSymbol = Symbol.for("contacts-relays");
7
+ export const PublicContactsSymbol = Symbol.for("public-contacts");
8
+ export const HiddenContactsSymbol = Symbol.for("hidden-contacts");
4
9
  export function getRelaysFromContactsEvent(event) {
5
10
  return getOrComputeCachedValue(event, ContactsRelaysSymbol, () => {
6
11
  try {
@@ -23,3 +28,32 @@ export function getRelaysFromContactsEvent(event) {
23
28
  }
24
29
  });
25
30
  }
31
+ /** Merges any number of contact lists into a single list */
32
+ export function mergeContacts(...pointers) {
33
+ const merged = new Map();
34
+ for (const arr of pointers) {
35
+ if (Array.isArray(arr)) {
36
+ for (const pointer of arr)
37
+ if (pointer)
38
+ merged.set(pointer.pubkey, pointer);
39
+ }
40
+ else if (arr) {
41
+ merged.set(arr.pubkey, arr);
42
+ }
43
+ }
44
+ return Array.from(merged.values());
45
+ }
46
+ /** Returns all public and hidden contacts from a contacts list event */
47
+ export function getContacts(event) {
48
+ return mergeContacts(getPublicContacts(event), getHiddenContacts(event));
49
+ }
50
+ /** Returns only the public contacts from a contacts list event */
51
+ export function getPublicContacts(event) {
52
+ return getOrComputeCachedValue(event, PublicContactsSymbol, () => processTags(event.tags, (t) => (isPTag(t) ? t : undefined), getProfilePointerFromPTag));
53
+ }
54
+ /** Returns only the hidden contacts from a contacts list event */
55
+ export function getHiddenContacts(event) {
56
+ if (isHiddenTagsLocked(event))
57
+ return undefined;
58
+ return getOrComputeCachedValue(event, HiddenContactsSymbol, () => processTags(getHiddenTags(event), (t) => (isPTag(t) ? t : undefined), getProfilePointerFromPTag));
59
+ }
@@ -24,11 +24,16 @@ export declare function isReplaceable(kind: number): boolean;
24
24
  /**
25
25
  * Returns the events Unique ID
26
26
  * For normal or ephemeral events this is ( event.id )
27
- * For replaceable events this is ( event.kind + ":" + event.pubkey )
28
- * For parametrized replaceable events this is ( event.kind + ":" + event.pubkey + ":" + event.tags.d.1 )
27
+ * For replaceable events this is ( event.kind + ":" + event.pubkey + ":" )
28
+ * For parametrized replaceable events this is ( event.kind + ":" + event.pubkey + ":" + event.tags.d )
29
29
  */
30
30
  export declare function getEventUID(event: NostrEvent): string;
31
- export declare function getReplaceableUID(kind: number, pubkey: string, d?: string): string;
31
+ /** Returns the replaceable event address for an addressable event */
32
+ export declare function getReplaceableAddress(event: NostrEvent): string;
33
+ /** Creates a replaceable event address from a kind, pubkey, and identifier */
34
+ export declare function createReplaceableAddress(kind: number, pubkey: string, identifier?: string): string;
35
+ /** @deprecated use createReplaceableAddress instead */
36
+ export declare const getReplaceableUID: typeof createReplaceableAddress;
32
37
  /** Returns a Set of tag names and values that are indexable */
33
38
  export declare function getIndexableTags(event: NostrEvent): Set<string>;
34
39
  /**
@@ -1,8 +1,8 @@
1
- import { kinds, verifiedSymbol } from "nostr-tools";
1
+ import { verifiedSymbol } from "nostr-tools";
2
2
  import { INDEXABLE_TAGS } from "../event-store/common.js";
3
3
  import { getHiddenTags } from "./hidden-tags.js";
4
4
  import { getOrComputeCachedValue } from "./cache.js";
5
- import { isParameterizedReplaceableKind } from "nostr-tools/kinds";
5
+ import { isAddressableKind, isReplaceableKind } from "nostr-tools/kinds";
6
6
  import { EventStoreSymbol } from "../event-store/event-store.js";
7
7
  export const EventUIDSymbol = Symbol.for("event-uid");
8
8
  export const EventIndexableTagsSymbol = Symbol.for("indexable-tags");
@@ -29,30 +29,38 @@ export function isEvent(event) {
29
29
  * or parameterized replaceable ( 30000 <= n < 40000 )
30
30
  */
31
31
  export function isReplaceable(kind) {
32
- return kinds.isReplaceableKind(kind) || kinds.isParameterizedReplaceableKind(kind);
32
+ return isReplaceableKind(kind) || isAddressableKind(kind);
33
33
  }
34
34
  /**
35
35
  * Returns the events Unique ID
36
36
  * For normal or ephemeral events this is ( event.id )
37
- * For replaceable events this is ( event.kind + ":" + event.pubkey )
38
- * For parametrized replaceable events this is ( event.kind + ":" + event.pubkey + ":" + event.tags.d.1 )
37
+ * For replaceable events this is ( event.kind + ":" + event.pubkey + ":" )
38
+ * For parametrized replaceable events this is ( event.kind + ":" + event.pubkey + ":" + event.tags.d )
39
39
  */
40
40
  export function getEventUID(event) {
41
41
  let uid = event[EventUIDSymbol];
42
42
  if (!uid) {
43
- if (isReplaceable(event.kind)) {
44
- let d = event.tags.find((t) => t[0] === "d")?.[1];
45
- uid = getReplaceableUID(event.kind, event.pubkey, d);
46
- }
43
+ if (isReplaceable(event.kind))
44
+ uid = getReplaceableAddress(event);
47
45
  else
48
46
  uid = event.id;
49
47
  event[EventUIDSymbol] = uid;
50
48
  }
51
49
  return uid;
52
50
  }
53
- export function getReplaceableUID(kind, pubkey, d) {
54
- return d ? kind + ":" + pubkey + ":" + d : kind + ":" + pubkey;
51
+ /** Returns the replaceable event address for an addressable event */
52
+ export function getReplaceableAddress(event) {
53
+ if (!isReplaceable(event.kind))
54
+ throw new Error("Event is not replaceable or addressable");
55
+ const identifier = isAddressableKind(event.kind) ? getReplaceableIdentifier(event) : undefined;
56
+ return createReplaceableAddress(event.kind, event.pubkey, identifier);
57
+ }
58
+ /** Creates a replaceable event address from a kind, pubkey, and identifier */
59
+ export function createReplaceableAddress(kind, pubkey, identifier) {
60
+ return kind + ":" + pubkey + ":" + (identifier ?? "");
55
61
  }
62
+ /** @deprecated use createReplaceableAddress instead */
63
+ export const getReplaceableUID = createReplaceableAddress;
56
64
  /** Returns a Set of tag names and values that are indexable */
57
65
  export function getIndexableTags(event) {
58
66
  let indexable = event[EventIndexableTagsSymbol];
@@ -100,8 +108,8 @@ export function getParentEventStore(event) {
100
108
  * @throws
101
109
  */
102
110
  export function getReplaceableIdentifier(event) {
103
- if (!isParameterizedReplaceableKind(event.kind))
104
- throw new Error("Event is not replaceable");
111
+ if (!isAddressableKind(event.kind))
112
+ throw new Error("Event is not addressable");
105
113
  return getOrComputeCachedValue(event, ReplaceableIdentifierSymbol, () => {
106
114
  const d = getTagValue(event, "d");
107
115
  if (d === undefined)
@@ -1,28 +1,56 @@
1
1
  import { AddressPointer, EventPointer, ProfilePointer } from "nostr-tools/nip19";
2
2
  import { NostrEvent } from "nostr-tools";
3
+ export declare const FAVORITE_RELAYS_KIND = 10012;
4
+ export type ReadListTags = "public" | "hidden" | "all";
5
+ /** Returns all the tags of a list or set */
6
+ export declare function getListTags(list: NostrEvent, type?: ReadListTags): string[][];
3
7
  /**
4
8
  * Checks if an event pointer is anywhere in a list or set
5
9
  * NOTE: Ignores the `relay` field in EventPointer
6
- * NOTE: This will check the hidden tags if the list has hidden tags and they are unlocked
10
+ * @param list - The list or set to check
11
+ * @param pointer - The event pointer to check
12
+ * @param type - Which types of tags to check
7
13
  */
8
- export declare function isEventPointerInList(list: NostrEvent, pointer: string | EventPointer): boolean;
14
+ export declare function isEventPointerInList(list: NostrEvent, pointer: string | EventPointer, type?: ReadListTags): boolean;
9
15
  /**
10
16
  * Checks if an address pointer is anywhere in a list or set
11
17
  * NOTE: Ignores the `relay` field in AddressPointer
12
- * NOTE: This will check the hidden tags if the list has hidden tags and they are unlocked
18
+ * @param list - The list or set to check
19
+ * @param pointer - The address pointer to check
20
+ * @param type - Which types of tags to check
13
21
  */
14
- export declare function isAddressPointerInList(list: NostrEvent, pointer: string | AddressPointer): boolean;
22
+ export declare function isAddressPointerInList(list: NostrEvent, pointer: string | AddressPointer, type?: ReadListTags): boolean;
15
23
  /**
16
24
  * Checks if an profile pointer is anywhere in a list or set
17
25
  * NOTE: Ignores the `relay` field in ProfilePointer
18
- * NOTE: This will check the hidden tags if the list has hidden tags and they are unlocked
26
+ * @param list - The list or set to check
27
+ * @param pointer - The profile pointer to check
28
+ * @param type - Which types of tags to check
19
29
  */
20
- export declare function isProfilePointerInList(list: NostrEvent, pointer: string | ProfilePointer): boolean;
21
- /** Returns all the EventPointer in a list or set */
22
- export declare function getEventPointersFromList(list: NostrEvent): EventPointer[];
23
- /** Returns all the AddressPointer in a list or set */
24
- export declare function getAddressPointersFromList(list: NostrEvent): AddressPointer[];
25
- /** Returns all the ProfilePointer in a list or set */
26
- export declare function getProfilePointersFromList(list: NostrEvent): ProfilePointer[];
30
+ export declare function isProfilePointerInList(list: NostrEvent, pointer: string | ProfilePointer, type?: ReadListTags): boolean;
31
+ /**
32
+ * Returns all the EventPointer in a list or set
33
+ * @param list - The list or set to get the event pointers from
34
+ * @param type - Which types of tags to read
35
+ */
36
+ export declare function getEventPointersFromList(list: NostrEvent, type?: ReadListTags): EventPointer[];
37
+ /**
38
+ * Returns all the AddressPointer in a list or set
39
+ * @param list - The list or set to get the address pointers from
40
+ * @param type - Which types of tags to read
41
+ */
42
+ export declare function getAddressPointersFromList(list: NostrEvent, type?: ReadListTags): AddressPointer[];
43
+ /**
44
+ * Returns all the ProfilePointer in a list or set
45
+ * @param list - The list or set to get the profile pointers from
46
+ * @param type - Which types of tags to read
47
+ */
48
+ export declare function getProfilePointersFromList(list: NostrEvent, type?: ReadListTags): ProfilePointer[];
49
+ /**
50
+ * Returns a deduplicated array of all 'relay' tags in a list or set
51
+ * @param list - The list or set to get the relays from
52
+ * @param type - Which types of tags to read
53
+ */
54
+ export declare function getRelaysFromList(list: NostrEvent, type?: ReadListTags): string[];
27
55
  /** Returns if an event is a valid list or set */
28
56
  export declare function isValidList(event: NostrEvent): boolean;