applesauce-core 3.1.0 → 4.1.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 (88) hide show
  1. package/dist/event-store/async-event-store.d.ts +136 -0
  2. package/dist/event-store/async-event-store.js +364 -0
  3. package/dist/event-store/{event-set.d.ts → event-memory.d.ts} +17 -25
  4. package/dist/event-store/{event-set.js → event-memory.js} +54 -53
  5. package/dist/event-store/event-store.d.ts +59 -63
  6. package/dist/event-store/event-store.js +126 -190
  7. package/dist/event-store/index.d.ts +2 -1
  8. package/dist/event-store/index.js +2 -1
  9. package/dist/event-store/interface.d.ts +115 -38
  10. package/dist/event-store/model-mixin.d.ts +59 -0
  11. package/dist/event-store/model-mixin.js +147 -0
  12. package/dist/helpers/app-data.d.ts +39 -0
  13. package/dist/helpers/app-data.js +68 -0
  14. package/dist/helpers/bookmarks.d.ts +11 -1
  15. package/dist/helpers/bookmarks.js +29 -4
  16. package/dist/helpers/comment.d.ts +13 -20
  17. package/dist/helpers/comment.js +16 -27
  18. package/dist/helpers/contacts.d.ts +10 -1
  19. package/dist/helpers/contacts.js +30 -3
  20. package/dist/helpers/encrypted-content-cache.js +7 -7
  21. package/dist/helpers/encrypted-content.d.ts +9 -2
  22. package/dist/helpers/encrypted-content.js +12 -9
  23. package/dist/helpers/event-cache.d.ts +3 -1
  24. package/dist/helpers/event-cache.js +3 -1
  25. package/dist/helpers/event-tags.d.ts +6 -0
  26. package/dist/helpers/event-tags.js +4 -0
  27. package/dist/helpers/event.d.ts +8 -1
  28. package/dist/helpers/event.js +6 -0
  29. package/dist/helpers/file-metadata.d.ts +4 -9
  30. package/dist/helpers/file-metadata.js +2 -10
  31. package/dist/helpers/filter.d.ts +4 -3
  32. package/dist/helpers/filter.js +3 -3
  33. package/dist/helpers/gift-wraps.d.ts +35 -14
  34. package/dist/helpers/gift-wraps.js +59 -50
  35. package/dist/helpers/groups.d.ts +2 -5
  36. package/dist/helpers/groups.js +2 -5
  37. package/dist/helpers/hidden-content.d.ts +14 -5
  38. package/dist/helpers/hidden-content.js +19 -8
  39. package/dist/helpers/hidden-tags.d.ts +16 -7
  40. package/dist/helpers/hidden-tags.js +47 -26
  41. package/dist/helpers/index.d.ts +1 -0
  42. package/dist/helpers/index.js +1 -0
  43. package/dist/helpers/legacy-messages.d.ts +17 -13
  44. package/dist/helpers/legacy-messages.js +21 -19
  45. package/dist/helpers/lists.js +2 -1
  46. package/dist/helpers/lnurl.d.ts +4 -0
  47. package/dist/helpers/lnurl.js +7 -3
  48. package/dist/helpers/mailboxes.d.ts +2 -6
  49. package/dist/helpers/mailboxes.js +26 -20
  50. package/dist/helpers/mutes.d.ts +11 -1
  51. package/dist/helpers/mutes.js +30 -5
  52. package/dist/helpers/picture-post.d.ts +2 -1
  53. package/dist/helpers/pointers.d.ts +3 -1
  54. package/dist/helpers/pointers.js +4 -1
  55. package/dist/helpers/profile.d.ts +7 -3
  56. package/dist/helpers/profile.js +7 -8
  57. package/dist/helpers/relay-selection.d.ts +17 -0
  58. package/dist/helpers/relay-selection.js +102 -0
  59. package/dist/helpers/relays.d.ts +3 -1
  60. package/dist/helpers/relays.js +18 -2
  61. package/dist/helpers/url.js +3 -3
  62. package/dist/helpers/wrapped-messages.d.ts +5 -3
  63. package/dist/helpers/wrapped-messages.js +5 -3
  64. package/dist/helpers/zap.d.ts +18 -14
  65. package/dist/helpers/zap.js +26 -28
  66. package/dist/models/common.d.ts +4 -4
  67. package/dist/models/common.js +79 -40
  68. package/dist/models/gift-wrap.d.ts +1 -1
  69. package/dist/models/gift-wrap.js +4 -4
  70. package/dist/models/index.d.ts +1 -0
  71. package/dist/models/index.js +1 -0
  72. package/dist/models/legacy-messages.d.ts +2 -2
  73. package/dist/models/legacy-messages.js +13 -10
  74. package/dist/models/outbox.d.ts +13 -0
  75. package/dist/models/outbox.js +18 -0
  76. package/dist/models/profile.d.ts +1 -1
  77. package/dist/models/zaps.d.ts +5 -4
  78. package/dist/models/zaps.js +2 -2
  79. package/dist/observable/index.d.ts +4 -3
  80. package/dist/observable/index.js +5 -4
  81. package/dist/observable/map-events-to-store.d.ts +5 -3
  82. package/dist/observable/map-events-to-store.js +14 -3
  83. package/dist/observable/map-events-to-timeline.js +12 -0
  84. package/dist/observable/relay-selection.d.ts +9 -0
  85. package/dist/observable/relay-selection.js +43 -0
  86. package/package.json +5 -3
  87. package/dist/observable/map-events-timeline.js +0 -9
  88. /package/dist/observable/{map-events-timeline.d.ts → map-events-to-timeline.d.ts} +0 -0
