applesauce-core 1.2.0 → 2.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 (204) hide show
  1. package/README.md +7 -13
  2. package/dist/event-store/{database.d.ts → event-set.d.ts} +35 -20
  3. package/dist/event-store/{database.js → event-set.js} +91 -60
  4. package/dist/event-store/event-store.d.ts +64 -25
  5. package/dist/event-store/event-store.js +163 -206
  6. package/dist/event-store/index.d.ts +1 -1
  7. package/dist/event-store/index.js +1 -1
  8. package/dist/event-store/interface.d.ts +71 -13
  9. package/dist/helpers/app-handlers.d.ts +23 -0
  10. package/dist/helpers/app-handlers.js +68 -0
  11. package/dist/helpers/article.d.ts +9 -0
  12. package/dist/helpers/article.js +21 -0
  13. package/dist/helpers/bolt11.d.ts +1 -0
  14. package/dist/helpers/bolt11.js +2 -0
  15. package/dist/helpers/bookmarks.js +1 -2
  16. package/dist/helpers/emoji.d.ts +10 -2
  17. package/dist/helpers/emoji.js +21 -3
  18. package/dist/helpers/encrypted-content-cache.d.ts +15 -0
  19. package/dist/helpers/encrypted-content-cache.js +125 -0
  20. package/dist/helpers/encrypted-content.d.ts +48 -0
  21. package/dist/helpers/encrypted-content.js +65 -0
  22. package/dist/helpers/encryption.d.ts +5 -0
  23. package/dist/helpers/encryption.js +10 -0
  24. package/dist/helpers/event.d.ts +4 -1
  25. package/dist/helpers/event.js +13 -3
  26. package/dist/helpers/expiration.d.ts +6 -0
  27. package/dist/helpers/expiration.js +16 -0
  28. package/dist/helpers/filter.d.ts +1 -3
  29. package/dist/helpers/filter.js +1 -3
  30. package/dist/helpers/gift-wraps.d.ts +17 -5
  31. package/dist/helpers/gift-wraps.js +65 -27
  32. package/dist/helpers/groups.d.ts +5 -0
  33. package/dist/helpers/groups.js +12 -2
  34. package/dist/helpers/hidden-content.d.ts +27 -32
  35. package/dist/helpers/hidden-content.js +35 -65
  36. package/dist/helpers/hidden-tags.d.ts +23 -4
  37. package/dist/helpers/hidden-tags.js +39 -4
  38. package/dist/helpers/index.d.ts +11 -1
  39. package/dist/helpers/index.js +11 -1
  40. package/dist/helpers/legacy-messages.d.ts +21 -0
  41. package/dist/helpers/legacy-messages.js +39 -0
  42. package/dist/helpers/lists.d.ts +1 -1
  43. package/dist/helpers/lists.js +2 -2
  44. package/dist/helpers/messages.d.ts +11 -0
  45. package/dist/helpers/messages.js +19 -0
  46. package/dist/helpers/mutes.js +1 -1
  47. package/dist/helpers/pointers.d.ts +33 -9
  48. package/dist/helpers/pointers.js +80 -44
  49. package/dist/helpers/profile.d.ts +10 -2
  50. package/dist/helpers/profile.js +33 -4
  51. package/dist/helpers/reactions.d.ts +8 -0
  52. package/dist/helpers/reactions.js +56 -0
  53. package/dist/helpers/reports.d.ts +28 -0
  54. package/dist/helpers/reports.js +38 -0
  55. package/dist/helpers/share.d.ts +10 -1
  56. package/dist/helpers/share.js +22 -8
  57. package/dist/helpers/url.d.ts +4 -0
  58. package/dist/helpers/url.js +20 -0
  59. package/dist/helpers/user-status.js +2 -1
  60. package/dist/helpers/wrapped-messages.d.ts +23 -0
  61. package/dist/helpers/wrapped-messages.js +38 -0
  62. package/dist/helpers/zap.d.ts +8 -5
  63. package/dist/helpers/zap.js +11 -6
  64. package/dist/index.d.ts +2 -2
  65. package/dist/index.js +2 -2
  66. package/dist/models/blossom.d.ts +3 -0
  67. package/dist/models/blossom.js +8 -0
  68. package/dist/models/bookmarks.d.ts +8 -0
  69. package/dist/{queries → models}/bookmarks.js +9 -9
  70. package/dist/models/channels.d.ts +11 -0
  71. package/dist/{queries → models}/channels.js +9 -9
  72. package/dist/models/comments.d.ts +4 -0
  73. package/dist/models/comments.js +11 -0
  74. package/dist/models/common.d.ts +16 -0
  75. package/dist/models/common.js +176 -0
  76. package/dist/models/contacts.d.ts +8 -0
  77. package/dist/{queries → models}/contacts.js +10 -10
  78. package/dist/models/encrypted-content.d.ts +4 -0
  79. package/dist/models/encrypted-content.js +11 -0
  80. package/dist/models/gift-wrap.d.ts +7 -0
  81. package/dist/models/gift-wrap.js +20 -0
  82. package/dist/{queries → models}/index.d.ts +6 -2
  83. package/dist/{queries → models}/index.js +6 -2
  84. package/dist/models/legacy-messages.d.ts +8 -0
  85. package/dist/models/legacy-messages.js +29 -0
  86. package/dist/models/mailboxes.d.ts +6 -0
  87. package/dist/{queries → models}/mailboxes.js +2 -2
  88. package/dist/models/mutes.d.ts +8 -0
  89. package/dist/{queries → models}/mutes.js +9 -9
  90. package/dist/models/pins.d.ts +4 -0
  91. package/dist/{queries → models}/pins.js +3 -3
  92. package/dist/models/profile.d.ts +4 -0
  93. package/dist/models/profile.js +14 -0
  94. package/dist/models/reactions.d.ts +4 -0
  95. package/dist/{queries → models}/reactions.js +2 -2
  96. package/dist/models/relays.d.ts +27 -0
  97. package/dist/{queries → models}/relays.js +13 -13
  98. package/dist/{queries → models}/thread.d.ts +6 -5
  99. package/dist/{queries → models}/thread.js +4 -3
  100. package/dist/models/user-status.d.ts +11 -0
  101. package/dist/{queries → models}/user-status.js +5 -5
  102. package/dist/models/wrapped-messages.d.ts +25 -0
  103. package/dist/models/wrapped-messages.js +61 -0
  104. package/dist/models/zaps.d.ts +9 -0
  105. package/dist/{queries → models}/zaps.js +11 -3
  106. package/dist/observable/claim-events.d.ts +3 -3
  107. package/dist/observable/claim-events.js +4 -4
  108. package/dist/observable/claim-latest.d.ts +3 -3
  109. package/dist/observable/claim-latest.js +4 -4
  110. package/dist/observable/index.d.ts +3 -1
  111. package/dist/observable/index.js +3 -1
  112. package/dist/observable/map-events-timeline.d.ts +7 -0
  113. package/dist/observable/map-events-timeline.js +9 -0
  114. package/dist/observable/map-events-to-store.d.ts +5 -0
  115. package/dist/observable/map-events-to-store.js +12 -0
  116. package/dist/observable/simple-timeout.d.ts +1 -0
  117. package/dist/observable/simple-timeout.js +1 -0
  118. package/dist/observable/watch-event-updates.d.ts +7 -0
  119. package/dist/observable/watch-event-updates.js +25 -0
  120. package/package.json +11 -16
  121. package/dist/__tests__/exports.test.d.ts +0 -1
  122. package/dist/__tests__/exports.test.js +0 -17
  123. package/dist/__tests__/fixtures.d.ts +0 -8
  124. package/dist/__tests__/fixtures.js +0 -20
  125. package/dist/event-store/__tests__/event-store.test.d.ts +0 -1
  126. package/dist/event-store/__tests__/event-store.test.js +0 -354
  127. package/dist/helpers/__tests__/blossom.test.d.ts +0 -1
  128. package/dist/helpers/__tests__/blossom.test.js +0 -13
  129. package/dist/helpers/__tests__/bookmarks.test.d.ts +0 -1
  130. package/dist/helpers/__tests__/bookmarks.test.js +0 -88
  131. package/dist/helpers/__tests__/comment.test.d.ts +0 -1
  132. package/dist/helpers/__tests__/comment.test.js +0 -249
  133. package/dist/helpers/__tests__/contacts.test.d.ts +0 -1
  134. package/dist/helpers/__tests__/contacts.test.js +0 -34
  135. package/dist/helpers/__tests__/emoji.test.d.ts +0 -1
  136. package/dist/helpers/__tests__/emoji.test.js +0 -15
  137. package/dist/helpers/__tests__/event.test.d.ts +0 -1
  138. package/dist/helpers/__tests__/event.test.js +0 -36
  139. package/dist/helpers/__tests__/events.test.d.ts +0 -1
  140. package/dist/helpers/__tests__/events.test.js +0 -32
  141. package/dist/helpers/__tests__/exports.test.d.ts +0 -1
  142. package/dist/helpers/__tests__/exports.test.js +0 -220
  143. package/dist/helpers/__tests__/file-metadata.test.d.ts +0 -1
  144. package/dist/helpers/__tests__/file-metadata.test.js +0 -103
  145. package/dist/helpers/__tests__/hidden-tags.test.d.ts +0 -1
  146. package/dist/helpers/__tests__/hidden-tags.test.js +0 -29
  147. package/dist/helpers/__tests__/mailboxes.test.d.ts +0 -1
  148. package/dist/helpers/__tests__/mailboxes.test.js +0 -81
  149. package/dist/helpers/__tests__/mutes.test.d.ts +0 -1
  150. package/dist/helpers/__tests__/mutes.test.js +0 -55
  151. package/dist/helpers/__tests__/nip-19.test.d.ts +0 -1
  152. package/dist/helpers/__tests__/nip-19.test.js +0 -42
  153. package/dist/helpers/__tests__/relays.test.d.ts +0 -1
  154. package/dist/helpers/__tests__/relays.test.js +0 -21
  155. package/dist/helpers/__tests__/tags.test.d.ts +0 -1
  156. package/dist/helpers/__tests__/tags.test.js +0 -24
  157. package/dist/helpers/__tests__/threading.test.d.ts +0 -1
  158. package/dist/helpers/__tests__/threading.test.js +0 -41
  159. package/dist/helpers/direct-messages.d.ts +0 -4
  160. package/dist/helpers/direct-messages.js +0 -5
  161. package/dist/helpers/nip-19.d.ts +0 -18
  162. package/dist/helpers/nip-19.js +0 -56
  163. package/dist/observable/__tests__/claim-events.test.d.ts +0 -1
  164. package/dist/observable/__tests__/claim-events.test.js +0 -23
  165. package/dist/observable/__tests__/claim-latest.test.d.ts +0 -1
  166. package/dist/observable/__tests__/claim-latest.test.js +0 -37
  167. package/dist/observable/__tests__/exports.test.d.ts +0 -1
  168. package/dist/observable/__tests__/exports.test.js +0 -18
  169. package/dist/observable/__tests__/listen-latest-updates.test.d.ts +0 -1
  170. package/dist/observable/__tests__/listen-latest-updates.test.js +0 -55
  171. package/dist/observable/__tests__/simple-timeout.test.d.ts +0 -1
  172. package/dist/observable/__tests__/simple-timeout.test.js +0 -34
  173. package/dist/observable/listen-latest-updates.d.ts +0 -5
  174. package/dist/observable/listen-latest-updates.js +0 -12
  175. package/dist/promise/__tests__/exports.test.d.ts +0 -1
  176. package/dist/promise/__tests__/exports.test.js +0 -11
  177. package/dist/queries/__tests__/exports.test.d.ts +0 -1
  178. package/dist/queries/__tests__/exports.test.js +0 -41
  179. package/dist/queries/blossom.d.ts +0 -2
  180. package/dist/queries/blossom.js +0 -5
  181. package/dist/queries/bookmarks.d.ts +0 -8
  182. package/dist/queries/channels.d.ts +0 -11
  183. package/dist/queries/comments.d.ts +0 -4
  184. package/dist/queries/comments.js +0 -11
  185. package/dist/queries/contacts.d.ts +0 -8
  186. package/dist/queries/mailboxes.d.ts +0 -6
  187. package/dist/queries/mutes.d.ts +0 -8
  188. package/dist/queries/pins.d.ts +0 -4
  189. package/dist/queries/profile.d.ts +0 -4
  190. package/dist/queries/profile.js +0 -7
  191. package/dist/queries/reactions.d.ts +0 -4
  192. package/dist/queries/relays.d.ts +0 -27
  193. package/dist/queries/simple.d.ts +0 -16
  194. package/dist/queries/simple.js +0 -21
  195. package/dist/queries/user-status.d.ts +0 -11
  196. package/dist/queries/zaps.d.ts +0 -5
  197. package/dist/query-store/__tests__/exports.test.d.ts +0 -1
  198. package/dist/query-store/__tests__/exports.test.js +0 -12
  199. package/dist/query-store/__tests__/query-store.test.d.ts +0 -1
  200. package/dist/query-store/__tests__/query-store.test.js +0 -63
  201. package/dist/query-store/index.d.ts +0 -1
  202. package/dist/query-store/index.js +0 -1
  203. package/dist/query-store/query-store.d.ts +0 -54
  204. package/dist/query-store/query-store.js +0 -102
