bitview: reorg part 9

This commit is contained in:
nym21
2025-10-01 23:17:48 +02:00
parent 62d4b35c93
commit c4ce718bb2
102 changed files with 1654 additions and 1798 deletions
Generated
+37 -37
View File
@@ -229,9 +229,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]] [[package]]
name = "axum" name = "axum"
version = "0.8.4" version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" checksum = "98e529aee37b5c8206bb4bf4c44797127566d72f76952c970bd3d1e85de8f4e2"
dependencies = [ dependencies = [
"axum-core", "axum-core",
"bytes", "bytes",
@@ -248,8 +248,7 @@ dependencies = [
"mime", "mime",
"percent-encoding", "percent-encoding",
"pin-project-lite", "pin-project-lite",
"rustversion", "serde_core",
"serde",
"serde_json", "serde_json",
"serde_path_to_error", "serde_path_to_error",
"serde_urlencoded", "serde_urlencoded",
@@ -263,9 +262,9 @@ dependencies = [
[[package]] [[package]]
name = "axum-core" name = "axum-core"
version = "0.5.2" version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" checksum = "0ac7a6beb1182c7e30253ee75c3e918080bfb83f5a3023bcdf7209d85fd147e6"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-core", "futures-core",
@@ -274,7 +273,6 @@ dependencies = [
"http-body-util", "http-body-util",
"mime", "mime",
"pin-project-lite", "pin-project-lite",
"rustversion",
"sync_wrapper", "sync_wrapper",
"tower-layer", "tower-layer",
"tower-service", "tower-service",
@@ -500,6 +498,7 @@ dependencies = [
name = "brk" name = "brk"
version = "0.0.109" version = "0.0.109"
dependencies = [ dependencies = [
"brk_binder",
"brk_bundler", "brk_bundler",
"brk_cli", "brk_cli",
"brk_computer", "brk_computer",
@@ -562,7 +561,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ff3e445e42475fba5e0cfaed51345f491e479b9f2069f29875f434a5327913" checksum = "91ff3e445e42475fba5e0cfaed51345f491e479b9f2069f29875f434a5327913"
[[package]] [[package]]
name = "brk_bridge" name = "brk_binder"
version = "0.0.109" version = "0.0.109"
dependencies = [ dependencies = [
"brk_interface", "brk_interface",
@@ -585,7 +584,7 @@ name = "brk_cli"
version = "0.0.109" version = "0.0.109"
dependencies = [ dependencies = [
"bitcoincore-rpc", "bitcoincore-rpc",
"brk_bridge", "brk_binder",
"brk_bundler", "brk_bundler",
"brk_computer", "brk_computer",
"brk_error", "brk_error",
@@ -639,7 +638,7 @@ dependencies = [
"fjall", "fjall",
"jiff", "jiff",
"minreq", "minreq",
"serde_json", "sonic-rs 0.5.5",
"vecdb", "vecdb",
"zerocopy", "zerocopy",
] ]
@@ -653,7 +652,7 @@ dependencies = [
"brk_structs", "brk_structs",
"log", "log",
"minreq", "minreq",
"serde_json", "sonic-rs 0.5.5",
] ]
[[package]] [[package]]
@@ -688,8 +687,8 @@ dependencies = [
"quick_cache", "quick_cache",
"schemars 1.0.4", "schemars 1.0.4",
"serde", "serde",
"serde_json",
"serde_with", "serde_with",
"sonic-rs 0.5.5",
"vecdb", "vecdb",
] ]
@@ -711,6 +710,8 @@ dependencies = [
"brk_interface", "brk_interface",
"brk_rmcp", "brk_rmcp",
"log", "log",
"schemars 1.0.4",
"serde",
"serde_json", "serde_json",
] ]
@@ -749,7 +750,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"sse-stream", "sse-stream",
"thiserror 2.0.16", "thiserror 2.0.17",
"tokio", "tokio",
"tokio-stream", "tokio-stream",
"tokio-util", "tokio-util",
@@ -1140,7 +1141,6 @@ dependencies = [
"log", "log",
"quick_cache", "quick_cache",
"serde", "serde",
"serde_json",
"sonic-rs 0.5.5", "sonic-rs 0.5.5",
"tokio", "tokio",
"tower-http", "tower-http",
@@ -2659,9 +2659,9 @@ checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
[[package]] [[package]]
name = "lsm-tree" name = "lsm-tree"
version = "2.10.3" version = "2.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab73c02eadb3dc12c0024e5b61d6284e6d59064e67e74fbad77856caa56f62c7" checksum = "799399117a2bfb37660e08be33f470958babb98386b04185288d829df362ea15"
dependencies = [ dependencies = [
"byteorder", "byteorder",
"crossbeam-skiplist", "crossbeam-skiplist",
@@ -2683,9 +2683,9 @@ dependencies = [
[[package]] [[package]]
name = "lz4_flex" name = "lz4_flex"
version = "0.11.3" version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75761162ae2b0e580d7e7c390558127e5f01b4194debd6221fd8c207fc80e3f5" checksum = "08ab2867e3eeeca90e844d1940eab391c9dc5228783db2ed999acbc0a9ed375a"
[[package]] [[package]]
name = "matchit" name = "matchit"
@@ -2978,7 +2978,7 @@ dependencies = [
"rustc-hash", "rustc-hash",
"serde", "serde",
"serde_json", "serde_json",
"thiserror 2.0.16", "thiserror 2.0.17",
"time", "time",
] ]
@@ -2992,7 +2992,7 @@ dependencies = [
"owo-colors", "owo-colors",
"oxc-miette-derive", "oxc-miette-derive",
"textwrap", "textwrap",
"thiserror 2.0.16", "thiserror 2.0.17",
"unicode-segmentation", "unicode-segmentation",
"unicode-width", "unicode-width",
] ]
@@ -3283,7 +3283,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"simdutf8", "simdutf8",
"thiserror 2.0.16", "thiserror 2.0.17",
"tracing", "tracing",
"url", "url",
"windows", "windows",
@@ -3645,7 +3645,7 @@ dependencies = [
"rustc-hash", "rustc-hash",
"serde", "serde",
"serde_json", "serde_json",
"thiserror 2.0.16", "thiserror 2.0.17",
] ]
[[package]] [[package]]
@@ -3739,9 +3739,9 @@ dependencies = [
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.40" version = "1.0.41"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@@ -3894,23 +3894,23 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
dependencies = [ dependencies = [
"getrandom 0.2.16", "getrandom 0.2.16",
"libredox", "libredox",
"thiserror 2.0.16", "thiserror 2.0.17",
] ]
[[package]] [[package]]
name = "ref-cast" name = "ref-cast"
version = "1.0.24" version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d"
dependencies = [ dependencies = [
"ref-cast-impl", "ref-cast-impl",
] ]
[[package]] [[package]]
name = "ref-cast-impl" name = "ref-cast-impl"
version = "1.0.24" version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -4433,7 +4433,7 @@ dependencies = [
"simdutf8", "simdutf8",
"sonic-number", "sonic-number",
"sonic-simd", "sonic-simd",
"thiserror 2.0.16", "thiserror 2.0.17",
] ]
[[package]] [[package]]
@@ -4454,7 +4454,7 @@ dependencies = [
"simdutf8", "simdutf8",
"sonic-number", "sonic-number",
"sonic-simd", "sonic-simd",
"thiserror 2.0.16", "thiserror 2.0.17",
] ]
[[package]] [[package]]
@@ -4619,11 +4619,11 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "2.0.16" version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
dependencies = [ dependencies = [
"thiserror-impl 2.0.16", "thiserror-impl 2.0.17",
] ]
[[package]] [[package]]
@@ -4639,9 +4639,9 @@ dependencies = [
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "2.0.16" version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -4963,7 +4963,7 @@ version = "11.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ef1b7a6d914a34127ed8e1fa927eb7088903787bcded4fa3eef8f85ee1568be" checksum = "6ef1b7a6d914a34127ed8e1fa927eb7088903787bcded4fa3eef8f85ee1568be"
dependencies = [ dependencies = [
"thiserror 2.0.16", "thiserror 2.0.17",
"ts-rs-macros", "ts-rs-macros",
] ]
+3 -3
View File
@@ -42,10 +42,10 @@ debug-assertions = false
[workspace.dependencies] [workspace.dependencies]
allocative = { version = "0.3.4", features = ["parking_lot"] } allocative = { version = "0.3.4", features = ["parking_lot"] }
axum = "0.8.4" axum = "0.8.5"
bitcoin = { version = "0.32.7", features = ["serde"] } bitcoin = { version = "0.32.7", features = ["serde"] }
bitcoincore-rpc = "0.19.0" 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_bundler = { version = "0.0.109", path = "crates/brk_bundler" }
brk_cli = { version = "0.0.109", path = "crates/brk_cli" } brk_cli = { version = "0.0.109", path = "crates/brk_cli" }
brk_computer = { version = "0.0.109", path = "crates/brk_computer" } 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" parking_lot = "0.12.4"
quick_cache = "0.6.16" quick_cache = "0.6.16"
rayon = "1.11.0" rayon = "1.11.0"
schemars = "1.0.4"
serde = "1.0.228" serde = "1.0.228"
serde_bytes = "0.11.19" serde_bytes = "0.11.19"
serde_derive = "1.0.228" serde_derive = "1.0.228"
serde_json = { version = "1.0.145", features = ["float_roundtrip"] }
sonic-rs = "0.5.5" sonic-rs = "0.5.5"
tokio = { version = "1.47.1", features = ["rt-multi-thread"] } tokio = { version = "1.47.1", features = ["rt-multi-thread"] }
# vecdb = { path = "../seqdb/crates/vecdb", features = ["derive"]} # vecdb = { path = "../seqdb/crates/vecdb", features = ["derive"]}
+3
View File
@@ -11,6 +11,7 @@ build = "build.rs"
[features] [features]
full = [ full = [
"binder",
"bundler", "bundler",
"computer", "computer",
"error", "error",
@@ -24,6 +25,7 @@ full = [
"store", "store",
"structs", "structs",
] ]
binder = ["brk_binder"]
bundler = ["brk_bundler"] bundler = ["brk_bundler"]
computer = ["brk_computer"] computer = ["brk_computer"]
error = ["brk_error"] error = ["brk_error"]
@@ -38,6 +40,7 @@ store = ["brk_store"]
structs = ["brk_structs"] structs = ["brk_structs"]
[dependencies] [dependencies]
brk_binder = { workspace = true, optional = true }
brk_bundler = { workspace = true, optional = true } brk_bundler = { workspace = true, optional = true }
brk_cli = { workspace = true } brk_cli = { workspace = true }
brk_computer = { workspace = true, optional = true } brk_computer = { workspace = true, optional = true }
+4
View File
@@ -1,5 +1,9 @@
#![doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]
#[cfg(feature = "binder")]
#[doc(inline)]
pub use brk_binder as binder;
#[cfg(feature = "bundler")] #[cfg(feature = "bundler")]
#[doc(inline)] #[doc(inline)]
pub use brk_bundler as bundler; pub use brk_bundler as bundler;
@@ -1,6 +1,6 @@
[package] [package]
name = "brk_bridge" name = "brk_binder"
description = "A generator of bridge files for other languages" description = "A generator of binding files for other languages"
version.workspace = true version.workspace = true
edition.workspace = true edition.workspace = true
license.workspace = true license.workspace = true
+1
View File
@@ -0,0 +1 @@
# brk_binder
@@ -15,12 +15,12 @@ const AUTO_GENERATED_DISCLAIMER: &str = "//
#[allow(clippy::upper_case_acronyms)] #[allow(clippy::upper_case_acronyms)]
pub trait Bridge { 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> { impl Bridge for Interface<'static> {
fn generate_js_files(&self, packages_path: &Path) -> io::Result<()> { fn generate_js_files(&self, modules_path: &Path) -> io::Result<()> {
let path = packages_path.join("brk-client"); let path = modules_path.join("brk-client");
if !fs::exists(&path)? { if !fs::exists(&path)? {
return Ok(()); return Ok(());
@@ -36,7 +36,7 @@ impl Bridge for Interface<'static> {
} }
fn generate_version_file(parent: &Path) -> io::Result<()> { 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!( let contents = format!(
"{AUTO_GENERATED_DISCLAIMER} "{AUTO_GENERATED_DISCLAIMER}
@@ -89,29 +89,39 @@ fn generate_metrics_file(interface: &Interface<'static>, parent: &Path) -> io::R
let mut contents = format!( let mut contents = format!(
"{AUTO_GENERATED_DISCLAIMER} "{AUTO_GENERATED_DISCLAIMER}
export const INDEXES = /** @type {{const}} */ ([
{}
]);
/** /**
" * @typedef {{typeof INDEXES[number]}} IndexName
);
contents += &indexes
.iter()
.map(|i| format!(" * @typedef {{\"{}\"}} {i}", i.serialize_long()))
.collect::<Vec<_>>()
.join("\n");
contents += &format!(
"
* @typedef {{{}}} Index
*/ */
", ",
indexes indexes
.iter() .iter()
.map(|i| i.to_string()) .map(|i| format!(" \"{}\"", i.serialize_long()))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(" | ") .join(",\n")
); );
// contents += &indexes
// .iter()
// .map(|i| format!(" * @typedef {{\"{}\"}} {i}", i.serialize_long()))
// .collect::<Vec<_>>()
// .join("\n");
// contents += &format!(
// "
// * @typedef {{{}}} Index
// */
// ",
// indexes
// .iter()
// .map(|i| i.to_string())
// .collect::<Vec<_>>()
// .join(" | ")
// );
let mut unique_index_groups = BTreeMap::new(); let mut unique_index_groups = BTreeMap::new();
let mut word_to_freq: BTreeMap<_, usize> = 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::<Vec<_>>(); .collect::<Vec<_>>();
contents += &format!( contents += &format!(
"export const INDEX_TO_WORD = [ "
export const INDEX_TO_WORD = [
{} {}
]; ];
", ",
words words
.iter() .iter()
.map(|w| format!("\"{w}\"")) .enumerate()
.map(|(index, word)| format!("\"{word}\", // {}", index_to_letters(index)))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(",\n ") .join("\n ")
); );
let word_to_base62 = words let word_to_base62 = words
@@ -150,7 +162,7 @@ fn generate_metrics_file(interface: &Interface<'static>, parent: &Path) -> io::R
.collect::<HashMap<_, _>>(); .collect::<HashMap<_, _>>();
let mut ser_metric_to_indexes = " let mut ser_metric_to_indexes = "
/** @type {Record<string, Index[]>} */ /** @type {Record<string, IndexName[]>} */
export const COMPRESSED_METRIC_TO_INDEXES = { export const COMPRESSED_METRIC_TO_INDEXES = {
" "
.to_string(); .to_string();
@@ -186,7 +198,7 @@ export const COMPRESSED_METRIC_TO_INDEXES = {
sorted_groups.sort_by_key(|(_, index)| *index); sorted_groups.sort_by_key(|(_, index)| *index);
sorted_groups.into_iter().for_each(|(group, index)| { sorted_groups.into_iter().for_each(|(group, index)| {
let index = index_to_letters(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; contents += &ser_metric_to_indexes;
-1
View File
@@ -1 +0,0 @@
# brk_bridge
+40 -14
View File
@@ -6,7 +6,10 @@ use std::{
sync::Arc, sync::Arc,
}; };
use brk_rolldown::{Bundler, BundlerOptions, RawMinifyOptions, SourceMapType}; use brk_rolldown::{
Bundler, BundlerOptions, InlineConstConfig, InlineConstMode, InlineConstOption,
OptimizationOption, RawMinifyOptions, SourceMapType,
};
use log::error; use log::error;
use notify::{EventKind, RecursiveMode, Watcher}; use notify::{EventKind, RecursiveMode, Watcher};
use sugar_path::SugarPath; use sugar_path::SugarPath;
@@ -15,17 +18,17 @@ use tokio::sync::Mutex;
const VERSION: &str = env!("CARGO_PKG_VERSION"); const VERSION: &str = env!("CARGO_PKG_VERSION");
pub async fn bundle( pub async fn bundle(
packages_path: &Path, modules_path: &Path,
websites_path: &Path, websites_path: &Path,
source_folder: &str, source_folder: &str,
watch: bool, watch: bool,
) -> io::Result<PathBuf> { ) -> io::Result<PathBuf> {
let relative_packages_path = packages_path; let relative_modules_path = modules_path;
let relative_source_path = websites_path.join(source_folder); let relative_source_path = websites_path.join(source_folder);
let relative_dist_path = websites_path.join("dist"); let relative_dist_path = websites_path.join("dist");
let absolute_packages_path = relative_packages_path.absolutize(); let absolute_modules_path = relative_modules_path.absolutize();
let absolute_packages_path_clone = absolute_packages_path.clone(); let absolute_modules_path_clone = absolute_modules_path.clone();
let absolute_websites_path = websites_path.absolutize(); let absolute_websites_path = websites_path.absolutize();
let absolute_websites_path_clone = absolute_websites_path.clone(); 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 = absolute_source_path.join("index.html");
let absolute_source_index_path_clone = absolute_source_index_path.clone(); 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_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 = absolute_source_path.join("service-worker.js");
let absolute_source_sw_path_clone = absolute_source_sw_path.clone(); 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 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_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( copy_dir_all(
&absolute_packages_path, &absolute_modules_path,
&absolute_source_scripts_packages_path, &absolute_source_scripts_modules_path,
)?; )?;
copy_dir_all(&absolute_source_path, &absolute_dist_path)?; copy_dir_all(&absolute_source_path, &absolute_dist_path)?;
fs::remove_dir_all(&absolute_dist_scripts_path)?; fs::remove_dir_all(&absolute_dist_scripts_path)?;
fs::create_dir(&absolute_dist_scripts_path)?; fs::create_dir(&absolute_dist_scripts_path)?;
// dbg!(BundlerOptions::default());
let mut bundler = Bundler::new(BundlerOptions { let mut bundler = Bundler::new(BundlerOptions {
input: Some(vec![format!("./{source_folder}/scripts/entry.js").into()]), input: Some(vec![format!("./{source_folder}/scripts/entry.js").into()]),
dir: Some("./dist/scripts".to_string()), dir: Some("./dist/scripts".to_string()),
cwd: Some(absolute_websites_path), cwd: Some(absolute_websites_path),
minify: Some(RawMinifyOptions::Bool(true)), minify: Some(RawMinifyOptions::Bool(true)),
sourcemap: Some(SourceMapType::File), 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() ..Default::default()
}) })
.unwrap(); .unwrap();
@@ -114,12 +141,11 @@ pub async fn bundle(
update_dist_index(); update_dist_index();
} else if path == absolute_source_sw_path_clone { } else if path == absolute_source_sw_path_clone {
update_source_sw(); update_source_sw();
} else if let Ok(suffix) = path.strip_prefix(&absolute_packages_path) { } else if let Ok(suffix) = path.strip_prefix(&absolute_modules_path) {
let source_packages_path = let source_modules_path = absolute_source_scripts_modules_path.join(suffix);
absolute_source_scripts_packages_path.join(suffix);
if path.is_file() { if path.is_file() {
let _ = fs::create_dir_all(path.parent().unwrap()); 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) } else if let Ok(suffix) = path.strip_prefix(&absolute_source_path)
// scripts are handled by rolldown // scripts are handled by rolldown
@@ -141,7 +167,7 @@ pub async fn bundle(
.watch(&absolute_websites_path_clone, RecursiveMode::Recursive) .watch(&absolute_websites_path_clone, RecursiveMode::Recursive)
.unwrap(); .unwrap();
event_watcher event_watcher
.watch(&absolute_packages_path_clone, RecursiveMode::Recursive) .watch(&absolute_modules_path_clone, RecursiveMode::Recursive)
.unwrap(); .unwrap();
let watcher = let watcher =
+1 -1
View File
@@ -11,7 +11,7 @@ build = "build.rs"
[dependencies] [dependencies]
bitcoincore-rpc = { workspace = true } bitcoincore-rpc = { workspace = true }
brk_bridge = { workspace = true } brk_binder = { workspace = true }
brk_bundler = { workspace = true } brk_bundler = { workspace = true }
brk_computer = { workspace = true } brk_computer = { workspace = true }
brk_error = { workspace = true } brk_error = { workspace = true }
+9 -9
View File
@@ -9,7 +9,7 @@ use std::{
}; };
use bitcoincore_rpc::{self, RpcApi}; use bitcoincore_rpc::{self, RpcApi};
use brk_bridge::Bridge; use brk_binder::Bridge;
use brk_bundler::bundle; use brk_bundler::bundle;
use brk_computer::Computer; use brk_computer::Computer;
use brk_error::Result; use brk_error::Result;
@@ -63,19 +63,19 @@ pub fn run() -> color_eyre::Result<()> {
let future = async move { let future = async move {
let bundle_path = if website.is_some() { let bundle_path = if website.is_some() {
let websites_dev_path = Path::new("../../websites"); 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 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(); 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 { } else {
let downloaded_brk_path = downloads_path.join(format!("brk-{VERSION}")); let downloaded_brk_path = downloads_path.join(format!("brk-{VERSION}"));
let downloaded_websites_path = downloaded_brk_path.join("websites"); 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)? { if !fs::exists(&downloaded_websites_path)? {
info!("Downloading source from Github..."); info!("Downloading source from Github...");
@@ -94,14 +94,14 @@ pub fn run() -> color_eyre::Result<()> {
} }
websites_path = downloaded_websites_path; 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( Some(
bundle( bundle(
&packages_path, &modules_path,
&websites_path, &websites_path,
website.to_folder_name(), website.to_folder_name(),
true, true,
+1 -1
View File
@@ -15,6 +15,6 @@ bitcoincore-rpc = { workspace = true }
fjall = { workspace = true } fjall = { workspace = true }
jiff = { workspace = true } jiff = { workspace = true }
minreq = { workspace = true } minreq = { workspace = true }
serde_json = { workspace = true } sonic-rs = { workspace = true }
vecdb = { workspace = true } vecdb = { workspace = true }
zerocopy = { workspace = true } zerocopy = { workspace = true }
+5 -5
View File
@@ -19,7 +19,7 @@ pub enum Error {
SystemTimeError(time::SystemTimeError), SystemTimeError(time::SystemTimeError),
BitcoinConsensusEncode(bitcoin::consensus::encode::Error), BitcoinConsensusEncode(bitcoin::consensus::encode::Error),
BitcoinBip34Error(bitcoin::block::Bip34Error), BitcoinBip34Error(bitcoin::block::Bip34Error),
SerdeJson(serde_json::Error), SonicRS(sonic_rs::Error),
ZeroCopyError, ZeroCopyError,
Vecs(vecdb::Error), Vecs(vecdb::Error),
@@ -49,9 +49,9 @@ impl From<time::SystemTimeError> for Error {
} }
} }
impl From<serde_json::Error> for Error { impl From<sonic_rs::Error> for Error {
fn from(error: serde_json::Error) -> Self { fn from(error: sonic_rs::Error) -> Self {
Self::SerdeJson(error) Self::SonicRS(error)
} }
} }
@@ -126,7 +126,7 @@ impl fmt::Display for Error {
Error::Jiff(error) => Display::fmt(&error, f), Error::Jiff(error) => Display::fmt(&error, f),
Error::Minreq(error) => Display::fmt(&error, f), Error::Minreq(error) => Display::fmt(&error, f),
Error::SeqDB(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::SystemTimeError(error) => Display::fmt(&error, f),
Error::VecDB(error) => Display::fmt(&error, f), Error::VecDB(error) => Display::fmt(&error, f),
Error::Vecs(error) => Display::fmt(&error, f), Error::Vecs(error) => Display::fmt(&error, f),
+1 -1
View File
@@ -15,4 +15,4 @@ brk_logger = { workspace = true }
brk_structs = { workspace = true } brk_structs = { workspace = true }
log = { workspace = true } log = { workspace = true }
minreq = { workspace = true } minreq = { workspace = true }
serde_json = { workspace = true } sonic-rs = { workspace = true }
+17 -16
View File
@@ -3,13 +3,12 @@ use std::{
fs::{self, File}, fs::{self, File},
io::BufReader, io::BufReader,
path::{Path, PathBuf}, path::{Path, PathBuf},
str::FromStr,
}; };
use brk_error::{Error, Result}; use brk_error::{Error, Result};
use brk_structs::{Cents, OHLCCents, Timestamp}; use brk_structs::{Cents, OHLCCents, Timestamp};
use log::info; use log::info;
use serde_json::Value; use sonic_rs::{JsonContainerTrait, JsonValueTrait, Value};
use crate::{Close, Date, Dollars, Fetcher, High, Low, Open, default_retry}; 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 { pub struct Binance {
path: Option<PathBuf>, path: Option<PathBuf>,
_1mn: Option<BTreeMap<Timestamp, OHLCCents>>, _1mn: Option<BTreeMap<Timestamp, OHLCCents>>,
pub _1d: Option<BTreeMap<Date, OHLCCents>>, _1d: Option<BTreeMap<Date, OHLCCents>>,
har: Option<BTreeMap<Timestamp, OHLCCents>>, har: Option<BTreeMap<Timestamp, OHLCCents>>,
} }
@@ -69,11 +68,11 @@ impl Binance {
info!("Fetching 1mn prices from Binance..."); info!("Fetching 1mn prices from Binance...");
default_retry(|_| { default_retry(|_| {
Self::json_to_timestamp_to_ohlc( Self::json_to_timestamp_to_ohlc(&sonic_rs::from_str(
&minreq::get(Self::url("interval=1m&limit=1000")) minreq::get(Self::url("interval=1m&limit=1000"))
.send()? .send()?
.json()?, .as_str()?,
) )?)
}) })
} }
@@ -94,7 +93,9 @@ impl Binance {
info!("Fetching daily prices from Binance..."); info!("Fetching daily prices from Binance...");
default_retry(|_| { 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 reader = BufReader::new(file);
let json: BTreeMap<String, Value> = if let Ok(json) = serde_json::from_reader(reader) { let json: BTreeMap<String, Value> = if let Ok(json) = sonic_rs::from_reader(reader) {
json json
} else { } else {
return Ok(Default::default()); return Ok(Default::default());
@@ -129,7 +130,7 @@ impl Binance {
.ok_or(Error::Str("Expect object to have log attribute"))? .ok_or(Error::Str("Expect object to have log attribute"))?
.as_object() .as_object()
.ok_or(Error::Str("Expect to be an object"))? .ok_or(Error::Str("Expect to be an object"))?
.get("entries") .get(&"entries")
.ok_or(Error::Str("Expect object to have entries"))? .ok_or(Error::Str("Expect object to have entries"))?
.as_array() .as_array()
.ok_or(Error::Str("Expect to be an array"))? .ok_or(Error::Str("Expect to be an array"))?
@@ -138,11 +139,11 @@ impl Binance {
entry entry
.as_object() .as_object()
.unwrap() .unwrap()
.get("request") .get(&"request")
.unwrap() .unwrap()
.as_object() .as_object()
.unwrap() .unwrap()
.get("url") .get(&"url")
.unwrap() .unwrap()
.as_str() .as_str()
.unwrap() .unwrap()
@@ -152,14 +153,14 @@ impl Binance {
let response = entry let response = entry
.as_object() .as_object()
.unwrap() .unwrap()
.get("response") .get(&"response")
.unwrap() .unwrap()
.as_object() .as_object()
.unwrap(); .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() { if text.is_none() {
return Ok(BTreeMap::new()); return Ok(BTreeMap::new());
@@ -167,7 +168,7 @@ impl Binance {
let text = text.unwrap().as_str().unwrap(); 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| { .try_fold(BTreeMap::default(), |mut all, res| {
all.append(&mut res?); all.append(&mut res?);
+3 -3
View File
@@ -3,7 +3,7 @@ use std::collections::BTreeMap;
use brk_error::{Error, Result}; use brk_error::{Error, Result};
use brk_structs::{Cents, CheckedSub, Date, DateIndex, Height, OHLCCents}; use brk_structs::{Cents, CheckedSub, Date, DateIndex, Height, OHLCCents};
use log::info; use log::info;
use serde_json::Value; use sonic_rs::{JsonContainerTrait, JsonValueTrait, Value};
use crate::{Close, Dollars, High, Low, Open, default_retry}; use crate::{Close, Dollars, High, Low, Open, default_retry};
@@ -51,7 +51,7 @@ impl BRK {
height + CHUNK_SIZE 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() body.as_array()
.ok_or(Error::Str("Expect to be an array"))? .ok_or(Error::Str("Expect to be an array"))?
@@ -96,7 +96,7 @@ impl BRK {
dateindex + CHUNK_SIZE 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() body.as_array()
.ok_or(Error::Str("Expect to be an array"))? .ok_or(Error::Str("Expect to be an array"))?
+11 -5
View File
@@ -3,7 +3,7 @@ use std::collections::BTreeMap;
use brk_error::{Error, Result}; use brk_error::{Error, Result};
use brk_structs::{Cents, Close, Date, Dollars, High, Low, OHLCCents, Open, Timestamp}; use brk_structs::{Cents, Close, Date, Dollars, High, Low, OHLCCents, Open, Timestamp};
use log::info; use log::info;
use serde_json::Value; use sonic_rs::{JsonContainerTrait, JsonValueTrait, Value};
use crate::{Fetcher, default_retry}; use crate::{Fetcher, default_retry};
@@ -36,7 +36,9 @@ impl Kraken {
info!("Fetching 1mn prices from Kraken..."); info!("Fetching 1mn prices from Kraken...");
default_retry(|_| { 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<BTreeMap<Date, OHLCCents>> { pub fn fetch_1d() -> Result<BTreeMap<Date, OHLCCents>> {
info!("Fetching daily prices from Kraken..."); 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<BTreeMap<Timestamp, OHLCCents>> { fn json_to_timestamp_to_ohlc(json: &Value) -> Result<BTreeMap<Timestamp, OHLCCents>> {
@@ -73,11 +79,11 @@ impl Kraken {
{ {
json.as_object() json.as_object()
.ok_or(Error::Str("Expect to be an object"))? .ok_or(Error::Str("Expect to be an object"))?
.get("result") .get(&"result")
.ok_or(Error::Str("Expect object to have result"))? .ok_or(Error::Str("Expect object to have result"))?
.as_object() .as_object()
.ok_or(Error::Str("Expect to be an object"))? .ok_or(Error::Str("Expect to be an object"))?
.get("XXBTZUSD") .get(&"XXBTZUSD")
.ok_or(Error::Str("Expect to have XXBTZUSD"))? .ok_or(Error::Str("Expect to have XXBTZUSD"))?
.as_array() .as_array()
.ok_or(Error::Str("Expect to be an array"))? .ok_or(Error::Str("Expect to be an array"))?
+1
View File
@@ -22,6 +22,7 @@ where
if i == retries || res.is_ok() { if i == retries || res.is_ok() {
return res; return res;
} else { } else {
let _ = dbg!(res);
info!("Failed, waiting {sleep_in_s} seconds..."); info!("Failed, waiting {sleep_in_s} seconds...");
sleep(Duration::from_secs(sleep_in_s)); sleep(Duration::from_secs(sleep_in_s));
} }
+2 -2
View File
@@ -19,8 +19,8 @@ brk_structs = { workspace = true }
vecdb = { workspace = true } vecdb = { workspace = true }
derive_deref = { workspace = true } derive_deref = { workspace = true }
quick_cache = { workspace = true } quick_cache = { workspace = true }
schemars = "1.0.4" schemars = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true } sonic-rs = { workspace = true }
serde_with = "3.14.1" serde_with = "3.14.1"
nucleo-matcher = "0.3.1" nucleo-matcher = "0.3.1"
+33 -36
View File
@@ -1,26 +1,27 @@
use serde::{Deserialize, Deserializer}; use serde::{Deserialize, Deserializer};
use sonic_rs::{JsonValueTrait, Value};
pub fn de_unquote_i64<'de, D>(deserializer: D) -> Result<Option<i64>, D::Error> pub fn de_unquote_i64<'de, D>(deserializer: D) -> Result<Option<i64>, D::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,
{ {
let value: Option<serde_json::Value> = Option::deserialize(deserializer)?; let value: Option<Value> = Option::deserialize(deserializer)?;
match value { if value.is_none() {
None => Ok(None), return 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(); let value = value.unwrap();
}
s.parse::<i64>().map(Some).map_err(serde::de::Error::custom) 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)) => { s.parse::<i64>().map(Some).map_err(serde::de::Error::custom)
// If it's a number, convert it to i64 } else if let Some(n) = value.as_i64() {
n.as_i64() Ok(Some(n))
.ok_or_else(|| serde::de::Error::custom("number out of range")) } else {
.map(Some) Err(serde::de::Error::custom("expected a string or number"))
}
_ => Err(serde::de::Error::custom("expected a string or number")),
} }
} }
@@ -28,28 +29,24 @@ pub fn de_unquote_usize<'de, D>(deserializer: D) -> Result<Option<usize>, D::Err
where where
D: Deserializer<'de>, D: Deserializer<'de>,
{ {
let value: Option<serde_json::Value> = Option::deserialize(deserializer)?; let value: Option<Value> = Option::deserialize(deserializer)?;
match value { if value.is_none() {
None => Ok(None), return 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(); let value = value.unwrap();
}
s.parse::<usize>() if let Some(mut s) = value.as_str().map(|s| s.to_string()) {
.map(Some) if s.starts_with('"') && s.ends_with('"') && s.len() >= 2 {
.map_err(serde::de::Error::custom) s = s[1..s.len() - 1].to_string();
}
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"))
} }
s.parse::<usize>()
.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"))
} }
} }
+19 -18
View File
@@ -3,6 +3,7 @@ use std::fmt;
use derive_deref::Deref; use derive_deref::Deref;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::Deserialize; use serde::Deserialize;
use sonic_rs::{JsonContainerTrait, JsonValueTrait, Value};
#[derive(Debug, Deref, JsonSchema)] #[derive(Debug, Deref, JsonSchema)]
pub struct MaybeIds(Vec<String>); pub struct MaybeIds(Vec<String>);
@@ -27,26 +28,26 @@ impl<'de> Deserialize<'de> for MaybeIds {
where where
D: serde::Deserializer<'de>, D: serde::Deserializer<'de>,
{ {
match serde_json::Value::deserialize(deserializer)? { let value = Value::deserialize(deserializer)?;
serde_json::Value::String(str) => {
if str.len() <= MAX_STRING_SIZE { if let Some(str) = value.as_str() {
Ok(MaybeIds(sanitize_ids( if str.len() <= MAX_STRING_SIZE {
str.split(",").map(|s| s.to_string()), Ok(MaybeIds(sanitize_ids(
))) str.split(",").map(|s| s.to_string()),
} else { )))
Err(serde::de::Error::custom("Given parameter is too long")) } else {
} Err(serde::de::Error::custom("Given parameter is too long"))
} }
serde_json::Value::Array(vec) => { } else if let Some(vec) = value.as_array() {
if vec.len() <= MAX_VECS { if vec.len() <= MAX_VECS {
Ok(MaybeIds(sanitize_ids( Ok(MaybeIds(sanitize_ids(
vec.into_iter().map(|s| s.as_str().unwrap().to_string()), vec.into_iter().map(|s| s.as_str().unwrap().to_string()),
))) )))
} else { } else {
Err(serde::de::Error::custom("Given parameter is too long")) 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"))
} }
} }
} }
+8 -8
View File
@@ -27,7 +27,7 @@ pub use format::Format;
pub use index::Index; pub use index::Index;
pub use output::{Output, Value}; pub use output::{Output, Value};
pub use pagination::{PaginatedIndexParam, PaginationParam}; pub use pagination::{PaginatedIndexParam, PaginationParam};
pub use params::{IdParam, Params, ParamsOpt}; pub use params::{Params, ParamsOpt};
use vecs::Vecs; use vecs::Vecs;
use crate::vecs::{IndexToVec, MetricToVec}; use crate::vecs::{IndexToVec, MetricToVec};
@@ -222,16 +222,16 @@ impl<'a> Interface<'a> {
&self.vecs.index_to_metric_to_vec &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 self.vecs.metric_count
} }
pub fn get_index_count(&self) -> usize { pub fn total_metric_count(&self) -> usize {
self.vecs.index_count self.vecs.vec_count
} }
pub fn get_vec_count(&self) -> usize { pub fn index_count(&self) -> usize {
self.vecs.vec_count self.vecs.index_count
} }
pub fn get_indexes(&self) -> &[&'static str] { pub fn get_indexes(&self) -> &[&'static str] {
@@ -250,8 +250,8 @@ impl<'a> Interface<'a> {
self.vecs.index_to_ids(paginated_index) self.vecs.index_to_ids(paginated_index)
} }
pub fn get_vecid_to_indexes(&self, id: String) -> Option<&Vec<&'static str>> { pub fn metric_to_indexes(&self, metric: String) -> Option<&Vec<&'static str>> {
self.vecs.id_to_indexes(id) self.vecs.metric_to_indexes(metric)
} }
pub fn parser(&self) -> &Parser { pub fn parser(&self) -> &Parser {
-5
View File
@@ -106,8 +106,3 @@ impl ParamsOpt {
self.format self.format
} }
} }
#[derive(Debug, Deserialize, JsonSchema)]
pub struct IdParam {
pub id: String,
}
+3 -2
View File
@@ -131,8 +131,9 @@ impl<'a> Vecs<'a> {
&self.metrics[start..end] &self.metrics[start..end]
} }
pub fn id_to_indexes(&self, id: String) -> Option<&Vec<&'static str>> { pub fn metric_to_indexes(&self, metric: String) -> Option<&Vec<&'static str>> {
self.metric_to_indexes.get(id.as_str()) self.metric_to_indexes
.get(metric.replace("-", "_").as_str())
} }
pub fn index_to_ids( pub fn index_to_ids(
+3 -1
View File
@@ -17,7 +17,9 @@ brk_rmcp = { version = "0.7.1", features = [
"transport-streamable-http-server", "transport-streamable-http-server",
] } ] }
log = { workspace = true } 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] [package.metadata.cargo-machete]
ignored = ["serde_json"] ignored = ["serde_json"]
+12 -5
View File
@@ -1,6 +1,6 @@
#![doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]
use brk_interface::{IdParam, Interface, PaginatedIndexParam, PaginationParam, Params}; use brk_interface::{Interface, PaginatedIndexParam, PaginationParam, Params};
use brk_rmcp::{ use brk_rmcp::{
ErrorData as McpError, RoleServer, ServerHandler, ErrorData as McpError, RoleServer, ServerHandler,
handler::server::{router::tool::ToolRouter, wrapper::Parameters}, handler::server::{router::tool::ToolRouter, wrapper::Parameters},
@@ -9,6 +9,8 @@ use brk_rmcp::{
tool, tool_handler, tool_router, tool, tool_handler, tool_router,
}; };
use log::info; use log::info;
use schemars::JsonSchema;
use serde::Deserialize;
pub mod route; pub mod route;
@@ -35,7 +37,7 @@ Get the count of all existing indexes.
async fn get_index_count(&self) -> Result<CallToolResult, McpError> { async fn get_index_count(&self) -> Result<CallToolResult, McpError> {
info!("mcp: get_index_count"); info!("mcp: get_index_count");
Ok(CallToolResult::success(vec![ 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<CallToolResult, McpError> { async fn get_metric_count(&self) -> Result<CallToolResult, McpError> {
info!("mcp: get_metric_count"); info!("mcp: get_metric_count");
Ok(CallToolResult::success(vec![ 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<CallToolResult, McpError> { async fn get_vec_count(&self) -> Result<CallToolResult, McpError> {
info!("mcp: get_vec_count"); info!("mcp: get_vec_count");
Ok(CallToolResult::success(vec![ 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<CallToolResult, McpError> { ) -> Result<CallToolResult, McpError> {
info!("mcp: get_vecid_to_indexes"); info!("mcp: get_vecid_to_indexes");
Ok(CallToolResult::success(vec![ 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()) Ok(self.get_info())
} }
} }
#[derive(Debug, Deserialize, JsonSchema)]
pub struct IdParam {
pub id: String,
}
-1
View File
@@ -27,7 +27,6 @@ jiff = { workspace = true }
log = { workspace = true } log = { workspace = true }
quick_cache = { workspace = true } quick_cache = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true }
sonic-rs = { workspace = true } sonic-rs = { workspace = true }
tokio = { workspace = true } tokio = { workspace = true }
tower-http = { version = "0.6.6", features = ["compression-full", "trace"] } tower-http = { version = "0.6.6", features = ["compression-full", "trace"] }
+238
View File
@@ -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<AppState> {
fn add_api_explorer_routes(self) -> Self {
self.route(
"/api/address/{address}",
get(
async |Path(address): Path<String>, state: State<AppState>| -> 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<String>, state: State<AppState>| -> 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()
},
),
)
}
}
+111
View File
@@ -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<AppState> {
fn add_api_metrics_routes(self) -> Self {
self.route(
"/api/metrics/count",
get(async |State(app_state): State<AppState>| -> 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<AppState>| -> Response {
Json(app_state.interface.get_accepted_indexes()).into_response()
}),
)
.route(
"/api/vecs/metrics",
get(
async |State(app_state): State<AppState>,
Query(pagination): Query<PaginationParam>|
-> Response {
Json(app_state.interface.get_metrics(pagination)).into_response()
},
),
)
.route(
"/api/vecs/index-to-metrics",
get(
async |State(app_state): State<AppState>,
Query(paginated_index): Query<PaginatedIndexParam>|
-> Response {
Json(app_state.interface.get_index_to_vecids(paginated_index)).into_response()
},
),
)
.route(
"/api/metrics/{metric}",
get(
async |State(app_state): State<AppState>, Path(metric): Path<String>| -> 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<AppState>,
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<String>,
Query(params_opt): Query<ParamsOpt>,
state: State<AppState>|
-> 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::<Vec<_>>().join(TO_SEPARATOR)),
params_opt,
));
data::handler(uri, headers, Query(params), state).await
} else {
"Bad variant".into_response()
}
},
),
)
}
}
+12 -306
View File
@@ -1,25 +1,6 @@
use std::{ use axum::{Router, response::Redirect, routing::get};
fs::File,
io::{Cursor, Read, Seek, SeekFrom},
str::FromStr,
};
use axum::{ use crate::api::{explorer::ApiExplorerRoutes, metrics::ApiMetricsRoutes};
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 super::AppState; use super::AppState;
@@ -30,292 +11,17 @@ pub trait ApiRoutes {
fn add_api_routes(self) -> Self; 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<AppState> { impl ApiRoutes for Router<AppState> {
fn add_api_routes(self) -> Self { fn add_api_routes(self) -> Self {
self.route( self.add_api_explorer_routes()
"/api/address/{address}", .add_api_metrics_routes()
get( .route(
async |Path(address): Path<String>, state: State<AppState>| -> Response { "/api",
let Ok(address) = Address::from_str(&address) else { get(|| async {
return "Invalid address".into_response(); Redirect::temporary(
}; "https://github.com/bitcoinresearchkit/brk/tree/main/crates/brk_server#api",
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<String>, state: State<AppState>| -> 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<AppState>| -> Response {
Json(app_state.interface.get_index_count()).into_response()
}),
)
.route(
"/api/vecs/metric-count",
get(async |State(app_state): State<AppState>| -> Response {
Json(app_state.interface.get_metric_count()).into_response()
}),
)
.route(
"/api/vecs/vec-count",
get(async |State(app_state): State<AppState>| -> Response {
Json(app_state.interface.get_vec_count()).into_response()
}),
)
.route(
"/api/vecs/indexes",
get(async |State(app_state): State<AppState>| -> Response {
Json(app_state.interface.get_indexes()).into_response()
}),
)
.route(
"/api/vecs/accepted-indexes",
get(async |State(app_state): State<AppState>| -> Response {
Json(app_state.interface.get_accepted_indexes()).into_response()
}),
)
.route(
"/api/vecs/metrics",
get(
async |State(app_state): State<AppState>,
Query(pagination): Query<PaginationParam>|
-> Response {
Json(app_state.interface.get_metrics(pagination)).into_response()
},
),
)
.route(
"/api/vecs/index-to-metrics",
get(
async |State(app_state): State<AppState>,
Query(paginated_index): Query<PaginatedIndexParam>|
-> 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<AppState>,
Query(param): Query<IdParam>|
-> 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<String>,
Query(params_opt): Query<ParamsOpt>,
state: State<AppState>|
-> 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::<Vec<_>>().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",
)
}),
)
} }
} }
+1 -1
View File
@@ -101,7 +101,7 @@ impl Server {
.route("/version", get(Json(VERSION))) .route("/version", get(Json(VERSION)))
.route( .route(
"/health", "/health",
get(Json(serde_json::json!({ get(Json(sonic_rs::json!({
"status": "healthy", "status": "healthy",
"service": "brk-server", "service": "brk-server",
"timestamp": jiff::Timestamp::now().to_string() "timestamp": jiff::Timestamp::now().to_string()
+1 -1
View File
@@ -1,5 +1,5 @@
LICENSE LICENSE
*/**/*.json **/*.*.*/*.json
*webcomponent* *webcomponent*
README*.md README*.md
cli* cli*
@@ -1,18 +1,25 @@
/**
* @import { IndexName } from "./generated/metrics"
* @import { Metric } from './metrics'
*
* @typedef {ReturnType<createClient>} BRK
*/
// client.metrics.catalog.a.b.c() -> string (uncompress inside)
import { runWhenIdle } from "./idle"; import { runWhenIdle } from "./idle";
import { POOL_ID_TO_POOL_NAME } from "./generated/pools"; import { POOL_ID_TO_POOL_NAME } from "./generated/pools";
import { hasMetric, metricToIndexes } from "./metrics"; import { INDEXES } from "./generated/metrics";
import { hasMetric, getIndexesFromMetric } from "./metrics";
/** import { VERSION } from "./generated/version";
* @typedef {ReturnType<createClient>} BRKClient
*/
const CACHE_NAME = "__BRK_CLIENT__"; const CACHE_NAME = "__BRK_CLIENT__";
/** /**
* @param {string} [origin] - defaults to / * @param {string} origin
*/ */
export function createClient(origin = "/") { export function createClient(origin) {
/** /**
* @template T * @template T
* @param {(value: T) => void} callback * @param {(value: T) => void} callback
@@ -80,7 +87,7 @@ export function createClient(origin = "/") {
/** /**
* @param {Metric} metric * @param {Metric} metric
* @param {Index} index * @param {IndexName} index
* @param {number} [from] * @param {number} [from]
* @param {number} [to] * @param {number} [to]
*/ */
@@ -102,7 +109,7 @@ export function createClient(origin = "/") {
/** /**
* @template T * @template T
* @param {(v: T[]) => void} callback * @param {(v: T[]) => void} callback
* @param {Index} index * @param {IndexName} index
* @param {Metric} metric * @param {Metric} metric
* @param {number} [from] * @param {number} [from]
* @param {number} [to] * @param {number} [to]
@@ -112,9 +119,12 @@ export function createClient(origin = "/") {
} }
return { return {
VERSION,
POOL_ID_TO_POOL_NAME, POOL_ID_TO_POOL_NAME,
INDEXES,
hasMetric, hasMetric,
metricToIndexes, getIndexesFromMetric,
genMetricURL, genMetricURL,
fetchMetric, fetchMetric,
@@ -3,6 +3,11 @@ import {
COMPRESSED_METRIC_TO_INDEXES, COMPRESSED_METRIC_TO_INDEXES,
} from "./generated/metrics"; } from "./generated/metrics";
/**
* @typedef {typeof import("./generated/metrics")["COMPRESSED_METRIC_TO_INDEXES"]} MetricToIndexes
* @typedef {string} Metric
*/
/** @type {Record<string, number>} */ /** @type {Record<string, number>} */
const WORD_TO_INDEX = {}; const WORD_TO_INDEX = {};
@@ -13,7 +18,7 @@ INDEX_TO_WORD.forEach((word, index) => {
/** /**
* @param {Metric} metric * @param {Metric} metric
*/ */
export function metricToIndexes(metric) { export function getIndexesFromMetric(metric) {
return COMPRESSED_METRIC_TO_INDEXES[compressMetric(metric)]; return COMPRESSED_METRIC_TO_INDEXES[compressMetric(metric)];
} }
@@ -1,17 +1,20 @@
/** /**
* @import { Signal, Signals } from "../brk-signals/index"; * @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<createResources>} BRKResources * @typedef {ReturnType<typeof createResources>} Resources
* @typedef {ReturnType<BRKResources["metrics"]["getOrCreate"]>} BRKMetricResource * @typedef {ReturnType<Resources["metrics"]["getOrCreate"]>} MetricResource
*/ */
/** /**
* @param {BRKClient} client * @param {BRK} brk
* @param {Signals} signals * @param {Signals} signals
*/ */
export function createResources(client, signals) { export function createResources(brk, signals) {
const owner = signals.getOwner(); const owner = signals.getOwner();
const defaultFrom = -10_000; const defaultFrom = -10_000;
@@ -29,10 +32,10 @@ export function createResources(client, signals) {
/** /**
* @template T * @template T
* @param {Metric} metric * @param {Metric} metric
* @param {Index} index * @param {IndexName} index
*/ */
function createMetricResource(metric, index) { function createMetricResource(metric, index) {
if (client.hasMetric(metric)) { if (!brk.hasMetric(metric)) {
throw Error(`${metric} is invalid`); throw Error(`${metric} is invalid`);
} }
@@ -44,7 +47,7 @@ export function createResources(client, signals) {
); );
return { return {
url: client.genMetricURL(metric, index, defaultFrom), url: brk.genMetricURL(metric, index, defaultFrom),
fetched: fetchedRecord, fetched: fetchedRecord,
/** /**
* Defaults * Defaults
@@ -81,7 +84,7 @@ export function createResources(client, signals) {
} }
fetched.loading = true; fetched.loading = true;
const res = /** @type {T[] | null} */ ( const res = /** @type {T[] | null} */ (
await client.fetchMetric( await brk.fetchMetric(
(data) => { (data) => {
if (data.length || !fetched.data()) { if (data.length || !fetched.data()) {
fetched.data.set(data); fetched.data.set(data);
@@ -108,7 +111,7 @@ export function createResources(client, signals) {
/** /**
* @template T * @template T
* @param {Metric} metric * @param {Metric} metric
* @param {Index} index * @param {IndexName} index
*/ */
getOrCreate(metric, index) { getOrCreate(metric, index) {
const key = `${metric}/${index}`; const key = `${metric}/${index}`;
+10
View File
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"checkJs": true,
"strict": true,
"target": "ESNext",
"module": "ESNext",
"skipLibCheck": true
},
"exclude": ["dist"]
}
+13
View File
@@ -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"]
}
+10
View File
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"checkJs": true,
"strict": true,
"target": "ESNext",
"module": "ESNext",
"skipLibCheck": true
},
"exclude": ["dist"]
}
+13
View File
@@ -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"]
}
-8
View File
@@ -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
*/
-3
View File
@@ -1,3 +0,0 @@
/**
* @import { BRKResources, BRKMetricResource } from "./index"
*/
+1 -1
View File
@@ -1 +1 @@
**/scripts/packages **/scripts/modules
+3 -3
View File
@@ -991,7 +991,7 @@
} }
.chart > legend, .chart > legend,
#charts > fieldset { #chart > fieldset {
text-transform: lowercase; text-transform: lowercase;
flex-shrink: 0; flex-shrink: 0;
display: flex; display: flex;
@@ -1161,7 +1161,7 @@
} }
} }
#charts { #chart {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
@@ -1824,7 +1824,7 @@
</main> </main>
<aside id="aside"> <aside id="aside">
<div id="explorer" hidden></div> <div id="explorer" hidden></div>
<div id="charts" hidden></div> <div id="chart" hidden></div>
<div id="table" hidden></div> <div id="table" hidden></div>
<div id="simulation" hidden></div> <div id="simulation" hidden></div>
</aside> </aside>
+1 -1
View File
@@ -6,5 +6,5 @@
"module": "ESNext", "module": "ESNext",
"skipLibCheck": true "skipLibCheck": true
}, },
"exclude": ["assets", "scripts/packages", "scripts/bridge"] "exclude": ["assets", "scripts/modules"]
} }
+38 -50
View File
@@ -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 { import {
createChart, createChart,
@@ -6,8 +6,8 @@ import {
HistogramSeries, HistogramSeries,
LineSeries, LineSeries,
BaselineSeries, BaselineSeries,
// } from "../packages/lightweight-charts/5.0.8/dist/lightweight-charts.standalone.development.mjs"; // } from "../modules/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.production.mjs";
import { import {
createHorizontalChoiceField, createHorizontalChoiceField,
@@ -17,6 +17,8 @@ import {
import { createOklchToRGBA } from "./oklch"; import { createOklchToRGBA } from "./oklch";
import { throttle } from "../timing"; import { throttle } from "../timing";
import { serdeBool } from "../serde"; import { serdeBool } from "../serde";
import { stringToId } from "../format";
import { style } from "../elements";
/** /**
* @typedef {Object} Valued * @typedef {Object} Valued
@@ -50,7 +52,7 @@ import { serdeBool } from "../serde";
* @typedef {_BaselineData<number>} BaselineData * @typedef {_BaselineData<number>} BaselineData
* @typedef {_HistogramData<number>} HistogramData * @typedef {_HistogramData<number>} HistogramData
* *
* @typedef {function({ iseries: ISeries; unit: Unit; index: Index }): void} SetDataCallback * @typedef {function({ iseries: ISeries; unit: Unit; index: IndexName }): void} SetDataCallback
*/ */
const oklchToRGBA = createOklchToRGBA(); const oklchToRGBA = createOklchToRGBA();
@@ -63,10 +65,8 @@ const lineWidth = /** @type {any} */ (1.5);
* @param {HTMLElement} args.parent * @param {HTMLElement} args.parent
* @param {Signals} args.signals * @param {Signals} args.signals
* @param {Colors} args.colors * @param {Colors} args.colors
* @param {Utilities} args.utils * @param {Resources} args.resources
* @param {Elements} args.elements * @param {Accessor<IndexName>} args.index
* @param {VecsResources} args.vecsResources
* @param {Accessor<Index>} args.index
* @param {((unknownTimeScaleCallback: VoidFunction) => void)} [args.timeScaleSetCallback] * @param {((unknownTimeScaleCallback: VoidFunction) => void)} [args.timeScaleSetCallback]
* @param {true} [args.fitContent] * @param {true} [args.fitContent]
* @param {{unit: Unit; blueprints: AnySeriesBlueprint[]}[]} [args.config] * @param {{unit: Unit; blueprints: AnySeriesBlueprint[]}[]} [args.config]
@@ -75,11 +75,9 @@ function createChartElement({
parent, parent,
signals, signals,
colors, colors,
utils,
elements,
id: chartId, id: chartId,
index, index,
vecsResources, resources,
timeScaleSetCallback, timeScaleSetCallback,
fitContent, fitContent,
config, config,
@@ -88,20 +86,14 @@ function createChartElement({
div.classList.add("chart"); div.classList.add("chart");
parent.append(div); parent.append(div);
const legendTop = createLegend({ const legendTop = createLegend(signals);
utils,
signals,
});
div.append(legendTop.element); div.append(legendTop.element);
const chartDiv = window.document.createElement("div"); const chartDiv = window.document.createElement("div");
chartDiv.classList.add("lightweight-chart"); chartDiv.classList.add("lightweight-chart");
div.append(chartDiv); div.append(chartDiv);
const legendBottom = createLegend({ const legendBottom = createLegend(signals);
utils,
signals,
});
div.append(legendBottom.element); div.append(legendBottom.element);
/** @type {IChartApi} */ /** @type {IChartApi} */
@@ -110,7 +102,7 @@ function createChartElement({
/** @satisfies {DeepPartial<ChartOptions>} */ ({ /** @satisfies {DeepPartial<ChartOptions>} */ ({
autoSize: true, autoSize: true,
layout: { layout: {
fontFamily: elements.style.fontFamily, fontFamily: style.fontFamily,
background: { color: "transparent" }, background: { color: "transparent" },
attributionLogo: false, attributionLogo: false,
colorSpace: "display-p3", colorSpace: "display-p3",
@@ -185,24 +177,24 @@ function createChartElement({
signals.createEffect(index, (index) => { signals.createEffect(index, (index) => {
const minBarSpacing = const minBarSpacing =
index === /** @satisfies {MonthIndex} */ (7) index === "monthindex"
? 1 ? 1
: index === /** @satisfies {QuarterIndex} */ (19) : index === "quarterindex"
? 2 ? 2
: index === /** @satisfies {SemesterIndex} */ (20) : index === "semesterindex"
? 3 ? 3
: index === /** @satisfies {YearIndex} */ (24) : index === "yearindex"
? 6 ? 6
: index === /** @satisfies {DecadeIndex} */ (1) : index === "decadeindex"
? 60 ? 60
: 0.5; : 0.5;
ichart.applyOptions({ ichart.applyOptions({
timeScale: { timeScale: {
timeVisible: timeVisible:
index === /** @satisfies {Height} */ (5) || index === "height" ||
index === /** @satisfies {DifficultyEpoch} */ (2) || index === "difficultyepoch" ||
index === /** @satisfies {HalvingEpoch} */ (4), index === "halvingepoch",
...(!fitContent ...(!fitContent
? { ? {
minBarSpacing, minBarSpacing,
@@ -212,7 +204,7 @@ function createChartElement({
}); });
}); });
const activeResources = /** @type {Set<VecResource>} */ (new Set()); const activeResources = /** @type {Set<MetricResource>} */ (new Set());
ichart.subscribeCrosshairMove( ichart.subscribeCrosshairMove(
throttle(() => { throttle(() => {
activeResources.forEach((v) => { activeResources.forEach((v) => {
@@ -292,7 +284,7 @@ function createChartElement({
createChild(pane) { createChild(pane) {
const { field, selected } = createHorizontalChoiceField({ const { field, selected } = createHorizontalChoiceField({
choices: /** @type {const} */ (["lin", "log"]), choices: /** @type {const} */ (["lin", "log"]),
id: utils.stringToId(`${id} ${paneIndex} ${unit}`), id: stringToId(`${id} ${paneIndex} ${unit}`),
defaultValue: defaultValue:
unit === "usd" && seriesType !== "Baseline" ? "log" : "lin", unit === "usd" && seriesType !== "Baseline" ? "log" : "lin",
key: `${id}-price-scale-${paneIndex}`, key: `${id}-price-scale-${paneIndex}`,
@@ -340,7 +332,7 @@ function createChartElement({
data, data,
}) { }) {
return signals.createRoot((dispose) => { return signals.createRoot((dispose) => {
const id = `${utils.stringToId(name)}-${paneIndex}`; const id = `${stringToId(name)}-${paneIndex}`;
const active = signals.createSignal(defaultActive ?? true, { const active = signals.createSignal(defaultActive ?? true, {
save: { save: {
@@ -361,7 +353,7 @@ function createChartElement({
iseries.setSeriesOrder(order); iseries.setSeriesOrder(order);
/** @type {VecResource | undefined} */ /** @type {MetricResource | undefined} */
let _valuesResource; let _valuesResource;
/** @type {Series} */ /** @type {Series} */
@@ -383,15 +375,13 @@ function createChartElement({
if (metric) { if (metric) {
signals.createEffect(index, (index) => { signals.createEffect(index, (index) => {
const timeResource = vecsResources.getOrCreate( const timeResource = resources.metrics.getOrCreate(
index === "height" ? "timestamp_fixed" : "timestamp",
index, index,
index === /** @satisfies {Height} */ (5)
? "timestamp_fixed"
: "timestamp",
); );
timeResource.fetch(); timeResource.fetch();
const valuesResource = vecsResources.getOrCreate(index, metric); const valuesResource = resources.metrics.getOrCreate(metric, index);
_valuesResource = valuesResource; _valuesResource = valuesResource;
series.url.set(() => valuesResource.url); series.url.set(() => valuesResource.url);
@@ -401,11 +391,11 @@ function createChartElement({
valuesResource.fetch(); valuesResource.fetch();
activeResources.add(valuesResource); activeResources.add(valuesResource);
const fetchedKey = vecsResources.defaultFetchedKey; const fetchedKey = resources.metrics.genKey();
signals.createEffect( signals.createEffect(
() => ({ () => ({
_indexes: timeResource.fetched().get(fetchedKey)?.vec(), _indexes: timeResource.fetched().get(fetchedKey)?.data(),
values: valuesResource.fetched().get(fetchedKey)?.vec(), values: valuesResource.fetched().get(fetchedKey)?.data(),
}), }),
({ _indexes, values }) => { ({ _indexes, values }) => {
if (!_indexes?.length || !values?.length) return; if (!_indexes?.length || !values?.length) return;
@@ -477,10 +467,10 @@ function createChartElement({
timeScaleSetCallback?.(() => { timeScaleSetCallback?.(() => {
if ( if (
index === /** @satisfies {QuarterIndex} */ (19) || index === "quarterindex" ||
index === /** @satisfies {SemesterIndex} */ (20) || index === "semesterindex" ||
index === /** @satisfies {YearIndex} */ (24) || index === "yearindex" ||
index === /** @satisfies {DecadeIndex} */ (1) index === "decadeindex"
) { ) {
ichart.timeScale().setVisibleLogicalRange({ ichart.timeScale().setVisibleLogicalRange({
from: -1, from: -1,
@@ -836,11 +826,9 @@ function createChartElement({
} }
/** /**
* @param {Object} args * @param {Signals} signals
* @param {Signals} args.signals
* @param {Utilities} args.utils
*/ */
function createLegend({ signals, utils }) { function createLegend(signals) {
const element = window.document.createElement("legend"); const element = window.document.createElement("legend");
const hovered = signals.createSignal(/** @type {Series | null} */ (null)); const hovered = signals.createSignal(/** @type {Series | null} */ (null));
@@ -874,8 +862,8 @@ function createLegend({ signals, utils }) {
legends[order] = div; legends[order] = div;
const { input, label } = createLabeledInput({ const { input, label } = createLabeledInput({
inputId: utils.stringToId(`legend-${series.id}`), inputId: stringToId(`legend-${series.id}`),
inputName: utils.stringToId(`selected-${series.id}`), inputName: stringToId(`selected-${series.id}`),
inputValue: "value", inputValue: "value",
title: "Click to toggle", title: "Click to toggle",
inputChecked: series.active(), inputChecked: series.active(),
+22 -19
View File
@@ -1,21 +1,24 @@
import { getElementById } from "./dom"; import { getElementById } from "./dom";
export default { export const style = getComputedStyle(window.document.documentElement);
head: window.document.getElementsByTagName("head")[0],
body: window.document.body, export const headElement = window.document.getElementsByTagName("head")[0];
main: getElementById("main"), export const bodyElement = window.document.body;
aside: getElementById("aside"),
asideLabel: getElementById("aside-selector-label"), export const mainElement = getElementById("main");
navLabel: getElementById(`nav-selector-label`), export const asideElement = getElementById("aside");
searchLabel: getElementById(`search-selector-label`), export const searchElement = getElementById("search");
search: getElementById("search"), export const navElement = getElementById("nav");
nav: getElementById("nav"), export const chartElement = getElementById("chart");
searchInput: /** @type {HTMLInputElement} */ (getElementById("search-input")), export const tableElement = getElementById("table");
searchResults: getElementById("search-results"), export const explorerElement = getElementById("explorer");
selectors: getElementById("frame-selectors"), export const simulationElement = getElementById("simulation");
style: getComputedStyle(window.document.documentElement),
charts: getElementById("charts"), export const asideLabelElement = getElementById("aside-selector-label");
table: getElementById("table"), export const navLabelElement = getElementById(`nav-selector-label`);
explorer: getElementById("explorer"), export const searchLabelElement = getElementById(`search-selector-label`);
simulation: getElementById("simulation"), export const searchInput = /** @type {HTMLInputElement} */ (
}; getElementById("search-input")
);
export const searchResultsElement = getElementById("search-results");
export const frameSelectorsElement = getElementById("frame-selectors");
+11 -23
View File
@@ -1,24 +1,12 @@
const localhost = window.location.hostname === "localhost"; export const localhost = window.location.hostname === "localhost";
const standalone = export const standalone =
"standalone" in window.navigator && !!window.navigator.standalone; "standalone" in window.navigator && !!window.navigator.standalone;
const userAgent = navigator.userAgent.toLowerCase(); export const userAgent = navigator.userAgent.toLowerCase();
const isChrome = userAgent.includes("chrome"); export const isChrome = userAgent.includes("chrome");
const safari = userAgent.includes("safari"); export const safari = userAgent.includes("safari");
const safariOnly = safari && !isChrome; export const safariOnly = safari && !isChrome;
const macOS = userAgent.includes("mac os"); export const macOS = userAgent.includes("mac os");
const iphone = userAgent.includes("iphone"); export const iphone = userAgent.includes("iphone");
const ipad = userAgent.includes("ipad"); export const ipad = userAgent.includes("ipad");
const ios = iphone || ipad; export const ios = iphone || ipad;
export const canShare = "canShare" in navigator;
export default {
standalone,
userAgent,
isChrome,
safari,
safariOnly,
macOS,
iphone,
ipad,
ios,
localhost,
};
@@ -3,7 +3,7 @@
* @param {number} [digits] * @param {number} [digits]
* @param {Intl.NumberFormatOptions} [options] * @param {Intl.NumberFormatOptions} [options]
*/ */
function numberToUSFormat(value, digits, options) { export function numberToUSNumber(value, digits, options) {
return value.toLocaleString("en-us", { return value.toLocaleString("en-us", {
...options, ...options,
minimumFractionDigits: digits, minimumFractionDigits: digits,
@@ -11,23 +11,18 @@ function numberToUSFormat(value, digits, options) {
}); });
} }
export const locale = { export const numberToDollars = new Intl.NumberFormat("en-US", {
numberToUSFormat, style: "currency",
}; currency: "USD",
minimumFractionDigits: 2,
maximumFractionDigits: 2,
});
export const formatters = { export const numberToPercentage = new Intl.NumberFormat("en-US", {
dollars: new Intl.NumberFormat("en-US", { style: "percent",
style: "currency", minimumFractionDigits: 2,
currency: "USD", maximumFractionDigits: 2,
minimumFractionDigits: 2, });
maximumFractionDigits: 2,
}),
percentage: new Intl.NumberFormat("en-US", {
style: "percent",
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}),
};
/** /**
* @param {string} s * @param {string} s
+8 -19
View File
@@ -7,26 +7,16 @@ import {
import { serdeUnit } from "../serde"; import { serdeUnit } from "../serde";
import { pushHistory, resetParams } from "../url"; import { pushHistory, resetParams } from "../url";
import { readStored, writeToStorage } from "../storage"; import { readStored, writeToStorage } from "../storage";
import { stringToId } from "../format";
/** /**
* @param {Object} args * @param {Object} args
* @param {Colors} args.colors * @param {Colors} args.colors
* @param {Signals} args.signals * @param {Signals} args.signals
* @param {Env} args.env * @param {BRK} args.brk
* @param {Utilities} args.utils
* @param {MetricToIndexes} args.metricToIndexes
* @param {Pools} args.pools
* @param {Signal<string | null>} args.qrcode * @param {Signal<string | null>} args.qrcode
*/ */
export function initOptions({ export function initOptions({ colors, signals, brk, qrcode }) {
colors,
signals,
env,
utils,
qrcode,
metricToIndexes,
pools,
}) {
const LS_SELECTED_KEY = `selected_path`; const LS_SELECTED_KEY = `selected_path`;
const urlPath_ = window.document.location.pathname const urlPath_ = window.document.location.pathname
@@ -42,10 +32,8 @@ export function initOptions({
const selected = signals.createSignal(/** @type {any} */ (undefined)); const selected = signals.createSignal(/** @type {any} */ (undefined));
const partialOptions = createPartialOptions({ const partialOptions = createPartialOptions({
env,
colors, colors,
metricToIndexes, brk,
pools,
}); });
/** @type {Option[]} */ /** @type {Option[]} */
@@ -58,7 +46,8 @@ export function initOptions({
*/ */
function arrayToRecord(arr = []) { function arrayToRecord(arr = []) {
return (arr || []).reduce((record, blueprint) => { 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`); throw Error(`${blueprint.metric} not recognized`);
} }
const unit = blueprint.unit ?? serdeUnit.deserialize(blueprint.metric); const unit = blueprint.unit ?? serdeUnit.deserialize(blueprint.metric);
@@ -186,7 +175,7 @@ export function initOptions({
/** @type {HTMLDivElement | HTMLDetailsElement | null} */ (null), /** @type {HTMLDivElement | HTMLDetailsElement | null} */ (null),
); );
const serName = utils.stringToId(anyPartial.name); const serName = stringToId(anyPartial.name);
const path = [...parentPath, serName]; const path = [...parentPath, serName];
const childOptionsCount = recursiveProcessPartialTree( const childOptionsCount = recursiveProcessPartialTree(
anyPartial.tree, anyPartial.tree,
@@ -250,7 +239,7 @@ export function initOptions({
const option = /** @type {Option} */ (anyPartial); const option = /** @type {Option} */ (anyPartial);
const name = option.name; const name = option.name;
const path = [...parentPath, utils.stringToId(option.name)]; const path = [...parentPath, stringToId(option.name)];
if ("kind" in anyPartial && anyPartial.kind === "explorer") { if ("kind" in anyPartial && anyPartial.kind === "explorer") {
Object.assign( Object.assign(
+194 -265
View File
@@ -1,5 +1,3 @@
// @ts_check
/** /**
* @typedef {Object} BaseSeriesBlueprint * @typedef {Object} BaseSeriesBlueprint
* @property {string} title * @property {string} title
@@ -119,70 +117,15 @@
* *
*/ */
import { localhost } from "../env";
/** /**
* @param {Object} args * @param {Object} args
* @param {Env} args.env
* @param {Colors} args.colors * @param {Colors} args.colors
* @param {MetricToIndexes} args.metricToIndexes * @param {BRK} args.brk
* @param {Pools} args.pools
* @returns {PartialOptionsTree} * @returns {PartialOptionsTree}
*/ */
export function createPartialOptions({ env, colors, metricToIndexes, pools }) { export function createPartialOptions({ colors, brk }) {
/**
* @template {string} S
* @typedef {Extract<Metric, `${S}${string}`>} StartsWith
*/
/**
* @template {string} S
* @typedef {Extract<Metric, `${string}${S}`>} 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<CumulativePrefix>} CumulativeMetric
* @typedef {ExcludeSubstring<WithoutPrefix<CumulativeMetric, CumulativePrefix>, _30DChageSubString>} CumulativeMetricBase
* @typedef {"_avg"} AverageSuffix
* @typedef {EndsWith<AverageSuffix>} MetricAverage
* @typedef {WithoutSuffix<MetricAverage, AverageSuffix>} MetricAverageBase
* @typedef {"_median"} MedianSuffix
* @typedef {EndsWith<MedianSuffix>} MetricMedian
* @typedef {WithoutSuffix<MetricMedian, MedianSuffix>} MetricMedianBase
* @typedef {"_pct90"} _pct90Suffix
* @typedef {EndsWith<_pct90Suffix>} MetricPct90
* @typedef {WithoutSuffix<MetricPct90, _pct90Suffix>} MetricPct90Base
* @typedef {"_pct75"} _pct75Suffix
* @typedef {EndsWith<_pct75Suffix>} MetricPct75
* @typedef {WithoutSuffix<MetricPct75, _pct75Suffix>} MetricPct75Base
* @typedef {"_pct25"} _pct25Suffix
* @typedef {EndsWith<_pct25Suffix>} MetricPct25
* @typedef {WithoutSuffix<MetricPct25, _pct25Suffix>} MetricPct25Base
* @typedef {"_pct10"} _pct10Suffix
* @typedef {EndsWith<_pct10Suffix>} MetricPct10
* @typedef {WithoutSuffix<MetricPct10, _pct10Suffix>} MetricPct10Base
* @typedef {"_max"} MaxSuffix
* @typedef {EndsWith<MaxSuffix>} MetricMax
* @typedef {WithoutSuffix<MetricMax, MaxSuffix>} MetricMaxBase
* @typedef {"_min"} MinSuffix
* @typedef {EndsWith<MinSuffix>} MetricMin
* @typedef {WithoutSuffix<MetricMin, MinSuffix>} MetricMinBase
*/
/** /**
* @param {string} id * @param {string} id
* @param {boolean} compoundAdjective * @param {boolean} compoundAdjective
@@ -681,7 +624,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) {
} }
/** /**
* @param {MetricAverageBase} metric * @param {Metric} metric
*/ */
function createAverageSeries(metric) { function createAverageSeries(metric) {
return /** @satisfies {AnyFetchedSeriesBlueprint} */ ({ return /** @satisfies {AnyFetchedSeriesBlueprint} */ ({
@@ -692,7 +635,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) {
/** /**
* @param {Object} args * @param {Object} args
* @param {CumulativeMetricBase} args.metric * @param {Metric} args.metric
* @param {Color} [args.sumColor] * @param {Color} [args.sumColor]
* @param {Color} [args.cumulativeColor] * @param {Color} [args.cumulativeColor]
* @param {string} [args.common] * @param {string} [args.common]
@@ -719,14 +662,14 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) {
/** /**
* @param {Object} args * @param {Object} args
* @param {CumulativeMetricBase} args.metric * @param {Metric} args.metric
* @param {string} [args.title] * @param {string} [args.title]
* @param {Color} [args.color] * @param {Color} [args.color]
*/ */
function createSumSeries({ metric, title = "", color }) { function createSumSeries({ metric, title = "", color }) {
const metric_sum = `${metric}_sum`; const metric_sum = `${metric}_sum`;
return /** @satisfies {AnyFetchedSeriesBlueprint} */ ({ return /** @satisfies {AnyFetchedSeriesBlueprint} */ ({
metric: metric_sum in metricToIndexes ? metric_sum : metric, metric: brk.hasMetric(metric_sum) ? metric_sum : metric,
title: `Sum ${title}`, title: `Sum ${title}`,
color: color ?? colors.red, color: color ?? colors.red,
}); });
@@ -734,7 +677,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) {
/** /**
* @param {Object} args * @param {Object} args
* @param {CumulativeMetricBase} args.metric * @param {Metric} args.metric
* @param {string} [args.title] * @param {string} [args.title]
* @param {Color} [args.color] * @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) { function createMinMaxPercentilesSeries(metric) {
return /** @satisfies {AnyFetchedSeriesBlueprint[]} */ ([ 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) { function createSumCumulativeMinMaxPercentilesSeries(metric) {
return [ 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) { function createAverageSumCumulativeMinMaxPercentilesSeries(metric) {
return [ return [
@@ -819,7 +762,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) {
/** /**
* @param {Object} args * @param {Object} args
* @param {Metric & MetricAverageBase & CumulativeMetricBase & MetricMinBase & MetricMaxBase & MetricPct90Base & MetricPct75Base & MetricMedianBase & MetricPct25Base & MetricPct10Base} args.metric * @param {Metric} args.metric
* @param {string} args.name * @param {string} args.name
*/ */
function createBaseAverageSumCumulativeMinMaxPercentilesSeries({ function createBaseAverageSumCumulativeMinMaxPercentilesSeries({
@@ -835,12 +778,6 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) {
]; ];
} }
/**
* @typedef {"_ratio_zscore"} RatioZScoreCapSuffix
* @typedef {EndsWith<RatioZScoreCapSuffix>} MetricRatioZScoreCap
* @typedef {WithoutSuffix<MetricRatioZScoreCap, RatioZScoreCapSuffix>} MetricRatioZScoreCapBase
*/
const percentiles = [ const percentiles = [
{ {
name: "pct1", name: "pct1",
@@ -906,7 +843,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) {
* @param {string} args.name * @param {string} args.name
* @param {string} args.legend * @param {string} args.legend
* @param {string} args.title * @param {string} args.title
* @param {MetricRatioZScoreCapBase} args.metric * @param {Metric} args.metric
* @param {Color} [args.color] * @param {Color} [args.color]
*/ */
function createPriceWithRatioOptions({ name, title, legend, metric, color }) { function createPriceWithRatioOptions({ name, title, legend, metric, color }) {
@@ -931,7 +868,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) {
name: legend, name: legend,
color, color,
}), }),
...(`${metric}_ratio_p1sd_usd` in metricToIndexes ...(brk.hasMetric(`${metric}_ratio_p1sd_usd`)
? percentiles.map(({ name, color }) => ? percentiles.map(({ name, color }) =>
createBaseSeries({ createBaseSeries({
metric: `${metric}_ratio_${name}_usd`, metric: `${metric}_ratio_${name}_usd`,
@@ -954,7 +891,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) {
baseValue: { price: 1 }, baseValue: { price: 1 },
}, },
}), }),
...(`${metric}_ratio_p1sd` in metricToIndexes ...(brk.hasMetric(`${metric}_ratio_p1sd`)
? percentiles.map(({ name, color }) => ? percentiles.map(({ name, color }) =>
createBaseSeries({ createBaseSeries({
metric: `${metric}_ratio_${name}`, 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 }) => ? ratioAverages.map(({ name, metric: metricAddon, color }) =>
createBaseSeries({ createBaseSeries({
metric: `${metric}_ratio_${metricAddon}`, 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", name: "ZScores",
@@ -1178,18 +1115,12 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) {
]; ];
} }
/**
* @typedef {"_supply_in_profit"} SupplyInProfitSuffix
* @typedef {EndsWith<SupplyInProfitSuffix>} MetricSupplyInProfit
* @typedef {WithoutSuffix<MetricSupplyInProfit, SupplyInProfitSuffix>} CohortId
*/
/** /**
* @typedef {Object} UTXOGroupObject * @typedef {Object} UTXOGroupObject
* @property {string} args.name * @property {string} args.name
* @property {string} args.title * @property {string} args.title
* @property {Color} args.color * @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 {string} id
* @param {T} id
*/ */
const fixId = (id) => function fixId(id) {
id !== "" return id !== "" ? `${id}_` : "";
? /** @type {Exclude<"" | `${T}_`, "_">} */ (`${id}_`) }
: /** @type {const} */ ("");
/** /**
* @param {UTXOGroupObject | UTXOGroupsObject} args * @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) .length > ("list" in args ? 1 : 0)
? !("list" in args) || ? !("list" in args) ||
list.filter( list.filter(({ id }) =>
({ id }) => `${fixId(id)}empty_addr_count` in metricToIndexes, brk.hasMetric(`${fixId(id)}empty_addr_count`),
).length <= 1 ).length <= 1
? [ ? [
{ {
@@ -1478,7 +1407,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) {
bottom: list.flatMap(({ name, color, id: _id }) => { bottom: list.flatMap(({ name, color, id: _id }) => {
const id = fixId(_id); const id = fixId(_id);
return [ return [
...(`${id}addr_count` in metricToIndexes ...(brk.hasMetric(`${id}addr_count`)
? /** @type {const} */ ([ ? /** @type {const} */ ([
createBaseSeries({ createBaseSeries({
metric: `${id}addr_count`, 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} */ ([ ? /** @type {const} */ ([
createBaseSeries({ createBaseSeries({
metric: `${id}empty_addr_count`, metric: `${id}empty_addr_count`,
@@ -1509,9 +1438,8 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) {
name: "loaded", name: "loaded",
title: `Loaded Address Count ${title}`, title: `Loaded Address Count ${title}`,
bottom: list bottom: list
.filter( .filter(({ id }) =>
({ id }) => brk.hasMetric(`${fixId(id)}addr_count`),
`${fixId(id)}addr_count` in metricToIndexes,
) )
.flatMap(({ name, color, id: _id }) => { .flatMap(({ name, color, id: _id }) => {
const id = fixId(_id); const id = fixId(_id);
@@ -1524,19 +1452,16 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) {
]; ];
}), }),
}, },
...(list.filter( ...(list.filter(({ id }) =>
({ id }) => brk.hasMetric(`${fixId(id)}empty_addr_count`),
`${fixId(id)}empty_addr_count` in metricToIndexes,
).length ).length
? [ ? [
{ {
name: "empty", name: "empty",
title: `Empty Address Count ${title}`, title: `Empty Address Count ${title}`,
bottom: list bottom: list
.filter( .filter(({ id }) =>
({ id }) => brk.hasMetric(`${fixId(id)}empty_addr_count`),
`${fixId(id)}empty_addr_count` in
metricToIndexes,
) )
.flatMap(({ name, color, id: _id }) => { .flatMap(({ name, color, id: _id }) => {
const id = fixId(_id); const id = fixId(_id);
@@ -1622,7 +1547,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) {
] ]
: []), : []),
...(!("list" in args) && ...(!("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} */ ({ /** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
type: "Baseline", type: "Baseline",
@@ -1658,8 +1583,9 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) {
color: colors.red, color: colors.red,
defaultActive: false, defaultActive: false,
}), }),
...(`${fixId(args.id)}realized_profit_to_loss_ratio` in ...(brk.hasMetric(
metricToIndexes `${fixId(args.id)}realized_profit_to_loss_ratio`,
)
? [ ? [
createBaseSeries({ createBaseSeries({
metric: `${fixId( metric: `${fixId(
@@ -1797,7 +1723,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) {
}, },
}, },
}), }),
...(asoprKey in metricToIndexes ...(brk.hasMetric(asoprKey)
? [ ? [
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({ /** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
type: "Baseline", type: "Baseline",
@@ -1825,7 +1751,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) {
}, },
}, },
}), }),
...(asoprKey in metricToIndexes ...(brk.hasMetric(asoprKey)
? [ ? [
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({ /** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
type: "Baseline", type: "Baseline",
@@ -1853,7 +1779,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) {
}, },
}, },
}), }),
...(asoprKey in metricToIndexes ...(brk.hasMetric(asoprKey)
? [ ? [
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({ /** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
type: "Baseline", type: "Baseline",
@@ -1940,8 +1866,9 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) {
name, name,
color, color,
}), }),
...(`${id}realized_profit_to_loss_ratio` in ...(brk.hasMetric(
metricToIndexes `${id}realized_profit_to_loss_ratio`,
)
? [ ? [
createBaseSeries({ createBaseSeries({
metric: `${id}realized_profit_to_loss_ratio`, metric: `${id}realized_profit_to_loss_ratio`,
@@ -2102,7 +2029,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) {
name, name,
metric: `${fixId(id)}adjusted_sopr`, metric: `${fixId(id)}adjusted_sopr`,
})) }))
.filter(({ metric }) => metric in metricToIndexes); .filter(({ metric }) => brk.hasMetric(metric));
return reducedList.length return reducedList.length
? [ ? [
@@ -2180,7 +2107,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) {
name: "normal", name: "normal",
color: colors.emerald, color: colors.emerald,
}), }),
...(adjKey in metricToIndexes ...(brk.hasMetric(adjKey)
? [ ? [
createBaseSeries({ createBaseSeries({
metric: adjKey, metric: adjKey,
@@ -2204,7 +2131,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) {
name: "normal", name: "normal",
color: colors.red, color: colors.red,
}), }),
...(adjKey in metricToIndexes ...(brk.hasMetric(adjKey)
? [ ? [
createBaseSeries({ createBaseSeries({
metric: adjKey, metric: adjKey,
@@ -2239,9 +2166,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) {
name, name,
metric: `${fixId(id)}adjusted_value_created`, metric: `${fixId(id)}adjusted_value_created`,
})) }))
.filter( .filter(({ metric }) => brk.hasMetric(metric));
({ metric }) => metric in metricToIndexes,
);
return reducedList.length return reducedList.length
? [ ? [
{ {
@@ -2282,9 +2207,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) {
name, name,
metric: `${fixId(id)}adjusted_value_destroyed`, metric: `${fixId(id)}adjusted_value_destroyed`,
})) }))
.filter( .filter(({ metric }) => brk.hasMetric(metric));
({ metric }) => metric in metricToIndexes,
);
return reducedList.length return reducedList.length
? [ ? [
{ {
@@ -2361,10 +2284,11 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) {
name: "Negative Loss", name: "Negative Loss",
color: colors.red, color: colors.red,
}), }),
...(`${fixId( ...(brk.hasMetric(
args.id, `${fixId(
)}unrealized_profit_rel_to_own_market_cap` in args.id,
metricToIndexes )}unrealized_profit_rel_to_own_market_cap`,
)
? [ ? [
createBaseSeries({ createBaseSeries({
metric: `${fixId( metric: `${fixId(
@@ -2397,10 +2321,11 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) {
}), }),
] ]
: []), : []),
...(`${fixId( ...(brk.hasMetric(
args.id, `${fixId(
)}unrealized_profit_rel_to_own_total_unrealized_pnl` in args.id,
metricToIndexes )}unrealized_profit_rel_to_own_total_unrealized_pnl`,
)
? [ ? [
createBaseSeries({ createBaseSeries({
metric: `${fixId( metric: `${fixId(
@@ -2505,8 +2430,9 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) {
title: useGroupName ? name : "Net", title: useGroupName ? name : "Net",
color: useGroupName ? color : undefined, color: useGroupName ? color : undefined,
}), }),
...(`${fixId(id)}net_unrealized_pnl_rel_to_own_market_cap` in ...(brk.hasMetric(
metricToIndexes `${fixId(id)}net_unrealized_pnl_rel_to_own_market_cap`,
)
? [ ? [
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({ /** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
type: "Baseline", type: "Baseline",
@@ -2521,10 +2447,11 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) {
}), }),
] ]
: []), : []),
...(`${fixId( ...(brk.hasMetric(
id, `${fixId(
)}net_unrealized_pnl_rel_to_own_total_unrealized_pnl` in id,
metricToIndexes )}net_unrealized_pnl_rel_to_own_total_unrealized_pnl`,
)
? [ ? [
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({ /** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
type: "Baseline", type: "Baseline",
@@ -2660,7 +2587,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) {
} }
return [ return [
...(env.localhost ...(localhost
? /** @type {const} */ ([ ? /** @type {const} */ ([
{ {
name: "Explorer", name: "Explorer",
@@ -2792,7 +2719,7 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) {
createPriceLine({ createPriceLine({
unit: "percentage", unit: "percentage",
}), }),
...(cagr in metricToIndexes ...(brk.hasMetric(cagr)
? [ ? [
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({ /** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
metric: cagr, metric: cagr,
@@ -3652,133 +3579,135 @@ export function createPartialOptions({ env, colors, metricToIndexes, pools }) {
}, },
{ {
name: "Pools", name: "Pools",
tree: Object.entries(pools).map(([_id, name]) => { tree: Object.entries(brk.POOL_ID_TO_POOL_NAME).map(
const id = /** @type {Pool} */ (_id); ([_id, name]) => {
return { const id = /** @type {PoolId} */ (_id);
name, return {
tree: [ name,
{ tree: [
name: "Dominance", {
title: `Mining Dominance of ${name}`, name: "Dominance",
bottom: [ title: `Mining Dominance of ${name}`,
createBaseSeries({ bottom: [
metric: `${id}_1d_dominance`, createBaseSeries({
name: "1d", metric: `${id}_1d_dominance`,
color: colors.rose, name: "1d",
defaultActive: false, 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,
}), }),
...createSumCumulativeSeries({ createBaseSeries({
metric: `${id}_${metricAddon}_btc`, metric: `${id}_1w_dominance`,
common: metricAddon, name: "1w",
sumColor, color: colors.red,
cumulativeColor, defaultActive: false,
}), }),
...createSumCumulativeSeries({ createBaseSeries({
metric: `${id}_${metricAddon}_usd`, metric: `${id}_1m_dominance`,
common: metricAddon, name: "1m",
sumColor, }),
cumulativeColor, 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",
name: "Days since block", title: `Blocks mined by ${name}`,
title: `Days since ${name} mined a block`, bottom: [
bottom: [ createBaseSeries({
createBaseSeries({ metric: `${id}_blocks_mined`,
metric: `${id}_days_since_block`, name: "Sum",
name: "Since block", }),
}), 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",
}),
],
},
],
};
},
),
}, },
], ],
}, },
+22 -24
View File
@@ -113,59 +113,57 @@ export const serdeBool = {
export const serdeChartableIndex = { export const serdeChartableIndex = {
/** /**
* @param {number} v * @param {IndexName} v
* @returns {SerializedChartableIndex | null} * @returns {ChartableIndexName | null}
*/ */
serialize(v) { serialize(v) {
switch (v) { switch (v) {
case /** @satisfies {DateIndex} */ (0): case "dateindex":
return "date"; return "date";
case /** @satisfies {DecadeIndex} */ (1): case "decadeindex":
return "decade"; return "decade";
case /** @satisfies {DifficultyEpoch} */ (2): case "difficultyepoch":
return "epoch"; return "epoch";
// case /** @satisfies {HalvingEpoch} */ (4): case "height":
// return "halving";
case /** @satisfies {Height} */ (5):
return "timestamp"; return "timestamp";
case /** @satisfies {MonthIndex} */ (7): case "monthindex":
return "month"; return "month";
case /** @satisfies {QuarterIndex} */ (19): case "quarterindex":
return "quarter"; return "quarter";
case /** @satisfies {SemesterIndex} */ (20): case "semesterindex":
return "semester"; return "semester";
case /** @satisfies {WeekIndex} */ (23): case "weekindex":
return "week"; return "week";
case /** @satisfies {YearIndex} */ (24): case "yearindex":
return "year"; return "year";
default: default:
return null; return null;
} }
}, },
/** /**
* @param {SerializedChartableIndex} v * @param {ChartableIndexName} v
* @returns {Index} * @returns {IndexName}
*/ */
deserialize(v) { deserialize(v) {
switch (v) { switch (v) {
case "timestamp": case "timestamp":
return /** @satisfies {Height} */ (5); return "height";
case "date": case "date":
return /** @satisfies {DateIndex} */ (0); return "dateindex";
case "week": case "week":
return /** @satisfies {WeekIndex} */ (23); return "weekindex";
case "epoch": case "epoch":
return /** @satisfies {DifficultyEpoch} */ (2); return "difficultyepoch";
case "month": case "month":
return /** @satisfies {MonthIndex} */ (7); return "monthindex";
case "quarter": case "quarter":
return /** @satisfies {QuarterIndex} */ (19); return "quarterindex";
case "semester": case "semester":
return /** @satisfies {SemesterIndex} */ (20); return "semesterindex";
case "year": case "year":
return /** @satisfies {YearIndex} */ (24); return "yearindex";
case "decade": case "decade":
return /** @satisfies {DecadeIndex} */ (1); return "decadeindex";
default: default:
throw Error("todo"); throw Error("todo");
} }
+18 -16
View File
@@ -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 { 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 { 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 { 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 { 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("./lazy")["default"]} Modules
* @typedef {typeof import("./core/utils")} Utilities * @typedef {[number, number, number, number]} OHLCTuple
* @typedef {typeof import("./core/env")["default"]} Env
* @typedef {typeof import("./core/elements")["default"]} Elements
*/ */
// DO NOT CHANGE, Exact format is expected in `brk_bundler` // DO NOT CHANGE, Exact format is expected in `brk_bundler`
+16 -16
View File
@@ -1,27 +1,27 @@
const imports = { const imports = {
async signals() { 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() { async chart() {
return window.document.fonts.ready.then(() => return window.document.fonts.ready.then(() =>
import("./core/chart/index.js").then((d) => d.default), 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() { async options() {
return import("./core/options/full.js").then((d) => d); return import("./core/options/full.js").then((d) => d);
}, },
File diff suppressed because it is too large Load Diff
@@ -2,9 +2,11 @@ import {
createShadow, createShadow,
createHorizontalChoiceField, createHorizontalChoiceField,
createHeader, createHeader,
} from "../core/dom"; } from "../../core/dom";
import { serdeChartableIndex, serdeOptNumber } from "../core/serde"; import { chartElement } from "../../core/elements";
import { throttle } from "../core/timing"; import { ios, canShare } from "../../core/env";
import { serdeChartableIndex, serdeOptNumber } from "../../core/serde";
import { throttle } from "../../core/timing";
const keyPrefix = "chart"; const keyPrefix = "chart";
const ONE_BTC_IN_SATS = 100_000_000; const ONE_BTC_IN_SATS = 100_000_000;
@@ -13,7 +15,7 @@ const LINE = "line";
const CANDLE = "candle"; 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 {CreateChartElement} args.createChartElement
* @param {Accessor<ChartOption>} args.option * @param {Accessor<ChartOption>} args.option
* @param {Signals} args.signals * @param {Signals} args.signals
* @param {Utilities} args.utils
* @param {WebSockets} args.webSockets * @param {WebSockets} args.webSockets
* @param {Elements} args.elements * @param {Resources} args.resources
* @param {Env} args.env * @param {BRK} args.brk
* @param {VecsResources} args.vecsResources
* @param {MetricToIndexes} args.metricToIndexes
* @param {Packages} args.packages
*/ */
export function init({ export function init({
colors, colors,
elements,
createChartElement, createChartElement,
option, option,
signals, signals,
utils,
env,
webSockets, webSockets,
vecsResources, resources,
metricToIndexes, brk,
packages,
}) { }) {
elements.charts.append(createShadow("left")); chartElement.append(createShadow("left"));
elements.charts.append(createShadow("right")); chartElement.append(createShadow("right"));
const { headerElement, headingElement } = createHeader(); const { headerElement, headingElement } = createHeader();
elements.charts.append(headerElement); chartElement.append(headerElement);
const { index, fieldset } = createIndexSelector({ const { index, fieldset } = createIndexSelector({
option, option,
metricToIndexes, brk,
signals, signals,
utils,
}); });
const TIMERANGE_LS_KEY = signals.createMemo( const TIMERANGE_LS_KEY = signals.createMemo(
@@ -80,13 +73,11 @@ export function init({
}); });
const chart = createChartElement({ const chart = createChartElement({
parent: elements.charts, parent: chartElement,
signals, signals,
colors, colors,
id: "charts", id: "charts",
utils, resources,
vecsResources,
elements,
index, index,
timeScaleSetCallback: (unknownTimeScaleCallback) => { 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 // 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( const chartBottomRightCanvas = Array.from(
chart.inner.chartElement().getElementsByTagName("tr"), chart.inner.chartElement().getElementsByTagName("tr"),
).at(-1)?.lastChild?.firstChild?.firstChild; ).at(-1)?.lastChild?.firstChild?.firstChild;
if (chartBottomRightCanvas) { if (chartBottomRightCanvas) {
const charts = elements.charts;
const domain = window.document.createElement("p"); const domain = window.document.createElement("p");
domain.innerText = `${window.location.host}`; domain.innerText = `${window.location.host}`;
domain.id = "domain"; domain.id = "domain";
@@ -121,20 +111,20 @@ export function init({
screenshotButton.title = "Screenshot"; screenshotButton.title = "Screenshot";
chartBottomRightCanvas.replaceWith(screenshotButton); chartBottomRightCanvas.replaceWith(screenshotButton);
screenshotButton.addEventListener("click", () => { screenshotButton.addEventListener("click", () => {
packages.modernScreenshot().then(async ({ screenshot }) => { import("./screenshot").then(async ({ screenshot }) => {
charts.dataset.screenshot = "true"; chartElement.dataset.screenshot = "true";
charts.append(domain); chartElement.append(domain);
seriesTypeField.hidden = true; seriesTypeField.hidden = true;
try { try {
await screenshot({ await screenshot({
element: charts, element: chartElement,
name: option().path.join("-"), name: option().path.join("-"),
title: option().title, title: option().title,
}); });
} catch {} } catch {}
charts.removeChild(domain); chartElement.removeChild(domain);
seriesTypeField.hidden = false; seriesTypeField.hidden = false;
charts.dataset.screenshot = "false"; chartElement.dataset.screenshot = "false";
}); });
}); });
} }
@@ -148,7 +138,7 @@ export function init({
}, 250), }, 250),
); );
elements.charts.append(fieldset); chartElement.append(fieldset);
const { field: seriesTypeField, selected: topSeriesType_ } = const { field: seriesTypeField, selected: topSeriesType_ } =
createHorizontalChoiceField({ createHorizontalChoiceField({
@@ -205,7 +195,7 @@ export function init({
* @param {Object} params * @param {Object} params
* @param {ISeries} params.iseries * @param {ISeries} params.iseries
* @param {Unit} params.unit * @param {Unit} params.unit
* @param {Index} params.index * @param {IndexName} params.index
*/ */
function printLatest({ iseries, unit, index }) { function printLatest({ iseries, unit, index }) {
const _latest = webSockets.kraken1dCandle.latest(); const _latest = webSockets.kraken1dCandle.latest();
@@ -234,9 +224,9 @@ export function init({
const date = new Date(latest.time * 1000); const date = new Date(latest.time * 1000);
switch (index) { switch (index) {
case /** @satisfies {Height} */ (5): case "height":
case /** @satisfies {DifficultyEpoch} */ (2): case "difficultyepoch":
case /** @satisfies {HalvingEpoch} */ (4): { case "halvingepoch": {
if ("close" in last) { if ("close" in last) {
last.low = Math.min(last.low, latest.close); last.low = Math.min(last.low, latest.close);
last.high = Math.max(last.high, latest.close); last.high = Math.max(last.high, latest.close);
@@ -244,24 +234,24 @@ export function init({
iseries.update(last); iseries.update(last);
break; break;
} }
case /** @satisfies {DateIndex} */ (0): { case "dateindex": {
iseries.update(last); iseries.update(last);
break; break;
} }
default: { default: {
if (index === /** @satisfies {WeekIndex} */ (23)) { if (index === "weekindex") {
date.setUTCDate(date.getUTCDate() - ((date.getUTCDay() + 6) % 7)); date.setUTCDate(date.getUTCDate() - ((date.getUTCDay() + 6) % 7));
} else if (index === /** @satisfies {MonthIndex} */ (7)) { } else if (index === "monthindex") {
date.setUTCDate(1); date.setUTCDate(1);
} else if (index === /** @satisfies {QuarterIndex} */ (19)) { } else if (index === "quarterindex") {
const month = date.getUTCMonth(); const month = date.getUTCMonth();
date.setUTCMonth(month - (month % 3), 1); date.setUTCMonth(month - (month % 3), 1);
} else if (index === /** @satisfies {SemesterIndex} */ (20)) { } else if (index === "semesterindex") {
const month = date.getUTCMonth(); const month = date.getUTCMonth();
date.setUTCMonth(month - (month % 6), 1); date.setUTCMonth(month - (month % 6), 1);
} else if (index === /** @satisfies {YearIndex} */ (24)) { } else if (index === "yearindex") {
date.setUTCMonth(0, 1); date.setUTCMonth(0, 1);
} else if (index === /** @satisfies {DecadeIndex} */ (1)) { } else if (index === "decadeindex") {
date.setUTCFullYear( date.setUTCFullYear(
Math.floor(date.getUTCFullYear() / 10) * 10, Math.floor(date.getUTCFullYear() / 10) * 10,
0, 0,
@@ -446,9 +436,7 @@ export function init({
blueprints[unit]?.forEach((blueprint, order) => { blueprints[unit]?.forEach((blueprint, order) => {
order += orderStart; order += orderStart;
const indexes = /** @type {readonly number[]} */ ( const indexes = brk.getIndexesFromMetric(blueprint.metric);
metricToIndexes[blueprint.metric]
);
if (indexes.includes(index)) { if (indexes.includes(index)) {
switch (blueprint.type) { switch (blueprint.type) {
@@ -519,12 +507,11 @@ export function init({
/** /**
* @param {Object} args * @param {Object} args
* @param {Accessor<ChartOption>} args.option * @param {Accessor<ChartOption>} args.option
* @param {MetricToIndexes} args.metricToIndexes * @param {BRK} args.brk
* @param {Signals} args.signals * @param {Signals} args.signals
* @param {Utilities} args.utils
*/ */
function createIndexSelector({ option, metricToIndexes, signals, utils }) { function createIndexSelector({ option, brk, signals }) {
const choices_ = /** @satisfies {SerializedChartableIndex[]} */ ([ const choices_ = /** @satisfies {ChartableIndexName[]} */ ([
"timestamp", "timestamp",
"date", "date",
"week", "week",
@@ -548,7 +535,7 @@ function createIndexSelector({ option, metricToIndexes, signals, utils }) {
[Object.values(o.top), Object.values(o.bottom)] [Object.values(o.top), Object.values(o.bottom)]
.flat(2) .flat(2)
.filter((blueprint) => !blueprint.metric.startsWith("constant_")) .filter((blueprint) => !blueprint.metric.startsWith("constant_"))
.map((blueprint) => metricToIndexes[blueprint.metric]) .map((blueprint) => brk.getIndexesFromMetric(blueprint.metric))
.flat(), .flat(),
); );
@@ -1,9 +1,5 @@
import { domToBlob } from "./4.6.6/dist/index.mjs"; import { ios } from "../../core/env";
import { domToBlob } from "../../modules/modern-screenshot/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;
/** /**
* @param {Object} args * @param {Object} args
+6 -9
View File
@@ -1,4 +1,5 @@
import { randomFromArray } from "../core/array"; import { randomFromArray } from "../core/array";
import { explorerElement } from "../core/elements";
/** /**
* @param {Object} args * @param {Object} args
@@ -6,26 +7,22 @@ import { randomFromArray } from "../core/array";
* @param {CreateChartElement} args.createChartElement * @param {CreateChartElement} args.createChartElement
* @param {Accessor<ChartOption>} args.option * @param {Accessor<ChartOption>} args.option
* @param {Signals} args.signals * @param {Signals} args.signals
* @param {Utilities} args.utils
* @param {WebSockets} args.webSockets * @param {WebSockets} args.webSockets
* @param {Elements} args.elements * @param {Resources} args.resources
* @param {VecsResources} args.vecsResources * @param {BRK} args.brk
* @param {MetricToIndexes} args.metricToIndexes
*/ */
export function init({ export function init({
colors, colors,
elements,
createChartElement, createChartElement,
option, option,
signals, signals,
utils,
webSockets, webSockets,
vecsResources, resources,
metricToIndexes, brk,
}) { }) {
const chain = window.document.createElement("div"); const chain = window.document.createElement("div");
chain.id = "chain"; chain.id = "chain";
elements.explorer.append(chain); explorerElement.append(chain);
// vecsResources.getOrCreate(/** @satisfies {Height}*/ (5), "height"); // vecsResources.getOrCreate(/** @satisfies {Height}*/ (5), "height");
// //
+20 -73
View File
@@ -9,6 +9,12 @@ import {
createHeader, createHeader,
createSelect, createSelect,
} from "../core/dom"; } from "../core/dom";
import { simulationElement } from "../core/elements";
import {
numberToDollars,
numberToPercentage,
numberToUSNumber,
} from "../core/format";
import { serdeDate, serdeOptDate, serdeOptNumber } from "../core/serde"; import { serdeDate, serdeOptDate, serdeOptNumber } from "../core/serde";
/** /**
@@ -16,18 +22,9 @@ import { serdeDate, serdeOptDate, serdeOptNumber } from "../core/serde";
* @param {Colors} args.colors * @param {Colors} args.colors
* @param {CreateChartElement} args.createChartElement * @param {CreateChartElement} args.createChartElement
* @param {Signals} args.signals * @param {Signals} args.signals
* @param {Utilities} args.utils * @param {Resources} args.resources
* @param {Elements} args.elements
* @param {VecsResources} args.vecsResources
*/ */
export function init({ export function init({ colors, createChartElement, signals, resources }) {
colors,
elements,
createChartElement,
signals,
utils,
vecsResources,
}) {
/** /**
* @typedef {Object} Frequency * @typedef {Object} Frequency
* @property {string} name * @property {string} name
@@ -39,8 +36,6 @@ export function init({
* @property {Frequency[]} list * @property {Frequency[]} list
*/ */
const simulationElement = elements.simulation;
const domExtended = { const domExtended = {
/** /**
* @param {Object} args * @param {Object} args
@@ -686,7 +681,8 @@ export function init({
}, },
); );
const index = () => /** @type {DateIndex} */ (0); /** @type {() => IndexName} */
const index = () => "dateindex";
createChartElement({ createChartElement({
index, index,
@@ -695,9 +691,7 @@ export function init({
colors, colors,
id: `result`, id: `result`,
fitContent: true, fitContent: true,
vecsResources, resources,
utils,
elements,
config: [ config: [
{ {
unit: "usd", unit: "usd",
@@ -740,9 +734,7 @@ export function init({
colors, colors,
id: `bitcoin`, id: `bitcoin`,
fitContent: true, fitContent: true,
vecsResources, resources,
elements,
utils,
config: [ config: [
{ {
unit: "btc", unit: "btc",
@@ -765,9 +757,7 @@ export function init({
colors, colors,
id: `average-price`, id: `average-price`,
fitContent: true, fitContent: true,
vecsResources, resources,
utils,
elements,
config: [ config: [
{ {
unit: "usd", unit: "usd",
@@ -794,11 +784,9 @@ export function init({
parent: resultsElement, parent: resultsElement,
signals, signals,
colors, colors,
vecsResources, resources,
id: `return-ratio`, id: `return-ratio`,
fitContent: true, fitContent: true,
utils,
elements,
config: [ config: [
{ {
unit: "usd", unit: "usd",
@@ -820,9 +808,7 @@ export function init({
colors, colors,
id: `simulation-profitability-ratios`, id: `simulation-profitability-ratios`,
fitContent: true, fitContent: true,
vecsResources, resources,
utils,
elements,
config: [ config: [
{ {
unit: "percentage", unit: "percentage",
@@ -844,8 +830,8 @@ export function init({
], ],
}); });
vecsResources resources.metrics
.getOrCreate(/** @satisfies {DateIndex} */ (0), "price_close") .getOrCreate("price_close", "dateindex")
.fetch() .fetch()
.then((_closes) => { .then((_closes) => {
if (!_closes) return; if (!_closes) return;
@@ -1055,11 +1041,11 @@ export function init({
}); });
}); });
const f = utils.locale.numberToUSFormat; const f = numberToUSNumber;
/** @param {number} v */ /** @param {number} v */
const fd = (v) => utils.formatters.dollars.format(v); const fd = (v) => numberToDollars.format(v);
/** @param {number} v */ /** @param {number} v */
const fp = (v) => utils.formatters.percentage.format(v); const fp = (v) => numberToPercentage.format(v);
/** /**
* @param {ColorName} c * @param {ColorName} c
* @param {string} t * @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.`; 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}.<br/>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); totalInvestedAmountData.set((a) => a);
bitcoinValueData.set((a) => a); bitcoinValueData.set((a) => a);
bitcoinData.set((a) => a); bitcoinData.set((a) => a);

Some files were not shown because too many files have changed in this diff Show More