From 8f259845b52594af585f44f2d911e267fb636a55 Mon Sep 17 00:00:00 2001 From: pablof7z Date: Thu, 5 Dec 2024 17:24:32 +0000 Subject: [PATCH] improve how event deletion is handled in the cache --- ndk-cache-dexie/src/index.ts | 7 ++++--- ndk-mobile/src/cache-adapter/sqlite.ts | 9 ++++----- ndk-mobile/src/hooks/subscribe.ts | 16 +++++++++++++--- ndk/src/cache/index.ts | 4 ++-- ndk/src/events/index.ts | 15 ++++++++++++--- 5 files changed, 35 insertions(+), 16 deletions(-) diff --git a/ndk-cache-dexie/src/index.ts b/ndk-cache-dexie/src/index.ts index 23496648..7b82ec6c 100644 --- a/ndk-cache-dexie/src/index.ts +++ b/ndk-cache-dexie/src/index.ts @@ -1,6 +1,7 @@ import { NDKEvent, NDKRelay, deserialize, profileFromEvent } from "@nostr-dev-kit/ndk"; import type { Hexpubkey, + NDKEventId, NDKCacheAdapter, NDKFilter, NDKSubscription, @@ -335,9 +336,9 @@ export default class NDKCacheAdapterDexie implements NDKCacheAdapter { } } - public async deleteEvent(event: NDKEvent): Promise { - this.events.delete(event.tagId()); - await db.events.where({ id: event.tagId() }).delete(); + public async deleteEventIds(eventIds: NDKEventId[]): Promise { + eventIds.forEach((id) => this.events.delete(id)); + await db.events.where({ id: eventIds }).delete(); } public addUnpublishedEvent = addUnpublishedEvent.bind(this); diff --git a/ndk-mobile/src/cache-adapter/sqlite.ts b/ndk-mobile/src/cache-adapter/sqlite.ts index d973557e..cb5363b0 100644 --- a/ndk-mobile/src/cache-adapter/sqlite.ts +++ b/ndk-mobile/src/cache-adapter/sqlite.ts @@ -137,14 +137,13 @@ export class NDKCacheAdapterSqlite implements NDKCacheAdapter { // if this event is a delete event, see if the deleted events are in the cache and remove them if (event.kind === NDKKind.EventDeletion) { - const deletedEventIds = event.tags.filter((tag) => tag[0] === 'e').map((tag) => tag[1]); - await this.db.runAsync(`DELETE FROM event_tags WHERE event_id IN (${deletedEventIds.map(() => '?').join(',')});`, deletedEventIds); + this.deleteEventIds(event.tags.filter((tag) => tag[0] === 'e').map((tag) => tag[1])); } } - async deleteEvent(event: NDKEvent): Promise { - await this.db.runAsync(`DELETE FROM events WHERE id = ?;`, [event.id]); - await this.db.runAsync(`DELETE FROM event_tags WHERE event_id = ?;`, [event.id]); + async deleteEventIds(eventIds: NDKEventId[]): Promise { + await this.db.runAsync(`DELETE FROM events WHERE id IN (${eventIds.map(() => '?').join(',')});`, eventIds); + await this.db.runAsync(`DELETE FROM event_tags WHERE event_id IN (${eventIds.map(() => '?').join(',')});`, eventIds); } fetchProfileSync(pubkey: Hexpubkey): NDKCacheEntry | null { diff --git a/ndk-mobile/src/hooks/subscribe.ts b/ndk-mobile/src/hooks/subscribe.ts index 948afd44..5dfd2b05 100644 --- a/ndk-mobile/src/hooks/subscribe.ts +++ b/ndk-mobile/src/hooks/subscribe.ts @@ -47,6 +47,7 @@ interface SubscribeStore { eose: boolean; isSubscribed: boolean; addEvent: (event: T) => void; + removeEventId: (id: string) => void; setEose: () => void; clearEvents: () => void; setSubscription: (sub: NDKSubscription | undefined) => void; @@ -107,6 +108,14 @@ const createSubscribeStore = (bufferMs: number | false = 16) } }, + removeEventId: (id) => { + set((state) => { + state.eventMap.delete(id); + const events = Array.from(state.eventMap.values()); + return { eventMap: state.eventMap, events }; + }); + }, + setEose: () => { if (timeout) { clearTimeout(timeout); @@ -129,8 +138,6 @@ const createSubscribeStore = (bufferMs: number | false = 16) * @returns {boolean} isSubscribed - Subscription status */ export const useSubscribe = ({ filters, opts = undefined, relays = undefined }: UseSubscribeParams) => { - const ref = useRef(0); - const { ndk } = useNDK(); const muteList = useSessionStore((state) => state.muteList); const store = useMemo(() => createSubscribeStore(opts?.bufferMs), [opts?.bufferMs]); @@ -189,6 +196,10 @@ export const useSubscribe = ({ filters, opts = undefined, re // If we need to convert the event, we do so if (opts?.klass) event = opts.klass.from(event); + event.once("deleted", () => { + storeInstance.removeEventId(id); + }); + // If conversion failed, we bail if (!event) return; @@ -208,7 +219,6 @@ export const useSubscribe = ({ filters, opts = undefined, re useEffect(() => { if (!filters || filters.length === 0 || !ndk) return; - ref.current += 1; if (storeInstance.subscriptionRef) { storeInstance.subscriptionRef.stop(); diff --git a/ndk/src/cache/index.ts b/ndk/src/cache/index.ts index c73dc07c..7aee0c1b 100644 --- a/ndk/src/cache/index.ts +++ b/ndk/src/cache/index.ts @@ -28,9 +28,9 @@ export interface NDKCacheAdapter { /** * Called when an event is deleted by the client. * Cache adapters should remove the event from their cache. - * @param event - The event that was deleted. + * @param eventIds - The ids of the events that were deleted. */ - deleteEvent?(event: NDKEvent): Promise; + deleteEventIds?(eventIds: NDKEventId[]): Promise; /** * Fetches a profile from the cache synchronously. diff --git a/ndk/src/events/index.ts b/ndk/src/events/index.ts index e77542bb..e636d130 100644 --- a/ndk/src/events/index.ts +++ b/ndk/src/events/index.ts @@ -453,8 +453,9 @@ export class NDKEvent extends EventEmitter { } // If the published event is a delete event, notify the cache if there is one - if (this.kind === NDKKind.EventDeletion && this.ndk.cacheAdapter?.deleteEvent) { - this.ndk.cacheAdapter.deleteEvent(this); + if (this.kind === NDKKind.EventDeletion && this.ndk.cacheAdapter?.deleteEventIds) { + const eTags = this.getMatchingTags('e').map((tag) => tag[1]); + this.ndk.cacheAdapter.deleteEventIds(eTags); } const rawEvent = this.rawEvent(); @@ -468,6 +469,11 @@ export class NDKEvent extends EventEmitter { } } + // if this is a delete event, send immediately to the cache + if (this.kind === NDKKind.EventDeletion && this.ndk.cacheAdapter?.deleteEventIds) { + this.ndk.cacheAdapter.deleteEventIds(this.getMatchingTags('e').map((tag) => tag[1])); + } + // send to active subscriptions that want this event this.ndk.subManager.subscriptions.forEach((sub) => { if (sub.filters.some((filter) => matchFilter(filter, rawEvent as any))) { @@ -738,7 +744,10 @@ export class NDKEvent extends EventEmitter { } as NostrEvent); e.tag(this, undefined, true); e.tags.push(["k", this.kind!.toString()]); - if (publish) await e.publish(); + if (publish) { + this.emit("deleted"); + await e.publish(); + } return e; }