move to pkg

This commit is contained in:
2023-06-08 12:45:23 +02:00
parent 2b80109e3b
commit 81ccb95d82
256 changed files with 4856 additions and 529 deletions

View File

@ -0,0 +1,53 @@
import { TaggedRawEvent } from "../src/Nostr";
import { describe, expect } from "@jest/globals";
import { FlatNoteStore, ReplaceableNoteStore } from "../src/NoteCollection";
describe("NoteStore", () => {
describe("flat", () => {
test("one event", () => {
const ev = { id: "one" } as TaggedRawEvent;
const c = new FlatNoteStore();
c.add(ev);
expect(c.getSnapshotData()).toEqual([ev]);
});
test("still one event", () => {
const ev = { id: "one" } as TaggedRawEvent;
const c = new FlatNoteStore();
c.add(ev);
c.add(ev);
expect(c.getSnapshotData()).toEqual([ev]);
});
test("clears", () => {
const ev = { id: "one" } as TaggedRawEvent;
const c = new FlatNoteStore();
c.add(ev);
expect(c.getSnapshotData()).toEqual([ev]);
c.clear();
expect(c.getSnapshotData()).toEqual([]);
});
});
describe("replacable", () => {
test("one event", () => {
const ev = { id: "test", created_at: 69 } as TaggedRawEvent;
const c = new ReplaceableNoteStore();
c.add(ev);
expect(c.getSnapshotData()).toEqual(ev);
});
test("dont replace with older", () => {
const ev = { id: "test", created_at: 69 } as TaggedRawEvent;
const evOlder = { id: "test2", created_at: 68 } as TaggedRawEvent;
const c = new ReplaceableNoteStore();
c.add(ev);
c.add(evOlder);
expect(c.getSnapshotData()).toEqual(ev);
});
test("replace with newer", () => {
const ev = { id: "test", created_at: 69 } as TaggedRawEvent;
const evNewer = { id: "test2", created_at: 70 } as TaggedRawEvent;
const c = new ReplaceableNoteStore();
c.add(ev);
c.add(evNewer);
expect(c.getSnapshotData()).toEqual(evNewer);
});
});
});

View File

@ -0,0 +1,113 @@
import { Connection } from "../src";
import { describe, expect } from "@jest/globals";
import { Query } from "../src/Query";
import { getRandomValues } from "crypto";
import { FlatNoteStore } from "../src/NoteCollection";
import { RequestStrategy } from "../src/RequestBuilder";
window.crypto = {} as any;
window.crypto.getRandomValues = getRandomValues as any;
describe("query", () => {
test("progress", () => {
const q = new Query("test", new FlatNoteStore());
const opt = {
read: true,
write: true,
};
const c1 = new Connection("wss://one.com", opt);
c1.Down = false;
const c2 = new Connection("wss://two.com", opt);
c2.Down = false;
const c3 = new Connection("wss://three.com", opt);
c3.Down = false;
const f = {
relay: "",
strategy: RequestStrategy.DefaultRelays,
filters: [
{
kinds: [1],
authors: ["test"],
},
],
};
const qt1 = q.sendToRelay(c1, f);
const qt2 = q.sendToRelay(c2, f);
const qt3 = q.sendToRelay(c3, f);
expect(q.progress).toBe(0);
q.eose(qt1!.id, c1);
expect(q.progress).toBe(1 / 3);
q.eose(qt1!.id, c1);
expect(q.progress).toBe(1 / 3);
q.eose(qt2!.id, c2);
expect(q.progress).toBe(2 / 3);
q.eose(qt3!.id, c3);
expect(q.progress).toBe(1);
const qs = {
relay: "",
strategy: RequestStrategy.DefaultRelays,
filters: [
{
kinds: [1],
authors: ["test-sub"],
},
],
};
const qt = q.sendToRelay(c1, qs);
expect(q.progress).toBe(3 / 4);
q.eose(qt!.id, c1);
expect(q.progress).toBe(1);
q.sendToRelay(c2, qs);
expect(q.progress).toBe(4 / 5);
});
it("should merge all sub-query filters", () => {
const q = new Query("test", new FlatNoteStore());
const c0 = new Connection("wss://test.com", { read: true, write: true });
q.sendToRelay(c0, {
filters: [
{
authors: ["a"],
kinds: [1],
},
],
relay: "",
strategy: RequestStrategy.DefaultRelays,
});
q.sendToRelay(c0, {
filters: [
{
authors: ["b"],
kinds: [1, 2],
},
],
relay: "",
strategy: RequestStrategy.DefaultRelays,
});
q.sendToRelay(c0, {
filters: [
{
authors: ["c"],
kinds: [2],
},
],
relay: "",
strategy: RequestStrategy.DefaultRelays,
});
expect(q.filters).toEqual([
{
authors: ["a", "b"],
kinds: [1],
},
{
authors: ["b", "c"],
kinds: [2],
},
]);
});
});

