clients: add .len()

This commit is contained in:
nym21
2026-04-29 12:06:22 +02:00
parent f1749472e7
commit a7e41df1c6
17 changed files with 265 additions and 149 deletions

View File

@@ -4,7 +4,7 @@ use std::fmt::Write;
use crate::{
Endpoint, Parameter,
generators::{normalize_return_type, write_description},
generators::{javascript::types::jsdoc_normalize, normalize_return_type, write_description},
to_camel_case,
};
@@ -16,8 +16,9 @@ pub fn generate_api_methods(output: &mut String, endpoints: &[Endpoint]) {
}
let method_name = endpoint_to_method_name(endpoint);
let base_return_type =
normalize_return_type(endpoint.response_type.as_deref().unwrap_or("*"));
let base_return_type = jsdoc_normalize(&normalize_return_type(
endpoint.response_type.as_deref().unwrap_or("*"),
));
let return_type = if endpoint.supports_csv {
format!("{} | string", base_return_type)
} else {
@@ -51,20 +52,17 @@ pub fn generate_api_methods(output: &mut String, endpoints: &[Endpoint]) {
for param in &endpoint.path_params {
let desc = format_param_desc(param.description.as_deref());
writeln!(
output,
" * @param {{{}}} {}{}",
param.param_type, param.name, desc
)
.unwrap();
let ty = jsdoc_normalize(&param.param_type);
writeln!(output, " * @param {{{}}} {}{}", ty, param.name, desc).unwrap();
}
for param in &endpoint.query_params {
let optional = if param.required { "" } else { "=" };
let desc = format_param_desc(param.description.as_deref());
let ty = jsdoc_normalize(&param.param_type);
writeln!(
output,
" * @param {{{}{}}} [{}]{}",
param.param_type, optional, param.name, desc
ty, optional, param.name, desc
)
.unwrap();
}

View File

@@ -198,7 +198,6 @@ function _wrapSeriesData(raw) {{
* @property {{number}} version - Version of the series data
* @property {{Index}} index - The index type used for this query
* @property {{string}} type - Value type (e.g. "f32", "u64", "Sats")
* @property {{number}} total - Total number of data points
* @property {{number}} start - Start index (inclusive)
* @property {{number}} end - End index (exclusive)
* @property {{string}} stamp - ISO 8601 timestamp of when the response was generated
@@ -236,6 +235,8 @@ function _wrapSeriesData(raw) {{
* @property {{(n: number) => SkippedBuilder<T>}} skip - Skip first n items, chain with take()
* @property {{(onUpdate?: (value: SeriesData<T>) => void) => Promise<SeriesData<T>>}} fetch - Fetch all data
* @property {{() => Promise<string>}} fetchCsv - Fetch all data as CSV
* @property {{() => Promise<number>}} len - Get total number of data points
* @property {{() => Promise<Version>}} version - Get the current version of the series
* @property {{Thenable<T>}} then - Thenable (await endpoint)
* @property {{string}} path - The endpoint path
*/
@@ -250,6 +251,8 @@ function _wrapSeriesData(raw) {{
* @property {{(n: number) => DateSkippedBuilder<T>}} skip - Skip first n items, chain with take()
* @property {{(onUpdate?: (value: DateSeriesData<T>) => void) => Promise<DateSeriesData<T>>}} fetch - Fetch all data
* @property {{() => Promise<string>}} fetchCsv - Fetch all data as CSV
* @property {{() => Promise<number>}} len - Get total number of data points
* @property {{() => Promise<Version>}} version - Get the current version of the series
* @property {{DateThenable<T>}} then - Thenable (await endpoint)
* @property {{string}} path - The endpoint path
*/
@@ -308,7 +311,7 @@ function _wrapSeriesData(raw) {{
/**
* Create a series endpoint builder with typestate pattern.
* @template T
* @param {{BrkClientBase}} client
* @param {{BrkClient}} client
* @param {{string}} name - The series vec name
* @param {{Index}} index - The index name
* @returns {{DateSeriesEndpoint<T>}}
@@ -376,6 +379,8 @@ function _endpoint(client, name, index) {{
skip(n) {{ return skippedBuilder(n); }},
fetch(onUpdate) {{ return client._fetchSeriesData(buildPath(), onUpdate); }},
fetchCsv() {{ return client.getText(buildPath(undefined, undefined, 'csv')); }},
len() {{ return client.getSeriesLen(name, index); }},
version() {{ return client.getSeriesVersion(name, index); }},
then(resolve, reject) {{ return this.fetch().then(resolve, reject); }},
get path() {{ return p; }},
}};
@@ -626,7 +631,7 @@ pub fn generate_index_accessors(output: &mut String, patterns: &[IndexSetPattern
r#"/**
* Generic series pattern factory.
* @template T
* @param {{BrkClientBase}} client
* @param {{BrkClient}} client
* @param {{string}} name - The series vec name
* @param {{readonly Index[]}} indexes - The supported indexes
*/
@@ -679,7 +684,7 @@ function _mp(client, name, indexes) {{
// Generate thin wrapper that calls the generic factory
writeln!(
output,
"/** @template T @param {{BrkClientBase}} client @param {{string}} name @returns {{{}<T>}} */",
"/** @template T @param {{BrkClient}} client @param {{string}} name @returns {{{}<T>}} */",
pattern.name
)
.unwrap();
@@ -741,7 +746,7 @@ pub fn generate_structural_patterns(
if pattern.is_generic {
writeln!(output, " * @template T").unwrap();
}
writeln!(output, " * @param {{BrkClientBase}} client").unwrap();
writeln!(output, " * @param {{BrkClient}} client").unwrap();
writeln!(output, " * @param {{string}} acc - Accumulated series name").unwrap();
if pattern.is_templated() {
writeln!(output, " * @param {{string}} disc - Discriminator suffix").unwrap();

View File

@@ -111,6 +111,25 @@ fn json_type_to_js(ty: &str, schema: &Value, current_type: Option<&str>) -> Stri
}
}
/// JSDoc has no `integer` keyword, only `number`. Map `integer` (and `integer[]`,
/// `Foo<integer>`, etc.) to `number` before emitting type strings to JS.
pub fn jsdoc_normalize(ty: &str) -> String {
let mut out = ty.to_string();
let mut prev = String::new();
while prev != out {
prev = out.clone();
out = out.replace("integer[]", "number[]");
out = out.replace("<integer>", "<number>");
out = out.replace("(integer)", "(number)");
out = out.replace("integer | ", "number | ");
out = out.replace(" | integer", " | number");
}
if out == "integer" {
return "number".to_string();
}
out
}
/// Convert a JSON schema to a JavaScript type string.
pub fn schema_to_js_type(schema: &Value, current_type: Option<&str>) -> String {
if let Some(all_of) = schema.get("allOf").and_then(|v| v.as_array()) {

View File

@@ -221,7 +221,6 @@ class SeriesData(Generic[T]):
version: int
index: Index
type: str
total: int
start: int
end: int
stamp: str

View File

@@ -364,7 +364,7 @@ fn single_type_to_name(t: &SchemaType, schema: &ObjectSchema) -> Option<String>
match t {
SchemaType::String => Some("string".to_string()),
SchemaType::Number => Some("number".to_string()),
SchemaType::Integer => Some("number".to_string()),
SchemaType::Integer => Some("integer".to_string()),
SchemaType::Boolean => Some("boolean".to_string()),
SchemaType::Array => {
let inner = match &schema.items {

View File

@@ -9350,7 +9350,7 @@ impl BrkClient {
/// Returns the total number of data points for a series at the given index.
///
/// Endpoint: `GET /api/series/{series}/{index}/len`
pub fn get_series_len(&self, series: SeriesName, index: Index) -> Result<f64> {
pub fn get_series_len(&self, series: SeriesName, index: Index) -> Result<i64> {
self.base.get_json(&format!("/api/series/{series}/{}/len", index.name()))
}
@@ -9845,7 +9845,7 @@ impl BrkClient {
/// *[Mempool.space docs](https://mempool.space/docs/api/rest#get-transaction-times)*
///
/// Endpoint: `GET /api/v1/transaction-times`
pub fn get_transaction_times(&self) -> Result<Vec<f64>> {
pub fn get_transaction_times(&self) -> Result<Vec<i64>> {
self.base.get_json(&format!("/api/v1/transaction-times"))
}

View File

@@ -31,10 +31,6 @@ impl Applier {
}
}
/// Move one tx from the live mempool into the graveyard. Removes
/// from every store + tracker, then hands the body to
/// `graveyard.bury`. Silently bails if the entry or tx body is
/// already gone (idempotent under repeated removals).
fn bury_one(s: &mut LockedState, prefix: &TxidPrefix, reason: TxRemoval) {
let Some(entry) = s.entries.remove(prefix) else {
return;
@@ -58,9 +54,6 @@ impl Applier {
s.txs.extend(to_store);
}
/// Materialize a `TxAddition` into the (tx, entry) pair the Applier
/// will publish. Fresh additions are already-decoded; Revived ones
/// pull the cached body out of the graveyard and skip if it's gone.
fn resolve_addition(
s: &mut LockedState,
addition: TxAddition,
@@ -74,9 +67,6 @@ impl Applier {
}
}
/// Publish one tx into the live mempool: fold its fee into info,
/// register addr deltas, store the entry. Returns `(txid, tx)` for
/// the caller to batch into `txs.extend` once at the end.
fn publish_one(s: &mut LockedState, tx: Transaction, entry: TxEntry) -> (Txid, Transaction) {
s.info.add(&tx, entry.fee);
s.addrs.add_tx(&tx, &entry.txid);

View File

@@ -3,10 +3,10 @@ use std::{collections::BTreeMap, sync::LazyLock};
use brk_error::{Error, Result};
use brk_traversable::TreeNode;
use brk_types::{
BlockHashPrefix, Date, DetailedSeriesCount, Epoch, Format, Halving, Height, Index, IndexInfo,
LegacyValue, Limit, Output, OutputLegacy, PaginatedSeries, Pagination, PaginationIndex,
RangeIndex, RangeMap, SearchQuery, SeriesData, SeriesInfo, SeriesName, SeriesOutput,
SeriesOutputLegacy, SeriesSelection, Timestamp, Version,
BlockHashPrefix, CacheClass, Date, DetailedSeriesCount, Epoch, Format, Halving, Height, Index,
IndexInfo, LegacyValue, Limit, Output, OutputLegacy, PaginatedSeries, Pagination,
PaginationIndex, RangeIndex, RangeMap, SearchQuery, SeriesData, SeriesInfo, SeriesName,
SeriesOutput, SeriesOutputLegacy, SeriesSelection, Timestamp, Version,
};
use parking_lot::RwLock;
use vecdb::{AnyExportableVec, ReadableVec};
@@ -196,6 +196,13 @@ impl Query {
});
}
// Snapshot tip-derived state together so the historical-branch ETag stays
// self-consistent: stable_count is computed from tip_height, hash_prefix
// is the live tip.
let tip_height = self.indexed_height();
let hash_prefix = self.tip_hash_prefix();
let stable_count = self.stable_count(params.index, total, tip_height);
Ok(ResolvedQuery {
vecs,
format: params.format(),
@@ -204,10 +211,58 @@ impl Query {
total,
start,
end,
hash_prefix: self.tip_hash_prefix(),
hash_prefix,
stable_count,
})
}
/// Count of leading entries provably immutable across a 6-block reorg, used
/// to gate the historical-branch series ETag.
///
/// - Bucketed indexes: `total - margin`.
/// - Entity indexes: `first_X_index[tip_height - 6]`, falling back to 0 if
/// the tip is shallower than 6 blocks. Clamped to `total` so a query
/// whose vecs are shorter than the entity-type's own count never marks
/// its live tail as stable.
/// - Mutable (Funded/Empty addr): `None`. No immutable region exists, so
/// the caller must use the tip-bound ETag for every range.
pub fn stable_count(
&self,
index: Index,
total: usize,
tip_height: Height,
) -> Option<usize> {
match index.cache_class() {
CacheClass::Bucket { margin } => Some(total.saturating_sub(margin)),
CacheClass::Entity => {
let h = Height::from((*tip_height).saturating_sub(6));
let v = &self.indexer().vecs;
let n = match index {
Index::TxIndex => v.transactions.first_tx_index.collect_one(h).map(usize::from),
Index::TxInIndex => v.inputs.first_txin_index.collect_one(h).map(usize::from),
Index::TxOutIndex => v.outputs.first_txout_index.collect_one(h).map(usize::from),
Index::EmptyOutputIndex => v.scripts.empty.first_index.collect_one(h).map(usize::from),
Index::OpReturnIndex => v.scripts.op_return.first_index.collect_one(h).map(usize::from),
Index::P2MSOutputIndex => v.scripts.p2ms.first_index.collect_one(h).map(usize::from),
Index::UnknownOutputIndex => v.scripts.unknown.first_index.collect_one(h).map(usize::from),
Index::P2AAddrIndex => v.addrs.p2a.first_index.collect_one(h).map(usize::from),
Index::P2PK33AddrIndex => v.addrs.p2pk33.first_index.collect_one(h).map(usize::from),
Index::P2PK65AddrIndex => v.addrs.p2pk65.first_index.collect_one(h).map(usize::from),
Index::P2PKHAddrIndex => v.addrs.p2pkh.first_index.collect_one(h).map(usize::from),
Index::P2SHAddrIndex => v.addrs.p2sh.first_index.collect_one(h).map(usize::from),
Index::P2TRAddrIndex => v.addrs.p2tr.first_index.collect_one(h).map(usize::from),
Index::P2WPKHAddrIndex => v.addrs.p2wpkh.first_index.collect_one(h).map(usize::from),
Index::P2WSHAddrIndex => v.addrs.p2wsh.first_index.collect_one(h).map(usize::from),
_ => unreachable!("non-entity index in CacheClass::Entity arm"),
}
.unwrap_or(0)
.min(total);
Some(n)
}
CacheClass::Mutable => None,
}
}
/// Format a resolved query (expensive).
/// Call after ETag/cache checks to avoid unnecessary work.
pub fn format(&self, resolved: ResolvedQuery) -> Result<SeriesOutput> {
@@ -449,8 +504,9 @@ impl Query {
}
/// A resolved series query ready for formatting.
/// Carries the vecs plus the metadata (version, total, end, hash_prefix) callers
/// need to derive an etag or cache policy.
/// Carries the vecs plus the metadata callers need to derive an etag or cache
/// policy. `stable_count` is `None` for indexes whose entries can mutate
/// retroactively (Funded/Empty addr).
pub struct ResolvedQuery {
pub vecs: Vec<&'static dyn AnyExportableVec>,
pub format: Format,
@@ -460,6 +516,7 @@ pub struct ResolvedQuery {
pub start: usize,
pub end: usize,
pub hash_prefix: BlockHashPrefix,
pub stable_count: Option<usize>,
}
impl ResolvedQuery {

View File

@@ -44,8 +44,9 @@ pub(super) async fn serve(
let csv_filename = resolved.csv_filename();
let cache_params = CacheParams::series(
resolved.version,
resolved.total,
resolved.start,
resolved.end,
resolved.stable_count,
resolved.hash_prefix,
);

View File

@@ -67,23 +67,39 @@ impl CacheParams {
}
}
/// Series query: tail-bound (`end >= total`) gets LIVE, historical gets CACHED.
/// Etag distinguishes the two: tail uses tip hash (per-block + reorgs),
/// historical uses total length (only changes when new data is appended).
pub fn series(version: Version, total: usize, end: usize, hash: BlockHashPrefix) -> Self {
/// Series query: tail-bound gets LIVE, historical gets CACHED.
///
/// `stable_count` is the count of leading entries provably immutable across
/// a 6-block reorg (per `Index::cache_class()` + `Query::stable_count`).
/// `None` (Funded/Empty addr indexes) forces the tail branch for every range.
///
/// Etag shapes:
/// - historical (`end <= stable_count`): `s{v}-h{start}-{end}`. Pure
/// range, stable across appends and reorgs of the volatile tail.
/// - tail (`end > stable_count` or `stable_count.is_none()`):
/// `s{v}-t{tip_hash:x}`. Invalidates per-block, reorg-safe.
///
/// The `h`/`t` discriminator after `s{v}-` prevents collision with old
/// `s{v}-{number}` ETags from before the migration.
pub fn series(
version: Version,
start: usize,
end: usize,
stable_count: Option<usize>,
hash: BlockHashPrefix,
) -> Self {
let v = u32::from(version);
if end >= total {
Self {
etag: format!("s{v}-{:x}", *hash).into(),
cache_control: CC,
cdn_cache_control: CDN_LIVE,
}
} else {
Self {
etag: format!("s{v}-{total}").into(),
match stable_count {
Some(s) if end <= s => Self {
etag: format!("s{v}-h{start}-{end}").into(),
cache_control: CC,
cdn_cache_control: cdn_cached(),
}
},
_ => Self {
etag: format!("s{v}-t{:x}", *hash).into(),
cache_control: CC,
cdn_cache_control: CDN_LIVE,
},
}
}
@@ -139,28 +155,55 @@ mod tests {
}
#[test]
fn series_tail_uses_tip_hash() {
let p = CacheParams::series(v(3), 100, 100, h(0xabcd));
assert_eq!(p.etag.as_str(), "s3-abcd");
fn series_tail_when_end_exceeds_stable_count() {
let p = CacheParams::series(v(3), 0, 60, Some(50), h(0xabcd));
assert_eq!(p.etag.as_str(), "s3-tabcd");
}
#[test]
fn series_historical_uses_total() {
let p = CacheParams::series(v(3), 100, 50, h(0xabcd));
assert_eq!(p.etag.as_str(), "s3-100");
fn series_historical_when_end_at_or_below_stable_count() {
let p = CacheParams::series(v(3), 10, 50, Some(50), h(0xabcd));
assert_eq!(p.etag.as_str(), "s3-h10-50");
}
#[test]
fn series_historical_ignores_tip_hash() {
let a = CacheParams::series(v(3), 100, 50, h(0xabcd));
let b = CacheParams::series(v(3), 100, 50, h(0xdead));
let a = CacheParams::series(v(3), 0, 50, Some(100), h(0xabcd));
let b = CacheParams::series(v(3), 0, 50, Some(100), h(0xdead));
assert_eq!(a.etag.as_str(), b.etag.as_str());
}
#[test]
fn series_tail_changes_with_tip_hash() {
let a = CacheParams::series(v(3), 100, 100, h(0xabcd));
let b = CacheParams::series(v(3), 100, 100, h(0xdead));
let a = CacheParams::series(v(3), 0, 100, Some(50), h(0xabcd));
let b = CacheParams::series(v(3), 0, 100, Some(50), h(0xdead));
assert_ne!(a.etag.as_str(), b.etag.as_str());
}
#[test]
fn series_mutable_class_always_tail() {
let small = CacheParams::series(v(3), 0, 5, None, h(0xabcd));
let large = CacheParams::series(v(3), 0, 1_000_000, None, h(0xabcd));
assert_eq!(small.etag.as_str(), "s3-tabcd");
assert_eq!(large.etag.as_str(), "s3-tabcd");
}
#[test]
fn series_at_stable_boundary_is_historical() {
let p = CacheParams::series(v(3), 0, 50, Some(50), h(0xabcd));
assert_eq!(p.etag.as_str(), "s3-h0-50");
}
#[test]
fn series_just_past_stable_boundary_is_tail() {
let p = CacheParams::series(v(3), 0, 51, Some(50), h(0xabcd));
assert_eq!(p.etag.as_str(), "s3-tabcd");
}
#[test]
fn series_different_ranges_get_different_etags() {
let a = CacheParams::series(v(3), 0, 50, Some(100), h(0xabcd));
let b = CacheParams::series(v(3), 10, 50, Some(100), h(0xabcd));
assert_ne!(a.etag.as_str(), b.etag.as_str());
}
}

View File

@@ -73,6 +73,23 @@ pub enum Index {
EmptyAddrIndex,
}
/// How the trailing edge of an [`Index`] mutates over time. Drives the series
/// cache: bucketed indexes have a small fixed volatile tail, entity indexes
/// derive their stable count from a height→first-index mapping, and mutable
/// addr indexes have no immutable region (entries change retroactively).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CacheClass {
/// Append-only with `margin` trailing entries volatile per ≥6-block reorg.
Bucket { margin: usize },
/// Append-only entity index (one number per tx / input / output / typed
/// addr). Stable count must be looked up via the indexer's
/// `first_X_index[tip - 6]` mapping.
Entity,
/// Retroactively mutable: no immutable region (`FundedAddrIndex`,
/// `EmptyAddrIndex`).
Mutable,
}
impl Index {
pub const fn all() -> [Self; 33] {
[
@@ -195,22 +212,19 @@ impl Index {
}
}
/// Number of trailing entries that may still mutate due to a 6-block reorg.
/// Used to size the cache invalidation tail: ranges ending within this margin
/// of `total` use a tip-bound ETag, others may use the cheaper total-only ETag.
///
/// Panics for cohort indexes (per-tx, per-output, per-addr): series queries
/// shouldn't reach this codepath under those indexes. If they do, the cache
/// strategy needs rethinking.
pub const fn safety_margin(&self) -> usize {
/// Classifies how the trailing edge of an index mutates, so the series cache
/// can pick the right stable-count strategy.
pub const fn cache_class(&self) -> CacheClass {
match self {
Self::Minute10 => 6,
Self::Minute30 => 2,
Self::Hour1 | Self::Hour4 | Self::Hour12 => 1,
Self::Day1 | Self::Day3 | Self::Week1 => 1,
Self::Month1 | Self::Month3 | Self::Month6 => 1,
Self::Year1 | Self::Year10 | Self::Halving | Self::Epoch => 1,
Self::Height => 6,
Self::Minute10 => CacheClass::Bucket { margin: 8 },
Self::Minute30 => CacheClass::Bucket { margin: 3 },
Self::Hour1 | Self::Hour4 | Self::Hour12 => CacheClass::Bucket { margin: 2 },
Self::Day1 | Self::Day3 | Self::Week1 => CacheClass::Bucket { margin: 2 },
Self::Month1 | Self::Month3 | Self::Month6 => CacheClass::Bucket { margin: 1 },
Self::Year1 | Self::Year10 | Self::Halving | Self::Epoch => {
CacheClass::Bucket { margin: 1 }
}
Self::Height => CacheClass::Bucket { margin: 6 },
Self::TxIndex
| Self::TxInIndex
| Self::TxOutIndex
@@ -225,11 +239,8 @@ impl Index {
| Self::P2TRAddrIndex
| Self::P2WPKHAddrIndex
| Self::P2WSHAddrIndex
| Self::UnknownOutputIndex
| Self::FundedAddrIndex
| Self::EmptyAddrIndex => {
panic!("cohort index has no series cache safety margin")
}
| Self::UnknownOutputIndex => CacheClass::Entity,
Self::FundedAddrIndex | Self::EmptyAddrIndex => CacheClass::Mutable,
}
}

View File

@@ -20,8 +20,6 @@ pub struct SeriesData<T = Value> {
/// Value type (e.g. "f32", "u64", "Sats")
#[serde(rename = "type", default)]
pub value_type: String,
/// Total number of data points in the series
pub total: usize,
/// Start index (inclusive) of the returned range
pub start: usize,
/// End index (exclusive) of the returned range
@@ -33,7 +31,8 @@ pub struct SeriesData<T = Value> {
}
impl SeriesData {
/// Write series data as JSON to buffer: `{"version":N,"index":"...","total":N,"start":N,"end":N,"stamp":"...","data":[...]}`
/// Write series data as JSON to buffer: `{"version":N,"index":"...","start":N,"end":N,"stamp":"...","data":[...]}`.
/// `total` is omitted so historical-range bodies stay cacheable across appends. Clients call `/len` for the live count.
pub fn serialize(
vec: &dyn AnySerializableVec,
index: Index,
@@ -53,9 +52,7 @@ impl SeriesData {
buf.extend_from_slice(index.name().as_bytes());
buf.extend_from_slice(b"\",\"type\":\"");
buf.extend_from_slice(vec.value_type_to_string().as_bytes());
buf.extend_from_slice(b"\",\"total\":");
buf.extend_from_slice(itoa_buf.format(total).as_bytes());
buf.extend_from_slice(b",\"start\":");
buf.extend_from_slice(b"\",\"start\":");
buf.extend_from_slice(itoa_buf.format(start).as_bytes());
buf.extend_from_slice(b",\"end\":");
buf.extend_from_slice(itoa_buf.format(end).as_bytes());

View File

@@ -1477,7 +1477,6 @@ function _wrapSeriesData(raw) {
* @property {number} version - Version of the series data
* @property {Index} index - The index type used for this query
* @property {string} type - Value type (e.g. "f32", "u64", "Sats")
* @property {number} total - Total number of data points
* @property {number} start - Start index (inclusive)
* @property {number} end - End index (exclusive)
* @property {string} stamp - ISO 8601 timestamp of when the response was generated
@@ -1515,6 +1514,8 @@ function _wrapSeriesData(raw) {
* @property {(n: number) => SkippedBuilder<T>} skip - Skip first n items, chain with take()
* @property {(onUpdate?: (value: SeriesData<T>) => void) => Promise<SeriesData<T>>} fetch - Fetch all data
* @property {() => Promise<string>} fetchCsv - Fetch all data as CSV
* @property {() => Promise<number>} len - Get total number of data points
* @property {() => Promise<Version>} version - Get the current version of the series
* @property {Thenable<T>} then - Thenable (await endpoint)
* @property {string} path - The endpoint path
*/
@@ -1529,6 +1530,8 @@ function _wrapSeriesData(raw) {
* @property {(n: number) => DateSkippedBuilder<T>} skip - Skip first n items, chain with take()
* @property {(onUpdate?: (value: DateSeriesData<T>) => void) => Promise<DateSeriesData<T>>} fetch - Fetch all data
* @property {() => Promise<string>} fetchCsv - Fetch all data as CSV
* @property {() => Promise<number>} len - Get total number of data points
* @property {() => Promise<Version>} version - Get the current version of the series
* @property {DateThenable<T>} then - Thenable (await endpoint)
* @property {string} path - The endpoint path
*/
@@ -1587,7 +1590,7 @@ function _wrapSeriesData(raw) {
/**
* Create a series endpoint builder with typestate pattern.
* @template T
* @param {BrkClientBase} client
* @param {BrkClient} client
* @param {string} name - The series vec name
* @param {Index} index - The index name
* @returns {DateSeriesEndpoint<T>}
@@ -1655,6 +1658,8 @@ function _endpoint(client, name, index) {
skip(n) { return skippedBuilder(n); },
fetch(onUpdate) { return client._fetchSeriesData(buildPath(), onUpdate); },
fetchCsv() { return client.getText(buildPath(undefined, undefined, 'csv')); },
len() { return client.getSeriesLen(name, index); },
version() { return client.getSeriesVersion(name, index); },
then(resolve, reject) { return this.fetch().then(resolve, reject); },
get path() { return p; },
};
@@ -1845,7 +1850,7 @@ const _i35 = /** @type {const} */ (["empty_addr_index"]);
/**
* Generic series pattern factory.
* @template T
* @param {BrkClientBase} client
* @param {BrkClient} client
* @param {string} name - The series vec name
* @param {readonly Index[]} indexes - The supported indexes
*/
@@ -1869,109 +1874,109 @@ function _mp(client, name, indexes) {
}
/** @template T @typedef {{ name: string, by: { readonly minute10: DateSeriesEndpoint<T>, readonly minute30: DateSeriesEndpoint<T>, readonly hour1: DateSeriesEndpoint<T>, readonly hour4: DateSeriesEndpoint<T>, readonly hour12: DateSeriesEndpoint<T>, readonly day1: DateSeriesEndpoint<T>, readonly day3: DateSeriesEndpoint<T>, readonly week1: DateSeriesEndpoint<T>, readonly month1: DateSeriesEndpoint<T>, readonly month3: DateSeriesEndpoint<T>, readonly month6: DateSeriesEndpoint<T>, readonly year1: DateSeriesEndpoint<T>, readonly year10: DateSeriesEndpoint<T>, readonly halving: SeriesEndpoint<T>, readonly epoch: SeriesEndpoint<T>, readonly height: SeriesEndpoint<T> }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint<T>|undefined }} SeriesPattern1 */
/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern1<T>} */
/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern1<T>} */
function createSeriesPattern1(client, name) { return /** @type {SeriesPattern1<T>} */ (_mp(client, name, _i1)); }
/** @template T @typedef {{ name: string, by: { readonly minute10: DateSeriesEndpoint<T>, readonly minute30: DateSeriesEndpoint<T>, readonly hour1: DateSeriesEndpoint<T>, readonly hour4: DateSeriesEndpoint<T>, readonly hour12: DateSeriesEndpoint<T>, readonly day1: DateSeriesEndpoint<T>, readonly day3: DateSeriesEndpoint<T>, readonly week1: DateSeriesEndpoint<T>, readonly month1: DateSeriesEndpoint<T>, readonly month3: DateSeriesEndpoint<T>, readonly month6: DateSeriesEndpoint<T>, readonly year1: DateSeriesEndpoint<T>, readonly year10: DateSeriesEndpoint<T>, readonly halving: SeriesEndpoint<T>, readonly epoch: SeriesEndpoint<T> }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint<T>|undefined }} SeriesPattern2 */
/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern2<T>} */
/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern2<T>} */
function createSeriesPattern2(client, name) { return /** @type {SeriesPattern2<T>} */ (_mp(client, name, _i2)); }
/** @template T @typedef {{ name: string, by: { readonly minute10: DateSeriesEndpoint<T> }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint<T>|undefined }} SeriesPattern3 */
/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern3<T>} */
/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern3<T>} */
function createSeriesPattern3(client, name) { return /** @type {SeriesPattern3<T>} */ (_mp(client, name, _i3)); }
/** @template T @typedef {{ name: string, by: { readonly minute30: DateSeriesEndpoint<T> }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint<T>|undefined }} SeriesPattern4 */
/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern4<T>} */
/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern4<T>} */
function createSeriesPattern4(client, name) { return /** @type {SeriesPattern4<T>} */ (_mp(client, name, _i4)); }
/** @template T @typedef {{ name: string, by: { readonly hour1: DateSeriesEndpoint<T> }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint<T>|undefined }} SeriesPattern5 */
/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern5<T>} */
/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern5<T>} */
function createSeriesPattern5(client, name) { return /** @type {SeriesPattern5<T>} */ (_mp(client, name, _i5)); }
/** @template T @typedef {{ name: string, by: { readonly hour4: DateSeriesEndpoint<T> }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint<T>|undefined }} SeriesPattern6 */
/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern6<T>} */
/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern6<T>} */
function createSeriesPattern6(client, name) { return /** @type {SeriesPattern6<T>} */ (_mp(client, name, _i6)); }
/** @template T @typedef {{ name: string, by: { readonly hour12: DateSeriesEndpoint<T> }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint<T>|undefined }} SeriesPattern7 */
/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern7<T>} */
/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern7<T>} */
function createSeriesPattern7(client, name) { return /** @type {SeriesPattern7<T>} */ (_mp(client, name, _i7)); }
/** @template T @typedef {{ name: string, by: { readonly day1: DateSeriesEndpoint<T> }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint<T>|undefined }} SeriesPattern8 */
/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern8<T>} */
/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern8<T>} */
function createSeriesPattern8(client, name) { return /** @type {SeriesPattern8<T>} */ (_mp(client, name, _i8)); }
/** @template T @typedef {{ name: string, by: { readonly day3: DateSeriesEndpoint<T> }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint<T>|undefined }} SeriesPattern9 */
/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern9<T>} */
/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern9<T>} */
function createSeriesPattern9(client, name) { return /** @type {SeriesPattern9<T>} */ (_mp(client, name, _i9)); }
/** @template T @typedef {{ name: string, by: { readonly week1: DateSeriesEndpoint<T> }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint<T>|undefined }} SeriesPattern10 */
/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern10<T>} */
/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern10<T>} */
function createSeriesPattern10(client, name) { return /** @type {SeriesPattern10<T>} */ (_mp(client, name, _i10)); }
/** @template T @typedef {{ name: string, by: { readonly month1: DateSeriesEndpoint<T> }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint<T>|undefined }} SeriesPattern11 */
/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern11<T>} */
/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern11<T>} */
function createSeriesPattern11(client, name) { return /** @type {SeriesPattern11<T>} */ (_mp(client, name, _i11)); }
/** @template T @typedef {{ name: string, by: { readonly month3: DateSeriesEndpoint<T> }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint<T>|undefined }} SeriesPattern12 */
/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern12<T>} */
/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern12<T>} */
function createSeriesPattern12(client, name) { return /** @type {SeriesPattern12<T>} */ (_mp(client, name, _i12)); }
/** @template T @typedef {{ name: string, by: { readonly month6: DateSeriesEndpoint<T> }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint<T>|undefined }} SeriesPattern13 */
/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern13<T>} */
/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern13<T>} */
function createSeriesPattern13(client, name) { return /** @type {SeriesPattern13<T>} */ (_mp(client, name, _i13)); }
/** @template T @typedef {{ name: string, by: { readonly year1: DateSeriesEndpoint<T> }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint<T>|undefined }} SeriesPattern14 */
/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern14<T>} */
/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern14<T>} */
function createSeriesPattern14(client, name) { return /** @type {SeriesPattern14<T>} */ (_mp(client, name, _i14)); }
/** @template T @typedef {{ name: string, by: { readonly year10: DateSeriesEndpoint<T> }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint<T>|undefined }} SeriesPattern15 */
/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern15<T>} */
/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern15<T>} */
function createSeriesPattern15(client, name) { return /** @type {SeriesPattern15<T>} */ (_mp(client, name, _i15)); }
/** @template T @typedef {{ name: string, by: { readonly halving: SeriesEndpoint<T> }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint<T>|undefined }} SeriesPattern16 */
/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern16<T>} */
/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern16<T>} */
function createSeriesPattern16(client, name) { return /** @type {SeriesPattern16<T>} */ (_mp(client, name, _i16)); }
/** @template T @typedef {{ name: string, by: { readonly epoch: SeriesEndpoint<T> }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint<T>|undefined }} SeriesPattern17 */
/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern17<T>} */
/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern17<T>} */
function createSeriesPattern17(client, name) { return /** @type {SeriesPattern17<T>} */ (_mp(client, name, _i17)); }
/** @template T @typedef {{ name: string, by: { readonly height: SeriesEndpoint<T> }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint<T>|undefined }} SeriesPattern18 */
/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern18<T>} */
/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern18<T>} */
function createSeriesPattern18(client, name) { return /** @type {SeriesPattern18<T>} */ (_mp(client, name, _i18)); }
/** @template T @typedef {{ name: string, by: { readonly tx_index: SeriesEndpoint<T> }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint<T>|undefined }} SeriesPattern19 */
/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern19<T>} */
/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern19<T>} */
function createSeriesPattern19(client, name) { return /** @type {SeriesPattern19<T>} */ (_mp(client, name, _i19)); }
/** @template T @typedef {{ name: string, by: { readonly txin_index: SeriesEndpoint<T> }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint<T>|undefined }} SeriesPattern20 */
/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern20<T>} */
/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern20<T>} */
function createSeriesPattern20(client, name) { return /** @type {SeriesPattern20<T>} */ (_mp(client, name, _i20)); }
/** @template T @typedef {{ name: string, by: { readonly txout_index: SeriesEndpoint<T> }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint<T>|undefined }} SeriesPattern21 */
/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern21<T>} */
/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern21<T>} */
function createSeriesPattern21(client, name) { return /** @type {SeriesPattern21<T>} */ (_mp(client, name, _i21)); }
/** @template T @typedef {{ name: string, by: { readonly empty_output_index: SeriesEndpoint<T> }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint<T>|undefined }} SeriesPattern22 */
/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern22<T>} */
/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern22<T>} */
function createSeriesPattern22(client, name) { return /** @type {SeriesPattern22<T>} */ (_mp(client, name, _i22)); }
/** @template T @typedef {{ name: string, by: { readonly op_return_index: SeriesEndpoint<T> }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint<T>|undefined }} SeriesPattern23 */
/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern23<T>} */
/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern23<T>} */
function createSeriesPattern23(client, name) { return /** @type {SeriesPattern23<T>} */ (_mp(client, name, _i23)); }
/** @template T @typedef {{ name: string, by: { readonly p2a_addr_index: SeriesEndpoint<T> }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint<T>|undefined }} SeriesPattern24 */
/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern24<T>} */
/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern24<T>} */
function createSeriesPattern24(client, name) { return /** @type {SeriesPattern24<T>} */ (_mp(client, name, _i24)); }
/** @template T @typedef {{ name: string, by: { readonly p2ms_output_index: SeriesEndpoint<T> }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint<T>|undefined }} SeriesPattern25 */
/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern25<T>} */
/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern25<T>} */
function createSeriesPattern25(client, name) { return /** @type {SeriesPattern25<T>} */ (_mp(client, name, _i25)); }
/** @template T @typedef {{ name: string, by: { readonly p2pk33_addr_index: SeriesEndpoint<T> }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint<T>|undefined }} SeriesPattern26 */
/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern26<T>} */
/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern26<T>} */
function createSeriesPattern26(client, name) { return /** @type {SeriesPattern26<T>} */ (_mp(client, name, _i26)); }
/** @template T @typedef {{ name: string, by: { readonly p2pk65_addr_index: SeriesEndpoint<T> }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint<T>|undefined }} SeriesPattern27 */
/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern27<T>} */
/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern27<T>} */
function createSeriesPattern27(client, name) { return /** @type {SeriesPattern27<T>} */ (_mp(client, name, _i27)); }
/** @template T @typedef {{ name: string, by: { readonly p2pkh_addr_index: SeriesEndpoint<T> }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint<T>|undefined }} SeriesPattern28 */
/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern28<T>} */
/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern28<T>} */
function createSeriesPattern28(client, name) { return /** @type {SeriesPattern28<T>} */ (_mp(client, name, _i28)); }
/** @template T @typedef {{ name: string, by: { readonly p2sh_addr_index: SeriesEndpoint<T> }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint<T>|undefined }} SeriesPattern29 */
/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern29<T>} */
/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern29<T>} */
function createSeriesPattern29(client, name) { return /** @type {SeriesPattern29<T>} */ (_mp(client, name, _i29)); }
/** @template T @typedef {{ name: string, by: { readonly p2tr_addr_index: SeriesEndpoint<T> }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint<T>|undefined }} SeriesPattern30 */
/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern30<T>} */
/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern30<T>} */
function createSeriesPattern30(client, name) { return /** @type {SeriesPattern30<T>} */ (_mp(client, name, _i30)); }
/** @template T @typedef {{ name: string, by: { readonly p2wpkh_addr_index: SeriesEndpoint<T> }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint<T>|undefined }} SeriesPattern31 */
/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern31<T>} */
/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern31<T>} */
function createSeriesPattern31(client, name) { return /** @type {SeriesPattern31<T>} */ (_mp(client, name, _i31)); }
/** @template T @typedef {{ name: string, by: { readonly p2wsh_addr_index: SeriesEndpoint<T> }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint<T>|undefined }} SeriesPattern32 */
/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern32<T>} */
/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern32<T>} */
function createSeriesPattern32(client, name) { return /** @type {SeriesPattern32<T>} */ (_mp(client, name, _i32)); }
/** @template T @typedef {{ name: string, by: { readonly unknown_output_index: SeriesEndpoint<T> }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint<T>|undefined }} SeriesPattern33 */
/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern33<T>} */
/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern33<T>} */
function createSeriesPattern33(client, name) { return /** @type {SeriesPattern33<T>} */ (_mp(client, name, _i33)); }
/** @template T @typedef {{ name: string, by: { readonly funded_addr_index: SeriesEndpoint<T> }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint<T>|undefined }} SeriesPattern34 */
/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern34<T>} */
/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern34<T>} */
function createSeriesPattern34(client, name) { return /** @type {SeriesPattern34<T>} */ (_mp(client, name, _i34)); }
/** @template T @typedef {{ name: string, by: { readonly empty_addr_index: SeriesEndpoint<T> }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint<T>|undefined }} SeriesPattern35 */
/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern35<T>} */
/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern35<T>} */
function createSeriesPattern35(client, name) { return /** @type {SeriesPattern35<T>} */ (_mp(client, name, _i35)); }
// Reusable structural pattern factories

