applesauce-core 2.3.0 → 3.0.1

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 (53) hide show
  1. package/dist/event-store/event-set.js +5 -3
  2. package/dist/event-store/event-store.d.ts +54 -24
  3. package/dist/event-store/event-store.js +139 -47
  4. package/dist/event-store/interface.d.ts +36 -18
  5. package/dist/helpers/calendar-event.d.ts +36 -0
  6. package/dist/helpers/calendar-event.js +110 -0
  7. package/dist/helpers/calendar-rsvp.d.ts +15 -0
  8. package/dist/helpers/calendar-rsvp.js +38 -0
  9. package/dist/helpers/calendar.d.ts +6 -0
  10. package/dist/helpers/calendar.js +11 -0
  11. package/dist/helpers/encrypted-content.d.ts +18 -10
  12. package/dist/helpers/encrypted-content.js +11 -3
  13. package/dist/helpers/event-cache.d.ts +15 -0
  14. package/dist/helpers/event-cache.js +32 -0
  15. package/dist/helpers/event.d.ts +1 -4
  16. package/dist/helpers/event.js +2 -10
  17. package/dist/helpers/expiration.js +1 -2
  18. package/dist/helpers/hidden-content.d.ts +3 -3
  19. package/dist/helpers/hidden-content.js +2 -2
  20. package/dist/helpers/hidden-tags.d.ts +5 -10
  21. package/dist/helpers/hidden-tags.js +3 -3
  22. package/dist/helpers/highlight.d.ts +45 -0
  23. package/dist/helpers/highlight.js +76 -0
  24. package/dist/helpers/index.d.ts +8 -0
  25. package/dist/helpers/index.js +8 -0
  26. package/dist/helpers/pointers.js +1 -1
  27. package/dist/helpers/poll.d.ts +46 -0
  28. package/dist/helpers/poll.js +78 -0
  29. package/dist/helpers/stream-chat.d.ts +4 -0
  30. package/dist/helpers/stream-chat.js +9 -0
  31. package/dist/helpers/stream.d.ts +31 -0
  32. package/dist/helpers/stream.js +81 -0
  33. package/dist/logger.d.ts +1 -0
  34. package/dist/logger.js +1 -0
  35. package/dist/models/blossom.d.ts +2 -1
  36. package/dist/models/blossom.js +4 -2
  37. package/dist/models/calendar.d.ts +6 -0
  38. package/dist/models/calendar.js +15 -0
  39. package/dist/models/common.d.ts +14 -10
  40. package/dist/models/common.js +47 -76
  41. package/dist/models/contacts.d.ts +1 -1
  42. package/dist/models/contacts.js +4 -2
  43. package/dist/models/index.d.ts +1 -0
  44. package/dist/models/index.js +1 -0
  45. package/dist/models/mailboxes.d.ts +2 -1
  46. package/dist/models/mailboxes.js +4 -2
  47. package/dist/models/mutes.d.ts +3 -2
  48. package/dist/models/mutes.js +4 -2
  49. package/dist/models/profile.d.ts +2 -1
  50. package/dist/models/profile.js +4 -2
  51. package/package.json +1 -1
  52. package/dist/event-store/common.d.ts +0 -1
  53. package/dist/event-store/common.js +0 -1
