applesauce-core 0.0.0-next-20250428164231 → 0.0.0-next-20250428223834

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 (42) hide show
  1. package/README.md +28 -10
  2. package/package.json +1 -1
  3. package/dist/event-store/event-store.test.d.ts +0 -1
  4. package/dist/event-store/event-store.test.js +0 -74
  5. package/dist/helpers/blossom.test.d.ts +0 -1
  6. package/dist/helpers/blossom.test.js +0 -13
  7. package/dist/helpers/bookmark.d.ts +0 -15
  8. package/dist/helpers/bookmark.js +0 -27
  9. package/dist/helpers/event.test.d.ts +0 -1
  10. package/dist/helpers/event.test.js +0 -36
  11. package/dist/helpers/file-metadata.test.d.ts +0 -1
  12. package/dist/helpers/file-metadata.test.js +0 -103
  13. package/dist/helpers/hidden-tags.test.d.ts +0 -1
  14. package/dist/helpers/hidden-tags.test.js +0 -29
  15. package/dist/helpers/mailboxes.test.d.ts +0 -1
  16. package/dist/helpers/mailboxes.test.js +0 -81
  17. package/dist/helpers/media-attachment.d.ts +0 -42
  18. package/dist/helpers/media-attachment.js +0 -72
  19. package/dist/helpers/media-attachment.test.d.ts +0 -1
  20. package/dist/helpers/media-attachment.test.js +0 -59
  21. package/dist/helpers/media-post.d.ts +0 -4
  22. package/dist/helpers/media-post.js +0 -6
  23. package/dist/helpers/mute.d.ts +0 -14
  24. package/dist/helpers/mute.js +0 -23
  25. package/dist/helpers/pipe.d.ts +0 -10
  26. package/dist/helpers/pipe.js +0 -3
  27. package/dist/helpers/tags.test.d.ts +0 -1
  28. package/dist/helpers/tags.test.js +0 -24
  29. package/dist/helpers/threading.test.d.ts +0 -1
  30. package/dist/helpers/threading.test.js +0 -41
  31. package/dist/observable/get-value.d.ts +0 -3
  32. package/dist/observable/get-value.js +0 -14
  33. package/dist/observable/share-latest-value.d.ts +0 -6
  34. package/dist/observable/share-latest-value.js +0 -24
  35. package/dist/observable/simple-timeout.test.d.ts +0 -1
  36. package/dist/observable/simple-timeout.test.js +0 -34
  37. package/dist/queries/comment.d.ts +0 -4
  38. package/dist/queries/comment.js +0 -14
  39. package/dist/queries/lists.d.ts +0 -14
  40. package/dist/queries/lists.js +0 -24
  41. package/dist/query-store/query-store.test.d.ts +0 -1
  42. package/dist/query-store/query-store.test.js +0 -33
package/README.md CHANGED
@@ -1,37 +1,55 @@
1
1
  # applesauce-core
2
2
 
