Upgrade NSpec, update `count` interface

This commit is contained in:
Alex Gleason 2024-02-12 10:48:26 -06:00
parent 676bd04229
commit cb80770fc7
No known key found for this signature in database
GPG Key ID: 7211D1F99744FBB7
10 changed files with 25 additions and 51 deletions

View File

@ -96,7 +96,7 @@ function connectStream(socket: WebSocket) {
/** Handle COUNT. Return the number of events matching the filters. */ /** Handle COUNT. Return the number of events matching the filters. */
async function handleCount([_, subId, ...rest]: ClientCOUNT): Promise<void> { async function handleCount([_, subId, ...rest]: ClientCOUNT): Promise<void> {
const count = await eventsDB.count(prepareFilters(rest)); const { count } = await eventsDB.count(prepareFilters(rest));
send(['COUNT', subId, { count, approximate: false }]); send(['COUNT', subId, { count, approximate: false }]);
} }

View File

@ -85,7 +85,6 @@ export { default as Debug } from 'https://gitlab.com/soapbox-pub/stickynotes/-/r
export { export {
LNURL, LNURL,
type LNURLDetails, type LNURLDetails,
type MapCache,
NCache, NCache,
NIP05, NIP05,
type NostrEvent, type NostrEvent,
@ -93,6 +92,6 @@ export {
NSet, NSet,
type NStore, type NStore,
type NStoreOpts, type NStoreOpts,
} from 'https://gitlab.com/soapbox-pub/NSpec/-/raw/8ce84d9acd9925f51b51c00374eae81a5031559b/mod.ts'; } from 'https://gitlab.com/soapbox-pub/NSpec/-/raw/v0.1.0/mod.ts';
export type * as TypeFest from 'npm:type-fest@^4.3.0'; export type * as TypeFest from 'npm:type-fest@^4.3.0';

View File

@ -46,7 +46,7 @@ async function handleEvent(event: DittoEvent, signal: AbortSignal): Promise<void
/** Encounter the event, and return whether it has already been encountered. */ /** Encounter the event, and return whether it has already been encountered. */
async function encounterEvent(event: NostrEvent, signal: AbortSignal): Promise<boolean> { async function encounterEvent(event: NostrEvent, signal: AbortSignal): Promise<boolean> {
const preexisting = (await cache.count([{ ids: [event.id] }])) > 0; const preexisting = (await cache.count([{ ids: [event.id] }])).count > 0;
cache.event(event); cache.event(event);
reqmeister.event(event, { signal }); reqmeister.event(event, { signal });
return preexisting; return preexisting;
@ -72,10 +72,10 @@ async function storeEvent(event: DittoEvent, opts: StoreEventOpts): Promise<void
const { force = false, signal } = opts; const { force = false, signal } = opts;
if (force || event.user || isAdminEvent(event) || await isLocallyFollowed(event.pubkey)) { if (force || event.user || isAdminEvent(event) || await isLocallyFollowed(event.pubkey)) {
const isDeleted = await eventsDB.count( const isDeleted = (await eventsDB.count(
[{ kinds: [5], authors: [Conf.pubkey, event.pubkey], '#e': [event.id], limit: 1 }], [{ kinds: [5], authors: [Conf.pubkey, event.pubkey], '#e': [event.id], limit: 1 }],
opts, opts,
) > 0; )).count > 0;
if (isDeleted) { if (isDeleted) {
return Promise.reject(new RelayError('blocked', 'event was deleted')); return Promise.reject(new RelayError('blocked', 'event was deleted'));
@ -145,16 +145,20 @@ function trackRelays(event: NostrEvent) {
} }
/** Queue related events to fetch. */ /** Queue related events to fetch. */
function fetchRelatedEvents(event: DittoEvent, signal: AbortSignal) { async function fetchRelatedEvents(event: DittoEvent, signal: AbortSignal) {
if (!event.user) { if (!event.user) {
reqmeister.req({ kinds: [0], authors: [event.pubkey] }, { signal }).catch(() => {}); reqmeister.req({ kinds: [0], authors: [event.pubkey] }, { signal }).catch(() => {});
} }
for (const [name, id, relay] of event.tags) { for (const [name, id, relay] of event.tags) {
if (name === 'e' && !cache.count([{ ids: [id] }])) { if (name === 'e') {
const { count } = await cache.count([{ ids: [id] }]);
if (!count) {
reqmeister.req({ ids: [id] }, { relays: [relay] }).catch(() => {}); reqmeister.req({ ids: [id] }, { relays: [relay] }).catch(() => {});
} }
} }
} }
}
/** Delete unattached media entries that are attached to the event. */ /** Delete unattached media entries that are attached to the event. */
function processMedia({ tags, pubkey, user }: DittoEvent) { function processMedia({ tags, pubkey, user }: DittoEvent) {

View File

@ -10,9 +10,9 @@ import { EventsDB } from './events-db.ts';
const eventsDB = new EventsDB(db); const eventsDB = new EventsDB(db);
Deno.test('count filters', async () => { Deno.test('count filters', async () => {
assertEquals(await eventsDB.count([{ kinds: [1] }]), 0); assertEquals((await eventsDB.count([{ kinds: [1] }])).count, 0);
await eventsDB.event(event1); await eventsDB.event(event1);
assertEquals(await eventsDB.count([{ kinds: [1] }]), 1); assertEquals((await eventsDB.count([{ kinds: [1] }])).count, 1);
}); });
Deno.test('insert and filter events', async () => { Deno.test('insert and filter events', async () => {
@ -55,11 +55,11 @@ Deno.test('query events with local filter', async () => {
}); });
Deno.test('inserting replaceable events', async () => { Deno.test('inserting replaceable events', async () => {
assertEquals(await eventsDB.count([{ kinds: [0], authors: [event0.pubkey] }]), 0); assertEquals((await eventsDB.count([{ kinds: [0], authors: [event0.pubkey] }])).count, 0);
await eventsDB.event(event0); await eventsDB.event(event0);
await assertRejects(() => eventsDB.event(event0)); await assertRejects(() => eventsDB.event(event0));
assertEquals(await eventsDB.count([{ kinds: [0], authors: [event0.pubkey] }]), 1); assertEquals((await eventsDB.count([{ kinds: [0], authors: [event0.pubkey] }])).count, 1);
const changeEvent = { ...event0, id: '123', created_at: event0.created_at + 1 }; const changeEvent = { ...event0, id: '123', created_at: event0.created_at + 1 };
await eventsDB.event(changeEvent); await eventsDB.event(changeEvent);

View File

@ -345,9 +345,9 @@ class EventsDB implements NStore {
} }
/** Get number of events that would be returned by filters. */ /** Get number of events that would be returned by filters. */
async count(filters: DittoFilter[], opts: NStoreOpts = {}): Promise<number> { async count(filters: DittoFilter[], opts: NStoreOpts = {}): Promise<{ count: number; approximate: boolean }> {
if (opts.signal?.aborted) return Promise.reject(abortError()); if (opts.signal?.aborted) return Promise.reject(abortError());
if (!filters.length) return Promise.resolve(0); if (!filters.length) return Promise.resolve({ count: 0, approximate: false });
this.#debug('COUNT', JSON.stringify(filters)); this.#debug('COUNT', JSON.stringify(filters));
const query = this.getEventsQuery(filters); const query = this.getEventsQuery(filters);
@ -357,7 +357,10 @@ class EventsDB implements NStore {
.select((eb) => eb.fn.count('id').as('count')) .select((eb) => eb.fn.count('id').as('count'))
.execute(); .execute();
return Number(count); return {
count: Number(count),
approximate: false,
};
} }
} }

View File

@ -94,14 +94,6 @@ class Optimizer implements NStore {
return getResults(); return getResults();
} }
count(_filters: DittoFilter[]): Promise<number> {
return Promise.reject(new Error('COUNT not implemented.'));
}
remove(_filters: DittoFilter[]): Promise<void> {
return Promise.reject(new Error('DELETE not implemented.'));
}
} }
export { Optimizer }; export { Optimizer };

View File

@ -92,14 +92,6 @@ class PoolStore implements NStore {
opts.signal?.addEventListener('abort', onAbort); opts.signal?.addEventListener('abort', onAbort);
}); });
} }
count() {
return Promise.reject(new Error('COUNT not implemented'));
}
remove() {
return Promise.reject(new Error('Cannot delete events from relays. Create a kind 5 event instead.'));
}
} }
export { PoolStore }; export { PoolStore };

View File

@ -133,14 +133,6 @@ class Reqmeister extends EventEmitter<{ [filterId: string]: (event: NostrEvent)
return Promise.all(promises); return Promise.all(promises);
} }
count(_filters: NostrFilter[]): Promise<number> {
return Promise.reject(new Error('COUNT not implemented.'));
}
remove(_filters: NostrFilter[]): Promise<void> {
return Promise.reject(new Error('DELETE not implemented.'));
}
} }
export { Reqmeister }; export { Reqmeister };