@@ -0,0 +1,38 @@
1
+ import { getTagValue } from "./index.js";
2
+ import { isPTag } from "./tags.js";
3
+ /**
4
+ * Returns all pubkeys of participants of a conversation
5
+ * @param participants - The conversation identifier (pubkey1:pubkey2:pubkey3), or a users pubkey, or a list of participant pubkeys, or a rumor message
6
+ * @returns The participants of the conversation
7
+ */
8
+ export function getConversationParticipants(participants) {
9
+ return Array.from(new Set(typeof participants === "string"
10
+ ? participants.split(":")
11
+ : Array.isArray(participants)
12
+ ? participants
13
+ : [participants.pubkey, ...participants.tags.filter(isPTag).map((t) => t[1])]));
14
+ }
15
+ /**
16
+ * Creates a conversation identifier from a users pubkey and alist of correspondants
17
+ * @param participants - The participants of the conversation
18
+ * @returns The conversation identifier
19
+ */
20
+ export function createConversationIdentifier(participants) {
21
+ return Array.from(new Set(participants)).sort().join(":");
22
+ }
23
+ /**
24
+ * Returns the conversation identifier for a wrapped direct message
25
+ * @param message - The message to get the conversation identifier for
26
+ * @returns The conversation identifier
27
+ */
28
+ export function getConversationIdentifierFromMessage(message) {
29
+ return createConversationIdentifier(getConversationParticipants(message));
30
+ }
31
+ /** Returns the subject of a warpped direct message */
32
+ export function getWrappedMessageSubject(message) {
33
+ return getTagValue(message, "subject");
34
+ }
35
+ /** Returns the parent id of a wrapped direct message */
36
+ export function getWrappedMessageParent(message) {
37
+ return getTagValue(message, "e");
38
+ }
@@ -1,6 +1,9 @@
1
1
  import { NostrEvent } from "nostr-tools";
