mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-28 19:28:11 -07:00
global: snapshot
This commit is contained in:
Generated
-8
@@ -422,7 +422,6 @@ version = "0.1.0-alpha.2"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"libmimalloc-sys",
|
"libmimalloc-sys",
|
||||||
"mimalloc",
|
"mimalloc",
|
||||||
"tracing",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -450,10 +449,8 @@ dependencies = [
|
|||||||
"brk_query",
|
"brk_query",
|
||||||
"brk_types",
|
"brk_types",
|
||||||
"oas3",
|
"oas3",
|
||||||
"schemars",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"vecdb",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -461,7 +458,6 @@ name = "brk_cli"
|
|||||||
version = "0.1.0-alpha.2"
|
version = "0.1.0-alpha.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"brk_alloc",
|
"brk_alloc",
|
||||||
"brk_bindgen",
|
|
||||||
"brk_computer",
|
"brk_computer",
|
||||||
"brk_error",
|
"brk_error",
|
||||||
"brk_fetcher",
|
"brk_fetcher",
|
||||||
@@ -493,7 +489,6 @@ dependencies = [
|
|||||||
"brk_types",
|
"brk_types",
|
||||||
"minreq",
|
"minreq",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -657,9 +652,6 @@ dependencies = [
|
|||||||
"derive_more",
|
"derive_more",
|
||||||
"jiff",
|
"jiff",
|
||||||
"quickmatch",
|
"quickmatch",
|
||||||
"schemars",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"tokio",
|
"tokio",
|
||||||
"vecdb",
|
"vecdb",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -9,5 +9,4 @@ repository.workspace = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libmimalloc-sys = { version = "0.1.44", features = ["extended"] }
|
libmimalloc-sys = { version = "0.1.44", features = ["extended"] }
|
||||||
tracing = { workspace = true }
|
|
||||||
mimalloc = { version = "0.1.48", features = ["v3"] }
|
mimalloc = { version = "0.1.48", features = ["v3"] }
|
||||||
|
|||||||
@@ -13,7 +13,5 @@ brk_cohort = { workspace = true }
|
|||||||
brk_query = { workspace = true }
|
brk_query = { workspace = true }
|
||||||
brk_types = { workspace = true }
|
brk_types = { workspace = true }
|
||||||
oas3 = "0.20"
|
oas3 = "0.20"
|
||||||
schemars = { workspace = true }
|
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
vecdb = { workspace = true }
|
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ use std::collections::{BTreeMap, BTreeSet, HashMap};
|
|||||||
|
|
||||||
use brk_types::{Index, TreeNode, extract_json_type};
|
use brk_types::{Index, TreeNode, extract_json_type};
|
||||||
|
|
||||||
use crate::analysis::names::analyze_pattern_level;
|
use crate::{IndexSetPattern, PatternField, analysis::names::analyze_pattern_level, child_type_name};
|
||||||
use crate::{IndexSetPattern, PatternField, child_type_name};
|
|
||||||
|
|
||||||
/// Get the first leaf name from a tree node.
|
/// Get the first leaf name from a tree node.
|
||||||
pub fn get_first_leaf_name(node: &TreeNode) -> Option<String> {
|
pub fn get_first_leaf_name(node: &TreeNode) -> Option<String> {
|
||||||
@@ -111,122 +110,29 @@ fn collect_indexes_from_tree(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the metric base for a pattern instance by analyzing all leaf descendants.
|
/// Get the metric base for a pattern instance by analyzing direct children.
|
||||||
///
|
///
|
||||||
/// For root-level instances (no common prefix/suffix among leaves), returns empty string.
|
/// Uses field names and first leaf names from direct children to determine
|
||||||
/// For cohort-level instances, returns the common prefix or suffix among all leaves.
|
/// the common base via `analyze_pattern_level`.
|
||||||
pub fn get_pattern_instance_base(node: &TreeNode) -> String {
|
pub fn get_pattern_instance_base(node: &TreeNode) -> String {
|
||||||
let leaf_names = get_all_leaf_names(node);
|
let child_names = get_direct_children_for_analysis(node);
|
||||||
find_common_base(&leaf_names)
|
if child_names.is_empty() {
|
||||||
|
return String::new();
|
||||||
|
}
|
||||||
|
analyze_pattern_level(&child_names).base
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find the common base from a set of metric names.
|
/// Get (field_name, first_leaf_name) pairs for direct children of a branch node.
|
||||||
/// Tries prefix, suffix, then strips first/last segments and retries.
|
fn get_direct_children_for_analysis(node: &TreeNode) -> Vec<(String, String)> {
|
||||||
fn find_common_base(names: &[String]) -> String {
|
match node {
|
||||||
if names.is_empty() {
|
TreeNode::Leaf(leaf) => vec![(leaf.name().to_string(), leaf.name().to_string())],
|
||||||
return String::new();
|
TreeNode::Branch(children) => children
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(field_name, child)| {
|
||||||
|
get_first_leaf_name(child).map(|leaf_name| (field_name.clone(), leaf_name))
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try common prefix
|
|
||||||
let common_prefix = find_common_prefix_at_underscore(names);
|
|
||||||
if !common_prefix.is_empty() {
|
|
||||||
return common_prefix.trim_end_matches('_').to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try common suffix
|
|
||||||
let common_suffix = find_common_suffix_at_underscore(names);
|
|
||||||
if !common_suffix.is_empty() {
|
|
||||||
return common_suffix.trim_start_matches('_').to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
// If neither works, the common part may be in the middle.
|
|
||||||
// Strip the first underscore segment (varying prefix) and try again.
|
|
||||||
let stripped_prefix: Vec<String> = names
|
|
||||||
.iter()
|
|
||||||
.filter_map(|name| name.split_once('_').map(|(_, rest)| rest.to_string()))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
if stripped_prefix.len() == names.len() {
|
|
||||||
let common_prefix = find_common_prefix_at_underscore(&stripped_prefix);
|
|
||||||
if !common_prefix.is_empty() {
|
|
||||||
return common_prefix.trim_end_matches('_').to_string();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try stripping last segment (varying suffix) and look for common suffix
|
|
||||||
let stripped_suffix: Vec<String> = names
|
|
||||||
.iter()
|
|
||||||
.filter_map(|name| name.rsplit_once('_').map(|(rest, _)| rest.to_string()))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
if stripped_suffix.len() == names.len() {
|
|
||||||
let common_suffix = find_common_suffix_at_underscore(&stripped_suffix);
|
|
||||||
if !common_suffix.is_empty() {
|
|
||||||
return common_suffix.trim_start_matches('_').to_string();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String::new()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Find the longest common prefix at an underscore boundary.
|
|
||||||
fn find_common_prefix_at_underscore(names: &[String]) -> String {
|
|
||||||
if names.is_empty() {
|
|
||||||
return String::new();
|
|
||||||
}
|
|
||||||
|
|
||||||
let first = &names[0];
|
|
||||||
if first.is_empty() {
|
|
||||||
return String::new();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find character-by-character common prefix
|
|
||||||
let mut prefix_len = 0;
|
|
||||||
for (i, ch) in first.chars().enumerate() {
|
|
||||||
if names.iter().all(|n| n.chars().nth(i) == Some(ch)) {
|
|
||||||
prefix_len = i + 1;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if prefix_len == 0 {
|
|
||||||
return String::new();
|
|
||||||
}
|
|
||||||
|
|
||||||
let raw_prefix = &first[..prefix_len];
|
|
||||||
|
|
||||||
// If raw_prefix exactly matches a leaf name, it's a complete metric name.
|
|
||||||
// In this case, return it with trailing underscore (will be trimmed by caller).
|
|
||||||
if names.iter().any(|n| n == raw_prefix) {
|
|
||||||
return format!("{}_", raw_prefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the last underscore position to get a clean boundary
|
|
||||||
if let Some(last_underscore) = raw_prefix.rfind('_')
|
|
||||||
&& last_underscore > 0
|
|
||||||
{
|
|
||||||
let clean_prefix = &first[..=last_underscore];
|
|
||||||
// Verify this still works for all names
|
|
||||||
if names.iter().all(|n| n.starts_with(clean_prefix)) {
|
|
||||||
return clean_prefix.to_string();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no underscore boundary works, check if full prefix ends at underscore
|
|
||||||
if raw_prefix.ends_with('_') {
|
|
||||||
return raw_prefix.to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
String::new()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Find the longest common suffix at an underscore boundary.
|
|
||||||
fn find_common_suffix_at_underscore(names: &[String]) -> String {
|
|
||||||
// Reverse strings, find common prefix, reverse result
|
|
||||||
let reversed: Vec<String> = names.iter().map(|s| s.chars().rev().collect()).collect();
|
|
||||||
let prefix = find_common_prefix_at_underscore(&reversed);
|
|
||||||
prefix.chars().rev().collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Infer the accumulated name for a child node based on a descendant leaf name.
|
/// Infer the accumulated name for a child node based on a descendant leaf name.
|
||||||
|
|||||||
@@ -96,4 +96,8 @@ impl LanguageSyntax for JavaScriptSyntax {
|
|||||||
fn string_literal(&self, value: &str) -> String {
|
fn string_literal(&self, value: &str) -> String {
|
||||||
format!("'{}'", value)
|
format!("'{}'", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn constructor_name(&self, type_name: &str) -> String {
|
||||||
|
format!("create{}", type_name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,4 +85,8 @@ impl LanguageSyntax for PythonSyntax {
|
|||||||
fn string_literal(&self, value: &str) -> String {
|
fn string_literal(&self, value: &str) -> String {
|
||||||
format!("'{}'", value)
|
format!("'{}'", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn constructor_name(&self, type_name: &str) -> String {
|
||||||
|
type_name.to_string()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,4 +86,8 @@ impl LanguageSyntax for RustSyntax {
|
|||||||
fn string_literal(&self, value: &str) -> String {
|
fn string_literal(&self, value: &str) -> String {
|
||||||
format!("\"{}\".to_string()", value)
|
format!("\"{}\".to_string()", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn constructor_name(&self, type_name: &str) -> String {
|
||||||
|
format!("{}::new", type_name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
|
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
use brk_types::MetricLeafWithSchema;
|
||||||
|
|
||||||
use crate::{ClientMetadata, LanguageSyntax, PatternField, StructuralPattern};
|
use crate::{ClientMetadata, LanguageSyntax, PatternField, StructuralPattern};
|
||||||
|
|
||||||
/// Create a path suffix from a name.
|
/// Create a path suffix from a name.
|
||||||
@@ -124,8 +126,14 @@ pub fn generate_tree_node_field<S: LanguageSyntax>(
|
|||||||
syntax.constructor(&field.rust_type, &path_expr)
|
syntax.constructor(&field.rust_type, &path_expr)
|
||||||
}
|
}
|
||||||
} else if let Some(accessor) = metadata.find_index_set_pattern(&field.indexes) {
|
} else if let Some(accessor) = metadata.find_index_set_pattern(&field.indexes) {
|
||||||
let path_expr = syntax.path_expr("base_path", &path_suffix(child_name));
|
// Leaf field - use actual metric name if provided
|
||||||
syntax.constructor(&accessor.name, &path_expr)
|
if let Some(metric_name) = pattern_base {
|
||||||
|
let path = syntax.string_literal(metric_name);
|
||||||
|
syntax.constructor(&accessor.name, &path)
|
||||||
|
} else {
|
||||||
|
let path_expr = syntax.path_expr("base_path", &path_suffix(child_name));
|
||||||
|
syntax.constructor(&accessor.name, &path_expr)
|
||||||
|
}
|
||||||
} else if field.is_branch() {
|
} else if field.is_branch() {
|
||||||
// Non-pattern branch - instantiate the nested struct
|
// Non-pattern branch - instantiate the nested struct
|
||||||
let path_expr = syntax.path_expr("base_path", &path_suffix(child_name));
|
let path_expr = syntax.path_expr("base_path", &path_suffix(child_name));
|
||||||
@@ -140,3 +148,53 @@ pub fn generate_tree_node_field<S: LanguageSyntax>(
|
|||||||
|
|
||||||
writeln!(output, "{}", syntax.field_init(indent, &field_name, &type_ann, &value)).unwrap();
|
writeln!(output, "{}", syntax.field_init(indent, &field_name, &type_ann, &value)).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generate a leaf field using the actual metric name from the TreeNode::Leaf.
|
||||||
|
///
|
||||||
|
/// This is the shared implementation for all language backends. It uses
|
||||||
|
/// `leaf.name()` directly to get the correct metric name, avoiding any
|
||||||
|
/// path concatenation that could produce incorrect names.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `output` - The string buffer to write to
|
||||||
|
/// * `syntax` - The language syntax implementation
|
||||||
|
/// * `client_expr` - The client expression (e.g., "client.clone()", "this", "client")
|
||||||
|
/// * `tree_field_name` - The field name from the tree structure
|
||||||
|
/// * `leaf` - The Leaf node containing the actual metric name and indexes
|
||||||
|
/// * `metadata` - Client metadata for looking up index patterns
|
||||||
|
/// * `indent` - Indentation string
|
||||||
|
pub fn generate_leaf_field<S: LanguageSyntax>(
|
||||||
|
output: &mut String,
|
||||||
|
syntax: &S,
|
||||||
|
client_expr: &str,
|
||||||
|
tree_field_name: &str,
|
||||||
|
leaf: &MetricLeafWithSchema,
|
||||||
|
metadata: &ClientMetadata,
|
||||||
|
indent: &str,
|
||||||
|
) {
|
||||||
|
let field_name = syntax.field_name(tree_field_name);
|
||||||
|
let accessor = metadata
|
||||||
|
.find_index_set_pattern(leaf.indexes())
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
panic!(
|
||||||
|
"Metric '{}' has no matching index pattern. All metrics must be indexed.",
|
||||||
|
leaf.name()
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let type_ann = metadata.field_type_annotation_from_leaf(leaf, syntax.generic_syntax());
|
||||||
|
let metric_name = syntax.string_literal(leaf.name());
|
||||||
|
let value = format!(
|
||||||
|
"{}({}, {})",
|
||||||
|
syntax.constructor_name(&accessor.name),
|
||||||
|
client_expr,
|
||||||
|
metric_name
|
||||||
|
);
|
||||||
|
|
||||||
|
writeln!(
|
||||||
|
output,
|
||||||
|
"{}",
|
||||||
|
syntax.field_init(indent, &field_name, &type_ann, &value)
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ use std::fmt::Write;
|
|||||||
use brk_types::TreeNode;
|
use brk_types::TreeNode;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ClientMetadata, Endpoint, PatternField, child_type_name, get_first_leaf_name, get_node_fields,
|
ClientMetadata, Endpoint, JavaScriptSyntax, PatternField, child_type_name, generate_leaf_field,
|
||||||
get_pattern_instance_base, infer_accumulated_name, prepare_tree_node, to_camel_case,
|
get_first_leaf_name, get_node_fields, get_pattern_instance_base, infer_accumulated_name,
|
||||||
|
prepare_tree_node, to_camel_case,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::api::generate_api_methods;
|
use super::api::generate_api_methods;
|
||||||
@@ -142,33 +143,24 @@ fn generate_tree_initializer(
|
|||||||
) {
|
) {
|
||||||
let indent_str = " ".repeat(indent);
|
let indent_str = " ".repeat(indent);
|
||||||
|
|
||||||
|
let syntax = JavaScriptSyntax;
|
||||||
if let TreeNode::Branch(children) = node {
|
if let TreeNode::Branch(children) = node {
|
||||||
for (i, (child_name, child_node)) in children.iter().enumerate() {
|
for (child_name, child_node) in children.iter() {
|
||||||
let field_name = to_camel_case(child_name);
|
|
||||||
let comma = if i < children.len() - 1 { "," } else { "" };
|
|
||||||
|
|
||||||
match child_node {
|
match child_node {
|
||||||
TreeNode::Leaf(leaf) => {
|
TreeNode::Leaf(leaf) => {
|
||||||
let accessor = metadata
|
// Use shared helper for leaf fields
|
||||||
.find_index_set_pattern(leaf.indexes())
|
generate_leaf_field(
|
||||||
.unwrap_or_else(|| {
|
|
||||||
panic!(
|
|
||||||
"Metric '{}' has no matching index pattern. All metrics must be indexed.",
|
|
||||||
leaf.name()
|
|
||||||
)
|
|
||||||
});
|
|
||||||
writeln!(
|
|
||||||
output,
|
output,
|
||||||
"{}{}: create{}(this, '{}'){}",
|
&syntax,
|
||||||
indent_str,
|
"this",
|
||||||
field_name,
|
child_name,
|
||||||
accessor.name,
|
leaf,
|
||||||
leaf.name(),
|
metadata,
|
||||||
comma
|
&indent_str,
|
||||||
)
|
);
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
TreeNode::Branch(grandchildren) => {
|
TreeNode::Branch(grandchildren) => {
|
||||||
|
let field_name = to_camel_case(child_name);
|
||||||
let child_fields = get_node_fields(grandchildren, pattern_lookup);
|
let child_fields = get_node_fields(grandchildren, pattern_lookup);
|
||||||
// Only use pattern factory if pattern is parameterizable
|
// Only use pattern factory if pattern is parameterizable
|
||||||
let pattern_name = pattern_lookup
|
let pattern_name = pattern_lookup
|
||||||
@@ -179,8 +171,8 @@ fn generate_tree_initializer(
|
|||||||
let arg = get_pattern_instance_base(child_node);
|
let arg = get_pattern_instance_base(child_node);
|
||||||
writeln!(
|
writeln!(
|
||||||
output,
|
output,
|
||||||
"{}{}: create{}(this, '{}'){}",
|
"{}{}: create{}(this, '{}'),",
|
||||||
indent_str, field_name, pattern_name, arg, comma
|
indent_str, field_name, pattern_name, arg
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
} else {
|
} else {
|
||||||
@@ -195,7 +187,7 @@ fn generate_tree_initializer(
|
|||||||
pattern_lookup,
|
pattern_lookup,
|
||||||
metadata,
|
metadata,
|
||||||
);
|
);
|
||||||
writeln!(output, "{}}}{}", indent_str, comma).unwrap();
|
writeln!(output, "{}}},", indent_str).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ use std::fmt::Write;
|
|||||||
use brk_types::TreeNode;
|
use brk_types::TreeNode;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ClientMetadata, PatternField, child_type_name, get_node_fields, get_pattern_instance_base,
|
ClientMetadata, PatternField, PythonSyntax, child_type_name, generate_leaf_field,
|
||||||
prepare_tree_node, to_snake_case,
|
get_node_fields, get_pattern_instance_base, prepare_tree_node, to_snake_case,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::client::field_type_with_generic;
|
use super::client::field_type_with_generic;
|
||||||
@@ -50,7 +50,8 @@ fn generate_tree_class(
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
for ((field, child_fields_opt), (_child_name, child_node)) in
|
let syntax = PythonSyntax;
|
||||||
|
for ((field, child_fields_opt), (child_name, child_node)) in
|
||||||
ctx.fields_with_child_info.iter().zip(ctx.children.iter())
|
ctx.fields_with_child_info.iter().zip(ctx.children.iter())
|
||||||
{
|
{
|
||||||
// Look up type parameter for generic patterns
|
// Look up type parameter for generic patterns
|
||||||
@@ -72,14 +73,8 @@ fn generate_tree_class(
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
} else if let TreeNode::Leaf(leaf) = child_node {
|
} else if let TreeNode::Leaf(leaf) = child_node {
|
||||||
// Leaf node: use actual metric name
|
// Leaf node: use shared helper
|
||||||
let accessor = metadata.find_index_set_pattern(&field.indexes).unwrap();
|
generate_leaf_field(output, &syntax, "client", child_name, leaf, metadata, " ");
|
||||||
writeln!(
|
|
||||||
output,
|
|
||||||
" self.{}: {} = {}(client, '{}')",
|
|
||||||
field_name_py, py_type, accessor.name, leaf.name()
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
} else if field.is_branch() {
|
} else if field.is_branch() {
|
||||||
// Non-parameterizable pattern or regular branch: generate inline class
|
// Non-parameterizable pattern or regular branch: generate inline class
|
||||||
let inline_class = child_type_name(name, &field.name);
|
let inline_class = child_type_name(name, &field.name);
|
||||||
|
|||||||
@@ -295,13 +295,14 @@ pub fn schema_to_python_type_ctx(schema: &Value, current_type: Option<&str>) ->
|
|||||||
"Any".to_string()
|
"Any".to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert JS-style type to Python type (e.g., "Txid[]" -> "List[Txid]", "number" -> "int")
|
/// Convert JS-style type to Python type (e.g., "Txid[]" -> "List[Txid]", "integer" -> "int")
|
||||||
pub fn js_type_to_python(js_type: &str) -> String {
|
pub fn js_type_to_python(js_type: &str) -> String {
|
||||||
if let Some(inner) = js_type.strip_suffix("[]") {
|
if let Some(inner) = js_type.strip_suffix("[]") {
|
||||||
format!("List[{}]", js_type_to_python(inner))
|
format!("List[{}]", js_type_to_python(inner))
|
||||||
} else {
|
} else {
|
||||||
match js_type {
|
match js_type {
|
||||||
"number" => "int".to_string(),
|
"integer" => "int".to_string(),
|
||||||
|
"number" => "float".to_string(),
|
||||||
"boolean" => "bool".to_string(),
|
"boolean" => "bool".to_string(),
|
||||||
"string" => "str".to_string(),
|
"string" => "str".to_string(),
|
||||||
"null" => "None".to_string(),
|
"null" => "None".to_string(),
|
||||||
|
|||||||
@@ -162,12 +162,12 @@ impl<T: DeserializeOwned> Endpoint<T> {{
|
|||||||
}}
|
}}
|
||||||
|
|
||||||
/// Fetch all data points for this metric/index.
|
/// Fetch all data points for this metric/index.
|
||||||
pub fn get(&self) -> Result<Vec<T>> {{
|
pub fn get(&self) -> Result<MetricData<T>> {{
|
||||||
self.client.get(&self.path())
|
self.client.get(&self.path())
|
||||||
}}
|
}}
|
||||||
|
|
||||||
/// Fetch data points within a range.
|
/// Fetch data points within a range.
|
||||||
pub fn range(&self, from: Option<i64>, to: Option<i64>) -> Result<Vec<T>> {{
|
pub fn range(&self, from: Option<i64>, to: Option<i64>) -> Result<MetricData<T>> {{
|
||||||
let mut params = Vec::new();
|
let mut params = Vec::new();
|
||||||
if let Some(f) = from {{ params.push(format!("from={{}}", f)); }}
|
if let Some(f) = from {{ params.push(format!("from={{}}", f)); }}
|
||||||
if let Some(t) = to {{ params.push(format!("to={{}}", t)); }}
|
if let Some(t) = to {{ params.push(format!("to={{}}", t)); }}
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ use brk_types::TreeNode;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ClientMetadata, LanguageSyntax, PatternField, RustSyntax, child_type_name,
|
ClientMetadata, LanguageSyntax, PatternField, RustSyntax, child_type_name,
|
||||||
generate_tree_node_field, get_node_fields, get_pattern_instance_base, prepare_tree_node,
|
generate_leaf_field, generate_tree_node_field, get_node_fields, get_pattern_instance_base,
|
||||||
to_snake_case,
|
prepare_tree_node, to_snake_case,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::client::field_type_with_generic;
|
use super::client::field_type_with_generic;
|
||||||
@@ -100,16 +100,21 @@ fn generate_tree_node(
|
|||||||
field_name, child_struct, path_expr
|
field_name, child_struct, path_expr
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
} else {
|
} else if let TreeNode::Leaf(leaf) = child_node {
|
||||||
// Leaf field
|
// Leaf field - use shared helper
|
||||||
generate_tree_node_field(
|
generate_leaf_field(
|
||||||
output,
|
output,
|
||||||
&syntax,
|
&syntax,
|
||||||
field_info,
|
"client.clone()",
|
||||||
|
child_name,
|
||||||
|
leaf,
|
||||||
metadata,
|
metadata,
|
||||||
" ",
|
" ",
|
||||||
child_name,
|
);
|
||||||
None,
|
} else {
|
||||||
|
panic!(
|
||||||
|
"Field '{}' is a leaf with no TreeNode::Leaf. This shouldn't happen.",
|
||||||
|
field_info.name
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ pub fn js_type_to_rust(js_type: &str) -> String {
|
|||||||
} else {
|
} else {
|
||||||
match js_type {
|
match js_type {
|
||||||
"string" => "String".to_string(),
|
"string" => "String".to_string(),
|
||||||
|
"integer" => "i64".to_string(),
|
||||||
"number" => "f64".to_string(),
|
"number" => "f64".to_string(),
|
||||||
"boolean" => "bool".to_string(),
|
"boolean" => "bool".to_string(),
|
||||||
"*" => "serde_json::Value".to_string(),
|
"*" => "serde_json::Value".to_string(),
|
||||||
|
|||||||
@@ -102,4 +102,11 @@ pub trait LanguageSyntax {
|
|||||||
/// - Python/JavaScript: `'value'` (single quotes)
|
/// - Python/JavaScript: `'value'` (single quotes)
|
||||||
/// - Rust: `"value"` (double quotes)
|
/// - Rust: `"value"` (double quotes)
|
||||||
fn string_literal(&self, value: &str) -> String;
|
fn string_literal(&self, value: &str) -> String;
|
||||||
|
|
||||||
|
/// Get the constructor name/prefix for a type.
|
||||||
|
///
|
||||||
|
/// - Python: `TypeName`
|
||||||
|
/// - JavaScript: `createTypeName`
|
||||||
|
/// - Rust: `TypeName::new`
|
||||||
|
fn constructor_name(&self, type_name: &str) -> String;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
use std::collections::{BTreeSet, HashMap};
|
use std::collections::{BTreeSet, HashMap};
|
||||||
|
|
||||||
use brk_query::Vecs;
|
use brk_query::Vecs;
|
||||||
use brk_types::Index;
|
use brk_types::{Index, MetricLeafWithSchema};
|
||||||
|
|
||||||
use super::{GenericSyntax, IndexSetPattern, PatternField, StructuralPattern, extract_inner_type};
|
use super::{GenericSyntax, IndexSetPattern, PatternField, StructuralPattern, extract_inner_type};
|
||||||
use crate::analysis;
|
use crate::analysis;
|
||||||
@@ -158,4 +158,21 @@ impl ClientMetadata {
|
|||||||
syntax.wrap("MetricNode", &value_type)
|
syntax.wrap("MetricNode", &value_type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generate type annotation for a leaf node with language-specific syntax.
|
||||||
|
///
|
||||||
|
/// This is a simpler version of `field_type_annotation` that works directly
|
||||||
|
/// with a `MetricLeafWithSchema` node instead of a `PatternField`.
|
||||||
|
pub fn field_type_annotation_from_leaf(
|
||||||
|
&self,
|
||||||
|
leaf: &MetricLeafWithSchema,
|
||||||
|
syntax: GenericSyntax,
|
||||||
|
) -> String {
|
||||||
|
let value_type = leaf.kind().to_string();
|
||||||
|
if let Some(accessor) = self.find_index_set_pattern(leaf.indexes()) {
|
||||||
|
syntax.wrap(&accessor.name, &value_type)
|
||||||
|
} else {
|
||||||
|
syntax.wrap("MetricNode", &value_type)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ build = "build.rs"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
brk_alloc = { workspace = true }
|
brk_alloc = { workspace = true }
|
||||||
brk_bindgen = { workspace = true }
|
|
||||||
brk_computer = { workspace = true }
|
brk_computer = { workspace = true }
|
||||||
brk_error = { workspace = true }
|
brk_error = { workspace = true }
|
||||||
brk_fetcher = { workspace = true }
|
brk_fetcher = { workspace = true }
|
||||||
|
|||||||
@@ -15,6 +15,3 @@ brk_cohort = { workspace = true }
|
|||||||
brk_types = { workspace = true }
|
brk_types = { workspace = true }
|
||||||
minreq = { workspace = true }
|
minreq = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
serde_json = { workspace = true }
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ fn main() -> brk_client::Result<()> {
|
|||||||
.close
|
.close
|
||||||
.by
|
.by
|
||||||
.dateindex()
|
.dateindex()
|
||||||
.range(None, Some(-3))?;
|
.range(Some(-3), None)?;
|
||||||
println!("Last 3 price close values: {:?}", price_close);
|
println!("Last 3 price close values: {:?}", price_close);
|
||||||
|
|
||||||
// Fetch block data
|
// Fetch block data
|
||||||
@@ -32,11 +32,22 @@ fn main() -> brk_client::Result<()> {
|
|||||||
.block_count
|
.block_count
|
||||||
.sum
|
.sum
|
||||||
.by
|
.by
|
||||||
.height()
|
.dateindex()
|
||||||
.range(None, Some(-3))?;
|
.range(Some(-3), None)?;
|
||||||
println!("Last 3 block count values: {:?}", block_count);
|
println!("Last 3 block count values: {:?}", block_count);
|
||||||
|
|
||||||
// Fetch supply data
|
// Fetch supply data
|
||||||
|
//
|
||||||
|
dbg!(
|
||||||
|
client
|
||||||
|
.tree()
|
||||||
|
.supply
|
||||||
|
.circulating
|
||||||
|
.bitcoin
|
||||||
|
.by
|
||||||
|
.dateindex()
|
||||||
|
.path()
|
||||||
|
);
|
||||||
let circulating = client
|
let circulating = client
|
||||||
.tree()
|
.tree()
|
||||||
.supply
|
.supply
|
||||||
@@ -44,12 +55,12 @@ fn main() -> brk_client::Result<()> {
|
|||||||
.bitcoin
|
.bitcoin
|
||||||
.by
|
.by
|
||||||
.dateindex()
|
.dateindex()
|
||||||
.range(None, Some(-3))?;
|
.range(Some(-3), None)?;
|
||||||
println!("Last 3 circulating supply values: {:?}", circulating);
|
println!("Last 3 circulating supply values: {:?}", circulating);
|
||||||
|
|
||||||
// Using generic metric fetching
|
// Using generic metric fetching
|
||||||
let metricdata =
|
let metricdata =
|
||||||
client.get_metric_by_index("dateindex", "price_close", None, None, None, None)?;
|
client.get_metric_by_index("dateindex", "price_close", None, None, Some("-3"), None)?;
|
||||||
println!("Generic fetch result count: {}", metricdata.data.len());
|
println!("Generic fetch result count: {}", metricdata.data.len());
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -1,15 +1,54 @@
|
|||||||
//! Comprehensive test that fetches all endpoints in the tree.
|
//! Comprehensive test that fetches all endpoints in the tree.
|
||||||
//!
|
//!
|
||||||
//! This example demonstrates how to iterate over all metrics and fetch data
|
//! This example demonstrates how to recursively traverse the metrics catalog tree
|
||||||
//! from each endpoint. Run with: cargo run --example test_all_endpoints
|
//! and fetch data from each endpoint. Run with: cargo run --example tree
|
||||||
|
|
||||||
use brk_client::{BrkClient, Index};
|
use brk_client::BrkClient;
|
||||||
|
use brk_types::{Index, TreeNode};
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
|
/// A collected metric with its path and available indexes.
|
||||||
|
struct CollectedMetric {
|
||||||
|
path: String,
|
||||||
|
name: String,
|
||||||
|
indexes: BTreeSet<Index>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recursively collect all metrics from the tree.
|
||||||
|
fn collect_metrics(node: &TreeNode, path: &str) -> Vec<CollectedMetric> {
|
||||||
|
let mut metrics = Vec::new();
|
||||||
|
|
||||||
|
match node {
|
||||||
|
TreeNode::Branch(children) => {
|
||||||
|
for (key, child) in children {
|
||||||
|
let child_path = if path.is_empty() {
|
||||||
|
key.clone()
|
||||||
|
} else {
|
||||||
|
format!("{}.{}", path, key)
|
||||||
|
};
|
||||||
|
metrics.extend(collect_metrics(child, &child_path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TreeNode::Leaf(leaf) => {
|
||||||
|
metrics.push(CollectedMetric {
|
||||||
|
path: path.to_string(),
|
||||||
|
name: leaf.name().to_string(),
|
||||||
|
indexes: leaf.indexes().clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
metrics
|
||||||
|
}
|
||||||
|
|
||||||
fn main() -> brk_client::Result<()> {
|
fn main() -> brk_client::Result<()> {
|
||||||
let client = BrkClient::new("http://localhost:3110");
|
let client = BrkClient::new("http://localhost:3110");
|
||||||
|
|
||||||
// Get all metrics from the tree
|
// Get the metrics catalog tree
|
||||||
let metrics = client.all_metrics();
|
let tree = client.get_metrics_catalog()?;
|
||||||
|
|
||||||
|
// Recursively collect all metrics
|
||||||
|
let metrics = collect_metrics(&tree, "");
|
||||||
println!("\nFound {} metrics", metrics.len());
|
println!("\nFound {} metrics", metrics.len());
|
||||||
|
|
||||||
let mut success = 0;
|
let mut success = 0;
|
||||||
@@ -17,36 +56,28 @@ fn main() -> brk_client::Result<()> {
|
|||||||
let mut errors: Vec<String> = Vec::new();
|
let mut errors: Vec<String> = Vec::new();
|
||||||
|
|
||||||
for metric in &metrics {
|
for metric in &metrics {
|
||||||
let name = metric.name();
|
for index in &metric.indexes {
|
||||||
let indexes = metric.indexes();
|
let index_str = index.serialize_long();
|
||||||
|
match client.get_metric_by_index(index_str, &metric.name, None, None, Some("-3"), None)
|
||||||
for index in indexes {
|
{
|
||||||
let path = format!("/api/metric/{}/{}", name, index.serialize_long());
|
|
||||||
match client.get::<serde_json::Value>(&format!("{}?to=-3", path)) {
|
|
||||||
Ok(data) => {
|
Ok(data) => {
|
||||||
let count = data
|
let count = data.data.len();
|
||||||
.get("data")
|
|
||||||
.and_then(|d| d.as_array())
|
|
||||||
.map(|a| a.len())
|
|
||||||
.unwrap_or(0);
|
|
||||||
if count != 3 {
|
if count != 3 {
|
||||||
failed += 1;
|
failed += 1;
|
||||||
let error_msg = format!(
|
let error_msg = format!(
|
||||||
"FAIL: {}.{} -> expected 3, got {}",
|
"FAIL: {}.by.{} -> expected 3, got {}",
|
||||||
name,
|
metric.path, index_str, count
|
||||||
index.serialize_long(),
|
|
||||||
count
|
|
||||||
);
|
);
|
||||||
errors.push(error_msg.clone());
|
errors.push(error_msg.clone());
|
||||||
println!("{}", error_msg);
|
println!("{}", error_msg);
|
||||||
} else {
|
} else {
|
||||||
success += 1;
|
success += 1;
|
||||||
println!("OK: {}.{} -> {} items", name, index.serialize_long(), count);
|
println!("OK: {}.by.{} -> {} items", metric.path, index_str, count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
failed += 1;
|
failed += 1;
|
||||||
let error_msg = format!("FAIL: {}.{} -> {}", name, index.serialize_long(), e);
|
let error_msg = format!("FAIL: {}.by.{} -> {}", metric.path, index_str, e);
|
||||||
errors.push(error_msg.clone());
|
errors.push(error_msg.clone());
|
||||||
println!("{}", error_msg);
|
println!("{}", error_msg);
|
||||||
}
|
}
|
||||||
|
|||||||
+1034
-2486
File diff suppressed because it is too large
Load Diff
@@ -25,8 +25,5 @@ derive_more = { workspace = true }
|
|||||||
jiff = { workspace = true }
|
jiff = { workspace = true }
|
||||||
# quickmatch = { path = "../../../quickmatch" }
|
# quickmatch = { path = "../../../quickmatch" }
|
||||||
quickmatch = "0.1.8"
|
quickmatch = "0.1.8"
|
||||||
schemars = { workspace = true }
|
|
||||||
serde = { workspace = true }
|
|
||||||
serde_json = { workspace = true }
|
|
||||||
tokio = { workspace = true, optional = true }
|
tokio = { workspace = true, optional = true }
|
||||||
vecdb = { workspace = true }
|
vecdb = { workspace = true }
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ use std::fmt::Display;
|
|||||||
|
|
||||||
use derive_more::Deref;
|
use derive_more::Deref;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::Deserialize;
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// Metric name
|
/// Metric name
|
||||||
#[derive(Debug, Clone, Deref, Deserialize, JsonSchema)]
|
#[derive(Debug, Clone, Deref, Serialize, Deserialize, JsonSchema)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
#[schemars(
|
#[schemars(
|
||||||
with = "String",
|
with = "String",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use vecdb::AnySerializableVec;
|
|||||||
///
|
///
|
||||||
/// All metric data endpoints return this structure when format is JSON.
|
/// All metric data endpoints return this structure when format is JSON.
|
||||||
/// This type is not instantiated - use `MetricData::serialize()` to write JSON bytes directly.
|
/// This type is not instantiated - use `MetricData::serialize()` to write JSON bytes directly.
|
||||||
#[derive(JsonSchema, Deserialize)]
|
#[derive(Debug, JsonSchema, Deserialize)]
|
||||||
pub struct MetricData<T = Value> {
|
pub struct MetricData<T = Value> {
|
||||||
/// Total number of data points in the metric
|
/// Total number of data points in the metric
|
||||||
pub total: usize,
|
pub total: usize,
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::Deserialize;
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{Index, Metric};
|
use crate::{Index, Metric};
|
||||||
|
|
||||||
#[derive(Deserialize, JsonSchema)]
|
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
|
||||||
pub struct MetricWithIndex {
|
pub struct MetricWithIndex {
|
||||||
/// Metric name
|
/// Metric name
|
||||||
pub metric: Metric,
|
pub metric: Metric,
|
||||||
@@ -11,3 +11,27 @@ pub struct MetricWithIndex {
|
|||||||
/// Aggregation index
|
/// Aggregation index
|
||||||
pub index: Index,
|
pub index: Index,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl MetricWithIndex {
|
||||||
|
pub fn new(metric: impl Into<Metric>, index: Index) -> Self {
|
||||||
|
Self {
|
||||||
|
metric: metric.into(),
|
||||||
|
index,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(Metric, Index)> for MetricWithIndex {
|
||||||
|
fn from((metric, index): (Metric, Index)) -> Self {
|
||||||
|
Self { metric, index }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(&str, Index)> for MetricWithIndex {
|
||||||
|
fn from((metric, index): (&str, Index)) -> Self {
|
||||||
|
Self {
|
||||||
|
metric: metric.into(),
|
||||||
|
index,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+2232
-3057
File diff suppressed because it is too large
Load Diff
@@ -168,7 +168,7 @@ Main BRK client with catalog tree and API methods.
|
|||||||
#### \_\_init\_\_
|
#### \_\_init\_\_
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def __init__(base_url: str = "http://localhost:3000", timeout: float = 30.0)
|
def __init__(base_url: str = 'http://localhost:3000', timeout: float = 30.0)
|
||||||
```
|
```
|
||||||
|
|
||||||
<a id="brk_client.BrkClient.get_address"></a>
|
<a id="brk_client.BrkClient.get_address"></a>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Python client for the [Bitcoin Research Kit](https://bitcoinresearchkit.org) - a suite of tools to extract, compute and display data stored on a Bitcoin Core node.
|
Python client for the [Bitcoin Research Kit](https://bitcoinresearchkit.org) - a suite of tools to extract, compute and display data stored on a Bitcoin Core node.
|
||||||
|
|
||||||
[API Documentation](/docs/API.md)
|
[Documentation](/DOCS.md)
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
+2578
-4625
File diff suppressed because it is too large
Load Diff
@@ -1,2 +1,3 @@
|
|||||||
|
uvx pydoc-markdown > DOCS.md
|
||||||
uv build
|
uv build
|
||||||
uvx uv-publish
|
uvx uv-publish
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ npm publish --access public
|
|||||||
echo ""
|
echo ""
|
||||||
echo "=== Python package ==="
|
echo "=== Python package ==="
|
||||||
cd "$ROOT_DIR/packages/brk_client"
|
cd "$ROOT_DIR/packages/brk_client"
|
||||||
uv run pydoc-markdown > docs/API.md
|
uvx pydoc-markdown > docs/API.md
|
||||||
uv build
|
uv build
|
||||||
uv publish
|
uv publish
|
||||||
|
|
||||||
|
|||||||
@@ -1563,8 +1563,9 @@
|
|||||||
<link rel="modulepreload" href="/scripts/entry.fe229b42.js">
|
<link rel="modulepreload" href="/scripts/entry.fe229b42.js">
|
||||||
<link rel="modulepreload" href="/scripts/lazy.1ae52534.js">
|
<link rel="modulepreload" href="/scripts/lazy.1ae52534.js">
|
||||||
<link rel="modulepreload" href="/scripts/main.22a5bd79.js">
|
<link rel="modulepreload" href="/scripts/main.22a5bd79.js">
|
||||||
<link rel="modulepreload" href="/scripts/modules/brk-client/index.d7a5bd12.js">
|
<link rel="modulepreload" href="/scripts/modules/brk-client/index.469dfd9f.js">
|
||||||
<link rel="modulepreload" href="/scripts/modules/brk-client/test.119c2b6e.js">
|
<link rel="modulepreload" href="/scripts/modules/brk-client/tests/basic.b92ff866.js">
|
||||||
|
<link rel="modulepreload" href="/scripts/modules/brk-client/tests/tree.ba9474f7.js">
|
||||||
<link rel="modulepreload" href="/scripts/modules/lean-qr/2.6.1/index.09195c13.mjs">
|
<link rel="modulepreload" href="/scripts/modules/lean-qr/2.6.1/index.09195c13.mjs">
|
||||||
<link rel="modulepreload" href="/scripts/modules/leeoniya-ufuzzy/1.0.19/dist/uFuzzy.803b7fb0.mjs">
|
<link rel="modulepreload" href="/scripts/modules/leeoniya-ufuzzy/1.0.19/dist/uFuzzy.803b7fb0.mjs">
|
||||||
<link rel="modulepreload" href="/scripts/modules/lightweight-charts/5.0.9/dist/lightweight-charts.standalone.production.1e264451.mjs">
|
<link rel="modulepreload" href="/scripts/modules/lightweight-charts/5.0.9/dist/lightweight-charts.standalone.production.1e264451.mjs">
|
||||||
@@ -1633,8 +1634,9 @@
|
|||||||
"/scripts/entry.js": "/scripts/entry.fe229b42.js",
|
"/scripts/entry.js": "/scripts/entry.fe229b42.js",
|
||||||
"/scripts/lazy.js": "/scripts/lazy.1ae52534.js",
|
"/scripts/lazy.js": "/scripts/lazy.1ae52534.js",
|
||||||
"/scripts/main.js": "/scripts/main.22a5bd79.js",
|
"/scripts/main.js": "/scripts/main.22a5bd79.js",
|
||||||
"/scripts/modules/brk-client/index.js": "/scripts/modules/brk-client/index.d7a5bd12.js",
|
"/scripts/modules/brk-client/index.js": "/scripts/modules/brk-client/index.469dfd9f.js",
|
||||||
"/scripts/modules/brk-client/test.js": "/scripts/modules/brk-client/test.119c2b6e.js",
|
"/scripts/modules/brk-client/tests/basic.js": "/scripts/modules/brk-client/tests/basic.b92ff866.js",
|
||||||
|
"/scripts/modules/brk-client/tests/tree.js": "/scripts/modules/brk-client/tests/tree.ba9474f7.js",
|
||||||
"/scripts/modules/lean-qr/2.6.1/index.mjs": "/scripts/modules/lean-qr/2.6.1/index.09195c13.mjs",
|
"/scripts/modules/lean-qr/2.6.1/index.mjs": "/scripts/modules/lean-qr/2.6.1/index.09195c13.mjs",
|
||||||
"/scripts/modules/leeoniya-ufuzzy/1.0.19/dist/uFuzzy.mjs": "/scripts/modules/leeoniya-ufuzzy/1.0.19/dist/uFuzzy.803b7fb0.mjs",
|
"/scripts/modules/leeoniya-ufuzzy/1.0.19/dist/uFuzzy.mjs": "/scripts/modules/leeoniya-ufuzzy/1.0.19/dist/uFuzzy.803b7fb0.mjs",
|
||||||
"/scripts/modules/lightweight-charts/5.0.9/dist/lightweight-charts.standalone.production.mjs": "/scripts/modules/lightweight-charts/5.0.9/dist/lightweight-charts.standalone.production.1e264451.mjs",
|
"/scripts/modules/lightweight-charts/5.0.9/dist/lightweight-charts.standalone.production.mjs": "/scripts/modules/lightweight-charts/5.0.9/dist/lightweight-charts.standalone.production.1e264451.mjs",
|
||||||
|
|||||||
Reference in New Issue
Block a user