applesauce-core 0.10.0 → 0.11.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 (120) hide show
  1. package/dist/__tests__/fixtures.d.ts +8 -0
  2. package/dist/__tests__/fixtures.js +20 -0
  3. package/dist/event-store/__tests__/event-store.test.js +259 -0
  4. package/dist/event-store/database.d.ts +6 -4
  5. package/dist/event-store/database.js +13 -7
  6. package/dist/event-store/event-store.d.ts +30 -16
  7. package/dist/event-store/event-store.js +248 -309
  8. package/dist/helpers/__tests__/blossom.test.js +13 -0
  9. package/dist/helpers/__tests__/comment.test.js +235 -0
  10. package/dist/helpers/__tests__/emoji.test.d.ts +1 -0
  11. package/dist/helpers/__tests__/emoji.test.js +15 -0
  12. package/dist/helpers/__tests__/event.test.d.ts +1 -0
  13. package/dist/helpers/__tests__/event.test.js +36 -0
  14. package/dist/helpers/__tests__/file-metadata.test.d.ts +1 -0
  15. package/dist/helpers/__tests__/file-metadata.test.js +103 -0
  16. package/dist/helpers/__tests__/hidden-tags.test.d.ts +1 -0
  17. package/dist/helpers/{hidden-tags.test.js → __tests__/hidden-tags.test.js} +2 -1
  18. package/dist/helpers/__tests__/mailboxes.test.d.ts +1 -0
  19. package/dist/helpers/{mailboxes.test.js → __tests__/mailboxes.test.js} +1 -1
  20. package/dist/helpers/__tests__/relays.test.d.ts +1 -0
  21. package/dist/helpers/__tests__/relays.test.js +21 -0
  22. package/dist/helpers/__tests__/tags.test.d.ts +1 -0
  23. package/dist/helpers/__tests__/tags.test.js +24 -0
  24. package/dist/helpers/__tests__/threading.test.d.ts +1 -0
  25. package/dist/helpers/{threading.test.js → __tests__/threading.test.js} +1 -1
  26. package/dist/helpers/blossom.d.ts +9 -0
  27. package/dist/helpers/blossom.js +22 -0
  28. package/dist/helpers/bookmarks.d.ts +15 -0
  29. package/dist/helpers/bookmarks.js +27 -0
  30. package/dist/helpers/channels.d.ts +10 -0
  31. package/dist/helpers/channels.js +27 -0
  32. package/dist/helpers/comment.d.ts +3 -4
  33. package/dist/helpers/comment.js +20 -16
  34. package/dist/helpers/contacts.d.ts +3 -0
  35. package/dist/helpers/contacts.js +25 -0
  36. package/dist/helpers/dns-identity.d.ts +7 -0
  37. package/dist/helpers/dns-identity.js +10 -0
  38. package/dist/helpers/emoji.d.ts +3 -1
  39. package/dist/helpers/emoji.js +2 -2
  40. package/dist/helpers/event.d.ts +8 -2
  41. package/dist/helpers/event.js +29 -11
  42. package/dist/helpers/file-metadata.d.ts +55 -0
  43. package/dist/helpers/file-metadata.js +99 -0
  44. package/dist/helpers/filter.d.ts +4 -0
  45. package/dist/helpers/filter.js +34 -1
  46. package/dist/helpers/groups.d.ts +24 -0
  47. package/dist/helpers/groups.js +39 -0
  48. package/dist/helpers/hidden-tags.d.ts +15 -15
  49. package/dist/helpers/hidden-tags.js +9 -31
  50. package/dist/helpers/index.d.ts +13 -1
  51. package/dist/helpers/index.js +13 -1
  52. package/dist/helpers/lists.d.ts +28 -0
  53. package/dist/helpers/lists.js +65 -0
  54. package/dist/helpers/mailboxes.js +16 -9
  55. package/dist/helpers/mutes.d.ts +14 -0
  56. package/dist/helpers/mutes.js +23 -0
  57. package/dist/helpers/picture-post.d.ts +4 -0
  58. package/dist/helpers/picture-post.js +6 -0
  59. package/dist/helpers/pointers.js +13 -17
  60. package/dist/helpers/profile.d.ts +6 -1
  61. package/dist/helpers/profile.js +4 -0
  62. package/dist/helpers/relays.d.ts +6 -3
  63. package/dist/helpers/relays.js +25 -18
  64. package/dist/helpers/share.d.ts +4 -0
  65. package/dist/helpers/share.js +12 -0
  66. package/dist/helpers/tags.d.ts +17 -0
  67. package/dist/helpers/tags.js +28 -6
  68. package/dist/helpers/threading.js +3 -3
  69. package/dist/helpers/url.d.ts +7 -0
  70. package/dist/helpers/url.js +27 -0
  71. package/dist/helpers/user-status.d.ts +18 -0
  72. package/dist/helpers/user-status.js +21 -0
  73. package/dist/observable/__tests__/claim-events.test.d.ts +1 -0
  74. package/dist/observable/__tests__/claim-events.test.js +23 -0
  75. package/dist/observable/__tests__/claim-latest.test.d.ts +1 -0
  76. package/dist/observable/__tests__/claim-latest.test.js +37 -0
  77. package/dist/observable/__tests__/simple-timeout.test.d.ts +1 -0
  78. package/dist/observable/__tests__/simple-timeout.test.js +34 -0
  79. package/dist/observable/claim-events.d.ts +5 -0
  80. package/dist/observable/claim-events.js +28 -0
  81. package/dist/observable/claim-latest.d.ts +4 -0
  82. package/dist/observable/claim-latest.js +20 -0
  83. package/dist/observable/{get-value.d.ts → get-observable-value.d.ts} +1 -1
  84. package/dist/observable/{get-value.js → get-observable-value.js} +3 -8
  85. package/dist/observable/index.d.ts +2 -1
  86. package/dist/observable/index.js +2 -1
  87. package/dist/observable/share-latest-value.d.ts +2 -4
  88. package/dist/observable/share-latest-value.js +19 -16
  89. package/dist/observable/simple-timeout.d.ts +4 -0
  90. package/dist/observable/simple-timeout.js +6 -0
  91. package/dist/queries/blossom.d.ts +2 -0
  92. package/dist/queries/blossom.js +10 -0
  93. package/dist/queries/bookmarks.d.ts +8 -0
  94. package/dist/queries/bookmarks.js +23 -0
  95. package/dist/queries/channels.d.ts +11 -0
  96. package/dist/queries/channels.js +73 -0
  97. package/dist/queries/contacts.d.ts +3 -0
  98. package/dist/queries/contacts.js +12 -0
  99. package/dist/queries/index.d.ts +6 -0
  100. package/dist/queries/index.js +6 -0
  101. package/dist/queries/mutes.d.ts +8 -0
  102. package/dist/queries/mutes.js +23 -0
  103. package/dist/queries/pins.d.ts +3 -0
  104. package/dist/queries/pins.js +12 -0
  105. package/dist/queries/simple.d.ts +2 -2
  106. package/dist/queries/thread.js +1 -1
  107. package/dist/queries/user-status.d.ts +11 -0
  108. package/dist/queries/user-status.js +39 -0
  109. package/dist/query-store/index.d.ts +1 -57
  110. package/dist/query-store/index.js +1 -66
  111. package/dist/query-store/query-store.d.ts +51 -0
  112. package/dist/query-store/query-store.js +88 -0
  113. package/dist/query-store/query-store.test.d.ts +1 -0
  114. package/dist/query-store/query-store.test.js +33 -0
  115. package/package.json +19 -8
  116. package/dist/helpers/media-attachment.d.ts +0 -33
  117. package/dist/helpers/media-attachment.js +0 -60
  118. /package/dist/{helpers/hidden-tags.test.d.ts → event-store/__tests__/event-store.test.d.ts} +0 -0
  119. /package/dist/helpers/{mailboxes.test.d.ts → __tests__/blossom.test.d.ts} +0 -0
  120. /package/dist/helpers/{threading.test.d.ts → __tests__/comment.test.d.ts} +0 -0
