From a7e41df1c62b3e32f629b58c938cb18bb1c249aa Mon Sep 17 00:00:00 2001 From: nym21 Date: Wed, 29 Apr 2026 12:06:22 +0200 Subject: [PATCH] clients: add .len() --- .../src/generators/javascript/api.rs | 18 ++-- .../src/generators/javascript/client.rs | 15 ++- .../src/generators/javascript/types.rs | 19 ++++ .../src/generators/python/client.rs | 1 - crates/brk_bindgen/src/openapi.rs | 2 +- crates/brk_client/src/lib.rs | 4 +- crates/brk_mempool/src/steps/applier.rs | 10 -- crates/brk_query/src/impl/series.rs | 71 +++++++++++++-- crates/brk_server/src/api/series.rs | 3 +- crates/brk_server/src/cache/params.rs | 91 ++++++++++++++----- crates/brk_types/src/index.rs | 51 +++++++---- crates/brk_types/src/series_data.rs | 9 +- modules/brk-client/index.js | 81 +++++++++-------- modules/brk-client/tests/basic.js | 20 ++-- modules/brk-client/tests/consistency.js | 2 +- modules/brk-client/tests/metric_data.js | 8 +- packages/brk_client/brk_client/__init__.py | 9 +- 17 files changed, 265 insertions(+), 149 deletions(-) diff --git a/crates/brk_bindgen/src/generators/javascript/api.rs b/crates/brk_bindgen/src/generators/javascript/api.rs index a064d9aef..e9f45bb08 100644 --- a/crates/brk_bindgen/src/generators/javascript/api.rs +++ b/crates/brk_bindgen/src/generators/javascript/api.rs @@ -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(¶m.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(¶m.param_type); writeln!( output, " * @param {{{}{}}} [{}]{}", - param.param_type, optional, param.name, desc + ty, optional, param.name, desc ) .unwrap(); } diff --git a/crates/brk_bindgen/src/generators/javascript/client.rs b/crates/brk_bindgen/src/generators/javascript/client.rs index 2aff616e3..9c714eb76 100644 --- a/crates/brk_bindgen/src/generators/javascript/client.rs +++ b/crates/brk_bindgen/src/generators/javascript/client.rs @@ -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}} skip - Skip first n items, chain with take() * @property {{(onUpdate?: (value: SeriesData) => void) => Promise>}} fetch - Fetch all data * @property {{() => Promise}} fetchCsv - Fetch all data as CSV + * @property {{() => Promise}} len - Get total number of data points + * @property {{() => Promise}} version - Get the current version of the series * @property {{Thenable}} then - Thenable (await endpoint) * @property {{string}} path - The endpoint path */ @@ -250,6 +251,8 @@ function _wrapSeriesData(raw) {{ * @property {{(n: number) => DateSkippedBuilder}} skip - Skip first n items, chain with take() * @property {{(onUpdate?: (value: DateSeriesData) => void) => Promise>}} fetch - Fetch all data * @property {{() => Promise}} fetchCsv - Fetch all data as CSV + * @property {{() => Promise}} len - Get total number of data points + * @property {{() => Promise}} version - Get the current version of the series * @property {{DateThenable}} 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}} @@ -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 {{{}}} */", + "/** @template T @param {{BrkClient}} client @param {{string}} name @returns {{{}}} */", 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(); diff --git a/crates/brk_bindgen/src/generators/javascript/types.rs b/crates/brk_bindgen/src/generators/javascript/types.rs index 2b3cfddbf..85f12d1cd 100644 --- a/crates/brk_bindgen/src/generators/javascript/types.rs +++ b/crates/brk_bindgen/src/generators/javascript/types.rs @@ -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`, 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("", ""); + 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()) { diff --git a/crates/brk_bindgen/src/generators/python/client.rs b/crates/brk_bindgen/src/generators/python/client.rs index 4a2294619..3e5e85361 100644 --- a/crates/brk_bindgen/src/generators/python/client.rs +++ b/crates/brk_bindgen/src/generators/python/client.rs @@ -221,7 +221,6 @@ class SeriesData(Generic[T]): version: int index: Index type: str - total: int start: int end: int stamp: str diff --git a/crates/brk_bindgen/src/openapi.rs b/crates/brk_bindgen/src/openapi.rs index 5833fa6ef..921564e9d 100644 --- a/crates/brk_bindgen/src/openapi.rs +++ b/crates/brk_bindgen/src/openapi.rs @@ -364,7 +364,7 @@ fn single_type_to_name(t: &SchemaType, schema: &ObjectSchema) -> Option 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 { diff --git a/crates/brk_client/src/lib.rs b/crates/brk_client/src/lib.rs index e59253705..1ad1d9b08 100644 --- a/crates/brk_client/src/lib.rs +++ b/crates/brk_client/src/lib.rs @@ -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 { + pub fn get_series_len(&self, series: SeriesName, index: Index) -> Result { 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> { + pub fn get_transaction_times(&self) -> Result> { self.base.get_json(&format!("/api/v1/transaction-times")) } diff --git a/crates/brk_mempool/src/steps/applier.rs b/crates/brk_mempool/src/steps/applier.rs index f848b59e8..fb3a6f680 100644 --- a/crates/brk_mempool/src/steps/applier.rs +++ b/crates/brk_mempool/src/steps/applier.rs @@ -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); diff --git a/crates/brk_query/src/impl/series.rs b/crates/brk_query/src/impl/series.rs index e93ad7f54..96039dde2 100644 --- a/crates/brk_query/src/impl/series.rs +++ b/crates/brk_query/src/impl/series.rs @@ -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 { + 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 { @@ -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, } impl ResolvedQuery { diff --git a/crates/brk_server/src/api/series.rs b/crates/brk_server/src/api/series.rs index 80d3eac6d..a30b0d37b 100644 --- a/crates/brk_server/src/api/series.rs +++ b/crates/brk_server/src/api/series.rs @@ -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, ); diff --git a/crates/brk_server/src/cache/params.rs b/crates/brk_server/src/cache/params.rs index f94eb9334..4410177c6 100644 --- a/crates/brk_server/src/cache/params.rs +++ b/crates/brk_server/src/cache/params.rs @@ -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, + 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()); } } diff --git a/crates/brk_types/src/index.rs b/crates/brk_types/src/index.rs index b3b9f2701..6af52771d 100644 --- a/crates/brk_types/src/index.rs +++ b/crates/brk_types/src/index.rs @@ -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, } } diff --git a/crates/brk_types/src/series_data.rs b/crates/brk_types/src/series_data.rs index e3b4905a7..6d3c73725 100644 --- a/crates/brk_types/src/series_data.rs +++ b/crates/brk_types/src/series_data.rs @@ -20,8 +20,6 @@ pub struct SeriesData { /// 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 { } 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()); diff --git a/modules/brk-client/index.js b/modules/brk-client/index.js index 5322bdc2c..274ff069b 100644 --- a/modules/brk-client/index.js +++ b/modules/brk-client/index.js @@ -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} skip - Skip first n items, chain with take() * @property {(onUpdate?: (value: SeriesData) => void) => Promise>} fetch - Fetch all data * @property {() => Promise} fetchCsv - Fetch all data as CSV + * @property {() => Promise} len - Get total number of data points + * @property {() => Promise} version - Get the current version of the series * @property {Thenable} then - Thenable (await endpoint) * @property {string} path - The endpoint path */ @@ -1529,6 +1530,8 @@ function _wrapSeriesData(raw) { * @property {(n: number) => DateSkippedBuilder} skip - Skip first n items, chain with take() * @property {(onUpdate?: (value: DateSeriesData) => void) => Promise>} fetch - Fetch all data * @property {() => Promise} fetchCsv - Fetch all data as CSV + * @property {() => Promise} len - Get total number of data points + * @property {() => Promise} version - Get the current version of the series * @property {DateThenable} 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} @@ -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, readonly minute30: DateSeriesEndpoint, readonly hour1: DateSeriesEndpoint, readonly hour4: DateSeriesEndpoint, readonly hour12: DateSeriesEndpoint, readonly day1: DateSeriesEndpoint, readonly day3: DateSeriesEndpoint, readonly week1: DateSeriesEndpoint, readonly month1: DateSeriesEndpoint, readonly month3: DateSeriesEndpoint, readonly month6: DateSeriesEndpoint, readonly year1: DateSeriesEndpoint, readonly year10: DateSeriesEndpoint, readonly halving: SeriesEndpoint, readonly epoch: SeriesEndpoint, readonly height: SeriesEndpoint }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint|undefined }} SeriesPattern1 */ -/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern1} */ +/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern1} */ function createSeriesPattern1(client, name) { return /** @type {SeriesPattern1} */ (_mp(client, name, _i1)); } /** @template T @typedef {{ name: string, by: { readonly minute10: DateSeriesEndpoint, readonly minute30: DateSeriesEndpoint, readonly hour1: DateSeriesEndpoint, readonly hour4: DateSeriesEndpoint, readonly hour12: DateSeriesEndpoint, readonly day1: DateSeriesEndpoint, readonly day3: DateSeriesEndpoint, readonly week1: DateSeriesEndpoint, readonly month1: DateSeriesEndpoint, readonly month3: DateSeriesEndpoint, readonly month6: DateSeriesEndpoint, readonly year1: DateSeriesEndpoint, readonly year10: DateSeriesEndpoint, readonly halving: SeriesEndpoint, readonly epoch: SeriesEndpoint }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint|undefined }} SeriesPattern2 */ -/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern2} */ +/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern2} */ function createSeriesPattern2(client, name) { return /** @type {SeriesPattern2} */ (_mp(client, name, _i2)); } /** @template T @typedef {{ name: string, by: { readonly minute10: DateSeriesEndpoint }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint|undefined }} SeriesPattern3 */ -/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern3} */ +/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern3} */ function createSeriesPattern3(client, name) { return /** @type {SeriesPattern3} */ (_mp(client, name, _i3)); } /** @template T @typedef {{ name: string, by: { readonly minute30: DateSeriesEndpoint }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint|undefined }} SeriesPattern4 */ -/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern4} */ +/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern4} */ function createSeriesPattern4(client, name) { return /** @type {SeriesPattern4} */ (_mp(client, name, _i4)); } /** @template T @typedef {{ name: string, by: { readonly hour1: DateSeriesEndpoint }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint|undefined }} SeriesPattern5 */ -/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern5} */ +/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern5} */ function createSeriesPattern5(client, name) { return /** @type {SeriesPattern5} */ (_mp(client, name, _i5)); } /** @template T @typedef {{ name: string, by: { readonly hour4: DateSeriesEndpoint }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint|undefined }} SeriesPattern6 */ -/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern6} */ +/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern6} */ function createSeriesPattern6(client, name) { return /** @type {SeriesPattern6} */ (_mp(client, name, _i6)); } /** @template T @typedef {{ name: string, by: { readonly hour12: DateSeriesEndpoint }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint|undefined }} SeriesPattern7 */ -/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern7} */ +/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern7} */ function createSeriesPattern7(client, name) { return /** @type {SeriesPattern7} */ (_mp(client, name, _i7)); } /** @template T @typedef {{ name: string, by: { readonly day1: DateSeriesEndpoint }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint|undefined }} SeriesPattern8 */ -/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern8} */ +/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern8} */ function createSeriesPattern8(client, name) { return /** @type {SeriesPattern8} */ (_mp(client, name, _i8)); } /** @template T @typedef {{ name: string, by: { readonly day3: DateSeriesEndpoint }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint|undefined }} SeriesPattern9 */ -/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern9} */ +/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern9} */ function createSeriesPattern9(client, name) { return /** @type {SeriesPattern9} */ (_mp(client, name, _i9)); } /** @template T @typedef {{ name: string, by: { readonly week1: DateSeriesEndpoint }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint|undefined }} SeriesPattern10 */ -/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern10} */ +/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern10} */ function createSeriesPattern10(client, name) { return /** @type {SeriesPattern10} */ (_mp(client, name, _i10)); } /** @template T @typedef {{ name: string, by: { readonly month1: DateSeriesEndpoint }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint|undefined }} SeriesPattern11 */ -/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern11} */ +/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern11} */ function createSeriesPattern11(client, name) { return /** @type {SeriesPattern11} */ (_mp(client, name, _i11)); } /** @template T @typedef {{ name: string, by: { readonly month3: DateSeriesEndpoint }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint|undefined }} SeriesPattern12 */ -/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern12} */ +/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern12} */ function createSeriesPattern12(client, name) { return /** @type {SeriesPattern12} */ (_mp(client, name, _i12)); } /** @template T @typedef {{ name: string, by: { readonly month6: DateSeriesEndpoint }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint|undefined }} SeriesPattern13 */ -/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern13} */ +/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern13} */ function createSeriesPattern13(client, name) { return /** @type {SeriesPattern13} */ (_mp(client, name, _i13)); } /** @template T @typedef {{ name: string, by: { readonly year1: DateSeriesEndpoint }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint|undefined }} SeriesPattern14 */ -/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern14} */ +/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern14} */ function createSeriesPattern14(client, name) { return /** @type {SeriesPattern14} */ (_mp(client, name, _i14)); } /** @template T @typedef {{ name: string, by: { readonly year10: DateSeriesEndpoint }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint|undefined }} SeriesPattern15 */ -/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern15} */ +/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern15} */ function createSeriesPattern15(client, name) { return /** @type {SeriesPattern15} */ (_mp(client, name, _i15)); } /** @template T @typedef {{ name: string, by: { readonly halving: SeriesEndpoint }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint|undefined }} SeriesPattern16 */ -/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern16} */ +/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern16} */ function createSeriesPattern16(client, name) { return /** @type {SeriesPattern16} */ (_mp(client, name, _i16)); } /** @template T @typedef {{ name: string, by: { readonly epoch: SeriesEndpoint }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint|undefined }} SeriesPattern17 */ -/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern17} */ +/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern17} */ function createSeriesPattern17(client, name) { return /** @type {SeriesPattern17} */ (_mp(client, name, _i17)); } /** @template T @typedef {{ name: string, by: { readonly height: SeriesEndpoint }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint|undefined }} SeriesPattern18 */ -/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern18} */ +/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern18} */ function createSeriesPattern18(client, name) { return /** @type {SeriesPattern18} */ (_mp(client, name, _i18)); } /** @template T @typedef {{ name: string, by: { readonly tx_index: SeriesEndpoint }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint|undefined }} SeriesPattern19 */ -/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern19} */ +/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern19} */ function createSeriesPattern19(client, name) { return /** @type {SeriesPattern19} */ (_mp(client, name, _i19)); } /** @template T @typedef {{ name: string, by: { readonly txin_index: SeriesEndpoint }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint|undefined }} SeriesPattern20 */ -/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern20} */ +/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern20} */ function createSeriesPattern20(client, name) { return /** @type {SeriesPattern20} */ (_mp(client, name, _i20)); } /** @template T @typedef {{ name: string, by: { readonly txout_index: SeriesEndpoint }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint|undefined }} SeriesPattern21 */ -/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern21} */ +/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern21} */ function createSeriesPattern21(client, name) { return /** @type {SeriesPattern21} */ (_mp(client, name, _i21)); } /** @template T @typedef {{ name: string, by: { readonly empty_output_index: SeriesEndpoint }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint|undefined }} SeriesPattern22 */ -/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern22} */ +/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern22} */ function createSeriesPattern22(client, name) { return /** @type {SeriesPattern22} */ (_mp(client, name, _i22)); } /** @template T @typedef {{ name: string, by: { readonly op_return_index: SeriesEndpoint }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint|undefined }} SeriesPattern23 */ -/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern23} */ +/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern23} */ function createSeriesPattern23(client, name) { return /** @type {SeriesPattern23} */ (_mp(client, name, _i23)); } /** @template T @typedef {{ name: string, by: { readonly p2a_addr_index: SeriesEndpoint }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint|undefined }} SeriesPattern24 */ -/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern24} */ +/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern24} */ function createSeriesPattern24(client, name) { return /** @type {SeriesPattern24} */ (_mp(client, name, _i24)); } /** @template T @typedef {{ name: string, by: { readonly p2ms_output_index: SeriesEndpoint }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint|undefined }} SeriesPattern25 */ -/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern25} */ +/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern25} */ function createSeriesPattern25(client, name) { return /** @type {SeriesPattern25} */ (_mp(client, name, _i25)); } /** @template T @typedef {{ name: string, by: { readonly p2pk33_addr_index: SeriesEndpoint }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint|undefined }} SeriesPattern26 */ -/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern26} */ +/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern26} */ function createSeriesPattern26(client, name) { return /** @type {SeriesPattern26} */ (_mp(client, name, _i26)); } /** @template T @typedef {{ name: string, by: { readonly p2pk65_addr_index: SeriesEndpoint }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint|undefined }} SeriesPattern27 */ -/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern27} */ +/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern27} */ function createSeriesPattern27(client, name) { return /** @type {SeriesPattern27} */ (_mp(client, name, _i27)); } /** @template T @typedef {{ name: string, by: { readonly p2pkh_addr_index: SeriesEndpoint }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint|undefined }} SeriesPattern28 */ -/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern28} */ +/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern28} */ function createSeriesPattern28(client, name) { return /** @type {SeriesPattern28} */ (_mp(client, name, _i28)); } /** @template T @typedef {{ name: string, by: { readonly p2sh_addr_index: SeriesEndpoint }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint|undefined }} SeriesPattern29 */ -/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern29} */ +/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern29} */ function createSeriesPattern29(client, name) { return /** @type {SeriesPattern29} */ (_mp(client, name, _i29)); } /** @template T @typedef {{ name: string, by: { readonly p2tr_addr_index: SeriesEndpoint }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint|undefined }} SeriesPattern30 */ -/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern30} */ +/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern30} */ function createSeriesPattern30(client, name) { return /** @type {SeriesPattern30} */ (_mp(client, name, _i30)); } /** @template T @typedef {{ name: string, by: { readonly p2wpkh_addr_index: SeriesEndpoint }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint|undefined }} SeriesPattern31 */ -/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern31} */ +/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern31} */ function createSeriesPattern31(client, name) { return /** @type {SeriesPattern31} */ (_mp(client, name, _i31)); } /** @template T @typedef {{ name: string, by: { readonly p2wsh_addr_index: SeriesEndpoint }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint|undefined }} SeriesPattern32 */ -/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern32} */ +/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern32} */ function createSeriesPattern32(client, name) { return /** @type {SeriesPattern32} */ (_mp(client, name, _i32)); } /** @template T @typedef {{ name: string, by: { readonly unknown_output_index: SeriesEndpoint }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint|undefined }} SeriesPattern33 */ -/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern33} */ +/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern33} */ function createSeriesPattern33(client, name) { return /** @type {SeriesPattern33} */ (_mp(client, name, _i33)); } /** @template T @typedef {{ name: string, by: { readonly funded_addr_index: SeriesEndpoint }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint|undefined }} SeriesPattern34 */ -/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern34} */ +/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern34} */ function createSeriesPattern34(client, name) { return /** @type {SeriesPattern34} */ (_mp(client, name, _i34)); } /** @template T @typedef {{ name: string, by: { readonly empty_addr_index: SeriesEndpoint }, indexes: () => readonly Index[], get: (index: Index) => SeriesEndpoint|undefined }} SeriesPattern35 */ -/** @template T @param {BrkClientBase} client @param {string} name @returns {SeriesPattern35} */ +/** @template T @param {BrkClient} client @param {string} name @returns {SeriesPattern35} */ function createSeriesPattern35(client, name) { return /** @type {SeriesPattern35} */ (_mp(client, name, _i35)); } // Reusable structural pattern factories diff --git a/modules/brk-client/tests/basic.js b/modules/brk-client/tests/basic.js index ea49e9eec..3dbe83b4a 100644 --- a/modules/brk-client/tests/basic.js +++ b/modules/brk-client/tests/basic.js @@ -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 diff --git a/modules/brk-client/tests/consistency.js b/modules/brk-client/tests/consistency.js index 008c54376..5014f534d 100644 --- a/modules/brk-client/tests/consistency.js +++ b/modules/brk-client/tests/consistency.js @@ -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, []); diff --git a/modules/brk-client/tests/metric_data.js b/modules/brk-client/tests/metric_data.js index 350e44625..d8497af5a 100644 --- a/modules/brk-client/tests/metric_data.js +++ b/modules/brk-client/tests/metric_data.js @@ -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"); diff --git a/packages/brk_client/brk_client/__init__.py b/packages/brk_client/brk_client/__init__.py index 0f536bea6..619dfddd4 100644 --- a/packages/brk_client/brk_client/__init__.py +++ b/packages/brk_client/brk_client/__init__.py @@ -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.