diff --git a/src/db/memorelay.ts b/src/db/memorelay.ts index 57714a0..3550346 100644 --- a/src/db/memorelay.ts +++ b/src/db/memorelay.ts @@ -1,69 +1,10 @@ -import { Debug, type Event, type Filter, LRUCache } from '@/deps.ts'; -import { getFilterId, getMicroFilters, isMicrofilter } from '@/filter.ts'; -import { type EventStore, type GetEventsOpts } from '@/store.ts'; +import { Memorelay } from '@/storages/memorelay.ts'; -const debug = Debug('ditto:memorelay'); - -const events = new LRUCache({ +/** In-memory data store for events using microfilters. */ +const memorelay = new Memorelay({ max: 3000, maxEntrySize: 5000, sizeCalculation: (event) => JSON.stringify(event).length, }); -/** Get events from memory. */ -function getEvents(filters: Filter[], opts: GetEventsOpts = {}): Promise[]> { - if (opts.signal?.aborted) return Promise.resolve([]); - if (!filters.length) return Promise.resolve([]); - debug('REQ', JSON.stringify(filters)); - - const results: Event[] = []; - - for (const filter of filters) { - if (isMicrofilter(filter)) { - const event = events.get(getFilterId(filter)); - if (event) { - results.push(event as Event); - } - } - } - - return Promise.resolve(results); -} - -/** Insert an event into memory. */ -function storeEvent(event: Event): Promise { - for (const microfilter of getMicroFilters(event)) { - const filterId = getFilterId(microfilter); - const existing = events.get(filterId); - if (!existing || event.created_at > existing.created_at) { - events.set(filterId, event); - } - } - return Promise.resolve(); -} - -/** Count events in memory for the filters. */ -async function countEvents(filters: Filter[]): Promise { - const events = await getEvents(filters); - return events.length; -} - -/** Delete events from memory. */ -function deleteEvents(filters: Filter[]): Promise { - 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: EventStore = { - getEvents, - storeEvent, - countEvents, - deleteEvents, -}; - export { memorelay }; diff --git a/src/deps.ts b/src/deps.ts index 3e763a7..164af3a 100644 --- a/src/deps.ts +++ b/src/deps.ts @@ -18,6 +18,7 @@ export { getEventHash, getPublicKey, getSignature, + matchFilter, matchFilters, nip04, nip05, diff --git a/src/db/memorelay.test.ts b/src/storages/memorelay.test.ts similarity index 71% rename from src/db/memorelay.test.ts rename to src/storages/memorelay.test.ts index 27c545d..fbf3ba3 100644 --- a/src/db/memorelay.test.ts +++ b/src/storages/memorelay.test.ts @@ -2,7 +2,13 @@ import { assertEquals } from '@/deps-test.ts'; import event1 from '~/fixtures/events/event-1.json' assert { type: 'json' }; -import { memorelay } from './memorelay.ts'; +import { Memorelay } from './memorelay.ts'; + +const memorelay = new Memorelay({ + max: 3000, + maxEntrySize: 5000, + sizeCalculation: (event) => JSON.stringify(event).length, +}); Deno.test('memorelay', async () => { assertEquals(await memorelay.countEvents([{ ids: [event1.id] }]), 0); diff --git a/src/storages/memorelay.ts b/src/storages/memorelay.ts index df24108..76328dc 100644 --- a/src/storages/memorelay.ts +++ b/src/storages/memorelay.ts @@ -1,8 +1,7 @@ -import { Debug, type Event, type Filter, LRUCache } from '@/deps.ts'; -import { getFilterId, getMicroFilters, isMicrofilter } from '@/filter.ts'; +import { Debug, type Event, type Filter, LRUCache, matchFilter, matchFilters } from '@/deps.ts'; import { type EventStore, type GetEventsOpts } from '@/store.ts'; -/** In-memory data store for events using microfilters. */ +/** In-memory data store for events. */ class Memorelay implements EventStore { #debug = Debug('ditto:memorelay'); #cache: LRUCache; @@ -11,6 +10,15 @@ class Memorelay implements EventStore { this.#cache = new LRUCache(...args); } + /** Iterate stored events. */ + *#events(): Generator { + for (const event of this.#cache.values()) { + if (event && !(event instanceof Promise)) { + yield event; + } + } + } + /** Get events from memory. */ getEvents(filters: Filter[], opts: GetEventsOpts = {}): Promise[]> { if (opts.signal?.aborted) return Promise.resolve([]); @@ -18,13 +26,27 @@ class Memorelay implements EventStore { this.#debug('REQ', JSON.stringify(filters)); const results: Event[] = []; + const usages: number[] = []; - for (const filter of filters) { - if (isMicrofilter(filter)) { - const event = this.#cache.get(getFilterId(filter)); - if (event) { + for (const event of this.#events()) { + let index = 0; + + for (const filter of filters) { + const limit = filter.limit ?? Infinity; + const usage = usages[index] ?? 0; + + if (usage >= limit) { + continue; + } else if (matchFilter(filter, event)) { results.push(event as Event); + usages[index] = usage + 1; } + + index++; + } + + if (filters.every((filter, index) => usages[index] >= (filter.limit ?? Infinity))) { + break; } } @@ -33,13 +55,7 @@ class Memorelay implements EventStore { /** Insert an event into memory. */ storeEvent(event: Event): Promise { - for (const microfilter of getMicroFilters(event)) { - const filterId = getFilterId(microfilter); - const existing = this.#cache.get(filterId); - if (!existing || event.created_at > existing.created_at) { - this.#cache.set(filterId, event); - } - } + this.#cache.set(event.id, event); return Promise.resolve(); } @@ -51,9 +67,9 @@ class Memorelay implements EventStore { /** Delete events from memory. */ deleteEvents(filters: Filter[]): Promise { - for (const filter of filters) { - if (isMicrofilter(filter)) { - this.#cache.delete(getFilterId(filter)); + for (const event of this.#events()) { + if (matchFilters(filters, event)) { + this.#cache.delete(event.id); } } return Promise.resolve();