View File

@ -1,6 +1,6 @@
import { NiceRelay } from 'https://gitlab.com/soapbox-pub/nostr-machina/-/raw/5f4fb59c90c092e5aa59c01e6556a4bec264c167/mod.ts'; import { NiceRelay } from 'https://gitlab.com/soapbox-pub/nostr-machina/-/raw/5f4fb59c90c092e5aa59c01e6556a4bec264c167/mod.ts';
import { Debug, type NostrEvent, type NostrFilter, NSet, type NStore, type NStoreOpts } from '@/deps.ts'; import { Debug, type NostrEvent, NSet, type NStore, type NStoreOpts } from '@/deps.ts';
import { normalizeFilters } from '@/filter.ts'; import { normalizeFilters } from '@/filter.ts';
import { type DittoEvent } from '@/interfaces/DittoEvent.ts'; import { type DittoEvent } from '@/interfaces/DittoEvent.ts';
import { type DittoFilter } from '@/interfaces/DittoFilter.ts'; import { type DittoFilter } from '@/interfaces/DittoFilter.ts';
@ -68,14 +68,6 @@ class SearchStore implements NStore {
return this.#fallback.query(filters, opts); return this.#fallback.query(filters, opts);
} }
} }
count(_filters: NostrFilter[]): Promise<number> {
return Promise.reject(new Error('COUNT not implemented.'));
}
remove(_filters: NostrFilter[]): Promise<void> {
return Promise.reject(new Error('DELETE not implemented.'));
}
} }
export { SearchStore }; export { SearchStore };

View File

@ -1,6 +1,6 @@
// deno-lint-ignore-file ban-types // deno-lint-ignore-file ban-types
import { LRUCache, type MapCache } from '@/deps.ts'; import { LRUCache } from '@/deps.ts';
type FetchFn<K extends {}, V extends {}, O extends {}> = (key: K, opts: O) => Promise<V>; type FetchFn<K extends {}, V extends {}, O extends {}> = (key: K, opts: O) => Promise<V>;
@ -12,7 +12,7 @@ export class SimpleLRU<
K extends {}, K extends {},
V extends {}, V extends {},
O extends {} = FetchFnOpts, O extends {} = FetchFnOpts,
> implements MapCache<K, V, O> { > {
protected cache: LRUCache<K, V, void>; protected cache: LRUCache<K, V, void>;
constructor(fetchFn: FetchFn<K, V, { signal: AbortSignal }>, opts: LRUCache.Options<K, V, void>) { constructor(fetchFn: FetchFn<K, V, { signal: AbortSignal }>, opts: LRUCache.Options<K, V, void>) {