@@ -1,6 +1,6 @@
1
1
  import { getAddressPointerFromATag } from "./pointers.js";
2
2
  import { getOrComputeCachedValue } from "./cache.js";
3
- import { safeRelayUrls } from "./relays.js";
3
+ import { isSafeRelayURL } from "./relays.js";
4
4
  export const Nip10ThreadRefsSymbol = Symbol.for("nip10-thread-refs");
5
5
  /**
6
6
  * Gets an EventPointer form a NIP-10 threading "e" tag
@@ -10,8 +10,8 @@ export function getEventPointerFromThreadTag(tag) {
10
10
  if (!tag[1])
11
11
  throw new Error("Missing event id in tag");
12
12
  let pointer = { id: tag[1] };
13
- if (tag[2])
14
- pointer.relays = safeRelayUrls([tag[2]]);
13
+ if (tag[2] && isSafeRelayURL(tag[2]))
14
+ pointer.relays = [tag[2]];
15
15
  // get author from NIP-18 quote tags, nip-22 comments tags, or nip-10 thread tags
16
16
  if (tag[0] === "e" &&
17
17
  (tag[3] === "root" || tag[3] === "reply" || tag[3] === "mention") &&
@@ -12,3 +12,10 @@ export declare function isVideoURL(url: string | URL): boolean;
12
12
  export declare function isStreamURL(url: string | URL): boolean;
13
13
  /** Checks if a url is a audio URL */
