124 lines
3.0 KiB
TypeScript
124 lines
3.0 KiB
TypeScript
import { CachedTable, CacheEvents, removeUndefined, unixNowMs, unwrap } from "@snort/shared";
|
|
import { CachedMetadata, CacheRelay, mapEventToProfile, NostrEvent } from "@snort/system";
|
|
import debug from "debug";
|
|
import { EventEmitter } from "eventemitter3";
|
|
|
|
export class ProfileCacheRelayWorker extends EventEmitter<CacheEvents> implements CachedTable<CachedMetadata> {
|
|
#relay: CacheRelay;
|
|
#keys = new Set<string>();
|
|
#cache = new Map<string, CachedMetadata>();
|
|
#log = debug("ProfileCacheRelayWorker");
|
|
|
|
constructor(relay: CacheRelay) {
|
|
super();
|
|
this.#relay = relay;
|
|
}
|
|
|
|
async preload() {
|
|
const start = unixNowMs();
|
|
const profiles = await this.#relay.query([
|
|
"REQ",
|
|
"profiles-preload",
|
|
{
|
|
kinds: [0],
|
|
},
|
|
]);
|
|
this.#cache = new Map<string, CachedMetadata>(profiles.map(a => [a.pubkey, unwrap(mapEventToProfile(a))]));
|
|
this.#keys = new Set<string>(this.#cache.keys());
|
|
this.#log(`Loaded %d/%d in %d ms`, this.#cache.size, this.#keys.size, (unixNowMs() - start).toLocaleString());
|
|
}
|
|
|
|
async search(q: string) {
|
|
const profiles = await this.#relay.query([
|
|
"REQ",
|
|
"profiles-search",
|
|
{
|
|
kinds: [0],
|
|
search: q,
|
|
},
|
|
]);
|
|
return removeUndefined(profiles.map(mapEventToProfile));
|
|
}
|
|
|
|
keysOnTable(): string[] {
|
|
return [...this.#keys];
|
|
}
|
|
|
|
getFromCache(key?: string | undefined) {
|
|
if (key) {
|
|
return this.#cache.get(key);
|
|
}
|
|
}
|
|
|
|
discover(ev: NostrEvent) {
|
|
if (ev.kind === 0) {
|
|
this.#keys.add(ev.pubkey);
|
|
}
|
|
}
|
|
|
|
async get(key?: string | undefined) {
|
|
if (key) {
|
|
const cached = this.getFromCache(key);
|
|
if (cached) {
|
|
return cached;
|
|
}
|
|
const res = await this.bulkGet([key]);
|
|
if (res.length > 0) {
|
|
return res[0];
|
|
}
|
|
}
|
|
}
|
|
|
|
async bulkGet(keys: string[]) {
|
|
if (keys.length === 0) return [];
|
|
|
|
const results = await this.#relay.query([
|
|
"REQ",
|
|
"ProfileCacheRelayWorker.bulkGet",
|
|
{
|
|
authors: keys,
|
|
kinds: [0],
|
|
},
|
|
]);
|
|
const mapped = removeUndefined(results.map(a => mapEventToProfile(a)));
|
|
for (const pf of mapped) {
|
|
this.#cache.set(this.key(pf), pf);
|
|
}
|
|
this.emit(
|
|
"change",
|
|
mapped.map(a => this.key(a)),
|
|
);
|
|
return mapped;
|
|
}
|
|
|
|
async set(obj: CachedMetadata) {
|
|
this.#keys.add(this.key(obj));
|
|
}
|
|
|
|
async bulkSet(obj: CachedMetadata[] | readonly CachedMetadata[]) {
|
|
const mapped = obj.map(a => this.key(a));
|
|
mapped.forEach(a => this.#keys.add(a));
|
|
// todo: store in cache
|
|
this.emit("change", mapped);
|
|
}
|
|
|
|
async update(): Promise<"new" | "refresh" | "updated" | "no_change"> {
|
|
// do nothing
|
|
return "refresh";
|
|
}
|
|
|
|
async buffer(keys: string[]) {
|
|
const missing = keys.filter(a => !this.#cache.has(a));
|
|
const res = await this.bulkGet(missing);
|
|
return missing.filter(a => !res.some(b => this.key(b) === a));
|
|
}
|
|
|
|
key(of: CachedMetadata) {
|
|
return of.pubkey;
|
|
}
|
|
|
|
snapshot() {
|
|
return [...this.#cache.values()];
|
|
}
|
|
}
|