mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-24 06:39:58 -07:00
global: snapshot
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -569,6 +569,7 @@ dependencies = [
|
||||
"brk_query",
|
||||
"brk_types",
|
||||
"schemars",
|
||||
"serde_json",
|
||||
"vecdb",
|
||||
]
|
||||
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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('_')
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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>),
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user