@@ -1,13 +1,13 @@
1
1
  import { Filter, NostrEvent } from "nostr-tools";
2
2
  import { AddressPointer, EventPointer } from "nostr-tools/nip19";
3
- import { Model } from "../event-store/interface.js";
3
+ import { IAsyncEventStore, IEventStore, Model } from "../event-store/interface.js";
4
4
  import { AddressPointerWithoutD } from "../helpers/index.js";
5
5
  /** A model that returns a single event or undefined when its removed */
6
- export declare function EventModel(pointer: string | EventPointer): Model<NostrEvent | undefined>;
6
+ export declare function EventModel(pointer: string | EventPointer): Model<NostrEvent | undefined, IEventStore | IAsyncEventStore>;
7
7
  /** A model that returns the latest version of a replaceable event or undefined if its removed */
8
- export declare function ReplaceableModel(pointer: AddressPointer | AddressPointerWithoutD): Model<NostrEvent | undefined>;
8
+ export declare function ReplaceableModel(pointer: AddressPointer | AddressPointerWithoutD): Model<NostrEvent | undefined, IEventStore | IAsyncEventStore>;
9
9
  /** A model that returns an array of sorted events matching the filters */
10
- export declare function TimelineModel(filters: Filter | Filter[], includeOldVersion?: boolean): Model<NostrEvent[]>;
10
+ export declare function TimelineModel(filters: Filter | Filter[], includeOldVersion?: boolean): Model<NostrEvent[], IEventStore | IAsyncEventStore>;
11
11
  /**
12
12
  * A model that returns a multiple events in a map
13
13
  * @deprecated use multiple {@link EventModel} instead
@@ -1,30 +1,73 @@
1
- import { combineLatest, defer, distinctUntilChanged, EMPTY, endWith, filter, finalize, from, map, merge, mergeWith, of, repeat, scan, takeUntil, tap, } from "rxjs";
1
+ import { combineLatest, defer, distinctUntilChanged, EMPTY, endWith, filter, finalize, from, ignoreElements, map, merge, mergeWith, of, repeat, scan, switchMap, take, takeUntil, tap, } from "rxjs";
2
2
  import { insertEventIntoDescendingList } from "nostr-tools/utils";
3
3
  import { createReplaceableAddress, getEventUID, getReplaceableIdentifier, isReplaceable, matchFilters, } from "../helpers/index.js";
4
4
  import { claimEvents } from "../observable/claim-events.js";
5
5
  import { claimLatest } from "../observable/claim-latest.js";
6
+ import { defined } from "../observable/defined.js";
6
7
  import { withImmediateValueOrDefault } from "../observable/with-immediate-value.js";
8
+ /** Gets a single event from both types of event stores and returns an observable that completes */
9
+ function getEventFromStores(store, pointer) {
10
+ const r = store.getEvent(pointer.id);
11
+ if (r instanceof Promise)
12
+ return from(r);
13
+ else
14
+ return of(r);
15
+ }
16
+ /** Gets a single replaceable event from both types of event stores and returns an observable that completes */
17
+ function getReplaceableFromStores(store, pointer) {
18
+ const r = store.getReplaceable(pointer.kind, pointer.pubkey, pointer.identifier);
19
+ if (r instanceof Promise)
20
+ return from(r);
21
+ else
22
+ return of(r);
23
+ }
24
+ /** If event is undefined, attempt to load using the fallback loader */
25
+ function loadEventUsingFallback(store, pointer) {
26
+ return switchMap((event) => {
27
+ if (event)
28
+ return of(event);
29
+ // If event was not found, attempt to load
30
+ if (!store.eventLoader)
31
+ return EMPTY;
32
+ return from(store.eventLoader(pointer));
33
+ });
34
+ }
35
+ /** If replaceable event is undefined, attempt to load using the fallback loader */
36
+ function loadReplaceableUsingFallback(store, pointer) {
37
+ return switchMap((event) => {
38
+ if (event)
39
+ return of(event);
40
+ else if (pointer.identifier !== undefined) {
41
+ if (!store.addressableLoader)
42
+ return EMPTY;
43
+ return from(store.addressableLoader(pointer)).pipe(filter((e) => !!e));
44
+ }
45
+ else {
46
+ if (!store.replaceableLoader)
47
+ return EMPTY;
48
+ return from(store.replaceableLoader(pointer)).pipe(filter((e) => !!e));
49
+ }
50
+ });
51
+ }
7
52
  /** A model that returns a single event or undefined when its removed */