View File

@ -0,0 +1,163 @@
import { RelayCache } from "../src/GossipModel";
import { RequestBuilder, RequestStrategy } from "../src/RequestBuilder";
import { describe, expect } from "@jest/globals";
const DummyCache = {
get: (pk?: string) => {
if (!pk) return undefined;
return [
{
url: `wss://${pk}.com/`,
settings: {
read: true,
write: true,
},
},
];
},
} as RelayCache;
describe("RequestBuilder", () => {
describe("basic", () => {
test("empty filter", () => {
const b = new RequestBuilder("test");
b.withFilter();
expect(b.buildRaw()).toEqual([{}]);
});
test("only kind", () => {
const b = new RequestBuilder("test");
b.withFilter().kinds([0]);
expect(b.buildRaw()).toMatchObject([{ kinds: [0] }]);
});
test("empty authors", () => {
const b = new RequestBuilder("test");
b.withFilter().authors([]);
expect(b.buildRaw()).toMatchObject([{ authors: [] }]);
});
test("authors/kinds/ids", () => {
const authors = ["a1", "a2"];
const kinds = [0, 1, 2, 3];
const ids = ["id1", "id2", "id3"];
const b = new RequestBuilder("test");
b.withFilter().authors(authors).kinds(kinds).ids(ids);
expect(b.buildRaw()).toMatchObject([{ ids, authors, kinds }]);
});
test("authors and kinds, duplicates removed", () => {
const authors = ["a1", "a2"];
const kinds = [0, 1, 2, 3];
const ids = ["id1", "id2", "id3"];
const b = new RequestBuilder("test");
b.withFilter().ids(ids).authors(authors).kinds(kinds).ids(ids).authors(authors).kinds(kinds);
expect(b.buildRaw()).toMatchObject([{ ids, authors, kinds }]);
});
test("search", () => {
const b = new RequestBuilder("test");
b.withFilter().kinds([1]).search("test-search");
expect(b.buildRaw()).toMatchObject([{ kinds: [1], search: "test-search" }]);
});
test("timeline", () => {
const authors = ["a1", "a2"];
const kinds = [0, 1, 2, 3];
const until = 10;
const since = 5;
const b = new RequestBuilder("test");
b.withFilter().kinds(kinds).authors(authors).since(since).until(until);
expect(b.buildRaw()).toMatchObject([{ kinds, authors, until, since }]);
});
test("multi-filter timeline", () => {
const authors = ["a1", "a2"];
const kinds = [0, 1, 2, 3];
const until = 10;
const since = 5;
const b = new RequestBuilder("test");
b.withFilter().kinds(kinds).authors(authors).since(since).until(until);
b.withFilter().kinds(kinds).authors(authors).since(since).until(until);
expect(b.buildRaw()).toMatchObject([
{ kinds, authors, until, since },
{ kinds, authors, until, since },
]);
});
});
describe("diff basic", () => {
const rb = new RequestBuilder("test");
const f0 = rb.withFilter();
const a = rb.buildRaw();
f0.authors(["a"]);
expect(a).toEqual([{}]);
const b = rb.buildDiff(DummyCache, a);
expect(b).toMatchObject([
{
filters: [{ authors: ["a"] }],
},
]);
});
describe("build gossip simply", () => {
const rb = new RequestBuilder("test");
rb.withFilter().authors(["a", "b"]).kinds([0]);
const a = rb.build(DummyCache);
expect(a).toEqual([
{
strategy: RequestStrategy.AuthorsRelays,
relay: "wss://a.com/",
filters: [
{
kinds: [0],
authors: ["a"],
},
],
},
{
strategy: RequestStrategy.AuthorsRelays,
relay: "wss://b.com/",
filters: [
{
kinds: [0],
authors: ["b"],
},
],
},
]);
});
describe("build gossip merged similar filters", () => {
const rb = new RequestBuilder("test");
rb.withFilter().authors(["a", "b"]).kinds([0]);
rb.withFilter().authors(["a", "b"]).kinds([10002]);
rb.withFilter().authors(["a"]).limit(10).kinds([4]);
const a = rb.build(DummyCache);
expect(a).toEqual([
{
strategy: RequestStrategy.AuthorsRelays,
relay: "wss://a.com/",
filters: [
{
kinds: [0, 10002],
authors: ["a"],
},
{
kinds: [4],
authors: ["a"],
limit: 10,
},
],
},
{
strategy: RequestStrategy.AuthorsRelays,
relay: "wss://b.com/",
filters: [
{
kinds: [0, 10002],
authors: ["b"],
},
],
},
]);
});
});

