diff --git a/Cargo.lock b/Cargo.lock index dc9f1c119..3d8e07c7f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -229,9 +229,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "axum" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" +checksum = "98e529aee37b5c8206bb4bf4c44797127566d72f76952c970bd3d1e85de8f4e2" dependencies = [ "axum-core", "bytes", @@ -248,8 +248,7 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rustversion", - "serde", + "serde_core", "serde_json", "serde_path_to_error", "serde_urlencoded", @@ -263,9 +262,9 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.5.2" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" +checksum = "0ac7a6beb1182c7e30253ee75c3e918080bfb83f5a3023bcdf7209d85fd147e6" dependencies = [ "bytes", "futures-core", @@ -274,7 +273,6 @@ dependencies = [ "http-body-util", "mime", "pin-project-lite", - "rustversion", "sync_wrapper", "tower-layer", "tower-service", @@ -500,6 +498,7 @@ dependencies = [ name = "brk" version = "0.0.109" dependencies = [ + "brk_binder", "brk_bundler", "brk_cli", "brk_computer", @@ -562,7 +561,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ff3e445e42475fba5e0cfaed51345f491e479b9f2069f29875f434a5327913" [[package]] -name = "brk_bridge" +name = "brk_binder" version = "0.0.109" dependencies = [ "brk_interface", @@ -585,7 +584,7 @@ name = "brk_cli" version = "0.0.109" dependencies = [ "bitcoincore-rpc", - "brk_bridge", + "brk_binder", "brk_bundler", "brk_computer", "brk_error", @@ -639,7 +638,7 @@ dependencies = [ "fjall", "jiff", "minreq", - "serde_json", + "sonic-rs 0.5.5", "vecdb", "zerocopy", ] @@ -653,7 +652,7 @@ dependencies = [ "brk_structs", "log", "minreq", - "serde_json", + "sonic-rs 0.5.5", ] [[package]] @@ -688,8 +687,8 @@ dependencies = [ "quick_cache", "schemars 1.0.4", "serde", - "serde_json", "serde_with", + "sonic-rs 0.5.5", "vecdb", ] @@ -711,6 +710,8 @@ dependencies = [ "brk_interface", "brk_rmcp", "log", + "schemars 1.0.4", + "serde", "serde_json", ] @@ -749,7 +750,7 @@ dependencies = [ "serde", "serde_json", "sse-stream", - "thiserror 2.0.16", + "thiserror 2.0.17", "tokio", "tokio-stream", "tokio-util", @@ -1140,7 +1141,6 @@ dependencies = [ "log", "quick_cache", "serde", - "serde_json", "sonic-rs 0.5.5", "tokio", "tower-http", @@ -2659,9 +2659,9 @@ checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "lsm-tree" -version = "2.10.3" +version = "2.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab73c02eadb3dc12c0024e5b61d6284e6d59064e67e74fbad77856caa56f62c7" +checksum = "799399117a2bfb37660e08be33f470958babb98386b04185288d829df362ea15" dependencies = [ "byteorder", "crossbeam-skiplist", @@ -2683,9 +2683,9 @@ dependencies = [ [[package]] name = "lz4_flex" -version = "0.11.3" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75761162ae2b0e580d7e7c390558127e5f01b4194debd6221fd8c207fc80e3f5" +checksum = "08ab2867e3eeeca90e844d1940eab391c9dc5228783db2ed999acbc0a9ed375a" [[package]] name = "matchit" @@ -2978,7 +2978,7 @@ dependencies = [ "rustc-hash", "serde", "serde_json", - "thiserror 2.0.16", + "thiserror 2.0.17", "time", ] @@ -2992,7 +2992,7 @@ dependencies = [ "owo-colors", "oxc-miette-derive", "textwrap", - "thiserror 2.0.16", + "thiserror 2.0.17", "unicode-segmentation", "unicode-width", ] @@ -3283,7 +3283,7 @@ dependencies = [ "serde", "serde_json", "simdutf8", - "thiserror 2.0.16", + "thiserror 2.0.17", "tracing", "url", "windows", @@ -3645,7 +3645,7 @@ dependencies = [ "rustc-hash", "serde", "serde_json", - "thiserror 2.0.16", + "thiserror 2.0.17", ] [[package]] @@ -3739,9 +3739,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.40" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] @@ -3894,23 +3894,23 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.16", "libredox", - "thiserror 2.0.16", + "thiserror 2.0.17", ] [[package]] name = "ref-cast" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", @@ -4433,7 +4433,7 @@ dependencies = [ "simdutf8", "sonic-number", "sonic-simd", - "thiserror 2.0.16", + "thiserror 2.0.17", ] [[package]] @@ -4454,7 +4454,7 @@ dependencies = [ "simdutf8", "sonic-number", "sonic-simd", - "thiserror 2.0.16", + "thiserror 2.0.17", ] [[package]] @@ -4619,11 +4619,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl 2.0.16", + "thiserror-impl 2.0.17", ] [[package]] @@ -4639,9 +4639,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", @@ -4963,7 +4963,7 @@ version = "11.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ef1b7a6d914a34127ed8e1fa927eb7088903787bcded4fa3eef8f85ee1568be" dependencies = [ - "thiserror 2.0.16", + "thiserror 2.0.17", "ts-rs-macros", ] diff --git a/Cargo.toml b/Cargo.toml index d61fef6bb..0fd359911 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,10 +42,10 @@ debug-assertions = false [workspace.dependencies] allocative = { version = "0.3.4", features = ["parking_lot"] } -axum = "0.8.4" +axum = "0.8.5" bitcoin = { version = "0.32.7", features = ["serde"] } bitcoincore-rpc = "0.19.0" -brk_bridge = { version = "0.0.109", path = "crates/brk_bridge" } +brk_binder = { version = "0.0.109", path = "crates/brk_binder" } brk_bundler = { version = "0.0.109", path = "crates/brk_bundler" } brk_cli = { version = "0.0.109", path = "crates/brk_cli" } brk_computer = { version = "0.0.109", path = "crates/brk_computer" } @@ -68,10 +68,10 @@ minreq = { version = "2.14.1", features = ["https", "serde_json"] } parking_lot = "0.12.4" quick_cache = "0.6.16" rayon = "1.11.0" +schemars = "1.0.4" serde = "1.0.228" serde_bytes = "0.11.19" serde_derive = "1.0.228" -serde_json = { version = "1.0.145", features = ["float_roundtrip"] } sonic-rs = "0.5.5" tokio = { version = "1.47.1", features = ["rt-multi-thread"] } # vecdb = { path = "../seqdb/crates/vecdb", features = ["derive"]} diff --git a/crates/brk/Cargo.toml b/crates/brk/Cargo.toml index 63ae2abc6..c88298e9b 100644 --- a/crates/brk/Cargo.toml +++ b/crates/brk/Cargo.toml @@ -11,6 +11,7 @@ build = "build.rs" [features] full = [ + "binder", "bundler", "computer", "error", @@ -24,6 +25,7 @@ full = [ "store", "structs", ] +binder = ["brk_binder"] bundler = ["brk_bundler"] computer = ["brk_computer"] error = ["brk_error"] @@ -38,6 +40,7 @@ store = ["brk_store"] structs = ["brk_structs"] [dependencies] +brk_binder = { workspace = true, optional = true } brk_bundler = { workspace = true, optional = true } brk_cli = { workspace = true } brk_computer = { workspace = true, optional = true } diff --git a/crates/brk/src/lib.rs b/crates/brk/src/lib.rs index f40f966b6..fceb49ee0 100644 --- a/crates/brk/src/lib.rs +++ b/crates/brk/src/lib.rs @@ -1,5 +1,9 @@ #![doc = include_str!("../README.md")] +#[cfg(feature = "binder")] +#[doc(inline)] +pub use brk_binder as binder; + #[cfg(feature = "bundler")] #[doc(inline)] pub use brk_bundler as bundler; diff --git a/crates/brk_bridge/Cargo.toml b/crates/brk_binder/Cargo.toml similarity index 76% rename from crates/brk_bridge/Cargo.toml rename to crates/brk_binder/Cargo.toml index 739c604c5..8d67817d7 100644 --- a/crates/brk_bridge/Cargo.toml +++ b/crates/brk_binder/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "brk_bridge" -description = "A generator of bridge files for other languages" +name = "brk_binder" +description = "A generator of binding files for other languages" version.workspace = true edition.workspace = true license.workspace = true diff --git a/crates/brk_binder/README.md b/crates/brk_binder/README.md new file mode 100644 index 000000000..696cfb890 --- /dev/null +++ b/crates/brk_binder/README.md @@ -0,0 +1 @@ +# brk_binder diff --git a/crates/brk_bridge/build.rs b/crates/brk_binder/build.rs similarity index 100% rename from crates/brk_bridge/build.rs rename to crates/brk_binder/build.rs diff --git a/crates/brk_bridge/src/js.rs b/crates/brk_binder/src/js.rs similarity index 81% rename from crates/brk_bridge/src/js.rs rename to crates/brk_binder/src/js.rs index 454699673..cbc12280b 100644 --- a/crates/brk_bridge/src/js.rs +++ b/crates/brk_binder/src/js.rs @@ -15,12 +15,12 @@ const AUTO_GENERATED_DISCLAIMER: &str = "// #[allow(clippy::upper_case_acronyms)] pub trait Bridge { - fn generate_js_files(&self, packages_path: &Path) -> io::Result<()>; + fn generate_js_files(&self, modules_path: &Path) -> io::Result<()>; } impl Bridge for Interface<'static> { - fn generate_js_files(&self, packages_path: &Path) -> io::Result<()> { - let path = packages_path.join("brk-client"); + fn generate_js_files(&self, modules_path: &Path) -> io::Result<()> { + let path = modules_path.join("brk-client"); if !fs::exists(&path)? { return Ok(()); @@ -36,7 +36,7 @@ impl Bridge for Interface<'static> { } fn generate_version_file(parent: &Path) -> io::Result<()> { - let path = parent.join(Path::new("metrics.js")); + let path = parent.join(Path::new("version.js")); let contents = format!( "{AUTO_GENERATED_DISCLAIMER} @@ -89,29 +89,39 @@ fn generate_metrics_file(interface: &Interface<'static>, parent: &Path) -> io::R let mut contents = format!( "{AUTO_GENERATED_DISCLAIMER} +export const INDEXES = /** @type {{const}} */ ([ +{} +]); + /** -" - ); - - contents += &indexes - .iter() - .map(|i| format!(" * @typedef {{\"{}\"}} {i}", i.serialize_long())) - .collect::>() - .join("\n"); - - contents += &format!( - " - * @typedef {{{}}} Index + * @typedef {{typeof INDEXES[number]}} IndexName */ - ", indexes .iter() - .map(|i| i.to_string()) + .map(|i| format!(" \"{}\"", i.serialize_long())) .collect::>() - .join(" | ") + .join(",\n") ); + // contents += &indexes + // .iter() + // .map(|i| format!(" * @typedef {{\"{}\"}} {i}", i.serialize_long())) + // .collect::>() + // .join("\n"); + + // contents += &format!( + // " + // * @typedef {{{}}} Index + // */ + // ", + // indexes + // .iter() + // .map(|i| i.to_string()) + // .collect::>() + // .join(" | ") + // ); + let mut unique_index_groups = BTreeMap::new(); let mut word_to_freq: BTreeMap<_, usize> = BTreeMap::new(); @@ -131,16 +141,18 @@ fn generate_metrics_file(interface: &Interface<'static>, parent: &Path) -> io::R .collect::>(); contents += &format!( - "export const INDEX_TO_WORD = [ + " +export const INDEX_TO_WORD = [ {} ]; ", words .iter() - .map(|w| format!("\"{w}\"")) + .enumerate() + .map(|(index, word)| format!("\"{word}\", // {}", index_to_letters(index))) .collect::>() - .join(",\n ") + .join("\n ") ); let word_to_base62 = words @@ -150,7 +162,7 @@ fn generate_metrics_file(interface: &Interface<'static>, parent: &Path) -> io::R .collect::>(); let mut ser_metric_to_indexes = " -/** @type {Record} */ +/** @type {Record} */ export const COMPRESSED_METRIC_TO_INDEXES = { " .to_string(); @@ -186,7 +198,7 @@ export const COMPRESSED_METRIC_TO_INDEXES = { sorted_groups.sort_by_key(|(_, index)| *index); sorted_groups.into_iter().for_each(|(group, index)| { let index = index_to_letters(index); - contents += &format!("/** @type {{Index[]}} */\nconst {index} = {group};\n"); + contents += &format!("/** @type {{IndexName[]}} */\nconst {index} = {group};\n"); }); contents += &ser_metric_to_indexes; diff --git a/crates/brk_bridge/src/lib.rs b/crates/brk_binder/src/lib.rs similarity index 100% rename from crates/brk_bridge/src/lib.rs rename to crates/brk_binder/src/lib.rs diff --git a/crates/brk_bridge/src/python.rs b/crates/brk_binder/src/python.rs similarity index 100% rename from crates/brk_bridge/src/python.rs rename to crates/brk_binder/src/python.rs diff --git a/crates/brk_bridge/README.md b/crates/brk_bridge/README.md deleted file mode 100644 index d11d53f46..000000000 --- a/crates/brk_bridge/README.md +++ /dev/null @@ -1 +0,0 @@ -# brk_bridge diff --git a/crates/brk_bundler/src/lib.rs b/crates/brk_bundler/src/lib.rs index b89261ffc..9910e9e93 100644 --- a/crates/brk_bundler/src/lib.rs +++ b/crates/brk_bundler/src/lib.rs @@ -6,7 +6,10 @@ use std::{ sync::Arc, }; -use brk_rolldown::{Bundler, BundlerOptions, RawMinifyOptions, SourceMapType}; +use brk_rolldown::{ + Bundler, BundlerOptions, InlineConstConfig, InlineConstMode, InlineConstOption, + OptimizationOption, RawMinifyOptions, SourceMapType, +}; use log::error; use notify::{EventKind, RecursiveMode, Watcher}; use sugar_path::SugarPath; @@ -15,17 +18,17 @@ use tokio::sync::Mutex; const VERSION: &str = env!("CARGO_PKG_VERSION"); pub async fn bundle( - packages_path: &Path, + modules_path: &Path, websites_path: &Path, source_folder: &str, watch: bool, ) -> io::Result { - let relative_packages_path = packages_path; + let relative_modules_path = modules_path; let relative_source_path = websites_path.join(source_folder); let relative_dist_path = websites_path.join("dist"); - let absolute_packages_path = relative_packages_path.absolutize(); - let absolute_packages_path_clone = absolute_packages_path.clone(); + let absolute_modules_path = relative_modules_path.absolutize(); + let absolute_modules_path_clone = absolute_modules_path.clone(); let absolute_websites_path = websites_path.absolutize(); let absolute_websites_path_clone = absolute_websites_path.clone(); @@ -33,7 +36,7 @@ pub async fn bundle( let absolute_source_index_path = absolute_source_path.join("index.html"); let absolute_source_index_path_clone = absolute_source_index_path.clone(); let absolute_source_scripts_path = absolute_source_path.join("scripts"); - let absolute_source_scripts_packages_path = absolute_source_scripts_path.join("packages"); + let absolute_source_scripts_modules_path = absolute_source_scripts_path.join("modules"); let absolute_source_sw_path = absolute_source_path.join("service-worker.js"); let absolute_source_sw_path_clone = absolute_source_sw_path.clone(); @@ -45,21 +48,45 @@ pub async fn bundle( let absolute_dist_sw_path = absolute_dist_path.join("service-worker.js"); let _ = fs::remove_dir_all(&absolute_dist_path); - let _ = fs::remove_dir_all(&absolute_source_scripts_packages_path); + let _ = fs::remove_dir_all(&absolute_source_scripts_modules_path); copy_dir_all( - &absolute_packages_path, - &absolute_source_scripts_packages_path, + &absolute_modules_path, + &absolute_source_scripts_modules_path, )?; copy_dir_all(&absolute_source_path, &absolute_dist_path)?; fs::remove_dir_all(&absolute_dist_scripts_path)?; fs::create_dir(&absolute_dist_scripts_path)?; + // dbg!(BundlerOptions::default()); + let mut bundler = Bundler::new(BundlerOptions { input: Some(vec![format!("./{source_folder}/scripts/entry.js").into()]), dir: Some("./dist/scripts".to_string()), cwd: Some(absolute_websites_path), minify: Some(RawMinifyOptions::Bool(true)), sourcemap: Some(SourceMapType::File), + // advanced_chunks: Some(AdvancedChunksOptions { + // // min_size: Some(1000.0), + // min_share_count: Some(20), + // // min_module_size: S + // // include_dependencies_recursively: Some(true), + // ..Default::default() + // }), + // + // inline_dynamic_imports + // experimental: Some(ExperimentalOptions { + // strict_execution_order: Some(true), + // ..Default::default() + // }), + optimization: Some(OptimizationOption { + inline_const: Some(InlineConstOption::Config(InlineConstConfig { + mode: Some(InlineConstMode::All), + ..Default::default() + })), + // Needs benchmarks + // pife_for_module_wrappers: Some(true), + ..Default::default() + }), ..Default::default() }) .unwrap(); @@ -114,12 +141,11 @@ pub async fn bundle( update_dist_index(); } else if path == absolute_source_sw_path_clone { update_source_sw(); - } else if let Ok(suffix) = path.strip_prefix(&absolute_packages_path) { - let source_packages_path = - absolute_source_scripts_packages_path.join(suffix); + } else if let Ok(suffix) = path.strip_prefix(&absolute_modules_path) { + let source_modules_path = absolute_source_scripts_modules_path.join(suffix); if path.is_file() { let _ = fs::create_dir_all(path.parent().unwrap()); - let _ = fs::copy(&path, &source_packages_path); + let _ = fs::copy(&path, &source_modules_path); } } else if let Ok(suffix) = path.strip_prefix(&absolute_source_path) // scripts are handled by rolldown @@ -141,7 +167,7 @@ pub async fn bundle( .watch(&absolute_websites_path_clone, RecursiveMode::Recursive) .unwrap(); event_watcher - .watch(&absolute_packages_path_clone, RecursiveMode::Recursive) + .watch(&absolute_modules_path_clone, RecursiveMode::Recursive) .unwrap(); let watcher = diff --git a/crates/brk_cli/Cargo.toml b/crates/brk_cli/Cargo.toml index b96b1db4f..d94dfe8f5 100644 --- a/crates/brk_cli/Cargo.toml +++ b/crates/brk_cli/Cargo.toml @@ -11,7 +11,7 @@ build = "build.rs" [dependencies] bitcoincore-rpc = { workspace = true } -brk_bridge = { workspace = true } +brk_binder = { workspace = true } brk_bundler = { workspace = true } brk_computer = { workspace = true } brk_error = { workspace = true } diff --git a/crates/brk_cli/src/lib.rs b/crates/brk_cli/src/lib.rs index 9b2526d7b..ecc51860e 100644 --- a/crates/brk_cli/src/lib.rs +++ b/crates/brk_cli/src/lib.rs @@ -9,7 +9,7 @@ use std::{ }; use bitcoincore_rpc::{self, RpcApi}; -use brk_bridge::Bridge; +use brk_binder::Bridge; use brk_bundler::bundle; use brk_computer::Computer; use brk_error::Result; @@ -63,19 +63,19 @@ pub fn run() -> color_eyre::Result<()> { let future = async move { let bundle_path = if website.is_some() { let websites_dev_path = Path::new("../../websites"); - let packages_dev_path = Path::new("../../packages"); + let modules_dev_path = Path::new("../../modules"); let websites_path; - let packages_path; + let modules_path; - if fs::exists(websites_dev_path)? && fs::exists(packages_dev_path)? { + if fs::exists(websites_dev_path)? && fs::exists(modules_dev_path)? { websites_path = websites_dev_path.to_path_buf(); - packages_path = packages_dev_path.to_path_buf(); + modules_path = modules_dev_path.to_path_buf(); } else { let downloaded_brk_path = downloads_path.join(format!("brk-{VERSION}")); let downloaded_websites_path = downloaded_brk_path.join("websites"); - let downloaded_packages_path = downloaded_brk_path.join("packages"); + let downloaded_modules_path = downloaded_brk_path.join("modules"); if !fs::exists(&downloaded_websites_path)? { info!("Downloading source from Github..."); @@ -94,14 +94,14 @@ pub fn run() -> color_eyre::Result<()> { } websites_path = downloaded_websites_path; - packages_path = downloaded_packages_path; + modules_path = downloaded_modules_path; } - interface.generate_js_files(&packages_path)?; + interface.generate_js_files(&modules_path)?; Some( bundle( - &packages_path, + &modules_path, &websites_path, website.to_folder_name(), true, diff --git a/crates/brk_error/Cargo.toml b/crates/brk_error/Cargo.toml index 37697dd5d..9d4b1d34d 100644 --- a/crates/brk_error/Cargo.toml +++ b/crates/brk_error/Cargo.toml @@ -15,6 +15,6 @@ bitcoincore-rpc = { workspace = true } fjall = { workspace = true } jiff = { workspace = true } minreq = { workspace = true } -serde_json = { workspace = true } +sonic-rs = { workspace = true } vecdb = { workspace = true } zerocopy = { workspace = true } diff --git a/crates/brk_error/src/lib.rs b/crates/brk_error/src/lib.rs index cef0975fc..6e49ac211 100644 --- a/crates/brk_error/src/lib.rs +++ b/crates/brk_error/src/lib.rs @@ -19,7 +19,7 @@ pub enum Error { SystemTimeError(time::SystemTimeError), BitcoinConsensusEncode(bitcoin::consensus::encode::Error), BitcoinBip34Error(bitcoin::block::Bip34Error), - SerdeJson(serde_json::Error), + SonicRS(sonic_rs::Error), ZeroCopyError, Vecs(vecdb::Error), @@ -49,9 +49,9 @@ impl From for Error { } } -impl From for Error { - fn from(error: serde_json::Error) -> Self { - Self::SerdeJson(error) +impl From for Error { + fn from(error: sonic_rs::Error) -> Self { + Self::SonicRS(error) } } @@ -126,7 +126,7 @@ impl fmt::Display for Error { Error::Jiff(error) => Display::fmt(&error, f), Error::Minreq(error) => Display::fmt(&error, f), Error::SeqDB(error) => Display::fmt(&error, f), - Error::SerdeJson(error) => Display::fmt(&error, f), + Error::SonicRS(error) => Display::fmt(&error, f), Error::SystemTimeError(error) => Display::fmt(&error, f), Error::VecDB(error) => Display::fmt(&error, f), Error::Vecs(error) => Display::fmt(&error, f), diff --git a/crates/brk_fetcher/Cargo.toml b/crates/brk_fetcher/Cargo.toml index 5df516679..2097e4d23 100644 --- a/crates/brk_fetcher/Cargo.toml +++ b/crates/brk_fetcher/Cargo.toml @@ -15,4 +15,4 @@ brk_logger = { workspace = true } brk_structs = { workspace = true } log = { workspace = true } minreq = { workspace = true } -serde_json = { workspace = true } +sonic-rs = { workspace = true } diff --git a/crates/brk_fetcher/src/binance.rs b/crates/brk_fetcher/src/binance.rs index 44bc813f5..50a20c898 100644 --- a/crates/brk_fetcher/src/binance.rs +++ b/crates/brk_fetcher/src/binance.rs @@ -3,13 +3,12 @@ use std::{ fs::{self, File}, io::BufReader, path::{Path, PathBuf}, - str::FromStr, }; use brk_error::{Error, Result}; use brk_structs::{Cents, OHLCCents, Timestamp}; use log::info; -use serde_json::Value; +use sonic_rs::{JsonContainerTrait, JsonValueTrait, Value}; use crate::{Close, Date, Dollars, Fetcher, High, Low, Open, default_retry}; @@ -17,7 +16,7 @@ use crate::{Close, Date, Dollars, Fetcher, High, Low, Open, default_retry}; pub struct Binance { path: Option, _1mn: Option>, - pub _1d: Option>, + _1d: Option>, har: Option>, } @@ -69,11 +68,11 @@ impl Binance { info!("Fetching 1mn prices from Binance..."); default_retry(|_| { - Self::json_to_timestamp_to_ohlc( - &minreq::get(Self::url("interval=1m&limit=1000")) + Self::json_to_timestamp_to_ohlc(&sonic_rs::from_str( + minreq::get(Self::url("interval=1m&limit=1000")) .send()? - .json()?, - ) + .as_str()?, + )?) }) } @@ -94,7 +93,9 @@ impl Binance { info!("Fetching daily prices from Binance..."); default_retry(|_| { - Self::json_to_date_to_ohlc(&minreq::get(Self::url("interval=1d")).send()?.json()?) + Self::json_to_date_to_ohlc(&sonic_rs::from_str( + minreq::get(Self::url("interval=1d")).send()?.as_str()?, + )?) }) } @@ -119,7 +120,7 @@ impl Binance { let reader = BufReader::new(file); - let json: BTreeMap = if let Ok(json) = serde_json::from_reader(reader) { + let json: BTreeMap = if let Ok(json) = sonic_rs::from_reader(reader) { json } else { return Ok(Default::default()); @@ -129,7 +130,7 @@ impl Binance { .ok_or(Error::Str("Expect object to have log attribute"))? .as_object() .ok_or(Error::Str("Expect to be an object"))? - .get("entries") + .get(&"entries") .ok_or(Error::Str("Expect object to have entries"))? .as_array() .ok_or(Error::Str("Expect to be an array"))? @@ -138,11 +139,11 @@ impl Binance { entry .as_object() .unwrap() - .get("request") + .get(&"request") .unwrap() .as_object() .unwrap() - .get("url") + .get(&"url") .unwrap() .as_str() .unwrap() @@ -152,14 +153,14 @@ impl Binance { let response = entry .as_object() .unwrap() - .get("response") + .get(&"response") .unwrap() .as_object() .unwrap(); - let content = response.get("content").unwrap().as_object().unwrap(); + let content = response.get(&"content").unwrap().as_object().unwrap(); - let text = content.get("text"); + let text = content.get(&"text"); if text.is_none() { return Ok(BTreeMap::new()); @@ -167,7 +168,7 @@ impl Binance { let text = text.unwrap().as_str().unwrap(); - Self::json_to_timestamp_to_ohlc(&serde_json::Value::from_str(text).unwrap()) + Self::json_to_timestamp_to_ohlc(&sonic_rs::from_str(text).unwrap()) }) .try_fold(BTreeMap::default(), |mut all, res| { all.append(&mut res?); diff --git a/crates/brk_fetcher/src/brk.rs b/crates/brk_fetcher/src/brk.rs index fe1ead2be..edac9818a 100644 --- a/crates/brk_fetcher/src/brk.rs +++ b/crates/brk_fetcher/src/brk.rs @@ -3,7 +3,7 @@ use std::collections::BTreeMap; use brk_error::{Error, Result}; use brk_structs::{Cents, CheckedSub, Date, DateIndex, Height, OHLCCents}; use log::info; -use serde_json::Value; +use sonic_rs::{JsonContainerTrait, JsonValueTrait, Value}; use crate::{Close, Dollars, High, Low, Open, default_retry}; @@ -51,7 +51,7 @@ impl BRK { height + CHUNK_SIZE ); - let body: Value = minreq::get(url).send()?.json()?; + let body: Value = sonic_rs::from_str(minreq::get(url).send()?.as_str()?)?; body.as_array() .ok_or(Error::Str("Expect to be an array"))? @@ -96,7 +96,7 @@ impl BRK { dateindex + CHUNK_SIZE ); - let body: Value = minreq::get(url).send()?.json()?; + let body: Value = sonic_rs::from_str(minreq::get(url).send()?.json()?)?; body.as_array() .ok_or(Error::Str("Expect to be an array"))? diff --git a/crates/brk_fetcher/src/kraken.rs b/crates/brk_fetcher/src/kraken.rs index 4a79afd96..329beb7e9 100644 --- a/crates/brk_fetcher/src/kraken.rs +++ b/crates/brk_fetcher/src/kraken.rs @@ -3,7 +3,7 @@ use std::collections::BTreeMap; use brk_error::{Error, Result}; use brk_structs::{Cents, Close, Date, Dollars, High, Low, OHLCCents, Open, Timestamp}; use log::info; -use serde_json::Value; +use sonic_rs::{JsonContainerTrait, JsonValueTrait, Value}; use crate::{Fetcher, default_retry}; @@ -36,7 +36,9 @@ impl Kraken { info!("Fetching 1mn prices from Kraken..."); default_retry(|_| { - Self::json_to_timestamp_to_ohlc(&minreq::get(Self::url(1)).send()?.json()?) + Self::json_to_timestamp_to_ohlc(&sonic_rs::from_str( + minreq::get(Self::url(1)).send()?.as_str()?, + )?) }) } @@ -55,7 +57,11 @@ impl Kraken { pub fn fetch_1d() -> Result> { info!("Fetching daily prices from Kraken..."); - default_retry(|_| Self::json_to_date_to_ohlc(&minreq::get(Self::url(1440)).send()?.json()?)) + default_retry(|_| { + Self::json_to_date_to_ohlc(&sonic_rs::from_str( + minreq::get(Self::url(1440)).send()?.as_str()?, + )?) + }) } fn json_to_timestamp_to_ohlc(json: &Value) -> Result> { @@ -73,11 +79,11 @@ impl Kraken { { json.as_object() .ok_or(Error::Str("Expect to be an object"))? - .get("result") + .get(&"result") .ok_or(Error::Str("Expect object to have result"))? .as_object() .ok_or(Error::Str("Expect to be an object"))? - .get("XXBTZUSD") + .get(&"XXBTZUSD") .ok_or(Error::Str("Expect to have XXBTZUSD"))? .as_array() .ok_or(Error::Str("Expect to be an array"))? diff --git a/crates/brk_fetcher/src/retry.rs b/crates/brk_fetcher/src/retry.rs index d6eb4cc78..e40629e41 100644 --- a/crates/brk_fetcher/src/retry.rs +++ b/crates/brk_fetcher/src/retry.rs @@ -22,6 +22,7 @@ where if i == retries || res.is_ok() { return res; } else { + let _ = dbg!(res); info!("Failed, waiting {sleep_in_s} seconds..."); sleep(Duration::from_secs(sleep_in_s)); } diff --git a/crates/brk_interface/Cargo.toml b/crates/brk_interface/Cargo.toml index b4af17c8c..2b2e2f1a3 100644 --- a/crates/brk_interface/Cargo.toml +++ b/crates/brk_interface/Cargo.toml @@ -19,8 +19,8 @@ brk_structs = { workspace = true } vecdb = { workspace = true } derive_deref = { workspace = true } quick_cache = { workspace = true } -schemars = "1.0.4" +schemars = { workspace = true } serde = { workspace = true } -serde_json = { workspace = true } +sonic-rs = { workspace = true } serde_with = "3.14.1" nucleo-matcher = "0.3.1" diff --git a/crates/brk_interface/src/deser.rs b/crates/brk_interface/src/deser.rs index bc0caecce..a667b1afb 100644 --- a/crates/brk_interface/src/deser.rs +++ b/crates/brk_interface/src/deser.rs @@ -1,26 +1,27 @@ use serde::{Deserialize, Deserializer}; +use sonic_rs::{JsonValueTrait, Value}; pub fn de_unquote_i64<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { - let value: Option = Option::deserialize(deserializer)?; + let value: Option = Option::deserialize(deserializer)?; - match value { - None => Ok(None), - Some(serde_json::Value::String(mut s)) => { - if s.starts_with('"') && s.ends_with('"') && s.len() >= 2 { - s = s[1..s.len() - 1].to_string(); - } - s.parse::().map(Some).map_err(serde::de::Error::custom) + if value.is_none() { + return Ok(None); + } + + let value = value.unwrap(); + + if let Some(mut s) = value.as_str().map(|s| s.to_string()) { + if s.starts_with('"') && s.ends_with('"') && s.len() >= 2 { + s = s[1..s.len() - 1].to_string(); } - Some(serde_json::Value::Number(n)) => { - // If it's a number, convert it to i64 - n.as_i64() - .ok_or_else(|| serde::de::Error::custom("number out of range")) - .map(Some) - } - _ => Err(serde::de::Error::custom("expected a string or number")), + s.parse::().map(Some).map_err(serde::de::Error::custom) + } else if let Some(n) = value.as_i64() { + Ok(Some(n)) + } else { + Err(serde::de::Error::custom("expected a string or number")) } } @@ -28,28 +29,24 @@ pub fn de_unquote_usize<'de, D>(deserializer: D) -> Result, D::Err where D: Deserializer<'de>, { - let value: Option = Option::deserialize(deserializer)?; + let value: Option = Option::deserialize(deserializer)?; - match value { - None => Ok(None), - Some(serde_json::Value::String(mut s)) => { - if s.starts_with('"') && s.ends_with('"') && s.len() >= 2 { - s = s[1..s.len() - 1].to_string(); - } - s.parse::() - .map(Some) - .map_err(serde::de::Error::custom) - } - Some(serde_json::Value::Number(n)) => { - // If it's a number, convert it to usize - n.as_u64() - .ok_or_else(|| serde::de::Error::custom("number out of range")) - .map(|v| v as usize) - .map(Some) - } - _ => { - dbg!(value); - Err(serde::de::Error::custom("expected a string or number")) + if value.is_none() { + return Ok(None); + } + + let value = value.unwrap(); + + if let Some(mut s) = value.as_str().map(|s| s.to_string()) { + if s.starts_with('"') && s.ends_with('"') && s.len() >= 2 { + s = s[1..s.len() - 1].to_string(); } + s.parse::() + .map(Some) + .map_err(serde::de::Error::custom) + } else if let Some(n) = value.as_u64() { + Ok(Some(n as usize)) + } else { + Err(serde::de::Error::custom("expected a string or number")) } } diff --git a/crates/brk_interface/src/ids.rs b/crates/brk_interface/src/ids.rs index 253b9dacd..54ea7e97c 100644 --- a/crates/brk_interface/src/ids.rs +++ b/crates/brk_interface/src/ids.rs @@ -3,6 +3,7 @@ use std::fmt; use derive_deref::Deref; use schemars::JsonSchema; use serde::Deserialize; +use sonic_rs::{JsonContainerTrait, JsonValueTrait, Value}; #[derive(Debug, Deref, JsonSchema)] pub struct MaybeIds(Vec); @@ -27,26 +28,26 @@ impl<'de> Deserialize<'de> for MaybeIds { where D: serde::Deserializer<'de>, { - match serde_json::Value::deserialize(deserializer)? { - serde_json::Value::String(str) => { - if str.len() <= MAX_STRING_SIZE { - Ok(MaybeIds(sanitize_ids( - str.split(",").map(|s| s.to_string()), - ))) - } else { - Err(serde::de::Error::custom("Given parameter is too long")) - } + let value = Value::deserialize(deserializer)?; + + if let Some(str) = value.as_str() { + if str.len() <= MAX_STRING_SIZE { + Ok(MaybeIds(sanitize_ids( + str.split(",").map(|s| s.to_string()), + ))) + } else { + Err(serde::de::Error::custom("Given parameter is too long")) } - serde_json::Value::Array(vec) => { - if vec.len() <= MAX_VECS { - Ok(MaybeIds(sanitize_ids( - vec.into_iter().map(|s| s.as_str().unwrap().to_string()), - ))) - } else { - Err(serde::de::Error::custom("Given parameter is too long")) - } + } else if let Some(vec) = value.as_array() { + if vec.len() <= MAX_VECS { + Ok(MaybeIds(sanitize_ids( + vec.into_iter().map(|s| s.as_str().unwrap().to_string()), + ))) + } else { + Err(serde::de::Error::custom("Given parameter is too long")) } - _ => Err(serde::de::Error::custom("Bad ids format")), + } else { + Err(serde::de::Error::custom("Bad ids format")) } } } diff --git a/crates/brk_interface/src/lib.rs b/crates/brk_interface/src/lib.rs index 6e3ede143..8ae903e4d 100644 --- a/crates/brk_interface/src/lib.rs +++ b/crates/brk_interface/src/lib.rs @@ -27,7 +27,7 @@ pub use format::Format; pub use index::Index; pub use output::{Output, Value}; pub use pagination::{PaginatedIndexParam, PaginationParam}; -pub use params::{IdParam, Params, ParamsOpt}; +pub use params::{Params, ParamsOpt}; use vecs::Vecs; use crate::vecs::{IndexToVec, MetricToVec}; @@ -222,16 +222,16 @@ impl<'a> Interface<'a> { &self.vecs.index_to_metric_to_vec } - pub fn get_metric_count(&self) -> usize { + pub fn distinct_metric_count(&self) -> usize { self.vecs.metric_count } - pub fn get_index_count(&self) -> usize { - self.vecs.index_count + pub fn total_metric_count(&self) -> usize { + self.vecs.vec_count } - pub fn get_vec_count(&self) -> usize { - self.vecs.vec_count + pub fn index_count(&self) -> usize { + self.vecs.index_count } pub fn get_indexes(&self) -> &[&'static str] { @@ -250,8 +250,8 @@ impl<'a> Interface<'a> { self.vecs.index_to_ids(paginated_index) } - pub fn get_vecid_to_indexes(&self, id: String) -> Option<&Vec<&'static str>> { - self.vecs.id_to_indexes(id) + pub fn metric_to_indexes(&self, metric: String) -> Option<&Vec<&'static str>> { + self.vecs.metric_to_indexes(metric) } pub fn parser(&self) -> &Parser { diff --git a/crates/brk_interface/src/params.rs b/crates/brk_interface/src/params.rs index cc7965783..88ed78d06 100644 --- a/crates/brk_interface/src/params.rs +++ b/crates/brk_interface/src/params.rs @@ -106,8 +106,3 @@ impl ParamsOpt { self.format } } - -#[derive(Debug, Deserialize, JsonSchema)] -pub struct IdParam { - pub id: String, -} diff --git a/crates/brk_interface/src/vecs.rs b/crates/brk_interface/src/vecs.rs index 22bc1b9f4..b0e9a10e7 100644 --- a/crates/brk_interface/src/vecs.rs +++ b/crates/brk_interface/src/vecs.rs @@ -131,8 +131,9 @@ impl<'a> Vecs<'a> { &self.metrics[start..end] } - pub fn id_to_indexes(&self, id: String) -> Option<&Vec<&'static str>> { - self.metric_to_indexes.get(id.as_str()) + pub fn metric_to_indexes(&self, metric: String) -> Option<&Vec<&'static str>> { + self.metric_to_indexes + .get(metric.replace("-", "_").as_str()) } pub fn index_to_ids( diff --git a/crates/brk_mcp/Cargo.toml b/crates/brk_mcp/Cargo.toml index bf9247d85..760090448 100644 --- a/crates/brk_mcp/Cargo.toml +++ b/crates/brk_mcp/Cargo.toml @@ -17,7 +17,9 @@ brk_rmcp = { version = "0.7.1", features = [ "transport-streamable-http-server", ] } log = { workspace = true } -serde_json = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true } +serde_json = { version = "1.0.145", features = ["float_roundtrip"] } [package.metadata.cargo-machete] ignored = ["serde_json"] diff --git a/crates/brk_mcp/src/lib.rs b/crates/brk_mcp/src/lib.rs index 2a0d2ff1c..f766898e6 100644 --- a/crates/brk_mcp/src/lib.rs +++ b/crates/brk_mcp/src/lib.rs @@ -1,6 +1,6 @@ #![doc = include_str!("../README.md")] -use brk_interface::{IdParam, Interface, PaginatedIndexParam, PaginationParam, Params}; +use brk_interface::{Interface, PaginatedIndexParam, PaginationParam, Params}; use brk_rmcp::{ ErrorData as McpError, RoleServer, ServerHandler, handler::server::{router::tool::ToolRouter, wrapper::Parameters}, @@ -9,6 +9,8 @@ use brk_rmcp::{ tool, tool_handler, tool_router, }; use log::info; +use schemars::JsonSchema; +use serde::Deserialize; pub mod route; @@ -35,7 +37,7 @@ Get the count of all existing indexes. async fn get_index_count(&self) -> Result { info!("mcp: get_index_count"); Ok(CallToolResult::success(vec![ - Content::json(self.interface.get_index_count()).unwrap(), + Content::json(self.interface.index_count()).unwrap(), ])) } @@ -45,7 +47,7 @@ Get the count of all existing metrics. async fn get_metric_count(&self) -> Result { info!("mcp: get_metric_count"); Ok(CallToolResult::success(vec![ - Content::json(self.interface.get_metric_count()).unwrap(), + Content::json(self.interface.distinct_metric_count()).unwrap(), ])) } @@ -56,7 +58,7 @@ Equals to the sum of supported Indexes of each vec id. async fn get_vec_count(&self) -> Result { info!("mcp: get_vec_count"); Ok(CallToolResult::success(vec![ - Content::json(self.interface.get_vec_count()).unwrap(), + Content::json(self.interface.total_metric_count()).unwrap(), ])) } @@ -120,7 +122,7 @@ The list will be empty if the vec id isn't correct. ) -> Result { info!("mcp: get_vecid_to_indexes"); Ok(CallToolResult::success(vec![ - Content::json(self.interface.get_vecid_to_indexes(param.id)).unwrap(), + Content::json(self.interface.metric_to_indexes(param.id)).unwrap(), ])) } @@ -186,3 +188,8 @@ An 'Index' (or indexes) is the timeframe of a dataset. Ok(self.get_info()) } } + +#[derive(Debug, Deserialize, JsonSchema)] +pub struct IdParam { + pub id: String, +} diff --git a/crates/brk_server/Cargo.toml b/crates/brk_server/Cargo.toml index cc9bb6fb9..b453dd6d8 100644 --- a/crates/brk_server/Cargo.toml +++ b/crates/brk_server/Cargo.toml @@ -27,7 +27,6 @@ jiff = { workspace = true } log = { workspace = true } quick_cache = { workspace = true } serde = { workspace = true } -serde_json = { workspace = true } sonic-rs = { workspace = true } tokio = { workspace = true } tower-http = { version = "0.6.6", features = ["compression-full", "trace"] } diff --git a/crates/brk_server/src/api/explorer/mod.rs b/crates/brk_server/src/api/explorer/mod.rs new file mode 100644 index 000000000..947f2f351 --- /dev/null +++ b/crates/brk_server/src/api/explorer/mod.rs @@ -0,0 +1,238 @@ +use std::{ + fs::File, + io::{Cursor, Read, Seek, SeekFrom}, + str::FromStr, +}; + +use axum::{ + Json, Router, + extract::{Path, State}, + response::{IntoResponse, Response}, + routing::get, +}; +use bitcoin::{Address, Network, Transaction, consensus::Decodable}; +use brk_parser::XORIndex; +use brk_structs::{ + AddressBytesHash, AnyAddressDataIndexEnum, Bitcoin, OutputType, TxIndex, Txid, TxidPrefix, +}; +use serde::Serialize; +use sonic_rs::{Number, Value}; +use vecdb::{AnyIterableVec, VecIterator}; + +use super::AppState; + +pub trait ApiExplorerRoutes { + fn add_api_explorer_routes(self) -> Self; +} + +#[derive(Serialize)] +struct TxResponse { + txid: Txid, + index: TxIndex, + tx: Transaction, +} + +impl ApiExplorerRoutes for Router { + fn add_api_explorer_routes(self) -> Self { + self.route( + "/api/address/{address}", + get( + async |Path(address): Path, state: State| -> Response { + let Ok(address) = Address::from_str(&address) else { + return "Invalid address".into_response(); + }; + if !address.is_valid_for_network(Network::Bitcoin) { + return "Invalid address".into_response(); + } + let address = address.assume_checked(); + let interface = state.interface; + let indexer = interface.indexer(); + let computer = interface.computer(); + let stores = &indexer.stores; + let hash = AddressBytesHash::from(&address); + + let Ok(Some(addri)) = stores + .addressbyteshash_to_typeindex + .get(&hash) + .map(|opt| opt.map(|cow| cow.into_owned())) + else { + return "Unknown address".into_response(); + }; + + let output_type = OutputType::from(&address); + let stateful = &computer.stateful; + let price = computer.price.as_ref().map(|v| { + *v.timeindexes_to_price_close + .dateindex + .as_ref() + .unwrap() + .iter() + .last() + .unwrap() + .1 + .into_owned() + }); + + let anyaddri = match output_type { + OutputType::P2PK33 => stateful + .p2pk33addressindex_to_anyaddressindex + .iter() + .unwrap_get_inner(addri.into()), + OutputType::P2PK65 => stateful + .p2pk65addressindex_to_anyaddressindex + .iter() + .unwrap_get_inner(addri.into()), + OutputType::P2PKH => stateful + .p2pkhaddressindex_to_anyaddressindex + .iter() + .unwrap_get_inner(addri.into()), + OutputType::P2SH => stateful + .p2shaddressindex_to_anyaddressindex + .iter() + .unwrap_get_inner(addri.into()), + OutputType::P2TR => stateful + .p2traddressindex_to_anyaddressindex + .iter() + .unwrap_get_inner(addri.into()), + OutputType::P2WPKH => stateful + .p2wpkhaddressindex_to_anyaddressindex + .iter() + .unwrap_get_inner(addri.into()), + OutputType::P2WSH => stateful + .p2wshaddressindex_to_anyaddressindex + .iter() + .unwrap_get_inner(addri.into()), + OutputType::P2A => stateful + .p2aaddressindex_to_anyaddressindex + .iter() + .unwrap_get_inner(addri.into()), + + _ => unreachable!(), + }; + + let addr_data = match anyaddri.to_enum() { + AnyAddressDataIndexEnum::Loaded(loadedi) => stateful + .loadedaddressindex_to_loadedaddressdata + .iter() + .unwrap_get_inner(loadedi), + AnyAddressDataIndexEnum::Empty(emptyi) => stateful + .emptyaddressindex_to_emptyaddressdata + .iter() + .unwrap_get_inner(emptyi) + .into(), + }; + + let amount = addr_data.amount(); + Json(sonic_rs::json!({ + "address": address, + "type": output_type, + "index": addri, + "chain_stats": { + "funded_txo_count": null, + "funded_txo_sum": addr_data.received, + "spent_txo_count": null, + "spent_txo_sum": addr_data.sent, + "utxo_count": addr_data.utxos, + "balance": amount, + "balance_usd": price.map_or(Value::new(), |p| { + Value::from(Number::from_f64(*(p * Bitcoin::from(amount))).unwrap()) + }), + "realized_value": addr_data.realized_cap, + "tx_count": null, + "avg_cost_basis": addr_data.realized_price() + }, + "mempool_stats": null + })) + .into_response() + }, + ), + ) + .route( + "/api/tx/{txid}", + get( + async |Path(txid): Path, state: State| -> Response { + let Ok(txid) = bitcoin::Txid::from_str(&txid) else { + return "Invalid txid".into_response(); + }; + + let txid = Txid::from(txid); + let prefix = TxidPrefix::from(&txid); + let interface = state.interface; + let indexer = interface.indexer(); + let Ok(Some(txindex)) = indexer + .stores + .txidprefix_to_txindex + .get(&prefix) + .map(|opt| opt.map(|cow| cow.into_owned())) + else { + return "Unknown transaction".into_response(); + }; + + let txid = indexer + .vecs + .txindex_to_txid + .iter() + .unwrap_get_inner(txindex); + + let parser = interface.parser(); + let computer = interface.computer(); + + let position = computer + .blks + .txindex_to_position + .iter() + .unwrap_get_inner(txindex); + let len = indexer + .vecs + .txindex_to_total_size + .iter() + .unwrap_get_inner(txindex); + + let blk_index_to_blk_path = parser.blk_index_to_blk_path(); + + let Some(blk_path) = blk_index_to_blk_path.get(&position.blk_index()) else { + return "Unknown blk index".into_response(); + }; + + let mut xori = XORIndex::default(); + xori.add_assign(position.offset() as usize); + + let Ok(mut file) = File::open(blk_path) else { + return "Error opening blk file".into_response(); + }; + + if file + .seek(SeekFrom::Start(position.offset() as u64)) + .is_err() + { + return "Error seeking position in blk file".into_response(); + } + + let mut buffer = vec![0u8; *len as usize]; + if file.read_exact(&mut buffer).is_err() { + return "File fail read exact".into_response(); + } + xori.bytes(&mut buffer, parser.xor_bytes()); + + let mut reader = Cursor::new(buffer); + let Ok(tx) = Transaction::consensus_decode(&mut reader) else { + return "Error decoding transaction".into_response(); + }; + + let response = TxResponse { + txid, + index: txindex, + tx, + }; + + let bytes = sonic_rs::to_vec(&response).unwrap(); + + Response::builder() + .header("content-type", "application/json") + .body(bytes.into()) + .unwrap() + }, + ), + ) + } +} diff --git a/crates/brk_server/src/api/explorer.rs b/crates/brk_server/src/api/explorer/reader.rs similarity index 100% rename from crates/brk_server/src/api/explorer.rs rename to crates/brk_server/src/api/explorer/reader.rs diff --git a/crates/brk_server/src/api/metrics.rs b/crates/brk_server/src/api/metrics/data.rs similarity index 100% rename from crates/brk_server/src/api/metrics.rs rename to crates/brk_server/src/api/metrics/data.rs diff --git a/crates/brk_server/src/api/metrics/mod.rs b/crates/brk_server/src/api/metrics/mod.rs new file mode 100644 index 000000000..0d88e906e --- /dev/null +++ b/crates/brk_server/src/api/metrics/mod.rs @@ -0,0 +1,111 @@ +use axum::{ + Json, Router, + extract::{Path, Query, State}, + http::{HeaderMap, Uri}, + response::{IntoResponse, Response}, + routing::get, +}; +use brk_interface::{Index, PaginatedIndexParam, PaginationParam, Params, ParamsOpt}; + +use super::AppState; + +mod data; + +pub trait ApiMetricsRoutes { + fn add_api_metrics_routes(self) -> Self; +} + +const TO_SEPARATOR: &str = "_to_"; + +impl ApiMetricsRoutes for Router { + fn add_api_metrics_routes(self) -> Self { + self.route( + "/api/metrics/count", + get(async |State(app_state): State| -> Response { + Json(sonic_rs::json!({ + "distinct": app_state.interface.distinct_metric_count(), + "total": app_state.interface.total_metric_count(), + })) + .into_response() + }), + ) + .route( + "/api/metrics/indexes", + get(async |State(app_state): State| -> Response { + Json(app_state.interface.get_accepted_indexes()).into_response() + }), + ) + .route( + "/api/vecs/metrics", + get( + async |State(app_state): State, + Query(pagination): Query| + -> Response { + Json(app_state.interface.get_metrics(pagination)).into_response() + }, + ), + ) + .route( + "/api/vecs/index-to-metrics", + get( + async |State(app_state): State, + Query(paginated_index): Query| + -> Response { + Json(app_state.interface.get_index_to_vecids(paginated_index)).into_response() + }, + ), + ) + .route( + "/api/metrics/{metric}", + get( + async |State(app_state): State, Path(metric): Path| -> Response { + // If not found do fuzzy search but here or in interface ? + Json(app_state.interface.metric_to_indexes(metric)).into_response() + }, + ), + ) + .route( + "/api/metrics/{metric}/{index}", + get( + async |State(app_state): State, + Path((metric, index)): Path<(String, Index)>| + -> Response { + // If not found do fuzzy search but here or in interface ? + Json( + format!("{metric}/{index}"), // app_state + // .interface + // .metric_to_indexes(metric.replace("-", "_")), + ) + .into_response() + }, + ), + ) + // DEPRECATED + .route("/api/vecs/query", get(data::handler)) + // DEPRECATED + .route( + "/api/vecs/{variant}", + get( + async |uri: Uri, + headers: HeaderMap, + Path(variant): Path, + Query(params_opt): Query, + state: State| + -> Response { + let variant = variant.replace("-", "_"); + let mut split = variant.split(TO_SEPARATOR); + + if let Ok(index) = Index::try_from(split.next().unwrap()) { + let params = Params::from(( + (index, split.collect::>().join(TO_SEPARATOR)), + params_opt, + )); + data::handler(uri, headers, Query(params), state).await + } else { + "Bad variant".into_response() + } + }, + ), + ) + } +} diff --git a/crates/brk_server/src/api/mod.rs b/crates/brk_server/src/api/mod.rs index 2a5d5c2e0..dec94d49a 100644 --- a/crates/brk_server/src/api/mod.rs +++ b/crates/brk_server/src/api/mod.rs @@ -1,25 +1,6 @@ -use std::{ - fs::File, - io::{Cursor, Read, Seek, SeekFrom}, - str::FromStr, -}; +use axum::{Router, response::Redirect, routing::get}; -use axum::{ - Json, Router, - extract::{Path, Query, State}, - http::{HeaderMap, Uri}, - response::{IntoResponse, Redirect, Response}, - routing::get, -}; -use bitcoin::{Address, Network, Transaction, consensus::Decodable}; -use brk_interface::{IdParam, Index, PaginatedIndexParam, PaginationParam, Params, ParamsOpt}; -use brk_parser::XORIndex; -use brk_structs::{ - AddressBytesHash, AnyAddressDataIndexEnum, Bitcoin, OutputType, TxIndex, Txid, TxidPrefix, -}; -use serde::Serialize; -use serde_json::Number; -use vecdb::{AnyIterableVec, VecIterator}; +use crate::api::{explorer::ApiExplorerRoutes, metrics::ApiMetricsRoutes}; use super::AppState; @@ -30,292 +11,17 @@ pub trait ApiRoutes { fn add_api_routes(self) -> Self; } -const TO_SEPARATOR: &str = "_to_"; - -#[derive(Serialize)] -struct TxResponse { - txid: Txid, - index: TxIndex, - tx: Transaction, -} - impl ApiRoutes for Router { fn add_api_routes(self) -> Self { - self.route( - "/api/address/{address}", - get( - async |Path(address): Path, state: State| -> Response { - let Ok(address) = Address::from_str(&address) else { - return "Invalid address".into_response(); - }; - if !address.is_valid_for_network(Network::Bitcoin) { - return "Invalid address".into_response(); - } - let address = address.assume_checked(); - let interface = state.interface; - let indexer = interface.indexer(); - let computer = interface.computer(); - let stores = &indexer.stores; - let hash = AddressBytesHash::from(&address); - - let Ok(Some(addri)) = stores - .addressbyteshash_to_typeindex - .get(&hash) - .map(|opt| opt.map(|cow| cow.into_owned())) else { - return "Unknown address".into_response(); - }; - - let output_type = OutputType::from(&address); - let stateful = &computer.stateful; - let price = computer.price.as_ref().map(|v| { - *v.timeindexes_to_price_close - .dateindex - .as_ref() - .unwrap() - .iter() - .last() - .unwrap() - .1 - .into_owned() - }); - - let anyaddri = match output_type { - OutputType::P2PK33 => stateful - .p2pk33addressindex_to_anyaddressindex - .iter() - .unwrap_get_inner(addri.into()), - OutputType::P2PK65 => stateful - .p2pk65addressindex_to_anyaddressindex - .iter() - .unwrap_get_inner(addri.into()), - OutputType::P2PKH => stateful - .p2pkhaddressindex_to_anyaddressindex - .iter() - .unwrap_get_inner(addri.into()), - OutputType::P2SH => stateful - .p2shaddressindex_to_anyaddressindex - .iter() - .unwrap_get_inner(addri.into()), - OutputType::P2TR => stateful - .p2traddressindex_to_anyaddressindex - .iter() - .unwrap_get_inner(addri.into()), - OutputType::P2WPKH => stateful - .p2wpkhaddressindex_to_anyaddressindex - .iter() - .unwrap_get_inner(addri.into()), - OutputType::P2WSH => stateful - .p2wshaddressindex_to_anyaddressindex - .iter() - .unwrap_get_inner(addri.into()), - OutputType::P2A => stateful - .p2aaddressindex_to_anyaddressindex - .iter() - .unwrap_get_inner(addri.into()), - - _ => unreachable!(), - }; - - let addr_data = match anyaddri.to_enum() { - AnyAddressDataIndexEnum::Loaded(loadedi) => stateful - .loadedaddressindex_to_loadedaddressdata - .iter() - .unwrap_get_inner(loadedi), - AnyAddressDataIndexEnum::Empty(emptyi) => stateful - .emptyaddressindex_to_emptyaddressdata - .iter() - .unwrap_get_inner(emptyi) - .into(), - }; - - let amount = addr_data.amount(); - Json(serde_json::json!({ - "address": address, - "type": output_type, - "index": addri, - "chain_stats": { - "funded_txo_count": serde_json::Value::Null, - "funded_txo_sum": addr_data.received, - "spent_txo_count": serde_json::Value::Null, - "spent_txo_sum": addr_data.sent, - "utxo_count": addr_data.utxos, - "balance": amount, - "balance_usd": price.map_or(serde_json::Value::Null, |p| serde_json::Value::Number(Number::from_f64( *(p * Bitcoin::from(amount))).unwrap())), - "realized_value": addr_data.realized_cap, - "tx_count": serde_json::Value::Null, - "avg_cost_basis": addr_data.realized_price() - }, - "mempool_stats": serde_json::Value::Null - })) - .into_response() - }, - ), - ) - .route( - "/api/tx/{txid}", - get( - async |Path(txid): Path, state: State| -> Response { - let Ok(txid) = bitcoin::Txid::from_str(&txid) else { - return "Invalid txid".into_response() - }; - - let txid = Txid::from(txid); - let prefix = TxidPrefix::from(&txid); - let interface = state.interface; - let indexer = interface.indexer(); - let Ok(Some(txindex)) = indexer - .stores - .txidprefix_to_txindex - .get(&prefix) - .map(|opt| opt.map(|cow| cow.into_owned())) else { - return "Unknown transaction".into_response(); - }; - - let txid = indexer - .vecs - .txindex_to_txid - .iter() - .unwrap_get_inner(txindex); - - let parser = interface.parser(); - let computer = interface.computer(); - - let position = computer.blks.txindex_to_position.iter().unwrap_get_inner(txindex); - let len = indexer.vecs.txindex_to_total_size.iter().unwrap_get_inner(txindex); - - let blk_index_to_blk_path = parser.blk_index_to_blk_path(); - - let Some(blk_path) = blk_index_to_blk_path.get(&position.blk_index()) else { - return "Unknown blk index".into_response(); - }; - - let mut xori = XORIndex::default(); - xori.add_assign(position.offset() as usize); - - let Ok(mut file) = File::open(blk_path) else { - return "Error opening blk file".into_response(); - }; - - if file.seek(SeekFrom::Start(position.offset() as u64)).is_err() { - return "Error seeking position in blk file".into_response(); - } - - let mut buffer = vec![0u8; *len as usize]; - if file.read_exact(&mut buffer).is_err() { - return "File fail read exact".into_response(); - } - xori.bytes(&mut buffer, parser.xor_bytes()); - - let mut reader = Cursor::new(buffer); - let Ok(tx) = Transaction::consensus_decode(&mut reader) else { - return "Error decoding transaction".into_response(); - }; - - let response = TxResponse { txid, index: txindex, tx }; - - let bytes = sonic_rs::to_vec(&response).unwrap(); - - Response::builder() - .header("content-type", "application/json") - .body(bytes.into()) - .unwrap() - }, - ), - ) - .route( - "/api/vecs/index-count", - get(async |State(app_state): State| -> Response { - Json(app_state.interface.get_index_count()).into_response() - }), - ) - .route( - "/api/vecs/metric-count", - get(async |State(app_state): State| -> Response { - Json(app_state.interface.get_metric_count()).into_response() - }), - ) - .route( - "/api/vecs/vec-count", - get(async |State(app_state): State| -> Response { - Json(app_state.interface.get_vec_count()).into_response() - }), - ) - .route( - "/api/vecs/indexes", - get(async |State(app_state): State| -> Response { - Json(app_state.interface.get_indexes()).into_response() - }), - ) - .route( - "/api/vecs/accepted-indexes", - get(async |State(app_state): State| -> Response { - Json(app_state.interface.get_accepted_indexes()).into_response() - }), - ) - .route( - "/api/vecs/metrics", - get( - async |State(app_state): State, - Query(pagination): Query| - -> Response { - Json(app_state.interface.get_metrics(pagination)).into_response() - }, - ), - ) - .route( - "/api/vecs/index-to-metrics", - get( - async |State(app_state): State, - Query(paginated_index): Query| - -> Response { - Json(app_state.interface.get_index_to_vecids(paginated_index)).into_response() - }, - ), - ) - .route( - "/api/vecs/metric-to-indexes", - get( - async |State(app_state): State, - Query(param): Query| - -> Response { - Json(app_state.interface.get_vecid_to_indexes(param.id)).into_response() - }, - ), - ) - // DEPRECATED - .route("/api/vecs/query", get(metrics::handler)) - // DEPRECATED - .route( - "/api/vecs/{variant}", - get( - async |uri: Uri, - headers: HeaderMap, - Path(variant): Path, - Query(params_opt): Query, - state: State| - -> Response { - let variant = variant.replace("-", "_"); - let mut split = variant.split(TO_SEPARATOR); - - if let Ok(index) = Index::try_from(split.next().unwrap()) { - let params = Params::from(( - (index, split.collect::>().join(TO_SEPARATOR)), - params_opt, - )); - metrics::handler(uri, headers, Query(params), state).await - } else { - "Bad variant".into_response() - } - }, - ), - ) - .route( - "/api", - get(|| async { - Redirect::temporary( - "https://github.com/bitcoinresearchkit/brk/tree/main/crates/brk_server#api", - ) - }), - ) + self.add_api_explorer_routes() + .add_api_metrics_routes() + .route( + "/api", + get(|| async { + Redirect::temporary( + "https://github.com/bitcoinresearchkit/brk/tree/main/crates/brk_server#api", + ) + }), + ) } } diff --git a/crates/brk_server/src/lib.rs b/crates/brk_server/src/lib.rs index d63d05544..1f80fb254 100644 --- a/crates/brk_server/src/lib.rs +++ b/crates/brk_server/src/lib.rs @@ -101,7 +101,7 @@ impl Server { .route("/version", get(Json(VERSION))) .route( "/health", - get(Json(serde_json::json!({ + get(Json(sonic_rs::json!({ "status": "healthy", "service": "brk-server", "timestamp": jiff::Timestamp::now().to_string() diff --git a/packages/.gitignore b/modules/.gitignore similarity index 87% rename from packages/.gitignore rename to modules/.gitignore index 715a3dd7d..b1337dbe4 100644 --- a/packages/.gitignore +++ b/modules/.gitignore @@ -1,5 +1,5 @@ LICENSE -*/**/*.json +**/*.*.*/*.json *webcomponent* README*.md cli* diff --git a/packages/brk-client/.gitignore b/modules/brk-client/.gitignore similarity index 100% rename from packages/brk-client/.gitignore rename to modules/brk-client/.gitignore diff --git a/packages/brk-client/idle.js b/modules/brk-client/idle.js similarity index 100% rename from packages/brk-client/idle.js rename to modules/brk-client/idle.js diff --git a/packages/brk-client/index.js b/modules/brk-client/index.js similarity index 82% rename from packages/brk-client/index.js rename to modules/brk-client/index.js index 0d9db8ac9..7a9dd388b 100644 --- a/packages/brk-client/index.js +++ b/modules/brk-client/index.js @@ -1,18 +1,25 @@ +/** + * @import { IndexName } from "./generated/metrics" + * @import { Metric } from './metrics' + * + * @typedef {ReturnType} BRK + */ + +// client.metrics.catalog.a.b.c() -> string (uncompress inside) + import { runWhenIdle } from "./idle"; import { POOL_ID_TO_POOL_NAME } from "./generated/pools"; -import { hasMetric, metricToIndexes } from "./metrics"; - -/** - * @typedef {ReturnType} BRKClient - */ +import { INDEXES } from "./generated/metrics"; +import { hasMetric, getIndexesFromMetric } from "./metrics"; +import { VERSION } from "./generated/version"; const CACHE_NAME = "__BRK_CLIENT__"; /** - * @param {string} [origin] - defaults to / + * @param {string} origin */ -export function createClient(origin = "/") { +export function createClient(origin) { /** * @template T * @param {(value: T) => void} callback @@ -80,7 +87,7 @@ export function createClient(origin = "/") { /** * @param {Metric} metric - * @param {Index} index + * @param {IndexName} index * @param {number} [from] * @param {number} [to] */ @@ -102,7 +109,7 @@ export function createClient(origin = "/") { /** * @template T * @param {(v: T[]) => void} callback - * @param {Index} index + * @param {IndexName} index * @param {Metric} metric * @param {number} [from] * @param {number} [to] @@ -112,9 +119,12 @@ export function createClient(origin = "/") { } return { + VERSION, POOL_ID_TO_POOL_NAME, + INDEXES, + hasMetric, - metricToIndexes, + getIndexesFromMetric, genMetricURL, fetchMetric, diff --git a/packages/jsconfig.json b/modules/brk-client/jsconfig.json similarity index 100% rename from packages/jsconfig.json rename to modules/brk-client/jsconfig.json diff --git a/packages/brk-client/metrics.js b/modules/brk-client/metrics.js similarity index 91% rename from packages/brk-client/metrics.js rename to modules/brk-client/metrics.js index 3a3fa55d5..f54e6cb7f 100644 --- a/packages/brk-client/metrics.js +++ b/modules/brk-client/metrics.js @@ -3,6 +3,11 @@ import { COMPRESSED_METRIC_TO_INDEXES, } from "./generated/metrics"; +/** + * @typedef {typeof import("./generated/metrics")["COMPRESSED_METRIC_TO_INDEXES"]} MetricToIndexes + * @typedef {string} Metric + */ + /** @type {Record} */ const WORD_TO_INDEX = {}; @@ -13,7 +18,7 @@ INDEX_TO_WORD.forEach((word, index) => { /** * @param {Metric} metric */ -export function metricToIndexes(metric) { +export function getIndexesFromMetric(metric) { return COMPRESSED_METRIC_TO_INDEXES[compressMetric(metric)]; } diff --git a/packages/tsconfig.json b/modules/brk-client/tsconfig.json similarity index 100% rename from packages/tsconfig.json rename to modules/brk-client/tsconfig.json diff --git a/packages/brk-resources/index.js b/modules/brk-resources/index.js similarity index 84% rename from packages/brk-resources/index.js rename to modules/brk-resources/index.js index 2b1a3fd48..56d96299d 100644 --- a/packages/brk-resources/index.js +++ b/modules/brk-resources/index.js @@ -1,17 +1,20 @@ /** * @import { Signal, Signals } from "../brk-signals/index"; + * @import { BRK } from '../brk-client/index' + * @import { Metric } from '../brk-client/metrics' + * @import { IndexName } from '../brk-client/generated/metrics' */ /** - * @typedef {ReturnType} BRKResources - * @typedef {ReturnType} BRKMetricResource + * @typedef {ReturnType} Resources + * @typedef {ReturnType} MetricResource */ /** - * @param {BRKClient} client + * @param {BRK} brk * @param {Signals} signals */ -export function createResources(client, signals) { +export function createResources(brk, signals) { const owner = signals.getOwner(); const defaultFrom = -10_000; @@ -29,10 +32,10 @@ export function createResources(client, signals) { /** * @template T * @param {Metric} metric - * @param {Index} index + * @param {IndexName} index */ function createMetricResource(metric, index) { - if (client.hasMetric(metric)) { + if (!brk.hasMetric(metric)) { throw Error(`${metric} is invalid`); } @@ -44,7 +47,7 @@ export function createResources(client, signals) { ); return { - url: client.genMetricURL(metric, index, defaultFrom), + url: brk.genMetricURL(metric, index, defaultFrom), fetched: fetchedRecord, /** * Defaults @@ -81,7 +84,7 @@ export function createResources(client, signals) { } fetched.loading = true; const res = /** @type {T[] | null} */ ( - await client.fetchMetric( + await brk.fetchMetric( (data) => { if (data.length || !fetched.data()) { fetched.data.set(data); @@ -108,7 +111,7 @@ export function createResources(client, signals) { /** * @template T * @param {Metric} metric - * @param {Index} index + * @param {IndexName} index */ getOrCreate(metric, index) { const key = `${metric}/${index}`; diff --git a/modules/brk-resources/jsconfig.json b/modules/brk-resources/jsconfig.json new file mode 100644 index 000000000..cffb39bbd --- /dev/null +++ b/modules/brk-resources/jsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "checkJs": true, + "strict": true, + "target": "ESNext", + "module": "ESNext", + "skipLibCheck": true + }, + "exclude": ["dist"] +} diff --git a/modules/brk-resources/tsconfig.json b/modules/brk-resources/tsconfig.json new file mode 100644 index 000000000..cfe38122e --- /dev/null +++ b/modules/brk-resources/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "strict": true, + "target": "ESNext", + "module": "ESNext", + "outDir": "/tmp/brk", + "lib": ["DOM", "DOM.Iterable", "ESNext", "WebWorker"], + "skipLibCheck": true + }, + "exclude": ["dist"] +} diff --git a/packages/brk-signals/index.js b/modules/brk-signals/index.js similarity index 100% rename from packages/brk-signals/index.js rename to modules/brk-signals/index.js diff --git a/modules/brk-signals/jsconfig.json b/modules/brk-signals/jsconfig.json new file mode 100644 index 000000000..cffb39bbd --- /dev/null +++ b/modules/brk-signals/jsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "checkJs": true, + "strict": true, + "target": "ESNext", + "module": "ESNext", + "skipLibCheck": true + }, + "exclude": ["dist"] +} diff --git a/modules/brk-signals/tsconfig.json b/modules/brk-signals/tsconfig.json new file mode 100644 index 000000000..cfe38122e --- /dev/null +++ b/modules/brk-signals/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "strict": true, + "target": "ESNext", + "module": "ESNext", + "outDir": "/tmp/brk", + "lib": ["DOM", "DOM.Iterable", "ESNext", "WebWorker"], + "skipLibCheck": true + }, + "exclude": ["dist"] +} diff --git a/packages/lean-qr/.gitignore b/modules/lean-qr/.gitignore similarity index 100% rename from packages/lean-qr/.gitignore rename to modules/lean-qr/.gitignore diff --git a/packages/lean-qr/2.6.0/index.d.ts b/modules/lean-qr/2.6.0/index.d.ts similarity index 100% rename from packages/lean-qr/2.6.0/index.d.ts rename to modules/lean-qr/2.6.0/index.d.ts diff --git a/packages/lean-qr/2.6.0/index.mjs b/modules/lean-qr/2.6.0/index.mjs similarity index 100% rename from packages/lean-qr/2.6.0/index.mjs rename to modules/lean-qr/2.6.0/index.mjs diff --git a/packages/leeoniya-ufuzzy/.gitignore b/modules/leeoniya-ufuzzy/.gitignore similarity index 100% rename from packages/leeoniya-ufuzzy/.gitignore rename to modules/leeoniya-ufuzzy/.gitignore diff --git a/packages/leeoniya-ufuzzy/1.0.19/dist/uFuzzy.d.ts b/modules/leeoniya-ufuzzy/1.0.19/dist/uFuzzy.d.ts similarity index 100% rename from packages/leeoniya-ufuzzy/1.0.19/dist/uFuzzy.d.ts rename to modules/leeoniya-ufuzzy/1.0.19/dist/uFuzzy.d.ts diff --git a/packages/leeoniya-ufuzzy/1.0.19/dist/uFuzzy.mjs b/modules/leeoniya-ufuzzy/1.0.19/dist/uFuzzy.mjs similarity index 100% rename from packages/leeoniya-ufuzzy/1.0.19/dist/uFuzzy.mjs rename to modules/leeoniya-ufuzzy/1.0.19/dist/uFuzzy.mjs diff --git a/packages/lightweight-charts/.gitignore b/modules/lightweight-charts/.gitignore similarity index 100% rename from packages/lightweight-charts/.gitignore rename to modules/lightweight-charts/.gitignore diff --git a/packages/lightweight-charts/5.0.8/dist/lightweight-charts.standalone.production.mjs b/modules/lightweight-charts/5.0.8/dist/lightweight-charts.standalone.production.mjs similarity index 100% rename from packages/lightweight-charts/5.0.8/dist/lightweight-charts.standalone.production.mjs rename to modules/lightweight-charts/5.0.8/dist/lightweight-charts.standalone.production.mjs diff --git a/packages/lightweight-charts/5.0.8/dist/typings.d.ts b/modules/lightweight-charts/5.0.8/dist/typings.d.ts similarity index 100% rename from packages/lightweight-charts/5.0.8/dist/typings.d.ts rename to modules/lightweight-charts/5.0.8/dist/typings.d.ts diff --git a/packages/lightweight-charts/NOTICE.md b/modules/lightweight-charts/NOTICE.md similarity index 100% rename from packages/lightweight-charts/NOTICE.md rename to modules/lightweight-charts/NOTICE.md diff --git a/packages/modern-screenshot/4.6.6/.gitignore b/modules/modern-screenshot/4.6.6/.gitignore similarity index 100% rename from packages/modern-screenshot/4.6.6/.gitignore rename to modules/modern-screenshot/4.6.6/.gitignore diff --git a/packages/modern-screenshot/4.6.6/dist/index.d.ts b/modules/modern-screenshot/4.6.6/dist/index.d.ts similarity index 100% rename from packages/modern-screenshot/4.6.6/dist/index.d.ts rename to modules/modern-screenshot/4.6.6/dist/index.d.ts diff --git a/packages/modern-screenshot/4.6.6/dist/index.mjs b/modules/modern-screenshot/4.6.6/dist/index.mjs similarity index 100% rename from packages/modern-screenshot/4.6.6/dist/index.mjs rename to modules/modern-screenshot/4.6.6/dist/index.mjs diff --git a/packages/solidjs-signals/0.6.3/dist/prod.js b/modules/solidjs-signals/0.6.3/dist/prod.js similarity index 100% rename from packages/solidjs-signals/0.6.3/dist/prod.js rename to modules/solidjs-signals/0.6.3/dist/prod.js diff --git a/packages/solidjs-signals/0.6.3/dist/types/boundaries.d.ts b/modules/solidjs-signals/0.6.3/dist/types/boundaries.d.ts similarity index 100% rename from packages/solidjs-signals/0.6.3/dist/types/boundaries.d.ts rename to modules/solidjs-signals/0.6.3/dist/types/boundaries.d.ts diff --git a/packages/solidjs-signals/0.6.3/dist/types/core/constants.d.ts b/modules/solidjs-signals/0.6.3/dist/types/core/constants.d.ts similarity index 100% rename from packages/solidjs-signals/0.6.3/dist/types/core/constants.d.ts rename to modules/solidjs-signals/0.6.3/dist/types/core/constants.d.ts diff --git a/packages/solidjs-signals/0.6.3/dist/types/core/core.d.ts b/modules/solidjs-signals/0.6.3/dist/types/core/core.d.ts similarity index 100% rename from packages/solidjs-signals/0.6.3/dist/types/core/core.d.ts rename to modules/solidjs-signals/0.6.3/dist/types/core/core.d.ts diff --git a/packages/solidjs-signals/0.6.3/dist/types/core/effect.d.ts b/modules/solidjs-signals/0.6.3/dist/types/core/effect.d.ts similarity index 100% rename from packages/solidjs-signals/0.6.3/dist/types/core/effect.d.ts rename to modules/solidjs-signals/0.6.3/dist/types/core/effect.d.ts diff --git a/packages/solidjs-signals/0.6.3/dist/types/core/error.d.ts b/modules/solidjs-signals/0.6.3/dist/types/core/error.d.ts similarity index 100% rename from packages/solidjs-signals/0.6.3/dist/types/core/error.d.ts rename to modules/solidjs-signals/0.6.3/dist/types/core/error.d.ts diff --git a/packages/solidjs-signals/0.6.3/dist/types/core/flags.d.ts b/modules/solidjs-signals/0.6.3/dist/types/core/flags.d.ts similarity index 100% rename from packages/solidjs-signals/0.6.3/dist/types/core/flags.d.ts rename to modules/solidjs-signals/0.6.3/dist/types/core/flags.d.ts diff --git a/packages/solidjs-signals/0.6.3/dist/types/core/index.d.ts b/modules/solidjs-signals/0.6.3/dist/types/core/index.d.ts similarity index 100% rename from packages/solidjs-signals/0.6.3/dist/types/core/index.d.ts rename to modules/solidjs-signals/0.6.3/dist/types/core/index.d.ts diff --git a/packages/solidjs-signals/0.6.3/dist/types/core/owner.d.ts b/modules/solidjs-signals/0.6.3/dist/types/core/owner.d.ts similarity index 100% rename from packages/solidjs-signals/0.6.3/dist/types/core/owner.d.ts rename to modules/solidjs-signals/0.6.3/dist/types/core/owner.d.ts diff --git a/packages/solidjs-signals/0.6.3/dist/types/core/scheduler.d.ts b/modules/solidjs-signals/0.6.3/dist/types/core/scheduler.d.ts similarity index 100% rename from packages/solidjs-signals/0.6.3/dist/types/core/scheduler.d.ts rename to modules/solidjs-signals/0.6.3/dist/types/core/scheduler.d.ts diff --git a/packages/solidjs-signals/0.6.3/dist/types/index.d.ts b/modules/solidjs-signals/0.6.3/dist/types/index.d.ts similarity index 100% rename from packages/solidjs-signals/0.6.3/dist/types/index.d.ts rename to modules/solidjs-signals/0.6.3/dist/types/index.d.ts diff --git a/packages/solidjs-signals/0.6.3/dist/types/map.d.ts b/modules/solidjs-signals/0.6.3/dist/types/map.d.ts similarity index 100% rename from packages/solidjs-signals/0.6.3/dist/types/map.d.ts rename to modules/solidjs-signals/0.6.3/dist/types/map.d.ts diff --git a/packages/solidjs-signals/0.6.3/dist/types/signals.d.ts b/modules/solidjs-signals/0.6.3/dist/types/signals.d.ts similarity index 100% rename from packages/solidjs-signals/0.6.3/dist/types/signals.d.ts rename to modules/solidjs-signals/0.6.3/dist/types/signals.d.ts diff --git a/packages/solidjs-signals/0.6.3/dist/types/store/index.d.ts b/modules/solidjs-signals/0.6.3/dist/types/store/index.d.ts similarity index 100% rename from packages/solidjs-signals/0.6.3/dist/types/store/index.d.ts rename to modules/solidjs-signals/0.6.3/dist/types/store/index.d.ts diff --git a/packages/solidjs-signals/0.6.3/dist/types/store/projection.d.ts b/modules/solidjs-signals/0.6.3/dist/types/store/projection.d.ts similarity index 100% rename from packages/solidjs-signals/0.6.3/dist/types/store/projection.d.ts rename to modules/solidjs-signals/0.6.3/dist/types/store/projection.d.ts diff --git a/packages/solidjs-signals/0.6.3/dist/types/store/reconcile.d.ts b/modules/solidjs-signals/0.6.3/dist/types/store/reconcile.d.ts similarity index 100% rename from packages/solidjs-signals/0.6.3/dist/types/store/reconcile.d.ts rename to modules/solidjs-signals/0.6.3/dist/types/store/reconcile.d.ts diff --git a/packages/solidjs-signals/0.6.3/dist/types/store/store.d.ts b/modules/solidjs-signals/0.6.3/dist/types/store/store.d.ts similarity index 100% rename from packages/solidjs-signals/0.6.3/dist/types/store/store.d.ts rename to modules/solidjs-signals/0.6.3/dist/types/store/store.d.ts diff --git a/packages/solidjs-signals/0.6.3/dist/types/store/utils.d.ts b/modules/solidjs-signals/0.6.3/dist/types/store/utils.d.ts similarity index 100% rename from packages/solidjs-signals/0.6.3/dist/types/store/utils.d.ts rename to modules/solidjs-signals/0.6.3/dist/types/store/utils.d.ts diff --git a/packages/unpkg.sh b/modules/unpkg.sh similarity index 100% rename from packages/unpkg.sh rename to modules/unpkg.sh diff --git a/packages/brk-client/types.js b/packages/brk-client/types.js deleted file mode 100644 index 9a1f13ee9..000000000 --- a/packages/brk-client/types.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @import { Index } from "./generated/metrics" - * @import { BRKClient } from "./index" - * - * @typedef {typeof import("./generated/metrics")["COMPRESSED_METRIC_TO_INDEXES"]} MetricToIndexes - * @typedef {string} Metric - * @typedef {[number, number, number, number]} OHLCTuple - */ diff --git a/packages/brk-resources/types.js b/packages/brk-resources/types.js deleted file mode 100644 index e098634d2..000000000 --- a/packages/brk-resources/types.js +++ /dev/null @@ -1,3 +0,0 @@ -/** - * @import { BRKResources, BRKMetricResource } from "./index" - */ diff --git a/websites/.gitignore b/websites/.gitignore index a43d257fa..3a599655c 100644 --- a/websites/.gitignore +++ b/websites/.gitignore @@ -1 +1 @@ -**/scripts/packages +**/scripts/modules diff --git a/websites/bitview/index.html b/websites/bitview/index.html index 6e9e90300..9c6f086fe 100644 --- a/websites/bitview/index.html +++ b/websites/bitview/index.html @@ -991,7 +991,7 @@ } .chart > legend, - #charts > fieldset { + #chart > fieldset { text-transform: lowercase; flex-shrink: 0; display: flex; @@ -1161,7 +1161,7 @@ } } - #charts { + #chart { display: flex; flex-direction: column; height: 100%; @@ -1824,7 +1824,7 @@ diff --git a/websites/bitview/jsconfig.json b/websites/bitview/jsconfig.json index 7f676c08e..ce25d430a 100644 --- a/websites/bitview/jsconfig.json +++ b/websites/bitview/jsconfig.json @@ -6,5 +6,5 @@ "module": "ESNext", "skipLibCheck": true }, - "exclude": ["assets", "scripts/packages", "scripts/bridge"] + "exclude": ["assets", "scripts/modules"] } diff --git a/websites/bitview/scripts/core/chart/index.js b/websites/bitview/scripts/core/chart/index.js index 5fb06d7c4..f4211935c 100644 --- a/websites/bitview/scripts/core/chart/index.js +++ b/websites/bitview/scripts/core/chart/index.js @@ -1,4 +1,4 @@ -/** @import { IChartApi, ISeriesApi as _ISeriesApi, SeriesDefinition, SingleValueData as _SingleValueData, CandlestickData as _CandlestickData, BaselineData as _BaselineData, HistogramData as _HistogramData, SeriesType, IPaneApi, LineSeriesPartialOptions as _LineSeriesPartialOptions, HistogramSeriesPartialOptions as _HistogramSeriesPartialOptions, BaselineSeriesPartialOptions as _BaselineSeriesPartialOptions, CandlestickSeriesPartialOptions as _CandlestickSeriesPartialOptions, WhitespaceData, DeepPartial, ChartOptions, Time, LineData as _LineData } from '../../packages/lightweight-charts/5.0.8/dist/typings' */ +/** @import { IChartApi, ISeriesApi as _ISeriesApi, SeriesDefinition, SingleValueData as _SingleValueData, CandlestickData as _CandlestickData, BaselineData as _BaselineData, HistogramData as _HistogramData, SeriesType, IPaneApi, LineSeriesPartialOptions as _LineSeriesPartialOptions, HistogramSeriesPartialOptions as _HistogramSeriesPartialOptions, BaselineSeriesPartialOptions as _BaselineSeriesPartialOptions, CandlestickSeriesPartialOptions as _CandlestickSeriesPartialOptions, WhitespaceData, DeepPartial, ChartOptions, Time, LineData as _LineData } from '../../modules/lightweight-charts/5.0.8/dist/typings' */ import { createChart, @@ -6,8 +6,8 @@ import { HistogramSeries, LineSeries, BaselineSeries, - // } from "../packages/lightweight-charts/5.0.8/dist/lightweight-charts.standalone.development.mjs"; -} from "../../packages/lightweight-charts/5.0.8/dist/lightweight-charts.standalone.production.mjs"; + // } from "../modules/lightweight-charts/5.0.8/dist/lightweight-charts.standalone.development.mjs"; +} from "../../modules/lightweight-charts/5.0.8/dist/lightweight-charts.standalone.production.mjs"; import { createHorizontalChoiceField, @@ -17,6 +17,8 @@ import { import { createOklchToRGBA } from "./oklch"; import { throttle } from "../timing"; import { serdeBool } from "../serde"; +import { stringToId } from "../format"; +import { style } from "../elements"; /** * @typedef {Object} Valued @@ -50,7 +52,7 @@ import { serdeBool } from "../serde"; * @typedef {_BaselineData} BaselineData * @typedef {_HistogramData} HistogramData * - * @typedef {function({ iseries: ISeries; unit: Unit; index: Index }): void} SetDataCallback + * @typedef {function({ iseries: ISeries; unit: Unit; index: IndexName }): void} SetDataCallback */ const oklchToRGBA = createOklchToRGBA(); @@ -63,10 +65,8 @@ const lineWidth = /** @type {any} */ (1.5); * @param {HTMLElement} args.parent * @param {Signals} args.signals * @param {Colors} args.colors - * @param {Utilities} args.utils - * @param {Elements} args.elements - * @param {VecsResources} args.vecsResources - * @param {Accessor} args.index + * @param {Resources} args.resources + * @param {Accessor} args.index * @param {((unknownTimeScaleCallback: VoidFunction) => void)} [args.timeScaleSetCallback] * @param {true} [args.fitContent] * @param {{unit: Unit; blueprints: AnySeriesBlueprint[]}[]} [args.config] @@ -75,11 +75,9 @@ function createChartElement({ parent, signals, colors, - utils, - elements, id: chartId, index, - vecsResources, + resources, timeScaleSetCallback, fitContent, config, @@ -88,20 +86,14 @@ function createChartElement({ div.classList.add("chart"); parent.append(div); - const legendTop = createLegend({ - utils, - signals, - }); + const legendTop = createLegend(signals); div.append(legendTop.element); const chartDiv = window.document.createElement("div"); chartDiv.classList.add("lightweight-chart"); div.append(chartDiv); - const legendBottom = createLegend({ - utils, - signals, - }); + const legendBottom = createLegend(signals); div.append(legendBottom.element); /** @type {IChartApi} */ @@ -110,7 +102,7 @@ function createChartElement({ /** @satisfies {DeepPartial} */ ({ autoSize: true, layout: { - fontFamily: elements.style.fontFamily, + fontFamily: style.fontFamily, background: { color: "transparent" }, attributionLogo: false, colorSpace: "display-p3", @@ -185,24 +177,24 @@ function createChartElement({ signals.createEffect(index, (index) => { const minBarSpacing = - index === /** @satisfies {MonthIndex} */ (7) + index === "monthindex" ? 1 - : index === /** @satisfies {QuarterIndex} */ (19) + : index === "quarterindex" ? 2 - : index === /** @satisfies {SemesterIndex} */ (20) + : index === "semesterindex" ? 3 - : index === /** @satisfies {YearIndex} */ (24) + : index === "yearindex" ? 6 - : index === /** @satisfies {DecadeIndex} */ (1) + : index === "decadeindex" ? 60 : 0.5; ichart.applyOptions({ timeScale: { timeVisible: - index === /** @satisfies {Height} */ (5) || - index === /** @satisfies {DifficultyEpoch} */ (2) || - index === /** @satisfies {HalvingEpoch} */ (4), + index === "height" || + index === "difficultyepoch" || + index === "halvingepoch", ...(!fitContent ? { minBarSpacing, @@ -212,7 +204,7 @@ function createChartElement({ }); }); - const activeResources = /** @type {Set} */ (new Set()); + const activeResources = /** @type {Set} */ (new Set()); ichart.subscribeCrosshairMove( throttle(() => { activeResources.forEach((v) => { @@ -292,7 +284,7 @@ function createChartElement({ createChild(pane) { const { field, selected } = createHorizontalChoiceField({ choices: /** @type {const} */ (["lin", "log"]), - id: utils.stringToId(`${id} ${paneIndex} ${unit}`), + id: stringToId(`${id} ${paneIndex} ${unit}`), defaultValue: unit === "usd" && seriesType !== "Baseline" ? "log" : "lin", key: `${id}-price-scale-${paneIndex}`, @@ -340,7 +332,7 @@ function createChartElement({ data, }) { return signals.createRoot((dispose) => { - const id = `${utils.stringToId(name)}-${paneIndex}`; + const id = `${stringToId(name)}-${paneIndex}`; const active = signals.createSignal(defaultActive ?? true, { save: { @@ -361,7 +353,7 @@ function createChartElement({ iseries.setSeriesOrder(order); - /** @type {VecResource | undefined} */ + /** @type {MetricResource | undefined} */ let _valuesResource; /** @type {Series} */ @@ -383,15 +375,13 @@ function createChartElement({ if (metric) { signals.createEffect(index, (index) => { - const timeResource = vecsResources.getOrCreate( + const timeResource = resources.metrics.getOrCreate( + index === "height" ? "timestamp_fixed" : "timestamp", index, - index === /** @satisfies {Height} */ (5) - ? "timestamp_fixed" - : "timestamp", ); timeResource.fetch(); - const valuesResource = vecsResources.getOrCreate(index, metric); + const valuesResource = resources.metrics.getOrCreate(metric, index); _valuesResource = valuesResource; series.url.set(() => valuesResource.url); @@ -401,11 +391,11 @@ function createChartElement({ valuesResource.fetch(); activeResources.add(valuesResource); - const fetchedKey = vecsResources.defaultFetchedKey; + const fetchedKey = resources.metrics.genKey(); signals.createEffect( () => ({ - _indexes: timeResource.fetched().get(fetchedKey)?.vec(), - values: valuesResource.fetched().get(fetchedKey)?.vec(), + _indexes: timeResource.fetched().get(fetchedKey)?.data(), + values: valuesResource.fetched().get(fetchedKey)?.data(), }), ({ _indexes, values }) => { if (!_indexes?.length || !values?.length) return; @@ -477,10 +467,10 @@ function createChartElement({ timeScaleSetCallback?.(() => { if ( - index === /** @satisfies {QuarterIndex} */ (19) || - index === /** @satisfies {SemesterIndex} */ (20) || - index === /** @satisfies {YearIndex} */ (24) || - index === /** @satisfies {DecadeIndex} */ (1) + index === "quarterindex" || + index === "semesterindex" || + index === "yearindex" || + index === "decadeindex" ) { ichart.timeScale().setVisibleLogicalRange({ from: -1, @@ -836,11 +826,9 @@ function createChartElement({ } /** - * @param {Object} args - * @param {Signals} args.signals - * @param {Utilities} args.utils + * @param {Signals} signals */ -function createLegend({ signals, utils }) { +function createLegend(signals) { const element = window.document.createElement("legend"); const hovered = signals.createSignal(/** @type {Series | null} */ (null)); @@ -874,8 +862,8 @@ function createLegend({ signals, utils }) { legends[order] = div; const { input, label } = createLabeledInput({ - inputId: utils.stringToId(`legend-${series.id}`), - inputName: utils.stringToId(`selected-${series.id}`), + inputId: stringToId(`legend-${series.id}`), + inputName: stringToId(`selected-${series.id}`), inputValue: "value", title: "Click to toggle", inputChecked: series.active(), diff --git a/websites/bitview/scripts/core/elements.js b/websites/bitview/scripts/core/elements.js index 1afb28525..4094b9323 100644 --- a/websites/bitview/scripts/core/elements.js +++ b/websites/bitview/scripts/core/elements.js @@ -1,21 +1,24 @@ import { getElementById } from "./dom"; -export default { - head: window.document.getElementsByTagName("head")[0], - body: window.document.body, - main: getElementById("main"), - aside: getElementById("aside"), - asideLabel: getElementById("aside-selector-label"), - navLabel: getElementById(`nav-selector-label`), - searchLabel: getElementById(`search-selector-label`), - search: getElementById("search"), - nav: getElementById("nav"), - searchInput: /** @type {HTMLInputElement} */ (getElementById("search-input")), - searchResults: getElementById("search-results"), - selectors: getElementById("frame-selectors"), - style: getComputedStyle(window.document.documentElement), - charts: getElementById("charts"), - table: getElementById("table"), - explorer: getElementById("explorer"), - simulation: getElementById("simulation"), -}; +export const style = getComputedStyle(window.document.documentElement); + +export const headElement = window.document.getElementsByTagName("head")[0]; +export const bodyElement = window.document.body; + +export const mainElement = getElementById("main"); +export const asideElement = getElementById("aside"); +export const searchElement = getElementById("search"); +export const navElement = getElementById("nav"); +export const chartElement = getElementById("chart"); +export const tableElement = getElementById("table"); +export const explorerElement = getElementById("explorer"); +export const simulationElement = getElementById("simulation"); + +export const asideLabelElement = getElementById("aside-selector-label"); +export const navLabelElement = getElementById(`nav-selector-label`); +export const searchLabelElement = getElementById(`search-selector-label`); +export const searchInput = /** @type {HTMLInputElement} */ ( + getElementById("search-input") +); +export const searchResultsElement = getElementById("search-results"); +export const frameSelectorsElement = getElementById("frame-selectors"); diff --git a/websites/bitview/scripts/core/env.js b/websites/bitview/scripts/core/env.js index 9c38acef6..1b3a26ce2 100644 --- a/websites/bitview/scripts/core/env.js +++ b/websites/bitview/scripts/core/env.js @@ -1,24 +1,12 @@ -const localhost = window.location.hostname === "localhost"; -const standalone = +export const localhost = window.location.hostname === "localhost"; +export const standalone = "standalone" in window.navigator && !!window.navigator.standalone; -const userAgent = navigator.userAgent.toLowerCase(); -const isChrome = userAgent.includes("chrome"); -const safari = userAgent.includes("safari"); -const safariOnly = safari && !isChrome; -const macOS = userAgent.includes("mac os"); -const iphone = userAgent.includes("iphone"); -const ipad = userAgent.includes("ipad"); -const ios = iphone || ipad; - -export default { - standalone, - userAgent, - isChrome, - safari, - safariOnly, - macOS, - iphone, - ipad, - ios, - localhost, -}; +export const userAgent = navigator.userAgent.toLowerCase(); +export const isChrome = userAgent.includes("chrome"); +export const safari = userAgent.includes("safari"); +export const safariOnly = safari && !isChrome; +export const macOS = userAgent.includes("mac os"); +export const iphone = userAgent.includes("iphone"); +export const ipad = userAgent.includes("ipad"); +export const ios = iphone || ipad; +export const canShare = "canShare" in navigator; diff --git a/websites/bitview/scripts/core/utils.js b/websites/bitview/scripts/core/format.js similarity index 50% rename from websites/bitview/scripts/core/utils.js rename to websites/bitview/scripts/core/format.js index 5cdd34b9c..de42a09d8 100644 --- a/websites/bitview/scripts/core/utils.js +++ b/websites/bitview/scripts/core/format.js @@ -3,7 +3,7 @@ * @param {number} [digits] * @param {Intl.NumberFormatOptions} [options] */ -function numberToUSFormat(value, digits, options) { +export function numberToUSNumber(value, digits, options) { return value.toLocaleString("en-us", { ...options, minimumFractionDigits: digits, @@ -11,23 +11,18 @@ function numberToUSFormat(value, digits, options) { }); } -export const locale = { - numberToUSFormat, -}; +export const numberToDollars = new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + minimumFractionDigits: 2, + maximumFractionDigits: 2, +}); -export const formatters = { - dollars: new Intl.NumberFormat("en-US", { - style: "currency", - currency: "USD", - minimumFractionDigits: 2, - maximumFractionDigits: 2, - }), - percentage: new Intl.NumberFormat("en-US", { - style: "percent", - minimumFractionDigits: 2, - maximumFractionDigits: 2, - }), -}; +export const numberToPercentage = new Intl.NumberFormat("en-US", { + style: "percent", + minimumFractionDigits: 2, + maximumFractionDigits: 2, +}); /** * @param {string} s diff --git a/websites/bitview/scripts/core/options/full.js b/websites/bitview/scripts/core/options/full.js index 40994189f..e36f01b56 100644 --- a/websites/bitview/scripts/core/options/full.js +++ b/websites/bitview/scripts/core/options/full.js @@ -7,26 +7,16 @@ import { import { serdeUnit } from "../serde"; import { pushHistory, resetParams } from "../url"; import { readStored, writeToStorage } from "../storage"; +import { stringToId } from "../format"; /** * @param {Object} args * @param {Colors} args.colors * @param {Signals} args.signals - * @param {Env} args.env - * @param {Utilities} args.utils - * @param {MetricToIndexes} args.metricToIndexes - * @param {Pools} args.pools + * @param {BRK} args.brk * @param {Signal} args.qrcode */ -export function initOptions({ - colors, - signals, - env, - utils, - qrcode, - metricToIndexes, - pools, -}) { +export function initOptions({ colors, signals, brk, qrcode }) { const LS_SELECTED_KEY = `selected_path`; const urlPath_ = window.document.location.pathname @@ -42,10 +32,8 @@ export function initOptions({ const selected = signals.createSignal(/** @type {any} */ (undefined)); const partialOptions = createPartialOptions({ - env, colors, - metricToIndexes, - pools, + brk, }); /** @type {Option[]} */ @@ -58,7 +46,8 @@ export function initOptions({ */ function arrayToRecord(arr = []) { return (arr || []).reduce((record, blueprint) => { - if (env.localhost && !(blueprint.metric in metricToIndexes)) { + if (!brk.hasMetric(blueprint.metric)) { + // if (localhost && !brk.hasMetric(blueprint.metric)) { throw Error(`${blueprint.metric} not recognized`); } const unit = blueprint.unit ?? serdeUnit.deserialize(blueprint.metric); @@ -186,7 +175,7 @@ export function initOptions({ /** @type {HTMLDivElement | HTMLDetailsElement | null} */ (null), ); - const serName = utils.stringToId(anyPartial.name); + const serName = stringToId(anyPartial.name); const path = [...parentPath, serName]; const childOptionsCount = recursiveProcessPartialTree( anyPartial.tree, @@ -250,7 +239,7 @@ export function initOptions({ const option = /** @type {Option} */ (anyPartial); const name = option.name; - const path = [...parentPath, utils.stringToId(option.name)]; + const path = [...parentPath, stringToId(option.name)]; if ("kind" in anyPartial && anyPartial.kind === "explorer") { Object.assign( diff --git a/websites/bitview/scripts/core/options/partial.js b/websites/bitview/scripts/core/options/partial.js index 894dd2587..f9547a75d 100644 --- a/websites/bitview/scripts/core/options/partial.js +++ b/websites/bitview/scripts/core/options/partial.js @@ -1,5 +1,3 @@ -// @ts_check - /** * @typedef {Object} BaseSeriesBlueprint * @property {string} title @@ -119,70 +117,15 @@ * */ +import { localhost } from "../env"; + /** * @param {Object} args - * @param {Env} args.env * @param {Colors} args.colors - * @param {MetricToIndexes} args.metricToIndexes - * @param {Pools} args.pools + * @param {BRK} args.brk * @returns {PartialOptionsTree} */ -export function createPartialOptions({ env, colors, metricToIndexes, pools }) { - /** - * @template {string} S - * @typedef {Extract} StartsWith - */ - /** - * @template {string} S - * @typedef {Extract} EndsWith - */ - /** - * @template {string} K - * @template {string} S - * @typedef {K extends `${S}${infer Rest}` ? Rest : never} WithoutPrefix - */ - /** - * @template {string} K - * @template {string} S - * @typedef {K extends `${infer Rest}${S}` ? Rest : never} WithoutSuffix - */ - /** - * @template {string} K - * @template {string} S - * @typedef {K extends `${infer _Prefix}${S}${infer _Suffix}` ? never : K} ExcludeSubstring - */ - - /** - * @typedef {"cumulative_"} CumulativePrefix - * @typedef {"_30d_delta"} _30DChageSubString - * @typedef {StartsWith} CumulativeMetric - * @typedef {ExcludeSubstring, _30DChageSubString>} CumulativeMetricBase - * @typedef {"_avg"} AverageSuffix - * @typedef {EndsWith} MetricAverage - * @typedef {WithoutSuffix} MetricAverageBase - * @typedef {"_median"} MedianSuffix - * @typedef {EndsWith} MetricMedian - * @typedef {WithoutSuffix} MetricMedianBase - * @typedef {"_pct90"} _pct90Suffix - * @typedef {EndsWith<_pct90Suffix>} MetricPct90 - * @typedef {WithoutSuffix} MetricPct90Base - * @typedef {"_pct75"} _pct75Suffix - * @typedef {EndsWith<_pct75Suffix>} MetricPct75 - * @typedef {WithoutSuffix} MetricPct75Base - * @typedef {"_pct25"} _pct25Suffix - * @typedef {EndsWith<_pct25Suffix>} MetricPct25 - * @typedef {WithoutSuffix} MetricPct25Base - * @typedef {"_pct10"} _pct10Suffix - * @typedef {EndsWith<_pct10Suffix>} MetricPct10 - * @typedef {WithoutSuffix} MetricPct10Base - * @typedef {"_max"} MaxSuffix - * @typedef {EndsWith} MetricMax - * @typedef {WithoutSuffix} MetricMaxBase - * @typedef {"_min"} MinSuffix - * @typedef {EndsWith} MetricMin - * @typedef {WithoutSuffix} MetricMinBase - */ - +export function createPartialOptions({ colors, brk }) { /** * @param {string} id * @param {boolean} compoundAdjective @@ -681,7 +624,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) { } /** - * @param {MetricAverageBase} metric + * @param {Metric} metric */ function createAverageSeries(metric) { return /** @satisfies {AnyFetchedSeriesBlueprint} */ ({ @@ -692,7 +635,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) { /** * @param {Object} args - * @param {CumulativeMetricBase} args.metric + * @param {Metric} args.metric * @param {Color} [args.sumColor] * @param {Color} [args.cumulativeColor] * @param {string} [args.common] @@ -719,14 +662,14 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) { /** * @param {Object} args - * @param {CumulativeMetricBase} args.metric + * @param {Metric} args.metric * @param {string} [args.title] * @param {Color} [args.color] */ function createSumSeries({ metric, title = "", color }) { const metric_sum = `${metric}_sum`; return /** @satisfies {AnyFetchedSeriesBlueprint} */ ({ - metric: metric_sum in metricToIndexes ? metric_sum : metric, + metric: brk.hasMetric(metric_sum) ? metric_sum : metric, title: `Sum ${title}`, color: color ?? colors.red, }); @@ -734,7 +677,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) { /** * @param {Object} args - * @param {CumulativeMetricBase} args.metric + * @param {Metric} args.metric * @param {string} [args.title] * @param {Color} [args.color] */ @@ -748,7 +691,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) { } /** - * @param {MetricMinBase & MetricMaxBase & MetricPct90Base & MetricPct75Base & MetricMedianBase & MetricPct25Base & MetricPct10Base} metric + * @param {Metric} metric */ function createMinMaxPercentilesSeries(metric) { return /** @satisfies {AnyFetchedSeriesBlueprint[]} */ ([ @@ -798,7 +741,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) { } /** - * @param {MetricAverageBase & CumulativeMetricBase & MetricMinBase & MetricMaxBase & MetricPct90Base & MetricPct75Base & MetricMedianBase & MetricPct25Base & MetricPct10Base} metric + * @param {Metric} metric */ function createSumCumulativeMinMaxPercentilesSeries(metric) { return [ @@ -808,7 +751,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) { } /** - * @param {MetricAverageBase & CumulativeMetricBase & MetricMinBase & MetricMaxBase & MetricPct90Base & MetricPct75Base & MetricMedianBase & MetricPct25Base & MetricPct10Base} metric + * @param {Metric} metric */ function createAverageSumCumulativeMinMaxPercentilesSeries(metric) { return [ @@ -819,7 +762,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) { /** * @param {Object} args - * @param {Metric & MetricAverageBase & CumulativeMetricBase & MetricMinBase & MetricMaxBase & MetricPct90Base & MetricPct75Base & MetricMedianBase & MetricPct25Base & MetricPct10Base} args.metric + * @param {Metric} args.metric * @param {string} args.name */ function createBaseAverageSumCumulativeMinMaxPercentilesSeries({ @@ -835,12 +778,6 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) { ]; } - /** - * @typedef {"_ratio_zscore"} RatioZScoreCapSuffix - * @typedef {EndsWith} MetricRatioZScoreCap - * @typedef {WithoutSuffix} MetricRatioZScoreCapBase - */ - const percentiles = [ { name: "pct1", @@ -906,7 +843,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) { * @param {string} args.name * @param {string} args.legend * @param {string} args.title - * @param {MetricRatioZScoreCapBase} args.metric + * @param {Metric} args.metric * @param {Color} [args.color] */ function createPriceWithRatioOptions({ name, title, legend, metric, color }) { @@ -931,7 +868,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) { name: legend, color, }), - ...(`${metric}_ratio_p1sd_usd` in metricToIndexes + ...(brk.hasMetric(`${metric}_ratio_p1sd_usd`) ? percentiles.map(({ name, color }) => createBaseSeries({ metric: `${metric}_ratio_${name}_usd`, @@ -954,7 +891,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) { baseValue: { price: 1 }, }, }), - ...(`${metric}_ratio_p1sd` in metricToIndexes + ...(brk.hasMetric(`${metric}_ratio_p1sd`) ? percentiles.map(({ name, color }) => createBaseSeries({ metric: `${metric}_ratio_${name}`, @@ -967,7 +904,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) { }), ) : []), - ...(`${metric}_ratio_sma` in metricToIndexes + ...(brk.hasMetric(`${metric}_ratio_sma`) ? ratioAverages.map(({ name, metric: metricAddon, color }) => createBaseSeries({ metric: `${metric}_ratio_${metricAddon}`, @@ -986,7 +923,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) { }), ], }, - ...(`${metric}_ratio_zscore` in metricToIndexes + ...(brk.hasMetric(`${metric}_ratio_zscore`) ? [ { name: "ZScores", @@ -1178,18 +1115,12 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) { ]; } - /** - * @typedef {"_supply_in_profit"} SupplyInProfitSuffix - * @typedef {EndsWith} MetricSupplyInProfit - * @typedef {WithoutSuffix} CohortId - */ - /** * @typedef {Object} UTXOGroupObject * @property {string} args.name * @property {string} args.title * @property {Color} args.color - * @property {"" | CohortId} args.id + * @property {string} args.id */ /** @@ -1200,13 +1131,11 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) { */ /** - * @template {"" | CohortId} T - * @param {T} id + * @param {string} id */ - const fixId = (id) => - id !== "" - ? /** @type {Exclude<"" | `${T}_`, "_">} */ (`${id}_`) - : /** @type {const} */ (""); + function fixId(id) { + return id !== "" ? `${id}_` : ""; + } /** * @param {UTXOGroupObject | UTXOGroupsObject} args @@ -1465,11 +1394,11 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) { ]); }), }, - ...(list.filter(({ id }) => `${fixId(id)}addr_count` in metricToIndexes) + ...(list.filter(({ id }) => brk.hasMetric(`${fixId(id)}addr_count`)) .length > ("list" in args ? 1 : 0) ? !("list" in args) || - list.filter( - ({ id }) => `${fixId(id)}empty_addr_count` in metricToIndexes, + list.filter(({ id }) => + brk.hasMetric(`${fixId(id)}empty_addr_count`), ).length <= 1 ? [ { @@ -1478,7 +1407,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) { bottom: list.flatMap(({ name, color, id: _id }) => { const id = fixId(_id); return [ - ...(`${id}addr_count` in metricToIndexes + ...(brk.hasMetric(`${id}addr_count`) ? /** @type {const} */ ([ createBaseSeries({ metric: `${id}addr_count`, @@ -1487,7 +1416,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) { }), ]) : []), - ...(`${id}empty_addr_count` in metricToIndexes + ...(brk.hasMetric(`${id}empty_addr_count`) ? /** @type {const} */ ([ createBaseSeries({ metric: `${id}empty_addr_count`, @@ -1509,9 +1438,8 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) { name: "loaded", title: `Loaded Address Count ${title}`, bottom: list - .filter( - ({ id }) => - `${fixId(id)}addr_count` in metricToIndexes, + .filter(({ id }) => + brk.hasMetric(`${fixId(id)}addr_count`), ) .flatMap(({ name, color, id: _id }) => { const id = fixId(_id); @@ -1524,19 +1452,16 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) { ]; }), }, - ...(list.filter( - ({ id }) => - `${fixId(id)}empty_addr_count` in metricToIndexes, + ...(list.filter(({ id }) => + brk.hasMetric(`${fixId(id)}empty_addr_count`), ).length ? [ { name: "empty", title: `Empty Address Count ${title}`, bottom: list - .filter( - ({ id }) => - `${fixId(id)}empty_addr_count` in - metricToIndexes, + .filter(({ id }) => + brk.hasMetric(`${fixId(id)}empty_addr_count`), ) .flatMap(({ name, color, id: _id }) => { const id = fixId(_id); @@ -1622,7 +1547,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) { ] : []), ...(!("list" in args) && - `${id}realized_cap_rel_to_own_market_cap` in metricToIndexes + brk.hasMetric(`${id}realized_cap_rel_to_own_market_cap`) ? [ /** @satisfies {FetchedBaselineSeriesBlueprint} */ ({ type: "Baseline", @@ -1658,8 +1583,9 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) { color: colors.red, defaultActive: false, }), - ...(`${fixId(args.id)}realized_profit_to_loss_ratio` in - metricToIndexes + ...(brk.hasMetric( + `${fixId(args.id)}realized_profit_to_loss_ratio`, + ) ? [ createBaseSeries({ metric: `${fixId( @@ -1797,7 +1723,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) { }, }, }), - ...(asoprKey in metricToIndexes + ...(brk.hasMetric(asoprKey) ? [ /** @satisfies {FetchedBaselineSeriesBlueprint} */ ({ type: "Baseline", @@ -1825,7 +1751,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) { }, }, }), - ...(asoprKey in metricToIndexes + ...(brk.hasMetric(asoprKey) ? [ /** @satisfies {FetchedBaselineSeriesBlueprint} */ ({ type: "Baseline", @@ -1853,7 +1779,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) { }, }, }), - ...(asoprKey in metricToIndexes + ...(brk.hasMetric(asoprKey) ? [ /** @satisfies {FetchedBaselineSeriesBlueprint} */ ({ type: "Baseline", @@ -1940,8 +1866,9 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) { name, color, }), - ...(`${id}realized_profit_to_loss_ratio` in - metricToIndexes + ...(brk.hasMetric( + `${id}realized_profit_to_loss_ratio`, + ) ? [ createBaseSeries({ metric: `${id}realized_profit_to_loss_ratio`, @@ -2102,7 +2029,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) { name, metric: `${fixId(id)}adjusted_sopr`, })) - .filter(({ metric }) => metric in metricToIndexes); + .filter(({ metric }) => brk.hasMetric(metric)); return reducedList.length ? [ @@ -2180,7 +2107,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) { name: "normal", color: colors.emerald, }), - ...(adjKey in metricToIndexes + ...(brk.hasMetric(adjKey) ? [ createBaseSeries({ metric: adjKey, @@ -2204,7 +2131,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) { name: "normal", color: colors.red, }), - ...(adjKey in metricToIndexes + ...(brk.hasMetric(adjKey) ? [ createBaseSeries({ metric: adjKey, @@ -2239,9 +2166,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) { name, metric: `${fixId(id)}adjusted_value_created`, })) - .filter( - ({ metric }) => metric in metricToIndexes, - ); + .filter(({ metric }) => brk.hasMetric(metric)); return reducedList.length ? [ { @@ -2282,9 +2207,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) { name, metric: `${fixId(id)}adjusted_value_destroyed`, })) - .filter( - ({ metric }) => metric in metricToIndexes, - ); + .filter(({ metric }) => brk.hasMetric(metric)); return reducedList.length ? [ { @@ -2361,10 +2284,11 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) { name: "Negative Loss", color: colors.red, }), - ...(`${fixId( - args.id, - )}unrealized_profit_rel_to_own_market_cap` in - metricToIndexes + ...(brk.hasMetric( + `${fixId( + args.id, + )}unrealized_profit_rel_to_own_market_cap`, + ) ? [ createBaseSeries({ metric: `${fixId( @@ -2397,10 +2321,11 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) { }), ] : []), - ...(`${fixId( - args.id, - )}unrealized_profit_rel_to_own_total_unrealized_pnl` in - metricToIndexes + ...(brk.hasMetric( + `${fixId( + args.id, + )}unrealized_profit_rel_to_own_total_unrealized_pnl`, + ) ? [ createBaseSeries({ metric: `${fixId( @@ -2505,8 +2430,9 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) { title: useGroupName ? name : "Net", color: useGroupName ? color : undefined, }), - ...(`${fixId(id)}net_unrealized_pnl_rel_to_own_market_cap` in - metricToIndexes + ...(brk.hasMetric( + `${fixId(id)}net_unrealized_pnl_rel_to_own_market_cap`, + ) ? [ /** @satisfies {FetchedBaselineSeriesBlueprint} */ ({ type: "Baseline", @@ -2521,10 +2447,11 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) { }), ] : []), - ...(`${fixId( - id, - )}net_unrealized_pnl_rel_to_own_total_unrealized_pnl` in - metricToIndexes + ...(brk.hasMetric( + `${fixId( + id, + )}net_unrealized_pnl_rel_to_own_total_unrealized_pnl`, + ) ? [ /** @satisfies {FetchedBaselineSeriesBlueprint} */ ({ type: "Baseline", @@ -2660,7 +2587,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) { } return [ - ...(env.localhost + ...(localhost ? /** @type {const} */ ([ { name: "Explorer", @@ -2792,7 +2719,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) { createPriceLine({ unit: "percentage", }), - ...(cagr in metricToIndexes + ...(brk.hasMetric(cagr) ? [ /** @satisfies {FetchedBaselineSeriesBlueprint} */ ({ metric: cagr, @@ -3652,133 +3579,135 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) { }, { name: "Pools", - tree: Object.entries(pools).map(([_id, name]) => { - const id = /** @type {Pool} */ (_id); - return { - name, - tree: [ - { - name: "Dominance", - title: `Mining Dominance of ${name}`, - bottom: [ - createBaseSeries({ - metric: `${id}_1d_dominance`, - name: "1d", - color: colors.rose, - defaultActive: false, - }), - createBaseSeries({ - metric: `${id}_1w_dominance`, - name: "1w", - color: colors.red, - defaultActive: false, - }), - createBaseSeries({ - metric: `${id}_1m_dominance`, - name: "1m", - }), - createBaseSeries({ - metric: `${id}_1y_dominance`, - name: "1y", - color: colors.lime, - defaultActive: false, - }), - createBaseSeries({ - metric: `${id}_dominance`, - name: "all time", - color: colors.teal, - defaultActive: false, - }), - ], - }, - { - name: "Blocks mined", - title: `Blocks mined by ${name}`, - bottom: [ - createBaseSeries({ - metric: `${id}_blocks_mined`, - name: "Sum", - }), - createBaseSeries({ - metric: `${id}_blocks_mined_cumulative`, - name: "Cumulative", - color: colors.blue, - }), - createBaseSeries({ - metric: `${id}_1w_blocks_mined`, - name: "1w Sum", - color: colors.red, - defaultActive: false, - }), - createBaseSeries({ - metric: `${id}_1m_blocks_mined`, - name: "1m Sum", - color: colors.pink, - defaultActive: false, - }), - createBaseSeries({ - metric: `${id}_1y_blocks_mined`, - name: "1y Sum", - color: colors.purple, - defaultActive: false, - }), - ], - }, - { - name: "Rewards", - title: `Rewards collected by ${name}`, - bottom: [ - { - metricAddon: "coinbase", - cumulativeColor: colors.red, - sumColor: colors.orange, - }, - { - metricAddon: "subsidy", - cumulativeColor: colors.emerald, - sumColor: colors.lime, - }, - { - metricAddon: "fee", - cumulativeColor: colors.indigo, - sumColor: colors.cyan, - }, - ].flatMap( - ({ metricAddon, sumColor, cumulativeColor }) => [ - ...createSumCumulativeSeries({ - metric: `${id}_${metricAddon}`, - common: metricAddon, - sumColor, - cumulativeColor, + tree: Object.entries(brk.POOL_ID_TO_POOL_NAME).map( + ([_id, name]) => { + const id = /** @type {PoolId} */ (_id); + return { + name, + tree: [ + { + name: "Dominance", + title: `Mining Dominance of ${name}`, + bottom: [ + createBaseSeries({ + metric: `${id}_1d_dominance`, + name: "1d", + color: colors.rose, + defaultActive: false, }), - ...createSumCumulativeSeries({ - metric: `${id}_${metricAddon}_btc`, - common: metricAddon, - sumColor, - cumulativeColor, + createBaseSeries({ + metric: `${id}_1w_dominance`, + name: "1w", + color: colors.red, + defaultActive: false, }), - ...createSumCumulativeSeries({ - metric: `${id}_${metricAddon}_usd`, - common: metricAddon, - sumColor, - cumulativeColor, + createBaseSeries({ + metric: `${id}_1m_dominance`, + name: "1m", + }), + createBaseSeries({ + metric: `${id}_1y_dominance`, + name: "1y", + color: colors.lime, + defaultActive: false, + }), + createBaseSeries({ + metric: `${id}_dominance`, + name: "all time", + color: colors.teal, + defaultActive: false, }), ], - ), - }, - { - name: "Days since block", - title: `Days since ${name} mined a block`, - bottom: [ - createBaseSeries({ - metric: `${id}_days_since_block`, - name: "Since block", - }), - ], - }, - ], - }; - }), + }, + { + name: "Blocks mined", + title: `Blocks mined by ${name}`, + bottom: [ + createBaseSeries({ + metric: `${id}_blocks_mined`, + name: "Sum", + }), + createBaseSeries({ + metric: `${id}_blocks_mined_cumulative`, + name: "Cumulative", + color: colors.blue, + }), + createBaseSeries({ + metric: `${id}_1w_blocks_mined`, + name: "1w Sum", + color: colors.red, + defaultActive: false, + }), + createBaseSeries({ + metric: `${id}_1m_blocks_mined`, + name: "1m Sum", + color: colors.pink, + defaultActive: false, + }), + createBaseSeries({ + metric: `${id}_1y_blocks_mined`, + name: "1y Sum", + color: colors.purple, + defaultActive: false, + }), + ], + }, + { + name: "Rewards", + title: `Rewards collected by ${name}`, + bottom: [ + { + metricAddon: "coinbase", + cumulativeColor: colors.red, + sumColor: colors.orange, + }, + { + metricAddon: "subsidy", + cumulativeColor: colors.emerald, + sumColor: colors.lime, + }, + { + metricAddon: "fee", + cumulativeColor: colors.indigo, + sumColor: colors.cyan, + }, + ].flatMap( + ({ metricAddon, sumColor, cumulativeColor }) => [ + ...createSumCumulativeSeries({ + metric: `${id}_${metricAddon}`, + common: metricAddon, + sumColor, + cumulativeColor, + }), + ...createSumCumulativeSeries({ + metric: `${id}_${metricAddon}_btc`, + common: metricAddon, + sumColor, + cumulativeColor, + }), + ...createSumCumulativeSeries({ + metric: `${id}_${metricAddon}_usd`, + common: metricAddon, + sumColor, + cumulativeColor, + }), + ], + ), + }, + { + name: "Days since block", + title: `Days since ${name} mined a block`, + bottom: [ + createBaseSeries({ + metric: `${id}_days_since_block`, + name: "Since block", + }), + ], + }, + ], + }; + }, + ), }, ], }, diff --git a/websites/bitview/scripts/core/serde.js b/websites/bitview/scripts/core/serde.js index 17895e042..de6bb0ff2 100644 --- a/websites/bitview/scripts/core/serde.js +++ b/websites/bitview/scripts/core/serde.js @@ -113,59 +113,57 @@ export const serdeBool = { export const serdeChartableIndex = { /** - * @param {number} v - * @returns {SerializedChartableIndex | null} + * @param {IndexName} v + * @returns {ChartableIndexName | null} */ serialize(v) { switch (v) { - case /** @satisfies {DateIndex} */ (0): + case "dateindex": return "date"; - case /** @satisfies {DecadeIndex} */ (1): + case "decadeindex": return "decade"; - case /** @satisfies {DifficultyEpoch} */ (2): + case "difficultyepoch": return "epoch"; - // case /** @satisfies {HalvingEpoch} */ (4): - // return "halving"; - case /** @satisfies {Height} */ (5): + case "height": return "timestamp"; - case /** @satisfies {MonthIndex} */ (7): + case "monthindex": return "month"; - case /** @satisfies {QuarterIndex} */ (19): + case "quarterindex": return "quarter"; - case /** @satisfies {SemesterIndex} */ (20): + case "semesterindex": return "semester"; - case /** @satisfies {WeekIndex} */ (23): + case "weekindex": return "week"; - case /** @satisfies {YearIndex} */ (24): + case "yearindex": return "year"; default: return null; } }, /** - * @param {SerializedChartableIndex} v - * @returns {Index} + * @param {ChartableIndexName} v + * @returns {IndexName} */ deserialize(v) { switch (v) { case "timestamp": - return /** @satisfies {Height} */ (5); + return "height"; case "date": - return /** @satisfies {DateIndex} */ (0); + return "dateindex"; case "week": - return /** @satisfies {WeekIndex} */ (23); + return "weekindex"; case "epoch": - return /** @satisfies {DifficultyEpoch} */ (2); + return "difficultyepoch"; case "month": - return /** @satisfies {MonthIndex} */ (7); + return "monthindex"; case "quarter": - return /** @satisfies {QuarterIndex} */ (19); + return "quarterindex"; case "semester": - return /** @satisfies {SemesterIndex} */ (20); + return "semesterindex"; case "year": - return /** @satisfies {YearIndex} */ (24); + return "yearindex"; case "decade": - return /** @satisfies {DecadeIndex} */ (1); + return "decadeindex"; default: throw Error("todo"); } diff --git a/websites/bitview/scripts/entry.js b/websites/bitview/scripts/entry.js index f6231ba83..11ea60b7b 100644 --- a/websites/bitview/scripts/entry.js +++ b/websites/bitview/scripts/entry.js @@ -1,31 +1,33 @@ /** + * @import * as _ from "./modules/leeoniya-ufuzzy/1.0.19/dist/uFuzzy.d.ts" + * + * @import { Signal, Signals, Accessor } from "./modules/brk-signals/index"; + * + * @import { BRK } from "./modules/brk-client/index.js" + * @import { Metric, MetricToIndexes } from "./modules/brk-client/metrics" + * @import { IndexName } from "./modules/brk-client/generated/metrics" + * @import { PoolId, PoolIdToPoolName } from "./modules/brk-client/generated/pools" + * + * @import { Resources, MetricResource } from './modules/brk-resources/index.js' * * @import { Valued, SingleValueData, CandlestickData, Series, ISeries, HistogramData, LineData, BaselineData, LineSeriesPartialOptions, BaselineSeriesPartialOptions, HistogramSeriesPartialOptions, CandlestickSeriesPartialOptions, CreateChartElement, Chart } from "./core/chart/index" * - * @import * as _ from "./packages/leeoniya-ufuzzy/1.0.19/dist/uFuzzy.d.ts" - * - * @import { SerializedChartableIndex } from "./panes/chart"; - * - * @import { Signal, Signals, Accessor } from "./packages/solidjs-signals/wrapper"; - * - * @import { DateIndex, DecadeIndex, DifficultyEpoch, Index, HalvingEpoch, Height, MonthIndex, P2PK33AddressIndex, P2PK65AddressIndex, P2PKHAddressIndex, P2SHAddressIndex, P2MSOutputIndex, P2AAddressIndex, P2TRAddressIndex, P2WPKHAddressIndex, P2WSHAddressIndex, TxIndex, InputIndex, OutputIndex, WeekIndex, SemesterIndex, YearIndex, MetricToIndexes, QuarterIndex, EmptyOutputIndex, OpReturnIndex, UnknownOutputIndex, EmptyAddressIndex, LoadedAddressIndex } from "./bridge/vecs" - * - * @import { Pools, Pool } from "./bridge/pools" - * * @import { Color, ColorName, Colors } from "./core/colors" * - * @import { Option, PartialChartOption, ChartOption, AnyPartialOption, ProcessedOptionAddons, OptionsTree, SimulationOption, AnySeriesBlueprint, SeriesType, AnyFetchedSeriesBlueprint, TableOption, ExplorerOption, UrlOption, PartialOptionsGroup, OptionsGroup, PartialOptionsTree } from "./core/options/partial" - * * @import { WebSockets } from "./core/ws" * + * @import { Option, PartialChartOption, ChartOption, AnyPartialOption, ProcessedOptionAddons, OptionsTree, SimulationOption, AnySeriesBlueprint, SeriesType, AnyFetchedSeriesBlueprint, TableOption, ExplorerOption, UrlOption, PartialOptionsGroup, OptionsGroup, PartialOptionsTree } from "./core/options/partial" + * * @import { Unit } from "./core/serde" + * + * @import { ChartableIndexName } from "./panes/chart/index.js"; */ +// import uFuzzy = require("./modules/leeoniya-ufuzzy/1.0.19/dist/uFuzzy.d.ts"); + /** - * @typedef {typeof import("./lazy")["default"]} Packages - * @typedef {typeof import("./core/utils")} Utilities - * @typedef {typeof import("./core/env")["default"]} Env - * @typedef {typeof import("./core/elements")["default"]} Elements + * @typedef {typeof import("./lazy")["default"]} Modules + * @typedef {[number, number, number, number]} OHLCTuple */ // DO NOT CHANGE, Exact format is expected in `brk_bundler` diff --git a/websites/bitview/scripts/lazy.js b/websites/bitview/scripts/lazy.js index 308c1aaf9..8dfb620e4 100644 --- a/websites/bitview/scripts/lazy.js +++ b/websites/bitview/scripts/lazy.js @@ -1,27 +1,27 @@ const imports = { async signals() { - return import("./packages/solidjs-signals/index.js").then((d) => d.default); + return import("./modules/brk-signals/index.js").then((d) => d.default); }, + async leanQr() { + return import("./modules/lean-qr/2.6.0/index.mjs").then((d) => d); + }, + async ufuzzy() { + return import("./modules/leeoniya-ufuzzy/1.0.19/dist/uFuzzy.mjs").then( + ({ default: d }) => d, + ); + }, + async brkClient() { + return import("./modules/brk-client/index.js").then((d) => d); + }, + async brkResources() { + return import("./modules/brk-resources/index.js").then((d) => d); + }, + async chart() { return window.document.fonts.ready.then(() => import("./core/chart/index.js").then((d) => d.default), ); }, - async leanQr() { - return import("./packages/lean-qr/2.5.0/index.mjs").then((d) => d); - }, - async ufuzzy() { - return import("./packages/leeoniya-ufuzzy/1.0.19/dist/uFuzzy.mjs").then( - ({ default: d }) => d, - ); - }, - async modernScreenshot() { - return import("./packages/modern-screenshot/index.js").then((d) => d); - }, - async brk() { - return import("./packages/brk/index.js").then((d) => d); - }, - async options() { return import("./core/options/full.js").then((d) => d); }, diff --git a/websites/bitview/scripts/main.js b/websites/bitview/scripts/main.js index 2576b5454..70c3468ab 100644 --- a/websites/bitview/scripts/main.js +++ b/websites/bitview/scripts/main.js @@ -1,17 +1,31 @@ import { createColors } from "./core/colors"; import { createWebSockets } from "./core/ws"; -import * as utils from "./core/utils"; -import elements from "./core/elements"; -import env from "./core/env"; -import packages from "./lazy"; +import * as formatters from "./core/format"; +import modules from "./lazy"; import { onFirstIntersection, getElementById, isHidden } from "./core/dom"; import { next } from "./core/timing"; import { replaceHistory } from "./core/url"; -import { serdeUnit } from "./core/serde"; import { removeStored, writeToStorage } from "./core/storage"; +import { + asideElement, + asideLabelElement, + bodyElement, + chartElement, + explorerElement, + frameSelectorsElement, + mainElement, + navElement, + navLabelElement, + searchElement, + searchInput, + searchResultsElement, + simulationElement, + style, + tableElement, +} from "./core/elements"; function initFrameSelectors() { - const children = Array.from(elements.selectors.children); + const children = Array.from(frameSelectorsElement.children); /** @type {HTMLElement | undefined} */ let focusedFrame = undefined; @@ -59,29 +73,30 @@ function initFrameSelectors() { } } - elements.asideLabel.click(); + asideLabelElement.click(); // When going from mobile view to desktop view, if selected frame was open, go to the nav frame new IntersectionObserver((entries) => { for (let i = 0; i < entries.length; i++) { if ( !entries[i].isIntersecting && - entries[i].target === elements.asideLabel && - focusedFrame == elements.aside + entries[i].target === asideLabelElement && + focusedFrame == asideElement ) { - elements.navLabel.click(); + navLabelElement.click(); } } - }).observe(elements.asideLabel); + }).observe(asideLabelElement); function setAsideParent() { const { clientWidth } = window.document.documentElement; - const { aside, body, main } = elements; const MEDIUM_WIDTH = 768; if (clientWidth >= MEDIUM_WIDTH) { - aside.parentElement !== body && body.append(aside); + asideElement.parentElement !== bodyElement && + bodyElement.append(asideElement); } else { - aside.parentElement !== main && main.append(aside); + asideElement.parentElement !== mainElement && + mainElement.append(asideElement); } } @@ -91,630 +106,602 @@ function initFrameSelectors() { } initFrameSelectors(); -Promise.all([packages.signals(), packages.brk(), packages.options()]).then( - ([signals, { initOptions }]) => - signals.createRoot(() => { - const owner = signals.getOwner(); +Promise.all([ + modules.signals(), + modules.brkClient(), + modules.brkResources(), + modules.options(), +]).then(([signals, { createClient }, { createResources }, { initOptions }]) => + signals.createRoot(() => { + const brk = createClient("/"); + const resources = createResources(brk, signals); + const owner = signals.getOwner(); - console.log(`VERSION = ${VERSION}`); + console.log(`VERSION = ${brk.VERSION}`); - if (env.localhost) { - Object.keys(metricToIndexes).forEach((metric) => { - serdeUnit.deserialize(metric); - }); - } - - function initDark() { - const preferredColorSchemeMatchMedia = window.matchMedia( - "(prefers-color-scheme: dark)", - ); - const dark = signals.createSignal( - preferredColorSchemeMatchMedia.matches, - ); - preferredColorSchemeMatchMedia.addEventListener( - "change", - ({ matches }) => { - dark.set(matches); - }, - ); - return dark; - } - const dark = initDark(); - - const qrcode = signals.createSignal(/** @type {string | null} */ (null)); - - // function createLastHeightResource() { - // const lastHeight = signals.createSignal(0); - // function fetchLastHeight() { - // utils.api.fetchLast( - // (h) => { - // lastHeight.set(h); - // }, - // /** @satisfies {Height} */ (5), - // "height", - // ); - // } - // fetchLastHeight(); - // setInterval(fetchLastHeight, 10_000); - // return lastHeight; - // } - // const lastHeight = createLastHeightResource(); - - const webSockets = createWebSockets(signals); - - const vecsResources = createVecsResources( - signals, - utils, - env, - metricToIndexes, + function initDark() { + const preferredColorSchemeMatchMedia = window.matchMedia( + "(prefers-color-scheme: dark)", ); + const dark = signals.createSignal(preferredColorSchemeMatchMedia.matches); + preferredColorSchemeMatchMedia.addEventListener( + "change", + ({ matches }) => { + dark.set(matches); + }, + ); + return dark; + } + const dark = initDark(); - const pools = createPools(); + const qrcode = signals.createSignal(/** @type {string | null} */ (null)); - const colors = createColors(dark); + // function createLastHeightResource() { + // const lastHeight = signals.createSignal(0); + // function fetchLastHeight() { + // utils.api.fetchLast( + // (h) => { + // lastHeight.set(h); + // }, + // /** @satisfies {Height} */ (5), + // "height", + // ); + // } + // fetchLastHeight(); + // setInterval(fetchLastHeight, 10_000); + // return lastHeight; + // } + // const lastHeight = createLastHeightResource(); - const options = initOptions({ - colors, - env, - signals, - utils, - qrcode, - metricToIndexes, - pools, - }); + const webSockets = createWebSockets(signals); - window.addEventListener("popstate", (event) => { - const path = window.document.location.pathname - .split("/") - .filter((v) => v); - let folder = options.tree; + const colors = createColors(dark); - while (path.length) { - const id = path.shift(); - const res = folder.find((v) => id === utils.stringToId(v.name)); - if (!res) throw "Option not found"; - if (path.length >= 1) { - if (!("tree" in res)) { - throw "Unreachable"; - } - folder = res.tree; - } else { - if ("tree" in res) { - throw "Unreachable"; - } - options.selected.set(res); + const options = initOptions({ + colors, + signals, + brk, + qrcode, + }); + + window.addEventListener("popstate", (event) => { + const path = window.document.location.pathname + .split("/") + .filter((v) => v); + let folder = options.tree; + + while (path.length) { + const id = path.shift(); + const res = folder.find((v) => id === formatters.stringToId(v.name)); + if (!res) throw "Option not found"; + if (path.length >= 1) { + if (!("tree" in res)) { + throw "Unreachable"; } + folder = res.tree; + } else { + if ("tree" in res) { + throw "Unreachable"; + } + options.selected.set(res); } - }); + } + }); - function initSelected() { - let firstRun = true; - function initSelectedFrame() { - if (!firstRun) throw Error("Unreachable"); - firstRun = false; + function initSelected() { + let firstRun = true; + function initSelectedFrame() { + if (!firstRun) throw Error("Unreachable"); + firstRun = false; - const owner = signals.getOwner(); + const owner = signals.getOwner(); - const chartOption = signals.createSignal( - /** @type {ChartOption | null} */ (null), - ); - const simOption = signals.createSignal( - /** @type {SimulationOption | null} */ (null), - ); + const chartOption = signals.createSignal( + /** @type {ChartOption | null} */ (null), + ); + const simOption = signals.createSignal( + /** @type {SimulationOption | null} */ (null), + ); - let previousElement = /** @type {HTMLElement | undefined} */ ( - undefined - ); - let firstTimeLoadingChart = true; - let firstTimeLoadingTable = true; - let firstTimeLoadingSimulation = true; - let firstTimeLoadingExplorer = true; + let previousElement = /** @type {HTMLElement | undefined} */ ( + undefined + ); + let firstTimeLoadingChart = true; + let firstTimeLoadingTable = true; + let firstTimeLoadingSimulation = true; + let firstTimeLoadingExplorer = true; - signals.createEffect(options.selected, (option) => { - /** @type {HTMLElement} */ - let element; + signals.createEffect(options.selected, (option) => { + /** @type {HTMLElement} */ + let element; - switch (option.kind) { - case "explorer": { - element = elements.explorer; + switch (option.kind) { + case "explorer": { + element = explorerElement; - if (firstTimeLoadingExplorer) { - const chartPkg = packages.chart(); - import("./panes/explorer.js").then(({ init }) => + if (firstTimeLoadingExplorer) { + const chartPkg = modules.chart(); + import("./panes/explorer.js").then(({ init }) => + chartPkg.then(({ createChartElement }) => + signals.runWithOwner(owner, () => + init({ + colors, + createChartElement, + option: /** @type {Accessor} */ ( + chartOption + ), + signals, + webSockets, + resources, + brk, + }), + ), + ), + ); + } + firstTimeLoadingExplorer = false; + + break; + } + case "chart": { + element = chartElement; + + chartOption.set(option); + + if (firstTimeLoadingChart) { + const chartPkg = modules.chart(); + import("./panes/chart/index.js").then( + ({ init: initChartsElement }) => chartPkg.then(({ createChartElement }) => signals.runWithOwner(owner, () => - init({ + initChartsElement({ colors, - elements, createChartElement, option: /** @type {Accessor} */ ( chartOption ), signals, - utils, webSockets, - vecsResources, - metricToIndexes, + resources, + brk, }), ), ), - ); - } - firstTimeLoadingExplorer = false; - - break; + ); } - case "chart": { - element = elements.charts; + firstTimeLoadingChart = false; - chartOption.set(option); + break; + } + case "table": { + element = tableElement; - if (firstTimeLoadingChart) { - const chartPkg = packages.chart(); - import("./panes/chart.js").then( - ({ init: initChartsElement }) => - chartPkg.then(({ createChartElement }) => - signals.runWithOwner(owner, () => - initChartsElement({ - colors, - elements, - createChartElement, - option: /** @type {Accessor} */ ( - chartOption - ), - env, - signals, - utils, - webSockets, - vecsResources, - metricToIndexes, - packages, - }), - ), - ), - ); - } - firstTimeLoadingChart = false; - - break; + if (firstTimeLoadingTable) { + import("./panes/table.js").then(({ init }) => + signals.runWithOwner(owner, () => + init({ + signals, + resources, + option, + brk, + }), + ), + ); } - case "table": { - element = elements.table; + firstTimeLoadingTable = false; - if (firstTimeLoadingTable) { - import("./panes/table.js").then(({ init }) => + break; + } + case "simulation": { + element = simulationElement; + + simOption.set(option); + + if (firstTimeLoadingSimulation) { + const chart = modules.chart(); + import("./panes/simulation.js").then(({ init }) => + chart.then(({ createChartElement }) => signals.runWithOwner(owner, () => init({ - elements, + colors, + createChartElement, signals, - utils, - vecsResources, - option, - metricToIndexes, + resources, }), ), - ); - } - firstTimeLoadingTable = false; - - break; + ), + ); } - case "simulation": { - element = elements.simulation; + firstTimeLoadingSimulation = false; - simOption.set(option); - - if (firstTimeLoadingSimulation) { - const chart = packages.chart(); - import("./panes/simulation.js").then(({ init }) => - chart.then(({ createChartElement }) => - signals.runWithOwner(owner, () => - init({ - colors, - elements, - createChartElement, - signals, - utils, - vecsResources, - }), - ), - ), - ); - } - firstTimeLoadingSimulation = false; - - break; - } - case "url": { - return; - } + break; } - - if (element !== previousElement) { - if (previousElement) previousElement.hidden = true; - element.hidden = false; + case "url": { + return; } + } - if (!previousElement) { - replaceHistory({ pathname: option.path }); - } + if (element !== previousElement) { + if (previousElement) previousElement.hidden = true; + element.hidden = false; + } - previousElement = element; - }); - } + if (!previousElement) { + replaceHistory({ pathname: option.path }); + } - function createMobileSwitchEffect() { - let firstRun = true; - signals.createEffect(options.selected, () => { - if (!firstRun && !isHidden(elements.asideLabel)) { - elements.asideLabel.click(); - } - firstRun = false; - }); - } - createMobileSwitchEffect(); - - onFirstIntersection(elements.aside, () => - signals.runWithOwner(owner, initSelectedFrame), - ); + previousElement = element; + }); } - initSelected(); - onFirstIntersection(elements.nav, async () => { - options.parent.set(elements.nav); + function createMobileSwitchEffect() { + let firstRun = true; + signals.createEffect(options.selected, () => { + if (!firstRun && !isHidden(asideLabelElement)) { + asideLabelElement.click(); + } + firstRun = false; + }); + } + createMobileSwitchEffect(); - const option = options.selected(); - if (!option) throw "Selected should be set by now"; - const path = [...option.path]; + onFirstIntersection(asideElement, () => + signals.runWithOwner(owner, initSelectedFrame), + ); + } + initSelected(); - /** @type {HTMLUListElement | null} */ - let ul = /** @type {any} */ (null); - async function getFirstChild() { - try { - ul = /** @type {HTMLUListElement} */ ( - elements.nav.firstElementChild - ); - await next(); - if (!ul) { - await getFirstChild(); - } - } catch (_) { - await next(); + onFirstIntersection(navElement, async () => { + options.parent.set(navElement); + + const option = options.selected(); + if (!option) throw "Selected should be set by now"; + const path = [...option.path]; + + /** @type {HTMLUListElement | null} */ + let ul = /** @type {any} */ (null); + async function getFirstChild() { + try { + ul = /** @type {HTMLUListElement} */ (navElement.firstElementChild); + await next(); + if (!ul) { await getFirstChild(); } + } catch (_) { + await next(); + await getFirstChild(); } - await getFirstChild(); - if (!ul) throw Error("Unreachable"); + } + await getFirstChild(); + if (!ul) throw Error("Unreachable"); - let i = 0; - while (path.length > 1) { - const name = path.shift(); - if (!name) throw "Unreachable"; - /** @type {HTMLDetailsElement[]} */ - let detailsList = []; - while (!detailsList.length) { - detailsList = Array.from( - ul.querySelectorAll(":scope > li > details"), - ); - if (!detailsList.length) { - await next(); - } - } - const details = detailsList.find((s) => s.dataset.name == name); - if (!details) return; - details.open = true; - ul = null; - while (!ul) { - const uls = /** @type {HTMLUListElement[]} */ ( - Array.from(details.querySelectorAll(":scope > ul")) - ); - if (!uls.length) { - await next(); - } else if (uls.length > 1) { - throw "Shouldn't be possible"; - } else { - ul = /** @type {HTMLUListElement} */ (uls.pop()); - } - } - } - /** @type {HTMLAnchorElement[]} */ - let anchors = []; - while (!anchors.length) { - anchors = Array.from(ul.querySelectorAll(":scope > li > a")); - if (!anchors.length) { + let i = 0; + while (path.length > 1) { + const name = path.shift(); + if (!name) throw "Unreachable"; + /** @type {HTMLDetailsElement[]} */ + let detailsList = []; + while (!detailsList.length) { + detailsList = Array.from( + ul.querySelectorAll(":scope > li > details"), + ); + if (!detailsList.length) { await next(); } } - anchors - .find( - (a) => a.getAttribute("href") == window.document.location.pathname, - ) - ?.scrollIntoView({ - behavior: "instant", - block: "center", - }); - }); + const details = detailsList.find((s) => s.dataset.name == name); + if (!details) return; + details.open = true; + ul = null; + while (!ul) { + const uls = /** @type {HTMLUListElement[]} */ ( + Array.from(details.querySelectorAll(":scope > ul")) + ); + if (!uls.length) { + await next(); + } else if (uls.length > 1) { + throw "Shouldn't be possible"; + } else { + ul = /** @type {HTMLUListElement} */ (uls.pop()); + } + } + } + /** @type {HTMLAnchorElement[]} */ + let anchors = []; + while (!anchors.length) { + anchors = Array.from(ul.querySelectorAll(":scope > li > a")); + if (!anchors.length) { + await next(); + } + } + anchors + .find( + (a) => a.getAttribute("href") == window.document.location.pathname, + ) + ?.scrollIntoView({ + behavior: "instant", + block: "center", + }); + }); - onFirstIntersection(elements.search, () => { - console.log("search: init"); + onFirstIntersection(searchElement, () => { + console.log("search: init"); - const haystack = options.list.map((option) => option.title); + const haystack = options.list.map((option) => option.title); - const RESULTS_PER_PAGE = 100; + const RESULTS_PER_PAGE = 100; - packages.ufuzzy().then((ufuzzy) => { - /** - * @param {uFuzzy.SearchResult} searchResult - * @param {number} pageIndex - */ - function computeResultPage(searchResult, pageIndex) { - /** @type {{ option: Option, title: string }[]} */ - let list = []; + modules.ufuzzy().then((ufuzzy) => { + /** + * @param {uFuzzy.SearchResult} searchResult + * @param {number} pageIndex + */ + function computeResultPage(searchResult, pageIndex) { + /** @type {{ option: Option, title: string }[]} */ + let list = []; - let [indexes, info, order] = searchResult || [null, null, null]; + let [indexes, info, order] = searchResult || [null, null, null]; - const minIndex = pageIndex * RESULTS_PER_PAGE; + const minIndex = pageIndex * RESULTS_PER_PAGE; - if (indexes?.length) { - const maxIndex = Math.min( - (order || indexes).length - 1, - minIndex + RESULTS_PER_PAGE - 1, - ); + if (indexes?.length) { + const maxIndex = Math.min( + (order || indexes).length - 1, + minIndex + RESULTS_PER_PAGE - 1, + ); - list = Array(maxIndex - minIndex + 1); + list = Array(maxIndex - minIndex + 1); - for (let i = minIndex; i <= maxIndex; i++) { - let index = indexes[i]; + for (let i = minIndex; i <= maxIndex; i++) { + let index = indexes[i]; - const title = haystack[index]; + const title = haystack[index]; - list[i % 100] = { - option: options.list[index], - title, - }; - } + list[i % 100] = { + option: options.list[index], + title, + }; } - - return list; } - /** @type {uFuzzy.Options} */ - const config = { - intraIns: Infinity, - intraChars: `[a-z\d' ]`, - }; + return list; + } - const fuzzyMultiInsert = /** @type {uFuzzy} */ ( - ufuzzy({ - intraIns: 1, - }) - ); - const fuzzyMultiInsertFuzzier = /** @type {uFuzzy} */ ( - ufuzzy(config) - ); - const fuzzySingleError = /** @type {uFuzzy} */ ( - ufuzzy({ - intraMode: 1, - ...config, - }) - ); - const fuzzySingleErrorFuzzier = /** @type {uFuzzy} */ ( - ufuzzy({ - intraMode: 1, - ...config, - }) - ); + /** @type {uFuzzy.Options} */ + const config = { + intraIns: Infinity, + intraChars: `[a-z\d' ]`, + }; - /** @type {VoidFunction | undefined} */ - let dispose; + const fuzzyMultiInsert = /** @type {uFuzzy} */ ( + ufuzzy({ + intraIns: 1, + }) + ); + const fuzzyMultiInsertFuzzier = /** @type {uFuzzy} */ (ufuzzy(config)); + const fuzzySingleError = /** @type {uFuzzy} */ ( + ufuzzy({ + intraMode: 1, + ...config, + }) + ); + const fuzzySingleErrorFuzzier = /** @type {uFuzzy} */ ( + ufuzzy({ + intraMode: 1, + ...config, + }) + ); - function inputEvent() { - signals.createRoot((_dispose) => { - const needle = /** @type {string} */ (elements.searchInput.value); + /** @type {VoidFunction | undefined} */ + let dispose; - dispose?.(); + function inputEvent() { + signals.createRoot((_dispose) => { + const needle = /** @type {string} */ (searchInput.value); - dispose = _dispose; + dispose?.(); - elements.searchResults.scrollTo({ - top: 0, - }); + dispose = _dispose; - if (!needle) { - elements.searchResults.innerHTML = ""; - return; - } + searchResultsElement.scrollTo({ + top: 0, + }); - const outOfOrder = 5; - const infoThresh = 5_000; + if (!needle) { + searchResultsElement.innerHTML = ""; + return; + } - let result = fuzzyMultiInsert?.search( + const outOfOrder = 5; + const infoThresh = 5_000; + + let result = fuzzyMultiInsert?.search( + haystack, + needle, + undefined, + infoThresh, + ); + + if (!result?.[0]?.length || !result?.[1]) { + result = fuzzyMultiInsert?.search( + haystack, + needle, + outOfOrder, + infoThresh, + ); + } + + if (!result?.[0]?.length || !result?.[1]) { + result = fuzzySingleError?.search( + haystack, + needle, + outOfOrder, + infoThresh, + ); + } + + if (!result?.[0]?.length || !result?.[1]) { + result = fuzzySingleErrorFuzzier?.search( + haystack, + needle, + outOfOrder, + infoThresh, + ); + } + + if (!result?.[0]?.length || !result?.[1]) { + result = fuzzyMultiInsertFuzzier?.search( haystack, needle, undefined, infoThresh, ); + } - if (!result?.[0]?.length || !result?.[1]) { - result = fuzzyMultiInsert?.search( - haystack, - needle, - outOfOrder, - infoThresh, - ); - } + if (!result?.[0]?.length || !result?.[1]) { + result = fuzzyMultiInsertFuzzier?.search( + haystack, + needle, + outOfOrder, + infoThresh, + ); + } - if (!result?.[0]?.length || !result?.[1]) { - result = fuzzySingleError?.search( - haystack, - needle, - outOfOrder, - infoThresh, - ); - } + searchResultsElement.innerHTML = ""; - if (!result?.[0]?.length || !result?.[1]) { - result = fuzzySingleErrorFuzzier?.search( - haystack, - needle, - outOfOrder, - infoThresh, - ); - } + const list = computeResultPage(result, 0); - if (!result?.[0]?.length || !result?.[1]) { - result = fuzzyMultiInsertFuzzier?.search( - haystack, - needle, - undefined, - infoThresh, - ); - } + list.forEach(({ option, title }) => { + const li = window.document.createElement("li"); + searchResultsElement.appendChild(li); - if (!result?.[0]?.length || !result?.[1]) { - result = fuzzyMultiInsertFuzzier?.search( - haystack, - needle, - outOfOrder, - infoThresh, - ); - } - - elements.searchResults.innerHTML = ""; - - const list = computeResultPage(result, 0); - - list.forEach(({ option, title }) => { - const li = window.document.createElement("li"); - elements.searchResults.appendChild(li); - - const element = options.createOptionElement({ - option, - frame: "search", - name: title, - qrcode, - }); - - if (element) { - li.append(element); - } + const element = options.createOptionElement({ + option, + frame: "search", + name: title, + qrcode, }); + + if (element) { + li.append(element); + } }); - } + }); + } - if (elements.searchInput.value) { - inputEvent(); - } + if (searchInput.value) { + inputEvent(); + } - elements.searchInput.addEventListener("input", inputEvent); - }); + searchInput.addEventListener("input", inputEvent); + }); + }); + + function initShare() { + const shareDiv = getElementById("share-div"); + const shareContentDiv = getElementById("share-content-div"); + + shareDiv.addEventListener("click", () => { + qrcode.set(null); }); - function initShare() { - const shareDiv = getElementById("share-div"); - const shareContentDiv = getElementById("share-content-div"); + shareContentDiv.addEventListener("click", (event) => { + event.stopPropagation(); + event.preventDefault(); + }); - shareDiv.addEventListener("click", () => { - qrcode.set(null); - }); + modules.leanQr().then(({ generate }) => + signals.runWithOwner(owner, () => { + const imgQrcode = /** @type {HTMLImageElement} */ ( + getElementById("share-img") + ); - shareContentDiv.addEventListener("click", (event) => { - event.stopPropagation(); - event.preventDefault(); - }); + const anchor = /** @type {HTMLAnchorElement} */ ( + getElementById("share-anchor") + ); - packages.leanQr().then(({ generate }) => - signals.runWithOwner(owner, () => { - const imgQrcode = /** @type {HTMLImageElement} */ ( - getElementById("share-img") - ); - - const anchor = /** @type {HTMLAnchorElement} */ ( - getElementById("share-anchor") - ); - - signals.createEffect(qrcode, (qrcode) => { - if (!qrcode) { - shareDiv.hidden = true; - return; - } - - const href = qrcode; - anchor.href = href; - anchor.innerText = - (href.startsWith("http") - ? href.split("//").at(-1) - : href.split(":").at(-1)) || ""; - - imgQrcode.src = - generate(/** @type {any} */ (href))?.toDataURL({ - // @ts-ignore - padX: 0, - padY: 0, - }) || ""; - - shareDiv.hidden = false; - }); - }), - ); - } - initShare(); - - function initDesktopResizeBar() { - const resizeBar = getElementById("resize-bar"); - let resize = false; - let startingWidth = 0; - let startingClientX = 0; - - const barWidthLocalStorageKey = "bar-width"; - - /** - * @param {number | null} width - */ - function setBarWidth(width) { - // TODO: Check if should be a signal ?? - try { - if (typeof width === "number") { - elements.main.style.width = `${width}px`; - writeToStorage(barWidthLocalStorageKey, String(width)); - } else { - elements.main.style.width = elements.style.getPropertyValue( - "--default-main-width", - ); - removeStored(barWidthLocalStorageKey); + signals.createEffect(qrcode, (qrcode) => { + if (!qrcode) { + shareDiv.hidden = true; + return; } - } catch (_) {} - } - /** - * @param {MouseEvent} event - */ - function mouseMoveEvent(event) { - if (resize) { - setBarWidth(startingWidth + (event.clientX - startingClientX)); + const href = qrcode; + anchor.href = href; + anchor.innerText = + (href.startsWith("http") + ? href.split("//").at(-1) + : href.split(":").at(-1)) || ""; + + imgQrcode.src = + generate(/** @type {any} */ (href))?.toDataURL({ + // @ts-ignore + padX: 0, + padY: 0, + }) || ""; + + shareDiv.hidden = false; + }); + }), + ); + } + initShare(); + + function initDesktopResizeBar() { + const resizeBar = getElementById("resize-bar"); + let resize = false; + let startingWidth = 0; + let startingClientX = 0; + + const barWidthLocalStorageKey = "bar-width"; + + /** + * @param {number | null} width + */ + function setBarWidth(width) { + // TODO: Check if should be a signal ?? + try { + if (typeof width === "number") { + mainElement.style.width = `${width}px`; + writeToStorage(barWidthLocalStorageKey, String(width)); + } else { + mainElement.style.width = style.getPropertyValue( + "--default-main-width", + ); + removeStored(barWidthLocalStorageKey); } - } - - resizeBar.addEventListener("mousedown", (event) => { - startingClientX = event.clientX; - startingWidth = elements.main.clientWidth; - resize = true; - window.document.documentElement.dataset.resize = ""; - window.addEventListener("mousemove", mouseMoveEvent); - }); - - resizeBar.addEventListener("dblclick", () => { - setBarWidth(null); - }); - - const setResizeFalse = () => { - resize = false; - delete window.document.documentElement.dataset.resize; - window.removeEventListener("mousemove", mouseMoveEvent); - }; - window.addEventListener("mouseup", setResizeFalse); - window.addEventListener("mouseleave", setResizeFalse); + } catch (_) {} } - initDesktopResizeBar(); - }), + + /** + * @param {MouseEvent} event + */ + function mouseMoveEvent(event) { + if (resize) { + setBarWidth(startingWidth + (event.clientX - startingClientX)); + } + } + + resizeBar.addEventListener("mousedown", (event) => { + startingClientX = event.clientX; + startingWidth = mainElement.clientWidth; + resize = true; + window.document.documentElement.dataset.resize = ""; + window.addEventListener("mousemove", mouseMoveEvent); + }); + + resizeBar.addEventListener("dblclick", () => { + setBarWidth(null); + }); + + const setResizeFalse = () => { + resize = false; + delete window.document.documentElement.dataset.resize; + window.removeEventListener("mousemove", mouseMoveEvent); + }; + window.addEventListener("mouseup", setResizeFalse); + window.addEventListener("mouseleave", setResizeFalse); + } + initDesktopResizeBar(); + }), ); diff --git a/websites/bitview/scripts/panes/chart.js b/websites/bitview/scripts/panes/chart/index.js similarity index 87% rename from websites/bitview/scripts/panes/chart.js rename to websites/bitview/scripts/panes/chart/index.js index 8bc3916e6..db930e048 100644 --- a/websites/bitview/scripts/panes/chart.js +++ b/websites/bitview/scripts/panes/chart/index.js @@ -2,9 +2,11 @@ import { createShadow, createHorizontalChoiceField, createHeader, -} from "../core/dom"; -import { serdeChartableIndex, serdeOptNumber } from "../core/serde"; -import { throttle } from "../core/timing"; +} from "../../core/dom"; +import { chartElement } from "../../core/elements"; +import { ios, canShare } from "../../core/env"; +import { serdeChartableIndex, serdeOptNumber } from "../../core/serde"; +import { throttle } from "../../core/timing"; const keyPrefix = "chart"; const ONE_BTC_IN_SATS = 100_000_000; @@ -13,7 +15,7 @@ const LINE = "line"; const CANDLE = "candle"; /** - * @typedef {"timestamp" | "date" | "week" | "epoch" | "month" | "quarter" | "semester" | "year" | "decade" } SerializedChartableIndex + * @typedef {"timestamp" | "date" | "week" | "epoch" | "month" | "quarter" | "semester" | "year" | "decade" } ChartableIndexName */ /** @@ -22,38 +24,29 @@ const CANDLE = "candle"; * @param {CreateChartElement} args.createChartElement * @param {Accessor} args.option * @param {Signals} args.signals - * @param {Utilities} args.utils * @param {WebSockets} args.webSockets - * @param {Elements} args.elements - * @param {Env} args.env - * @param {VecsResources} args.vecsResources - * @param {MetricToIndexes} args.metricToIndexes - * @param {Packages} args.packages + * @param {Resources} args.resources + * @param {BRK} args.brk */ export function init({ colors, - elements, createChartElement, option, signals, - utils, - env, webSockets, - vecsResources, - metricToIndexes, - packages, + resources, + brk, }) { - elements.charts.append(createShadow("left")); - elements.charts.append(createShadow("right")); + chartElement.append(createShadow("left")); + chartElement.append(createShadow("right")); const { headerElement, headingElement } = createHeader(); - elements.charts.append(headerElement); + chartElement.append(headerElement); const { index, fieldset } = createIndexSelector({ option, - metricToIndexes, + brk, signals, - utils, }); const TIMERANGE_LS_KEY = signals.createMemo( @@ -80,13 +73,11 @@ export function init({ }); const chart = createChartElement({ - parent: elements.charts, + parent: chartElement, signals, colors, id: "charts", - utils, - vecsResources, - elements, + resources, index, timeScaleSetCallback: (unknownTimeScaleCallback) => { // TODO: Although it mostly works in practice, need to make it more robust, there is no guarantee that this runs in order and wait for `from` and `to` to update when `index` and thus `TIMERANGE_LS_KEY` is updated @@ -105,12 +96,11 @@ export function init({ }, }); - if (!(env.ios && !("canShare" in navigator))) { + if (!(ios && !canShare)) { const chartBottomRightCanvas = Array.from( chart.inner.chartElement().getElementsByTagName("tr"), ).at(-1)?.lastChild?.firstChild?.firstChild; if (chartBottomRightCanvas) { - const charts = elements.charts; const domain = window.document.createElement("p"); domain.innerText = `${window.location.host}`; domain.id = "domain"; @@ -121,20 +111,20 @@ export function init({ screenshotButton.title = "Screenshot"; chartBottomRightCanvas.replaceWith(screenshotButton); screenshotButton.addEventListener("click", () => { - packages.modernScreenshot().then(async ({ screenshot }) => { - charts.dataset.screenshot = "true"; - charts.append(domain); + import("./screenshot").then(async ({ screenshot }) => { + chartElement.dataset.screenshot = "true"; + chartElement.append(domain); seriesTypeField.hidden = true; try { await screenshot({ - element: charts, + element: chartElement, name: option().path.join("-"), title: option().title, }); } catch {} - charts.removeChild(domain); + chartElement.removeChild(domain); seriesTypeField.hidden = false; - charts.dataset.screenshot = "false"; + chartElement.dataset.screenshot = "false"; }); }); } @@ -148,7 +138,7 @@ export function init({ }, 250), ); - elements.charts.append(fieldset); + chartElement.append(fieldset); const { field: seriesTypeField, selected: topSeriesType_ } = createHorizontalChoiceField({ @@ -205,7 +195,7 @@ export function init({ * @param {Object} params * @param {ISeries} params.iseries * @param {Unit} params.unit - * @param {Index} params.index + * @param {IndexName} params.index */ function printLatest({ iseries, unit, index }) { const _latest = webSockets.kraken1dCandle.latest(); @@ -234,9 +224,9 @@ export function init({ const date = new Date(latest.time * 1000); switch (index) { - case /** @satisfies {Height} */ (5): - case /** @satisfies {DifficultyEpoch} */ (2): - case /** @satisfies {HalvingEpoch} */ (4): { + case "height": + case "difficultyepoch": + case "halvingepoch": { if ("close" in last) { last.low = Math.min(last.low, latest.close); last.high = Math.max(last.high, latest.close); @@ -244,24 +234,24 @@ export function init({ iseries.update(last); break; } - case /** @satisfies {DateIndex} */ (0): { + case "dateindex": { iseries.update(last); break; } default: { - if (index === /** @satisfies {WeekIndex} */ (23)) { + if (index === "weekindex") { date.setUTCDate(date.getUTCDate() - ((date.getUTCDay() + 6) % 7)); - } else if (index === /** @satisfies {MonthIndex} */ (7)) { + } else if (index === "monthindex") { date.setUTCDate(1); - } else if (index === /** @satisfies {QuarterIndex} */ (19)) { + } else if (index === "quarterindex") { const month = date.getUTCMonth(); date.setUTCMonth(month - (month % 3), 1); - } else if (index === /** @satisfies {SemesterIndex} */ (20)) { + } else if (index === "semesterindex") { const month = date.getUTCMonth(); date.setUTCMonth(month - (month % 6), 1); - } else if (index === /** @satisfies {YearIndex} */ (24)) { + } else if (index === "yearindex") { date.setUTCMonth(0, 1); - } else if (index === /** @satisfies {DecadeIndex} */ (1)) { + } else if (index === "decadeindex") { date.setUTCFullYear( Math.floor(date.getUTCFullYear() / 10) * 10, 0, @@ -446,9 +436,7 @@ export function init({ blueprints[unit]?.forEach((blueprint, order) => { order += orderStart; - const indexes = /** @type {readonly number[]} */ ( - metricToIndexes[blueprint.metric] - ); + const indexes = brk.getIndexesFromMetric(blueprint.metric); if (indexes.includes(index)) { switch (blueprint.type) { @@ -519,12 +507,11 @@ export function init({ /** * @param {Object} args * @param {Accessor} args.option - * @param {MetricToIndexes} args.metricToIndexes + * @param {BRK} args.brk * @param {Signals} args.signals - * @param {Utilities} args.utils */ -function createIndexSelector({ option, metricToIndexes, signals, utils }) { - const choices_ = /** @satisfies {SerializedChartableIndex[]} */ ([ +function createIndexSelector({ option, brk, signals }) { + const choices_ = /** @satisfies {ChartableIndexName[]} */ ([ "timestamp", "date", "week", @@ -548,7 +535,7 @@ function createIndexSelector({ option, metricToIndexes, signals, utils }) { [Object.values(o.top), Object.values(o.bottom)] .flat(2) .filter((blueprint) => !blueprint.metric.startsWith("constant_")) - .map((blueprint) => metricToIndexes[blueprint.metric]) + .map((blueprint) => brk.getIndexesFromMetric(blueprint.metric)) .flat(), ); diff --git a/packages/modern-screenshot/index.js b/websites/bitview/scripts/panes/chart/screenshot.js similarity index 77% rename from packages/modern-screenshot/index.js rename to websites/bitview/scripts/panes/chart/screenshot.js index 59cf714ae..d0cfc2de1 100644 --- a/packages/modern-screenshot/index.js +++ b/websites/bitview/scripts/panes/chart/screenshot.js @@ -1,9 +1,5 @@ -import { domToBlob } from "./4.6.6/dist/index.mjs"; - -const userAgent = navigator.userAgent.toLowerCase(); -const iphone = userAgent.includes("iphone"); -const ipad = userAgent.includes("ipad"); -const ios = iphone || ipad; +import { ios } from "../../core/env"; +import { domToBlob } from "../../modules/modern-screenshot/4.6.6/dist/index.mjs"; /** * @param {Object} args diff --git a/websites/bitview/scripts/panes/explorer.js b/websites/bitview/scripts/panes/explorer.js index e57920d79..d9e71ea00 100644 --- a/websites/bitview/scripts/panes/explorer.js +++ b/websites/bitview/scripts/panes/explorer.js @@ -1,4 +1,5 @@ import { randomFromArray } from "../core/array"; +import { explorerElement } from "../core/elements"; /** * @param {Object} args @@ -6,26 +7,22 @@ import { randomFromArray } from "../core/array"; * @param {CreateChartElement} args.createChartElement * @param {Accessor} args.option * @param {Signals} args.signals - * @param {Utilities} args.utils * @param {WebSockets} args.webSockets - * @param {Elements} args.elements - * @param {VecsResources} args.vecsResources - * @param {MetricToIndexes} args.metricToIndexes + * @param {Resources} args.resources + * @param {BRK} args.brk */ export function init({ colors, - elements, createChartElement, option, signals, - utils, webSockets, - vecsResources, - metricToIndexes, + resources, + brk, }) { const chain = window.document.createElement("div"); chain.id = "chain"; - elements.explorer.append(chain); + explorerElement.append(chain); // vecsResources.getOrCreate(/** @satisfies {Height}*/ (5), "height"); // diff --git a/websites/bitview/scripts/panes/simulation.js b/websites/bitview/scripts/panes/simulation.js index ed6cd7946..3e27c9ee0 100644 --- a/websites/bitview/scripts/panes/simulation.js +++ b/websites/bitview/scripts/panes/simulation.js @@ -9,6 +9,12 @@ import { createHeader, createSelect, } from "../core/dom"; +import { simulationElement } from "../core/elements"; +import { + numberToDollars, + numberToPercentage, + numberToUSNumber, +} from "../core/format"; import { serdeDate, serdeOptDate, serdeOptNumber } from "../core/serde"; /** @@ -16,18 +22,9 @@ import { serdeDate, serdeOptDate, serdeOptNumber } from "../core/serde"; * @param {Colors} args.colors * @param {CreateChartElement} args.createChartElement * @param {Signals} args.signals - * @param {Utilities} args.utils - * @param {Elements} args.elements - * @param {VecsResources} args.vecsResources + * @param {Resources} args.resources */ -export function init({ - colors, - elements, - createChartElement, - signals, - utils, - vecsResources, -}) { +export function init({ colors, createChartElement, signals, resources }) { /** * @typedef {Object} Frequency * @property {string} name @@ -39,8 +36,6 @@ export function init({ * @property {Frequency[]} list */ - const simulationElement = elements.simulation; - const domExtended = { /** * @param {Object} args @@ -686,7 +681,8 @@ export function init({ }, ); - const index = () => /** @type {DateIndex} */ (0); + /** @type {() => IndexName} */ + const index = () => "dateindex"; createChartElement({ index, @@ -695,9 +691,7 @@ export function init({ colors, id: `result`, fitContent: true, - vecsResources, - utils, - elements, + resources, config: [ { unit: "usd", @@ -740,9 +734,7 @@ export function init({ colors, id: `bitcoin`, fitContent: true, - vecsResources, - elements, - utils, + resources, config: [ { unit: "btc", @@ -765,9 +757,7 @@ export function init({ colors, id: `average-price`, fitContent: true, - vecsResources, - utils, - elements, + resources, config: [ { unit: "usd", @@ -794,11 +784,9 @@ export function init({ parent: resultsElement, signals, colors, - vecsResources, + resources, id: `return-ratio`, fitContent: true, - utils, - elements, config: [ { unit: "usd", @@ -820,9 +808,7 @@ export function init({ colors, id: `simulation-profitability-ratios`, fitContent: true, - vecsResources, - utils, - elements, + resources, config: [ { unit: "percentage", @@ -844,8 +830,8 @@ export function init({ ], }); - vecsResources - .getOrCreate(/** @satisfies {DateIndex} */ (0), "price_close") + resources.metrics + .getOrCreate("price_close", "dateindex") .fetch() .then((_closes) => { if (!_closes) return; @@ -1055,11 +1041,11 @@ export function init({ }); }); - const f = utils.locale.numberToUSFormat; + const f = numberToUSNumber; /** @param {number} v */ - const fd = (v) => utils.formatters.dollars.format(v); + const fd = (v) => numberToDollars.format(v); /** @param {number} v */ - const fp = (v) => utils.formatters.percentage.format(v); + const fp = (v) => numberToPercentage.format(v); /** * @param {ColorName} c * @param {string} t @@ -1098,45 +1084,6 @@ export function init({ p3.innerHTML = `You would've been ${serProfitableDaysRatio} of the time profitable and ${serUnprofitableDaysRatio} of the time unprofitable.`; - signals.createEffect( - () => 0.073, - (lowestAnnual4YReturn) => { - const serLowestAnnual4YReturn = c( - "cyan", - `${fp(lowestAnnual4YReturn)}`, - ); - - const lowestAnnual4YReturnPercentage = 1 + lowestAnnual4YReturn; - /** - * @param {number} power - */ - function bitcoinValueReturn(power) { - return ( - bitcoinValue * - Math.pow(lowestAnnual4YReturnPercentage, power) - ); - } - const bitcoinValueAfter4y = bitcoinValueReturn(4); - const serBitcoinValueAfter4y = c( - "purple", - fd(bitcoinValueAfter4y), - ); - const bitcoinValueAfter10y = bitcoinValueReturn(10); - const serBitcoinValueAfter10y = c( - "fuchsia", - fd(bitcoinValueAfter10y), - ); - const bitcoinValueAfter21y = bitcoinValueReturn(21); - const serBitcoinValueAfter21y = c( - "pink", - fd(bitcoinValueAfter21y), - ); - - /** @param {number} v */ - p4.innerHTML = `The lowest annual return after 4 years has historically been ${serLowestAnnual4YReturn}.
Using it as the baseline, your Bitcoin would be worth ${serBitcoinValueAfter4y} after 4 years, ${serBitcoinValueAfter10y} after 10 years and ${serBitcoinValueAfter21y} after 21 years.`; - }, - ); - totalInvestedAmountData.set((a) => a); bitcoinValueData.set((a) => a); bitcoinData.set((a) => a); diff --git a/websites/bitview/scripts/panes/table.js b/websites/bitview/scripts/panes/table.js index 274cdc503..e17eac5e6 100644 --- a/websites/bitview/scripts/panes/table.js +++ b/websites/bitview/scripts/panes/table.js @@ -1,23 +1,17 @@ import { randomFromArray } from "../core/array"; import { createButtonElement, createHeader, createSelect } from "../core/dom"; +import { tableElement } from "../core/elements"; import { serdeMetrics, serdeString, serdeUnit } from "../core/serde"; import { resetParams } from "../core/url"; /** * @param {Object} args - * @param {MetricToIndexes} args.metricToIndexes * @param {Option} args.option - * @param {Utilities} args.utils * @param {Signals} args.signals - * @param {VecsResources} args.vecsResources + * @param {BRK} args.brk + * @param {Resources} args.resources */ -function createTable({ - utils, - metricToIndexes, - signals, - option, - vecsResources, -}) { +function createTable({ brk, signals, option, resources }) { const indexToMetrics = createIndexToMetrics(metricToIndexes); const serializedIndexes = createSerializedIndexes(); @@ -150,7 +144,7 @@ function createTable({ let from = 0; let to = 0; - vecsResources + resources .getOrCreate(index, serializedIndex()) .fetch() .then((vec) => { @@ -292,11 +286,11 @@ function createTable({ const unit = serdeUnit.deserialize(metric); th.setUnit(unit); - const vec = vecsResources.getOrCreate(index, metric); + const vec = resources.getOrCreate(index, metric); vec.fetch({ from, to }); - const fetchedKey = vecsResources.genFetchedKey({ from, to }); + const fetchedKey = resources.genFetchedKey({ from, to }); columns.set((l) => { const i = l.indexOf(prevMetric ?? metric); @@ -355,21 +349,12 @@ function createTable({ /** * @param {Object} args * @param {Signals} args.signals - * @param {Utilities} args.utils * @param {Option} args.option - * @param {Elements} args.elements - * @param {VecsResources} args.vecsResources - * @param {MetricToIndexes} args.metricToIndexes + * @param {Resources} args.resources + * @param {BRK} args.brk */ -export function init({ - elements, - signals, - option, - utils, - vecsResources, - metricToIndexes, -}) { - const parent = elements.table; +export function init({ signals, option, resources, brk }) { + const parent = tableElement; const { headerElement } = createHeader("Table"); parent.append(headerElement); @@ -378,9 +363,8 @@ export function init({ const table = createTable({ signals, - utils, - metricToIndexes, - vecsResources, + brk, + resources, option, }); div.append(table.element); @@ -398,103 +382,6 @@ export function init({ ); } -function createSerializedIndexes() { - return /** @type {const} */ ([ - /** @satisfies {Metric} */ ("dateindex"), - /** @satisfies {Metric} */ ("decadeindex"), - /** @satisfies {Metric} */ ("difficultyepoch"), - /** @satisfies {Metric} */ ("emptyoutputindex"), - /** @satisfies {Metric} */ ("halvingepoch"), - /** @satisfies {Metric} */ ("height"), - /** @satisfies {Metric} */ ("inputindex"), - /** @satisfies {Metric} */ ("monthindex"), - /** @satisfies {Metric} */ ("opreturnindex"), - /** @satisfies {Metric} */ ("semesterindex"), - /** @satisfies {Metric} */ ("outputindex"), - /** @satisfies {Metric} */ ("p2aaddressindex"), - /** @satisfies {Metric} */ ("p2msoutputindex"), - /** @satisfies {Metric} */ ("p2pk33addressindex"), - /** @satisfies {Metric} */ ("p2pk65addressindex"), - /** @satisfies {Metric} */ ("p2pkhaddressindex"), - /** @satisfies {Metric} */ ("p2shaddressindex"), - /** @satisfies {Metric} */ ("p2traddressindex"), - /** @satisfies {Metric} */ ("p2wpkhaddressindex"), - /** @satisfies {Metric} */ ("p2wshaddressindex"), - /** @satisfies {Metric} */ ("quarterindex"), - /** @satisfies {Metric} */ ("txindex"), - /** @satisfies {Metric} */ ("unknownoutputindex"), - /** @satisfies {Metric} */ ("weekindex"), - /** @satisfies {Metric} */ ("yearindex"), - /** @satisfies {Metric} */ ("loadedaddressindex"), - /** @satisfies {Metric} */ ("emptyaddressindex"), - ]); -} -/** @typedef {ReturnType} SerializedIndexes */ -/** @typedef {SerializedIndexes[number]} SerializedIndex */ - -/** - * @param {SerializedIndex} serializedIndex - * @returns {Index} - */ -function serializedIndexToIndex(serializedIndex) { - switch (serializedIndex) { - case "height": - return /** @satisfies {Height} */ (5); - case "dateindex": - return /** @satisfies {DateIndex} */ (0); - case "weekindex": - return /** @satisfies {WeekIndex} */ (23); - case "difficultyepoch": - return /** @satisfies {DifficultyEpoch} */ (2); - case "monthindex": - return /** @satisfies {MonthIndex} */ (7); - case "quarterindex": - return /** @satisfies {QuarterIndex} */ (19); - case "semesterindex": - return /** @satisfies {SemesterIndex} */ (20); - case "yearindex": - return /** @satisfies {YearIndex} */ (24); - case "decadeindex": - return /** @satisfies {DecadeIndex} */ (1); - case "halvingepoch": - return /** @satisfies {HalvingEpoch} */ (4); - case "txindex": - return /** @satisfies {TxIndex} */ (21); - case "inputindex": - return /** @satisfies {InputIndex} */ (6); - case "outputindex": - return /** @satisfies {OutputIndex} */ (9); - case "p2pk33addressindex": - return /** @satisfies {P2PK33AddressIndex} */ (12); - case "p2pk65addressindex": - return /** @satisfies {P2PK65AddressIndex} */ (13); - case "p2pkhaddressindex": - return /** @satisfies {P2PKHAddressIndex} */ (14); - case "p2shaddressindex": - return /** @satisfies {P2SHAddressIndex} */ (15); - case "p2traddressindex": - return /** @satisfies {P2TRAddressIndex} */ (16); - case "p2wpkhaddressindex": - return /** @satisfies {P2WPKHAddressIndex} */ (17); - case "p2wshaddressindex": - return /** @satisfies {P2WSHAddressIndex} */ (18); - case "p2aaddressindex": - return /** @satisfies {P2AAddressIndex} */ (10); - case "p2msoutputindex": - return /** @satisfies {P2MSOutputIndex} */ (11); - case "opreturnindex": - return /** @satisfies {OpReturnIndex} */ (8); - case "emptyoutputindex": - return /** @satisfies {EmptyOutputIndex} */ (3); - case "unknownoutputindex": - return /** @satisfies {UnknownOutputIndex} */ (22); - case "emptyaddressindex": - return /** @satisfies {EmptyAddressIndex} */ (26); - case "loadedaddressindex": - return /** @satisfies {LoadedAddressIndex} */ (25); - } -} - /** * @param {MetricToIndexes} metricToIndexes */ diff --git a/websites/bitview/tsconfig.json b/websites/bitview/tsconfig.json index 5de6981df..6ccb37c6e 100644 --- a/websites/bitview/tsconfig.json +++ b/websites/bitview/tsconfig.json @@ -9,5 +9,5 @@ "lib": ["DOM", "DOM.Iterable", "ESNext", "WebWorker"], "skipLibCheck": true }, - "exclude": ["assets", "scripts/packages", "scripts/bridge"] + "exclude": ["assets", "scripts/modules"] }