14
14
  export declare function isAudioURL(url: string | URL): boolean;
15
+ /** Tests if two URLs are the same */
16
+ export declare function isSameURL(a: string | URL, b: string | URL): boolean;
17
+ /**
18
+ * Normalizes a string into a relay URL
19
+ * Does not remove the trailing slash
20
+ */
21
+ export declare function normalizeURL<T extends string | URL>(url: T): T;
@@ -28,3 +28,30 @@ export function isAudioURL(url) {
28
28
  const filename = getURLFilename(url);
29
29
  return !!filename && AUDIO_EXT.some((ext) => filename.endsWith(ext));
30
30
  }
31
+ /** Tests if two URLs are the same */
32
+ export function isSameURL(a, b) {
33
+ try {
34
+ a = normalizeURL(a);
35
+ b = normalizeURL(b);
36
+ return a === b;
37
+ }
38
+ catch (error) {
39
+ return false;
40
+ }
41
+ }
42
+ /**
43
+ * Normalizes a string into a relay URL
44
+ * Does not remove the trailing slash
45
+ */
46
+ export function normalizeURL(url) {
47
+ let p = new URL(url);
48
+ // remove any double slashes
49
+ p.pathname = p.pathname.replace(/\/+/g, "/");
50
+ // drop the port if its not needed
51
+ if ((p.port === "80" && (p.protocol === "ws:" || p.protocol === "http:")) ||
52
+ (p.port === "443" && (p.protocol === "wss:" || p.protocol === "https:")))
53
+ p.port = "";
54
+ // return a string if a string was passed in
55
+ // @ts-expect-error
56
+ return typeof url === "string" ? p.toString() : p;
57
+ }
@@ -0,0 +1,18 @@
1
+ import { NostrEvent } from "nostr-tools";
2
+ import { AddressPointer, EventPointer, ProfilePointer } from "nostr-tools/nip19";
3
+ export declare const UserStatusPointerSymbol: unique symbol;
4
+ export type UserStatusPointer = {
5
+ type: "nevent";
6
+ data: EventPointer;
7
+ } | {
8
+ type: "nprofile";
9
+ data: ProfilePointer;
10
+ } | {
11
+ type: "naddr";
12
+ data: AddressPointer;
13
+ } | {
14
+ type: "url";
15
+ data: string;
16
+ };
17
+ /** Gets the {@link UserStatusPointer} for a status event */
18
+ export declare function getUserStatusPointer(status: NostrEvent): UserStatusPointer | null;
@@ -0,0 +1,21 @@
1
+ import { getAddressPointerFromATag, getEventPointerFromETag, getOrComputeCachedValue, getProfilePointerFromPTag, } from "applesauce-core/helpers";
2
+ export const UserStatusPointerSymbol = Symbol.for("user-status-pointer");
3
+ function getStatusPointer(status) {
4
+ const pTag = status.tags.find((t) => t[0] === "p" && t[1]);
5
+ if (pTag)
6
+ return { type: "nprofile", data: getProfilePointerFromPTag(pTag) };
7
+ const eTag = status.tags.find((t) => t[0] === "e" && t[1]);
8
+ if (eTag)
9
+ return { type: "nevent", data: getEventPointerFromETag(eTag) };
10
+ const aTag = status.tags.find((t) => t[0] === "a" && t[1]);
11
+ if (aTag)
12
+ return { type: "naddr", data: getAddressPointerFromATag(aTag) };
13
+ const rTag = status.tags.find((t) => t[0] === "r" && t[1]);
14
+ if (rTag)
15
+ return { type: "url", data: rTag[1] };
16
+ return null;
17
+ }
18
+ /** Gets the {@link UserStatusPointer} for a status event */
19
+ export function getUserStatusPointer(status) {
20
+ return getOrComputeCachedValue(status, UserStatusPointerSymbol, () => getStatusPointer(status));
21
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,23 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { Database } from "../../event-store/database.js";
3
+ import { claimEvents } from "../claim-events.js";
4
+ const event = {
5
+ 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":""}',
6
+ created_at: 1738362529,
7
+ id: "e9df8d5898c4ccfbd21fcd59f3f48abb3ff0ab7259b19570e2f1756de1e9306b",
8
+ kind: 0,
9
+ pubkey: "266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5",
10
+ relays: [""],
11
+ sig: "465a47b93626a587bf81dadc2b306b8f713a62db31d6ce1533198e9ae1e665a6eaf376a03250bf9ffbb02eb9059c8eafbd37ae1092d05d215757575bd8357586",
12
+ tags: [],
13
+ };
14
+ describe("claimEvents", () => {
15
+ it("it should claim events", () => {
16
+ const database = new Database();
17
+ const sub = database.inserted.pipe(claimEvents(database)).subscribe();
18
+ database.addEvent(event);
19
+ expect(database.isClaimed(event)).toBe(true);
20
+ sub.unsubscribe();
21
+ expect(database.isClaimed(event)).toBe(false);
22
+ });
23
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,37 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { Database } from "../../event-store/database.js";
3
+ import { claimLatest } from "../claim-latest.js";
4
+ const event1 = {
5
+ 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":""}',
6
+ created_at: 1738362529,
7
+ id: "e9df8d5898c4ccfbd21fcd59f3f48abb3ff0ab7259b19570e2f1756de1e9306b",
8
+ kind: 0,
9
+ pubkey: "266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5",
10
+ relays: [""],
11
+ sig: "465a47b93626a587bf81dadc2b306b8f713a62db31d6ce1533198e9ae1e665a6eaf376a03250bf9ffbb02eb9059c8eafbd37ae1092d05d215757575bd8357586",
12
+ tags: [],
13
+ };
14
+ const event2 = {
15
+ content: '{"name":"Cesar Dias","website":"dev.nosotros.app","picture":"https://nostr.build/i/5b0e4387b0fdfff9897ee7f8dcc554761fe377583a5fb71bbf3b915e7c4971c2.jpg","display_name":"Cesar Dias","nip05":"_@nosotros.app","lud16":"cesardias@getalby.com","about":"Developer 🇧🇷, building a client https://dev.nosotros.app and nostr-editor https://github.com/cesardeazevedo/nostr-editor","banner":"https://image.nostr.build/87dbc55a6391d15bddda206561d53867a5679dd95e84fe8ed62bfe2e3adcadf3.jpg\\",\\"ox 87dbc55a6391d15bddda206561d53867a5679dd95e84fe8ed62bfe2e3adcadf3"}',
16
+ created_at: 1727998492,
17
+ id: "c771fe19ac255ea28690c5547258a5e146d2f47805f7f48093b773478bdd137c",
18
+ kind: 0,
19
+ pubkey: "c6603b0f1ccfec625d9c08b753e4f774eaf7d1cf2769223125b5fd4da728019e",
20
+ relays: [""],
21
+ sig: "5220d6a8cdb4837b2569c26a84a2ac6a44427a224cb1602c05c578c6a63fe122a37e16455b09cb38bf297fc8161a8e715d7b444d017624c044d87a77e092c881",
22
+ tags: [["alt", "User profile for Cesar Dias"]],
23
+ };
24
+ describe("claimLatest", () => {
25
+ it("it should claim events", () => {
26
+ const database = new Database();
27
+ const sub = database.inserted.pipe(claimLatest(database)).subscribe();
28
+ database.addEvent(event1);
29
+ expect(database.isClaimed(event1)).toBe(true);
30
+ database.addEvent(event2);
31
+ expect(database.isClaimed(event1)).toBe(false);
32
+ expect(database.isClaimed(event2)).toBe(true);
33
+ sub.unsubscribe();
34
+ expect(database.isClaimed(event1)).toBe(false);
35
+ expect(database.isClaimed(event2)).toBe(false);
36
+ });
37
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,34 @@
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
+ });
@@ -0,0 +1,5 @@
1
+ import { MonoTypeOperatorFunction } from "rxjs";
2
+ import { NostrEvent } from "nostr-tools";
3
+ import { Database } from "../event-store/database.js";
4
+ /** keep a claim on any event that goes through this observable, claims are removed when the observable completes */
5
+ export declare function claimEvents<T extends NostrEvent[] | NostrEvent | undefined>(database: Database): MonoTypeOperatorFunction<T>;
@@ -0,0 +1,28 @@
1
+ import { finalize, tap } from "rxjs";
2
+ /** keep a claim on any event that goes through this observable, claims are removed when the observable completes */
3
+ export function claimEvents(database) {
4
+ return (source) => {
5
+ const seen = new Set();
6
+ return source.pipe(
7
+ // claim all events
8
+ tap((message) => {
9
+ if (message === undefined)
10
+ return;
11
+ if (Array.isArray(message)) {
12
+ for (const event of message) {
13
+ seen.add(event);
14
+ database.claimEvent(event, source);
15
+ }
16
+ }
17
+ else {
18
+ seen.add(message);
19
+ database.claimEvent(message, source);
20
+ }
21
+ }),
22
+ // remove claims on cleanup
23
+ finalize(() => {
24
+ for (const e of seen)
25
+ database.removeClaim(e, source);
26
+ }));
27
+ };
28
+ }
@@ -0,0 +1,4 @@
1
+ import { MonoTypeOperatorFunction } from "rxjs";
2
+ import { NostrEvent } from "nostr-tools";
3
+ import { Database } from "../event-store/database.js";
4
+ export declare function claimLatest(database: Database): MonoTypeOperatorFunction<NostrEvent | undefined>;
@@ -0,0 +1,20 @@
1
+ import { finalize, tap } from "rxjs";
2
+ export function claimLatest(database) {
3
+ return (source) => {
4
+ let latest = undefined;
5
+ return source.pipe(tap((event) => {
6
+ // remove old claim
7
+ if (latest)
8
+ database.removeClaim(latest, source);
9
+ // claim new event
10
+ if (event)
11
+ database.claimEvent(event, source);
12
+ // update state
13
+ latest = event;
14
+ }), finalize(() => {
15
+ // late claim
16
+ if (latest)
17
+ database.removeClaim(latest, source);
18
+ }));
19
+ };
20
+ }
@@ -1,3 +1,3 @@
1
1
  import { Observable } from "rxjs";
2
2
  /** Subscribes and returns the observables current or next value */
3
- export declare function getValue<T>(observable: Observable<T>): T | Promise<T>;
3
+ export declare function getObservableValue<T>(observable: Observable<T>): T | Promise<T>;
@@ -1,14 +1,9 @@
1
- import { BehaviorSubject } from "rxjs";
1
+ import { BehaviorSubject, firstValueFrom } from "rxjs";
2
2
  /** Subscribes and returns the observables current or next value */
3
- export function getValue(observable) {
3
+ export function getObservableValue(observable) {
4
4
  if (observable instanceof BehaviorSubject)
5
5
  return observable.value;
6
6
  if (Reflect.has(observable, "value"))
7
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
- });
8
+ return firstValueFrom(observable);
14
9
  }
@@ -1,2 +1,3 @@
1
- export * from "./get-value.js";
1
+ export * from "./get-observable-value.js";
2
2
  export * from "./share-latest-value.js";
3
+ export * from "./simple-timeout.js";
@@ -1,2 +1,3 @@
1
- export * from "./get-value.js";
1
+ export * from "./get-observable-value.js";
2
2
  export * from "./share-latest-value.js";
3
+ export * from "./simple-timeout.js";
@@ -1,8 +1,6 @@
1
- import { Observable, ShareConfig } from "rxjs";
1
+ import { OperatorFunction } from "rxjs";
2
2
  /**
3
3
  * Creates an operator that adds a 'value' property and multiplexes the source
4
4
  * @param config Optional ShareConfig for customizing sharing behavior
5
5
  */
6
- export declare function shareLatestValue<T>(config?: ShareConfig<T>): (source: Observable<T>) => Observable<T> & {
7
- value: T | undefined;
8
- };
6
+ export declare function shareLatestValue<T>(): OperatorFunction<T, T | undefined>;
@@ -1,21 +1,24 @@
1
- import { share } from "rxjs";
2
- import { tap } from "rxjs/operators";
1
+ import { BehaviorSubject, share } from "rxjs";
3
2
  /**
4
3
  * Creates an operator that adds a 'value' property and multiplexes the source
5
4
  * @param config Optional ShareConfig for customizing sharing behavior
6
5
  */
7
- export function shareLatestValue(config = {}) {
8
- return (source) => {
9
- // Create storage for latest value
10
- let latestValue = undefined;
11
- // Create shared source with value tracking
12
- const shared$ = source.pipe(tap((value) => {
13
- latestValue = value;
14
- }), share(config));
15
- // Add value property
16
- Object.defineProperty(shared$, "value", {
17
- get: () => latestValue,
18
- });
19
- return shared$;
20
- };
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
+ // };
21
24
  }
@@ -0,0 +1,4 @@
1
+ import { MonoTypeOperatorFunction } from "rxjs";
2
+ export declare class TimeoutError extends Error {
3
+ }
4
+ export declare function simpleTimeout<T extends unknown>(first: number, message?: string): MonoTypeOperatorFunction<T>;
@@ -0,0 +1,6 @@
1
+ import { throwError, timeout } from "rxjs";
2
+ export class TimeoutError extends Error {
3
+ }
4
+ export function simpleTimeout(first, message) {
5
+ return timeout({ first, with: () => throwError(() => new TimeoutError(message ?? "Timeout")) });
6
+ }
@@ -0,0 +1,2 @@
1
+ import { Query } from "../query-store/index.js";
2
+ export declare function UserBlossomServersQuery(pubkey: string): Query<URL[] | undefined>;
@@ -0,0 +1,10 @@
1
+ import { map } from "rxjs/operators";
2
+ import { BLOSSOM_SERVER_LIST_KIND, getBlossomServersFromList } from "../helpers/blossom.js";
3
+ export function UserBlossomServersQuery(pubkey) {
4
+ return {
5
+ key: pubkey,
6
+ run: (store) => store
7
+ .replaceable(BLOSSOM_SERVER_LIST_KIND, pubkey)
8
+ .pipe(map((event) => event && getBlossomServersFromList(event))),
9
+ };
10
+ }
@@ -0,0 +1,8 @@
1
+ import { Bookmarks } from "../helpers/bookmarks.js";
2
+ import { Query } from "../query-store/index.js";
3
+ export declare function UserBookmarkQuery(pubkey: string): Query<Bookmarks | undefined>;
4
+ export declare function UserHiddenBookmarkQuery(pubkey: string): Query<(Bookmarks & {
5
+ locked: false;
6
+ }) | {
7
+ locked: true;
8
+ } | undefined>;
@@ -0,0 +1,23 @@
1
+ import { kinds } from "nostr-tools";
2
+ import { map } from "rxjs/operators";
3
+ import { isHiddenTagsLocked } from "../helpers/index.js";
4
+ import { getBookmarks, getHiddenBookmarks } from "../helpers/bookmarks.js";
5
+ export function UserBookmarkQuery(pubkey) {
6
+ return {
7
+ key: pubkey,
8
+ run: (store) => store.replaceable(kinds.Mutelist, pubkey).pipe(map((event) => event && getBookmarks(event))),
9
+ };
10
+ }
11
+ export function UserHiddenBookmarkQuery(pubkey) {
12
+ return {
13
+ key: pubkey,
14
+ run: (store) => store.replaceable(kinds.Mutelist, pubkey).pipe(map((event) => {
15
+ if (!event)
16
+ return undefined;
17
+ const bookmarks = getHiddenBookmarks(event);
18
+ if (isHiddenTagsLocked(event) || !bookmarks)
19
+ return { locked: true };
20
+ return { locked: false, ...bookmarks };
21
+ })),
22
+ };
23
+ }
@@ -0,0 +1,11 @@
1
+ import { Query } from "applesauce-core";
2
+ import { NostrEvent } from "nostr-tools";
3
+ import { ChannelMetadataContent } from "../helpers/channels.js";
4
+ /** A query that returns a map of hidden messages Map<id, reason> */
5
+ export declare function ChannelHiddenQuery(channel: NostrEvent, authors?: string[]): Query<Map<string, string>>;
6
+ /** A query that returns all messages in a channel */
7
+ export declare function ChannelMessagesQuery(channel: NostrEvent): Query<NostrEvent[]>;
8
+ /** A query that returns the latest parsed metadata */
9
+ export declare function ChannelMetadataQuery(channel: NostrEvent): Query<ChannelMetadataContent | undefined>;
10
+ /** A query that returns a map of muted users Map<pubkey, reason> */
11
+ export declare function ChannelMutedQuery(channel: NostrEvent, authors?: string[]): Query<Map<string, string>>;
@@ -0,0 +1,73 @@
1
+ import { safeParse } from "applesauce-core/helpers/json";
2
+ import { kinds } from "nostr-tools";
3
+ import { map } from "rxjs";
4
+ import { getChannelMetadataContent } from "../helpers/channels.js";
5
+ /** A query that returns a map of hidden messages Map<id, reason> */
6
+ export function ChannelHiddenQuery(channel, authors = []) {
7
+ return {
8
+ key: channel.id,
9
+ run: (events) => {
10
+ const hidden = new Map();
11
+ return events
12
+ .filters([{ kinds: [kinds.ChannelHideMessage], "#e": [channel.id], authors: [channel.pubkey, ...authors] }])
13
+ .pipe(map((event) => {
14
+ const reason = safeParse(event.content)?.reason;
15
+ for (const tag of event.tags) {
16
+ if (tag[0] === "e" && tag[1])
17
+ hidden.set(tag[1], reason ?? "");
18
+ }
19
+ return hidden;
20
+ }));
21
+ },
22
+ };
23
+ }
24
+ /** A query that returns all messages in a channel */
25
+ export function ChannelMessagesQuery(channel) {
26
+ return {
27
+ key: channel.id,
28
+ run: (events) => events.timeline([{ kinds: [kinds.ChannelMessage], "#e": [channel.id] }]),
29
+ };
30
+ }
31
+ /** A query that returns the latest parsed metadata */
32
+ export function ChannelMetadataQuery(channel) {
33
+ return {
34
+ key: channel.id,
35
+ run: (events) => {
36
+ const filters = [
37
+ { ids: [channel.id] },
38
+ { kinds: [kinds.ChannelMetadata], "#e": [channel.id], authors: [channel.pubkey] },
39
+ ];
40
+ let latest = channel;
41
+ return events.filters(filters).pipe(map((event) => {
42
+ try {
43
+ if (event.pubkey === latest.pubkey && event.created_at > latest.created_at) {
44
+ latest = event;
45
+ }
46
+ return getChannelMetadataContent(latest);
47
+ }
48
+ catch (error) {
49
+ return undefined;
50
+ }
51
+ }));
52
+ },
53
+ };
54
+ }
55
+ /** A query that returns a map of muted users Map<pubkey, reason> */
56
+ export function ChannelMutedQuery(channel, authors = []) {
57
+ return {
58
+ key: channel.id + authors.join(","),
59
+ run: (events) => {
60
+ const muted = new Map();
61
+ return events
62
+ .filters([{ kinds: [kinds.ChannelMuteUser], "#e": [channel.id], authors: [channel.pubkey, ...authors] }])
63
+ .pipe(map((event) => {
64
+ const reason = safeParse(event.content)?.reason;
65
+ for (const tag of event.tags) {
66
+ if (tag[0] === "p" && tag[1])
67
+ muted.set(tag[1], reason ?? "");
68
+ }
69
+ return muted;
70
+ }));
71
+ },
72
+ };
73
+ }
@@ -0,0 +1,3 @@
1
+ import { ProfilePointer } from "nostr-tools/nip19";
2
+ import { Query } from "../query-store/index.js";
3
+ export declare function UserContactsQuery(pubkey: string): Query<ProfilePointer[] | undefined>;
@@ -0,0 +1,12 @@
1
+ import { kinds } from "nostr-tools";
2
+ import { map } from "rxjs/operators";
3
+ import { isPTag, processTags } from "../helpers/tags.js";
4
+ import { getProfilePointerFromPTag } from "../helpers/pointers.js";
5
+ export function UserContactsQuery(pubkey) {
6
+ return {
7
+ key: pubkey,
8
+ run: (store) => store
9
+ .replaceable(kinds.Contacts, pubkey)
10
+ .pipe(map((event) => event && processTags(event.tags.filter(isPTag), getProfilePointerFromPTag))),
11
+ };
12
+ }
@@ -1,5 +1,11 @@
1
+ export * from "./blossom.js";
2
+ export * from "./bookmarks.js";
3
+ export * from "./channels.js";
1
4
  export * from "./comments.js";
5
+ export * from "./contacts.js";
2
6
  export * from "./mailboxes.js";
7
+ export * from "./mutes.js";
8
+ export * from "./pins.js";
3
9
  export * from "./profile.js";
4
10
  export * from "./reactions.js";
5
11
  export * from "./simple.js";
@@ -1,5 +1,11 @@
1
+ export * from "./blossom.js";
2
+ export * from "./bookmarks.js";
3
+ export * from "./channels.js";
1
4
  export * from "./comments.js";
5
+ export * from "./contacts.js";
2
6
  export * from "./mailboxes.js";
7
+ export * from "./mutes.js";
8
+ export * from "./pins.js";
3
9
  export * from "./profile.js";
4
10
  export * from "./reactions.js";
5
11
  export * from "./simple.js";
@@ -0,0 +1,8 @@
1
+ import { Mutes } from "../helpers/mutes.js";
2
+ import { Query } from "../query-store/index.js";
3
+ export declare function UserMuteQuery(pubkey: string): Query<Mutes | undefined>;
4
+ export declare function UserHiddenMuteQuery(pubkey: string): Query<(Mutes & {
5
+ locked: false;
6
+ }) | {
7
+ locked: true;
8
+ } | undefined>;
@@ -0,0 +1,23 @@
1
+ import { kinds } from "nostr-tools";
2
+ import { map } from "rxjs/operators";
3
+ import { getHiddenMutedThings, getMutedThings } from "../helpers/mutes.js";
4
+ import { isHiddenTagsLocked } from "../helpers/hidden-tags.js";
5
+ export function UserMuteQuery(pubkey) {
6
+ return {
7
+ key: pubkey,
8
+ run: (store) => store.replaceable(kinds.Mutelist, pubkey).pipe(map((event) => event && getMutedThings(event))),
9
+ };
10
+ }
11
+ export function UserHiddenMuteQuery(pubkey) {
12
+ return {
13
+ key: pubkey,
14
+ run: (store) => store.replaceable(kinds.Mutelist, pubkey).pipe(map((event) => {
15
+ if (!event)
16
+ return undefined;
17
+ const muted = getHiddenMutedThings(event);
18
+ if (isHiddenTagsLocked(event) || !muted)
19
+ return { locked: true };
20
+ return { locked: false, ...muted };
21
+ })),
22
+ };
23
+ }
@@ -0,0 +1,3 @@
1
+ import { Query } from "applesauce-core";
2
+ import { EventPointer } from "nostr-tools/nip19";
3
+ export declare function UserPinnedQuery(pubkey: string): Query<EventPointer[] | undefined>;
@@ -0,0 +1,12 @@
1
+ import { kinds } from "nostr-tools";
2
+ import { map } from "rxjs/operators";
3
+ import { isETag, processTags } from "../helpers/tags.js";
4
+ import { getEventPointerFromETag } from "../helpers/pointers.js";
5
+ export function UserPinnedQuery(pubkey) {
6
+ return {
7
+ key: pubkey,
8
+ run: (store) => store
9
+ .replaceable(kinds.Pinlist, pubkey)
10
+ .pipe(map((event) => event && processTags(event.tags.filter(isETag), getEventPointerFromETag))),
11
+ };
12
+ }
@@ -3,7 +3,7 @@ import { Query } from "../query-store/index.js";
3
3
  /** Creates a Query that returns a single event or undefined */
4
4
  export declare function SingleEventQuery(id: string): Query<NostrEvent | undefined>;
5
5
  /** Creates a Query that returns a multiple events in a map */
6
- export declare function MultipleEventsQuery(ids: string[]): Query<Map<string, NostrEvent>>;
6
+ export declare function MultipleEventsQuery(ids: string[]): Query<Record<string, NostrEvent>>;
7
7
  /** Creates a Query returning the latest version of a replaceable event */
8
8
  export declare function ReplaceableQuery(kind: number, pubkey: string, d?: string): Query<NostrEvent | undefined>;
9
9
  /** Creates a Query that returns an array of sorted events matching the filters */
@@ -13,4 +13,4 @@ export declare function ReplaceableSetQuery(pointers: {
13
13
  kind: number;
14
14
  pubkey: string;
15
15
  identifier?: string;
16
- }[]): Query<Map<string, NostrEvent>>;
16
+ }[]): Query<Record<string, NostrEvent>>;