applesauce-core 4.0.0 → 4.2.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.
@@ -96,6 +96,8 @@ export declare class AsyncEventStore extends AsyncEventStore_base implements IAs
96
96
  add(event: NostrEvent, fromRelay?: string): Promise<NostrEvent | null>;
97
97
  /** Removes an event from the store and updates subscriptions */
98
98
  remove(event: string | NostrEvent): Promise<boolean>;
99
+ /** Remove multiple events that match the given filters */
100
+ removeByFilters(filters: Filter | Filter[]): Promise<number>;
99
101
  /** Add an event to the store and notifies all subscribes it has updated */
100
102
  update(event: NostrEvent): Promise<void>;
101
103
  /** Check if the store has an event by id */
@@ -243,6 +243,21 @@ export class AsyncEventStore extends EventStoreModelMixin(class {
243
243
  }
244
244
  return removed;
245
245
  }
246
+ /** Remove multiple events that match the given filters */
247
+ async removeByFilters(filters) {
248
+ // Get events that will be removed for notification
249
+ const eventsToRemove = await this.getByFilters(filters);
250
+ // Remove from memory if available
251
+ if (this.memory)
252
+ this.memory.removeByFilters(filters);
253
+ // Remove from database
254
+ const removedCount = await this.database.removeByFilters(filters);
255
+ // Notify subscriptions for each removed event
256
+ for (const event of eventsToRemove) {
257
+ this.remove$.next(event);
258
+ }
259
+ return removedCount;
260
+ }
246
261
  /** Add an event to the store and notifies all subscribes it has updated */