View File

@ -0,0 +1,34 @@
import { expandFilter } from "../src/RequestExpander";
describe("RequestExpander", () => {
test("expand filter", () => {
const a = {
authors: ["a", "b", "c"],
kinds: [1, 2, 3],
ids: ["x", "y"],
"#p": ["a"],
since: 99,
limit: 10,
};
expect(expandFilter(a)).toEqual([
{ authors: "a", kinds: 1, ids: "x", "#p": "a", since: 99, limit: 10 },
{ authors: "a", kinds: 1, ids: "y", "#p": "a", since: 99, limit: 10 },
{ authors: "a", kinds: 2, ids: "x", "#p": "a", since: 99, limit: 10 },
{ authors: "a", kinds: 2, ids: "y", "#p": "a", since: 99, limit: 10 },
{ authors: "a", kinds: 3, ids: "x", "#p": "a", since: 99, limit: 10 },
{ authors: "a", kinds: 3, ids: "y", "#p": "a", since: 99, limit: 10 },
{ authors: "b", kinds: 1, ids: "x", "#p": "a", since: 99, limit: 10 },
{ authors: "b", kinds: 1, ids: "y", "#p": "a", since: 99, limit: 10 },
{ authors: "b", kinds: 2, ids: "x", "#p": "a", since: 99, limit: 10 },
{ authors: "b", kinds: 2, ids: "y", "#p": "a", since: 99, limit: 10 },
{ authors: "b", kinds: 3, ids: "x", "#p": "a", since: 99, limit: 10 },
{ authors: "b", kinds: 3, ids: "y", "#p": "a", since: 99, limit: 10 },
{ authors: "c", kinds: 1, ids: "x", "#p": "a", since: 99, limit: 10 },
{ authors: "c", kinds: 1, ids: "y", "#p": "a", since: 99, limit: 10 },
{ authors: "c", kinds: 2, ids: "x", "#p": "a", since: 99, limit: 10 },
{ authors: "c", kinds: 2, ids: "y", "#p": "a", since: 99, limit: 10 },
{ authors: "c", kinds: 3, ids: "x", "#p": "a", since: 99, limit: 10 },
{ authors: "c", kinds: 3, ids: "y", "#p": "a", since: 99, limit: 10 },
]);
});
});

View File

@ -0,0 +1,23 @@
import { eventMatchesFilter } from "../src/RequestMatcher";
describe("RequestMatcher", () => {
it("should match simple filter", () => {
const ev = {
id: "test",
kind: 1,
pubkey: "pubkey",
created_at: 99,
tags: [],
content: "test",
sig: "",
};
const filter = {
ids: ["test"],
authors: ["pubkey", "other"],
kinds: [1, 2, 3],
since: 1,
before: 100,
};
expect(eventMatchesFilter(ev, filter)).toBe(true);
});
});

View File

