kibo: database: part 1

This commit is contained in:
nym21
2025-04-21 23:17:37 +02:00
parent 68d2bf736f
commit 3439422057
16 changed files with 1279 additions and 337 deletions

32
Cargo.lock generated
View File

@@ -694,9 +694,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.36" version = "4.5.37"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2df961d8c8a0d08aa9945718ccf584145eee3f3aa06cddbeac12933781102e04" checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@@ -704,9 +704,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.36" version = "4.5.37"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "132dbda40fb6753878316a489d5a1242a8ef2f0d9e47ba01c951ea8aa7d013a5" checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@@ -1107,9 +1107,9 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
[[package]] [[package]]
name = "fjall" name = "fjall"
version = "2.8.0" version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26b2ced3483989a62b3533c9f99054d73b527c6c0045cf22b00fe87956f1a46f" checksum = "958511f67d1f80e6bff9ffac05c626bb340d4602ca6ea5617d9901c218c894f0"
dependencies = [ dependencies = [
"byteorder", "byteorder",
"byteview", "byteview",
@@ -1478,9 +1478,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]] [[package]]
name = "jiff" name = "jiff"
version = "0.2.8" version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5ad87c89110f55e4cd4dc2893a9790820206729eaf221555f742d540b0724a0" checksum = "59ec30f7142be6fe14e1b021f50b85db8df2d4324ea6e91ec3e5dcde092021d0"
dependencies = [ dependencies = [
"jiff-static", "jiff-static",
"jiff-tzdb-platform", "jiff-tzdb-platform",
@@ -1493,9 +1493,9 @@ dependencies = [
[[package]] [[package]]
name = "jiff-static" name = "jiff-static"
version = "0.2.8" version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d076d5b64a7e2fe6f0743f02c43ca4a6725c0f904203bfe276a5b3e793103605" checksum = "526b834d727fd59d37b076b0c3236d9adde1b1729a4361e20b2026f738cc1dbe"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -1585,9 +1585,9 @@ checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]] [[package]]
name = "lsm-tree" name = "lsm-tree"
version = "2.8.0" version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a63a5e98a38b51765274137d8aedfbd848da5f4d016867e186b673fcc06a8c" checksum = "87d58bdef2dcbf50fce9f343265bdbd7fb08a458d241eb837ce426be22d674b4"
dependencies = [ dependencies = [
"byteorder", "byteorder",
"crossbeam-skiplist", "crossbeam-skiplist",
@@ -2718,9 +2718,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]] [[package]]
name = "signal-hook-registry" name = "signal-hook-registry"
version = "1.4.2" version = "1.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" checksum = "a1ee1aca2bc74ef9589efa7ccaa0f3752751399940356209b3fd80c078149b5e"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@@ -3153,9 +3153,9 @@ checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]] [[package]]
name = "value-log" name = "value-log"
version = "1.8.0" version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd29b17c041f94e0885179637289815cd038f0c9fc19c4549d5a97017404fb7d" checksum = "62fc7c4ce161f049607ecea654dca3f2d727da5371ae85e2e4f14ce2b98ed67c"
dependencies = [ dependencies = [
"byteorder", "byteorder",
"byteview", "byteview",

View File

@@ -31,11 +31,11 @@ brk_query = { version = "0", path = "crates/brk_query" }
brk_server = { version = "0", path = "crates/brk_server" } brk_server = { version = "0", path = "crates/brk_server" }
brk_vec = { version = "0", path = "crates/brk_vec" } brk_vec = { version = "0", path = "crates/brk_vec" }
byteview = "0.6.1" byteview = "0.6.1"
clap = { version = "4.5.36", features = ["derive", "string"] } clap = { version = "4.5.37", features = ["derive", "string"] }
color-eyre = "0.6.3" color-eyre = "0.6.3"
derive_deref = "1.1.1" derive_deref = "1.1.1"
fjall = "2.8.0" fjall = "2.9.0"
jiff = "0.2.8" jiff = "0.2.9"
log = { version = "0.4.27" } log = { version = "0.4.27" }
minreq = { version = "2.13.4", features = ["https", "serde_json"] } minreq = { version = "2.13.4", features = ["https", "serde_json"] }
rayon = "1.10.0" rayon = "1.10.0"

View File

@@ -92,6 +92,7 @@ Heartfelt thanks go out to every donor on [Nostr](https://primal.net/p/npub1jagm
If you'd like to have your own instance hosted for you please contact [hosting@bitcoinresearchkit.org](mailto:hosting@bitcoinresearchkit.org). If you'd like to have your own instance hosted for you please contact [hosting@bitcoinresearchkit.org](mailto:hosting@bitcoinresearchkit.org).
- 2 separate dedicated servers (1 GB/s each) with different ISPs and Cloudflare integration for enhanced performance and optimal availability - 2 separate dedicated servers (1 GB/s each) with different ISPs and Cloudflare integration for enhanced performance and optimal availability
- 99.9% SLA
- Direct communication for feature requests and support - Direct communication for feature requests and support
- Updates delivered at your convenience - Updates delivered at your convenience
- Optional subdomains: `*.bitcoinresearchkit.org`, `*.kibo.money` and `*.satonomics.xyz` - Optional subdomains: `*.bitcoinresearchkit.org`, `*.kibo.money` and `*.satonomics.xyz`

View File

@@ -154,6 +154,30 @@ where
Ok(()) Ok(())
} }
pub fn compute_range<A, F>(
&mut self,
max_from: I,
other: &mut StoredVec<I, A>,
mut t: F,
exit: &Exit,
) -> Result<()>
where
A: StoredType,
F: FnMut(I) -> (I, T),
{
self.validate_computed_version_or_reset_file(
Version::ZERO + self.version() + other.version(),
)?;
let index = max_from.min(I::from(self.len()));
(index.to_usize()?..other.len()).try_for_each(|i| {
let (i, v) = t(I::from(i));
self.forced_push_at(i, v, exit)
})?;
self.safe_flush(exit)
}
pub fn compute_transform<A, B, F>( pub fn compute_transform<A, B, F>(
&mut self, &mut self,
max_from: A, max_from: A,

View File

@@ -120,10 +120,10 @@ impl Vecs {
starting_indexes, starting_indexes,
exit, exit,
|v, indexer, _, starting_indexes, exit| { |v, indexer, _, starting_indexes, exit| {
v.compute_transform( v.compute_range(
starting_indexes.height, starting_indexes.height,
indexer.mut_vecs().height_to_weight.mut_vec(), indexer.mut_vecs().height_to_weight.mut_vec(),
|(h, ..)| (h, StoredU32::from(1_u32)), |h| (h, StoredU32::from(1_u32)),
exit, exit,
) )
}, },

View File

@@ -1,8 +1,10 @@
use std::{fs, ops::Deref, path::Path}; use std::{fs, ops::Deref, path::Path};
use brk_core::{ use brk_core::{
Date, Dateindex, Decadeindex, Difficultyepoch, Halvingepoch, Height, Monthindex, Quarterindex, Addressindex, Date, Dateindex, Decadeindex, Difficultyepoch, Emptyindex, Halvingepoch, Height,
Timestamp, Txindex, Txinindex, Txoutindex, Weekindex, Yearindex, Monthindex, Multisigindex, Opreturnindex, P2PK33index, P2PK65index, P2PKHindex, P2SHindex,
P2TRindex, P2WPKHindex, P2WSHindex, Pushonlyindex, Quarterindex, Timestamp, Txindex, Txinindex,
Txoutindex, Unknownindex, Weekindex, Yearindex,
}; };
use brk_exit::Exit; use brk_exit::Exit;
use brk_indexer::Indexer; use brk_indexer::Indexer;
@@ -12,6 +14,7 @@ use super::ComputedVec;
#[derive(Clone)] #[derive(Clone)]
pub struct Vecs { pub struct Vecs {
pub addressindex_to_addressindex: ComputedVec<Addressindex, Addressindex>,
pub dateindex_to_date: ComputedVec<Dateindex, Date>, pub dateindex_to_date: ComputedVec<Dateindex, Date>,
pub dateindex_to_dateindex: ComputedVec<Dateindex, Dateindex>, pub dateindex_to_dateindex: ComputedVec<Dateindex, Dateindex>,
pub dateindex_to_first_height: ComputedVec<Dateindex, Height>, pub dateindex_to_first_height: ComputedVec<Dateindex, Height>,
@@ -27,6 +30,7 @@ pub struct Vecs {
pub difficultyepoch_to_first_height: ComputedVec<Difficultyepoch, Height>, pub difficultyepoch_to_first_height: ComputedVec<Difficultyepoch, Height>,
pub difficultyepoch_to_last_height: ComputedVec<Difficultyepoch, Height>, pub difficultyepoch_to_last_height: ComputedVec<Difficultyepoch, Height>,
pub difficultyepoch_to_timestamp: ComputedVec<Difficultyepoch, Timestamp>, pub difficultyepoch_to_timestamp: ComputedVec<Difficultyepoch, Timestamp>,
pub emptyindex_to_emptyindex: ComputedVec<Emptyindex, Emptyindex>,
pub halvingepoch_to_first_height: ComputedVec<Halvingepoch, Height>, pub halvingepoch_to_first_height: ComputedVec<Halvingepoch, Height>,
pub halvingepoch_to_halvingepoch: ComputedVec<Halvingepoch, Halvingepoch>, pub halvingepoch_to_halvingepoch: ComputedVec<Halvingepoch, Halvingepoch>,
pub halvingepoch_to_last_height: ComputedVec<Halvingepoch, Height>, pub halvingepoch_to_last_height: ComputedVec<Halvingepoch, Height>,
@@ -45,12 +49,26 @@ pub struct Vecs {
pub monthindex_to_quarterindex: ComputedVec<Monthindex, Quarterindex>, pub monthindex_to_quarterindex: ComputedVec<Monthindex, Quarterindex>,
pub monthindex_to_timestamp: ComputedVec<Monthindex, Timestamp>, pub monthindex_to_timestamp: ComputedVec<Monthindex, Timestamp>,
pub monthindex_to_yearindex: ComputedVec<Monthindex, Yearindex>, pub monthindex_to_yearindex: ComputedVec<Monthindex, Yearindex>,
pub multisigindex_to_multisigindex: ComputedVec<Multisigindex, Multisigindex>,
pub opreturnindex_to_opreturnindex: ComputedVec<Opreturnindex, Opreturnindex>,
pub p2pk33index_to_p2pk33index: ComputedVec<P2PK33index, P2PK33index>,
pub p2pk65index_to_p2pk65index: ComputedVec<P2PK65index, P2PK65index>,
pub p2pkhindex_to_p2pkhindex: ComputedVec<P2PKHindex, P2PKHindex>,
pub p2shindex_to_p2shindex: ComputedVec<P2SHindex, P2SHindex>,
pub p2trindex_to_p2trindex: ComputedVec<P2TRindex, P2TRindex>,
pub p2wpkhindex_to_p2wpkhindex: ComputedVec<P2WPKHindex, P2WPKHindex>,
pub p2wshindex_to_p2wshindex: ComputedVec<P2WSHindex, P2WSHindex>,
pub pushonlyindex_to_pushonlyindex: ComputedVec<Pushonlyindex, Pushonlyindex>,
pub quarterindex_to_first_monthindex: ComputedVec<Quarterindex, Monthindex>, pub quarterindex_to_first_monthindex: ComputedVec<Quarterindex, Monthindex>,
pub quarterindex_to_last_monthindex: ComputedVec<Quarterindex, Monthindex>, pub quarterindex_to_last_monthindex: ComputedVec<Quarterindex, Monthindex>,
pub quarterindex_to_quarterindex: ComputedVec<Quarterindex, Quarterindex>, pub quarterindex_to_quarterindex: ComputedVec<Quarterindex, Quarterindex>,
pub quarterindex_to_timestamp: ComputedVec<Quarterindex, Timestamp>, pub quarterindex_to_timestamp: ComputedVec<Quarterindex, Timestamp>,
pub txindex_to_last_txinindex: ComputedVec<Txindex, Txinindex>, pub txindex_to_last_txinindex: ComputedVec<Txindex, Txinindex>,
pub txindex_to_last_txoutindex: ComputedVec<Txindex, Txoutindex>, pub txindex_to_last_txoutindex: ComputedVec<Txindex, Txoutindex>,
pub txindex_to_txindex: ComputedVec<Txindex, Txindex>,
pub txinindex_to_txinindex: ComputedVec<Txinindex, Txinindex>,
pub txoutindex_to_txoutindex: ComputedVec<Txoutindex, Txoutindex>,
pub unknownindex_to_unknownindex: ComputedVec<Unknownindex, Unknownindex>,
pub weekindex_to_first_dateindex: ComputedVec<Weekindex, Dateindex>, pub weekindex_to_first_dateindex: ComputedVec<Weekindex, Dateindex>,
pub weekindex_to_last_dateindex: ComputedVec<Weekindex, Dateindex>, pub weekindex_to_last_dateindex: ComputedVec<Weekindex, Dateindex>,
pub weekindex_to_timestamp: ComputedVec<Weekindex, Timestamp>, pub weekindex_to_timestamp: ComputedVec<Weekindex, Timestamp>,
@@ -307,6 +325,86 @@ impl Vecs {
Version::ZERO, Version::ZERO,
compressed, compressed,
)?, )?,
p2pk33index_to_p2pk33index: ComputedVec::forced_import(
&path.join("p2pk33index_to_p2pk33index"),
Version::ZERO,
compressed,
)?,
p2pk65index_to_p2pk65index: ComputedVec::forced_import(
&path.join("p2pk65index_to_p2pk65index"),
Version::ZERO,
compressed,
)?,
p2pkhindex_to_p2pkhindex: ComputedVec::forced_import(
&path.join("p2pkhindex_to_p2pkhindex"),
Version::ZERO,
compressed,
)?,
p2shindex_to_p2shindex: ComputedVec::forced_import(
&path.join("p2shindex_to_p2shindex"),
Version::ZERO,
compressed,
)?,
p2trindex_to_p2trindex: ComputedVec::forced_import(
&path.join("p2trindex_to_p2trindex"),
Version::ZERO,
compressed,
)?,
p2wpkhindex_to_p2wpkhindex: ComputedVec::forced_import(
&path.join("p2wpkhindex_to_p2wpkhindex"),
Version::ZERO,
compressed,
)?,
p2wshindex_to_p2wshindex: ComputedVec::forced_import(
&path.join("p2wshindex_to_p2wshindex"),
Version::ZERO,
compressed,
)?,
txindex_to_txindex: ComputedVec::forced_import(
&path.join("txindex_to_txindex"),
Version::ZERO,
compressed,
)?,
txinindex_to_txinindex: ComputedVec::forced_import(
&path.join("txinindex_to_txinindex"),
Version::ZERO,
compressed,
)?,
emptyindex_to_emptyindex: ComputedVec::forced_import(
&path.join("emptyindex_to_emptyindex"),
Version::ZERO,
compressed,
)?,
multisigindex_to_multisigindex: ComputedVec::forced_import(
&path.join("multisigindex_to_multisigindex"),
Version::ZERO,
compressed,
)?,
opreturnindex_to_opreturnindex: ComputedVec::forced_import(
&path.join("opreturnindex_to_opreturnindex"),
Version::ZERO,
compressed,
)?,
pushonlyindex_to_pushonlyindex: ComputedVec::forced_import(
&path.join("pushonlyindex_to_pushonlyindex"),
Version::ZERO,
compressed,
)?,
unknownindex_to_unknownindex: ComputedVec::forced_import(
&path.join("unknownindex_to_unknownindex"),
Version::ZERO,
compressed,
)?,
addressindex_to_addressindex: ComputedVec::forced_import(
&path.join("addressindex_to_addressindex"),
Version::ZERO,
compressed,
)?,
txoutindex_to_txoutindex: ComputedVec::forced_import(
&path.join("txoutindex_to_txoutindex"),
Version::ZERO,
compressed,
)?,
}) })
} }
@@ -323,10 +421,10 @@ impl Vecs {
let txinindexes_count = indexer_vecs.txinindex_to_txoutindex.len(); let txinindexes_count = indexer_vecs.txinindex_to_txoutindex.len();
let txoutindexes_count = indexer_vecs.txoutindex_to_addressindex.len(); let txoutindexes_count = indexer_vecs.txoutindex_to_addressindex.len();
self.height_to_height.compute_transform( self.height_to_height.compute_range(
starting_indexes.height, starting_indexes.height,
indexer_vecs.height_to_timestamp.mut_vec(), indexer_vecs.height_to_timestamp.mut_vec(),
|(h, ..)| (h, h), |h| (h, h),
exit, exit,
)?; )?;
@@ -340,12 +438,12 @@ impl Vecs {
self.height_to_fixed_timestamp.compute_transform( self.height_to_fixed_timestamp.compute_transform(
starting_indexes.height, starting_indexes.height,
indexer_vecs.height_to_timestamp.mut_vec(), indexer_vecs.height_to_timestamp.mut_vec(),
|(h, d, s, ..)| { |(h, timestamp, s, ..)| {
let d = h let timestamp = h
.decremented() .decremented()
.and_then(|h| s.unwrap_cached_get(h)) .and_then(|h| s.unwrap_cached_get(h))
.map_or(d, |prev_d| prev_d.max(d)); .map_or(timestamp, |prev_d| prev_d.max(timestamp));
(h, d) (h, timestamp)
}, },
exit, exit,
)?; )?;
@@ -397,17 +495,17 @@ impl Vecs {
exit, exit,
)?; )?;
self.dateindex_to_dateindex.compute_transform( self.dateindex_to_dateindex.compute_range(
starting_dateindex, starting_dateindex,
self.dateindex_to_first_height.mut_vec(), self.dateindex_to_first_height.mut_vec(),
|(di, ..)| (di, di), |di| (di, di),
exit, exit,
)?; )?;
self.dateindex_to_date.compute_transform( self.dateindex_to_date.compute_range(
starting_dateindex, starting_dateindex,
self.dateindex_to_dateindex.mut_vec(), self.dateindex_to_dateindex.mut_vec(),
|(di, ..)| (di, Date::from(di)), |di| (di, Date::from(di)),
exit, exit,
)?; )?;
@@ -448,10 +546,10 @@ impl Vecs {
.unwrap_cached_get(starting_dateindex) .unwrap_cached_get(starting_dateindex)
.unwrap_or_default(); .unwrap_or_default();
self.dateindex_to_weekindex.compute_transform( self.dateindex_to_weekindex.compute_range(
starting_dateindex, starting_dateindex,
self.dateindex_to_dateindex.mut_vec(), self.dateindex_to_dateindex.mut_vec(),
|(di, ..)| (di, Weekindex::from(di)), |di| (di, Weekindex::from(di)),
exit, exit,
)?; )?;
@@ -470,10 +568,10 @@ impl Vecs {
exit, exit,
)?; )?;
self.weekindex_to_weekindex.compute_transform( self.weekindex_to_weekindex.compute_range(
starting_weekindex, starting_weekindex,
self.weekindex_to_first_dateindex.mut_vec(), self.weekindex_to_first_dateindex.mut_vec(),
|(wi, ..)| (wi, wi), |wi| (wi, wi),
exit, exit,
)?; )?;
@@ -491,10 +589,10 @@ impl Vecs {
.unwrap_cached_get(starting_dateindex) .unwrap_cached_get(starting_dateindex)
.unwrap_or_default(); .unwrap_or_default();
self.dateindex_to_monthindex.compute_transform( self.dateindex_to_monthindex.compute_range(
starting_dateindex, starting_dateindex,
self.dateindex_to_dateindex.mut_vec(), self.dateindex_to_dateindex.mut_vec(),
|(di, ..)| (di, Monthindex::from(di)), |di| (di, Monthindex::from(di)),
exit, exit,
)?; )?;
@@ -515,10 +613,10 @@ impl Vecs {
exit, exit,
)?; )?;
self.monthindex_to_monthindex.compute_transform( self.monthindex_to_monthindex.compute_range(
starting_monthindex, starting_monthindex,
self.monthindex_to_first_dateindex.mut_vec(), self.monthindex_to_first_dateindex.mut_vec(),
|(mi, ..)| (mi, mi), |mi| (mi, mi),
exit, exit,
)?; )?;
@@ -536,10 +634,10 @@ impl Vecs {
.unwrap_cached_get(starting_monthindex) .unwrap_cached_get(starting_monthindex)
.unwrap_or_default(); .unwrap_or_default();
self.monthindex_to_quarterindex.compute_transform( self.monthindex_to_quarterindex.compute_range(
starting_monthindex, starting_monthindex,
self.monthindex_to_monthindex.mut_vec(), self.monthindex_to_monthindex.mut_vec(),
|(mi, ..)| (mi, Quarterindex::from(mi)), |mi| (mi, Quarterindex::from(mi)),
exit, exit,
)?; )?;
@@ -560,10 +658,10 @@ impl Vecs {
exit, exit,
)?; )?;
self.quarterindex_to_quarterindex.compute_transform( self.quarterindex_to_quarterindex.compute_range(
starting_quarterindex, starting_quarterindex,
self.quarterindex_to_first_monthindex.mut_vec(), self.quarterindex_to_first_monthindex.mut_vec(),
|(yi, ..)| (yi, yi), |i| (i, i),
exit, exit,
)?; )?;
@@ -581,10 +679,10 @@ impl Vecs {
.unwrap_cached_get(starting_monthindex) .unwrap_cached_get(starting_monthindex)
.unwrap_or_default(); .unwrap_or_default();
self.monthindex_to_yearindex.compute_transform( self.monthindex_to_yearindex.compute_range(
starting_monthindex, starting_monthindex,
self.monthindex_to_monthindex.mut_vec(), self.monthindex_to_monthindex.mut_vec(),
|(mi, ..)| (mi, Yearindex::from(mi)), |i| (i, Yearindex::from(i)),
exit, exit,
)?; )?;
@@ -605,10 +703,10 @@ impl Vecs {
exit, exit,
)?; )?;
self.yearindex_to_yearindex.compute_transform( self.yearindex_to_yearindex.compute_range(
starting_yearindex, starting_yearindex,
self.yearindex_to_first_monthindex.mut_vec(), self.yearindex_to_first_monthindex.mut_vec(),
|(yi, ..)| (yi, yi), |i| (i, i),
exit, exit,
)?; )?;
@@ -626,10 +724,10 @@ impl Vecs {
.unwrap_cached_get(starting_yearindex) .unwrap_cached_get(starting_yearindex)
.unwrap_or_default(); .unwrap_or_default();
self.yearindex_to_decadeindex.compute_transform( self.yearindex_to_decadeindex.compute_range(
starting_yearindex, starting_yearindex,
self.yearindex_to_yearindex.mut_vec(), self.yearindex_to_yearindex.mut_vec(),
|(yi, ..)| (yi, Decadeindex::from(yi)), |i| (i, Decadeindex::from(i)),
exit, exit,
)?; )?;
@@ -648,10 +746,10 @@ impl Vecs {
exit, exit,
)?; )?;
self.decadeindex_to_decadeindex.compute_transform( self.decadeindex_to_decadeindex.compute_range(
starting_decadeindex, starting_decadeindex,
self.decadeindex_to_first_yearindex.mut_vec(), self.decadeindex_to_first_yearindex.mut_vec(),
|(di, ..)| (di, di), |i| (i, i),
exit, exit,
)?; )?;
@@ -669,10 +767,10 @@ impl Vecs {
.unwrap_cached_get(decremented_starting_height) .unwrap_cached_get(decremented_starting_height)
.unwrap_or_default(); .unwrap_or_default();
self.height_to_difficultyepoch.compute_transform( self.height_to_difficultyepoch.compute_range(
starting_indexes.height, starting_indexes.height,
self.height_to_height.mut_vec(), self.height_to_height.mut_vec(),
|(h, ..)| (h, Difficultyepoch::from(h)), |h| (h, Difficultyepoch::from(h)),
exit, exit,
)?; )?;
@@ -691,10 +789,10 @@ impl Vecs {
exit, exit,
)?; )?;
self.difficultyepoch_to_difficultyepoch.compute_transform( self.difficultyepoch_to_difficultyepoch.compute_range(
starting_difficultyepoch, starting_difficultyepoch,
self.difficultyepoch_to_first_height.mut_vec(), self.difficultyepoch_to_first_height.mut_vec(),
|(de, ..)| (de, de), |i| (i, i),
exit, exit,
)?; )?;
@@ -717,10 +815,10 @@ impl Vecs {
.unwrap_cached_get(decremented_starting_height) .unwrap_cached_get(decremented_starting_height)
.unwrap_or_default(); .unwrap_or_default();
self.height_to_halvingepoch.compute_transform( self.height_to_halvingepoch.compute_range(
starting_indexes.height, starting_indexes.height,
self.height_to_height.mut_vec(), self.height_to_height.mut_vec(),
|(h, ..)| (h, Halvingepoch::from(h)), |h| (h, Halvingepoch::from(h)),
exit, exit,
)?; )?;
@@ -739,10 +837,10 @@ impl Vecs {
exit, exit,
)?; )?;
self.halvingepoch_to_halvingepoch.compute_transform( self.halvingepoch_to_halvingepoch.compute_range(
starting_halvingepoch, starting_halvingepoch,
self.halvingepoch_to_first_height.mut_vec(), self.halvingepoch_to_first_height.mut_vec(),
|(he, ..)| (he, he), |i| (i, i),
exit, exit,
)?; )?;
@@ -758,6 +856,105 @@ impl Vecs {
// exit, // exit,
// )?; // )?;
// ---
self.addressindex_to_addressindex.compute_range(
starting_indexes.addressindex,
indexer_vecs.addressindex_to_height.mut_vec(),
|i| (i, i),
exit,
)?;
self.txoutindex_to_txoutindex.compute_range(
starting_indexes.txoutindex,
indexer_vecs.txoutindex_to_height.mut_vec(),
|i| (i, i),
exit,
)?;
self.p2pk33index_to_p2pk33index.compute_range(
starting_indexes.p2pk33index,
indexer_vecs.p2pk33index_to_height.mut_vec(),
|i| (i, i),
exit,
)?;
self.p2pk65index_to_p2pk65index.compute_range(
starting_indexes.p2pk65index,
indexer_vecs.p2pk65index_to_height.mut_vec(),
|i| (i, i),
exit,
)?;
self.p2pkhindex_to_p2pkhindex.compute_range(
starting_indexes.p2pkhindex,
indexer_vecs.p2pkhindex_to_height.mut_vec(),
|i| (i, i),
exit,
)?;
self.p2shindex_to_p2shindex.compute_range(
starting_indexes.p2shindex,
indexer_vecs.p2shindex_to_height.mut_vec(),
|i| (i, i),
exit,
)?;
self.p2trindex_to_p2trindex.compute_range(
starting_indexes.p2trindex,
indexer_vecs.p2trindex_to_height.mut_vec(),
|i| (i, i),
exit,
)?;
self.p2wpkhindex_to_p2wpkhindex.compute_range(
starting_indexes.p2wpkhindex,
indexer_vecs.p2wpkhindex_to_height.mut_vec(),
|i| (i, i),
exit,
)?;
self.p2wshindex_to_p2wshindex.compute_range(
starting_indexes.p2wshindex,
indexer_vecs.p2wshindex_to_height.mut_vec(),
|i| (i, i),
exit,
)?;
self.txindex_to_txindex.compute_range(
starting_indexes.txindex,
indexer_vecs.txindex_to_height.mut_vec(),
|i| (i, i),
exit,
)?;
self.txinindex_to_txinindex.compute_range(
starting_indexes.txinindex,
indexer_vecs.txinindex_to_height.mut_vec(),
|i| (i, i),
exit,
)?;
self.emptyindex_to_emptyindex.compute_range(
starting_indexes.emptyindex,
indexer_vecs.emptyindex_to_height.mut_vec(),
|i| (i, i),
exit,
)?;
self.multisigindex_to_multisigindex.compute_range(
starting_indexes.multisigindex,
indexer_vecs.multisigindex_to_height.mut_vec(),
|i| (i, i),
exit,
)?;
self.opreturnindex_to_opreturnindex.compute_range(
starting_indexes.opreturnindex,
indexer_vecs.opreturnindex_to_height.mut_vec(),
|i| (i, i),
exit,
)?;
self.pushonlyindex_to_pushonlyindex.compute_range(
starting_indexes.pushonlyindex,
indexer_vecs.pushonlyindex_to_height.mut_vec(),
|i| (i, i),
exit,
)?;
self.unknownindex_to_unknownindex.compute_range(
starting_indexes.unknownindex,
indexer_vecs.unknownindex_to_height.mut_vec(),
|i| (i, i),
exit,
)?;
Ok(Indexes { Ok(Indexes {
indexes: starting_indexes, indexes: starting_indexes,
dateindex: starting_dateindex, dateindex: starting_dateindex,
@@ -821,6 +1018,22 @@ impl Vecs {
self.quarterindex_to_last_monthindex.any_vec(), self.quarterindex_to_last_monthindex.any_vec(),
self.quarterindex_to_quarterindex.any_vec(), self.quarterindex_to_quarterindex.any_vec(),
self.quarterindex_to_timestamp.any_vec(), self.quarterindex_to_timestamp.any_vec(),
self.p2pk33index_to_p2pk33index.any_vec(),
self.p2pk65index_to_p2pk65index.any_vec(),
self.p2pkhindex_to_p2pkhindex.any_vec(),
self.p2shindex_to_p2shindex.any_vec(),
self.p2trindex_to_p2trindex.any_vec(),
self.p2wpkhindex_to_p2wpkhindex.any_vec(),
self.p2wshindex_to_p2wshindex.any_vec(),
self.txindex_to_txindex.any_vec(),
self.txinindex_to_txinindex.any_vec(),
self.emptyindex_to_emptyindex.any_vec(),
self.multisigindex_to_multisigindex.any_vec(),
self.opreturnindex_to_opreturnindex.any_vec(),
self.pushonlyindex_to_pushonlyindex.any_vec(),
self.unknownindex_to_unknownindex.any_vec(),
self.addressindex_to_addressindex.any_vec(),
self.txoutindex_to_txoutindex.any_vec(),
] ]
} }
} }

View File

@@ -37,9 +37,8 @@
line-height: 1.5; line-height: 1.5;
-webkit-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;
tab-size: 4; tab-size: 4;
font-family: "Geist mono", ui-sans-serif, system-ui, sans-serif, font-family: "Geist mono", ui-monospace, SFMono-Regular, Menlo, Monaco,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", Consolas, "Liberation Mono", "Courier New", monospace;
"Noto Color Emoji";
font-feature-settings: "ss03"; font-feature-settings: "ss03";
-webkit-tap-highlight-color: transparent; -webkit-tap-highlight-color: transparent;
} }
@@ -134,6 +133,7 @@
letter-spacing: inherit; letter-spacing: inherit;
color: inherit; color: inherit;
background: transparent; background: transparent;
text-transform: inherit;
} }
button, button,
@@ -590,7 +590,7 @@
> fieldset { > fieldset {
width: 100%; width: 100%;
display: flex; display: flex;
gap: 1rem; gap: 1.25rem;
> label { > label {
pointer-events: auto; pointer-events: auto;
@@ -626,6 +626,7 @@
} }
select { select {
cursor: pointer;
-webkit-appearance: none; -webkit-appearance: none;
-moz-appearance: none; -moz-appearance: none;
appearance: none; appearance: none;
@@ -884,7 +885,7 @@
bottom: 0; bottom: 0;
left: 0; left: 0;
right: 0; right: 0;
z-index: 10; z-index: 20;
pointer-events: none; pointer-events: none;
} }
.shadow-left { .shadow-left {
@@ -979,7 +980,6 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 0; min-height: 0;
z-index: 20;
flex: 1; flex: 1;
margin-top: 2rem; margin-top: 2rem;
margin-bottom: 1rem; margin-bottom: 1rem;
@@ -1607,9 +1607,8 @@
</main> </main>
<aside id="aside"> <aside id="aside">
<div id="charts" hidden></div> <div id="charts" hidden></div>
<div id="database" hidden></div>
<div id="simulation" hidden></div> <div id="simulation" hidden></div>
<div id="live-price" hidden></div>
<div id="moscow-time" hidden></div>
</aside> </aside>
<div id="share-div" hidden> <div id="share-div" hidden>
<div id="share-content-div"> <div id="share-content-div">

View File

@@ -32,6 +32,7 @@ export default import("./v5.0.5-treeshaked/script.js").then((lc) => {
* @param {Colors} args.colors * @param {Colors} args.colors
* @param {Index} args.index * @param {Index} args.index
* @param {Utilities} args.utils * @param {Utilities} args.utils
* @param {Elements} args.elements
* @param {DeepPartial<ChartOptions>} [args.options] * @param {DeepPartial<ChartOptions>} [args.options]
*/ */
function createLightweightChart({ function createLightweightChart({
@@ -40,13 +41,15 @@ export default import("./v5.0.5-treeshaked/script.js").then((lc) => {
colors, colors,
index, index,
utils, utils,
elements,
options: _options = {}, options: _options = {},
}) { }) {
console.log(elements.style.fontFamily);
/** @satisfies {DeepPartial<ChartOptions>} */ /** @satisfies {DeepPartial<ChartOptions>} */
const options = { const options = {
autoSize: true, autoSize: true,
layout: { layout: {
fontFamily: "Geist mono", fontFamily: elements.style.fontFamily,
// fontSize: 13, // fontSize: 13,
background: { color: "transparent" }, background: { color: "transparent" },
attributionLogo: false, attributionLogo: false,
@@ -134,6 +137,7 @@ export default import("./v5.0.5-treeshaked/script.js").then((lc) => {
* @param {Signals} args.signals * @param {Signals} args.signals
* @param {Colors} args.colors * @param {Colors} args.colors
* @param {Utilities} args.utils * @param {Utilities} args.utils
* @param {Elements} args.elements
* @param {VecsResources} args.vecsResources * @param {VecsResources} args.vecsResources
* @param {Owner | null} [args.owner] * @param {Owner | null} [args.owner]
* @param {true} [args.fitContentOnResize] * @param {true} [args.fitContentOnResize]
@@ -144,6 +148,7 @@ export default import("./v5.0.5-treeshaked/script.js").then((lc) => {
signals, signals,
colors, colors,
utils, utils,
elements,
id, id,
vecsResources, vecsResources,
owner: _owner, owner: _owner,
@@ -281,6 +286,7 @@ export default import("./v5.0.5-treeshaked/script.js").then((lc) => {
signals, signals,
colors, colors,
utils, utils,
elements,
}); });
if (fitContentOnResize) { if (fitContentOnResize) {

View File

@@ -38,6 +38,7 @@ export function init({
id: "charts", id: "charts",
utils, utils,
vecsResources, vecsResources,
elements,
}); });
const index = createIndexSelector({ elements, signals, utils }); const index = createIndexSelector({ elements, signals, utils });

View File

@@ -0,0 +1,460 @@
// @ts-check
/**
* @param {Object} args
* @param {VecIdToIndexes} args.vecIdToIndexes
* @param {Utilities} args.utils
* @param {Signals} args.signals
* @param {VecsResources} args.vecsResources
*/
function createTable({ utils, vecIdToIndexes, signals, vecsResources }) {
const indexToVecIds = createIndexToVecIds(vecIdToIndexes);
const serializedIndexes = createSerializedIndexes();
/** @type {SerializedIndex} */
const defaultSerializedIndex = "height";
const serializedIndex = signals.createSignal({
name: /** @type {SerializedIndex} */ (defaultSerializedIndex),
value: String(serializedIndexToIndex(defaultSerializedIndex)),
});
const index = signals.createMemo(() =>
serializedIndexToIndex(serializedIndex().name),
);
const table = window.document.createElement("table");
const obj = {
element: table,
/** @type {VoidFunction | undefined} */
addRandomCol: undefined,
};
signals.createEffect(index, (index) => {
table.innerHTML = "";
const thead = window.document.createElement("thead");
table.append(thead);
const trHead = window.document.createElement("tr");
const selects = signals.createSignal(
/** @type {HTMLSelectElement[]} */ ([]),
{
equals: false,
},
);
thead.append(trHead);
const tbody = window.document.createElement("tbody");
table.append(tbody);
const rowElements = signals.createSignal(
/** @type {HTMLTableRowElement[]} */ ([]),
);
/**
* @param {Object} args
* @param {HTMLSelectElement} args.select
* @param {VoidFunction} [args.onLeft]
* @param {VoidFunction} [args.onRight]
* @param {VoidFunction} [args.onRemove]
*/
function addThCol({ select, onLeft, onRight, onRemove }) {
const th = window.document.createElement("th");
th.scope = "col";
trHead.append(th);
const div = window.document.createElement("div");
div.append(select);
const strip = window.document.createElement("div");
const unit = window.document.createElement("span");
const moveLeft = utils.dom.createButtonElement({
inside: "←",
title: "Move column to the left",
onClick: onLeft || (() => {}),
});
const moveRight = utils.dom.createButtonElement({
inside: "→",
title: "Move column to the right",
onClick: onRight || (() => {}),
});
const remove = utils.dom.createButtonElement({
inside: "×",
title: "Remove column",
onClick: onRemove || (() => {}),
});
strip.append(unit);
strip.append(moveLeft);
strip.append(moveRight);
strip.append(remove);
div.append(strip);
th.append(div);
return {
element: th,
/**
* @param {Unit} _unit
*/
setUnit(_unit) {
unit.innerHTML = _unit;
},
};
}
const { select } = utils.dom.createSelect({
id: "col-index",
list: serializedIndexes.map((serializedIndex) => ({
name: serializedIndex,
value: String(serializedIndexToIndex(serializedIndex)),
})),
signal: serializedIndex,
});
const th = addThCol({ select });
th.setUnit("Index");
vecsResources
.getOrCreate(index, serializedIndex().name)
.fetch()
.then((vec) => {
if (!vec) return;
const trs = /** @type {HTMLTableRowElement[]} */ ([]);
for (let i = vec.length - 1; i >= 0; i--) {
const value = vec[i];
const tr = window.document.createElement("tr");
trs.push(tr);
tbody.append(tr);
const th = window.document.createElement("th");
th.innerHTML = String(value);
th.scope = "row";
tr.append(th);
}
rowElements.set(() => trs);
});
const columnIds = signals.createSignal(/** @type {VecId[]} */ ([]), {
equals: false,
});
const owner = signals.getOwner();
const possibleVecids = indexToVecIds[index];
obj.addRandomCol = function () {
signals.runWithOwner(owner, () => {
const vecId =
possibleVecids[Math.round(Math.random() * possibleVecids.length)];
const colIndex = signals.createSignal(columnIds().length);
const vecIdOption = signals.createSignal({
name: vecId,
value: vecId,
});
const { select } = utils.dom.createSelect({
id: `col-${colIndex() + 1}`,
list: possibleVecids.map((vecId) => ({
name: vecId,
value: vecId,
})),
signal: vecIdOption,
});
selects.set((l) => {
l.push(select);
return l;
});
/**
* @param {boolean} right
*/
function createMoveColumnFunction(right) {
return () =>
colIndex.set((oldI) => {
const newI = oldI + (right ? 1 : -1);
const currentSelect = selects()[oldI];
const currentSelectSibling = currentSelect.nextSibling;
const newSelect = selects()[newI];
[selects()[oldI], selects()[newI]] = [
selects()[newI],
selects()[oldI],
];
console.log(oldI, newI, selects());
const newSelectSibling = newSelect.nextSibling;
newSelectSibling?.before(currentSelect);
currentSelectSibling?.before(newSelect);
return newI;
});
}
const th = addThCol({
select,
onLeft: createMoveColumnFunction(false),
onRight: createMoveColumnFunction(true),
});
signals.createEffect(rowElements, (rowElements) => {
if (!rowElements.length) return;
for (let i = 0; i < rowElements.length; i++) {
const td = window.document.createElement("td");
rowElements[i].append(td);
}
signals.createEffect(vecIdOption, ({ name: vecId }) => {
const unit = utils.vecidToUnit(vecId);
th.setUnit(unit);
const valuesPromise = vecsResources
.getOrCreate(index, vecId)
.fetch();
columnIds.set((l) => {
if (columnIds().length === colIndex()) {
l.push(vecId);
} else {
l[colIndex()] = vecId;
}
console.log(l);
return l;
});
valuesPromise.then((vec) => {
if (!vec) return;
// const diff = vec.length - rowElements.length;
for (let i = 0; i < rowElements.length; i++) {
const iRev = vec.length - 1 - i;
const value = vec[iRev];
/** @type {string | number | undefined} */
let serialized;
if (typeof value !== "number") {
serialized = value;
} else if (value !== 18446744073709552000) {
if (
unit === "USD" ||
unit === "Difficulty" ||
unit === "sat/vB"
) {
serialized = value.toLocaleString("en-us", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
});
} else if (unit === "BTC") {
serialized = value.toLocaleString("en-us", {
minimumFractionDigits: 8,
maximumFractionDigits: 8,
});
} else {
serialized = value.toLocaleString("en-us");
}
}
signals.runWithOwner(owner, () => {
signals.createEffect(colIndex, (colIndex) => {
// @ts-ignore
rowElements[i].childNodes[colIndex + 1].innerHTML =
serialized;
});
});
}
});
});
});
});
// signals.runWithOwner(owner, () => {
// const thIndex = thHead.length;
// const possibleVecids = indexToVecIds[index()];
// const i = Math.round(Math.random() * possibleVecids.length);
// const vecId = signals.createSignal({
// name: possibleVecids[i],
// value: possibleVecids[i],
// });
// const th = addThCol();
// const { select } = utils.dom.createSelect({
// id: `col-${vecId}`,
// list: possibleVecids.map((vecId) => ({
// name: vecId,
// value: vecId,
// })),
// signal: vecId,
// });
// th.append(select);
// signals.createEffect(
// () => /** @type {const} */ ([index(), vecId(), rowElements()]),
// ([index, vecId, trsBody]) => {
// if (!trsBody.length) return;
// vecsResources
// .getOrCreate(index, vecId.name)
// .fetch()
// .then((vec) => {
// if (!vec) return;
// console.log({ vec, trsBody, index });
// for (let i = 0; i < vec.length; i++) {
// const iRev = vec.length - 1 - i;
// const value = vec[iRev];
// const td = window.document.createElement("td");
// td.innerHTML = String(value);
// trsBody[i].append(td);
// }
// });
// },
// );
// });
};
// setTimeout(addCol, 2000);
// addRandomCol();
// addRandomCol();
// addRandomCol();
});
return obj;
}
/**
* @param {Object} args
* @param {Colors} args.colors
* @param {Signals} args.signals
* @param {Utilities} args.utils
* @param {Elements} args.elements
* @param {VecsResources} args.vecsResources
* @param {VecIdToIndexes} args.vecIdToIndexes
*/
export function init({
colors,
elements,
signals,
utils,
vecsResources,
vecIdToIndexes,
}) {
const parent = elements.database;
const { headerElement } = utils.dom.createHeader({
title: "Database",
});
parent.append(headerElement);
const div = window.document.createElement("div");
parent.append(div);
const table = createTable({
signals,
utils,
vecIdToIndexes,
vecsResources,
});
div.append(table.element);
const span = window.document.createElement("span");
span.innerHTML = "Add column";
div.append(
utils.dom.createButtonElement({
onClick: () => {
table.addRandomCol?.();
},
inside: span,
title: "Click or tap to add a column to the table",
}),
);
}
function createSerializedIndexes() {
return /** @type {const} */ ([
/** @satisfies {VecId} */ ("height"),
/** @satisfies {VecId} */ ("dateindex"),
/** @satisfies {VecId} */ ("weekindex"),
/** @satisfies {VecId} */ ("difficultyepoch"),
/** @satisfies {VecId} */ ("monthindex"),
/** @satisfies {VecId} */ ("quarterindex"),
/** @satisfies {VecId} */ ("yearindex"),
/** @satisfies {VecId} */ ("decadeindex"),
/** @satisfies {VecId} */ ("halvingepoch"),
/** @satisfies {VecId} */ ("addressindex"),
/** @satisfies {VecId} */ ("p2pk33index"),
/** @satisfies {VecId} */ ("p2pk65index"),
/** @satisfies {VecId} */ ("p2pkhindex"),
/** @satisfies {VecId} */ ("p2shindex"),
/** @satisfies {VecId} */ ("p2trindex"),
/** @satisfies {VecId} */ ("p2wpkhindex"),
/** @satisfies {VecId} */ ("p2wshindex"),
/** @satisfies {VecId} */ ("txindex"),
/** @satisfies {VecId} */ ("txinindex"),
/** @satisfies {VecId} */ ("txoutindex"),
/** @satisfies {VecId} */ ("emptyindex"),
/** @satisfies {VecId} */ ("multisigindex"),
/** @satisfies {VecId} */ ("opreturnindex"),
/** @satisfies {VecId} */ ("pushonlyindex"),
/** @satisfies {VecId} */ ("unknownindex"),
]);
}
/** @typedef {ReturnType<typeof createSerializedIndexes>} SerializedIndexes */
/** @typedef {SerializedIndexes[number]} SerializedIndex */
/**
* @param {SerializedIndex} serializedIndex
* @returns {Index}
*/
function serializedIndexToIndex(serializedIndex) {
switch (serializedIndex) {
case "height":
return /** @satisfies {Height} */ (0);
case "dateindex":
return /** @satisfies {Dateindex} */ (1);
case "weekindex":
return /** @satisfies {Weekindex} */ (2);
case "difficultyepoch":
return /** @satisfies {Difficultyepoch} */ (3);
case "monthindex":
return /** @satisfies {Monthindex} */ (4);
case "quarterindex":
return /** @satisfies {Quarterindex} */ (5);
case "yearindex":
return /** @satisfies {Yearindex} */ (6);
case "decadeindex":
return /** @satisfies {Decadeindex} */ (7);
case "halvingepoch":
return /** @satisfies {Halvingepoch} */ (8);
case "addressindex":
return /** @satisfies {Addressindex} */ (9);
case "p2pk33index":
return /** @satisfies {P2PK33index} */ (10);
case "p2pk65index":
return /** @satisfies {P2PK65index} */ (11);
case "p2pkhindex":
return /** @satisfies {P2PKHindex} */ (12);
case "p2shindex":
return /** @satisfies {P2SHindex} */ (13);
case "p2trindex":
return /** @satisfies {P2TRindex} */ (14);
case "p2wpkhindex":
return /** @satisfies {P2WPKHindex} */ (15);
case "p2wshindex":
return /** @satisfies {P2WSHindex} */ (16);
case "txindex":
return /** @satisfies {Txindex} */ (17);
case "txinindex":
return /** @satisfies {Txinindex} */ (18);
case "txoutindex":
return /** @satisfies {Txoutindex} */ (19);
case "emptyindex":
return /** @satisfies {Emptyindex} */ (20);
case "multisigindex":
return /** @satisfies {Multisigindex} */ (21);
case "opreturnindex":
return /** @satisfies {Opreturnindex} */ (22);
case "pushonlyindex":
return /** @satisfies {Pushonlyindex} */ (23);
case "unknownindex":
return /** @satisfies {Unknownindex} */ (24);
}
}
/**
* @param {VecIdToIndexes} vecIdToIndexes
*/
function createIndexToVecIds(vecIdToIndexes) {
const indexToVecIds = Object.entries(vecIdToIndexes).reduce(
(arr, [_id, indexes]) => {
const id = /** @type {VecId} */ (_id);
indexes.forEach((i) => {
arr[i] ??= [];
arr[i].push(id);
});
return arr;
},
/** @type {VecId[][]} */ (new Array(24)),
);
indexToVecIds.forEach((arr) => {
arr.sort();
});
return indexToVecIds;
}

View File

@@ -1,7 +1,7 @@
// @ts-check // @ts-check
/** /**
* @import { Option, PartialChartOption, ChartOption, AnyPartialOption, ProcessedOptionAddons, OptionsTree, SimulationOption, Unit, AnySeriesBlueprint, ChartableIndex } from "./options" * @import { Option, PartialChartOption, ChartOption, AnyPartialOption, ProcessedOptionAddons, OptionsTree, SimulationOption, AnySeriesBlueprint, ChartableIndex } from "./options"
* @import {Valued, SingleValueData, CandlestickData, ChartData, OHLCTuple} from "../packages/lightweight-charts/wrapper" * @import {Valued, SingleValueData, CandlestickData, ChartData, OHLCTuple} from "../packages/lightweight-charts/wrapper"
* @import * as _ from "../packages/ufuzzy/v1.0.14/types" * @import * as _ from "../packages/ufuzzy/v1.0.14/types"
* @import { createChart as CreateClassicChart, LineStyleOptions, DeepPartial, ChartOptions, IChartApi, IHorzScaleBehavior, WhitespaceData, ISeriesApi, Time, LineData, LogicalRange, SeriesType, BaselineStyleOptions, SeriesOptionsCommon, BaselineData, CandlestickStyleOptions } from "../packages/lightweight-charts/v5.0.5-treeshaked/types" * @import { createChart as CreateClassicChart, LineStyleOptions, DeepPartial, ChartOptions, IChartApi, IHorzScaleBehavior, WhitespaceData, ISeriesApi, Time, LineData, LogicalRange, SeriesType, BaselineStyleOptions, SeriesOptionsCommon, BaselineData, CandlestickStyleOptions } from "../packages/lightweight-charts/v5.0.5-treeshaked/types"
@@ -9,7 +9,38 @@
* @import {Signal, Signals} from "../packages/solid-signals/types"; * @import {Signal, Signals} from "../packages/solid-signals/types";
* @import { getOwner as GetOwner, onCleanup as OnCleanup, Owner } from "../packages/solid-signals/v0.2.4-treeshaked/types/core/owner" * @import { getOwner as GetOwner, onCleanup as OnCleanup, Owner } from "../packages/solid-signals/v0.2.4-treeshaked/types/core/owner"
* @import { createEffect as CreateEffect, Accessor, Setter, createMemo as CreateMemo } from "../packages/solid-signals/v0.2.4-treeshaked/types/signals"; * @import { createEffect as CreateEffect, Accessor, Setter, createMemo as CreateMemo } from "../packages/solid-signals/v0.2.4-treeshaked/types/signals";
* @import {Addressindex, Dateindex, Decadeindex, Difficultyepoch, Index, Halvingepoch, Height, Monthindex, P2PK33index, P2PK65index, P2PKHindex, P2SHindex, P2TRindex, P2WPKHindex, P2WSHindex, Txindex, Txinindex, Txoutindex, VecId, Weekindex, Yearindex, VecIdToIndexes, Quarterindex} from "./vecid-to-indexes" * @import {Addressindex, Dateindex, Decadeindex, Difficultyepoch, Index, Halvingepoch, Height, Monthindex, P2PK33index, P2PK65index, P2PKHindex, P2SHindex, P2TRindex, P2WPKHindex, P2WSHindex, Txindex, Txinindex, Txoutindex, VecId, Weekindex, Yearindex, VecIdToIndexes, Quarterindex, Emptyindex, Multisigindex, Opreturnindex, Pushonlyindex, Unknownindex} from "./vecid-to-indexes"
*/
/**
* @typedef {"" |
* "BTC" |
* "Cents" |
* "Coinblocks" |
* "Count" |
* "Date" |
* "Difficulty" |
* "ExaHash / Second" |
* "Gigabytes" |
* "Hash" |
* "Index" |
* "mb" |
* "%" |
* "Ratio" |
* "Sats" |
* "Seconds" |
* "Timestamp" |
* "tx" |
* "Type" |
* "USD / (PetaHash / Second)" |
* "USD" |
* "Version" |
* "WU" |
* "Bool" |
* "Locktime" |
* "sat/vB" |
* "vB"
* } Unit
*/ */
function initPackages() { function initPackages() {
@@ -113,22 +144,6 @@ function createUtils() {
if (!element) throw `Element with id = "${id}" should exist`; if (!element) throw `Element with id = "${id}" should exist`;
return element; return element;
}, },
/**
* @param {string} name
* @param {Elements} elements
*/
queryOrCreateMetaElement(name, elements) {
let meta = /** @type {HTMLMetaElement | null} */ (
window.document.querySelector(`meta[name="${name}"]`)
);
if (!meta) {
meta = window.document.createElement("meta");
meta.name = name;
elements.head.appendChild(meta);
}
return meta;
},
/** /**
* @param {HTMLElement} element * @param {HTMLElement} element
*/ */
@@ -206,14 +221,14 @@ function createUtils() {
}, },
/** /**
* @param {Object} arg * @param {Object} arg
* @param {string} arg.text * @param {string | HTMLElement} arg.inside
* @param {string} arg.title * @param {string} arg.title
* @param {VoidFunction} arg.onClick * @param {VoidFunction} arg.onClick
*/ */
createButtonElement({ text, onClick, title }) { createButtonElement({ inside: text, onClick, title }) {
const button = window.document.createElement("button"); const button = window.document.createElement("button");
button.innerHTML = text; button.append(text);
button.title = title; button.title = title;
@@ -404,128 +419,6 @@ function createUtils() {
return { field, selected }; return { field, selected };
}, },
/**
* @param {Object} args
* @param {string} args.id
* @param {string} args.title
* @param {string} args.placeholder
* @param {Signal<number | null>} args.signal
* @param {number} args.min
* @param {number} args.step
* @param {number} [args.max]
* @param {{createEffect: typeof CreateEffect}} args.signals
*/
createInputNumberElement({
id,
title,
signal,
min,
max,
step,
placeholder,
signals,
}) {
const input = window.document.createElement("input");
if (!id || !title || !placeholder) throw Error("input attribute missing");
input.id = id;
input.title = title;
input.placeholder = placeholder;
input.type = "number";
input.min = String(min);
if (max) {
input.max = String(max);
}
input.step = String(step);
let stateValue = /** @type {string | null} */ (null);
signals.createEffect(
() => {
const value = signal();
return value ? String(value) : "";
},
(value) => {
if (stateValue !== value) {
input.value = value;
stateValue = value;
}
},
);
input.addEventListener("input", () => {
const valueSer = input.value;
stateValue = valueSer;
const value = Number(valueSer);
if (value >= min && (max ? value <= max : true)) {
signal.set(value);
}
});
return { input, signal };
},
/**
* @param {Object} args
* @param {string} args.id
* @param {string} args.title
* @param {Signal<number | null>} args.signal
* @param {{createEffect: typeof CreateEffect}} args.signals
*/
createInputDollar({ id, title, signal, signals }) {
return this.createInputNumberElement({
id,
placeholder: "USD",
min: 0,
title,
signal,
signals,
step: 1,
});
},
/**
* @param {Object} args
* @param {string} args.id
* @param {string} args.title
* @param {Signal<Date | null>} args.signal
* @param {{createEffect: typeof CreateEffect}} args.signals
*/
createInputDate({ id, title, signal, signals }) {
const input = window.document.createElement("input");
input.id = id;
input.title = title;
input.type = "date";
const min = "2011-01-01";
const minDate = new Date(min);
const maxDate = new Date();
const max = date.toString(maxDate);
input.min = min;
input.max = max;
let stateValue = /** @type {string | null} */ (null);
signals.createEffect(
() => {
const dateSignal = signal();
return dateSignal ? date.toString(dateSignal) : "";
},
(value) => {
if (stateValue !== value) {
input.value = value;
stateValue = value;
}
},
);
input.addEventListener("change", () => {
const value = input.value;
const date = new Date(value);
if (date >= minDate && date <= maxDate) {
stateValue = value;
signal.set(value ? date : null);
}
});
return { input, signal };
},
/** /**
* @param {Object} args * @param {Object} args
* @param {1 | 2 | 3} [args.level] * @param {1 | 2 | 3} [args.level]
@@ -556,13 +449,16 @@ function createUtils() {
return option; return option;
}, },
/** /**
* @template {{name: string; value: string}} T * @template {string} Name
* @param {Object} param0 * @template {string} Value
* @param {string} param0.id * @template {{name: Name; value: Value}} T
* @param {(({name: string; value: string} & T) | {name: string; list: ({name: string; value: string} & T)[]})[]} param0.list * @param {Object} args
* @param {Signal<T>} param0.signal * @param {string} args.id
* @param {boolean} [args.deep]
* @param {(({name: Name; value: Value} & T) | {name: string; list: ({name: Name; value: Value} & T)[]})[]} args.list
* @param {Signal<T>} args.signal
*/ */
createSelect({ id, list, signal }) { createSelect({ id, list, signal, deep = false }) {
const select = window.document.createElement("select"); const select = window.document.createElement("select");
select.name = id; select.name = id;
select.id = id; select.id = id;
@@ -584,7 +480,7 @@ function createUtils() {
select.append(this.createOption(anyOption)); select.append(this.createOption(anyOption));
setters[anyOption.value] = () => signal.set(() => anyOption); setters[anyOption.value] = () => signal.set(() => anyOption);
} }
if (index !== list.length - 1) { if (deep && index !== list.length - 1) {
select.append(window.document.createElement("hr")); select.append(window.document.createElement("hr"));
} }
}); });
@@ -601,30 +497,6 @@ function createUtils() {
return { select, signal }; return { select, signal };
}, },
/**
* @param {Object} param0
* @param {Signal<any>} param0.signal
* @param {HTMLInputElement} [param0.input]
* @param {HTMLSelectElement} [param0.select]
*/
createResetableInput({ input, select, signal }) {
const div = window.document.createElement("div");
const element = input || select;
if (!element) throw "createResetableField element missing";
div.append(element);
const button = this.createButtonElement({
onClick: signal.reset,
text: "Reset",
title: "Reset field",
});
button.type = "reset";
div.append(button);
return div;
},
/** /**
* @param {Object} args * @param {Object} args
* @param {string} args.title * @param {string} args.title
@@ -786,6 +658,79 @@ function createUtils() {
}); });
} }
/**
* @param {VecId} id
*/
function vecidToUnit(id) {
/** @type {Unit} */
let unit;
if (id.includes("index") || id.includes("height") || id.includes("epoch")) {
unit = "Index";
} else if (id.includes("type")) {
unit = "Type";
} else if (id === "locktime") {
unit = "Locktime";
} else if (id.startsWith("is-")) {
unit = "Bool";
} else if (
id.includes("hash") ||
id.includes("address") ||
id.includes("txid")
) {
unit = "Hash";
} else if (id.includes("interval")) {
unit = "Seconds";
} else if (id.includes("feerate")) {
unit = "sat/vB";
} else if (id.includes("in-cents")) {
unit = "Cents";
} else if (id.includes("in-usd")) {
unit = "USD";
} else if (id.includes("in-btc")) {
unit = "BTC";
} else if (
id.includes("in-sats") ||
id.startsWith("sats-") ||
id.includes("input-value") ||
id.includes("output-value") ||
id.includes("fee") ||
id.includes("coinbase") ||
id.includes("subsidy")
) {
unit = "Sats";
} else if (
id.includes("open") ||
id.includes("high") ||
id.includes("low") ||
id.includes("close") ||
id.includes("ohlc")
) {
unit = "USD";
} else if (id.includes("count")) {
unit = "Count";
} else if (id.includes("date")) {
unit = "Date";
} else if (id.includes("timestamp")) {
unit = "Timestamp";
} else if (id.includes("difficulty")) {
unit = "Difficulty";
} else if (id.includes("-size")) {
unit = "mb";
} else if (id.includes("weight")) {
unit = "WU";
} else if (id.includes("vbytes") || id.includes("vsize")) {
unit = "vB";
} else if (id.includes("version") || id.match(/v[1-3]/g)) {
unit = "Version";
} else if (id === "value") {
unit = "Sats";
} else {
console.log();
throw Error(`Unit not set for "${id}"`);
}
return unit;
}
const locale = { const locale = {
numberToUSFormat, numberToUSFormat,
/** @param {number} value */ /** @param {number} value */
@@ -1298,6 +1243,7 @@ function createUtils() {
runWhenIdle, runWhenIdle,
getNumberOfDaysBetweenTwoDates, getNumberOfDaysBetweenTwoDates,
stringToId, stringToId,
vecidToUnit,
}; };
} }
/** @typedef {ReturnType<typeof createUtils>} Utilities */ /** @typedef {ReturnType<typeof createUtils>} Utilities */
@@ -1435,6 +1381,7 @@ function getElements() {
selectors: getElementById("frame-selectors"), selectors: getElementById("frame-selectors"),
style: getComputedStyle(window.document.documentElement), style: getComputedStyle(window.document.documentElement),
charts: getElementById("charts"), charts: getElementById("charts"),
database: getElementById("database"),
simulation: getElementById("simulation"), simulation: getElementById("simulation"),
}; };
} }
@@ -1920,6 +1867,12 @@ function main() {
optionsPromise.then(async ({ initOptions }) => { optionsPromise.then(async ({ initOptions }) => {
const vecIdToIndexes = createVecIdToIndexes(); const vecIdToIndexes = createVecIdToIndexes();
if (env.localhost) {
Object.keys(vecIdToIndexes).forEach((id) => {
utils.vecidToUnit(/** @type {VecId} */ (id));
});
}
function initDark() { function initDark() {
const preferredColorSchemeMatchMedia = window.matchMedia( const preferredColorSchemeMatchMedia = window.matchMedia(
"(prefers-color-scheme: dark)", "(prefers-color-scheme: dark)",
@@ -2003,7 +1956,8 @@ function main() {
undefined undefined
); );
let firstTimeLoadingChart = true; let firstTimeLoadingChart = true;
let firstTimeLoadingSim = true; let firstTimeLoadingDatabase = true;
let firstTimeLoadingSimulation = true;
signals.createEffect(options.selected, (option) => { signals.createEffect(options.selected, (option) => {
if (previousElement) { if (previousElement) {
@@ -2036,7 +1990,9 @@ function main() {
colors, colors,
elements, elements,
lightweightCharts, lightweightCharts,
selected: /** @type {any} */ (lastChartOption), selected: /** @type {Accessor<ChartOption>} */ (
lastChartOption
),
signals, signals,
utils, utils,
webSockets, webSockets,
@@ -2052,15 +2008,38 @@ function main() {
break; break;
} }
case "database": {
element = elements.database;
if (firstTimeLoadingDatabase) {
const databaseScript = import("./database.js");
utils.dom.importStyleAndThen("/styles/database.css", () =>
databaseScript.then(({ init }) =>
signals.runWithOwner(owner, () =>
init({
colors,
elements,
signals,
utils,
vecsResources,
vecIdToIndexes,
}),
),
),
);
}
firstTimeLoadingDatabase = false;
break;
}
case "simulation": { case "simulation": {
element = elements.simulation; element = elements.simulation;
lastSimulationOption.set(option); lastSimulationOption.set(option);
if (firstTimeLoadingSim) { if (firstTimeLoadingSimulation) {
const lightweightCharts = packages.lightweightCharts(); const lightweightCharts = packages.lightweightCharts();
const simulationScript = import("./simulation.js"); const simulationScript = import("./simulation.js");
utils.dom.importStyleAndThen( utils.dom.importStyleAndThen(
"/styles/simulation.css", "/styles/simulation.css",
() => () =>
@@ -2080,7 +2059,7 @@ function main() {
), ),
); );
} }
firstTimeLoadingSim = false; firstTimeLoadingSimulation = false;
break; break;
} }

View File

@@ -12,28 +12,6 @@
*/ */
/** /**
* @typedef {"" |
* "BTC" |
* "Coinblocks" |
* "Count" |
* "Date" |
* "USD / (PetaHash / Second)" |
* "ExaHash / Second" |
* "Height" |
* "Gigabytes" |
* "Megabytes" |
* "Percentage" |
* "Ratio" |
* "Sats" |
* "sat/vB" |
* "Seconds" |
* "Transactions" |
* "USD" |
* "Version" |
* "vB" |
* "WU"
* } Unit
*
* @typedef {Object} BaseSeriesBlueprint * @typedef {Object} BaseSeriesBlueprint
* @property {string} title * @property {string} title
* @property {boolean} [defaultActive] * @property {boolean} [defaultActive]
@@ -85,6 +63,14 @@
* *
* @typedef {Required<Omit<PartialChartOption, "top" | "bottom">> & ProcessedChartOptionAddons & ProcessedOptionAddons} ChartOption * @typedef {Required<Omit<PartialChartOption, "top" | "bottom">> & ProcessedChartOptionAddons & ProcessedOptionAddons} ChartOption
* *
* @typedef {Object} PartialDatabaseOptionSpecific
* @property {"database"} kind
* @property {string} title
*
* @typedef {PartialOption & PartialDatabaseOptionSpecific} PartialDatabaseOption
*
* @typedef {Required<PartialDatabaseOption> & ProcessedOptionAddons} DatabaseOption
*
* @typedef {Object} PartialSimulationOptionSpecific * @typedef {Object} PartialSimulationOptionSpecific
* @property {"simulation"} kind * @property {"simulation"} kind
* @property {string} title * @property {string} title
@@ -102,9 +88,9 @@
* *
* @typedef {Required<PartialUrlOption> & ProcessedOptionAddons} UrlOption * @typedef {Required<PartialUrlOption> & ProcessedOptionAddons} UrlOption
* *
* @typedef {PartialChartOption | PartialSimulationOption | PartialUrlOption} AnyPartialOption * @typedef {PartialChartOption | PartialDatabaseOption | PartialSimulationOption | PartialUrlOption} AnyPartialOption
* *
* @typedef {ChartOption | SimulationOption | UrlOption} Option * @typedef {ChartOption | DatabaseOption | SimulationOption | UrlOption} Option
* *
* @typedef {Object} PartialOptionsGroup * @typedef {Object} PartialOptionsGroup
* @property {string} name * @property {string} name
@@ -542,6 +528,11 @@ function createPartialOptions(colors) {
}, },
], ],
}, },
{
kind: "database",
title: "Database",
name: "Database",
},
{ {
name: "Simulations", name: "Simulations",
tree: [ tree: [
@@ -677,41 +668,7 @@ export function initOptions({
*/ */
function arrayToRecord(id, arr = []) { function arrayToRecord(id, arr = []) {
return (arr || []).reduce((record, blueprint) => { return (arr || []).reduce((record, blueprint) => {
const key = blueprint.key; const unit = utils.vecidToUnit(blueprint.key);
/** @type {Unit} */
let unit;
if (key.includes("interval")) {
unit = "Seconds";
} else if (key.includes("feerate")) {
unit = "sat/vB";
} else if (key.includes("in-usd")) {
unit = "USD";
} else if (key.includes("in-btc")) {
unit = "BTC";
} else if (
key.includes("-in-sats") ||
key.startsWith("sats-") ||
key.includes("input-value") ||
key.includes("output-value") ||
key.includes("fee") ||
key.includes("coinbase") ||
key.includes("subsidy")
) {
unit = "Sats";
} else if (key.includes("count")) {
unit = "Count";
} else if (key.includes("-size")) {
unit = "Megabytes";
} else if (key.includes("weight")) {
unit = "WU";
} else if (key.includes("vbytes") || key.includes("vsize")) {
unit = "vB";
} else if (key.match(/v[1-3]/g)) {
unit = "Version";
} else {
console.log([id, key]);
throw Error("Unit not set");
}
record[unit] ??= []; record[unit] ??= [];
record[unit].push(blueprint); record[unit].push(blueprint);
return record; return record;
@@ -733,7 +690,7 @@ export function initOptions({
if (option.qrcode) { if (option.qrcode) {
return utils.dom.createButtonElement({ return utils.dom.createButtonElement({
text: option.name, inside: option.name,
title: option.title, title: option.title,
onClick: () => { onClick: () => {
qrcode.set(option.url); qrcode.set(option.url);
@@ -918,16 +875,17 @@ export function initOptions({
/** @type {Option} */ /** @type {Option} */
let option; let option;
/** @type {string} */ if ("kind" in anyPartial && anyPartial.kind === "database") {
let id; option = /** @satisfies {DatabaseOption} */ ({
/** @type {Option["kind"]} */ kind: anyPartial.kind,
let kind; id: anyPartial.kind,
/** @type {string} */ name: anyPartial.name,
let title; path: path || [],
title: anyPartial.title,
if ("kind" in anyPartial && anyPartial.kind === "simulation") { });
} else if ("kind" in anyPartial && anyPartial.kind === "simulation") {
option = /** @satisfies {SimulationOption} */ ({ option = /** @satisfies {SimulationOption} */ ({
kind: "simulation", kind: anyPartial.kind,
id: anyPartial.kind, id: anyPartial.kind,
name: anyPartial.name, name: anyPartial.name,
path: path || [], path: path || [],

View File

@@ -30,6 +30,155 @@ export function init({
const simulationElement = elements.simulation; const simulationElement = elements.simulation;
const dom = {
/**
* @param {Object} args
* @param {string} args.id
* @param {string} args.title
* @param {string} args.placeholder
* @param {Signal<number | null>} args.signal
* @param {number} args.min
* @param {number} args.step
* @param {number} [args.max]
* @param {{createEffect: typeof CreateEffect}} args.signals
*/
createInputNumberElement({
id,
title,
signal,
min,
max,
step,
placeholder,
signals,
}) {
const input = window.document.createElement("input");
if (!id || !title || !placeholder) throw Error("input attribute missing");
input.id = id;
input.title = title;
input.placeholder = placeholder;
input.type = "number";
input.min = String(min);
if (max) {
input.max = String(max);
}
input.step = String(step);
let stateValue = /** @type {string | null} */ (null);
signals.createEffect(
() => {
const value = signal();
return value ? String(value) : "";
},
(value) => {
if (stateValue !== value) {
input.value = value;
stateValue = value;
}
},
);
input.addEventListener("input", () => {
const valueSer = input.value;
stateValue = valueSer;
const value = Number(valueSer);
if (value >= min && (max ? value <= max : true)) {
signal.set(value);
}
});
return { input, signal };
},
/**
* @param {Object} args
* @param {string} args.id
* @param {string} args.title
* @param {Signal<number | null>} args.signal
* @param {{createEffect: typeof CreateEffect}} args.signals
*/
createInputDollar({ id, title, signal, signals }) {
return this.createInputNumberElement({
id,
placeholder: "USD",
min: 0,
title,
signal,
signals,
step: 1,
});
},
/**
* @param {Object} args
* @param {string} args.id
* @param {string} args.title
* @param {Signal<Date | null>} args.signal
* @param {{createEffect: typeof CreateEffect}} args.signals
*/
createInputDate({ id, title, signal, signals }) {
const input = window.document.createElement("input");
input.id = id;
input.title = title;
input.type = "date";
const min = "2011-01-01";
const minDate = new Date(min);
const maxDate = new Date();
const max = utils.date.toString(maxDate);
input.min = min;
input.max = max;
let stateValue = /** @type {string | null} */ (null);
signals.createEffect(
() => {
const dateSignal = signal();
return dateSignal ? utils.date.toString(dateSignal) : "";
},
(value) => {
if (stateValue !== value) {
input.value = value;
stateValue = value;
}
},
);
input.addEventListener("change", () => {
const value = input.value;
const date = new Date(value);
if (date >= minDate && date <= maxDate) {
stateValue = value;
signal.set(value ? date : null);
}
});
return { input, signal };
},
/**
* @param {Object} param0
* @param {Signal<any>} param0.signal
* @param {HTMLInputElement} [param0.input]
* @param {HTMLSelectElement} [param0.select]
*/
createResetableInput({ input, select, signal }) {
const div = window.document.createElement("div");
const element = input || select;
if (!element) throw "createResetableField element missing";
div.append(element);
const button = utils.dom.createButtonElement({
onClick: signal.reset,
inside: "Reset",
title: "Reset field",
});
button.type = "reset";
div.append(button);
return div;
},
};
const parametersElement = window.document.createElement("div"); const parametersElement = window.document.createElement("div");
simulationElement.append(parametersElement); simulationElement.append(parametersElement);
const resultsElement = window.document.createElement("div"); const resultsElement = window.document.createElement("div");
@@ -276,8 +425,8 @@ export function init({
}), }),
description: description:
"The amount of dollars you have ready on the exchange on day one.", "The amount of dollars you have ready on the exchange on day one.",
input: utils.dom.createResetableInput( input: dom.createResetableInput(
utils.dom.createInputDollar({ dom.createInputDollar({
id: "simulation-dollars-initial", id: "simulation-dollars-initial",
title: "Initial Dollar Amount", title: "Initial Dollar Amount",
signal: settings.dollars.initial.amount, signal: settings.dollars.initial.amount,
@@ -296,11 +445,12 @@ export function init({
}), }),
description: description:
"The frequency at which you'll top up your account at the exchange.", "The frequency at which you'll top up your account at the exchange.",
input: utils.dom.createResetableInput( input: dom.createResetableInput(
utils.dom.createSelect({ utils.dom.createSelect({
id: "top-up-frequency", id: "top-up-frequency",
list: frequencies.list, list: frequencies.list,
signal: settings.dollars.topUp.frenquency, signal: settings.dollars.topUp.frenquency,
deep: true,
}), }),
), ),
}), }),
@@ -315,8 +465,8 @@ export function init({
}), }),
description: description:
"The recurrent amount of dollars you'll be transfering to said exchange.", "The recurrent amount of dollars you'll be transfering to said exchange.",
input: utils.dom.createResetableInput( input: dom.createResetableInput(
utils.dom.createInputDollar({ dom.createInputDollar({
id: "simulation-dollars-top-up-amount", id: "simulation-dollars-top-up-amount",
title: "Top Up Dollar Amount", title: "Top Up Dollar Amount",
signal: settings.dollars.topUp.amount, signal: settings.dollars.topUp.amount,
@@ -335,8 +485,8 @@ export function init({
}), }),
description: description:
"The amount, if available, of dollars that will be used to buy Bitcoin on day one.", "The amount, if available, of dollars that will be used to buy Bitcoin on day one.",
input: utils.dom.createResetableInput( input: dom.createResetableInput(
utils.dom.createInputDollar({ dom.createInputDollar({
id: "simulation-bitcoin-initial-investment", id: "simulation-bitcoin-initial-investment",
title: "Initial Swap Amount", title: "Initial Swap Amount",
signal: settings.bitcoin.investment.initial, signal: settings.bitcoin.investment.initial,
@@ -354,11 +504,12 @@ export function init({
text: "Investment Frequency", text: "Investment Frequency",
}), }),
description: "The frequency at which you'll be buying Bitcoin.", description: "The frequency at which you'll be buying Bitcoin.",
input: utils.dom.createResetableInput( input: dom.createResetableInput(
utils.dom.createSelect({ utils.dom.createSelect({
id: "investment-frequency", id: "investment-frequency",
list: frequencies.list, list: frequencies.list,
signal: settings.bitcoin.investment.frequency, signal: settings.bitcoin.investment.frequency,
deep: true,
}), }),
), ),
}), }),
@@ -373,8 +524,8 @@ export function init({
}), }),
description: description:
"The recurrent amount, if available, of dollars that will be used to buy Bitcoin.", "The recurrent amount, if available, of dollars that will be used to buy Bitcoin.",
input: utils.dom.createResetableInput( input: dom.createResetableInput(
utils.dom.createInputDollar({ dom.createInputDollar({
id: "simulation-bitcoin-recurrent-investment", id: "simulation-bitcoin-recurrent-investment",
title: "Bitcoin Recurrent Investment", title: "Bitcoin Recurrent Investment",
signal: settings.bitcoin.investment.recurrent, signal: settings.bitcoin.investment.recurrent,
@@ -392,8 +543,8 @@ export function init({
text: "Start", text: "Start",
}), }),
description: "The first day of the simulation.", description: "The first day of the simulation.",
input: utils.dom.createResetableInput( input: dom.createResetableInput(
utils.dom.createInputDate({ dom.createInputDate({
id: "simulation-inverval-start", id: "simulation-inverval-start",
title: "First Simulation Date", title: "First Simulation Date",
signal: settings.interval.start, signal: settings.interval.start,
@@ -411,8 +562,8 @@ export function init({
text: "End", text: "End",
}), }),
description: "The last day of the simulation.", description: "The last day of the simulation.",
input: utils.dom.createResetableInput( input: dom.createResetableInput(
utils.dom.createInputDate({ dom.createInputDate({
id: "simulation-inverval-end", id: "simulation-inverval-end",
title: "Last Simulation Day", title: "Last Simulation Day",
signal: settings.interval.end, signal: settings.interval.end,
@@ -430,8 +581,8 @@ export function init({
text: "Exchange", text: "Exchange",
}), }),
description: "The amount of trading fees (in %) at the exchange.", description: "The amount of trading fees (in %) at the exchange.",
input: utils.dom.createResetableInput( input: dom.createResetableInput(
utils.dom.createInputNumberElement({ dom.createInputNumberElement({
id: "simulation-fees", id: "simulation-fees",
title: "Exchange Fees", title: "Exchange Fees",
signal: settings.fees.percentage, signal: settings.fees.percentage,
@@ -555,6 +706,7 @@ export function init({
fitContentOnResize: true, fitContentOnResize: true,
vecsResources, vecsResources,
utils, utils,
elements,
config: [ config: [
{ {
unit: "USD", unit: "USD",
@@ -597,6 +749,7 @@ export function init({
id: `bitcoin`, id: `bitcoin`,
fitContentOnResize: true, fitContentOnResize: true,
vecsResources, vecsResources,
elements,
utils, utils,
config: [ config: [
{ {
@@ -621,6 +774,7 @@ export function init({
fitContentOnResize: true, fitContentOnResize: true,
vecsResources, vecsResources,
utils, utils,
elements,
config: [ config: [
{ {
unit: "USD", unit: "USD",
@@ -650,6 +804,8 @@ export function init({
id: `return-ratio`, id: `return-ratio`,
fitContentOnResize: true, fitContentOnResize: true,
utils, utils,
elements,
config: [ config: [
{ {
unit: "USD", unit: "USD",
@@ -672,10 +828,11 @@ export function init({
fitContentOnResize: true, fitContentOnResize: true,
vecsResources, vecsResources,
utils, utils,
elements,
owner, owner,
config: [ config: [
{ {
unit: "Percentage", unit: "%",
blueprints: [ blueprints: [
{ {
title: "Profitable Days Ratio", title: "Profitable Days Ratio",

View File

@@ -58,7 +58,7 @@ export function createVecIdToIndexes() {
const Unknownindex = /** @satisfies {Unknownindex} */ (24); const Unknownindex = /** @satisfies {Unknownindex} */ (24);
return /** @type {const} */ ({ return /** @type {const} */ ({
addressindex: [Txoutindex], addressindex: [Addressindex, Txoutindex],
addresstype: [Addressindex], addresstype: [Addressindex],
addresstypeindex: [Addressindex], addresstypeindex: [Addressindex],
"base-size": [Txindex], "base-size": [Txindex],
@@ -114,6 +114,7 @@ export function createVecIdToIndexes() {
decadeindex: [Yearindex, Decadeindex], decadeindex: [Yearindex, Decadeindex],
difficulty: [Height], difficulty: [Height],
difficultyepoch: [Height, Difficultyepoch], difficultyepoch: [Height, Difficultyepoch],
emptyindex: [Emptyindex],
fee: [Txindex], fee: [Txindex],
"fee-10p": [Height], "fee-10p": [Height],
"fee-25p": [Height], "fee-25p": [Height],
@@ -208,12 +209,14 @@ export function createVecIdToIndexes() {
"low-in-cents": [Dateindex, Height], "low-in-cents": [Dateindex, Height],
"low-in-sats": [Dateindex, Height, Weekindex, Monthindex, Quarterindex, Yearindex, Decadeindex, Difficultyepoch], "low-in-sats": [Dateindex, Height, Weekindex, Monthindex, Quarterindex, Yearindex, Decadeindex, Difficultyepoch],
monthindex: [Dateindex, Monthindex], monthindex: [Dateindex, Monthindex],
multisigindex: [Multisigindex],
ohlc: [Dateindex, Height, Weekindex, Monthindex, Quarterindex, Yearindex, Decadeindex, Difficultyepoch], ohlc: [Dateindex, Height, Weekindex, Monthindex, Quarterindex, Yearindex, Decadeindex, Difficultyepoch],
"ohlc-in-cents": [Dateindex, Height], "ohlc-in-cents": [Dateindex, Height],
"ohlc-in-sats": [Dateindex, Height, Weekindex, Monthindex, Quarterindex, Yearindex, Decadeindex, Difficultyepoch], "ohlc-in-sats": [Dateindex, Height, Weekindex, Monthindex, Quarterindex, Yearindex, Decadeindex, Difficultyepoch],
open: [Dateindex, Height, Weekindex, Monthindex, Quarterindex, Yearindex, Decadeindex, Difficultyepoch], open: [Dateindex, Height, Weekindex, Monthindex, Quarterindex, Yearindex, Decadeindex, Difficultyepoch],
"open-in-cents": [Dateindex, Height], "open-in-cents": [Dateindex, Height],
"open-in-sats": [Dateindex, Height, Weekindex, Monthindex, Quarterindex, Yearindex, Decadeindex, Difficultyepoch], "open-in-sats": [Dateindex, Height, Weekindex, Monthindex, Quarterindex, Yearindex, Decadeindex, Difficultyepoch],
opreturnindex: [Opreturnindex],
"output-count": [Txindex], "output-count": [Txindex],
"output-count-10p": [Height], "output-count-10p": [Height],
"output-count-25p": [Height], "output-count-25p": [Height],
@@ -228,12 +231,20 @@ export function createVecIdToIndexes() {
"output-value-average": [Dateindex, Height, Weekindex, Monthindex, Quarterindex, Yearindex, Decadeindex, Difficultyepoch], "output-value-average": [Dateindex, Height, Weekindex, Monthindex, Quarterindex, Yearindex, Decadeindex, Difficultyepoch],
"output-value-sum": [Dateindex, Height, Weekindex, Monthindex, Quarterindex, Yearindex, Decadeindex, Difficultyepoch], "output-value-sum": [Dateindex, Height, Weekindex, Monthindex, Quarterindex, Yearindex, Decadeindex, Difficultyepoch],
p2pk33addressbytes: [P2PK33index], p2pk33addressbytes: [P2PK33index],
p2pk33index: [P2PK33index],
p2pk65addressbytes: [P2PK65index], p2pk65addressbytes: [P2PK65index],
p2pk65index: [P2PK65index],
p2pkhaddressbytes: [P2PKHindex], p2pkhaddressbytes: [P2PKHindex],
p2pkhindex: [P2PKHindex],
p2shaddressbytes: [P2SHindex], p2shaddressbytes: [P2SHindex],
p2shindex: [P2SHindex],
p2traddressbytes: [P2TRindex], p2traddressbytes: [P2TRindex],
p2trindex: [P2TRindex],
p2wpkhaddressbytes: [P2WPKHindex], p2wpkhaddressbytes: [P2WPKHindex],
p2wpkhindex: [P2WPKHindex],
p2wshaddressbytes: [P2WSHindex], p2wshaddressbytes: [P2WSHindex],
p2wshindex: [P2WSHindex],
pushonlyindex: [Pushonlyindex],
quarterindex: [Monthindex, Quarterindex], quarterindex: [Monthindex, Quarterindex],
"real-date": [Height], "real-date": [Height],
subsidy: [Height], subsidy: [Height],
@@ -322,8 +333,11 @@ export function createVecIdToIndexes() {
"tx-weight-median": [Height], "tx-weight-median": [Height],
"tx-weight-min": [Dateindex, Height, Weekindex, Monthindex, Quarterindex, Yearindex, Decadeindex, Difficultyepoch], "tx-weight-min": [Dateindex, Height, Weekindex, Monthindex, Quarterindex, Yearindex, Decadeindex, Difficultyepoch],
txid: [Txindex], txid: [Txindex],
txoutindex: [Txinindex], txindex: [Txindex],
txinindex: [Txinindex],
txoutindex: [Txinindex, Txoutindex],
txversion: [Txindex], txversion: [Txindex],
unknownindex: [Unknownindex],
value: [Txinindex, Txoutindex], value: [Txinindex, Txoutindex],
vbytes: [Height], vbytes: [Height],
vsize: [Txindex], vsize: [Txindex],

View File

@@ -27,4 +27,8 @@
.chart { .chart {
flex: 1; flex: 1;
} }
> * {
z-index: 30;
}
} }

View File

@@ -0,0 +1,126 @@
#database {
width: 100%;
display: flex;
flex-direction: column;
gap: 2rem;
padding: var(--main-padding);
> div {
display: flex;
font-size: var(--font-size-sm);
margin-left: var(--negative-main-padding);
margin-right: var(--negative-main-padding);
table {
z-index: 10;
border-top-width: 1px;
border-style: dashed !important;
/* width: 100%; */
line-height: var(--line-height-sm);
text-transform: uppercase;
table-layout: auto;
border-collapse: separate;
border-spacing: 0;
/* border: 3px solid purple; */
/* min-height: 100%; */
}
th {
font-weight: 600;
}
th,
td {
/* border-top: 1px; */
border-right: 1px;
border-bottom: 1px;
border-color: var(--off-color);
border-style: dashed !important;
padding: 0.25rem 1rem;
}
th:first-child {
padding-left: var(--main-padding);
}
th[scope="col"] {
position: sticky;
top: 0;
background-color: var(--background-color);
> div {
display: flex;
flex-direction: column;
padding-top: 0.275rem;
> div {
display: flex;
gap: 0.25rem;
text-transform: lowercase;
color: var(--off-color);
text-align: left;
gap: 1rem;
> span {
width: 100%;
}
> button {
padding: 0 0.25rem;
margin: 0 -0.25rem;
font-size: 1rem;
line-height: 0;
}
}
}
&:first-child {
button {
display: none;
}
}
&:nth-child(2) {
button:nth-of-type(1) {
display: none;
}
}
&:last-child {
button:nth-of-type(2) {
display: none;
}
}
}
/* select {
width: 100%;
} */
tbody {
text-align: right;
}
> button {
padding: 1rem;
min-width: 10rem;
display: flex;
flex-direction: column;
flex: 1;
position: relative;
border-top-width: 1px;
width: 100%;
/* border-right-width: 1px; */
border-bottom-width: 1px;
border-style: dashed !important;
> span {
text-align: left;
position: sticky;
top: 2rem;
left: 0;
right: 0;
}
}
}
}