mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-06-08 06:01:57 -07:00
global: snapshot
This commit is contained in:
Generated
+6
-6
@@ -568,6 +568,8 @@ version = "0.1.0-alpha.0"
|
||||
dependencies = [
|
||||
"brk_query",
|
||||
"brk_types",
|
||||
"schemars",
|
||||
"vecdb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -634,6 +636,7 @@ dependencies = [
|
||||
"pco",
|
||||
"rayon",
|
||||
"rustc-hash",
|
||||
"schemars",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"vecdb",
|
||||
@@ -1253,7 +1256,9 @@ version = "0.1.0-alpha.0"
|
||||
dependencies = [
|
||||
"brk_traversable_derive",
|
||||
"brk_types",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"vecdb",
|
||||
]
|
||||
|
||||
@@ -4193,8 +4198,6 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rawdb"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e76ca8167fb720e8feb864618de99327b628fa0ff0e5221f09ebea1abdffcac2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
@@ -5385,8 +5388,6 @@ checksum = "8f54a172d0620933a27a4360d3db3e2ae0dd6cceae9730751a036bbf182c4b23"
|
||||
[[package]]
|
||||
name = "vecdb"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf728869972437c4b600555ed98b8102378a0194010e6399f2430a4f79b6eaa7"
|
||||
dependencies = [
|
||||
"ctrlc",
|
||||
"log",
|
||||
@@ -5394,6 +5395,7 @@ dependencies = [
|
||||
"parking_lot",
|
||||
"pco",
|
||||
"rawdb",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.17",
|
||||
@@ -5405,8 +5407,6 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "vecdb_derive"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ba8822fd1709751e026000483d85211870112d89e8404b33226416c2d9a6de4"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
|
||||
+2
-4
@@ -65,8 +65,6 @@ byteview = "0.9.1"
|
||||
color-eyre = "0.6.5"
|
||||
derive_deref = "1.1.1"
|
||||
fjall = "3.0.0-rc.6"
|
||||
# fjall3 = { path = "../fjall3", package = "fjall" }
|
||||
# fjall3 = { git = "https://github.com/fjall-rs/fjall.git", rev = "434979ef59d8fd2b36b91e6ff759a36d19a397ee", package = "fjall" }
|
||||
jiff = "0.2.16"
|
||||
log = "0.4.29"
|
||||
mimalloc = { version = "0.1.48", features = ["v3"] }
|
||||
@@ -81,8 +79,8 @@ serde_derive = "1.0.228"
|
||||
serde_json = { version = "1.0.145", features = ["float_roundtrip"] }
|
||||
smallvec = "1.15.1"
|
||||
tokio = { version = "1.48.0", features = ["rt-multi-thread"] }
|
||||
vecdb = { version = "0.4.3", features = ["derive", "serde_json", "pco"] }
|
||||
# vecdb = { path = "../anydb/crates/vecdb", features = ["derive", "serde_json", "pco"] }
|
||||
# vecdb = { version = "0.4.3", features = ["derive", "serde_json", "pco"] }
|
||||
vecdb = { path = "../anydb/crates/vecdb", features = ["derive", "serde_json", "pco", "schemars"] }
|
||||
# vecdb = { git = "https://github.com/anydb-rs/anydb", features = ["derive", "serde_json", "pco"] }
|
||||
|
||||
[workspace.metadata.release]
|
||||
|
||||
@@ -11,3 +11,5 @@ build = "build.rs"
|
||||
[dependencies]
|
||||
brk_query = { workspace = true }
|
||||
brk_types = { workspace = true }
|
||||
schemars = { workspace = true }
|
||||
vecdb = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,336 @@
|
||||
# brk_binder Design Document
|
||||
|
||||
## Goal
|
||||
|
||||
Generate typed API clients for **Rust, TypeScript, and Python** with:
|
||||
- **Discoverability**: Full IDE autocomplete for 20k+ metrics
|
||||
- **Ease of use**: Fluent API with `.fetch()` on each metric node
|
||||
|
||||
## Current State
|
||||
|
||||
### What Exists
|
||||
|
||||
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`)
|
||||
- `rust.rs`: Rust client generation (stub)
|
||||
- `typescript.rs`: TypeScript client generation (stub)
|
||||
- `python.rs`: Python client generation (stub)
|
||||
|
||||
### 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
|
||||
|
||||
## Target Architecture
|
||||
|
||||
### Input Sources
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Input Sources │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 1. OpenAPI spec (from aide) - endpoint definitions │
|
||||
│ 2. brk_query catalog - metric tree structure │
|
||||
│ 3. brk_types - Rust types for responses (Rust client only) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Output: Fluent Client (Option B)
|
||||
|
||||
```typescript
|
||||
// TypeScript
|
||||
const client = new BrkClient("http://localhost:3000");
|
||||
const data = await client.tree.supply.active.by_date.fetch();
|
||||
// ^^^^ autocomplete all the way down
|
||||
```
|
||||
|
||||
```python
|
||||
# Python
|
||||
client = BrkClient("http://localhost:3000")
|
||||
data = await client.tree.supply.active.by_date.fetch()
|
||||
```
|
||||
|
||||
```rust
|
||||
// Rust
|
||||
let client = BrkClient::new("http://localhost:3000");
|
||||
let data = client.tree.supply.active.by_date.fetch().await?;
|
||||
```
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Smart Metric Nodes
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```python
|
||||
# Python
|
||||
class MetricNode(Generic[T]):
|
||||
def __init__(self, client: BrkClient, path: str):
|
||||
self._client = client
|
||||
self._path = path
|
||||
|
||||
async def fetch(self) -> T:
|
||||
return await self._client.get(self._path)
|
||||
```
|
||||
|
||||
```rust
|
||||
// Rust
|
||||
pub struct MetricNode<T> {
|
||||
client: Arc<BrkClient>,
|
||||
path: &'static str,
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: DeserializeOwned> MetricNode<T> {
|
||||
pub async fn fetch(&self) -> Result<T, BrkError> {
|
||||
self.client.get(&self.path).await
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern Reuse (from tree.rs)
|
||||
|
||||
To avoid 20k+ individual types, reuse structural patterns:
|
||||
|
||||
```rust
|
||||
// Shared pattern for metrics with same index groupings
|
||||
struct ByDateHeightMonth<T> {
|
||||
by_date: MetricNode<T>,
|
||||
by_height: MetricNode<T>,
|
||||
by_month: MetricNode<T>,
|
||||
}
|
||||
|
||||
// Composed into full tree
|
||||
struct Supply {
|
||||
active: ByDateHeightMonth<Vec<f64>>,
|
||||
total: ByDateHeightMonth<Vec<f64>>,
|
||||
}
|
||||
```
|
||||
|
||||
### Rust Client: Using brk_types
|
||||
|
||||
The Rust client should import `brk_types` rather than generating duplicate types:
|
||||
|
||||
```rust
|
||||
use brk_types::{Height, Sats, DateIndex, ...};
|
||||
|
||||
// Response types come from brk_types
|
||||
pub struct MetricNode<T: brk_types::Metric> { ... }
|
||||
```
|
||||
|
||||
## Type Discovery Solution ✅ IMPLEMENTED
|
||||
|
||||
### The Problem
|
||||
|
||||
Type information was erased at runtime because metrics are stored as `&dyn AnyExportableVec` trait objects.
|
||||
|
||||
### The Solution
|
||||
|
||||
Use `std::any::type_name::<T>()` with caching to extract short type names.
|
||||
|
||||
> **Note**: Unlike `PrintableIndex` which needs `to_possible_strings()` for parsing from
|
||||
> multiple string representations, for values we only need output, so `type_name` suffices.
|
||||
|
||||
#### Implementation (vecdb)
|
||||
|
||||
Added `short_type_name<T>()` helper in `traits/printable.rs`:
|
||||
|
||||
```rust
|
||||
pub fn short_type_name<T: 'static>() -> &'static str {
|
||||
static CACHE: OnceLock<Mutex<HashMap<&'static str, &'static str>>> = OnceLock::new();
|
||||
|
||||
let full: &'static str = std::any::type_name::<T>();
|
||||
// ... caching logic, extracts "Sats" from "brk_types::sats::Sats"
|
||||
}
|
||||
```
|
||||
|
||||
Added `value_type_to_string()` to `AnyVec` trait in `traits/any.rs`:
|
||||
|
||||
```rust
|
||||
pub trait AnyVec: Send + Sync {
|
||||
// ... existing methods
|
||||
fn value_type_to_string(&self) -> &'static str;
|
||||
}
|
||||
```
|
||||
|
||||
Implemented in all vec variants:
|
||||
- `variants/eager/mod.rs`
|
||||
- `variants/lazy/from1/mod.rs`, `from2/mod.rs`, `from3/mod.rs`
|
||||
- `variants/raw/inner/mod.rs`
|
||||
- `variants/compressed/inner/mod.rs`
|
||||
- `variants/macros.rs` (for wrapper types)
|
||||
|
||||
```rust
|
||||
fn value_type_to_string(&self) -> &'static str {
|
||||
short_type_name::<V::T>()
|
||||
}
|
||||
```
|
||||
|
||||
**No changes needed to brk_types** - works automatically for all types.
|
||||
|
||||
### Result
|
||||
|
||||
`brk_query` now exposes:
|
||||
|
||||
```rust
|
||||
for (metric_name, index_to_vec) in &vecs.metric_to_index_to_vec {
|
||||
for (index, vec) in index_to_vec {
|
||||
println!("{} @ {} -> {}",
|
||||
metric_name, // "difficulty"
|
||||
vec.index_type_to_string(), // "Height"
|
||||
vec.value_type_to_string(), // "StoredF64"
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This enables fully typed client generation.
|
||||
|
||||
## TreeNode Enhancement ✅ IMPLEMENTED
|
||||
|
||||
### The Problem
|
||||
|
||||
`TreeNode::Leaf` originally held just a `String` (the metric name), losing type and index information.
|
||||
|
||||
### The Solution
|
||||
|
||||
Changed `TreeNode::Leaf(String)` to `TreeNode::Leaf(MetricLeaf)` where:
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, Eq, JsonSchema)]
|
||||
pub struct MetricLeaf {
|
||||
pub name: String,
|
||||
pub value_type: String,
|
||||
pub indexes: BTreeSet<Index>,
|
||||
}
|
||||
```
|
||||
|
||||
#### 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
|
||||
|
||||
**brk_traversable/src/lib.rs**:
|
||||
- Added `make_leaf<I, V>()` helper that creates `MetricLeaf` with proper fields
|
||||
- Updated all `Traversable::to_tree_node()` implementations
|
||||
|
||||
### Result
|
||||
|
||||
The catalog tree now includes full type information at each leaf:
|
||||
|
||||
```rust
|
||||
TreeNode::Leaf(MetricLeaf {
|
||||
name: "difficulty".to_string(),
|
||||
value_type: "StoredF64".to_string(),
|
||||
indexes: btreeset![Index::Height, Index::Date],
|
||||
})
|
||||
```
|
||||
|
||||
When trees are merged/simplified, indexes are unioned together.
|
||||
|
||||
### 2. Async Runtime
|
||||
|
||||
- TypeScript: Native `Promise`
|
||||
- Python: `asyncio` or sync variant?
|
||||
- Rust: `tokio` assumed, or feature-flag for other runtimes?
|
||||
|
||||
### 3. Error Handling
|
||||
|
||||
- HTTP errors (4xx, 5xx)
|
||||
- Deserialization errors
|
||||
- Network errors
|
||||
- Should errors be typed per language?
|
||||
|
||||
### 4. Additional Client Features
|
||||
|
||||
- Request timeout configuration
|
||||
- Retry logic
|
||||
- Rate limiting
|
||||
- Caching
|
||||
- Batch requests (fetch multiple metrics at once)
|
||||
|
||||
## Tasks
|
||||
|
||||
### Phase 0: Type Infrastructure ✅ COMPLETE
|
||||
|
||||
- [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] **brk_query**: Export `Vecs` publicly for client generation
|
||||
- [x] **brk_binder**: Set up generator module structure (types, rust, typescript, python stubs)
|
||||
- [x] **brk**: Verify compilation
|
||||
|
||||
### Phase 1: Client Foundation
|
||||
|
||||
- [ ] 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
|
||||
|
||||
### Phase 2: Type-Aware Generation
|
||||
|
||||
- [ ] 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
|
||||
|
||||
### Phase 3: OpenAPI Integration
|
||||
|
||||
- [ ] Parse OpenAPI spec with `openapiv3` crate
|
||||
- [ ] Extract non-metric endpoint definitions
|
||||
- [ ] Generate methods for health, info, catalog, etc.
|
||||
|
||||
### Phase 4: Polish
|
||||
|
||||
- [ ] Error types per language
|
||||
- [ ] Documentation generation
|
||||
- [ ] Tests
|
||||
- [ ] Example usage in each language
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
crates/brk_binder/
|
||||
├── src/
|
||||
│ ├── lib.rs
|
||||
│ ├── js.rs # JS constants generation (existing)
|
||||
│ ├── 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
|
||||
├── Cargo.toml
|
||||
├── README.md
|
||||
└── DESIGN.md # This file
|
||||
```
|
||||
|
||||
## Dependencies (Proposed)
|
||||
|
||||
```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
|
||||
```
|
||||
@@ -0,0 +1,303 @@
|
||||
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 super::{to_camel_case, ClientMetadata, IndexPattern};
|
||||
|
||||
/// Generate TypeScript client from metadata
|
||||
pub fn generate_typescript_client(metadata: &ClientMetadata, output_dir: &Path) -> io::Result<()> {
|
||||
let mut output = String::new();
|
||||
|
||||
// Header
|
||||
writeln!(output, "// Auto-generated BRK TypeScript 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 the base client class
|
||||
generate_base_client(&mut output);
|
||||
|
||||
// Generate tree types from catalog
|
||||
generate_tree_types(&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)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Generate TypeScript interfaces for common index patterns
|
||||
fn generate_pattern_interfaces(output: &mut String, patterns: &[IndexPattern]) {
|
||||
writeln!(output, "// Index pattern interfaces").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();
|
||||
|
||||
for index in &pattern.indexes {
|
||||
let field_name = to_camel_case(&index.serialize_long());
|
||||
writeln!(output, " {}: MetricNode<T>;", field_name).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;
|
||||
}}
|
||||
|
||||
export class BrkClientBase {{
|
||||
private baseUrl: string;
|
||||
private timeout: number;
|
||||
|
||||
constructor(options: BrkClientOptions | string) {{
|
||||
if (typeof options === 'string') {{
|
||||
this.baseUrl = options.replace(/\/$/, '');
|
||||
this.timeout = 30000;
|
||||
}} else {{
|
||||
this.baseUrl = options.baseUrl.replace(/\/$/, '');
|
||||
this.timeout = options.timeout ?? 30000;
|
||||
}}
|
||||
}}
|
||||
|
||||
async get<T>(path: string): Promise<T> {{
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
||||
|
||||
try {{
|
||||
const response = await fetch(`${{this.baseUrl}}${{path}}`, {{
|
||||
signal: controller.signal,
|
||||
headers: {{ 'Accept': 'application/json' }},
|
||||
}});
|
||||
|
||||
if (!response.ok) {{
|
||||
throw new BrkError(`HTTP ${{response.status}}: ${{response.statusText}}`, response.status);
|
||||
}}
|
||||
|
||||
return await response.json();
|
||||
}} finally {{
|
||||
clearTimeout(timeoutId);
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
|
||||
export class BrkError extends Error {{
|
||||
constructor(message: string, public statusCode?: number) {{
|
||||
super(message);
|
||||
this.name = 'BrkError';
|
||||
}}
|
||||
}}
|
||||
|
||||
// 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);
|
||||
}}
|
||||
|
||||
toString(): string {{
|
||||
return this.path;
|
||||
}}
|
||||
}}
|
||||
|
||||
"#
|
||||
)
|
||||
.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, "");
|
||||
}
|
||||
|
||||
/// Recursively generate type for a tree node
|
||||
fn generate_node_type(output: &mut String, name: &str, node: &TreeNode, path: &str) {
|
||||
match node {
|
||||
TreeNode::Leaf(leaf) => {
|
||||
// Leaf nodes are MetricNode<ValueType>
|
||||
// No separate interface needed, handled inline
|
||||
}
|
||||
TreeNode::Branch(children) => {
|
||||
writeln!(output, "export interface {} {{", 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();
|
||||
}
|
||||
TreeNode::Branch(_) => {
|
||||
let child_type_name = format!("{}_{}", name, to_pascal_case(child_name));
|
||||
writeln!(output, " {}: {};", field_name, child_type_name).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writeln!(output, "}}\n").unwrap();
|
||||
|
||||
// Generate child types
|
||||
for (child_name, child_node) in children {
|
||||
if let TreeNode::Branch(_) = child_node {
|
||||
let child_type_name = format!("{}_{}", name, to_pascal_case(child_name));
|
||||
let child_path = if path.is_empty() {
|
||||
format!("/{}", child_name)
|
||||
} else {
|
||||
format!("{}/{}", path, child_name)
|
||||
};
|
||||
generate_node_type(output, &child_type_name, child_node, &child_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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, " super(options);").unwrap();
|
||||
writeln!(output, " this.tree = this._buildTree();").unwrap();
|
||||
writeln!(output, " }}\n").unwrap();
|
||||
|
||||
// Generate _buildTree method
|
||||
writeln!(output, " private _buildTree(): CatalogTree {{").unwrap();
|
||||
writeln!(output, " return {{").unwrap();
|
||||
generate_tree_initializer(output, catalog, "", 3);
|
||||
writeln!(output, " }};").unwrap();
|
||||
writeln!(output, " }}").unwrap();
|
||||
writeln!(output, "}}").unwrap();
|
||||
}
|
||||
|
||||
/// Generate the tree initializer code
|
||||
fn generate_tree_initializer(output: &mut String, node: &TreeNode, path: &str, indent: usize) {
|
||||
let indent_str = " ".repeat(indent);
|
||||
|
||||
if let TreeNode::Branch(children) = node {
|
||||
for (i, (child_name, child_node)) in children.iter().enumerate() {
|
||||
let field_name = to_camel_case(child_name);
|
||||
let child_path = if path.is_empty() {
|
||||
format!("/{}", child_name)
|
||||
} else {
|
||||
format!("{}/{}", path, child_name)
|
||||
};
|
||||
|
||||
let comma = if i < children.len() - 1 { "," } else { "" };
|
||||
|
||||
match child_node {
|
||||
TreeNode::Leaf(leaf) => {
|
||||
writeln!(
|
||||
output,
|
||||
"{}{}: new MetricNode(this, '{}'){}",
|
||||
indent_str, field_name, child_path, comma
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
TreeNode::Branch(_) => {
|
||||
writeln!(output, "{}{}: {{", indent_str, field_name).unwrap();
|
||||
generate_tree_initializer(output, child_node, &child_path, indent + 1);
|
||||
writeln!(output, "{}}}{}", indent_str, comma).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert pattern to a TypeScript interface name
|
||||
fn pattern_to_name(pattern: &IndexPattern) -> String {
|
||||
let index_names: Vec<String> = pattern
|
||||
.indexes
|
||||
.iter()
|
||||
.map(|i| to_pascal_case(&i.serialize_long()))
|
||||
.collect();
|
||||
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('_')
|
||||
.map(|word| {
|
||||
let mut chars = word.chars();
|
||||
match chars.next() {
|
||||
None => String::new(),
|
||||
Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
mod javascript;
|
||||
mod python;
|
||||
mod rust;
|
||||
mod types;
|
||||
|
||||
pub use javascript::generate_javascript_client;
|
||||
pub use python::generate_python_client;
|
||||
pub use rust::generate_rust_client;
|
||||
pub use types::*;
|
||||
|
||||
use brk_query::Vecs;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
|
||||
/// Generate all client libraries from the query vecs
|
||||
pub fn generate_clients(vecs: &Vecs, output_dir: &Path) -> io::Result<()> {
|
||||
let metadata = ClientMetadata::from_vecs(vecs);
|
||||
|
||||
// Generate Rust client
|
||||
let rust_path = output_dir.join("rust");
|
||||
std::fs::create_dir_all(&rust_path)?;
|
||||
generate_rust_client(&metadata, &rust_path)?;
|
||||
|
||||
// Generate JavaScript client
|
||||
let js_path = output_dir.join("javascript");
|
||||
std::fs::create_dir_all(&js_path)?;
|
||||
generate_javascript_client(&metadata, &js_path)?;
|
||||
|
||||
// Generate Python client
|
||||
let python_path = output_dir.join("python");
|
||||
std::fs::create_dir_all(&python_path)?;
|
||||
generate_python_client(&metadata, &python_path)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
|
||||
use super::ClientMetadata;
|
||||
|
||||
/// Generate Python client from metadata
|
||||
pub fn generate_python_client(_metadata: &ClientMetadata, _output_dir: &Path) -> io::Result<()> {
|
||||
// TODO: Implement Python client generation
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
|
||||
use super::ClientMetadata;
|
||||
|
||||
/// Generate Rust client from metadata
|
||||
pub fn generate_rust_client(_metadata: &ClientMetadata, _output_dir: &Path) -> io::Result<()> {
|
||||
// TODO: Implement Rust client generation
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use brk_query::Vecs;
|
||||
use brk_types::{Index, TreeNode};
|
||||
|
||||
/// Metadata extracted from brk_query for client generation
|
||||
#[derive(Debug)]
|
||||
pub struct ClientMetadata {
|
||||
/// All metrics with their available indexes and value type
|
||||
pub metrics: BTreeMap<String, MetricInfo>,
|
||||
/// The catalog tree structure (with schemas in leaves)
|
||||
pub catalog: TreeNode,
|
||||
/// Discovered patterns (sets of indexes that appear together frequently)
|
||||
pub patterns: Vec<IndexPattern>,
|
||||
}
|
||||
|
||||
/// Information about a single metric
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MetricInfo {
|
||||
/// Metric name (e.g., "difficulty", "supply_total")
|
||||
pub name: String,
|
||||
/// Available indexes for this metric
|
||||
pub indexes: BTreeSet<Index>,
|
||||
/// Value type name (e.g., "Sats", "StoredF64")
|
||||
pub value_type: String,
|
||||
}
|
||||
|
||||
/// A pattern of indexes that appears multiple times across metrics
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IndexPattern {
|
||||
/// Unique identifier for this pattern
|
||||
pub id: usize,
|
||||
/// The set of indexes in this pattern
|
||||
pub indexes: BTreeSet<Index>,
|
||||
/// How many metrics use this exact pattern
|
||||
pub usage_count: usize,
|
||||
}
|
||||
|
||||
impl ClientMetadata {
|
||||
/// Extract metadata from brk_query::Vecs
|
||||
pub fn from_vecs(vecs: &Vecs) -> Self {
|
||||
let mut metrics = BTreeMap::new();
|
||||
let mut pattern_counts: BTreeMap<BTreeSet<Index>, usize> = BTreeMap::new();
|
||||
|
||||
// Extract metric information
|
||||
for (name, index_to_vec) in &vecs.metric_to_index_to_vec {
|
||||
let indexes: BTreeSet<Index> = index_to_vec.keys().copied().collect();
|
||||
|
||||
// Get value type from the first available vec
|
||||
let value_type = index_to_vec
|
||||
.values()
|
||||
.next()
|
||||
.map(|v| v.value_type_to_string().to_string())
|
||||
.unwrap_or_else(|| "unknown".to_string());
|
||||
|
||||
// Count pattern usage
|
||||
*pattern_counts.entry(indexes.clone()).or_insert(0) += 1;
|
||||
|
||||
metrics.insert(
|
||||
name.to_string(),
|
||||
MetricInfo {
|
||||
name: name.to_string(),
|
||||
indexes,
|
||||
value_type,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Extract patterns that are used by multiple metrics
|
||||
let mut patterns: Vec<IndexPattern> = pattern_counts
|
||||
.into_iter()
|
||||
.filter(|(_, count)| *count >= 2) // Only patterns used by 2+ metrics
|
||||
.enumerate()
|
||||
.map(|(id, (indexes, usage_count))| IndexPattern {
|
||||
id,
|
||||
indexes,
|
||||
usage_count,
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Sort by usage count descending
|
||||
patterns.sort_by(|a, b| b.usage_count.cmp(&a.usage_count));
|
||||
|
||||
ClientMetadata {
|
||||
metrics,
|
||||
catalog: vecs.catalog().clone(),
|
||||
patterns,
|
||||
}
|
||||
}
|
||||
|
||||
/// Find the pattern that matches a metric's indexes, if any
|
||||
pub fn find_pattern_for_metric(&self, metric: &MetricInfo) -> Option<&IndexPattern> {
|
||||
self.patterns
|
||||
.iter()
|
||||
.find(|p| p.indexes == metric.indexes)
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a metric name to PascalCase (for struct/class names)
|
||||
pub fn to_pascal_case(s: &str) -> String {
|
||||
s.split('_')
|
||||
.map(|word| {
|
||||
let mut chars = word.chars();
|
||||
match chars.next() {
|
||||
None => String::new(),
|
||||
Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Convert a metric name to snake_case (already snake_case, but sanitize)
|
||||
pub fn to_snake_case(s: &str) -> String {
|
||||
let sanitized = s.replace('-', "_");
|
||||
// Handle Rust keywords
|
||||
match sanitized.as_str() {
|
||||
"type" | "const" | "static" | "match" | "if" | "else" | "loop" | "while" | "for"
|
||||
| "break" | "continue" | "return" | "fn" | "let" | "mut" | "ref" | "self" | "super"
|
||||
| "mod" | "use" | "pub" | "crate" | "extern" | "impl" | "trait" | "struct" | "enum"
|
||||
| "where" | "async" | "await" | "dyn" | "move" => format!("r#{}", sanitized),
|
||||
_ => sanitized,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a metric name to camelCase (for JS/TS)
|
||||
pub fn to_camel_case(s: &str) -> String {
|
||||
let pascal = to_pascal_case(s);
|
||||
let mut chars = pascal.chars();
|
||||
match chars.next() {
|
||||
None => String::new(),
|
||||
Some(first) => first.to_lowercase().collect::<String>() + chars.as_str(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a serde_json::Value (JSON Schema) to a JSDoc type annotation
|
||||
pub fn schema_to_jsdoc(schema: &serde_json::Value) -> String {
|
||||
if let Some(ty) = schema.get("type").and_then(|v| v.as_str()) {
|
||||
match ty {
|
||||
"null" => "null".to_string(),
|
||||
"boolean" => "boolean".to_string(),
|
||||
"integer" | "number" => "number".to_string(),
|
||||
"string" => "string".to_string(),
|
||||
"array" => {
|
||||
if let Some(items) = schema.get("items") {
|
||||
format!("{}[]", schema_to_jsdoc(items))
|
||||
} else {
|
||||
"Array<*>".to_string()
|
||||
}
|
||||
}
|
||||
"object" => "Object".to_string(),
|
||||
_ => "*".to_string(),
|
||||
}
|
||||
} else if schema.get("anyOf").is_some() || schema.get("oneOf").is_some() {
|
||||
let variants = schema
|
||||
.get("anyOf")
|
||||
.or_else(|| schema.get("oneOf"))
|
||||
.and_then(|v| v.as_array())
|
||||
.map(|arr| {
|
||||
arr.iter()
|
||||
.map(schema_to_jsdoc)
|
||||
.collect::<Vec<_>>()
|
||||
.join("|")
|
||||
})
|
||||
.unwrap_or_else(|| "*".to_string());
|
||||
format!("({})", variants)
|
||||
} else if let Some(reference) = schema.get("$ref").and_then(|v| v.as_str()) {
|
||||
reference.rsplit('/').next().unwrap_or("*").to_string()
|
||||
} else {
|
||||
"*".to_string()
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,10 @@
|
||||
mod js;
|
||||
mod generator;
|
||||
|
||||
// tree.rs is kept for reference but not compiled
|
||||
// mod tree;
|
||||
|
||||
pub use js::*;
|
||||
pub use generator::*;
|
||||
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
@@ -0,0 +1,917 @@
|
||||
use serde_json::{Map, Value};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fs;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
struct Pattern {
|
||||
fields: Vec<String>,
|
||||
field_count: usize,
|
||||
}
|
||||
|
||||
fn sanitize_name(name: &str) -> String {
|
||||
// Python identifiers can't start with numbers
|
||||
if name.chars().next().unwrap().is_numeric() {
|
||||
format!("_{}", name)
|
||||
} else {
|
||||
name.replace("-", "_")
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_pattern(obj: &Map<String, Value>) -> Pattern {
|
||||
let mut fields: Vec<String> = obj.keys().cloned().collect();
|
||||
fields.sort();
|
||||
Pattern {
|
||||
field_count: fields.len(),
|
||||
fields,
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate similarity between two patterns (0.0 = different, 1.0 = identical)
|
||||
fn pattern_similarity(p1: &Pattern, p2: &Pattern) -> f64 {
|
||||
if p1.field_count == 0 || p2.field_count == 0 {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let set1: HashSet<_> = p1.fields.iter().collect();
|
||||
let set2: HashSet<_> = p2.fields.iter().collect();
|
||||
|
||||
let intersection = set1.intersection(&set2).count();
|
||||
let union = set1.union(&set2).count();
|
||||
|
||||
intersection as f64 / union as f64
|
||||
}
|
||||
|
||||
// Group similar patterns together
|
||||
fn cluster_patterns(patterns: &HashMap<Pattern, Vec<String>>) -> Vec<Vec<(Pattern, Vec<String>)>> {
|
||||
let mut clusters: Vec<Vec<(Pattern, Vec<String>)>> = Vec::new();
|
||||
let similarity_threshold = 0.7; // 70% overlap
|
||||
|
||||
for (pattern, paths) in patterns {
|
||||
let mut found_cluster = false;
|
||||
|
||||
for cluster in clusters.iter_mut() {
|
||||
let representative = &cluster[0].0;
|
||||
if pattern_similarity(pattern, representative) >= similarity_threshold {
|
||||
cluster.push((pattern.clone(), paths.clone()));
|
||||
found_cluster = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if !found_cluster {
|
||||
clusters.push(vec![(pattern.clone(), paths.clone())]);
|
||||
}
|
||||
}
|
||||
|
||||
clusters
|
||||
}
|
||||
|
||||
// Merge similar patterns into a flexible pattern
|
||||
fn merge_patterns_in_cluster(
|
||||
cluster: &[(Pattern, Vec<String>)],
|
||||
) -> (Pattern, HashMap<String, bool>) {
|
||||
let mut all_fields: HashSet<String> = HashSet::new();
|
||||
let mut field_counts: HashMap<String, usize> = HashMap::new();
|
||||
let total_patterns = cluster.len();
|
||||
|
||||
// Collect all fields and count occurrences
|
||||
for (pattern, _) in cluster {
|
||||
for field in &pattern.fields {
|
||||
all_fields.insert(field.clone());
|
||||
*field_counts.entry(field.clone()).or_insert(0) += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort fields
|
||||
let mut sorted_fields: Vec<String> = all_fields.into_iter().collect();
|
||||
sorted_fields.sort();
|
||||
|
||||
// Mark which fields are required (present in >80% of patterns)
|
||||
let mut required_fields: HashMap<String, bool> = HashMap::new();
|
||||
for field in &sorted_fields {
|
||||
let count = field_counts.get(field).unwrap_or(&0);
|
||||
required_fields.insert(field.clone(), *count as f64 / total_patterns as f64 > 0.8);
|
||||
}
|
||||
|
||||
(
|
||||
Pattern {
|
||||
fields: sorted_fields,
|
||||
field_count: field_counts.len(),
|
||||
},
|
||||
required_fields,
|
||||
)
|
||||
}
|
||||
|
||||
fn find_patterns(tree: &Value, patterns: &mut HashMap<Pattern, Vec<String>>, path: String) {
|
||||
match tree {
|
||||
Value::Object(map) => {
|
||||
// Check if this is a leaf object (all values are strings)
|
||||
let is_leaf = map.values().all(|v| v.is_string());
|
||||
|
||||
if is_leaf && map.len() > 5 {
|
||||
// This might be a reusable pattern
|
||||
let pattern = extract_pattern(map);
|
||||
patterns
|
||||
.entry(pattern)
|
||||
.or_insert_with(Vec::new)
|
||||
.push(path.clone());
|
||||
}
|
||||
|
||||
// Recurse into children
|
||||
for (key, value) in map {
|
||||
let new_path = if path.is_empty() {
|
||||
key.clone()
|
||||
} else {
|
||||
format!("{}.{}", path, key)
|
||||
};
|
||||
find_patterns(value, patterns, new_path);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn traverse_to_path<'a>(tree: &'a Value, path: &[&str]) -> Option<&'a Value> {
|
||||
let mut current = tree;
|
||||
for segment in path {
|
||||
if let Value::Object(map) = current {
|
||||
current = map.get(*segment)?;
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(current)
|
||||
}
|
||||
|
||||
fn generate_python_pattern_class(
|
||||
merged_pattern: &Pattern,
|
||||
required_fields: &HashMap<String, bool>,
|
||||
class_name: &str,
|
||||
example_path: &str,
|
||||
tree: &Value,
|
||||
) -> String {
|
||||
let mut output = String::new();
|
||||
|
||||
output.push_str(&format!("class {}Namespace:\n", class_name));
|
||||
output.push_str(&format!(
|
||||
" \"\"\"Pattern for {} (supports {} fields)\"\"\"\n",
|
||||
class_name, merged_pattern.field_count
|
||||
));
|
||||
|
||||
let slots: Vec<String> = merged_pattern
|
||||
.fields
|
||||
.iter()
|
||||
.map(|f| sanitize_name(f))
|
||||
.collect();
|
||||
output.push_str(&format!(
|
||||
" __slots__ = ({})\n\n",
|
||||
slots
|
||||
.iter()
|
||||
.map(|s| format!("'{}'", s))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
));
|
||||
|
||||
output.push_str(" def __init__(self, path: str, prefix: str):\n");
|
||||
|
||||
let path_segments: Vec<&str> = example_path.split('.').collect();
|
||||
if let Some(obj) = traverse_to_path(tree, &path_segments) {
|
||||
if let Value::Object(map) = obj {
|
||||
for field in &merged_pattern.fields {
|
||||
let safe_field = sanitize_name(field);
|
||||
if let Some(Value::String(metric_name)) = map.get(field) {
|
||||
output.push_str(&format!(
|
||||
" self.{} = f\"{{path}}/{{prefix}}_{}\"\n",
|
||||
safe_field, metric_name
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output.push_str("\n\n");
|
||||
output
|
||||
}
|
||||
|
||||
fn generate_python_namespace_class(
|
||||
name: &str,
|
||||
obj: &Map<String, Value>,
|
||||
tree: &Value,
|
||||
api_path: &str,
|
||||
pattern_classes: &HashMap<Pattern, String>,
|
||||
) -> String {
|
||||
let mut output = String::new();
|
||||
let class_name = format!(
|
||||
"{}Namespace",
|
||||
name.split('_')
|
||||
.map(|s| {
|
||||
let mut c = s.chars();
|
||||
match c.next() {
|
||||
None => String::new(),
|
||||
Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
|
||||
}
|
||||
})
|
||||
.collect::<String>()
|
||||
);
|
||||
|
||||
output.push_str(&format!("class {}:\n", class_name));
|
||||
output.push_str(&format!(" \"\"\"Namespace for {} metrics\"\"\"\n", name));
|
||||
|
||||
let mut slots = vec![];
|
||||
let mut init_lines = vec![];
|
||||
|
||||
for (key, value) in obj {
|
||||
let safe_key = sanitize_name(key);
|
||||
slots.push(safe_key.clone());
|
||||
|
||||
match value {
|
||||
Value::String(metric_name) => {
|
||||
init_lines.push(format!(
|
||||
" self.{} = f\"{}/{}\"",
|
||||
safe_key, api_path, metric_name
|
||||
));
|
||||
}
|
||||
Value::Object(nested_map) => {
|
||||
let pattern = extract_pattern(nested_map);
|
||||
if let Some(pattern_class) = pattern_classes.get(&pattern) {
|
||||
init_lines.push(format!(
|
||||
" self.{} = {}Namespace(\"{}\", \"{}\")",
|
||||
safe_key, pattern_class, api_path, key
|
||||
));
|
||||
} else {
|
||||
let nested_class = format!(
|
||||
"{}{}",
|
||||
class_name.trim_end_matches("Namespace"),
|
||||
key.split('_')
|
||||
.map(|s| {
|
||||
let mut c = s.chars();
|
||||
match c.next() {
|
||||
None => String::new(),
|
||||
Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
|
||||
}
|
||||
})
|
||||
.collect::<String>()
|
||||
);
|
||||
init_lines.push(format!(" self.{} = {}Namespace()", safe_key));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
output.push_str(&format!(
|
||||
" __slots__ = ({})\n\n",
|
||||
slots
|
||||
.iter()
|
||||
.map(|s| format!("'{}'", s))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
));
|
||||
|
||||
output.push_str(" def __init__(self):\n");
|
||||
for line in init_lines {
|
||||
output.push_str(&format!("{}\n", line));
|
||||
}
|
||||
|
||||
output.push_str("\n\n");
|
||||
output
|
||||
}
|
||||
|
||||
fn generate_python_namespaces_recursive(
|
||||
obj: &Map<String, Value>,
|
||||
tree: &Value,
|
||||
pattern_classes: &HashMap<Pattern, String>,
|
||||
path: &str,
|
||||
output: &mut String,
|
||||
) {
|
||||
for (key, value) in obj {
|
||||
if let Value::Object(nested_map) = value {
|
||||
let new_path = if path.is_empty() {
|
||||
key.clone()
|
||||
} else {
|
||||
format!("{}/{}", path, key)
|
||||
};
|
||||
|
||||
let is_leaf = nested_map.values().all(|v| v.is_string());
|
||||
if !is_leaf {
|
||||
generate_python_namespaces_recursive(
|
||||
nested_map,
|
||||
tree,
|
||||
pattern_classes,
|
||||
&new_path,
|
||||
output,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let api_path = path.replace(".", "/");
|
||||
let name = path.split('/').last().unwrap_or("Root");
|
||||
output.push_str(&generate_python_namespace_class(
|
||||
name,
|
||||
obj,
|
||||
tree,
|
||||
&api_path,
|
||||
pattern_classes,
|
||||
));
|
||||
}
|
||||
|
||||
fn generate_python_client(tree: &Value) -> String {
|
||||
let mut output = String::new();
|
||||
|
||||
output.push_str(
|
||||
r#""""
|
||||
BRK API Tree - Auto-generated from config
|
||||
|
||||
Each attribute is a string representing the API path + metric name.
|
||||
Use these paths with your own fetch implementation.
|
||||
|
||||
DO NOT EDIT - This file is generated by codegen
|
||||
"""
|
||||
|
||||
"#,
|
||||
);
|
||||
|
||||
output.push_str(
|
||||
"# ============================================================================\n",
|
||||
);
|
||||
output.push_str("# PATTERN CLASSES\n");
|
||||
output.push_str(
|
||||
"# ============================================================================\n\n",
|
||||
);
|
||||
|
||||
let mut patterns: HashMap<Pattern, Vec<String>> = HashMap::new();
|
||||
find_patterns(tree, &mut patterns, String::new());
|
||||
|
||||
let clusters = cluster_patterns(&patterns);
|
||||
let mut pattern_classes: HashMap<Pattern, String> = HashMap::new();
|
||||
let mut cluster_id = 0;
|
||||
|
||||
for cluster in clusters.iter() {
|
||||
let total_usage: usize = cluster.iter().map(|(_, paths)| paths.len()).sum();
|
||||
|
||||
if total_usage >= 3 && cluster[0].0.field_count >= 8 {
|
||||
let (merged_pattern, required_fields) = merge_patterns_in_cluster(cluster);
|
||||
|
||||
let class_name = if merged_pattern.fields.iter().any(|f| f.contains("ratio")) {
|
||||
format!("RatioPattern{}", cluster_id)
|
||||
} else if merged_pattern.fields.iter().any(|f| f.contains("count")) {
|
||||
format!("CountPattern{}", cluster_id)
|
||||
} else {
|
||||
format!("CommonPattern{}", cluster_id)
|
||||
};
|
||||
|
||||
output.push_str(&generate_python_pattern_class(
|
||||
&merged_pattern,
|
||||
&required_fields,
|
||||
&class_name,
|
||||
&cluster[0].1[0],
|
||||
tree,
|
||||
));
|
||||
|
||||
for (pattern, _) in cluster {
|
||||
pattern_classes.insert(pattern.clone(), class_name.clone());
|
||||
}
|
||||
|
||||
cluster_id += 1;
|
||||
}
|
||||
}
|
||||
|
||||
output.push_str(
|
||||
"# ============================================================================\n",
|
||||
);
|
||||
output.push_str("# NAMESPACE CLASSES\n");
|
||||
output.push_str(
|
||||
"# ============================================================================\n\n",
|
||||
);
|
||||
|
||||
if let Value::Object(root) = tree {
|
||||
generate_python_namespaces_recursive(root, tree, &pattern_classes, "", &mut output);
|
||||
}
|
||||
|
||||
output.push_str(
|
||||
r#"
|
||||
class BRKTree:
|
||||
"""
|
||||
BRK API Tree
|
||||
|
||||
Usage:
|
||||
tree = BRKTree()
|
||||
path = tree.computed.chain.block_count.base
|
||||
# path is now "computed/chain/block_count"
|
||||
# Use this path with your own HTTP client
|
||||
"""
|
||||
__slots__ = ("computed", "cointime", "constants", "fetched", "indexes", "market")
|
||||
|
||||
def __init__(self):
|
||||
"#,
|
||||
);
|
||||
|
||||
if let Value::Object(root) = tree {
|
||||
for key in root.keys() {
|
||||
output.push_str(&format!(
|
||||
" self.{} = {}Namespace()\n",
|
||||
sanitize_name(key),
|
||||
key.split('_')
|
||||
.map(|s| {
|
||||
let mut c = s.chars();
|
||||
match c.next() {
|
||||
None => String::new(),
|
||||
Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
|
||||
}
|
||||
})
|
||||
.collect::<String>()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
fn to_pascal_case(s: &str) -> String {
|
||||
s.split('_')
|
||||
.map(|word| {
|
||||
let mut chars = word.chars();
|
||||
match chars.next() {
|
||||
None => String::new(),
|
||||
Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn generate_typescript_pattern_class(
|
||||
merged_pattern: &Pattern,
|
||||
class_name: &str,
|
||||
example_path: &str,
|
||||
tree: &Value,
|
||||
) -> String {
|
||||
let mut output = String::new();
|
||||
|
||||
output.push_str(&format!("export class {}Namespace {{\n", class_name));
|
||||
|
||||
for field in &merged_pattern.fields {
|
||||
let safe_field = sanitize_name(field);
|
||||
output.push_str(&format!(" readonly {}: string;\n", safe_field));
|
||||
}
|
||||
|
||||
output.push_str("\n constructor(path: string, prefix: string) {\n");
|
||||
|
||||
let path_segments: Vec<&str> = example_path.split('.').collect();
|
||||
if let Some(obj) = traverse_to_path(tree, &path_segments) {
|
||||
if let Value::Object(map) = obj {
|
||||
for field in &merged_pattern.fields {
|
||||
let safe_field = sanitize_name(field);
|
||||
if let Some(Value::String(metric_name)) = map.get(field) {
|
||||
output.push_str(&format!(
|
||||
" this.{} = `${{path}}/${{prefix}}_{}`;\n",
|
||||
safe_field, metric_name
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output.push_str(" }\n}\n\n");
|
||||
output
|
||||
}
|
||||
|
||||
fn generate_typescript_namespaces_recursive(
|
||||
obj: &Map<String, Value>,
|
||||
tree: &Value,
|
||||
pattern_classes: &HashMap<Pattern, String>,
|
||||
path: &str,
|
||||
output: &mut String,
|
||||
) {
|
||||
for (key, value) in obj {
|
||||
if let Value::Object(nested_map) = value {
|
||||
let new_path = if path.is_empty() {
|
||||
key.clone()
|
||||
} else {
|
||||
format!("{}/{}", path, key)
|
||||
};
|
||||
|
||||
let is_leaf = nested_map.values().all(|v| v.is_string());
|
||||
if !is_leaf {
|
||||
generate_typescript_namespaces_recursive(
|
||||
nested_map,
|
||||
tree,
|
||||
pattern_classes,
|
||||
&new_path,
|
||||
output,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let api_path = path.replace(".", "/");
|
||||
let name = path.split('/').last().unwrap_or("Root");
|
||||
let class_name = to_pascal_case(name);
|
||||
|
||||
output.push_str(&format!("export class {}Namespace {{\n", class_name));
|
||||
|
||||
for (key, value) in obj {
|
||||
let safe_key = sanitize_name(key);
|
||||
match value {
|
||||
Value::String(_) => {
|
||||
output.push_str(&format!(" readonly {}: string;\n", safe_key));
|
||||
}
|
||||
Value::Object(nested_map) => {
|
||||
let pattern = extract_pattern(nested_map);
|
||||
if let Some(pattern_class) = pattern_classes.get(&pattern) {
|
||||
output.push_str(&format!(
|
||||
" readonly {}: {}Namespace;\n",
|
||||
safe_key, pattern_class
|
||||
));
|
||||
} else {
|
||||
let nested_class = format!("{}{}", class_name, to_pascal_case(key));
|
||||
output.push_str(&format!(
|
||||
" readonly {}: {}Namespace;\n",
|
||||
safe_key, nested_class
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
output.push_str("\n constructor() {\n");
|
||||
|
||||
for (key, value) in obj {
|
||||
let safe_key = sanitize_name(key);
|
||||
match value {
|
||||
Value::String(metric_name) => {
|
||||
output.push_str(&format!(
|
||||
" this.{} = '{}/{}';\n",
|
||||
safe_key, api_path, metric_name
|
||||
));
|
||||
}
|
||||
Value::Object(nested_map) => {
|
||||
let pattern = extract_pattern(nested_map);
|
||||
if let Some(pattern_class) = pattern_classes.get(&pattern) {
|
||||
output.push_str(&format!(
|
||||
" this.{} = new {}Namespace('{}', '{}');\n",
|
||||
safe_key, pattern_class, api_path, key
|
||||
));
|
||||
} else {
|
||||
let nested_class = format!("{}{}", class_name, to_pascal_case(key));
|
||||
output.push_str(&format!(
|
||||
" this.{} = new {}Namespace();\n",
|
||||
safe_key, nested_class
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
output.push_str(" }\n}\n\n");
|
||||
}
|
||||
|
||||
fn generate_typescript_client(tree: &Value) -> String {
|
||||
let mut output = String::new();
|
||||
|
||||
output.push_str(
|
||||
r#"/**
|
||||
* BRK API Tree - Auto-generated from config
|
||||
*
|
||||
* Each property is a string representing the API path + metric name.
|
||||
* Use these paths with your own fetch implementation.
|
||||
*
|
||||
* DO NOT EDIT - This file is generated by codegen
|
||||
*/
|
||||
|
||||
"#,
|
||||
);
|
||||
|
||||
let mut patterns: HashMap<Pattern, Vec<String>> = HashMap::new();
|
||||
find_patterns(tree, &mut patterns, String::new());
|
||||
let clusters = cluster_patterns(&patterns);
|
||||
|
||||
let mut pattern_classes: HashMap<Pattern, String> = HashMap::new();
|
||||
let mut cluster_id = 0;
|
||||
|
||||
for cluster in clusters.iter() {
|
||||
let total_usage: usize = cluster.iter().map(|(_, paths)| paths.len()).sum();
|
||||
|
||||
if total_usage >= 3 && cluster[0].0.field_count >= 8 {
|
||||
let (merged_pattern, _) = merge_patterns_in_cluster(cluster);
|
||||
|
||||
let class_name = if merged_pattern.fields.iter().any(|f| f.contains("ratio")) {
|
||||
format!("RatioPattern{}", cluster_id)
|
||||
} else if merged_pattern.fields.iter().any(|f| f.contains("count")) {
|
||||
format!("CountPattern{}", cluster_id)
|
||||
} else {
|
||||
format!("CommonPattern{}", cluster_id)
|
||||
};
|
||||
|
||||
output.push_str(&generate_typescript_pattern_class(
|
||||
&merged_pattern,
|
||||
&class_name,
|
||||
&cluster[0].1[0],
|
||||
tree,
|
||||
));
|
||||
|
||||
for (pattern, _) in cluster {
|
||||
pattern_classes.insert(pattern.clone(), class_name.clone());
|
||||
}
|
||||
|
||||
cluster_id += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if let Value::Object(root) = tree {
|
||||
generate_typescript_namespaces_recursive(root, tree, &pattern_classes, "", &mut output);
|
||||
}
|
||||
|
||||
output.push_str(
|
||||
r#"
|
||||
export class BRKTree {
|
||||
"#,
|
||||
);
|
||||
|
||||
if let Value::Object(root) = tree {
|
||||
for key in root.keys() {
|
||||
let class_name = to_pascal_case(key);
|
||||
output.push_str(&format!(
|
||||
" readonly {}: {}Namespace;\n",
|
||||
sanitize_name(key),
|
||||
class_name
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
output.push_str("\n constructor() {\n");
|
||||
|
||||
if let Value::Object(root) = tree {
|
||||
for key in root.keys() {
|
||||
let class_name = to_pascal_case(key);
|
||||
output.push_str(&format!(
|
||||
" this.{} = new {}Namespace();\n",
|
||||
sanitize_name(key),
|
||||
class_name
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
output.push_str(" }\n}\n");
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
fn to_snake_case(s: &str) -> String {
|
||||
let sanitized = s.replace("-", "_");
|
||||
match sanitized.as_str() {
|
||||
"type" | "const" | "static" | "match" | "if" | "else" | "loop" | "while" => {
|
||||
format!("r#{}", sanitized)
|
||||
}
|
||||
_ => sanitized,
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_rust_pattern_struct(
|
||||
merged_pattern: &Pattern,
|
||||
struct_name: &str,
|
||||
example_path: &str,
|
||||
tree: &Value,
|
||||
) -> String {
|
||||
let mut output = String::new();
|
||||
|
||||
output.push_str(&format!("/// Pattern for {} metrics\n", struct_name));
|
||||
output.push_str("#[derive(Clone, Debug)]\n");
|
||||
output.push_str(&format!("pub struct {}Namespace {{\n", struct_name));
|
||||
|
||||
for field in &merged_pattern.fields {
|
||||
let safe_field = to_snake_case(&sanitize_name(field));
|
||||
output.push_str(&format!(" pub {}: String,\n", safe_field));
|
||||
}
|
||||
|
||||
output.push_str("}\n\n");
|
||||
|
||||
output.push_str(&format!("impl {}Namespace {{\n", struct_name));
|
||||
output.push_str(" fn new(path: &str, prefix: &str) -> Self {\n");
|
||||
output.push_str(" Self {\n");
|
||||
|
||||
let path_segments: Vec<&str> = example_path.split('.').collect();
|
||||
if let Some(obj) = traverse_to_path(tree, &path_segments) {
|
||||
if let Value::Object(map) = obj {
|
||||
for field in &merged_pattern.fields {
|
||||
let safe_field = to_snake_case(&sanitize_name(field));
|
||||
if let Some(Value::String(metric_name)) = map.get(field) {
|
||||
output.push_str(&format!(
|
||||
" {}: format!(\"{{}}/{{}}_{}}\", path, prefix),\n",
|
||||
safe_field, metric_name
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output.push_str(" }\n }\n}\n\n");
|
||||
output
|
||||
}
|
||||
|
||||
fn generate_rust_namespaces_recursive(
|
||||
obj: &Map<String, Value>,
|
||||
tree: &Value,
|
||||
pattern_classes: &HashMap<Pattern, String>,
|
||||
path: &str,
|
||||
output: &mut String,
|
||||
) {
|
||||
for (key, value) in obj {
|
||||
if let Value::Object(nested_map) = value {
|
||||
let new_path = if path.is_empty() {
|
||||
key.clone()
|
||||
} else {
|
||||
format!("{}/{}", path, key)
|
||||
};
|
||||
|
||||
let is_leaf = nested_map.values().all(|v| v.is_string());
|
||||
if !is_leaf {
|
||||
generate_rust_namespaces_recursive(
|
||||
nested_map,
|
||||
tree,
|
||||
pattern_classes,
|
||||
&new_path,
|
||||
output,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let api_path = path.replace(".", "/");
|
||||
let name = path.split('/').last().unwrap_or("Root");
|
||||
let struct_name = to_pascal_case(name);
|
||||
|
||||
output.push_str(&format!("/// Namespace for {} metrics\n", name));
|
||||
output.push_str("#[derive(Clone, Debug)]\n");
|
||||
output.push_str(&format!("pub struct {}Namespace {{\n", struct_name));
|
||||
|
||||
for (key, value) in obj {
|
||||
let safe_key = to_snake_case(&sanitize_name(key));
|
||||
match value {
|
||||
Value::String(_) => {
|
||||
output.push_str(&format!(" pub {}: String,\n", safe_key));
|
||||
}
|
||||
Value::Object(nested_map) => {
|
||||
let pattern = extract_pattern(nested_map);
|
||||
if let Some(pattern_class) = pattern_classes.get(&pattern) {
|
||||
output.push_str(&format!(
|
||||
" pub {}: {}Namespace,\n",
|
||||
safe_key, pattern_class
|
||||
));
|
||||
} else {
|
||||
let nested_struct = format!("{}{}", struct_name, to_pascal_case(key));
|
||||
output.push_str(&format!(
|
||||
" pub {}: {}Namespace,\n",
|
||||
safe_key, nested_struct
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
output.push_str("}\n\n");
|
||||
|
||||
output.push_str(&format!("impl {}Namespace {{\n", struct_name));
|
||||
output.push_str(" fn new() -> Self {\n Self {\n");
|
||||
|
||||
for (key, value) in obj {
|
||||
let safe_key = to_snake_case(&sanitize_name(key));
|
||||
match value {
|
||||
Value::String(metric_name) => {
|
||||
output.push_str(&format!(
|
||||
" {}: \"{}/{}\".to_string(),\n",
|
||||
safe_key, api_path, metric_name
|
||||
));
|
||||
}
|
||||
Value::Object(nested_map) => {
|
||||
let pattern = extract_pattern(nested_map);
|
||||
if let Some(pattern_class) = pattern_classes.get(&pattern) {
|
||||
output.push_str(&format!(
|
||||
" {}: {}Namespace::new(\"{}\", \"{}\"),\n",
|
||||
safe_key, pattern_class, api_path, key
|
||||
));
|
||||
} else {
|
||||
let nested_struct = format!("{}{}", struct_name, to_pascal_case(key));
|
||||
output.push_str(&format!(
|
||||
" {}: {}Namespace::new(),\n",
|
||||
safe_key, nested_struct
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
output.push_str(" }\n }\n}\n\n");
|
||||
}
|
||||
|
||||
fn generate_rust_client(tree: &Value) -> String {
|
||||
let mut output = String::new();
|
||||
|
||||
output.push_str(
|
||||
r#"//! BRK API Tree - Auto-generated from config
|
||||
//!
|
||||
//! Each field is a String representing the API path + metric name.
|
||||
//! Use these paths with your own HTTP client.
|
||||
//!
|
||||
//! DO NOT EDIT - This file is generated by codegen
|
||||
|
||||
"#,
|
||||
);
|
||||
|
||||
let mut patterns: HashMap<Pattern, Vec<String>> = HashMap::new();
|
||||
find_patterns(tree, &mut patterns, String::new());
|
||||
let clusters = cluster_patterns(&patterns);
|
||||
|
||||
let mut pattern_classes: HashMap<Pattern, String> = HashMap::new();
|
||||
let mut cluster_id = 0;
|
||||
|
||||
for cluster in clusters.iter() {
|
||||
let total_usage: usize = cluster.iter().map(|(_, paths)| paths.len()).sum();
|
||||
|
||||
if total_usage >= 3 && cluster[0].0.field_count >= 8 {
|
||||
let (merged_pattern, _) = merge_patterns_in_cluster(cluster);
|
||||
|
||||
let class_name = if merged_pattern.fields.iter().any(|f| f.contains("ratio")) {
|
||||
format!("RatioPattern{}", cluster_id)
|
||||
} else if merged_pattern.fields.iter().any(|f| f.contains("count")) {
|
||||
format!("CountPattern{}", cluster_id)
|
||||
} else {
|
||||
format!("CommonPattern{}", cluster_id)
|
||||
};
|
||||
|
||||
output.push_str(&generate_rust_pattern_struct(
|
||||
&merged_pattern,
|
||||
&class_name,
|
||||
&cluster[0].1[0],
|
||||
tree,
|
||||
));
|
||||
|
||||
for (pattern, _) in cluster {
|
||||
pattern_classes.insert(pattern.clone(), class_name.clone());
|
||||
}
|
||||
|
||||
cluster_id += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if let Value::Object(root) = tree {
|
||||
generate_rust_namespaces_recursive(root, tree, &pattern_classes, "", &mut output);
|
||||
}
|
||||
|
||||
output.push_str("/// Main BRK API tree\n");
|
||||
output.push_str("#[derive(Clone, Debug)]\n");
|
||||
output.push_str("pub struct BRKTree {\n");
|
||||
|
||||
if let Value::Object(root) = tree {
|
||||
for key in root.keys() {
|
||||
let struct_name = to_pascal_case(key);
|
||||
output.push_str(&format!(
|
||||
" pub {}: {}Namespace,\n",
|
||||
to_snake_case(key),
|
||||
struct_name
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
output.push_str("}\n\nimpl BRKTree {\n pub fn new() -> Self {\n Self {\n");
|
||||
|
||||
if let Value::Object(root) = tree {
|
||||
for key in root.keys() {
|
||||
let struct_name = to_pascal_case(key);
|
||||
output.push_str(&format!(
|
||||
" {}: {}Namespace::new(),\n",
|
||||
to_snake_case(key),
|
||||
struct_name
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
output.push_str(" }\n }\n}\n\nimpl Default for BRKTree {\n fn default() -> Self {\n Self::new()\n }\n}\n");
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let json_str = fs::read_to_string("brk_config.json").expect("Failed to read config file");
|
||||
|
||||
let tree: Value = serde_json::from_str(&json_str).expect("Failed to parse JSON");
|
||||
|
||||
// Generate Python tree
|
||||
let python_code = generate_python_client(&tree);
|
||||
fs::write("brk_tree_generated.py", python_code).expect("Failed to write Python file");
|
||||
println!("✓ Generated brk_tree_generated.py");
|
||||
|
||||
// Generate TypeScript tree
|
||||
let ts_code = generate_typescript_client(&tree);
|
||||
fs::write("brk_tree_generated.ts", ts_code).expect("Failed to write TypeScript file");
|
||||
println!("✓ Generated brk_tree_generated.ts");
|
||||
|
||||
// Generate Rust tree
|
||||
let rust_code = generate_rust_client(&tree);
|
||||
fs::write("brk_tree_generated.rs", rust_code).expect("Failed to write Rust file");
|
||||
println!("✓ Generated brk_tree_generated.rs");
|
||||
}
|
||||
@@ -27,6 +27,7 @@ log = { workspace = true }
|
||||
pco = "0.4.7"
|
||||
rayon = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
schemars = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
smallvec = { workspace = true }
|
||||
vecdb = { workspace = true }
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use brk_error::{Error, Result};
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{CheckedSub, StoredU64, Version};
|
||||
use schemars::JsonSchema;
|
||||
use vecdb::{
|
||||
AnyStoredVec, Database, EagerVec, Exit, GenericStoredVec, ImportableVec, IterableVec, PcoVec,
|
||||
VecIndex, VecValue,
|
||||
@@ -16,7 +17,7 @@ const VERSION: Version = Version::ZERO;
|
||||
pub struct EagerVecsBuilder<I, T>
|
||||
where
|
||||
I: VecIndex,
|
||||
T: ComputedVecValue,
|
||||
T: ComputedVecValue + JsonSchema,
|
||||
{
|
||||
pub first: Option<Box<EagerVec<PcoVec<I, T>>>>,
|
||||
pub average: Option<Box<EagerVec<PcoVec<I, T>>>>,
|
||||
@@ -35,7 +36,7 @@ where
|
||||
impl<I, T> EagerVecsBuilder<I, T>
|
||||
where
|
||||
I: VecIndex,
|
||||
T: ComputedVecValue,
|
||||
T: ComputedVecValue + JsonSchema,
|
||||
{
|
||||
pub fn forced_import(
|
||||
db: &Database,
|
||||
@@ -159,7 +160,12 @@ where
|
||||
/// Compute percentiles from sorted values (assumes values is already sorted)
|
||||
fn compute_percentiles_from_sorted(&mut self, index: usize, values: &[T]) -> Result<()> {
|
||||
if let Some(max) = self.max.as_mut() {
|
||||
max.truncate_push_at(index, *values.last().ok_or(Error::Internal("Empty values for percentiles"))?)?;
|
||||
max.truncate_push_at(
|
||||
index,
|
||||
*values
|
||||
.last()
|
||||
.ok_or(Error::Internal("Empty values for percentiles"))?,
|
||||
)?;
|
||||
}
|
||||
if let Some(pct90) = self.pct90.as_mut() {
|
||||
pct90.truncate_push_at(index, get_percentile(values, 0.90))?;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::Version;
|
||||
use schemars::JsonSchema;
|
||||
use vecdb::{FromCoarserIndex, IterableBoxedVec, IterableCloneableVec, LazyVecFrom2, VecIndex};
|
||||
|
||||
use crate::grouped::{EagerVecsBuilder, VecBuilderOptions};
|
||||
@@ -12,7 +13,7 @@ use super::ComputedVecValue;
|
||||
pub struct LazyVecsBuilder<I, T, S1I, S2T>
|
||||
where
|
||||
I: VecIndex,
|
||||
T: ComputedVecValue,
|
||||
T: ComputedVecValue + JsonSchema,
|
||||
S1I: VecIndex,
|
||||
S2T: ComputedVecValue,
|
||||
{
|
||||
@@ -30,7 +31,7 @@ const VERSION: Version = Version::ZERO;
|
||||
impl<I, T, S1I, S2T> LazyVecsBuilder<I, T, S1I, S2T>
|
||||
where
|
||||
I: VecIndex,
|
||||
T: ComputedVecValue + 'static,
|
||||
T: ComputedVecValue + JsonSchema + 'static,
|
||||
S1I: VecIndex + 'static + FromCoarserIndex<I>,
|
||||
S2T: ComputedVecValue,
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@ use brk_traversable::Traversable;
|
||||
use brk_types::{
|
||||
DateIndex, DecadeIndex, MonthIndex, QuarterIndex, SemesterIndex, Version, WeekIndex, YearIndex,
|
||||
};
|
||||
use schemars::JsonSchema;
|
||||
use vecdb::{
|
||||
AnyExportableVec, Database, EagerVec, Exit, ImportableVec, IterableCloneableVec, IterableVec,
|
||||
PcoVec,
|
||||
@@ -16,7 +17,7 @@ use super::{ComputedVecValue, EagerVecsBuilder, Source, VecBuilderOptions};
|
||||
#[derive(Clone)]
|
||||
pub struct ComputedVecsFromDateIndex<T>
|
||||
where
|
||||
T: ComputedVecValue + PartialOrd,
|
||||
T: ComputedVecValue + PartialOrd + JsonSchema,
|
||||
{
|
||||
pub dateindex: Option<EagerVec<PcoVec<DateIndex, T>>>,
|
||||
pub dateindex_extra: EagerVecsBuilder<DateIndex, T>,
|
||||
@@ -32,7 +33,7 @@ const VERSION: Version = Version::ZERO;
|
||||
|
||||
impl<T> ComputedVecsFromDateIndex<T>
|
||||
where
|
||||
T: ComputedVecValue + 'static,
|
||||
T: ComputedVecValue + JsonSchema + 'static,
|
||||
{
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn forced_import(
|
||||
@@ -149,7 +150,7 @@ where
|
||||
|
||||
impl<T> Traversable for ComputedVecsFromDateIndex<T>
|
||||
where
|
||||
T: ComputedVecValue,
|
||||
T: ComputedVecValue + JsonSchema,
|
||||
{
|
||||
fn to_tree_node(&self) -> brk_traversable::TreeNode {
|
||||
let dateindex_extra_node = self.dateindex_extra.to_tree_node();
|
||||
|
||||
@@ -5,6 +5,7 @@ use brk_types::{
|
||||
DateIndex, DecadeIndex, DifficultyEpoch, Height, MonthIndex, QuarterIndex, SemesterIndex,
|
||||
Version, WeekIndex, YearIndex,
|
||||
};
|
||||
use schemars::JsonSchema;
|
||||
use vecdb::{
|
||||
AnyExportableVec, Database, EagerVec, Exit, ImportableVec, IterableCloneableVec, IterableVec,
|
||||
PcoVec,
|
||||
@@ -22,7 +23,7 @@ use super::{ComputedVecValue, EagerVecsBuilder, VecBuilderOptions};
|
||||
#[derive(Clone)]
|
||||
pub struct ComputedVecsFromHeight<T>
|
||||
where
|
||||
T: ComputedVecValue + PartialOrd,
|
||||
T: ComputedVecValue + PartialOrd + JsonSchema,
|
||||
{
|
||||
pub height: Option<EagerVec<PcoVec<Height, T>>>,
|
||||
pub height_extra: EagerVecsBuilder<Height, T>,
|
||||
@@ -41,7 +42,7 @@ const VERSION: Version = Version::ZERO;
|
||||
|
||||
impl<T> ComputedVecsFromHeight<T>
|
||||
where
|
||||
T: ComputedVecValue + Ord + From<f64> + 'static,
|
||||
T: ComputedVecValue + Ord + From<f64> + JsonSchema + 'static,
|
||||
f64: From<T>,
|
||||
{
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -202,7 +203,7 @@ where
|
||||
|
||||
impl<T> Traversable for ComputedVecsFromHeight<T>
|
||||
where
|
||||
T: ComputedVecValue,
|
||||
T: ComputedVecValue + JsonSchema,
|
||||
{
|
||||
fn to_tree_node(&self) -> brk_traversable::TreeNode {
|
||||
let height_extra_node = self.height_extra.to_tree_node();
|
||||
|
||||
@@ -2,6 +2,7 @@ use brk_error::Result;
|
||||
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{DifficultyEpoch, Height, Version};
|
||||
use schemars::JsonSchema;
|
||||
use vecdb::{AnyExportableVec, Database, EagerVec, Exit, ImportableVec, PcoVec};
|
||||
|
||||
use crate::{Indexes, indexes};
|
||||
@@ -11,7 +12,7 @@ use super::{ComputedVecValue, EagerVecsBuilder, VecBuilderOptions};
|
||||
#[derive(Clone)]
|
||||
pub struct ComputedVecsFromHeightStrict<T>
|
||||
where
|
||||
T: ComputedVecValue + PartialOrd,
|
||||
T: ComputedVecValue + PartialOrd + JsonSchema,
|
||||
{
|
||||
pub height: EagerVec<PcoVec<Height, T>>,
|
||||
pub height_extra: EagerVecsBuilder<Height, T>,
|
||||
@@ -23,7 +24,7 @@ const VERSION: Version = Version::ZERO;
|
||||
|
||||
impl<T> ComputedVecsFromHeightStrict<T>
|
||||
where
|
||||
T: ComputedVecValue + Ord + From<f64>,
|
||||
T: ComputedVecValue + Ord + From<f64> + JsonSchema,
|
||||
f64: From<T>,
|
||||
{
|
||||
pub fn forced_import(
|
||||
@@ -85,7 +86,7 @@ where
|
||||
|
||||
impl<T> Traversable for ComputedVecsFromHeightStrict<T>
|
||||
where
|
||||
T: ComputedVecValue,
|
||||
T: ComputedVecValue + JsonSchema,
|
||||
{
|
||||
fn to_tree_node(&self) -> brk_traversable::TreeNode {
|
||||
let height_extra_node = self.height_extra.to_tree_node();
|
||||
|
||||
@@ -5,6 +5,7 @@ use brk_types::{
|
||||
Bitcoin, DateIndex, DecadeIndex, DifficultyEpoch, Dollars, Height, MonthIndex, QuarterIndex,
|
||||
Sats, SemesterIndex, TxIndex, Version, WeekIndex, YearIndex,
|
||||
};
|
||||
use schemars::JsonSchema;
|
||||
use vecdb::{
|
||||
AnyExportableVec, AnyVec, CollectableVec, Database, EagerVec, Exit, GenericStoredVec,
|
||||
ImportableVec, IterableCloneableVec, PcoVec, TypedVecIterator, VecIndex,
|
||||
@@ -22,7 +23,7 @@ use super::{ComputedVecValue, EagerVecsBuilder, VecBuilderOptions};
|
||||
#[derive(Clone)]
|
||||
pub struct ComputedVecsFromTxindex<T>
|
||||
where
|
||||
T: ComputedVecValue + PartialOrd,
|
||||
T: ComputedVecValue + PartialOrd + JsonSchema,
|
||||
{
|
||||
pub txindex: Option<Box<EagerVec<PcoVec<TxIndex, T>>>>,
|
||||
pub height: EagerVecsBuilder<Height, T>,
|
||||
@@ -41,7 +42,7 @@ const VERSION: Version = Version::ZERO;
|
||||
|
||||
impl<T> ComputedVecsFromTxindex<T>
|
||||
where
|
||||
T: ComputedVecValue + Ord + From<f64> + 'static,
|
||||
T: ComputedVecValue + Ord + From<f64> + JsonSchema + 'static,
|
||||
f64: From<T>,
|
||||
{
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -256,10 +257,8 @@ impl ComputedVecsFromTxindex<Bitcoin> {
|
||||
.map(Height::from)
|
||||
.try_for_each(|height| -> Result<()> {
|
||||
if let Some(first) = self.height.first.as_mut() {
|
||||
first.truncate_push(
|
||||
height,
|
||||
Bitcoin::from(first_iter.um().get_unwrap(height)),
|
||||
)?;
|
||||
first
|
||||
.truncate_push(height, Bitcoin::from(first_iter.um().get_unwrap(height)))?;
|
||||
}
|
||||
if let Some(average) = self.height.average.as_mut() {
|
||||
average.truncate_push(
|
||||
@@ -268,28 +267,18 @@ impl ComputedVecsFromTxindex<Bitcoin> {
|
||||
)?;
|
||||
}
|
||||
if let Some(sum) = self.height.sum.as_mut() {
|
||||
sum.truncate_push(
|
||||
height,
|
||||
Bitcoin::from(sum_iter.um().get_unwrap(height)),
|
||||
)?;
|
||||
sum.truncate_push(height, Bitcoin::from(sum_iter.um().get_unwrap(height)))?;
|
||||
}
|
||||
if let Some(max) = self.height.max.as_mut() {
|
||||
max.truncate_push(
|
||||
height,
|
||||
Bitcoin::from(max_iter.um().get_unwrap(height)),
|
||||
)?;
|
||||
max.truncate_push(height, Bitcoin::from(max_iter.um().get_unwrap(height)))?;
|
||||
}
|
||||
if let Some(pct90) = self.height.pct90.as_mut() {
|
||||
pct90.truncate_push(
|
||||
height,
|
||||
Bitcoin::from(pct90_iter.um().get_unwrap(height)),
|
||||
)?;
|
||||
pct90
|
||||
.truncate_push(height, Bitcoin::from(pct90_iter.um().get_unwrap(height)))?;
|
||||
}
|
||||
if let Some(pct75) = self.height.pct75.as_mut() {
|
||||
pct75.truncate_push(
|
||||
height,
|
||||
Bitcoin::from(pct75_iter.um().get_unwrap(height)),
|
||||
)?;
|
||||
pct75
|
||||
.truncate_push(height, Bitcoin::from(pct75_iter.um().get_unwrap(height)))?;
|
||||
}
|
||||
if let Some(median) = self.height.median.as_mut() {
|
||||
median.truncate_push(
|
||||
@@ -298,28 +287,18 @@ impl ComputedVecsFromTxindex<Bitcoin> {
|
||||
)?;
|
||||
}
|
||||
if let Some(pct25) = self.height.pct25.as_mut() {
|
||||
pct25.truncate_push(
|
||||
height,
|
||||
Bitcoin::from(pct25_iter.um().get_unwrap(height)),
|
||||
)?;
|
||||
pct25
|
||||
.truncate_push(height, Bitcoin::from(pct25_iter.um().get_unwrap(height)))?;
|
||||
}
|
||||
if let Some(pct10) = self.height.pct10.as_mut() {
|
||||
pct10.truncate_push(
|
||||
height,
|
||||
Bitcoin::from(pct10_iter.um().get_unwrap(height)),
|
||||
)?;
|
||||
pct10
|
||||
.truncate_push(height, Bitcoin::from(pct10_iter.um().get_unwrap(height)))?;
|
||||
}
|
||||
if let Some(min) = self.height.min.as_mut() {
|
||||
min.truncate_push(
|
||||
height,
|
||||
Bitcoin::from(min_iter.um().get_unwrap(height)),
|
||||
)?;
|
||||
min.truncate_push(height, Bitcoin::from(min_iter.um().get_unwrap(height)))?;
|
||||
}
|
||||
if let Some(last) = self.height.last.as_mut() {
|
||||
last.truncate_push(
|
||||
height,
|
||||
Bitcoin::from(last_iter.um().get_unwrap(height)),
|
||||
)?;
|
||||
last.truncate_push(height, Bitcoin::from(last_iter.um().get_unwrap(height)))?;
|
||||
}
|
||||
if let Some(cumulative) = self.height.cumulative.as_mut() {
|
||||
cumulative.truncate_push(
|
||||
@@ -381,76 +360,41 @@ impl ComputedVecsFromTxindex<Dollars> {
|
||||
let price = *close_iter.get_unwrap(height);
|
||||
|
||||
if let Some(first) = self.height.first.as_mut() {
|
||||
first.truncate_push(
|
||||
height,
|
||||
price * first_iter.um().get_unwrap(height),
|
||||
)?;
|
||||
first.truncate_push(height, price * first_iter.um().get_unwrap(height))?;
|
||||
}
|
||||
if let Some(average) = self.height.average.as_mut() {
|
||||
average.truncate_push(
|
||||
height,
|
||||
price * average_iter.um().get_unwrap(height),
|
||||
)?;
|
||||
average.truncate_push(height, price * average_iter.um().get_unwrap(height))?;
|
||||
}
|
||||
if let Some(sum) = self.height.sum.as_mut() {
|
||||
sum.truncate_push(
|
||||
height,
|
||||
price * sum_iter.um().get_unwrap(height),
|
||||
)?;
|
||||
sum.truncate_push(height, price * sum_iter.um().get_unwrap(height))?;
|
||||
}
|
||||
if let Some(max) = self.height.max.as_mut() {
|
||||
max.truncate_push(
|
||||
height,
|
||||
price * max_iter.um().get_unwrap(height),
|
||||
)?;
|
||||
max.truncate_push(height, price * max_iter.um().get_unwrap(height))?;
|
||||
}
|
||||
if let Some(pct90) = self.height.pct90.as_mut() {
|
||||
pct90.truncate_push(
|
||||
height,
|
||||
price * pct90_iter.um().get_unwrap(height),
|
||||
)?;
|
||||
pct90.truncate_push(height, price * pct90_iter.um().get_unwrap(height))?;
|
||||
}
|
||||
if let Some(pct75) = self.height.pct75.as_mut() {
|
||||
pct75.truncate_push(
|
||||
height,
|
||||
price * pct75_iter.um().get_unwrap(height),
|
||||
)?;
|
||||
pct75.truncate_push(height, price * pct75_iter.um().get_unwrap(height))?;
|
||||
}
|
||||
if let Some(median) = self.height.median.as_mut() {
|
||||
median.truncate_push(
|
||||
height,
|
||||
price * median_iter.um().get_unwrap(height),
|
||||
)?;
|
||||
median.truncate_push(height, price * median_iter.um().get_unwrap(height))?;
|
||||
}
|
||||
if let Some(pct25) = self.height.pct25.as_mut() {
|
||||
pct25.truncate_push(
|
||||
height,
|
||||
price * pct25_iter.um().get_unwrap(height),
|
||||
)?;
|
||||
pct25.truncate_push(height, price * pct25_iter.um().get_unwrap(height))?;
|
||||
}
|
||||
if let Some(pct10) = self.height.pct10.as_mut() {
|
||||
pct10.truncate_push(
|
||||
height,
|
||||
price * pct10_iter.um().get_unwrap(height),
|
||||
)?;
|
||||
pct10.truncate_push(height, price * pct10_iter.um().get_unwrap(height))?;
|
||||
}
|
||||
if let Some(min) = self.height.min.as_mut() {
|
||||
min.truncate_push(
|
||||
height,
|
||||
price * min_iter.um().get_unwrap(height),
|
||||
)?;
|
||||
min.truncate_push(height, price * min_iter.um().get_unwrap(height))?;
|
||||
}
|
||||
if let Some(last) = self.height.last.as_mut() {
|
||||
last.truncate_push(
|
||||
height,
|
||||
price * last_iter.um().get_unwrap(height),
|
||||
)?;
|
||||
last.truncate_push(height, price * last_iter.um().get_unwrap(height))?;
|
||||
}
|
||||
if let Some(cumulative) = self.height.cumulative.as_mut() {
|
||||
cumulative.truncate_push(
|
||||
height,
|
||||
price * cumulative_iter.um().get_unwrap(height),
|
||||
)?;
|
||||
cumulative
|
||||
.truncate_push(height, price * cumulative_iter.um().get_unwrap(height))?;
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
@@ -463,7 +407,7 @@ impl ComputedVecsFromTxindex<Dollars> {
|
||||
|
||||
impl<T> Traversable for ComputedVecsFromTxindex<T>
|
||||
where
|
||||
T: ComputedVecValue,
|
||||
T: ComputedVecValue + JsonSchema,
|
||||
{
|
||||
fn to_tree_node(&self) -> brk_traversable::TreeNode {
|
||||
brk_traversable::TreeNode::Branch(
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use std::mem;
|
||||
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Date, DateIndex, Dollars, StoredF32, Version};
|
||||
use vecdb::{PcoVec,
|
||||
use vecdb::{
|
||||
AnyStoredVec, AnyVec, BoxedVecIterator, CollectableVec, Database, EagerVec, Exit,
|
||||
GenericStoredVec, IterableVec, VecIndex,
|
||||
GenericStoredVec, IterableVec, PcoVec, VecIndex,
|
||||
};
|
||||
|
||||
use crate::{Indexes, grouped::source::Source, indexes, utils::OptionExt};
|
||||
@@ -109,8 +111,14 @@ impl ComputedStandardDeviationVecsFromDateIndex {
|
||||
macro_rules! import {
|
||||
($suffix:expr) => {
|
||||
ComputedVecsFromDateIndex::forced_import(
|
||||
db, &format!("{name}_{}", $suffix), Source::Compute, version, indexes, opts,
|
||||
).unwrap()
|
||||
db,
|
||||
&format!("{name}_{}", $suffix),
|
||||
Source::Compute,
|
||||
version,
|
||||
indexes,
|
||||
opts,
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -183,9 +191,7 @@ impl ComputedStandardDeviationVecsFromDateIndex {
|
||||
source: &impl CollectableVec<DateIndex, StoredF32>,
|
||||
price_opt: Option<&impl IterableVec<DateIndex, Dollars>>,
|
||||
) -> Result<()> {
|
||||
let sma = sma_opt.unwrap_or_else(|| unsafe {
|
||||
std::mem::transmute(&self.sma.u().dateindex)
|
||||
});
|
||||
let sma = sma_opt.unwrap_or_else(|| unsafe { mem::transmute(&self.sma.u().dateindex) });
|
||||
|
||||
let min_date = DateIndex::try_from(Date::MIN_RATIO).unwrap();
|
||||
|
||||
@@ -345,7 +351,11 @@ impl ComputedStandardDeviationVecsFromDateIndex {
|
||||
.try_for_each(|v| v.safe_flush(exit))?;
|
||||
|
||||
self.mut_stateful_computed().try_for_each(|v| {
|
||||
v.compute_rest(starting_indexes, exit, None as Option<&EagerVec<PcoVec<_, _>>>)
|
||||
v.compute_rest(
|
||||
starting_indexes,
|
||||
exit,
|
||||
None as Option<&EagerVec<PcoVec<_, _>>>,
|
||||
)
|
||||
})?;
|
||||
|
||||
if let Some(zscore) = self.zscore.as_mut() {
|
||||
@@ -536,7 +546,6 @@ impl ComputedStandardDeviationVecsFromDateIndex {
|
||||
fn mut_stateful_date_vecs(
|
||||
&mut self,
|
||||
) -> impl Iterator<Item = &mut EagerVec<PcoVec<DateIndex, StoredF32>>> {
|
||||
self.mut_stateful_computed()
|
||||
.map(|c| c.dateindex.um())
|
||||
self.mut_stateful_computed().map(|c| c.dateindex.um())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,7 +217,7 @@ impl Vecs {
|
||||
.or_else(|| self.pools.find_from_coinbase_tag(&coinbase_tag))
|
||||
.unwrap_or(unknown);
|
||||
|
||||
self.height_to_pool.push_if_needed(height, pool.slug)?;
|
||||
self.height_to_pool.truncate_push(height, pool.slug)?;
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ use brk_traversable::Traversable;
|
||||
use brk_types::{Height, StoredU64, Version};
|
||||
use derive_deref::{Deref, DerefMut};
|
||||
use vecdb::{
|
||||
AnyStoredVec, Database, EagerVec, Exit, GenericStoredVec, ImportableVec, PcoVec,
|
||||
AnyStoredVec, AnyVec, Database, EagerVec, Exit, GenericStoredVec, ImportableVec, PcoVec,
|
||||
TypedVecIterator,
|
||||
};
|
||||
|
||||
@@ -54,6 +54,18 @@ impl From<ByAddressType<EagerVec<PcoVec<Height, StoredU64>>>>
|
||||
}
|
||||
|
||||
impl AddressTypeToHeightToAddressCount {
|
||||
pub fn min_len(&self) -> usize {
|
||||
self.p2pk65
|
||||
.len()
|
||||
.min(self.p2pk33.len())
|
||||
.min(self.p2pkh.len())
|
||||
.min(self.p2sh.len())
|
||||
.min(self.p2wpkh.len())
|
||||
.min(self.p2wsh.len())
|
||||
.min(self.p2tr.len())
|
||||
.min(self.p2a.len())
|
||||
}
|
||||
|
||||
pub fn forced_import(db: &Database, name: &str, version: Version) -> Result<Self> {
|
||||
Ok(Self::from(ByAddressType::new_with_name(|type_name| {
|
||||
Ok(EagerVec::forced_import(
|
||||
|
||||
@@ -36,6 +36,8 @@ pub fn process_address_updates(
|
||||
empty_updates: AddressTypeToTypeIndexMap<EmptyAddressDataWithSource>,
|
||||
loaded_updates: AddressTypeToTypeIndexMap<LoadedAddressDataWithSource>,
|
||||
) -> Result<()> {
|
||||
info!("Processing address updates...");
|
||||
|
||||
let empty_result = process_empty_addresses(addresses_data, empty_updates)?;
|
||||
let loaded_result = process_loaded_addresses(addresses_data, loaded_updates)?;
|
||||
let all_updates = empty_result.merge(loaded_result);
|
||||
|
||||
@@ -5,52 +5,52 @@ use brk_types::{LoadedAddressData, OutputType, TypeIndex};
|
||||
use super::super::address::AddressTypeToTypeIndexMap;
|
||||
use super::{EmptyAddressDataWithSource, LoadedAddressDataWithSource, WithAddressDataSource};
|
||||
|
||||
/// Source of an address in lookup - reports where the data came from.
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum AddressSource {
|
||||
/// Brand new address (never seen before)
|
||||
New,
|
||||
/// Loaded from disk (has existing balance)
|
||||
Loaded,
|
||||
/// Was empty (zero balance), now receiving
|
||||
FromEmpty,
|
||||
}
|
||||
|
||||
/// Context for looking up and storing address data during block processing.
|
||||
///
|
||||
/// All addresses should be pre-fetched into the cache before using this.
|
||||
/// - `loaded`: addresses with non-zero balance (wrapped with source info)
|
||||
/// - `empty`: addresses that became empty this block (wrapped with source info)
|
||||
pub struct AddressLookup<'a> {
|
||||
/// Loaded addresses touched in current block
|
||||
pub loaded: &'a mut AddressTypeToTypeIndexMap<LoadedAddressDataWithSource>,
|
||||
/// Empty addresses touched in current block
|
||||
pub empty: &'a mut AddressTypeToTypeIndexMap<EmptyAddressDataWithSource>,
|
||||
}
|
||||
|
||||
impl<'a> AddressLookup<'a> {
|
||||
/// Get or create address data for a receive operation.
|
||||
///
|
||||
/// Returns (address_data, is_new, from_empty)
|
||||
pub fn get_or_create_for_receive(
|
||||
&mut self,
|
||||
output_type: OutputType,
|
||||
type_index: TypeIndex,
|
||||
) -> (&mut LoadedAddressDataWithSource, bool, bool) {
|
||||
) -> (&mut LoadedAddressDataWithSource, AddressSource) {
|
||||
use std::collections::hash_map::Entry;
|
||||
|
||||
let map = self.loaded.get_mut(output_type).unwrap();
|
||||
|
||||
match map.entry(type_index) {
|
||||
Entry::Occupied(entry) => {
|
||||
// Entry already exists - check its source
|
||||
let data = entry.into_mut();
|
||||
let is_new = data.is_new();
|
||||
let from_empty = data.is_from_emptyaddressdata();
|
||||
(data, is_new, from_empty)
|
||||
let source = match entry.get() {
|
||||
WithAddressDataSource::New(_) => AddressSource::New,
|
||||
WithAddressDataSource::FromLoaded(..) => AddressSource::Loaded,
|
||||
WithAddressDataSource::FromEmpty(..) => AddressSource::FromEmpty,
|
||||
};
|
||||
(entry.into_mut(), source)
|
||||
}
|
||||
Entry::Vacant(entry) => {
|
||||
// Check if it was in empty set
|
||||
if let Some(empty_data) =
|
||||
self.empty.get_mut(output_type).unwrap().remove(&type_index)
|
||||
{
|
||||
let data = entry.insert(empty_data.into());
|
||||
return (data, false, true);
|
||||
return (entry.insert(empty_data.into()), AddressSource::FromEmpty);
|
||||
}
|
||||
|
||||
// Not found - create new address
|
||||
let data =
|
||||
entry.insert(WithAddressDataSource::New(LoadedAddressData::default()));
|
||||
(data, true, false)
|
||||
(
|
||||
entry.insert(WithAddressDataSource::New(LoadedAddressData::default())),
|
||||
AddressSource::New,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
//! Process received outputs for address cohorts.
|
||||
//!
|
||||
//! Updates address cohort states when addresses receive funds:
|
||||
//! - New addresses enter a cohort
|
||||
//! - Existing addresses may cross cohort boundaries
|
||||
//! - Empty addresses become non-empty again
|
||||
|
||||
use brk_grouper::{ByAddressType, Filtered};
|
||||
use brk_grouper::{AmountBucket, ByAddressType};
|
||||
use brk_types::{Dollars, Sats, TypeIndex};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use super::super::address::AddressTypeToVec;
|
||||
use super::super::cohorts::AddressCohorts;
|
||||
use super::lookup::AddressLookup;
|
||||
use super::lookup::{AddressLookup, AddressSource};
|
||||
|
||||
/// Process received outputs for address cohorts.
|
||||
///
|
||||
/// For each received output:
|
||||
/// 1. Look up or create address data
|
||||
/// 2. Update address balance and cohort membership
|
||||
/// 3. Update cohort states (add/subtract for boundary crossings, receive otherwise)
|
||||
pub fn process_received(
|
||||
received_data: AddressTypeToVec<(TypeIndex, Sats)>,
|
||||
cohorts: &mut AddressCohorts,
|
||||
@@ -31,30 +21,47 @@ pub fn process_received(
|
||||
continue;
|
||||
}
|
||||
|
||||
// Aggregate receives by address - each address processed exactly once
|
||||
// Track (total_value, output_count) for correct UTXO counting
|
||||
let mut aggregated: FxHashMap<TypeIndex, (Sats, u32)> = FxHashMap::default();
|
||||
for (type_index, value) in vec {
|
||||
let (addr_data, is_new, from_empty) =
|
||||
lookup.get_or_create_for_receive(output_type, type_index);
|
||||
let entry = aggregated.entry(type_index).or_default();
|
||||
entry.0 += value;
|
||||
entry.1 += 1;
|
||||
}
|
||||
|
||||
// Update address counts
|
||||
if is_new || from_empty {
|
||||
*addr_count.get_mut(output_type).unwrap() += 1;
|
||||
if from_empty {
|
||||
for (type_index, (total_value, output_count)) in aggregated {
|
||||
let (addr_data, source) = lookup.get_or_create_for_receive(output_type, type_index);
|
||||
|
||||
match source {
|
||||
AddressSource::New => {
|
||||
*addr_count.get_mut(output_type).unwrap() += 1;
|
||||
}
|
||||
AddressSource::FromEmpty => {
|
||||
*addr_count.get_mut(output_type).unwrap() += 1;
|
||||
*empty_addr_count.get_mut(output_type).unwrap() -= 1;
|
||||
}
|
||||
AddressSource::Loaded => {}
|
||||
}
|
||||
|
||||
let prev_balance = addr_data.balance();
|
||||
let new_balance = prev_balance + value;
|
||||
let is_new_entry = matches!(source, AddressSource::New | AddressSource::FromEmpty);
|
||||
|
||||
// Check if crossing cohort boundary
|
||||
let prev_cohort = cohorts.amount_range.get(prev_balance);
|
||||
let new_cohort = cohorts.amount_range.get(new_balance);
|
||||
let filters_differ = prev_cohort.filter() != new_cohort.filter();
|
||||
if is_new_entry {
|
||||
// New/from-empty address - just add to cohort
|
||||
addr_data.receive_outputs(total_value, price, output_count);
|
||||
cohorts
|
||||
.amount_range
|
||||
.get_mut(total_value) // new_balance = 0 + total_value
|
||||
.state
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.add(addr_data);
|
||||
} else {
|
||||
let prev_balance = addr_data.balance();
|
||||
let new_balance = prev_balance + total_value;
|
||||
|
||||
if is_new || from_empty || filters_differ {
|
||||
// Address entering or changing cohorts
|
||||
if !is_new && !from_empty {
|
||||
// Subtract from old cohort
|
||||
if AmountBucket::from(prev_balance) != AmountBucket::from(new_balance) {
|
||||
// Crossing cohort boundary - subtract from old, add to new
|
||||
cohorts
|
||||
.amount_range
|
||||
.get_mut(prev_balance)
|
||||
@@ -62,28 +69,24 @@ pub fn process_received(
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.subtract(addr_data);
|
||||
addr_data.receive_outputs(total_value, price, output_count);
|
||||
cohorts
|
||||
.amount_range
|
||||
.get_mut(new_balance)
|
||||
.state
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.add(addr_data);
|
||||
} else {
|
||||
// Staying in same cohort - just receive
|
||||
cohorts
|
||||
.amount_range
|
||||
.get_mut(new_balance)
|
||||
.state
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.receive_outputs(addr_data, total_value, price, output_count);
|
||||
}
|
||||
|
||||
// Update address data
|
||||
addr_data.receive(value, price);
|
||||
|
||||
// Add to new cohort
|
||||
cohorts
|
||||
.amount_range
|
||||
.get_mut(new_balance)
|
||||
.state
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.add(addr_data);
|
||||
} else {
|
||||
// Address staying in same cohort - update in place
|
||||
cohorts
|
||||
.amount_range
|
||||
.get_mut(new_balance)
|
||||
.state
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.receive(addr_data, value, price);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,16 +26,6 @@ pub enum WithAddressDataSource<T> {
|
||||
FromEmpty(EmptyAddressIndex, T),
|
||||
}
|
||||
|
||||
impl<T> WithAddressDataSource<T> {
|
||||
pub fn is_new(&self) -> bool {
|
||||
matches!(self, Self::New(_))
|
||||
}
|
||||
|
||||
pub fn is_from_emptyaddressdata(&self) -> bool {
|
||||
matches!(self, Self::FromEmpty(..))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::ops::Deref for WithAddressDataSource<T> {
|
||||
type Target = T;
|
||||
|
||||
|
||||
@@ -89,6 +89,16 @@ impl AddressCohortState {
|
||||
address_data: &mut LoadedAddressData,
|
||||
value: Sats,
|
||||
price: Option<Dollars>,
|
||||
) {
|
||||
self.receive_outputs(address_data, value, price, 1);
|
||||
}
|
||||
|
||||
pub fn receive_outputs(
|
||||
&mut self,
|
||||
address_data: &mut LoadedAddressData,
|
||||
value: Sats,
|
||||
price: Option<Dollars>,
|
||||
output_count: u32,
|
||||
) {
|
||||
let compute_price = price.is_some();
|
||||
|
||||
@@ -98,7 +108,7 @@ impl AddressCohortState {
|
||||
value: address_data.balance(),
|
||||
};
|
||||
|
||||
address_data.receive(value, price);
|
||||
address_data.receive_outputs(value, price, output_count);
|
||||
|
||||
let supply_state = SupplyState {
|
||||
utxo_count: address_data.utxo_count() as u64,
|
||||
@@ -107,7 +117,7 @@ impl AddressCohortState {
|
||||
|
||||
self.inner.receive_(
|
||||
&SupplyState {
|
||||
utxo_count: 1,
|
||||
utxo_count: output_count as u64,
|
||||
value,
|
||||
},
|
||||
price,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use std::ops::{Add, AddAssign, SubAssign};
|
||||
|
||||
use brk_types::{CheckedSub, LoadedAddressData, Sats};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{Bytes, Formattable};
|
||||
|
||||
#[derive(Debug, Default, Clone, Serialize)]
|
||||
#[derive(Debug, Default, Clone, Serialize, JsonSchema)]
|
||||
pub struct SupplyState {
|
||||
pub utxo_count: u64,
|
||||
pub value: Sats,
|
||||
|
||||
@@ -269,7 +269,11 @@ impl Vecs {
|
||||
.min(self.any_address_indexes.min_stamped_height())
|
||||
.min(self.addresses_data.min_stamped_height())
|
||||
.min(Height::from(self.height_to_unspendable_supply.len()))
|
||||
.min(Height::from(self.height_to_opreturn_supply.len()));
|
||||
.min(Height::from(self.height_to_opreturn_supply.len()))
|
||||
.min(Height::from(self.addresstype_to_height_to_addr_count.min_len()))
|
||||
.min(Height::from(
|
||||
self.addresstype_to_height_to_empty_addr_count.min_len(),
|
||||
));
|
||||
|
||||
// 2. Determine start mode and recover/reset state
|
||||
let start_mode = determine_start_mode(stateful_min, chain_state_height);
|
||||
|
||||
@@ -64,7 +64,10 @@ impl<T: PriceSource> TrackedSource<T> {
|
||||
}
|
||||
|
||||
/// Try to fetch, tracking health state
|
||||
fn try_fetch<R>(&mut self, fetch: impl FnOnce(&mut T) -> Option<Result<R>>) -> Option<Result<R>> {
|
||||
fn try_fetch<R>(
|
||||
&mut self,
|
||||
fetch: impl FnOnce(&mut T) -> Option<Result<R>>,
|
||||
) -> Option<Result<R>> {
|
||||
if !self.is_healthy() {
|
||||
return Some(Err(Error::FetchFailed(format!(
|
||||
"{} temporarily disabled (recheck in {}s)",
|
||||
|
||||
@@ -6,6 +6,33 @@ use rayon::prelude::*;
|
||||
|
||||
use super::{AmountFilter, Filter};
|
||||
|
||||
/// Bucket index for amount ranges. Use for cheap comparisons.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct AmountBucket(u8);
|
||||
|
||||
impl From<Sats> for AmountBucket {
|
||||
#[inline(always)]
|
||||
fn from(value: Sats) -> Self {
|
||||
Self(match value {
|
||||
v if v < Sats::_1 => 0,
|
||||
v if v < Sats::_10 => 1,
|
||||
v if v < Sats::_100 => 2,
|
||||
v if v < Sats::_1K => 3,
|
||||
v if v < Sats::_10K => 4,
|
||||
v if v < Sats::_100K => 5,
|
||||
v if v < Sats::_1M => 6,
|
||||
v if v < Sats::_10M => 7,
|
||||
v if v < Sats::_1BTC => 8,
|
||||
v if v < Sats::_10BTC => 9,
|
||||
v if v < Sats::_100BTC => 10,
|
||||
v if v < Sats::_1K_BTC => 11,
|
||||
v if v < Sats::_10K_BTC => 12,
|
||||
v if v < Sats::_100K_BTC => 13,
|
||||
_ => 14,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Traversable)]
|
||||
pub struct ByAmountRange<T> {
|
||||
pub _0sats: T,
|
||||
@@ -35,87 +62,79 @@ impl<T> ByAmountRange<T> {
|
||||
_1sat_to_10sats: create(Filter::Amount(AmountFilter::Range(Sats::_1..Sats::_10))),
|
||||
_10sats_to_100sats: create(Filter::Amount(AmountFilter::Range(Sats::_10..Sats::_100))),
|
||||
_100sats_to_1k_sats: create(Filter::Amount(AmountFilter::Range(Sats::_100..Sats::_1K))),
|
||||
_1k_sats_to_10k_sats: create(Filter::Amount(AmountFilter::Range(Sats::_1K..Sats::_10K))),
|
||||
_10k_sats_to_100k_sats: create(Filter::Amount(AmountFilter::Range(Sats::_10K..Sats::_100K))),
|
||||
_100k_sats_to_1m_sats: create(Filter::Amount(AmountFilter::Range(Sats::_100K..Sats::_1M))),
|
||||
_1m_sats_to_10m_sats: create(Filter::Amount(AmountFilter::Range(Sats::_1M..Sats::_10M))),
|
||||
_1k_sats_to_10k_sats: create(Filter::Amount(AmountFilter::Range(
|
||||
Sats::_1K..Sats::_10K,
|
||||
))),
|
||||
_10k_sats_to_100k_sats: create(Filter::Amount(AmountFilter::Range(
|
||||
Sats::_10K..Sats::_100K,
|
||||
))),
|
||||
_100k_sats_to_1m_sats: create(Filter::Amount(AmountFilter::Range(
|
||||
Sats::_100K..Sats::_1M,
|
||||
))),
|
||||
_1m_sats_to_10m_sats: create(Filter::Amount(AmountFilter::Range(
|
||||
Sats::_1M..Sats::_10M,
|
||||
))),
|
||||
_10m_sats_to_1btc: create(Filter::Amount(AmountFilter::Range(Sats::_10M..Sats::_1BTC))),
|
||||
_1btc_to_10btc: create(Filter::Amount(AmountFilter::Range(Sats::_1BTC..Sats::_10BTC))),
|
||||
_10btc_to_100btc: create(Filter::Amount(AmountFilter::Range(Sats::_10BTC..Sats::_100BTC))),
|
||||
_100btc_to_1k_btc: create(Filter::Amount(AmountFilter::Range(Sats::_100BTC..Sats::_1K_BTC))),
|
||||
_1k_btc_to_10k_btc: create(Filter::Amount(AmountFilter::Range(Sats::_1K_BTC..Sats::_10K_BTC))),
|
||||
_10k_btc_to_100k_btc: create(Filter::Amount(AmountFilter::Range(Sats::_10K_BTC..Sats::_100K_BTC))),
|
||||
_100k_btc_or_more: create(Filter::Amount(AmountFilter::Range(Sats::_100K_BTC..Sats::MAX))),
|
||||
_1btc_to_10btc: create(Filter::Amount(AmountFilter::Range(
|
||||
Sats::_1BTC..Sats::_10BTC,
|
||||
))),
|
||||
_10btc_to_100btc: create(Filter::Amount(AmountFilter::Range(
|
||||
Sats::_10BTC..Sats::_100BTC,
|
||||
))),
|
||||
_100btc_to_1k_btc: create(Filter::Amount(AmountFilter::Range(
|
||||
Sats::_100BTC..Sats::_1K_BTC,
|
||||
))),
|
||||
_1k_btc_to_10k_btc: create(Filter::Amount(AmountFilter::Range(
|
||||
Sats::_1K_BTC..Sats::_10K_BTC,
|
||||
))),
|
||||
_10k_btc_to_100k_btc: create(Filter::Amount(AmountFilter::Range(
|
||||
Sats::_10K_BTC..Sats::_100K_BTC,
|
||||
))),
|
||||
_100k_btc_or_more: create(Filter::Amount(AmountFilter::Range(
|
||||
Sats::_100K_BTC..Sats::MAX,
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::inconsistent_digit_grouping)]
|
||||
#[inline(always)]
|
||||
pub fn get(&self, value: Sats) -> &T {
|
||||
if value == Sats::ZERO {
|
||||
&self._0sats
|
||||
} else if value < Sats::_10 {
|
||||
&self._1sat_to_10sats
|
||||
} else if value < Sats::_100 {
|
||||
&self._10sats_to_100sats
|
||||
} else if value < Sats::_1K {
|
||||
&self._100sats_to_1k_sats
|
||||
} else if value < Sats::_10K {
|
||||
&self._1k_sats_to_10k_sats
|
||||
} else if value < Sats::_100K {
|
||||
&self._10k_sats_to_100k_sats
|
||||
} else if value < Sats::_1M {
|
||||
&self._100k_sats_to_1m_sats
|
||||
} else if value < Sats::_10M {
|
||||
&self._1m_sats_to_10m_sats
|
||||
} else if value < Sats::_1BTC {
|
||||
&self._10m_sats_to_1btc
|
||||
} else if value < Sats::_10BTC {
|
||||
&self._1btc_to_10btc
|
||||
} else if value < Sats::_100BTC {
|
||||
&self._10btc_to_100btc
|
||||
} else if value < Sats::_1K_BTC {
|
||||
&self._100btc_to_1k_btc
|
||||
} else if value < Sats::_10K_BTC {
|
||||
&self._1k_btc_to_10k_btc
|
||||
} else if value < Sats::_100K_BTC {
|
||||
&self._10k_btc_to_100k_btc
|
||||
} else {
|
||||
&self._100k_btc_or_more
|
||||
match AmountBucket::from(value).0 {
|
||||
0 => &self._0sats,
|
||||
1 => &self._1sat_to_10sats,
|
||||
2 => &self._10sats_to_100sats,
|
||||
3 => &self._100sats_to_1k_sats,
|
||||
4 => &self._1k_sats_to_10k_sats,
|
||||
5 => &self._10k_sats_to_100k_sats,
|
||||
6 => &self._100k_sats_to_1m_sats,
|
||||
7 => &self._1m_sats_to_10m_sats,
|
||||
8 => &self._10m_sats_to_1btc,
|
||||
9 => &self._1btc_to_10btc,
|
||||
10 => &self._10btc_to_100btc,
|
||||
11 => &self._100btc_to_1k_btc,
|
||||
12 => &self._1k_btc_to_10k_btc,
|
||||
13 => &self._10k_btc_to_100k_btc,
|
||||
_ => &self._100k_btc_or_more,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::inconsistent_digit_grouping)]
|
||||
#[inline(always)]
|
||||
pub fn get_mut(&mut self, value: Sats) -> &mut T {
|
||||
if value == Sats::ZERO {
|
||||
&mut self._0sats
|
||||
} else if value < Sats::_10 {
|
||||
&mut self._1sat_to_10sats
|
||||
} else if value < Sats::_100 {
|
||||
&mut self._10sats_to_100sats
|
||||
} else if value < Sats::_1K {
|
||||
&mut self._100sats_to_1k_sats
|
||||
} else if value < Sats::_10K {
|
||||
&mut self._1k_sats_to_10k_sats
|
||||
} else if value < Sats::_100K {
|
||||
&mut self._10k_sats_to_100k_sats
|
||||
} else if value < Sats::_1M {
|
||||
&mut self._100k_sats_to_1m_sats
|
||||
} else if value < Sats::_10M {
|
||||
&mut self._1m_sats_to_10m_sats
|
||||
} else if value < Sats::_1BTC {
|
||||
&mut self._10m_sats_to_1btc
|
||||
} else if value < Sats::_10BTC {
|
||||
&mut self._1btc_to_10btc
|
||||
} else if value < Sats::_100BTC {
|
||||
&mut self._10btc_to_100btc
|
||||
} else if value < Sats::_1K_BTC {
|
||||
&mut self._100btc_to_1k_btc
|
||||
} else if value < Sats::_10K_BTC {
|
||||
&mut self._1k_btc_to_10k_btc
|
||||
} else if value < Sats::_100K_BTC {
|
||||
&mut self._10k_btc_to_100k_btc
|
||||
} else {
|
||||
&mut self._100k_btc_or_more
|
||||
match AmountBucket::from(value).0 {
|
||||
0 => &mut self._0sats,
|
||||
1 => &mut self._1sat_to_10sats,
|
||||
2 => &mut self._10sats_to_100sats,
|
||||
3 => &mut self._100sats_to_1k_sats,
|
||||
4 => &mut self._1k_sats_to_10k_sats,
|
||||
5 => &mut self._10k_sats_to_100k_sats,
|
||||
6 => &mut self._100k_sats_to_1m_sats,
|
||||
7 => &mut self._1m_sats_to_10m_sats,
|
||||
8 => &mut self._10m_sats_to_1btc,
|
||||
9 => &mut self._1btc_to_10btc,
|
||||
10 => &mut self._10btc_to_100btc,
|
||||
11 => &mut self._100btc_to_1k_btc,
|
||||
12 => &mut self._1k_btc_to_10k_btc,
|
||||
13 => &mut self._10k_btc_to_100k_btc,
|
||||
_ => &mut self._100k_btc_or_more,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,53 +66,53 @@ impl Indexes {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_if_needed(&self, vecs: &mut Vecs) -> Result<()> {
|
||||
pub fn checked_push(&self, vecs: &mut Vecs) -> Result<()> {
|
||||
let height = self.height;
|
||||
vecs.tx
|
||||
.height_to_first_txindex
|
||||
.push_if_needed(height, self.txindex)?;
|
||||
.checked_push(height, self.txindex)?;
|
||||
vecs.txin
|
||||
.height_to_first_txinindex
|
||||
.push_if_needed(height, self.txinindex)?;
|
||||
.checked_push(height, self.txinindex)?;
|
||||
vecs.txout
|
||||
.height_to_first_txoutindex
|
||||
.push_if_needed(height, self.txoutindex)?;
|
||||
.checked_push(height, self.txoutindex)?;
|
||||
vecs.output
|
||||
.height_to_first_emptyoutputindex
|
||||
.push_if_needed(height, self.emptyoutputindex)?;
|
||||
.checked_push(height, self.emptyoutputindex)?;
|
||||
vecs.output
|
||||
.height_to_first_p2msoutputindex
|
||||
.push_if_needed(height, self.p2msoutputindex)?;
|
||||
.checked_push(height, self.p2msoutputindex)?;
|
||||
vecs.output
|
||||
.height_to_first_opreturnindex
|
||||
.push_if_needed(height, self.opreturnindex)?;
|
||||
.checked_push(height, self.opreturnindex)?;
|
||||
vecs.address
|
||||
.height_to_first_p2aaddressindex
|
||||
.push_if_needed(height, self.p2aaddressindex)?;
|
||||
.checked_push(height, self.p2aaddressindex)?;
|
||||
vecs.output
|
||||
.height_to_first_unknownoutputindex
|
||||
.push_if_needed(height, self.unknownoutputindex)?;
|
||||
.checked_push(height, self.unknownoutputindex)?;
|
||||
vecs.address
|
||||
.height_to_first_p2pk33addressindex
|
||||
.push_if_needed(height, self.p2pk33addressindex)?;
|
||||
.checked_push(height, self.p2pk33addressindex)?;
|
||||
vecs.address
|
||||
.height_to_first_p2pk65addressindex
|
||||
.push_if_needed(height, self.p2pk65addressindex)?;
|
||||
.checked_push(height, self.p2pk65addressindex)?;
|
||||
vecs.address
|
||||
.height_to_first_p2pkhaddressindex
|
||||
.push_if_needed(height, self.p2pkhaddressindex)?;
|
||||
.checked_push(height, self.p2pkhaddressindex)?;
|
||||
vecs.address
|
||||
.height_to_first_p2shaddressindex
|
||||
.push_if_needed(height, self.p2shaddressindex)?;
|
||||
.checked_push(height, self.p2shaddressindex)?;
|
||||
vecs.address
|
||||
.height_to_first_p2traddressindex
|
||||
.push_if_needed(height, self.p2traddressindex)?;
|
||||
.checked_push(height, self.p2traddressindex)?;
|
||||
vecs.address
|
||||
.height_to_first_p2wpkhaddressindex
|
||||
.push_if_needed(height, self.p2wpkhaddressindex)?;
|
||||
.checked_push(height, self.p2wpkhaddressindex)?;
|
||||
vecs.address
|
||||
.height_to_first_p2wshaddressindex
|
||||
.push_if_needed(height, self.p2wshaddressindex)?;
|
||||
.checked_push(height, self.p2wshaddressindex)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ impl<'a> BlockProcessor<'a> {
|
||||
return Err(Error::Internal("BlockHash prefix collision"));
|
||||
}
|
||||
|
||||
self.indexes.push_if_needed(self.vecs)?;
|
||||
self.indexes.checked_push(self.vecs)?;
|
||||
|
||||
self.stores
|
||||
.blockhashprefix_to_height
|
||||
@@ -94,23 +94,23 @@ impl<'a> BlockProcessor<'a> {
|
||||
self.vecs
|
||||
.block
|
||||
.height_to_blockhash
|
||||
.push_if_needed(height, blockhash.clone())?;
|
||||
.checked_push(height, blockhash.clone())?;
|
||||
self.vecs
|
||||
.block
|
||||
.height_to_difficulty
|
||||
.push_if_needed(height, self.block.header.difficulty_float().into())?;
|
||||
.checked_push(height, self.block.header.difficulty_float().into())?;
|
||||
self.vecs
|
||||
.block
|
||||
.height_to_timestamp
|
||||
.push_if_needed(height, Timestamp::from(self.block.header.time))?;
|
||||
.checked_push(height, Timestamp::from(self.block.header.time))?;
|
||||
self.vecs
|
||||
.block
|
||||
.height_to_total_size
|
||||
.push_if_needed(height, self.block.total_size().into())?;
|
||||
.checked_push(height, self.block.total_size().into())?;
|
||||
self.vecs
|
||||
.block
|
||||
.height_to_weight
|
||||
.push_if_needed(height, self.block.weight().into())?;
|
||||
.checked_push(height, self.block.weight().into())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -431,21 +431,21 @@ impl<'a> BlockProcessor<'a> {
|
||||
self.vecs
|
||||
.tx
|
||||
.txindex_to_first_txoutindex
|
||||
.push_if_needed(txindex, txoutindex)?;
|
||||
.checked_push(txindex, txoutindex)?;
|
||||
}
|
||||
|
||||
self.vecs
|
||||
.txout
|
||||
.txoutindex_to_value
|
||||
.push_if_needed(txoutindex, sats)?;
|
||||
.checked_push(txoutindex, sats)?;
|
||||
self.vecs
|
||||
.txout
|
||||
.txoutindex_to_txindex
|
||||
.push_if_needed(txoutindex, txindex)?;
|
||||
.checked_push(txoutindex, txindex)?;
|
||||
self.vecs
|
||||
.txout
|
||||
.txoutindex_to_outputtype
|
||||
.push_if_needed(txoutindex, outputtype)?;
|
||||
.checked_push(txoutindex, outputtype)?;
|
||||
|
||||
let typeindex = if let Some(ti) = existing_typeindex {
|
||||
ti
|
||||
@@ -476,28 +476,28 @@ impl<'a> BlockProcessor<'a> {
|
||||
self.vecs
|
||||
.output
|
||||
.p2msoutputindex_to_txindex
|
||||
.push_if_needed(self.indexes.p2msoutputindex, txindex)?;
|
||||
.checked_push(self.indexes.p2msoutputindex, txindex)?;
|
||||
self.indexes.p2msoutputindex.copy_then_increment()
|
||||
}
|
||||
OutputType::OpReturn => {
|
||||
self.vecs
|
||||
.output
|
||||
.opreturnindex_to_txindex
|
||||
.push_if_needed(self.indexes.opreturnindex, txindex)?;
|
||||
.checked_push(self.indexes.opreturnindex, txindex)?;
|
||||
self.indexes.opreturnindex.copy_then_increment()
|
||||
}
|
||||
OutputType::Empty => {
|
||||
self.vecs
|
||||
.output
|
||||
.emptyoutputindex_to_txindex
|
||||
.push_if_needed(self.indexes.emptyoutputindex, txindex)?;
|
||||
.checked_push(self.indexes.emptyoutputindex, txindex)?;
|
||||
self.indexes.emptyoutputindex.copy_then_increment()
|
||||
}
|
||||
OutputType::Unknown => {
|
||||
self.vecs
|
||||
.output
|
||||
.unknownoutputindex_to_txindex
|
||||
.push_if_needed(self.indexes.unknownoutputindex, txindex)?;
|
||||
.checked_push(self.indexes.unknownoutputindex, txindex)?;
|
||||
self.indexes.unknownoutputindex.copy_then_increment()
|
||||
}
|
||||
_ => unreachable!(),
|
||||
@@ -507,7 +507,7 @@ impl<'a> BlockProcessor<'a> {
|
||||
self.vecs
|
||||
.txout
|
||||
.txoutindex_to_typeindex
|
||||
.push_if_needed(txoutindex, typeindex)?;
|
||||
.checked_push(txoutindex, typeindex)?;
|
||||
|
||||
if outputtype.is_unspendable() {
|
||||
continue;
|
||||
@@ -592,17 +592,17 @@ impl<'a> BlockProcessor<'a> {
|
||||
self.vecs
|
||||
.tx
|
||||
.txindex_to_first_txinindex
|
||||
.push_if_needed(txindex, txinindex)?;
|
||||
.checked_push(txindex, txinindex)?;
|
||||
}
|
||||
|
||||
self.vecs
|
||||
.txin
|
||||
.txinindex_to_txindex
|
||||
.push_if_needed(txinindex, txindex)?;
|
||||
.checked_push(txinindex, txindex)?;
|
||||
self.vecs
|
||||
.txin
|
||||
.txinindex_to_outpoint
|
||||
.push_if_needed(txinindex, outpoint)?;
|
||||
.checked_push(txinindex, outpoint)?;
|
||||
|
||||
let Some((addresstype, addressindex)) = address_info else {
|
||||
continue;
|
||||
@@ -678,31 +678,31 @@ impl<'a> BlockProcessor<'a> {
|
||||
self.vecs
|
||||
.tx
|
||||
.txindex_to_height
|
||||
.push_if_needed(ct.txindex, height)?;
|
||||
.checked_push(ct.txindex, height)?;
|
||||
self.vecs
|
||||
.tx
|
||||
.txindex_to_txversion
|
||||
.push_if_needed(ct.txindex, ct.tx.version.into())?;
|
||||
.checked_push(ct.txindex, ct.tx.version.into())?;
|
||||
self.vecs
|
||||
.tx
|
||||
.txindex_to_txid
|
||||
.push_if_needed(ct.txindex, ct.txid)?;
|
||||
.checked_push(ct.txindex, ct.txid)?;
|
||||
self.vecs
|
||||
.tx
|
||||
.txindex_to_rawlocktime
|
||||
.push_if_needed(ct.txindex, ct.tx.lock_time.into())?;
|
||||
.checked_push(ct.txindex, ct.tx.lock_time.into())?;
|
||||
self.vecs
|
||||
.tx
|
||||
.txindex_to_base_size
|
||||
.push_if_needed(ct.txindex, ct.tx.base_size().into())?;
|
||||
.checked_push(ct.txindex, ct.tx.base_size().into())?;
|
||||
self.vecs
|
||||
.tx
|
||||
.txindex_to_total_size
|
||||
.push_if_needed(ct.txindex, ct.tx.total_size().into())?;
|
||||
.checked_push(ct.txindex, ct.tx.total_size().into())?;
|
||||
self.vecs
|
||||
.tx
|
||||
.txindex_to_is_explicitly_rbf
|
||||
.push_if_needed(ct.txindex, StoredBool::from(ct.tx.is_explicitly_rbf()))?;
|
||||
.checked_push(ct.txindex, StoredBool::from(ct.tx.is_explicitly_rbf()))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -12,7 +12,7 @@ use log::info;
|
||||
use rayon::prelude::*;
|
||||
use vecdb::{AnyVec, TypedVecIterator, VecIndex, VecIterator};
|
||||
|
||||
use crate::{Indexes, constants::DUPLICATE_TXID_PREFIXES};
|
||||
use crate::{constants::DUPLICATE_TXID_PREFIXES, Indexes};
|
||||
|
||||
use super::Vecs;
|
||||
|
||||
@@ -267,7 +267,8 @@ impl Stores {
|
||||
|
||||
if starting_indexes.txoutindex != TxOutIndex::ZERO {
|
||||
let mut txoutindex_to_txindex_iter = vecs.txout.txoutindex_to_txindex.iter()?;
|
||||
let mut txindex_to_first_txoutindex_iter = vecs.tx.txindex_to_first_txoutindex.iter()?;
|
||||
let mut txindex_to_first_txoutindex_iter =
|
||||
vecs.tx.txindex_to_first_txoutindex.iter()?;
|
||||
vecs.txout
|
||||
.txoutindex_to_outputtype
|
||||
.iter()?
|
||||
@@ -303,23 +304,27 @@ impl Stores {
|
||||
});
|
||||
|
||||
// Add back outputs that were spent after the rollback point
|
||||
let mut txindex_to_first_txoutindex_iter = vecs.tx.txindex_to_first_txoutindex.iter()?;
|
||||
let mut txindex_to_first_txoutindex_iter =
|
||||
vecs.tx.txindex_to_first_txoutindex.iter()?;
|
||||
let mut txoutindex_to_outputtype_iter = vecs.txout.txoutindex_to_outputtype.iter()?;
|
||||
let mut txoutindex_to_typeindex_iter = vecs.txout.txoutindex_to_typeindex.iter()?;
|
||||
let mut txinindex_to_txindex_iter = vecs.txin.txinindex_to_txindex.iter()?;
|
||||
vecs.txin
|
||||
.txinindex_to_outpoint
|
||||
.iter()?
|
||||
.enumerate()
|
||||
.skip(starting_indexes.txinindex.to_usize())
|
||||
.for_each(|outpoint: OutPoint| {
|
||||
.for_each(|(txinindex, outpoint): (usize, OutPoint)| {
|
||||
if outpoint.is_coinbase() {
|
||||
return;
|
||||
}
|
||||
|
||||
let txindex = outpoint.txindex();
|
||||
let output_txindex = outpoint.txindex();
|
||||
let vout = outpoint.vout();
|
||||
|
||||
// Calculate txoutindex from txindex and vout
|
||||
let txoutindex = txindex_to_first_txoutindex_iter.get_unwrap(txindex) + vout;
|
||||
// Calculate txoutindex from output's txindex and vout
|
||||
let txoutindex =
|
||||
txindex_to_first_txoutindex_iter.get_unwrap(output_txindex) + vout;
|
||||
|
||||
// Only process if this output was created before the rollback point
|
||||
if txoutindex < starting_indexes.txoutindex {
|
||||
@@ -329,9 +334,16 @@ impl Stores {
|
||||
let addresstype = outputtype;
|
||||
let addressindex = txoutindex_to_typeindex_iter.get_unwrap(txoutindex);
|
||||
|
||||
// Get the SPENDING tx's index (not the output's tx)
|
||||
let spending_txindex =
|
||||
txinindex_to_txindex_iter.get_at_unwrap(txinindex);
|
||||
|
||||
self.addresstype_to_addressindex_and_txindex
|
||||
.get_mut_unwrap(addresstype)
|
||||
.remove(AddressIndexTxIndex::from((addressindex, txindex)));
|
||||
.remove(AddressIndexTxIndex::from((
|
||||
addressindex,
|
||||
spending_txindex,
|
||||
)));
|
||||
|
||||
self.addresstype_to_addressindex_and_unspentoutpoint
|
||||
.get_mut_unwrap(addresstype)
|
||||
|
||||
@@ -208,28 +208,28 @@ impl AddressVecs {
|
||||
match bytes {
|
||||
AddressBytes::P2PK65(bytes) => self
|
||||
.p2pk65addressindex_to_p2pk65bytes
|
||||
.push_if_needed(index.into(), *bytes)?,
|
||||
.checked_push(index.into(), *bytes)?,
|
||||
AddressBytes::P2PK33(bytes) => self
|
||||
.p2pk33addressindex_to_p2pk33bytes
|
||||
.push_if_needed(index.into(), *bytes)?,
|
||||
.checked_push(index.into(), *bytes)?,
|
||||
AddressBytes::P2PKH(bytes) => self
|
||||
.p2pkhaddressindex_to_p2pkhbytes
|
||||
.push_if_needed(index.into(), *bytes)?,
|
||||
.checked_push(index.into(), *bytes)?,
|
||||
AddressBytes::P2SH(bytes) => self
|
||||
.p2shaddressindex_to_p2shbytes
|
||||
.push_if_needed(index.into(), *bytes)?,
|
||||
.checked_push(index.into(), *bytes)?,
|
||||
AddressBytes::P2WPKH(bytes) => self
|
||||
.p2wpkhaddressindex_to_p2wpkhbytes
|
||||
.push_if_needed(index.into(), *bytes)?,
|
||||
.checked_push(index.into(), *bytes)?,
|
||||
AddressBytes::P2WSH(bytes) => self
|
||||
.p2wshaddressindex_to_p2wshbytes
|
||||
.push_if_needed(index.into(), *bytes)?,
|
||||
.checked_push(index.into(), *bytes)?,
|
||||
AddressBytes::P2TR(bytes) => self
|
||||
.p2traddressindex_to_p2trbytes
|
||||
.push_if_needed(index.into(), *bytes)?,
|
||||
.checked_push(index.into(), *bytes)?,
|
||||
AddressBytes::P2A(bytes) => self
|
||||
.p2aaddressindex_to_p2abytes
|
||||
.push_if_needed(index.into(), *bytes)?,
|
||||
.checked_push(index.into(), *bytes)?,
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ pub use brk_types::{
|
||||
pub use r#impl::BLOCK_TXS_PAGE_SIZE;
|
||||
pub use output::{LegacyValue, Output, OutputLegacy};
|
||||
|
||||
use vecs::Vecs;
|
||||
pub use vecs::Vecs;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Query(Arc<QueryInner<'static>>);
|
||||
|
||||
@@ -18,5 +18,7 @@ zstd = ["vecdb/zstd"]
|
||||
[dependencies]
|
||||
brk_types = { workspace = true }
|
||||
brk_traversable_derive = { workspace = true, optional = true }
|
||||
schemars = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
vecdb = { workspace = true }
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use std::{collections::BTreeMap, fmt::Debug};
|
||||
|
||||
pub use brk_types::TreeNode;
|
||||
pub use brk_types::{Index, MetricLeaf, MetricLeafWithSchema, TreeNode};
|
||||
|
||||
#[cfg(feature = "derive")]
|
||||
pub use brk_traversable_derive::Traversable;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{
|
||||
AnyExportableVec, AnyVec, BytesVec, BytesVecValue, EagerVec, Formattable, LazyVecFrom1,
|
||||
@@ -15,18 +16,36 @@ pub trait Traversable {
|
||||
fn iter_any_exportable(&self) -> impl Iterator<Item = &dyn AnyExportableVec>;
|
||||
}
|
||||
|
||||
/// Helper to create a MetricLeafWithSchema from a vec
|
||||
fn make_leaf<I: VecIndex, T: JsonSchema, V: AnyVec>(vec: &V) -> TreeNode {
|
||||
let index_str = I::to_string();
|
||||
let index = Index::try_from(index_str).ok();
|
||||
let indexes = index.into_iter().collect();
|
||||
|
||||
let leaf = MetricLeaf::new(
|
||||
vec.name().to_string(),
|
||||
vec.value_type_to_string().to_string(),
|
||||
indexes,
|
||||
);
|
||||
|
||||
let schema = schemars::SchemaGenerator::default().into_root_schema_for::<T>();
|
||||
let schema_json = serde_json::to_value(schema).unwrap_or_default();
|
||||
|
||||
TreeNode::Leaf(MetricLeafWithSchema::new(leaf, schema_json))
|
||||
}
|
||||
|
||||
// BytesVec implementation
|
||||
impl<I, T> Traversable for BytesVec<I, T>
|
||||
where
|
||||
I: VecIndex,
|
||||
T: BytesVecValue + Formattable + Serialize,
|
||||
T: BytesVecValue + Formattable + Serialize + JsonSchema,
|
||||
{
|
||||
fn iter_any_exportable(&self) -> impl Iterator<Item = &dyn AnyExportableVec> {
|
||||
std::iter::once(self as &dyn AnyExportableVec)
|
||||
}
|
||||
|
||||
fn to_tree_node(&self) -> TreeNode {
|
||||
TreeNode::Leaf(self.name().to_string())
|
||||
make_leaf::<I, T, _>(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,14 +54,14 @@ where
|
||||
impl<I, T> Traversable for vecdb::ZeroCopyVec<I, T>
|
||||
where
|
||||
I: VecIndex,
|
||||
T: vecdb::ZeroCopyVecValue + Formattable + Serialize,
|
||||
T: vecdb::ZeroCopyVecValue + Formattable + Serialize + JsonSchema,
|
||||
{
|
||||
fn iter_any_exportable(&self) -> impl Iterator<Item = &dyn AnyExportableVec> {
|
||||
std::iter::once(self as &dyn AnyExportableVec)
|
||||
}
|
||||
|
||||
fn to_tree_node(&self) -> TreeNode {
|
||||
TreeNode::Leaf(self.name().to_string())
|
||||
make_leaf::<I, T, _>(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,14 +70,14 @@ where
|
||||
impl<I, T> Traversable for vecdb::PcoVec<I, T>
|
||||
where
|
||||
I: VecIndex,
|
||||
T: vecdb::PcoVecValue + Formattable + Serialize,
|
||||
T: vecdb::PcoVecValue + Formattable + Serialize + JsonSchema,
|
||||
{
|
||||
fn iter_any_exportable(&self) -> impl Iterator<Item = &dyn AnyExportableVec> {
|
||||
std::iter::once(self as &dyn AnyExportableVec)
|
||||
}
|
||||
|
||||
fn to_tree_node(&self) -> TreeNode {
|
||||
TreeNode::Leaf(self.name().to_string())
|
||||
make_leaf::<I, T, _>(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,14 +86,14 @@ where
|
||||
impl<I, T> Traversable for vecdb::LZ4Vec<I, T>
|
||||
where
|
||||
I: VecIndex,
|
||||
T: vecdb::LZ4VecValue + Formattable + Serialize,
|
||||
T: vecdb::LZ4VecValue + Formattable + Serialize + JsonSchema,
|
||||
{
|
||||
fn iter_any_exportable(&self) -> impl Iterator<Item = &dyn AnyExportableVec> {
|
||||
std::iter::once(self as &dyn AnyExportableVec)
|
||||
}
|
||||
|
||||
fn to_tree_node(&self) -> TreeNode {
|
||||
TreeNode::Leaf(self.name().to_string())
|
||||
make_leaf::<I, T, _>(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,14 +102,14 @@ where
|
||||
impl<I, T> Traversable for vecdb::ZstdVec<I, T>
|
||||
where
|
||||
I: VecIndex,
|
||||
T: vecdb::ZstdVecValue + Formattable + Serialize,
|
||||
T: vecdb::ZstdVecValue + Formattable + Serialize + JsonSchema,
|
||||
{
|
||||
fn iter_any_exportable(&self) -> impl Iterator<Item = &dyn AnyExportableVec> {
|
||||
std::iter::once(self as &dyn AnyExportableVec)
|
||||
}
|
||||
|
||||
fn to_tree_node(&self) -> TreeNode {
|
||||
TreeNode::Leaf(self.name().to_string())
|
||||
make_leaf::<I, T, _>(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,21 +117,21 @@ where
|
||||
impl<V> Traversable for EagerVec<V>
|
||||
where
|
||||
V: StoredVec,
|
||||
V::T: Formattable + Serialize,
|
||||
V::T: Formattable + Serialize + JsonSchema,
|
||||
{
|
||||
fn iter_any_exportable(&self) -> impl Iterator<Item = &dyn AnyExportableVec> {
|
||||
std::iter::once(self as &dyn AnyExportableVec)
|
||||
}
|
||||
|
||||
fn to_tree_node(&self) -> TreeNode {
|
||||
TreeNode::Leaf(self.name().to_string())
|
||||
make_leaf::<V::I, V::T, _>(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, T, S1I, S1T> Traversable for LazyVecFrom1<I, T, S1I, S1T>
|
||||
where
|
||||
I: VecIndex,
|
||||
T: VecValue + Formattable + Serialize,
|
||||
T: VecValue + Formattable + Serialize + JsonSchema,
|
||||
S1I: VecIndex,
|
||||
S1T: VecValue,
|
||||
{
|
||||
@@ -121,14 +140,14 @@ where
|
||||
}
|
||||
|
||||
fn to_tree_node(&self) -> TreeNode {
|
||||
TreeNode::Leaf(self.name().to_string())
|
||||
make_leaf::<I, T, _>(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, T, S1I, S1T, S2I, S2T> Traversable for LazyVecFrom2<I, T, S1I, S1T, S2I, S2T>
|
||||
where
|
||||
I: VecIndex,
|
||||
T: VecValue + Formattable + Serialize,
|
||||
T: VecValue + Formattable + Serialize + JsonSchema,
|
||||
S1I: VecIndex,
|
||||
S1T: VecValue,
|
||||
S2I: VecIndex,
|
||||
@@ -139,7 +158,7 @@ where
|
||||
}
|
||||
|
||||
fn to_tree_node(&self) -> TreeNode {
|
||||
TreeNode::Leaf(self.name().to_string())
|
||||
make_leaf::<I, T, _>(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +166,7 @@ impl<I, T, S1I, S1T, S2I, S2T, S3I, S3T> Traversable
|
||||
for LazyVecFrom3<I, T, S1I, S1T, S2I, S2T, S3I, S3T>
|
||||
where
|
||||
I: VecIndex,
|
||||
T: VecValue + Formattable + Serialize,
|
||||
T: VecValue + Formattable + Serialize + JsonSchema,
|
||||
S1I: VecIndex,
|
||||
S1T: VecValue,
|
||||
S2I: VecIndex,
|
||||
@@ -160,7 +179,7 @@ where
|
||||
}
|
||||
|
||||
fn to_tree_node(&self) -> TreeNode {
|
||||
TreeNode::Leaf(self.name().to_string())
|
||||
make_leaf::<I, T, _>(self)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{Bytes, Formattable};
|
||||
|
||||
@@ -5,7 +6,7 @@ use crate::{EmptyAddressIndex, LoadedAddressIndex, TypeIndex};
|
||||
|
||||
const MIN_EMPTY_INDEX: u32 = u32::MAX - 4_000_000_000;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Bytes)]
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Bytes, JsonSchema)]
|
||||
pub struct AnyAddressIndex(TypeIndex);
|
||||
|
||||
impl AnyAddressIndex {
|
||||
|
||||
@@ -3,12 +3,13 @@ use std::{
|
||||
ops::{Add, AddAssign, Div, Mul},
|
||||
};
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{CheckedSub, Formattable, Pco};
|
||||
|
||||
use super::{Sats, StoredF64};
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, Serialize, Pco)]
|
||||
#[derive(Debug, Default, Clone, Copy, Serialize, Pco, JsonSchema)]
|
||||
pub struct Bitcoin(f64);
|
||||
|
||||
impl Add for Bitcoin {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use std::ops::Add;
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{Formattable, Pco};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Pco)]
|
||||
#[derive(Debug, Clone, Copy, Serialize, Pco, JsonSchema)]
|
||||
pub struct BlkPosition(u64);
|
||||
|
||||
impl BlkPosition {
|
||||
|
||||
@@ -1,8 +1,22 @@
|
||||
use derive_deref::{Deref, DerefMut};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::Bytes;
|
||||
|
||||
#[derive(Debug, Clone, Deref, DerefMut, PartialEq, Eq, PartialOrd, Ord, Serialize, Bytes, Hash)]
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
Deref,
|
||||
DerefMut,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Serialize,
|
||||
Bytes,
|
||||
Hash,
|
||||
JsonSchema,
|
||||
)]
|
||||
pub struct U8x2([u8; 2]);
|
||||
impl From<&[u8]> for U8x2 {
|
||||
#[inline]
|
||||
@@ -13,7 +27,20 @@ impl From<&[u8]> for U8x2 {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deref, DerefMut, PartialEq, Eq, PartialOrd, Ord, Serialize, Bytes, Hash)]
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
Deref,
|
||||
DerefMut,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Serialize,
|
||||
Bytes,
|
||||
Hash,
|
||||
JsonSchema,
|
||||
)]
|
||||
pub struct U8x20([u8; 20]);
|
||||
impl From<&[u8]> for U8x20 {
|
||||
#[inline]
|
||||
@@ -24,7 +51,20 @@ impl From<&[u8]> for U8x20 {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deref, DerefMut, PartialEq, Eq, PartialOrd, Ord, Serialize, Bytes, Hash)]
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
Deref,
|
||||
DerefMut,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Serialize,
|
||||
Bytes,
|
||||
Hash,
|
||||
JsonSchema,
|
||||
)]
|
||||
pub struct U8x32([u8; 32]);
|
||||
impl From<&[u8]> for U8x32 {
|
||||
#[inline]
|
||||
@@ -35,8 +75,20 @@ impl From<&[u8]> for U8x32 {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deref, DerefMut, PartialEq, Eq, PartialOrd, Ord, Serialize, Bytes, Hash)]
|
||||
#[derive(Debug, Clone, Deref, DerefMut, PartialEq, Eq, PartialOrd, Ord, Bytes, Hash, Serialize)]
|
||||
pub struct U8x33(#[serde(with = "serde_bytes")] [u8; 33]);
|
||||
|
||||
impl JsonSchema for U8x33 {
|
||||
fn schema_name() -> std::borrow::Cow<'static, str> {
|
||||
"U8x33".into()
|
||||
}
|
||||
|
||||
fn json_schema(_gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
|
||||
// Represent as a byte string
|
||||
String::json_schema(_gen)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[u8]> for U8x33 {
|
||||
#[inline]
|
||||
fn from(slice: &[u8]) -> Self {
|
||||
@@ -46,8 +98,20 @@ impl From<&[u8]> for U8x33 {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deref, DerefMut, PartialEq, Eq, PartialOrd, Ord, Serialize, Bytes, Hash)]
|
||||
#[derive(Debug, Clone, Deref, DerefMut, PartialEq, Eq, PartialOrd, Ord, Bytes, Hash, Serialize)]
|
||||
pub struct U8x65(#[serde(with = "serde_bytes")] [u8; 65]);
|
||||
|
||||
impl JsonSchema for U8x65 {
|
||||
fn schema_name() -> std::borrow::Cow<'static, str> {
|
||||
"U8x65".into()
|
||||
}
|
||||
|
||||
fn json_schema(_gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
|
||||
// Represent as a byte string
|
||||
String::json_schema(_gen)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[u8]> for U8x65 {
|
||||
#[inline]
|
||||
fn from(slice: &[u8]) -> Self {
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
use std::ops::{Add, Div, Mul};
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{CheckedSub, Formattable, Pco};
|
||||
|
||||
use super::Dollars;
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Pco)]
|
||||
#[derive(
|
||||
Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Pco, JsonSchema,
|
||||
)]
|
||||
pub struct Cents(i64);
|
||||
|
||||
impl Cents {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use jiff::{Span, Zoned, civil::Date as Date_, tz::TimeZone};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Serialize, Serializer};
|
||||
use vecdb::{Formattable, Pco};
|
||||
|
||||
@@ -6,7 +7,7 @@ use crate::ONE_DAY_IN_SEC_F64;
|
||||
|
||||
use super::{DateIndex, Timestamp};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Pco)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Pco, JsonSchema)]
|
||||
pub struct Date(u32);
|
||||
|
||||
impl Date {
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::ops::{Add, Rem};
|
||||
|
||||
use brk_error::Error;
|
||||
use jiff::Span;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{CheckedSub, Formattable, FromCoarserIndex, Pco, PrintableIndex};
|
||||
|
||||
@@ -9,7 +10,9 @@ use crate::{DecadeIndex, MonthIndex, QuarterIndex, SemesterIndex, WeekIndex, Yea
|
||||
|
||||
use super::Date;
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Pco)]
|
||||
#[derive(
|
||||
Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Pco, JsonSchema,
|
||||
)]
|
||||
pub struct DateIndex(u16);
|
||||
|
||||
impl DateIndex {
|
||||
|
||||
@@ -3,13 +3,25 @@ use std::{
|
||||
ops::{Add, AddAssign, Div},
|
||||
};
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
|
||||
|
||||
use super::{Date, DateIndex, YearIndex};
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Serialize, Deserialize, Pco,
|
||||
Debug,
|
||||
Clone,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Default,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Pco,
|
||||
JsonSchema,
|
||||
)]
|
||||
pub struct DecadeIndex(u16);
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::{
|
||||
ops::{Add, AddAssign, Div},
|
||||
};
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
|
||||
|
||||
@@ -11,7 +12,18 @@ use super::Height;
|
||||
pub const BLOCKS_PER_DIFF_EPOCHS: u32 = 2016;
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Serialize, Deserialize, Pco,
|
||||
Debug,
|
||||
Clone,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Default,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Pco,
|
||||
JsonSchema,
|
||||
)]
|
||||
pub struct DifficultyEpoch(u16);
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{Bytes, Formattable};
|
||||
|
||||
use crate::{LoadedAddressData, Sats};
|
||||
|
||||
/// Data of an empty address
|
||||
#[derive(Debug, Default, Clone, Serialize)]
|
||||
#[derive(Debug, Default, Clone, Serialize, JsonSchema)]
|
||||
#[repr(C)]
|
||||
pub struct EmptyAddressData {
|
||||
/// Total transaction count
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
use std::ops::Add;
|
||||
|
||||
use derive_deref::Deref;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
|
||||
|
||||
use crate::TypeIndex;
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Deref, Serialize, Pco)]
|
||||
#[derive(
|
||||
Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Deref, Serialize, Pco, JsonSchema,
|
||||
)]
|
||||
pub struct EmptyAddressIndex(TypeIndex);
|
||||
|
||||
impl From<TypeIndex> for EmptyAddressIndex {
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
use std::ops::Add;
|
||||
|
||||
use derive_deref::{Deref, DerefMut};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
|
||||
|
||||
use crate::TypeIndex;
|
||||
|
||||
#[derive(
|
||||
Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Deref, DerefMut, Default, Serialize, Pco,
|
||||
Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Deref, DerefMut, Default, Serialize, Pco, JsonSchema,
|
||||
)]
|
||||
pub struct EmptyOutputIndex(TypeIndex);
|
||||
impl From<TypeIndex> for EmptyOutputIndex {
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::{
|
||||
ops::{Add, AddAssign, Div},
|
||||
};
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
|
||||
|
||||
@@ -11,7 +12,18 @@ use super::Height;
|
||||
pub const BLOCKS_PER_HALVING: u32 = 210_000;
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Serialize, Deserialize, Pco,
|
||||
Debug,
|
||||
Clone,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Default,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Pco,
|
||||
JsonSchema,
|
||||
)]
|
||||
pub struct HalvingEpoch(u16);
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use brk_error::{Error, Result};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{Bytes, CheckedSub, Formattable};
|
||||
|
||||
use crate::{Bitcoin, Dollars, EmptyAddressData, Sats};
|
||||
|
||||
/// Data for a loaded (non-empty) address with current balance
|
||||
#[derive(Debug, Default, Clone, Serialize)]
|
||||
#[derive(Debug, Default, Clone, Serialize, JsonSchema)]
|
||||
#[repr(C)]
|
||||
pub struct LoadedAddressData {
|
||||
/// Total transaction count
|
||||
@@ -64,8 +65,12 @@ impl LoadedAddressData {
|
||||
}
|
||||
|
||||
pub fn receive(&mut self, amount: Sats, price: Option<Dollars>) {
|
||||
self.receive_outputs(amount, price, 1);
|
||||
}
|
||||
|
||||
pub fn receive_outputs(&mut self, amount: Sats, price: Option<Dollars>, output_count: u32) {
|
||||
self.received += amount;
|
||||
self.funded_txo_count += 1;
|
||||
self.funded_txo_count += output_count;
|
||||
if let Some(price) = price {
|
||||
let added = price * amount;
|
||||
self.realized_cap += added;
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
use std::ops::Add;
|
||||
|
||||
use derive_deref::Deref;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
|
||||
|
||||
use crate::TypeIndex;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Deref, Default, Serialize, Pco)]
|
||||
#[derive(
|
||||
Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Deref, Default, Serialize, Pco, JsonSchema,
|
||||
)]
|
||||
pub struct LoadedAddressIndex(TypeIndex);
|
||||
|
||||
impl From<TypeIndex> for LoadedAddressIndex {
|
||||
|
||||
@@ -3,13 +3,25 @@ use std::{
|
||||
ops::{Add, AddAssign, Div},
|
||||
};
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
|
||||
|
||||
use super::{Date, DateIndex, YearIndex};
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Serialize, Deserialize, Pco,
|
||||
Debug,
|
||||
Clone,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Default,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Pco,
|
||||
JsonSchema,
|
||||
)]
|
||||
pub struct MonthIndex(u16);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ use std::{
|
||||
};
|
||||
|
||||
use derive_deref::{Deref, DerefMut};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Serialize, Serializer, ser::SerializeTuple};
|
||||
use vecdb::{Bytes, Formattable, Pco, TransparentPco};
|
||||
|
||||
@@ -12,7 +13,7 @@ use crate::StoredF64;
|
||||
|
||||
use super::{Cents, Dollars, Sats};
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
#[derive(Debug, Default, Clone, JsonSchema)]
|
||||
#[repr(C)]
|
||||
pub struct OHLCCents {
|
||||
pub open: Open<Cents>,
|
||||
@@ -98,7 +99,7 @@ impl Bytes for OHLCCents {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
#[derive(Debug, Default, Clone, Copy, JsonSchema)]
|
||||
#[repr(C)]
|
||||
pub struct OHLCDollars {
|
||||
pub open: Open<Dollars>,
|
||||
@@ -210,7 +211,7 @@ impl Bytes for OHLCDollars {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
#[derive(Debug, Default, Clone, Copy, JsonSchema)]
|
||||
#[repr(C)]
|
||||
pub struct OHLCSats {
|
||||
pub open: Open<Sats>,
|
||||
@@ -304,7 +305,19 @@ impl Bytes for OHLCSats {
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Deref, DerefMut, Serialize, Pco,
|
||||
Debug,
|
||||
Default,
|
||||
Clone,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Deref,
|
||||
DerefMut,
|
||||
Serialize,
|
||||
Pco,
|
||||
JsonSchema,
|
||||
)]
|
||||
#[repr(transparent)]
|
||||
pub struct Open<T>(T);
|
||||
@@ -421,7 +434,19 @@ where
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug, Default, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Deref, DerefMut, Serialize, Pco,
|
||||
Debug,
|
||||
Default,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Clone,
|
||||
Copy,
|
||||
Deref,
|
||||
DerefMut,
|
||||
Serialize,
|
||||
Pco,
|
||||
JsonSchema,
|
||||
)]
|
||||
#[repr(transparent)]
|
||||
pub struct High<T>(T);
|
||||
@@ -538,7 +563,19 @@ where
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Deref, DerefMut, Serialize, Pco,
|
||||
Debug,
|
||||
Default,
|
||||
Clone,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Deref,
|
||||
DerefMut,
|
||||
Serialize,
|
||||
Pco,
|
||||
JsonSchema,
|
||||
)]
|
||||
#[repr(transparent)]
|
||||
pub struct Low<T>(T);
|
||||
@@ -655,7 +692,19 @@ where
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Deref, DerefMut, Serialize, Pco,
|
||||
Debug,
|
||||
Default,
|
||||
Clone,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Deref,
|
||||
DerefMut,
|
||||
Serialize,
|
||||
Pco,
|
||||
JsonSchema,
|
||||
)]
|
||||
#[repr(transparent)]
|
||||
pub struct Close<T>(T);
|
||||
|
||||
@@ -1,13 +1,26 @@
|
||||
use std::ops::Add;
|
||||
|
||||
use derive_deref::{Deref, DerefMut};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
|
||||
|
||||
use crate::TypeIndex;
|
||||
|
||||
#[derive(
|
||||
Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Deref, DerefMut, Default, Serialize, Pco,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Clone,
|
||||
Copy,
|
||||
Deref,
|
||||
DerefMut,
|
||||
Default,
|
||||
Serialize,
|
||||
Pco,
|
||||
JsonSchema,
|
||||
)]
|
||||
pub struct OpReturnIndex(TypeIndex);
|
||||
|
||||
@@ -17,30 +30,35 @@ impl From<TypeIndex> for OpReturnIndex {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OpReturnIndex> for usize {
|
||||
#[inline]
|
||||
fn from(value: OpReturnIndex) -> Self {
|
||||
Self::from(*value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OpReturnIndex> for u64 {
|
||||
#[inline]
|
||||
fn from(value: OpReturnIndex) -> Self {
|
||||
Self::from(*value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for OpReturnIndex {
|
||||
#[inline]
|
||||
fn from(value: usize) -> Self {
|
||||
Self(TypeIndex::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<usize> for OpReturnIndex {
|
||||
type Output = Self;
|
||||
fn add(self, rhs: usize) -> Self::Output {
|
||||
Self(*self + rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl CheckedSub<OpReturnIndex> for OpReturnIndex {
|
||||
fn checked_sub(self, rhs: Self) -> Option<Self> {
|
||||
self.0.checked_sub(rhs.0).map(Self)
|
||||
|
||||
@@ -1,68 +1,91 @@
|
||||
use std::ops::Add;
|
||||
|
||||
use derive_deref::{Deref, DerefMut};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
|
||||
|
||||
use crate::TypeIndex;
|
||||
|
||||
#[derive(
|
||||
Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Deref, DerefMut, Default, Serialize, Pco,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Clone,
|
||||
Copy,
|
||||
Deref,
|
||||
DerefMut,
|
||||
Default,
|
||||
Serialize,
|
||||
Pco,
|
||||
JsonSchema,
|
||||
)]
|
||||
pub struct P2AAddressIndex(TypeIndex);
|
||||
|
||||
impl From<TypeIndex> for P2AAddressIndex {
|
||||
#[inline]
|
||||
fn from(value: TypeIndex) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2AAddressIndex> for TypeIndex {
|
||||
#[inline]
|
||||
fn from(value: P2AAddressIndex) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2AAddressIndex> for u32 {
|
||||
#[inline]
|
||||
fn from(value: P2AAddressIndex) -> Self {
|
||||
Self::from(*value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2AAddressIndex> for u64 {
|
||||
#[inline]
|
||||
fn from(value: P2AAddressIndex) -> Self {
|
||||
Self::from(*value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for P2AAddressIndex {
|
||||
#[inline]
|
||||
fn from(value: u32) -> Self {
|
||||
Self(TypeIndex::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2AAddressIndex> for usize {
|
||||
#[inline]
|
||||
fn from(value: P2AAddressIndex) -> Self {
|
||||
Self::from(*value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for P2AAddressIndex {
|
||||
#[inline]
|
||||
fn from(value: usize) -> Self {
|
||||
Self(TypeIndex::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<usize> for P2AAddressIndex {
|
||||
type Output = Self;
|
||||
fn add(self, rhs: usize) -> Self::Output {
|
||||
Self(*self + rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl CheckedSub<P2AAddressIndex> for P2AAddressIndex {
|
||||
fn checked_sub(self, rhs: Self) -> Option<Self> {
|
||||
self.0.checked_sub(rhs.0).map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
impl PrintableIndex for P2AAddressIndex {
|
||||
fn to_string() -> &'static str {
|
||||
"p2aaddressindex"
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use std::fmt;
|
||||
|
||||
use derive_deref::Deref;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{Bytes, Formattable};
|
||||
|
||||
use crate::U8x2;
|
||||
|
||||
#[derive(Debug, Clone, Deref, PartialEq, Eq, PartialOrd, Ord, Serialize, Bytes, Hash)]
|
||||
#[derive(Debug, Clone, Deref, PartialEq, Eq, PartialOrd, Ord, Serialize, Bytes, Hash, JsonSchema)]
|
||||
pub struct P2ABytes(U8x2);
|
||||
|
||||
impl From<&[u8]> for P2ABytes {
|
||||
|
||||
@@ -1,45 +1,64 @@
|
||||
use std::ops::Add;
|
||||
|
||||
use derive_deref::{Deref, DerefMut};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
|
||||
|
||||
use crate::TypeIndex;
|
||||
|
||||
#[derive(
|
||||
Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Deref, DerefMut, Default, Serialize, Pco,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Clone,
|
||||
Copy,
|
||||
Deref,
|
||||
DerefMut,
|
||||
Default,
|
||||
Serialize,
|
||||
Pco,
|
||||
JsonSchema,
|
||||
)]
|
||||
pub struct P2MSOutputIndex(TypeIndex);
|
||||
|
||||
impl From<TypeIndex> for P2MSOutputIndex {
|
||||
#[inline]
|
||||
fn from(value: TypeIndex) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2MSOutputIndex> for usize {
|
||||
#[inline]
|
||||
fn from(value: P2MSOutputIndex) -> Self {
|
||||
Self::from(*value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2MSOutputIndex> for u64 {
|
||||
#[inline]
|
||||
fn from(value: P2MSOutputIndex) -> Self {
|
||||
Self::from(*value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for P2MSOutputIndex {
|
||||
#[inline]
|
||||
fn from(value: usize) -> Self {
|
||||
Self(TypeIndex::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<usize> for P2MSOutputIndex {
|
||||
type Output = Self;
|
||||
fn add(self, rhs: usize) -> Self::Output {
|
||||
Self(*self + rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl CheckedSub<P2MSOutputIndex> for P2MSOutputIndex {
|
||||
fn checked_sub(self, rhs: Self) -> Option<Self> {
|
||||
self.0.checked_sub(rhs.0).map(Self)
|
||||
|
||||
@@ -1,63 +1,85 @@
|
||||
use std::ops::Add;
|
||||
|
||||
use derive_deref::{Deref, DerefMut};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
|
||||
|
||||
use crate::TypeIndex;
|
||||
|
||||
#[derive(
|
||||
Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Deref, DerefMut, Default, Serialize, Pco,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Clone,
|
||||
Copy,
|
||||
Deref,
|
||||
DerefMut,
|
||||
Default,
|
||||
Serialize,
|
||||
Pco,
|
||||
JsonSchema,
|
||||
)]
|
||||
pub struct P2PK33AddressIndex(TypeIndex);
|
||||
|
||||
impl From<TypeIndex> for P2PK33AddressIndex {
|
||||
#[inline]
|
||||
fn from(value: TypeIndex) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2PK33AddressIndex> for TypeIndex {
|
||||
#[inline]
|
||||
fn from(value: P2PK33AddressIndex) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2PK33AddressIndex> for u32 {
|
||||
#[inline]
|
||||
fn from(value: P2PK33AddressIndex) -> Self {
|
||||
Self::from(*value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2PK33AddressIndex> for u64 {
|
||||
#[inline]
|
||||
fn from(value: P2PK33AddressIndex) -> Self {
|
||||
Self::from(*value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for P2PK33AddressIndex {
|
||||
#[inline]
|
||||
fn from(value: u32) -> Self {
|
||||
Self(TypeIndex::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2PK33AddressIndex> for usize {
|
||||
#[inline]
|
||||
fn from(value: P2PK33AddressIndex) -> Self {
|
||||
Self::from(*value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for P2PK33AddressIndex {
|
||||
#[inline]
|
||||
fn from(value: usize) -> Self {
|
||||
Self(TypeIndex::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<usize> for P2PK33AddressIndex {
|
||||
type Output = Self;
|
||||
fn add(self, rhs: usize) -> Self::Output {
|
||||
Self(*self + rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl CheckedSub<P2PK33AddressIndex> for P2PK33AddressIndex {
|
||||
fn checked_sub(self, rhs: Self) -> Option<Self> {
|
||||
self.0.checked_sub(rhs.0).map(Self)
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use std::fmt;
|
||||
|
||||
use derive_deref::Deref;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{Bytes, Formattable};
|
||||
|
||||
use crate::U8x33;
|
||||
|
||||
#[derive(Debug, Clone, Deref, PartialEq, Eq, PartialOrd, Ord, Serialize, Bytes, Hash)]
|
||||
#[derive(Debug, Clone, Deref, PartialEq, Eq, PartialOrd, Ord, Serialize, Bytes, Hash, JsonSchema)]
|
||||
pub struct P2PK33Bytes(U8x33);
|
||||
|
||||
impl From<&[u8]> for P2PK33Bytes {
|
||||
|
||||
@@ -1,63 +1,85 @@
|
||||
use std::ops::Add;
|
||||
|
||||
use derive_deref::{Deref, DerefMut};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
|
||||
|
||||
use crate::TypeIndex;
|
||||
|
||||
#[derive(
|
||||
Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Deref, DerefMut, Default, Serialize, Pco,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Clone,
|
||||
Copy,
|
||||
Deref,
|
||||
DerefMut,
|
||||
Default,
|
||||
Serialize,
|
||||
Pco,
|
||||
JsonSchema,
|
||||
)]
|
||||
pub struct P2PK65AddressIndex(TypeIndex);
|
||||
|
||||
impl From<TypeIndex> for P2PK65AddressIndex {
|
||||
#[inline]
|
||||
fn from(value: TypeIndex) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2PK65AddressIndex> for TypeIndex {
|
||||
#[inline]
|
||||
fn from(value: P2PK65AddressIndex) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2PK65AddressIndex> for u32 {
|
||||
#[inline]
|
||||
fn from(value: P2PK65AddressIndex) -> Self {
|
||||
Self::from(*value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2PK65AddressIndex> for u64 {
|
||||
#[inline]
|
||||
fn from(value: P2PK65AddressIndex) -> Self {
|
||||
Self::from(*value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2PK65AddressIndex> for usize {
|
||||
#[inline]
|
||||
fn from(value: P2PK65AddressIndex) -> Self {
|
||||
Self::from(*value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for P2PK65AddressIndex {
|
||||
#[inline]
|
||||
fn from(value: u32) -> Self {
|
||||
Self(TypeIndex::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for P2PK65AddressIndex {
|
||||
#[inline]
|
||||
fn from(value: usize) -> Self {
|
||||
Self(TypeIndex::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<usize> for P2PK65AddressIndex {
|
||||
type Output = Self;
|
||||
fn add(self, rhs: usize) -> Self::Output {
|
||||
Self(*self + rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl CheckedSub<P2PK65AddressIndex> for P2PK65AddressIndex {
|
||||
fn checked_sub(self, rhs: Self) -> Option<Self> {
|
||||
self.0.checked_sub(rhs.0).map(Self)
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use std::fmt;
|
||||
|
||||
use derive_deref::Deref;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{Bytes, Formattable};
|
||||
|
||||
use crate::U8x65;
|
||||
|
||||
#[derive(Debug, Clone, Deref, PartialEq, Eq, PartialOrd, Ord, Serialize, Bytes, Hash)]
|
||||
#[derive(Debug, Clone, Deref, PartialEq, Eq, PartialOrd, Ord, Serialize, Bytes, Hash, JsonSchema)]
|
||||
pub struct P2PK65Bytes(U8x65);
|
||||
|
||||
impl From<&[u8]> for P2PK65Bytes {
|
||||
|
||||
@@ -1,63 +1,85 @@
|
||||
use std::ops::Add;
|
||||
|
||||
use derive_deref::{Deref, DerefMut};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
|
||||
|
||||
use crate::TypeIndex;
|
||||
|
||||
#[derive(
|
||||
Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Deref, DerefMut, Default, Serialize, Pco,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Clone,
|
||||
Copy,
|
||||
Deref,
|
||||
DerefMut,
|
||||
Default,
|
||||
Serialize,
|
||||
Pco,
|
||||
JsonSchema,
|
||||
)]
|
||||
pub struct P2PKHAddressIndex(TypeIndex);
|
||||
|
||||
impl From<TypeIndex> for P2PKHAddressIndex {
|
||||
#[inline]
|
||||
fn from(value: TypeIndex) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2PKHAddressIndex> for TypeIndex {
|
||||
#[inline]
|
||||
fn from(value: P2PKHAddressIndex) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2PKHAddressIndex> for usize {
|
||||
#[inline]
|
||||
fn from(value: P2PKHAddressIndex) -> Self {
|
||||
Self::from(*value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2PKHAddressIndex> for u64 {
|
||||
#[inline]
|
||||
fn from(value: P2PKHAddressIndex) -> Self {
|
||||
Self::from(*value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2PKHAddressIndex> for u32 {
|
||||
#[inline]
|
||||
fn from(value: P2PKHAddressIndex) -> Self {
|
||||
Self::from(*value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for P2PKHAddressIndex {
|
||||
#[inline]
|
||||
fn from(value: u32) -> Self {
|
||||
Self(TypeIndex::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for P2PKHAddressIndex {
|
||||
#[inline]
|
||||
fn from(value: usize) -> Self {
|
||||
Self(TypeIndex::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<usize> for P2PKHAddressIndex {
|
||||
type Output = Self;
|
||||
fn add(self, rhs: usize) -> Self::Output {
|
||||
Self(*self + rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl CheckedSub<P2PKHAddressIndex> for P2PKHAddressIndex {
|
||||
fn checked_sub(self, rhs: Self) -> Option<Self> {
|
||||
self.0.checked_sub(rhs.0).map(Self)
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use std::fmt;
|
||||
|
||||
use derive_deref::Deref;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{Bytes, Formattable};
|
||||
|
||||
use crate::U8x20;
|
||||
|
||||
#[derive(Debug, Clone, Deref, PartialEq, Eq, PartialOrd, Ord, Serialize, Bytes, Hash)]
|
||||
#[derive(Debug, Clone, Deref, PartialEq, Eq, PartialOrd, Ord, Serialize, Bytes, Hash, JsonSchema)]
|
||||
pub struct P2PKHBytes(U8x20);
|
||||
|
||||
impl From<&[u8]> for P2PKHBytes {
|
||||
|
||||
@@ -1,69 +1,92 @@
|
||||
use std::ops::Add;
|
||||
|
||||
use derive_deref::{Deref, DerefMut};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
|
||||
|
||||
use crate::TypeIndex;
|
||||
|
||||
#[derive(
|
||||
Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Deref, DerefMut, Default, Serialize, Pco,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Clone,
|
||||
Copy,
|
||||
Deref,
|
||||
DerefMut,
|
||||
Default,
|
||||
Serialize,
|
||||
Pco,
|
||||
JsonSchema,
|
||||
)]
|
||||
pub struct P2SHAddressIndex(TypeIndex);
|
||||
|
||||
impl From<TypeIndex> for P2SHAddressIndex {
|
||||
#[inline]
|
||||
fn from(value: TypeIndex) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2SHAddressIndex> for TypeIndex {
|
||||
#[inline]
|
||||
fn from(value: P2SHAddressIndex) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2SHAddressIndex> for u32 {
|
||||
#[inline]
|
||||
fn from(value: P2SHAddressIndex) -> Self {
|
||||
Self::from(*value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2SHAddressIndex> for u64 {
|
||||
#[inline]
|
||||
fn from(value: P2SHAddressIndex) -> Self {
|
||||
Self::from(*value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for P2SHAddressIndex {
|
||||
#[inline]
|
||||
fn from(value: u32) -> Self {
|
||||
Self(TypeIndex::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for P2SHAddressIndex {
|
||||
#[inline]
|
||||
fn from(value: u64) -> Self {
|
||||
Self(TypeIndex::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2SHAddressIndex> for usize {
|
||||
#[inline]
|
||||
fn from(value: P2SHAddressIndex) -> Self {
|
||||
Self::from(*value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for P2SHAddressIndex {
|
||||
#[inline]
|
||||
fn from(value: usize) -> Self {
|
||||
Self(TypeIndex::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<usize> for P2SHAddressIndex {
|
||||
type Output = Self;
|
||||
fn add(self, rhs: usize) -> Self::Output {
|
||||
Self(*self + rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl CheckedSub<P2SHAddressIndex> for P2SHAddressIndex {
|
||||
fn checked_sub(self, rhs: Self) -> Option<Self> {
|
||||
self.0.checked_sub(rhs.0).map(Self)
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use std::fmt;
|
||||
|
||||
use derive_deref::Deref;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{Bytes, Formattable};
|
||||
|
||||
use crate::U8x20;
|
||||
|
||||
#[derive(Debug, Clone, Deref, PartialEq, Eq, PartialOrd, Ord, Serialize, Bytes, Hash)]
|
||||
#[derive(Debug, Clone, Deref, PartialEq, Eq, PartialOrd, Ord, Serialize, Bytes, Hash, JsonSchema)]
|
||||
pub struct P2SHBytes(U8x20);
|
||||
|
||||
impl From<&[u8]> for P2SHBytes {
|
||||
|
||||
@@ -1,63 +1,85 @@
|
||||
use std::ops::Add;
|
||||
|
||||
use derive_deref::{Deref, DerefMut};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
|
||||
|
||||
use crate::TypeIndex;
|
||||
|
||||
#[derive(
|
||||
Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Deref, DerefMut, Default, Serialize, Pco,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Clone,
|
||||
Copy,
|
||||
Deref,
|
||||
DerefMut,
|
||||
Default,
|
||||
Serialize,
|
||||
Pco,
|
||||
JsonSchema,
|
||||
)]
|
||||
pub struct P2TRAddressIndex(TypeIndex);
|
||||
|
||||
impl From<TypeIndex> for P2TRAddressIndex {
|
||||
#[inline]
|
||||
fn from(value: TypeIndex) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2TRAddressIndex> for TypeIndex {
|
||||
#[inline]
|
||||
fn from(value: P2TRAddressIndex) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2TRAddressIndex> for u32 {
|
||||
#[inline]
|
||||
fn from(value: P2TRAddressIndex) -> Self {
|
||||
Self::from(*value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2TRAddressIndex> for u64 {
|
||||
#[inline]
|
||||
fn from(value: P2TRAddressIndex) -> Self {
|
||||
Self::from(*value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for P2TRAddressIndex {
|
||||
#[inline]
|
||||
fn from(value: u32) -> Self {
|
||||
Self(TypeIndex::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2TRAddressIndex> for usize {
|
||||
#[inline]
|
||||
fn from(value: P2TRAddressIndex) -> Self {
|
||||
Self::from(*value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for P2TRAddressIndex {
|
||||
#[inline]
|
||||
fn from(value: usize) -> Self {
|
||||
Self(TypeIndex::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<usize> for P2TRAddressIndex {
|
||||
type Output = Self;
|
||||
fn add(self, rhs: usize) -> Self::Output {
|
||||
Self(*self + rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl CheckedSub<P2TRAddressIndex> for P2TRAddressIndex {
|
||||
fn checked_sub(self, rhs: Self) -> Option<Self> {
|
||||
self.0.checked_sub(rhs.0).map(Self)
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use std::fmt;
|
||||
|
||||
use derive_deref::Deref;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{Bytes, Formattable};
|
||||
|
||||
use crate::U8x32;
|
||||
|
||||
#[derive(Debug, Clone, Deref, PartialEq, Eq, PartialOrd, Ord, Serialize, Bytes, Hash)]
|
||||
#[derive(Debug, Clone, Deref, PartialEq, Eq, PartialOrd, Ord, Serialize, Bytes, Hash, JsonSchema)]
|
||||
pub struct P2TRBytes(U8x32);
|
||||
|
||||
impl From<&[u8]> for P2TRBytes {
|
||||
|
||||
@@ -1,63 +1,85 @@
|
||||
use std::ops::Add;
|
||||
|
||||
use derive_deref::{Deref, DerefMut};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
|
||||
|
||||
use crate::TypeIndex;
|
||||
|
||||
#[derive(
|
||||
Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Deref, DerefMut, Default, Serialize, Pco,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Clone,
|
||||
Copy,
|
||||
Deref,
|
||||
DerefMut,
|
||||
Default,
|
||||
Serialize,
|
||||
Pco,
|
||||
JsonSchema,
|
||||
)]
|
||||
pub struct P2WPKHAddressIndex(TypeIndex);
|
||||
|
||||
impl From<TypeIndex> for P2WPKHAddressIndex {
|
||||
#[inline]
|
||||
fn from(value: TypeIndex) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2WPKHAddressIndex> for TypeIndex {
|
||||
#[inline]
|
||||
fn from(value: P2WPKHAddressIndex) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2WPKHAddressIndex> for u32 {
|
||||
#[inline]
|
||||
fn from(value: P2WPKHAddressIndex) -> Self {
|
||||
Self::from(*value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2WPKHAddressIndex> for u64 {
|
||||
#[inline]
|
||||
fn from(value: P2WPKHAddressIndex) -> Self {
|
||||
Self::from(*value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2WPKHAddressIndex> for usize {
|
||||
#[inline]
|
||||
fn from(value: P2WPKHAddressIndex) -> Self {
|
||||
Self::from(*value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for P2WPKHAddressIndex {
|
||||
#[inline]
|
||||
fn from(value: u32) -> Self {
|
||||
Self(TypeIndex::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for P2WPKHAddressIndex {
|
||||
#[inline]
|
||||
fn from(value: usize) -> Self {
|
||||
Self(TypeIndex::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<usize> for P2WPKHAddressIndex {
|
||||
type Output = Self;
|
||||
fn add(self, rhs: usize) -> Self::Output {
|
||||
Self(*self + rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl CheckedSub<P2WPKHAddressIndex> for P2WPKHAddressIndex {
|
||||
fn checked_sub(self, rhs: Self) -> Option<Self> {
|
||||
self.0.checked_sub(rhs.0).map(Self)
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use std::fmt;
|
||||
|
||||
use derive_deref::Deref;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{Bytes, Formattable};
|
||||
|
||||
use crate::U8x20;
|
||||
|
||||
#[derive(Debug, Clone, Deref, PartialEq, Eq, PartialOrd, Ord, Serialize, Bytes, Hash)]
|
||||
#[derive(Debug, Clone, Deref, PartialEq, Eq, PartialOrd, Ord, Serialize, Bytes, Hash, JsonSchema)]
|
||||
pub struct P2WPKHBytes(U8x20);
|
||||
|
||||
impl From<&[u8]> for P2WPKHBytes {
|
||||
|
||||
@@ -1,63 +1,85 @@
|
||||
use std::ops::Add;
|
||||
|
||||
use derive_deref::{Deref, DerefMut};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
|
||||
|
||||
use crate::TypeIndex;
|
||||
|
||||
#[derive(
|
||||
Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Deref, DerefMut, Default, Serialize, Pco,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Clone,
|
||||
Copy,
|
||||
Deref,
|
||||
DerefMut,
|
||||
Default,
|
||||
Serialize,
|
||||
Pco,
|
||||
JsonSchema,
|
||||
)]
|
||||
pub struct P2WSHAddressIndex(TypeIndex);
|
||||
|
||||
impl From<TypeIndex> for P2WSHAddressIndex {
|
||||
#[inline]
|
||||
fn from(value: TypeIndex) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2WSHAddressIndex> for TypeIndex {
|
||||
#[inline]
|
||||
fn from(value: P2WSHAddressIndex) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2WSHAddressIndex> for u32 {
|
||||
#[inline]
|
||||
fn from(value: P2WSHAddressIndex) -> Self {
|
||||
Self::from(*value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2WSHAddressIndex> for u64 {
|
||||
#[inline]
|
||||
fn from(value: P2WSHAddressIndex) -> Self {
|
||||
Self::from(*value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for P2WSHAddressIndex {
|
||||
#[inline]
|
||||
fn from(value: u32) -> Self {
|
||||
Self(TypeIndex::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2WSHAddressIndex> for usize {
|
||||
#[inline]
|
||||
fn from(value: P2WSHAddressIndex) -> Self {
|
||||
Self::from(*value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for P2WSHAddressIndex {
|
||||
#[inline]
|
||||
fn from(value: usize) -> Self {
|
||||
Self(TypeIndex::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<usize> for P2WSHAddressIndex {
|
||||
type Output = Self;
|
||||
fn add(self, rhs: usize) -> Self::Output {
|
||||
Self(*self + rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl CheckedSub<P2WSHAddressIndex> for P2WSHAddressIndex {
|
||||
fn checked_sub(self, rhs: Self) -> Option<Self> {
|
||||
self.0.checked_sub(rhs.0).map(Self)
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use std::fmt;
|
||||
|
||||
use derive_deref::Deref;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{Bytes, Formattable};
|
||||
|
||||
use crate::U8x32;
|
||||
|
||||
#[derive(Debug, Clone, Deref, PartialEq, Eq, PartialOrd, Ord, Serialize, Bytes, Hash)]
|
||||
#[derive(Debug, Clone, Deref, PartialEq, Eq, PartialOrd, Ord, Serialize, Bytes, Hash, JsonSchema)]
|
||||
pub struct P2WSHBytes(U8x32);
|
||||
|
||||
impl From<&[u8]> for P2WSHBytes {
|
||||
|
||||
@@ -3,13 +3,25 @@ use std::{
|
||||
ops::{Add, AddAssign, Div},
|
||||
};
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
|
||||
|
||||
use super::MonthIndex;
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Serialize, Deserialize, Pco,
|
||||
Debug,
|
||||
Clone,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Default,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Pco,
|
||||
JsonSchema,
|
||||
)]
|
||||
pub struct QuarterIndex(u16);
|
||||
|
||||
|
||||
@@ -3,13 +3,25 @@ use std::{
|
||||
ops::{Add, AddAssign, Div},
|
||||
};
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
|
||||
|
||||
use super::MonthIndex;
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Serialize, Deserialize, Pco,
|
||||
Debug,
|
||||
Clone,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Default,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Pco,
|
||||
JsonSchema,
|
||||
)]
|
||||
pub struct SemesterIndex(u16);
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use derive_deref::Deref;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{Formattable, Pco, PrintableIndex};
|
||||
|
||||
#[derive(Debug, Deref, Clone, Default, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Pco)]
|
||||
#[derive(Debug, Deref, Clone, Default, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Pco, JsonSchema)]
|
||||
pub struct StoredBool(u16);
|
||||
|
||||
impl StoredBool {
|
||||
|
||||
@@ -7,6 +7,7 @@ use std::{
|
||||
};
|
||||
|
||||
use derive_deref::Deref;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
|
||||
|
||||
@@ -14,7 +15,7 @@ use crate::{Close, StoredU32};
|
||||
|
||||
use super::{Dollars, StoredF64};
|
||||
|
||||
#[derive(Debug, Deref, Default, Clone, Copy, Serialize, Pco)]
|
||||
#[derive(Debug, Deref, Default, Clone, Copy, Serialize, Pco, JsonSchema)]
|
||||
pub struct StoredF32(f32);
|
||||
|
||||
impl StoredF32 {
|
||||
|
||||
@@ -6,12 +6,13 @@ use std::{
|
||||
};
|
||||
|
||||
use derive_deref::Deref;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
|
||||
|
||||
use crate::{Bitcoin, Dollars};
|
||||
|
||||
#[derive(Debug, Deref, Default, Clone, Copy, Serialize, Pco)]
|
||||
#[derive(Debug, Deref, Default, Clone, Copy, Serialize, Pco, JsonSchema)]
|
||||
pub struct StoredF64(f64);
|
||||
|
||||
impl StoredF64 {
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
use std::ops::{Add, AddAssign, Div};
|
||||
|
||||
use derive_deref::Deref;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
|
||||
|
||||
#[derive(Debug, Deref, Clone, Default, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Pco)]
|
||||
#[derive(
|
||||
Debug, Deref, Clone, Default, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Pco, JsonSchema,
|
||||
)]
|
||||
pub struct StoredI16(i16);
|
||||
|
||||
impl StoredI16 {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::ops::{Add, AddAssign, Div};
|
||||
|
||||
use derive_deref::Deref;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
|
||||
|
||||
@@ -10,7 +11,9 @@ use super::{
|
||||
P2WSHAddressIndex, UnknownOutputIndex,
|
||||
};
|
||||
|
||||
#[derive(Debug, Deref, Clone, Default, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Pco)]
|
||||
#[derive(
|
||||
Debug, Deref, Clone, Default, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Pco, JsonSchema,
|
||||
)]
|
||||
pub struct StoredU16(u16);
|
||||
|
||||
impl StoredU16 {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::ops::{Add, AddAssign, Div, Mul};
|
||||
|
||||
use derive_deref::Deref;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
|
||||
|
||||
@@ -10,7 +11,7 @@ use super::{
|
||||
P2WSHAddressIndex, UnknownOutputIndex,
|
||||
};
|
||||
|
||||
#[derive(Debug, Deref, Clone, Default, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Pco)]
|
||||
#[derive(Debug, Deref, Clone, Default, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Pco, JsonSchema)]
|
||||
pub struct StoredU32(u32);
|
||||
|
||||
impl StoredU32 {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::ops::{Add, AddAssign, Div};
|
||||
|
||||
use derive_deref::Deref;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
|
||||
|
||||
@@ -11,7 +12,7 @@ use super::{
|
||||
UnknownOutputIndex, YearIndex,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default, Deref, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Pco)]
|
||||
#[derive(Debug, Default, Deref, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Pco, JsonSchema)]
|
||||
pub struct StoredU64(u64);
|
||||
|
||||
impl StoredU64 {
|
||||
|
||||
@@ -1,19 +1,97 @@
|
||||
use std::{collections::BTreeMap, sync::LazyLock};
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
sync::LazyLock,
|
||||
};
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
use super::Index;
|
||||
|
||||
/// Leaf node containing metric metadata
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, Eq, JsonSchema)]
|
||||
pub struct MetricLeaf {
|
||||
/// The metric name/identifier
|
||||
pub name: String,
|
||||
/// The value type (e.g., "Sats", "StoredF64")
|
||||
pub value_type: String,
|
||||
/// Available indexes for this metric
|
||||
pub indexes: BTreeSet<Index>,
|
||||
}
|
||||
|
||||
impl MetricLeaf {
|
||||
pub fn new(name: String, value_type: String, indexes: BTreeSet<Index>) -> Self {
|
||||
Self {
|
||||
name,
|
||||
value_type,
|
||||
indexes,
|
||||
}
|
||||
}
|
||||
|
||||
/// Merge another leaf's indexes into this one (union)
|
||||
pub fn merge_indexes(&mut self, other: &MetricLeaf) {
|
||||
self.indexes.extend(other.indexes.iter().copied());
|
||||
}
|
||||
}
|
||||
|
||||
/// MetricLeaf with JSON Schema for client generation
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct MetricLeafWithSchema {
|
||||
/// The core metric metadata
|
||||
#[serde(flatten)]
|
||||
pub leaf: MetricLeaf,
|
||||
/// JSON Schema for the value type
|
||||
#[serde(skip)]
|
||||
pub schema: serde_json::Value,
|
||||
}
|
||||
|
||||
impl MetricLeafWithSchema {
|
||||
pub fn new(leaf: MetricLeaf, schema: serde_json::Value) -> Self {
|
||||
Self { leaf, schema }
|
||||
}
|
||||
|
||||
/// The metric name/identifier
|
||||
pub fn name(&self) -> &str {
|
||||
&self.leaf.name
|
||||
}
|
||||
|
||||
/// The value type (e.g., "Sats", "StoredF64")
|
||||
pub fn value_type(&self) -> &str {
|
||||
&self.leaf.value_type
|
||||
}
|
||||
|
||||
/// Available indexes for this metric
|
||||
pub fn indexes(&self) -> &BTreeSet<Index> {
|
||||
&self.leaf.indexes
|
||||
}
|
||||
|
||||
/// Check if this leaf refers to the same metric as another
|
||||
pub fn is_same_metric(&self, other: &MetricLeafWithSchema) -> bool {
|
||||
self.leaf.name == other.leaf.name
|
||||
}
|
||||
|
||||
/// Merge another leaf's indexes into this one (union)
|
||||
pub fn merge_indexes(&mut self, other: &MetricLeafWithSchema) {
|
||||
self.leaf.merge_indexes(&other.leaf);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for MetricLeafWithSchema {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.leaf == other.leaf
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for MetricLeafWithSchema {}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
|
||||
#[serde(untagged)]
|
||||
/// Hierarchical tree node for organizing metrics into categories
|
||||
pub enum TreeNode {
|
||||
/// Branch node containing subcategories
|
||||
Branch(BTreeMap<String, TreeNode>),
|
||||
/// Leaf node containing the metric name
|
||||
#[schemars(example = &"price_close", example = &"market_cap", example = &"realized_price")]
|
||||
Leaf(String),
|
||||
/// Leaf node containing metric metadata with schema
|
||||
Leaf(MetricLeafWithSchema),
|
||||
}
|
||||
|
||||
const BASE: &str = "base";
|
||||
@@ -55,8 +133,8 @@ impl TreeNode {
|
||||
|
||||
for node in tree.values() {
|
||||
match node {
|
||||
Self::Leaf(value) => {
|
||||
Self::merge_node(&mut merged, BASE, &Self::Leaf(value.clone()))?;
|
||||
Self::Leaf(leaf) => {
|
||||
Self::merge_node(&mut merged, BASE, &Self::Leaf(leaf.clone()))?;
|
||||
}
|
||||
Self::Branch(inner) => {
|
||||
for (key, inner_node) in inner {
|
||||
@@ -68,33 +146,36 @@ impl TreeNode {
|
||||
|
||||
let result = Self::Branch(merged);
|
||||
|
||||
// Check if all leaves have the same value
|
||||
if let Some(common_value) = result.all_leaves_same() {
|
||||
Some(Self::Leaf(common_value))
|
||||
// Check if all leaves have the same name (can be collapsed)
|
||||
if let Some(common_leaf) = result.all_leaves_same() {
|
||||
Some(Self::Leaf(common_leaf))
|
||||
} else {
|
||||
Some(result)
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if all leaves in the tree have the same value.
|
||||
/// Returns Some(value) if all leaves are identical, None otherwise.
|
||||
fn all_leaves_same(&self) -> Option<String> {
|
||||
/// Checks if all leaves in the tree have the same metric name.
|
||||
/// Returns Some(merged_leaf) if all leaves have the same name, None otherwise.
|
||||
/// When merging, indexes are unioned together.
|
||||
fn all_leaves_same(&self) -> Option<MetricLeafWithSchema> {
|
||||
match self {
|
||||
Self::Leaf(value) => Some(value.clone()),
|
||||
Self::Leaf(leaf) => Some(leaf.clone()),
|
||||
Self::Branch(map) => {
|
||||
let mut common_value: Option<String> = None;
|
||||
let mut common_leaf: Option<MetricLeafWithSchema> = None;
|
||||
|
||||
for node in map.values() {
|
||||
let node_value = node.all_leaves_same()?;
|
||||
let node_leaf = node.all_leaves_same()?;
|
||||
|
||||
match &common_value {
|
||||
None => common_value = Some(node_value),
|
||||
Some(existing) if existing != &node_value => return None,
|
||||
_ => {}
|
||||
match &mut common_leaf {
|
||||
None => common_leaf = Some(node_leaf),
|
||||
Some(existing) if existing.is_same_metric(&node_leaf) => {
|
||||
existing.merge_indexes(&node_leaf);
|
||||
}
|
||||
Some(_) => return None,
|
||||
}
|
||||
}
|
||||
|
||||
common_value
|
||||
common_leaf
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -111,39 +192,42 @@ impl TreeNode {
|
||||
target.insert(key.to_string(), node.clone());
|
||||
Some(())
|
||||
}
|
||||
Some(existing) => match (&existing, node) {
|
||||
// Same leaf values: ok
|
||||
(Self::Leaf(a), Self::Leaf(b)) if a == b => Some(()),
|
||||
// Different leaf values: conflict
|
||||
(Self::Leaf(a), Self::Leaf(b)) => {
|
||||
eprintln!("Conflict: Different leaf values for key '{key}'");
|
||||
eprintln!(" Existing: {a:?}");
|
||||
eprintln!(" New: {b:?}");
|
||||
None
|
||||
}
|
||||
(Self::Leaf(leaf), Self::Branch(branch)) => {
|
||||
let mut new_branch = BTreeMap::new();
|
||||
new_branch.insert(BASE.to_string(), Self::Leaf(leaf.clone()));
|
||||
|
||||
for (k, v) in branch {
|
||||
Self::merge_node(&mut new_branch, k, v)?;
|
||||
Some(existing) => {
|
||||
match (&mut *existing, node) {
|
||||
(Self::Leaf(a), Self::Leaf(b)) if a.is_same_metric(b) => {
|
||||
a.merge_indexes(b);
|
||||
Some(())
|
||||
}
|
||||
|
||||
*existing = Self::Branch(new_branch);
|
||||
Some(())
|
||||
}
|
||||
(Self::Branch(_), Self::Leaf(leaf)) => {
|
||||
Self::merge_node(existing.as_mut_branch(), BASE, &Self::Leaf(leaf.clone()))?;
|
||||
Some(())
|
||||
}
|
||||
// Both branches: merge recursively
|
||||
(Self::Branch(_), Self::Branch(new_inner)) => {
|
||||
for (k, v) in new_inner {
|
||||
Self::merge_node(existing.as_mut_branch(), k, v)?;
|
||||
(Self::Leaf(a), Self::Leaf(b)) => {
|
||||
eprintln!("Conflict: Different leaf values for key '{key}'");
|
||||
eprintln!(" Existing: {a:?}");
|
||||
eprintln!(" New: {b:?}");
|
||||
None
|
||||
}
|
||||
(Self::Leaf(leaf), Self::Branch(branch)) => {
|
||||
let mut new_branch = BTreeMap::new();
|
||||
new_branch.insert(BASE.to_string(), Self::Leaf(leaf.clone()));
|
||||
|
||||
for (k, v) in branch {
|
||||
Self::merge_node(&mut new_branch, k, v)?;
|
||||
}
|
||||
|
||||
*existing = Self::Branch(new_branch);
|
||||
Some(())
|
||||
}
|
||||
(Self::Branch(existing_branch), Self::Leaf(leaf)) => {
|
||||
Self::merge_node(existing_branch, BASE, &Self::Leaf(leaf.clone()))?;
|
||||
Some(())
|
||||
}
|
||||
// Both branches: merge recursively
|
||||
(Self::Branch(existing_branch), Self::Branch(new_inner)) => {
|
||||
for (k, v) in new_inner {
|
||||
Self::merge_node(existing_branch, k, v)?;
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
use std::ops::{Add, AddAssign};
|
||||
|
||||
use derive_deref::{Deref, DerefMut};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
|
||||
|
||||
use super::Vin;
|
||||
|
||||
#[derive(
|
||||
Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Deref, DerefMut, Default, Serialize, Pco,
|
||||
Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Deref, DerefMut, Default, Serialize, Pco, JsonSchema,
|
||||
)]
|
||||
pub struct TxInIndex(u64);
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
use std::ops::{Add, AddAssign};
|
||||
|
||||
use derive_deref::{Deref, DerefMut};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
|
||||
|
||||
use super::Vout;
|
||||
|
||||
#[derive(
|
||||
Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Deref, DerefMut, Default, Serialize, Pco,
|
||||
Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Deref, DerefMut, Default, Serialize, Pco, JsonSchema,
|
||||
)]
|
||||
pub struct TxOutIndex(u64);
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
use std::ops::Add;
|
||||
|
||||
use derive_deref::{Deref, DerefMut};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
|
||||
|
||||
use crate::TypeIndex;
|
||||
|
||||
#[derive(
|
||||
Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Deref, DerefMut, Default, Serialize, Pco,
|
||||
Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Deref, DerefMut, Default, Serialize, Pco, JsonSchema,
|
||||
)]
|
||||
pub struct UnknownOutputIndex(TypeIndex);
|
||||
|
||||
|
||||
@@ -3,13 +3,25 @@ use std::{
|
||||
ops::{Add, AddAssign, Div},
|
||||
};
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
|
||||
|
||||
use super::{Date, DateIndex};
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Serialize, Deserialize, Pco,
|
||||
Debug,
|
||||
Clone,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Default,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Pco,
|
||||
JsonSchema,
|
||||
)]
|
||||
pub struct WeekIndex(u16);
|
||||
|
||||
|
||||
@@ -3,13 +3,25 @@ use std::{
|
||||
ops::{Add, AddAssign, Div},
|
||||
};
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
|
||||
|
||||
use super::{Date, DateIndex, MonthIndex};
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Serialize, Deserialize, Pco,
|
||||
Debug,
|
||||
Clone,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Default,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Pco,
|
||||
JsonSchema,
|
||||
)]
|
||||
pub struct YearIndex(u16);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user