global: snapshot

This commit is contained in:
nym21
2025-12-19 15:48:32 +01:00
parent 03b83846ef
commit e134ed11a9
30 changed files with 265 additions and 184 deletions

1
Cargo.lock generated
View File

@@ -569,6 +569,7 @@ dependencies = [
"brk_query",
"brk_types",
"schemars",
"serde_json",
"vecdb",
]

View File

@@ -12,4 +12,5 @@ build = "build.rs"
brk_query = { workspace = true }
brk_types = { workspace = true }
schemars = { workspace = true }
serde_json = { workspace = true }
vecdb = { workspace = true }

View File

@@ -2,7 +2,7 @@
## Goal
Generate typed API clients for **Rust, TypeScript, and Python** with:
Generate typed API clients for **Rust, JavaScript, and Python** with:
- **Discoverability**: Full IDE autocomplete for 20k+ metrics
- **Ease of use**: Fluent API with `.fetch()` on each metric node
@@ -13,17 +13,22 @@ Generate typed API clients for **Rust, TypeScript, and Python** with:
1. **`js.rs`**: Generates compressed metric catalogs for JS (constants only, no HTTP client)
2. **`tree.rs`**: (kept for reference, not compiled) Brainstorming output for pattern extraction
3. **`generator/`**: Module structure for client generation
- `types.rs`: Intermediate representation (`ClientMetadata`, `MetricInfo`, `IndexPattern`)
- `types.rs`: Intermediate representation (`ClientMetadata`, `MetricInfo`, `IndexPattern`, `schema_to_jsdoc`)
- `rust.rs`: Rust client generation (stub)
- `typescript.rs`: TypeScript client generation (stub)
- `javascript.rs`: JavaScript + JSDoc client generation ✅ IMPLEMENTED
- `python.rs`: Python client generation (stub)
### What's Working
- **JS + JSDoc generator**: Generates `client.js` with full JSDoc type annotations
- **schemars integration**: JSON schemas embedded in `MetricLeafWithSchema` for type info
- **Tree navigation**: `client.tree.blocks.difficulty.fetch()` pattern
### What's Missing
- HTTP client integration (`.fetch()` methods)
- OpenAPI as input source
- Rust client using `brk_types` instead of generating types
- Typed response types per metric
- OpenAPI integration for non-metric endpoints
- Python client implementation
- Rust client implementation
## Target Architecture
@@ -39,10 +44,10 @@ Generate typed API clients for **Rust, TypeScript, and Python** with:
└─────────────────────────────────────────────────────────────┘
```
### Output: Fluent Client (Option B)
### Output: Fluent Client
```typescript
// TypeScript
```javascript
// JavaScript (with JSDoc for IDE support)
const client = new BrkClient("http://localhost:3000");
const data = await client.tree.supply.active.by_date.fetch();
// ^^^^ autocomplete all the way down
@@ -66,12 +71,28 @@ let data = client.tree.supply.active.by_date.fetch().await?;
Each tree leaf becomes a "smart node" holding a client reference:
```typescript
// TypeScript
class MetricNode<T = unknown> {
constructor(private client: BrkClient, private path: string) {}
async fetch(): Promise<T> {
return this.client.get<T>(this.path);
```javascript
// JavaScript + JSDoc
/**
* Metric node with fetch capability
* @template T
*/
class MetricNode {
/**
* @param {BrkClientBase} client
* @param {string} path
*/
constructor(client, path) {
this._client = client;
this._path = path;
}
/**
* Fetch the metric value
* @returns {Promise<T>}
*/
async fetch() {
return this._client.get(this._path);
}
}
```
@@ -208,7 +229,7 @@ This enables fully typed client generation.
### The Solution
Changed `TreeNode::Leaf(String)` to `TreeNode::Leaf(MetricLeaf)` where:
Changed `TreeNode::Leaf(String)` to `TreeNode::Leaf(MetricLeafWithSchema)` where:
```rust
#[derive(Debug, Clone, Serialize, PartialEq, Eq, JsonSchema)]
@@ -217,29 +238,45 @@ pub struct MetricLeaf {
pub value_type: String,
pub indexes: BTreeSet<Index>,
}
#[derive(Debug, Clone, Serialize, JsonSchema)]
pub struct MetricLeafWithSchema {
#[serde(flatten)]
pub leaf: MetricLeaf,
#[serde(skip)]
pub schema: serde_json::Value, // JSON Schema from schemars
}
```
#### Implementation
**brk_types/src/treenode.rs**:
- Added `MetricLeaf` struct with `name`, `value_type`, and `indexes`
- Added `merge_indexes()` method to union indexes when flattening tree
- Updated `TreeNode` enum to use `Leaf(MetricLeaf)`
- Updated merge logic to handle index merging
- Added `MetricLeafWithSchema` wrapper with JSON schema
- Helper methods: `name()`, `value_type()`, `indexes()`, `is_same_metric()`, `merge_indexes()`
- Updated `TreeNode` enum to use `Leaf(MetricLeafWithSchema)`
**brk_traversable/src/lib.rs**:
- Added `make_leaf<I, V>()` helper that creates `MetricLeaf` with proper fields
- Updated all `Traversable::to_tree_node()` implementations
- Added `make_leaf<I, T, V>()` helper that creates `MetricLeafWithSchema` with schema from schemars
- Updated all `Traversable::to_tree_node()` implementations with `JsonSchema` bounds
- Schema generated via `schemars::SchemaGenerator::default().into_root_schema_for::<T>()`
**vecdb** (schemars feature):
- Added optional `schemars` dependency
- Added `AnySchemaVec` trait with blanket impl for `TypedVec where T: JsonSchema`
### Result
The catalog tree now includes full type information at each leaf:
The catalog tree now includes full type information and JSON schema at each leaf:
```rust
TreeNode::Leaf(MetricLeaf {
name: "difficulty".to_string(),
value_type: "StoredF64".to_string(),
indexes: btreeset![Index::Height, Index::Date],
TreeNode::Leaf(MetricLeafWithSchema {
leaf: MetricLeaf {
name: "difficulty".to_string(),
value_type: "StoredF64".to_string(),
indexes: btreeset![Index::Height, Index::Date],
},
schema: json!({ "type": "number" }), // schemars-generated
})
```
@@ -273,32 +310,46 @@ When trees are merged/simplified, indexes are unioned together.
- [x] **vecdb**: Add `short_type_name<T>()` helper in `traits/printable.rs`
- [x] **vecdb**: Add `value_type_to_string()` to `AnyVec` trait
- [x] **vecdb**: Implement in all vec variants (eager, lazy, raw, compressed, macros)
- [x] **brk_types**: Enhance `TreeNode::Leaf` to include `MetricLeaf` with name, value_type, indexes
- [x] **brk_traversable**: Update all `to_tree_node()` implementations to populate `MetricLeaf`
- [x] **vecdb**: Add optional `schemars` feature with `AnySchemaVec` trait
- [x] **brk_types**: Enhance `TreeNode::Leaf` to include `MetricLeafWithSchema`
- [x] **brk_types**: Add `JsonSchema` derives to all value types
- [x] **brk_traversable**: Update all `to_tree_node()` implementations with schemars integration
- [x] **brk_query**: Export `Vecs` publicly for client generation
- [x] **brk_binder**: Set up generator module structure (types, rust, typescript, python stubs)
- [x] **brk_binder**: Set up generator module structure
- [x] **brk**: Verify compilation
### Phase 1: Client Foundation
### Phase 1: JavaScript Client ✅ COMPLETE
- [ ] Define `MetricNode<T>` struct/class for each language
- [ ] Define `BrkClient` with base HTTP functionality
- [ ] Implement `ClientMetadata::from_vecs()` to extract metadata from `brk_query::Vecs`
- [ ] Client holds reference, nodes borrow it
- [x] Define `MetricNode` class with JSDoc generics
- [x] Define `BrkClient` with base HTTP functionality
- [x] Implement `ClientMetadata::from_vecs()` to extract metadata
- [x] Generate `client.js` with full JSDoc type annotations
- [x] Use `schema_to_jsdoc()` to convert JSON schemas to JSDoc types
- [x] Tree navigation: `client.tree.category.metric.fetch()`
### Phase 2: Type-Aware Generation
### Phase 2: OpenAPI Integration (NEXT)
- [ ] Create type mapping: `value_type_string → Rust type / TS type / Python type`
- [ ] Generate typed `MetricNode<T>` with correct `T` per metric
- [ ] For Rust: import from `brk_types` instead of generating
- [ ] Add `openapiv3` crate dependency
- [ ] Parse OpenAPI spec from aide (brk_server generates this)
- [ ] Extract non-metric endpoint definitions (health, info, catalog, etc.)
- [ ] Generate methods for each endpoint with proper types
- [ ] Merge with tree-based metric access
### Phase 3: OpenAPI Integration
### Phase 3: Python Client
- [ ] Parse OpenAPI spec with `openapiv3` crate
- [ ] Extract non-metric endpoint definitions
- [ ] Generate methods for health, info, catalog, etc.
- [ ] Define `MetricNode` class with type hints
- [ ] Define `BrkClient` with httpx/aiohttp
- [ ] Generate typed methods from OpenAPI
- [ ] Generate tree navigation
### Phase 4: Polish
### Phase 4: Rust Client
- [ ] Define `MetricNode<T>` struct using `brk_types`
- [ ] Define `BrkClient` with reqwest
- [ ] Import types from `brk_types` instead of generating
- [ ] Generate tree navigation with proper lifetimes
### Phase 5: Polish
- [ ] Error types per language
- [ ] Documentation generation
@@ -315,22 +366,26 @@ crates/brk_binder/
│ ├── tree.rs # Pattern extraction (reference only, not compiled)
│ └── generator/
│ ├── mod.rs
│ ├── types.rs # ClientMetadata, MetricInfo, IndexPattern
│ ├── rust.rs # Rust client generation
│ ├── typescript.rs
│ └── python.rs
│ ├── types.rs # ClientMetadata, MetricInfo, IndexPattern, schema_to_jsdoc
│ ├── javascript.rs # JavaScript + JSDoc client generation
│ ├── python.rs # Python client generation (stub)
│ └── rust.rs # Rust client generation (stub)
├── Cargo.toml
├── README.md
└── DESIGN.md # This file
```
## Dependencies (Proposed)
## Dependencies
```toml
[dependencies]
openapiv3 = "2" # OpenAPI parsing
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_yaml = "0.9" # If parsing YAML specs
tera = "1" # Optional: templating
brk_query = { workspace = true }
brk_types = { workspace = true }
schemars = { workspace = true }
serde_json = { workspace = true }
vecdb = { workspace = true }
# For OpenAPI integration (Phase 2):
# openapiv3 = "2" # OpenAPI parsing
# serde_yaml = "0.9" # If parsing YAML specs
```

View File

@@ -1,95 +1,75 @@
use std::collections::BTreeMap;
use std::fmt::Write as FmtWrite;
use std::fs;
use std::io;
use std::path::Path;
use brk_types::{MetricLeaf, TreeNode};
use brk_types::TreeNode;
use super::{to_camel_case, ClientMetadata, IndexPattern};
use super::{schema_to_jsdoc, to_camel_case, ClientMetadata, IndexPattern};
/// Generate TypeScript client from metadata
pub fn generate_typescript_client(metadata: &ClientMetadata, output_dir: &Path) -> io::Result<()> {
/// Generate JavaScript + JSDoc client from metadata
pub fn generate_javascript_client(metadata: &ClientMetadata, output_dir: &Path) -> io::Result<()> {
let mut output = String::new();
// Header
writeln!(output, "// Auto-generated BRK TypeScript client").unwrap();
writeln!(output, "// Auto-generated BRK JavaScript client").unwrap();
writeln!(output, "// Do not edit manually\n").unwrap();
// Generate pattern interfaces for index groupings
generate_pattern_interfaces(&mut output, &metadata.patterns);
// Generate value type aliases
generate_value_types(&mut output, metadata);
// Generate pattern JSDoc typedefs for index groupings
generate_pattern_typedefs(&mut output, &metadata.patterns);
// Generate the base client class
generate_base_client(&mut output);
// Generate tree types from catalog
generate_tree_types(&mut output, &metadata.catalog);
// Generate tree JSDoc typedefs from catalog
generate_tree_typedefs(&mut output, &metadata.catalog);
// Generate the main client class with tree
generate_main_client(&mut output, &metadata.catalog);
fs::write(output_dir.join("client.ts"), output)?;
fs::write(output_dir.join("client.js"), output)?;
Ok(())
}
/// Generate TypeScript interfaces for common index patterns
fn generate_pattern_interfaces(output: &mut String, patterns: &[IndexPattern]) {
writeln!(output, "// Index pattern interfaces").unwrap();
/// Generate JSDoc typedefs for common index patterns
fn generate_pattern_typedefs(output: &mut String, patterns: &[IndexPattern]) {
writeln!(output, "// Index pattern typedefs").unwrap();
writeln!(output, "// Reusable patterns for metrics with the same index groupings\n").unwrap();
for pattern in patterns {
let pattern_name = pattern_to_name(pattern);
writeln!(output, "export interface {}<T> {{", pattern_name).unwrap();
writeln!(output, "/**").unwrap();
writeln!(output, " * @template T").unwrap();
writeln!(output, " * @typedef {{Object}} {}", pattern_name).unwrap();
for index in &pattern.indexes {
let field_name = to_camel_case(&index.serialize_long());
writeln!(output, " {}: MetricNode<T>;", field_name).unwrap();
writeln!(output, " * @property {{MetricNode<T>}} {}", field_name).unwrap();
}
writeln!(output, "}}\n").unwrap();
writeln!(output, " */\n").unwrap();
}
}
/// Generate TypeScript type aliases for value types
fn generate_value_types(output: &mut String, metadata: &ClientMetadata) {
writeln!(output, "// Value type aliases").unwrap();
writeln!(output, "// Maps Rust types to TypeScript types\n").unwrap();
// Collect unique value types
let mut value_types: Vec<&str> = metadata
.metrics
.values()
.map(|m| m.value_type.as_str())
.collect();
value_types.sort();
value_types.dedup();
for vt in value_types {
let ts_type = rust_type_to_ts(vt);
writeln!(output, "export type {} = {};", vt, ts_type).unwrap();
}
writeln!(output).unwrap();
}
/// Generate the base BrkClient class with HTTP functionality
fn generate_base_client(output: &mut String) {
writeln!(
output,
r#"// Base HTTP client
export interface BrkClientOptions {{
baseUrl: string;
timeout?: number;
}}
r#"/**
* @typedef {{Object}} BrkClientOptions
* @property {{string}} baseUrl - The base URL for the API
* @property {{number}} [timeout] - Request timeout in ms (default: 30000)
*/
export class BrkClientBase {{
private baseUrl: string;
private timeout: number;
constructor(options: BrkClientOptions | string) {{
/**
* Base HTTP client
*/
class BrkClientBase {{
/**
* @param {{BrkClientOptions|string}} options
*/
constructor(options) {{
if (typeof options === 'string') {{
this.baseUrl = options.replace(/\/$/, '');
this.timeout = 30000;
@@ -99,7 +79,12 @@ export class BrkClientBase {{
}}
}}
async get<T>(path: string): Promise<T> {{
/**
* @template T
* @param {{string}} path
* @returns {{Promise<T>}}
*/
async get(path) {{
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
@@ -120,23 +105,45 @@ export class BrkClientBase {{
}}
}}
export class BrkError extends Error {{
constructor(message: string, public statusCode?: number) {{
/**
* Error class for BRK API errors
*/
class BrkError extends Error {{
/**
* @param {{string}} message
* @param {{number}} [statusCode]
*/
constructor(message, statusCode) {{
super(message);
this.name = 'BrkError';
this.statusCode = statusCode;
}}
}}
// Metric node with fetch capability
export class MetricNode<T> {{
constructor(private client: BrkClientBase, private path: string) {{}}
async fetch(): Promise<T> {{
return this.client.get<T>(this.path);
/**
* Metric node with fetch capability
* @template T
*/
class MetricNode {{
/**
* @param {{BrkClientBase}} client
* @param {{string}} path
*/
constructor(client, path) {{
this._client = client;
this._path = path;
}}
toString(): string {{
return this.path;
/**
* Fetch the metric value
* @returns {{Promise<T>}}
*/
async fetch() {{
return this._client.get(this._path);
}}
toString() {{
return this._path;
}}
}}
@@ -145,45 +152,47 @@ export class MetricNode<T> {{
.unwrap();
}
/// Generate TypeScript types for the catalog tree
fn generate_tree_types(output: &mut String, catalog: &TreeNode) {
writeln!(output, "// Catalog tree types\n").unwrap();
generate_node_type(output, "CatalogTree", catalog, "");
/// Generate JSDoc typedefs for the catalog tree
fn generate_tree_typedefs(output: &mut String, catalog: &TreeNode) {
writeln!(output, "// Catalog tree typedefs\n").unwrap();
generate_node_typedef(output, "CatalogTree", catalog, "");
}
/// Recursively generate type for a tree node
fn generate_node_type(output: &mut String, name: &str, node: &TreeNode, path: &str) {
/// Recursively generate typedef for a tree node
fn generate_node_typedef(output: &mut String, name: &str, node: &TreeNode, path: &str) {
match node {
TreeNode::Leaf(leaf) => {
TreeNode::Leaf(_leaf) => {
// Leaf nodes are MetricNode<ValueType>
// No separate interface needed, handled inline
// No separate typedef needed, handled inline
}
TreeNode::Branch(children) => {
writeln!(output, "export interface {} {{", name).unwrap();
writeln!(output, "/**").unwrap();
writeln!(output, " * @typedef {{Object}} {}", name).unwrap();
for (child_name, child_node) in children {
let field_name = to_camel_case(child_name);
let child_path = if path.is_empty() {
format!("/{}", child_name)
} else {
format!("{}/{}", path, child_name)
};
match child_node {
TreeNode::Leaf(leaf) => {
let value_type = &leaf.value_type;
writeln!(output, " {}: MetricNode<{}>;", field_name, value_type).unwrap();
let js_type = schema_to_jsdoc(&leaf.schema);
writeln!(
output,
" * @property {{MetricNode<{}>}} {}",
js_type, field_name
)
.unwrap();
}
TreeNode::Branch(_) => {
let child_type_name = format!("{}_{}", name, to_pascal_case(child_name));
writeln!(output, " {}: {};", field_name, child_type_name).unwrap();
writeln!(output, " * @property {{{}}} {}", child_type_name, field_name)
.unwrap();
}
}
}
writeln!(output, "}}\n").unwrap();
writeln!(output, " */\n").unwrap();
// Generate child types
// Generate child typedefs
for (child_name, child_node) in children {
if let TreeNode::Branch(_) = child_node {
let child_type_name = format!("{}_{}", name, to_pascal_case(child_name));
@@ -192,7 +201,7 @@ fn generate_node_type(output: &mut String, name: &str, node: &TreeNode, path: &s
} else {
format!("{}/{}", path, child_name)
};
generate_node_type(output, &child_type_name, child_node, &child_path);
generate_node_typedef(output, &child_type_name, child_node, &child_path);
}
}
}
@@ -201,21 +210,34 @@ fn generate_node_type(output: &mut String, name: &str, node: &TreeNode, path: &s
/// Generate the main client class with initialized tree
fn generate_main_client(output: &mut String, catalog: &TreeNode) {
writeln!(output, "// Main client class with catalog tree").unwrap();
writeln!(output, "export class BrkClient extends BrkClientBase {{").unwrap();
writeln!(output, " readonly tree: CatalogTree;\n").unwrap();
writeln!(output, " constructor(options: BrkClientOptions | string) {{").unwrap();
writeln!(output, "/**").unwrap();
writeln!(output, " * Main BRK client with catalog tree").unwrap();
writeln!(output, " * @extends BrkClientBase").unwrap();
writeln!(output, " */").unwrap();
writeln!(output, "class BrkClient extends BrkClientBase {{").unwrap();
writeln!(output, " /**").unwrap();
writeln!(output, " * @param {{BrkClientOptions|string}} options").unwrap();
writeln!(output, " */").unwrap();
writeln!(output, " constructor(options) {{").unwrap();
writeln!(output, " super(options);").unwrap();
writeln!(output, " /** @type {{CatalogTree}} */").unwrap();
writeln!(output, " this.tree = this._buildTree();").unwrap();
writeln!(output, " }}\n").unwrap();
// Generate _buildTree method
writeln!(output, " private _buildTree(): CatalogTree {{").unwrap();
writeln!(output, " /**").unwrap();
writeln!(output, " * @private").unwrap();
writeln!(output, " * @returns {{CatalogTree}}").unwrap();
writeln!(output, " */").unwrap();
writeln!(output, " _buildTree() {{").unwrap();
writeln!(output, " return {{").unwrap();
generate_tree_initializer(output, catalog, "", 3);
writeln!(output, " }};").unwrap();
writeln!(output, " }}").unwrap();
writeln!(output, "}}").unwrap();
writeln!(output, "}}\n").unwrap();
// Export for ES modules
writeln!(output, "export {{ BrkClient, BrkClientBase, BrkError, MetricNode }};").unwrap();
}
/// Generate the tree initializer code
@@ -234,7 +256,7 @@ fn generate_tree_initializer(output: &mut String, node: &TreeNode, path: &str, i
let comma = if i < children.len() - 1 { "," } else { "" };
match child_node {
TreeNode::Leaf(leaf) => {
TreeNode::Leaf(_) => {
writeln!(
output,
"{}{}: new MetricNode(this, '{}'){}",
@@ -252,7 +274,7 @@ fn generate_tree_initializer(output: &mut String, node: &TreeNode, path: &str, i
}
}
/// Convert pattern to a TypeScript interface name
/// Convert pattern to a JSDoc typedef name
fn pattern_to_name(pattern: &IndexPattern) -> String {
let index_names: Vec<String> = pattern
.indexes
@@ -262,33 +284,6 @@ fn pattern_to_name(pattern: &IndexPattern) -> String {
format!("Pattern_{}", index_names.join("_"))
}
/// Convert Rust type name to TypeScript type
fn rust_type_to_ts(rust_type: &str) -> &'static str {
match rust_type {
// Numeric types
"f32" | "f64" | "StoredF32" | "StoredF64" => "number",
"u8" | "u16" | "u32" | "u64" | "i8" | "i16" | "i32" | "i64" => "number",
"usize" | "isize" => "number",
// Boolean
"bool" => "boolean",
// String types
"String" | "str" => "string",
// Bitcoin types (typically numeric or string representations)
"Sats" | "SatsPerVbyte" | "WU" | "VBytes" => "number",
"Height" | "Timestamp" => "number",
"Blockhash" | "Txid" => "string",
// Arrays/Vecs become arrays
_ if rust_type.starts_with("Vec<") => "unknown[]",
// Default to unknown for unmapped types
_ => "unknown",
}
}
/// Convert string to PascalCase
fn to_pascal_case(s: &str) -> String {
s.split('_')

View File

@@ -5,9 +5,12 @@ use schemars::JsonSchema;
use serde::Serialize;
use vecdb::{Bytes, Formattable};
/// Current supply state tracking UTXO count and total value
#[derive(Debug, Default, Clone, Serialize, JsonSchema)]
pub struct SupplyState {
/// Number of unspent transaction outputs
pub utxo_count: u64,
/// Total value in satoshis
pub value: Sats,
}

View File

@@ -2,8 +2,8 @@ use crate::{Address, AddressChainStats, AddressMempoolStats};
use schemars::JsonSchema;
use serde::Serialize;
#[derive(Debug, Serialize, JsonSchema)]
/// Address information compatible with mempool.space API format
#[derive(Debug, Serialize, JsonSchema)]
pub struct AddressStats {
/// Bitcoin address string
#[schemars(

View File

@@ -6,6 +6,7 @@ use crate::{EmptyAddressIndex, LoadedAddressIndex, TypeIndex};
const MIN_EMPTY_INDEX: u32 = u32::MAX - 4_000_000_000;
/// Unified index for any address type (loaded or empty)
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Bytes, JsonSchema)]
pub struct AnyAddressIndex(TypeIndex);

View File

@@ -9,6 +9,7 @@ use vecdb::{CheckedSub, Formattable, Pco};
use super::{Sats, StoredF64};
/// Bitcoin amount as floating point (1 BTC = 100,000,000 satoshis)
#[derive(Debug, Default, Clone, Copy, Serialize, Pco, JsonSchema)]
pub struct Bitcoin(f64);

View File

@@ -4,6 +4,7 @@ use schemars::JsonSchema;
use serde::Serialize;
use vecdb::{Formattable, Pco};
/// Position within a .blk file, encoding file index and byte offset
#[derive(Debug, Clone, Copy, Serialize, Pco, JsonSchema)]
pub struct BlkPosition(u64);

View File

@@ -7,6 +7,7 @@ use crate::ONE_DAY_IN_SEC_F64;
use super::{DateIndex, Timestamp};
/// Date in YYYYMMDD format stored as u32
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Pco, JsonSchema)]
pub struct Date(u32);

View File

@@ -14,6 +14,7 @@ use crate::{Low, Open};
use super::{Bitcoin, Cents, Close, High, Sats, StoredF32, StoredF64};
/// US Dollar amount as floating point
#[derive(Debug, Default, Clone, Copy, Deref, Serialize, Deserialize, Pco, JsonSchema)]
pub struct Dollars(f64);

View File

@@ -1,6 +1,7 @@
use schemars::JsonSchema;
use serde::Deserialize;
/// Output format for API responses
#[allow(clippy::upper_case_acronyms)]
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Deserialize, JsonSchema)]
#[serde(rename_all = "lowercase")]

View File

@@ -1,8 +1,8 @@
use schemars::JsonSchema;
use serde::Serialize;
#[derive(Debug, Serialize, JsonSchema)]
/// Server health status
#[derive(Debug, Serialize, JsonSchema)]
pub struct Health {
pub status: &'static str,
pub service: &'static str,

View File

@@ -3,8 +3,8 @@ use serde::Serialize;
use super::Index;
#[derive(Clone, Copy, Serialize, JsonSchema)]
/// Information about an available index and its query aliases
#[derive(Clone, Copy, Serialize, JsonSchema)]
pub struct IndexInfo {
/// The canonical index name
pub index: Index,

View File

@@ -1,13 +1,13 @@
use schemars::JsonSchema;
use serde::Serialize;
#[derive(Debug, Serialize, JsonSchema)]
/// Metric count statistics - distinct metrics and total metric-index combinations
#[derive(Debug, Serialize, JsonSchema)]
pub struct MetricCount {
#[schemars(example = 3141)]
/// Number of unique metrics available (e.g., realized_price, market_cap)
#[schemars(example = 3141)]
pub distinct_metrics: usize,
#[schemars(example = 21000)]
/// Total number of metric-index combinations across all timeframes
#[schemars(example = 21000)]
pub total_endpoints: usize,
}

View File

@@ -13,6 +13,7 @@ use crate::StoredF64;
use super::{Cents, Dollars, Sats};
/// OHLC (Open, High, Low, Close) data in cents
#[derive(Debug, Default, Clone, JsonSchema)]
#[repr(C)]
pub struct OHLCCents {
@@ -99,6 +100,7 @@ impl Bytes for OHLCCents {
}
}
/// OHLC (Open, High, Low, Close) data in dollars
#[derive(Debug, Default, Clone, Copy, JsonSchema)]
#[repr(C)]
pub struct OHLCDollars {
@@ -211,6 +213,7 @@ impl Bytes for OHLCDollars {
}
}
/// OHLC (Open, High, Low, Close) data in satoshis
#[derive(Debug, Default, Clone, Copy, JsonSchema)]
#[repr(C)]
pub struct OHLCSats {
@@ -304,6 +307,7 @@ impl Bytes for OHLCSats {
}
}
/// Opening price value for a time period
#[derive(
Debug,
Default,
@@ -433,6 +437,7 @@ where
}
}
/// Highest price value for a time period
#[derive(
Debug,
Default,
@@ -562,6 +567,7 @@ where
}
}
/// Lowest price value for a time period
#[derive(
Debug,
Default,
@@ -691,6 +697,7 @@ where
}
}
/// Closing price value for a time period
#[derive(
Debug,
Default,

View File

@@ -1,6 +1,7 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
/// Pagination parameters for paginated API endpoints
#[derive(Debug, Default, Serialize, Deserialize, JsonSchema)]
pub struct Pagination {
/// Pagination index

View File

@@ -3,10 +3,13 @@ use serde::Serialize;
use super::{Height, Sats};
/// Block reward statistics over a range of blocks
#[derive(Debug, Clone, Serialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct RewardStats {
/// First block in the range
pub start_block: Height,
/// Last block in the range
pub end_block: Height,
#[serde(serialize_with = "sats_as_string")]
pub total_reward: Sats,

View File

@@ -3,6 +3,7 @@ use schemars::JsonSchema;
use serde::Serialize;
use vecdb::{Formattable, Pco, PrintableIndex};
/// Fixed-size boolean value optimized for on-disk storage (stored as u16)
#[derive(Debug, Deref, Clone, Default, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Pco, JsonSchema)]
pub struct StoredBool(u16);

View File

@@ -15,6 +15,7 @@ use crate::{Close, StoredU32};
use super::{Dollars, StoredF64};
/// Stored 32-bit floating point value
#[derive(Debug, Deref, Default, Clone, Copy, Serialize, Pco, JsonSchema)]
pub struct StoredF32(f32);

View File

@@ -12,6 +12,7 @@ use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
use crate::{Bitcoin, Dollars};
/// Fixed-size 64-bit floating point value optimized for on-disk storage
#[derive(Debug, Deref, Default, Clone, Copy, Serialize, Pco, JsonSchema)]
pub struct StoredF64(f64);

View File

@@ -11,6 +11,7 @@ use super::{
P2WSHAddressIndex, UnknownOutputIndex,
};
/// Fixed-size 32-bit unsigned integer optimized for on-disk storage
#[derive(Debug, Deref, Clone, Default, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Pco, JsonSchema)]
pub struct StoredU32(u32);

View File

@@ -12,6 +12,7 @@ use super::{
UnknownOutputIndex, YearIndex,
};
/// Fixed-size 64-bit unsigned integer optimized for on-disk storage
#[derive(Debug, Default, Deref, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Pco, JsonSchema)]
pub struct StoredU64(u64);

View File

@@ -35,7 +35,7 @@ impl MetricLeaf {
}
/// MetricLeaf with JSON Schema for client generation
#[derive(Debug, Clone, Serialize)]
#[derive(Debug, Clone, Serialize, JsonSchema)]
pub struct MetricLeafWithSchema {
/// The core metric metadata
#[serde(flatten)]
@@ -84,9 +84,9 @@ impl PartialEq for MetricLeafWithSchema {
impl Eq for MetricLeafWithSchema {}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
#[serde(untagged)]
/// Hierarchical tree node for organizing metrics into categories
#[derive(Debug, Clone, Serialize, PartialEq, Eq, JsonSchema)]
#[serde(untagged)]
pub enum TreeNode {
/// Branch node containing subcategories
Branch(BTreeMap<String, TreeNode>),

View File

@@ -3,8 +3,8 @@ use schemars::JsonSchema;
use serde::Serialize;
use vecdb::CheckedSub;
#[derive(Debug, Clone, Serialize, JsonSchema)]
/// Transaction information compatible with mempool.space API format
#[derive(Debug, Clone, Serialize, JsonSchema)]
pub struct Transaction {
#[schemars(example = TxIndex::new(0))]
pub index: Option<TxIndex>,

View File

@@ -3,8 +3,11 @@ use serde::Deserialize;
use crate::{Txid, Vout};
/// Transaction output reference (txid + output index)
#[derive(Deserialize, JsonSchema)]
pub struct TxidVout {
/// Transaction ID
pub txid: Txid,
/// Output index
pub vout: Vout,
}

View File

@@ -3,8 +3,8 @@ use bitcoin::{Script, ScriptBuf};
use schemars::JsonSchema;
use serde::{Serialize, Serializer, ser::SerializeStruct};
#[derive(Debug, Clone, JsonSchema)]
/// Transaction input
#[derive(Debug, Clone, JsonSchema)]
pub struct TxIn {
/// Transaction ID of the output being spent
#[schemars(example = "0000000000000000000000000000000000000000000000000000000000000000")]

View File

@@ -3,8 +3,8 @@ use bitcoin::ScriptBuf;
use schemars::JsonSchema;
use serde::{Serialize, Serializer, ser::SerializeStruct};
#[derive(Debug, Clone, JsonSchema)]
/// Transaction output
#[derive(Debug, Clone, JsonSchema)]
pub struct TxOut {
/// Script pubkey (locking script)
#[serde(

View File

@@ -3,8 +3,8 @@ use serde::Serialize;
use crate::{BlockHash, Height, Timestamp};
#[derive(Debug, Clone, Serialize, JsonSchema)]
/// Transaction confirmation status
#[derive(Debug, Clone, Serialize, JsonSchema)]
pub struct TxStatus {
/// Whether the transaction is confirmed
#[schemars(example = true)]

View File

@@ -5,6 +5,7 @@ use schemars::JsonSchema;
use serde::Serialize;
use vecdb::{Formattable, Pco};
/// Transaction or block weight in weight units (WU)
#[derive(Debug, Deref, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Pco, JsonSchema)]
pub struct Weight(u64);