View File

@@ -7,48 +7,44 @@ console.log("Testing idiomatic API...\n");
// Test getter access (property)
console.log("1. Getter access (.by.dateindex):");
const all = await client.series.prices.split.close.usd.by.day1;
console.log(` Total: ${all.total}, Got: ${all.data.length} items\n`);
console.log(` Got: ${all.data.length} items\n`);
// Test dynamic access (bracket notation)
console.log("2. Dynamic access (.by['dateindex']):");
const allDynamic = await client.series.prices.split.close.usd.by.day1;
console.log(
` Total: ${allDynamic.total}, Got: ${allDynamic.data.length} items\n`,
);
console.log(` Got: ${allDynamic.data.length} items\n`);
// Test fetch all (explicit .fetch())
console.log("3. Explicit .fetch():");
const allExplicit = await client.series.prices.split.close.usd.by.day1.fetch();
console.log(
` Total: ${allExplicit.total}, Got: ${allExplicit.data.length} items\n`,
);
console.log(` Got: ${allExplicit.data.length} items\n`);
// Test first(n)
console.log("4. First 5 items (.first(5)):");
const first5 = await client.series.prices.split.close.usd.by.day1.first(5);
console.log(
` Total: ${first5.total}, Start: ${first5.start}, End: ${first5.end}, Got: ${first5.data.length} items\n`,
` Start: ${first5.start}, End: ${first5.end}, Got: ${first5.data.length} items\n`,
);
// Test last(n)
console.log("5. Last 5 items (.last(5)):");
const last5 = await client.series.prices.split.close.usd.by.day1.last(5);
console.log(
` Total: ${last5.total}, Start: ${last5.start}, End: ${last5.end}, Got: ${last5.data.length} items\n`,
` Start: ${last5.start}, End: ${last5.end}, Got: ${last5.data.length} items\n`,
);
// Test slice(start, end)
console.log("6. Slice 10-20 (.slice(10, 20)):");
const sliced = await client.series.prices.split.close.usd.by.day1.slice(10, 20);
console.log(
` Total: ${sliced.total}, Start: ${sliced.start}, End: ${sliced.end}, Got: ${sliced.data.length} items\n`,
` Start: ${sliced.start}, End: ${sliced.end}, Got: ${sliced.data.length} items\n`,
);
// Test get(index) - single item
console.log("7. Single item (.get(100)):");
const single = await client.series.prices.split.close.usd.by.day1.get(100);
console.log(
` Total: ${single.total}, Start: ${single.start}, End: ${single.end}, Got: ${single.data.length} item(s)\n`,
` Start: ${single.start}, End: ${single.end}, Got: ${single.data.length} item(s)\n`,
);
// Test skip(n).take(m) chaining
@@ -57,7 +53,7 @@ const skipTake = await client.series.prices.split.close.usd.by.day1
.skip(100)
.take(10);
console.log(
` Total: ${skipTake.total}, Start: ${skipTake.start}, End: ${skipTake.end}, Got: ${skipTake.data.length} items\n`,
` Start: ${skipTake.start}, End: ${skipTake.end}, Got: ${skipTake.data.length} items\n`,
);
// Test fetchCsv

