applesauce-core 3.0.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/dist/event-store/async-event-store.d.ts +134 -0
  2. package/dist/event-store/async-event-store.js +349 -0
  3. package/dist/event-store/{event-set.d.ts → event-memory.d.ts} +15 -25
  4. package/dist/event-store/{event-set.js → event-memory.js} +43 -53
  5. package/dist/event-store/event-store.d.ts +57 -63
  6. package/dist/event-store/event-store.js +111 -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 +111 -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 +5 -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 +19 -8
  44. package/dist/helpers/legacy-messages.js +25 -14
  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 +1 -1
  54. package/dist/helpers/pointers.js +2 -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 +13 -0
  58. package/dist/helpers/relay-selection.js +84 -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 +10 -1
  63. package/dist/helpers/wrapped-messages.js +15 -2
  64. package/dist/helpers/zap.d.ts +16 -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 +7 -0
  85. package/dist/observable/relay-selection.js +38 -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
@@ -0,0 +1,134 @@
1
+ import { Filter, NostrEvent } from "nostr-tools";
2
+ import { AddressPointer, EventPointer } from "nostr-tools/nip19";
3
+ import { Observable, Subject } from "rxjs";
4
+ import { AddressPointerWithoutD } from "../helpers/pointers.js";
5
+ import { EventMemory } from "./event-memory.js";
6
+ import { IAsyncEventDatabase, IAsyncEventStore } from "./interface.js";
7
+ declare const AsyncEventStore_base: {
8
+ new (...args: any[]): {
9
+ [x: string]: any;
10
+ models: Map<import("./interface.js").ModelConstructor<any, any[], import("./interface.js").IEventStore | IAsyncEventStore>, Map<string, Observable<any>>>;
11
+ modelKeepWarm: number;
12
+ model<T extends unknown, Args extends Array<any>>(constructor: import("./interface.js").ModelConstructor<T, Args, import("./interface.js").IEventStore | IAsyncEventStore>, ...args: Args): Observable<T>;
13
+ filters(filters: Filter | Filter[], onlyNew?: boolean): Observable<NostrEvent>;
14
+ event(pointer: string | EventPointer): Observable<NostrEvent | undefined>;
15
+ replaceable(pointer: AddressPointer | AddressPointerWithoutD): Observable<NostrEvent | undefined>;
16
+ replaceable(kind: number, pubkey: string, identifier?: string): Observable<NostrEvent | undefined>;
17
+ addressable(pointer: AddressPointer): Observable<NostrEvent | undefined>;
18
+ timeline(filters: Filter | Filter[], includeOldVersion?: boolean): Observable<NostrEvent[]>;
19
+ profile(user: string | import("nostr-tools/nip19").ProfilePointer): Observable<import("../helpers/profile.js").ProfileContent | undefined>;
20
+ contacts(user: string | import("nostr-tools/nip19").ProfilePointer): Observable<import("nostr-tools/nip19").ProfilePointer[]>;
21
+ mutes(user: string | import("nostr-tools/nip19").ProfilePointer): Observable<import("../helpers/mutes.js").Mutes | undefined>;
22
+ mailboxes(user: string | import("nostr-tools/nip19").ProfilePointer): Observable<{
23
+ inboxes: string[];
24
+ outboxes: string[];
25
+ } | undefined>;
26
+ blossomServers(user: string | import("nostr-tools/nip19").ProfilePointer): Observable<URL[]>;
27
+ reactions(event: NostrEvent): Observable<import("nostr-tools").Event[]>;
28
+ thread(root: string | EventPointer | AddressPointer): Observable<import("../models/thread.js").Thread>;
29
+ comments(event: NostrEvent): Observable<import("nostr-tools").Event[]>;
30
+ events(ids: string[]): Observable<Record<string, NostrEvent | undefined>>;
31
+ replaceableSet(pointers: {
32
+ kind: number;
33
+ pubkey: string;
34
+ identifier?: string;
35
+ }[]): Observable<Record<string, NostrEvent | undefined>>;
36
+ };
37
+ } & {
38
+ new (): {};
39
+ };
40
+ /** An async wrapper around an async event database that handles replaceable events, deletes, and models */
41
+ export declare class AsyncEventStore extends AsyncEventStore_base implements IAsyncEventStore {
42
+ database: IAsyncEventDatabase;
43
+ /** Optional memory database for ensuring single event instances */
44
+ memory?: EventMemory;
45
+ /** Enable this to keep old versions of replaceable events */
46
+ keepOldVersions: boolean;
47
+ /** Enable this to keep expired events */
48
+ keepExpired: boolean;
49
+ /**
50
+ * A method used to verify new events before added them
51
+ * @returns true if the event is valid, false if it should be ignored
52
+ */
53
+ verifyEvent?: (event: NostrEvent) => boolean;
54
+ /** A stream of new events added to the store */
55
+ insert$: Subject<import("nostr-tools").Event>;
56
+ /** A stream of events that have been updated */
57
+ update$: Subject<import("nostr-tools").Event>;
58
+ /** A stream of events that have been removed */
59
+ remove$: Subject<import("nostr-tools").Event>;
60
+ /**
61
+ * A method that will be called when an event isn't found in the store
62
+ * @experimental
63
+ */
64
+ eventLoader?: (pointer: EventPointer) => Observable<NostrEvent> | Promise<NostrEvent | undefined>;
65
+ /**
66
+ * A method that will be called when a replaceable event isn't found in the store
67
+ * @experimental
68
+ */
69
+ replaceableLoader?: (pointer: AddressPointerWithoutD) => Observable<NostrEvent> | Promise<NostrEvent | undefined>;
70
+ /**
71
+ * A method that will be called when an addressable event isn't found in the store
72
+ * @experimental
73
+ */
74
+ addressableLoader?: (pointer: AddressPointer) => Observable<NostrEvent> | Promise<NostrEvent | undefined>;
75
+ constructor(database: IAsyncEventDatabase);
76
+ /** A method to add all events to memory to ensure there is only ever a single instance of an event */
77
+ private mapToMemory;
78
+ protected deletedIds: Set<string>;
79
+ protected deletedCoords: Map<string, number>;
80
+ protected checkDeleted(event: string | NostrEvent): boolean;
81
+ protected expirations: Map<string, number>;
82
+ /** Adds an event to the expiration map */
83
+ protected addExpiration(event: NostrEvent): void;
84
+ protected expirationTimeout: number | null;
85
+ protected nextExpirationCheck: number | null;
86
+ protected handleExpiringEvent(event: NostrEvent): void;
87
+ /** Remove expired events from the store */
88
+ protected pruneExpired(): Promise<void>;
89
+ protected handleDeleteEvent(deleteEvent: NostrEvent): Promise<void>;
90
+ /** Copies important metadata from and identical event to another */
91
+ static mergeDuplicateEvent(source: NostrEvent, dest: NostrEvent): void;
92
+ /**
93
+ * Adds an event to the store and update subscriptions
94
+ * @returns The existing event or the event that was added, if it was ignored returns null
95
+ */
96
+ add(event: NostrEvent, fromRelay?: string): Promise<NostrEvent | null>;
97
+ /** Removes an event from the store and updates subscriptions */
98
+ remove(event: string | NostrEvent): Promise<boolean>;
99
+ /** Add an event to the store and notifies all subscribes it has updated */
100
+ update(event: NostrEvent): Promise<void>;
101
+ /** Check if the store has an event by id */
102
+ hasEvent(id: string): Promise<boolean>;
103
+ /** Get an event by id from the store */
104
+ getEvent(id: string): Promise<NostrEvent | undefined>;
105
+ /** Check if the store has a replaceable event */
106
+ hasReplaceable(kind: number, pubkey: string, d?: string): Promise<boolean>;
107
+ /** Gets the latest version of a replaceable event */
108
+ getReplaceable(kind: number, pubkey: string, identifier?: string): Promise<NostrEvent | undefined>;
109
+ /** Returns all versions of a replaceable event */
110
+ getReplaceableHistory(kind: number, pubkey: string, identifier?: string): Promise<NostrEvent[] | undefined>;
111
+ /** Get all events matching a filter */
112
+ getByFilters(filters: Filter | Filter[]): Promise<NostrEvent[]>;
113
+ /** Returns a timeline of events that match filters */
114
+ getTimeline(filters: Filter | Filter[]): Promise<NostrEvent[]>;
115
+ /** Passthrough method for the database.touch */
116
+ touch(event: NostrEvent): void | undefined;
117
+ /** Sets the claim on the event and touches it */
118
+ claim(event: NostrEvent, claim: any): void;
119
+ /** Checks if an event is claimed by anything */
120
+ isClaimed(event: NostrEvent): boolean;
121
+ /** Removes a claim from an event */
122
+ removeClaim(event: NostrEvent, claim: any): void;
123
+ /** Removes all claims on an event */
124
+ clearClaim(event: NostrEvent): void;
125
+ /** Pass through method for the database.unclaimed */
126
+ unclaimed(): Generator<NostrEvent>;
127
+ /** Removes any event that is not being used by a subscription */
128
+ prune(limit?: number): number;
129
+ /** Returns an observable that completes when an event is removed */
130
+ removed(id: string): Observable<never>;
131
+ /** Creates an observable that emits when event is updated */
132
+ updated(event: string | NostrEvent): Observable<NostrEvent>;
133
+ }
134
+ export {};
@@ -0,0 +1,349 @@
1
+ import { kinds } from "nostr-tools";
2
+ import { isAddressableKind } from "nostr-tools/kinds";
3
+ import { EMPTY, filter, mergeMap, Subject, take } from "rxjs";
4
+ import { getDeleteCoordinates, getDeleteIds } from "../helpers/delete.js";
5
+ import { createReplaceableAddress, EventStoreSymbol, FromCacheSymbol, isReplaceable } from "../helpers/event.js";
6
+ import { getExpirationTimestamp } from "../helpers/expiration.js";
7
+ import { parseCoordinate } from "../helpers/pointers.js";
8
+ import { addSeenRelay, getSeenRelays } from "../helpers/relays.js";
9
+ import { unixNow } from "../helpers/time.js";
10
+ import { EventMemory } from "./event-memory.js";
11
+ import { EventStoreModelMixin } from "./model-mixin.js";
12
+ /** An async wrapper around an async event database that handles replaceable events, deletes, and models */
13
+ export class AsyncEventStore extends EventStoreModelMixin(class {
14
+ }) {
15
+ database;
16
+ /** Optional memory database for ensuring single event instances */
17
+ memory;
18
+ /** Enable this to keep old versions of replaceable events */
19
+ keepOldVersions = false;
20
+ /** Enable this to keep expired events */
21
+ keepExpired = false;
22
+ /**
23
+ * A method used to verify new events before added them
24
+ * @returns true if the event is valid, false if it should be ignored
25
+ */
26
+ verifyEvent;
27
+ /** A stream of new events added to the store */
28
+ insert$ = new Subject();
29
+ /** A stream of events that have been updated */
30
+ update$ = new Subject();
31
+ /** A stream of events that have been removed */
32
+ remove$ = new Subject();
33
+ /**
34
+ * A method that will be called when an event isn't found in the store
35
+ * @experimental
36
+ */
37
+ eventLoader;
38
+ /**
39
+ * A method that will be called when a replaceable event isn't found in the store
40
+ * @experimental
41
+ */
42
+ replaceableLoader;
43
+ /**
44
+ * A method that will be called when an addressable event isn't found in the store
45
+ * @experimental
46
+ */
47
+ addressableLoader;
48
+ constructor(database) {
49
+ super();
50
+ this.database = database;
51
+ this.memory = new EventMemory();
52
+ // when events are added to the database, add the symbol
53
+ this.insert$.subscribe((event) => {
54
+ Reflect.set(event, EventStoreSymbol, this);
55
+ });
56
+ // when events are removed from the database, remove the symbol
57
+ this.remove$.subscribe((event) => {
58
+ Reflect.deleteProperty(event, EventStoreSymbol);
59
+ });
60
+ }
61
+ mapToMemory(event) {
62
+ if (event === undefined)
63
+ return undefined;
64
+ if (!this.memory)
65
+ return event;
66
+ return this.memory.add(event);
67
+ }
68
+ // delete state
69
+ deletedIds = new Set();
70
+ deletedCoords = new Map();
71
+ checkDeleted(event) {
72
+ if (typeof event === "string")
73
+ return this.deletedIds.has(event);
74
+ else {
75
+ if (this.deletedIds.has(event.id))
76
+ return true;
77
+ if (isAddressableKind(event.kind)) {
78
+ const identifier = event.tags.find((t) => t[0] === "d")?.[1];
79
+ const deleted = this.deletedCoords.get(createReplaceableAddress(event.kind, event.pubkey, identifier));
80
+ if (deleted)
81
+ return deleted > event.created_at;
82
+ }
83
+ }
84
+ return false;
85
+ }
86
+ expirations = new Map();
87
+ /** Adds an event to the expiration map */
88
+ addExpiration(event) {
89
+ const expiration = getExpirationTimestamp(event);
90
+ if (expiration && Number.isFinite(expiration))
91
+ this.expirations.set(event.id, expiration);
92
+ }
93
+ expirationTimeout = null;
94
+ nextExpirationCheck = null;
95
+ handleExpiringEvent(event) {
96
+ const expiration = getExpirationTimestamp(event);
97
+ if (!expiration)
98
+ return;
99
+ // Add event to expiration map
100
+ this.expirations.set(event.id, expiration);
101
+ // Exit if the next check is already less than the next expiration
102
+ if (this.expirationTimeout && this.nextExpirationCheck && this.nextExpirationCheck < expiration)
103
+ return;
104
+ // Set timeout to prune expired events
105
+ if (this.expirationTimeout)
106
+ clearTimeout(this.expirationTimeout);
107
+ const timeout = expiration - unixNow();
108
+ this.expirationTimeout = setTimeout(this.pruneExpired.bind(this), timeout * 1000 + 10);
109
+ this.nextExpirationCheck = expiration;
110
+ }
111
+ /** Remove expired events from the store */
112
+ async pruneExpired() {
113
+ const now = unixNow();
114
+ for (const [id, expiration] of this.expirations) {
115
+ if (expiration <= now) {
116
+ this.expirations.delete(id);
117
+ await this.remove(id);
118
+ }
119
+ }
120
+ // Cleanup timers
121
+ if (this.expirationTimeout)
122
+ clearTimeout(this.expirationTimeout);
123
+ this.nextExpirationCheck = null;
124
+ this.expirationTimeout = null;
125
+ }
126
+ // handling delete events
127
+ async handleDeleteEvent(deleteEvent) {
128
+ const ids = getDeleteIds(deleteEvent);
129
+ for (const id of ids) {
130
+ this.deletedIds.add(id);
131
+ // remove deleted events in the database
132
+ await this.remove(id);
133
+ }
134
+ const coords = getDeleteCoordinates(deleteEvent);
135
+ for (const coord of coords) {
136
+ this.deletedCoords.set(coord, Math.max(this.deletedCoords.get(coord) ?? 0, deleteEvent.created_at));
137
+ // Parse the nostr address coordinate
138
+ const parsed = parseCoordinate(coord);
139
+ if (!parsed)
140
+ continue;
141
+ // Remove older versions of replaceable events
142
+ const events = await this.database.getReplaceableHistory(parsed.kind, parsed.pubkey, parsed.identifier);
143
+ if (events) {
144
+ for (const event of events) {
145
+ if (event.created_at < deleteEvent.created_at)
146
+ await this.remove(event);
147
+ }
148
+ }
149
+ }
150
+ }
151
+ /** Copies important metadata from and identical event to another */
152
+ static mergeDuplicateEvent(source, dest) {
153
+ const relays = getSeenRelays(source);
154
+ if (relays) {
155
+ for (const relay of relays)
156
+ addSeenRelay(dest, relay);
157
+ }
158
+ // copy the from cache symbol only if its true
159
+ const fromCache = Reflect.get(source, FromCacheSymbol);
160
+ if (fromCache && !Reflect.get(dest, FromCacheSymbol))
161
+ Reflect.set(dest, FromCacheSymbol, fromCache);
162
+ }
163
+ /**
164
+ * Adds an event to the store and update subscriptions
165
+ * @returns The existing event or the event that was added, if it was ignored returns null
166
+ */
167
+ async add(event, fromRelay) {
168
+ // Handle delete events differently
169
+ if (event.kind === kinds.EventDeletion)
170
+ await this.handleDeleteEvent(event);
171
+ // Ignore if the event was deleted
172
+ if (this.checkDeleted(event))
173
+ return event;
174
+ // Reject expired events if keepExpired is false
175
+ const expiration = getExpirationTimestamp(event);
176
+ if (this.keepExpired === false && expiration && expiration <= unixNow())
177
+ return null;
178
+ // Get the replaceable identifier
179
+ const identifier = isReplaceable(event.kind) ? event.tags.find((t) => t[0] === "d")?.[1] : undefined;
180
+ // Don't insert the event if there is already a newer version
181
+ if (!this.keepOldVersions && isReplaceable(event.kind)) {
182
+ const existing = await this.database.getReplaceableHistory(event.kind, event.pubkey, identifier);
183
+ // If there is already a newer version, copy cached symbols and return existing event
184
+ if (existing && existing.length > 0 && existing[0].created_at >= event.created_at) {
185
+ AsyncEventStore.mergeDuplicateEvent(event, existing[0]);
186
+ return existing[0];
187
+ }
188
+ }
189
+ // Verify event before inserting into the database
190
+ if (this.verifyEvent && this.verifyEvent(event) === false)
191
+ return null;
192
+ // Always add event to memory
193
+ const existing = this.memory?.add(event);
194
+ // If the memory returned a different instance, this is a duplicate event
195
+ if (existing && existing !== event) {
196
+ // Copy cached symbols and return existing event
197
+ AsyncEventStore.mergeDuplicateEvent(event, existing);
198
+ // attach relay this event was from
199
+ if (fromRelay)
200
+ addSeenRelay(existing, fromRelay);
201
+ return existing;
202
+ }
203
+ // Insert event into database
204
+ const inserted = this.mapToMemory(await this.database.add(event));
205
+ // Copy cached data if its a duplicate event
206
+ if (event !== inserted)
207
+ AsyncEventStore.mergeDuplicateEvent(event, inserted);
208
+ // attach relay this event was from
209
+ if (fromRelay)
210
+ addSeenRelay(inserted, fromRelay);
211
+ // Emit insert$ signal
212
+ if (inserted === event)
213
+ this.insert$.next(inserted);
214
+ // remove all old version of the replaceable event
215
+ if (!this.keepOldVersions && isReplaceable(event.kind)) {
216
+ const existing = await this.database.getReplaceableHistory(event.kind, event.pubkey, identifier);
217
+ if (existing && existing.length > 0) {
218
+ const older = Array.from(existing).filter((e) => e.created_at < event.created_at);
219
+ for (const old of older)
220
+ await this.remove(old);
221
+ // return the newest version of the replaceable event
222
+ // most of the time this will be === event, but not always
223
+ if (existing.length !== older.length)
224
+ return existing[0];
225
+ }
226
+ }
227
+ // Add event to expiration map
228
+ if (this.keepExpired === false && expiration)
229
+ this.handleExpiringEvent(inserted);
230
+ return inserted;
231
+ }
232
+ /** Removes an event from the store and updates subscriptions */
233
+ async remove(event) {
234
+ let instance = this.memory?.getEvent(typeof event === "string" ? event : event.id);
235
+ // Remove from memory if available
236
+ if (this.memory)
237
+ this.memory.remove(event);
238
+ // Remove the event from the database
239
+ const removed = await this.database.remove(event);
240
+ // If the event was removed, notify the subscriptions
241
+ if (removed && instance) {
242
+ this.remove$.next(instance);
243
+ }
244
+ return removed;
245
+ }
246
+ /** Add an event to the store and notifies all subscribes it has updated */
247
+ async update(event) {
248
+ // Map the event to the current instance in the database
249
+ const e = await this.database.add(event);
250
+ if (!e)
251
+ return;
252
+ // Notify the database that the event has updated
253
+ this.database.update?.(event);
254
+ this.update$.next(event);
255
+ }
256
+ /** Check if the store has an event by id */
257
+ async hasEvent(id) {
258
+ // Check if the event exists in memory first, then in the database
259
+ return (this.memory?.hasEvent(id) ?? false) || (await this.database.hasEvent(id));
260
+ }
261
+ /** Get an event by id from the store */
262
+ async getEvent(id) {
263
+ // Get the event from memory first, then from the database
264
+ return this.memory?.getEvent(id) ?? this.mapToMemory(await this.database.getEvent(id));
265
+ }
266
+ /** Check if the store has a replaceable event */
267
+ async hasReplaceable(kind, pubkey, d) {
268
+ // Check if the event exists in memory first, then in the database
269
+ return ((this.memory?.hasReplaceable(kind, pubkey, d) ?? false) || (await this.database.hasReplaceable(kind, pubkey, d)));
270
+ }
271
+ /** Gets the latest version of a replaceable event */
272
+ async getReplaceable(kind, pubkey, identifier) {
273
+ // Get the event from memory first, then from the database
274
+ return (this.memory?.getReplaceable(kind, pubkey, identifier) ??
275
+ this.mapToMemory(await this.database.getReplaceable(kind, pubkey, identifier)));
276
+ }
277
+ /** Returns all versions of a replaceable event */
278
+ async getReplaceableHistory(kind, pubkey, identifier) {
279
+ // Get the events from memory first, then from the database
280
+ const memoryEvents = this.memory?.getReplaceableHistory(kind, pubkey, identifier);
281
+ if (memoryEvents)
282
+ return memoryEvents;
283
+ const dbEvents = await this.database.getReplaceableHistory(kind, pubkey, identifier);
284
+ return dbEvents?.map((e) => this.mapToMemory(e) ?? e);
285
+ }
286
+ /** Get all events matching a filter */
287
+ async getByFilters(filters) {
288
+ // NOTE: no way to read from memory since memory won't have the full set of events
289
+ const events = await this.database.getByFilters(filters);
290
+ // Map events to memory if available for better performance
291
+ if (this.memory)
292
+ return events.map((e) => this.mapToMemory(e) ?? e);
293
+ else
294
+ return events;
295
+ }
296
+ /** Returns a timeline of events that match filters */
297
+ async getTimeline(filters) {
298
+ const events = await this.database.getTimeline(filters);
299
+ if (this.memory)
300
+ return events.map((e) => this.mapToMemory(e));
301
+ else
302
+ return events;
303
+ }
304
+ /** Passthrough method for the database.touch */
305
+ touch(event) {
306
+ return this.memory?.touch(event);
307
+ }
308
+ /** Sets the claim on the event and touches it */
309
+ claim(event, claim) {
310
+ return this.memory?.claim(event, claim);
311
+ }
312
+ /** Checks if an event is claimed by anything */
313
+ isClaimed(event) {
314
+ return this.memory?.isClaimed(event) ?? false;
315
+ }
316
+ /** Removes a claim from an event */
317
+ removeClaim(event, claim) {
318
+ return this.memory?.removeClaim(event, claim);
319
+ }
320
+ /** Removes all claims on an event */
321
+ clearClaim(event) {
322
+ return this.memory?.clearClaim(event);
323
+ }
324
+ /** Pass through method for the database.unclaimed */
325
+ unclaimed() {
326
+ return this.memory?.unclaimed() || (function* () { })();
327
+ }
328
+ /** Removes any event that is not being used by a subscription */
329
+ prune(limit) {
330
+ return this.memory?.prune(limit) ?? 0;
331
+ }
332
+ /** Returns an observable that completes when an event is removed */
333
+ removed(id) {
334
+ const deleted = this.checkDeleted(id);
335
+ if (deleted)
336
+ return EMPTY;
337
+ return this.remove$.pipe(
338
+ // listen for removed events
339
+ filter((e) => e.id === id),
340
+ // complete as soon as we find a matching removed event
341
+ take(1),
342
+ // switch to empty
343
+ mergeMap(() => EMPTY));
344
+ }
345
+ /** Creates an observable that emits when event is updated */
346
+ updated(event) {
347
+ return this.update$.pipe(filter((e) => e.id === event || e === event));
348
+ }
349
+ }
@@ -1,12 +1,8 @@
1
1
  import { Filter, NostrEvent } from "nostr-tools";
