From aabe6350a765d4988bdc04d661f49d391405f045 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 18 Feb 2025 15:08:00 -0600 Subject: [PATCH] Remove SearchStore --- packages/ditto/controllers/api/accounts.ts | 2 +- packages/ditto/controllers/api/search.ts | 4 +- packages/ditto/filter.test.ts | 46 ---------- packages/ditto/filter.ts | 97 ---------------------- packages/ditto/storages.ts | 15 ---- packages/ditto/storages/search-store.ts | 60 ------------- 6 files changed, 3 insertions(+), 221 deletions(-) delete mode 100644 packages/ditto/filter.test.ts delete mode 100644 packages/ditto/filter.ts delete mode 100644 packages/ditto/storages/search-store.ts diff --git a/packages/ditto/controllers/api/accounts.ts b/packages/ditto/controllers/api/accounts.ts index 252ddad6..8a1b9e3d 100644 --- a/packages/ditto/controllers/api/accounts.ts +++ b/packages/ditto/controllers/api/accounts.ts @@ -115,6 +115,7 @@ const accountSearchQuerySchema = z.object({ }); const accountSearchController: AppController = async (c) => { + const { store } = c.var; const { signal } = c.req.raw; const { limit } = c.get('pagination'); @@ -128,7 +129,6 @@ const accountSearchController: AppController = async (c) => { } const query = decodeURIComponent(result.data.q); - const store = await Storages.search(); const lookup = extractIdentifier(query); const event = await lookupAccount(lookup ?? query); diff --git a/packages/ditto/controllers/api/search.ts b/packages/ditto/controllers/api/search.ts index e5761f32..e890f166 100644 --- a/packages/ditto/controllers/api/search.ts +++ b/packages/ditto/controllers/api/search.ts @@ -94,7 +94,7 @@ async function searchEvents( return Promise.resolve([]); } - const store = await Storages.search(); + const store = await Storages.db(); const filter: NostrFilter = { kinds: typeToKinds(type), @@ -150,7 +150,7 @@ function typeToKinds(type: SearchQuery['type']): number[] { /** Resolve a searched value into an event, if applicable. */ async function lookupEvent(query: SearchQuery, signal: AbortSignal): Promise { const filters = await getLookupFilters(query, signal); - const store = await Storages.search(); + const store = await Storages.db(); return store.query(filters, { limit: 1, signal }) .then((events) => hydrateEvents({ events, store, signal })) diff --git a/packages/ditto/filter.test.ts b/packages/ditto/filter.test.ts deleted file mode 100644 index 9379208e..00000000 --- a/packages/ditto/filter.test.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { assertEquals } from '@std/assert'; - -import event0 from '~/fixtures/events/event-0.json' with { type: 'json' }; -import event1 from '~/fixtures/events/event-1.json' with { type: 'json' }; - -import { eventToMicroFilter, getFilterId, getFilterLimit, getMicroFilters, isMicrofilter } from './filter.ts'; - -Deno.test('getMicroFilters', () => { - const event = event0; - const microfilters = getMicroFilters(event); - assertEquals(microfilters.length, 2); - assertEquals(microfilters[0], { authors: [event.pubkey], kinds: [0] }); - assertEquals(microfilters[1], { ids: [event.id] }); -}); - -Deno.test('eventToMicroFilter', () => { - assertEquals(eventToMicroFilter(event0), { authors: [event0.pubkey], kinds: [0] }); - assertEquals(eventToMicroFilter(event1), { ids: [event1.id] }); -}); - -Deno.test('isMicrofilter', () => { - assertEquals(isMicrofilter({ ids: [event0.id] }), true); - assertEquals(isMicrofilter({ authors: [event0.pubkey], kinds: [0] }), true); - assertEquals(isMicrofilter({ ids: [event0.id], authors: [event0.pubkey], kinds: [0] }), false); -}); - -Deno.test('getFilterId', () => { - assertEquals( - getFilterId({ ids: [event0.id] }), - '{"ids":["63d38c9b483d2d98a46382eadefd272e0e4bdb106a5b6eddb400c4e76f693d35"]}', - ); - assertEquals( - getFilterId({ authors: [event0.pubkey], kinds: [0] }), - '{"authors":["79c2cae114ea28a981e7559b4fe7854a473521a8d22a66bbab9fa248eb820ff6"],"kinds":[0]}', - ); -}); - -Deno.test('getFilterLimit', () => { - assertEquals(getFilterLimit({ ids: [event0.id] }), 1); - assertEquals(getFilterLimit({ ids: [event0.id], limit: 2 }), 1); - assertEquals(getFilterLimit({ ids: [event0.id], limit: 0 }), 0); - assertEquals(getFilterLimit({ ids: [event0.id], limit: -1 }), 0); - assertEquals(getFilterLimit({ kinds: [0], authors: [event0.pubkey] }), 1); - assertEquals(getFilterLimit({ kinds: [1], authors: [event0.pubkey] }), Infinity); - assertEquals(getFilterLimit({}), Infinity); -}); diff --git a/packages/ditto/filter.ts b/packages/ditto/filter.ts deleted file mode 100644 index f9288c8a..00000000 --- a/packages/ditto/filter.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { NKinds, NostrEvent, NostrFilter, NSchema as n } from '@nostrify/nostrify'; -import stringifyStable from 'fast-stable-stringify'; -import { z } from 'zod'; - -/** Microfilter to get one specific event by ID. */ -type IdMicrofilter = { ids: [NostrEvent['id']] }; -/** Microfilter to get an author. */ -type AuthorMicrofilter = { kinds: [0]; authors: [NostrEvent['pubkey']] }; -/** Filter to get one specific event. */ -type MicroFilter = IdMicrofilter | AuthorMicrofilter; - -/** Get deterministic ID for a microfilter. */ -function getFilterId(filter: MicroFilter): string { - if ('ids' in filter) { - return stringifyStable({ ids: [filter.ids[0]] }); - } else { - return stringifyStable({ - kinds: [filter.kinds[0]], - authors: [filter.authors[0]], - }); - } -} - -/** Get a microfilter from a Nostr event. */ -function eventToMicroFilter(event: NostrEvent): MicroFilter { - const [microfilter] = getMicroFilters(event); - return microfilter; -} - -/** Get all the microfilters for an event, in order of priority. */ -function getMicroFilters(event: NostrEvent): MicroFilter[] { - const microfilters: MicroFilter[] = []; - if (event.kind === 0) { - microfilters.push({ kinds: [0], authors: [event.pubkey] }); - } - microfilters.push({ ids: [event.id] }); - return microfilters; -} - -/** Microfilter schema. */ -const microFilterSchema = z.union([ - z.object({ ids: z.tuple([n.id()]) }).strict(), - z.object({ kinds: z.tuple([z.literal(0)]), authors: z.tuple([n.id()]) }).strict(), -]); - -/** Checks whether the filter is a microfilter. */ -function isMicrofilter(filter: NostrFilter): filter is MicroFilter { - return microFilterSchema.safeParse(filter).success; -} - -/** Returns true if the filter could potentially return any stored events at all. */ -function canFilter(filter: NostrFilter): boolean { - return getFilterLimit(filter) > 0; -} - -/** Normalize the `limit` of each filter, and remove filters that can't produce any events. */ -function normalizeFilters(filters: F[]): F[] { - return filters.reduce((acc, filter) => { - const limit = getFilterLimit(filter); - if (limit > 0) { - acc.push(limit === Infinity ? filter : { ...filter, limit }); - } - return acc; - }, []); -} - -/** Calculate the intrinsic limit of a filter. This function may return `Infinity`. */ -function getFilterLimit(filter: NostrFilter): number { - if (filter.ids && !filter.ids.length) return 0; - if (filter.kinds && !filter.kinds.length) return 0; - if (filter.authors && !filter.authors.length) return 0; - - for (const [key, value] of Object.entries(filter)) { - if (key[0] === '#' && Array.isArray(value) && !value.length) return 0; - } - - return Math.min( - Math.max(0, filter.limit ?? Infinity), - filter.ids?.length ?? Infinity, - filter.authors?.length && filter.kinds?.every((kind) => NKinds.replaceable(kind)) - ? filter.authors.length * filter.kinds.length - : Infinity, - ); -} - -export { - type AuthorMicrofilter, - canFilter, - eventToMicroFilter, - getFilterId, - getFilterLimit, - getMicroFilters, - type IdMicrofilter, - isMicrofilter, - type MicroFilter, - normalizeFilters, -}; diff --git a/packages/ditto/storages.ts b/packages/ditto/storages.ts index be61beb6..1494dc8c 100644 --- a/packages/ditto/storages.ts +++ b/packages/ditto/storages.ts @@ -7,7 +7,6 @@ import { Conf } from '@/config.ts'; import { wsUrlSchema } from '@/schema.ts'; import { AdminStore } from '@/storages/AdminStore.ts'; import { EventsDB } from '@/storages/EventsDB.ts'; -import { SearchStore } from '@/storages/search-store.ts'; import { InternalRelay } from '@/storages/InternalRelay.ts'; import { NPool, NRelay1 } from '@nostrify/nostrify'; import { getRelays } from '@/utils/outbox.ts'; @@ -19,7 +18,6 @@ export class Storages { private static _admin: Promise | undefined; private static _client: Promise> | undefined; private static _pubsub: Promise | undefined; - private static _search: Promise | undefined; public static async database(): Promise { if (!this._database) { @@ -124,17 +122,4 @@ export class Storages { } return this._client; } - - /** Storage to use for remote search. */ - public static async search(): Promise { - if (!this._search) { - this._search = Promise.resolve( - new SearchStore({ - relay: Conf.searchRelay, - fallback: await this.db(), - }), - ); - } - return this._search; - } } diff --git a/packages/ditto/storages/search-store.ts b/packages/ditto/storages/search-store.ts deleted file mode 100644 index 44dc1519..00000000 --- a/packages/ditto/storages/search-store.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { NostrEvent, NostrFilter, NRelay1, NStore } from '@nostrify/nostrify'; -import { logi } from '@soapbox/logi'; -import { JsonValue } from '@std/json'; - -import { normalizeFilters } from '@/filter.ts'; -import { type DittoEvent } from '@/interfaces/DittoEvent.ts'; -import { hydrateEvents } from '@/storages/hydrate.ts'; -import { abortError } from '@/utils/abort.ts'; - -interface SearchStoreOpts { - relay: string | undefined; - fallback: NStore; - hydrator?: NStore; -} - -class SearchStore implements NStore { - #fallback: NStore; - #hydrator: NStore; - #relay: NRelay1 | undefined; - - constructor(opts: SearchStoreOpts) { - this.#fallback = opts.fallback; - this.#hydrator = opts.hydrator ?? this; - - if (opts.relay) { - this.#relay = new NRelay1(opts.relay); - } - } - - event(_event: NostrEvent, _opts?: { signal?: AbortSignal }): Promise { - return Promise.reject(new Error('EVENT not implemented.')); - } - - async query(filters: NostrFilter[], opts?: { signal?: AbortSignal; limit?: number }): Promise { - filters = normalizeFilters(filters); - - if (opts?.signal?.aborted) return Promise.reject(abortError()); - if (!filters.length) return Promise.resolve([]); - - logi({ level: 'debug', ns: 'ditto.req', source: 'search', filters: filters as JsonValue }); - const query = filters[0]?.search; - - if (this.#relay && this.#relay.socket.readyState === WebSocket.OPEN) { - logi({ level: 'debug', ns: 'ditto.search', query, source: 'relay', relay: this.#relay.socket.url }); - - const events = await this.#relay.query(filters, opts); - - return hydrateEvents({ - events, - store: this.#hydrator, - signal: opts?.signal, - }); - } else { - logi({ level: 'debug', ns: 'ditto.search', query, source: 'db' }); - return this.#fallback.query(filters, opts); - } - } -} - -export { SearchStore };