8
53
  export function EventModel(pointer) {
9
54
  if (typeof pointer === "string")
10
55
  pointer = { id: pointer };
11
- return (events) => merge(
56
+ return (store) => merge(
12
57
  // get current event and ignore if there is none
13
- defer(() => {
14
- let event = events.getEvent(pointer.id);
15
- if (event)
16
- return of(event);
17
- // If there is a loader, use it to get the event
18
- if (!events.eventLoader)
19
- return EMPTY;
20
- return from(events.eventLoader(pointer)).pipe(filter((e) => !!e));
21
- }),
58
+ defer(() => getEventFromStores(store, pointer)).pipe(
59
+ // If the event isn't found, attempt to load using the fallback loader
60
+ loadEventUsingFallback(store, pointer),
61
+ // Only emit found events
62
+ defined()),
22
63
  // Listen for new events
23
- events.insert$.pipe(filter((e) => e.id === pointer.id)),
64
+ store.insert$.pipe(filter((e) => e.id === pointer.id)),
24
65
  // emit undefined when deleted
25
- events.removed(pointer.id).pipe(endWith(undefined))).pipe(
66
+ store.remove$.pipe(filter((e) => e.id === pointer.id), take(1), ignoreElements(),
67
+ // Emit undefined when deleted
68
+ endWith(undefined))).pipe(
26
69
  // claim all events
27
- claimLatest(events),
70
+ claimLatest(store),
28
71
  // ignore duplicate events
29
72
  distinctUntilChanged((a, b) => a?.id === b?.id),
30
73
  // always emit undefined so the observable is synchronous
@@ -32,27 +75,17 @@ export function EventModel(pointer) {
32
75
  }
33
76
  /** A model that returns the latest version of a replaceable event or undefined if its removed */
34
77
  export function ReplaceableModel(pointer) {
35
- return (events) => {
78
+ return (store) => {
36
79
  let current = undefined;
37
80
  return merge(
38
81
  // lazily get current event
39
- defer(() => {
40
- let event = events.getReplaceable(pointer.kind, pointer.pubkey, pointer.identifier);
41
- if (event)
42
- return of(event);
43
- else if (pointer.identifier !== undefined) {
44
- if (!events.addressableLoader)
45
- return EMPTY;
46
- return from(events.addressableLoader(pointer)).pipe(filter((e) => !!e));
47
- }
48
- else {
49
- if (!events.replaceableLoader)
50
- return EMPTY;
51
- return from(events.replaceableLoader(pointer)).pipe(filter((e) => !!e));
52
- }
53
- }),
54
- // subscribe to new events
55
- events.insert$.pipe(filter((e) => e.pubkey == pointer.pubkey &&
82
+ defer(() => getReplaceableFromStores(store, pointer)).pipe(
83
+ // If the event isn't found, attempt to load using the fallback loader
84
+ loadReplaceableUsingFallback(store, pointer),
85
+ // Only emit found events
86
+ defined()),
87
+ // Subscribe to new events that match the pointer
88
+ store.insert$.pipe(filter((e) => e.pubkey == pointer.pubkey &&
56
89
  e.kind === pointer.kind &&
57
90
  (pointer.identifier !== undefined ? getReplaceableIdentifier(e) === pointer.identifier : true)))).pipe(
58
91
  // only update if event is newer
@@ -63,13 +96,13 @@ export function ReplaceableModel(pointer) {
63
96
  // Hacky way to extract the current event so takeUntil can access it
64
97
  tap((event) => (current = event)),
65
98
  // complete when event is removed
66
- takeUntil(events.remove$.pipe(filter((e) => e.id === current?.id))),
99
+ takeUntil(store.remove$.pipe(filter((e) => e.id === current?.id))),
67
100
  // emit undefined when removed
68
101
  endWith(undefined),
69
102
  // keep the observable hot
70
103
  repeat(),
71
104
  // claim latest event
72
- claimLatest(events),
105
+ claimLatest(store),
73
106
  // always emit undefined so the observable is synchronous
74
107
  withImmediateValueOrDefault(undefined));
75
108
  };
@@ -77,18 +110,24 @@ export function ReplaceableModel(pointer) {
77
110
  /** A model that returns an array of sorted events matching the filters */
78
111
  export function TimelineModel(filters, includeOldVersion) {
79
112
  filters = Array.isArray(filters) ? filters : [filters];
80
- return (events) => {
113
+ return (store) => {
81
114
  const seen = new Map();
82
115
  // get current events
83
- return defer(() => of(Array.from(events.getTimeline(filters)))).pipe(
116
+ return defer(() => {
117
+ const r = store.getTimeline(filters);
118
+ if (r instanceof Promise)
119
+ return from(r);
120
+ else
121
+ return of(r);
122
+ }).pipe(
84
123
  // claim existing events
85
- claimEvents(events),
124
+ claimEvents(store),
86
125
  // subscribe to newer events
87
- mergeWith(events.insert$.pipe(filter((e) => matchFilters(filters, e)),
126
+ mergeWith(store.insert$.pipe(filter((e) => matchFilters(filters, e)),
88
127
  // claim all new events
89
- claimEvents(events))),
128
+ claimEvents(store))),
90
129
  // subscribe to delete events
91
- mergeWith(events.remove$.pipe(filter((e) => matchFilters(filters, e)), map((e) => e.id))),
130
+ mergeWith(store.remove$.pipe(filter((e) => matchFilters(filters, e)), map((e) => e.id))),
92
131
  // build a timeline
93
132
  scan((timeline, event) => {
94
133
  // filter out removed events from timeline
@@ -2,6 +2,6 @@ import { NostrEvent } from "nostr-tools";
2
2
  import { Model } from "../event-store/interface.js";
3
3
  import { Rumor } from "../helpers/gift-wraps.js";
4
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[]>;
5
+ export declare function GiftWrapsModel(pubkey: string, unlocked?: boolean): Model<NostrEvent[]>;
6
6
  /** A model that returns the rumor event of a gift wrap event when its unlocked */
7
7
  export declare function GiftWrapRumorModel(gift: NostrEvent | string): Model<Rumor | undefined>;
@@ -1,14 +1,14 @@
1
1
  import { kinds } from "nostr-tools";
2
2
  import { identity, map, of } from "rxjs";
3
- import { getGiftWrapRumor, isGiftWrapLocked } from "../helpers/gift-wraps.js";
3
+ import { getGiftWrapRumor, isGiftWrapUnlocked } from "../helpers/gift-wraps.js";
4
4
  import { watchEventsUpdates, watchEventUpdates } from "../observable/watch-event-updates.js";
5
5
  /** A model that returns all gift wrap events for a pubkey, optionally filtered by locked status */
6
- export function GiftWrapsModel(pubkey, locked) {
6
+ export function GiftWrapsModel(pubkey, unlocked) {
7
7
  return (store) => store.timeline({ kinds: [kinds.GiftWrap], "#p": [pubkey] }).pipe(
8
8
  // Update the timeline when events are updated
9
9
  watchEventsUpdates(store),
10
- // If lock is specified filter on locked status
11
- locked !== undefined ? map((events) => events.filter((e) => isGiftWrapLocked(e) === locked)) : identity);
10
+ // If unlock is specified filter on unlocked status
11
+ unlocked !== undefined ? map((events) => events.filter((e) => isGiftWrapUnlocked(e) === unlocked)) : identity);
12
12
  }
13
13
  /** A model that returns the rumor event of a gift wrap event when its unlocked */
14
14
  export function GiftWrapRumorModel(gift) {
@@ -17,3 +17,4 @@ export * from "./relays.js";
17
17
  export * from "./thread.js";
18
18
  export * from "./wrapped-messages.js";
19
19
  export * from "./zaps.js";
20
+ export * from "./outbox.js";
@@ -17,3 +17,4 @@ export * from "./relays.js";
17
17
  export * from "./thread.js";
18
18
  export * from "./wrapped-messages.js";
19
19
  export * from "./zaps.js";
20
+ export * from "./outbox.js";
@@ -7,8 +7,8 @@ export declare function LegacyMessagesGroups(self: string): Model<{
7
7
  lastMessage: NostrEvent;
8
8
  }[]>;
9
9
  /** Returns all legacy direct messages in a group */
10
- export declare function LegacyMessagesGroup(self: string, corraspondant: string): Model<NostrEvent[]>;
10
+ export declare function LegacyMessagesGroup(self: string, correspondent: string): Model<NostrEvent[]>;
11
11
  /** Returns an array of legacy messages that have replies */
12
- export declare function LegacyMessageThreads(self: string, corraspondant: string): Model<NostrEvent[]>;
12
+ export declare function LegacyMessageThreads(self: string, correspondent: string): Model<NostrEvent[]>;
13
13
  /** Returns all the legacy direct messages that are replies to a given message */
14
14
  export declare function LegacyMessageReplies(self: string, message: NostrEvent): Model<NostrEvent[]>;
@@ -1,7 +1,8 @@
1
1
  import { kinds } from "nostr-tools";
2
- import { getLegacyMessageCorraspondant, getLegacyMessageParent } from "../helpers/legacy-messages.js";
2
+ import { getLegacyMessageCorrespondent, getLegacyMessageParent } from "../helpers/legacy-messages.js";
3
3
  import { map } from "rxjs";
4
4
  import { getConversationIdentifierFromMessage, getConversationParticipants } from "../helpers/messages.js";
5
+ import { hasNameValueTag } from "../helpers/event-tags.js";
5
6
  /** A model that returns all legacy message groups (1-1) that a pubkey is participating in */
6
7
  export function LegacyMessagesGroups(self) {
7
8
  return (store) => store.timeline({ kinds: [kinds.EncryptedDirectMessage], "#p": [self] }).pipe(map((messages) => {
@@ -19,41 +20,43 @@ export function LegacyMessagesGroups(self) {
19
20
  }));
20
21
  }
21
22
  /** Returns all legacy direct messages in a group */
22
- export function LegacyMessagesGroup(self, corraspondant) {
23
+ export function LegacyMessagesGroup(self, correspondent) {
23
24
  return (store) => store.timeline([
24
25
  {
25
26
  kinds: [kinds.EncryptedDirectMessage],
26
27
  "#p": [self],
27
- authors: [corraspondant],
28
+ authors: [correspondent],
28
29
  },
29
30
  {
30
31
  kinds: [kinds.EncryptedDirectMessage],
31
- "#p": [corraspondant],
32
+ "#p": [correspondent],
32
33
  authors: [self],
33
34
  },
34
35
  ]);
35
36
  }
36
37
  /** Returns an array of legacy messages that have replies */
37
- export function LegacyMessageThreads(self, corraspondant) {
38
- return (store) => store.model(LegacyMessagesGroup, self, corraspondant).pipe(map((messages) => messages.filter((message) =>
38
+ export function LegacyMessageThreads(self, correspondent) {
39
+ return (store) => store.model(LegacyMessagesGroup, self, correspondent).pipe(map((messages) => messages.filter((message) =>
39
40
  // Only select messages that are not replies
40
41
  !getLegacyMessageParent(message) &&
41
42
  // Check if message has any replies
42
- store.getByFilters({ "#e": [message.id], kinds: [kinds.EncryptedDirectMessage] }).size > 0)));
43
+ messages.some((m) => hasNameValueTag(m, "e", message.id)))));
43
44
  }
44
45
  /** Returns all the legacy direct messages that are replies to a given message */
45
46
  export function LegacyMessageReplies(self, message) {
46
- const corraspondant = getLegacyMessageCorraspondant(message, self);
47
+ const correspondent = getLegacyMessageCorrespondent(message, self);
48
+ if (!correspondent)
49
+ throw new Error("Legacy message has no correspondent");
47
50
  return (store) => store.timeline([
48
51
  {
49
52
  kinds: [kinds.EncryptedDirectMessage],
50
53
  "#p": [self],
51
- authors: [corraspondant],
54
+ authors: [correspondent],
52
55
  "#e": [message.id],
53
56
  },
54
57
  {
55
58
  kinds: [kinds.EncryptedDirectMessage],
56
- "#p": [corraspondant],
59
+ "#p": [correspondent],
57
60
  authors: [self],
58
61
  "#e": [message.id],
59
62
  },
@@ -0,0 +1,13 @@
1
+ import { ProfilePointer } from "nostr-tools/nip19";
2
+ import { Model } from "../event-store/interface.js";
3
+ import { SelectOptimalRelaysOptions } from "../helpers/relay-selection.js";
4
+ import { ignoreBlacklistedRelays } from "../observable/relay-selection.js";
5
+ export type OutboxModelOptions = SelectOptimalRelaysOptions & {
6
+ type?: "inbox" | "outbox";
7
+ blacklist?: Parameters<typeof ignoreBlacklistedRelays>[0];
8
+ };
9
+ /** A model that returns the users contacts with the relays to connect to */
10
+ export declare function OutboxModel(user: string | ProfilePointer, opts: OutboxModelOptions): Model<ProfilePointer[]>;
11
+ export declare namespace OutboxModel {
12
+ var getKey: (user: string | ProfilePointer, opts: OutboxModelOptions) => string;
13
+ }
@@ -0,0 +1,18 @@
1
+ import hash_sum from "hash-sum";
2
+ import { identity, map } from "rxjs";
3
+ import { selectOptimalRelays } from "../helpers/relay-selection.js";
4
+ import { ignoreBlacklistedRelays, includeMailboxes } from "../observable/relay-selection.js";
5
+ /** A model that returns the users contacts with the relays to connect to */
6
+ export function OutboxModel(user, opts) {
7
+ return (store) => store.contacts(user).pipe(
8
+ /** Ignore blacklisted relays */
9
+ opts?.blacklist ? ignoreBlacklistedRelays(opts.blacklist) : identity,
10
+ /** Include mailboxes */
11
+ includeMailboxes(store, opts.type),
12
+ /** Select the optimal relays */
13
+ map((users) => selectOptimalRelays(users, opts)));
14
+ }
15
+ OutboxModel.getKey = (user, opts) => {
16
+ const p = typeof user === "string" ? user : user.pubkey;
17
+ return hash_sum([p, opts.type, opts.maxConnections, opts.maxRelaysPerUser]);
18
+ };
@@ -1,5 +1,5 @@
1
+ import { ProfilePointer } from "nostr-tools/nip19";
1
2
  import { Model } from "../event-store/interface.js";
2
3
  import { ProfileContent } from "../helpers/profile.js";
3
- import { ProfilePointer } from "nostr-tools/nip19";
4
4
  /** A model that gets and parses the kind 0 metadata for a pubkey */
5
5
  export declare function ProfileModel(user: string | ProfilePointer): Model<ProfileContent | undefined>;
@@ -1,9 +1,10 @@
1
- import { NostrEvent } from "nostr-tools";
1
+ import { kinds } from "nostr-tools";
2
2
  import { AddressPointer, EventPointer } from "nostr-tools/nip19";
3
3
  import { Model } from "../event-store/interface.js";
4
+ import { KnownEvent } from "../helpers/index.js";
4
5
  /** A model that gets all zap events for an event */
5
- export declare function EventZapsModel(id: string | EventPointer | AddressPointer): Model<NostrEvent[]>;
6
+ export declare function EventZapsModel(id: string | EventPointer | AddressPointer): Model<KnownEvent<kinds.Zap>[]>;
6
7
  /** A model that returns all zaps sent by a user */
7
- export declare function SentZapsModel(pubkey: string): Model<NostrEvent[]>;
8
+ export declare function SentZapsModel(pubkey: string): Model<KnownEvent<kinds.Zap>[]>;
8
9
  /** A model that returns all zaps received by a user */
9
- export declare function ReceivedZapsModel(pubkey: string): Model<NostrEvent[]>;
10
+ export declare function ReceivedZapsModel(pubkey: string): Model<KnownEvent<kinds.Zap>[]>;
@@ -18,9 +18,9 @@ export function EventZapsModel(id) {
18
18
  }
19
19
  /** A model that returns all zaps sent by a user */
20
20
  export function SentZapsModel(pubkey) {
21
- return (events) => events.timeline([{ kinds: [kinds.Zap], authors: [pubkey] }]);
21
+ return (events) => events.timeline([{ kinds: [kinds.Zap], authors: [pubkey] }]).pipe(map((events) => events.filter(isValidZap)));
22
22
  }
23
23
  /** A model that returns all zaps received by a user */
24
24
  export function ReceivedZapsModel(pubkey) {
25
- return (events) => events.timeline([{ kinds: [kinds.Zap], "#a": [pubkey] }]);
25
+ return (events) => events.timeline([{ kinds: [kinds.Zap], "#p": [pubkey] }]).pipe(map((events) => events.filter(isValidZap)));
26
26
  }
@@ -1,9 +1,10 @@
1
- import { firstValueFrom, lastValueFrom } from "rxjs";
1
+ export { firstValueFrom, lastValueFrom, combineLatest, merge } from "rxjs";
2
+ export { Observable, Subject, BehaviorSubject, ReplaySubject } from "rxjs";
2
3
  export * from "./defined.js";
3
4
  export * from "./get-observable-value.js";
4
- export * from "./map-events-timeline.js";
5
+ export * from "./map-events-to-timeline.js";
5
6
  export * from "./map-events-to-store.js";
6
7
  export * from "./simple-timeout.js";
7
8
  export * from "./watch-event-updates.js";
8
9
  export * from "./with-immediate-value.js";
9
- export { firstValueFrom, lastValueFrom };
10
+ export * from "./relay-selection.js";
@@ -1,10 +1,11 @@
1
- import { firstValueFrom, lastValueFrom } from "rxjs";
1
+ // Re-export some useful rxjs functions
2
+ export { firstValueFrom, lastValueFrom, combineLatest, merge } from "rxjs";
3
+ export { Observable, Subject, BehaviorSubject, ReplaySubject } from "rxjs";
2
4
  export * from "./defined.js";
3
5
  export * from "./get-observable-value.js";
4
- export * from "./map-events-timeline.js";
6
+ export * from "./map-events-to-timeline.js";
5
7
  export * from "./map-events-to-store.js";
6
8
  export * from "./simple-timeout.js";
7
9
  export * from "./watch-event-updates.js";
8
10
  export * from "./with-immediate-value.js";
9
- // Re-export some useful rxjs functions
10
- export { firstValueFrom, lastValueFrom };
11
+ export * from "./relay-selection.js";
@@ -1,5 +1,7 @@
1
- import { MonoTypeOperatorFunction } from "rxjs";
2
1
  import { NostrEvent } from "nostr-tools";
3
- import { IEventStore } from "../event-store/interface.js";
2
+ import { MonoTypeOperatorFunction } from "rxjs";
3
+ import { IAsyncEventStoreActions, IEventStoreActions } from "../event-store/interface.js";
4
4
  /** Saves all events to an event store and filters out invalid events */
5
- export declare function mapEventsToStore(store: IEventStore, removeDuplicates?: boolean): MonoTypeOperatorFunction<NostrEvent>;
5
+ export declare function mapEventsToStore(store: IEventStoreActions | IAsyncEventStoreActions, removeDuplicates?: boolean): MonoTypeOperatorFunction<NostrEvent>;
6
+ /** Alias for {@link mapEventsToStore} */
7
+ export declare const filterDuplicateEvents: (store: IEventStoreActions | IAsyncEventStoreActions) => MonoTypeOperatorFunction<import("nostr-tools").Event>;
@@ -1,12 +1,23 @@
1
- import { distinct, filter, identity, map } from "rxjs";
1
+ import { catchError, distinct, filter, from, identity, mergeMap, of } from "rxjs";
2
2
  /** Saves all events to an event store and filters out invalid events */
3
3
  export function mapEventsToStore(store, removeDuplicates = true) {
4
4
  return (source) => source.pipe(
5
5
  // Map all events to the store
6
- // NOTE: map is used here because we want to return the single cononical version of the event so that distinct() can be used later
7
- map((event) => store.add(event)),
6
+ // NOTE: mergeMap is used here because we want to return the single instance of the event so that distinct() can be used later
7
+ mergeMap((event) => {
8
+ const r = store.add(event);
9
+ // Unwrap the promise from the async store
10
+ if (r instanceof Promise)
11
+ return from(r);
12
+ else
13
+ return of(r);
14
+ }),
15
+ // Ignore errors when inserting events into the store
16
+ catchError(() => of(null)),
8
17
  // Ignore invalid events
9
18
  filter((e) => e !== null),
10
19
  // Remove duplicates if requested
11
20
  removeDuplicates ? distinct() : identity);
12
21
  }
22
+ /** Alias for {@link mapEventsToStore} */
23
+ export const filterDuplicateEvents = (store) => mapEventsToStore(store, true);
@@ -0,0 +1,12 @@
1
+ import { insertEventIntoDescendingList } from "nostr-tools/utils";
2
+ import { pipe, scan } from "rxjs";
3
+ import { withImmediateValueOrDefault } from "./with-immediate-value.js";
4
+ /**
5
+ * Accumulate events into an ordered timeline
6
+ * @note This does not remove duplicate events
7
+ */
8
+ export function mapEventsToTimeline() {
9
+ return pipe(scan((timeline, event) => insertEventIntoDescendingList(timeline, event), []),
10
+ // Emit an empty array first. This is to prevent empty observables completing without a value (EMPTY)
11
+ withImmediateValueOrDefault([]));
12
+ }
@@ -0,0 +1,9 @@
1
+ import { ProfilePointer } from "nostr-tools/nip19";
2
+ import { type MonoTypeOperatorFunction, type Observable, type OperatorFunction } from "rxjs";
3
+ import { IEventSubscriptions } from "../event-store/interface.js";
4
+ /** RxJS operator that fetches outboxes for profile pointers from the event store */
5
+ export declare function includeMailboxes(store: IEventSubscriptions, type?: "inbox" | "outbox"): OperatorFunction<ProfilePointer[], ProfilePointer[]>;
6
+ /** Removes blacklisted relays from the user's relays */
7
+ export declare function ignoreBlacklistedRelays(blacklist: string[] | Observable<string[]>): MonoTypeOperatorFunction<ProfilePointer[]>;
8
+ /** Sets fallback relays for any user that has 0 relays */
9
+ export declare function includeFallbackRelays(fallbacks: string[] | Observable<string[]>): MonoTypeOperatorFunction<ProfilePointer[]>;
@@ -0,0 +1,43 @@
1
+ import { combineLatest, combineLatestWith, isObservable, map, of, pipe, switchMap, } from "rxjs";
2
+ import { getInboxes, getOutboxes } from "../helpers/mailboxes.js";
3
+ import { addRelayHintsToPointer } from "../helpers/pointers.js";
4
+ import { removeBlacklistedRelays, setFallbackRelays } from "../helpers/relay-selection.js";
5
+ /** RxJS operator that fetches outboxes for profile pointers from the event store */
6
+ export function includeMailboxes(store, type = "outbox") {
7
+ // Get the outboxes for all contacts
8
+ return switchMap((contacts) => combineLatest(contacts.map((user) =>
9
+ // Subscribe to the outboxes for the user
10
+ store
11
+ .replaceable({
12
+ kind: 10002,
13
+ pubkey: user.pubkey,
14
+ })
15
+ .pipe(
16
+ // Add the relays to the user
17
+ map((event) => {
18
+ if (!event)
19
+ return user;
20
+ // Get the relays from the event
21
+ const relays = type === "outbox" ? getOutboxes(event) : getInboxes(event);
22
+ if (!relays)
23
+ return user;
24
+ // Add the relays to the user
25
+ return addRelayHintsToPointer(user, relays);
26
+ })))));
27
+ }
28
+ /** Removes blacklisted relays from the user's relays */
29
+ export function ignoreBlacklistedRelays(blacklist) {
30
+ return pipe(
31
+ // Combine with the observable so it re-emits when the blacklist changes
32
+ combineLatestWith(isObservable(blacklist) ? blacklist : of(blacklist)),
33
+ // Filter the relays for the user
34
+ map(([users, blacklist]) => removeBlacklistedRelays(users, blacklist)));
35
+ }
36
+ /** Sets fallback relays for any user that has 0 relays */
37
+ export function includeFallbackRelays(fallbacks) {
38
+ return pipe(
39
+ // Get the fallbacks from the observable
40
+ combineLatestWith(isObservable(fallbacks) ? fallbacks : of(fallbacks)),
41
+ // Set the fallback relays for the users
42
+ map(([users, fallbacks]) => setFallbackRelays(users, fallbacks)));
43
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "applesauce-core",
3
- "version": "3.1.0",
3
+ "version": "4.1.0",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -64,21 +64,23 @@
64
64
  "hash-sum": "^2.0.0",
65
65
  "light-bolt11-decoder": "^3.2.0",
66
66
  "nanoid": "^5.0.9",
67
- "nostr-tools": "~2.15",
67
+ "nostr-tools": "~2.17",
68
68
  "rxjs": "^7.8.1"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@hirez_io/observer-spy": "^2.2.0",
72
72
  "@types/debug": "^4.1.12",
73
73
  "@types/hash-sum": "^1.0.2",
74
+ "rimraf": "^6.0.1",
74
75
  "typescript": "^5.8.3",
75
- "vitest": "^3.2.3"
76
+ "vitest": "^3.2.4"
76
77
  },
77
78
  "funding": {
78
79
  "type": "lightning",
79
80
  "url": "lightning:nostrudel@geyser.fund"
80
81
  },
81
82
  "scripts": {
83
+ "prebuild": "rimraf dist",
82
84
  "build": "tsc",
83
85
  "watch:build": "tsc --watch > /dev/null",
84
86
  "test": "vitest run --passWithNoTests",
@@ -1,9 +0,0 @@
1
- import { insertEventIntoDescendingList } from "nostr-tools/utils";
2
- import { scan } from "rxjs";
3
- /**
4
- * Accumulate events into an ordered timeline
5
- * @note This does not remove duplicate events
6
- */
7
- export function mapEventsToTimeline() {
8
- return scan((timeline, event) => insertEventIntoDescendingList(timeline, event), []);
9
- }