2
+ import { ParsedInvoice } from "./bolt11.js";
3
+ import { AddressPointer, EventPointer } from "nostr-tools/nip19";
2
4
  export declare const ZapRequestSymbol: unique symbol;
3
- export declare const ZapFromSymbol: unique symbol;
5
+ export declare const ZapSenderSymbol: unique symbol;
6
+ export declare const ZapReceiverSymbol: unique symbol;
4
7
  export declare const ZapInvoiceSymbol: unique symbol;
5
8
  export declare const ZapEventPointerSymbol: unique symbol;
6
9
  export declare const ZapAddressPointerSymbol: unique symbol;
@@ -12,18 +15,18 @@ export declare function getZapSender(zap: NostrEvent): string;
12
15
  */
13
16
  export declare function getZapRecipient(zap: NostrEvent): string;
14
17
  /** Returns the parsed bolt11 invoice */
15
- export declare function getZapPayment(zap: NostrEvent): import("./bolt11.js").ParsedInvoice | undefined;
18
+ export declare function getZapPayment(zap: NostrEvent): ParsedInvoice | undefined;
16
19
  /** Gets the AddressPointer that was zapped */
17
- export declare function getZapAddressPointer(zap: NostrEvent): import("nostr-tools/nip19").AddressPointer | null;
20
+ export declare function getZapAddressPointer(zap: NostrEvent): AddressPointer | null;
18
21
  /** Gets the EventPointer that was zapped */
19
- export declare function getZapEventPointer(zap: NostrEvent): import("nostr-tools/nip19").EventPointer | null;
22
+ export declare function getZapEventPointer(zap: NostrEvent): EventPointer | null;
20
23
  /** Gets the preimage for the bolt11 invoice */
21
24
  export declare function getZapPreimage(zap: NostrEvent): string | undefined;
22
25
  /**
23
26
  * Returns the zap request event inside the zap receipt
24
27
  * @throws
25
28
  */
