refactor: Query emits Filters
This commit is contained in:
@ -4,9 +4,7 @@ import EventEmitter from "eventemitter3";
|
||||
import { FeedCache } from "@snort/shared";
|
||||
import { NostrEvent, ReqFilter, TaggedNostrEvent } from "./nostr";
|
||||
import { RelaySettings, ConnectionStateSnapshot, OkResponse } from "./connection";
|
||||
import { Query } from "./query";
|
||||
import { NoteStore } from "./note-collection";
|
||||
import { BuiltRawReqFilter, RequestBuilder } from "./request-builder";
|
||||
import { RequestBuilder } from "./request-builder";
|
||||
import { RelayMetricHandler } from "./relay-metric-handler";
|
||||
import {
|
||||
CachedMetadata,
|
||||
@ -23,12 +21,10 @@ import {
|
||||
QueryLike,
|
||||
} from ".";
|
||||
import { EventsCache } from "./cache/events";
|
||||
import { RelayCache, RelayMetadataLoader } from "./outbox-model";
|
||||
import { RelayMetadataLoader } from "./outbox-model";
|
||||
import { Optimizer, DefaultOptimizer } from "./query-optimizer";
|
||||
import { trimFilters } from "./request-trim";
|
||||
import { NostrConnectionPool } from "./nostr-connection-pool";
|
||||
import { NostrQueryManager } from "./nostr-query-manager";
|
||||
import { FilterCacheLayer, IdsFilterCacheLayer } from "./filter-cache-layer";
|
||||
|
||||
export interface NostrSystemEvents {
|
||||
change: (state: SystemSnapshot) => void;
|
||||
@ -52,77 +48,67 @@ export interface NostrsystemProps {
|
||||
*/
|
||||
export class NostrSystem extends EventEmitter<NostrSystemEvents> implements SystemInterface {
|
||||
#log = debug("System");
|
||||
#pool = new NostrConnectionPool();
|
||||
#queryManager: NostrQueryManager;
|
||||
|
||||
/**
|
||||
* Storage class for user relay lists
|
||||
*/
|
||||
#relayCache: FeedCache<UsersRelays>;
|
||||
readonly relayCache: FeedCache<UsersRelays>;
|
||||
|
||||
/**
|
||||
* Storage class for user profiles
|
||||
*/
|
||||
#profileCache: FeedCache<CachedMetadata>;
|
||||
readonly profileCache: FeedCache<CachedMetadata>;
|
||||
|
||||
/**
|
||||
* Storage class for relay metrics (connects/disconnects)
|
||||
*/
|
||||
#relayMetricsCache: FeedCache<RelayMetrics>;
|
||||
readonly relayMetricsCache: FeedCache<RelayMetrics>;
|
||||
|
||||
/**
|
||||
* Profile loading service
|
||||
*/
|
||||
#profileLoader: ProfileLoaderService;
|
||||
readonly profileLoader: ProfileLoaderService;
|
||||
|
||||
/**
|
||||
* Relay metrics handler cache
|
||||
*/
|
||||
#relayMetrics: RelayMetricHandler;
|
||||
|
||||
/**
|
||||
* General events cache
|
||||
*/
|
||||
#eventsCache: FeedCache<NostrEvent>;
|
||||
readonly relayMetricsHandler: RelayMetricHandler;
|
||||
|
||||
/**
|
||||
* Optimizer instance, contains optimized functions for processing data
|
||||
*/
|
||||
#optimizer: Optimizer;
|
||||
readonly optimizer: Optimizer;
|
||||
|
||||
readonly pool = new NostrConnectionPool();
|
||||
readonly eventsCache: FeedCache<NostrEvent>;
|
||||
readonly relayLoader: RelayMetadataLoader;
|
||||
|
||||
/**
|
||||
* Check event signatures (reccomended)
|
||||
*/
|
||||
checkSigs: boolean;
|
||||
|
||||
#relayLoader: RelayMetadataLoader;
|
||||
|
||||
/**
|
||||
* Query cache processing layers which can take data from a cache
|
||||
*/
|
||||
#queryCacheLayers: Array<FilterCacheLayer> = [];
|
||||
|
||||
constructor(props: NostrsystemProps) {
|
||||
super();
|
||||
this.#relayCache = props.relayCache ?? new UserRelaysCache(props.db?.userRelays);
|
||||
this.#profileCache = props.profileCache ?? new UserProfileCache(props.db?.users);
|
||||
this.#relayMetricsCache = props.relayMetrics ?? new RelayMetricCache(props.db?.relayMetrics);
|
||||
this.#eventsCache = props.eventsCache ?? new EventsCache(props.db?.events);
|
||||
this.#optimizer = props.optimizer ?? DefaultOptimizer;
|
||||
this.relayCache = props.relayCache ?? new UserRelaysCache(props.db?.userRelays);
|
||||
this.profileCache = props.profileCache ?? new UserProfileCache(props.db?.users);
|
||||
this.relayMetricsCache = props.relayMetrics ?? new RelayMetricCache(props.db?.relayMetrics);
|
||||
this.eventsCache = props.eventsCache ?? new EventsCache(props.db?.events);
|
||||
this.optimizer = props.optimizer ?? DefaultOptimizer;
|
||||
|
||||
this.#profileLoader = new ProfileLoaderService(this, this.#profileCache);
|
||||
this.#relayMetrics = new RelayMetricHandler(this.#relayMetricsCache);
|
||||
this.#relayLoader = new RelayMetadataLoader(this, this.#relayCache);
|
||||
this.profileLoader = new ProfileLoaderService(this, this.profileCache);
|
||||
this.relayMetricsHandler = new RelayMetricHandler(this.relayMetricsCache);
|
||||
this.relayLoader = new RelayMetadataLoader(this, this.relayCache);
|
||||
this.checkSigs = props.checkSigs ?? true;
|
||||
|
||||
this.#queryManager = new NostrQueryManager(this);
|
||||
this.#queryCacheLayers.push(new IdsFilterCacheLayer(this.#eventsCache));
|
||||
|
||||
// hook connection pool
|
||||
this.#pool.on("connected", (id, wasReconnect) => {
|
||||
const c = this.#pool.getConnection(id);
|
||||
this.pool.on("connected", (id, wasReconnect) => {
|
||||
const c = this.pool.getConnection(id);
|
||||
if (c) {
|
||||
this.#relayMetrics.onConnect(c.Address);
|
||||
this.relayMetricsHandler.onConnect(c.Address);
|
||||
if (wasReconnect) {
|
||||
for (const [, q] of this.#queryManager) {
|
||||
q.connectionRestored(c);
|
||||
@ -130,18 +116,18 @@ export class NostrSystem extends EventEmitter<NostrSystemEvents> implements Syst
|
||||
}
|
||||
}
|
||||
});
|
||||
this.#pool.on("connectFailed", address => {
|
||||
this.#relayMetrics.onDisconnect(address, 0);
|
||||
this.pool.on("connectFailed", address => {
|
||||
this.relayMetricsHandler.onDisconnect(address, 0);
|
||||
});
|
||||
this.#pool.on("event", (_, sub, ev) => {
|
||||
ev.relays?.length && this.#relayMetrics.onEvent(ev.relays[0]);
|
||||
this.pool.on("event", (_, sub, ev) => {
|
||||
ev.relays?.length && this.relayMetricsHandler.onEvent(ev.relays[0]);
|
||||
|
||||
if (!EventExt.isValid(ev)) {
|
||||
this.#log("Rejecting invalid event %O", ev);
|
||||
return;
|
||||
}
|
||||
if (this.checkSigs) {
|
||||
if (!this.#optimizer.schnorrVerify(ev)) {
|
||||
if (!this.optimizer.schnorrVerify(ev)) {
|
||||
this.#log("Invalid sig %O", ev);
|
||||
return;
|
||||
}
|
||||
@ -149,84 +135,68 @@ export class NostrSystem extends EventEmitter<NostrSystemEvents> implements Syst
|
||||
|
||||
this.emit("event", sub, ev);
|
||||
});
|
||||
this.#pool.on("disconnect", (id, code) => {
|
||||
const c = this.#pool.getConnection(id);
|
||||
this.pool.on("disconnect", (id, code) => {
|
||||
const c = this.pool.getConnection(id);
|
||||
if (c) {
|
||||
this.#relayMetrics.onDisconnect(c.Address, code);
|
||||
this.relayMetricsHandler.onDisconnect(c.Address, code);
|
||||
for (const [, q] of this.#queryManager) {
|
||||
q.connectionLost(c.Id);
|
||||
}
|
||||
}
|
||||
});
|
||||
this.#pool.on("eose", (id, sub) => {
|
||||
const c = this.#pool.getConnection(id);
|
||||
this.pool.on("eose", (id, sub) => {
|
||||
const c = this.pool.getConnection(id);
|
||||
if (c) {
|
||||
for (const [, v] of this.#queryManager) {
|
||||
v.eose(sub, c);
|
||||
}
|
||||
}
|
||||
});
|
||||
this.#pool.on("auth", (_, c, r, cb) => this.emit("auth", c, r, cb));
|
||||
this.#pool.on("notice", (addr, msg) => {
|
||||
this.pool.on("auth", (_, c, r, cb) => this.emit("auth", c, r, cb));
|
||||
this.pool.on("notice", (addr, msg) => {
|
||||
this.#log("NOTICE: %s %s", addr, msg);
|
||||
});
|
||||
this.#queryManager.on("change", () => this.emit("change", this.takeSnapshot()));
|
||||
this.#queryManager.on("sendQuery", (q, f) => this.#sendQuery(q, f));
|
||||
this.#queryManager.on("trace", t => {
|
||||
this.#relayMetrics.onTraceReport(t);
|
||||
this.relayMetricsHandler.onTraceReport(t);
|
||||
});
|
||||
|
||||
// internal handler for on-event
|
||||
this.on("event", (sub, ev) => {
|
||||
for (const [, v] of this.#queryManager) {
|
||||
const trace = v.handleEvent(sub, ev);
|
||||
// inject events to cache if query by id
|
||||
if (trace && trace.filters.some(a => a.ids)) {
|
||||
this.#eventsCache.set(ev);
|
||||
this.eventsCache.set(ev);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get ProfileLoader() {
|
||||
return this.#profileLoader;
|
||||
}
|
||||
|
||||
get Sockets(): ConnectionStateSnapshot[] {
|
||||
return this.#pool.getState();
|
||||
}
|
||||
|
||||
get RelayCache(): RelayCache {
|
||||
return this.#relayCache;
|
||||
}
|
||||
|
||||
get UserProfileCache(): FeedCache<CachedMetadata> {
|
||||
return this.#profileCache;
|
||||
}
|
||||
|
||||
get Optimizer(): Optimizer {
|
||||
return this.#optimizer;
|
||||
return this.pool.getState();
|
||||
}
|
||||
|
||||
async Init() {
|
||||
const t = [
|
||||
this.#relayCache.preload(),
|
||||
this.#profileCache.preload(),
|
||||
this.#relayMetricsCache.preload(),
|
||||
this.#eventsCache.preload(),
|
||||
this.relayCache.preload(),
|
||||
this.profileCache.preload(),
|
||||
this.relayMetricsCache.preload(),
|
||||
this.eventsCache.preload(),
|
||||
];
|
||||
await Promise.all(t);
|
||||
}
|
||||
|
||||
async ConnectToRelay(address: string, options: RelaySettings) {
|
||||
await this.#pool.connect(address, options, false);
|
||||
await this.pool.connect(address, options, false);
|
||||
}
|
||||
|
||||
ConnectEphemeralRelay(address: string) {
|
||||
return this.#pool.connect(address, { read: true, write: true }, true);
|
||||
return this.pool.connect(address, { read: true, write: true }, true);
|
||||
}
|
||||
|
||||
DisconnectRelay(address: string) {
|
||||
this.#pool.disconnect(address);
|
||||
this.pool.disconnect(address);
|
||||
}
|
||||
|
||||
GetQuery(id: string): QueryLike | undefined {
|
||||
@ -237,60 +207,8 @@ export class NostrSystem extends EventEmitter<NostrSystemEvents> implements Syst
|
||||
return this.#queryManager.fetch(req, cb);
|
||||
}
|
||||
|
||||
Query<T extends NoteStore>(type: { new (): T }, req: RequestBuilder): QueryLike {
|
||||
return this.#queryManager.query(type, req) as QueryLike;
|
||||
}
|
||||
|
||||
async #sendQuery(q: Query, qSend: BuiltRawReqFilter) {
|
||||
for (const qfl of this.#queryCacheLayers) {
|
||||
qSend = await qfl.processFilter(q, qSend);
|
||||
}
|
||||
for (const f of qSend.filters) {
|
||||
if (f.authors) {
|
||||
this.#relayLoader.TrackKeys(f.authors);
|
||||
}
|
||||
}
|
||||
|
||||
// check for empty filters
|
||||
const fNew = trimFilters(qSend.filters);
|
||||
if (fNew.length === 0) {
|
||||
return;
|
||||
}
|
||||
qSend.filters = fNew;
|
||||
|
||||
if (qSend.relay) {
|
||||
this.#log("Sending query to %s %O", qSend.relay, qSend);
|
||||
const s = this.#pool.getConnection(qSend.relay);
|
||||
if (s) {
|
||||
const qt = q.sendToRelay(s, qSend);
|
||||
if (qt) {
|
||||
return [qt];
|
||||
}
|
||||
} else {
|
||||
const nc = await this.ConnectEphemeralRelay(qSend.relay);
|
||||
if (nc) {
|
||||
const qt = q.sendToRelay(nc, qSend);
|
||||
if (qt) {
|
||||
return [qt];
|
||||
}
|
||||
} else {
|
||||
console.warn("Failed to connect to new relay for:", qSend.relay, q);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const ret = [];
|
||||
for (const [a, s] of this.#pool) {
|
||||
if (!s.Ephemeral) {
|
||||
this.#log("Sending query to %s %O", a, qSend);
|
||||
const qt = q.sendToRelay(s, qSend);
|
||||
if (qt) {
|
||||
ret.push(qt);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
return [];
|
||||
Query(req: RequestBuilder): QueryLike {
|
||||
return this.#queryManager.query(req) as QueryLike;
|
||||
}
|
||||
|
||||
HandleEvent(ev: TaggedNostrEvent) {
|
||||
@ -299,11 +217,11 @@ export class NostrSystem extends EventEmitter<NostrSystemEvents> implements Syst
|
||||
|
||||
async BroadcastEvent(ev: NostrEvent, cb?: (rsp: OkResponse) => void): Promise<OkResponse[]> {
|
||||
this.HandleEvent({ ...ev, relays: [] });
|
||||
return await this.#pool.broadcast(this, ev, cb);
|
||||
return await this.pool.broadcast(this, ev, cb);
|
||||
}
|
||||
|
||||
async WriteOnceToRelay(address: string, ev: NostrEvent): Promise<OkResponse> {
|
||||
return await this.#pool.broadcastTo(address, ev);
|
||||
return await this.pool.broadcastTo(address, ev);
|
||||
}
|
||||
|
||||
takeSnapshot(): SystemSnapshot {
|
||||
|
Reference in New Issue
Block a user