From a0f1031b85bf4b9f1d68b429e1dc9e05c456b962 Mon Sep 17 00:00:00 2001 From: Kieran Date: Wed, 6 Sep 2023 13:45:25 +0100 Subject: [PATCH 01/14] Add system-query --- package.json | 6 +- packages/app/package.json | 1 + packages/app/src/index.tsx | 4 + packages/app/webpack.config.js | 4 +- packages/system-query/.gitignore | 2 + packages/system-query/Cargo.lock | 244 ++++++++++++++++++++++++ packages/system-query/Cargo.toml | 17 ++ packages/system-query/README.md | 1 + packages/system-query/package.json | 18 ++ packages/system-query/src/diff.rs | 180 +++++++++++++++++ packages/system-query/src/expand.rs | 182 ++++++++++++++++++ packages/system-query/src/lib.rs | 78 ++++++++ packages/system-query/src/utils.rs | 0 packages/system/package.json | 1 + packages/system/src/request-expander.ts | 8 +- packages/system/src/request-merger.ts | 2 +- packages/system/src/request-splitter.ts | 14 +- yarn.lock | 42 +++- 18 files changed, 792 insertions(+), 12 deletions(-) create mode 100644 packages/system-query/.gitignore create mode 100644 packages/system-query/Cargo.lock create mode 100644 packages/system-query/Cargo.toml create mode 100644 packages/system-query/README.md create mode 100644 packages/system-query/package.json create mode 100644 packages/system-query/src/diff.rs create mode 100644 packages/system-query/src/expand.rs create mode 100644 packages/system-query/src/lib.rs create mode 100644 packages/system-query/src/utils.rs diff --git a/package.json b/package.json index ec436ed4..74c6e9a0 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,9 @@ "packages/*" ], "scripts": { - "build": "yarn workspace @snort/shared build && yarn workspace @snort/system build && yarn workspace @snort/system-react build && yarn workspace @snort/app build", - "start": "yarn workspace @snort/shared build && yarn workspace @snort/system build && yarn workspace @snort/system-react build && yarn workspace @snort/app start", - "test": "yarn workspace @snort/shared build && yarn workspace @snort/system build && yarn workspace @snort/app test && yarn workspace @snort/system test" + "build": "yarn workspace @snort/system-query build && yarn workspace @snort/shared build && yarn workspace @snort/system build && yarn workspace @snort/system-react build && yarn workspace @snort/app build", + "start": "arn workspace @snort/system-query build && yarn workspace @snort/shared build && yarn workspace @snort/system build && yarn workspace @snort/system-react build && yarn workspace @snort/app start", + "test": "arn workspace @snort/system-query build && yarn workspace @snort/shared build && yarn workspace @snort/system build && yarn workspace @snort/app test && yarn workspace @snort/system test" }, "devDependencies": { "@cloudflare/workers-types": "^4.20230307.0", diff --git a/packages/app/package.json b/packages/app/package.json index d1d00624..7a8df136 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -12,6 +12,7 @@ "@scure/bip39": "^1.1.1", "@snort/shared": "workspace:*", "@snort/system": "workspace:*", + "@snort/system-query": "workspace:*", "@snort/system-react": "workspace:*", "@szhsin/react-menu": "^3.3.1", "@void-cat/api": "^1.0.4", diff --git a/packages/app/src/index.tsx b/packages/app/src/index.tsx index 7666afb0..7bce8257 100644 --- a/packages/app/src/index.tsx +++ b/packages/app/src/index.tsx @@ -2,6 +2,9 @@ import "./index.css"; import "@szhsin/react-menu/dist/index.css"; import "./fonts/inter.css"; +import {default as wasmInit} from "@snort/system-query"; +import WasmPath from "@snort/system-query/pkg/system_query_bg.wasm"; + import { StrictMode } from "react"; import * as ReactDOM from "react-dom/client"; import { Provider } from "react-redux"; @@ -69,6 +72,7 @@ export const DefaultPowWorker = new PowWorker("/pow.js"); serviceWorkerRegistration.register(); async function initSite() { + await wasmInit(WasmPath); const login = LoginStore.takeSnapshot(); db.ready = await db.isAvailable(); if (db.ready) { diff --git a/packages/app/webpack.config.js b/packages/app/webpack.config.js index 1708f242..dce59a24 100644 --- a/packages/app/webpack.config.js +++ b/packages/app/webpack.config.js @@ -115,7 +115,7 @@ const config = { use: [MiniCssExtractPlugin.loader, require.resolve("css-loader")], }, { - test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif|webp)$/i, + test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif|webp|wasm)$/i, type: "asset", }, ], @@ -150,7 +150,7 @@ const config = { extensions: ["...", ".tsx", ".ts", ".jsx", ".js"], modules: ["...", __dirname, path.resolve(__dirname, "src")], fallback: { crypto: false }, - }, + } }; module.exports = () => config; diff --git a/packages/system-query/.gitignore b/packages/system-query/.gitignore new file mode 100644 index 00000000..e673575a --- /dev/null +++ b/packages/system-query/.gitignore @@ -0,0 +1,2 @@ +.idea/ +target/ \ No newline at end of file diff --git a/packages/system-query/Cargo.lock b/packages/system-query/Cargo.lock new file mode 100644 index 00000000..036190d1 --- /dev/null +++ b/packages/system-query/Cargo.lock @@ -0,0 +1,244 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bumpalo" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "serde" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde_derive" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "system-query" +version = "0.1.0" +dependencies = [ + "itertools", + "serde", + "serde-wasm-bindgen", + "wasm-bindgen", + "wasm-bindgen-test", +] + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "wasm-bindgen-test" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e6e302a7ea94f83a6d09e78e7dc7d9ca7b186bc2829c24a22d0753efd680671" +dependencies = [ + "console_error_panic_hook", + "js-sys", + "scoped-tls", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecb993dd8c836930ed130e020e77d9b2e65dd0fbab1b67c790b0f5d80b11a575" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "web-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] diff --git a/packages/system-query/Cargo.toml b/packages/system-query/Cargo.toml new file mode 100644 index 00000000..0ced6b86 --- /dev/null +++ b/packages/system-query/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "system-query" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +crate-type = ["cdylib"] + +[dependencies] +itertools = "0.11.0" +serde = { version = "1.0.188", features = ["derive"] } +serde-wasm-bindgen = "0.5.0" +wasm-bindgen = "0.2.87" + +[dev-dependencies] +wasm-bindgen-test = "0.3.37" diff --git a/packages/system-query/README.md b/packages/system-query/README.md new file mode 100644 index 00000000..f3891463 --- /dev/null +++ b/packages/system-query/README.md @@ -0,0 +1 @@ +# system-query diff --git a/packages/system-query/package.json b/packages/system-query/package.json new file mode 100644 index 00000000..a5d52590 --- /dev/null +++ b/packages/system-query/package.json @@ -0,0 +1,18 @@ +{ + "name": "@snort/system-query", + "version": "0.1.0", + "packageManager": "yarn@3.6.3", + "scripts": { + "build": "npx -y wasm-pack build -t web -s snort" + }, + "files": [ + "pkg/system_query_bg.wasm", + "pkg/system_query.js", + "pkg/system_query.d.ts" + ], + "module": "pkg/system_query.js", + "types": "pkg/system_query.d.ts", + "devDependencies": { + "wasm-pack": "^0.12.1" + } +} diff --git a/packages/system-query/src/diff.rs b/packages/system-query/src/diff.rs new file mode 100644 index 00000000..948362aa --- /dev/null +++ b/packages/system-query/src/diff.rs @@ -0,0 +1,180 @@ +use itertools::Itertools; +use crate::FlatReqFilter; + +pub fn diff_filter(prev: &Vec, next: &Vec) -> Vec:: { + let mut added: Vec:: = vec![]; + + for n in next.iter() { + if !prev.iter().contains(&n) { + added.push(n.clone()) + } + } + + added +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn simple_diff_same() { + let prev = vec![ + FlatReqFilter { + id: Some("a".to_owned()), + author: None, + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + } + ]; + let next = vec![ + FlatReqFilter { + id: Some("a".to_owned()), + author: None, + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + } + ]; + + let result = diff_filter(&prev, &next); + assert_eq!(result, vec![]) + } + + #[test] + fn simple_diff_add() { + let prev = vec![ + FlatReqFilter { + id: Some("a".to_owned()), + author: None, + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + } + ]; + let next = vec![ + FlatReqFilter { + id: Some("a".to_owned()), + author: None, + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }, + FlatReqFilter { + id: Some("b".to_owned()), + author: None, + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }, + ]; + + let result = diff_filter(&prev, &next); + assert_eq!(result, vec![ + FlatReqFilter { + id: Some("b".to_owned()), + author: None, + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + } + ]) + } + + #[test] + fn simple_diff_replace() { + let prev = vec![ + FlatReqFilter { + id: Some("a".to_owned()), + author: None, + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + } + ]; + let next = vec![ + FlatReqFilter { + id: Some("b".to_owned()), + author: None, + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }, + ]; + + let result = diff_filter(&prev, &next); + assert_eq!(result, vec![ + FlatReqFilter { + id: Some("b".to_owned()), + author: None, + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + } + ]) + } +} \ No newline at end of file diff --git a/packages/system-query/src/expand.rs b/packages/system-query/src/expand.rs new file mode 100644 index 00000000..ea619c12 --- /dev/null +++ b/packages/system-query/src/expand.rs @@ -0,0 +1,182 @@ +use itertools::Itertools; +use crate::{FlatReqFilter, ReqFilter}; + +#[derive(Clone)] +enum StringOrNumberEntry<'a> { + String((&'static str, &'a String)), + Number((&'static str, &'a i32)), +} + +pub fn expand_filter(filter: &ReqFilter) -> Vec { + let mut ret: Vec = Vec::new(); + + let mut inputs: Vec> = vec![]; + if let Some(ids) = &filter.ids { + let t_ids = ids.iter().map(|z| StringOrNumberEntry::String(("id", z))).collect_vec(); + inputs.push(t_ids); + } + if let Some(authors) = &filter.authors { + let t_ids = authors.iter().map(|z| StringOrNumberEntry::String(("author", z))).collect_vec(); + inputs.push(t_ids); + } + if let Some(kinds) = &filter.kinds { + let t_ids = kinds.iter().map(|z| StringOrNumberEntry::Number(("kind", z))).collect_vec(); + inputs.push(t_ids); + } + if let Some(e_tags) = &filter.e_tag { + let t_ids = e_tags.iter().map(|z| StringOrNumberEntry::String(("e_tag", z))).collect_vec(); + inputs.push(t_ids); + } + if let Some(p_tags) = &filter.p_tag { + let t_ids = p_tags.iter().map(|z| StringOrNumberEntry::String(("p_tag", z))).collect_vec(); + inputs.push(t_ids); + } + if let Some(d_tags) = &filter.d_tag { + let t_ids = d_tags.iter().map(|z| StringOrNumberEntry::String(("d_tag", z))).collect_vec(); + inputs.push(t_ids); + } + if let Some(t_tags) = &filter.t_tag { + let t_ids = t_tags.iter().map(|z| StringOrNumberEntry::String(("t_tag", z))).collect_vec(); + inputs.push(t_ids); + } + if let Some(r_tags) = &filter.r_tag { + let t_ids = r_tags.iter().map(|z| StringOrNumberEntry::String(("r_tag", z))).collect_vec(); + inputs.push(t_ids); + } + if let Some(search) = &filter.search { + let t_ids = search.iter().map(|z| StringOrNumberEntry::String(("search", z))).collect_vec(); + inputs.push(t_ids); + } + + for p in inputs.iter().multi_cartesian_product() { + ret.push(FlatReqFilter { + id: p.iter().find_map(|q| { + if let StringOrNumberEntry::String((k, v)) = q { + if (*k).eq("id") { + return Some((*v).to_string()); + } + } + None + }), + author: p.iter().find_map(|q| { + if let StringOrNumberEntry::String((k, v)) = q { + if (*k).eq("author") { + return Some((*v).to_string()); + } + } + None + }), + kind: p.iter().find_map(|q| { + if let StringOrNumberEntry::Number((k, v)) = q { + if (*k).eq("kind") { + return Some((*v).clone()); + } + } + None + }), + e_tag: p.iter().find_map(|q| { + if let StringOrNumberEntry::String((k, v)) = q { + if (*k).eq("e_tag") { + return Some((*v).to_string()); + } + } + None + }), + p_tag: p.iter().find_map(|q| { + if let StringOrNumberEntry::String((k, v)) = q { + if (*k).eq("p_tag") { + return Some((*v).to_string()); + } + } + None + }), + t_tag: p.iter().find_map(|q| { + if let StringOrNumberEntry::String((k, v)) = q { + if (*k).eq("t_tag") { + return Some((*v).to_string()); + } + } + None + }), + d_tag: p.iter().find_map(|q| { + if let StringOrNumberEntry::String((k, v)) = q { + if (*k).eq("d_tag") { + return Some((*v).to_string()); + } + } + None + }), + r_tag: p.iter().find_map(|q| { + if let StringOrNumberEntry::String((k, v)) = q { + if (*k).eq("r_tag") { + return Some((*v).to_string()); + } + } + None + }), + search: p.iter().find_map(|q| { + if let StringOrNumberEntry::String((k, v)) = q { + if (*k).eq("search") { + return Some((*v).to_string()); + } + } + None + }), + since: filter.since, + until: filter.until, + limit: filter.limit, + }) + } + ret +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ReqFilter; + + #[test] + fn test_expand_filter() { + let input = ReqFilter { + authors: Some(vec!["a".to_owned(), "b".to_owned(), "c".to_owned()]), + kinds: Some(vec![1, 2, 3]), + ids: Some(vec!["x".to_owned(), "y".to_owned()]), + p_tag: Some(vec!["a".to_owned()]), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }; + + let output = expand_filter(&input); + output.iter().take(5).for_each(|x| println!("{:?}", x)); + let expected = vec![ + FlatReqFilter { author: Some("a".to_owned()), kind: Some(1), id: Some("x".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, + FlatReqFilter { author: Some("a".to_owned()), kind: Some(1), id: Some("y".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, + FlatReqFilter { author: Some("a".to_owned()), kind: Some(2), id: Some("x".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, + FlatReqFilter { author: Some("a".to_owned()), kind: Some(2), id: Some("y".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, + FlatReqFilter { author: Some("a".to_owned()), kind: Some(3), id: Some("x".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, + FlatReqFilter { author: Some("a".to_owned()), kind: Some(3), id: Some("y".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, + FlatReqFilter { author: Some("b".to_owned()), kind: Some(1), id: Some("x".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, + FlatReqFilter { author: Some("b".to_owned()), kind: Some(1), id: Some("y".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, + FlatReqFilter { author: Some("b".to_owned()), kind: Some(2), id: Some("x".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, + FlatReqFilter { author: Some("b".to_owned()), kind: Some(2), id: Some("y".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, + FlatReqFilter { author: Some("b".to_owned()), kind: Some(3), id: Some("x".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, + FlatReqFilter { author: Some("b".to_owned()), kind: Some(3), id: Some("y".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, + FlatReqFilter { author: Some("c".to_owned()), kind: Some(1), id: Some("x".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, + FlatReqFilter { author: Some("c".to_owned()), kind: Some(1), id: Some("y".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, + FlatReqFilter { author: Some("c".to_owned()), kind: Some(2), id: Some("x".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, + FlatReqFilter { author: Some("c".to_owned()), kind: Some(2), id: Some("y".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, + FlatReqFilter { author: Some("c".to_owned()), kind: Some(3), id: Some("x".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, + FlatReqFilter { author: Some("c".to_owned()), kind: Some(3), id: Some("y".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, + ]; + assert_eq!(output.len(), expected.len()); + output + .iter() + .for_each(|a| assert!(expected.contains(a))); + } +} \ No newline at end of file diff --git a/packages/system-query/src/lib.rs b/packages/system-query/src/lib.rs new file mode 100644 index 00000000..bcdc5f46 --- /dev/null +++ b/packages/system-query/src/lib.rs @@ -0,0 +1,78 @@ +extern crate wasm_bindgen; + +use serde::{Deserialize, Serialize}; +use wasm_bindgen::prelude::*; + +mod expand; +mod diff; + +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +pub struct ReqFilter { + #[serde(rename = "ids", skip_serializing_if = "Option::is_none")] + pub ids: Option>, + #[serde(rename = "authors", skip_serializing_if = "Option::is_none")] + pub authors: Option>, + #[serde(rename = "kinds", skip_serializing_if = "Option::is_none")] + pub kinds: Option>, + #[serde(rename = "#e", skip_serializing_if = "Option::is_none")] + pub e_tag: Option>, + #[serde(rename = "#p", skip_serializing_if = "Option::is_none")] + pub p_tag: Option>, + #[serde(rename = "#t", skip_serializing_if = "Option::is_none")] + pub t_tag: Option>, + #[serde(rename = "#d", skip_serializing_if = "Option::is_none")] + pub d_tag: Option>, + #[serde(rename = "#r", skip_serializing_if = "Option::is_none")] + pub r_tag: Option>, + #[serde(rename = "search", skip_serializing_if = "Option::is_none")] + pub search: Option>, + #[serde(rename = "since", skip_serializing_if = "Option::is_none")] + pub since: Option, + #[serde(rename = "until", skip_serializing_if = "Option::is_none")] + pub until: Option, + #[serde(rename = "limit", skip_serializing_if = "Option::is_none")] + pub limit: Option, +} + +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +pub struct FlatReqFilter { + #[serde(rename = "ids", skip_serializing_if = "Option::is_none")] + id: Option, + #[serde(rename = "authors", skip_serializing_if = "Option::is_none")] + author: Option, + #[serde(rename = "kinds", skip_serializing_if = "Option::is_none")] + kind: Option, + #[serde(rename = "#e", skip_serializing_if = "Option::is_none")] + e_tag: Option, + #[serde(rename = "#p", skip_serializing_if = "Option::is_none")] + p_tag: Option, + #[serde(rename = "#t", skip_serializing_if = "Option::is_none")] + t_tag: Option, + #[serde(rename = "#d", skip_serializing_if = "Option::is_none")] + d_tag: Option, + #[serde(rename = "#r", skip_serializing_if = "Option::is_none")] + r_tag: Option, + #[serde(rename = "search", skip_serializing_if = "Option::is_none")] + search: Option, + #[serde(rename = "since", skip_serializing_if = "Option::is_none")] + since: Option, + #[serde(rename = "until", skip_serializing_if = "Option::is_none")] + until: Option, + #[serde(rename = "limit", skip_serializing_if = "Option::is_none")] + limit: Option, +} + +#[wasm_bindgen] +pub fn diff_filters(prev: JsValue, next: JsValue) -> Result { + let prev_parsed: Vec = serde_wasm_bindgen::from_value(prev)?; + let next_parsed: Vec = serde_wasm_bindgen::from_value(next)?; + let result = diff::diff_filter(&prev_parsed, &next_parsed); + Ok(serde_wasm_bindgen::to_value(&result)?) +} + +#[wasm_bindgen] +pub fn expand_filter(val: JsValue) -> Result { + let parsed: ReqFilter = serde_wasm_bindgen::from_value(val)?; + let result = expand::expand_filter(&parsed); + Ok(serde_wasm_bindgen::to_value(&result)?) +} \ No newline at end of file diff --git a/packages/system-query/src/utils.rs b/packages/system-query/src/utils.rs new file mode 100644 index 00000000..e69de29b diff --git a/packages/system/package.json b/packages/system/package.json index 434683af..b10f9a48 100644 --- a/packages/system/package.json +++ b/packages/system/package.json @@ -34,6 +34,7 @@ "@noble/hashes": "^1.3.2", "@scure/base": "^1.1.2", "@snort/shared": "^1.0.5", + "@snort/system-query": "workspace:*", "@stablelib/xchacha20": "^1.0.1", "debug": "^4.3.4", "dexie": "^3.2.4", diff --git a/packages/system/src/request-expander.ts b/packages/system/src/request-expander.ts index 669c55a3..2efa1d2c 100644 --- a/packages/system/src/request-expander.ts +++ b/packages/system/src/request-expander.ts @@ -1,4 +1,5 @@ import { ReqFilter } from "./nostr"; +import {expand_filter} from "@snort/system-query"; export interface FlatReqFilter { keys: number; @@ -20,7 +21,7 @@ export interface FlatReqFilter { * Expand a filter into its most fine grained form */ export function expandFilter(f: ReqFilter): Array { - const ret: Array = []; + /*const ret: Array = []; const src = Object.entries(f); const keys = src.filter(([, v]) => Array.isArray(v)).map(a => a[0]); const props = src.filter(([, v]) => !Array.isArray(v)); @@ -46,5 +47,8 @@ export function expandFilter(f: ReqFilter): Array { ...Object.fromEntries(props), }); - return ret; + return ret;*/ + + const ret = expand_filter(f); + return ret as Array; } diff --git a/packages/system/src/request-merger.ts b/packages/system/src/request-merger.ts index a345ef03..7b2f5cac 100644 --- a/packages/system/src/request-merger.ts +++ b/packages/system/src/request-merger.ts @@ -105,7 +105,7 @@ export function flatMerge(all: Array): Array { function mergeFiltersInSet(filters: Array) { return filters.reduce((acc, a) => { Object.entries(a).forEach(([k, v]) => { - if (k === "keys") return; + if (k === "keys" || v === undefined) return; if (DiscriminatorKeys.includes(k)) { acc[k] = v; } else { diff --git a/packages/system/src/request-splitter.ts b/packages/system/src/request-splitter.ts index 7f51295d..931458d4 100644 --- a/packages/system/src/request-splitter.ts +++ b/packages/system/src/request-splitter.ts @@ -1,8 +1,9 @@ import { flatFilterEq } from "./utils"; import { FlatReqFilter } from "./request-expander"; +import { diff_filters } from "@snort/system-query"; export function diffFilters(prev: Array, next: Array, calcRemoved?: boolean) { - const added = []; + /*const added = []; const removed = []; for (const n of next) { @@ -28,5 +29,12 @@ export function diffFilters(prev: Array, next: Array 0, + added: (added as Array), + removed: [] + } +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index ed4e9b98..8ac893ca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2711,6 +2711,7 @@ __metadata: "@scure/bip39": ^1.1.1 "@snort/shared": "workspace:*" "@snort/system": "workspace:*" + "@snort/system-query": "workspace:*" "@snort/system-react": "workspace:*" "@szhsin/react-menu": ^3.3.1 "@types/debug": ^4.1.8 @@ -2822,6 +2823,14 @@ __metadata: languageName: unknown linkType: soft +"@snort/system-query@workspace:*, @snort/system-query@workspace:packages/system-query": + version: 0.0.0-use.local + resolution: "@snort/system-query@workspace:packages/system-query" + dependencies: + wasm-pack: ^0.12.1 + languageName: unknown + linkType: soft + "@snort/system-react@workspace:*, @snort/system-react@workspace:packages/system-react": version: 0.0.0-use.local resolution: "@snort/system-react@workspace:packages/system-react" @@ -4613,6 +4622,15 @@ __metadata: languageName: node linkType: hard +"axios@npm:^0.26.1": + version: 0.26.1 + resolution: "axios@npm:0.26.1" + dependencies: + follow-redirects: ^1.14.8 + checksum: d9eb58ff4bc0b36a04783fc9ff760e9245c829a5a1052ee7ca6013410d427036b1d10d04e7380c02f3508c5eaf3485b1ae67bd2adbfec3683704745c8d7a6e1a + languageName: node + linkType: hard + "axios@npm:^1.2.1": version: 1.5.0 resolution: "axios@npm:1.5.0" @@ -4812,6 +4830,17 @@ __metadata: languageName: node linkType: hard +"binary-install@npm:^1.0.1": + version: 1.1.0 + resolution: "binary-install@npm:1.1.0" + dependencies: + axios: ^0.26.1 + rimraf: ^3.0.2 + tar: ^6.1.11 + checksum: 271344b49f42460f5e3ec29d681cd4b749aaf9592040d49f6ae86d267b997b5fd094fd7a710df4d477fc299a51833b8cb94206595db6c46edea3f1603266a0d2 + languageName: node + linkType: hard + "binaryextensions@npm:^4.15.0, binaryextensions@npm:^4.16.0": version: 4.18.0 resolution: "binaryextensions@npm:4.18.0" @@ -7060,7 +7089,7 @@ __metadata: languageName: node linkType: hard -"follow-redirects@npm:^1.0.0, follow-redirects@npm:^1.15.0": +"follow-redirects@npm:^1.0.0, follow-redirects@npm:^1.14.8, follow-redirects@npm:^1.15.0": version: 1.15.2 resolution: "follow-redirects@npm:1.15.2" peerDependenciesMeta: @@ -13956,6 +13985,17 @@ __metadata: languageName: node linkType: hard +"wasm-pack@npm:^0.12.1": + version: 0.12.1 + resolution: "wasm-pack@npm:0.12.1" + dependencies: + binary-install: ^1.0.1 + bin: + wasm-pack: run.js + checksum: 497abceb48b15329bb30f614b9ed7b07d82554ff225b93485794fb91b074a98aeda8091146aafe969c7f9615d106c770d215cf916bebf65451978f7222fcbe0b + languageName: node + linkType: hard + "watchpack@npm:^2.4.0": version: 2.4.0 resolution: "watchpack@npm:2.4.0" -- 2.45.2 From d672336ea1beff30459e9e0fac152e07a69cac16 Mon Sep 17 00:00:00 2001 From: Kieran Date: Wed, 6 Sep 2023 13:52:35 +0100 Subject: [PATCH 02/14] Try build directly with wasm-pack command --- packages/system-query/package.json | 7 ++---- yarn.lock | 35 +----------------------------- 2 files changed, 3 insertions(+), 39 deletions(-) diff --git a/packages/system-query/package.json b/packages/system-query/package.json index a5d52590..f3ce5ed2 100644 --- a/packages/system-query/package.json +++ b/packages/system-query/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "packageManager": "yarn@3.6.3", "scripts": { - "build": "npx -y wasm-pack build -t web -s snort" + "build": "wasm-pack build -t web -s snort" }, "files": [ "pkg/system_query_bg.wasm", @@ -11,8 +11,5 @@ "pkg/system_query.d.ts" ], "module": "pkg/system_query.js", - "types": "pkg/system_query.d.ts", - "devDependencies": { - "wasm-pack": "^0.12.1" - } + "types": "pkg/system_query.d.ts" } diff --git a/yarn.lock b/yarn.lock index 8ac893ca..05e7d01d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2826,8 +2826,6 @@ __metadata: "@snort/system-query@workspace:*, @snort/system-query@workspace:packages/system-query": version: 0.0.0-use.local resolution: "@snort/system-query@workspace:packages/system-query" - dependencies: - wasm-pack: ^0.12.1 languageName: unknown linkType: soft @@ -4622,15 +4620,6 @@ __metadata: languageName: node linkType: hard -"axios@npm:^0.26.1": - version: 0.26.1 - resolution: "axios@npm:0.26.1" - dependencies: - follow-redirects: ^1.14.8 - checksum: d9eb58ff4bc0b36a04783fc9ff760e9245c829a5a1052ee7ca6013410d427036b1d10d04e7380c02f3508c5eaf3485b1ae67bd2adbfec3683704745c8d7a6e1a - languageName: node - linkType: hard - "axios@npm:^1.2.1": version: 1.5.0 resolution: "axios@npm:1.5.0" @@ -4830,17 +4819,6 @@ __metadata: languageName: node linkType: hard -"binary-install@npm:^1.0.1": - version: 1.1.0 - resolution: "binary-install@npm:1.1.0" - dependencies: - axios: ^0.26.1 - rimraf: ^3.0.2 - tar: ^6.1.11 - checksum: 271344b49f42460f5e3ec29d681cd4b749aaf9592040d49f6ae86d267b997b5fd094fd7a710df4d477fc299a51833b8cb94206595db6c46edea3f1603266a0d2 - languageName: node - linkType: hard - "binaryextensions@npm:^4.15.0, binaryextensions@npm:^4.16.0": version: 4.18.0 resolution: "binaryextensions@npm:4.18.0" @@ -7089,7 +7067,7 @@ __metadata: languageName: node linkType: hard -"follow-redirects@npm:^1.0.0, follow-redirects@npm:^1.14.8, follow-redirects@npm:^1.15.0": +"follow-redirects@npm:^1.0.0, follow-redirects@npm:^1.15.0": version: 1.15.2 resolution: "follow-redirects@npm:1.15.2" peerDependenciesMeta: @@ -13985,17 +13963,6 @@ __metadata: languageName: node linkType: hard -"wasm-pack@npm:^0.12.1": - version: 0.12.1 - resolution: "wasm-pack@npm:0.12.1" - dependencies: - binary-install: ^1.0.1 - bin: - wasm-pack: run.js - checksum: 497abceb48b15329bb30f614b9ed7b07d82554ff225b93485794fb91b074a98aeda8091146aafe969c7f9615d106c770d215cf916bebf65451978f7222fcbe0b - languageName: node - linkType: hard - "watchpack@npm:^2.4.0": version: 2.4.0 resolution: "watchpack@npm:2.4.0" -- 2.45.2 From 3232a8679c5ab7546b52bc361321636acf279e5a Mon Sep 17 00:00:00 2001 From: Kieran Date: Wed, 6 Sep 2023 13:56:49 +0100 Subject: [PATCH 03/14] install wasm-pack --- packages/system-query/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/system-query/package.json b/packages/system-query/package.json index f3ce5ed2..6110aabe 100644 --- a/packages/system-query/package.json +++ b/packages/system-query/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "packageManager": "yarn@3.6.3", "scripts": { - "build": "wasm-pack build -t web -s snort" + "build": "cargo install wasm-pack && wasm-pack build -t web -s snort" }, "files": [ "pkg/system_query_bg.wasm", -- 2.45.2 From dc7ac061a5b21ac70dedd88e8d66cfa1c72f4813 Mon Sep 17 00:00:00 2001 From: Kieran Date: Wed, 6 Sep 2023 14:05:18 +0100 Subject: [PATCH 04/14] Commit wasm build --- packages/system-query/package.json | 2 +- packages/system-query/pkg/README.md | 1 + packages/system-query/pkg/package.json | 14 + packages/system-query/pkg/system_query.d.ts | 46 ++ packages/system-query/pkg/system_query.js | 499 ++++++++++++++++++ .../system-query/pkg/system_query_bg.wasm | Bin 0 -> 83956 bytes .../pkg/system_query_bg.wasm.d.ts | 9 + 7 files changed, 570 insertions(+), 1 deletion(-) create mode 100644 packages/system-query/pkg/README.md create mode 100644 packages/system-query/pkg/package.json create mode 100644 packages/system-query/pkg/system_query.d.ts create mode 100644 packages/system-query/pkg/system_query.js create mode 100644 packages/system-query/pkg/system_query_bg.wasm create mode 100644 packages/system-query/pkg/system_query_bg.wasm.d.ts diff --git a/packages/system-query/package.json b/packages/system-query/package.json index 6110aabe..f3ce5ed2 100644 --- a/packages/system-query/package.json +++ b/packages/system-query/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "packageManager": "yarn@3.6.3", "scripts": { - "build": "cargo install wasm-pack && wasm-pack build -t web -s snort" + "build": "wasm-pack build -t web -s snort" }, "files": [ "pkg/system_query_bg.wasm", diff --git a/packages/system-query/pkg/README.md b/packages/system-query/pkg/README.md new file mode 100644 index 00000000..f3891463 --- /dev/null +++ b/packages/system-query/pkg/README.md @@ -0,0 +1 @@ +# system-query diff --git a/packages/system-query/pkg/package.json b/packages/system-query/pkg/package.json new file mode 100644 index 00000000..79f43f39 --- /dev/null +++ b/packages/system-query/pkg/package.json @@ -0,0 +1,14 @@ +{ + "name": "@snort/system-query", + "version": "0.1.0", + "files": [ + "system_query_bg.wasm", + "system_query.js", + "system_query.d.ts" + ], + "module": "system_query.js", + "types": "system_query.d.ts", + "sideEffects": [ + "./snippets/*" + ] +} \ No newline at end of file diff --git a/packages/system-query/pkg/system_query.d.ts b/packages/system-query/pkg/system_query.d.ts new file mode 100644 index 00000000..3935fee6 --- /dev/null +++ b/packages/system-query/pkg/system_query.d.ts @@ -0,0 +1,46 @@ +/* tslint:disable */ +/* eslint-disable */ +/** +* @param {any} prev +* @param {any} next +* @returns {any} +*/ +export function diff_filters(prev: any, next: any): any; +/** +* @param {any} val +* @returns {any} +*/ +export function expand_filter(val: any): any; + +export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; + +export interface InitOutput { + readonly memory: WebAssembly.Memory; + readonly diff_filters: (a: number, b: number, c: number) => void; + readonly expand_filter: (a: number, b: number) => void; + readonly __wbindgen_malloc: (a: number, b: number) => number; + readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; + readonly __wbindgen_add_to_stack_pointer: (a: number) => number; + readonly __wbindgen_exn_store: (a: number) => void; +} + +export type SyncInitInput = BufferSource | WebAssembly.Module; +/** +* Instantiates the given `module`, which can either be bytes or +* a precompiled `WebAssembly.Module`. +* +* @param {SyncInitInput} module +* +* @returns {InitOutput} +*/ +export function initSync(module: SyncInitInput): InitOutput; + +/** +* If `module_or_path` is {RequestInfo} or {URL}, makes a request and +* for everything else, calls `WebAssembly.instantiate` directly. +* +* @param {InitInput | Promise} module_or_path +* +* @returns {Promise} +*/ +export default function __wbg_init (module_or_path?: InitInput | Promise): Promise; diff --git a/packages/system-query/pkg/system_query.js b/packages/system-query/pkg/system_query.js new file mode 100644 index 00000000..1b85b0e1 --- /dev/null +++ b/packages/system-query/pkg/system_query.js @@ -0,0 +1,499 @@ +let wasm; + +const heap = new Array(128).fill(undefined); + +heap.push(undefined, null, true, false); + +function getObject(idx) { return heap[idx]; } + +let heap_next = heap.length; + +function dropObject(idx) { + if (idx < 132) return; + heap[idx] = heap_next; + heap_next = idx; +} + +function takeObject(idx) { + const ret = getObject(idx); + dropObject(idx); + return ret; +} + +let WASM_VECTOR_LEN = 0; + +let cachedUint8Memory0 = null; + +function getUint8Memory0() { + if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) { + cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8Memory0; +} + +const cachedTextEncoder = (typeof TextEncoder !== 'undefined' ? new TextEncoder('utf-8') : { encode: () => { throw Error('TextEncoder not available') } } ); + +const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' + ? function (arg, view) { + return cachedTextEncoder.encodeInto(arg, view); +} + : function (arg, view) { + const buf = cachedTextEncoder.encode(arg); + view.set(buf); + return { + read: arg.length, + written: buf.length + }; +}); + +function passStringToWasm0(arg, malloc, realloc) { + + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg); + const ptr = malloc(buf.length, 1) >>> 0; + getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf); + WASM_VECTOR_LEN = buf.length; + return ptr; + } + + let len = arg.length; + let ptr = malloc(len, 1) >>> 0; + + const mem = getUint8Memory0(); + + let offset = 0; + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset); + if (code > 0x7F) break; + mem[ptr + offset] = code; + } + + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset); + } + ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; + const view = getUint8Memory0().subarray(ptr + offset, ptr + len); + const ret = encodeString(arg, view); + + offset += ret.written; + } + + WASM_VECTOR_LEN = offset; + return ptr; +} + +function isLikeNone(x) { + return x === undefined || x === null; +} + +let cachedInt32Memory0 = null; + +function getInt32Memory0() { + if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) { + cachedInt32Memory0 = new Int32Array(wasm.memory.buffer); + } + return cachedInt32Memory0; +} + +const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); + +if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; + +function getStringFromWasm0(ptr, len) { + ptr = ptr >>> 0; + return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); +} + +function addHeapObject(obj) { + if (heap_next === heap.length) heap.push(heap.length + 1); + const idx = heap_next; + heap_next = heap[idx]; + + heap[idx] = obj; + return idx; +} + +let cachedFloat64Memory0 = null; + +function getFloat64Memory0() { + if (cachedFloat64Memory0 === null || cachedFloat64Memory0.byteLength === 0) { + cachedFloat64Memory0 = new Float64Array(wasm.memory.buffer); + } + return cachedFloat64Memory0; +} + +function debugString(val) { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `"${val}"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; +} +/** +* @param {any} prev +* @param {any} next +* @returns {any} +*/ +export function diff_filters(prev, next) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.diff_filters(retptr, addHeapObject(prev), addHeapObject(next)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +} + +/** +* @param {any} val +* @returns {any} +*/ +export function expand_filter(val) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.expand_filter(retptr, addHeapObject(val)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +} + +function handleError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + wasm.__wbindgen_exn_store(addHeapObject(e)); + } +} + +async function __wbg_load(module, imports) { + if (typeof Response === 'function' && module instanceof Response) { + if (typeof WebAssembly.instantiateStreaming === 'function') { + try { + return await WebAssembly.instantiateStreaming(module, imports); + + } catch (e) { + if (module.headers.get('Content-Type') != 'application/wasm') { + console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e); + + } else { + throw e; + } + } + } + + const bytes = await module.arrayBuffer(); + return await WebAssembly.instantiate(bytes, imports); + + } else { + const instance = await WebAssembly.instantiate(module, imports); + + if (instance instanceof WebAssembly.Instance) { + return { instance, module }; + + } else { + return instance; + } + } +} + +function __wbg_get_imports() { + const imports = {}; + imports.wbg = {}; + imports.wbg.__wbindgen_object_drop_ref = function(arg0) { + takeObject(arg0); + }; + imports.wbg.__wbindgen_string_get = function(arg0, arg1) { + const obj = getObject(arg1); + const ret = typeof(obj) === 'string' ? obj : undefined; + var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len1 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len1; + getInt32Memory0()[arg0 / 4 + 0] = ptr1; + }; + imports.wbg.__wbindgen_is_object = function(arg0) { + const val = getObject(arg0); + const ret = typeof(val) === 'object' && val !== null; + return ret; + }; + imports.wbg.__wbindgen_is_undefined = function(arg0) { + const ret = getObject(arg0) === undefined; + return ret; + }; + imports.wbg.__wbindgen_in = function(arg0, arg1) { + const ret = getObject(arg0) in getObject(arg1); + return ret; + }; + imports.wbg.__wbindgen_error_new = function(arg0, arg1) { + const ret = new Error(getStringFromWasm0(arg0, arg1)); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_object_clone_ref = function(arg0) { + const ret = getObject(arg0); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_jsval_loose_eq = function(arg0, arg1) { + const ret = getObject(arg0) == getObject(arg1); + return ret; + }; + imports.wbg.__wbindgen_boolean_get = function(arg0) { + const v = getObject(arg0); + const ret = typeof(v) === 'boolean' ? (v ? 1 : 0) : 2; + return ret; + }; + imports.wbg.__wbindgen_number_get = function(arg0, arg1) { + const obj = getObject(arg1); + const ret = typeof(obj) === 'number' ? obj : undefined; + getFloat64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? 0 : ret; + getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret); + }; + imports.wbg.__wbindgen_number_new = function(arg0) { + const ret = arg0; + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_string_new = function(arg0, arg1) { + const ret = getStringFromWasm0(arg0, arg1); + return addHeapObject(ret); + }; + imports.wbg.__wbg_getwithrefkey_5e6d9547403deab8 = function(arg0, arg1) { + const ret = getObject(arg0)[getObject(arg1)]; + return addHeapObject(ret); + }; + imports.wbg.__wbg_set_841ac57cff3d672b = function(arg0, arg1, arg2) { + getObject(arg0)[takeObject(arg1)] = takeObject(arg2); + }; + imports.wbg.__wbg_get_44be0491f933a435 = function(arg0, arg1) { + const ret = getObject(arg0)[arg1 >>> 0]; + return addHeapObject(ret); + }; + imports.wbg.__wbg_length_fff51ee6522a1a18 = function(arg0) { + const ret = getObject(arg0).length; + return ret; + }; + imports.wbg.__wbg_new_898a68150f225f2e = function() { + const ret = new Array(); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_is_function = function(arg0) { + const ret = typeof(getObject(arg0)) === 'function'; + return ret; + }; + imports.wbg.__wbg_next_526fc47e980da008 = function(arg0) { + const ret = getObject(arg0).next; + return addHeapObject(ret); + }; + imports.wbg.__wbg_next_ddb3312ca1c4e32a = function() { return handleError(function (arg0) { + const ret = getObject(arg0).next(); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_done_5c1f01fb660d73b5 = function(arg0) { + const ret = getObject(arg0).done; + return ret; + }; + imports.wbg.__wbg_value_1695675138684bd5 = function(arg0) { + const ret = getObject(arg0).value; + return addHeapObject(ret); + }; + imports.wbg.__wbg_iterator_97f0c81209c6c35a = function() { + const ret = Symbol.iterator; + return addHeapObject(ret); + }; + imports.wbg.__wbg_get_97b561fb56f034b5 = function() { return handleError(function (arg0, arg1) { + const ret = Reflect.get(getObject(arg0), getObject(arg1)); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_call_cb65541d95d71282 = function() { return handleError(function (arg0, arg1) { + const ret = getObject(arg0).call(getObject(arg1)); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_new_b51585de1b234aff = function() { + const ret = new Object(); + return addHeapObject(ret); + }; + imports.wbg.__wbg_set_502d29070ea18557 = function(arg0, arg1, arg2) { + getObject(arg0)[arg1 >>> 0] = takeObject(arg2); + }; + imports.wbg.__wbg_isArray_4c24b343cb13cfb1 = function(arg0) { + const ret = Array.isArray(getObject(arg0)); + return ret; + }; + imports.wbg.__wbg_instanceof_ArrayBuffer_39ac22089b74fddb = function(arg0) { + let result; + try { + result = getObject(arg0) instanceof ArrayBuffer; + } catch { + result = false; + } + const ret = result; + return ret; + }; + imports.wbg.__wbg_isSafeInteger_bb8e18dd21c97288 = function(arg0) { + const ret = Number.isSafeInteger(getObject(arg0)); + return ret; + }; + imports.wbg.__wbg_buffer_085ec1f694018c4f = function(arg0) { + const ret = getObject(arg0).buffer; + return addHeapObject(ret); + }; + imports.wbg.__wbg_new_8125e318e6245eed = function(arg0) { + const ret = new Uint8Array(getObject(arg0)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_set_5cf90238115182c3 = function(arg0, arg1, arg2) { + getObject(arg0).set(getObject(arg1), arg2 >>> 0); + }; + imports.wbg.__wbg_length_72e2208bbc0efc61 = function(arg0) { + const ret = getObject(arg0).length; + return ret; + }; + imports.wbg.__wbg_instanceof_Uint8Array_d8d9cb2b8e8ac1d4 = function(arg0) { + let result; + try { + result = getObject(arg0) instanceof Uint8Array; + } catch { + result = false; + } + const ret = result; + return ret; + }; + imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { + const ret = debugString(getObject(arg1)); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len1; + getInt32Memory0()[arg0 / 4 + 0] = ptr1; + }; + imports.wbg.__wbindgen_throw = function(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); + }; + imports.wbg.__wbindgen_memory = function() { + const ret = wasm.memory; + return addHeapObject(ret); + }; + + return imports; +} + +function __wbg_init_memory(imports, maybe_memory) { + +} + +function __wbg_finalize_init(instance, module) { + wasm = instance.exports; + __wbg_init.__wbindgen_wasm_module = module; + cachedFloat64Memory0 = null; + cachedInt32Memory0 = null; + cachedUint8Memory0 = null; + + + return wasm; +} + +function initSync(module) { + if (wasm !== undefined) return wasm; + + const imports = __wbg_get_imports(); + + __wbg_init_memory(imports); + + if (!(module instanceof WebAssembly.Module)) { + module = new WebAssembly.Module(module); + } + + const instance = new WebAssembly.Instance(module, imports); + + return __wbg_finalize_init(instance, module); +} + +async function __wbg_init(input) { + if (wasm !== undefined) return wasm; + + if (typeof input === 'undefined') { + input = new URL('system_query_bg.wasm', import.meta.url); + } + const imports = __wbg_get_imports(); + + if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) { + input = fetch(input); + } + + __wbg_init_memory(imports); + + const { instance, module } = await __wbg_load(await input, imports); + + return __wbg_finalize_init(instance, module); +} + +export { initSync } +export default __wbg_init; diff --git a/packages/system-query/pkg/system_query_bg.wasm b/packages/system-query/pkg/system_query_bg.wasm new file mode 100644 index 0000000000000000000000000000000000000000..a01a0ca886b0f301fb5e3330e4afcdffbc850b31 GIT binary patch literal 83956 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 literal 0 HcmV?d00001 diff --git a/packages/system-query/pkg/system_query_bg.wasm.d.ts b/packages/system-query/pkg/system_query_bg.wasm.d.ts new file mode 100644 index 00000000..de6a2575 --- /dev/null +++ b/packages/system-query/pkg/system_query_bg.wasm.d.ts @@ -0,0 +1,9 @@ +/* tslint:disable */ +/* eslint-disable */ +export const memory: WebAssembly.Memory; +export function diff_filters(a: number, b: number, c: number): void; +export function expand_filter(a: number, b: number): void; +export function __wbindgen_malloc(a: number, b: number): number; +export function __wbindgen_realloc(a: number, b: number, c: number, d: number): number; +export function __wbindgen_add_to_stack_pointer(a: number): number; +export function __wbindgen_exn_store(a: number): void; -- 2.45.2 From 28b98864a6d79d409dec5c4c7fb3694035624051 Mon Sep 17 00:00:00 2001 From: Kieran Date: Wed, 6 Sep 2023 14:05:44 +0100 Subject: [PATCH 05/14] Remove build command --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 74c6e9a0..ec436ed4 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,9 @@ "packages/*" ], "scripts": { - "build": "yarn workspace @snort/system-query build && yarn workspace @snort/shared build && yarn workspace @snort/system build && yarn workspace @snort/system-react build && yarn workspace @snort/app build", - "start": "arn workspace @snort/system-query build && yarn workspace @snort/shared build && yarn workspace @snort/system build && yarn workspace @snort/system-react build && yarn workspace @snort/app start", - "test": "arn workspace @snort/system-query build && yarn workspace @snort/shared build && yarn workspace @snort/system build && yarn workspace @snort/app test && yarn workspace @snort/system test" + "build": "yarn workspace @snort/shared build && yarn workspace @snort/system build && yarn workspace @snort/system-react build && yarn workspace @snort/app build", + "start": "yarn workspace @snort/shared build && yarn workspace @snort/system build && yarn workspace @snort/system-react build && yarn workspace @snort/app start", + "test": "yarn workspace @snort/shared build && yarn workspace @snort/system build && yarn workspace @snort/app test && yarn workspace @snort/system test" }, "devDependencies": { "@cloudflare/workers-types": "^4.20230307.0", -- 2.45.2 From 08bc4cafa90b41e8cfed0ae520b6498e9eb2107f Mon Sep 17 00:00:00 2001 From: Kieran Date: Wed, 6 Sep 2023 14:27:59 +0100 Subject: [PATCH 06/14] Optimize note reaction --- packages/app/src/Element/NoteReaction.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/app/src/Element/NoteReaction.tsx b/packages/app/src/Element/NoteReaction.tsx index 66f19abb..b639b951 100644 --- a/packages/app/src/Element/NoteReaction.tsx +++ b/packages/app/src/Element/NoteReaction.tsx @@ -10,6 +10,7 @@ import useModeration from "Hooks/useModeration"; import { FormattedMessage } from "react-intl"; import Icon from "Icons/Icon"; import { useUserProfile } from "@snort/system-react"; +import { useInView } from "react-intersection-observer"; export interface NoteReactionProps { data: TaggedNostrEvent; @@ -19,7 +20,8 @@ export interface NoteReactionProps { export default function NoteReaction(props: NoteReactionProps) { const { data: ev } = props; const { isMuted } = useModeration(); - const profile = useUserProfile(ev.pubkey); + const { inView, ref } = useInView({ triggerOnce: true }); + const profile = useUserProfile(inView ? ev.pubkey : ""); const refEvent = useMemo(() => { if (ev) { @@ -44,6 +46,7 @@ export default function NoteReaction(props: NoteReactionProps) { * Some clients embed the reposted note in the content */ function extractRoot() { + if(!inView) return null; if (ev?.kind === EventKind.Repost && ev.content.length > 0 && ev.content !== "#[0]") { try { const r: NostrEvent = JSON.parse(ev.content); @@ -60,7 +63,11 @@ export default function NoteReaction(props: NoteReactionProps) { return props.root; } - const root = extractRoot(); + const root = useMemo(() => extractRoot(), [ev, props.root, inView]); + + if (!inView) { + return (
) + } const isOpMuted = root && isMuted(root.pubkey); const shouldNotBeRendered = isOpMuted || root?.kind !== EventKind.TextNote; const opt = { -- 2.45.2 From 3e0fae2c3355512f1cb40220c1ec06e4b9227a5d Mon Sep 17 00:00:00 2001 From: Kieran Date: Fri, 8 Sep 2023 17:25:36 +0100 Subject: [PATCH 07/14] Merger --- packages/system-query/.gitignore | 3 +- packages/system-query/Cargo.lock | 84 +++ packages/system-query/Cargo.toml | 2 + packages/system-query/pkg/system_query.d.ts | 7 + packages/system-query/pkg/system_query.js | 21 + .../system-query/pkg/system_query_bg.wasm | Bin 83956 -> 89560 bytes .../pkg/system_query_bg.wasm.d.ts | 1 + packages/system-query/src/diff.rs | 176 +++--- packages/system-query/src/expand.rs | 323 +++++++++-- packages/system-query/src/lib.rs | 136 ++++- packages/system-query/src/merge.rs | 512 ++++++++++++++++++ packages/system-query/system-query.iml | 12 + packages/system/src/nostr-system.ts | 2 +- packages/system/src/query.ts | 10 +- packages/system/src/request-builder.ts | 15 +- 15 files changed, 1156 insertions(+), 148 deletions(-) create mode 100644 packages/system-query/src/merge.rs create mode 100644 packages/system-query/system-query.iml diff --git a/packages/system-query/.gitignore b/packages/system-query/.gitignore index e673575a..d6c6ec7e 100644 --- a/packages/system-query/.gitignore +++ b/packages/system-query/.gitignore @@ -1,2 +1,3 @@ .idea/ -target/ \ No newline at end of file +target/ +*.txt \ No newline at end of file diff --git a/packages/system-query/Cargo.lock b/packages/system-query/Cargo.lock index 036190d1..c1a9a84a 100644 --- a/packages/system-query/Cargo.lock +++ b/packages/system-query/Cargo.lock @@ -30,6 +30,17 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "itertools" version = "0.11.0" @@ -39,6 +50,12 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + [[package]] name = "js-sys" version = "0.3.64" @@ -48,6 +65,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + [[package]] name = "log" version = "0.4.20" @@ -60,6 +83,12 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro2" version = "1.0.66" @@ -78,6 +107,42 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + [[package]] name = "scoped-tls" version = "1.0.1" @@ -115,6 +180,17 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "syn" version = "2.0.31" @@ -131,8 +207,10 @@ name = "system-query" version = "0.1.0" dependencies = [ "itertools", + "rand", "serde", "serde-wasm-bindgen", + "serde_json", "wasm-bindgen", "wasm-bindgen-test", ] @@ -143,6 +221,12 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "wasm-bindgen" version = "0.2.87" diff --git a/packages/system-query/Cargo.toml b/packages/system-query/Cargo.toml index 0ced6b86..17f087db 100644 --- a/packages/system-query/Cargo.toml +++ b/packages/system-query/Cargo.toml @@ -9,9 +9,11 @@ crate-type = ["cdylib"] [dependencies] itertools = "0.11.0" +rand = "0.8.5" serde = { version = "1.0.188", features = ["derive"] } serde-wasm-bindgen = "0.5.0" wasm-bindgen = "0.2.87" +serde_json = "1.0.105" [dev-dependencies] wasm-bindgen-test = "0.3.37" diff --git a/packages/system-query/pkg/system_query.d.ts b/packages/system-query/pkg/system_query.d.ts index 3935fee6..1b244910 100644 --- a/packages/system-query/pkg/system_query.d.ts +++ b/packages/system-query/pkg/system_query.d.ts @@ -11,6 +11,12 @@ export function diff_filters(prev: any, next: any): any; * @returns {any} */ export function expand_filter(val: any): any; +/** +* @param {any} prev +* @param {any} next +* @returns {any} +*/ +export function get_diff(prev: any, next: any): any; export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; @@ -18,6 +24,7 @@ export interface InitOutput { readonly memory: WebAssembly.Memory; readonly diff_filters: (a: number, b: number, c: number) => void; readonly expand_filter: (a: number, b: number) => void; + readonly get_diff: (a: number, b: number, c: number) => void; readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_add_to_stack_pointer: (a: number) => number; diff --git a/packages/system-query/pkg/system_query.js b/packages/system-query/pkg/system_query.js index 1b85b0e1..2cb5f0aa 100644 --- a/packages/system-query/pkg/system_query.js +++ b/packages/system-query/pkg/system_query.js @@ -229,6 +229,27 @@ export function expand_filter(val) { } } +/** +* @param {any} prev +* @param {any} next +* @returns {any} +*/ +export function get_diff(prev, next) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.get_diff(retptr, addHeapObject(prev), addHeapObject(next)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +} + function handleError(f, args) { try { return f.apply(this, args); diff --git a/packages/system-query/pkg/system_query_bg.wasm b/packages/system-query/pkg/system_query_bg.wasm index a01a0ca886b0f301fb5e3330e4afcdffbc850b31..1a585083228ae3cf7c94f2dfd83a96e351b62f94 100644 GIT binary patch delta 35689 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 Ee^#u-8vp, next: &Vec) -> Vec:: { - let mut added: Vec:: = vec![]; +pub fn diff_filter(prev: &Vec, next: &Vec) -> Vec { + let mut added: Vec = vec![]; for n in next.iter() { if !prev.iter().contains(&n) { @@ -19,38 +19,34 @@ mod tests { #[test] fn simple_diff_same() { - let prev = vec![ - FlatReqFilter { - id: Some("a".to_owned()), - author: None, - kind: None, - e_tag: None, - p_tag: None, - t_tag: None, - d_tag: None, - r_tag: None, - search: None, - since: None, - until: None, - limit: None, - } - ]; - let next = vec![ - FlatReqFilter { - id: Some("a".to_owned()), - author: None, - kind: None, - e_tag: None, - p_tag: None, - t_tag: None, - d_tag: None, - r_tag: None, - search: None, - since: None, - until: None, - limit: None, - } - ]; + let prev = vec![FlatReqFilter { + id: Some("a".to_owned()), + author: None, + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }]; + let next = vec![FlatReqFilter { + id: Some("a".to_owned()), + author: None, + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }]; let result = diff_filter(&prev, &next); assert_eq!(result, vec![]) @@ -58,22 +54,20 @@ mod tests { #[test] fn simple_diff_add() { - let prev = vec![ - FlatReqFilter { - id: Some("a".to_owned()), - author: None, - kind: None, - e_tag: None, - p_tag: None, - t_tag: None, - d_tag: None, - r_tag: None, - search: None, - since: None, - until: None, - limit: None, - } - ]; + let prev = vec![FlatReqFilter { + id: Some("a".to_owned()), + author: None, + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }]; let next = vec![ FlatReqFilter { id: Some("a".to_owned()), @@ -106,8 +100,9 @@ mod tests { ]; let result = diff_filter(&prev, &next); - assert_eq!(result, vec![ - FlatReqFilter { + assert_eq!( + result, + vec![FlatReqFilter { id: Some("b".to_owned()), author: None, kind: None, @@ -120,48 +115,45 @@ mod tests { since: None, until: None, limit: None, - } - ]) + }] + ) } #[test] fn simple_diff_replace() { - let prev = vec![ - FlatReqFilter { - id: Some("a".to_owned()), - author: None, - kind: None, - e_tag: None, - p_tag: None, - t_tag: None, - d_tag: None, - r_tag: None, - search: None, - since: None, - until: None, - limit: None, - } - ]; - let next = vec![ - FlatReqFilter { - id: Some("b".to_owned()), - author: None, - kind: None, - e_tag: None, - p_tag: None, - t_tag: None, - d_tag: None, - r_tag: None, - search: None, - since: None, - until: None, - limit: None, - }, - ]; + let prev = vec![FlatReqFilter { + id: Some("a".to_owned()), + author: None, + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }]; + let next = vec![FlatReqFilter { + id: Some("b".to_owned()), + author: None, + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }]; let result = diff_filter(&prev, &next); - assert_eq!(result, vec![ - FlatReqFilter { + assert_eq!( + result, + vec![FlatReqFilter { id: Some("b".to_owned()), author: None, kind: None, @@ -174,7 +166,7 @@ mod tests { since: None, until: None, limit: None, - } - ]) + }] + ) } -} \ No newline at end of file +} diff --git a/packages/system-query/src/expand.rs b/packages/system-query/src/expand.rs index ea619c12..46eb37f3 100644 --- a/packages/system-query/src/expand.rs +++ b/packages/system-query/src/expand.rs @@ -1,5 +1,5 @@ -use itertools::Itertools; use crate::{FlatReqFilter, ReqFilter}; +use itertools::Itertools; #[derive(Clone)] enum StringOrNumberEntry<'a> { @@ -12,39 +12,66 @@ pub fn expand_filter(filter: &ReqFilter) -> Vec { let mut inputs: Vec> = vec![]; if let Some(ids) = &filter.ids { - let t_ids = ids.iter().map(|z| StringOrNumberEntry::String(("id", z))).collect_vec(); + let t_ids = ids + .iter() + .map(|z| StringOrNumberEntry::String(("id", z))) + .collect_vec(); inputs.push(t_ids); } if let Some(authors) = &filter.authors { - let t_ids = authors.iter().map(|z| StringOrNumberEntry::String(("author", z))).collect_vec(); + let t_ids = authors + .iter() + .map(|z| StringOrNumberEntry::String(("author", z))) + .collect_vec(); inputs.push(t_ids); } if let Some(kinds) = &filter.kinds { - let t_ids = kinds.iter().map(|z| StringOrNumberEntry::Number(("kind", z))).collect_vec(); + let t_ids = kinds + .iter() + .map(|z| StringOrNumberEntry::Number(("kind", z))) + .collect_vec(); inputs.push(t_ids); } if let Some(e_tags) = &filter.e_tag { - let t_ids = e_tags.iter().map(|z| StringOrNumberEntry::String(("e_tag", z))).collect_vec(); + let t_ids = e_tags + .iter() + .map(|z| StringOrNumberEntry::String(("e_tag", z))) + .collect_vec(); inputs.push(t_ids); } if let Some(p_tags) = &filter.p_tag { - let t_ids = p_tags.iter().map(|z| StringOrNumberEntry::String(("p_tag", z))).collect_vec(); + let t_ids = p_tags + .iter() + .map(|z| StringOrNumberEntry::String(("p_tag", z))) + .collect_vec(); inputs.push(t_ids); } if let Some(d_tags) = &filter.d_tag { - let t_ids = d_tags.iter().map(|z| StringOrNumberEntry::String(("d_tag", z))).collect_vec(); + let t_ids = d_tags + .iter() + .map(|z| StringOrNumberEntry::String(("d_tag", z))) + .collect_vec(); inputs.push(t_ids); } if let Some(t_tags) = &filter.t_tag { - let t_ids = t_tags.iter().map(|z| StringOrNumberEntry::String(("t_tag", z))).collect_vec(); + let t_ids = t_tags + .iter() + .map(|z| StringOrNumberEntry::String(("t_tag", z))) + .collect_vec(); inputs.push(t_ids); } if let Some(r_tags) = &filter.r_tag { - let t_ids = r_tags.iter().map(|z| StringOrNumberEntry::String(("r_tag", z))).collect_vec(); + let t_ids = r_tags + .iter() + .map(|z| StringOrNumberEntry::String(("r_tag", z))) + .collect_vec(); inputs.push(t_ids); } if let Some(search) = &filter.search { - let t_ids = search.iter().map(|z| StringOrNumberEntry::String(("search", z))).collect_vec(); + let t_ids = search + .iter() + .map(|z| StringOrNumberEntry::String(("search", z))) + .collect_vec(); inputs.push(t_ids); } @@ -155,28 +182,260 @@ mod tests { let output = expand_filter(&input); output.iter().take(5).for_each(|x| println!("{:?}", x)); let expected = vec![ - FlatReqFilter { author: Some("a".to_owned()), kind: Some(1), id: Some("x".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, - FlatReqFilter { author: Some("a".to_owned()), kind: Some(1), id: Some("y".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, - FlatReqFilter { author: Some("a".to_owned()), kind: Some(2), id: Some("x".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, - FlatReqFilter { author: Some("a".to_owned()), kind: Some(2), id: Some("y".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, - FlatReqFilter { author: Some("a".to_owned()), kind: Some(3), id: Some("x".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, - FlatReqFilter { author: Some("a".to_owned()), kind: Some(3), id: Some("y".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, - FlatReqFilter { author: Some("b".to_owned()), kind: Some(1), id: Some("x".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, - FlatReqFilter { author: Some("b".to_owned()), kind: Some(1), id: Some("y".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, - FlatReqFilter { author: Some("b".to_owned()), kind: Some(2), id: Some("x".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, - FlatReqFilter { author: Some("b".to_owned()), kind: Some(2), id: Some("y".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, - FlatReqFilter { author: Some("b".to_owned()), kind: Some(3), id: Some("x".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, - FlatReqFilter { author: Some("b".to_owned()), kind: Some(3), id: Some("y".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, - FlatReqFilter { author: Some("c".to_owned()), kind: Some(1), id: Some("x".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, - FlatReqFilter { author: Some("c".to_owned()), kind: Some(1), id: Some("y".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, - FlatReqFilter { author: Some("c".to_owned()), kind: Some(2), id: Some("x".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, - FlatReqFilter { author: Some("c".to_owned()), kind: Some(2), id: Some("y".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, - FlatReqFilter { author: Some("c".to_owned()), kind: Some(3), id: Some("x".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, - FlatReqFilter { author: Some("c".to_owned()), kind: Some(3), id: Some("y".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, + FlatReqFilter { + author: Some("a".to_owned()), + kind: Some(1), + id: Some("x".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("a".to_owned()), + kind: Some(1), + id: Some("y".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("a".to_owned()), + kind: Some(2), + id: Some("x".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("a".to_owned()), + kind: Some(2), + id: Some("y".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("a".to_owned()), + kind: Some(3), + id: Some("x".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("a".to_owned()), + kind: Some(3), + id: Some("y".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("b".to_owned()), + kind: Some(1), + id: Some("x".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("b".to_owned()), + kind: Some(1), + id: Some("y".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("b".to_owned()), + kind: Some(2), + id: Some("x".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("b".to_owned()), + kind: Some(2), + id: Some("y".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("b".to_owned()), + kind: Some(3), + id: Some("x".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("b".to_owned()), + kind: Some(3), + id: Some("y".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("c".to_owned()), + kind: Some(1), + id: Some("x".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("c".to_owned()), + kind: Some(1), + id: Some("y".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("c".to_owned()), + kind: Some(2), + id: Some("x".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("c".to_owned()), + kind: Some(2), + id: Some("y".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("c".to_owned()), + kind: Some(3), + id: Some("x".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("c".to_owned()), + kind: Some(3), + id: Some("y".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, ]; assert_eq!(output.len(), expected.len()); - output - .iter() - .for_each(|a| assert!(expected.contains(a))); + output.iter().for_each(|a| assert!(expected.contains(a))); } -} \ No newline at end of file +} diff --git a/packages/system-query/src/lib.rs b/packages/system-query/src/lib.rs index bcdc5f46..75a8f131 100644 --- a/packages/system-query/src/lib.rs +++ b/packages/system-query/src/lib.rs @@ -1,12 +1,12 @@ -extern crate wasm_bindgen; - +use std::fmt::{Debug}; use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::*; -mod expand; mod diff; +mod expand; +mod merge; -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[derive(PartialEq, Clone, Serialize, Deserialize)] pub struct ReqFilter { #[serde(rename = "ids", skip_serializing_if = "Option::is_none")] pub ids: Option>, @@ -34,7 +34,13 @@ pub struct ReqFilter { pub limit: Option, } -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +impl Debug for ReqFilter { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&serde_json::to_string(self).unwrap().to_owned()) + } +} + +#[derive(PartialEq, Clone, Serialize, Deserialize)] pub struct FlatReqFilter { #[serde(rename = "ids", skip_serializing_if = "Option::is_none")] id: Option, @@ -62,6 +68,12 @@ pub struct FlatReqFilter { limit: Option, } +impl Debug for FlatReqFilter { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&serde_json::to_string(self).unwrap().to_owned()) + } +} + #[wasm_bindgen] pub fn diff_filters(prev: JsValue, next: JsValue) -> Result { let prev_parsed: Vec = serde_wasm_bindgen::from_value(prev)?; @@ -75,4 +87,116 @@ pub fn expand_filter(val: JsValue) -> Result { let parsed: ReqFilter = serde_wasm_bindgen::from_value(val)?; let result = expand::expand_filter(&parsed); Ok(serde_wasm_bindgen::to_value(&result)?) -} \ No newline at end of file +} + +#[wasm_bindgen] +pub fn get_diff(prev: JsValue, next: JsValue) -> Result { + let prev_parsed: Vec = serde_wasm_bindgen::from_value(prev)?; + let next_parsed: Vec = serde_wasm_bindgen::from_value(next)?; + let expanded_prev: Vec = prev_parsed + .iter() + .flat_map(|v| expand::expand_filter(v)) + .collect(); + let expanded_next: Vec = next_parsed + .iter() + .flat_map(|v| expand::expand_filter(v)) + .collect(); + let result = diff::diff_filter(&expanded_prev, &expanded_next); + Ok(serde_wasm_bindgen::to_value(&result)?) +} + +#[wasm_bindgen] +pub fn flat_merge(val: JsValue) -> Result { + let val_parsed: Vec = serde_wasm_bindgen::from_value(val)?; + let result = merge::flat_merge(&val_parsed); + Ok(serde_wasm_bindgen::to_value(&result)?) +} + +#[cfg(test)] +mod tests { + use super::*; + use itertools::Itertools; + use std::cmp::Ordering; + + #[test] + fn flat_merge_expanded() { + let input = vec![ + ReqFilter { + ids: None, + kinds: Some(vec![1, 6969, 6]), + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + authors: Some(vec![ + "kieran".to_string(), + "snort".to_string(), + "c".to_string(), + "d".to_string(), + "e".to_string(), + ]), + since: Some(1), + until: Some(100), + search: None, + limit: None, + }, + ReqFilter { + ids: None, + kinds: Some(vec![4]), + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + authors: Some(vec!["kieran".to_string()]), + limit: None, + }, + ReqFilter { + ids: None, + authors: None, + kinds: Some(vec![4]), + e_tag: None, + p_tag: Some(vec!["kieran".to_string()]), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }, + ReqFilter { + ids: None, + kinds: Some(vec![1000]), + authors: Some(vec!["snort".to_string()]), + p_tag: Some(vec!["kieran".to_string()]), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + e_tag: None, + limit: None, + }, + ]; + + let expanded = input + .iter() + .flat_map(|v| expand::expand_filter(v)) + .sorted_by(|_, _| { + if rand::random() { + Ordering::Less + } else { + Ordering::Greater + } + }) + .collect_vec(); + let expanded_flat = merge::flat_merge(&expanded); + assert_eq!(expanded_flat, input); + } +} diff --git a/packages/system-query/src/merge.rs b/packages/system-query/src/merge.rs new file mode 100644 index 00000000..47a8ba81 --- /dev/null +++ b/packages/system-query/src/merge.rs @@ -0,0 +1,512 @@ +use crate::{FlatReqFilter, ReqFilter}; +use itertools::Itertools; +use std::cmp::Ordering; + +pub fn flat_merge(all: &Vec) -> Vec { + let mut ret: Vec = vec![]; + + let merge_sets: Vec> = vec![vec![all.first().unwrap()]]; + let merge_sets = all + .iter() + .skip(1) + .sorted_by(|a, b| match distance(&a, &b) { + 0 => Ordering::Equal, + 1 => Ordering::Less, + _ => Ordering::Greater, + }) + .fold(merge_sets, |mut acc, x| { + let mut did_match = false; + for y in acc.iter_mut() { + if y.iter().all(|z| can_merge_filters(z, x)) { + y.push(x); + did_match = true; + break; + } + } + if !did_match { + acc.push(vec![x]); + } + acc + }); + + for s in merge_sets.iter() { + ret.push(merge_set(s)); + } + ret +} + +fn merge_set(set: &Vec<&FlatReqFilter>) -> ReqFilter { + let ret = ReqFilter { + ids: None, + authors: None, + kinds: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }; + set.iter().fold(ret, |mut acc, x| { + array_prop_append(&x.id, &mut acc.ids); + array_prop_append(&x.author, &mut acc.authors); + array_prop_append(&x.kind, &mut acc.kinds); + array_prop_append(&x.e_tag, &mut acc.e_tag); + array_prop_append(&x.p_tag, &mut acc.p_tag); + array_prop_append(&x.t_tag, &mut acc.t_tag); + array_prop_append(&x.d_tag, &mut acc.d_tag); + array_prop_append(&x.r_tag, &mut acc.r_tag); + array_prop_append(&x.search, &mut acc.search); + acc.since = x.since; + acc.until = x.until; + acc.limit = x.limit; + + acc + }) +} + +fn can_merge_filters(a: &FlatReqFilter, b: &FlatReqFilter) -> bool { + if a.since != b.since || a.until != b.until || a.limit != b.limit || a.search != b.search { + return false; + } + + distance(a, b) <= 1 +} + +/// Calculate the distance in terms of similarity for merging +/// +/// The goal of this function is to find 2 filters which are very similar where +/// one filter may have a single property change like so: +/// +/// ```javascript +/// const a = { "kinds": 1, "authors": "a", "since": 99 }; +/// const b = { "kinds": 1, "authors": "b", "since": 99 }; +/// ``` +/// In this case these 2 filters could be merged because their distance is `1` +/// ```javascript +/// const result = { "kinds": [1], "authors": ["a", "b"], "since": 99 }; +/// ``` +fn distance(a: &FlatReqFilter, b: &FlatReqFilter) -> u32 { + let mut ret = 0u32; + + ret += prop_dist(&a.id, &b.id); + ret += prop_dist(&a.kind, &b.kind); + ret += prop_dist(&a.author, &b.author); + ret += prop_dist(&a.e_tag, &b.e_tag); + ret += prop_dist(&a.p_tag, &b.p_tag); + ret += prop_dist(&a.d_tag, &b.d_tag); + ret += prop_dist(&a.r_tag, &b.r_tag); + ret += prop_dist(&a.t_tag, &b.t_tag); + ret += prop_dist(&a.search, &b.search); + + ret +} + +#[inline(always)] +fn prop_dist(a: &Option, b: &Option) -> u32 { + if (a.is_some() && b.is_none()) || (a.is_none() && b.is_some()) { + return 10; + } else if a.is_some() && a != b { + return 1; + } + 0 +} + +#[inline(always)] +fn array_prop_append(val: &Option, arr: &mut Option>) { + if let Some(ap) = val { + if arr.is_none() { + *arr = Some(vec![ap.clone()]) + } else if !arr.as_ref().unwrap().contains(ap) { + arr.as_mut().unwrap().push(ap.clone()); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn distance() { + let a = FlatReqFilter { + id: Some("a".to_owned()), + author: None, + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }; + let b = FlatReqFilter { + id: Some("a".to_owned()), + author: None, + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }; + let c = FlatReqFilter { + id: Some("c".to_owned()), + author: None, + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }; + let d = FlatReqFilter { + id: Some("a".to_owned()), + author: None, + kind: Some(1), + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }; + let e = FlatReqFilter { + id: Some("e".to_owned()), + author: None, + kind: Some(1), + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }; + assert_eq!(super::distance(&a, &b), 0); + assert_eq!(super::distance(&a, &c), 1); + assert_eq!(super::distance(&a, &d), 10); + assert_eq!(super::distance(&a, &e), 11); + } + + #[test] + fn merge_set() { + let a = FlatReqFilter { + id: Some("0".to_owned()), + author: Some("a".to_owned()), + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: Some(10), + }; + let b = FlatReqFilter { + id: Some("0".to_owned()), + author: Some("b".to_owned()), + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: Some(10), + }; + + let output = ReqFilter { + ids: Some(vec!["0".to_owned()]), + authors: Some(vec!["a".to_owned(), "b".to_owned()]), + kinds: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: Some(10), + }; + assert_eq!(super::merge_set(&vec![&a, &b]), output); + } + + #[test] + fn can_merge_filters() { + let a = FlatReqFilter { + id: Some("0".to_owned()), + author: Some("a".to_owned()), + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: Some(10), + }; + let b = FlatReqFilter { + id: Some("0".to_owned()), + author: Some("b".to_owned()), + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: Some(10), + }; + let c = FlatReqFilter { + id: Some("0".to_owned()), + author: Some("b".to_owned()), + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: Some(100), + }; + assert!(super::can_merge_filters(&a, &b)); + assert!(!super::can_merge_filters(&b, &c)); + } + + #[test] + fn flat_merge() { + let input = vec![ + FlatReqFilter { + id: Some("0".to_owned()), + author: Some("a".to_owned()), + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }, + FlatReqFilter { + id: Some("0".to_owned()), + author: Some("b".to_owned()), + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }, + FlatReqFilter { + id: None, + author: None, + kind: Some(1), + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }, + FlatReqFilter { + id: None, + author: None, + kind: Some(2), + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }, + FlatReqFilter { + id: None, + author: None, + kind: Some(2), + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }, + FlatReqFilter { + id: Some("0".to_owned()), + author: Some("c".to_owned()), + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }, + FlatReqFilter { + id: None, + author: Some("c".to_owned()), + kind: Some(1), + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }, + FlatReqFilter { + id: None, + author: Some("c".to_owned()), + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: Some(100), + }, + FlatReqFilter { + id: Some("1".to_owned()), + author: Some("c".to_owned()), + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }, + ]; + let output = vec![ + ReqFilter { + ids: Some(vec!["0".to_owned()]), + authors: Some(vec!["a".to_owned(), "b".to_owned(), "c".to_owned()]), + kinds: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }, + ReqFilter { + ids: None, + authors: None, + kinds: Some(vec![1, 2]), + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }, + ReqFilter { + ids: None, + authors: Some(vec!["c".to_owned()]), + kinds: Some(vec![1]), + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }, + ReqFilter { + ids: None, + authors: Some(vec!["c".to_owned()]), + kinds: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: Some(100), + }, + ReqFilter { + ids: Some(vec!["1".to_owned()]), + authors: Some(vec!["c".to_owned()]), + kinds: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }, + ]; + + assert_eq!(super::flat_merge(&input), output) + } +} diff --git a/packages/system-query/system-query.iml b/packages/system-query/system-query.iml new file mode 100644 index 00000000..2fecef3b --- /dev/null +++ b/packages/system-query/system-query.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/system/src/nostr-system.ts b/packages/system/src/nostr-system.ts index b0786757..90f8ecf6 100644 --- a/packages/system/src/nostr-system.ts +++ b/packages/system/src/nostr-system.ts @@ -241,7 +241,7 @@ export class NostrSystem extends ExternalStore implements System return existing; } const filters = !req.options?.skipDiff - ? req.buildDiff(this.#relayCache, existing.flatFilters) + ? req.buildDiff(this.#relayCache, existing.filters) : req.build(this.#relayCache); if (filters.length === 0 && !!req.options?.skipDiff) { return existing; diff --git a/packages/system/src/query.ts b/packages/system/src/query.ts index 0be31a46..065b0468 100644 --- a/packages/system/src/query.ts +++ b/packages/system/src/query.ts @@ -4,9 +4,7 @@ import { unixNowMs, unwrap } from "@snort/shared"; import { Connection, ReqFilter, Nips, TaggedNostrEvent } from "."; import { NoteStore } from "./note-collection"; -import { flatMerge } from "./request-merger"; import { BuiltRawReqFilter } from "./request-builder"; -import { FlatReqFilter, expandFilter } from "./request-expander"; import { eventMatchesFilter } from "./request-matcher"; /** @@ -19,7 +17,6 @@ class QueryTrace { eose?: number; close?: number; #wasForceClosed = false; - readonly flatFilters: Array; readonly #fnClose: (id: string) => void; readonly #fnProgress: () => void; @@ -34,7 +31,6 @@ class QueryTrace { this.start = unixNowMs(); this.#fnClose = fnClose; this.#fnProgress = fnProgress; - this.flatFilters = filters.flatMap(expandFilter); } sentToRelay() { @@ -166,11 +162,7 @@ export class Query implements QueryBase { * Recompute the complete set of compressed filters from all query traces */ get filters() { - return flatMerge(this.flatFilters); - } - - get flatFilters() { - return this.#tracing.flatMap(a => a.flatFilters); + return this.#tracing.flatMap(a => a.filters); } get feed() { diff --git a/packages/system/src/request-builder.ts b/packages/system/src/request-builder.ts index 747a97a4..311d41e5 100644 --- a/packages/system/src/request-builder.ts +++ b/packages/system/src/request-builder.ts @@ -1,12 +1,12 @@ import debug from "debug"; import { v4 as uuid } from "uuid"; import { appendDedupe, sanitizeRelayUrl, unixNowMs } from "@snort/shared"; +import { get_diff }from "@snort/system-query"; import { ReqFilter, u256, HexKey, EventKind } from "."; -import { diffFilters } from "./request-splitter"; import { RelayCache, splitByWriteRelays, splitFlatByWriteRelays } from "./gossip-model"; import { flatMerge, mergeSimilar } from "./request-merger"; -import { FlatReqFilter, expandFilter } from "./request-expander"; +import { FlatReqFilter } from "./request-expander"; /** * Which strategy is used when building REQ filters @@ -103,15 +103,16 @@ export class RequestBuilder { /** * Detects a change in request from a previous set of filters */ - buildDiff(relays: RelayCache, prev: Array): Array { + buildDiff(relays: RelayCache, prev: Array): Array { const start = unixNowMs(); - const next = this.#builders.flatMap(f => expandFilter(f.filter)); - const diff = diffFilters(prev, next); + //const next = this.#builders.flatMap(f => expandFilter(f.filter)); + //const diff = diffFilters(prev, next); + const diff = get_diff(prev, this.buildRaw()) as Array; const ts = unixNowMs() - start; this.#log("buildDiff %s %d ms", this.id, ts); - if (diff.changed) { - return splitFlatByWriteRelays(relays, diff.added).map(a => { + if (diff.length > 0) { + return splitFlatByWriteRelays(relays, diff).map(a => { return { strategy: RequestStrategy.AuthorsRelays, filters: flatMerge(a.filters), -- 2.45.2 From d67beb8dbfe430130544d8377237453faeccb3fd Mon Sep 17 00:00:00 2001 From: Kieran Date: Mon, 11 Sep 2023 14:27:11 +0100 Subject: [PATCH 08/14] Cache zap parser results --- packages/system/src/zaps.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/system/src/zaps.ts b/packages/system/src/zaps.ts index 57c3b355..ee22cb15 100644 --- a/packages/system/src/zaps.ts +++ b/packages/system/src/zaps.ts @@ -4,6 +4,8 @@ import { HexKey, NostrEvent } from "./nostr"; import { findTag } from "./utils"; import { MetadataCache } from "./cache"; +const ParsedZapCache = new Map(); + function getInvoice(zap: NostrEvent): InvoiceDetails | undefined { const bolt11 = findTag(zap, "bolt11"); if (!bolt11) { @@ -13,6 +15,11 @@ function getInvoice(zap: NostrEvent): InvoiceDetails | undefined { } export function parseZap(zapReceipt: NostrEvent, userCache: FeedCache, refNote?: NostrEvent): ParsedZap { + const existing = ParsedZapCache.get(zapReceipt.id); + if(existing) { + return existing; + } + let innerZapJson = findTag(zapReceipt, "description"); if (innerZapJson) { try { @@ -67,7 +74,7 @@ export function parseZap(zapReceipt: NostrEvent, userCache: FeedCache Date: Mon, 11 Sep 2023 14:27:39 +0100 Subject: [PATCH 09/14] flat_merge to rust --- packages/system-query/Cargo.toml | 4 +- packages/system-query/pkg/system_query.d.ts | 6 + packages/system-query/pkg/system_query.js | 20 ++ .../system-query/pkg/system_query_bg.wasm | Bin 89560 -> 118793 bytes .../pkg/system_query_bg.wasm.d.ts | 1 + packages/system-query/src/expand.rs | 13 +- packages/system-query/src/filter.rs | 287 ++++++++++++++++++ packages/system-query/src/lib.rs | 100 ++---- packages/system-query/src/merge.rs | 214 +++++-------- packages/system/src/nostr-system.ts | 8 +- packages/system/src/request-builder.ts | 4 +- 11 files changed, 423 insertions(+), 234 deletions(-) create mode 100644 packages/system-query/src/filter.rs diff --git a/packages/system-query/Cargo.toml b/packages/system-query/Cargo.toml index 17f087db..fb15f816 100644 --- a/packages/system-query/Cargo.toml +++ b/packages/system-query/Cargo.toml @@ -9,11 +9,11 @@ crate-type = ["cdylib"] [dependencies] itertools = "0.11.0" -rand = "0.8.5" serde = { version = "1.0.188", features = ["derive"] } serde-wasm-bindgen = "0.5.0" wasm-bindgen = "0.2.87" -serde_json = "1.0.105" [dev-dependencies] +rand = "0.8.5" wasm-bindgen-test = "0.3.37" +serde_json = "1.0.105" diff --git a/packages/system-query/pkg/system_query.d.ts b/packages/system-query/pkg/system_query.d.ts index 1b244910..4b24908a 100644 --- a/packages/system-query/pkg/system_query.d.ts +++ b/packages/system-query/pkg/system_query.d.ts @@ -17,6 +17,11 @@ export function expand_filter(val: any): any; * @returns {any} */ export function get_diff(prev: any, next: any): any; +/** +* @param {any} val +* @returns {any} +*/ +export function flat_merge(val: any): any; export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; @@ -25,6 +30,7 @@ export interface InitOutput { readonly diff_filters: (a: number, b: number, c: number) => void; readonly expand_filter: (a: number, b: number) => void; readonly get_diff: (a: number, b: number, c: number) => void; + readonly flat_merge: (a: number, b: number) => void; readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_add_to_stack_pointer: (a: number) => number; diff --git a/packages/system-query/pkg/system_query.js b/packages/system-query/pkg/system_query.js index 2cb5f0aa..9fb7deef 100644 --- a/packages/system-query/pkg/system_query.js +++ b/packages/system-query/pkg/system_query.js @@ -250,6 +250,26 @@ export function get_diff(prev, next) { } } +/** +* @param {any} val +* @returns {any} +*/ +export function flat_merge(val) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.flat_merge(retptr, addHeapObject(val)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +} + function handleError(f, args) { try { return f.apply(this, args); diff --git a/packages/system-query/pkg/system_query_bg.wasm b/packages/system-query/pkg/system_query_bg.wasm index 1a585083228ae3cf7c94f2dfd83a96e351b62f94..394d0f79785d2ed823f835286cfe364280ea8c71 100644 GIT binary patch literal 118793 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�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
  • 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 Pmhs*WFrK{F%J}~QB@!xO diff --git a/packages/system-query/pkg/system_query_bg.wasm.d.ts b/packages/system-query/pkg/system_query_bg.wasm.d.ts index ac811304..506a7865 100644 --- a/packages/system-query/pkg/system_query_bg.wasm.d.ts +++ b/packages/system-query/pkg/system_query_bg.wasm.d.ts @@ -4,6 +4,7 @@ export const memory: WebAssembly.Memory; export function diff_filters(a: number, b: number, c: number): void; export function expand_filter(a: number, b: number): void; export function get_diff(a: number, b: number, c: number): void; +export function flat_merge(a: number, b: number): void; export function __wbindgen_malloc(a: number, b: number): number; export function __wbindgen_realloc(a: number, b: number, c: number, d: number): number; export function __wbindgen_add_to_stack_pointer(a: number): number; diff --git a/packages/system-query/src/expand.rs b/packages/system-query/src/expand.rs index 46eb37f3..4871a0fe 100644 --- a/packages/system-query/src/expand.rs +++ b/packages/system-query/src/expand.rs @@ -161,14 +161,19 @@ pub fn expand_filter(filter: &ReqFilter) -> Vec { mod tests { use super::*; use crate::ReqFilter; + use std::collections::HashSet; #[test] fn test_expand_filter() { let input = ReqFilter { - authors: Some(vec!["a".to_owned(), "b".to_owned(), "c".to_owned()]), - kinds: Some(vec![1, 2, 3]), - ids: Some(vec!["x".to_owned(), "y".to_owned()]), - p_tag: Some(vec!["a".to_owned()]), + authors: Some(HashSet::from([ + "a".to_owned(), + "b".to_owned(), + "c".to_owned(), + ])), + kinds: Some(HashSet::from([1, 2, 3])), + ids: Some(HashSet::from(["x".to_owned(), "y".to_owned()])), + p_tag: Some(HashSet::from(["a".to_owned()])), t_tag: None, d_tag: None, r_tag: None, diff --git a/packages/system-query/src/filter.rs b/packages/system-query/src/filter.rs new file mode 100644 index 00000000..43f812ef --- /dev/null +++ b/packages/system-query/src/filter.rs @@ -0,0 +1,287 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashSet; +#[cfg(test)] +use std::fmt::Debug; +use std::hash::Hash; + +#[derive(PartialEq, Clone, Serialize, Deserialize)] +pub struct ReqFilter { + #[serde(rename = "ids", skip_serializing_if = "Option::is_none")] + pub ids: Option>, + #[serde(rename = "authors", skip_serializing_if = "Option::is_none")] + pub authors: Option>, + #[serde(rename = "kinds", skip_serializing_if = "Option::is_none")] + pub kinds: Option>, + #[serde(rename = "#e", skip_serializing_if = "Option::is_none")] + pub e_tag: Option>, + #[serde(rename = "#p", skip_serializing_if = "Option::is_none")] + pub p_tag: Option>, + #[serde(rename = "#t", skip_serializing_if = "Option::is_none")] + pub t_tag: Option>, + #[serde(rename = "#d", skip_serializing_if = "Option::is_none")] + pub d_tag: Option>, + #[serde(rename = "#r", skip_serializing_if = "Option::is_none")] + pub r_tag: Option>, + #[serde(rename = "search", skip_serializing_if = "Option::is_none")] + pub search: Option>, + #[serde(rename = "since", skip_serializing_if = "Option::is_none")] + pub since: Option, + #[serde(rename = "until", skip_serializing_if = "Option::is_none")] + pub until: Option, + #[serde(rename = "limit", skip_serializing_if = "Option::is_none")] + pub limit: Option, +} + +#[cfg(test)] +impl Debug for ReqFilter { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&serde_json::to_string(self).unwrap().to_owned()) + } +} + +#[derive(PartialEq, PartialOrd, Clone, Serialize, Deserialize)] +pub struct FlatReqFilter { + #[serde(rename = "ids", skip_serializing_if = "Option::is_none")] + pub id: Option, + #[serde(rename = "authors", skip_serializing_if = "Option::is_none")] + pub author: Option, + #[serde(rename = "kinds", skip_serializing_if = "Option::is_none")] + pub kind: Option, + #[serde(rename = "#e", skip_serializing_if = "Option::is_none")] + pub e_tag: Option, + #[serde(rename = "#p", skip_serializing_if = "Option::is_none")] + pub p_tag: Option, + #[serde(rename = "#t", skip_serializing_if = "Option::is_none")] + pub t_tag: Option, + #[serde(rename = "#d", skip_serializing_if = "Option::is_none")] + pub d_tag: Option, + #[serde(rename = "#r", skip_serializing_if = "Option::is_none")] + pub r_tag: Option, + #[serde(rename = "search", skip_serializing_if = "Option::is_none")] + pub search: Option, + #[serde(rename = "since", skip_serializing_if = "Option::is_none")] + pub since: Option, + #[serde(rename = "until", skip_serializing_if = "Option::is_none")] + pub until: Option, + #[serde(rename = "limit", skip_serializing_if = "Option::is_none")] + pub limit: Option, +} + +#[cfg(test)] +impl Debug for FlatReqFilter { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&serde_json::to_string(self).unwrap().to_owned()) + } +} + +pub trait Distance { + /// Calculate the distance in terms of similarity for merging + /// + /// The goal of this function is to find 2 filters which are very similar where + /// one filter may have a single property change like so: + /// + /// ```javascript + /// const a = { "kinds": 1, "authors": "a", "since": 99 }; + /// const b = { "kinds": 1, "authors": "b", "since": 99 }; + /// ``` + /// In this case these 2 filters could be merged because their distance is `1` + /// ```javascript + /// const result = { "kinds": [1], "authors": ["a", "b"], "since": 99 }; + /// ``` + fn distance(&self, other: &Self) -> u32; +} + +pub trait CanMerge { + fn can_merge(&self, other: &Self) -> bool; +} + +impl Distance for FlatReqFilter { + fn distance(&self, b: &Self) -> u32 { + let mut ret = 0u32; + + ret += prop_dist(&self.id, &b.id); + ret += prop_dist(&self.kind, &b.kind); + ret += prop_dist(&self.author, &b.author); + ret += prop_dist(&self.e_tag, &b.e_tag); + ret += prop_dist(&self.p_tag, &b.p_tag); + ret += prop_dist(&self.d_tag, &b.d_tag); + ret += prop_dist(&self.r_tag, &b.r_tag); + ret += prop_dist(&self.t_tag, &b.t_tag); + ret += prop_dist(&self.search, &b.search); + + ret + } +} + +impl CanMerge for FlatReqFilter { + fn can_merge(&self, other: &Self) -> bool { + if self.since != other.since + || self.until != other.until + || self.limit != other.limit + || self.search != other.search + { + return false; + } + + self.distance(other) <= 1 + } +} + +impl From> for ReqFilter { + fn from(value: Vec<&FlatReqFilter>) -> Self { + let ret = ReqFilter { + ids: None, + authors: None, + kinds: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }; + value.iter().fold(ret, |mut acc, x| { + array_prop_append(&x.id, &mut acc.ids); + array_prop_append(&x.author, &mut acc.authors); + array_prop_append(&x.kind, &mut acc.kinds); + array_prop_append(&x.e_tag, &mut acc.e_tag); + array_prop_append(&x.p_tag, &mut acc.p_tag); + array_prop_append(&x.t_tag, &mut acc.t_tag); + array_prop_append(&x.d_tag, &mut acc.d_tag); + array_prop_append(&x.r_tag, &mut acc.r_tag); + array_prop_append(&x.search, &mut acc.search); + acc.since = x.since; + acc.until = x.until; + acc.limit = x.limit; + + acc + }) + } +} + +impl From> for ReqFilter { + fn from(value: Vec<&ReqFilter>) -> Self { + let ret = ReqFilter { + ids: None, + authors: None, + kinds: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }; + value.iter().fold(ret, |mut acc, x| { + array_prop_append_vec(&x.ids, &mut acc.ids); + array_prop_append_vec(&x.authors, &mut acc.authors); + array_prop_append_vec(&x.kinds, &mut acc.kinds); + array_prop_append_vec(&x.e_tag, &mut acc.e_tag); + array_prop_append_vec(&x.p_tag, &mut acc.p_tag); + array_prop_append_vec(&x.t_tag, &mut acc.t_tag); + array_prop_append_vec(&x.d_tag, &mut acc.d_tag); + array_prop_append_vec(&x.r_tag, &mut acc.r_tag); + array_prop_append_vec(&x.search, &mut acc.search); + acc.since = x.since; + acc.until = x.until; + acc.limit = x.limit; + + acc + }) + } +} + +impl Distance for ReqFilter { + fn distance(&self, b: &Self) -> u32 { + let mut ret = 0u32; + + ret += prop_dist_vec(&self.ids, &b.ids); + ret += prop_dist_vec(&self.kinds, &b.kinds); + ret += prop_dist_vec(&self.authors, &b.authors); + ret += prop_dist_vec(&self.e_tag, &b.e_tag); + ret += prop_dist_vec(&self.p_tag, &b.p_tag); + ret += prop_dist_vec(&self.d_tag, &b.d_tag); + ret += prop_dist_vec(&self.r_tag, &b.r_tag); + ret += prop_dist_vec(&self.t_tag, &b.t_tag); + ret += prop_dist_vec(&self.search, &b.search); + + ret + } +} + +impl CanMerge for ReqFilter { + fn can_merge(&self, other: &Self) -> bool { + if self.since != other.since + || self.until != other.until + || self.limit != other.limit + || self.search != other.search + { + return false; + } + + self.distance(other) <= 1 + } +} + +#[inline(always)] +fn prop_dist(a: &Option, b: &Option) -> u32 { + if (a.is_some() && b.is_none()) || (a.is_none() && b.is_some()) { + return 10; + } else if a.is_some() && a != b { + return 1; + } + 0 +} + +#[inline(always)] +fn prop_dist_vec(a: &Option>, b: &Option>) -> u32 { + if (a.is_some() && b.is_none()) || (a.is_none() && b.is_some()) { + return 10; + } + match (a, b) { + (Some(aa), Some(bb)) => { + if aa.len() != bb.len() { + 1 + } else if aa == bb { + 0 + } else { + 1 + } + } + (None, None) => 0, + _ => panic!("Should not reach here!"), + } +} + +#[inline(always)] +fn array_prop_append(val: &Option, arr: &mut Option>) { + if let Some(ap) = val { + if arr.is_none() { + *arr = Some(HashSet::from([ap.clone()])) + } else { + arr.as_mut().unwrap().insert(ap.clone()); + } + } +} + +#[inline(always)] +fn array_prop_append_vec( + val: &Option>, + arr: &mut Option>, +) { + if let Some(ap) = val { + if arr.is_none() { + *arr = Some(ap.clone()) + } else { + ap.iter().for_each(|v| { + arr.as_mut().unwrap().insert((*v).clone()); + }); + } + } +} diff --git a/packages/system-query/src/lib.rs b/packages/system-query/src/lib.rs index 75a8f131..0b118608 100644 --- a/packages/system-query/src/lib.rs +++ b/packages/system-query/src/lib.rs @@ -1,79 +1,11 @@ -use std::fmt::{Debug}; -use serde::{Deserialize, Serialize}; +use crate::filter::{FlatReqFilter, ReqFilter}; use wasm_bindgen::prelude::*; mod diff; mod expand; +mod filter; mod merge; -#[derive(PartialEq, Clone, Serialize, Deserialize)] -pub struct ReqFilter { - #[serde(rename = "ids", skip_serializing_if = "Option::is_none")] - pub ids: Option>, - #[serde(rename = "authors", skip_serializing_if = "Option::is_none")] - pub authors: Option>, - #[serde(rename = "kinds", skip_serializing_if = "Option::is_none")] - pub kinds: Option>, - #[serde(rename = "#e", skip_serializing_if = "Option::is_none")] - pub e_tag: Option>, - #[serde(rename = "#p", skip_serializing_if = "Option::is_none")] - pub p_tag: Option>, - #[serde(rename = "#t", skip_serializing_if = "Option::is_none")] - pub t_tag: Option>, - #[serde(rename = "#d", skip_serializing_if = "Option::is_none")] - pub d_tag: Option>, - #[serde(rename = "#r", skip_serializing_if = "Option::is_none")] - pub r_tag: Option>, - #[serde(rename = "search", skip_serializing_if = "Option::is_none")] - pub search: Option>, - #[serde(rename = "since", skip_serializing_if = "Option::is_none")] - pub since: Option, - #[serde(rename = "until", skip_serializing_if = "Option::is_none")] - pub until: Option, - #[serde(rename = "limit", skip_serializing_if = "Option::is_none")] - pub limit: Option, -} - -impl Debug for ReqFilter { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&serde_json::to_string(self).unwrap().to_owned()) - } -} - -#[derive(PartialEq, Clone, Serialize, Deserialize)] -pub struct FlatReqFilter { - #[serde(rename = "ids", skip_serializing_if = "Option::is_none")] - id: Option, - #[serde(rename = "authors", skip_serializing_if = "Option::is_none")] - author: Option, - #[serde(rename = "kinds", skip_serializing_if = "Option::is_none")] - kind: Option, - #[serde(rename = "#e", skip_serializing_if = "Option::is_none")] - e_tag: Option, - #[serde(rename = "#p", skip_serializing_if = "Option::is_none")] - p_tag: Option, - #[serde(rename = "#t", skip_serializing_if = "Option::is_none")] - t_tag: Option, - #[serde(rename = "#d", skip_serializing_if = "Option::is_none")] - d_tag: Option, - #[serde(rename = "#r", skip_serializing_if = "Option::is_none")] - r_tag: Option, - #[serde(rename = "search", skip_serializing_if = "Option::is_none")] - search: Option, - #[serde(rename = "since", skip_serializing_if = "Option::is_none")] - since: Option, - #[serde(rename = "until", skip_serializing_if = "Option::is_none")] - until: Option, - #[serde(rename = "limit", skip_serializing_if = "Option::is_none")] - limit: Option, -} - -impl Debug for FlatReqFilter { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&serde_json::to_string(self).unwrap().to_owned()) - } -} - #[wasm_bindgen] pub fn diff_filters(prev: JsValue, next: JsValue) -> Result { let prev_parsed: Vec = serde_wasm_bindgen::from_value(prev)?; @@ -108,7 +40,7 @@ pub fn get_diff(prev: JsValue, next: JsValue) -> Result { #[wasm_bindgen] pub fn flat_merge(val: JsValue) -> Result { let val_parsed: Vec = serde_wasm_bindgen::from_value(val)?; - let result = merge::flat_merge(&val_parsed); + let result = merge::merge::(val_parsed.iter().collect()); Ok(serde_wasm_bindgen::to_value(&result)?) } @@ -117,25 +49,26 @@ mod tests { use super::*; use itertools::Itertools; use std::cmp::Ordering; + use std::collections::HashSet; #[test] fn flat_merge_expanded() { let input = vec![ ReqFilter { ids: None, - kinds: Some(vec![1, 6969, 6]), + kinds: Some(HashSet::from([1, 6969, 6])), e_tag: None, p_tag: None, t_tag: None, d_tag: None, r_tag: None, - authors: Some(vec![ + authors: Some(HashSet::from([ "kieran".to_string(), "snort".to_string(), "c".to_string(), "d".to_string(), "e".to_string(), - ]), + ])), since: Some(1), until: Some(100), search: None, @@ -143,7 +76,7 @@ mod tests { }, ReqFilter { ids: None, - kinds: Some(vec![4]), + kinds: Some(HashSet::from([4])), e_tag: None, p_tag: None, t_tag: None, @@ -152,15 +85,15 @@ mod tests { search: None, since: None, until: None, - authors: Some(vec!["kieran".to_string()]), + authors: Some(HashSet::from(["kieran".to_string()])), limit: None, }, ReqFilter { ids: None, authors: None, - kinds: Some(vec![4]), + kinds: Some(HashSet::from([4])), e_tag: None, - p_tag: Some(vec!["kieran".to_string()]), + p_tag: Some(HashSet::from(["kieran".to_string()])), t_tag: None, d_tag: None, r_tag: None, @@ -171,9 +104,9 @@ mod tests { }, ReqFilter { ids: None, - kinds: Some(vec![1000]), - authors: Some(vec!["snort".to_string()]), - p_tag: Some(vec!["kieran".to_string()]), + kinds: Some(HashSet::from([1000])), + authors: Some(HashSet::from(["snort".to_string()])), + p_tag: Some(HashSet::from(["kieran".to_string()])), t_tag: None, d_tag: None, r_tag: None, @@ -196,7 +129,8 @@ mod tests { } }) .collect_vec(); - let expanded_flat = merge::flat_merge(&expanded); - assert_eq!(expanded_flat, input); + let merged_expanded: Vec = merge::merge(expanded.iter().collect()); + assert_eq!(merged_expanded.len(), input.len()); + assert!(merged_expanded.iter().all(|v| input.contains(v))); } } diff --git a/packages/system-query/src/merge.rs b/packages/system-query/src/merge.rs index 47a8ba81..3b245578 100644 --- a/packages/system-query/src/merge.rs +++ b/packages/system-query/src/merge.rs @@ -1,134 +1,59 @@ -use crate::{FlatReqFilter, ReqFilter}; -use itertools::Itertools; -use std::cmp::Ordering; +use crate::filter::CanMerge; -pub fn flat_merge(all: &Vec) -> Vec { - let mut ret: Vec = vec![]; - - let merge_sets: Vec> = vec![vec![all.first().unwrap()]]; - let merge_sets = all - .iter() - .skip(1) - .sorted_by(|a, b| match distance(&a, &b) { - 0 => Ordering::Equal, - 1 => Ordering::Less, - _ => Ordering::Greater, - }) - .fold(merge_sets, |mut acc, x| { - let mut did_match = false; - for y in acc.iter_mut() { - if y.iter().all(|z| can_merge_filters(z, x)) { - y.push(x); - did_match = true; - break; - } - } - if !did_match { - acc.push(vec![x]); - } - acc - }); - - for s in merge_sets.iter() { - ret.push(merge_set(s)); - } - ret -} - -fn merge_set(set: &Vec<&FlatReqFilter>) -> ReqFilter { - let ret = ReqFilter { - ids: None, - authors: None, - kinds: None, - e_tag: None, - p_tag: None, - t_tag: None, - d_tag: None, - r_tag: None, - search: None, - since: None, - until: None, - limit: None, - }; - set.iter().fold(ret, |mut acc, x| { - array_prop_append(&x.id, &mut acc.ids); - array_prop_append(&x.author, &mut acc.authors); - array_prop_append(&x.kind, &mut acc.kinds); - array_prop_append(&x.e_tag, &mut acc.e_tag); - array_prop_append(&x.p_tag, &mut acc.p_tag); - array_prop_append(&x.t_tag, &mut acc.t_tag); - array_prop_append(&x.d_tag, &mut acc.d_tag); - array_prop_append(&x.r_tag, &mut acc.r_tag); - array_prop_append(&x.search, &mut acc.search); - acc.since = x.since; - acc.until = x.until; - acc.limit = x.limit; - - acc - }) -} - -fn can_merge_filters(a: &FlatReqFilter, b: &FlatReqFilter) -> bool { - if a.since != b.since || a.until != b.until || a.limit != b.limit || a.search != b.search { - return false; - } - - distance(a, b) <= 1 -} - -/// Calculate the distance in terms of similarity for merging -/// -/// The goal of this function is to find 2 filters which are very similar where -/// one filter may have a single property change like so: -/// -/// ```javascript -/// const a = { "kinds": 1, "authors": "a", "since": 99 }; -/// const b = { "kinds": 1, "authors": "b", "since": 99 }; -/// ``` -/// In this case these 2 filters could be merged because their distance is `1` -/// ```javascript -/// const result = { "kinds": [1], "authors": ["a", "b"], "since": 99 }; -/// ``` -fn distance(a: &FlatReqFilter, b: &FlatReqFilter) -> u32 { - let mut ret = 0u32; - - ret += prop_dist(&a.id, &b.id); - ret += prop_dist(&a.kind, &b.kind); - ret += prop_dist(&a.author, &b.author); - ret += prop_dist(&a.e_tag, &b.e_tag); - ret += prop_dist(&a.p_tag, &b.p_tag); - ret += prop_dist(&a.d_tag, &b.d_tag); - ret += prop_dist(&a.r_tag, &b.r_tag); - ret += prop_dist(&a.t_tag, &b.t_tag); - ret += prop_dist(&a.search, &b.search); - - ret -} - -#[inline(always)] -fn prop_dist(a: &Option, b: &Option) -> u32 { - if (a.is_some() && b.is_none()) || (a.is_none() && b.is_some()) { - return 10; - } else if a.is_some() && a != b { - return 1; - } - 0 -} - -#[inline(always)] -fn array_prop_append(val: &Option, arr: &mut Option>) { - if let Some(ap) = val { - if arr.is_none() { - *arr = Some(vec![ap.clone()]) - } else if !arr.as_ref().unwrap().contains(ap) { - arr.as_mut().unwrap().push(ap.clone()); +pub fn merge<'a, T, Z>(all: Vec<&'a T>) -> Vec +where + T: CanMerge, + for<'b> Z: CanMerge + From> + From>, +{ + let mut ret: Vec = merge_once(all); + loop { + let last_len = ret.len(); + ret = merge_once(ret.iter().collect()); + if last_len == ret.len() { + break; } } + ret +} + +fn merge_once<'a, T, Z>(all: Vec<&'a T>) -> Vec +where + T: CanMerge, + for<'b> Z: CanMerge + From> + From>, +{ + let mut ret: Vec = vec![]; + if all.is_empty() { + return ret; + } + + let merge_sets: Vec> = vec![vec![all.first().unwrap()]]; + let merge_sets = all.iter().skip(1).fold(merge_sets, |mut acc, x| { + let mut did_match = false; + for y in acc.iter_mut() { + if y.iter().all(|z| z.can_merge(x)) { + y.push(x); + did_match = true; + break; + } + } + if !did_match { + acc.push(vec![x]); + } + acc + }); + + for s in merge_sets { + ret.push(Z::from(s)); + } + + ret } #[cfg(test)] mod tests { use super::*; + use crate::filter::{Distance, FlatReqFilter, ReqFilter}; + use std::collections::HashSet; #[test] fn distance() { @@ -202,10 +127,10 @@ mod tests { until: None, limit: None, }; - assert_eq!(super::distance(&a, &b), 0); - assert_eq!(super::distance(&a, &c), 1); - assert_eq!(super::distance(&a, &d), 10); - assert_eq!(super::distance(&a, &e), 11); + assert_eq!(a.distance(&b), 0); + assert_eq!(a.distance(&c), 1); + assert_eq!(a.distance(&d), 10); + assert_eq!(a.distance(&e), 11); } #[test] @@ -240,8 +165,8 @@ mod tests { }; let output = ReqFilter { - ids: Some(vec!["0".to_owned()]), - authors: Some(vec!["a".to_owned(), "b".to_owned()]), + ids: Some(HashSet::from(["0".to_owned()])), + authors: Some(HashSet::from(["a".to_owned(), "b".to_owned()])), kinds: None, e_tag: None, p_tag: None, @@ -253,7 +178,7 @@ mod tests { until: None, limit: Some(10), }; - assert_eq!(super::merge_set(&vec![&a, &b]), output); + assert_eq!(ReqFilter::from(vec![&a, &b]), output); } #[test] @@ -300,8 +225,8 @@ mod tests { until: None, limit: Some(100), }; - assert!(super::can_merge_filters(&a, &b)); - assert!(!super::can_merge_filters(&b, &c)); + assert!(&a.can_merge(&b)); + assert!(!&b.can_merge(&c)); } #[test] @@ -436,8 +361,12 @@ mod tests { ]; let output = vec![ ReqFilter { - ids: Some(vec!["0".to_owned()]), - authors: Some(vec!["a".to_owned(), "b".to_owned(), "c".to_owned()]), + ids: Some(HashSet::from(["0".to_owned()])), + authors: Some(HashSet::from([ + "a".to_owned(), + "b".to_owned(), + "c".to_owned(), + ])), kinds: None, e_tag: None, p_tag: None, @@ -452,7 +381,7 @@ mod tests { ReqFilter { ids: None, authors: None, - kinds: Some(vec![1, 2]), + kinds: Some(HashSet::from([1, 2])), e_tag: None, p_tag: None, t_tag: None, @@ -465,8 +394,8 @@ mod tests { }, ReqFilter { ids: None, - authors: Some(vec!["c".to_owned()]), - kinds: Some(vec![1]), + authors: Some(HashSet::from(["c".to_owned()])), + kinds: Some(HashSet::from([1])), e_tag: None, p_tag: None, t_tag: None, @@ -479,7 +408,7 @@ mod tests { }, ReqFilter { ids: None, - authors: Some(vec!["c".to_owned()]), + authors: Some(HashSet::from(["c".to_owned()])), kinds: None, e_tag: None, p_tag: None, @@ -492,8 +421,8 @@ mod tests { limit: Some(100), }, ReqFilter { - ids: Some(vec!["1".to_owned()]), - authors: Some(vec!["c".to_owned()]), + ids: Some(HashSet::from(["1".to_owned()])), + authors: Some(HashSet::from(["c".to_owned()])), kinds: None, e_tag: None, p_tag: None, @@ -507,6 +436,9 @@ mod tests { }, ]; - assert_eq!(super::flat_merge(&input), output) + assert_eq!( + merge::(input.iter().collect()), + output + ) } } diff --git a/packages/system/src/nostr-system.ts b/packages/system/src/nostr-system.ts index 90f8ecf6..ec22de6c 100644 --- a/packages/system/src/nostr-system.ts +++ b/packages/system/src/nostr-system.ts @@ -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 { NoteCollection, NoteStore, NoteStoreHook, NoteStoreSnapshotData } from "./note-collection"; +import { NoteCollection, NoteStore, NoteStoreSnapshotData } from "./note-collection"; import { BuiltRawReqFilter, RequestBuilder } from "./request-builder"; import { RelayMetricHandler } from "./relay-metric-handler"; import { @@ -258,8 +258,12 @@ export class NostrSystem extends ExternalStore 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))) { + const expectIds = new Set(filters.flatMap(a => a.filters).flatMap(a => a.ids ?? [])); q.feed.onEvent(async evs => { - await this.#eventsCache.bulkSet(evs); + const toSet = evs.filter(a => expectIds.has(a.id) && this.#eventsCache.getFromCache(a.id) === undefined); + if(toSet.length > 0) { + await this.#eventsCache.bulkSet(toSet); + } }); } this.Queries.set(req.id, q); diff --git a/packages/system/src/request-builder.ts b/packages/system/src/request-builder.ts index 311d41e5..9a7c49aa 100644 --- a/packages/system/src/request-builder.ts +++ b/packages/system/src/request-builder.ts @@ -1,7 +1,7 @@ import debug from "debug"; import { v4 as uuid } from "uuid"; import { appendDedupe, sanitizeRelayUrl, unixNowMs } from "@snort/shared"; -import { get_diff }from "@snort/system-query"; +import { flat_merge, get_diff }from "@snort/system-query"; import { ReqFilter, u256, HexKey, EventKind } from "."; import { RelayCache, splitByWriteRelays, splitFlatByWriteRelays } from "./gossip-model"; @@ -115,7 +115,7 @@ export class RequestBuilder { return splitFlatByWriteRelays(relays, diff).map(a => { return { strategy: RequestStrategy.AuthorsRelays, - filters: flatMerge(a.filters), + filters: flat_merge(a.filters) as Array, relay: a.relay, }; }); -- 2.45.2 From a4c1ba8450162ae4e374a3f41a3465c17959785e Mon Sep 17 00:00:00 2001 From: Kieran Date: Mon, 11 Sep 2023 14:33:56 +0100 Subject: [PATCH 10/14] Remove unused constraint --- packages/system-query/src/merge.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/system-query/src/merge.rs b/packages/system-query/src/merge.rs index 3b245578..b8e797be 100644 --- a/packages/system-query/src/merge.rs +++ b/packages/system-query/src/merge.rs @@ -19,7 +19,7 @@ where fn merge_once<'a, T, Z>(all: Vec<&'a T>) -> Vec where T: CanMerge, - for<'b> Z: CanMerge + From> + From>, + Z: From>, { let mut ret: Vec = vec![]; if all.is_empty() { -- 2.45.2 From e2e1bb90caf00dcac93b896625999191073da932 Mon Sep 17 00:00:00 2001 From: Kieran Date: Mon, 11 Sep 2023 15:33:16 +0100 Subject: [PATCH 11/14] Make query optimizer pluggable --- packages/app/src/index.tsx | 20 +++++++- packages/system-query/pkg/system_query.d.ts | 6 +++ packages/system-query/pkg/system_query.js | 20 ++++++++ .../system-query/pkg/system_query_bg.wasm | Bin 118793 -> 120018 bytes .../pkg/system_query_bg.wasm.d.ts | 1 + packages/system-query/src/lib.rs | 7 +++ packages/system/src/gossip-model.ts | 2 +- packages/system/src/index.ts | 17 ++++++- packages/system/src/nostr-system.ts | 28 +++++++++--- packages/system/src/query-optimizer/index.ts | 43 ++++++++++++++++++ .../{ => query-optimizer}/request-expander.ts | 27 ++--------- .../{ => query-optimizer}/request-merger.ts | 4 +- .../{ => query-optimizer}/request-splitter.ts | 16 ++----- packages/system/src/request-builder.ts | 27 +++++------ packages/system/src/system-worker.ts | 9 ++++ packages/system/src/utils.ts | 2 +- 16 files changed, 164 insertions(+), 65 deletions(-) create mode 100644 packages/system/src/query-optimizer/index.ts rename packages/system/src/{ => query-optimizer}/request-expander.ts (64%) rename packages/system/src/{ => query-optimizer}/request-merger.ts (97%) rename packages/system/src/{ => query-optimizer}/request-splitter.ts (67%) diff --git a/packages/app/src/index.tsx b/packages/app/src/index.tsx index 7bce8257..8435f23c 100644 --- a/packages/app/src/index.tsx +++ b/packages/app/src/index.tsx @@ -2,14 +2,14 @@ import "./index.css"; import "@szhsin/react-menu/dist/index.css"; import "./fonts/inter.css"; -import {default as wasmInit} from "@snort/system-query"; +import { compress, expand_filter, flat_merge, get_diff, default as wasmInit } from "@snort/system-query"; import WasmPath from "@snort/system-query/pkg/system_query_bg.wasm"; import { StrictMode } from "react"; import * as ReactDOM from "react-dom/client"; import { Provider } from "react-redux"; import { createBrowserRouter, RouterProvider } from "react-router-dom"; -import { EventPublisher, NostrSystem, ProfileLoaderService, Nip7Signer, PowWorker } from "@snort/system"; +import { EventPublisher, NostrSystem, ProfileLoaderService, Nip7Signer, PowWorker, QueryOptimizer, FlatReqFilter, ReqFilter } from "@snort/system"; import { SnortContext } from "@snort/system-react"; import * as serviceWorkerRegistration from "serviceWorkerRegistration"; @@ -39,6 +39,21 @@ import { db } from "Db"; import { preload, RelayMetrics, UserCache, UserRelays } from "Cache"; import { LoginStore } from "Login"; +const WasmQueryOptimizer = { + expandFilter: (f: ReqFilter) => { + return expand_filter(f) as Array; + }, + getDiff: (prev: Array, next: Array) => { + return get_diff(prev, next) as Array; + }, + flatMerge: (all: Array) => { + return flat_merge(all) as Array; + }, + compress: (all: Array) => { + return compress(all) as Array; + } +} as QueryOptimizer; + /** * Singleton nostr system */ @@ -46,6 +61,7 @@ export const System = new NostrSystem({ relayCache: UserRelays, profileCache: UserCache, relayMetrics: RelayMetrics, + queryOptimizer: WasmQueryOptimizer, authHandler: async (c, r) => { const { publicKey, privateKey } = LoginStore.snapshot(); if (privateKey) { diff --git a/packages/system-query/pkg/system_query.d.ts b/packages/system-query/pkg/system_query.d.ts index 4b24908a..4fdcb2a9 100644 --- a/packages/system-query/pkg/system_query.d.ts +++ b/packages/system-query/pkg/system_query.d.ts @@ -22,6 +22,11 @@ export function get_diff(prev: any, next: any): any; * @returns {any} */ export function flat_merge(val: any): any; +/** +* @param {any} val +* @returns {any} +*/ +export function compress(val: any): any; export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; @@ -31,6 +36,7 @@ export interface InitOutput { readonly expand_filter: (a: number, b: number) => void; readonly get_diff: (a: number, b: number, c: number) => void; readonly flat_merge: (a: number, b: number) => void; + readonly compress: (a: number, b: number) => void; readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_add_to_stack_pointer: (a: number) => number; diff --git a/packages/system-query/pkg/system_query.js b/packages/system-query/pkg/system_query.js index 9fb7deef..57dd87cf 100644 --- a/packages/system-query/pkg/system_query.js +++ b/packages/system-query/pkg/system_query.js @@ -270,6 +270,26 @@ export function flat_merge(val) { } } +/** +* @param {any} val +* @returns {any} +*/ +export function compress(val) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.compress(retptr, addHeapObject(val)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +} + function handleError(f, args) { try { return f.apply(this, args); diff --git a/packages/system-query/pkg/system_query_bg.wasm b/packages/system-query/pkg/system_query_bg.wasm index 394d0f79785d2ed823f835286cfe364280ea8c71..8590da75f1d85807cab185e994d2f223b8f054d5 100644 GIT binary patch delta 24748 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 delta 23730 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 Result { Ok(serde_wasm_bindgen::to_value(&result)?) } +#[wasm_bindgen] +pub fn compress(val: JsValue) -> Result { + let val_parsed: Vec = serde_wasm_bindgen::from_value(val)?; + let result = merge::merge::(val_parsed.iter().collect()); + Ok(serde_wasm_bindgen::to_value(&result)?) +} + #[cfg(test)] mod tests { use super::*; diff --git a/packages/system/src/gossip-model.ts b/packages/system/src/gossip-model.ts index d931d6b6..2f2d9be8 100644 --- a/packages/system/src/gossip-model.ts +++ b/packages/system/src/gossip-model.ts @@ -1,7 +1,7 @@ import { ReqFilter, UsersRelays } from "."; import { dedupe, unwrap } from "@snort/shared"; import debug from "debug"; -import { FlatReqFilter } from "request-expander"; +import { FlatReqFilter } from "./query-optimizer"; const PickNRelays = 2; diff --git a/packages/system/src/index.ts b/packages/system/src/index.ts index dd1f1f1d..9bc4dd06 100644 --- a/packages/system/src/index.ts +++ b/packages/system/src/index.ts @@ -1,9 +1,11 @@ import { AuthHandler, RelaySettings, ConnectionStateSnapshot } from "./connection"; import { RequestBuilder } from "./request-builder"; -import { NoteStore, NoteStoreHook, NoteStoreSnapshotData } from "./note-collection"; +import { NoteStore, NoteStoreSnapshotData } from "./note-collection"; import { Query } from "./query"; import { NostrEvent, ReqFilter, TaggedNostrEvent } from "./nostr"; import { ProfileLoaderService } from "./profile-cache"; +import { RelayCache } from "./gossip-model"; +import { QueryOptimizer } from "./query-optimizer"; export * from "./nostr-system"; export { default as EventKind } from "./event-kind"; @@ -24,6 +26,7 @@ export * from "./signer"; export * from "./text"; export * from "./pow"; export * from "./pow-util"; +export * from "./query-optimizer"; export * from "./impl/nip4"; export * from "./impl/nip44"; @@ -96,6 +99,16 @@ export interface SystemInterface { * Profile cache/loader */ get ProfileLoader(): ProfileLoaderService; + + /** + * Relay cache for "Gossip" model + */ + get RelayCache(): RelayCache; + + /** + * Query optimizer + */ + get QueryOptimizer(): QueryOptimizer; } export interface SystemSnapshot { @@ -121,4 +134,4 @@ export interface MessageEncryptor { getSharedSecret(privateKey: string, publicKey: string): Promise | Uint8Array; encryptData(plaintext: string, sharedSecet: Uint8Array): Promise | MessageEncryptorPayload; decryptData(payload: MessageEncryptorPayload, sharedSecet: Uint8Array): Promise | string; -} +} \ No newline at end of file diff --git a/packages/system/src/nostr-system.ts b/packages/system/src/nostr-system.ts index ec22de6c..b7105016 100644 --- a/packages/system/src/nostr-system.ts +++ b/packages/system/src/nostr-system.ts @@ -20,6 +20,8 @@ import { UsersRelays, } from "."; import { EventsCache } from "./cache/events"; +import { RelayCache } from "./gossip-model"; +import { QueryOptimizer, DefaultQueryOptimizer } from "./query-optimizer"; /** * Manages nostr content retrieval system @@ -72,12 +74,18 @@ export class NostrSystem extends ExternalStore implements System */ #eventsCache: FeedCache; + /** + * Query optimizer instance + */ + #queryOptimizer: QueryOptimizer; + constructor(props: { authHandler?: AuthHandler; relayCache?: FeedCache; profileCache?: FeedCache; relayMetrics?: FeedCache; eventsCache?: FeedCache; + queryOptimizer?: QueryOptimizer; }) { super(); this.#handleAuth = props.authHandler; @@ -85,6 +93,7 @@ export class NostrSystem extends ExternalStore implements System this.#profileCache = props.profileCache ?? new UserProfileCache(); this.#relayMetricsCache = props.relayMetrics ?? new RelayMetricCache(); this.#eventsCache = props.eventsCache ?? new EventsCache(); + this.#queryOptimizer = props.queryOptimizer ?? DefaultQueryOptimizer; this.#profileLoader = new ProfileLoaderService(this, this.#profileCache); this.#relayMetrics = new RelayMetricHandler(this.#relayMetricsCache); @@ -92,9 +101,6 @@ export class NostrSystem extends ExternalStore implements System } HandleAuth?: AuthHandler | undefined; - /** - * Profile loader service allows you to request profiles - */ get ProfileLoader() { return this.#profileLoader; } @@ -103,6 +109,14 @@ export class NostrSystem extends ExternalStore implements System return [...this.#sockets.values()].map(a => a.snapshot()); } + get RelayCache(): RelayCache { + return this.#relayCache; + } + + get QueryOptimizer(): QueryOptimizer { + return this.#queryOptimizer; + } + /** * Setup caches */ @@ -241,8 +255,8 @@ export class NostrSystem extends ExternalStore implements System return existing; } const filters = !req.options?.skipDiff - ? req.buildDiff(this.#relayCache, existing.filters) - : req.build(this.#relayCache); + ? req.buildDiff(this, existing.filters) + : req.build(this); if (filters.length === 0 && !!req.options?.skipDiff) { return existing; } else { @@ -255,7 +269,7 @@ export class NostrSystem extends ExternalStore implements System } else { const store = new type(); - const filters = req.build(this.#relayCache); + const filters = req.build(this); const q = new Query(req.id, req.instance, store, req.options?.leaveOpen); if (filters.some(a => a.filters.some(b => b.ids))) { const expectIds = new Set(filters.flatMap(a => a.filters).flatMap(a => a.ids ?? [])); @@ -397,4 +411,4 @@ export class NostrSystem extends ExternalStore implements System } setTimeout(() => this.#cleanup(), 1_000); } -} +} \ No newline at end of file diff --git a/packages/system/src/query-optimizer/index.ts b/packages/system/src/query-optimizer/index.ts new file mode 100644 index 00000000..eb5a166d --- /dev/null +++ b/packages/system/src/query-optimizer/index.ts @@ -0,0 +1,43 @@ +import { ReqFilter } from "../nostr" +import { expandFilter } from "./request-expander" +import { flatMerge, mergeSimilar } from "./request-merger" +import { diffFilters } from "./request-splitter" + +export interface FlatReqFilter { + keys: number; + ids?: string; + authors?: string; + kinds?: number; + "#e"?: string; + "#p"?: string; + "#t"?: string; + "#d"?: string; + "#r"?: string; + search?: string; + since?: number; + until?: number; + limit?: number; +} + +export interface QueryOptimizer { + expandFilter(f: ReqFilter): Array + getDiff(prev: Array, next: Array): Array + flatMerge(all: Array): Array + compress(all: Array): Array +} + +export const DefaultQueryOptimizer = { + expandFilter: (f: ReqFilter) => { + return expandFilter(f); + }, + getDiff: (prev: Array, next: Array) => { + const diff = diffFilters(prev.flatMap(a => expandFilter(a)), next.flatMap(a => expandFilter(a))); + return diff.added; + }, + flatMerge: (all: Array) => { + return flatMerge(all); + }, + compress: (all: Array) => { + return mergeSimilar(all); + } + } as QueryOptimizer; \ No newline at end of file diff --git a/packages/system/src/request-expander.ts b/packages/system/src/query-optimizer/request-expander.ts similarity index 64% rename from packages/system/src/request-expander.ts rename to packages/system/src/query-optimizer/request-expander.ts index 2efa1d2c..3dfdb5ef 100644 --- a/packages/system/src/request-expander.ts +++ b/packages/system/src/query-optimizer/request-expander.ts @@ -1,27 +1,11 @@ -import { ReqFilter } from "./nostr"; -import {expand_filter} from "@snort/system-query"; - -export interface FlatReqFilter { - keys: number; - ids?: string; - authors?: string; - kinds?: number; - "#e"?: string; - "#p"?: string; - "#t"?: string; - "#d"?: string; - "#r"?: string; - search?: string; - since?: number; - until?: number; - limit?: number; -} +import { FlatReqFilter } from "."; +import { ReqFilter } from "../nostr"; /** * Expand a filter into its most fine grained form */ export function expandFilter(f: ReqFilter): Array { - /*const ret: Array = []; + const ret: Array = []; const src = Object.entries(f); const keys = src.filter(([, v]) => Array.isArray(v)).map(a => a[0]); const props = src.filter(([, v]) => !Array.isArray(v)); @@ -47,8 +31,5 @@ export function expandFilter(f: ReqFilter): Array { ...Object.fromEntries(props), }); - return ret;*/ - - const ret = expand_filter(f); - return ret as Array; + return ret; } diff --git a/packages/system/src/request-merger.ts b/packages/system/src/query-optimizer/request-merger.ts similarity index 97% rename from packages/system/src/request-merger.ts rename to packages/system/src/query-optimizer/request-merger.ts index 7b2f5cac..5ed3900e 100644 --- a/packages/system/src/request-merger.ts +++ b/packages/system/src/query-optimizer/request-merger.ts @@ -1,6 +1,6 @@ import { distance } from "@snort/shared"; -import { ReqFilter } from "."; -import { FlatReqFilter } from "./request-expander"; +import { ReqFilter } from ".."; +import { FlatReqFilter } from "."; /** * Keys which can change the entire meaning of the filter outside the array types diff --git a/packages/system/src/request-splitter.ts b/packages/system/src/query-optimizer/request-splitter.ts similarity index 67% rename from packages/system/src/request-splitter.ts rename to packages/system/src/query-optimizer/request-splitter.ts index 931458d4..97f9b628 100644 --- a/packages/system/src/request-splitter.ts +++ b/packages/system/src/query-optimizer/request-splitter.ts @@ -1,9 +1,8 @@ -import { flatFilterEq } from "./utils"; -import { FlatReqFilter } from "./request-expander"; -import { diff_filters } from "@snort/system-query"; +import { flatFilterEq } from "../utils"; +import { FlatReqFilter } from "."; export function diffFilters(prev: Array, next: Array, calcRemoved?: boolean) { - /*const added = []; + const added = []; const removed = []; for (const n of next) { @@ -29,12 +28,5 @@ export function diffFilters(prev: Array, next: Array 0, - added: (added as Array), - removed: [] - } + }; } \ No newline at end of file diff --git a/packages/system/src/request-builder.ts b/packages/system/src/request-builder.ts index 9a7c49aa..dcd0ba66 100644 --- a/packages/system/src/request-builder.ts +++ b/packages/system/src/request-builder.ts @@ -1,12 +1,11 @@ import debug from "debug"; import { v4 as uuid } from "uuid"; import { appendDedupe, sanitizeRelayUrl, unixNowMs } from "@snort/shared"; -import { flat_merge, get_diff }from "@snort/system-query"; -import { ReqFilter, u256, HexKey, EventKind } from "."; +import EventKind from "./event-kind"; +import { SystemInterface } from "index"; +import { ReqFilter, u256, HexKey } from "./nostr"; import { RelayCache, splitByWriteRelays, splitFlatByWriteRelays } from "./gossip-model"; -import { flatMerge, mergeSimilar } from "./request-merger"; -import { FlatReqFilter } from "./request-expander"; /** * Which strategy is used when building REQ filters @@ -95,27 +94,25 @@ export class RequestBuilder { return this.#builders.map(f => f.filter); } - build(relays: RelayCache): Array { - const expanded = this.#builders.flatMap(a => a.build(relays, this.id)); - return this.#groupByRelay(expanded); + build(system: SystemInterface): Array { + const expanded = this.#builders.flatMap(a => a.build(system.RelayCache, this.id)); + return this.#groupByRelay(system, expanded); } /** * Detects a change in request from a previous set of filters */ - buildDiff(relays: RelayCache, prev: Array): Array { + buildDiff(system: SystemInterface, prev: Array): Array { const start = unixNowMs(); - //const next = this.#builders.flatMap(f => expandFilter(f.filter)); - //const diff = diffFilters(prev, next); - const diff = get_diff(prev, this.buildRaw()) as Array; + const diff = system.QueryOptimizer.getDiff(prev, this.buildRaw()); const ts = unixNowMs() - start; this.#log("buildDiff %s %d ms", this.id, ts); if (diff.length > 0) { - return splitFlatByWriteRelays(relays, diff).map(a => { + return splitFlatByWriteRelays(system.RelayCache, diff).map(a => { return { strategy: RequestStrategy.AuthorsRelays, - filters: flat_merge(a.filters) as Array, + filters: system.QueryOptimizer.flatMerge(a.filters), relay: a.relay, }; }); @@ -130,7 +127,7 @@ export class RequestBuilder { * @param expanded * @returns */ - #groupByRelay(expanded: Array) { + #groupByRelay(system: SystemInterface, expanded: Array) { const relayMerged = expanded.reduce((acc, v) => { const existing = acc.get(v.relay); if (existing) { @@ -143,7 +140,7 @@ export class RequestBuilder { const filtersSquashed = [...relayMerged.values()].map(a => { return { - filters: mergeSimilar(a.flatMap(b => b.filters)), + filters: system.QueryOptimizer.compress(a.flatMap(b => b.filters)), relay: a[0].relay, strategy: a[0].strategy, } as BuiltRawReqFilter; diff --git a/packages/system/src/system-worker.ts b/packages/system/src/system-worker.ts index 622f03dd..5871cc03 100644 --- a/packages/system/src/system-worker.ts +++ b/packages/system/src/system-worker.ts @@ -6,6 +6,8 @@ import { NostrEvent, TaggedNostrEvent } from "./nostr"; import { NoteStore, NoteStoreSnapshotData } from "./note-collection"; import { Query } from "./query"; import { RequestBuilder } from "./request-builder"; +import { RelayCache } from "./gossip-model"; +import { QueryOptimizer } from "./query-optimizer"; export class SystemWorker extends ExternalStore implements SystemInterface { #port: MessagePort; @@ -29,6 +31,13 @@ export class SystemWorker extends ExternalStore implements Syste throw new Error("Method not implemented."); } + get RelayCache(): RelayCache { + throw new Error("Method not implemented."); + } + + get QueryOptimizer(): QueryOptimizer { + throw new Error("Method not implemented."); + } HandleAuth?: AuthHandler; get Sockets(): ConnectionStateSnapshot[] { diff --git a/packages/system/src/utils.ts b/packages/system/src/utils.ts index ec9a2b35..319e30c0 100644 --- a/packages/system/src/utils.ts +++ b/packages/system/src/utils.ts @@ -1,5 +1,5 @@ import { equalProp } from "@snort/shared"; -import { FlatReqFilter } from "./request-expander"; +import { FlatReqFilter } from "./query-optimizer"; import { NostrEvent, ReqFilter } from "./nostr"; export function findTag(e: NostrEvent, tag: string) { -- 2.45.2 From 3ed62d9f9ad5e27e6170e930fa25b2e30efe5bc7 Mon Sep 17 00:00:00 2001 From: Kieran Date: Mon, 11 Sep 2023 16:46:50 +0100 Subject: [PATCH 12/14] Build benchmarks page --- packages/app/package.json | 1 + packages/app/public/bench.html | 10 ++++ packages/app/src/benchmarks.ts | 105 +++++++++++++++++++++++++++++++++ packages/app/webpack.config.js | 8 ++- yarn.lock | 8 +++ 5 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 packages/app/public/bench.html create mode 100644 packages/app/src/benchmarks.ts diff --git a/packages/app/package.json b/packages/app/package.json index 7a8df136..d3a6454d 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -102,6 +102,7 @@ "prop-types": "^15.8.1", "source-map-loader": "^4.0.1", "terser-webpack-plugin": "^5.3.9", + "tinybench": "^2.5.0", "ts-jest": "^29.1.0", "ts-loader": "^9.4.4", "typescript": "^5.2.2", diff --git a/packages/app/public/bench.html b/packages/app/public/bench.html new file mode 100644 index 00000000..00b952b0 --- /dev/null +++ b/packages/app/public/bench.html @@ -0,0 +1,10 @@ + + + + + Snort Benchmarks + + + Check console + + \ No newline at end of file diff --git a/packages/app/src/benchmarks.ts b/packages/app/src/benchmarks.ts new file mode 100644 index 00000000..29531e4e --- /dev/null +++ b/packages/app/src/benchmarks.ts @@ -0,0 +1,105 @@ +import { bytesToHex } from "@noble/hashes/utils"; +import { DefaultQueryOptimizer, FlatReqFilter, QueryOptimizer, ReqFilter } from "@snort/system"; +import { compress, expand_filter, flat_merge, get_diff, default as wasmInit } from "@snort/system-query"; +import WasmPath from "@snort/system-query/pkg/system_query_bg.wasm"; +import { Bench } from 'tinybench'; + +const WasmQueryOptimizer = { + expandFilter: (f: ReqFilter) => { + return expand_filter(f) as Array; + }, + getDiff: (prev: Array, next: Array) => { + return get_diff(prev, next) as Array; + }, + flatMerge: (all: Array) => { + return flat_merge(all) as Array; + }, + compress: (all: Array) => { + return compress(all) as Array; + } + } as QueryOptimizer; + + const makeOnePubkey = () => { + const rnd = globalThis.crypto.getRandomValues(new Uint8Array(32)); + return bytesToHex(rnd); + } + + const randomPubkeys = (() => { + const ret = []; + for(let x =0;x<50;x++) { + ret.push(makeOnePubkey()); + } + return ret; + })(); + + const testExpand = (q: QueryOptimizer) => { + q.expandFilter({ + kinds: [1, 6, 7, 6969], + authors: randomPubkeys + }); + } + const testGetDiff = (q: QueryOptimizer) => { + q.getDiff([{ + kinds: [1, 2, 3], + authors: randomPubkeys + }], [{ + kinds: [1, 2, 3, 4, 5], + authors: randomPubkeys + }]); +} +const testFlatMerge = (q: QueryOptimizer) => { + q.flatMerge(q.expandFilter({ + kinds: [1, 6, 7, 6969], + authors: randomPubkeys + })); +} +const testCompress = (q: QueryOptimizer) => { + q.compress([ + { + kinds: [1, 6, 7, 6969], + authors: randomPubkeys + }, + { + kinds: [1, 6, 7, 6969], + authors: randomPubkeys + }, + { + kinds: [1, 6, 7, 6969], + authors: randomPubkeys + }, + { + kinds: [1, 6, 7, 6969], + authors: randomPubkeys + }, + { + kinds: [1, 6, 7, 6969], + authors: randomPubkeys + } + ]) +} + + const wasmSuite = new Bench({ time: 1_000 }); + const suite = new Bench({ time: 1_000 }); + + const addTests = (s: Bench, q: QueryOptimizer) => { + s.add("expand", () => testExpand(q)); + s.add("get_diff", () => testGetDiff(q)); + s.add("flat_merge", () => testFlatMerge(q)); + s.add("compress", () => testCompress(q)); + } + + addTests(suite, DefaultQueryOptimizer); + addTests(wasmSuite, WasmQueryOptimizer); + + const runAll = async() => { + await wasmInit(WasmPath); + + console.log("DefaultQueryOptimizer"); + await suite.run(); + console.table(suite.table()); + + console.log("WasmQueryOptimizer"); + await wasmSuite.run(); + console.table(wasmSuite.table()); + }; + runAll().catch(console.error); \ No newline at end of file diff --git a/packages/app/webpack.config.js b/packages/app/webpack.config.js index dce59a24..3c8edf01 100644 --- a/packages/app/webpack.config.js +++ b/packages/app/webpack.config.js @@ -19,6 +19,7 @@ const config = { import: require.resolve("@snort/system/dist/pow-worker.js"), filename: "pow.js", }, + bench: "./src/benchmarks.ts" }, target: "browserslist", mode: isProduction ? "production" : "development", @@ -47,7 +48,12 @@ const config = { new HtmlWebpackPlugin({ template: "public/index.html", favicon: "public/favicon.ico", - excludeChunks: ["pow"], + excludeChunks: ["pow", "bench"], + }), + new HtmlWebpackPlugin({ + filename: "bench.html", + template: "public/bench.html", + chunks: ["bench"], }), new ESLintPlugin({ extensions: ["js", "mjs", "jsx", "ts", "tsx"], diff --git a/yarn.lock b/yarn.lock index 05e7d01d..149d04eb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2757,6 +2757,7 @@ __metadata: react-twitter-embed: ^4.0.4 source-map-loader: ^4.0.1 terser-webpack-plugin: ^5.3.9 + tinybench: ^2.5.0 ts-jest: ^29.1.0 ts-loader: ^9.4.4 typescript: ^5.2.2 @@ -13160,6 +13161,13 @@ __metadata: languageName: node linkType: hard +"tinybench@npm:^2.5.0": + version: 2.5.0 + resolution: "tinybench@npm:2.5.0" + checksum: 284bb9428f197ec8b869c543181315e65e41ccfdad3c4b6c916bb1fdae1b5c6785661b0d90cf135b48d833b03cb84dc5357b2d33ec65a1f5971fae0ab2023821 + languageName: node + linkType: hard + "tmp@npm:^0.0.33": version: 0.0.33 resolution: "tmp@npm:0.0.33" -- 2.45.2 From f42096fa64043ea7ddc33e106589c39b7246dfa4 Mon Sep 17 00:00:00 2001 From: Kieran Date: Tue, 12 Sep 2023 14:56:24 +0100 Subject: [PATCH 13/14] Benchmarks --- packages/app/src/benchmarks.ts | 2 +- packages/system-query/Cargo.lock | 505 ++++++++++++++++++++++++- packages/system-query/Cargo.toml | 7 +- packages/system-query/benches/basic.rs | 65 ++++ packages/system-query/src/expand.rs | 446 ---------------------- packages/system-query/src/filter.rs | 445 ++++++++++++++++++++++ packages/system-query/src/lib.rs | 28 +- packages/system-query/system-query.iml | 1 + 8 files changed, 1040 insertions(+), 459 deletions(-) create mode 100644 packages/system-query/benches/basic.rs delete mode 100644 packages/system-query/src/expand.rs diff --git a/packages/app/src/benchmarks.ts b/packages/app/src/benchmarks.ts index 29531e4e..c56a7415 100644 --- a/packages/app/src/benchmarks.ts +++ b/packages/app/src/benchmarks.ts @@ -34,7 +34,7 @@ const WasmQueryOptimizer = { const testExpand = (q: QueryOptimizer) => { q.expandFilter({ - kinds: [1, 6, 7, 6969], + kinds: [1,2,3], authors: randomPubkeys }); } diff --git a/packages/system-query/Cargo.lock b/packages/system-query/Cargo.lock index c1a9a84a..067a7bdd 100644 --- a/packages/system-query/Cargo.lock +++ b/packages/system-query/Cargo.lock @@ -2,18 +2,118 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" +dependencies = [ + "memchr", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstyle" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b84bf0a05bbb2a83e5eb6fa36bb6e87baa08193c35ff52bbf6b38d8af2890e46" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + [[package]] name = "bumpalo" version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "ciborium" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" + +[[package]] +name = "ciborium-ll" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a13b88d2c62ff462f88e4a121f17a82c1af05693a2f192b5c38d14de73c19f6" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" + [[package]] name = "console_error_panic_hook" version = "0.1.7" @@ -24,12 +124,112 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools 0.10.5", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools 0.10.5", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if", +] + [[package]] name = "either" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +[[package]] +name = "errno" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "getrandom" version = "0.2.10" @@ -41,6 +241,38 @@ dependencies = [ "wasi", ] +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + +[[package]] +name = "hermit-abi" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" + +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi", + "rustix", + "windows-sys", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.11.0" @@ -71,18 +303,92 @@ version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +[[package]] +name = "linux-raw-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" + [[package]] name = "log" version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +[[package]] +name = "memchr" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" + +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num-traits" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "once_cell" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "oorandom" +version = "11.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" + +[[package]] +name = "plotters" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" + +[[package]] +name = "plotters-svg" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +dependencies = [ + "plotters-backend", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -137,18 +443,97 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rayon" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + +[[package]] +name = "regex" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + +[[package]] +name = "rustix" +version = "0.38.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "ryu" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scoped-tls" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "serde" version = "1.0.188" @@ -206,7 +591,8 @@ dependencies = [ name = "system-query" version = "0.1.0" dependencies = [ - "itertools", + "criterion", + "itertools 0.11.0", "rand", "serde", "serde-wasm-bindgen", @@ -215,12 +601,32 @@ dependencies = [ "wasm-bindgen-test", ] +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "unicode-ident" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +[[package]] +name = "walkdir" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -326,3 +732,100 @@ dependencies = [ "js-sys", "wasm-bindgen", ] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/packages/system-query/Cargo.toml b/packages/system-query/Cargo.toml index fb15f816..eb5f616d 100644 --- a/packages/system-query/Cargo.toml +++ b/packages/system-query/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] -crate-type = ["cdylib"] +crate-type = ["cdylib", "rlib"] [dependencies] itertools = "0.11.0" @@ -17,3 +17,8 @@ wasm-bindgen = "0.2.87" rand = "0.8.5" wasm-bindgen-test = "0.3.37" serde_json = "1.0.105" +criterion = { version = "0.5" } + +[[bench]] +name = "basic" +harness = false \ No newline at end of file diff --git a/packages/system-query/benches/basic.rs b/packages/system-query/benches/basic.rs new file mode 100644 index 00000000..1e48e6ab --- /dev/null +++ b/packages/system-query/benches/basic.rs @@ -0,0 +1,65 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use rand::prelude::*; +use std::collections::HashSet; +use system_query::diff::diff_filter; +use system_query::filter::{FlatReqFilter, ReqFilter}; + +fn random_pubkey(rng: &mut ThreadRng) -> String { + let mut bytes = [0u8; 32]; + rng.fill_bytes(&mut bytes); + bytes.iter().map(|byte| format!("{:02x}", byte)).collect() +} + +fn criterion_benchmark(c: &mut Criterion) { + let mut rng = thread_rng(); + let mut random_pubkeys = HashSet::new(); + for _ in 0..50 { + random_pubkeys.insert(random_pubkey(&mut rng)); + } + let input_authors = ReqFilter { + authors: Some(random_pubkeys.clone()), + kinds: Some(HashSet::from([1, 2, 3])), + ids: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + e_tag: None, + }; + + let input_authors_diff = ReqFilter { + authors: Some(random_pubkeys.clone()), + kinds: Some(HashSet::from([1, 2, 3, 4, 5])), + ids: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + e_tag: None, + }; + + + c.bench_function("expand", |b| { + b.iter(|| { + let _: Vec = (&input_authors).into(); + }) + }); + c.bench_function("diff", |b| { + b.iter(|| { + let prev: Vec = (&input_authors).into(); + let next: Vec = (&input_authors_diff).into(); + let _ = diff_filter(&prev, &next); + }) + }); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/packages/system-query/src/expand.rs b/packages/system-query/src/expand.rs deleted file mode 100644 index 4871a0fe..00000000 --- a/packages/system-query/src/expand.rs +++ /dev/null @@ -1,446 +0,0 @@ -use crate::{FlatReqFilter, ReqFilter}; -use itertools::Itertools; - -#[derive(Clone)] -enum StringOrNumberEntry<'a> { - String((&'static str, &'a String)), - Number((&'static str, &'a i32)), -} - -pub fn expand_filter(filter: &ReqFilter) -> Vec { - let mut ret: Vec = Vec::new(); - - let mut inputs: Vec> = vec![]; - if let Some(ids) = &filter.ids { - let t_ids = ids - .iter() - .map(|z| StringOrNumberEntry::String(("id", z))) - .collect_vec(); - inputs.push(t_ids); - } - if let Some(authors) = &filter.authors { - let t_ids = authors - .iter() - .map(|z| StringOrNumberEntry::String(("author", z))) - .collect_vec(); - inputs.push(t_ids); - } - if let Some(kinds) = &filter.kinds { - let t_ids = kinds - .iter() - .map(|z| StringOrNumberEntry::Number(("kind", z))) - .collect_vec(); - inputs.push(t_ids); - } - if let Some(e_tags) = &filter.e_tag { - let t_ids = e_tags - .iter() - .map(|z| StringOrNumberEntry::String(("e_tag", z))) - .collect_vec(); - inputs.push(t_ids); - } - if let Some(p_tags) = &filter.p_tag { - let t_ids = p_tags - .iter() - .map(|z| StringOrNumberEntry::String(("p_tag", z))) - .collect_vec(); - inputs.push(t_ids); - } - if let Some(d_tags) = &filter.d_tag { - let t_ids = d_tags - .iter() - .map(|z| StringOrNumberEntry::String(("d_tag", z))) - .collect_vec(); - inputs.push(t_ids); - } - if let Some(t_tags) = &filter.t_tag { - let t_ids = t_tags - .iter() - .map(|z| StringOrNumberEntry::String(("t_tag", z))) - .collect_vec(); - inputs.push(t_ids); - } - if let Some(r_tags) = &filter.r_tag { - let t_ids = r_tags - .iter() - .map(|z| StringOrNumberEntry::String(("r_tag", z))) - .collect_vec(); - inputs.push(t_ids); - } - if let Some(search) = &filter.search { - let t_ids = search - .iter() - .map(|z| StringOrNumberEntry::String(("search", z))) - .collect_vec(); - inputs.push(t_ids); - } - - for p in inputs.iter().multi_cartesian_product() { - ret.push(FlatReqFilter { - id: p.iter().find_map(|q| { - if let StringOrNumberEntry::String((k, v)) = q { - if (*k).eq("id") { - return Some((*v).to_string()); - } - } - None - }), - author: p.iter().find_map(|q| { - if let StringOrNumberEntry::String((k, v)) = q { - if (*k).eq("author") { - return Some((*v).to_string()); - } - } - None - }), - kind: p.iter().find_map(|q| { - if let StringOrNumberEntry::Number((k, v)) = q { - if (*k).eq("kind") { - return Some((*v).clone()); - } - } - None - }), - e_tag: p.iter().find_map(|q| { - if let StringOrNumberEntry::String((k, v)) = q { - if (*k).eq("e_tag") { - return Some((*v).to_string()); - } - } - None - }), - p_tag: p.iter().find_map(|q| { - if let StringOrNumberEntry::String((k, v)) = q { - if (*k).eq("p_tag") { - return Some((*v).to_string()); - } - } - None - }), - t_tag: p.iter().find_map(|q| { - if let StringOrNumberEntry::String((k, v)) = q { - if (*k).eq("t_tag") { - return Some((*v).to_string()); - } - } - None - }), - d_tag: p.iter().find_map(|q| { - if let StringOrNumberEntry::String((k, v)) = q { - if (*k).eq("d_tag") { - return Some((*v).to_string()); - } - } - None - }), - r_tag: p.iter().find_map(|q| { - if let StringOrNumberEntry::String((k, v)) = q { - if (*k).eq("r_tag") { - return Some((*v).to_string()); - } - } - None - }), - search: p.iter().find_map(|q| { - if let StringOrNumberEntry::String((k, v)) = q { - if (*k).eq("search") { - return Some((*v).to_string()); - } - } - None - }), - since: filter.since, - until: filter.until, - limit: filter.limit, - }) - } - ret -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::ReqFilter; - use std::collections::HashSet; - - #[test] - fn test_expand_filter() { - let input = ReqFilter { - authors: Some(HashSet::from([ - "a".to_owned(), - "b".to_owned(), - "c".to_owned(), - ])), - kinds: Some(HashSet::from([1, 2, 3])), - ids: Some(HashSet::from(["x".to_owned(), "y".to_owned()])), - p_tag: Some(HashSet::from(["a".to_owned()])), - t_tag: None, - d_tag: None, - r_tag: None, - search: None, - since: Some(99), - until: None, - limit: Some(10), - e_tag: None, - }; - - let output = expand_filter(&input); - output.iter().take(5).for_each(|x| println!("{:?}", x)); - let expected = vec![ - FlatReqFilter { - author: Some("a".to_owned()), - kind: Some(1), - id: Some("x".to_owned()), - p_tag: Some("a".to_owned()), - t_tag: None, - d_tag: None, - r_tag: None, - search: None, - since: Some(99), - until: None, - limit: Some(10), - e_tag: None, - }, - FlatReqFilter { - author: Some("a".to_owned()), - kind: Some(1), - id: Some("y".to_owned()), - p_tag: Some("a".to_owned()), - t_tag: None, - d_tag: None, - r_tag: None, - search: None, - since: Some(99), - until: None, - limit: Some(10), - e_tag: None, - }, - FlatReqFilter { - author: Some("a".to_owned()), - kind: Some(2), - id: Some("x".to_owned()), - p_tag: Some("a".to_owned()), - t_tag: None, - d_tag: None, - r_tag: None, - search: None, - since: Some(99), - until: None, - limit: Some(10), - e_tag: None, - }, - FlatReqFilter { - author: Some("a".to_owned()), - kind: Some(2), - id: Some("y".to_owned()), - p_tag: Some("a".to_owned()), - t_tag: None, - d_tag: None, - r_tag: None, - search: None, - since: Some(99), - until: None, - limit: Some(10), - e_tag: None, - }, - FlatReqFilter { - author: Some("a".to_owned()), - kind: Some(3), - id: Some("x".to_owned()), - p_tag: Some("a".to_owned()), - t_tag: None, - d_tag: None, - r_tag: None, - search: None, - since: Some(99), - until: None, - limit: Some(10), - e_tag: None, - }, - FlatReqFilter { - author: Some("a".to_owned()), - kind: Some(3), - id: Some("y".to_owned()), - p_tag: Some("a".to_owned()), - t_tag: None, - d_tag: None, - r_tag: None, - search: None, - since: Some(99), - until: None, - limit: Some(10), - e_tag: None, - }, - FlatReqFilter { - author: Some("b".to_owned()), - kind: Some(1), - id: Some("x".to_owned()), - p_tag: Some("a".to_owned()), - t_tag: None, - d_tag: None, - r_tag: None, - search: None, - since: Some(99), - until: None, - limit: Some(10), - e_tag: None, - }, - FlatReqFilter { - author: Some("b".to_owned()), - kind: Some(1), - id: Some("y".to_owned()), - p_tag: Some("a".to_owned()), - t_tag: None, - d_tag: None, - r_tag: None, - search: None, - since: Some(99), - until: None, - limit: Some(10), - e_tag: None, - }, - FlatReqFilter { - author: Some("b".to_owned()), - kind: Some(2), - id: Some("x".to_owned()), - p_tag: Some("a".to_owned()), - t_tag: None, - d_tag: None, - r_tag: None, - search: None, - since: Some(99), - until: None, - limit: Some(10), - e_tag: None, - }, - FlatReqFilter { - author: Some("b".to_owned()), - kind: Some(2), - id: Some("y".to_owned()), - p_tag: Some("a".to_owned()), - t_tag: None, - d_tag: None, - r_tag: None, - search: None, - since: Some(99), - until: None, - limit: Some(10), - e_tag: None, - }, - FlatReqFilter { - author: Some("b".to_owned()), - kind: Some(3), - id: Some("x".to_owned()), - p_tag: Some("a".to_owned()), - t_tag: None, - d_tag: None, - r_tag: None, - search: None, - since: Some(99), - until: None, - limit: Some(10), - e_tag: None, - }, - FlatReqFilter { - author: Some("b".to_owned()), - kind: Some(3), - id: Some("y".to_owned()), - p_tag: Some("a".to_owned()), - t_tag: None, - d_tag: None, - r_tag: None, - search: None, - since: Some(99), - until: None, - limit: Some(10), - e_tag: None, - }, - FlatReqFilter { - author: Some("c".to_owned()), - kind: Some(1), - id: Some("x".to_owned()), - p_tag: Some("a".to_owned()), - t_tag: None, - d_tag: None, - r_tag: None, - search: None, - since: Some(99), - until: None, - limit: Some(10), - e_tag: None, - }, - FlatReqFilter { - author: Some("c".to_owned()), - kind: Some(1), - id: Some("y".to_owned()), - p_tag: Some("a".to_owned()), - t_tag: None, - d_tag: None, - r_tag: None, - search: None, - since: Some(99), - until: None, - limit: Some(10), - e_tag: None, - }, - FlatReqFilter { - author: Some("c".to_owned()), - kind: Some(2), - id: Some("x".to_owned()), - p_tag: Some("a".to_owned()), - t_tag: None, - d_tag: None, - r_tag: None, - search: None, - since: Some(99), - until: None, - limit: Some(10), - e_tag: None, - }, - FlatReqFilter { - author: Some("c".to_owned()), - kind: Some(2), - id: Some("y".to_owned()), - p_tag: Some("a".to_owned()), - t_tag: None, - d_tag: None, - r_tag: None, - search: None, - since: Some(99), - until: None, - limit: Some(10), - e_tag: None, - }, - FlatReqFilter { - author: Some("c".to_owned()), - kind: Some(3), - id: Some("x".to_owned()), - p_tag: Some("a".to_owned()), - t_tag: None, - d_tag: None, - r_tag: None, - search: None, - since: Some(99), - until: None, - limit: Some(10), - e_tag: None, - }, - FlatReqFilter { - author: Some("c".to_owned()), - kind: Some(3), - id: Some("y".to_owned()), - p_tag: Some("a".to_owned()), - t_tag: None, - d_tag: None, - r_tag: None, - search: None, - since: Some(99), - until: None, - limit: Some(10), - e_tag: None, - }, - ]; - assert_eq!(output.len(), expected.len()); - output.iter().for_each(|a| assert!(expected.contains(a))); - } -} diff --git a/packages/system-query/src/filter.rs b/packages/system-query/src/filter.rs index 43f812ef..333b2ee8 100644 --- a/packages/system-query/src/filter.rs +++ b/packages/system-query/src/filter.rs @@ -3,6 +3,13 @@ use std::collections::HashSet; #[cfg(test)] use std::fmt::Debug; use std::hash::Hash; +use itertools::Itertools; + +#[derive(Clone)] +enum StringOrNumberEntry<'a> { + String((&'static str, &'a String)), + Number((&'static str, &'a i32)), +} #[derive(PartialEq, Clone, Serialize, Deserialize)] pub struct ReqFilter { @@ -197,6 +204,157 @@ impl From> for ReqFilter { } } +impl Into> for &ReqFilter { + fn into(self) -> Vec { + let mut ret: Vec = Vec::new(); + + let mut inputs: Vec> = vec![]; + if let Some(ids) = &self.ids { + let t_ids = ids + .iter() + .map(|z| StringOrNumberEntry::String(("id", z))) + .collect(); + inputs.push(t_ids); + } + if let Some(authors) = &self.authors { + let t_ids = authors + .iter() + .map(|z| StringOrNumberEntry::String(("author", z))) + .collect(); + inputs.push(t_ids); + } + if let Some(kinds) = &self.kinds { + let t_ids = kinds + .iter() + .map(|z| StringOrNumberEntry::Number(("kind", z))) + .collect(); + inputs.push(t_ids); + } + if let Some(e_tags) = &self.e_tag { + let t_ids = e_tags + .iter() + .map(|z| StringOrNumberEntry::String(("e_tag", z))) + .collect(); + inputs.push(t_ids); + } + if let Some(p_tags) = &self.p_tag { + let t_ids = p_tags + .iter() + .map(|z| StringOrNumberEntry::String(("p_tag", z))) + .collect(); + inputs.push(t_ids); + } + if let Some(d_tags) = &self.d_tag { + let t_ids = d_tags + .iter() + .map(|z| StringOrNumberEntry::String(("d_tag", z))) + .collect(); + inputs.push(t_ids); + } + if let Some(t_tags) = &self.t_tag { + let t_ids = t_tags + .iter() + .map(|z| StringOrNumberEntry::String(("t_tag", z))) + .collect(); + inputs.push(t_ids); + } + if let Some(r_tags) = &self.r_tag { + let t_ids = r_tags + .iter() + .map(|z| StringOrNumberEntry::String(("r_tag", z))) + .collect(); + inputs.push(t_ids); + } + if let Some(search) = &self.search { + let t_ids = search + .iter() + .map(|z| StringOrNumberEntry::String(("search", z))) + .collect(); + inputs.push(t_ids); + } + + for p in inputs.iter().multi_cartesian_product() { + ret.push(FlatReqFilter { + id: p.iter().find_map(|q| { + if let StringOrNumberEntry::String((k, v)) = q { + if (*k).eq("id") { + return Some((*v).to_string()); + } + } + None + }), + author: p.iter().find_map(|q| { + if let StringOrNumberEntry::String((k, v)) = q { + if (*k).eq("author") { + return Some((*v).to_string()); + } + } + None + }), + kind: p.iter().find_map(|q| { + if let StringOrNumberEntry::Number((k, v)) = q { + if (*k).eq("kind") { + return Some((*v).clone()); + } + } + None + }), + e_tag: p.iter().find_map(|q| { + if let StringOrNumberEntry::String((k, v)) = q { + if (*k).eq("e_tag") { + return Some((*v).to_string()); + } + } + None + }), + p_tag: p.iter().find_map(|q| { + if let StringOrNumberEntry::String((k, v)) = q { + if (*k).eq("p_tag") { + return Some((*v).to_string()); + } + } + None + }), + t_tag: p.iter().find_map(|q| { + if let StringOrNumberEntry::String((k, v)) = q { + if (*k).eq("t_tag") { + return Some((*v).to_string()); + } + } + None + }), + d_tag: p.iter().find_map(|q| { + if let StringOrNumberEntry::String((k, v)) = q { + if (*k).eq("d_tag") { + return Some((*v).to_string()); + } + } + None + }), + r_tag: p.iter().find_map(|q| { + if let StringOrNumberEntry::String((k, v)) = q { + if (*k).eq("r_tag") { + return Some((*v).to_string()); + } + } + None + }), + search: p.iter().find_map(|q| { + if let StringOrNumberEntry::String((k, v)) = q { + if (*k).eq("search") { + return Some((*v).to_string()); + } + } + None + }), + since: self.since, + until: self.until, + limit: self.limit, + }) + } + ret + } +} impl Distance for ReqFilter { fn distance(&self, b: &Self) -> u32 { let mut ret = 0u32; @@ -285,3 +443,290 @@ fn array_prop_append_vec( } } } + +#[cfg(test)] +mod tests { + use crate::ReqFilter; + use std::collections::HashSet; + use crate::filter::FlatReqFilter; + + #[test] + fn test_expand_filter() { + let input = ReqFilter { + authors: Some(HashSet::from([ + "a".to_owned(), + "b".to_owned(), + "c".to_owned(), + ])), + kinds: Some(HashSet::from([1, 2, 3])), + ids: Some(HashSet::from(["x".to_owned(), "y".to_owned()])), + p_tag: Some(HashSet::from(["a".to_owned()])), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }; + + let output : Vec = (&input).into(); + let expected = vec![ + FlatReqFilter { + author: Some("a".to_owned()), + kind: Some(1), + id: Some("x".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("a".to_owned()), + kind: Some(1), + id: Some("y".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("a".to_owned()), + kind: Some(2), + id: Some("x".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("a".to_owned()), + kind: Some(2), + id: Some("y".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("a".to_owned()), + kind: Some(3), + id: Some("x".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("a".to_owned()), + kind: Some(3), + id: Some("y".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("b".to_owned()), + kind: Some(1), + id: Some("x".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("b".to_owned()), + kind: Some(1), + id: Some("y".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("b".to_owned()), + kind: Some(2), + id: Some("x".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("b".to_owned()), + kind: Some(2), + id: Some("y".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("b".to_owned()), + kind: Some(3), + id: Some("x".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("b".to_owned()), + kind: Some(3), + id: Some("y".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("c".to_owned()), + kind: Some(1), + id: Some("x".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("c".to_owned()), + kind: Some(1), + id: Some("y".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("c".to_owned()), + kind: Some(2), + id: Some("x".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("c".to_owned()), + kind: Some(2), + id: Some("y".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("c".to_owned()), + kind: Some(3), + id: Some("x".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("c".to_owned()), + kind: Some(3), + id: Some("y".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + ]; + assert_eq!(output.len(), expected.len()); + output.iter().for_each(|a| assert!(expected.contains(a))); + } +} diff --git a/packages/system-query/src/lib.rs b/packages/system-query/src/lib.rs index c5766353..001f2ecc 100644 --- a/packages/system-query/src/lib.rs +++ b/packages/system-query/src/lib.rs @@ -1,10 +1,9 @@ use crate::filter::{FlatReqFilter, ReqFilter}; use wasm_bindgen::prelude::*; -mod diff; -mod expand; -mod filter; -mod merge; +pub mod diff; +pub mod filter; +pub mod merge; #[wasm_bindgen] pub fn diff_filters(prev: JsValue, next: JsValue) -> Result { @@ -17,7 +16,7 @@ pub fn diff_filters(prev: JsValue, next: JsValue) -> Result { #[wasm_bindgen] pub fn expand_filter(val: JsValue) -> Result { let parsed: ReqFilter = serde_wasm_bindgen::from_value(val)?; - let result = expand::expand_filter(&parsed); + let result: Vec = (&parsed).into(); Ok(serde_wasm_bindgen::to_value(&result)?) } @@ -27,11 +26,17 @@ pub fn get_diff(prev: JsValue, next: JsValue) -> Result { let next_parsed: Vec = serde_wasm_bindgen::from_value(next)?; let expanded_prev: Vec = prev_parsed .iter() - .flat_map(|v| expand::expand_filter(v)) + .flat_map(|v| { + let vec: Vec = v.into(); + vec + }) .collect(); let expanded_next: Vec = next_parsed .iter() - .flat_map(|v| expand::expand_filter(v)) + .flat_map(|v| { + let vec: Vec = v.into(); + vec + }) .collect(); let result = diff::diff_filter(&expanded_prev, &expanded_next); Ok(serde_wasm_bindgen::to_value(&result)?) @@ -125,9 +130,12 @@ mod tests { }, ]; - let expanded = input + let expanded: Vec = input .iter() - .flat_map(|v| expand::expand_filter(v)) + .flat_map(|v| { + let r: Vec = v.into(); + r + }) .sorted_by(|_, _| { if rand::random() { Ordering::Less @@ -135,7 +143,7 @@ mod tests { Ordering::Greater } }) - .collect_vec(); + .collect(); let merged_expanded: Vec = merge::merge(expanded.iter().collect()); assert_eq!(merged_expanded.len(), input.len()); assert!(merged_expanded.iter().all(|v| input.contains(v))); diff --git a/packages/system-query/system-query.iml b/packages/system-query/system-query.iml index 2fecef3b..75504b26 100644 --- a/packages/system-query/system-query.iml +++ b/packages/system-query/system-query.iml @@ -3,6 +3,7 @@ + -- 2.45.2 From 9e2453444d522a662734c659b34869a149a8ff2c Mon Sep 17 00:00:00 2001 From: Kieran Date: Tue, 12 Sep 2023 15:02:16 +0100 Subject: [PATCH 14/14] Prettier --- packages/app/public/bench.html | 18 +- packages/app/public/index.html | 2 +- packages/app/src/Cache/GiftWrapCache.ts | 2 +- packages/app/src/Element/CashuNuts.tsx | 2 +- packages/app/src/Element/FollowButton.tsx | 2 +- packages/app/src/Element/Nip5Service.tsx | 4 +- packages/app/src/Element/Note.tsx | 6 +- packages/app/src/Element/NoteCreator.css | 3 +- packages/app/src/Element/NoteCreator.tsx | 10 +- packages/app/src/Element/NoteFooter.tsx | 2 +- packages/app/src/Element/NoteReaction.tsx | 4 +- packages/app/src/Element/Poll.tsx | 10 +- packages/app/src/Element/PubkeyList.tsx | 2 +- packages/app/src/Element/ReBroadcaster.tsx | 6 +- packages/app/src/Element/Reactions.tsx | 2 +- packages/app/src/Element/Relay.tsx | 4 +- packages/app/src/Element/SendSats.tsx | 2 +- packages/app/src/Element/Thread.tsx | 8 +- packages/app/src/Element/Timeline.tsx | 4 +- packages/app/src/Element/TimelineFollows.tsx | 8 +- packages/app/src/Element/ZapstrEmbed.tsx | 2 +- packages/app/src/Feed/BadgesFeed.ts | 6 +- packages/app/src/Feed/FeedReactions.ts | 2 +- packages/app/src/Feed/FollowersFeed.ts | 2 +- packages/app/src/Feed/LoginFeed.ts | 6 +- packages/app/src/Feed/ThreadFeed.ts | 8 +- packages/app/src/Feed/TimelineFeed.ts | 4 +- packages/app/src/Hooks/useImgProxy.ts | 2 +- .../app/src/Hooks/useInteractionCache.tsx | 2 +- packages/app/src/Hooks/useLogin.tsx | 2 +- packages/app/src/Login/MultiAccountStore.ts | 2 +- packages/app/src/Nip05/ServiceProvider.ts | 2 +- .../app/src/Nip05/SnortServiceProvider.ts | 2 +- packages/app/src/Pages/ErrorPage.tsx | 2 +- packages/app/src/Pages/Layout.tsx | 4 +- packages/app/src/Pages/LoginPage.tsx | 4 +- packages/app/src/Pages/Notifications.tsx | 6 +- packages/app/src/Pages/ProfilePage.tsx | 4 +- packages/app/src/Pages/ZapPool.tsx | 2 +- packages/app/src/Pages/settings/Accounts.tsx | 2 +- .../src/Pages/settings/handle/LNAddress.tsx | 2 +- .../app/src/Pages/settings/wallet/Cashu.tsx | 2 +- .../app/src/Pages/settings/wallet/LNC.tsx | 2 +- .../app/src/Pages/settings/wallet/LNDHub.tsx | 2 +- .../app/src/Pages/settings/wallet/NWC.tsx | 2 +- packages/app/src/SnortApi.ts | 4 +- packages/app/src/SnortUtils/index.ts | 8 +- packages/app/src/Toaster.tsx | 2 +- packages/app/src/Upload/VoidCat.ts | 2 +- packages/app/src/Wallet/Cashu.ts | 2 +- packages/app/src/Wallet/LNCWallet.ts | 2 +- packages/app/src/Wallet/NostrWalletConnect.ts | 2 +- packages/app/src/Wallet/WebLN.ts | 2 +- packages/app/src/Wallet/index.ts | 2 +- packages/app/src/ZapPoolController.ts | 2 +- packages/app/src/benchmarks.ts | 185 ++-- packages/app/src/chat/index.ts | 14 +- packages/app/src/chat/nip24.ts | 6 +- packages/app/src/chat/nip29.ts | 4 +- packages/app/src/chat/nip4.ts | 17 +- packages/app/src/index.tsx | 15 +- packages/app/webpack.config.js | 4 +- packages/system-query/pkg/package.json | 2 +- packages/system-query/pkg/system_query.d.ts | 64 +- packages/system-query/pkg/system_query.js | 910 +++++++++--------- .../system-query/pkg/system_query_bg.wasm | Bin 120018 -> 120012 bytes packages/system/src/index.ts | 2 +- packages/system/src/nostr-system.ts | 10 +- packages/system/src/query-optimizer/index.ts | 47 +- .../src/query-optimizer/request-splitter.ts | 2 +- packages/system/src/system-worker.ts | 2 +- packages/system/src/zaps.ts | 2 +- packages/system/tests/node.ts | 12 +- yarn.lock | 1 + 74 files changed, 776 insertions(+), 728 deletions(-) diff --git a/packages/app/public/bench.html b/packages/app/public/bench.html index 00b952b0..95159cd9 100644 --- a/packages/app/public/bench.html +++ b/packages/app/public/bench.html @@ -1,10 +1,10 @@ - + - - - Snort Benchmarks - - - Check console - - \ No newline at end of file + + + Snort Benchmarks + + + Check console + + diff --git a/packages/app/public/index.html b/packages/app/public/index.html index ee84c3b7..b5a13832 100644 --- a/packages/app/public/index.html +++ b/packages/app/public/index.html @@ -1,4 +1,4 @@ - + diff --git a/packages/app/src/Cache/GiftWrapCache.ts b/packages/app/src/Cache/GiftWrapCache.ts index 71e82b45..67f00b91 100644 --- a/packages/app/src/Cache/GiftWrapCache.ts +++ b/packages/app/src/Cache/GiftWrapCache.ts @@ -38,7 +38,7 @@ export class GiftWrapCache extends RefreshFeedCache { } catch (e) { console.debug(e, v); } - }) + }), ) ) .filter(a => a !== undefined) diff --git a/packages/app/src/Element/CashuNuts.tsx b/packages/app/src/Element/CashuNuts.tsx index 8674657b..4e124e38 100644 --- a/packages/app/src/Element/CashuNuts.tsx +++ b/packages/app/src/Element/CashuNuts.tsx @@ -26,7 +26,7 @@ export default function CashuNuts({ token }: { token: string }) { e.stopPropagation(); const lnurl = profile?.lud16 ?? ""; const url = `https://redeem.cashu.me?token=${encodeURIComponent(token)}&lightning=${encodeURIComponent( - lnurl + lnurl, )}&autopay=yes`; window.open(url, "_blank"); } diff --git a/packages/app/src/Element/FollowButton.tsx b/packages/app/src/Element/FollowButton.tsx index 64a1dc38..c2cb0e54 100644 --- a/packages/app/src/Element/FollowButton.tsx +++ b/packages/app/src/Element/FollowButton.tsx @@ -34,7 +34,7 @@ export default function FollowButton(props: FollowButtonProps) { if (publisher) { const ev = await publisher.contactList( follows.item.filter(a => a !== pubkey), - relays.item + relays.item, ); System.BroadcastEvent(ev); } diff --git a/packages/app/src/Element/Nip5Service.tsx b/packages/app/src/Element/Nip5Service.tsx index 19318901..5a389c44 100644 --- a/packages/app/src/Element/Nip5Service.tsx +++ b/packages/app/src/Element/Nip5Service.tsx @@ -261,9 +261,7 @@ export default function Nip5Service(props: Nip05ServiceProps) { />  @  )} diff --git a/packages/app/src/Element/Note.tsx b/packages/app/src/Element/Note.tsx index db857738..85deb9dc 100644 --- a/packages/app/src/Element/Note.tsx +++ b/packages/app/src/Element/Note.tsx @@ -135,7 +135,7 @@ export function NoteInner(props: NoteProps) { { [Reaction.Positive]: [] as TaggedNostrEvent[], [Reaction.Negative]: [] as TaggedNostrEvent[], - } + }, ); return { [Reaction.Positive]: dedupeByPubkey(result[Reaction.Positive]), @@ -150,7 +150,7 @@ export function NoteInner(props: NoteProps) { ...getReactions(related, ev.id, EventKind.TextNote).filter(e => e.tags.some(tagFilterOfTextRepost(e, ev.id))), ...getReactions(related, ev.id, EventKind.Repost), ]), - [related, ev] + [related, ev], ); const zaps = useMemo(() => { const sortedZaps = getReactions(related, ev.id, EventKind.ZapReceipt) @@ -241,7 +241,7 @@ export function NoteInner(props: NoteProps) { function goToEvent( e: React.MouseEvent, eTarget: TaggedNostrEvent, - isTargetAllowed: boolean = e.target === e.currentTarget + isTargetAllowed: boolean = e.target === e.currentTarget, ) { if (!isTargetAllowed || opt?.canClick === false) { return; diff --git a/packages/app/src/Element/NoteCreator.css b/packages/app/src/Element/NoteCreator.css index e879558e..44417017 100644 --- a/packages/app/src/Element/NoteCreator.css +++ b/packages/app/src/Element/NoteCreator.css @@ -2,7 +2,8 @@ border: 1px solid transparent; border-radius: 12px; box-shadow: 0px 0px 6px 1px rgba(182, 108, 156, 0.3); - background: linear-gradient(var(--gray-superdark), var(--gray-superdark)) padding-box, + background: + linear-gradient(var(--gray-superdark), var(--gray-superdark)) padding-box, linear-gradient(90deg, #ef9644, #fd7c49, #ff5e58, #ff3b70, #ff088e, #eb00b1, #c31ed5, #7b41f6) border-box; } diff --git a/packages/app/src/Element/NoteCreator.tsx b/packages/app/src/Element/NoteCreator.tsx index d9a5ab85..3ea2e7dd 100644 --- a/packages/app/src/Element/NoteCreator.tsx +++ b/packages/app/src/Element/NoteCreator.tsx @@ -76,8 +76,8 @@ export function NoteCreator() { setError( formatMessage({ defaultMessage: "Invalid LNURL", - }) - ) + }), + ), ); return; } @@ -256,9 +256,9 @@ export function NoteCreator() { ? false : // otherwise return selectedCustomRelays with target relay added / removed a.filter(el => - el === r ? e.target.checked : !selectedCustomRelays || selectedCustomRelays.includes(el) - ) - ) + el === r ? e.target.checked : !selectedCustomRelays || selectedCustomRelays.includes(el), + ), + ), ) } /> diff --git a/packages/app/src/Element/NoteFooter.tsx b/packages/app/src/Element/NoteFooter.tsx index 6e74f1f1..068c8ac5 100644 --- a/packages/app/src/Element/NoteFooter.tsx +++ b/packages/app/src/Element/NoteFooter.tsx @@ -71,7 +71,7 @@ export default function NoteFooter(props: NoteFooterProps) { }, { captureEvent: true, - } + }, ); function hasReacted(emoji: string) { diff --git a/packages/app/src/Element/NoteReaction.tsx b/packages/app/src/Element/NoteReaction.tsx index b639b951..a2992c7f 100644 --- a/packages/app/src/Element/NoteReaction.tsx +++ b/packages/app/src/Element/NoteReaction.tsx @@ -46,7 +46,7 @@ export default function NoteReaction(props: NoteReactionProps) { * Some clients embed the reposted note in the content */ function extractRoot() { - if(!inView) return null; + if (!inView) return null; if (ev?.kind === EventKind.Repost && ev.content.length > 0 && ev.content !== "#[0]") { try { const r: NostrEvent = JSON.parse(ev.content); @@ -66,7 +66,7 @@ export default function NoteReaction(props: NoteReactionProps) { const root = useMemo(() => extractRoot(), [ev, props.root, inView]); if (!inView) { - return (
    ) + return
    ; } const isOpMuted = root && isMuted(root.pubkey); const shouldNotBeRendered = isOpMuted || root?.kind !== EventKind.TextNote; diff --git a/packages/app/src/Element/Poll.tsx b/packages/app/src/Element/Poll.tsx index a72cf2cd..d1c163db 100644 --- a/packages/app/src/Element/Poll.tsx +++ b/packages/app/src/Element/Poll.tsx @@ -47,15 +47,15 @@ export default function Poll(props: PollProps) { }, { amount, - } - ) + }, + ), ); } setVoting(opt); const r = Object.keys(relays.item); const zap = await publisher.zap(amount * 1000, props.ev.pubkey, r, props.ev.id, undefined, eb => - eb.tag(["poll_option", opt.toString()]) + eb.tag(["poll_option", opt.toString()]), ); const lnurl = props.ev.tags.find(a => a[0] === "zap")?.[1] || pollerProfile?.lud16 || pollerProfile?.lud06; @@ -68,7 +68,7 @@ export default function Poll(props: PollProps) { throw new Error( formatMessage({ defaultMessage: "Can't vote because LNURL service does not support zaps", - }) + }), ); } @@ -85,7 +85,7 @@ export default function Poll(props: PollProps) { setError( formatMessage({ defaultMessage: "Failed to send vote", - }) + }), ); } } finally { diff --git a/packages/app/src/Element/PubkeyList.tsx b/packages/app/src/Element/PubkeyList.tsx index 17648433..8a5d14df 100644 --- a/packages/app/src/Element/PubkeyList.tsx +++ b/packages/app/src/Element/PubkeyList.tsx @@ -34,7 +34,7 @@ export default function PubkeyList({ ev, className }: { ev: NostrEvent; classNam pk, Object.keys(login.relays.item), undefined, - `Zap from ${hexToBech32("note", ev.id)}` + `Zap from ${hexToBech32("note", ev.id)}`, ); const invoice = await svc.getInvoice(amtSend, undefined, zap); if (invoice.pr) { diff --git a/packages/app/src/Element/ReBroadcaster.tsx b/packages/app/src/Element/ReBroadcaster.tsx index b3ad9dab..53029aeb 100644 --- a/packages/app/src/Element/ReBroadcaster.tsx +++ b/packages/app/src/Element/ReBroadcaster.tsx @@ -55,9 +55,9 @@ export function ReBroadcaster() { ? false : // otherwise return selectedCustomRelays with target relay added / removed a.filter(el => - el === r ? e.target.checked : !selectedCustomRelays || selectedCustomRelays.includes(el) - ) - ) + el === r ? e.target.checked : !selectedCustomRelays || selectedCustomRelays.includes(el), + ), + ), ) } /> diff --git a/packages/app/src/Element/Reactions.tsx b/packages/app/src/Element/Reactions.tsx index cd755b26..076089c2 100644 --- a/packages/app/src/Element/Reactions.tsx +++ b/packages/app/src/Element/Reactions.tsx @@ -60,7 +60,7 @@ const Reactions = ({ show, setShow, positive, negative, reposts, zaps }: Reactio value: 3, }, ] - : [] + : [], ); const [tab, setTab] = useState(tabs[0]); diff --git a/packages/app/src/Element/Relay.tsx b/packages/app/src/Element/Relay.tsx index 1fca237a..0de4d288 100644 --- a/packages/app/src/Element/Relay.tsx +++ b/packages/app/src/Element/Relay.tsx @@ -22,7 +22,7 @@ export default function Relay(props: RelayProps) { const navigate = useNavigate(); const login = useLogin(); const relaySettings = unwrap( - login.relays.item[props.addr] ?? System.Sockets.find(a => a.address === props.addr)?.settings ?? {} + login.relays.item[props.addr] ?? System.Sockets.find(a => a.address === props.addr)?.settings ?? {}, ); const state = useRelayState(props.addr); const name = useMemo(() => getRelayName(props.addr), [props.addr]); @@ -34,7 +34,7 @@ export default function Relay(props: RelayProps) { ...login.relays.item, [props.addr]: o, }, - unixNowMs() + unixNowMs(), ); } diff --git a/packages/app/src/Element/SendSats.tsx b/packages/app/src/Element/SendSats.tsx index 368095d9..1c0823f0 100644 --- a/packages/app/src/Element/SendSats.tsx +++ b/packages/app/src/Element/SendSats.tsx @@ -285,7 +285,7 @@ export default function SendSats(props: SendSatsProps) { {makeTab(ZapType.AnonZap, )} {makeTab( ZapType.NonZap, - + , )} diff --git a/packages/app/src/Element/Thread.tsx b/packages/app/src/Element/Thread.tsx index ee6512b5..73532db7 100644 --- a/packages/app/src/Element/Thread.tsx +++ b/packages/app/src/Element/Thread.tsx @@ -243,7 +243,7 @@ export default function Thread() { if (t?.root?.key === "a" && t?.root?.value) { const parsed = t.root.value.split(":"); replyTo = thread.data?.find( - a => a.kind === Number(parsed[0]) && a.pubkey === parsed[1] && findTag(a, "d") === parsed[2] + a => a.kind === Number(parsed[0]) && a.pubkey === parsed[1] && findTag(a, "d") === parsed[2], )?.id; } if (replyTo) { @@ -264,7 +264,7 @@ export default function Thread() { thread.data?.find( ne => ne.id === currentId || - (link.type === NostrPrefix.Address && findTag(ne, "d") === currentId && ne.pubkey === link.author) + (link.type === NostrPrefix.Address && findTag(ne, "d") === currentId && ne.pubkey === link.author), ) ?? (location.state && "sig" in location.state ? (location.state as TaggedNostrEvent) : undefined); if (currentNote) { const currentThread = EventExt.extractThread(currentNote); @@ -280,7 +280,7 @@ export default function Thread() { if (replyTo.key === "a" && replyTo.value) { const parsed = replyTo.value.split(":"); return thread.data?.find( - a => a.kind === Number(parsed[0]) && a.pubkey === parsed[1] && findTag(a, "d") === parsed[2] + a => a.kind === Number(parsed[0]) && a.pubkey === parsed[1] && findTag(a, "d") === parsed[2], ); } if (replyTo.value) { @@ -348,7 +348,7 @@ export default function Thread() { notes={replies} related={getAllReactions( thread.data, - replies.map(a => a.id) + replies.map(a => a.id), )} chains={chains} onNavigate={navigateThread} diff --git a/packages/app/src/Element/Timeline.tsx b/packages/app/src/Element/Timeline.tsx index acdd1221..0b5c545a 100644 --- a/packages/app/src/Element/Timeline.tsx +++ b/packages/app/src/Element/Timeline.tsx @@ -47,7 +47,7 @@ const Timeline = (props: TimelineProps) => { ?.filter(a => (props.postsOnly ? !a.tags.some(b => b[0] === "e") : true)) .filter(a => props.ignoreModeration || !isMuted(a.pubkey)); }, - [props.postsOnly, muted, props.ignoreModeration] + [props.postsOnly, muted, props.ignoreModeration], ); const mainFeed = useMemo(() => { @@ -60,7 +60,7 @@ const Timeline = (props: TimelineProps) => { (id: u256) => { return (feed.related ?? []).filter(a => findTag(a, "e") === id); }, - [feed.related] + [feed.related], ); const liveStreams = useMemo(() => { return (feed.main ?? []).filter(a => a.kind === EventKind.LiveEvent && findTag(a, "status") === "live"); diff --git a/packages/app/src/Element/TimelineFollows.tsx b/packages/app/src/Element/TimelineFollows.tsx index 8866a285..c9649968 100644 --- a/packages/app/src/Element/TimelineFollows.tsx +++ b/packages/app/src/Element/TimelineFollows.tsx @@ -28,11 +28,11 @@ const TimelineFollows = (props: TimelineFollowsProps) => { const [latest, setLatest] = useState(unixNow()); const feed = useSyncExternalStore( cb => FollowsFeed.hook(cb, "*"), - () => FollowsFeed.snapshot() + () => FollowsFeed.snapshot(), ); const reactions = useReactions( "follows-feed-reactions", - feed.map(a => a.id) + feed.map(a => a.id), ); const system = useContext(SnortContext); const login = useLogin(); @@ -48,7 +48,7 @@ const TimelineFollows = (props: TimelineFollowsProps) => { ?.filter(a => (props.postsOnly ? !a.tags.some(b => b[0] === "e") : true)) .filter(a => !isMuted(a.pubkey) && login.follows.item.includes(a.pubkey)); }, - [props.postsOnly, muted, login.follows.timestamp] + [props.postsOnly, muted, login.follows.timestamp], ); const mainFeed = useMemo(() => { @@ -63,7 +63,7 @@ const TimelineFollows = (props: TimelineFollowsProps) => { (id: u256) => { return (reactions?.data ?? []).filter(a => findTag(a, "e") === id); }, - [reactions] + [reactions], ); const liveStreams = useMemo(() => { diff --git a/packages/app/src/Element/ZapstrEmbed.tsx b/packages/app/src/Element/ZapstrEmbed.tsx index 844b5b67..fa112e8a 100644 --- a/packages/app/src/Element/ZapstrEmbed.tsx +++ b/packages/app/src/Element/ZapstrEmbed.tsx @@ -17,7 +17,7 @@ export default function ZapstrEmbed({ ev }: { ev: NostrEvent }) { ev.tags.find(a => a[0] === "d")?.[1] ?? "", undefined, ev.kind, - ev.pubkey + ev.pubkey, ); return ( <> diff --git a/packages/app/src/Feed/BadgesFeed.ts b/packages/app/src/Feed/BadgesFeed.ts index d6905c5d..d180f160 100644 --- a/packages/app/src/Feed/BadgesFeed.ts +++ b/packages/app/src/Feed/BadgesFeed.ts @@ -23,7 +23,7 @@ export default function useProfileBadges(pubkey?: HexKey) { if (profileBadges.data) { return chunks( profileBadges.data.tags.filter(t => t[0] === "a" || t[0] === "e"), - 2 + 2, ).reduce((acc, [a, e]) => { return { ...acc, @@ -44,7 +44,7 @@ export default function useProfileBadges(pubkey?: HexKey) { } return acc; }, - { pubkeys: [], ds: [] } as BadgeAwards + { pubkeys: [], ds: [] } as BadgeAwards, ) as BadgeAwards; }, [profile]); @@ -77,7 +77,7 @@ export default function useProfileBadges(pubkey?: HexKey) { }) .filter( ({ award, badge }) => - badge && award.pubkey === badge.pubkey && award.tags.find(t => t[0] === "p" && t[1] === pubkey) + badge && award.pubkey === badge.pubkey && award.tags.find(t => t[0] === "p" && t[1] === pubkey), ) .map(({ badge }) => unwrap(badge)); } diff --git a/packages/app/src/Feed/FeedReactions.ts b/packages/app/src/Feed/FeedReactions.ts index cd32b57a..5faaeb00 100644 --- a/packages/app/src/Feed/FeedReactions.ts +++ b/packages/app/src/Feed/FeedReactions.ts @@ -13,7 +13,7 @@ export function useReactions(subId: string, ids: Array, others?: (rb: Re .kinds( pref.enableReactions ? [EventKind.Reaction, EventKind.Repost, EventKind.ZapReceipt] - : [EventKind.ZapReceipt, EventKind.Repost] + : [EventKind.ZapReceipt, EventKind.Repost], ) .tag("e", ids); } diff --git a/packages/app/src/Feed/FollowersFeed.ts b/packages/app/src/Feed/FollowersFeed.ts index 84544a29..e8b5c289 100644 --- a/packages/app/src/Feed/FollowersFeed.ts +++ b/packages/app/src/Feed/FollowersFeed.ts @@ -14,7 +14,7 @@ export default function useFollowersFeed(pubkey?: HexKey) { const followers = useMemo(() => { const contactLists = followersFeed.data?.filter( - a => a.kind === EventKind.ContactList && a.tags.some(b => b[0] === "p" && b[1] === pubkey) + a => a.kind === EventKind.ContactList && a.tags.some(b => b[0] === "p" && b[1] === pubkey), ); return [...new Set(contactLists?.map(a => a.pubkey))]; }, [followersFeed, pubkey]); diff --git a/packages/app/src/Feed/LoginFeed.ts b/packages/app/src/Feed/LoginFeed.ts index 8eceb79f..f1eda0d6 100644 --- a/packages/app/src/Feed/LoginFeed.ts +++ b/packages/app/src/Feed/LoginFeed.ts @@ -85,7 +85,7 @@ export default function useLoginFeed() { Nip4Chats.onEvent(loginFeed.data); const subs = loginFeed.data.filter( - a => a.kind === EventKind.SnortSubscriptions && a.pubkey === bech32ToHex(SnortPubKey) + a => a.kind === EventKind.SnortSubscriptions && a.pubkey === bech32ToHex(SnortPubKey), ); Promise.all( subs.map(async a => { @@ -97,7 +97,7 @@ export default function useLoginFeed() { ...ex, } as SubscriptionEvent; } - }) + }), ).then(a => addSubscription(login, ...a.filter(a => a !== undefined).map(unwrap))); } }, [loginFeed, publisher]); @@ -106,7 +106,7 @@ export default function useLoginFeed() { useEffect(() => { if (loginFeed.data) { const replies = loginFeed.data.filter( - a => a.kind === EventKind.TextNote && !isMuted(a.pubkey) && a.created_at > readNotifications + a => a.kind === EventKind.TextNote && !isMuted(a.pubkey) && a.created_at > readNotifications, ); replies.forEach(async nx => { const n = await makeNotification(nx); diff --git a/packages/app/src/Feed/ThreadFeed.ts b/packages/app/src/Feed/ThreadFeed.ts index 6603bd2d..8fe289a2 100644 --- a/packages/app/src/Feed/ThreadFeed.ts +++ b/packages/app/src/Feed/ThreadFeed.ts @@ -35,11 +35,11 @@ export default function useThreadFeed(link: NostrLink) { .kinds( pref.enableReactions ? [EventKind.Reaction, EventKind.TextNote, EventKind.Repost, EventKind.ZapReceipt] - : [EventKind.TextNote, EventKind.ZapReceipt, EventKind.Repost] + : [EventKind.TextNote, EventKind.ZapReceipt, EventKind.Repost], ) .tag( "e", - allEvents.map(a => a.id) + allEvents.map(a => a.id), ); } if (trackingATags.length > 0) { @@ -50,7 +50,7 @@ export default function useThreadFeed(link: NostrLink) { .authors(parsed.map(a => a[1])) .tag( "d", - parsed.map(a => a[2]) + parsed.map(a => a[2]), ); sub.withFilter().tag("a", trackingATags); } @@ -85,7 +85,7 @@ export default function useThreadFeed(link: NostrLink) { id: b[1], relay: b[2], }; - }) + }), ) .flat(); const eTagsMissing = eTags.filter(a => !mainNotes.some(b => b.id === a.id)); diff --git a/packages/app/src/Feed/TimelineFeed.ts b/packages/app/src/Feed/TimelineFeed.ts index ea45ede4..6feb29da 100644 --- a/packages/app/src/Feed/TimelineFeed.ts +++ b/packages/app/src/Feed/TimelineFeed.ts @@ -43,7 +43,7 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel .kinds( subject.type === "profile_keyword" ? [EventKind.SetMetadata] - : [EventKind.TextNote, EventKind.Repost, EventKind.Polls] + : [EventKind.TextNote, EventKind.Repost, EventKind.Polls], ); if (subject.relay) { @@ -149,7 +149,7 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel .map(a => unwrap(a)[1]); const repostsByKind1 = main.data .filter( - a => (a.kind === EventKind.Repost || a.kind === EventKind.TextNote) && a.tags.some(tagFilterOfTextRepost(a)) + a => (a.kind === EventKind.Repost || a.kind === EventKind.TextNote) && a.tags.some(tagFilterOfTextRepost(a)), ) .map(a => a.tags.find(tagFilterOfTextRepost(a))) .filter(a => a) diff --git a/packages/app/src/Hooks/useImgProxy.ts b/packages/app/src/Hooks/useImgProxy.ts index 090fa4f4..2f1a04cd 100644 --- a/packages/app/src/Hooks/useImgProxy.ts +++ b/packages/app/src/Hooks/useImgProxy.ts @@ -21,7 +21,7 @@ export default function useImgProxy() { const result = hmacSha256( utils.hexToBytes(unwrap(settings).key), utils.hexToBytes(unwrap(settings).salt), - te.encode(u) + te.encode(u), ); return urlSafe(base64.encode(result)); } diff --git a/packages/app/src/Hooks/useInteractionCache.tsx b/packages/app/src/Hooks/useInteractionCache.tsx index ab73a99c..64cc8122 100644 --- a/packages/app/src/Hooks/useInteractionCache.tsx +++ b/packages/app/src/Hooks/useInteractionCache.tsx @@ -15,7 +15,7 @@ export function useInteractionCache(pubkey?: HexKey, event?: u256) { const data = useSyncExternalStore( c => InteractionCache.hook(c, id), - () => InteractionCache.snapshot().find(a => a.id === id) + () => InteractionCache.snapshot().find(a => a.id === id), ) || EmptyInteraction; return { data: data, diff --git a/packages/app/src/Hooks/useLogin.tsx b/packages/app/src/Hooks/useLogin.tsx index b4ffedff..cf3a205e 100644 --- a/packages/app/src/Hooks/useLogin.tsx +++ b/packages/app/src/Hooks/useLogin.tsx @@ -4,6 +4,6 @@ import { useSyncExternalStore } from "react"; export default function useLogin() { return useSyncExternalStore( s => LoginStore.hook(s), - () => LoginStore.snapshot() + () => LoginStore.snapshot(), ); } diff --git a/packages/app/src/Login/MultiAccountStore.ts b/packages/app/src/Login/MultiAccountStore.ts index 3c35ec22..1b25a66c 100644 --- a/packages/app/src/Login/MultiAccountStore.ts +++ b/packages/app/src/Login/MultiAccountStore.ts @@ -96,7 +96,7 @@ export class MultiAccountStore extends ExternalStore { type: LoginSessionType, relays?: Record, remoteSignerRelays?: Array, - privateKey?: string + privateKey?: string, ) { if (this.#accounts.has(key)) { throw new Error("Already logged in with this pubkey"); diff --git a/packages/app/src/Nip05/ServiceProvider.ts b/packages/app/src/Nip05/ServiceProvider.ts index 610d4ed5..eda71371 100644 --- a/packages/app/src/Nip05/ServiceProvider.ts +++ b/packages/app/src/Nip05/ServiceProvider.ts @@ -97,7 +97,7 @@ export class ServiceProvider { path: string, method?: "GET" | string, body?: unknown, - headers?: { [key: string]: string } + headers?: { [key: string]: string }, ): Promise { try { const rsp = await fetch(`${this.url}${path}`, { diff --git a/packages/app/src/Nip05/SnortServiceProvider.ts b/packages/app/src/Nip05/SnortServiceProvider.ts index d6efa314..f4de626a 100644 --- a/packages/app/src/Nip05/SnortServiceProvider.ts +++ b/packages/app/src/Nip05/SnortServiceProvider.ts @@ -54,7 +54,7 @@ export default class SnortServiceProvider extends ServiceProvider { path: string, method?: "GET" | string, body?: unknown, - headers?: { [key: string]: string } + headers?: { [key: string]: string }, ): Promise { const auth = await this.#publisher.generic(eb => { eb.kind(EventKind.HttpAuthentication); diff --git a/packages/app/src/Pages/ErrorPage.tsx b/packages/app/src/Pages/ErrorPage.tsx index 687c068d..b3fcb541 100644 --- a/packages/app/src/Pages/ErrorPage.tsx +++ b/packages/app/src/Pages/ErrorPage.tsx @@ -25,7 +25,7 @@ const ErrorPage = () => { {JSON.stringify( error instanceof Error ? { name: error.name, message: error.message, stack: error.stack } : error, undefined, - " " + " ", )} } diff --git a/packages/app/src/Pages/Layout.tsx b/packages/app/src/Pages/Layout.tsx index 1df8c604..a428f941 100644 --- a/packages/app/src/Pages/Layout.tsx +++ b/packages/app/src/Pages/Layout.tsx @@ -89,7 +89,7 @@ export default function Layout() { useEffect(() => { const osTheme = window.matchMedia("(prefers-color-scheme: light)"); setTheme( - preferences.theme === "system" && osTheme.matches ? "light" : preferences.theme === "light" ? "light" : "dark" + preferences.theme === "system" && osTheme.matches ? "light" : preferences.theme === "light" ? "light" : "dark", ); osTheme.onchange = e => { @@ -174,7 +174,7 @@ const AccountHeader = () => { const hasNotifications = useMemo( () => latestNotification > readNotifications, - [latestNotification, readNotifications] + [latestNotification, readNotifications], ); const unreadDms = useMemo(() => (publicKey ? 0 : 0), [publicKey]); diff --git a/packages/app/src/Pages/LoginPage.tsx b/packages/app/src/Pages/LoginPage.tsx index ba004d7b..07401626 100644 --- a/packages/app/src/Pages/LoginPage.tsx +++ b/packages/app/src/Pages/LoginPage.tsx @@ -55,7 +55,7 @@ const Artwork: Array = [ export async function getNip05PubKey(addr: string): Promise { const [username, domain] = addr.split("@"); const rsp = await fetch( - `https://${domain}/.well-known/nostr.json?name=${encodeURIComponent(username.toLocaleLowerCase())}` + `https://${domain}/.well-known/nostr.json?name=${encodeURIComponent(username.toLocaleLowerCase())}`, ); if (rsp.ok) { const data = await rsp.json(); @@ -103,7 +103,7 @@ export default function LoginPage() { setError( formatMessage({ defaultMessage: "Unknown login error", - }) + }), ); } console.error(e); diff --git a/packages/app/src/Pages/Notifications.tsx b/packages/app/src/Pages/Notifications.tsx index 7af56025..23cefcd7 100644 --- a/packages/app/src/Pages/Notifications.tsx +++ b/packages/app/src/Pages/Notifications.tsx @@ -75,7 +75,7 @@ export default function NotificationsPage() { const notifications = useSyncExternalStore( c => Notifications.hook(c, "*"), - () => Notifications.snapshot() + () => Notifications.snapshot(), ); const timeKey = (ev: NostrEvent) => { @@ -119,7 +119,7 @@ function NotificationGroup({ evs }: { evs: Array }) { return zap.anonZap ? "anon" : zap.sender ?? a.pubkey; } return a.pubkey; - }) + }), ); const firstPubkey = pubkeys[0]; const firstPubkeyProfile = useUserProfile(inView ? (firstPubkey === "anon" ? "" : firstPubkey) : ""); @@ -213,7 +213,7 @@ function NotificationGroup({ evs }: { evs: Array }) { pubkeys.length - 1, firstPubkey === "anon" ? formatMessage({ defaultMessage: "Anon" }) - : getDisplayName(firstPubkeyProfile, firstPubkey) + : getDisplayName(firstPubkeyProfile, firstPubkey), )} )} diff --git a/packages/app/src/Pages/ProfilePage.tsx b/packages/app/src/Pages/ProfilePage.tsx index 434b816b..b5655eec 100644 --- a/packages/app/src/Pages/ProfilePage.tsx +++ b/packages/app/src/Pages/ProfilePage.tsx @@ -221,7 +221,7 @@ export default function ProfilePage() { } as { [key: string]: Tab }; const [tab, setTab] = useState(ProfileTab.Notes); const optionalTabs = [ProfileTab.Zaps, ProfileTab.Relays, ProfileTab.Bookmarks, ProfileTab.Muted].filter(a => - unwrap(a) + unwrap(a), ) as Tab[]; const horizontalScroll = useHorizontalScroll(); @@ -433,7 +433,7 @@ export default function ProfilePage() { type: TLVEntryType.Author, length: 64, value: id, - })}` + })}`, ) }> diff --git a/packages/app/src/Pages/ZapPool.tsx b/packages/app/src/Pages/ZapPool.tsx index f6703ee3..26664371 100644 --- a/packages/app/src/Pages/ZapPool.tsx +++ b/packages/app/src/Pages/ZapPool.tsx @@ -73,7 +73,7 @@ export default function ZapPoolPage() { const login = useLogin(); const zapPool = useSyncExternalStore( c => ZapPoolController.hook(c), - () => ZapPoolController.snapshot() + () => ZapPoolController.snapshot(), ); const { wallet } = useWallet(); diff --git a/packages/app/src/Pages/settings/Accounts.tsx b/packages/app/src/Pages/settings/Accounts.tsx index 9ad66696..ce113010 100644 --- a/packages/app/src/Pages/settings/Accounts.tsx +++ b/packages/app/src/Pages/settings/Accounts.tsx @@ -27,7 +27,7 @@ export default function AccountsPage() { setError( formatMessage({ defaultMessage: "Unknown login error", - }) + }), ); } console.error(e); diff --git a/packages/app/src/Pages/settings/handle/LNAddress.tsx b/packages/app/src/Pages/settings/handle/LNAddress.tsx index 562dc571..0cad952a 100644 --- a/packages/app/src/Pages/settings/handle/LNAddress.tsx +++ b/packages/app/src/Pages/settings/handle/LNAddress.tsx @@ -29,7 +29,7 @@ export default function LNForwardAddress({ handle }: { handle: ManageHandle }) { setError( formatMessage({ defaultMessage: "Invalid LNURL", - }) + }), ); return; } diff --git a/packages/app/src/Pages/settings/wallet/Cashu.tsx b/packages/app/src/Pages/settings/wallet/Cashu.tsx index 5a2f4efa..e1a6d411 100644 --- a/packages/app/src/Pages/settings/wallet/Cashu.tsx +++ b/packages/app/src/Pages/settings/wallet/Cashu.tsx @@ -39,7 +39,7 @@ const ConnectCashu = () => { setError( formatMessage({ defaultMessage: "Unknown error", - }) + }), ); } } diff --git a/packages/app/src/Pages/settings/wallet/LNC.tsx b/packages/app/src/Pages/settings/wallet/LNC.tsx index d33fa9fe..64c66d21 100644 --- a/packages/app/src/Pages/settings/wallet/LNC.tsx +++ b/packages/app/src/Pages/settings/wallet/LNC.tsx @@ -32,7 +32,7 @@ const ConnectLNC = () => { setError( formatMessage({ defaultMessage: "Unknown error", - }) + }), ); } } diff --git a/packages/app/src/Pages/settings/wallet/LNDHub.tsx b/packages/app/src/Pages/settings/wallet/LNDHub.tsx index b33462ec..d2c20f44 100644 --- a/packages/app/src/Pages/settings/wallet/LNDHub.tsx +++ b/packages/app/src/Pages/settings/wallet/LNDHub.tsx @@ -37,7 +37,7 @@ const ConnectLNDHub = () => { setError( formatMessage({ defaultMessage: "Unknown error", - }) + }), ); } } diff --git a/packages/app/src/Pages/settings/wallet/NWC.tsx b/packages/app/src/Pages/settings/wallet/NWC.tsx index 921d6c59..41419615 100644 --- a/packages/app/src/Pages/settings/wallet/NWC.tsx +++ b/packages/app/src/Pages/settings/wallet/NWC.tsx @@ -37,7 +37,7 @@ const ConnectNostrWallet = () => { setError( formatMessage({ defaultMessage: "Unknown error", - }) + }), ); } } diff --git a/packages/app/src/SnortApi.ts b/packages/app/src/SnortApi.ts index a69f71e6..f29a529e 100644 --- a/packages/app/src/SnortApi.ts +++ b/packages/app/src/SnortApi.ts @@ -88,7 +88,7 @@ export default class SnortApi { path: string, method?: "GET" | string, body?: { [key: string]: string }, - headers?: { [key: string]: string } + headers?: { [key: string]: string }, ): Promise { if (!this.#publisher) { throw new Error("Publisher not set"); @@ -110,7 +110,7 @@ export default class SnortApi { path: string, method?: "GET" | string, body?: { [key: string]: string }, - headers?: { [key: string]: string } + headers?: { [key: string]: string }, ): Promise { const rsp = await fetch(`${this.#url}${path}`, { method: method, diff --git a/packages/app/src/SnortUtils/index.ts b/packages/app/src/SnortUtils/index.ts index 242b516f..65062b2a 100644 --- a/packages/app/src/SnortUtils/index.ts +++ b/packages/app/src/SnortUtils/index.ts @@ -50,7 +50,7 @@ export async function openFile(): Promise { } }, 300); }, - { once: true } + { once: true }, ); }); } @@ -209,7 +209,7 @@ export function dedupeByPubkey(events: TaggedNostrEvent[]) { list: [...list, ev], }; }, - { list: [], seen: new Set([]) } + { list: [], seen: new Set([]) }, ); return deduped.list as TaggedNostrEvent[]; } @@ -226,7 +226,7 @@ export function dedupeById(events: Array) { list: [...list, ev], }; }, - { list: [], seen: new Set([]) } + { list: [], seen: new Set([]) }, ); return deduped.list as Array; } @@ -487,7 +487,7 @@ export function kvToObject(o: string, sep?: string) { return [match[1], match[2]]; } return []; - }) + }), ) as T; } diff --git a/packages/app/src/Toaster.tsx b/packages/app/src/Toaster.tsx index eecb85ca..1d45a177 100644 --- a/packages/app/src/Toaster.tsx +++ b/packages/app/src/Toaster.tsx @@ -39,7 +39,7 @@ export const Toastore = new ToasterSlots(); export default function Toaster() { const toast = useSyncExternalStore( c => Toastore.hook(c), - () => Toastore.snapshot() + () => Toastore.snapshot(), ); return ( diff --git a/packages/app/src/Upload/VoidCat.ts b/packages/app/src/Upload/VoidCat.ts index 8eb8a11a..0fe5e010 100644 --- a/packages/app/src/Upload/VoidCat.ts +++ b/packages/app/src/Upload/VoidCat.ts @@ -12,7 +12,7 @@ import { magnetURIDecode } from "SnortUtils"; export default async function VoidCatUpload( file: File | Blob, filename: string, - publisher?: EventPublisher + publisher?: EventPublisher, ): Promise { const api = new VoidApi(VoidCatHost); const uploader = api.getUploader(file); diff --git a/packages/app/src/Wallet/Cashu.ts b/packages/app/src/Wallet/Cashu.ts index 541e65ba..b88dc13c 100644 --- a/packages/app/src/Wallet/Cashu.ts +++ b/packages/app/src/Wallet/Cashu.ts @@ -57,6 +57,6 @@ export interface NutStashBackup { mints: [ { mintURL: string; - } + }, ]; } diff --git a/packages/app/src/Wallet/LNCWallet.ts b/packages/app/src/Wallet/LNCWallet.ts index 342dfec8..6093289e 100644 --- a/packages/app/src/Wallet/LNCWallet.ts +++ b/packages/app/src/Wallet/LNCWallet.ts @@ -126,7 +126,7 @@ export class LNCWallet implements LNWallet { err => { this.#log(err); reject(err); - } + }, ); }); } diff --git a/packages/app/src/Wallet/NostrWalletConnect.ts b/packages/app/src/Wallet/NostrWalletConnect.ts index d2d1113d..5930e60d 100644 --- a/packages/app/src/Wallet/NostrWalletConnect.ts +++ b/packages/app/src/Wallet/NostrWalletConnect.ts @@ -182,7 +182,7 @@ export class NostrConnectWallet implements LNWallet { ], () => { // ignored - } + }, ); await this.#conn.SendAsync(evCommand); return await new Promise((resolve, reject) => { diff --git a/packages/app/src/Wallet/WebLN.ts b/packages/app/src/Wallet/WebLN.ts index e9708277..68db4212 100644 --- a/packages/app/src/Wallet/WebLN.ts +++ b/packages/app/src/Wallet/WebLN.ts @@ -84,7 +84,7 @@ export class WebLNWallet implements LNWallet { await window.webln?.makeInvoice({ amount: req.amount, defaultMemo: req.memo, - }) + }), ); if (rsp) { const invoice = prToWalletInvoice(rsp.paymentRequest); diff --git a/packages/app/src/Wallet/index.ts b/packages/app/src/Wallet/index.ts index ad8a01dd..cb5b98e0 100644 --- a/packages/app/src/Wallet/index.ts +++ b/packages/app/src/Wallet/index.ts @@ -248,7 +248,7 @@ window.document.addEventListener("close", () => { export function useWallet() { const wallet = useSyncExternalStore( h => Wallets.hook(h), - () => Wallets.snapshot() + () => Wallets.snapshot(), ); useEffect(() => { if (wallet.wallet?.isReady() === false && wallet.wallet.canAutoLogin()) { diff --git a/packages/app/src/ZapPoolController.ts b/packages/app/src/ZapPoolController.ts index 0695b5d7..617933bc 100644 --- a/packages/app/src/ZapPoolController.ts +++ b/packages/app/src/ZapPoolController.ts @@ -54,7 +54,7 @@ class ZapPool extends ExternalStore> { Toastore.push({ element: `Sent ${amtSend.toLocaleString()} sats to ${getDisplayName( profile, - x.pubkey + x.pubkey, )} from your zap pool`, expire: unixNow() + 10, icon: "zap", diff --git a/packages/app/src/benchmarks.ts b/packages/app/src/benchmarks.ts index c56a7415..1b6acb80 100644 --- a/packages/app/src/benchmarks.ts +++ b/packages/app/src/benchmarks.ts @@ -2,104 +2,113 @@ import { bytesToHex } from "@noble/hashes/utils"; import { DefaultQueryOptimizer, FlatReqFilter, QueryOptimizer, ReqFilter } from "@snort/system"; import { compress, expand_filter, flat_merge, get_diff, default as wasmInit } from "@snort/system-query"; import WasmPath from "@snort/system-query/pkg/system_query_bg.wasm"; -import { Bench } from 'tinybench'; +import { Bench } from "tinybench"; const WasmQueryOptimizer = { - expandFilter: (f: ReqFilter) => { - return expand_filter(f) as Array; - }, - getDiff: (prev: Array, next: Array) => { - return get_diff(prev, next) as Array; - }, - flatMerge: (all: Array) => { - return flat_merge(all) as Array; - }, - compress: (all: Array) => { - return compress(all) as Array; - } - } as QueryOptimizer; - - const makeOnePubkey = () => { - const rnd = globalThis.crypto.getRandomValues(new Uint8Array(32)); - return bytesToHex(rnd); - } + expandFilter: (f: ReqFilter) => { + return expand_filter(f) as Array; + }, + getDiff: (prev: Array, next: Array) => { + return get_diff(prev, next) as Array; + }, + flatMerge: (all: Array) => { + return flat_merge(all) as Array; + }, + compress: (all: Array) => { + return compress(all) as Array; + }, +} as QueryOptimizer; - const randomPubkeys = (() => { - const ret = []; - for(let x =0;x<50;x++) { - ret.push(makeOnePubkey()); - } - return ret; - })(); +const makeOnePubkey = () => { + const rnd = globalThis.crypto.getRandomValues(new Uint8Array(32)); + return bytesToHex(rnd); +}; - const testExpand = (q: QueryOptimizer) => { - q.expandFilter({ - kinds: [1,2,3], - authors: randomPubkeys - }); +const randomPubkeys = (() => { + const ret = []; + for (let x = 0; x < 50; x++) { + ret.push(makeOnePubkey()); } - const testGetDiff = (q: QueryOptimizer) => { - q.getDiff([{ + return ret; +})(); + +const testExpand = (q: QueryOptimizer) => { + q.expandFilter({ + kinds: [1, 2, 3], + authors: randomPubkeys, + }); +}; +const testGetDiff = (q: QueryOptimizer) => { + q.getDiff( + [ + { kinds: [1, 2, 3], - authors: randomPubkeys - }], [{ + authors: randomPubkeys, + }, + ], + [ + { kinds: [1, 2, 3, 4, 5], - authors: randomPubkeys - }]); -} + authors: randomPubkeys, + }, + ], + ); +}; const testFlatMerge = (q: QueryOptimizer) => { - q.flatMerge(q.expandFilter({ - kinds: [1, 6, 7, 6969], - authors: randomPubkeys - })); -} + q.flatMerge( + q.expandFilter({ + kinds: [1, 6, 7, 6969], + authors: randomPubkeys, + }), + ); +}; const testCompress = (q: QueryOptimizer) => { - q.compress([ - { - kinds: [1, 6, 7, 6969], - authors: randomPubkeys - }, - { - kinds: [1, 6, 7, 6969], - authors: randomPubkeys - }, - { - kinds: [1, 6, 7, 6969], - authors: randomPubkeys - }, - { - kinds: [1, 6, 7, 6969], - authors: randomPubkeys - }, - { - kinds: [1, 6, 7, 6969], - authors: randomPubkeys - } - ]) -} + q.compress([ + { + kinds: [1, 6, 7, 6969], + authors: randomPubkeys, + }, + { + kinds: [1, 6, 7, 6969], + authors: randomPubkeys, + }, + { + kinds: [1, 6, 7, 6969], + authors: randomPubkeys, + }, + { + kinds: [1, 6, 7, 6969], + authors: randomPubkeys, + }, + { + kinds: [1, 6, 7, 6969], + authors: randomPubkeys, + }, + ]); +}; - const wasmSuite = new Bench({ time: 1_000 }); - const suite = new Bench({ time: 1_000 }); +const wasmSuite = new Bench({ time: 1_000 }); +const suite = new Bench({ time: 1_000 }); - const addTests = (s: Bench, q: QueryOptimizer) => { - s.add("expand", () => testExpand(q)); - s.add("get_diff", () => testGetDiff(q)); - s.add("flat_merge", () => testFlatMerge(q)); - s.add("compress", () => testCompress(q)); - } - - addTests(suite, DefaultQueryOptimizer); - addTests(wasmSuite, WasmQueryOptimizer); - - const runAll = async() => { - await wasmInit(WasmPath); +const addTests = (s: Bench, q: QueryOptimizer) => { + s.add("expand", () => testExpand(q)); + s.add("get_diff", () => testGetDiff(q)); + s.add("flat_merge", () => testFlatMerge(q)); + s.add("compress", () => testCompress(q)); +}; - console.log("DefaultQueryOptimizer"); - await suite.run(); - console.table(suite.table()); +addTests(suite, DefaultQueryOptimizer); +addTests(wasmSuite, WasmQueryOptimizer); - console.log("WasmQueryOptimizer"); - await wasmSuite.run(); - console.table(wasmSuite.table()); - }; - runAll().catch(console.error); \ No newline at end of file +const runAll = async () => { + await wasmInit(WasmPath); + + console.log("DefaultQueryOptimizer"); + await suite.run(); + console.table(suite.table()); + + console.log("WasmQueryOptimizer"); + await wasmSuite.run(); + console.table(wasmSuite.table()); +}; +runAll().catch(console.error); diff --git a/packages/app/src/chat/index.ts b/packages/app/src/chat/index.ts index f29f9122..d6b9326a 100644 --- a/packages/app/src/chat/index.ts +++ b/packages/app/src/chat/index.ts @@ -116,7 +116,7 @@ export function createChatLink(type: ChatType, ...params: Array) { type: TLVEntryType.Author, length: params[0].length, value: params[0], - } as TLVEntry + } as TLVEntry, )}`; } case ChatType.PrivateDirectMessage: { @@ -127,7 +127,7 @@ export function createChatLink(type: ChatType, ...params: Array) { type: TLVEntryType.Author, length: params[0].length, value: params[0], - } as TLVEntry + } as TLVEntry, )}`; } case ChatType.PrivateGroupChat: { @@ -139,8 +139,8 @@ export function createChatLink(type: ChatType, ...params: Array) { type: TLVEntryType.Author, length: a.length, value: a, - } as TLVEntry) - ) + }) as TLVEntry, + ), )}`; } } @@ -161,14 +161,14 @@ export function useNip4Chat() { const { publicKey } = useLogin(); return useSyncExternalStore( c => Nip4Chats.hook(c), - () => Nip4Chats.snapshot(publicKey) + () => Nip4Chats.snapshot(publicKey), ); } export function useNip29Chat() { return useSyncExternalStore( c => Nip29Chats.hook(c), - () => Nip29Chats.snapshot() + () => Nip29Chats.snapshot(), ); } @@ -176,7 +176,7 @@ export function useNip24Chat() { const { publicKey } = useLogin(); return useSyncExternalStore( c => Nip24Chats.hook(c), - () => Nip24Chats.snapshot(publicKey) + () => Nip24Chats.snapshot(publicKey), ); } diff --git a/packages/app/src/chat/nip24.ts b/packages/app/src/chat/nip24.ts index 9344e6fb..6281a6b7 100644 --- a/packages/app/src/chat/nip24.ts +++ b/packages/app/src/chat/nip24.ts @@ -39,8 +39,8 @@ export class Nip24ChatSystem extends ExternalStore> implements ChatS value: v, type: TLVEntryType.Author, length: v.length, - } as TLVEntry) - ) + }) as TLVEntry, + ), ); }; return dedupe(messages.map(a => chatId(a))).map(a => { @@ -72,7 +72,7 @@ export class Nip24ChatSystem extends ExternalStore> implements ChatS } as { t: number; title: string | undefined; - } + }, ); return { type: ChatType.PrivateDirectMessage, diff --git a/packages/app/src/chat/nip29.ts b/packages/app/src/chat/nip29.ts index 956472e7..261f7e86 100644 --- a/packages/app/src/chat/nip29.ts +++ b/packages/app/src/chat/nip29.ts @@ -46,12 +46,12 @@ export class Nip29ChatSystem extends ExternalStore> implements ChatS .map(a => a.tags.find(b => b[0] === "g")) .filter(a => a !== undefined) .map(a => unwrap(a)) - .map(a => `${a[2]}${a[1]}`) + .map(a => `${a[2]}${a[1]}`), ); return groups.map(g => { const [relay, channel] = g.split("/", 2); const messages = allMessages.filter( - a => `${a.tags.find(b => b[0] === "g")?.[2]}${a.tags.find(b => b[0] === "g")?.[1]}` === g + a => `${a.tags.find(b => b[0] === "g")?.[2]}${a.tags.find(b => b[0] === "g")?.[1]}` === g, ); const lastRead = lastReadInChat(g); return { diff --git a/packages/app/src/chat/nip4.ts b/packages/app/src/chat/nip4.ts index c766ba5a..f011d575 100644 --- a/packages/app/src/chat/nip4.ts +++ b/packages/app/src/chat/nip4.ts @@ -34,7 +34,7 @@ export class Nip4ChatSystem extends ExternalStore> implements ChatSy const dms = this.#cache.snapshot(); const dmSince = dms.reduce( (acc, v) => (v.created_at > acc && v.kind === EventKind.DirectMessage ? (acc = v.created_at) : acc), - 0 + 0, ); this.#log("Loading DMS since %s", new Date(dmSince * 1000)); @@ -49,12 +49,15 @@ export class Nip4ChatSystem extends ExternalStore> implements ChatSy listChats(pk: string): Chat[] { const myDms = this.#nip4Events(); - const chats = myDms.reduce((acc, v) => { - const chatId = inChatWith(v, pk); - acc[chatId] ??= []; - acc[chatId].push(v); - return acc; - }, {} as Record>); + const chats = myDms.reduce( + (acc, v) => { + const chatId = inChatWith(v, pk); + acc[chatId] ??= []; + acc[chatId].push(v); + return acc; + }, + {} as Record>, + ); return [...Object.entries(chats)].map(([k, v]) => Nip4ChatSystem.createChatObj(k, v)); } diff --git a/packages/app/src/index.tsx b/packages/app/src/index.tsx index 8435f23c..b86f360c 100644 --- a/packages/app/src/index.tsx +++ b/packages/app/src/index.tsx @@ -9,7 +9,16 @@ import { StrictMode } from "react"; import * as ReactDOM from "react-dom/client"; import { Provider } from "react-redux"; import { createBrowserRouter, RouterProvider } from "react-router-dom"; -import { EventPublisher, NostrSystem, ProfileLoaderService, Nip7Signer, PowWorker, QueryOptimizer, FlatReqFilter, ReqFilter } from "@snort/system"; +import { + EventPublisher, + NostrSystem, + ProfileLoaderService, + Nip7Signer, + PowWorker, + QueryOptimizer, + FlatReqFilter, + ReqFilter, +} from "@snort/system"; import { SnortContext } from "@snort/system-react"; import * as serviceWorkerRegistration from "serviceWorkerRegistration"; @@ -51,7 +60,7 @@ const WasmQueryOptimizer = { }, compress: (all: Array) => { return compress(all) as Array; - } + }, } as QueryOptimizer; /** @@ -193,5 +202,5 @@ root.render( - + , ); diff --git a/packages/app/webpack.config.js b/packages/app/webpack.config.js index 3c8edf01..d002edba 100644 --- a/packages/app/webpack.config.js +++ b/packages/app/webpack.config.js @@ -19,7 +19,7 @@ const config = { import: require.resolve("@snort/system/dist/pow-worker.js"), filename: "pow.js", }, - bench: "./src/benchmarks.ts" + bench: "./src/benchmarks.ts", }, target: "browserslist", mode: isProduction ? "production" : "development", @@ -156,7 +156,7 @@ const config = { extensions: ["...", ".tsx", ".ts", ".jsx", ".js"], modules: ["...", __dirname, path.resolve(__dirname, "src")], fallback: { crypto: false }, - } + }, }; module.exports = () => config; diff --git a/packages/system-query/pkg/package.json b/packages/system-query/pkg/package.json index 79f43f39..74743550 100644 --- a/packages/system-query/pkg/package.json +++ b/packages/system-query/pkg/package.json @@ -11,4 +11,4 @@ "sideEffects": [ "./snippets/*" ] -} \ No newline at end of file +} diff --git a/packages/system-query/pkg/system_query.d.ts b/packages/system-query/pkg/system_query.d.ts index 4fdcb2a9..b1781e0d 100644 --- a/packages/system-query/pkg/system_query.d.ts +++ b/packages/system-query/pkg/system_query.d.ts @@ -1,31 +1,31 @@ /* tslint:disable */ /* eslint-disable */ /** -* @param {any} prev -* @param {any} next -* @returns {any} -*/ + * @param {any} prev + * @param {any} next + * @returns {any} + */ export function diff_filters(prev: any, next: any): any; /** -* @param {any} val -* @returns {any} -*/ + * @param {any} val + * @returns {any} + */ export function expand_filter(val: any): any; /** -* @param {any} prev -* @param {any} next -* @returns {any} -*/ + * @param {any} prev + * @param {any} next + * @returns {any} + */ export function get_diff(prev: any, next: any): any; /** -* @param {any} val -* @returns {any} -*/ + * @param {any} val + * @returns {any} + */ export function flat_merge(val: any): any; /** -* @param {any} val -* @returns {any} -*/ + * @param {any} val + * @returns {any} + */ export function compress(val: any): any; export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; @@ -45,21 +45,21 @@ export interface InitOutput { export type SyncInitInput = BufferSource | WebAssembly.Module; /** -* Instantiates the given `module`, which can either be bytes or -* a precompiled `WebAssembly.Module`. -* -* @param {SyncInitInput} module -* -* @returns {InitOutput} -*/ + * Instantiates the given `module`, which can either be bytes or + * a precompiled `WebAssembly.Module`. + * + * @param {SyncInitInput} module + * + * @returns {InitOutput} + */ export function initSync(module: SyncInitInput): InitOutput; /** -* If `module_or_path` is {RequestInfo} or {URL}, makes a request and -* for everything else, calls `WebAssembly.instantiate` directly. -* -* @param {InitInput | Promise} module_or_path -* -* @returns {Promise} -*/ -export default function __wbg_init (module_or_path?: InitInput | Promise): Promise; + * If `module_or_path` is {RequestInfo} or {URL}, makes a request and + * for everything else, calls `WebAssembly.instantiate` directly. + * + * @param {InitInput | Promise} module_or_path + * + * @returns {Promise} + */ +export default function __wbg_init(module_or_path?: InitInput | Promise): Promise; diff --git a/packages/system-query/pkg/system_query.js b/packages/system-query/pkg/system_query.js index 57dd87cf..67836cbd 100644 --- a/packages/system-query/pkg/system_query.js +++ b/packages/system-query/pkg/system_query.js @@ -4,20 +4,22 @@ const heap = new Array(128).fill(undefined); heap.push(undefined, null, true, false); -function getObject(idx) { return heap[idx]; } +function getObject(idx) { + return heap[idx]; +} let heap_next = heap.length; function dropObject(idx) { - if (idx < 132) return; - heap[idx] = heap_next; - heap_next = idx; + if (idx < 132) return; + heap[idx] = heap_next; + heap_next = idx; } function takeObject(idx) { - const ret = getObject(idx); - dropObject(idx); - return ret; + const ret = getObject(idx); + dropObject(idx); + return ret; } let WASM_VECTOR_LEN = 0; @@ -25,536 +27,560 @@ let WASM_VECTOR_LEN = 0; let cachedUint8Memory0 = null; function getUint8Memory0() { - if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) { - cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer); - } - return cachedUint8Memory0; + if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) { + cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8Memory0; } -const cachedTextEncoder = (typeof TextEncoder !== 'undefined' ? new TextEncoder('utf-8') : { encode: () => { throw Error('TextEncoder not available') } } ); +const cachedTextEncoder = + typeof TextEncoder !== "undefined" + ? new TextEncoder("utf-8") + : { + encode: () => { + throw Error("TextEncoder not available"); + }, + }; -const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' +const encodeString = + typeof cachedTextEncoder.encodeInto === "function" ? function (arg, view) { - return cachedTextEncoder.encodeInto(arg, view); -} + return cachedTextEncoder.encodeInto(arg, view); + } : function (arg, view) { - const buf = cachedTextEncoder.encode(arg); - view.set(buf); - return { - read: arg.length, - written: buf.length - }; -}); + const buf = cachedTextEncoder.encode(arg); + view.set(buf); + return { + read: arg.length, + written: buf.length, + }; + }; function passStringToWasm0(arg, malloc, realloc) { - - if (realloc === undefined) { - const buf = cachedTextEncoder.encode(arg); - const ptr = malloc(buf.length, 1) >>> 0; - getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf); - WASM_VECTOR_LEN = buf.length; - return ptr; - } - - let len = arg.length; - let ptr = malloc(len, 1) >>> 0; - - const mem = getUint8Memory0(); - - let offset = 0; - - for (; offset < len; offset++) { - const code = arg.charCodeAt(offset); - if (code > 0x7F) break; - mem[ptr + offset] = code; - } - - if (offset !== len) { - if (offset !== 0) { - arg = arg.slice(offset); - } - ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; - const view = getUint8Memory0().subarray(ptr + offset, ptr + len); - const ret = encodeString(arg, view); - - offset += ret.written; - } - - WASM_VECTOR_LEN = offset; + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg); + const ptr = malloc(buf.length, 1) >>> 0; + getUint8Memory0() + .subarray(ptr, ptr + buf.length) + .set(buf); + WASM_VECTOR_LEN = buf.length; return ptr; + } + + let len = arg.length; + let ptr = malloc(len, 1) >>> 0; + + const mem = getUint8Memory0(); + + let offset = 0; + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset); + if (code > 0x7f) break; + mem[ptr + offset] = code; + } + + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset); + } + ptr = realloc(ptr, len, (len = offset + arg.length * 3), 1) >>> 0; + const view = getUint8Memory0().subarray(ptr + offset, ptr + len); + const ret = encodeString(arg, view); + + offset += ret.written; + } + + WASM_VECTOR_LEN = offset; + return ptr; } function isLikeNone(x) { - return x === undefined || x === null; + return x === undefined || x === null; } let cachedInt32Memory0 = null; function getInt32Memory0() { - if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) { - cachedInt32Memory0 = new Int32Array(wasm.memory.buffer); - } - return cachedInt32Memory0; + if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) { + cachedInt32Memory0 = new Int32Array(wasm.memory.buffer); + } + return cachedInt32Memory0; } -const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); +const cachedTextDecoder = + typeof TextDecoder !== "undefined" + ? new TextDecoder("utf-8", { ignoreBOM: true, fatal: true }) + : { + decode: () => { + throw Error("TextDecoder not available"); + }, + }; -if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; +if (typeof TextDecoder !== "undefined") { + cachedTextDecoder.decode(); +} function getStringFromWasm0(ptr, len) { - ptr = ptr >>> 0; - return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); + ptr = ptr >>> 0; + return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); } function addHeapObject(obj) { - if (heap_next === heap.length) heap.push(heap.length + 1); - const idx = heap_next; - heap_next = heap[idx]; + if (heap_next === heap.length) heap.push(heap.length + 1); + const idx = heap_next; + heap_next = heap[idx]; - heap[idx] = obj; - return idx; + heap[idx] = obj; + return idx; } let cachedFloat64Memory0 = null; function getFloat64Memory0() { - if (cachedFloat64Memory0 === null || cachedFloat64Memory0.byteLength === 0) { - cachedFloat64Memory0 = new Float64Array(wasm.memory.buffer); - } - return cachedFloat64Memory0; + if (cachedFloat64Memory0 === null || cachedFloat64Memory0.byteLength === 0) { + cachedFloat64Memory0 = new Float64Array(wasm.memory.buffer); + } + return cachedFloat64Memory0; } function debugString(val) { - // primitive types - const type = typeof val; - if (type == 'number' || type == 'boolean' || val == null) { - return `${val}`; - } - if (type == 'string') { - return `"${val}"`; - } - if (type == 'symbol') { - const description = val.description; - if (description == null) { - return 'Symbol'; - } else { - return `Symbol(${description})`; - } - } - if (type == 'function') { - const name = val.name; - if (typeof name == 'string' && name.length > 0) { - return `Function(${name})`; - } else { - return 'Function'; - } - } - // objects - if (Array.isArray(val)) { - const length = val.length; - let debug = '['; - if (length > 0) { - debug += debugString(val[0]); - } - for(let i = 1; i < length; i++) { - debug += ', ' + debugString(val[i]); - } - debug += ']'; - return debug; - } - // Test for built-in - const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); - let className; - if (builtInMatches.length > 1) { - className = builtInMatches[1]; + // primitive types + const type = typeof val; + if (type == "number" || type == "boolean" || val == null) { + return `${val}`; + } + if (type == "string") { + return `"${val}"`; + } + if (type == "symbol") { + const description = val.description; + if (description == null) { + return "Symbol"; } else { - // Failed to match the standard '[object ClassName]' - return toString.call(val); + return `Symbol(${description})`; } - if (className == 'Object') { - // we're a user defined class or Object - // JSON.stringify avoids problems with cycles, and is generally much - // easier than looping through ownProperties of `val`. - try { - return 'Object(' + JSON.stringify(val) + ')'; - } catch (_) { - return 'Object'; - } + } + if (type == "function") { + const name = val.name; + if (typeof name == "string" && name.length > 0) { + return `Function(${name})`; + } else { + return "Function"; } - // errors - if (val instanceof Error) { - return `${val.name}: ${val.message}\n${val.stack}`; + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = "["; + if (length > 0) { + debug += debugString(val[0]); } - // TODO we could test for more things here, like `Set`s and `Map`s. - return className; + for (let i = 1; i < length; i++) { + debug += ", " + debugString(val[i]); + } + debug += "]"; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == "Object") { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return "Object(" + JSON.stringify(val) + ")"; + } catch (_) { + return "Object"; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; } /** -* @param {any} prev -* @param {any} next -* @returns {any} -*/ + * @param {any} prev + * @param {any} next + * @returns {any} + */ export function diff_filters(prev, next) { - try { - const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); - wasm.diff_filters(retptr, addHeapObject(prev), addHeapObject(next)); - var r0 = getInt32Memory0()[retptr / 4 + 0]; - var r1 = getInt32Memory0()[retptr / 4 + 1]; - var r2 = getInt32Memory0()[retptr / 4 + 2]; - if (r2) { - throw takeObject(r1); - } - return takeObject(r0); - } finally { - wasm.__wbindgen_add_to_stack_pointer(16); + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.diff_filters(retptr, addHeapObject(prev), addHeapObject(next)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } } /** -* @param {any} val -* @returns {any} -*/ + * @param {any} val + * @returns {any} + */ export function expand_filter(val) { - try { - const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); - wasm.expand_filter(retptr, addHeapObject(val)); - var r0 = getInt32Memory0()[retptr / 4 + 0]; - var r1 = getInt32Memory0()[retptr / 4 + 1]; - var r2 = getInt32Memory0()[retptr / 4 + 2]; - if (r2) { - throw takeObject(r1); - } - return takeObject(r0); - } finally { - wasm.__wbindgen_add_to_stack_pointer(16); + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.expand_filter(retptr, addHeapObject(val)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } } /** -* @param {any} prev -* @param {any} next -* @returns {any} -*/ + * @param {any} prev + * @param {any} next + * @returns {any} + */ export function get_diff(prev, next) { - try { - const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); - wasm.get_diff(retptr, addHeapObject(prev), addHeapObject(next)); - var r0 = getInt32Memory0()[retptr / 4 + 0]; - var r1 = getInt32Memory0()[retptr / 4 + 1]; - var r2 = getInt32Memory0()[retptr / 4 + 2]; - if (r2) { - throw takeObject(r1); - } - return takeObject(r0); - } finally { - wasm.__wbindgen_add_to_stack_pointer(16); + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.get_diff(retptr, addHeapObject(prev), addHeapObject(next)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } } /** -* @param {any} val -* @returns {any} -*/ + * @param {any} val + * @returns {any} + */ export function flat_merge(val) { - try { - const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); - wasm.flat_merge(retptr, addHeapObject(val)); - var r0 = getInt32Memory0()[retptr / 4 + 0]; - var r1 = getInt32Memory0()[retptr / 4 + 1]; - var r2 = getInt32Memory0()[retptr / 4 + 2]; - if (r2) { - throw takeObject(r1); - } - return takeObject(r0); - } finally { - wasm.__wbindgen_add_to_stack_pointer(16); + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.flat_merge(retptr, addHeapObject(val)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } } /** -* @param {any} val -* @returns {any} -*/ + * @param {any} val + * @returns {any} + */ export function compress(val) { - try { - const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); - wasm.compress(retptr, addHeapObject(val)); - var r0 = getInt32Memory0()[retptr / 4 + 0]; - var r1 = getInt32Memory0()[retptr / 4 + 1]; - var r2 = getInt32Memory0()[retptr / 4 + 2]; - if (r2) { - throw takeObject(r1); - } - return takeObject(r0); - } finally { - wasm.__wbindgen_add_to_stack_pointer(16); + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.compress(retptr, addHeapObject(val)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } } function handleError(f, args) { - try { - return f.apply(this, args); - } catch (e) { - wasm.__wbindgen_exn_store(addHeapObject(e)); - } + try { + return f.apply(this, args); + } catch (e) { + wasm.__wbindgen_exn_store(addHeapObject(e)); + } } async function __wbg_load(module, imports) { - if (typeof Response === 'function' && module instanceof Response) { - if (typeof WebAssembly.instantiateStreaming === 'function') { - try { - return await WebAssembly.instantiateStreaming(module, imports); - - } catch (e) { - if (module.headers.get('Content-Type') != 'application/wasm') { - console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e); - - } else { - throw e; - } - } - } - - const bytes = await module.arrayBuffer(); - return await WebAssembly.instantiate(bytes, imports); - - } else { - const instance = await WebAssembly.instantiate(module, imports); - - if (instance instanceof WebAssembly.Instance) { - return { instance, module }; - + if (typeof Response === "function" && module instanceof Response) { + if (typeof WebAssembly.instantiateStreaming === "function") { + try { + return await WebAssembly.instantiateStreaming(module, imports); + } catch (e) { + if (module.headers.get("Content-Type") != "application/wasm") { + console.warn( + "`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", + e, + ); } else { - return instance; + throw e; } + } } + + const bytes = await module.arrayBuffer(); + return await WebAssembly.instantiate(bytes, imports); + } else { + const instance = await WebAssembly.instantiate(module, imports); + + if (instance instanceof WebAssembly.Instance) { + return { instance, module }; + } else { + return instance; + } + } } function __wbg_get_imports() { - const imports = {}; - imports.wbg = {}; - imports.wbg.__wbindgen_object_drop_ref = function(arg0) { - takeObject(arg0); - }; - imports.wbg.__wbindgen_string_get = function(arg0, arg1) { - const obj = getObject(arg1); - const ret = typeof(obj) === 'string' ? obj : undefined; - var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - var len1 = WASM_VECTOR_LEN; - getInt32Memory0()[arg0 / 4 + 1] = len1; - getInt32Memory0()[arg0 / 4 + 0] = ptr1; - }; - imports.wbg.__wbindgen_is_object = function(arg0) { - const val = getObject(arg0); - const ret = typeof(val) === 'object' && val !== null; - return ret; - }; - imports.wbg.__wbindgen_is_undefined = function(arg0) { - const ret = getObject(arg0) === undefined; - return ret; - }; - imports.wbg.__wbindgen_in = function(arg0, arg1) { - const ret = getObject(arg0) in getObject(arg1); - return ret; - }; - imports.wbg.__wbindgen_error_new = function(arg0, arg1) { - const ret = new Error(getStringFromWasm0(arg0, arg1)); - return addHeapObject(ret); - }; - imports.wbg.__wbindgen_object_clone_ref = function(arg0) { - const ret = getObject(arg0); - return addHeapObject(ret); - }; - imports.wbg.__wbindgen_jsval_loose_eq = function(arg0, arg1) { - const ret = getObject(arg0) == getObject(arg1); - return ret; - }; - imports.wbg.__wbindgen_boolean_get = function(arg0) { - const v = getObject(arg0); - const ret = typeof(v) === 'boolean' ? (v ? 1 : 0) : 2; - return ret; - }; - imports.wbg.__wbindgen_number_get = function(arg0, arg1) { - const obj = getObject(arg1); - const ret = typeof(obj) === 'number' ? obj : undefined; - getFloat64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? 0 : ret; - getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret); - }; - imports.wbg.__wbindgen_number_new = function(arg0) { - const ret = arg0; - return addHeapObject(ret); - }; - imports.wbg.__wbindgen_string_new = function(arg0, arg1) { - const ret = getStringFromWasm0(arg0, arg1); - return addHeapObject(ret); - }; - imports.wbg.__wbg_getwithrefkey_5e6d9547403deab8 = function(arg0, arg1) { - const ret = getObject(arg0)[getObject(arg1)]; - return addHeapObject(ret); - }; - imports.wbg.__wbg_set_841ac57cff3d672b = function(arg0, arg1, arg2) { - getObject(arg0)[takeObject(arg1)] = takeObject(arg2); - }; - imports.wbg.__wbg_get_44be0491f933a435 = function(arg0, arg1) { - const ret = getObject(arg0)[arg1 >>> 0]; - return addHeapObject(ret); - }; - imports.wbg.__wbg_length_fff51ee6522a1a18 = function(arg0) { - const ret = getObject(arg0).length; - return ret; - }; - imports.wbg.__wbg_new_898a68150f225f2e = function() { - const ret = new Array(); - return addHeapObject(ret); - }; - imports.wbg.__wbindgen_is_function = function(arg0) { - const ret = typeof(getObject(arg0)) === 'function'; - return ret; - }; - imports.wbg.__wbg_next_526fc47e980da008 = function(arg0) { - const ret = getObject(arg0).next; - return addHeapObject(ret); - }; - imports.wbg.__wbg_next_ddb3312ca1c4e32a = function() { return handleError(function (arg0) { - const ret = getObject(arg0).next(); - return addHeapObject(ret); - }, arguments) }; - imports.wbg.__wbg_done_5c1f01fb660d73b5 = function(arg0) { - const ret = getObject(arg0).done; - return ret; - }; - imports.wbg.__wbg_value_1695675138684bd5 = function(arg0) { - const ret = getObject(arg0).value; - return addHeapObject(ret); - }; - imports.wbg.__wbg_iterator_97f0c81209c6c35a = function() { - const ret = Symbol.iterator; - return addHeapObject(ret); - }; - imports.wbg.__wbg_get_97b561fb56f034b5 = function() { return handleError(function (arg0, arg1) { - const ret = Reflect.get(getObject(arg0), getObject(arg1)); - return addHeapObject(ret); - }, arguments) }; - imports.wbg.__wbg_call_cb65541d95d71282 = function() { return handleError(function (arg0, arg1) { - const ret = getObject(arg0).call(getObject(arg1)); - return addHeapObject(ret); - }, arguments) }; - imports.wbg.__wbg_new_b51585de1b234aff = function() { - const ret = new Object(); - return addHeapObject(ret); - }; - imports.wbg.__wbg_set_502d29070ea18557 = function(arg0, arg1, arg2) { - getObject(arg0)[arg1 >>> 0] = takeObject(arg2); - }; - imports.wbg.__wbg_isArray_4c24b343cb13cfb1 = function(arg0) { - const ret = Array.isArray(getObject(arg0)); - return ret; - }; - imports.wbg.__wbg_instanceof_ArrayBuffer_39ac22089b74fddb = function(arg0) { - let result; - try { - result = getObject(arg0) instanceof ArrayBuffer; - } catch { - result = false; - } - const ret = result; - return ret; - }; - imports.wbg.__wbg_isSafeInteger_bb8e18dd21c97288 = function(arg0) { - const ret = Number.isSafeInteger(getObject(arg0)); - return ret; - }; - imports.wbg.__wbg_buffer_085ec1f694018c4f = function(arg0) { - const ret = getObject(arg0).buffer; - return addHeapObject(ret); - }; - imports.wbg.__wbg_new_8125e318e6245eed = function(arg0) { - const ret = new Uint8Array(getObject(arg0)); - return addHeapObject(ret); - }; - imports.wbg.__wbg_set_5cf90238115182c3 = function(arg0, arg1, arg2) { - getObject(arg0).set(getObject(arg1), arg2 >>> 0); - }; - imports.wbg.__wbg_length_72e2208bbc0efc61 = function(arg0) { - const ret = getObject(arg0).length; - return ret; - }; - imports.wbg.__wbg_instanceof_Uint8Array_d8d9cb2b8e8ac1d4 = function(arg0) { - let result; - try { - result = getObject(arg0) instanceof Uint8Array; - } catch { - result = false; - } - const ret = result; - return ret; - }; - imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { - const ret = debugString(getObject(arg1)); - const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len1 = WASM_VECTOR_LEN; - getInt32Memory0()[arg0 / 4 + 1] = len1; - getInt32Memory0()[arg0 / 4 + 0] = ptr1; - }; - imports.wbg.__wbindgen_throw = function(arg0, arg1) { - throw new Error(getStringFromWasm0(arg0, arg1)); - }; - imports.wbg.__wbindgen_memory = function() { - const ret = wasm.memory; - return addHeapObject(ret); - }; + const imports = {}; + imports.wbg = {}; + imports.wbg.__wbindgen_object_drop_ref = function (arg0) { + takeObject(arg0); + }; + imports.wbg.__wbindgen_string_get = function (arg0, arg1) { + const obj = getObject(arg1); + const ret = typeof obj === "string" ? obj : undefined; + var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len1 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len1; + getInt32Memory0()[arg0 / 4 + 0] = ptr1; + }; + imports.wbg.__wbindgen_is_object = function (arg0) { + const val = getObject(arg0); + const ret = typeof val === "object" && val !== null; + return ret; + }; + imports.wbg.__wbindgen_is_undefined = function (arg0) { + const ret = getObject(arg0) === undefined; + return ret; + }; + imports.wbg.__wbindgen_in = function (arg0, arg1) { + const ret = getObject(arg0) in getObject(arg1); + return ret; + }; + imports.wbg.__wbindgen_error_new = function (arg0, arg1) { + const ret = new Error(getStringFromWasm0(arg0, arg1)); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_object_clone_ref = function (arg0) { + const ret = getObject(arg0); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_jsval_loose_eq = function (arg0, arg1) { + const ret = getObject(arg0) == getObject(arg1); + return ret; + }; + imports.wbg.__wbindgen_boolean_get = function (arg0) { + const v = getObject(arg0); + const ret = typeof v === "boolean" ? (v ? 1 : 0) : 2; + return ret; + }; + imports.wbg.__wbindgen_number_get = function (arg0, arg1) { + const obj = getObject(arg1); + const ret = typeof obj === "number" ? obj : undefined; + getFloat64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? 0 : ret; + getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret); + }; + imports.wbg.__wbindgen_number_new = function (arg0) { + const ret = arg0; + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_string_new = function (arg0, arg1) { + const ret = getStringFromWasm0(arg0, arg1); + return addHeapObject(ret); + }; + imports.wbg.__wbg_getwithrefkey_5e6d9547403deab8 = function (arg0, arg1) { + const ret = getObject(arg0)[getObject(arg1)]; + return addHeapObject(ret); + }; + imports.wbg.__wbg_set_841ac57cff3d672b = function (arg0, arg1, arg2) { + getObject(arg0)[takeObject(arg1)] = takeObject(arg2); + }; + imports.wbg.__wbg_get_44be0491f933a435 = function (arg0, arg1) { + const ret = getObject(arg0)[arg1 >>> 0]; + return addHeapObject(ret); + }; + imports.wbg.__wbg_length_fff51ee6522a1a18 = function (arg0) { + const ret = getObject(arg0).length; + return ret; + }; + imports.wbg.__wbg_new_898a68150f225f2e = function () { + const ret = new Array(); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_is_function = function (arg0) { + const ret = typeof getObject(arg0) === "function"; + return ret; + }; + imports.wbg.__wbg_next_526fc47e980da008 = function (arg0) { + const ret = getObject(arg0).next; + return addHeapObject(ret); + }; + imports.wbg.__wbg_next_ddb3312ca1c4e32a = function () { + return handleError(function (arg0) { + const ret = getObject(arg0).next(); + return addHeapObject(ret); + }, arguments); + }; + imports.wbg.__wbg_done_5c1f01fb660d73b5 = function (arg0) { + const ret = getObject(arg0).done; + return ret; + }; + imports.wbg.__wbg_value_1695675138684bd5 = function (arg0) { + const ret = getObject(arg0).value; + return addHeapObject(ret); + }; + imports.wbg.__wbg_iterator_97f0c81209c6c35a = function () { + const ret = Symbol.iterator; + return addHeapObject(ret); + }; + imports.wbg.__wbg_get_97b561fb56f034b5 = function () { + return handleError(function (arg0, arg1) { + const ret = Reflect.get(getObject(arg0), getObject(arg1)); + return addHeapObject(ret); + }, arguments); + }; + imports.wbg.__wbg_call_cb65541d95d71282 = function () { + return handleError(function (arg0, arg1) { + const ret = getObject(arg0).call(getObject(arg1)); + return addHeapObject(ret); + }, arguments); + }; + imports.wbg.__wbg_new_b51585de1b234aff = function () { + const ret = new Object(); + return addHeapObject(ret); + }; + imports.wbg.__wbg_set_502d29070ea18557 = function (arg0, arg1, arg2) { + getObject(arg0)[arg1 >>> 0] = takeObject(arg2); + }; + imports.wbg.__wbg_isArray_4c24b343cb13cfb1 = function (arg0) { + const ret = Array.isArray(getObject(arg0)); + return ret; + }; + imports.wbg.__wbg_instanceof_ArrayBuffer_39ac22089b74fddb = function (arg0) { + let result; + try { + result = getObject(arg0) instanceof ArrayBuffer; + } catch { + result = false; + } + const ret = result; + return ret; + }; + imports.wbg.__wbg_isSafeInteger_bb8e18dd21c97288 = function (arg0) { + const ret = Number.isSafeInteger(getObject(arg0)); + return ret; + }; + imports.wbg.__wbg_buffer_085ec1f694018c4f = function (arg0) { + const ret = getObject(arg0).buffer; + return addHeapObject(ret); + }; + imports.wbg.__wbg_new_8125e318e6245eed = function (arg0) { + const ret = new Uint8Array(getObject(arg0)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_set_5cf90238115182c3 = function (arg0, arg1, arg2) { + getObject(arg0).set(getObject(arg1), arg2 >>> 0); + }; + imports.wbg.__wbg_length_72e2208bbc0efc61 = function (arg0) { + const ret = getObject(arg0).length; + return ret; + }; + imports.wbg.__wbg_instanceof_Uint8Array_d8d9cb2b8e8ac1d4 = function (arg0) { + let result; + try { + result = getObject(arg0) instanceof Uint8Array; + } catch { + result = false; + } + const ret = result; + return ret; + }; + imports.wbg.__wbindgen_debug_string = function (arg0, arg1) { + const ret = debugString(getObject(arg1)); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len1; + getInt32Memory0()[arg0 / 4 + 0] = ptr1; + }; + imports.wbg.__wbindgen_throw = function (arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); + }; + imports.wbg.__wbindgen_memory = function () { + const ret = wasm.memory; + return addHeapObject(ret); + }; - return imports; + return imports; } -function __wbg_init_memory(imports, maybe_memory) { - -} +function __wbg_init_memory(imports, maybe_memory) {} function __wbg_finalize_init(instance, module) { - wasm = instance.exports; - __wbg_init.__wbindgen_wasm_module = module; - cachedFloat64Memory0 = null; - cachedInt32Memory0 = null; - cachedUint8Memory0 = null; + wasm = instance.exports; + __wbg_init.__wbindgen_wasm_module = module; + cachedFloat64Memory0 = null; + cachedInt32Memory0 = null; + cachedUint8Memory0 = null; - - return wasm; + return wasm; } function initSync(module) { - if (wasm !== undefined) return wasm; + if (wasm !== undefined) return wasm; - const imports = __wbg_get_imports(); + const imports = __wbg_get_imports(); - __wbg_init_memory(imports); + __wbg_init_memory(imports); - if (!(module instanceof WebAssembly.Module)) { - module = new WebAssembly.Module(module); - } + if (!(module instanceof WebAssembly.Module)) { + module = new WebAssembly.Module(module); + } - const instance = new WebAssembly.Instance(module, imports); + const instance = new WebAssembly.Instance(module, imports); - return __wbg_finalize_init(instance, module); + return __wbg_finalize_init(instance, module); } async function __wbg_init(input) { - if (wasm !== undefined) return wasm; + if (wasm !== undefined) return wasm; - if (typeof input === 'undefined') { - input = new URL('system_query_bg.wasm', import.meta.url); - } - const imports = __wbg_get_imports(); + if (typeof input === "undefined") { + input = new URL("system_query_bg.wasm", import.meta.url); + } + const imports = __wbg_get_imports(); - if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) { - input = fetch(input); - } + if ( + typeof input === "string" || + (typeof Request === "function" && input instanceof Request) || + (typeof URL === "function" && input instanceof URL) + ) { + input = fetch(input); + } - __wbg_init_memory(imports); + __wbg_init_memory(imports); - const { instance, module } = await __wbg_load(await input, imports); + const { instance, module } = await __wbg_load(await input, imports); - return __wbg_finalize_init(instance, module); + return __wbg_finalize_init(instance, module); } -export { initSync } +export { initSync }; export default __wbg_init; diff --git a/packages/system-query/pkg/system_query_bg.wasm b/packages/system-query/pkg/system_query_bg.wasm index 8590da75f1d85807cab185e994d2f223b8f054d5..9ab1834bc2edd04da5a3dad0369b69ee3dfff4b4 100644 GIT binary patch literal 120012 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

    sNBJzQ>dGogK2i)yVo~`&_W= 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 XXT64dAMEQvpQS?fqu>c=9v%2ME_R3F literal 120018 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 E-{I | Uint8Array; encryptData(plaintext: string, sharedSecet: Uint8Array): Promise | MessageEncryptorPayload; decryptData(payload: MessageEncryptorPayload, sharedSecet: Uint8Array): Promise | string; -} \ No newline at end of file +} diff --git a/packages/system/src/nostr-system.ts b/packages/system/src/nostr-system.ts index b7105016..4382471e 100644 --- a/packages/system/src/nostr-system.ts +++ b/packages/system/src/nostr-system.ts @@ -93,7 +93,7 @@ export class NostrSystem extends ExternalStore implements System this.#profileCache = props.profileCache ?? new UserProfileCache(); this.#relayMetricsCache = props.relayMetrics ?? new RelayMetricCache(); this.#eventsCache = props.eventsCache ?? new EventsCache(); - this.#queryOptimizer = props.queryOptimizer ?? DefaultQueryOptimizer; + this.#queryOptimizer = props.queryOptimizer ?? DefaultQueryOptimizer; this.#profileLoader = new ProfileLoaderService(this, this.#profileCache); this.#relayMetrics = new RelayMetricHandler(this.#relayMetricsCache); @@ -254,9 +254,7 @@ export class NostrSystem extends ExternalStore implements System if (existing.fromInstance === req.instance) { return existing; } - const filters = !req.options?.skipDiff - ? req.buildDiff(this, existing.filters) - : req.build(this); + const filters = !req.options?.skipDiff ? req.buildDiff(this, existing.filters) : req.build(this); if (filters.length === 0 && !!req.options?.skipDiff) { return existing; } else { @@ -275,7 +273,7 @@ export class NostrSystem extends ExternalStore implements System const expectIds = new Set(filters.flatMap(a => a.filters).flatMap(a => a.ids ?? [])); q.feed.onEvent(async evs => { const toSet = evs.filter(a => expectIds.has(a.id) && this.#eventsCache.getFromCache(a.id) === undefined); - if(toSet.length > 0) { + if (toSet.length > 0) { await this.#eventsCache.bulkSet(toSet); } }); @@ -411,4 +409,4 @@ export class NostrSystem extends ExternalStore implements System } setTimeout(() => this.#cleanup(), 1_000); } -} \ No newline at end of file +} diff --git a/packages/system/src/query-optimizer/index.ts b/packages/system/src/query-optimizer/index.ts index eb5a166d..21d8b44a 100644 --- a/packages/system/src/query-optimizer/index.ts +++ b/packages/system/src/query-optimizer/index.ts @@ -1,7 +1,7 @@ -import { ReqFilter } from "../nostr" -import { expandFilter } from "./request-expander" -import { flatMerge, mergeSimilar } from "./request-merger" -import { diffFilters } from "./request-splitter" +import { ReqFilter } from "../nostr"; +import { expandFilter } from "./request-expander"; +import { flatMerge, mergeSimilar } from "./request-merger"; +import { diffFilters } from "./request-splitter"; export interface FlatReqFilter { keys: number; @@ -20,24 +20,27 @@ export interface FlatReqFilter { } export interface QueryOptimizer { - expandFilter(f: ReqFilter): Array - getDiff(prev: Array, next: Array): Array - flatMerge(all: Array): Array - compress(all: Array): Array + expandFilter(f: ReqFilter): Array; + getDiff(prev: Array, next: Array): Array; + flatMerge(all: Array): Array; + compress(all: Array): Array; } export const DefaultQueryOptimizer = { - expandFilter: (f: ReqFilter) => { - return expandFilter(f); - }, - getDiff: (prev: Array, next: Array) => { - const diff = diffFilters(prev.flatMap(a => expandFilter(a)), next.flatMap(a => expandFilter(a))); - return diff.added; - }, - flatMerge: (all: Array) => { - return flatMerge(all); - }, - compress: (all: Array) => { - return mergeSimilar(all); - } - } as QueryOptimizer; \ No newline at end of file + expandFilter: (f: ReqFilter) => { + return expandFilter(f); + }, + getDiff: (prev: Array, next: Array) => { + const diff = diffFilters( + prev.flatMap(a => expandFilter(a)), + next.flatMap(a => expandFilter(a)), + ); + return diff.added; + }, + flatMerge: (all: Array) => { + return flatMerge(all); + }, + compress: (all: Array) => { + return mergeSimilar(all); + }, +} as QueryOptimizer; diff --git a/packages/system/src/query-optimizer/request-splitter.ts b/packages/system/src/query-optimizer/request-splitter.ts index 97f9b628..623a2798 100644 --- a/packages/system/src/query-optimizer/request-splitter.ts +++ b/packages/system/src/query-optimizer/request-splitter.ts @@ -29,4 +29,4 @@ export function diffFilters(prev: Array, next: Array implements Syste get RelayCache(): RelayCache { throw new Error("Method not implemented."); } - + get QueryOptimizer(): QueryOptimizer { throw new Error("Method not implemented."); } diff --git a/packages/system/src/zaps.ts b/packages/system/src/zaps.ts index ee22cb15..bf9a65b5 100644 --- a/packages/system/src/zaps.ts +++ b/packages/system/src/zaps.ts @@ -16,7 +16,7 @@ function getInvoice(zap: NostrEvent): InvoiceDetails | undefined { export function parseZap(zapReceipt: NostrEvent, userCache: FeedCache, refNote?: NostrEvent): ParsedZap { const existing = ParsedZapCache.get(zapReceipt.id); - if(existing) { + if (existing) { return existing; } diff --git a/packages/system/tests/node.ts b/packages/system/tests/node.ts index 8dd7862e..d572afb0 100644 --- a/packages/system/tests/node.ts +++ b/packages/system/tests/node.ts @@ -1,14 +1,14 @@ -import {NostrSystem, SystemInterface} from ".."; +import { NostrSystem, SystemInterface } from ".."; const Relay = "wss://relay.snort.social/"; const system = new NostrSystem({}) as SystemInterface; async function test() { - await system.ConnectToRelay(Relay, {read: true, write: true}); - setTimeout(() => { - system.DisconnectRelay(Relay); - }, 1000); + await system.ConnectToRelay(Relay, { read: true, write: true }); + setTimeout(() => { + system.DisconnectRelay(Relay); + }, 1000); } -test().catch(console.error); \ No newline at end of file +test().catch(console.error); diff --git a/yarn.lock b/yarn.lock index 149d04eb..9946756b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2852,6 +2852,7 @@ __metadata: "@peculiar/webcrypto": ^1.4.3 "@scure/base": ^1.1.2 "@snort/shared": ^1.0.5 + "@snort/system-query": "workspace:*" "@stablelib/xchacha20": ^1.0.1 "@types/debug": ^4.1.8 "@types/jest": ^29.5.1 -- 2.45.2