26
- export declare function getZapRequest(zap: NostrEvent): import("nostr-tools").Event;
29
+ export declare function getZapRequest(zap: NostrEvent): NostrEvent;
27
30
  /**
28
31
  * Checks if the zap is valid
29
32
  * DOES NOT validate LNURL address
@@ -5,23 +5,28 @@ import { isATag, isETag } from "./tags.js";
5
5
  import { getAddressPointerFromATag, getEventPointerFromETag } from "./pointers.js";
6
6
  import { parseBolt11 } from "./bolt11.js";
7
7
  export const ZapRequestSymbol = Symbol.for("zap-request");
8
- export const ZapFromSymbol = Symbol.for("zap-from");
8
+ export const ZapSenderSymbol = Symbol.for("zap-sender");
9
+ export const ZapReceiverSymbol = Symbol.for("zap-receiver");
9
10
  export const ZapInvoiceSymbol = Symbol.for("zap-bolt11");
10
11
  export const ZapEventPointerSymbol = Symbol.for("zap-event-pointer");
11
12
  export const ZapAddressPointerSymbol = Symbol.for("zap-address-pointer");
12
13
  /** Returns the senders pubkey */
13
14
  export function getZapSender(zap) {
14
- return getTagValue(zap, "P") || getZapRequest(zap).pubkey;
15
+ return getOrComputeCachedValue(zap, ZapSenderSymbol, () => {
16
+ return getTagValue(zap, "P") || getZapRequest(zap).pubkey;
17
+ });
15
18
  }
16
19
  /**
17
20
  * Gets the receivers pubkey
18
21
  * @throws
19
22
  */
20
23
  export function getZapRecipient(zap) {
21
- const recipient = getTagValue(zap, "p");
22
- if (!recipient)
23
- throw new Error("Missing recipient");
24
- return recipient;
24
+ return getOrComputeCachedValue(zap, ZapReceiverSymbol, () => {
25
+ const recipient = getTagValue(zap, "p");
26
+ if (!recipient)
27
+ throw new Error("Missing recipient");
28
+ return recipient;
29
+ });
25
30
  }
26
31
  /** Returns the parsed bolt11 invoice */
