applesauce-core 0.0.0-next-20251205152544 → 0.0.0-next-20251220152312

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 (55) hide show
  1. package/dist/event-factory/methods.js +4 -0
  2. package/dist/event-store/async-delete-manager.d.ts +26 -0
  3. package/dist/event-store/async-delete-manager.js +33 -0
  4. package/dist/event-store/async-event-store.d.ts +32 -24
  5. package/dist/event-store/async-event-store.js +130 -164
  6. package/dist/event-store/delete-manager.d.ts +30 -0
  7. package/dist/event-store/delete-manager.js +99 -0
  8. package/dist/event-store/event-models.d.ts +5 -22
  9. package/dist/event-store/event-models.js +10 -7
  10. package/dist/event-store/event-store.d.ts +33 -23
  11. package/dist/event-store/event-store.js +151 -162
  12. package/dist/event-store/expiration-manager.d.ts +37 -0
  13. package/dist/event-store/expiration-manager.js +97 -0
  14. package/dist/event-store/index.d.ts +3 -0
  15. package/dist/event-store/index.js +3 -0
  16. package/dist/event-store/interface.d.ts +73 -6
  17. package/dist/helpers/contacts.d.ts +1 -0
  18. package/dist/helpers/contacts.js +5 -5
  19. package/dist/helpers/delete.d.ts +8 -1
  20. package/dist/helpers/delete.js +25 -1
  21. package/dist/helpers/encrypted-content.d.ts +3 -1
  22. package/dist/helpers/event-cache.js +1 -1
  23. package/dist/helpers/event.d.ts +4 -4
  24. package/dist/helpers/event.js +5 -5
  25. package/dist/helpers/factory.js +4 -4
  26. package/dist/helpers/hidden-content.js +4 -4
  27. package/dist/helpers/hidden-tags.d.ts +6 -2
  28. package/dist/helpers/hidden-tags.js +9 -3
  29. package/dist/helpers/index.d.ts +1 -1
  30. package/dist/helpers/index.js +1 -1
  31. package/dist/helpers/model.d.ts +5 -0
  32. package/dist/helpers/model.js +17 -0
  33. package/dist/helpers/pointers.d.ts +19 -38
  34. package/dist/helpers/pointers.js +78 -84
  35. package/dist/helpers/profile.d.ts +1 -2
  36. package/dist/helpers/profile.js +1 -1
  37. package/dist/helpers/relays.d.ts +2 -0
  38. package/dist/helpers/relays.js +4 -0
  39. package/dist/helpers/string.js +8 -2
  40. package/dist/helpers/tags.d.ts +3 -1
  41. package/dist/helpers/tags.js +5 -1
  42. package/dist/models/base.js +43 -33
  43. package/dist/models/index.d.ts +0 -1
  44. package/dist/models/index.js +0 -1
  45. package/dist/observable/watch-event-updates.d.ts +2 -0
  46. package/dist/observable/watch-event-updates.js +2 -0
  47. package/dist/operations/client.js +2 -2
  48. package/dist/operations/delete.js +5 -3
  49. package/dist/operations/tag/common.d.ts +5 -4
  50. package/dist/operations/tag/common.js +24 -11
  51. package/package.json +6 -1
  52. package/dist/helpers/lists.d.ts +0 -58
  53. package/dist/helpers/lists.js +0 -110
  54. package/dist/models/relays.d.ts +0 -27
  55. package/dist/models/relays.js +0 -36
@@ -1,4 +1,5 @@
1
1
  import { EncryptedContentSymbol } from "../helpers/encrypted-content.js";
2
+ import { isEvent } from "../helpers/event.js";
2
3
  import { eventPipe } from "../helpers/pipeline.js";
3
4
  import { unixNow } from "../helpers/time.js";
4
5
  import { setClient } from "../operations/client.js";
@@ -36,5 +37,8 @@ export async function createEvent(context, blueprint, ...args) {
36
37
  }
37
38
  /** Modifies an event using a context and a set of operations */
38
39
  export async function modifyEvent(event, context, ...operations) {
40
+ // NOTE: Unwrapping evnet object in order to handle cast events from applesauce-common
41
+ if ("event" in event && isEvent(event.event))
42
+ event = event.event;
39
43
  return await wrapCommon(stripSignature(), stripStamp(), updateCreatedAt(), ...operations)(event, context);
40
44
  }
