Refactor memorelay as an EventStore

This commit is contained in:
Alex Gleason 2023-12-29 13:35:57 -06:00
parent ccfdbfeb8d
commit 56373c4ce3
No known key found for this signature in database
GPG Key ID: 7211D1F99744FBB7
6 changed files with 36 additions and 49 deletions

View File

@ -354,8 +354,8 @@ function buildUserSearchContent(event: Event<0>): string {
/** SQLite database storage adapter for Nostr events. */
const eventsDB: EventStore = {
storeEvent,
getEvents,
storeEvent,
countEvents,
deleteEvents,
};

View File

@ -5,14 +5,12 @@ import event1 from '~/fixtures/events/event-1.json' assert { type: 'json' };
import { memorelay } from './memorelay.ts';
Deno.test('memorelay', async () => {
assertEquals(memorelay.hasEvent(event1), false);
assertEquals(memorelay.hasEventById(event1.id), false);
assertEquals(await memorelay.countEvents([{ ids: [event1.id] }]), 0);
memorelay.insertEvent(event1);
await memorelay.storeEvent(event1);
assertEquals(memorelay.hasEvent(event1), true);
assertEquals(memorelay.hasEventById(event1.id), true);
assertEquals(await memorelay.countEvents([{ ids: [event1.id] }]), 1);
const result = await memorelay.getFilters([{ ids: [event1.id] }]);
const result = await memorelay.getEvents([{ ids: [event1.id] }]);
assertEquals(result[0], event1);
});

View File

@ -1,5 +1,6 @@
import { Debug, type Event, type Filter, LRUCache } from '@/deps.ts';
import { getFilterId, type GetFiltersOpts, getMicroFilters, isMicrofilter } from '@/filter.ts';
import { getFilterId, getMicroFilters, isMicrofilter } from '@/filter.ts';
import { type EventStore, type GetEventsOpts } from '@/store.ts';
const debug = Debug('ditto:memorelay');
@ -10,7 +11,7 @@ const events = new LRUCache<string, Event>({
});
/** Get events from memory. */
function getFilters<K extends number>(filters: Filter<K>[], opts: GetFiltersOpts = {}): Promise<Event<K>[]> {
function getEvents<K extends number>(filters: Filter<K>[], opts: GetEventsOpts = {}): Promise<Event<K>[]> {
if (opts.signal?.aborted) return Promise.resolve([]);
if (!filters.length) return Promise.resolve([]);
debug('REQ', JSON.stringify(filters));
@ -30,7 +31,7 @@ function getFilters<K extends number>(filters: Filter<K>[], opts: GetFiltersOpts
}
/** Insert an event into memory. */
function insertEvent(event: Event): void {
function storeEvent(event: Event): Promise<void> {
for (const microfilter of getMicroFilters(event)) {
const filterId = getFilterId(microfilter);
const existing = events.get(filterId);
@ -38,32 +39,31 @@ function insertEvent(event: Event): void {
events.set(filterId, event);
}
}
return Promise.resolve();
}
/** Check if an event is in memory. */
function hasEvent(event: Event): boolean {
for (const microfilter of getMicroFilters(event)) {
const filterId = getFilterId(microfilter);
const existing = events.get(filterId);
if (existing) {
return true;
}
}
return false;
/** Count events in memory for the filters. */
async function countEvents(filters: Filter[]): Promise<number> {
const events = await getEvents(filters);
return events.length;
}
/** Check if an event is in memory by ID. */
function hasEventById(eventId: string): boolean {
const filterId = getFilterId({ ids: [eventId] });
return events.has(filterId);
/** Delete events from memory. */
function deleteEvents(filters: Filter[]): Promise<void> {
for (const filter of filters) {
if (isMicrofilter(filter)) {
events.delete(getFilterId(filter));
}
}
return Promise.resolve();
}
/** In-memory data store for events using microfilters. */
const memorelay = {
getFilters,
insertEvent,
hasEvent,
hasEventById,
const memorelay: EventStore = {
getEvents,
storeEvent,
countEvents,
deleteEvents,
};
export { memorelay };

View File

@ -21,16 +21,6 @@ type AuthorMicrofilter = { kinds: [0]; authors: [Event['pubkey']] };
/** Filter to get one specific event. */
type MicroFilter = IdMicrofilter | AuthorMicrofilter;
/** Additional options to apply to the whole subscription. */
interface GetFiltersOpts {
/** Signal to abort the request. */
signal?: AbortSignal;
/** Event limit for the whole subscription. */
limit?: number;
/** Relays to use, if applicable. */
relays?: WebSocket['url'][];
}
function matchDittoFilter(filter: DittoFilter, event: Event, data: EventData): boolean {
if (filter.local && !(data.user || event.pubkey === Conf.pubkey)) {
return false;
@ -97,7 +87,6 @@ export {
type DittoFilter,
eventToMicroFilter,
getFilterId,
type GetFiltersOpts,
getMicroFilters,
type IdMicrofilter,
isMicrofilter,

View File

@ -26,7 +26,7 @@ const debug = Debug('ditto:pipeline');
async function handleEvent(event: Event): Promise<void> {
if (!(await verifySignatureWorker(event))) return;
const wanted = reqmeister.isWanted(event);
if (encounterEvent(event)) return;
if (await encounterEvent(event)) return;
debug(`Event<${event.kind}> ${event.id}`);
const data = await getEventData(event);
@ -43,9 +43,9 @@ async function handleEvent(event: Event): Promise<void> {
}
/** Encounter the event, and return whether it has already been encountered. */
function encounterEvent(event: Event): boolean {
const preexisting = memorelay.hasEvent(event);
memorelay.insertEvent(event);
async function encounterEvent(event: Event): Promise<boolean> {
const preexisting = (await memorelay.countEvents([{ ids: [event.id] }])) > 0;
memorelay.storeEvent(event);
reqmeister.encounter(event);
return preexisting;
}
@ -142,7 +142,7 @@ function fetchRelatedEvents(event: Event, data: EventData) {
reqmeister.req({ kinds: [0], authors: [event.pubkey] }).catch(() => {});
}
for (const [name, id, relay] of event.tags) {
if (name === 'e' && !memorelay.hasEventById(id)) {
if (name === 'e' && !memorelay.countEvents([{ ids: [id] }])) {
reqmeister.req({ ids: [id] }, { relays: [relay] }).catch(() => {});
}
}

View File

@ -22,7 +22,7 @@ const getEvent = async <K extends number = number>(
const { kind, relations, signal = AbortSignal.timeout(1000) } = opts;
const microfilter: IdMicrofilter = { ids: [id] };
const [memoryEvent] = await memorelay.getFilters([microfilter], opts) as DittoEvent<K>[];
const [memoryEvent] = await memorelay.getEvents([microfilter], opts) as DittoEvent<K>[];
if (memoryEvent && !relations) {
return memoryEvent;
@ -39,14 +39,14 @@ const getEvent = async <K extends number = number>(
// TODO: make this DRY-er.
if (dbEvent && !dbEvent.author) {
const [author] = await memorelay.getFilters([{ kinds: [0], authors: [dbEvent.pubkey] }], opts);
const [author] = await memorelay.getEvents([{ kinds: [0], authors: [dbEvent.pubkey] }], opts);
dbEvent.author = author;
}
if (dbEvent) return dbEvent;
if (memoryEvent && !memoryEvent.author) {
const [author] = await memorelay.getFilters([{ kinds: [0], authors: [memoryEvent.pubkey] }], opts);
const [author] = await memorelay.getEvents([{ kinds: [0], authors: [memoryEvent.pubkey] }], opts);
memoryEvent.author = author;
}
@ -60,7 +60,7 @@ const getAuthor = async (pubkey: string, opts: GetEventOpts<0> = {}): Promise<Ev
const { relations, signal = AbortSignal.timeout(1000) } = opts;
const microfilter: AuthorMicrofilter = { kinds: [0], authors: [pubkey] };
const [memoryEvent] = await memorelay.getFilters([microfilter], opts);
const [memoryEvent] = await memorelay.getEvents([microfilter], opts);
if (memoryEvent && !relations) {
return memoryEvent;