27
32
  export function getZapPayment(zap) {
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export * from "./event-store/index.js";
2
- export * from "./query-store/index.js";
3
2
  export * as Helpers from "./helpers/index.js";
4
- export * as Queries from "./queries/index.js";
3
+ export * as Models from "./models/index.js";
4
+ export * from "./observable/index.js";
5
5
  export * from "./logger.js";
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export * from "./event-store/index.js";
2
- export * from "./query-store/index.js";
3
2
  export * as Helpers from "./helpers/index.js";
4
- export * as Queries from "./queries/index.js";
3
+ export * as Models from "./models/index.js";
4
+ export * from "./observable/index.js";
5
5
  export * from "./logger.js";
@@ -0,0 +1,3 @@
1
+ import { Model } from "../event-store/interface.js";
2
+ /** A model that returns a users blossom servers */
3
+ export declare function UserBlossomServersModel(pubkey: string): Model<URL[]>;
@@ -0,0 +1,8 @@
1
+ import { map } from "rxjs/operators";
2
+ import { BLOSSOM_SERVER_LIST_KIND, getBlossomServersFromList } from "../helpers/blossom.js";
3
+ /** A model that returns a users blossom servers */
4
+ export function UserBlossomServersModel(pubkey) {
5
+ return (store) => store
6
+ .replaceable(BLOSSOM_SERVER_LIST_KIND, pubkey)
7
+ .pipe(map((event) => (event ? getBlossomServersFromList(event) : [])));
8
+ }
@@ -0,0 +1,8 @@
1
+ import { Bookmarks } from "../helpers/bookmarks.js";
2
+ import { Model } from "../event-store/interface.js";
3
+ /** A model that returns all the bookmarks of a user */
4
+ export declare function UserBookmarkModel(pubkey: string): Model<Bookmarks | undefined>;
5
+ /** A model that returns all the public bookmarks of a user */
6
+ export declare function UserPublicBookmarkModel(pubkey: string): Model<Bookmarks | undefined>;
7
+ /** A model that returns all the hidden bookmarks of a user */
8
+ export declare function UserHiddenBookmarkModel(pubkey: string): Model<Bookmarks | null | undefined>;
@@ -1,24 +1,24 @@
1
1
  import { kinds } from "nostr-tools";
2
2
  import { map } from "rxjs/operators";
3
3
  import { getBookmarks, getHiddenBookmarks, getPublicBookmarks } from "../helpers/bookmarks.js";
4
- import { listenLatestUpdates } from "../observable/index.js";
5
- /** A query that returns all the bookmarks of a user */
6
- export function UserBookmarkQuery(pubkey) {
4
+ import { watchEventUpdates } from "../observable/index.js";
5
+ /** A model that returns all the bookmarks of a user */
6
+ export function UserBookmarkModel(pubkey) {
7
7
  return (events) => events.replaceable(kinds.Mutelist, pubkey).pipe(
8
8
  // listen for event updates (hidden tags unlocked)
9
- listenLatestUpdates(events),
9
+ watchEventUpdates(events),
10
10
  // Get all bookmarks
11
11
  map((event) => event && getBookmarks(event)));
12
12
  }
13
- /** A query that returns all the public bookmarks of a user */
14
- export function UserPublicBookmarkQuery(pubkey) {
13
+ /** A model that returns all the public bookmarks of a user */
14
+ export function UserPublicBookmarkModel(pubkey) {
15
15
  return (events) => events.replaceable(kinds.Mutelist, pubkey).pipe(map((event) => event && getPublicBookmarks(event)));
16
16
  }
17
- /** A query that returns all the hidden bookmarks of a user */
18
- export function UserHiddenBookmarkQuery(pubkey) {
17
+ /** A model that returns all the hidden bookmarks of a user */
18
+ export function UserHiddenBookmarkModel(pubkey) {
19
19
  return (events) => events.replaceable(kinds.Mutelist, pubkey).pipe(
20
20
  // listen for event updates (hidden tags unlocked)
21
- listenLatestUpdates(events),
21
+ watchEventUpdates(events),
22
22
  // Get hidden bookmarks
23
23
  map((event) => event && (getHiddenBookmarks(event) ?? null)));
24
24
  }
@@ -0,0 +1,11 @@
1
+ import { NostrEvent } from "nostr-tools";
2
+ import { Model } from "../event-store/interface.js";
3
+ import { ChannelMetadataContent } from "../helpers/channels.js";
4
+ /** A model that returns a map of hidden messages Map<id, reason> */
5
+ export declare function ChannelHiddenModel(channel: NostrEvent, authors?: string[]): Model<Map<string, string>>;
6
+ /** A model that returns all messages in a channel */
7
+ export declare function ChannelMessagesModel(channel: NostrEvent): Model<NostrEvent[]>;
8
+ /** A model that returns the latest parsed metadata */
9
+ export declare function ChannelMetadataModel(channel: NostrEvent): Model<ChannelMetadataContent | undefined>;
10
+ /** A model that returns a map of muted users Map<pubkey, reason> */
11
+ export declare function ChannelMutedModel(channel: NostrEvent, authors?: string[]): Model<Map<string, string>>;
@@ -1,9 +1,9 @@
1
- import { safeParse } from "applesauce-core/helpers/json";
2
1
  import { kinds } from "nostr-tools";
3
2
  import { map } from "rxjs";
4
3
  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 = []) {
4
+ import { safeParse } from "../helpers/json.js";
5
+ /** A model that returns a map of hidden messages Map<id, reason> */
6
+ export function ChannelHiddenModel(channel, authors = []) {
7
7
  return (events) => {
8
8
  const hidden = new Map();
9
9
  return events
@@ -18,12 +18,12 @@ export function ChannelHiddenQuery(channel, authors = []) {
18
18
  }));
19
19
  };
20
20
  }
21
- /** A query that returns all messages in a channel */
22
- export function ChannelMessagesQuery(channel) {
21
+ /** A model that returns all messages in a channel */
22
+ export function ChannelMessagesModel(channel) {
23
23
  return (events) => events.timeline([{ kinds: [kinds.ChannelMessage], "#e": [channel.id] }]);
24
24
  }
25
- /** A query that returns the latest parsed metadata */
26
- export function ChannelMetadataQuery(channel) {
25
+ /** A model that returns the latest parsed metadata */
26
+ export function ChannelMetadataModel(channel) {
27
27
  return (events) => {
28
28
  const filters = [
29
29
  { ids: [channel.id] },
@@ -43,8 +43,8 @@ export function ChannelMetadataQuery(channel) {
43
43
  }));
44
44
  };
45
45
  }
46
- /** A query that returns a map of muted users Map<pubkey, reason> */
47
- export function ChannelMutedQuery(channel, authors = []) {
46
+ /** A model that returns a map of muted users Map<pubkey, reason> */
47
+ export function ChannelMutedModel(channel, authors = []) {
48
48
  return (events) => {
49
49
  const muted = new Map();
50
50
  return events
@@ -0,0 +1,4 @@
1
+ import { NostrEvent } from "nostr-tools";
2
+ import { Model } from "../event-store/interface.js";
3
+ /** A model that returns all NIP-22 comment replies for the event */
4
+ export declare function CommentsModel(parent: NostrEvent): Model<NostrEvent[]>;
@@ -0,0 +1,11 @@
1
+ import { isAddressableKind } from "nostr-tools/kinds";
2
+ import { COMMENT_KIND, getReplaceableAddress } from "../helpers/index.js";
3
+ /** A model that returns all NIP-22 comment replies for the event */
4
+ export function CommentsModel(parent) {
5
+ return (events) => {
6
+ const filters = [{ kinds: [COMMENT_KIND], "#e": [parent.id] }];
7
+ if (isAddressableKind(parent.kind))
8
+ filters.push({ kinds: [COMMENT_KIND], "#a": [getReplaceableAddress(parent)] });
9
+ return events.timeline(filters);
10
+ };
11
+ }
@@ -0,0 +1,16 @@
1
+ import { Filter, NostrEvent } from "nostr-tools";
2
+ import { Model } from "../event-store/interface.js";
3
+ /** A model that returns a single event or undefined when its removed */
4
+ export declare function EventModel(id: string): Model<NostrEvent | undefined>;
5
+ /** A model that returns the latest version of a replaceable event or undefined if its removed */
6
+ export declare function ReplaceableModel(kind: number, pubkey: string, d?: string): Model<NostrEvent | undefined>;
7
+ /** A model that returns an array of sorted events matching the filters */
8
+ export declare function TimelineModel(filters: Filter | Filter[], includeOldVersion?: boolean): Model<NostrEvent[]>;
9
+ /** A model that returns a multiple events in a map */
10
+ export declare function EventsModel(ids: string[]): Model<Record<string, NostrEvent>>;
11
+ /** A model that returns a directory of events by their UID */
12
+ export declare function ReplaceableSetModel(pointers: {
13
+ kind: number;
14
+ pubkey: string;
15
+ identifier?: string;
16
+ }[]): Model<Record<string, NostrEvent>>;
@@ -0,0 +1,176 @@
1
+ import { defer, distinctUntilChanged, EMPTY, endWith, filter, finalize, from, map, merge, mergeWith, of, repeat, scan, takeUntil, tap, } from "rxjs";
2
+ import { createReplaceableAddress, getEventUID, getReplaceableIdentifier, isReplaceable, matchFilters, } from "../helpers/index.js";
3
+ import { claimEvents } from "../observable/claim-events.js";
4
+ import { claimLatest } from "../observable/claim-latest.js";
5
+ import { insertEventIntoDescendingList } from "nostr-tools/utils";
6
+ import { withImmediateValueOrDefault } from "../observable/with-immediate-value.js";
7
+ /** A model that returns a single event or undefined when its removed */
8
+ export function EventModel(id) {
9
+ return (events) => merge(
10
+ // get current event and ignore if there is none
11
+ defer(() => {
12
+ let event = events.getEvent(id);
13
+ return event ? of(event) : EMPTY;
14
+ }),
15
+ // subscribe to updates
16
+ events.insert$.pipe(filter((e) => e.id === id)),
17
+ // subscribe to updates
18
+ events.updated(id),
19
+ // emit undefined when deleted
20
+ events.removed(id).pipe(endWith(undefined))).pipe(
21
+ // claim all events
22
+ claimLatest(events),
23
+ // always emit undefined so the observable is synchronous
24
+ withImmediateValueOrDefault(undefined));
25
+ }
26
+ /** A model that returns the latest version of a replaceable event or undefined if its removed */
27
+ export function ReplaceableModel(kind, pubkey, d) {
28
+ return (events) => {
29
+ let current = undefined;
30
+ return merge(
31
+ // lazily get current event
32
+ defer(() => {
33
+ let event = events.getReplaceable(kind, pubkey, d);
34
+ return event ? of(event) : EMPTY;
35
+ }),
36
+ // subscribe to new events
37
+ events.insert$.pipe(filter((e) => e.pubkey == pubkey && e.kind === kind && (d !== undefined ? getReplaceableIdentifier(e) === d : true)))).pipe(
38
+ // only update if event is newer
39
+ distinctUntilChanged((prev, event) => {
40
+ // are the events the same? i.e. is the prev event older
41
+ return prev.created_at >= event.created_at;
42
+ }),
43
+ // Hacky way to extract the current event so takeUntil can access it
44
+ tap((event) => (current = event)),
45
+ // complete when event is removed
46
+ takeUntil(events.remove$.pipe(filter((e) => e.id === current?.id))),
47
+ // emit undefined when removed
48
+ endWith(undefined),
49
+ // keep the observable hot
50
+ repeat(),
51
+ // claim latest event
52
+ claimLatest(events),
53
+ // always emit undefined so the observable is synchronous
54
+ withImmediateValueOrDefault(undefined));
55
+ };
56
+ }
57
+ /** A model that returns an array of sorted events matching the filters */
58
+ export function TimelineModel(filters, includeOldVersion) {
59
+ filters = Array.isArray(filters) ? filters : [filters];
60
+ return (events) => {
61
+ const seen = new Map();
62
+ // get current events
63
+ return defer(() => of(Array.from(events.getTimeline(filters)))).pipe(
64
+ // claim existing events
65
+ claimEvents(events),
66
+ // subscribe to newer events
67
+ mergeWith(events.insert$.pipe(filter((e) => matchFilters(filters, e)),
68
+ // claim all new events
69
+ claimEvents(events))),
70
+ // subscribe to delete events
71
+ mergeWith(events.remove$.pipe(filter((e) => matchFilters(filters, e)), map((e) => e.id))),
72
+ // build a timeline
73
+ scan((timeline, event) => {
74
+ // filter out removed events from timeline
75
+ if (typeof event === "string")
76
+ return timeline.filter((e) => e.id !== event);
77
+ // initial timeline array
78
+ if (Array.isArray(event)) {
79
+ if (!includeOldVersion) {
80
+ for (const e of event)
81
+ if (isReplaceable(e.kind))
82
+ seen.set(getEventUID(e), e);
83
+ }
84
+ return event;
85
+ }
86
+ // create a new timeline and insert the event into it
87
+ let newTimeline = [...timeline];
88
+ // remove old replaceable events if enabled
89
+ if (!includeOldVersion && isReplaceable(event.kind)) {
90
+ const uid = getEventUID(event);
91
+ const existing = seen.get(uid);
92
+ // if this is an older replaceable event, exit
93
+ if (existing && event.created_at < existing.created_at)
94
+ return timeline;
95
+ // update latest version
96
+ seen.set(uid, event);
97
+ // remove old event from timeline
98
+ if (existing)
99
+ newTimeline.slice(newTimeline.indexOf(existing), 1);
100
+ }
101
+ // add event into timeline
102
+ insertEventIntoDescendingList(newTimeline, event);
103
+ return newTimeline;
104
+ }, []),
105
+ // ignore changes that do not modify the timeline instance
106
+ distinctUntilChanged(),
107
+ // hacky hack to clear seen on unsubscribe
108
+ finalize(() => seen.clear()));
109
+ };
110
+ }
111
+ /** A model that returns a multiple events in a map */
112
+ export function EventsModel(ids) {
113
+ return (events) => merge(
114
+ // lazily get existing events
115
+ defer(() => from(ids.map((id) => events.getEvent(id)))),
116
+ // subscribe to new events
117
+ events.insert$.pipe(filter((e) => ids.includes(e.id))),
118
+ // subscribe to updates
119
+ events.update$.pipe(filter((e) => ids.includes(e.id)))).pipe(
120
+ // ignore empty messages
121
+ filter((e) => !!e),
122
+ // claim all events until cleanup
123
+ claimEvents(events),
124
+ // watch for removed events
125
+ mergeWith(events.remove$.pipe(filter((e) => ids.includes(e.id)), map((e) => e.id))),
126
+ // merge all events into a directory
127
+ scan((dir, event) => {
128
+ if (typeof event === "string") {
129
+ // delete event by id
130
+ const clone = { ...dir };
131
+ delete clone[event];
132
+ return clone;
133
+ }
134
+ else {
135
+ // add even to directory
136
+ return { ...dir, [event.id]: event };
137
+ }
138
+ }, {}));
139
+ }
140
+ /** A model that returns a directory of events by their UID */
141
+ export function ReplaceableSetModel(pointers) {
142
+ return (events) => {
143
+ const uids = new Set(pointers.map((p) => createReplaceableAddress(p.kind, p.pubkey, p.identifier)));
144
+ return merge(
145
+ // start with existing events
146
+ defer(() => from(pointers.map((p) => events.getReplaceable(p.kind, p.pubkey, p.identifier)))),
147
+ // subscribe to new events
148
+ events.insert$.pipe(filter((e) => isReplaceable(e.kind) && uids.has(getEventUID(e))))).pipe(
149
+ // filter out undefined
150
+ filter((e) => !!e),
151
+ // claim all events
152
+ claimEvents(events),
153
+ // convert events to add commands
154
+ map((e) => ["add", e]),
155
+ // watch for removed events
156
+ mergeWith(events.remove$.pipe(filter((e) => isReplaceable(e.kind) && uids.has(getEventUID(e))), map((e) => ["remove", e]))),
157
+ // reduce events into directory
158
+ scan((dir, [action, event]) => {
159
+ const uid = getEventUID(event);
160
+ if (action === "add") {
161
+ // add event to dir if its newer
162
+ if (!dir[uid] || dir[uid].created_at < event.created_at)
163
+ return { ...dir, [uid]: event };
164
+ }
165
+ else if (action === "remove" && dir[uid] === event) {
166
+ // remove event from dir
167
+ let newDir = { ...dir };
168
+ delete newDir[uid];
169
+ return newDir;
170
+ }
171
+ return dir;
172
+ }, {}),
173
+ // ignore changes that do not modify the directory
174
+ distinctUntilChanged());
175
+ };
176
+ }
@@ -0,0 +1,8 @@
1
+ import { ProfilePointer } from "nostr-tools/nip19";
2
+ import { Model } from "../event-store/interface.js";
3
+ /** A model that returns all contacts for a user */
4
+ export declare function ContactsModel(pubkey: string): Model<ProfilePointer[]>;
5
+ /** A model that returns all public contacts for a user */
6
+ export declare function PublicContactsModel(pubkey: string): Model<ProfilePointer[] | undefined>;
7
+ /** A model that returns all hidden contacts for a user */
8
+ export declare function HiddenContactsModel(pubkey: string): Model<ProfilePointer[] | null | undefined>;
@@ -1,24 +1,24 @@
1
1
  import { kinds } from "nostr-tools";
2
2
  import { map } from "rxjs/operators";
3
3
  import { getContacts, getHiddenContacts, getPublicContacts } from "../helpers/contacts.js";
4
- import { listenLatestUpdates } from "../observable/index.js";
5
- /** A query that returns all contacts for a user */
6
- export function ContactsQuery(pubkey) {
4
+ import { watchEventUpdates } from "../observable/index.js";
5
+ /** A model that returns all contacts for a user */
6
+ export function ContactsModel(pubkey) {
7
7
  return (events) => events.replaceable(kinds.Contacts, pubkey).pipe(
8
8
  // listen for event updates (hidden tags unlocked)
9
- listenLatestUpdates(events),
9
+ watchEventUpdates(events),
10
10
  // Get all contacts
11
- map((e) => e && getContacts(e)));
11
+ map((e) => (e ? getContacts(e) : [])));
12
12
  }
13
- /** A query that returns all public contacts for a user */
14
- export function PublicContactsQuery(pubkey) {
13
+ /** A model that returns all public contacts for a user */
14
+ export function PublicContactsModel(pubkey) {
15
15
  return (events) => events.replaceable(kinds.Contacts, pubkey).pipe(map((e) => e && getPublicContacts(e)));
16
16
  }
17
- /** A query that returns all hidden contacts for a user */
18
- export function HiddenContactsQuery(pubkey) {
17
+ /** A model that returns all hidden contacts for a user */
18
+ export function HiddenContactsModel(pubkey) {
19
19
  return (events) => events.replaceable(kinds.Contacts, pubkey).pipe(
20
20
  // listen for event updates (hidden tags unlocked)
21
- listenLatestUpdates(events),
21
+ watchEventUpdates(events),
22
22
  // Get hidden contacts
23
23
  map((e) => e && (getHiddenContacts(e) ?? null)));
24
24
  }
@@ -0,0 +1,4 @@
1
+ import { NostrEvent } from "nostr-tools";
2
+ import { Model } from "../event-store/interface.js";
3
+ /** A model that returns the encrypted content of an event or event id */
4
+ export declare function EncryptedContentModel(event: NostrEvent | string): Model<string | undefined>;
@@ -0,0 +1,11 @@
1
+ import { map, of } from "rxjs";
2
+ import { getEncryptedContent } from "../helpers/encrypted-content.js";
3
+ import { watchEventUpdates } from "../observable/watch-event-updates.js";
4
+ /** A model that returns the encrypted content of an event or event id */
5
+ export function EncryptedContentModel(event) {
6
+ return (events) => (typeof event === "string" ? events.event(event) : of(event)).pipe(
7
+ // Listen for updates to the event
8
+ watchEventUpdates(events),
9
+ // Get the encrypted content
10
+ map((event) => event && getEncryptedContent(event)));
11
+ }
@@ -0,0 +1,7 @@
1
+ import { NostrEvent } from "nostr-tools";
2
+ import { Model } from "../event-store/interface.js";
3
+ import { Rumor } from "../helpers/gift-wraps.js";
4
+ /** A model that returns all gift wrap events for a pubkey, optionally filtered by locked status */
5
+ export declare function GiftWrapsModel(pubkey: string, locked?: boolean): Model<NostrEvent[]>;
6
+ /** A model that returns the rumor event of a gift wrap event when its unlocked */
7
+ export declare function GiftWrapRumorModel(gift: NostrEvent | string): Model<Rumor | undefined>;
@@ -0,0 +1,20 @@
1
+ import { kinds } from "nostr-tools";
2
+ import { identity, map, of } from "rxjs";
3
+ import { getGiftWrapRumor, isGiftWrapLocked } from "../helpers/gift-wraps.js";
4
+ import { watchEventsUpdates, watchEventUpdates } from "../observable/watch-event-updates.js";
5
+ /** A model that returns all gift wrap events for a pubkey, optionally filtered by locked status */
6
+ export function GiftWrapsModel(pubkey, locked) {
7
+ return (store) => store.timeline({ kinds: [kinds.GiftWrap], "#p": [pubkey] }).pipe(
8
+ // Update the timeline when events are updated
9
+ watchEventsUpdates(store),
10
+ // If lock is specified filter on locked status
11
+ locked !== undefined ? map((events) => events.filter((e) => isGiftWrapLocked(e) === locked)) : identity);
12
+ }
13
+ /** A model that returns the rumor event of a gift wrap event when its unlocked */
14
+ export function GiftWrapRumorModel(gift) {
15
+ return (events) => (typeof gift === "string" ? events.event(gift) : of(gift)).pipe(
16
+ // Listen for updates to the event
17
+ watchEventUpdates(events),
18
+ // Get the rumor event
19
+ map((event) => event && getGiftWrapRumor(event)));
20
+ }
@@ -2,13 +2,17 @@ export * from "./blossom.js";
2
2
  export * from "./bookmarks.js";
3
3
  export * from "./channels.js";
4
4
  export * from "./comments.js";
5
+ export * from "./common.js";
5
6
  export * from "./contacts.js";
6
- export * from "./relays.js";
7
+ export * from "./encrypted-content.js";
8
+ export * from "./gift-wrap.js";
9
+ export * from "./legacy-messages.js";
7
10
  export * from "./mailboxes.js";
8
11
  export * from "./mutes.js";
9
12
  export * from "./pins.js";
10
13
  export * from "./profile.js";
11
14
  export * from "./reactions.js";
12
- export * from "./simple.js";
15
+ export * from "./relays.js";
13
16
  export * from "./thread.js";
17
+ export * from "./wrapped-messages.js";
14
18
  export * from "./zaps.js";
@@ -2,13 +2,17 @@ export * from "./blossom.js";
2
2
  export * from "./bookmarks.js";
3
3
  export * from "./channels.js";
4
4
  export * from "./comments.js";
5
+ export * from "./common.js";
5
6
  export * from "./contacts.js";
6
- export * from "./relays.js";
7
+ export * from "./encrypted-content.js";
8
+ export * from "./gift-wrap.js";
9
+ export * from "./legacy-messages.js";
7
10
  export * from "./mailboxes.js";
8
11
  export * from "./mutes.js";
9
12
  export * from "./pins.js";
10
13
  export * from "./profile.js";
11
14
  export * from "./reactions.js";
12
- export * from "./simple.js";
15
+ export * from "./relays.js";
13
16
  export * from "./thread.js";
17
+ export * from "./wrapped-messages.js";
14
18
  export * from "./zaps.js";
@@ -0,0 +1,8 @@
1
+ import { NostrEvent } from "nostr-tools";
2
+ import { Model } from "../event-store/interface.js";
3
+ /** Returns all legacy direct messages in a conversation */
4
+ export declare function LegacyMessagesConversation(self: string, corraspondant: string): Model<NostrEvent[]>;
5
+ /** Returns an array of legacy messages that have replies */
6
+ export declare function LegacyMessageThreads(self: string, corraspondant: string): Model<NostrEvent[]>;
7
+ /** Returns all the legacy direct messages that are replies to a given message */
8
+ export declare function LegacyMessageReplies(self: string, message: NostrEvent): Model<NostrEvent[]>;
@@ -0,0 +1,29 @@
1
+ import { kinds } from "nostr-tools";
2
+ import { getLegacyMessageCorraspondant, getLegacyMessageParent } from "../helpers/legacy-messages.js";
3
+ import { map } from "rxjs";
4
+ /** Returns all legacy direct messages in a conversation */
5
+ export function LegacyMessagesConversation(self, corraspondant) {
6
+ return (store) => store.timeline({
7
+ kinds: [kinds.EncryptedDirectMessage],
8
+ "#p": [self, corraspondant],
9
+ authors: [self, corraspondant],
10
+ });
11
+ }
12
+ /** Returns an array of legacy messages that have replies */
13
+ export function LegacyMessageThreads(self, corraspondant) {
14
+ return (store) => store.model(LegacyMessagesConversation, self, corraspondant).pipe(map((messages) => messages.filter((message) =>
15
+ // Only select messages that are not replies
16
+ !getLegacyMessageParent(message) &&
17
+ // Check if message has any replies
18
+ store.getByFilters({ "#e": [message.id], kinds: [kinds.EncryptedDirectMessage] }).size > 0)));
19
+ }
20
+ /** Returns all the legacy direct messages that are replies to a given message */
21
+ export function LegacyMessageReplies(self, message) {
22
+ const corraspondant = getLegacyMessageCorraspondant(message, self);
23
+ return (store) => store.timeline({
24
+ kinds: [kinds.EncryptedDirectMessage],
25
+ "#p": [self, corraspondant],
26
+ authors: [self, corraspondant],
27
+ "#e": [message.id],
28
+ });
29
+ }
@@ -0,0 +1,6 @@
1
+ import { Model } from "../event-store/interface.js";
2
+ /** A model that gets and parses the inbox and outbox relays for a pubkey */
3
+ export declare function MailboxesModel(pubkey: string): Model<{
4
+ inboxes: string[];
5
+ outboxes: string[];
6
+ } | undefined>;