@@ -0,0 +1,26 @@
1
+ import { Observable } from "rxjs";
2
+ import { NostrEvent } from "../helpers/event.js";
3
+ import { DeleteEventNotification, IAsyncDeleteManager } from "./interface.js";
4
+ /** Async manager for deletion state, ensuring users can only delete their own events */
5
+ export declare class AsyncDeleteManager implements IAsyncDeleteManager {
6
+ /** A stream of pointers that may have been deleted */
7
+ readonly deleted$: Observable<DeleteEventNotification>;
8
+ /** Internal sync delete manager instance for state */
9
+ private internal;
10
+ constructor();
11
+ /**
12
+ * Process a kind 5 delete event
13
+ * Extracts event pointers and address pointers from the delete event
14
+ * Enforces that users can only delete their own events
15
+ */
16
+ add(deleteEvent: NostrEvent): Promise<DeleteEventNotification[]>;
17
+ /**
18
+ * Check if an event is deleted
19
+ * Verifies the event was deleted by its own author
20
+ */
21
+ check(event: NostrEvent): Promise<boolean>;
22
+ /**
23
+ * Filter out all deleted events from an array of events
24
+ */
25
+ filter(events: NostrEvent[]): Promise<NostrEvent[]>;
26
+ }
@@ -0,0 +1,33 @@
1
+ import { DeleteManager } from "./delete-manager.js";
2
+ /** Async manager for deletion state, ensuring users can only delete their own events */
3
+ export class AsyncDeleteManager {
4
+ /** A stream of pointers that may have been deleted */
5
+ deleted$;
6
+ /** Internal sync delete manager instance for state */
7
+ internal;
8
+ constructor() {
9
+ this.internal = new DeleteManager();
10
+ this.deleted$ = this.internal.deleted$;
11
+ }
12
+ /**
13
+ * Process a kind 5 delete event
14
+ * Extracts event pointers and address pointers from the delete event
15
+ * Enforces that users can only delete their own events
16
+ */
17
+ async add(deleteEvent) {
18
+ return this.internal.add(deleteEvent);
19
+ }
20
+ /**
21
+ * Check if an event is deleted
22
+ * Verifies the event was deleted by its own author
23
+ */
24
+ async check(event) {
25
+ return this.internal.check(event);
26
+ }
27
+ /**
28
+ * Filter out all deleted events from an array of events
29
+ */
30
+ async filter(events) {
31
+ return this.internal.filter(events);
32
+ }
33
+ }
@@ -4,16 +4,38 @@ import { Filter } from "../helpers/filter.js";
4
4
  import { AddressPointer, AddressPointerWithoutD, EventPointer } from "../helpers/pointers.js";
5
5
  import { EventMemory } from "./event-memory.js";
6
6
  import { EventModels } from "./event-models.js";
7
- import { IAsyncEventDatabase, IAsyncEventStore } from "./interface.js";
7
+ import { IAsyncDeleteManager, IAsyncEventDatabase, IAsyncEventStore, IExpirationManager } from "./interface.js";
8
+ export type AsyncEventStoreOptions = {
9
+ /** Keep deleted events in the store */
10
+ keepDeleted?: boolean;
11
+ /** Keep expired events in the store */
12
+ keepExpired?: boolean;
13
+ /** Enable this to keep old versions of replaceable events */
14
+ keepOldVersions?: boolean;
15
+ /** The database to use for storing events */
16
+ database: IAsyncEventDatabase;
17
+ /** Custom {@link IAsyncDeleteManager} implementation */
18
+ deleteManager?: IAsyncDeleteManager;
19
+ /** Custom {@link IExpirationManager} implementation */
20
+ expirationManager?: IExpirationManager;
21
+ /** The method used to verify events */
22
+ verifyEvent?: (event: NostrEvent) => boolean;
23
+ };
8
24
  /** An async wrapper around an async event database that handles replaceable events, deletes, and models */