@@ -0,0 +1,78 @@
1
+ import { getOrComputeCachedValue } from "./cache.js";
2
+ import { getTagValue } from "./event-tags.js";
3
+ // NIP-88 Poll kinds
4
+ export const POLL_KIND = 1068;
5
+ export const POLL_RESPONSE_KIND = 1018;
6
+ // Cache symbols
7
+ export const PollOptionsSymbol = Symbol.for("poll-options");
8
+ /**
9
+ * Get the poll question/label from a poll event
10
+ * Returns the content field which contains the poll question
11
+ */
12
+ export function getPollQuestion(event) {
13
+ return event.content;
14
+ }
15
+ /**
16
+ * Get the poll options from a poll event
17
+ * Returns array of options with id and label
18
+ */
19
+ export function getPollOptions(event) {
20
+ return getOrComputeCachedValue(event, PollOptionsSymbol, () => {
21
+ return event.tags
22
+ .filter((tag) => tag[0] === "option" && tag.length >= 3)
23
+ .map((tag) => ({
24
+ id: tag[1],
25
+ label: tag[2],
26
+ }));
27
+ });
28
+ }
29
+ /**
30
+ * Get the relays specified for poll responses (from 'relay' tags)
31
+ * Returns undefined if no relays are specified
32
+ */
33
+ export function getPollRelays(event) {
34
+ return event.tags.filter((tag) => tag[0] === "relay" && tag.length >= 2).map((tag) => tag[1]);
35
+ }
36
+ /**
37
+ * Get the poll type from a poll event (from 'polltype' tag)
38
+ * Returns "singlechoice" or "multiplechoice", defaults to "singlechoice"
39
+ */
40
+ export function getPollType(event) {
41
+ const type = getTagValue(event, "polltype");
42
+ return type === "multiplechoice" || type === "singlechoice" ? type : "singlechoice";
43
+ }
44
+ /**
45
+ * Get the poll expiration timestamp (from 'endsAt' tag)
46
+ * Returns undefined if no expiration is set
47
+ */
48
+ export function getPollEndsAt(event) {
49
+ const endsAt = getTagValue(event, "endsAt");
50
+ return endsAt ? parseInt(endsAt, 10) : undefined;
51
+ }
52
+ /**
53
+ * Get the poll ID that a response is referencing (from 'e' tag)
54
+ * Returns undefined if no poll reference is found
55
+ */
56
+ export function getPollResponsePollId(event) {
57
+ return getTagValue(event, "e");
58
+ }
59
+ /** Get the selected option IDs from a poll response event (from 'response' tags) */
60
+ export function getPollResponseOptions(event) {
61
+ return event.tags.filter((tag) => tag[0] === "response" && tag.length >= 2).map((tag) => tag[1]);
62
+ }
63
+ /**
64
+ * Gets the options that a user has voted for in a poll event
65
+ * Returns undefined if the response is not valid
66
+ */
67
+ export function getPollResponseVotes(poll, response) {
68
+ if (poll.id !== getPollResponsePollId(response))
69
+ return;
70
+ const pollOptions = getPollOptions(poll);
71
+ const responseOptions = getPollResponseOptions(response);
72
+ const votes = responseOptions.filter((opts) => pollOptions.some((option) => option.id === opts));
73
+ const type = getPollType(poll);
74
+ // If its a single choice poll, return the first vote
75
+ if (type === "singlechoice")
76
+ return votes.length === 1 ? [votes[0]] : undefined;
77
+ return Array.from(new Set(votes));
78
+ }
@@ -0,0 +1,4 @@
1
+ import { AddressPointer } from "nostr-tools/nip19";
2
+ import { NostrEvent } from "nostr-tools";
3
+ /** Returns the pointer to the stream chat message stream */
4
+ export declare function getStreamChatMessageStream(message: NostrEvent): AddressPointer | undefined;
@@ -0,0 +1,9 @@
1
+ import { getAddressPointerFromATag } from "./pointers.js";
2
+ import { isATag } from "./tags.js";
3
+ /** Returns the pointer to the stream chat message stream */
4
+ export function getStreamChatMessageStream(message) {
5
+ const tag = message.tags.find(isATag);
6
+ if (!tag)
7
+ return undefined;
8
+ return getAddressPointerFromATag(tag);
9
+ }
@@ -0,0 +1,31 @@
1
+ import { NostrEvent } from "nostr-tools";
2
+ import { EventPointer, ProfilePointer } from "nostr-tools/nip19";
3
+ export type StreamStatus = "live" | "ended" | "planned";
4
+ export type StreamRole = "host" | "participant" | "speaker";
5
+ export declare function getStreamTitle(stream: NostrEvent): string | undefined;
6
+ export declare function getStreamSummary(stream: NostrEvent): string | undefined;
7
+ export declare function getStreamImage(stream: NostrEvent): string | undefined;
8
+ /** Returns the status of the stream, defaults to ended if the stream is older than 2 weeks */
9
+ export declare function getStreamStatus(stream: NostrEvent): StreamStatus;
10
+ /** Returns the pubkey of the host of the stream */
11
+ export declare function getStreamHost(stream: NostrEvent): ProfilePointer;
12
+ /** Returns the participants of a stream */
13
+ export declare function getStreamParticipants(stream: NostrEvent): (ProfilePointer & {
14
+ role: StreamRole;
15
+ })[];
16
+ export declare function getStreamGoalPointer(stream: NostrEvent): EventPointer | undefined;
17
+ /** Gets all the streaming urls for a stream */
18
+ export declare function getStreamStreamingURLs(stream: NostrEvent): string[];
19
+ export declare function getStreamRecording(stream: NostrEvent): string | undefined;
20
+ /** Gets the relays for a stream */
21
+ export declare function getStreamRelays(stream: NostrEvent): string[] | undefined;
22
+ /** Gets the stream start time if it has one */
23
+ export declare function getStreamStartTime(stream: NostrEvent): number | undefined;
24
+ /** Gets the stream end time if it has one */
25
+ export declare function getStreamEndTime(stream: NostrEvent): number | undefined;
26
+ /** Returns the current number of participants in the stream */
27
+ export declare function getStreamViewers(stream: NostrEvent): number | undefined;
28
+ /** Returns the maximum number of participants in the stream */
29
+ export declare function getStreamMaxViewers(stream: NostrEvent): number | undefined;
30
+ /** Returns the hashtags for a stream */
31
+ export declare function getStreamHashtags(stream: NostrEvent): string[];
@@ -0,0 +1,81 @@
1
+ import { getTagValue } from "./event-tags.js";
2
+ import { addRelayHintsToPointer, getEventPointerFromETag, getProfilePointerFromPTag } from "./pointers.js";
3
+ import { mergeRelaySets } from "./relays.js";
4
+ import { isPTag } from "./tags.js";
5
+ import { unixNow } from "./time.js";
6
+ export function getStreamTitle(stream) {
7
+ return getTagValue(stream, "title");
8
+ }
9
+ export function getStreamSummary(stream) {
10
+ return getTagValue(stream, "summary");
11
+ }
12
+ export function getStreamImage(stream) {
13
+ return getTagValue(stream, "image");
14
+ }
15
+ const TWO_WEEKS = 60 * 60 * 24 * 14;
16
+ /** Returns the status of the stream, defaults to ended if the stream is older than 2 weeks */
17
+ export function getStreamStatus(stream) {
18
+ if (stream.created_at < unixNow() - TWO_WEEKS)
19
+ return "ended";
20
+ else
21
+ return getTagValue(stream, "status") || "ended";
22
+ }
23
+ /** Returns the pubkey of the host of the stream */
24
+ export function getStreamHost(stream) {
25
+ let host = undefined;
26
+ for (const tag of stream.tags) {
27
+ if (isPTag(tag) && (!host || (tag[3] && tag[3].toLowerCase() === "host"))) {
28
+ host = getProfilePointerFromPTag(tag);
29
+ }
30
+ }
31
+ return host || { pubkey: stream.pubkey };
32
+ }
33
+ /** Returns the participants of a stream */
34
+ export function getStreamParticipants(stream) {
35
+ return stream.tags
36
+ .filter((t) => isPTag(t) && t[3])
37
+ .map((t) => ({ ...getProfilePointerFromPTag(t), role: t[3].toLowerCase() }));
38
+ }
39
+ export function getStreamGoalPointer(stream) {
40
+ const goalTag = stream.tags.find((t) => t[0] === "goal");
41
+ return goalTag && addRelayHintsToPointer(getEventPointerFromETag(goalTag), getStreamRelays(stream));
42
+ }
43
+ /** Gets all the streaming urls for a stream */
44
+ export function getStreamStreamingURLs(stream) {
45
+ return stream.tags.filter((t) => t[0] === "streaming").map((t) => t[1]);
46
+ }
47
+ export function getStreamRecording(stream) {
48
+ return getTagValue(stream, "recording");
49
+ }
50
+ /** Gets the relays for a stream */
51
+ export function getStreamRelays(stream) {
52
+ for (const tag of stream.tags) {
53
+ if (tag[0] === "relays")
54
+ return mergeRelaySets(tag.slice(1));
55
+ }
56
+ return undefined;
57
+ }
58
+ /** Gets the stream start time if it has one */
59
+ export function getStreamStartTime(stream) {
60
+ const str = getTagValue(stream, "starts");
61
+ return str ? parseInt(str) : undefined;
62
+ }
63
+ /** Gets the stream end time if it has one */
64
+ export function getStreamEndTime(stream) {
65
+ const str = getTagValue(stream, "ends");
66
+ return str ? parseInt(str) : getStreamStatus(stream) === "ended" ? stream.created_at : undefined;
67
+ }
68
+ /** Returns the current number of participants in the stream */
69
+ export function getStreamViewers(stream) {
70
+ const viewers = getTagValue(stream, "current_participants");
71
+ return viewers ? parseInt(viewers) : undefined;
72
+ }
73
+ /** Returns the maximum number of participants in the stream */
74
+ export function getStreamMaxViewers(stream) {
75
+ const viewers = getTagValue(stream, "total_participants");
76
+ return viewers ? parseInt(viewers) : undefined;
77
+ }
78
+ /** Returns the hashtags for a stream */
79
+ export function getStreamHashtags(stream) {
80
+ return stream.tags.filter((t) => t[0] === "t").map((t) => t[1]);
81
+ }
package/dist/logger.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  import debug from "debug";
2
+ /** @hidden */
2
3
  export declare const logger: debug.Debugger;