@ -0,0 +1,110 @@
import { ReqFilter } from "../src";
import { filterIncludes, flatMerge, mergeSimilar, simpleMerge } from "../src/RequestMerger";
import { FlatReqFilter, expandFilter } from "../src/RequestExpander";
import { distance } from "../src/Util";
describe("RequestMerger", () => {
it("should simple merge authors", () => {
const a = {
authors: ["a"],
} as ReqFilter;
const b = {
authors: ["b"],
} as ReqFilter;
const merged = mergeSimilar([a, b]);
expect(merged).toEqual([
{
authors: ["a", "b"],
},
]);
});
it("should append non-mergable filters", () => {
const a = {
authors: ["a"],
} as ReqFilter;
const b = {
authors: ["b"],
} as ReqFilter;
const c = {
limit: 5,
authors: ["a"],
};
const merged = mergeSimilar([a, b, c]);
expect(merged).toEqual([
{
authors: ["a", "b"],
},
{
limit: 5,
authors: ["a"],
},
]);
});
it("filterIncludes", () => {
const bigger = {
authors: ["a", "b", "c"],
since: 99,
} as ReqFilter;
const smaller = {
authors: ["c"],
since: 100,
} as ReqFilter;
expect(filterIncludes(bigger, smaller)).toBe(true);
});
it("simpleMerge", () => {
const a = {
authors: ["a", "b", "c"],
since: 99,
} as ReqFilter;
const b = {
authors: ["c", "d", "e"],
since: 100,
} as ReqFilter;
expect(simpleMerge([a, b])).toEqual({
authors: ["a", "b", "c", "d", "e"],
since: 100,
});
});
});
describe("flatMerge", () => {
it("should flat merge simple", () => {
const input = [
{ ids: 0, authors: "a" },
{ ids: 0, authors: "b" },
{ kinds: 1 },
{ kinds: 2 },
{ kinds: 2 },
{ ids: 0, authors: "c" },
{ authors: "c", kinds: 1 },
{ authors: "c", limit: 100 },
{ ids: 1, authors: "c" },
] as Array<FlatReqFilter>;
const output = [
{ ids: [0], authors: ["a", "b", "c"] },
{ kinds: [1, 2] },
{ authors: ["c"], kinds: [1] },
{ authors: ["c"], limit: 100 },
{ ids: [1], authors: ["c"] },
] as Array<ReqFilter>;
expect(flatMerge(input)).toEqual(output);
});
it("should expand and flat merge complex same", () => {
const input = [
{ kinds: [1, 6969, 6], authors: ["kieran", "snort", "c", "d", "e"], since: 1, until: 100 },
{ kinds: [4], authors: ["kieran"] },
{ kinds: [4], "#p": ["kieran"] },
{ kinds: [1000], authors: ["snort"], "#p": ["kieran"] },
] as Array<ReqFilter>;
const dut = flatMerge(input.flatMap(expandFilter).sort(() => (Math.random() > 0.5 ? 1 : -1)));
expect(dut.every(a => input.some(b => distance(b, a) === 0))).toEqual(true);
});
});

View File

@ -0,0 +1,87 @@
import { ReqFilter } from "../src";
import { describe, expect } from "@jest/globals";
import { diffFilters } from "../src/RequestSplitter";
describe("RequestSplitter", () => {
test("single filter add value", () => {
const a: Array<ReqFilter> = [{ kinds: [0], authors: ["a"] }];
const b: Array<ReqFilter> = [{ kinds: [0], authors: ["a", "b"] }];
const diff = diffFilters(a, b);
expect(diff).toEqual({
added: [{ kinds: [0], authors: ["b"] }],
removed: [],
changed: true,
});
});
test("single filter remove value", () => {
const a: Array<ReqFilter> = [{ kinds: [0], authors: ["a"] }];
const b: Array<ReqFilter> = [{ kinds: [0], authors: ["b"] }];
const diff = diffFilters(a, b);
expect(diff).toEqual({
added: [{ kinds: [0], authors: ["b"] }],
removed: [{ kinds: [0], authors: ["a"] }],
changed: true,
});
});
test("single filter change critical key", () => {
const a: Array<ReqFilter> = [{ kinds: [0], authors: ["a"], since: 100 }];
const b: Array<ReqFilter> = [{ kinds: [0], authors: ["a", "b"], since: 101 }];
const diff = diffFilters(a, b);
expect(diff).toEqual({
added: [{ kinds: [0], authors: ["a", "b"], since: 101 }],
removed: [{ kinds: [0], authors: ["a"], since: 100 }],
changed: true,
});
});
test("multiple filter add value", () => {
const a: Array<ReqFilter> = [
{ kinds: [0], authors: ["a"] },
{ kinds: [69], authors: ["a"] },
];
const b: Array<ReqFilter> = [
{ kinds: [0], authors: ["a", "b"] },
{ kinds: [69], authors: ["a", "c"] },
];
const diff = diffFilters(a, b);
expect(diff).toEqual({
added: [
{ kinds: [0], authors: ["b"] },
{ kinds: [69], authors: ["c"] },
],
removed: [],
changed: true,
});
});
test("multiple filter remove value", () => {
const a: Array<ReqFilter> = [
{ kinds: [0], authors: ["a"] },
{ kinds: [69], authors: ["a"] },
];
const b: Array<ReqFilter> = [
{ kinds: [0], authors: ["b"] },
{ kinds: [69], authors: ["c"] },
];
const diff = diffFilters(a, b);
expect(diff).toEqual({
added: [
{ kinds: [0], authors: ["b"] },
{ kinds: [69], authors: ["c"] },
],
removed: [{ kinds: [0, 69], authors: ["a"] }],
changed: true,
});
});
test("add filter", () => {
const a: Array<ReqFilter> = [{ kinds: [0], authors: ["a"] }];
const b: Array<ReqFilter> = [
{ kinds: [0], authors: ["a"] },
{ kinds: [69], authors: ["c"] },
];
const diff = diffFilters(a, b);
expect(diff).toEqual({
added: [{ kinds: [69], authors: ["c"] }],
removed: [],
changed: true,
});
});
});