3
- AppleSauce Core is an interpretation layer for nostr clients, Push events into the in-memory [database](https://hzrd149.github.io/applesauce/typedoc/classes/Database.html) and get nicely formatted data out with [queries](https://hzrd149.github.io/applesauce/typedoc/modules/Queries)
3
+ AppleSauce is a collection of utilities for building reactive nostr applications. The core package provides an in-memory event database and reactive queries to help you build nostr UIs with less code.
4
4
 
5
- # Example
5
+ ## Key Components
6
+
7
+ - **Helpers**: Core utility methods for parsing and extracting data from nostr events
8
+ - **EventStore**: In-memory database for storing and subscribing to nostr events
9
+ - **QueryStore**: Manages queries and ensures efficient subscription handling
10
+ - **Queries**: Complex subscriptions for common nostr data patterns
11
+
12
+ ## Documentation
13
+
14
+ For detailed documentation and guides, visit:
15
+
16
+ - [Getting Started](https://hzrd149.github.io/applesauce/introduction/getting-started)
17
+ - [API Reference](https://hzrd149.github.io/applesauce/typedoc/)
18
+
19
+ ## Example
6
20
 
7
21
  ```js
8
22
  import { EventStore, QueryStore } from "applesauce-core";
9
23
  import { Relay } from "nostr-tools/relay";
10
24
 
11
- // The EventStore handles all the events
25
+ // Create a single EventStore instance for your app
12
26
  const eventStore = new EventStore();
13
27
 
14
- // The QueryStore handles queries and makes sure not to run multiple of the same query
28
+ // Create a QueryStore to manage subscriptions efficiently
15
29
  const queryStore = new QueryStore(eventStore);
16
30
 
17
- // Use nostr-tools or anything else to talk to relays
31
+ // Use any nostr library for relay connections (nostr-tools, ndk, nostrify, etc...)
18
32
  const relay = await Relay.connect("wss://relay.example.com");
19
33
 
20
- const sub = relay.subscribe([{ authors: ["266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5"] }], {
34
+ // Subscribe to events and add them to the store
35
+ const sub = relay.subscribe([{ authors: ["3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"] }], {
21
36
  onevent(event) {
22
37
  eventStore.add(event);
23
38
  },
24
39
  });
25
40
 
26
- // This will return an Observable<ProfileContent | undefined> of the parsed metadata
27
- const profile = queryStore.profile("266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5");
41
+ // Subscribe to profile changes using ProfileQuery
42
+ const profile = queryStore.createQuery(
43
+ ProfileQuery,
44
+ "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d",
45
+ );
28
46
 
29
47
  profile.subscribe((parsed) => {
30
48
  if (parsed) console.log(parsed);
31
49
  });
32
50
 
33
- // This will return an Observable<NostrEvent[]> of all kind 1 events sorted by created_at
34
- const timeline = queryStore.timeline({ kinds: [1] });
51
+ // Subscribe to a timeline of events
52
+ const timeline = queryStore.createQuery(TimelineQuery, { kinds: [1] });
35
53
 
36
54
  timeline.subscribe((events) => {
37
55
  console.log(events);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "applesauce-core",
3
- "version": "0.0.0-next-20250428164231",
3
+ "version": "0.0.0-next-20250428223834",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1 +0,0 @@
1
- export {};
@@ -1,74 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from "vitest";
2
- import { kinds } from "nostr-tools";
3
- import { EventStore } from "./event-store.js";
4
- import { addSeenRelay, getSeenRelays } from "../helpers/relays.js";
5
- let eventStore;
6
- beforeEach(() => {
7
- eventStore = new EventStore();
8
- });
9
- const event = {
10
- content: '{"name":"hzrd149","picture":"https://cdn.hzrd149.com/5ed3fe5df09a74e8c126831eac999364f9eb7624e2b86d521521b8021de20bdc.png","about":"JavaScript developer working on some nostr stuff\\n- noStrudel https://nostrudel.ninja/ \\n- Blossom https://github.com/hzrd149/blossom \\n- Applesauce https://hzrd149.github.io/applesauce/","website":"https://hzrd149.com","nip05":"_@hzrd149.com","lud16":"hzrd1499@minibits.cash","pubkey":"266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5","display_name":"hzrd149","displayName":"hzrd149","banner":""}',
11
- created_at: 1738362529,
12
- id: "e9df8d5898c4ccfbd21fcd59f3f48abb3ff0ab7259b19570e2f1756de1e9306b",
13
- kind: 0,
14
- pubkey: "266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5",
15
- sig: "465a47b93626a587bf81dadc2b306b8f713a62db31d6ce1533198e9ae1e665a6eaf376a03250bf9ffbb02eb9059c8eafbd37ae1092d05d215757575bd8357586",
16
- tags: [],
17
- };
18
- describe("add", () => {
19
- it("should return original event in case of duplicates", () => {
20
- const a = { ...event };
21
- expect(eventStore.add(a)).toBe(a);
22
- const b = { ...event };
23
- expect(eventStore.add(b)).toBe(a);
24
- const c = { ...event };
25
- expect(eventStore.add(c)).toBe(a);
26
- });
27
- it("should merge seen relays on duplicate events", () => {
28
- const a = { ...event };
29
- addSeenRelay(a, "wss://relay.a.com");
30
- eventStore.add(a);
31
- const b = { ...event };
32
- addSeenRelay(b, "wss://relay.b.com");
33
- eventStore.add(b);
34
- expect(eventStore.getEvent(event.id)).toBeDefined();
35
- expect([...getSeenRelays(eventStore.getEvent(event.id))]).toEqual(expect.arrayContaining(["wss://relay.a.com", "wss://relay.b.com"]));
36
- });
37
- it("should ignore deleted events", () => {
38
- const deleteEvent = {
39
- id: "delete event id",
40
- kind: kinds.EventDeletion,
41
- created_at: event.created_at + 100,
42
- pubkey: event.pubkey,
43
- tags: [["e", event.id]],
44
- sig: "this should be ignored for the test",
45
- content: "test",
46
- };
47
- // add delete event first
48
- eventStore.add(deleteEvent);
49
- // now event should be ignored
50
- eventStore.add(event);
51
- expect(eventStore.getEvent(event.id)).toBeUndefined();
52
- });
53
- });
54
- describe("verifyEvent", () => {
55
- it("should be called for all events added", () => {
56
- const verifyEvent = vi.fn().mockReturnValue(true);
57
- eventStore.verifyEvent = verifyEvent;
58
- eventStore.add(event);
59
- expect(verifyEvent).toHaveBeenCalledWith(event);
60
- });
61
- it("should not be called for duplicate events", () => {
62
- const verifyEvent = vi.fn().mockReturnValue(true);
63
- eventStore.verifyEvent = verifyEvent;
64
- const a = { ...event };
65
- eventStore.add(a);
66
- expect(verifyEvent).toHaveBeenCalledWith(a);
67
- const b = { ...event };
68
- eventStore.add(b);
69
- expect(verifyEvent).toHaveBeenCalledTimes(1);
70
- const c = { ...event };
71
- eventStore.add(c);
72
- expect(verifyEvent).toHaveBeenCalledTimes(1);
73
- });
74
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,13 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { areBlossomServersEqual } from "./blossom.js";
3
- describe("areBlossomServersEqual", () => {
4
- it("should ignore path", () => {
5
- expect(areBlossomServersEqual("https://cdn.server.com/pathname", "https://cdn.server.com")).toBe(true);
6
- });
7
- it("should not ignore protocol", () => {
8
- expect(areBlossomServersEqual("http://cdn.server.com", "https://cdn.server.com")).toBe(false);
9
- });
10
- it("should not ignore port", () => {
11
- expect(areBlossomServersEqual("http://cdn.server.com:4658", "https://cdn.server.com")).toBe(false);
12
- });
13
- });
@@ -1,15 +0,0 @@
1
- import { NostrEvent } from "nostr-tools";
2
- import { AddressPointer, EventPointer } from "nostr-tools/nip19";
3
- export declare const BookmarkPublicSymbol: unique symbol;
4
- export declare const BookmarkHiddenSymbol: unique symbol;
5
- export type Bookmarks = {
6
- notes: EventPointer[];
7
- articles: AddressPointer[];
8
- hashtags: string[];
9
- urls: string[];
10
- };
11
- export declare function parseBookmarkTags(tags: string[][]): Bookmarks;
12
- /** Returns the public bookmarks of the event */
13
- export declare function getBookmarks(bookmark: NostrEvent): Bookmarks;
14
- /** Returns the bookmarks of the event if its unlocked */
15
- export declare function getHiddenBookmarks(bookmark: NostrEvent): Bookmarks | undefined;
@@ -1,27 +0,0 @@
1
- import { kinds } from "nostr-tools";
2
- import { getAddressPointerFromATag, getEventPointerFromETag } from "./pointers.js";
3
- import { getOrComputeCachedValue } from "./cache.js";
4
- import { getHiddenTags } from "./index.js";
5
- export const BookmarkPublicSymbol = Symbol.for("bookmark-public");
6
- export const BookmarkHiddenSymbol = Symbol.for("bookmark-hidden");
7
- export function parseBookmarkTags(tags) {
8
- const notes = tags.filter((t) => t[0] === "e" && t[1]).map(getEventPointerFromETag);
9
- const articles = tags
10
- .filter((t) => t[0] === "a" && t[1])
11
- .map(getAddressPointerFromATag)
12
- .filter((addr) => addr.kind === kinds.LongFormArticle);
13
- const hashtags = tags.filter((t) => t[0] === "t" && t[1]).map((t) => t[1]);
14
- const urls = tags.filter((t) => t[0] === "r" && t[1]).map((t) => t[1]);
15
- return { notes, articles, hashtags, urls };
16
- }
17
- /** Returns the public bookmarks of the event */
18
- export function getBookmarks(bookmark) {
19
- return getOrComputeCachedValue(bookmark, BookmarkPublicSymbol, () => parseBookmarkTags(bookmark.tags));
20
- }
21
- /** Returns the bookmarks of the event if its unlocked */
22
- export function getHiddenBookmarks(bookmark) {
23
- return getOrComputeCachedValue(bookmark, BookmarkHiddenSymbol, () => {
24
- const tags = getHiddenTags(bookmark);
25
- return tags && parseBookmarkTags(tags);
26
- });
27
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,36 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { EventIndexableTagsSymbol, getIndexableTags, getTagValue } from "./event.js";
3
- const event = {
4
- content: "",
5
- created_at: 1732889913,
6
- id: "2d53511f321cc82dd13eedfb597c9fe834d12d271c10d8068e9d8cfb8f58d1b4",
7
- kind: 30000,
8
- pubkey: "266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5",
9
- sig: "e6a442487ef44a8a00ec1e0a852e547991fcd5cbf19aa1a4219fa65d6f41022675e0745207649f4b16fe9a6c5c7c3693dc3e13966ffa5b2891634867c874cf22",
10
- tags: [
11
- ["d", "qRxLhBbTfRlxsvKSu0iUl"],
12
- ["title", "Musicians"],
13
- ["client", "noStrudel", "31990:266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5:1686066542546"],
14
- ["p", "2842e34860c59dfacd5df48ba7a65065e6760d08c35f779553d83c2c2310b493"],
15
- ["p", "28ca019b78b494c25a9da2d645975a8501c7e99b11302e5cbe748ee593fcb2cc"],
16
- ["p", "f46192b8b9be1b43fc30ea27c7cb16210aede17252b3aa9692fbb3f2ba153199"],
17
- ],
18
- };
19
- describe("getIndexableTags", () => {
20
- it("should return a set of indexable tags for event", () => {
21
- expect(Array.from(getIndexableTags(event))).toEqual(expect.arrayContaining([
22
- "p:2842e34860c59dfacd5df48ba7a65065e6760d08c35f779553d83c2c2310b493",
23
- "p:28ca019b78b494c25a9da2d645975a8501c7e99b11302e5cbe748ee593fcb2cc",
24
- "p:f46192b8b9be1b43fc30ea27c7cb16210aede17252b3aa9692fbb3f2ba153199",
25
- ]));
26
- });
27
- it("should cache value on EventIndexableTagsSymbol", () => {
28
- getIndexableTags(event);
29
- expect(Reflect.has(event, EventIndexableTagsSymbol)).toBe(true);
30
- });
31
- });
32
- describe("getTagValue", () => {
33
- it("should return value of tag if present", () => {
34
- expect(getTagValue(event, "title")).toBe("Musicians");
35
- });
36
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,103 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { getFileMetadataFromImetaTag, parseFileMetadataTags } from "./file-metadata.js";
3
- describe("file metadata helpers", () => {
4
- describe("parseFileMetadataTags", () => {
5
- it("should parse a simple 1060 event", () => {
6
- const tags = [
7
- ["url", "https://image.nostr.build/30696696e57a2732d4e9f1b15ff4d4d4eaa64b759df6876863f436ff5d736eae.gif"],
8
- ["ox", "30696696e57a2732d4e9f1b15ff4d4d4eaa64b759df6876863f436ff5d736eae"],
9
- ["fallback", "https://media.tenor.com/wpvrkjn192gAAAAC/daenerys-targaryen.gif"],
10
- ["x", "77fcf42b2b720babcdbe686eff67273d8a68862d74a2672db672bc48439a3ea5"],
11
- ["m", "image/gif"],
12
- ["dim", "360x306"],
13
- ["bh", "L38zleNL00~W^kRj0L-p0KM_^kx]"],
14
- ["blurhash", "L38zleNL00~W^kRj0L-p0KM_^kx]"],
15
- [
16
- "thumb",
17
- "https://image.nostr.build/thumb/30696696e57a2732d4e9f1b15ff4d4d4eaa64b759df6876863f436ff5d736eae.gif",
18
- ],
19
- ["t", "gifbuddy"],
20
- ["summary", "Khaleesi call dragons Daenerys Targaryen"],
21
- ["alt", "a woman with blonde hair and a brooch on her shoulder"],
22
- [
23
- "thumb",
24
- "https://media.tenor.com/wpvrkjn192gAAAAx/daenerys-targaryen.webp",
25
- "5d92423664fc15874b1d26c70a05a541ec09b5c438bf157977a87c8e64b31463",
26
- ],
27
- [
28
- "image",
29
- "https://media.tenor.com/wpvrkjn192gAAAAe/daenerys-targaryen.png",
30
- "5d92423664fc15874b1d26c70a05a541ec09b5c438bf157977a87c8e64b31463",
31
- ],
32
- ];
33
- expect(parseFileMetadataTags(tags)).toEqual({
34
- url: "https://image.nostr.build/30696696e57a2732d4e9f1b15ff4d4d4eaa64b759df6876863f436ff5d736eae.gif",
35
- type: "image/gif",
36
- dimensions: "360x306",
37
- blurhash: "L38zleNL00~W^kRj0L-p0KM_^kx]",
38
- sha256: "77fcf42b2b720babcdbe686eff67273d8a68862d74a2672db672bc48439a3ea5",
39
- originalSha256: "30696696e57a2732d4e9f1b15ff4d4d4eaa64b759df6876863f436ff5d736eae",
40
- thumbnail: "https://media.tenor.com/wpvrkjn192gAAAAx/daenerys-targaryen.webp",
41
- image: "https://media.tenor.com/wpvrkjn192gAAAAe/daenerys-targaryen.png",
42
- summary: "Khaleesi call dragons Daenerys Targaryen",
43
- fallback: ["https://media.tenor.com/wpvrkjn192gAAAAC/daenerys-targaryen.gif"],
44
- alt: "a woman with blonde hair and a brooch on her shoulder",
45
- });
46
- });
47
- });
48
- describe("getFileMetadataFromImetaTag", () => {
49
- it("should parse simple imeta tag", () => {
50
- expect(getFileMetadataFromImetaTag([
51
- "imeta",
52
- "url https://blossom.primal.net/3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c.jpg",
53
- "x 3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c",
54
- "dim 1024x1024",
55
- "m image/jpeg",
56
- "blurhash ggH{Aws:RPWBRjaeay?^ozV@aeRjaej[$gt7kCofWVofkCrrofxuofa|ozbHx]s:tRofaet7ay",
57
- ])).toEqual({
58
- url: "https://blossom.primal.net/3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c.jpg",
59
- sha256: "3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c",
60
- dimensions: "1024x1024",
61
- type: "image/jpeg",
62
- blurhash: "ggH{Aws:RPWBRjaeay?^ozV@aeRjaej[$gt7kCofWVofkCrrofxuofa|ozbHx]s:tRofaet7ay",
63
- });
64
- });
65
- it("should parse thumbnail url", () => {
66
- expect(getFileMetadataFromImetaTag([
67
- "imeta",
68
- "url https://blossom.primal.net/3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c.jpg",
69
- "x 3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c",
70
- "dim 1024x1024",
71
- "m image/jpeg",
72
- "blurhash ggH{Aws:RPWBRjaeay?^ozV@aeRjaej[$gt7kCofWVofkCrrofxuofa|ozbHx]s:tRofaet7ay",
73
- "thumb https://exmaple.com/thumb.jpg",
74
- ])).toEqual({
75
- url: "https://blossom.primal.net/3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c.jpg",
76
- sha256: "3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c",
77
- dimensions: "1024x1024",
78
- type: "image/jpeg",
79
- blurhash: "ggH{Aws:RPWBRjaeay?^ozV@aeRjaej[$gt7kCofWVofkCrrofxuofa|ozbHx]s:tRofaet7ay",
80
- thumbnail: "https://exmaple.com/thumb.jpg",
81
- });
82
- });
83
- it("should parse multiple fallback urls", () => {
84
- expect(getFileMetadataFromImetaTag([
85
- "imeta",
86
- "url https://blossom.primal.net/3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c.jpg",
87
- "x 3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c",
88
- "dim 1024x1024",
89
- "m image/jpeg",
90
- "blurhash ggH{Aws:RPWBRjaeay?^ozV@aeRjaej[$gt7kCofWVofkCrrofxuofa|ozbHx]s:tRofaet7ay",
91
- "fallback https://exmaple.com/image2.jpg",
92
- "fallback https://exmaple.com/image3.jpg",
93
- ])).toEqual({
94
- url: "https://blossom.primal.net/3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c.jpg",
95
- sha256: "3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c",
96
- dimensions: "1024x1024",
97
- type: "image/jpeg",
98
- blurhash: "ggH{Aws:RPWBRjaeay?^ozV@aeRjaej[$gt7kCofWVofkCrrofxuofa|ozbHx]s:tRofaet7ay",
99
- fallback: ["https://exmaple.com/image2.jpg", "https://exmaple.com/image3.jpg"],
100
- });
101
- });
102
- });
103
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,29 +0,0 @@
1
- import { describe, beforeEach, it, expect } from "vitest";
2
- import { finalizeEvent, generateSecretKey, getPublicKey, kinds, nip04 } from "nostr-tools";
3
- import { getHiddenTags, unlockHiddenTags } from "./hidden-tags.js";
4
- import { unixNow } from "./time.js";
5
- const key = generateSecretKey();
6
- const pubkey = getPublicKey(key);
7
- const signer = {
8
- nip04: {
9
- encrypt: (pubkey, plaintext) => nip04.encrypt(key, pubkey, plaintext),
10
- decrypt: (pubkey, ciphertext) => nip04.decrypt(key, pubkey, ciphertext),
11
- },
12
- };
13
- describe("Private Lists", () => {
14
- describe("unlockHiddenTags", () => {
15
- let list;
16
- beforeEach(async () => {
17
- list = finalizeEvent({
18
- kind: kinds.Mutelist,
19
- created_at: unixNow(),
20
- content: await nip04.encrypt(key, pubkey, JSON.stringify([["p", "npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr"]])),
21
- tags: [],
22
- }, key);
23
- });
24
- it("should unlock hidden tags", async () => {
25
- await unlockHiddenTags(list, signer);
26
- expect(getHiddenTags(list)).toEqual(expect.arrayContaining([["p", "npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr"]]));
27
- });
28
- });
29
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,81 +0,0 @@
1
- import { describe, test, expect } from "vitest";
2
- import { getInboxes, getOutboxes } from "./mailboxes.js";
3
- const emptyEvent = {
4
- kind: 10002,
5
- content: "",
6
- tags: [],
7
- created_at: 0,
8
- sig: "",
9
- id: "",
10
- pubkey: "",
11
- };
12
- describe("Mailboxes", () => {
13
- describe("getInboxes", () => {
14
- test("should transform urls", () => {
15
- expect(Array.from(getInboxes({
16
- ...emptyEvent,
17
- tags: [["r", "wss://inbox.com"]],
18
- }))).toEqual(expect.arrayContaining(["wss://inbox.com/"]));
19
- });
20
- test("should remove bad urls", () => {
21
- expect(Array.from(getInboxes({
22
- ...emptyEvent,
23
- tags: [["r", "bad://inbox.com"]],
24
- }))).toHaveLength(0);
25
- expect(Array.from(getInboxes({
26
- ...emptyEvent,
27
- tags: [["r", "something that is not a url"]],
28
- }))).toHaveLength(0);
29
- expect(Array.from(getInboxes({
30
- ...emptyEvent,
31
- tags: [["r", "wss://inbox.com,wss://inbox.org"]],
32
- }))).toHaveLength(0);
33
- });
34
- test("without marker", () => {
35
- expect(Array.from(getInboxes({
36
- ...emptyEvent,
37
- tags: [["r", "wss://inbox.com/"]],
38
- }))).toEqual(expect.arrayContaining(["wss://inbox.com/"]));
39
- });
40
- test("with marker", () => {
41
- expect(Array.from(getInboxes({
42
- ...emptyEvent,
43
- tags: [["r", "wss://inbox.com/", "read"]],
44
- }))).toEqual(expect.arrayContaining(["wss://inbox.com/"]));
45
- });
46
- });
47
- describe("getOutboxes", () => {
48
- test("should transform urls", () => {
49
- expect(Array.from(getOutboxes({
50
- ...emptyEvent,
51
- tags: [["r", "wss://outbox.com"]],
52
- }))).toEqual(expect.arrayContaining(["wss://outbox.com/"]));
53
- });
54
- test("should remove bad urls", () => {
55
- expect(Array.from(getOutboxes({
56
- ...emptyEvent,
57
- tags: [["r", "bad://inbox.com"]],
58
- }))).toHaveLength(0);
59
- expect(Array.from(getOutboxes({
60
- ...emptyEvent,
61
- tags: [["r", "something that is not a url"]],
62
- }))).toHaveLength(0);
63
- expect(Array.from(getOutboxes({
64
- ...emptyEvent,
65
- tags: [["r", "wss://outbox.com,wss://inbox.org"]],
66
- }))).toHaveLength(0);
67
- });
68
- test("without marker", () => {
69
- expect(Array.from(getOutboxes({
70
- ...emptyEvent,
71
- tags: [["r", "wss://outbox.com/"]],
72
- }))).toEqual(expect.arrayContaining(["wss://outbox.com/"]));
73
- });
74
- test("with marker", () => {
75
- expect(Array.from(getOutboxes({
76
- ...emptyEvent,
77
- tags: [["r", "wss://outbox.com/", "write"]],
78
- }))).toEqual(expect.arrayContaining(["wss://outbox.com/"]));
79
- });
80
- });
81
- });
@@ -1,42 +0,0 @@
1
- import { NostrEvent } from "nostr-tools";
2
- export type MediaAttachment = {
3
- /** URL of the file */
4
- url: string;
5
- /** MIME type */
6
- type?: string;
7
- /** sha256 hash of the file */
8
- sha256?: string;
9
- /**
10
- * The original sha256 hash before the file was transformed
11
- * @deprecated
12
- */
13
- originalSha256?: string;
14
- /** size of the file in bytes */
15
- size?: number;
16
- /** size of file in pixels in the form <width>x<height> */
17
- dimensions?: string;
18
- /** magnet */
19
- magnet?: string;
20
- /** torrent infohash */
21
- infohash?: string;
22
- /** URL to a thumbnail */
23
- thumbnail?: string;
24
- /** URL to a preview image with the same dimensions */
25
- image?: string;
26
- /** summary */
27
- summary?: string;
28
- /** description for accessability */
29
- alt?: string;
30
- /** blurhash */
31
- blurhash?: string;
32
- /** fallback URLs */
33
- fallback?: string[];
34
- };
35
- /**
36
- * Parses a imeta tag into a {@link MediaAttachment}
37
- * @throws
38
- */
39
- export declare function parseMediaAttachmentTag(tag: string[]): MediaAttachment;
40
- export declare const MediaAttachmentsSymbol: unique symbol;
41
- /** Gets all the media attachments on an event */
42
- export declare function getMediaAttachments(event: NostrEvent): MediaAttachment[];
@@ -1,72 +0,0 @@
1
- import { getOrComputeCachedValue } from "./cache.js";
2
- /**
3
- * Parses a imeta tag into a {@link MediaAttachment}
4
- * @throws
5
- */
6
- export function parseMediaAttachmentTag(tag) {
7
- const parts = tag.slice(1);
8
- const fields = {};
9
- let fallback = undefined;
10
- for (const part of parts) {
11
- const match = part.match(/^(.+?)\s(.+)$/);
12
- if (match) {
13
- const [_, name, value] = match;
14
- switch (name) {
15
- case "fallback":
16
- fallback = fallback ? [...fallback, value] : [value];
17
- break;
18
- default:
19
- fields[name] = value;
20
- break;
21
- }
22
- }
23
- }
24
- if (!fields.url)
25
- throw new Error("Missing required url in attachment");
26
- const attachment = { url: fields.url, fallback };
27
- // parse size
28
- if (fields.size)
29
- attachment.size = parseInt(fields.size);
30
- // copy optional fields
31
- if (fields.m)
32
- attachment.type = fields.m;
33
- if (fields.x)
34
- attachment.sha256 = fields.x;
35
- if (fields.ox)
36
- attachment.originalSha256 = fields.ox;
37
- if (fields.dim)
38
- attachment.dimensions = fields.dim;
39
- if (fields.magnet)
40
- attachment.magnet = fields.magnet;
41
- if (fields.i)
42
- attachment.infohash = fields.i;
43
- if (fields.thumb)
44
- attachment.thumbnail = fields.thumb;
45
- if (fields.image)
46
- attachment.image = fields.image;
47
- if (fields.summary)
48
- attachment.summary = fields.summary;
49
- if (fields.alt)
50
- attachment.alt = fields.alt;
51
- if (fields.blurhash)
52
- attachment.blurhash = fields.blurhash;
53
- return attachment;
54
- }
55
- export const MediaAttachmentsSymbol = Symbol.for("media-attachments");
56
- /** Gets all the media attachments on an event */
57
- export function getMediaAttachments(event) {
58
- return getOrComputeCachedValue(event, MediaAttachmentsSymbol, () => {
59
- return event.tags
60
- .filter((t) => t[0] === "imeta")
61
- .map((tag) => {
62
- try {
63
- return parseMediaAttachmentTag(tag);
64
- }
65
- catch (error) {
66
- // ignore invalid attachments
67
- return undefined;
68
- }
69
- })
70
- .filter((a) => !!a);
71
- });
72
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,59 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { parseMediaAttachmentTag } from "./media-attachment.js";
3
- describe("media attachment helpers", () => {
4
- describe("parseMediaAttachmentTag", () => {
5
- it("should parse simple imeta tag", () => {
6
- expect(parseMediaAttachmentTag([
7
- "imeta",
8
- "url https://blossom.primal.net/3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c.jpg",
9
- "x 3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c",
10
- "dim 1024x1024",
11
- "m image/jpeg",
12
- "blurhash ggH{Aws:RPWBRjaeay?^ozV@aeRjaej[$gt7kCofWVofkCrrofxuofa|ozbHx]s:tRofaet7ay",
13
- ])).toEqual({
14
- url: "https://blossom.primal.net/3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c.jpg",
15
- sha256: "3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c",
16
- dimensions: "1024x1024",
17
- type: "image/jpeg",
18
- blurhash: "ggH{Aws:RPWBRjaeay?^ozV@aeRjaej[$gt7kCofWVofkCrrofxuofa|ozbHx]s:tRofaet7ay",
19
- });
20
- });
21
- it("should parse thumbnail url", () => {
22
- expect(parseMediaAttachmentTag([
23
- "imeta",
24
- "url https://blossom.primal.net/3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c.jpg",
25
- "x 3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c",
26
- "dim 1024x1024",
27
- "m image/jpeg",
28
- "blurhash ggH{Aws:RPWBRjaeay?^ozV@aeRjaej[$gt7kCofWVofkCrrofxuofa|ozbHx]s:tRofaet7ay",
29
- "thumb https://exmaple.com/thumb.jpg",
30
- ])).toEqual({
31
- url: "https://blossom.primal.net/3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c.jpg",
32
- sha256: "3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c",
33
- dimensions: "1024x1024",
34
- type: "image/jpeg",
35
- blurhash: "ggH{Aws:RPWBRjaeay?^ozV@aeRjaej[$gt7kCofWVofkCrrofxuofa|ozbHx]s:tRofaet7ay",
36
- thumbnail: "https://exmaple.com/thumb.jpg",
37
- });
38
- });
39
- it("should parse multiple fallback urls", () => {
40
- expect(parseMediaAttachmentTag([
41
- "imeta",
42
- "url https://blossom.primal.net/3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c.jpg",
43
- "x 3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c",
44
- "dim 1024x1024",
45
- "m image/jpeg",
46
- "blurhash ggH{Aws:RPWBRjaeay?^ozV@aeRjaej[$gt7kCofWVofkCrrofxuofa|ozbHx]s:tRofaet7ay",
47
- "fallback https://exmaple.com/image2.jpg",
48
- "fallback https://exmaple.com/image3.jpg",
49
- ])).toEqual({
50
- url: "https://blossom.primal.net/3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c.jpg",
51
- sha256: "3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c",
52
- dimensions: "1024x1024",
53
- type: "image/jpeg",
54
- blurhash: "ggH{Aws:RPWBRjaeay?^ozV@aeRjaej[$gt7kCofWVofkCrrofxuofa|ozbHx]s:tRofaet7ay",
55
- fallback: ["https://exmaple.com/image2.jpg", "https://exmaple.com/image3.jpg"],
56
- });
57
- });
58
- });
59
- });
@@ -1,4 +0,0 @@
1
- import { NostrEvent } from "nostr-tools";
2
- export declare const MEDIA_POST_KIND = 20;
3
- /** Return the media attachments from a kind 20 media post */
4
- export declare function getMediaPostAttachments(post: NostrEvent): import("./file-metadata.js").FileMetadata[];
@@ -1,6 +0,0 @@
1
- import { getMediaAttachments } from "./file-metadata.js";
2
- export const MEDIA_POST_KIND = 20;
3
- /** Return the media attachments from a kind 20 media post */
4
- export function getMediaPostAttachments(post) {
5
- return getMediaAttachments(post);
6
- }
@@ -1,14 +0,0 @@
1
- import { NostrEvent } from "nostr-tools";
2
- export declare const MutePublicSymbol: unique symbol;
3
- export declare const MuteHiddenSymbol: unique symbol;
4
- export type Mutes = {
5
- pubkeys: Set<string>;
6
- threads: Set<string>;
7
- hashtags: Set<string>;
8
- words: Set<string>;
9
- };
10
- export declare function parseMutedTags(tags: string[][]): Mutes;
11
- /** Returns muted things */
12
- export declare function getMutedThings(mute: NostrEvent): Mutes;
13
- /** Returns the hidden muted content if the event is unlocked */
14
- export declare function getHiddenMutedThings(mute: NostrEvent): Mutes | undefined;
@@ -1,23 +0,0 @@
1
- import { isETag, isPTag, isTTag } from "./tags.js";
2
- import { getOrComputeCachedValue } from "./cache.js";
3
- import { getHiddenTags } from "./hidden-tags.js";
4
- export const MutePublicSymbol = Symbol.for("mute-public");
5
- export const MuteHiddenSymbol = Symbol.for("mute-hidden");
6
- export function parseMutedTags(tags) {
7
- const pubkeys = new Set(tags.filter(isPTag).map((t) => t[1]));
8
- const threads = new Set(tags.filter(isETag).map((t) => t[1]));
9
- const hashtags = new Set(tags.filter(isTTag).map((t) => t[1].toLocaleLowerCase()));
10
- const words = new Set(tags.filter((t) => t[0] === "word" && t[1]).map((t) => t[1].toLocaleLowerCase()));
11
- return { pubkeys, threads, hashtags, words };
12
- }
13
- /** Returns muted things */
14
- export function getMutedThings(mute) {
15
- return getOrComputeCachedValue(mute, MutePublicSymbol, (e) => parseMutedTags(e.tags));
16
- }
17
- /** Returns the hidden muted content if the event is unlocked */
18
- export function getHiddenMutedThings(mute) {
19
- return getOrComputeCachedValue(mute, MuteHiddenSymbol, () => {
20
- const tags = getHiddenTags(mute);
21
- return tags && parseMutedTags(tags);
22
- });
23
- }
@@ -1,10 +0,0 @@
1
- export type Pipe = {
2
- <A>(a: A): A;
3
- <A, B>(a: A, ab: (a: A) => B): B;
4
- <A, B, C>(a: A, ab: (a: A) => B, bc: (b: B) => C): C;
5
- <A, B, C, D>(a: A, ab: (a: A) => B, bc: (b: B) => C, cd: (c: C) => D): D;
6
- <A, B, C, D, E>(a: A, ab: (a: A) => B, bc: (b: B) => C, cd: (c: C) => D, de: (d: D) => E): E;
7
- <A, B, C, D, E, F>(a: A, ab: (a: A) => B, bc: (b: B) => C, cd: (c: C) => D, de: (d: D) => E, ef: (e: E) => F): F;
8
- <A, B, C, D, E, F, G>(a: A, ab: (a: A) => B, bc: (b: B) => C, cd: (c: C) => D, de: (d: D) => E, ef: (e: E) => F, fg: (f: F) => G): G;
9
- };
10
- export declare const pipe: Pipe;
@@ -1,3 +0,0 @@
1
- export const pipe = (initial, ...fns) => {
2
- return fns.reduce((acc, fn) => fn(acc), initial);
3
- };
@@ -1 +0,0 @@
1
- export {};
@@ -1,24 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { isATag, isNameValueTag, processTags } from "./tags.js";
3
- import { getAddressPointerFromATag } from "./pointers.js";
4
- describe("isNameValueTag", () => {
5
- it("should return true if tag has at least two indexes", () => {
6
- expect(isNameValueTag(["a", "30000:pubkey:list"])).toBe(true);
7
- expect(isNameValueTag(["title", "article", "other-value"])).toBe(true);
8
- });
9
- it("should ignore tags without values", () => {
10
- expect(isNameValueTag(["a"])).toBe(false);
11
- expect(isNameValueTag(["title"])).toBe(false);
12
- });
13
- });
14
- describe("processTags", () => {
15
- it("should filter out errors", () => {
16
- expect(processTags([["a", "bad coordinate"], ["e"], ["a", "30000:pubkey:list"]], getAddressPointerFromATag)).toEqual([{ identifier: "list", kind: 30000, pubkey: "pubkey" }]);
17
- });
18
- it("should filter out undefined", () => {
19
- expect(processTags([["a", "bad coordinate"], ["e"], ["a", "30000:pubkey:list"]], (tag) => isATag(tag) ? tag : undefined)).toEqual([
20
- ["a", "bad coordinate"],
21
- ["a", "30000:pubkey:list"],
22
- ]);
23
- });
24
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,41 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { interpretThreadTags } from "./threading.js";
3
- describe("threading helpers", () => {
4
- describe("interpretThreadTags", () => {
5
- it("should handle legacy tags", () => {
6
- expect(interpretThreadTags([
7
- ["e", "root-id"],
8
- ["e", "reply-id"],
9
- ])).toEqual({ root: { a: undefined, e: ["e", "root-id"] }, reply: { a: undefined, e: ["e", "reply-id"] } });
10
- });
11
- it("should handle nip-10 tags", () => {
12
- expect(interpretThreadTags([
13
- ["e", "root-id", "relay", "root"],
14
- ["e", "reply-id", "relay", "reply"],
15
- ])).toEqual({
16
- root: { a: undefined, e: ["e", "root-id", "relay", "root"] },
17
- reply: { a: undefined, e: ["e", "reply-id", "relay", "reply"] },
18
- });
19
- });
20
- it("should ignore mention nip-10 tags", () => {
21
- expect(interpretThreadTags([
22
- ["e", "root-id", "relay", "root"],
23
- ["e", "mention-id", "relay", "mention"],
24
- ["e", "reply-id", "relay", "reply"],
25
- ])).toEqual({
26
- root: { a: undefined, e: ["e", "root-id", "relay", "root"] },
27
- reply: { a: undefined, e: ["e", "reply-id", "relay", "reply"] },
28
- });
29
- });
30
- it("should handle single nip-10 tags", () => {
31
- expect(interpretThreadTags([["e", "root-id", "relay", "root"]])).toEqual({
32
- root: { a: undefined, e: ["e", "root-id", "relay", "root"] },
33
- reply: { a: undefined, e: ["e", "root-id", "relay", "root"] },
34
- });
35
- expect(interpretThreadTags([["e", "reply-id", "relay", "reply"]])).toEqual({
36
- root: { a: undefined, e: ["e", "reply-id", "relay", "reply"] },
37
- reply: { a: undefined, e: ["e", "reply-id", "relay", "reply"] },
38
- });
39
- });
40
- });
41
- });
@@ -1,3 +0,0 @@
1
- import { Observable } from "rxjs";
2
- /** Subscribes and returns the observables current or next value */
3
- export declare function getValue<T>(observable: Observable<T>): T | Promise<T>;
@@ -1,14 +0,0 @@
1
- import { BehaviorSubject } from "rxjs";
2
- /** Subscribes and returns the observables current or next value */
3
- export function getValue(observable) {
4
- if (observable instanceof BehaviorSubject)
5
- return observable.value;
6
- if (Reflect.has(observable, "value"))
7
- return Reflect.get(observable, "value");
8
- return new Promise((res) => {
9
- const sub = observable.subscribe((v) => {
10
- res(v);
11
- sub.unsubscribe();
12
- });
13
- });
14
- }
@@ -1,6 +0,0 @@
1
- import { OperatorFunction } from "rxjs";
2
- /**
3
- * Creates an operator that adds a 'value' property and multiplexes the source
4
- * @param config Optional ShareConfig for customizing sharing behavior
5
- */
6
- export declare function shareLatestValue<T>(): OperatorFunction<T, T | undefined>;
@@ -1,24 +0,0 @@
1
- import { BehaviorSubject, share } from "rxjs";
2
- /**
3
- * Creates an operator that adds a 'value' property and multiplexes the source
4
- * @param config Optional ShareConfig for customizing sharing behavior
5
- */
6
- export function shareLatestValue() {
7
- return (source$) => source$.pipe(share({ connector: () => new BehaviorSubject(undefined) }));
8
- // return (source: Observable<T>): Observable<T> & { value: T | undefined } => {
9
- // // Create storage for latest value
10
- // let latestValue: T | undefined = undefined;
11
- // // Create shared source with value tracking
12
- // const shared$ = source.pipe(
13
- // tap((value) => {
14
- // latestValue = value;
15
- // }),
16
- // share(config),
17
- // );
18
- // // Add value property
19
- // Object.defineProperty(shared$, "value", {
20
- // get: () => latestValue,
21
- // });
22
- // return shared$ as Observable<T> & { value: T | undefined };
23
- // };
24
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,34 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { Observable, Subject, firstValueFrom } from "rxjs";
3
- import { simpleTimeout, TimeoutError } from "./simple-timeout.js";
4
- describe("simpleTimeout operator", () => {
5
- it("should throw TimeoutError after specified timeout period", async () => {
6
- const subject = new Subject();
7
- const obs = subject.pipe(simpleTimeout(10));
8
- const promise = firstValueFrom(obs);
9
- await expect(promise).rejects.toThrow(TimeoutError);
10
- await expect(promise).rejects.toThrow("Timeout");
11
- });
12
- it("should throw TimeoutError with custom message", async () => {
13
- const subject = new Subject();
14
- const customMessage = "Custom timeout message";
15
- const obs = subject.pipe(simpleTimeout(10, customMessage));
16
- const promise = firstValueFrom(obs);
17
- await expect(promise).rejects.toThrow(TimeoutError);
18
- await expect(promise).rejects.toThrow(customMessage);
19
- });
20
- it("should not throw when value emitted before timeout", async () => {
21
- const subject = new Subject();
22
- const obs = subject.pipe(simpleTimeout(1000));
23
- const promise = firstValueFrom(obs);
24
- subject.next("test value");
25
- await expect(promise).resolves.toBe("test value");
26
- });
27
- it("should complete without error when source emits non-null value before timeout", async () => {
28
- const source = new Observable((subscriber) => {
29
- subscriber.next("test value");
30
- });
31
- const result = await firstValueFrom(source.pipe(simpleTimeout(10)));
32
- expect(result).toBe("test value");
33
- });
34
- });
@@ -1,4 +0,0 @@
1
- import { NostrEvent } from "nostr-tools";
2
- import { Query } from "../query-store/index.js";
3
- /** Returns all NIP-22 comment replies for the event */
4
- export declare function CommentsQuery(parent: NostrEvent): Query<NostrEvent[]>;
@@ -1,14 +0,0 @@
1
- import { COMMENT_KIND, getEventUID } from "../helpers/index.js";
2
- import { isParameterizedReplaceableKind } from "nostr-tools/kinds";
3
- /** Returns all NIP-22 comment replies for the event */
4
- export function CommentsQuery(parent) {
5
- return {
6
- key: `${getEventUID(parent)}-comments`,
7
- run: (events) => {
8
- const filter = { kinds: [COMMENT_KIND], "#e": [parent.id] };
9
- if (isParameterizedReplaceableKind(parent.kind))
10
- filter["#a"] = [getEventUID(parent)];
11
- return events.timeline(filter);
12
- },
13
- };
14
- }
@@ -1,14 +0,0 @@
1
- import { AddressPointer } from "nostr-tools/nip19";
2
- import { Query } from "../query-store/query-store.js";
3
- /**
4
- * A query that returns all favorite relays for a pubkey
5
- * @param pubkey - The pubkey to get the favorite relays for
6
- * @param hidden - Whether to read hidden tags instead of public tags
7
- */
8
- export declare function FavoriteRelays(pubkey: string, hidden?: boolean): Query<string[] | undefined>;
9
- /**
10
- * A query that returns all favorite relay sets for a pubkey
11
- * @param pubkey - The pubkey to get the favorite relay sets for
12
- * @param hidden - Whether to read hidden tags instead of public tags
13
- */
14
- export declare function FavoriteRelaySets(pubkey: string, hidden?: boolean): Query<AddressPointer[] | undefined>;
@@ -1,24 +0,0 @@
1
- import { map } from "rxjs";
2
- import { FAVORITE_RELAYS_KIND, getAddressPointersFromList, getRelaysFromList } from "../helpers/lists.js";
3
- /**
4
- * A query that returns all favorite relays for a pubkey
5
- * @param pubkey - The pubkey to get the favorite relays for
6
- * @param hidden - Whether to read hidden tags instead of public tags
7
- */
8
- export function FavoriteRelays(pubkey, hidden = false) {
9
- return (events) => {
10
- return events.replaceable(FAVORITE_RELAYS_KIND, pubkey).pipe(map((e) => e && getRelaysFromList(e, hidden)));
11
- };
12
- }
13
- /**
14
- * A query that returns all favorite relay sets for a pubkey
15
- * @param pubkey - The pubkey to get the favorite relay sets for
16
- * @param hidden - Whether to read hidden tags instead of public tags
17
- */
18
- export function FavoriteRelaySets(pubkey, hidden = false) {
19
- return (events) => {
20
- return events
21
- .replaceable(FAVORITE_RELAYS_KIND, pubkey)
22
- .pipe(map((e) => e && getAddressPointersFromList(e, hidden)));
23
- };
24
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,33 +0,0 @@
1
- import { describe, it, expect, beforeEach } from "vitest";
2
- import { EventStore } from "../event-store/event-store.js";
3
- import { QueryStore } from "./query-store.js";
4
- import { ProfileQuery } from "../queries/profile.js";
5
- let eventStore;
6
- let queryStore;
7
- const event = {
8
- content: '{"name":"hzrd149","picture":"https://cdn.hzrd149.com/5ed3fe5df09a74e8c126831eac999364f9eb7624e2b86d521521b8021de20bdc.png","about":"JavaScript developer working on some nostr stuff\\n- noStrudel https://nostrudel.ninja/ \\n- Blossom https://github.com/hzrd149/blossom \\n- Applesauce https://hzrd149.github.io/applesauce/","website":"https://hzrd149.com","nip05":"_@hzrd149.com","lud16":"hzrd1499@minibits.cash","pubkey":"266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5","display_name":"hzrd149","displayName":"hzrd149","banner":""}',
9
- created_at: 1738362529,
10
- id: "e9df8d5898c4ccfbd21fcd59f3f48abb3ff0ab7259b19570e2f1756de1e9306b",
11
- kind: 0,
12
- pubkey: "266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5",
13
- sig: "465a47b93626a587bf81dadc2b306b8f713a62db31d6ce1533198e9ae1e665a6eaf376a03250bf9ffbb02eb9059c8eafbd37ae1092d05d215757575bd8357586",
14
- tags: [],
15
- };
16
- beforeEach(() => {
17
- eventStore = new EventStore();
18
- queryStore = new QueryStore(eventStore);
19
- });
20
- describe("executeQuery", () => {
21
- it("should resolve when value is already present", async () => {
22
- eventStore.add(event);
23
- expect(await queryStore.executeQuery(ProfileQuery, event.pubkey)).toEqual(expect.objectContaining({ name: "hzrd149" }));
24
- });
25
- it("should resolve when value is added to event store", async () => {
26
- const p = queryStore.executeQuery(ProfileQuery, event.pubkey);
27
- // delay adding the event
28
- setTimeout(() => {
29
- eventStore.add(event);
30
- }, 10);
31
- await expect(p).resolves.toEqual(expect.objectContaining({ name: "hzrd149" }));
32
- });
33
- });