package/dist/logger.js CHANGED
@@ -1,2 +1,3 @@
1
1
  import debug from "debug";
2
+ /** @hidden */
2
3
  export const logger = debug("applesauce");
@@ -1,3 +1,4 @@
1
+ import { ProfilePointer } from "nostr-tools/nip19";
1
2
  import { Model } from "../event-store/interface.js";
2
3
  /** A model that returns a users blossom servers */
3
- export declare function UserBlossomServersModel(pubkey: string): Model<URL[]>;
4
+ export declare function UserBlossomServersModel(user: string | ProfilePointer): Model<URL[]>;
@@ -1,8 +1,10 @@
1
1
  import { map } from "rxjs/operators";
2
2
  import { BLOSSOM_SERVER_LIST_KIND, getBlossomServersFromList } from "../helpers/blossom.js";
3
3
  /** A model that returns a users blossom servers */
4
- export function UserBlossomServersModel(pubkey) {
4
+ export function UserBlossomServersModel(user) {
5
+ if (typeof user === "string")
6
+ user = { pubkey: user };
5
7
  return (store) => store
6
- .replaceable(BLOSSOM_SERVER_LIST_KIND, pubkey)
8
+ .replaceable({ kind: BLOSSOM_SERVER_LIST_KIND, pubkey: user.pubkey, relays: user.relays })
7
9
  .pipe(map((event) => (event ? getBlossomServersFromList(event) : [])));
8
10
  }
@@ -0,0 +1,6 @@
1
+ import { NostrEvent } from "nostr-tools";
2
+ import { Model } from "../event-store/interface.js";
3
+ /** A model that gets all the events for a calendar */
4
+ export declare function CalendarEventsModel(calendar: NostrEvent): Model<NostrEvent[]>;
5
+ /** A model that gets all the RSVPs for a calendar event */
6
+ export declare function CalendarEventRSVPsModel(event: NostrEvent): Model<NostrEvent[]>;
@@ -0,0 +1,15 @@
1
+ import { combineLatest } from "rxjs";
2
+ import { CALENDAR_EVENT_RSVP_KIND } from "../helpers/calendar-rsvp.js";
3
+ import { getCalendarAddressPointers } from "../helpers/calendar.js";
4
+ import { getReplaceableAddress } from "../helpers/index.js";
5
+ import { defined } from "../observable/defined.js";
6
+ /** A model that gets all the events for a calendar */
7
+ export function CalendarEventsModel(calendar) {
8
+ return (events) => combineLatest(getCalendarAddressPointers(calendar).map((p) => events.replaceable(p.kind, p.pubkey, p.identifier).pipe(defined())));
9
+ }
10
+ /** A model that gets all the RSVPs for a calendar event */
11
+ export function CalendarEventRSVPsModel(event) {
12
+ return (events) => {
13
+ return events.timeline({ kinds: [CALENDAR_EVENT_RSVP_KIND], "#a": [getReplaceableAddress(event)] });
14
+ };
15
+ }
@@ -1,16 +1,20 @@
1
1
  import { Filter, NostrEvent } from "nostr-tools";
2
+ import { AddressPointer, EventPointer } from "nostr-tools/nip19";
2
3
  import { Model } from "../event-store/interface.js";
4
+ import { AddressPointerWithoutD } from "../helpers/index.js";
3
5
  /** A model that returns a single event or undefined when its removed */
4
- export declare function EventModel(id: string): Model<NostrEvent | undefined>;
6
+ export declare function EventModel(pointer: string | EventPointer): Model<NostrEvent | undefined>;
5
7
  /** 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>;
8
+ export declare function ReplaceableModel(pointer: AddressPointer | AddressPointerWithoutD): Model<NostrEvent | undefined>;
7
9
  /** A model that returns an array of sorted events matching the filters */
8
10
  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>>;
11
+ /**
12
+ * A model that returns a multiple events in a map
13
+ * @deprecated use multiple {@link EventModel} instead
14
+ */
15
+ export declare function EventsModel(ids: string[]): Model<Record<string, NostrEvent | undefined>>;
16
+ /**
17
+ * A model that returns a directory of events by their UID
18
+ * @deprecated use multiple {@link ReplaceableModel} instead
19
+ */
20
+ export declare function ReplaceableSetModel(pointers: (AddressPointer | AddressPointerWithoutD)[]): Model<Record<string, NostrEvent | undefined>>;
@@ -1,40 +1,60 @@
1
- import { 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, map, merge, mergeWith, of, repeat, scan, takeUntil, tap, } from "rxjs";
2
+ import { insertEventIntoDescendingList } from "nostr-tools/utils";
2
3
  import { createReplaceableAddress, getEventUID, getReplaceableIdentifier, isReplaceable, matchFilters, } from "../helpers/index.js";
3
4
  import { claimEvents } from "../observable/claim-events.js";
4
5
  import { claimLatest } from "../observable/claim-latest.js";
5
- import { insertEventIntoDescendingList } from "nostr-tools/utils";
6
6
  import { withImmediateValueOrDefault } from "../observable/with-immediate-value.js";
7
7
  /** A model that returns a single event or undefined when its removed */
8
- export function EventModel(id) {
8
+ export function EventModel(pointer) {
9
+ if (typeof pointer === "string")
10
+ pointer = { id: pointer };
9
11
  return (events) => merge(
10
12
  // get current event and ignore if there is none
11
13
  defer(() => {
12
- let event = events.getEvent(id);
13
- return event ? of(event) : EMPTY;
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));
14
21
  }),
15
- // subscribe to updates
16
- events.insert$.pipe(filter((e) => e.id === id)),
17
- // subscribe to updates
18
- events.updated(id),
22
+ // Listen for new events
23
+ events.insert$.pipe(filter((e) => e.id === pointer.id)),
19
24
  // emit undefined when deleted
20
- events.removed(id).pipe(endWith(undefined))).pipe(
25
+ events.removed(pointer.id).pipe(endWith(undefined))).pipe(
21
26
  // claim all events
22
27
  claimLatest(events),
28
+ // ignore duplicate events
29
+ distinctUntilChanged((a, b) => a?.id === b?.id),
23
30
  // always emit undefined so the observable is synchronous
24
31
  withImmediateValueOrDefault(undefined));
25
32
  }
