Cache all the things

This commit is contained in:
2023-09-05 14:57:50 +01:00
parent 5521f685fc
commit b1459d0f49
49 changed files with 805 additions and 243 deletions

View File

@ -4,7 +4,7 @@ import { unwrap, sanitizeRelayUrl, ExternalStore, FeedCache } from "@snort/share
import { NostrEvent, TaggedNostrEvent } from "./nostr";
import { AuthHandler, Connection, RelaySettings, ConnectionStateSnapshot } from "./connection";
import { Query } from "./query";
import { NoteStore } from "./note-collection";
import { NoteCollection, NoteStore, NoteStoreHook, NoteStoreSnapshotData } from "./note-collection";
import { BuiltRawReqFilter, RequestBuilder } from "./request-builder";
import { RelayMetricHandler } from "./relay-metric-handler";
import {
@ -19,6 +19,7 @@ import {
db,
UsersRelays,
} from ".";
import { EventsCache } from "./cache/events";
/**
* Manages nostr content retrieval system
@ -66,22 +67,30 @@ export class NostrSystem extends ExternalStore<SystemSnapshot> implements System
*/
#relayMetrics: RelayMetricHandler;
/**
* General events cache
*/
#eventsCache: FeedCache<NostrEvent>;
constructor(props: {
authHandler?: AuthHandler;
relayCache?: FeedCache<UsersRelays>;
profileCache?: FeedCache<MetadataCache>;
relayMetrics?: FeedCache<RelayMetrics>;
eventsCache?: FeedCache<NostrEvent>;
}) {
super();
this.#handleAuth = props.authHandler;
this.#relayCache = props.relayCache ?? new UserRelaysCache();
this.#profileCache = props.profileCache ?? new UserProfileCache();
this.#relayMetricsCache = props.relayMetrics ?? new RelayMetricCache();
this.#eventsCache = props.eventsCache ?? new EventsCache();
this.#profileLoader = new ProfileLoaderService(this, this.#profileCache);
this.#relayMetrics = new RelayMetricHandler(this.#relayMetricsCache);
this.#cleanup();
}
HandleAuth?: AuthHandler | undefined;
/**
* Profile loader service allows you to request profiles
@ -99,7 +108,12 @@ export class NostrSystem extends ExternalStore<SystemSnapshot> implements System
*/
async Init() {
db.ready = await db.isAvailable();
const t = [this.#relayCache.preload(), this.#profileCache.preload(), this.#relayMetricsCache.preload()];
const t = [
this.#relayCache.preload(),
this.#profileCache.preload(),
this.#relayMetricsCache.preload(),
this.#eventsCache.preload()
];
await Promise.all(t);
}
@ -190,6 +204,33 @@ export class NostrSystem extends ExternalStore<SystemSnapshot> implements System
return this.Queries.get(id);
}
Fetch(req: RequestBuilder, cb?: (evs: Array<TaggedNostrEvent>) => void) {
const q = this.Query(NoteCollection, req);
return new Promise<NoteStoreSnapshotData>((resolve) => {
let t: ReturnType<typeof setTimeout> | undefined;
let tBuf: Array<TaggedNostrEvent> = [];
const releaseOnEvent = cb ? q.feed.onEvent(evs => {
if(!t) {
tBuf = [...evs];
t = setTimeout(() => {
t = undefined;
cb(tBuf);
}, 100);
} else {
tBuf.push(...evs);
}
}) : undefined;
const releaseFeedHook = q.feed.hook(() => {
if(q.progress === 1) {
releaseOnEvent?.();
releaseFeedHook();
q.cancel();
resolve(unwrap(q.feed.snapshot.data));
}
})
})
}
Query<T extends NoteStore>(type: { new (): T }, req: RequestBuilder): Query {
const existing = this.Queries.get(req.id);
if (existing) {
@ -214,6 +255,11 @@ export class NostrSystem extends ExternalStore<SystemSnapshot> implements System
const filters = req.build(this.#relayCache);
const q = new Query(req.id, req.instance, store, req.options?.leaveOpen);
if(filters.some(a => a.filters.some(b=>b.ids))) {
q.feed.onEvent(async evs => {
await this.#eventsCache.bulkSet(evs);
});
}
this.Queries.set(req.id, q);
for (const subQ of filters) {
this.SendQuery(q, subQ);
@ -224,6 +270,24 @@ export class NostrSystem extends ExternalStore<SystemSnapshot> implements System
}
async SendQuery(q: Query, qSend: BuiltRawReqFilter) {
// trim query of cached ids
for(const f of qSend.filters) {
if (f.ids) {
const cacheResults = await this.#eventsCache.bulkGet(f.ids);
if(cacheResults.length > 0) {
const resultIds = new Set(cacheResults.map(a => a.id));
f.ids = f.ids.filter(a => !resultIds.has(a));
q.feed.add(cacheResults as Array<TaggedNostrEvent>);
}
}
}
// check for empty filters
qSend.filters = qSend.filters.filter(a => Object.values(a).filter(v => Array.isArray(v)).every(b => (b as Array<string | number>).length > 0));
if(qSend.filters.length === 0) {
return;
}
if (qSend.relay) {
this.#log("Sending query to %s %O", qSend.relay, qSend);
const s = this.#sockets.get(qSend.relay);