View File

@ -0,0 +1,117 @@
import { distance } from "../src/Util";
describe("distance", () => {
it("should have 0 distance", () => {
const a = {
ids: "a",
};
const b = {
ids: "a",
};
expect(distance(a, b)).toEqual(0);
});
it("should have 1 distance", () => {
const a = {
ids: "a",
};
const b = {
ids: "b",
};
expect(distance(a, b)).toEqual(1);
});
it("should have 10 distance", () => {
const a = {
ids: "a",
};
const b = {
ids: "a",
kinds: 1,
};
expect(distance(a, b)).toEqual(10);
});
it("should have 11 distance", () => {
const a = {
ids: "a",
};
const b = {
ids: "b",
kinds: 1,
};
expect(distance(a, b)).toEqual(11);
});
it("should have 1 distance, arrays", () => {
const a = {
since: 1,
until: 100,
kinds: [1],
authors: ["kieran", "snort", "c", "d", "e"],
};
const b = {
since: 1,
until: 100,
kinds: [6969],
authors: ["kieran", "snort", "c", "d", "e"],
};
expect(distance(a, b)).toEqual(1);
});
it("should have 1 distance, array change extra", () => {
const a = {
since: 1,
until: 100,
kinds: [1],
authors: ["f", "kieran", "snort", "c", "d"],
};
const b = {
since: 1,
until: 100,
kinds: [1],
authors: ["kieran", "snort", "c", "d", "e"],
};
expect(distance(a, b)).toEqual(1);
});
});
describe("tryParseNostrLink", () => {
it("is a valid nostr link", () => {
expect(parseNostrLink("nostr:npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg")).toMatchObject({
id: "7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e",
type: NostrPrefix.PublicKey,
});
expect(parseNostrLink("web+nostr:npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg")).toMatchObject({
id: "7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e",
type: NostrPrefix.PublicKey,
});
expect(parseNostrLink("nostr:note15449edq4qa5wzgqvh8td0q0dp6hwtes4pknsrm7eygeenhlj99xsq94wu9")).toMatchObject({
id: "a56a5cb4150768e1200cb9d6d781ed0eaee5e6150da701efd9223399dff2294d",
type: NostrPrefix.Note,
});
expect(
parseNostrLink(
"nostr:nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p"
)
).toMatchObject({
id: "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d",
type: NostrPrefix.Profile,
relays: ["wss://r.x.com", "wss://djbas.sadkb.com"],
});
expect(parseNostrLink("nostr:nevent1qqs226juks2sw68pyqxtn4khs8ksath9uc2smfcpalvjyvuemlezjngrd87dq")).toMatchObject({
id: "a56a5cb4150768e1200cb9d6d781ed0eaee5e6150da701efd9223399dff2294d",
type: NostrPrefix.Event,
});
expect(
parseNostrLink(
"nostr:naddr1qqzkjurnw4ksz9thwden5te0wfjkccte9ehx7um5wghx7un8qgs2d90kkcq3nk2jry62dyf50k0h36rhpdtd594my40w9pkal876jxgrqsqqqa28pccpzu"
)
).toMatchObject({
id: "ipsum",
type: NostrPrefix.Address,
relays: ["wss://relay.nostr.org"],
author: "a695f6b60119d9521934a691347d9f78e8770b56da16bb255ee286ddf9fda919",
kind: 30023,
});
});
test.each(["nostr:npub", "web+nostr:npub", "nostr:nevent1xxx"])("should return false for invalid nostr links", lb => {
expect(tryParseNostrLink(lb)).toBeUndefined();
});
});

View File

@ -0,0 +1,3 @@
import { TextEncoder, TextDecoder } from "util";
Object.assign(global, { TextDecoder, TextEncoder });