26
33
  /** A model that returns the latest version of a replaceable event or undefined if its removed */
27
- export function ReplaceableModel(kind, pubkey, d) {
34
+ export function ReplaceableModel(pointer) {
28
35
  return (events) => {
29
36
  let current = undefined;
30
37
  return merge(
31
38
  // lazily get current event
32
39
  defer(() => {
33
- let event = events.getReplaceable(kind, pubkey, d);
34
- return event ? of(event) : EMPTY;
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
+ }
35
53
  }),
36
54
  // subscribe to new events
37
- events.insert$.pipe(filter((e) => e.pubkey == pubkey && e.kind === kind && (d !== undefined ? getReplaceableIdentifier(e) === d : true)))).pipe(
55
+ events.insert$.pipe(filter((e) => e.pubkey == pointer.pubkey &&
56
+ e.kind === pointer.kind &&
57
+ (pointer.identifier !== undefined ? getReplaceableIdentifier(e) === pointer.identifier : true)))).pipe(
38
58
  // only update if event is newer
39
59
  distinctUntilChanged((prev, event) => {
40
60
  // are the events the same? i.e. is the prev event older
@@ -108,69 +128,20 @@ export function TimelineModel(filters, includeOldVersion) {
108
128
  finalize(() => seen.clear()));
109
129
  };
110
130
  }
111
- /** A model that returns a multiple events in a map */
131
+ /**
132
+ * A model that returns a multiple events in a map
133
+ * @deprecated use multiple {@link EventModel} instead
134
+ */
112
135
  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
- }, {}));
136
+ return (events) => combineLatest(Object.fromEntries(ids.map((id) => [id, events.model(EventModel, { id })])));
139
137
  }
