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

View File

@@ -31,11 +31,11 @@ brk_query = { version = "0", path = "crates/brk_query" }
brk_server = { version = "0", path = "crates/brk_server" }
brk_vec = { version = "0", path = "crates/brk_vec" }
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"
derive_deref = "1.1.1"
fjall = "2.8.0"
jiff = "0.2.8"
fjall = "2.9.0"
jiff = "0.2.9"
log = { version = "0.4.27" }
minreq = { version = "2.13.4", features = ["https", "serde_json"] }
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).
- 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
- Updates delivered at your convenience
- Optional subdomains: `*.bitcoinresearchkit.org`, `*.kibo.money` and `*.satonomics.xyz`

View File

@@ -154,6 +154,30 @@ where
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>(
&mut self,
max_from: A,

View File

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

View File

@@ -1,8 +1,10 @@
use std::{fs, ops::Deref, path::Path};
use brk_core::{
Date, Dateindex, Decadeindex, Difficultyepoch, Halvingepoch, Height, Monthindex, Quarterindex,
Timestamp, Txindex, Txinindex, Txoutindex, Weekindex, Yearindex,
Addressindex, Date, Dateindex, Decadeindex, Difficultyepoch, Emptyindex, Halvingepoch, Height,
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_indexer::Indexer;
@@ -12,6 +14,7 @@ use super::ComputedVec;
#[derive(Clone)]
pub struct Vecs {
pub addressindex_to_addressindex: ComputedVec<Addressindex, Addressindex>,
pub dateindex_to_date: ComputedVec<Dateindex, Date>,
pub dateindex_to_dateindex: ComputedVec<Dateindex, Dateindex>,
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_last_height: ComputedVec<Difficultyepoch, Height>,
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_halvingepoch: ComputedVec<Halvingepoch, Halvingepoch>,
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_timestamp: ComputedVec<Monthindex, Timestamp>,
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_last_monthindex: ComputedVec<Quarterindex, Monthindex>,
pub quarterindex_to_quarterindex: ComputedVec<Quarterindex, Quarterindex>,
pub quarterindex_to_timestamp: ComputedVec<Quarterindex, Timestamp>,
pub txindex_to_last_txinindex: ComputedVec<Txindex, Txinindex>,
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_last_dateindex: ComputedVec<Weekindex, Dateindex>,
pub weekindex_to_timestamp: ComputedVec<Weekindex, Timestamp>,
@@ -307,6 +325,86 @@ impl Vecs {
Version::ZERO,
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 txoutindexes_count = indexer_vecs.txoutindex_to_addressindex.len();
self.height_to_height.compute_transform(
self.height_to_height.compute_range(
starting_indexes.height,
indexer_vecs.height_to_timestamp.mut_vec(),
|(h, ..)| (h, h),
|h| (h, h),
exit,
)?;
@@ -340,12 +438,12 @@ impl Vecs {
self.height_to_fixed_timestamp.compute_transform(
starting_indexes.height,
indexer_vecs.height_to_timestamp.mut_vec(),
|(h, d, s, ..)| {
let d = h
|(h, timestamp, s, ..)| {
let timestamp = h
.decremented()
.and_then(|h| s.unwrap_cached_get(h))
.map_or(d, |prev_d| prev_d.max(d));
(h, d)
.map_or(timestamp, |prev_d| prev_d.max(timestamp));
(h, timestamp)
},
exit,
)?;
@@ -397,17 +495,17 @@ impl Vecs {
exit,
)?;
self.dateindex_to_dateindex.compute_transform(
self.dateindex_to_dateindex.compute_range(
starting_dateindex,
self.dateindex_to_first_height.mut_vec(),
|(di, ..)| (di, di),
|di| (di, di),
exit,
)?;
self.dateindex_to_date.compute_transform(
self.dateindex_to_date.compute_range(
starting_dateindex,
self.dateindex_to_dateindex.mut_vec(),
|(di, ..)| (di, Date::from(di)),
|di| (di, Date::from(di)),
exit,
)?;
@@ -448,10 +546,10 @@ impl Vecs {
.unwrap_cached_get(starting_dateindex)
.unwrap_or_default();
self.dateindex_to_weekindex.compute_transform(
self.dateindex_to_weekindex.compute_range(
starting_dateindex,
self.dateindex_to_dateindex.mut_vec(),
|(di, ..)| (di, Weekindex::from(di)),
|di| (di, Weekindex::from(di)),
exit,
)?;
@@ -470,10 +568,10 @@ impl Vecs {
exit,
)?;
self.weekindex_to_weekindex.compute_transform(
self.weekindex_to_weekindex.compute_range(
starting_weekindex,
self.weekindex_to_first_dateindex.mut_vec(),
|(wi, ..)| (wi, wi),
|wi| (wi, wi),
exit,
)?;
@@ -491,10 +589,10 @@ impl Vecs {
.unwrap_cached_get(starting_dateindex)
.unwrap_or_default();
self.dateindex_to_monthindex.compute_transform(
self.dateindex_to_monthindex.compute_range(
starting_dateindex,
self.dateindex_to_dateindex.mut_vec(),
|(di, ..)| (di, Monthindex::from(di)),
|di| (di, Monthindex::from(di)),
exit,
)?;
@@ -515,10 +613,10 @@ impl Vecs {
exit,
)?;
self.monthindex_to_monthindex.compute_transform(
self.monthindex_to_monthindex.compute_range(
starting_monthindex,
self.monthindex_to_first_dateindex.mut_vec(),
|(mi, ..)| (mi, mi),
|mi| (mi, mi),
exit,
)?;
@@ -536,10 +634,10 @@ impl Vecs {
.unwrap_cached_get(starting_monthindex)
.unwrap_or_default();
self.monthindex_to_quarterindex.compute_transform(
self.monthindex_to_quarterindex.compute_range(
starting_monthindex,
self.monthindex_to_monthindex.mut_vec(),
|(mi, ..)| (mi, Quarterindex::from(mi)),
|mi| (mi, Quarterindex::from(mi)),
exit,
)?;
@@ -560,10 +658,10 @@ impl Vecs {
exit,
)?;
self.quarterindex_to_quarterindex.compute_transform(
self.quarterindex_to_quarterindex.compute_range(
starting_quarterindex,
self.quarterindex_to_first_monthindex.mut_vec(),
|(yi, ..)| (yi, yi),
|i| (i, i),
exit,
)?;
@@ -581,10 +679,10 @@ impl Vecs {
.unwrap_cached_get(starting_monthindex)
.unwrap_or_default();
self.monthindex_to_yearindex.compute_transform(
self.monthindex_to_yearindex.compute_range(
starting_monthindex,
self.monthindex_to_monthindex.mut_vec(),
|(mi, ..)| (mi, Yearindex::from(mi)),
|i| (i, Yearindex::from(i)),
exit,
)?;
@@ -605,10 +703,10 @@ impl Vecs {
exit,
)?;
self.yearindex_to_yearindex.compute_transform(
self.yearindex_to_yearindex.compute_range(
starting_yearindex,
self.yearindex_to_first_monthindex.mut_vec(),
|(yi, ..)| (yi, yi),
|i| (i, i),
exit,
)?;
@@ -626,10 +724,10 @@ impl Vecs {
.unwrap_cached_get(starting_yearindex)
.unwrap_or_default();
self.yearindex_to_decadeindex.compute_transform(
self.yearindex_to_decadeindex.compute_range(
starting_yearindex,
self.yearindex_to_yearindex.mut_vec(),
|(yi, ..)| (yi, Decadeindex::from(yi)),
|i| (i, Decadeindex::from(i)),
exit,
)?;
@@ -648,10 +746,10 @@ impl Vecs {
exit,
)?;
self.decadeindex_to_decadeindex.compute_transform(
self.decadeindex_to_decadeindex.compute_range(
starting_decadeindex,
self.decadeindex_to_first_yearindex.mut_vec(),
|(di, ..)| (di, di),
|i| (i, i),
exit,
)?;
@@ -669,10 +767,10 @@ impl Vecs {
.unwrap_cached_get(decremented_starting_height)
.unwrap_or_default();
self.height_to_difficultyepoch.compute_transform(
self.height_to_difficultyepoch.compute_range(
starting_indexes.height,
self.height_to_height.mut_vec(),
|(h, ..)| (h, Difficultyepoch::from(h)),
|h| (h, Difficultyepoch::from(h)),
exit,
)?;
@@ -691,10 +789,10 @@ impl Vecs {
exit,
)?;
self.difficultyepoch_to_difficultyepoch.compute_transform(
self.difficultyepoch_to_difficultyepoch.compute_range(
starting_difficultyepoch,
self.difficultyepoch_to_first_height.mut_vec(),
|(de, ..)| (de, de),
|i| (i, i),
exit,
)?;
@@ -717,10 +815,10 @@ impl Vecs {
.unwrap_cached_get(decremented_starting_height)
.unwrap_or_default();
self.height_to_halvingepoch.compute_transform(
self.height_to_halvingepoch.compute_range(
starting_indexes.height,
self.height_to_height.mut_vec(),
|(h, ..)| (h, Halvingepoch::from(h)),
|h| (h, Halvingepoch::from(h)),
exit,
)?;
@@ -739,10 +837,10 @@ impl Vecs {
exit,
)?;
self.halvingepoch_to_halvingepoch.compute_transform(
self.halvingepoch_to_halvingepoch.compute_range(
starting_halvingepoch,
self.halvingepoch_to_first_height.mut_vec(),
|(he, ..)| (he, he),
|i| (i, i),
exit,
)?;
@@ -758,6 +856,105 @@ impl Vecs {
// 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 {
indexes: starting_indexes,
dateindex: starting_dateindex,
@@ -821,6 +1018,22 @@ impl Vecs {
self.quarterindex_to_last_monthindex.any_vec(),
self.quarterindex_to_quarterindex.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;
-webkit-text-size-adjust: 100%;
tab-size: 4;
font-family: "Geist mono", ui-sans-serif, system-ui, sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol",
"Noto Color Emoji";
font-family: "Geist mono", ui-monospace, SFMono-Regular, Menlo, Monaco,
Consolas, "Liberation Mono", "Courier New", monospace;
font-feature-settings: "ss03";
-webkit-tap-highlight-color: transparent;
}
@@ -134,6 +133,7 @@
letter-spacing: inherit;
color: inherit;
background: transparent;
text-transform: inherit;
}
button,
@@ -590,7 +590,7 @@
> fieldset {
width: 100%;
display: flex;
gap: 1rem;
gap: 1.25rem;
> label {
pointer-events: auto;
@@ -626,6 +626,7 @@
}
select {
cursor: pointer;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
@@ -884,7 +885,7 @@
bottom: 0;
left: 0;
right: 0;
z-index: 10;
z-index: 20;
pointer-events: none;
}
.shadow-left {
@@ -979,7 +980,6 @@
display: flex;
flex-direction: column;
min-height: 0;
z-index: 20;
flex: 1;
margin-top: 2rem;
margin-bottom: 1rem;
@@ -1607,9 +1607,8 @@
</main>
<aside id="aside">
<div id="charts" hidden></div>
<div id="database" hidden></div>
<div id="simulation" hidden></div>
<div id="live-price" hidden></div>
<div id="moscow-time" hidden></div>
</aside>
<div id="share-div" hidden>
<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 {Index} args.index
* @param {Utilities} args.utils
* @param {Elements} args.elements
* @param {DeepPartial<ChartOptions>} [args.options]
*/
function createLightweightChart({
@@ -40,13 +41,15 @@ export default import("./v5.0.5-treeshaked/script.js").then((lc) => {
colors,
index,
utils,
elements,
options: _options = {},
}) {
console.log(elements.style.fontFamily);
/** @satisfies {DeepPartial<ChartOptions>} */
const options = {
autoSize: true,
layout: {
fontFamily: "Geist mono",
fontFamily: elements.style.fontFamily,
// fontSize: 13,
background: { color: "transparent" },
attributionLogo: false,
@@ -134,6 +137,7 @@ export default import("./v5.0.5-treeshaked/script.js").then((lc) => {
* @param {Signals} args.signals
* @param {Colors} args.colors
* @param {Utilities} args.utils
* @param {Elements} args.elements
* @param {VecsResources} args.vecsResources
* @param {Owner | null} [args.owner]
* @param {true} [args.fitContentOnResize]
@@ -144,6 +148,7 @@ export default import("./v5.0.5-treeshaked/script.js").then((lc) => {
signals,
colors,
utils,
elements,
id,
vecsResources,
owner: _owner,
@@ -281,6 +286,7 @@ export default import("./v5.0.5-treeshaked/script.js").then((lc) => {
signals,
colors,
utils,
elements,
});
if (fitContentOnResize) {

View File

@@ -38,6 +38,7 @@ export function init({
id: "charts",
utils,
vecsResources,
elements,
});
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
/**
* @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 * 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"
@@ -9,7 +9,38 @@
* @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 { 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() {
@@ -113,22 +144,6 @@ function createUtils() {
if (!element) throw `Element with id = "${id}" should exist`;
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
*/
@@ -206,14 +221,14 @@ function createUtils() {
},
/**
* @param {Object} arg
* @param {string} arg.text
* @param {string | HTMLElement} arg.inside
* @param {string} arg.title
* @param {VoidFunction} arg.onClick
*/
createButtonElement({ text, onClick, title }) {
createButtonElement({ inside: text, onClick, title }) {
const button = window.document.createElement("button");
button.innerHTML = text;
button.append(text);
button.title = title;
@@ -404,128 +419,6 @@ function createUtils() {
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 {1 | 2 | 3} [args.level]
@@ -556,13 +449,16 @@ function createUtils() {
return option;
},
/**
* @template {{name: string; value: string}} T
* @param {Object} param0
* @param {string} param0.id
* @param {(({name: string; value: string} & T) | {name: string; list: ({name: string; value: string} & T)[]})[]} param0.list
* @param {Signal<T>} param0.signal
* @template {string} Name
* @template {string} Value
* @template {{name: Name; value: Value}} T
* @param {Object} args
* @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");
select.name = id;
select.id = id;
@@ -584,7 +480,7 @@ function createUtils() {
select.append(this.createOption(anyOption));
setters[anyOption.value] = () => signal.set(() => anyOption);
}
if (index !== list.length - 1) {
if (deep && index !== list.length - 1) {
select.append(window.document.createElement("hr"));
}
});
@@ -601,30 +497,6 @@ function createUtils() {
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 {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 = {
numberToUSFormat,
/** @param {number} value */
@@ -1298,6 +1243,7 @@ function createUtils() {
runWhenIdle,
getNumberOfDaysBetweenTwoDates,
stringToId,
vecidToUnit,
};
}
/** @typedef {ReturnType<typeof createUtils>} Utilities */
@@ -1435,6 +1381,7 @@ function getElements() {
selectors: getElementById("frame-selectors"),
style: getComputedStyle(window.document.documentElement),
charts: getElementById("charts"),
database: getElementById("database"),
simulation: getElementById("simulation"),
};
}
@@ -1920,6 +1867,12 @@ function main() {
optionsPromise.then(async ({ initOptions }) => {
const vecIdToIndexes = createVecIdToIndexes();
if (env.localhost) {
Object.keys(vecIdToIndexes).forEach((id) => {
utils.vecidToUnit(/** @type {VecId} */ (id));
});
}
function initDark() {
const preferredColorSchemeMatchMedia = window.matchMedia(
"(prefers-color-scheme: dark)",
@@ -2003,7 +1956,8 @@ function main() {
undefined
);
let firstTimeLoadingChart = true;
let firstTimeLoadingSim = true;
let firstTimeLoadingDatabase = true;
let firstTimeLoadingSimulation = true;
signals.createEffect(options.selected, (option) => {
if (previousElement) {
@@ -2036,7 +1990,9 @@ function main() {
colors,
elements,
lightweightCharts,
selected: /** @type {any} */ (lastChartOption),
selected: /** @type {Accessor<ChartOption>} */ (
lastChartOption
),
signals,
utils,
webSockets,
@@ -2052,15 +2008,38 @@ function main() {
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": {
element = elements.simulation;
lastSimulationOption.set(option);
if (firstTimeLoadingSim) {
if (firstTimeLoadingSimulation) {
const lightweightCharts = packages.lightweightCharts();
const simulationScript = import("./simulation.js");
utils.dom.importStyleAndThen(
"/styles/simulation.css",
() =>
@@ -2080,7 +2059,7 @@ function main() {
),
);
}
firstTimeLoadingSim = false;
firstTimeLoadingSimulation = false;
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
* @property {string} title
* @property {boolean} [defaultActive]
@@ -85,6 +63,14 @@
*
* @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
* @property {"simulation"} kind
* @property {string} title
@@ -102,9 +88,9 @@
*
* @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
* @property {string} name
@@ -542,6 +528,11 @@ function createPartialOptions(colors) {
},
],
},
{
kind: "database",
title: "Database",
name: "Database",
},
{
name: "Simulations",
tree: [
@@ -677,41 +668,7 @@ export function initOptions({
*/
function arrayToRecord(id, arr = []) {
return (arr || []).reduce((record, blueprint) => {
const key = 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");
}
const unit = utils.vecidToUnit(blueprint.key);
record[unit] ??= [];
record[unit].push(blueprint);
return record;
@@ -733,7 +690,7 @@ export function initOptions({
if (option.qrcode) {
return utils.dom.createButtonElement({
text: option.name,
inside: option.name,
title: option.title,
onClick: () => {
qrcode.set(option.url);
@@ -918,16 +875,17 @@ export function initOptions({
/** @type {Option} */
let option;
/** @type {string} */
let id;
/** @type {Option["kind"]} */
let kind;
/** @type {string} */
let title;
if ("kind" in anyPartial && anyPartial.kind === "simulation") {
if ("kind" in anyPartial && anyPartial.kind === "database") {
option = /** @satisfies {DatabaseOption} */ ({
kind: anyPartial.kind,
id: anyPartial.kind,
name: anyPartial.name,
path: path || [],
title: anyPartial.title,
});
} else if ("kind" in anyPartial && anyPartial.kind === "simulation") {
option = /** @satisfies {SimulationOption} */ ({
kind: "simulation",
kind: anyPartial.kind,
id: anyPartial.kind,
name: anyPartial.name,
path: path || [],

View File

@@ -30,6 +30,155 @@ export function init({
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");
simulationElement.append(parametersElement);
const resultsElement = window.document.createElement("div");
@@ -276,8 +425,8 @@ export function init({
}),
description:
"The amount of dollars you have ready on the exchange on day one.",
input: utils.dom.createResetableInput(
utils.dom.createInputDollar({
input: dom.createResetableInput(
dom.createInputDollar({
id: "simulation-dollars-initial",
title: "Initial Dollar Amount",
signal: settings.dollars.initial.amount,
@@ -296,11 +445,12 @@ export function init({
}),
description:
"The frequency at which you'll top up your account at the exchange.",
input: utils.dom.createResetableInput(
input: dom.createResetableInput(
utils.dom.createSelect({
id: "top-up-frequency",
list: frequencies.list,
signal: settings.dollars.topUp.frenquency,
deep: true,
}),
),
}),
@@ -315,8 +465,8 @@ export function init({
}),
description:
"The recurrent amount of dollars you'll be transfering to said exchange.",
input: utils.dom.createResetableInput(
utils.dom.createInputDollar({
input: dom.createResetableInput(
dom.createInputDollar({
id: "simulation-dollars-top-up-amount",
title: "Top Up Dollar Amount",
signal: settings.dollars.topUp.amount,
@@ -335,8 +485,8 @@ export function init({
}),
description:
"The amount, if available, of dollars that will be used to buy Bitcoin on day one.",
input: utils.dom.createResetableInput(
utils.dom.createInputDollar({
input: dom.createResetableInput(
dom.createInputDollar({
id: "simulation-bitcoin-initial-investment",
title: "Initial Swap Amount",
signal: settings.bitcoin.investment.initial,
@@ -354,11 +504,12 @@ export function init({
text: "Investment Frequency",
}),
description: "The frequency at which you'll be buying Bitcoin.",
input: utils.dom.createResetableInput(
input: dom.createResetableInput(
utils.dom.createSelect({
id: "investment-frequency",
list: frequencies.list,
signal: settings.bitcoin.investment.frequency,
deep: true,
}),
),
}),
@@ -373,8 +524,8 @@ export function init({
}),
description:
"The recurrent amount, if available, of dollars that will be used to buy Bitcoin.",
input: utils.dom.createResetableInput(
utils.dom.createInputDollar({
input: dom.createResetableInput(
dom.createInputDollar({
id: "simulation-bitcoin-recurrent-investment",
title: "Bitcoin Recurrent Investment",
signal: settings.bitcoin.investment.recurrent,
@@ -392,8 +543,8 @@ export function init({
text: "Start",
}),
description: "The first day of the simulation.",
input: utils.dom.createResetableInput(
utils.dom.createInputDate({
input: dom.createResetableInput(
dom.createInputDate({
id: "simulation-inverval-start",
title: "First Simulation Date",
signal: settings.interval.start,
@@ -411,8 +562,8 @@ export function init({
text: "End",
}),
description: "The last day of the simulation.",
input: utils.dom.createResetableInput(
utils.dom.createInputDate({
input: dom.createResetableInput(
dom.createInputDate({
id: "simulation-inverval-end",
title: "Last Simulation Day",
signal: settings.interval.end,
@@ -430,8 +581,8 @@ export function init({
text: "Exchange",
}),
description: "The amount of trading fees (in %) at the exchange.",
input: utils.dom.createResetableInput(
utils.dom.createInputNumberElement({
input: dom.createResetableInput(
dom.createInputNumberElement({
id: "simulation-fees",
title: "Exchange Fees",
signal: settings.fees.percentage,
@@ -555,6 +706,7 @@ export function init({
fitContentOnResize: true,
vecsResources,
utils,
elements,
config: [
{
unit: "USD",
@@ -597,6 +749,7 @@ export function init({
id: `bitcoin`,
fitContentOnResize: true,
vecsResources,
elements,
utils,
config: [
{
@@ -621,6 +774,7 @@ export function init({
fitContentOnResize: true,
vecsResources,
utils,
elements,
config: [
{
unit: "USD",
@@ -650,6 +804,8 @@ export function init({
id: `return-ratio`,
fitContentOnResize: true,
utils,
elements,
config: [
{
unit: "USD",
@@ -672,10 +828,11 @@ export function init({
fitContentOnResize: true,
vecsResources,
utils,
elements,
owner,
config: [
{
unit: "Percentage",
unit: "%",
blueprints: [
{
title: "Profitable Days Ratio",

View File

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

View File

@@ -27,4 +27,8 @@
.chart {
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;
}
}
}
}