9
25
  export declare class AsyncEventStore extends EventModels implements IAsyncEventStore {
10
26
  database: IAsyncEventDatabase;
11
27
  /** Optional memory database for ensuring single event instances */
12
28
  memory: EventMemory;
29
+ /** Manager for handling event deletions with authorization */
30
+ private deletes;
31
+ /** Manager for handling event expirations */
32
+ private expiration;
13
33
  /** Enable this to keep old versions of replaceable events */
14
34
  keepOldVersions: boolean;
15
- /** Enable this to keep expired events */
35
+ /** Keep expired events in the store */
16
36
  keepExpired: boolean;
37
+ /** Keep deleted events in the store */
38
+ keepDeleted: boolean;
17
39
  /** The method used to verify events */
18
40
  private _verifyEventMethod?;
19
41
  /** Get the method used to verify events */
@@ -22,7 +44,7 @@ export declare class AsyncEventStore extends EventModels implements IAsyncEventS
22
44
  set verifyEvent(method: undefined | ((event: NostrEvent) => boolean));
23
45
  /** A stream of new events added to the store */
24
46
  insert$: Subject<import("nostr-tools/core").Event>;
25
- /** A stream of events that have been updated */
47
+ /** A stream of events that have been updated (Warning: this is a very noisy stream, use with caution) */
26
48
  update$: Subject<import("nostr-tools/core").Event>;
27
49
  /** A stream of events that have been removed */
28
50
  remove$: Subject<import("nostr-tools/core").Event>;
@@ -30,23 +52,13 @@ export declare class AsyncEventStore extends EventModels implements IAsyncEventS
30
52
  * A method that will be called when an event isn't found in the store
31
53
  */
32
54
  eventLoader?: (pointer: EventPointer | AddressPointer | AddressPointerWithoutD) => Observable<NostrEvent> | Promise<NostrEvent | undefined>;
33
- constructor(database: IAsyncEventDatabase);
55
+ constructor(options: AsyncEventStoreOptions);
34
56
  /** A method to add all events to memory to ensure there is only ever a single instance of an event */
35
57
  private mapToMemory;
36
- protected deletedIds: Set<string>;
37
- protected deletedCoords: Map<string, number>;
38
- protected checkDeleted(event: string | NostrEvent): boolean;
39
- protected expirations: Map<string, number>;
40
- /** Adds an event to the expiration map */
41
- protected addExpiration(event: NostrEvent): void;
42
- protected expirationTimeout: number | null;
43
- protected nextExpirationCheck: number | null;
44
- protected handleExpiringEvent(event: NostrEvent): void;
45
- /** Remove expired events from the store */
46
- protected pruneExpired(): Promise<void>;
47
- protected handleDeleteEvent(deleteEvent: NostrEvent): Promise<void>;
48
- /** Copies important metadata from and identical event to another */
49
- static mergeDuplicateEvent(source: NostrEvent, dest: NostrEvent): void;
58
+ /** Handle a delete event by pointer */
59
+ private handleDeleteNotification;
60
+ /** Handle an expired event by id */
61
+ private handleExpiredNotification;
50
62
  /**
51
63
  * Adds an event to the store and update subscriptions
52
64
  * @returns The existing event or the event that was added, if it was ignored returns null
@@ -59,9 +71,9 @@ export declare class AsyncEventStore extends EventModels implements IAsyncEventS
59
71
  /** Add an event to the store and notifies all subscribes it has updated */
60
72
  update(event: NostrEvent): Promise<void>;
61
73
  /** Check if the store has an event by id */
62
- hasEvent(id: string): Promise<boolean>;
74
+ hasEvent(id: string | EventPointer | AddressPointer | AddressPointerWithoutD): Promise<boolean>;
63
75
  /** Get an event by id from the store */
64
- getEvent(id: string): Promise<NostrEvent | undefined>;
76
+ getEvent(id: string | EventPointer | AddressPointer | AddressPointerWithoutD): Promise<NostrEvent | undefined>;
65
77
  /** Check if the store has a replaceable event */
66
78
  hasReplaceable(kind: number, pubkey: string, d?: string): Promise<boolean>;
67
79
  /** Gets the latest version of a replaceable event */
@@ -86,8 +98,4 @@ export declare class AsyncEventStore extends EventModels implements IAsyncEventS
86
98
  unclaimed(): Generator<NostrEvent>;
87
99
  /** Removes any event that is not being used by a subscription */
88
100
  prune(limit?: number): number;
89
- /** Returns an observable that completes when an event is removed */
90
- removed(id: string): Observable<never>;
91
- /** Creates an observable that emits when event is updated */
92
- updated(event: string | NostrEvent): Observable<NostrEvent>;
93
101
  }