140
- /** A model that returns a directory of events by their UID */
138
+ /**
139
+ * A model that returns a directory of events by their UID
140
+ * @deprecated use multiple {@link ReplaceableModel} instead
141
+ */
141
142
  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
- };
143
+ return (events) => combineLatest(Object.fromEntries(pointers.map((pointer) => [
144
+ createReplaceableAddress(pointer.kind, pointer.pubkey, pointer.identifier),
145
+ events.model(ReplaceableModel, pointer),
146
+ ])));
176
147
  }
@@ -1,7 +1,7 @@
1
1
  import { ProfilePointer } from "nostr-tools/nip19";
2
2
  import { Model } from "../event-store/interface.js";
3
3
  /** A model that returns all contacts for a user */
4
- export declare function ContactsModel(pubkey: string): Model<ProfilePointer[]>;
4
+ export declare function ContactsModel(user: string | ProfilePointer): Model<ProfilePointer[]>;
5
5
  /** A model that returns all public contacts for a user */
6
6
  export declare function PublicContactsModel(pubkey: string): Model<ProfilePointer[] | undefined>;
7
7
  /** A model that returns all hidden contacts for a user */
@@ -3,8 +3,10 @@ import { map } from "rxjs/operators";
3
3
  import { getContacts, getHiddenContacts, getPublicContacts } from "../helpers/contacts.js";
