global: snapshot

This commit is contained in:
nym21
2026-01-12 22:43:56 +01:00
parent b675b70067
commit 5ffb66c0dc
39 changed files with 8207 additions and 11957 deletions

View File

@@ -63,57 +63,65 @@ class BrkError extends Error {{
* @property {{number}} end - End index (exclusive)
* @property {{T[]}} data - The metric data
*/
/** @typedef {{MetricData<unknown>}} AnyMetricData */
/** @typedef {{MetricData<any>}} AnyMetricData */
/**
* Thenable interface for await support.
* @template T
* @typedef {{(onfulfilled?: (value: MetricData<T>) => MetricData<T>, onrejected?: (reason: Error) => never) => Promise<MetricData<T>>}} Thenable
*/
/**
* Metric endpoint builder. Callable (returns itself) so both .by.dateindex and .by.dateindex() work.
* @template T
* @typedef {{Object}} MetricEndpointBuilder
* @property {{(n: number) => RangeBuilder<T>}} first - Fetch first n data points
* @property {{(n: number) => RangeBuilder<T>}} last - Fetch last n data points
* @property {{(start: number, end: number) => RangeBuilder<T>}} range - Set explicit range [start, end)
* @property {{(start: number) => FromBuilder<T>}} from - Set start position, chain with take() or to()
* @property {{(end: number) => ToBuilder<T>}} to - Set end position, chain with takeLast() or from()
* @property {{(onUpdate?: (value: MetricData<T>) => void) => Promise<MetricData<T>>}} json - Execute and return JSON (all data)
* @property {{() => Promise<string>}} csv - Execute and return CSV (all data)
* @property {{(index: number) => SingleItemBuilder<T>}} get - Get single item at index
* @property {{(start?: number, end?: number) => RangeBuilder<T>}} slice - Slice like Array.slice
* @property {{(n: number) => RangeBuilder<T>}} first - Get first n items
* @property {{(n: number) => RangeBuilder<T>}} last - Get last n items
* @property {{(n: number) => SkippedBuilder<T>}} skip - Skip first n items, chain with take()
* @property {{(onUpdate?: (value: MetricData<T>) => void) => Promise<MetricData<T>>}} fetch - Fetch all data
* @property {{() => Promise<string>}} fetchCsv - Fetch all data as CSV
* @property {{Thenable<T>}} then - Thenable (await endpoint)
* @property {{string}} path - The endpoint path
*/
/** @typedef {{MetricEndpointBuilder<unknown>}} AnyMetricEndpointBuilder */
/** @typedef {{MetricEndpointBuilder<any>}} AnyMetricEndpointBuilder */
/**
* @template T
* @typedef {{Object}} FromBuilder
* @property {{(n: number) => RangeBuilder<T>}} take - Take n items from start position
* @property {{(end: number) => RangeBuilder<T>}} to - Set end position
* @property {{(onUpdate?: (value: MetricData<T>) => void) => Promise<MetricData<T>>}} json - Execute and return JSON
* @property {{() => Promise<string>}} csv - Execute and return CSV
* @typedef {{Object}} SingleItemBuilder
* @property {{(onUpdate?: (value: MetricData<T>) => void) => Promise<MetricData<T>>}} fetch - Fetch the item
* @property {{() => Promise<string>}} fetchCsv - Fetch as CSV
* @property {{Thenable<T>}} then - Thenable
*/
/**
* @template T
* @typedef {{Object}} ToBuilder
* @property {{(n: number) => RangeBuilder<T>}} takeLast - Take last n items before end position
* @property {{(start: number) => RangeBuilder<T>}} from - Set start position
* @property {{(onUpdate?: (value: MetricData<T>) => void) => Promise<MetricData<T>>}} json - Execute and return JSON
* @property {{() => Promise<string>}} csv - Execute and return CSV
* @typedef {{Object}} SkippedBuilder
* @property {{(n: number) => RangeBuilder<T>}} take - Take n items after skipped position
* @property {{(onUpdate?: (value: MetricData<T>) => void) => Promise<MetricData<T>>}} fetch - Fetch from skipped position to end
* @property {{() => Promise<string>}} fetchCsv - Fetch as CSV
* @property {{Thenable<T>}} then - Thenable
*/
/**
* @template T
* @typedef {{Object}} RangeBuilder
* @property {{(onUpdate?: (value: MetricData<T>) => void) => Promise<MetricData<T>>}} json - Execute and return JSON
* @property {{() => Promise<string>}} csv - Execute and return CSV
* @property {{(onUpdate?: (value: MetricData<T>) => void) => Promise<MetricData<T>>}} fetch - Fetch the range
* @property {{() => Promise<string>}} fetchCsv - Fetch as CSV
* @property {{Thenable<T>}} then - Thenable
*/
/**
* @template T
* @typedef {{Object}} MetricPattern
* @property {{string}} name - The metric name
* @property {{Partial<Record<Index, MetricEndpointBuilder<T>>>}} by - Index endpoints (lazy getters)
* @property {{Readonly<Partial<Record<Index, MetricEndpointBuilder<T>>>>}} by - Index endpoints as lazy getters. Access via .by.dateindex or .by['dateindex']
* @property {{() => Index[]}} indexes - Get the list of available indexes
* @property {{(index: Index) => MetricEndpointBuilder<T>|undefined}} get - Get an endpoint for a specific index
*/
/** @typedef {{MetricPattern<unknown>}} AnyMetricPattern */
/** @typedef {{MetricPattern<any>}} AnyMetricPattern */
/**
* Create a metric endpoint builder with typestate pattern.
@@ -147,50 +155,46 @@ function _endpoint(client, name, index) {{
* @returns {{RangeBuilder<T>}}
*/
const rangeBuilder = (start, end) => ({{
json(/** @type {{((value: MetricData<T>) => void) | undefined}} */ onUpdate) {{
return client.getJson(buildPath(start, end), onUpdate);
}},
csv() {{ return client.getText(buildPath(start, end, 'csv')); }},
fetch(onUpdate) {{ return client.getJson(buildPath(start, end), onUpdate); }},
fetchCsv() {{ return client.getText(buildPath(start, end, 'csv')); }},
then(resolve, reject) {{ return this.fetch().then(resolve, reject); }},
}});
/**
* @param {{number}} index
* @returns {{SingleItemBuilder<T>}}
*/
const singleItemBuilder = (index) => ({{
fetch(onUpdate) {{ return client.getJson(buildPath(index, index + 1), onUpdate); }},
fetchCsv() {{ return client.getText(buildPath(index, index + 1, 'csv')); }},
then(resolve, reject) {{ return this.fetch().then(resolve, reject); }},
}});
/**
* @param {{number}} start
* @returns {{FromBuilder<T>}}
* @returns {{SkippedBuilder<T>}}
*/
const fromBuilder = (start) => ({{
take(/** @type {{number}} */ n) {{ return rangeBuilder(start, start + n); }},
to(/** @type {{number}} */ end) {{ return rangeBuilder(start, end); }},
json(/** @type {{((value: MetricData<T>) => void) | undefined}} */ onUpdate) {{
return client.getJson(buildPath(start, undefined), onUpdate);
}},
csv() {{ return client.getText(buildPath(start, undefined, 'csv')); }},
const skippedBuilder = (start) => ({{
take(n) {{ return rangeBuilder(start, start + n); }},
fetch(onUpdate) {{ return client.getJson(buildPath(start, undefined), onUpdate); }},
fetchCsv() {{ return client.getText(buildPath(start, undefined, 'csv')); }},
then(resolve, reject) {{ return this.fetch().then(resolve, reject); }},
}});
/**
* @param {{number}} end
* @returns {{ToBuilder<T>}}
*/
const toBuilder = (end) => ({{
takeLast(/** @type {{number}} */ n) {{ return rangeBuilder(end - n, end); }},
from(/** @type {{number}} */ start) {{ return rangeBuilder(start, end); }},
json(/** @type {{((value: MetricData<T>) => void) | undefined}} */ onUpdate) {{
return client.getJson(buildPath(undefined, end), onUpdate);
}},
csv() {{ return client.getText(buildPath(undefined, end, 'csv')); }},
}});
return {{
first(/** @type {{number}} */ n) {{ return rangeBuilder(undefined, n); }},
last(/** @type {{number}} */ n) {{ return rangeBuilder(-n, undefined); }},
range(/** @type {{number}} */ start, /** @type {{number}} */ end) {{ return rangeBuilder(start, end); }},
from(/** @type {{number}} */ start) {{ return fromBuilder(start); }},
to(/** @type {{number}} */ end) {{ return toBuilder(end); }},
json(/** @type {{((value: MetricData<T>) => void) | undefined}} */ onUpdate) {{
return client.getJson(buildPath(), onUpdate);
}},
csv() {{ return client.getText(buildPath(undefined, undefined, 'csv')); }},
/** @type {{MetricEndpointBuilder<T>}} */
const endpoint = {{
get(index) {{ return singleItemBuilder(index); }},
slice(start, end) {{ return rangeBuilder(start, end); }},
first(n) {{ return rangeBuilder(undefined, n); }},
last(n) {{ return rangeBuilder(-n, undefined); }},
skip(n) {{ return skippedBuilder(n); }},
fetch(onUpdate) {{ return client.getJson(buildPath(), onUpdate); }},
fetchCsv() {{ return client.getText(buildPath(undefined, undefined, 'csv')); }},
then(resolve, reject) {{ return this.fetch().then(resolve, reject); }},
get path() {{ return p; }},
}};
return endpoint;
}}
/**
@@ -235,7 +239,7 @@ class BrkClientBase {{
const cachedJson = cachedRes ? await cachedRes.json() : null;
if (cachedJson) onUpdate?.(cachedJson);
if (!globalThis.navigator?.onLine) {{
if (globalThis.navigator?.onLine === false) {{
if (cachedJson) return cachedJson;
throw new BrkError('Offline and no cached data available');
}}
@@ -305,7 +309,11 @@ pub fn generate_static_constants(output: &mut String) {
fn instance_const_camel<T: Serialize>(output: &mut String, name: &str, value: &T) {
let json_value: Value = serde_json::to_value(value).unwrap();
let camel_value = camel_case_top_level_keys(json_value);
write_static_const(output, name, &serde_json::to_string_pretty(&camel_value).unwrap());
write_static_const(
output,
name,
&serde_json::to_string_pretty(&camel_value).unwrap(),
);
}
instance_const_camel(output, "TERM_NAMES", &TERM_NAMES);
@@ -336,13 +344,25 @@ fn camel_case_top_level_keys(value: Value) -> Value {
fn indent_json_const(json: &str) -> String {
json.lines()
.enumerate()
.map(|(i, line)| if i == 0 { line.to_string() } else { format!(" {}", line) })
.map(|(i, line)| {
if i == 0 {
line.to_string()
} else {
format!(" {}", line)
}
})
.collect::<Vec<_>>()
.join("\n")
}
fn write_static_const(output: &mut String, name: &str, json: &str) {
writeln!(output, " {} = /** @type {{const}} */ ({});\n", name, indent_json_const(json)).unwrap();
writeln!(
output,
" {} = /** @type {{const}} */ ({});\n",
name,
indent_json_const(json)
)
.unwrap();
}
/// Generate index accessor factory functions.
@@ -354,14 +374,30 @@ pub fn generate_index_accessors(output: &mut String, patterns: &[IndexSetPattern
writeln!(output, "// Index accessor factory functions\n").unwrap();
for pattern in patterns {
// Use 'readonly' to indicate these are getters (lazy evaluation)
let by_fields: Vec<String> = pattern
.indexes
.iter()
.map(|idx| format!("{}: MetricEndpointBuilder<T>", idx.serialize_long()))
.map(|idx| {
format!(
"readonly {}: MetricEndpointBuilder<T>",
idx.serialize_long()
)
})
.collect();
let by_type = format!("{{ {} }}", by_fields.join(", "));
writeln!(output, "/**").unwrap();
writeln!(
output,
" * Metric pattern with index endpoints as lazy getters."
)
.unwrap();
writeln!(
output,
" * Access via property (.by.dateindex) or bracket notation (.by['dateindex'])."
)
.unwrap();
writeln!(output, " * @template T").unwrap();
writeln!(
output,
@@ -385,7 +421,11 @@ pub fn generate_index_accessors(output: &mut String, patterns: &[IndexSetPattern
for (i, index) in pattern.indexes.iter().enumerate() {
let index_name = index.serialize_long();
let comma = if i < pattern.indexes.len() - 1 { "," } else { "" };
let comma = if i < pattern.indexes.len() - 1 {
","
} else {
""
};
writeln!(
output,
" get {}() {{ return _endpoint(client, name, '{}'); }}{}",
@@ -438,8 +478,12 @@ pub fn generate_structural_patterns(
}
writeln!(output, " * @typedef {{Object}} {}", pattern.name).unwrap();
for field in &pattern.fields {
let js_type =
metadata.field_type_annotation(field, pattern.is_generic, None, GenericSyntax::JAVASCRIPT);
let js_type = metadata.field_type_annotation(
field,
pattern.is_generic,
None,
GenericSyntax::JAVASCRIPT,
);
writeln!(
output,
" * @property {{{}}} {}",
@@ -469,8 +513,17 @@ pub fn generate_structural_patterns(
writeln!(output, " * @returns {{{}}}", return_type).unwrap();
writeln!(output, " */").unwrap();
let param_name = if is_parameterizable { "acc" } else { "basePath" };
writeln!(output, "function create{}(client, {}) {{", pattern.name, param_name).unwrap();
let param_name = if is_parameterizable {
"acc"
} else {
"basePath"
};
writeln!(
output,
"function create{}(client, {}) {{",
pattern.name, param_name
)
.unwrap();
writeln!(output, " return {{").unwrap();
let syntax = JavaScriptSyntax;

View File

@@ -6,7 +6,7 @@ use std::fmt::Write;
use brk_types::TreeNode;
use crate::{
ClientMetadata, Endpoint, GenericSyntax, JavaScriptSyntax, PatternField, child_type_name,
ClientMetadata, Endpoint, GenericSyntax, JavaScriptSyntax, PatternField,
generate_leaf_field, get_first_leaf_name, get_node_fields, get_pattern_instance_base,
infer_accumulated_name, prepare_tree_node, to_camel_case,
};
@@ -45,43 +45,41 @@ fn generate_tree_typedef(
writeln!(output, "/**").unwrap();
writeln!(output, " * @typedef {{Object}} {}", name).unwrap();
for ((field, child_fields), (child_name, _)) in
ctx.fields_with_child_info.iter().zip(ctx.children.iter())
{
let js_type = metadata.resolve_tree_field_type(
field,
child_fields.as_deref(),
name,
child_name,
GenericSyntax::JAVASCRIPT,
);
for child in &ctx.children {
let js_type = if child.should_inline {
child.inline_type_name.clone()
} else {
metadata.resolve_tree_field_type(
&child.field,
child.child_fields.as_deref(),
name,
child.name,
GenericSyntax::JAVASCRIPT,
)
};
writeln!(
output,
" * @property {{{}}} {}",
js_type,
to_camel_case(&field.name)
to_camel_case(&child.field.name)
)
.unwrap();
}
writeln!(output, " */\n").unwrap();
for (child_name, child_node) in ctx.children {
if let TreeNode::Branch(grandchildren) = child_node {
let child_fields = get_node_fields(grandchildren, pattern_lookup);
// Generate typedef if no pattern match OR pattern is not parameterizable
if !metadata.is_parameterizable_fields(&child_fields) {
let child_type = child_type_name(name, child_name);
generate_tree_typedef(
output,
&child_type,
child_node,
pattern_lookup,
metadata,
generated,
);
}
// Generate child typedefs
for child in &ctx.children {
if child.should_inline {
generate_tree_typedef(
output,
&child.inline_type_name,
child.node,
pattern_lookup,
metadata,
generated,
);
}
}
}
@@ -182,12 +180,14 @@ fn generate_tree_initializer(
.get(&child_fields)
.filter(|name| metadata.is_parameterizable(name));
if let Some(pattern_name) = pattern_name {
let arg = get_pattern_instance_base(child_node);
let base_result = get_pattern_instance_base(child_node);
// Use pattern factory only if no outlier was detected
if let Some(pattern_name) = pattern_name.filter(|_| !base_result.has_outlier) {
writeln!(
output,
"{}{}: create{}(this, '{}'),",
indent_str, field_name, pattern_name, arg
indent_str, field_name, pattern_name, base_result.base
)
.unwrap();
} else {

View File

@@ -195,143 +195,138 @@ class _EndpointConfig:
class RangeBuilder(Generic[T]):
"""Final builder with range fully specified. Can only call json() or csv()."""
"""Builder with range specified."""
def __init__(self, config: _EndpointConfig):
self._config = config
def json(self) -> MetricData[T]:
"""Execute the query and return parsed JSON data."""
def fetch(self) -> MetricData[T]:
"""Fetch the range as parsed JSON."""
return self._config.get_json()
def csv(self) -> str:
"""Execute the query and return CSV data as a string."""
def fetch_csv(self) -> str:
"""Fetch the range as CSV string."""
return self._config.get_csv()
class FromBuilder(Generic[T]):
"""Builder after calling from(start). Can chain with take() or to()."""
class SingleItemBuilder(Generic[T]):
"""Builder for single item access."""
def __init__(self, config: _EndpointConfig):
self._config = config
def fetch(self) -> MetricData[T]:
"""Fetch the single item."""
return self._config.get_json()
def fetch_csv(self) -> str:
"""Fetch as CSV."""
return self._config.get_csv()
class SkippedBuilder(Generic[T]):
"""Builder after calling skip(n). Chain with take() to specify count."""
def __init__(self, config: _EndpointConfig):
self._config = config
def take(self, n: int) -> RangeBuilder[T]:
"""Take n items from the start position."""
"""Take n items after the skipped position."""
start = self._config.start or 0
return RangeBuilder(_EndpointConfig(
self._config.client, self._config.name, self._config.index,
start, start + n
))
def to(self, end: int) -> RangeBuilder[T]:
"""Set the end position."""
return RangeBuilder(_EndpointConfig(
self._config.client, self._config.name, self._config.index,
self._config.start, end
))
def json(self) -> MetricData[T]:
"""Execute the query and return parsed JSON data (from start to end of data)."""
def fetch(self) -> MetricData[T]:
"""Fetch from skipped position to end."""
return self._config.get_json()
def csv(self) -> str:
"""Execute the query and return CSV data as a string."""
return self._config.get_csv()
class ToBuilder(Generic[T]):
"""Builder after calling to(end). Can chain with take_last() or from()."""
def __init__(self, config: _EndpointConfig):
self._config = config
def take_last(self, n: int) -> RangeBuilder[T]:
"""Take last n items before the end position."""
end = self._config.end or 0
return RangeBuilder(_EndpointConfig(
self._config.client, self._config.name, self._config.index,
end - n, end
))
def from_(self, start: int) -> RangeBuilder[T]:
"""Set the start position."""
return RangeBuilder(_EndpointConfig(
self._config.client, self._config.name, self._config.index,
start, self._config.end
))
def json(self) -> MetricData[T]:
"""Execute the query and return parsed JSON data (from start of data to end)."""
return self._config.get_json()
def csv(self) -> str:
"""Execute the query and return CSV data as a string."""
def fetch_csv(self) -> str:
"""Fetch as CSV."""
return self._config.get_csv()
class MetricEndpointBuilder(Generic[T]):
"""Initial builder for metric endpoint queries.
"""Builder for metric endpoint queries.
Use method chaining to specify the data range, then call json() or csv() to execute.
Use method chaining to specify the data range, then call fetch() or fetch_csv() to execute.
Examples:
# Get all data
endpoint.json()
# Fetch all data
data = endpoint.fetch()
# Get last 10 points
endpoint.last(10).json()
# Single item access
data = endpoint[5].fetch()
# Get range [100, 200)
endpoint.range(100, 200).json()
# Slice syntax (Python-native)
data = endpoint[:10].fetch() # First 10
data = endpoint[-5:].fetch() # Last 5
data = endpoint[100:110].fetch() # Range
# Get 10 points starting from position 100
endpoint.from_(100).take(10).json()
# Convenience methods (pandas-style)
data = endpoint.head().fetch() # First 10 (default)
data = endpoint.head(20).fetch() # First 20
data = endpoint.tail(5).fetch() # Last 5
# Iterator-style chaining
data = endpoint.skip(100).take(10).fetch()
"""
def __init__(self, client: BrkClientBase, name: str, index: Index):
self._config = _EndpointConfig(client, name, index)
def first(self, n: int) -> RangeBuilder[T]:
"""Fetch the first n data points."""
@overload
def __getitem__(self, key: int) -> SingleItemBuilder[T]: ...
@overload
def __getitem__(self, key: slice) -> RangeBuilder[T]: ...
def __getitem__(self, key: Union[int, slice]) -> Union[SingleItemBuilder[T], RangeBuilder[T]]:
"""Access single item or slice.
Examples:
endpoint[5] # Single item at index 5
endpoint[:10] # First 10
endpoint[-5:] # Last 5
endpoint[100:110] # Range 100-109
"""
if isinstance(key, int):
return SingleItemBuilder(_EndpointConfig(
self._config.client, self._config.name, self._config.index,
key, key + 1
))
return RangeBuilder(_EndpointConfig(
self._config.client, self._config.name, self._config.index,
key.start, key.stop
))
def head(self, n: int = 10) -> RangeBuilder[T]:
"""Get the first n items (pandas-style)."""
return RangeBuilder(_EndpointConfig(
self._config.client, self._config.name, self._config.index,
None, n
))
def last(self, n: int) -> RangeBuilder[T]:
"""Fetch the last n data points."""
def tail(self, n: int = 10) -> RangeBuilder[T]:
"""Get the last n items (pandas-style)."""
return RangeBuilder(_EndpointConfig(
self._config.client, self._config.name, self._config.index,
-n, None
))
def range(self, start: int, end: int) -> RangeBuilder[T]:
"""Set an explicit range [start, end)."""
return RangeBuilder(_EndpointConfig(
def skip(self, n: int) -> SkippedBuilder[T]:
"""Skip the first n items. Chain with take() to get a range."""
return SkippedBuilder(_EndpointConfig(
self._config.client, self._config.name, self._config.index,
start, end
n, None
))
def from_(self, start: int) -> FromBuilder[T]:
"""Set the start position. Chain with take() or to()."""
return FromBuilder(_EndpointConfig(
self._config.client, self._config.name, self._config.index,
start, None
))
def to(self, end: int) -> ToBuilder[T]:
"""Set the end position. Chain with take_last() or from_()."""
return ToBuilder(_EndpointConfig(
self._config.client, self._config.name, self._config.index,
None, end
))
def json(self) -> MetricData[T]:
"""Execute the query and return parsed JSON data (all data)."""
def fetch(self) -> MetricData[T]:
"""Fetch all data as parsed JSON."""
return self._config.get_json()
def csv(self) -> str:
"""Execute the query and return CSV data as a string (all data)."""
def fetch_csv(self) -> str:
"""Fetch all data as CSV string."""
return self._config.get_csv()
def path(self) -> str:

View File

@@ -26,7 +26,7 @@ pub fn generate_python_client(
writeln!(output, "# Do not edit manually\n").unwrap();
writeln!(
output,
"from typing import TypeVar, Generic, Any, Optional, List, Literal, TypedDict, Union, Protocol"
"from typing import TypeVar, Generic, Any, Optional, List, Literal, TypedDict, Union, Protocol, overload"
)
.unwrap();
writeln!(output, "from http.client import HTTPSConnection, HTTPConnection").unwrap();

View File

@@ -6,8 +6,8 @@ use std::fmt::Write;
use brk_types::TreeNode;
use crate::{
ClientMetadata, GenericSyntax, PatternField, PythonSyntax, child_type_name, generate_leaf_field,
get_node_fields, get_pattern_instance_base, prepare_tree_node, to_snake_case,
ClientMetadata, GenericSyntax, PatternField, PythonSyntax, generate_leaf_field,
prepare_tree_node, to_snake_case,
};
/// Generate tree classes
@@ -41,22 +41,16 @@ fn generate_tree_class(
// Generate child classes FIRST (post-order traversal)
// This ensures children are defined before parent references them
for (child_name, child_node) in ctx.children.iter() {
if let TreeNode::Branch(grandchildren) = child_node {
let child_fields = get_node_fields(grandchildren, pattern_lookup);
// Generate inline class if no pattern match OR pattern is not parameterizable
if !metadata.is_parameterizable_fields(&child_fields) {
let child_class = child_type_name(name, child_name);
generate_tree_class(
output,
&child_class,
child_node,
pattern_lookup,
metadata,
generated,
);
}
for child in &ctx.children {
if child.should_inline {
generate_tree_class(
output,
&child.inline_type_name,
child.node,
pattern_lookup,
metadata,
generated,
);
}
}
@@ -71,45 +65,36 @@ fn generate_tree_class(
.unwrap();
let syntax = PythonSyntax;
for ((field, child_fields_opt), (child_name, child_node)) in
ctx.fields_with_child_info.iter().zip(ctx.children.iter())
{
let py_type = metadata.resolve_tree_field_type(
field,
child_fields_opt.as_deref(),
name,
child_name,
GenericSyntax::PYTHON,
);
let field_name_py = to_snake_case(&field.name);
for child in &ctx.children {
let field_name_py = to_snake_case(child.name);
if metadata.is_pattern_type(&field.rust_type) && metadata.is_parameterizable(&field.rust_type)
{
// Parameterizable pattern: use pattern class with metric base
let metric_base = get_pattern_instance_base(child_node);
writeln!(
output,
" self.{}: {} = {}(client, '{}')",
field_name_py, py_type, field.rust_type, metric_base
)
.unwrap();
} else if let TreeNode::Leaf(leaf) = child_node {
// Leaf node: use shared helper
generate_leaf_field(output, &syntax, "client", child_name, leaf, metadata, " ");
} else if field.is_branch() {
// Non-parameterizable pattern or regular branch: generate inline class
let inline_class = child_type_name(name, &field.name);
if child.is_leaf {
if let TreeNode::Leaf(leaf) = child.node {
generate_leaf_field(output, &syntax, "client", child.name, leaf, metadata, " ");
}
} else if child.should_inline {
// Inline class
writeln!(
output,
" self.{}: {} = {}(client)",
field_name_py, inline_class, inline_class
field_name_py, child.inline_type_name, child.inline_type_name
)
.unwrap();
} else {
panic!(
"Field '{}' has no matching index pattern. All metrics must be indexed.",
field.name
// Use pattern class with metric base
let py_type = metadata.resolve_tree_field_type(
&child.field,
child.child_fields.as_deref(),
name,
child.name,
GenericSyntax::PYTHON,
);
writeln!(
output,
" self.{}: {} = {}(client, '{}')",
field_name_py, py_type, child.field.rust_type, child.base_result.base
)
.unwrap();
}
}

View File

@@ -12,6 +12,7 @@ pub fn generate_imports(output: &mut String) {
writeln!(
output,
r#"use std::sync::Arc;
use std::ops::{{Bound, RangeBounds}};
use serde::de::DeserializeOwned;
pub use brk_cohort::*;
pub use brk_types::*;
@@ -193,21 +194,30 @@ impl EndpointConfig {{
/// Initial builder for metric endpoint queries.
///
/// Use method chaining to specify the data range, then call `json()` or `csv()` to execute.
/// Use method chaining to specify the data range, then call `fetch()` or `fetch_csv()` to execute.
///
/// # Examples
/// ```ignore
/// // Get all data
/// endpoint.json()?;
/// // Fetch all data
/// let data = endpoint.fetch()?;
///
/// // Get last 10 points
/// endpoint.last(10).json()?;
/// // Get single item at index 5
/// let data = endpoint.get(5).fetch()?;
///
/// // Get first 10 using range
/// let data = endpoint.range(..10).fetch()?;
///
/// // Get range [100, 200)
/// endpoint.range(100, 200).json()?;
/// let data = endpoint.range(100..200).fetch()?;
///
/// // Get 10 points starting from position 100
/// endpoint.from(100).take(10).json()?;
/// // Get first 10 (convenience)
/// let data = endpoint.take(10).fetch()?;
///
/// // Get last 10
/// let data = endpoint.last(10).fetch()?;
///
/// // Iterator-style chaining
/// let data = endpoint.skip(100).take(10).fetch()?;
/// ```
pub struct MetricEndpointBuilder<T> {{
config: EndpointConfig,
@@ -219,44 +229,59 @@ impl<T: DeserializeOwned> MetricEndpointBuilder<T> {{
Self {{ config: EndpointConfig::new(client, name, index), _marker: std::marker::PhantomData }}
}}
/// Fetch the first n data points.
pub fn first(mut self, n: u64) -> RangeBuilder<T> {{
self.config.end = Some(n as i64);
/// Select a specific index position.
pub fn get(mut self, index: usize) -> SingleItemBuilder<T> {{
self.config.start = Some(index as i64);
self.config.end = Some(index as i64 + 1);
SingleItemBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
}}
/// Select a range using Rust range syntax.
///
/// # Examples
/// ```ignore
/// endpoint.range(..10) // first 10
/// endpoint.range(100..110) // indices 100-109
/// endpoint.range(100..) // from 100 to end
/// ```
pub fn range<R: RangeBounds<usize>>(mut self, range: R) -> RangeBuilder<T> {{
self.config.start = match range.start_bound() {{
Bound::Included(&n) => Some(n as i64),
Bound::Excluded(&n) => Some(n as i64 + 1),
Bound::Unbounded => None,
}};
self.config.end = match range.end_bound() {{
Bound::Included(&n) => Some(n as i64 + 1),
Bound::Excluded(&n) => Some(n as i64),
Bound::Unbounded => None,
}};
RangeBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
}}
/// Fetch the last n data points.
pub fn last(mut self, n: u64) -> RangeBuilder<T> {{
/// Take the first n items.
pub fn take(self, n: usize) -> RangeBuilder<T> {{
self.range(..n)
}}
/// Take the last n items.
pub fn last(mut self, n: usize) -> RangeBuilder<T> {{
self.config.start = Some(-(n as i64));
RangeBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
}}
/// Set an explicit range [start, end).
pub fn range(mut self, start: i64, end: i64) -> RangeBuilder<T> {{
self.config.start = Some(start);
self.config.end = Some(end);
RangeBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
/// Skip the first n items. Chain with `take(n)` to get a range.
pub fn skip(mut self, n: usize) -> SkippedBuilder<T> {{
self.config.start = Some(n as i64);
SkippedBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
}}
/// Set the start position. Chain with `take(n)` or `to(end)`.
pub fn from(mut self, start: i64) -> FromBuilder<T> {{
self.config.start = Some(start);
FromBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
}}
/// Set the end position. Chain with `takeLast(n)` or `from(start)`.
pub fn to(mut self, end: i64) -> ToBuilder<T> {{
self.config.end = Some(end);
ToBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
}}
/// Execute the query and return parsed JSON data (all data).
pub fn json(self) -> Result<MetricData<T>> {{
/// Fetch all data as parsed JSON.
pub fn fetch(self) -> Result<MetricData<T>> {{
self.config.get_json(None)
}}
/// Execute the query and return CSV data as a string (all data).
pub fn csv(self) -> Result<String> {{
/// Fetch all data as CSV string.
pub fn fetch_csv(self) -> Result<String> {{
self.config.get_text(Some("csv"))
}}
@@ -266,82 +291,63 @@ impl<T: DeserializeOwned> MetricEndpointBuilder<T> {{
}}
}}
/// Builder after calling `from(start)`. Can chain with `take(n)` or `to(end)`.
pub struct FromBuilder<T> {{
/// Builder for single item access.
pub struct SingleItemBuilder<T> {{
config: EndpointConfig,
_marker: std::marker::PhantomData<T>,
}}
impl<T: DeserializeOwned> FromBuilder<T> {{
/// Take n items from the start position.
pub fn take(mut self, n: u64) -> RangeBuilder<T> {{
impl<T: DeserializeOwned> SingleItemBuilder<T> {{
/// Fetch the single item.
pub fn fetch(self) -> Result<MetricData<T>> {{
self.config.get_json(None)
}}
/// Fetch the single item as CSV.
pub fn fetch_csv(self) -> Result<String> {{
self.config.get_text(Some("csv"))
}}
}}
/// Builder after calling `skip(n)`. Chain with `take(n)` to specify count.
pub struct SkippedBuilder<T> {{
config: EndpointConfig,
_marker: std::marker::PhantomData<T>,
}}
impl<T: DeserializeOwned> SkippedBuilder<T> {{
/// Take n items after the skipped position.
pub fn take(mut self, n: usize) -> RangeBuilder<T> {{
let start = self.config.start.unwrap_or(0);
self.config.end = Some(start + n as i64);
RangeBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
}}
/// Set the end position.
pub fn to(mut self, end: i64) -> RangeBuilder<T> {{
self.config.end = Some(end);
RangeBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
}}
/// Execute the query and return parsed JSON data (from start to end of data).
pub fn json(self) -> Result<MetricData<T>> {{
/// Fetch from the skipped position to the end.
pub fn fetch(self) -> Result<MetricData<T>> {{
self.config.get_json(None)
}}
/// Execute the query and return CSV data as a string.
pub fn csv(self) -> Result<String> {{
/// Fetch from the skipped position to the end as CSV.
pub fn fetch_csv(self) -> Result<String> {{
self.config.get_text(Some("csv"))
}}
}}
/// Builder after calling `to(end)`. Can chain with `takeLast(n)` or `from(start)`.
pub struct ToBuilder<T> {{
config: EndpointConfig,
_marker: std::marker::PhantomData<T>,
}}
impl<T: DeserializeOwned> ToBuilder<T> {{
/// Take last n items before the end position.
pub fn take_last(mut self, n: u64) -> RangeBuilder<T> {{
let end = self.config.end.unwrap_or(0);
self.config.start = Some(end - n as i64);
RangeBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
}}
/// Set the start position.
pub fn from(mut self, start: i64) -> RangeBuilder<T> {{
self.config.start = Some(start);
RangeBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
}}
/// Execute the query and return parsed JSON data (from start of data to end).
pub fn json(self) -> Result<MetricData<T>> {{
self.config.get_json(None)
}}
/// Execute the query and return CSV data as a string.
pub fn csv(self) -> Result<String> {{
self.config.get_text(Some("csv"))
}}
}}
/// Final builder with range fully specified. Can only call `json()` or `csv()`.
/// Builder with range fully specified.
pub struct RangeBuilder<T> {{
config: EndpointConfig,
_marker: std::marker::PhantomData<T>,
}}
impl<T: DeserializeOwned> RangeBuilder<T> {{
/// Execute the query and return parsed JSON data.
pub fn json(self) -> Result<MetricData<T>> {{
/// Fetch the range as parsed JSON.
pub fn fetch(self) -> Result<MetricData<T>> {{
self.config.get_json(None)
}}
/// Execute the query and return CSV data as a string.
pub fn csv(self) -> Result<String> {{
/// Fetch the range as CSV string.
pub fn fetch_csv(self) -> Result<String> {{
self.config.get_text(Some("csv"))
}}
}}

View File

@@ -6,9 +6,8 @@ use std::fmt::Write;
use brk_types::TreeNode;
use crate::{
ClientMetadata, GenericSyntax, LanguageSyntax, PatternField, RustSyntax, child_type_name,
generate_leaf_field, generate_tree_node_field, get_node_fields, get_pattern_instance_base,
prepare_tree_node, to_snake_case,
ClientMetadata, GenericSyntax, LanguageSyntax, PatternField, RustSyntax,
generate_leaf_field, generate_tree_node_field, prepare_tree_node, to_snake_case,
};
/// Generate tree structs.
@@ -39,25 +38,29 @@ fn generate_tree_node(
return;
};
// Generate struct definition
writeln!(output, "/// Metrics tree node.").unwrap();
writeln!(output, "pub struct {} {{", name).unwrap();
for ((field, child_fields), (child_name, _)) in
ctx.fields_with_child_info.iter().zip(ctx.children.iter())
{
let field_name = to_snake_case(&field.name);
let type_annotation = metadata.resolve_tree_field_type(
field,
child_fields.as_deref(),
name,
child_name,
GenericSyntax::RUST,
);
for child in &ctx.children {
let field_name = to_snake_case(child.name);
let type_annotation = if child.should_inline {
child.inline_type_name.clone()
} else {
metadata.resolve_tree_field_type(
&child.field,
child.child_fields.as_deref(),
name,
child.name,
GenericSyntax::RUST,
)
};
writeln!(output, " pub {}: {},", field_name, type_annotation).unwrap();
}
writeln!(output, "}}\n").unwrap();
// Generate impl block
writeln!(output, "impl {} {{", name).unwrap();
writeln!(
output,
@@ -67,53 +70,40 @@ fn generate_tree_node(
writeln!(output, " Self {{").unwrap();
let syntax = RustSyntax;
for ((field_info, child_fields), (child_name, child_node)) in
ctx.fields_with_child_info.iter().zip(ctx.children.iter())
{
let field_name = to_snake_case(&field_info.name);
for child in &ctx.children {
let field_name = to_snake_case(child.name);
// Check if this is a pattern type and if it's parameterizable
let is_parameterizable = child_fields
.as_ref()
.is_some_and(|cf| metadata.is_parameterizable_fields(cf));
if metadata.is_pattern_type(&field_info.rust_type) && is_parameterizable {
// Parameterizable pattern: use pattern constructor with metric base
let pattern_base = get_pattern_instance_base(child_node);
generate_tree_node_field(
output,
&syntax,
field_info,
metadata,
" ",
child_name,
Some(&pattern_base),
);
} else if child_fields.is_some() {
// Non-parameterizable pattern or regular branch: use inline struct
let child_struct = child_type_name(name, child_name);
let path_expr = syntax.path_expr("base_path", &format!("_{}", child_name));
if child.is_leaf {
if let TreeNode::Leaf(leaf) = child.node {
generate_leaf_field(
output,
&syntax,
"client.clone()",
child.name,
leaf,
metadata,
" ",
);
}
} else if child.should_inline {
// Inline struct
let path_expr = syntax.path_expr("base_path", &format!("_{}", child.name));
writeln!(
output,
" {}: {}::new(client.clone(), {}),",
field_name, child_struct, path_expr
field_name, child.inline_type_name, path_expr
)
.unwrap();
} else if let TreeNode::Leaf(leaf) = child_node {
// Leaf field - use shared helper
generate_leaf_field(
} else {
// Use pattern constructor
generate_tree_node_field(
output,
&syntax,
"client.clone()",
child_name,
leaf,
&child.field,
metadata,
" ",
);
} else {
panic!(
"Field '{}' is a leaf with no TreeNode::Leaf. This shouldn't happen.",
field_info.name
child.name,
Some(&child.base_result.base),
);
}
}
@@ -122,21 +112,17 @@ fn generate_tree_node(
writeln!(output, " }}").unwrap();
writeln!(output, "}}\n").unwrap();
for (child_name, child_node) in ctx.children {
if let TreeNode::Branch(grandchildren) = child_node {
let child_fields = get_node_fields(grandchildren, pattern_lookup);
// Generate child struct if no pattern match OR pattern is not parameterizable
if !metadata.is_parameterizable_fields(&child_fields) {
let child_struct = child_type_name(name, child_name);
generate_tree_node(
output,
&child_struct,
child_node,
pattern_lookup,
metadata,
generated,
);
}
// Generate child structs
for child in &ctx.children {
if child.should_inline {
generate_tree_node(
output,
&child.inline_type_name,
child.node,
pattern_lookup,
metadata,
generated,
);
}
}
}