applesauce-core 0.0.0-next-20250725170333 → 0.0.0-next-20250726160247

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.
@@ -1,8 +1,9 @@
1
1
  import { Filter, NostrEvent } from "nostr-tools";
2
2
  import { Observable } from "rxjs";
3
+ import { AddressPointer, EventPointer, ProfilePointer } from "nostr-tools/nip19";
4
+ import { AddressPointerWithoutD } from "../helpers/pointers.js";
3
5
  import { EventSet } from "./event-set.js";
4
6
  import { IEventStore, ModelConstructor } from "./interface.js";
5
- import { AddressPointer, EventPointer } from "nostr-tools/nip19";
6
7
  /** An extended {@link EventSet} that handles replaceable events, delets, and models */
7
8
  export declare class EventStore implements IEventStore {
8
9
  database: EventSet;
@@ -19,6 +20,21 @@ export declare class EventStore implements IEventStore {
19
20
  update$: Observable<NostrEvent>;
20
21
  /** A stream of events that have been removed */
21
22
  remove$: Observable<NostrEvent>;
23
+ /**
24
+ * A method that will be called when an event isn't found in the store
25
+ * @experimental
26
+ */
27
+ eventLoader?: (pointer: EventPointer) => Observable<NostrEvent>;
28
+ /**
29
+ * A method that will be called when a replaceable event isn't found in the store
30
+ * @experimental
31
+ */
32
+ replaceableLoader?: (pointer: AddressPointerWithoutD) => Observable<NostrEvent>;
33
+ /**
34
+ * A method that will be called when an addressable event isn't found in the store
35
+ * @experimental
36
+ */
37
+ addressableLoader?: (pointer: AddressPointer) => Observable<NostrEvent>;
22
38
  constructor();
23
39
  protected deletedIds: Set<string>;
24
40
  protected deletedCoords: Map<string, number>;
@@ -76,36 +92,39 @@ export declare class EventStore implements IEventStore {
76
92
  /** Creates an observable that emits when event is updated */
77
93
  updated(event: string | NostrEvent): Observable<NostrEvent>;
78
94
  /** Creates a {@link EventModel} */
79
- event(id: string): Observable<NostrEvent | undefined>;
95
+ event(pointer: string | EventPointer): Observable<NostrEvent | undefined>;
80
96
  /** Creates a {@link ReplaceableModel} */
97
+ replaceable(pointer: AddressPointer | AddressPointerWithoutD): Observable<NostrEvent | undefined>;
81
98
  replaceable(kind: number, pubkey: string, identifier?: string): Observable<NostrEvent | undefined>;
99
+ /** Subscribe to an addressable event by pointer */
100
+ addressable(pointer: AddressPointer): Observable<NostrEvent | undefined>;
82
101
  /** Creates a {@link TimelineModel} */
83
102
  timeline(filters: Filter | Filter[], includeOldVersion?: boolean): Observable<NostrEvent[]>;
84
- /** Creates a {@link EventsModel} */
85
- events(ids: string[]): Observable<Record<string, NostrEvent>>;
86
- /** Creates a {@link ReplaceableSetModel} */
87
- replaceableSet(pointers: {
88
- kind: number;
89
- pubkey: string;
90
- identifier?: string;
91
- }[]): Observable<Record<string, NostrEvent>>;
92
- /** Creates a {@link ProfileModel} */
93
- profile(pubkey: string): Observable<import("../helpers/profile.js").ProfileContent | undefined>;
94
- /** Creates a {@link ContactsModel} */
95
- contacts(pubkey: string): Observable<import("nostr-tools/nip19").ProfilePointer[]>;
96
- /** Creates a {@link MuteModel} */
97
- mutes(pubkey: string): Observable<import("../helpers/mutes.js").Mutes | undefined>;
98
- /** Creates a {@link ReactionsModel} */
99
- reactions(event: NostrEvent): Observable<import("nostr-tools").Event[]>;
100
- /** Creates a {@link MailboxesModel} */
101
- mailboxes(pubkey: string): Observable<{
103
+ /** Subscribe to a users profile */
104
+ profile(user: string | ProfilePointer): Observable<import("../helpers/profile.js").ProfileContent | undefined>;
105
+ /** Subscribe to a users contacts */
106
+ contacts(user: string | ProfilePointer): Observable<ProfilePointer[]>;
107
+ /** Subscribe to a users mutes */
108
+ mutes(user: string | ProfilePointer): Observable<import("../helpers/mutes.js").Mutes | undefined>;
109
+ /** Subscribe to a users NIP-65 mailboxes */
110
+ mailboxes(user: string | ProfilePointer): Observable<{
102
111
  inboxes: string[];
103
112
  outboxes: string[];
104
113
  } | undefined>;
105
- /** Creates a {@link UserBlossomServersModel} */
106
- blossomServers(pubkey: string): Observable<URL[]>;
107
- /** Creates a {@link ThreadModel} */
114
+ /** Subscribe to a users blossom servers */
115
+ blossomServers(user: string | ProfilePointer): Observable<URL[]>;
116
+ /** Subscribe to an event's reactions */
117
+ reactions(event: NostrEvent): Observable<import("nostr-tools").Event[]>;
118
+ /** Subscribe to a thread */
108
119
  thread(root: string | EventPointer | AddressPointer): Observable<import("../models/thread.js").Thread>;
109
- /** Creates a {@link CommentsModel} */
120
+ /** Subscribe to a event's comments */
110
121
  comments(event: NostrEvent): Observable<import("nostr-tools").Event[]>;
122
+ /** @deprecated use multiple {@link EventModel} instead */
123
+ events(ids: string[]): Observable<Record<string, NostrEvent | undefined>>;
124
+ /** @deprecated use multiple {@link ReplaceableModel} instead */
125
+ replaceableSet(pointers: {
126
+ kind: number;
127
+ pubkey: string;
128
+ identifier?: string;
129
+ }[]): Observable<Record<string, NostrEvent | undefined>>;
111
130
  }
@@ -7,15 +7,15 @@ import { EventStoreSymbol, FromCacheSymbol, getReplaceableAddress, isReplaceable
7
7
  import { matchFilters } from "../helpers/filter.js";
8
8
  import { parseCoordinate } from "../helpers/pointers.js";
9
9
  import { addSeenRelay, getSeenRelays } from "../helpers/relays.js";
10
+ import { UserBlossomServersModel } from "../models/blossom.js";
10
11
  import { EventModel, EventsModel, ReplaceableModel, ReplaceableSetModel, TimelineModel } from "../models/common.js";
11
- import { EventSet } from "./event-set.js";
12
- import { ProfileModel } from "../models/profile.js";
13
12
  import { ContactsModel } from "../models/contacts.js";
13
+ import { CommentsModel, ThreadModel } from "../models/index.js";
14
+ import { MailboxesModel } from "../models/mailboxes.js";
14
15
  import { MuteModel } from "../models/mutes.js";
16
+ import { ProfileModel } from "../models/profile.js";
15
17
  import { ReactionsModel } from "../models/reactions.js";
16
- import { MailboxesModel } from "../models/mailboxes.js";
17
- import { UserBlossomServersModel } from "../models/blossom.js";
18
- import { CommentsModel, ThreadModel } from "../models/index.js";
18
+ import { EventSet } from "./event-set.js";
19
19
  /** An extended {@link EventSet} that handles replaceable events, delets, and models */
20
20
  export class EventStore {
21
21
  database;
@@ -32,6 +32,21 @@ export class EventStore {
32
32
  update$;
33
33
  /** A stream of events that have been removed */
34
34
  remove$;
35
+ /**
36
+ * A method that will be called when an event isn't found in the store
37
+ * @experimental
38
+ */
39
+ eventLoader;
40
+ /**
41
+ * A method that will be called when a replaceable event isn't found in the store
42
+ * @experimental
43
+ */
44
+ replaceableLoader;
45
+ /**
46
+ * A method that will be called when an addressable event isn't found in the store
47
+ * @experimental
48
+ */
49
+ addressableLoader;
35
50
  constructor() {
36
51
  this.database = new EventSet();
37
52
  // verify events before they are added to the database
@@ -285,55 +300,79 @@ export class EventStore {
285
300
  }
286
301
  // Helper methods for creating models
287
302
  /** Creates a {@link EventModel} */
288
- event(id) {
289
- return this.model(EventModel, id);
303
+ event(pointer) {
304
+ if (typeof pointer === "string")
305
+ pointer = { id: pointer };
306
+ return this.model(EventModel, pointer);
307
+ }
308
+ replaceable(...args) {
309
+ let pointer;
310
+ // Parse arguments
311
+ if (args.length === 1) {
312
+ pointer = args[0];
313
+ }
314
+ else if (args.length === 3 || args.length === 2) {
315
+ let [kind, pubkey, identifier] = args;
316
+ pointer = { kind, pubkey, identifier };
317
+ }
318
+ if (!pointer)
319
+ throw new Error("Invalid arguments, expected address pointer or kind, pubkey, identifier");
320
+ return this.model(ReplaceableModel, pointer);
290
321
  }
291
- /** Creates a {@link ReplaceableModel} */
292
- replaceable(kind, pubkey, identifier) {
293
- return this.model(ReplaceableModel, kind, pubkey, identifier);
322
+ /** Subscribe to an addressable event by pointer */
323
+ addressable(pointer) {
324
+ return this.model(ReplaceableModel, pointer);
294
325
  }
295
326
  /** Creates a {@link TimelineModel} */
296
327
  timeline(filters, includeOldVersion = false) {
297
328
  return this.model(TimelineModel, filters, includeOldVersion);
298
329
  }
299
- /** Creates a {@link EventsModel} */
300
- events(ids) {
301
- return this.model(EventsModel, ids);
302
- }
303
- /** Creates a {@link ReplaceableSetModel} */
304
- replaceableSet(pointers) {
305
- return this.model(ReplaceableSetModel, pointers);
306
- }
307
- /** Creates a {@link ProfileModel} */
308
- profile(pubkey) {
309
- return this.model(ProfileModel, pubkey);
310
- }
311
- /** Creates a {@link ContactsModel} */
312
- contacts(pubkey) {
313
- return this.model(ContactsModel, pubkey);
314
- }
315
- /** Creates a {@link MuteModel} */
316
- mutes(pubkey) {
317
- return this.model(MuteModel, pubkey);
318
- }
319
- /** Creates a {@link ReactionsModel} */
330
+ /** Subscribe to a users profile */
331
+ profile(user) {
332
+ return this.model(ProfileModel, user);
333
+ }
334
+ /** Subscribe to a users contacts */
335
+ contacts(user) {
336
+ if (typeof user === "string")
337
+ user = { pubkey: user };
338
+ return this.model(ContactsModel, user);
339
+ }
340
+ /** Subscribe to a users mutes */
341
+ mutes(user) {
342
+ if (typeof user === "string")
343
+ user = { pubkey: user };
344
+ return this.model(MuteModel, user);
345
+ }
346
+ /** Subscribe to a users NIP-65 mailboxes */
347
+ mailboxes(user) {
348
+ if (typeof user === "string")
349
+ user = { pubkey: user };
350
+ return this.model(MailboxesModel, user);
351
+ }
352
+ /** Subscribe to a users blossom servers */
353
+ blossomServers(user) {
354
+ if (typeof user === "string")
355
+ user = { pubkey: user };
356
+ return this.model(UserBlossomServersModel, user);
357
+ }
358
+ /** Subscribe to an event's reactions */
320
359
  reactions(event) {
321
360
  return this.model(ReactionsModel, event);
322
361
  }
323
- /** Creates a {@link MailboxesModel} */
324
- mailboxes(pubkey) {
325
- return this.model(MailboxesModel, pubkey);
326
- }
327
- /** Creates a {@link UserBlossomServersModel} */
328
- blossomServers(pubkey) {
329
- return this.model(UserBlossomServersModel, pubkey);
330
- }
331
- /** Creates a {@link ThreadModel} */
362
+ /** Subscribe to a thread */
332
363
  thread(root) {
333
364
  return this.model(ThreadModel, root);
334
365
  }
335
- /** Creates a {@link CommentsModel} */
366
+ /** Subscribe to a event's comments */
336
367
  comments(event) {
337
368
  return this.model(CommentsModel, event);
338
369
  }
370
+ /** @deprecated use multiple {@link EventModel} instead */
371
+ events(ids) {
372
+ return this.model(EventsModel, ids);
373
+ }
374
+ /** @deprecated use multiple {@link ReplaceableModel} instead */
375
+ replaceableSet(pointers) {
376
+ return this.model(ReplaceableSetModel, pointers);
377
+ }
339
378
  }
@@ -65,25 +65,13 @@ export interface IEventStoreSubscriptions {
65
65
  }
66
66
  /** Methods for creating common models */
67
67
  export interface IEventStoreModels {
68
+ model<T extends unknown, Args extends Array<any>>(constructor: ModelConstructor<T, Args>, ...args: Args): Observable<T>;
68
69
  event(id: string): Observable<NostrEvent | undefined>;
69
- events(ids: string[]): Observable<Record<string, NostrEvent>>;
70
- replaceable(kind: number, pubkey: string, identifier?: string): Observable<NostrEvent | undefined>;
71
- replaceableSet(pointers: {
72
- kind: number;
73
- pubkey: string;
74
- identifier?: string;
75
- }[]): Observable<Record<string, NostrEvent>>;
70
+ replaceable(pointer: AddressPointerWithoutD): Observable<NostrEvent | undefined>;
71
+ addressable(pointer: AddressPointer): Observable<NostrEvent | undefined>;
76
72
  timeline(filters: Filter | Filter[], includeOldVersion?: boolean): Observable<NostrEvent[]>;
77
- profile(pubkey: string): Observable<ProfileContent | undefined>;
78
- contacts(pubkey: string): Observable<ProfilePointer[]>;
79
- mutes(pubkey: string): Observable<Mutes | undefined>;
80
- reactions(event: NostrEvent): Observable<NostrEvent[]>;
81
- mailboxes(pubkey: string): Observable<{
82
- inboxes: string[];
83
- outboxes: string[];
84
- } | undefined>;
85
- blossomServers(pubkey: string): Observable<URL[]>;
86
- thread(root: string | EventPointer | AddressPointer): Observable<Thread>;
73
+ events(ids: string[]): Observable<Record<string, NostrEvent | undefined>>;
74
+ replaceableSet(pointers: (AddressPointer | AddressPointerWithoutD)[]): Observable<Record<string, NostrEvent | undefined>>;
87
75
  }
88
76
  /** A computed view of an event set or event store */
89
77
  export type Model<T extends unknown> = (events: IEventStore) => Observable<T>;
@@ -99,5 +87,19 @@ export interface IEventStore extends IEventStoreRead, IEventStoreStreams, IEvent
99
87
  filters(filters: Filter | Filter[]): Observable<NostrEvent>;
100
88
  updated(id: string | NostrEvent): Observable<NostrEvent>;
101
89
  removed(id: string): Observable<never>;
102
- model<T extends unknown, Args extends Array<any>>(constructor: ModelConstructor<T, Args>, ...args: Args): Observable<T>;
90
+ replaceable(kind: number, pubkey: string, identifier?: string): Observable<NostrEvent | undefined>;
91
+ replaceable(pointer: AddressPointerWithoutD): Observable<NostrEvent | undefined>;
92
+ eventLoader?: (pointer: EventPointer) => Observable<NostrEvent>;
93
+ replaceableLoader?: (pointer: AddressPointerWithoutD) => Observable<NostrEvent>;
94
+ addressableLoader?: (pointer: AddressPointer) => Observable<NostrEvent>;
95
+ profile(user: string | ProfilePointer): Observable<ProfileContent | undefined>;
96
+ contacts(user: string | ProfilePointer): Observable<ProfilePointer[]>;
97
+ mutes(user: string | ProfilePointer): Observable<Mutes | undefined>;
98
+ mailboxes(user: string | ProfilePointer): Observable<{
99
+ inboxes: string[];
100
+ outboxes: string[];
101
+ } | undefined>;
102
+ blossomServers(user: string | ProfilePointer): Observable<URL[]>;
103
+ reactions(event: NostrEvent): Observable<NostrEvent[]>;
104
+ thread(root: string | EventPointer | AddressPointer): Observable<Thread>;
103
105
  }
@@ -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
  }
@@ -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,52 @@
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, 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
+ return events.eventLoader?.(pointer) ?? EMPTY;
14
19
  }),
15
- // subscribe to updates
16
- events.insert$.pipe(filter((e) => e.id === id)),
17
- // subscribe to updates
18
- events.updated(id),
20
+ // Listen for new events
21
+ events.insert$.pipe(filter((e) => e.id === pointer.id)),
19
22
  // emit undefined when deleted
20
- events.removed(id).pipe(endWith(undefined))).pipe(
23
+ events.removed(pointer.id).pipe(endWith(undefined))).pipe(
21
24
  // claim all events
22
25
  claimLatest(events),
26
+ // ignore duplicate events
27
+ distinctUntilChanged((a, b) => a?.id === b?.id),
23
28
  // always emit undefined so the observable is synchronous
24
29
  withImmediateValueOrDefault(undefined));
25
30
  }
26
31
  /** A model that returns the latest version of a replaceable event or undefined if its removed */
27
- export function ReplaceableModel(kind, pubkey, d) {
32
+ export function ReplaceableModel(pointer) {
28
33
  return (events) => {
29
34
  let current = undefined;
30
35
  return merge(
31
36
  // lazily get current event
32
37
  defer(() => {
33
- let event = events.getReplaceable(kind, pubkey, d);
34
- return event ? of(event) : EMPTY;
38
+ let event = events.getReplaceable(pointer.kind, pointer.pubkey, pointer.identifier);
39
+ if (event)
40
+ return of(event);
41
+ else if (pointer.identifier !== undefined)
42
+ return events.addressableLoader?.(pointer) ?? EMPTY;
43
+ else
44
+ return events.replaceableLoader?.(pointer) ?? EMPTY;
35
45
  }),
36
46
  // subscribe to new events
37
- events.insert$.pipe(filter((e) => e.pubkey == pubkey && e.kind === kind && (d !== undefined ? getReplaceableIdentifier(e) === d : true)))).pipe(
47
+ events.insert$.pipe(filter((e) => e.pubkey == pointer.pubkey &&
48
+ e.kind === pointer.kind &&
49
+ (pointer.identifier !== undefined ? getReplaceableIdentifier(e) === pointer.identifier : true)))).pipe(
38
50
  // only update if event is newer
39
51
  distinctUntilChanged((prev, event) => {
40
52
  // are the events the same? i.e. is the prev event older
@@ -108,69 +120,20 @@ export function TimelineModel(filters, includeOldVersion) {
108
120
  finalize(() => seen.clear()));
109
121
  };
110
122
  }
111
- /** A model that returns a multiple events in a map */
123
+ /**
124
+ * A model that returns a multiple events in a map
125
+ * @deprecated use multiple {@link EventModel} instead
126
+ */
112
127
  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
- }, {}));
128
+ return (events) => combineLatest(Object.fromEntries(ids.map((id) => [id, events.model(EventModel, { id })])));
139
129
  }
140
- /** A model that returns a directory of events by their UID */
130
+ /**
131
+ * A model that returns a directory of events by their UID
132
+ * @deprecated use multiple {@link ReplaceableModel} instead
133
+ */
141
134
  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
- };
135
+ return (events) => combineLatest(Object.fromEntries(pointers.map((pointer) => [
136
+ createReplaceableAddress(pointer.kind, pointer.pubkey, pointer.identifier),
137
+ events.model(ReplaceableModel, pointer),
138
+ ])));
176
139
  }
@@ -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,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": "0.0.0-next-20250725170333",
3
+ "version": "0.0.0-next-20250726160247",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",