4
4
  import { watchEventUpdates } from "../observable/index.js";
5
5
  /** A model that returns all contacts for a user */
6
- export function ContactsModel(pubkey) {
7
- return (events) => events.replaceable(kinds.Contacts, pubkey).pipe(
6
+ export function ContactsModel(user) {
7
+ if (typeof user === "string")
8
+ user = { pubkey: user };
9
+ return (events) => events.replaceable({ kind: kinds.Contacts, pubkey: user.pubkey, relays: user.relays }).pipe(
8
10
  // listen for event updates (hidden tags unlocked)
9
11
  watchEventUpdates(events),
10
12
  // Get all contacts
@@ -1,5 +1,6 @@
1
1
  export * from "./blossom.js";
2
2
  export * from "./bookmarks.js";
3
+ export * from "./calendar.js";
3
4
  export * from "./channels.js";
4
5
  export * from "./comments.js";
5
6
  export * from "./common.js";
@@ -1,5 +1,6 @@
1
1
  export * from "./blossom.js";
2
2
  export * from "./bookmarks.js";
3
+ export * from "./calendar.js";
3
4
  export * from "./channels.js";
4
5
  export * from "./comments.js";
5
6
  export * from "./common.js";
@@ -1,6 +1,7 @@
1
+ import { ProfilePointer } from "nostr-tools/nip19";
1
2
  import { Model } from "../event-store/interface.js";
2
3
  /** A model that gets and parses the inbox and outbox relays for a pubkey */
3
- export declare function MailboxesModel(pubkey: string): Model<{
4
+ export declare function MailboxesModel(user: string | ProfilePointer): Model<{
4
5
  inboxes: string[];
5
6
  outboxes: string[];
6
7
  } | undefined>;
@@ -2,8 +2,10 @@ import { kinds } from "nostr-tools";
2
2
  import { map } from "rxjs/operators";
3
3
  import { getInboxes, getOutboxes } from "../helpers/mailboxes.js";
4
4
  /** A model that gets and parses the inbox and outbox relays for a pubkey */
5
- export function MailboxesModel(pubkey) {
6
- return (events) => events.replaceable(kinds.RelayList, pubkey).pipe(map((event) => event && {
5
+ export function MailboxesModel(user) {
6
+ if (typeof user === "string")
7
+ user = { pubkey: user };
8
+ return (events) => events.replaceable({ kind: kinds.RelayList, pubkey: user.pubkey, relays: user.relays }).pipe(map((event) => event && {
7
9
  inboxes: getInboxes(event),
8
10
  outboxes: getOutboxes(event),
9
11
  }));
@@ -1,7 +1,8 @@
1
- import { Mutes } from "../helpers/mutes.js";
1
+ import { ProfilePointer } from "nostr-tools/nip19";
2
2
  import { Model } from "../event-store/interface.js";
3
+ import { Mutes } from "../helpers/mutes.js";
3
4
  /** A model that returns all a users muted things */
4
- export declare function MuteModel(pubkey: string): Model<Mutes | undefined>;
5
+ export declare function MuteModel(user: string | ProfilePointer): Model<Mutes | undefined>;
5
6
  /** A model that returns all a users public muted things */
6
7
  export declare function PublicMuteModel(pubkey: string): Model<Mutes | undefined>;
7
8
  /** A model that returns all a users hidden muted things */
@@ -3,8 +3,10 @@ import { map } from "rxjs/operators";
3
3
  import { getHiddenMutedThings, getMutedThings, getPublicMutedThings } from "../helpers/mutes.js";
4
4
  import { watchEventUpdates } from "../observable/watch-event-updates.js";
5
5
  /** A model that returns all a users muted things */
6
- export function MuteModel(pubkey) {
7
- return (events) => events.replaceable(kinds.Mutelist, pubkey).pipe(
6
+ export function MuteModel(user) {
7
+ if (typeof user === "string")
8
+ user = { pubkey: user };
9
+ return (events) => events.replaceable({ kind: kinds.Mutelist, pubkey: user.pubkey, relays: user.relays }).pipe(
8
10
  // listen for event updates (hidden tags unlocked)
9
11
  watchEventUpdates(events),
10
12
  // Get all muted things
@@ -1,4 +1,5 @@
1
1
  import { Model } from "../event-store/interface.js";
2
2
  import { ProfileContent } from "../helpers/profile.js";
3
+ import { ProfilePointer } from "nostr-tools/nip19";
3
4
  /** A model that gets and parses the kind 0 metadata for a pubkey */
4
- export declare function ProfileModel(pubkey: string): Model<ProfileContent | undefined>;
5
+ export declare function ProfileModel(user: string | ProfilePointer): Model<ProfileContent | undefined>;
@@ -3,8 +3,10 @@ import { filter, map } from "rxjs/operators";
3
3
  import { getProfileContent, isValidProfile } from "../helpers/profile.js";
4
4
  import { withImmediateValueOrDefault } from "../observable/with-immediate-value.js";
5
5
  /** A model that gets and parses the kind 0 metadata for a pubkey */
6
- export function ProfileModel(pubkey) {
7
- return (events) => events.replaceable(kinds.Metadata, pubkey).pipe(
6
+ export function ProfileModel(user) {
7
+ if (typeof user === "string")
8
+ user = { pubkey: user };
9
+ return (events) => events.replaceable({ kind: kinds.Metadata, pubkey: user.pubkey, relays: user.relays }).pipe(
8
10
  // Filter out invalid profile events
9
11
  filter(isValidProfile),
10
12
  // Parse the profile event into a ProfileContent
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "applesauce-core",
3
- "version": "2.3.0",
3
+ "version": "3.0.1",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};