feat: @snort/system CacheRelay

This commit is contained in:
2024-01-23 15:35:28 +00:00
parent d6c578fafc
commit 5cea096067
29 changed files with 296 additions and 380 deletions

View File

@ -1,4 +1,4 @@
import { NostrEvent, ReqCommand, WorkerMessage, WorkerMessageCommand } from "./types";
import { NostrEvent, OkResponse, ReqCommand, ReqFilter, WorkerMessage, WorkerMessageCommand } from "./types";
import { v4 as uuid } from "uuid";
export class WorkerRelayInterface {
@ -18,35 +18,31 @@ export class WorkerRelayInterface {
}
async init(path: string) {
return (await this.#workerRpc<string, boolean>("init", path)).result;
return await this.#workerRpc<string, boolean>("init", path);
}
async event(ev: NostrEvent) {
return (await this.#workerRpc<NostrEvent, boolean>("event", ev)).result;
return await this.#workerRpc<NostrEvent, OkResponse>("event", ev);
}
async req(req: ReqCommand) {
async query(req: ReqCommand) {
return await this.#workerRpc<ReqCommand, Array<NostrEvent>>("req", req);
}
async count(req: ReqCommand) {
return (await this.#workerRpc<ReqCommand, number>("count", req)).result;
return await this.#workerRpc<ReqCommand, number>("count", req);
}
async summary() {
return (await this.#workerRpc<void, Record<string, number>>("summary")).result;
return await this.#workerRpc<void, Record<string, number>>("summary");
}
async close(id: string) {
return (await this.#workerRpc<string, boolean>("close", id)).result;
return await this.#workerRpc<string, boolean>("close", id);
}
async dump() {
return (await this.#workerRpc<void, Uint8Array>("dumpDb")).result;
}
async sql(sql: string, params: Array<string | number>) {
return (await this.#workerRpc<object, Array<Array<any>>>("sql", { sql, params })).result;
return await this.#workerRpc<void, Uint8Array>("dumpDb");
}
#workerRpc<T, R>(cmd: WorkerMessageCommand, args?: T) {
@ -57,16 +53,10 @@ export class WorkerRelayInterface {
args,
} as WorkerMessage<T>;
this.#worker.postMessage(msg);
return new Promise<{
result: R;
port: MessagePort | undefined;
}>(resolve => {
return new Promise<R>(resolve => {
this.#commandQueue.set(id, (v, port) => {
const cmdReply = v as WorkerMessage<R>;
resolve({
result: cmdReply.args,
port: port.length > 0 ? port[0] : undefined,
});
resolve(cmdReply.args);
});
});
}

View File

@ -69,7 +69,11 @@ export class InMemoryRelay extends EventEmitter<RelayHandlerEvents> implements R
const ret = [];
for (const [, e] of this.#events) {
if (eventMatchesFilter(e, filter)) {
ret.push(e);
if (filter.ids_only === true) {
ret.push(e.id);
} else {
ret.push(e);
}
}
}
return ret;

View File

@ -184,7 +184,13 @@ export class SqliteRelay extends EventEmitter<RelayHandlerEvents> implements Rel
const [sql, params] = this.#buildQuery(req);
const res = this.#db?.selectArrays(sql, params);
const results = res?.map(a => JSON.parse(a[0] as string) as NostrEvent) ?? [];
const results =
res?.map(a => {
if (req.ids_only === true) {
return a[0] as string;
}
return JSON.parse(a[0] as string) as NostrEvent;
}) ?? [];
const time = unixNowMs() - start;
this.#log(`Query ${id} results took ${time.toLocaleString()}ms`);
return results;
@ -245,7 +251,13 @@ export class SqliteRelay extends EventEmitter<RelayHandlerEvents> implements Rel
const conditions: Array<string> = [];
const params: Array<any> = [];
let sql = `select ${count ? "count(json)" : "json"} from events`;
let resultType = "json";
if (count) {
resultType = "count(json)";
} else if (req.ids_only === true) {
resultType = "id";
}
let sql = `select ${resultType} from events`;
const tags = Object.entries(req).filter(([k]) => k.startsWith("#"));
for (const [key, values] of tags) {
const vArray = values as Array<string>;

View File

@ -9,7 +9,7 @@ export type WorkerMessageCommand =
| "summary"
| "close"
| "dumpDb"
| "sql";
| "emit-event";
export interface WorkerMessage<T> {
id: string;
@ -27,11 +27,7 @@ export interface NostrEvent {
sig: string;
}
export interface ReqCommand {
id: string;
filters: Array<ReqFilter>;
leaveOpen?: boolean;
}
export type ReqCommand = ["REQ", id: string, ...filters: Array<ReqFilter>];
export interface ReqFilter {
ids?: string[];
@ -41,8 +37,16 @@ export interface ReqFilter {
since?: number;
until?: number;
limit?: number;
not?: ReqFilter;
[key: string]: Array<string> | Array<number> | string | number | undefined | ReqFilter;
ids_only?: boolean;
[key: string]: Array<string> | Array<number> | string | number | undefined | boolean;
}
export interface OkResponse {
ok: boolean;
id: string;
relay: string;
message?: string;
event: NostrEvent;
}
export interface RelayHandler extends EventEmitter<RelayHandlerEvents> {
@ -55,7 +59,7 @@ export interface RelayHandler extends EventEmitter<RelayHandlerEvents> {
* Run any SQL command
*/
sql(sql: string, params: Array<string | number>): Array<Array<string | number>>;
req(id: string, req: ReqFilter): Array<NostrEvent>;
req(id: string, req: ReqFilter): Array<NostrEvent | string>;
count(req: ReqFilter): number;
summary(): Record<string, number>;
dump(): Promise<Uint8Array>;

View File

@ -15,15 +15,12 @@ const ActiveSubscriptions = new Map<string, PortedFilter>();
let relay: RelayHandler | undefined;
async function reply<T>(id: string, obj?: T, transferables?: Transferable[]) {
globalThis.postMessage(
{
id,
cmd: "reply",
args: obj,
} as WorkerMessage<T>,
transferables ?? [],
);
async function reply<T>(id: string, obj?: T) {
globalThis.postMessage({
id,
cmd: "reply",
args: obj,
} as WorkerMessage<T>);
}
// Event inserter queue
@ -108,25 +105,18 @@ globalThis.onmessage = async ev => {
break;
}
case "close": {
ActiveSubscriptions.delete(msg.args as string);
reply(msg.id, true);
break;
}
case "req": {
await barrierQueue(cmdQueue, async () => {
const req = msg.args as ReqCommand;
const chan = new MessageChannel();
if (req.leaveOpen) {
ActiveSubscriptions.set(req.id, {
filters: req.filters,
port: chan.port1,
});
}
const filters = req.slice(2) as Array<ReqFilter>;
const results = [];
for (const r of req.filters) {
results.push(...relay!.req(req.id, r as ReqFilter));
for (const r of filters) {
results.push(...relay!.req(req[1], r));
}
reply(msg.id, results, req.leaveOpen ? [chan.port2] : undefined);
reply(msg.id, results);
});
break;
}
@ -134,8 +124,9 @@ globalThis.onmessage = async ev => {
await barrierQueue(cmdQueue, async () => {
const req = msg.args as ReqCommand;
let results = 0;
for (const r of req.filters) {
const c = relay!.count(r as ReqFilter);
const filters = req.slice(2) as Array<ReqFilter>;
for (const r of filters) {
const c = relay!.count(r);
results += c;
}
reply(msg.id, results);
@ -156,17 +147,6 @@ globalThis.onmessage = async ev => {
});
break;
}
case "sql": {
await barrierQueue(cmdQueue, async () => {
const req = msg.args as {
sql: string;
params: Array<any>;
};
const res = relay!.sql(req.sql, req.params);
reply(msg.id, res);
});
break;
}
default: {
reply(msg.id, { error: "Unknown command" });
break;