@@ -1,22 +1,30 @@
1
- import { EMPTY, filter, mergeMap, Subject, take } from "rxjs";
2
- import { getDeleteCoordinates, getDeleteIds } from "../helpers/delete.js";
3
- import { createReplaceableAddress, EventStoreSymbol, FromCacheSymbol, isAddressableKind, isReplaceable, kinds, } from "../helpers/event.js";
1
+ import { verifyEvent as coreVerifyEvent } from "nostr-tools/pure";
2
+ import { Subject } from "rxjs";
3
+ import { EventStoreSymbol, getReplaceableIdentifier, isReplaceable, kinds } from "../helpers/event.js";
4
4
  import { getExpirationTimestamp } from "../helpers/expiration.js";
5
- import { parseCoordinate } from "../helpers/pointers.js";
6
- import { addSeenRelay, getSeenRelays } from "../helpers/relays.js";
5
+ import { eventMatchesPointer, isAddressPointer, isEventPointer, } from "../helpers/pointers.js";
6
+ import { addSeenRelay } from "../helpers/relays.js";
7
7
  import { unixNow } from "../helpers/time.js";
8
+ import { AsyncDeleteManager } from "./async-delete-manager.js";
8
9
  import { EventMemory } from "./event-memory.js";
9
10
  import { EventModels } from "./event-models.js";
10
- import { verifyEvent as coreVerifyEvent } from "nostr-tools/pure";
11
+ import { EventStore } from "./event-store.js";
12
+ import { ExpirationManager } from "./expiration-manager.js";
11
13
  /** An async wrapper around an async event database that handles replaceable events, deletes, and models */
