mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-06-11 07:23:32 -07:00
global: snapshot
This commit is contained in:
Generated
+12
-21
@@ -1444,13 +1444,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.5"
|
||||
version = "1.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb"
|
||||
checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"libz-rs-sys",
|
||||
"miniz_oxide",
|
||||
"zlib-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1637,9 +1637,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.16"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
@@ -1954,9 +1954,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "importmap"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f6515f68008bdbc79963205f7e6dab4bb4564a1ca79ae22238d0ae8edb2528f"
|
||||
checksum = "8d13d6361899f14d58146b6214c07e63cda8270c3ef3b8c30626f8e20e6766eb"
|
||||
dependencies = [
|
||||
"rapidhash",
|
||||
"serde",
|
||||
@@ -2145,15 +2145,6 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libz-rs-sys"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c10501e7805cee23da17c7790e59df2870c0d4043ec6d03f67d31e2b53e77415"
|
||||
dependencies = [
|
||||
"zlib-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.11.0"
|
||||
@@ -2722,7 +2713,7 @@ version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
"getrandom 0.2.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2802,7 +2793,7 @@ version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
|
||||
dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
"getrandom 0.2.17",
|
||||
"libredox",
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
@@ -2864,7 +2855,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"getrandom 0.2.16",
|
||||
"getrandom 0.2.17",
|
||||
"libc",
|
||||
"untrusted",
|
||||
"windows-sys 0.52.0",
|
||||
@@ -4237,9 +4228,9 @@ checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3"
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.12"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8"
|
||||
checksum = "ac93432f5b761b22864c774aac244fa5c0fd877678a4c37ebf6cf42208f9c9ec"
|
||||
|
||||
[[package]]
|
||||
name = "zopfli"
|
||||
|
||||
@@ -13,7 +13,7 @@ Single dependency to access any BRK component. Enable only what you need via fea
|
||||
brk = { version = "0.1", features = ["query", "types"] }
|
||||
```
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
use brk::query::Query;
|
||||
use brk::types::Height;
|
||||
```
|
||||
|
||||
@@ -162,11 +162,21 @@ pub fn analyze_pattern_level(child_names: &[(String, String)]) -> PatternAnalysi
|
||||
positions.insert(field_name.clone(), FieldNamePosition::Identity);
|
||||
}
|
||||
|
||||
// Use the first name as base (they're all independent)
|
||||
let base = child_names
|
||||
.first()
|
||||
.map(|(_, n)| n.clone())
|
||||
.unwrap_or_default();
|
||||
// Check if all fields are "true Identity" (field_name == effective_name)
|
||||
// In that case, the base should be empty since metrics are accessed directly by field name
|
||||
let all_true_identity = child_names
|
||||
.iter()
|
||||
.all(|(field_name, effective)| field_name == effective);
|
||||
|
||||
let base = if all_true_identity {
|
||||
String::new()
|
||||
} else {
|
||||
// Use the first name as base (they're all independent but have different names)
|
||||
child_names
|
||||
.first()
|
||||
.map(|(_, n)| n.clone())
|
||||
.unwrap_or_default()
|
||||
};
|
||||
|
||||
PatternAnalysis {
|
||||
common: CommonDenominator::None,
|
||||
|
||||
@@ -67,43 +67,128 @@ class BrkError extends Error {{
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {{Object}} MetricEndpoint
|
||||
* @property {{(onUpdate?: (value: MetricData<T>) => void) => Promise<MetricData<T>>}} get - Fetch all data points
|
||||
* @property {{(start?: number, end?: number, onUpdate?: (value: MetricData<T>) => void) => Promise<MetricData<T>>}} range - Fetch data in range
|
||||
* @typedef {{Object}} MetricEndpointBuilder
|
||||
* @property {{(n: number) => RangeBuilder<T>}} first - Fetch first n data points
|
||||
* @property {{(n: number) => RangeBuilder<T>}} last - Fetch last n data points
|
||||
* @property {{(start: number, end: number) => RangeBuilder<T>}} range - Set explicit range [start, end)
|
||||
* @property {{(start: number) => FromBuilder<T>}} from - Set start position, chain with take() or to()
|
||||
* @property {{(end: number) => ToBuilder<T>}} to - Set end position, chain with takeLast() or from()
|
||||
* @property {{(onUpdate?: (value: MetricData<T>) => void) => Promise<MetricData<T>>}} json - Execute and return JSON (all data)
|
||||
* @property {{() => Promise<string>}} csv - Execute and return CSV (all data)
|
||||
* @property {{string}} path - The endpoint path
|
||||
*/
|
||||
/** @typedef {{MetricEndpoint<unknown>}} AnyMetricEndpoint */
|
||||
/** @typedef {{MetricEndpointBuilder<unknown>}} AnyMetricEndpointBuilder */
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {{Object}} FromBuilder
|
||||
* @property {{(n: number) => RangeBuilder<T>}} take - Take n items from start position
|
||||
* @property {{(end: number) => RangeBuilder<T>}} to - Set end position
|
||||
* @property {{(onUpdate?: (value: MetricData<T>) => void) => Promise<MetricData<T>>}} json - Execute and return JSON
|
||||
* @property {{() => Promise<string>}} csv - Execute and return CSV
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {{Object}} ToBuilder
|
||||
* @property {{(n: number) => RangeBuilder<T>}} takeLast - Take last n items before end position
|
||||
* @property {{(start: number) => RangeBuilder<T>}} from - Set start position
|
||||
* @property {{(onUpdate?: (value: MetricData<T>) => void) => Promise<MetricData<T>>}} json - Execute and return JSON
|
||||
* @property {{() => Promise<string>}} csv - Execute and return CSV
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {{Object}} RangeBuilder
|
||||
* @property {{(onUpdate?: (value: MetricData<T>) => void) => Promise<MetricData<T>>}} json - Execute and return JSON
|
||||
* @property {{() => Promise<string>}} csv - Execute and return CSV
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {{Object}} MetricPattern
|
||||
* @property {{string}} name - The metric name
|
||||
* @property {{Partial<Record<Index, MetricEndpoint<T>>>}} by - Index endpoints (lazy getters)
|
||||
* @property {{Partial<Record<Index, MetricEndpointBuilder<T>>>}} by - Index endpoints (lazy getters)
|
||||
* @property {{() => Index[]}} indexes - Get the list of available indexes
|
||||
* @property {{(index: Index) => MetricEndpoint<T>|undefined}} get - Get an endpoint for a specific index
|
||||
* @property {{(index: Index) => MetricEndpointBuilder<T>|undefined}} get - Get an endpoint for a specific index
|
||||
*/
|
||||
|
||||
/** @typedef {{MetricPattern<unknown>}} AnyMetricPattern */
|
||||
|
||||
/**
|
||||
* Create an endpoint for a metric index.
|
||||
* Create a metric endpoint builder with typestate pattern.
|
||||
* @template T
|
||||
* @param {{BrkClientBase}} client
|
||||
* @param {{string}} name - The metric vec name
|
||||
* @param {{Index}} index - The index name
|
||||
* @returns {{MetricEndpoint<T>}}
|
||||
* @returns {{MetricEndpointBuilder<T>}}
|
||||
*/
|
||||
function _endpoint(client, name, index) {{
|
||||
const p = `/api/metric/${{name}}/${{index}}`;
|
||||
return {{
|
||||
get: (onUpdate) => client.getJson(p, onUpdate),
|
||||
range: (start, end, onUpdate) => {{
|
||||
const params = new URLSearchParams();
|
||||
if (start !== undefined) params.set('start', String(start));
|
||||
if (end !== undefined) params.set('end', String(end));
|
||||
const query = params.toString();
|
||||
return client.getJson(query ? `${{p}}?${{query}}` : p, onUpdate);
|
||||
|
||||
/**
|
||||
* @param {{number}} [start]
|
||||
* @param {{number}} [end]
|
||||
* @param {{string}} [format]
|
||||
* @returns {{string}}
|
||||
*/
|
||||
const buildPath = (start, end, format) => {{
|
||||
const params = new URLSearchParams();
|
||||
if (start !== undefined) params.set('start', String(start));
|
||||
if (end !== undefined) params.set('end', String(end));
|
||||
if (format) params.set('format', format);
|
||||
const query = params.toString();
|
||||
return query ? `${{p}}?${{query}}` : p;
|
||||
}};
|
||||
|
||||
/**
|
||||
* @param {{number}} [start]
|
||||
* @param {{number}} [end]
|
||||
* @returns {{RangeBuilder<T>}}
|
||||
*/
|
||||
const rangeBuilder = (start, end) => ({{
|
||||
json(/** @type {{((value: MetricData<T>) => void) | undefined}} */ onUpdate) {{
|
||||
return client.getJson(buildPath(start, end), onUpdate);
|
||||
}},
|
||||
csv() {{ return client.getText(buildPath(start, end, 'csv')); }},
|
||||
}});
|
||||
|
||||
/**
|
||||
* @param {{number}} start
|
||||
* @returns {{FromBuilder<T>}}
|
||||
*/
|
||||
const fromBuilder = (start) => ({{
|
||||
take(/** @type {{number}} */ n) {{ return rangeBuilder(start, start + n); }},
|
||||
to(/** @type {{number}} */ end) {{ return rangeBuilder(start, end); }},
|
||||
json(/** @type {{((value: MetricData<T>) => void) | undefined}} */ onUpdate) {{
|
||||
return client.getJson(buildPath(start, undefined), onUpdate);
|
||||
}},
|
||||
csv() {{ return client.getText(buildPath(start, undefined, 'csv')); }},
|
||||
}});
|
||||
|
||||
/**
|
||||
* @param {{number}} end
|
||||
* @returns {{ToBuilder<T>}}
|
||||
*/
|
||||
const toBuilder = (end) => ({{
|
||||
takeLast(/** @type {{number}} */ n) {{ return rangeBuilder(end - n, end); }},
|
||||
from(/** @type {{number}} */ start) {{ return rangeBuilder(start, end); }},
|
||||
json(/** @type {{((value: MetricData<T>) => void) | undefined}} */ onUpdate) {{
|
||||
return client.getJson(buildPath(undefined, end), onUpdate);
|
||||
}},
|
||||
csv() {{ return client.getText(buildPath(undefined, end, 'csv')); }},
|
||||
}});
|
||||
|
||||
return {{
|
||||
first(/** @type {{number}} */ n) {{ return rangeBuilder(undefined, n); }},
|
||||
last(/** @type {{number}} */ n) {{ return rangeBuilder(-n, undefined); }},
|
||||
range(/** @type {{number}} */ start, /** @type {{number}} */ end) {{ return rangeBuilder(start, end); }},
|
||||
from(/** @type {{number}} */ start) {{ return fromBuilder(start); }},
|
||||
to(/** @type {{number}} */ end) {{ return toBuilder(end); }},
|
||||
json(/** @type {{((value: MetricData<T>) => void) | undefined}} */ onUpdate) {{
|
||||
return client.getJson(buildPath(), onUpdate);
|
||||
}},
|
||||
csv() {{ return client.getText(buildPath(undefined, undefined, 'csv')); }},
|
||||
get path() {{ return p; }},
|
||||
}};
|
||||
}}
|
||||
@@ -272,7 +357,7 @@ pub fn generate_index_accessors(output: &mut String, patterns: &[IndexSetPattern
|
||||
let by_fields: Vec<String> = pattern
|
||||
.indexes
|
||||
.iter()
|
||||
.map(|idx| format!("{}: MetricEndpoint<T>", idx.serialize_long()))
|
||||
.map(|idx| format!("{}: MetricEndpointBuilder<T>", idx.serialize_long()))
|
||||
.collect();
|
||||
let by_type = format!("{{ {} }}", by_fields.join(", "));
|
||||
|
||||
@@ -280,7 +365,7 @@ pub fn generate_index_accessors(output: &mut String, patterns: &[IndexSetPattern
|
||||
writeln!(output, " * @template T").unwrap();
|
||||
writeln!(
|
||||
output,
|
||||
" * @typedef {{{{ name: string, by: {}, indexes: () => Index[], get: (index: Index) => MetricEndpoint<T>|undefined }}}} {}",
|
||||
" * @typedef {{{{ name: string, by: {}, indexes: () => Index[], get: (index: Index) => MetricEndpointBuilder<T>|undefined }}}} {}",
|
||||
by_type, pattern.name
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -127,6 +127,20 @@ pub fn generate_main_client(
|
||||
writeln!(output, " }};").unwrap();
|
||||
writeln!(output, " }}\n").unwrap();
|
||||
|
||||
writeln!(output, " /**").unwrap();
|
||||
writeln!(output, " * Create a dynamic metric endpoint builder for any metric/index combination.").unwrap();
|
||||
writeln!(output, " *").unwrap();
|
||||
writeln!(output, " * Use this for programmatic access when the metric name is determined at runtime.").unwrap();
|
||||
writeln!(output, " * For type-safe access, use the `metrics` tree instead.").unwrap();
|
||||
writeln!(output, " *").unwrap();
|
||||
writeln!(output, " * @param {{string}} metric - The metric name").unwrap();
|
||||
writeln!(output, " * @param {{Index}} index - The index name").unwrap();
|
||||
writeln!(output, " * @returns {{MetricEndpointBuilder<unknown>}}").unwrap();
|
||||
writeln!(output, " */").unwrap();
|
||||
writeln!(output, " metric(metric, index) {{").unwrap();
|
||||
writeln!(output, " return _endpoint(this, metric, index);").unwrap();
|
||||
writeln!(output, " }}\n").unwrap();
|
||||
|
||||
generate_api_methods(output, endpoints);
|
||||
|
||||
writeln!(output, "}}\n").unwrap();
|
||||
|
||||
@@ -29,6 +29,16 @@ pub fn generate_main_client(output: &mut String, endpoints: &[Endpoint]) {
|
||||
writeln!(output, " self.metrics = MetricsTree(self)").unwrap();
|
||||
writeln!(output).unwrap();
|
||||
|
||||
// Generate metric() method for dynamic metric access
|
||||
writeln!(output, " def metric(self, metric: str, index: Index) -> MetricEndpointBuilder[Any]:").unwrap();
|
||||
writeln!(output, " \"\"\"Create a dynamic metric endpoint builder for any metric/index combination.").unwrap();
|
||||
writeln!(output).unwrap();
|
||||
writeln!(output, " Use this for programmatic access when the metric name is determined at runtime.").unwrap();
|
||||
writeln!(output, " For type-safe access, use the `metrics` tree instead.").unwrap();
|
||||
writeln!(output, " \"\"\"").unwrap();
|
||||
writeln!(output, " return MetricEndpointBuilder(self, metric, index)").unwrap();
|
||||
writeln!(output).unwrap();
|
||||
|
||||
// Generate API methods
|
||||
generate_api_methods(output, endpoints);
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ def _m(acc: str, s: str) -> str:
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Generate the MetricData and MetricEndpoint classes
|
||||
/// Generate the MetricData and MetricEndpointBuilder classes
|
||||
pub fn generate_endpoint_class(output: &mut String) {
|
||||
writeln!(
|
||||
output,
|
||||
@@ -156,36 +156,191 @@ pub fn generate_endpoint_class(output: &mut String) {
|
||||
AnyMetricData = MetricData[Any]
|
||||
|
||||
|
||||
class MetricEndpoint(Generic[T]):
|
||||
"""An endpoint for a specific metric + index combination."""
|
||||
class _EndpointConfig:
|
||||
"""Shared endpoint configuration."""
|
||||
client: BrkClientBase
|
||||
name: str
|
||||
index: Index
|
||||
start: Optional[int]
|
||||
end: Optional[int]
|
||||
|
||||
def __init__(self, client: BrkClientBase, name: str, index: str):
|
||||
self._client = client
|
||||
self._name = name
|
||||
self._index = index
|
||||
|
||||
def get(self) -> MetricData[T]:
|
||||
"""Fetch all data points for this metric/index."""
|
||||
return self._client.get_json(self.path())
|
||||
|
||||
def range(self, start: Optional[int] = None, end: Optional[int] = None) -> MetricData[T]:
|
||||
"""Fetch data points within a range."""
|
||||
params = []
|
||||
if start is not None:
|
||||
params.append(f"start={{start}}")
|
||||
if end is not None:
|
||||
params.append(f"end={{end}}")
|
||||
query = "&".join(params)
|
||||
p = self.path()
|
||||
return self._client.get_json(f"{{p}}?{{query}}" if query else p)
|
||||
def __init__(self, client: BrkClientBase, name: str, index: Index,
|
||||
start: Optional[int] = None, end: Optional[int] = None):
|
||||
self.client = client
|
||||
self.name = name
|
||||
self.index = index
|
||||
self.start = start
|
||||
self.end = end
|
||||
|
||||
def path(self) -> str:
|
||||
"""Get the endpoint path."""
|
||||
return f"/api/metric/{{self._name}}/{{self._index}}"
|
||||
return f"/api/metric/{{self.name}}/{{self.index}}"
|
||||
|
||||
def _build_path(self, format: Optional[str] = None) -> str:
|
||||
params = []
|
||||
if self.start is not None:
|
||||
params.append(f"start={{self.start}}")
|
||||
if self.end is not None:
|
||||
params.append(f"end={{self.end}}")
|
||||
if format is not None:
|
||||
params.append(f"format={{format}}")
|
||||
query = "&".join(params)
|
||||
p = self.path()
|
||||
return f"{{p}}?{{query}}" if query else p
|
||||
|
||||
def get_json(self) -> Any:
|
||||
return self.client.get_json(self._build_path())
|
||||
|
||||
def get_csv(self) -> str:
|
||||
return self.client.get_text(self._build_path(format='csv'))
|
||||
|
||||
|
||||
class RangeBuilder(Generic[T]):
|
||||
"""Final builder with range fully specified. Can only call json() or csv()."""
|
||||
|
||||
def __init__(self, config: _EndpointConfig):
|
||||
self._config = config
|
||||
|
||||
def json(self) -> MetricData[T]:
|
||||
"""Execute the query and return parsed JSON data."""
|
||||
return self._config.get_json()
|
||||
|
||||
def csv(self) -> str:
|
||||
"""Execute the query and return CSV data as a string."""
|
||||
return self._config.get_csv()
|
||||
|
||||
|
||||
class FromBuilder(Generic[T]):
|
||||
"""Builder after calling from(start). Can chain with take() or to()."""
|
||||
|
||||
def __init__(self, config: _EndpointConfig):
|
||||
self._config = config
|
||||
|
||||
def take(self, n: int) -> RangeBuilder[T]:
|
||||
"""Take n items from the start position."""
|
||||
start = self._config.start or 0
|
||||
return RangeBuilder(_EndpointConfig(
|
||||
self._config.client, self._config.name, self._config.index,
|
||||
start, start + n
|
||||
))
|
||||
|
||||
def to(self, end: int) -> RangeBuilder[T]:
|
||||
"""Set the end position."""
|
||||
return RangeBuilder(_EndpointConfig(
|
||||
self._config.client, self._config.name, self._config.index,
|
||||
self._config.start, end
|
||||
))
|
||||
|
||||
def json(self) -> MetricData[T]:
|
||||
"""Execute the query and return parsed JSON data (from start to end of data)."""
|
||||
return self._config.get_json()
|
||||
|
||||
def csv(self) -> str:
|
||||
"""Execute the query and return CSV data as a string."""
|
||||
return self._config.get_csv()
|
||||
|
||||
|
||||
class ToBuilder(Generic[T]):
|
||||
"""Builder after calling to(end). Can chain with take_last() or from()."""
|
||||
|
||||
def __init__(self, config: _EndpointConfig):
|
||||
self._config = config
|
||||
|
||||
def take_last(self, n: int) -> RangeBuilder[T]:
|
||||
"""Take last n items before the end position."""
|
||||
end = self._config.end or 0
|
||||
return RangeBuilder(_EndpointConfig(
|
||||
self._config.client, self._config.name, self._config.index,
|
||||
end - n, end
|
||||
))
|
||||
|
||||
def from_(self, start: int) -> RangeBuilder[T]:
|
||||
"""Set the start position."""
|
||||
return RangeBuilder(_EndpointConfig(
|
||||
self._config.client, self._config.name, self._config.index,
|
||||
start, self._config.end
|
||||
))
|
||||
|
||||
def json(self) -> MetricData[T]:
|
||||
"""Execute the query and return parsed JSON data (from start of data to end)."""
|
||||
return self._config.get_json()
|
||||
|
||||
def csv(self) -> str:
|
||||
"""Execute the query and return CSV data as a string."""
|
||||
return self._config.get_csv()
|
||||
|
||||
|
||||
class MetricEndpointBuilder(Generic[T]):
|
||||
"""Initial builder for metric endpoint queries.
|
||||
|
||||
Use method chaining to specify the data range, then call json() or csv() to execute.
|
||||
|
||||
Examples:
|
||||
# Get all data
|
||||
endpoint.json()
|
||||
|
||||
# Get last 10 points
|
||||
endpoint.last(10).json()
|
||||
|
||||
# Get range [100, 200)
|
||||
endpoint.range(100, 200).json()
|
||||
|
||||
# Get 10 points starting from position 100
|
||||
endpoint.from_(100).take(10).json()
|
||||
"""
|
||||
|
||||
def __init__(self, client: BrkClientBase, name: str, index: Index):
|
||||
self._config = _EndpointConfig(client, name, index)
|
||||
|
||||
def first(self, n: int) -> RangeBuilder[T]:
|
||||
"""Fetch the first n data points."""
|
||||
return RangeBuilder(_EndpointConfig(
|
||||
self._config.client, self._config.name, self._config.index,
|
||||
None, n
|
||||
))
|
||||
|
||||
def last(self, n: int) -> RangeBuilder[T]:
|
||||
"""Fetch the last n data points."""
|
||||
return RangeBuilder(_EndpointConfig(
|
||||
self._config.client, self._config.name, self._config.index,
|
||||
-n, None
|
||||
))
|
||||
|
||||
def range(self, start: int, end: int) -> RangeBuilder[T]:
|
||||
"""Set an explicit range [start, end)."""
|
||||
return RangeBuilder(_EndpointConfig(
|
||||
self._config.client, self._config.name, self._config.index,
|
||||
start, end
|
||||
))
|
||||
|
||||
def from_(self, start: int) -> FromBuilder[T]:
|
||||
"""Set the start position. Chain with take() or to()."""
|
||||
return FromBuilder(_EndpointConfig(
|
||||
self._config.client, self._config.name, self._config.index,
|
||||
start, None
|
||||
))
|
||||
|
||||
def to(self, end: int) -> ToBuilder[T]:
|
||||
"""Set the end position. Chain with take_last() or from_()."""
|
||||
return ToBuilder(_EndpointConfig(
|
||||
self._config.client, self._config.name, self._config.index,
|
||||
None, end
|
||||
))
|
||||
|
||||
def json(self) -> MetricData[T]:
|
||||
"""Execute the query and return parsed JSON data (all data)."""
|
||||
return self._config.get_json()
|
||||
|
||||
def csv(self) -> str:
|
||||
"""Execute the query and return CSV data as a string (all data)."""
|
||||
return self._config.get_csv()
|
||||
|
||||
def path(self) -> str:
|
||||
"""Get the base endpoint path."""
|
||||
return self._config.path()
|
||||
|
||||
|
||||
# Type alias for non-generic usage
|
||||
AnyMetricEndpoint = MetricEndpoint[Any]
|
||||
AnyMetricEndpointBuilder = MetricEndpointBuilder[Any]
|
||||
|
||||
|
||||
class MetricPattern(Protocol[T]):
|
||||
@@ -200,8 +355,8 @@ class MetricPattern(Protocol[T]):
|
||||
"""Get the list of available indexes for this metric."""
|
||||
...
|
||||
|
||||
def get(self, index: str) -> Optional[MetricEndpoint[T]]:
|
||||
"""Get an endpoint for a specific index, if supported."""
|
||||
def get(self, index: Index) -> Optional[MetricEndpointBuilder[T]]:
|
||||
"""Get an endpoint builder for a specific index, if supported."""
|
||||
...
|
||||
|
||||
"#
|
||||
@@ -237,10 +392,10 @@ pub fn generate_index_accessors(output: &mut String, patterns: &[IndexSetPattern
|
||||
for index in &pattern.indexes {
|
||||
let method_name = index_to_field_name(index);
|
||||
let index_name = index.serialize_long();
|
||||
writeln!(output, " def {}(self) -> MetricEndpoint[T]:", method_name).unwrap();
|
||||
writeln!(output, " def {}(self) -> MetricEndpointBuilder[T]:", method_name).unwrap();
|
||||
writeln!(
|
||||
output,
|
||||
" return MetricEndpoint(self._client, self._name, '{}')",
|
||||
" return MetricEndpointBuilder(self._client, self._name, '{}')",
|
||||
index_name
|
||||
)
|
||||
.unwrap();
|
||||
@@ -288,8 +443,8 @@ pub fn generate_index_accessors(output: &mut String, patterns: &[IndexSetPattern
|
||||
writeln!(output).unwrap();
|
||||
|
||||
// Generate get(index) method
|
||||
writeln!(output, " def get(self, index: str) -> Optional[MetricEndpoint[T]]:").unwrap();
|
||||
writeln!(output, " \"\"\"Get an endpoint for a specific index, if supported.\"\"\"").unwrap();
|
||||
writeln!(output, " def get(self, index: Index) -> Optional[MetricEndpointBuilder[T]]:").unwrap();
|
||||
writeln!(output, " \"\"\"Get an endpoint builder for a specific index, if supported.\"\"\"").unwrap();
|
||||
for (i, index) in pattern.indexes.iter().enumerate() {
|
||||
let method_name = index_to_field_name(index);
|
||||
let index_name = index.serialize_long();
|
||||
|
||||
@@ -38,6 +38,25 @@ impl BrkClient {{
|
||||
pub fn metrics(&self) -> &MetricsTree {{
|
||||
&self.metrics
|
||||
}}
|
||||
|
||||
/// Create a dynamic metric endpoint builder for any metric/index combination.
|
||||
///
|
||||
/// Use this for programmatic access when the metric name is determined at runtime.
|
||||
/// For type-safe access, use the `metrics()` tree instead.
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// let data = client.metric("realized_price", Index::Height)
|
||||
/// .last(10)
|
||||
/// .json::<f64>()?;
|
||||
/// ```
|
||||
pub fn metric(&self, metric: impl Into<Metric>, index: Index) -> MetricEndpointBuilder<serde_json::Value> {{
|
||||
MetricEndpointBuilder::new(
|
||||
self.base.clone(),
|
||||
Arc::from(metric.into().as_str()),
|
||||
index,
|
||||
)
|
||||
}}
|
||||
"#,
|
||||
VERSION = VERSION
|
||||
)
|
||||
|
||||
@@ -141,8 +141,8 @@ pub trait AnyMetricPattern {{
|
||||
|
||||
/// Generic trait for metric patterns with endpoint access.
|
||||
pub trait MetricPattern<T>: AnyMetricPattern {{
|
||||
/// Get an endpoint for a specific index, if supported.
|
||||
fn get(&self, index: Index) -> Option<Endpoint<T>>;
|
||||
/// Get an endpoint builder for a specific index, if supported.
|
||||
fn get(&self, index: Index) -> Option<MetricEndpointBuilder<T>>;
|
||||
}}
|
||||
|
||||
"#
|
||||
@@ -150,50 +150,199 @@ pub trait MetricPattern<T>: AnyMetricPattern {{
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Generate the Endpoint struct.
|
||||
/// Generate the MetricEndpointBuilder structs with typestate pattern.
|
||||
pub fn generate_endpoint(output: &mut String) {
|
||||
writeln!(
|
||||
output,
|
||||
r#"/// An endpoint for a specific metric + index combination.
|
||||
pub struct Endpoint<T> {{
|
||||
r#"/// Shared endpoint configuration.
|
||||
#[derive(Clone)]
|
||||
struct EndpointConfig {{
|
||||
client: Arc<BrkClientBase>,
|
||||
name: Arc<str>,
|
||||
index: Index,
|
||||
start: Option<i64>,
|
||||
end: Option<i64>,
|
||||
}}
|
||||
|
||||
impl EndpointConfig {{
|
||||
fn new(client: Arc<BrkClientBase>, name: Arc<str>, index: Index) -> Self {{
|
||||
Self {{ client, name, index, start: None, end: None }}
|
||||
}}
|
||||
|
||||
fn path(&self) -> String {{
|
||||
format!("/api/metric/{{}}/{{}}", self.name, self.index.serialize_long())
|
||||
}}
|
||||
|
||||
fn build_path(&self, format: Option<&str>) -> String {{
|
||||
let mut params = Vec::new();
|
||||
if let Some(s) = self.start {{ params.push(format!("start={{}}", s)); }}
|
||||
if let Some(e) = self.end {{ params.push(format!("end={{}}", e)); }}
|
||||
if let Some(fmt) = format {{ params.push(format!("format={{}}", fmt)); }}
|
||||
let p = self.path();
|
||||
if params.is_empty() {{ p }} else {{ format!("{{}}?{{}}", p, params.join("&")) }}
|
||||
}}
|
||||
|
||||
fn get_json<T: DeserializeOwned>(&self, format: Option<&str>) -> Result<T> {{
|
||||
self.client.get_json(&self.build_path(format))
|
||||
}}
|
||||
|
||||
fn get_text(&self, format: Option<&str>) -> Result<String> {{
|
||||
self.client.get_text(&self.build_path(format))
|
||||
}}
|
||||
}}
|
||||
|
||||
/// Initial builder for metric endpoint queries.
|
||||
///
|
||||
/// Use method chaining to specify the data range, then call `json()` or `csv()` to execute.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```ignore
|
||||
/// // Get all data
|
||||
/// endpoint.json()?;
|
||||
///
|
||||
/// // Get last 10 points
|
||||
/// endpoint.last(10).json()?;
|
||||
///
|
||||
/// // Get range [100, 200)
|
||||
/// endpoint.range(100, 200).json()?;
|
||||
///
|
||||
/// // Get 10 points starting from position 100
|
||||
/// endpoint.from(100).take(10).json()?;
|
||||
/// ```
|
||||
pub struct MetricEndpointBuilder<T> {{
|
||||
config: EndpointConfig,
|
||||
_marker: std::marker::PhantomData<T>,
|
||||
}}
|
||||
|
||||
impl<T: DeserializeOwned> Endpoint<T> {{
|
||||
impl<T: DeserializeOwned> MetricEndpointBuilder<T> {{
|
||||
pub fn new(client: Arc<BrkClientBase>, name: Arc<str>, index: Index) -> Self {{
|
||||
Self {{
|
||||
client,
|
||||
name,
|
||||
index,
|
||||
_marker: std::marker::PhantomData,
|
||||
}}
|
||||
Self {{ config: EndpointConfig::new(client, name, index), _marker: std::marker::PhantomData }}
|
||||
}}
|
||||
|
||||
/// Fetch all data points for this metric/index.
|
||||
pub fn get(&self) -> Result<MetricData<T>> {{
|
||||
self.client.get_json(&self.path())
|
||||
/// Fetch the first n data points.
|
||||
pub fn first(mut self, n: u64) -> RangeBuilder<T> {{
|
||||
self.config.end = Some(n as i64);
|
||||
RangeBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
|
||||
}}
|
||||
|
||||
/// Fetch data points within a range.
|
||||
pub fn range(&self, start: Option<i64>, end: Option<i64>) -> Result<MetricData<T>> {{
|
||||
let mut params = Vec::new();
|
||||
if let Some(s) = start {{ params.push(format!("start={{}}", s)); }}
|
||||
if let Some(e) = end {{ params.push(format!("end={{}}", e)); }}
|
||||
let p = self.path();
|
||||
let path = if params.is_empty() {{
|
||||
p
|
||||
}} else {{
|
||||
format!("{{}}?{{}}", p, params.join("&"))
|
||||
}};
|
||||
self.client.get_json(&path)
|
||||
/// Fetch the last n data points.
|
||||
pub fn last(mut self, n: u64) -> RangeBuilder<T> {{
|
||||
self.config.start = Some(-(n as i64));
|
||||
RangeBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
|
||||
}}
|
||||
|
||||
/// Get the endpoint path.
|
||||
/// Set an explicit range [start, end).
|
||||
pub fn range(mut self, start: i64, end: i64) -> RangeBuilder<T> {{
|
||||
self.config.start = Some(start);
|
||||
self.config.end = Some(end);
|
||||
RangeBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
|
||||
}}
|
||||
|
||||
/// Set the start position. Chain with `take(n)` or `to(end)`.
|
||||
pub fn from(mut self, start: i64) -> FromBuilder<T> {{
|
||||
self.config.start = Some(start);
|
||||
FromBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
|
||||
}}
|
||||
|
||||
/// Set the end position. Chain with `takeLast(n)` or `from(start)`.
|
||||
pub fn to(mut self, end: i64) -> ToBuilder<T> {{
|
||||
self.config.end = Some(end);
|
||||
ToBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
|
||||
}}
|
||||
|
||||
/// Execute the query and return parsed JSON data (all data).
|
||||
pub fn json(self) -> Result<MetricData<T>> {{
|
||||
self.config.get_json(None)
|
||||
}}
|
||||
|
||||
/// Execute the query and return CSV data as a string (all data).
|
||||
pub fn csv(self) -> Result<String> {{
|
||||
self.config.get_text(Some("csv"))
|
||||
}}
|
||||
|
||||
/// Get the base endpoint path.
|
||||
pub fn path(&self) -> String {{
|
||||
format!("/api/metric/{{}}/{{}}", self.name, self.index.serialize_long())
|
||||
self.config.path()
|
||||
}}
|
||||
}}
|
||||
|
||||
/// Builder after calling `from(start)`. Can chain with `take(n)` or `to(end)`.
|
||||
pub struct FromBuilder<T> {{
|
||||
config: EndpointConfig,
|
||||
_marker: std::marker::PhantomData<T>,
|
||||
}}
|
||||
|
||||
impl<T: DeserializeOwned> FromBuilder<T> {{
|
||||
/// Take n items from the start position.
|
||||
pub fn take(mut self, n: u64) -> RangeBuilder<T> {{
|
||||
let start = self.config.start.unwrap_or(0);
|
||||
self.config.end = Some(start + n as i64);
|
||||
RangeBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
|
||||
}}
|
||||
|
||||
/// Set the end position.
|
||||
pub fn to(mut self, end: i64) -> RangeBuilder<T> {{
|
||||
self.config.end = Some(end);
|
||||
RangeBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
|
||||
}}
|
||||
|
||||
/// Execute the query and return parsed JSON data (from start to end of data).
|
||||
pub fn json(self) -> Result<MetricData<T>> {{
|
||||
self.config.get_json(None)
|
||||
}}
|
||||
|
||||
/// Execute the query and return CSV data as a string.
|
||||
pub fn csv(self) -> Result<String> {{
|
||||
self.config.get_text(Some("csv"))
|
||||
}}
|
||||
}}
|
||||
|
||||
/// Builder after calling `to(end)`. Can chain with `takeLast(n)` or `from(start)`.
|
||||
pub struct ToBuilder<T> {{
|
||||
config: EndpointConfig,
|
||||
_marker: std::marker::PhantomData<T>,
|
||||
}}
|
||||
|
||||
impl<T: DeserializeOwned> ToBuilder<T> {{
|
||||
/// Take last n items before the end position.
|
||||
pub fn take_last(mut self, n: u64) -> RangeBuilder<T> {{
|
||||
let end = self.config.end.unwrap_or(0);
|
||||
self.config.start = Some(end - n as i64);
|
||||
RangeBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
|
||||
}}
|
||||
|
||||
/// Set the start position.
|
||||
pub fn from(mut self, start: i64) -> RangeBuilder<T> {{
|
||||
self.config.start = Some(start);
|
||||
RangeBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
|
||||
}}
|
||||
|
||||
/// Execute the query and return parsed JSON data (from start of data to end).
|
||||
pub fn json(self) -> Result<MetricData<T>> {{
|
||||
self.config.get_json(None)
|
||||
}}
|
||||
|
||||
/// Execute the query and return CSV data as a string.
|
||||
pub fn csv(self) -> Result<String> {{
|
||||
self.config.get_text(Some("csv"))
|
||||
}}
|
||||
}}
|
||||
|
||||
/// Final builder with range fully specified. Can only call `json()` or `csv()`.
|
||||
pub struct RangeBuilder<T> {{
|
||||
config: EndpointConfig,
|
||||
_marker: std::marker::PhantomData<T>,
|
||||
}}
|
||||
|
||||
impl<T: DeserializeOwned> RangeBuilder<T> {{
|
||||
/// Execute the query and return parsed JSON data.
|
||||
pub fn json(self) -> Result<MetricData<T>> {{
|
||||
self.config.get_json(None)
|
||||
}}
|
||||
|
||||
/// Execute the query and return CSV data as a string.
|
||||
pub fn csv(self) -> Result<String> {{
|
||||
self.config.get_text(Some("csv"))
|
||||
}}
|
||||
}}
|
||||
|
||||
@@ -225,10 +374,10 @@ pub fn generate_index_accessors(output: &mut String, patterns: &[IndexSetPattern
|
||||
writeln!(output, "impl<T: DeserializeOwned> {}<T> {{", by_name).unwrap();
|
||||
for index in &pattern.indexes {
|
||||
let method_name = index_to_field_name(index);
|
||||
writeln!(output, " pub fn {}(&self) -> Endpoint<T> {{", method_name).unwrap();
|
||||
writeln!(output, " pub fn {}(&self) -> MetricEndpointBuilder<T> {{", method_name).unwrap();
|
||||
writeln!(
|
||||
output,
|
||||
" Endpoint::new(self.client.clone(), self.name.clone(), Index::{})",
|
||||
" MetricEndpointBuilder::new(self.client.clone(), self.name.clone(), Index::{})",
|
||||
index
|
||||
)
|
||||
.unwrap();
|
||||
@@ -291,7 +440,7 @@ pub fn generate_index_accessors(output: &mut String, patterns: &[IndexSetPattern
|
||||
|
||||
// Implement MetricPattern<T> trait
|
||||
writeln!(output, "impl<T: DeserializeOwned> MetricPattern<T> for {}<T> {{", pattern.name).unwrap();
|
||||
writeln!(output, " fn get(&self, index: Index) -> Option<Endpoint<T>> {{").unwrap();
|
||||
writeln!(output, " fn get(&self, index: Index) -> Option<MetricEndpointBuilder<T>> {{").unwrap();
|
||||
writeln!(output, " match index {{").unwrap();
|
||||
for index in &pattern.indexes {
|
||||
let method_name = index_to_field_name(index);
|
||||
|
||||
@@ -23,7 +23,7 @@ brk_rpc = { workspace = true }
|
||||
brk_server = { workspace = true }
|
||||
clap = { version = "4.5.54", features = ["derive", "string"] }
|
||||
color-eyre = { workspace = true }
|
||||
importmap = "0.1.1"
|
||||
importmap = "0.1.2"
|
||||
# importmap = { path = "../../../importmap" }
|
||||
tracing = { workspace = true }
|
||||
minreq = { workspace = true }
|
||||
|
||||
@@ -22,7 +22,8 @@ fn main() -> brk_client::Result<()> {
|
||||
.close
|
||||
.by
|
||||
.dateindex()
|
||||
.range(Some(-3), None)?;
|
||||
.from(-3)
|
||||
.json()?;
|
||||
println!("Last 3 price close values: {:?}", price_close);
|
||||
|
||||
// Fetch block data
|
||||
@@ -34,7 +35,8 @@ fn main() -> brk_client::Result<()> {
|
||||
.sum
|
||||
.by
|
||||
.dateindex()
|
||||
.range(Some(-3), None)?;
|
||||
.from(-3)
|
||||
.json()?;
|
||||
println!("Last 3 block count values: {:?}", block_count);
|
||||
|
||||
// Fetch supply data
|
||||
@@ -56,7 +58,8 @@ fn main() -> brk_client::Result<()> {
|
||||
.bitcoin
|
||||
.by
|
||||
.dateindex()
|
||||
.range(Some(-3), None)?;
|
||||
.from(-3)
|
||||
.csv()?;
|
||||
println!("Last 3 circulating supply values: {:?}", circulating);
|
||||
|
||||
// Using generic metric fetching
|
||||
|
||||
@@ -45,7 +45,7 @@ fn main() -> brk_client::Result<()> {
|
||||
let client = BrkClient::new("http://localhost:3110");
|
||||
|
||||
// Get the metrics catalog tree
|
||||
let tree = client.get_metrics_catalog()?;
|
||||
let tree = client.get_metrics_tree()?;
|
||||
|
||||
// Recursively collect all metrics
|
||||
let metrics = collect_metrics(&tree, "");
|
||||
@@ -58,10 +58,16 @@ fn main() -> brk_client::Result<()> {
|
||||
for metric in &metrics {
|
||||
for index in &metric.indexes {
|
||||
let index_str = index.serialize_long();
|
||||
match client.get_metric_by_index(index_str, &metric.name, None, None, Some("-3"), None)
|
||||
{
|
||||
Ok(data) => {
|
||||
let count = data.data.len();
|
||||
match client.get_metric(
|
||||
metric.name.as_str().into(),
|
||||
*index,
|
||||
None,
|
||||
None,
|
||||
Some("-3"),
|
||||
None,
|
||||
) {
|
||||
Ok(response) => {
|
||||
let count = response.json().data.len();
|
||||
if count != 3 {
|
||||
failed += 1;
|
||||
let error_msg = format!(
|
||||
|
||||
+2457
-932
File diff suppressed because it is too large
Load Diff
+3293
-2347
File diff suppressed because it is too large
Load Diff
+5125
-2958
File diff suppressed because it is too large
Load Diff
@@ -19,38 +19,46 @@ def test_tree_exists():
|
||||
|
||||
def test_fetch_block():
|
||||
client = BrkClient("http://localhost:3110")
|
||||
print(client.get_block_height(800000))
|
||||
print(client.get_block_by_height(800000))
|
||||
|
||||
|
||||
def test_fetch_json_metric():
|
||||
client = BrkClient("http://localhost:3110")
|
||||
a = client.get_metric_by_index("price_close", "dateindex")
|
||||
a = client.get_metric("price_close", "dateindex")
|
||||
print(a)
|
||||
|
||||
|
||||
def test_fetch_csv_metric():
|
||||
client = BrkClient("http://localhost:3110")
|
||||
a = client.get_metric_by_index("price_close", "dateindex", -10, None, None, "csv")
|
||||
a = client.get_metric("price_close", "dateindex", -10, None, None, "csv")
|
||||
print(a)
|
||||
|
||||
|
||||
def test_fetch_typed_metric():
|
||||
client = BrkClient("http://localhost:3110")
|
||||
a = client.metrics.constants.constant_0.by.dateindex().range(-10)
|
||||
a = client.metrics.constants.constant_0.by.dateindex().from_(-10).json()
|
||||
print(a)
|
||||
b = client.metrics.outputs.count.utxo_count.by.height().range(-10)
|
||||
b = client.metrics.outputs.count.utxo_count.by.height().from_(-10).json()
|
||||
print(b)
|
||||
c = client.metrics.price.usd.split.close.by.dateindex().range(-10)
|
||||
c = client.metrics.price.usd.split.close.by.dateindex().from_(-10).json()
|
||||
print(c)
|
||||
d = client.metrics.market.dca.period_lump_sum_stack._10y.dollars.by.dateindex().range(
|
||||
-10
|
||||
d = (
|
||||
client.metrics.market.dca.period_lump_sum_stack._10y.dollars.by.dateindex()
|
||||
.from_(-10)
|
||||
.json()
|
||||
)
|
||||
print(d)
|
||||
e = client.metrics.market.dca.class_average_price._2017.by.dateindex().range(-10)
|
||||
e = (
|
||||
client.metrics.market.dca.class_average_price._2017.by.dateindex()
|
||||
.from_(-10)
|
||||
.json()
|
||||
)
|
||||
print(e)
|
||||
f = client.metrics.distribution.address_cohorts.amount_range._10k_sats_to_100k_sats.activity.sent.dollars.cumulative.by.dateindex().range(
|
||||
-10
|
||||
f = (
|
||||
client.metrics.distribution.address_cohorts.amount_range._10k_sats_to_100k_sats.activity.sent.dollars.cumulative.by.dateindex()
|
||||
.from_(-10)
|
||||
.json()
|
||||
)
|
||||
print(f)
|
||||
g = client.metrics.price.usd.ohlc.by.dateindex().range(-10)
|
||||
g = client.metrics.price.usd.ohlc.by.dateindex().from_(-10).json()
|
||||
print(g)
|
||||
|
||||
+113
-113
@@ -10,7 +10,6 @@
|
||||
/>
|
||||
<link rel="manifest" href="/manifest.webmanifest" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<script type="module" src="/scripts/main.js"></script>
|
||||
|
||||
<!-- ------ -->
|
||||
<!-- Styles -->
|
||||
@@ -1515,126 +1514,16 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- ------- -->
|
||||
<!-- Scripts -->
|
||||
<!-- ------- -->
|
||||
|
||||
<script>
|
||||
const preferredColorSchemeMatchMedia = window.matchMedia(
|
||||
"(prefers-color-scheme: dark)",
|
||||
);
|
||||
|
||||
const themeColor = window.document.createElement("meta");
|
||||
themeColor.name = "theme-color";
|
||||
window.document.getElementsByTagName("head")[0].appendChild(themeColor);
|
||||
|
||||
/** @param {boolean} dark */
|
||||
function updateThemeColor(dark) {
|
||||
const theme = getComputedStyle(
|
||||
window.document.documentElement,
|
||||
).getPropertyValue(dark ? "--black" : "--white");
|
||||
themeColor.content = theme;
|
||||
}
|
||||
|
||||
updateThemeColor(preferredColorSchemeMatchMedia.matches);
|
||||
preferredColorSchemeMatchMedia.addEventListener(
|
||||
"change",
|
||||
({ matches }) => {
|
||||
updateThemeColor(matches);
|
||||
},
|
||||
);
|
||||
|
||||
if ("standalone" in window.navigator && !!window.navigator.standalone) {
|
||||
window.document.documentElement.dataset.display = "standalone";
|
||||
}
|
||||
|
||||
if ("serviceWorker" in navigator) {
|
||||
window.addEventListener("load", () => {
|
||||
navigator.serviceWorker.register("/service-worker.js", {
|
||||
scope: "/",
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- IMPORTMAP -->
|
||||
<link rel="modulepreload" href="/scripts/chart/index.024e5d6b.js">
|
||||
<link rel="modulepreload" href="/scripts/chart/oklch.21450255.js">
|
||||
<link rel="modulepreload" href="/scripts/entry.7b7383d1.js">
|
||||
<link rel="modulepreload" href="/scripts/lazy.1ae52534.js">
|
||||
<link rel="modulepreload" href="/scripts/main.22a5bd79.js">
|
||||
<link rel="modulepreload" href="/scripts/modules/brk-client/index.74c13abc.js">
|
||||
<link rel="modulepreload" href="/scripts/modules/brk-client/tests/basic.b92ff866.js">
|
||||
<link rel="modulepreload" href="/scripts/modules/brk-client/tests/tree.ba9474f7.js">
|
||||
<link rel="modulepreload" href="/scripts/modules/lean-qr/2.6.1/index.09195c13.mjs">
|
||||
<link rel="modulepreload" href="/scripts/modules/leeoniya-ufuzzy/1.0.19/dist/uFuzzy.803b7fb0.mjs">
|
||||
<link rel="modulepreload" href="/scripts/modules/lightweight-charts/5.0.9/dist/lightweight-charts.standalone.production.1e264451.mjs">
|
||||
<link rel="modulepreload" href="/scripts/modules/lightweight-charts/5.1.0/dist/lightweight-charts.standalone.production.5c2a821a.mjs">
|
||||
<link rel="modulepreload" href="/scripts/modules/modern-screenshot/4.6.6/dist/index.0f951334.mjs">
|
||||
<link rel="modulepreload" href="/scripts/modules/modern-screenshot/4.6.7/dist/index.070574d6.js">
|
||||
<link rel="modulepreload" href="/scripts/modules/modern-screenshot/4.6.7/dist/index.e9e389fe.mjs">
|
||||
<link rel="modulepreload" href="/scripts/modules/modern-screenshot/4.6.7/dist/worker.1265d9cd.js">
|
||||
<link rel="modulepreload" href="/scripts/modules/solidjs-signals/0.6.3/dist/prod.2f80e335.js">
|
||||
<link rel="modulepreload" href="/scripts/modules/solidjs-signals/0.8.5/dist/prod.8ae56250.js">
|
||||
<link rel="modulepreload" href="/scripts/options/_partial_old.62bf3faa.js">
|
||||
<link rel="modulepreload" href="/scripts/options/chain.c173ace8.js">
|
||||
<link rel="modulepreload" href="/scripts/options/cohorts/address.2e8a42cb.js">
|
||||
<link rel="modulepreload" href="/scripts/options/cohorts/data.11381d83.js">
|
||||
<link rel="modulepreload" href="/scripts/options/cohorts/index.b0e57c9d.js">
|
||||
<link rel="modulepreload" href="/scripts/options/cohorts/shared.87b9837c.js">
|
||||
<link rel="modulepreload" href="/scripts/options/cohorts/utxo.f0e69857.js">
|
||||
<link rel="modulepreload" href="/scripts/options/cointime.e25633d9.js">
|
||||
<link rel="modulepreload" href="/scripts/options/colors/cohorts.262d4551.js">
|
||||
<link rel="modulepreload" href="/scripts/options/colors/index.a54dc83f.js">
|
||||
<link rel="modulepreload" href="/scripts/options/colors/misc.bee7dbee.js">
|
||||
<link rel="modulepreload" href="/scripts/options/constants.16dfce27.js">
|
||||
<link rel="modulepreload" href="/scripts/options/context.8bf2932e.js">
|
||||
<link rel="modulepreload" href="/scripts/options/full.11772605.js">
|
||||
<link rel="modulepreload" href="/scripts/options/market/averages.d95aa3e1.js">
|
||||
<link rel="modulepreload" href="/scripts/options/market/index.e3b750d6.js">
|
||||
<link rel="modulepreload" href="/scripts/options/market/indicators/bands.81b19b83.js">
|
||||
<link rel="modulepreload" href="/scripts/options/market/indicators/index.70e9b3e4.js">
|
||||
<link rel="modulepreload" href="/scripts/options/market/indicators/momentum.48e71442.js">
|
||||
<link rel="modulepreload" href="/scripts/options/market/indicators/onchain.f75ddfd9.js">
|
||||
<link rel="modulepreload" href="/scripts/options/market/indicators/volatility.08ec92d7.js">
|
||||
<link rel="modulepreload" href="/scripts/options/market/investing.57ded805.js">
|
||||
<link rel="modulepreload" href="/scripts/options/market/performance.36c7ad40.js">
|
||||
<link rel="modulepreload" href="/scripts/options/market/utils.e3e058d8.js">
|
||||
<link rel="modulepreload" href="/scripts/options/partial.ab8fdf12.js">
|
||||
<link rel="modulepreload" href="/scripts/options/series.5a2a34ed.js">
|
||||
<link rel="modulepreload" href="/scripts/options/types.64db5149.js">
|
||||
<link rel="modulepreload" href="/scripts/options/unused.24a71427.js">
|
||||
<link rel="modulepreload" href="/scripts/panes/chart/index.947ceee8.js">
|
||||
<link rel="modulepreload" href="/scripts/panes/chart/screenshot.adc8da89.js">
|
||||
<link rel="modulepreload" href="/scripts/panes/explorer.91a5a9ae.js">
|
||||
<link rel="modulepreload" href="/scripts/panes/nav.0338dc4b.js">
|
||||
<link rel="modulepreload" href="/scripts/panes/search.0338dc4b.js">
|
||||
<link rel="modulepreload" href="/scripts/panes/simulation.abf9ee5d.js">
|
||||
<link rel="modulepreload" href="/scripts/panes/table.00486691.js">
|
||||
<link rel="modulepreload" href="/scripts/resources.77bdf76f.js">
|
||||
<link rel="modulepreload" href="/scripts/signals.2ba0669e.js">
|
||||
<link rel="modulepreload" href="/scripts/utils/array.1863f57c.js">
|
||||
<link rel="modulepreload" href="/scripts/utils/colors.c95b2e03.js">
|
||||
<link rel="modulepreload" href="/scripts/utils/date.12ad717d.js">
|
||||
<link rel="modulepreload" href="/scripts/utils/dom.4d99f37f.js">
|
||||
<link rel="modulepreload" href="/scripts/utils/elements.6fe024ed.js">
|
||||
<link rel="modulepreload" href="/scripts/utils/env.594127e7.js">
|
||||
<link rel="modulepreload" href="/scripts/utils/format.4bdbfe40.js">
|
||||
<link rel="modulepreload" href="/scripts/utils/serde.f9c7ed2b.js">
|
||||
<link rel="modulepreload" href="/scripts/utils/storage.cbd2ff9c.js">
|
||||
<link rel="modulepreload" href="/scripts/utils/timing.ae6a47b8.js">
|
||||
<link rel="modulepreload" href="/scripts/utils/units.30278ea7.js">
|
||||
<link rel="modulepreload" href="/scripts/utils/url.20469bf9.js">
|
||||
<link rel="modulepreload" href="/scripts/utils/ws.fe3fb4b1.js">
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
"/scripts/chart/index.js": "/scripts/chart/index.024e5d6b.js",
|
||||
"/scripts/chart/oklch.js": "/scripts/chart/oklch.21450255.js",
|
||||
"/scripts/entry.js": "/scripts/entry.7b7383d1.js",
|
||||
"/scripts/entry.js": "/scripts/entry.93446a1b.js",
|
||||
"/scripts/lazy.js": "/scripts/lazy.1ae52534.js",
|
||||
"/scripts/main.js": "/scripts/main.22a5bd79.js",
|
||||
"/scripts/modules/brk-client/index.js": "/scripts/modules/brk-client/index.74c13abc.js",
|
||||
"/scripts/modules/brk-client/index.js": "/scripts/modules/brk-client/index.664c39c8.js",
|
||||
"/scripts/modules/brk-client/tests/basic.js": "/scripts/modules/brk-client/tests/basic.b92ff866.js",
|
||||
"/scripts/modules/brk-client/tests/tree.js": "/scripts/modules/brk-client/tests/tree.ba9474f7.js",
|
||||
"/scripts/modules/lean-qr/2.6.1/index.mjs": "/scripts/modules/lean-qr/2.6.1/index.09195c13.mjs",
|
||||
@@ -1700,8 +1589,119 @@
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<link rel="modulepreload" href="/scripts/chart/index.024e5d6b.js">
|
||||
<link rel="modulepreload" href="/scripts/chart/oklch.21450255.js">
|
||||
<link rel="modulepreload" href="/scripts/entry.93446a1b.js">
|
||||
<link rel="modulepreload" href="/scripts/lazy.1ae52534.js">
|
||||
<link rel="modulepreload" href="/scripts/main.22a5bd79.js">
|
||||
<link rel="modulepreload" href="/scripts/modules/brk-client/index.664c39c8.js">
|
||||
<link rel="modulepreload" href="/scripts/modules/brk-client/tests/basic.b92ff866.js">
|
||||
<link rel="modulepreload" href="/scripts/modules/brk-client/tests/tree.ba9474f7.js">
|
||||
<link rel="modulepreload" href="/scripts/modules/lean-qr/2.6.1/index.09195c13.mjs">
|
||||
<link rel="modulepreload" href="/scripts/modules/leeoniya-ufuzzy/1.0.19/dist/uFuzzy.803b7fb0.mjs">
|
||||
<link rel="modulepreload" href="/scripts/modules/lightweight-charts/5.0.9/dist/lightweight-charts.standalone.production.1e264451.mjs">
|
||||
<link rel="modulepreload" href="/scripts/modules/lightweight-charts/5.1.0/dist/lightweight-charts.standalone.production.5c2a821a.mjs">
|
||||
<link rel="modulepreload" href="/scripts/modules/modern-screenshot/4.6.6/dist/index.0f951334.mjs">
|
||||
<link rel="modulepreload" href="/scripts/modules/modern-screenshot/4.6.7/dist/index.070574d6.js">
|
||||
<link rel="modulepreload" href="/scripts/modules/modern-screenshot/4.6.7/dist/index.e9e389fe.mjs">
|
||||
<link rel="modulepreload" href="/scripts/modules/modern-screenshot/4.6.7/dist/worker.1265d9cd.js">
|
||||
<link rel="modulepreload" href="/scripts/modules/solidjs-signals/0.6.3/dist/prod.2f80e335.js">
|
||||
<link rel="modulepreload" href="/scripts/modules/solidjs-signals/0.8.5/dist/prod.8ae56250.js">
|
||||
<link rel="modulepreload" href="/scripts/options/_partial_old.62bf3faa.js">
|
||||
<link rel="modulepreload" href="/scripts/options/chain.c173ace8.js">
|
||||
<link rel="modulepreload" href="/scripts/options/cohorts/address.2e8a42cb.js">
|
||||
<link rel="modulepreload" href="/scripts/options/cohorts/data.11381d83.js">
|
||||
<link rel="modulepreload" href="/scripts/options/cohorts/index.b0e57c9d.js">
|
||||
<link rel="modulepreload" href="/scripts/options/cohorts/shared.87b9837c.js">
|
||||
<link rel="modulepreload" href="/scripts/options/cohorts/utxo.f0e69857.js">
|
||||
<link rel="modulepreload" href="/scripts/options/cointime.e25633d9.js">
|
||||
<link rel="modulepreload" href="/scripts/options/colors/cohorts.262d4551.js">
|
||||
<link rel="modulepreload" href="/scripts/options/colors/index.a54dc83f.js">
|
||||
<link rel="modulepreload" href="/scripts/options/colors/misc.bee7dbee.js">
|
||||
<link rel="modulepreload" href="/scripts/options/constants.16dfce27.js">
|
||||
<link rel="modulepreload" href="/scripts/options/context.8bf2932e.js">
|
||||
<link rel="modulepreload" href="/scripts/options/full.11772605.js">
|
||||
<link rel="modulepreload" href="/scripts/options/market/averages.d95aa3e1.js">
|
||||
<link rel="modulepreload" href="/scripts/options/market/index.e3b750d6.js">
|
||||
<link rel="modulepreload" href="/scripts/options/market/indicators/bands.81b19b83.js">
|
||||
<link rel="modulepreload" href="/scripts/options/market/indicators/index.70e9b3e4.js">
|
||||
<link rel="modulepreload" href="/scripts/options/market/indicators/momentum.48e71442.js">
|
||||
<link rel="modulepreload" href="/scripts/options/market/indicators/onchain.f75ddfd9.js">
|
||||
<link rel="modulepreload" href="/scripts/options/market/indicators/volatility.08ec92d7.js">
|
||||
<link rel="modulepreload" href="/scripts/options/market/investing.57ded805.js">
|
||||
<link rel="modulepreload" href="/scripts/options/market/performance.36c7ad40.js">
|
||||
<link rel="modulepreload" href="/scripts/options/market/utils.e3e058d8.js">
|
||||
<link rel="modulepreload" href="/scripts/options/partial.ab8fdf12.js">
|
||||
<link rel="modulepreload" href="/scripts/options/series.5a2a34ed.js">
|
||||
<link rel="modulepreload" href="/scripts/options/types.64db5149.js">
|
||||
<link rel="modulepreload" href="/scripts/options/unused.24a71427.js">
|
||||
<link rel="modulepreload" href="/scripts/panes/chart/index.947ceee8.js">
|
||||
<link rel="modulepreload" href="/scripts/panes/chart/screenshot.adc8da89.js">
|
||||
<link rel="modulepreload" href="/scripts/panes/explorer.91a5a9ae.js">
|
||||
<link rel="modulepreload" href="/scripts/panes/nav.0338dc4b.js">
|
||||
<link rel="modulepreload" href="/scripts/panes/search.0338dc4b.js">
|
||||
<link rel="modulepreload" href="/scripts/panes/simulation.abf9ee5d.js">
|
||||
<link rel="modulepreload" href="/scripts/panes/table.00486691.js">
|
||||
<link rel="modulepreload" href="/scripts/resources.77bdf76f.js">
|
||||
<link rel="modulepreload" href="/scripts/signals.2ba0669e.js">
|
||||
<link rel="modulepreload" href="/scripts/utils/array.1863f57c.js">
|
||||
<link rel="modulepreload" href="/scripts/utils/colors.c95b2e03.js">
|
||||
<link rel="modulepreload" href="/scripts/utils/date.12ad717d.js">
|
||||
<link rel="modulepreload" href="/scripts/utils/dom.4d99f37f.js">
|
||||
<link rel="modulepreload" href="/scripts/utils/elements.6fe024ed.js">
|
||||
<link rel="modulepreload" href="/scripts/utils/env.594127e7.js">
|
||||
<link rel="modulepreload" href="/scripts/utils/format.4bdbfe40.js">
|
||||
<link rel="modulepreload" href="/scripts/utils/serde.f9c7ed2b.js">
|
||||
<link rel="modulepreload" href="/scripts/utils/storage.cbd2ff9c.js">
|
||||
<link rel="modulepreload" href="/scripts/utils/timing.ae6a47b8.js">
|
||||
<link rel="modulepreload" href="/scripts/utils/units.30278ea7.js">
|
||||
<link rel="modulepreload" href="/scripts/utils/url.20469bf9.js">
|
||||
<link rel="modulepreload" href="/scripts/utils/ws.fe3fb4b1.js">
|
||||
<!-- /IMPORTMAP -->
|
||||
|
||||
<!-- ------- -->
|
||||
<!-- Scripts -->
|
||||
<!-- ------- -->
|
||||
|
||||
<script>
|
||||
const preferredColorSchemeMatchMedia = window.matchMedia(
|
||||
"(prefers-color-scheme: dark)",
|
||||
);
|
||||
|
||||
const themeColor = window.document.createElement("meta");
|
||||
themeColor.name = "theme-color";
|
||||
window.document.getElementsByTagName("head")[0].appendChild(themeColor);
|
||||
|
||||
/** @param {boolean} dark */
|
||||
function updateThemeColor(dark) {
|
||||
const theme = getComputedStyle(
|
||||
window.document.documentElement,
|
||||
).getPropertyValue(dark ? "--black" : "--white");
|
||||
themeColor.content = theme;
|
||||
}
|
||||
|
||||
updateThemeColor(preferredColorSchemeMatchMedia.matches);
|
||||
preferredColorSchemeMatchMedia.addEventListener(
|
||||
"change",
|
||||
({ matches }) => {
|
||||
updateThemeColor(matches);
|
||||
},
|
||||
);
|
||||
|
||||
if ("standalone" in window.navigator && !!window.navigator.standalone) {
|
||||
window.document.documentElement.dataset.display = "standalone";
|
||||
}
|
||||
|
||||
if ("serviceWorker" in navigator) {
|
||||
window.addEventListener("load", () => {
|
||||
navigator.serviceWorker.register("/service-worker.js", {
|
||||
scope: "/",
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<script type="module" src="/scripts/main.js"></script>
|
||||
|
||||
<!-- --- -->
|
||||
<!-- PWA -->
|
||||
<!-- --- -->
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
*
|
||||
* @import { Signal, Signals, Accessor } from "./signals.js";
|
||||
*
|
||||
* @import { BrkClient, MetricsTree_Distribution_UtxoCohorts as UtxoCohortTree, MetricsTree_Distribution_AddressCohorts as AddressCohortTree, MetricsTree_Distribution_UtxoCohorts_All as AllUtxoPattern, MetricsTree_Distribution_UtxoCohorts_Term_Short as ShortTermPattern, MetricsTree_Distribution_UtxoCohorts_Term_Long as LongTermPattern, _10yPattern as MaxAgePattern, _10yTo12yPattern as AgeRangePattern, _0satsPattern2 as UtxoAmountPattern, _0satsPattern as AddressAmountPattern, _100btcPattern as BasicUtxoPattern, _0satsPattern2 as EpochPattern, Ratio1ySdPattern, Dollars, Price111dSmaPattern as EmaRatioPattern, Index, BlockCountPattern, SizePattern, FullnessPattern, FeeRatePattern, CoinbasePattern, ActivePriceRatioPattern, _0satsPattern, UnclaimedRewardsPattern as ValuePattern, Metric, MetricPattern, AnyMetricPattern, MetricEndpoint, MetricData, AnyMetricEndpoint, AnyMetricData, AddrCountPattern, MetricsTree_Blocks_Interval as IntervalPattern, _24hCoinbaseSumPattern as SupplyPattern, RelativePattern, RelativePattern2, RelativePattern5, MetricsTree_Distribution_UtxoCohorts_All_Relative as AllRelativePattern } from "./modules/brk-client/index.js"
|
||||
* @import * as Brk from "./modules/brk-client/index.js"
|
||||
* @import { BrkClient} from "./modules/brk-client/index.js"
|
||||
*
|
||||
* @import { Resources, MetricResource } from './resources.js'
|
||||
*
|
||||
@@ -61,11 +62,6 @@
|
||||
* @typedef {{ name: string, title: string, color: Color, tree: PatternWithActivity }} CohortWithActivity
|
||||
* @typedef {{ name: string, title: string, color: Color, tree: PatternWithCostBasisPercentiles }} CohortWithCostBasisPercentiles
|
||||
*
|
||||
* Tree branch types
|
||||
* @typedef {InstanceType<typeof BrkClient>["tree"]["market"]} Market
|
||||
* @typedef {Market["movingAverage"]} MarketMovingAverage
|
||||
* @typedef {Market["dca"]} MarketDca
|
||||
*
|
||||
* Generic tree node type for walking
|
||||
* @typedef {AnyMetricPattern | Record<string, unknown>} TreeNode
|
||||
*
|
||||
|
||||
@@ -178,7 +178,7 @@ export function fromBitcoin(colors, pattern, title, color) {
|
||||
/**
|
||||
* Create series from a SizePattern ({ sum, cumulative, average, min, max, percentiles })
|
||||
* @param {Colors} colors
|
||||
* @param {SizePattern<any>} pattern
|
||||
* @param {SizePattern} pattern
|
||||
* @param {string} title
|
||||
* @param {Color} [color]
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
@@ -241,7 +241,7 @@ export function fromBlockSize(colors, pattern, title, color) {
|
||||
/**
|
||||
* Create series from a SizePattern ({ average, sum, cumulative, min, max, percentiles })
|
||||
* @param {Colors} colors
|
||||
* @param {SizePattern<any>} pattern
|
||||
* @param {SizePattern} pattern
|
||||
* @param {string} title
|
||||
* @param {Unit} unit
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
|
||||
@@ -238,8 +238,8 @@
|
||||
* @property {HistogramSeriesFn} histogram
|
||||
* @property {(pattern: BlockCountPattern<any>, title: string, color?: Color) => AnyFetchedSeriesBlueprint[]} fromBlockCount
|
||||
* @property {(pattern: FullnessPattern<any>, title: string, color?: Color) => AnyFetchedSeriesBlueprint[]} fromBitcoin
|
||||
* @property {(pattern: SizePattern<any>, title: string, color?: Color) => AnyFetchedSeriesBlueprint[]} fromBlockSize
|
||||
* @property {(pattern: SizePattern<any>, title: string, unit: Unit) => AnyFetchedSeriesBlueprint[]} fromSizePattern
|
||||
* @property {(pattern: SizePattern, title: string, color?: Color) => AnyFetchedSeriesBlueprint[]} fromBlockSize
|
||||
* @property {(pattern: SizePattern, title: string, unit: Unit) => AnyFetchedSeriesBlueprint[]} fromSizePattern
|
||||
* @property {(pattern: FullnessPattern<any>, title: string, unit: Unit) => AnyFetchedSeriesBlueprint[]} fromFullnessPattern
|
||||
* @property {(pattern: FeeRatePattern<any>, title: string, unit: Unit) => AnyFetchedSeriesBlueprint[]} fromFeeRatePattern
|
||||
* @property {(pattern: CoinbasePattern, title: string) => AnyFetchedSeriesBlueprint[]} fromCoinbasePattern
|
||||
|
||||
Reference in New Issue
Block a user