2
- import { Subject } from "rxjs";
3
2
  import { LRU } from "../helpers/lru.js";
4
- import { IEventSet } from "./interface.js";
5
- /**
6
- * A set of nostr events that can be queried and subscribed to
7
- * NOTE: does not handle replaceable events or any deletion logic
8
- */
9
- export declare class EventSet implements IEventSet {
3
+ import { IEventMemory } from "./interface.js";
4
+ /** An in-memory database of events */
5
+ export declare class EventMemory implements IEventMemory {
10
6
  protected log: import("debug").Debugger;
11
7
  /** Indexes */
12
8
  protected kinds: Map<number, Set<import("nostr-tools").Event>>;
@@ -17,18 +13,8 @@ export declare class EventSet implements IEventSet {
17
13
  events: LRU<import("nostr-tools").Event>;
18
14
  /** A sorted array of replaceable events by address */
19
15
  protected replaceable: Map<string, import("nostr-tools").Event[]>;
20
- /** A stream of events inserted into the database */
21
- insert$: Subject<import("nostr-tools").Event>;
22
- /** A stream of events that have been updated */
23
- update$: Subject<import("nostr-tools").Event>;
24
- /** A stream of events removed from the database */
25
- remove$: Subject<import("nostr-tools").Event>;
26
- /** A method thats called before a new event is inserted */
27
- onBeforeInsert?: (event: NostrEvent) => boolean;
28
16
  /** The number of events in the event set */
29
17
  get size(): number;
30
- /** Moves an event to the top of the LRU cache */
31
- touch(event: NostrEvent): void;
32
18
  /** Checks if the database contains an event without touching it */
33
19
  hasEvent(id: string): boolean;
34
20
  /** Gets a single event based on id */
@@ -40,17 +26,19 @@ export declare class EventSet implements IEventSet {
40
26
  /** Gets the history of a replaceable event */
41
27
  getReplaceableHistory(kind: number, pubkey: string, identifier?: string): NostrEvent[] | undefined;
42
28
  /** Gets all events that match the filters */
43
- getByFilters(filters: Filter | Filter[]): Set<NostrEvent>;
29
+ getByFilters(filters: Filter | Filter[]): NostrEvent[];
44
30
  /** Gets a timeline of events that match the filters */
45
31
  getTimeline(filters: Filter | Filter[]): NostrEvent[];
46
32
  /** Inserts an event into the database and notifies all subscriptions */
47
- add(event: NostrEvent): NostrEvent | null;
48
- /** Inserts and event into the database and notifies all subscriptions that the event has updated */
49
- update(event: NostrEvent): boolean;
33
+ add(event: NostrEvent): NostrEvent;
50
34
  /** Removes an event from the database and notifies all subscriptions */
51
35
  remove(eventOrId: string | NostrEvent): boolean;
36
+ /** Notify the database that an event has updated */
37
+ update(_event: NostrEvent): void;
52
38
  /** A weak map of events that are claimed by other things */
53
39
  protected claims: WeakMap<import("nostr-tools").Event, any>;
40
+ /** Moves an event to the top of the LRU cache */
41
+ touch(event: NostrEvent): void;
54
42
  /** Sets the claim on the event and touches it */
55
43
  claim(event: NostrEvent, claim: any): void;
56
44
  /** Checks if an event is claimed by anything */
@@ -59,6 +47,10 @@ export declare class EventSet implements IEventSet {
59
47
  removeClaim(event: NostrEvent, claim: any): void;
60
48
  /** Removes all claims on an event */
61
49
  clearClaim(event: NostrEvent): void;
50
+ /** Returns a generator of unclaimed events in order of least used */
51
+ unclaimed(): Generator<NostrEvent>;
52
+ /** Removes events that are not claimed (free up memory) */
53
+ prune(limit?: number): number;
62
54
  /** Index helper methods */
63
55
  protected getKindIndex(kind: number): Set<import("nostr-tools").Event>;
64
56
  protected getAuthorsIndex(author: string): Set<import("nostr-tools").Event>;
@@ -74,11 +66,9 @@ export declare class EventSet implements IEventSet {
74
66
  /** Iterates over all events by id */
75
67
  iterateIds(ids: Iterable<string>): Generator<NostrEvent>;
76
68
  /** Returns all events that match the filter */
77
- getEventsForFilter(filter: Filter): Set<NostrEvent>;
69
+ protected getEventsForFilter(filter: Filter): Set<NostrEvent>;
78
70
  /** Returns all events that match the filters */
79
- getEventsForFilters(filters: Filter[]): Set<NostrEvent>;
80
- /** Remove the oldest events that are not claimed */
81
- prune(limit?: number): number;
71
+ protected getEventsForFilters(filters: Filter[]): Set<NostrEvent>;
82
72
  /** Resets the event set */
83
73
  reset(): void;
84
74
  }