12
14
  export class AsyncEventStore extends EventModels {
13
15
  database;
14
16
  /** Optional memory database for ensuring single event instances */
15
17
  memory;
18
+ /** Manager for handling event deletions with authorization */
19
+ deletes;
20
+ /** Manager for handling event expirations */
21
+ expiration;
16
22
  /** Enable this to keep old versions of replaceable events */
17
23
  keepOldVersions = false;
18
- /** Enable this to keep expired events */
24
+ /** Keep expired events in the store */
19
25
  keepExpired = false;
26
+ /** Keep deleted events in the store */
27
+ keepDeleted = false;
20
28
  /** The method used to verify events */
21
29
  _verifyEventMethod = coreVerifyEvent;
22
30
  /** Get the method used to verify events */
@@ -26,13 +34,12 @@ export class AsyncEventStore extends EventModels {
26
34
  /** Sets the method used to verify events */
27
35
  set verifyEvent(method) {
28
36
  this._verifyEventMethod = method;
29
- if (method === undefined) {
37
+ if (method === undefined)
30
38
  console.warn("[applesauce-core] AsyncEventStore.verifyEvent is undefined; signature checks are disabled.");
31
- }
32
39
  }
33
40
  /** A stream of new events added to the store */
34
41
  insert$ = new Subject();
35
- /** A stream of events that have been updated */
42
+ /** A stream of events that have been updated (Warning: this is a very noisy stream, use with caution) */
36
43
  update$ = new Subject();
37
44
  /** A stream of events that have been removed */
38
45
  remove$ = new Subject();
@@ -40,17 +47,34 @@ export class AsyncEventStore extends EventModels {
40
47
  * A method that will be called when an event isn't found in the store
41
48
  */
42
49
  eventLoader;
43
- constructor(database) {
50
+ constructor(options) {
44
51
  super();
45
- this.database = database;
52
+ this.database = options.database;
46
53
  this.memory = new EventMemory();
47
- // when events are added to the database, add the symbol
48
- this.insert$.subscribe((event) => {
49
- Reflect.set(event, EventStoreSymbol, this);
54
+ // Set options if provided
55
+ if (options.keepDeleted !== undefined)
56
+ this.keepDeleted = options.keepDeleted;
57
+ if (options.keepExpired !== undefined)
58
+ this.keepExpired = options.keepExpired;
59
+ if (options.keepOldVersions !== undefined)
60
+ this.keepOldVersions = options.keepOldVersions;
61
+ if (options.verifyEvent)
62
+ this.verifyEvent = options.verifyEvent;
63
+ // Use provided delete manager or create a default one
64
+ this.deletes = options.deleteManager ?? new AsyncDeleteManager();
65
+ // Listen to delete notifications and remove matching events
66
+ this.deletes.deleted$.subscribe((notification) => {
67
+ this.handleDeleteNotification(notification).catch((error) => {
68
+ console.error("[applesauce-core] Error handling delete notification:", error);
69
+ });
50
70
  });
51
- // when events are removed from the database, remove the symbol
52
- this.remove$.subscribe((event) => {
53
- Reflect.deleteProperty(event, EventStoreSymbol);
71
+ // Create expiration manager
72
+ this.expiration = options.expirationManager ?? new ExpirationManager();
73
+ // Listen to expired events and remove them from the store
74
+ this.expiration.expired$.subscribe((id) => {
75
+ this.handleExpiredNotification(id).catch((error) => {
76
+ console.error("[applesauce-core] Error handling expired notification:", error);
77
+ });
54
78
  });
55
79
  }
56
80
  mapToMemory(event) {
@@ -60,100 +84,36 @@ export class AsyncEventStore extends EventModels {
60
84
  return event;
61
85
  return this.memory.add(event);
62
86
  }
63
- // delete state
64
- deletedIds = new Set();
65
- deletedCoords = new Map();
66
- checkDeleted(event) {
67
- if (typeof event === "string")
68
- return this.deletedIds.has(event);
69
- else {
70
- if (this.deletedIds.has(event.id))
71
- return true;
72
- if (isAddressableKind(event.kind)) {
73
- const identifier = event.tags.find((t) => t[0] === "d")?.[1];
74
- const deleted = this.deletedCoords.get(createReplaceableAddress(event.kind, event.pubkey, identifier));
75
- if (deleted)
76
- return deleted > event.created_at;
77
- }
78
- }
79
- return false;
80
- }
81
- expirations = new Map();
82
- /** Adds an event to the expiration map */
83
- addExpiration(event) {
84
- const expiration = getExpirationTimestamp(event);
85
- if (expiration && Number.isFinite(expiration))
86
- this.expirations.set(event.id, expiration);
87
- }
88
- expirationTimeout = null;
89
- nextExpirationCheck = null;
90
- handleExpiringEvent(event) {
91
- const expiration = getExpirationTimestamp(event);
92
- if (!expiration)
87
+ /** Handle a delete event by pointer */
88
+ async handleDeleteNotification({ pointer, until }) {
89
+ // Skip if keeping deleted events
90
+ if (this.keepDeleted)
93
91
  return;
94
- // Add event to expiration map
95
- this.expirations.set(event.id, expiration);
96
- // Exit if the next check is already less than the next expiration
97
- if (this.expirationTimeout && this.nextExpirationCheck && this.nextExpirationCheck < expiration)
98
- return;
99
- // Set timeout to prune expired events
100
- if (this.expirationTimeout)
101
- clearTimeout(this.expirationTimeout);
102
- const timeout = expiration - unixNow();
103
- this.expirationTimeout = setTimeout(this.pruneExpired.bind(this), timeout * 1000 + 10);
104
- this.nextExpirationCheck = expiration;
105
- }
106
- /** Remove expired events from the store */
107
- async pruneExpired() {
108
- const now = unixNow();
109
- for (const [id, expiration] of this.expirations) {
110
- if (expiration <= now) {
111
- this.expirations.delete(id);
112
- await this.remove(id);
113
- }
114
- }
115
- // Cleanup timers
116
- if (this.expirationTimeout)
117
- clearTimeout(this.expirationTimeout);
118
- this.nextExpirationCheck = null;
119
- this.expirationTimeout = null;
120
- }
121
- // handling delete events
122
- async handleDeleteEvent(deleteEvent) {
123
- const ids = getDeleteIds(deleteEvent);
124
- for (const id of ids) {
125
- this.deletedIds.add(id);
126
- // remove deleted events in the database
127
- await this.remove(id);
92
+ if (isEventPointer(pointer)) {
93
+ // For event pointers, get the event by ID and remove if it exists
94
+ const event = await this.getEvent(pointer.id);
95
+ if (event && until >= event.created_at && eventMatchesPointer(event, pointer))
96
+ await this.remove(event);
128
97
  }
129
- const coords = getDeleteCoordinates(deleteEvent);
130
- for (const coord of coords) {
131
- this.deletedCoords.set(coord, Math.max(this.deletedCoords.get(coord) ?? 0, deleteEvent.created_at));
132
- // Parse the nostr address coordinate
133
- const parsed = parseCoordinate(coord);
134
- if (!parsed)
135
- continue;
136
- // Remove older versions of replaceable events
137
- const events = await this.database.getReplaceableHistory(parsed.kind, parsed.pubkey, parsed.identifier);
98
+ else if (isAddressPointer(pointer)) {
99
+ // For address pointers, get all events matching the address and remove if deleted
100
+ const events = await this.getReplaceableHistory(pointer.kind, pointer.pubkey, pointer.identifier);
138
101
  if (events) {
139
102
  for (const event of events) {
140
- if (event.created_at < deleteEvent.created_at)
103
+ // Remove the event if its older than the delete notification and matches the pointer
104
+ if (until >= event.created_at && eventMatchesPointer(event, pointer)) {
141
105
  await this.remove(event);
106
+ }
142
107
  }
143
108
  }
144
109
  }
145
110
  }
146
- /** Copies important metadata from and identical event to another */
147
- static mergeDuplicateEvent(source, dest) {
148
- const relays = getSeenRelays(source);
149
- if (relays) {
150
- for (const relay of relays)
151
- addSeenRelay(dest, relay);
152
- }
153
- // copy the from cache symbol only if its true
154
- const fromCache = Reflect.get(source, FromCacheSymbol);
155
- if (fromCache && !Reflect.get(dest, FromCacheSymbol))
156
- Reflect.set(dest, FromCacheSymbol, fromCache);
111
+ /** Handle an expired event by id */
112
+ async handleExpiredNotification(id) {
113
+ // Skip if keeping expired events
114
+ if (this.keepExpired)
115
+ return;
116
+ await this.remove(id);
157
117
  }
158
118
  /**
159
119
  * Adds an event to the store and update subscriptions
@@ -161,23 +121,29 @@ export class AsyncEventStore extends EventModels {
161
121
  */
162
122
  async add(event, fromRelay) {
163
123
  // Handle delete events differently
164
- if (event.kind === kinds.EventDeletion)
165
- await this.handleDeleteEvent(event);
124
+ if (event.kind === kinds.EventDeletion) {
125
+ await this.deletes.add(event);
126
+ return event;
127
+ }
166
128
  // Ignore if the event was deleted
167
- if (this.checkDeleted(event))
129
+ if (await this.deletes.check(event))
168
130
  return event;
169
131
  // Reject expired events if keepExpired is false
170
132
  const expiration = getExpirationTimestamp(event);
171
133
  if (this.keepExpired === false && expiration && expiration <= unixNow())
172
134
  return null;
135
+ // Attach relay this event was from
136
+ if (fromRelay)
137
+ addSeenRelay(event, fromRelay);
173
138
  // Get the replaceable identifier
174
- const identifier = isReplaceable(event.kind) ? event.tags.find((t) => t[0] === "d")?.[1] : undefined;
139
+ const identifier = isReplaceable(event.kind) ? getReplaceableIdentifier(event) : undefined;
175
140
  // Don't insert the event if there is already a newer version
176
- if (!this.keepOldVersions && isReplaceable(event.kind)) {
141
+ if (this.keepOldVersions === false && isReplaceable(event.kind)) {
177
142
  const existing = await this.database.getReplaceableHistory(event.kind, event.pubkey, identifier);
178
143
  // If there is already a newer version, copy cached symbols and return existing event
179
144
  if (existing && existing.length > 0 && existing[0].created_at >= event.created_at) {
180
- AsyncEventStore.mergeDuplicateEvent(event, existing[0]);
145
+ if (EventStore.copySymbolsToDuplicateEvent(event, existing[0]))
146
+ await this.update(existing[0]);
181
147
  return existing[0];
182
148
  }
183
149
  }
@@ -185,29 +151,30 @@ export class AsyncEventStore extends EventModels {
185
151
  if (this.verifyEvent && this.verifyEvent(event) === false)
186
152
  return null;
187
153
  // Always add event to memory
188
- const existing = this.memory?.add(event);
154
+ const existing = this.memory.add(event);
189
155
  // If the memory returned a different instance, this is a duplicate event
190
156
  if (existing && existing !== event) {
191
157
  // Copy cached symbols and return existing event
192
- AsyncEventStore.mergeDuplicateEvent(event, existing);
193
- // attach relay this event was from
194
- if (fromRelay)
195
- addSeenRelay(existing, fromRelay);
158
+ if (EventStore.copySymbolsToDuplicateEvent(event, existing))
159
+ await this.update(existing);
196
160
  return existing;
197
161
  }
198
162
  // Insert event into database
199
163
  const inserted = this.mapToMemory(await this.database.add(event));
200
- // Copy cached data if its a duplicate event
201
- if (event !== inserted)
202
- AsyncEventStore.mergeDuplicateEvent(event, inserted);
203
- // attach relay this event was from
204
- if (fromRelay)
205
- addSeenRelay(inserted, fromRelay);
206
- // Emit insert$ signal
207
- if (inserted === event)
164
+ // If the event is the same as the inserted event, its a new event
165
+ if (inserted === event) {
166
+ // Set the event store on the event
167
+ Reflect.set(inserted, EventStoreSymbol, this);
168
+ // Emit insert$ signal
208
169
  this.insert$.next(inserted);
170
+ }
171
+ else {
172
+ // Copy cached data if its a duplicate event
173
+ if (EventStore.copySymbolsToDuplicateEvent(event, inserted))
174
+ await this.update(inserted);
175
+ }
209
176
  // remove all old version of the replaceable event
210
- if (!this.keepOldVersions && isReplaceable(event.kind)) {
177
+ if (this.keepOldVersions === false && isReplaceable(event.kind)) {
211
178
  const existing = await this.database.getReplaceableHistory(event.kind, event.pubkey, identifier);
212
179
  if (existing && existing.length > 0) {
213
180
  const older = Array.from(existing).filter((e) => e.created_at < event.created_at);
@@ -219,29 +186,37 @@ export class AsyncEventStore extends EventModels {
219
186
  return existing[0];
220
187
  }
221
188
  }
222
- // Add event to expiration map
223
- if (this.keepExpired === false && expiration)
224
- this.handleExpiringEvent(inserted);
189
+ // Add event to expiration manager if it has an expiration tag
190
+ if (this.keepExpired === false && expiration !== undefined)
191
+ this.expiration.track(inserted);
225
192
  return inserted;
226
193
  }
227
194
  /** Removes an event from the store and updates subscriptions */
228
195
  async remove(event) {
229
- let instance = this.memory?.getEvent(typeof event === "string" ? event : event.id);
196
+ const eventId = typeof event === "string" ? event : event.id;
197
+ let instance = this.memory.getEvent(eventId);
198
+ // Remove from expiration manager
199
+ this.expiration.forget(eventId);
200
+ // Remove the event store from the event
201
+ if (instance)
202
+ Reflect.deleteProperty(instance, EventStoreSymbol);
230
203
  // Remove from memory if available
231
204
  if (this.memory)
232
205
  this.memory.remove(event);
233
206
  // Remove the event from the database
234
207
  const removed = await this.database.remove(event);
235
208
  // If the event was removed, notify the subscriptions
236
- if (removed && instance) {
209
+ if (removed && instance)
237
210
  this.remove$.next(instance);
238
- }
239
211
  return removed;
240
212
  }
241
213
  /** Remove multiple events that match the given filters */
242
214
  async removeByFilters(filters) {
243
215
  // Get events that will be removed for notification
244
216
  const eventsToRemove = await this.getByFilters(filters);
217
+ // Remove from expiration manager
218
+ for (const event of eventsToRemove)
219
+ this.expiration.forget(event.id);
245
220
  // Remove from memory if available
246
221
  if (this.memory)
247
222
  this.memory.removeByFilters(filters);
@@ -265,33 +240,41 @@ export class AsyncEventStore extends EventModels {
265
240
  }
266
241
  /** Check if the store has an event by id */
267
242
  async hasEvent(id) {
268
- // Check if the event exists in memory first, then in the database
269
- return (this.memory?.hasEvent(id) ?? false) || (await this.database.hasEvent(id));
243
+ if (typeof id === "string")
244
+ return this.memory.hasEvent(id) || this.database.hasEvent(id);
245
+ // If its a pointer, use the advanced has event method to resolve
246
+ else if (isEventPointer(id))
247
+ return this.memory.hasEvent(id.id) || this.database.hasEvent(id.id);
248
+ else
249
+ return this.hasReplaceable(id.kind, id.pubkey, id.identifier);
270
250
  }
271
251
  /** Get an event by id from the store */
272
252
  async getEvent(id) {
273
253
  // Get the event from memory first, then from the database
274
- return this.memory?.getEvent(id) ?? this.mapToMemory(await this.database.getEvent(id));
254
+ if (typeof id === "string")
255
+ return this.memory.getEvent(id) ?? this.mapToMemory(await this.database.getEvent(id));
256
+ // If its a pointer, use the advanced get event method to resolve
257
+ else if (isEventPointer(id))
258
+ return this.memory.getEvent(id.id) ?? this.mapToMemory(await this.database.getEvent(id.id));
259
+ else
260
+ return this.getReplaceable(id.kind, id.pubkey, id.identifier);
275
261
  }
276
262
  /** Check if the store has a replaceable event */
277
263
  async hasReplaceable(kind, pubkey, d) {
278
264
  // Check if the event exists in memory first, then in the database
279
- return ((this.memory?.hasReplaceable(kind, pubkey, d) ?? false) || (await this.database.hasReplaceable(kind, pubkey, d)));
265
+ return this.memory.hasReplaceable(kind, pubkey, d) || this.database.hasReplaceable(kind, pubkey, d);
280
266
  }
281
267
  /** Gets the latest version of a replaceable event */
282
268
  async getReplaceable(kind, pubkey, identifier) {
283
269
  // Get the event from memory first, then from the database
284
- return (this.memory?.getReplaceable(kind, pubkey, identifier) ??
270
+ return (this.memory.getReplaceable(kind, pubkey, identifier) ??
285
271
  this.mapToMemory(await this.database.getReplaceable(kind, pubkey, identifier)));
286
272
  }
287
273
  /** Returns all versions of a replaceable event */
288
274
  async getReplaceableHistory(kind, pubkey, identifier) {
289
275
  // Get the events from memory first, then from the database
290
- const memoryEvents = this.memory?.getReplaceableHistory(kind, pubkey, identifier);
291
- if (memoryEvents)
292
- return memoryEvents;
293
- const dbEvents = await this.database.getReplaceableHistory(kind, pubkey, identifier);
294
- return dbEvents?.map((e) => this.mapToMemory(e) ?? e);
276
+ return (this.memory.getReplaceableHistory(kind, pubkey, identifier) ??
277
+ (await this.database.getReplaceableHistory(kind, pubkey, identifier))?.map((e) => this.mapToMemory(e) ?? e));
295
278
  }
296
279
  /** Get all events matching a filter */
297
280
  async getByFilters(filters) {
@@ -299,7 +282,7 @@ export class AsyncEventStore extends EventModels {
299
282
  const events = await this.database.getByFilters(filters);
300
283
  // Map events to memory if available for better performance
301
284
  if (this.memory)
302
- return events.map((e) => this.mapToMemory(e) ?? e);
285
+ return events.map((e) => this.mapToMemory(e));
303
286
  else
304
287
  return events;
305
288
  }
@@ -313,47 +296,30 @@ export class AsyncEventStore extends EventModels {
313
296
  }
314
297
  /** Passthrough method for the database.touch */
315
298
  touch(event) {
316
- return this.memory?.touch(event);
299
+ return this.memory.touch(event);
317
300
  }
318
301
  /** Increments the claim count on the event and touches it */
319
302
  claim(event) {
320
- return this.memory?.claim(event);
303
+ return this.memory.claim(event);
321
304
  }
322
305
  /** Checks if an event is claimed by anything */
323
306
  isClaimed(event) {
324
- return this.memory?.isClaimed(event) ?? false;
307
+ return this.memory.isClaimed(event) ?? false;
325
308
  }
326
309
  /** Decrements the claim count on an event */
327
310
  removeClaim(event) {
328
- return this.memory?.removeClaim(event);
311
+ return this.memory.removeClaim(event);
329
312
  }
330
313
  /** Removes all claims on an event */
331
314
  clearClaim(event) {
332
- return this.memory?.clearClaim(event);
315
+ return this.memory.clearClaim(event);
333
316
  }
334
317
  /** Pass through method for the database.unclaimed */
335
318
  unclaimed() {
336
- return this.memory?.unclaimed() || (function* () { })();
319
+ return this.memory.unclaimed() || (function* () { })();
337
320
  }
338
321
  /** Removes any event that is not being used by a subscription */
339
322
  prune(limit) {
340
- return this.memory?.prune(limit) ?? 0;
341
- }
342
- /** Returns an observable that completes when an event is removed */
343
- removed(id) {
344
- const deleted = this.checkDeleted(id);
345
- if (deleted)
346
- return EMPTY;
347
- return this.remove$.pipe(
348
- // listen for removed events
349
- filter((e) => e.id === id),
350
- // complete as soon as we find a matching removed event
351
- take(1),
352
- // switch to empty
353
- mergeMap(() => EMPTY));
354
- }
355
- /** Creates an observable that emits when event is updated */
356
- updated(event) {
357
- return this.update$.pipe(filter((e) => e.id === event || e === event));
323
+ return this.memory.prune(limit) ?? 0;
358
324
  }
359
325
  }