View File

@@ -77,7 +77,7 @@ async function testConsistency() {
try {
const result = await endpoint.last(0);
const total = result.total;
const total = result.end;
if (!byIndex.has(idxName)) {
byIndex.set(idxName, []);

View File

@@ -12,9 +12,7 @@ console.log("Testing MetricData helpers...\n");
// Fetch a date-based metric
console.log("1. Fetching price data (day1):");
const price = await client.series.prices.split.close.usd.by.day1.first(5);
console.log(
` Total: ${price.total}, Start: ${price.start}, End: ${price.end}`,
);
console.log(` Start: ${price.start}, End: ${price.end}`);
// Test isDateBased
console.log("\n2. isDateBased:");
@@ -98,9 +96,7 @@ if (count !== 5) throw new Error("Expected 5 iterations");
// Test with non-date-based index (height)
console.log("\n11. Testing height-based metric:");
const heightMetric = await client.series.prices.spot.usd.by.height.last(3);
console.log(
` Total: ${heightMetric.total}, Start: ${heightMetric.start}, End: ${heightMetric.end}`,
);
console.log(` Start: ${heightMetric.start}, End: ${heightMetric.end}`);
if (heightMetric.isDateBased)
throw new Error("height should not be date-based");

View File

@@ -1812,7 +1812,6 @@ class SeriesData(Generic[T]):
version: int
index: Index
type: str
total: int
start: int
end: int
stamp: str
@@ -7974,7 +7973,7 @@ class BrkClient(BrkClientBase):
Endpoint: `GET /api/series/indexes`"""
return self.get_json('/api/series/indexes')
def list_series(self, page: Optional[float] = None, per_page: Optional[float] = None) -> PaginatedSeries:
def list_series(self, page: Optional[int] = None, per_page: Optional[int] = None) -> PaginatedSeries:
"""Series list.
Paginated flat list of all available series names. Use `page` query param for pagination.
@@ -8050,7 +8049,7 @@ class BrkClient(BrkClientBase):
Endpoint: `GET /api/series/{series}/{index}/latest`"""
return self.get_text(f'/api/series/{series}/{index}/latest')
def get_series_len(self, series: SeriesName, index: Index) -> float:
def get_series_len(self, series: SeriesName, index: Index) -> int:
"""Get series data length.
Returns the total number of data points for a series at the given index.
@@ -8478,7 +8477,7 @@ class BrkClient(BrkClientBase):
Endpoint: `GET /api/v1/mining/pools/{time_period}`"""
return self.get_json(f'/api/v1/mining/pools/{time_period}')
def get_reward_stats(self, block_count: float) -> RewardStats:
def get_reward_stats(self, block_count: int) -> RewardStats:
"""Mining reward statistics.
Get mining reward statistics for the last N blocks including total rewards, fees, and transaction count.
@@ -8498,7 +8497,7 @@ class BrkClient(BrkClientBase):
Endpoint: `GET /api/v1/prices`"""
return self.get_json('/api/v1/prices')
def get_transaction_times(self) -> List[float]:
def get_transaction_times(self) -> List[int]:
"""Transaction first-seen times.
Returns timestamps when transactions were first seen in the mempool. Returns 0 for mined or unknown transactions.