247
262
  async update(event) {
248
263
  // Map the event to the current instance in the database
@@ -33,6 +33,8 @@ export declare class EventMemory implements IEventMemory {
33
33
  add(event: NostrEvent): NostrEvent;
34
34
  /** Removes an event from the database and notifies all subscriptions */
35
35
  remove(eventOrId: string | NostrEvent): boolean;
36
+ /** Remove multiple events that match the given filters */
37
+ removeByFilters(filters: Filter | Filter[]): number;
36
38
  /** Notify the database that an event has updated */
37
39
  update(_event: NostrEvent): void;
38
40
  /** A weak map of events that are claimed by other things */
@@ -120,6 +120,17 @@ export class EventMemory {
120
120
  this.claims.delete(event);
121
121
  return true;
122
122
  }
123
+ /** Remove multiple events that match the given filters */
124
+ removeByFilters(filters) {
125
+ const eventsToRemove = this.getByFilters(filters);
126
+ let removedCount = 0;
127
+ for (const event of eventsToRemove) {
128
+ if (this.remove(event)) {
129
+ removedCount++;
130
+ }
131
+ }
132
+ return removedCount;
133
+ }
123
134
  /** Notify the database that an event has updated */
124
135
  update(_event) {
125
136
  // Do nothing
@@ -96,6 +96,8 @@ export declare class EventStore extends EventStore_base implements IEventStore {
96
96
  add(event: NostrEvent, fromRelay?: string): NostrEvent | null;
97
97
  /** Removes an event from the store and updates subscriptions */
98
98
  remove(event: string | NostrEvent): boolean;
99
+ /** Remove multiple events that match the given filters */
100
+ removeByFilters(filters: Filter | Filter[]): number;
99
101
  /** Add an event to the store and notifies all subscribes it has updated */
100
102
  update(event: NostrEvent): boolean;
101
103
  /** Check if the store has an event by id */
@@ -247,6 +247,21 @@ export class EventStore extends EventStoreModelMixin(class {
247
247
  }
248
248
  return removed;
249
249
  }
250
+ /** Remove multiple events that match the given filters */
251
+ removeByFilters(filters) {
252
+ // Get events that will be removed for notification
253
+ const eventsToRemove = this.getByFilters(filters);
254
+ // Remove from memory if available
255
+ if (this.memory)
256
+ this.memory.removeByFilters(filters);
257
+ // Remove from database
258
+ const removedCount = this.database.removeByFilters(filters);
259
+ // Notify subscriptions for each removed event
260
+ for (const event of eventsToRemove) {
261
+ this.remove$.next(event);
262
+ }
263
+ return removedCount;
264
+ }
250
265
  /** Add an event to the store and notifies all subscribes it has updated */
251
266
  update(event) {
252
267
  // Map the event to the current instance in the database
@@ -146,6 +146,8 @@ export interface IEventDatabase extends IEventStoreRead {
146
146
  add(event: NostrEvent): NostrEvent;
147
147
  /** Remove an event from the database */
148
148
  remove(event: string | NostrEvent): boolean;
149
+ /** Remove multiple events that match the given filters */
150
+ removeByFilters(filters: Filter | Filter[]): number;
149
151
  /** Notifies the database that an event has updated */
150
152
  update?: (event: NostrEvent) => void;
151
153
  }
@@ -155,6 +157,8 @@ export interface IAsyncEventDatabase extends IAsyncEventStoreRead {
155
157
  add(event: NostrEvent): Promise<NostrEvent>;
156
158
  /** Remove an event from the database */
157
159
  remove(event: string | NostrEvent): Promise<boolean>;
160
+ /** Remove multiple events that match the given filters */
161
+ removeByFilters(filters: Filter | Filter[]): Promise<number>;
158
162
  /** Notifies the database that an event has updated */
159
163
  update?: (event: NostrEvent) => void;
160
164
  }
@@ -1,5 +1,9 @@
1
- import { NostrEvent } from "nostr-tools";
1
+ import { kinds, NostrEvent } from "nostr-tools";
2
+ import { KnownEvent } from "./index.js";
3
+ /** Type for validated article events */
4
+ export type ArticleEvent = KnownEvent<kinds.LongFormArticle>;
2
5
  /** Returns an articles title, if it exists */
6
+ export declare function getArticleTitle(article: ArticleEvent): string;
3
7
  export declare function getArticleTitle(article: NostrEvent): string | undefined;
4
8
  /** Returns an articles image, if it exists */
5
9
  export declare function getArticleImage(article: NostrEvent): string | undefined;
@@ -7,3 +11,5 @@ export declare function getArticleImage(article: NostrEvent): string | undefined
7
11
  export declare function getArticleSummary(article: NostrEvent): string | undefined;
8
12
  /** Returns an articles published date, if it exists */
9
13
  export declare function getArticlePublished(article: NostrEvent): number;
14
+ /** validates that an event is a valid article event */
15
+ export declare function isValidArticle(article: NostrEvent): article is ArticleEvent;
@@ -1,5 +1,5 @@
1
+ import { kinds } from "nostr-tools";
1
2
  import { getTagValue } from "./event-tags.js";
2
- /** Returns an articles title, if it exists */
3
3
  export function getArticleTitle(article) {
4
4
  return getTagValue(article, "title");
5
5
  }
@@ -19,3 +19,7 @@ export function getArticlePublished(article) {
19
19
  else
20
20
  return article.created_at;
21
21
  }
22
+ /** validates that an event is a valid article event */
23
+ export function isValidArticle(article) {
24
+ return article.kind === kinds.LongFormArticle && getArticleTitle(article) !== undefined && article.content.length > 0;
25
+ }
@@ -1,5 +1,7 @@
1
1
  import { AddressPointer, EventPointer, ProfilePointer, decode } from "nostr-tools/nip19";
2
2
  import { NostrEvent } from "nostr-tools";
3
+ export type { AddressPointer, EventPointer, ProfilePointer } from "nostr-tools/nip19";
4
+ export { naddrEncode, neventEncode, noteEncode, nprofileEncode, npubEncode, nsecEncode, decode as decodePointer, } from "nostr-tools/nip19";
3
5
  export type DecodeResult = ReturnType<typeof decode>;
4
6
  export type AddressPointerWithoutD = Omit<AddressPointer, "identifier"> & {
5
7
  identifier?: string;
@@ -1,5 +1,7 @@
1
1
  import { naddrEncode, neventEncode, noteEncode, nprofileEncode, npubEncode, nsecEncode, } from "nostr-tools/nip19";
2
2
  import { getPublicKey, kinds, nip19 } from "nostr-tools";
3
+ // export nip-19 helpers
4
+ export { naddrEncode, neventEncode, noteEncode, nprofileEncode, npubEncode, nsecEncode, decode as decodePointer, } from "nostr-tools/nip19";
3
5
  import { getReplaceableIdentifier } from "./event.js";
4
6
  import { isAddressableKind } from "nostr-tools/kinds";
5
7
  import { isSafeRelayURL, mergeRelaySets } from "./relays.js";
@@ -1,4 +1,5 @@
1
- import { ProfilePointer } from "nostr-tools/nip19";
1
+ import { ProfilePointer } from "./pointers.js";
2
+ import { Filter } from "./filter.js";
2
3
  export type SelectOptimalRelaysOptions = {
3
4
  /** Maximum number of connections (relays) to select */
4
5
  maxConnections: number;
@@ -7,7 +8,17 @@ export type SelectOptimalRelaysOptions = {
7
8
  };
8
9
  /** Selects the optimal relays for a list of ProfilePointers */
9
10
  export declare function selectOptimalRelays(users: ProfilePointer[], { maxConnections, maxRelaysPerUser }: SelectOptimalRelaysOptions): ProfilePointer[];
11
+ /** Sets relays for any user that has 0 relays */
12
+ export declare function setFallbackRelays(users: ProfilePointer[], fallbacks: string[]): ProfilePointer[];
13
+ /** Removes blacklisted relays from the user's relays */
14
+ export declare function removeBlacklistedRelays(users: ProfilePointer[], blacklist: string[]): ProfilePointer[];
10
15
  /** A map of pubkeys by relay */
11
16
  export type OutboxMap = Record<string, ProfilePointer[]>;
12
- /** RxJS operator that aggregates contacts with outboxes into a relay -> pubkeys map */
17
+ /** A map of filters by relay */
18
+ export type FilterMap = Record<string, Filter | Filter[]>;
19
+ /** Creates an {@link OutboxMap} for an array of profile points (groups users by relay) */
13
20
  export declare function groupPubkeysByRelay(pointers: ProfilePointer[]): OutboxMap;
21
+ /** Alias for {@link groupPubkeysByRelay} */
22
+ export declare const createOutboxMap: typeof groupPubkeysByRelay;
23
+ /** Creates a {@link FilterMap} for an {@link OutboxMap} */
24
+ export declare function createFilterMap(outboxMap: OutboxMap, filter: Omit<Filter, "authors">): FilterMap;
@@ -68,7 +68,25 @@ export function selectOptimalRelays(users, { maxConnections, maxRelaysPerUser })
68
68
  : user.relays?.filter((relay) => selection.has(relay)),
69
69
  }));
70
70
  }
71
- /** RxJS operator that aggregates contacts with outboxes into a relay -> pubkeys map */
71
+ /** Sets relays for any user that has 0 relays */
72
+ export function setFallbackRelays(users, fallbacks) {
73
+ return users.map((user) => {
74
+ if (!user.relays || user.relays.length === 0)
75
+ return { ...user, relays: fallbacks };
76
+ else
77
+ return user;
78
+ });
79
+ }
80
+ /** Removes blacklisted relays from the user's relays */
81
+ export function removeBlacklistedRelays(users, blacklist) {
82
+ return users.map((user) => {
83
+ if (!user.relays || user.relays.length === 0)
84
+ return user;
85
+ else
86
+ return { ...user, relays: user.relays.filter((relay) => !blacklist.includes(relay)) };
87
+ });
88
+ }
89
+ /** Creates an {@link OutboxMap} for an array of profile points (groups users by relay) */
72
90
  export function groupPubkeysByRelay(pointers) {
73
91
  const outbox = {};
74
92
  for (const pointer of pointers) {
@@ -82,3 +100,12 @@ export function groupPubkeysByRelay(pointers) {
82
100
  }
83
101
  return outbox;
84
102
  }
103
+ /** Alias for {@link groupPubkeysByRelay} */
104
+ export const createOutboxMap = groupPubkeysByRelay;
105
+ /** Creates a {@link FilterMap} for an {@link OutboxMap} */
106
+ export function createFilterMap(outboxMap, filter) {
107
+ return Object.fromEntries(Array.from(Object.entries(outboxMap)).map(([relay, users]) => [
108
+ relay,
109
+ { authors: users.map((user) => user.pubkey), ...filter },
110
+ ]));
111
+ }
@@ -2,6 +2,8 @@ import { kinds, NostrEvent } from "nostr-tools";
2
2
  import { AddressPointer, EventPointer } from "nostr-tools/nip19";
3
3
  import { ParsedInvoice } from "./bolt11.js";
4
4
  import { KnownEvent } from "./index.js";
5
+ /** Type for validated zap event */
6
+ export type ZapEvent = KnownEvent<kinds.Zap>;
5
7
  export declare const ZapRequestSymbol: unique symbol;
6
8
  export declare const ZapSenderSymbol: unique symbol;
7
9
  export declare const ZapReceiverSymbol: unique symbol;
@@ -9,16 +11,16 @@ export declare const ZapInvoiceSymbol: unique symbol;
9
11
  export declare const ZapEventPointerSymbol: unique symbol;
10
12
  export declare const ZapAddressPointerSymbol: unique symbol;
11
13
  /** Returns the senders pubkey */
12
- export declare function getZapSender(zap: KnownEvent<kinds.Zap>): string;
14
+ export declare function getZapSender(zap: ZapEvent): string;
13
15
  export declare function getZapSender(zap: NostrEvent): string | undefined;
14
16
  /** Gets the receivers pubkey */
15
- export declare function getZapRecipient(zap: KnownEvent<kinds.Zap>): string;
17
+ export declare function getZapRecipient(zap: ZapEvent): string;
16
18
  export declare function getZapRecipient(zap: NostrEvent): string | undefined;
17
19
  /** Returns the parsed bolt11 invoice */
18
- export declare function getZapPayment(zap: KnownEvent<kinds.Zap>): ParsedInvoice;
20
+ export declare function getZapPayment(zap: ZapEvent): ParsedInvoice;
19
21
  export declare function getZapPayment(zap: NostrEvent): ParsedInvoice | undefined;
20
22
  /** Returns the zap event amount in msats */
21
- export declare function getZapAmount(zap: KnownEvent<kinds.Zap>): number;
23
+ export declare function getZapAmount(zap: ZapEvent): number;
22
24
  export declare function getZapAmount(zap: NostrEvent): number | undefined;
23
25
  /** Gets the AddressPointer that was zapped */
24
26
  export declare function getZapAddressPointer(zap: NostrEvent): AddressPointer | null;
@@ -27,13 +29,13 @@ export declare function getZapEventPointer(zap: NostrEvent): EventPointer | null
27
29
  /** Gets the preimage for the bolt11 invoice */
28
30
  export declare function getZapPreimage(zap: NostrEvent): string | undefined;
29
31
  /** Returns the zap request event inside the zap receipt */
30
- export declare function getZapRequest(zap: KnownEvent<kinds.Zap>): NostrEvent;
32
+ export declare function getZapRequest(zap: ZapEvent): NostrEvent;
31
33
  export declare function getZapRequest(zap: NostrEvent): NostrEvent | undefined;
32
34
  /**
33
35
  * Checks if a zap event is valid (not missing fields)
34
36
  * DOES NOT validate LNURL address
35
37
  */
36
- export declare function isValidZap(zap?: NostrEvent): zap is KnownEvent<kinds.Zap>;
38
+ export declare function isValidZap(zap?: NostrEvent): zap is ZapEvent;
37
39
  export type ZapSplit = {
38
40
  pubkey: string;
39
41
  percent: number;
@@ -5,3 +5,7 @@ import { IEventSubscriptions } from "../event-store/interface.js";
5
5
  export declare function includeMailboxes(store: IEventSubscriptions, type?: "inbox" | "outbox"): OperatorFunction<ProfilePointer[], ProfilePointer[]>;
6
6
  /** Removes blacklisted relays from the user's relays */
7
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[]>;
10
+ /** A operator calls {@link selectOptimalRelays} and filters the relays for the user */
11
+ export declare function filterOptimalRelays(maxConnections: number | Observable<number>, maxRelaysPerUser: number | Observable<number>): MonoTypeOperatorFunction<ProfilePointer[]>;
@@ -1,6 +1,7 @@
1
1
  import { combineLatest, combineLatestWith, isObservable, map, of, pipe, switchMap, } from "rxjs";
2
2
  import { getInboxes, getOutboxes } from "../helpers/mailboxes.js";
3
3
  import { addRelayHintsToPointer } from "../helpers/pointers.js";
4
+ import { removeBlacklistedRelays, selectOptimalRelays, setFallbackRelays } from "../helpers/relay-selection.js";
4
5
  /** RxJS operator that fetches outboxes for profile pointers from the event store */
5
6
  export function includeMailboxes(store, type = "outbox") {
6
7
  // Get the outboxes for all contacts
@@ -30,9 +31,21 @@ export function ignoreBlacklistedRelays(blacklist) {
30
31
  // Combine with the observable so it re-emits when the blacklist changes
31
32
  combineLatestWith(isObservable(blacklist) ? blacklist : of(blacklist)),
32
33
  // Filter the relays for the user
33
- map(([users, blacklist]) => users.map((user) => {
34
- if (!user.relays)
35
- return user;
36
- return { ...user, relays: user.relays.filter((relay) => !blacklist.includes(relay)) };
37
- })));
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
+ }
44
+ /** A operator calls {@link selectOptimalRelays} and filters the relays for the user */
45
+ export function filterOptimalRelays(maxConnections, maxRelaysPerUser) {
46
+ return pipe(
47
+ // Combine with the observable so it re-emits when the max connections and max relays per user change
48
+ combineLatestWith(isObservable(maxConnections) ? maxConnections : of(maxConnections), isObservable(maxRelaysPerUser) ? maxRelaysPerUser : of(maxRelaysPerUser)),
49
+ // Filter the relays for the user
50
+ map(([users, maxConnections, maxRelaysPerUser]) => selectOptimalRelays(users, { maxConnections, maxRelaysPerUser })));
38
51
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "applesauce-core",
3
- "version": "4.0.0",
3
+ "version": "4.2.0",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",