mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-01 22:39:03 -07:00
global: fixes
This commit is contained in:
@@ -14,141 +14,235 @@ pub fn generate_api_methods(output: &mut String, endpoints: &[Endpoint]) {
|
||||
if !endpoint.should_generate() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let method_name = endpoint_to_method_name(endpoint);
|
||||
let base_return_type = if endpoint.returns_binary() {
|
||||
"Uint8Array".to_string()
|
||||
} else {
|
||||
jsdoc_normalize(&normalize_return_type(
|
||||
endpoint.schema_name().unwrap_or("*"),
|
||||
))
|
||||
};
|
||||
let return_type = if endpoint.supports_csv {
|
||||
format!("{} | string", base_return_type)
|
||||
} else {
|
||||
base_return_type
|
||||
};
|
||||
|
||||
writeln!(output, " /**").unwrap();
|
||||
if let Some(summary) = &endpoint.summary {
|
||||
writeln!(output, " * {}", summary).unwrap();
|
||||
}
|
||||
if let Some(desc) = &endpoint.description
|
||||
&& endpoint.summary.as_ref() != Some(desc)
|
||||
{
|
||||
writeln!(output, " *").unwrap();
|
||||
write_description(output, desc, " * ", " *");
|
||||
match endpoint.method.as_str() {
|
||||
"GET" => generate_get_method(output, endpoint),
|
||||
"POST" => generate_post_method(output, endpoint),
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add endpoint path
|
||||
fn generate_get_method(output: &mut String, endpoint: &Endpoint) {
|
||||
let method_name = endpoint_to_method_name(endpoint);
|
||||
let return_type = build_return_type(endpoint);
|
||||
|
||||
write_method_doc(output, endpoint);
|
||||
for param in &endpoint.path_params {
|
||||
let desc = format_param_desc(param.description.as_deref());
|
||||
let ty = jsdoc_normalize(¶m.param_type);
|
||||
writeln!(output, " * @param {{{}}} {}{}", ty, param.name, desc).unwrap();
|
||||
}
|
||||
for param in &endpoint.query_params {
|
||||
let optional = if param.required { "" } else { "=" };
|
||||
let desc = format_param_desc(param.description.as_deref());
|
||||
let ty = jsdoc_normalize(¶m.param_type);
|
||||
writeln!(
|
||||
output,
|
||||
" * @param {{{}{}}} [{}]{}",
|
||||
ty, optional, param.name, desc
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
writeln!(
|
||||
output,
|
||||
" * @param {{{{ signal?: AbortSignal, onValue?: (value: {}) => void }}}} [options]",
|
||||
return_type
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(output, " * @returns {{Promise<{}>}}", return_type).unwrap();
|
||||
writeln!(output, " */").unwrap();
|
||||
|
||||
let params = build_method_params(endpoint);
|
||||
let params_with_opts = if params.is_empty() {
|
||||
"{ signal, onValue } = {}".to_string()
|
||||
} else {
|
||||
format!("{}, {{ signal, onValue }} = {{}}", params)
|
||||
};
|
||||
writeln!(output, " async {}({}) {{", method_name, params_with_opts).unwrap();
|
||||
|
||||
let path = build_path_template(&endpoint.path, &endpoint.path_params);
|
||||
|
||||
let fetch_call: String = if endpoint.returns_binary() {
|
||||
"this.getBytes(path, { signal, onValue })".to_string()
|
||||
} else if endpoint.returns_json() {
|
||||
"this.getJson(path, { signal, onValue })".to_string()
|
||||
} else if endpoint.response_kind.text_is_numeric() {
|
||||
"Number(await this.getText(path, { signal, onValue }))".to_string()
|
||||
} else {
|
||||
"this.getText(path, { signal, onValue })".to_string()
|
||||
};
|
||||
|
||||
write_path_assignment(output, endpoint, &path);
|
||||
|
||||
if endpoint.supports_csv {
|
||||
writeln!(
|
||||
output,
|
||||
" if (format === 'csv') return this.getText(path, {{ signal, onValue }});"
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
writeln!(output, " return {};", fetch_call).unwrap();
|
||||
writeln!(output, " }}\n").unwrap();
|
||||
}
|
||||
|
||||
fn generate_post_method(output: &mut String, endpoint: &Endpoint) {
|
||||
let method_name = endpoint_to_method_name(endpoint);
|
||||
let return_type = build_return_type(endpoint);
|
||||
|
||||
write_method_doc(output, endpoint);
|
||||
for param in &endpoint.path_params {
|
||||
let desc = format_param_desc(param.description.as_deref());
|
||||
let ty = jsdoc_normalize(¶m.param_type);
|
||||
writeln!(output, " * @param {{{}}} {}{}", ty, param.name, desc).unwrap();
|
||||
}
|
||||
for param in &endpoint.query_params {
|
||||
let optional = if param.required { "" } else { "=" };
|
||||
let desc = format_param_desc(param.description.as_deref());
|
||||
let ty = jsdoc_normalize(¶m.param_type);
|
||||
writeln!(
|
||||
output,
|
||||
" * @param {{{}{}}} [{}]{}",
|
||||
ty, optional, param.name, desc
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
if let Some(body) = &endpoint.request_body {
|
||||
let optional = if body.required { "" } else { "=" };
|
||||
let ty = jsdoc_normalize(&body.body_type);
|
||||
writeln!(
|
||||
output,
|
||||
" * @param {{{}{}}} body - Request body",
|
||||
ty, optional
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
writeln!(
|
||||
output,
|
||||
" * @param {{{{ signal?: AbortSignal }}}} [options]"
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(output, " * @returns {{Promise<{}>}}", return_type).unwrap();
|
||||
writeln!(output, " */").unwrap();
|
||||
|
||||
let mut params = build_method_params(endpoint);
|
||||
if endpoint.request_body.is_some() {
|
||||
if !params.is_empty() {
|
||||
params.push_str(", ");
|
||||
}
|
||||
params.push_str("body");
|
||||
}
|
||||
let params_with_opts = if params.is_empty() {
|
||||
"{ signal } = {}".to_string()
|
||||
} else {
|
||||
format!("{}, {{ signal }} = {{}}", params)
|
||||
};
|
||||
writeln!(output, " async {}({}) {{", method_name, params_with_opts).unwrap();
|
||||
|
||||
let path = build_path_template(&endpoint.path, &endpoint.path_params);
|
||||
let body_arg = if endpoint.request_body.is_some() {
|
||||
"body"
|
||||
} else {
|
||||
"''"
|
||||
};
|
||||
|
||||
let fetch_call: String = if endpoint.returns_binary() {
|
||||
format!("this.postBytes(path, {}, {{ signal }})", body_arg)
|
||||
} else if endpoint.returns_json() {
|
||||
format!("this.postJson(path, {}, {{ signal }})", body_arg)
|
||||
} else if endpoint.response_kind.text_is_numeric() {
|
||||
format!(
|
||||
"Number(await this.postText(path, {}, {{ signal }}))",
|
||||
body_arg
|
||||
)
|
||||
} else {
|
||||
format!("this.postText(path, {}, {{ signal }})", body_arg)
|
||||
};
|
||||
|
||||
write_path_assignment(output, endpoint, &path);
|
||||
|
||||
writeln!(output, " return {};", fetch_call).unwrap();
|
||||
writeln!(output, " }}\n").unwrap();
|
||||
}
|
||||
|
||||
fn build_return_type(endpoint: &Endpoint) -> String {
|
||||
let base = if endpoint.returns_binary() {
|
||||
"Uint8Array".to_string()
|
||||
} else {
|
||||
jsdoc_normalize(&normalize_return_type(
|
||||
endpoint.schema_name().unwrap_or("*"),
|
||||
))
|
||||
};
|
||||
if endpoint.supports_csv {
|
||||
format!("{} | string", base)
|
||||
} else {
|
||||
base
|
||||
}
|
||||
}
|
||||
|
||||
fn write_method_doc(output: &mut String, endpoint: &Endpoint) {
|
||||
writeln!(output, " /**").unwrap();
|
||||
if let Some(summary) = &endpoint.summary {
|
||||
writeln!(output, " * {}", summary).unwrap();
|
||||
}
|
||||
if let Some(desc) = &endpoint.description
|
||||
&& endpoint.summary.as_ref() != Some(desc)
|
||||
{
|
||||
writeln!(output, " *").unwrap();
|
||||
writeln!(
|
||||
output,
|
||||
" * Endpoint: `{} {}`",
|
||||
endpoint.method.to_uppercase(),
|
||||
endpoint.path
|
||||
)
|
||||
.unwrap();
|
||||
write_description(output, desc, " * ", " *");
|
||||
}
|
||||
writeln!(output, " *").unwrap();
|
||||
writeln!(
|
||||
output,
|
||||
" * Endpoint: `{} {}`",
|
||||
endpoint.method.to_uppercase(),
|
||||
endpoint.path
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
if !endpoint.path_params.is_empty() || !endpoint.query_params.is_empty() {
|
||||
writeln!(output, " *").unwrap();
|
||||
}
|
||||
let has_body_param = endpoint.method == "POST" && endpoint.request_body.is_some();
|
||||
if !endpoint.path_params.is_empty() || !endpoint.query_params.is_empty() || has_body_param {
|
||||
writeln!(output, " *").unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
for param in &endpoint.path_params {
|
||||
let desc = format_param_desc(param.description.as_deref());
|
||||
let ty = jsdoc_normalize(¶m.param_type);
|
||||
writeln!(output, " * @param {{{}}} {}{}", ty, param.name, desc).unwrap();
|
||||
}
|
||||
fn write_path_assignment(output: &mut String, endpoint: &Endpoint, path: &str) {
|
||||
if endpoint.query_params.is_empty() {
|
||||
writeln!(output, " const path = `{}`;", path).unwrap();
|
||||
} else {
|
||||
writeln!(output, " const params = new URLSearchParams();").unwrap();
|
||||
for param in &endpoint.query_params {
|
||||
let optional = if param.required { "" } else { "=" };
|
||||
let desc = format_param_desc(param.description.as_deref());
|
||||
let ty = jsdoc_normalize(¶m.param_type);
|
||||
writeln!(
|
||||
output,
|
||||
" * @param {{{}{}}} [{}]{}",
|
||||
ty, optional, param.name, desc
|
||||
)
|
||||
.unwrap();
|
||||
let ident = sanitize_ident(¶m.name);
|
||||
let is_array = param.param_type.ends_with("[]");
|
||||
if is_array {
|
||||
writeln!(
|
||||
output,
|
||||
" for (const _v of {}) params.append('{}', String(_v));",
|
||||
ident, param.name
|
||||
)
|
||||
.unwrap();
|
||||
} else if param.required {
|
||||
writeln!(
|
||||
output,
|
||||
" params.set('{}', String({}));",
|
||||
param.name, ident
|
||||
)
|
||||
.unwrap();
|
||||
} else {
|
||||
writeln!(
|
||||
output,
|
||||
" if ({} !== undefined) params.set('{}', String({}));",
|
||||
ident, param.name, ident
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
writeln!(output, " const query = params.toString();").unwrap();
|
||||
writeln!(
|
||||
output,
|
||||
" * @param {{{{ signal?: AbortSignal, onValue?: (value: {}) => void }}}} [options]",
|
||||
return_type
|
||||
" const path = `{}${{query ? '?' + query : ''}}`;",
|
||||
path
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(output, " * @returns {{Promise<{}>}}", return_type).unwrap();
|
||||
writeln!(output, " */").unwrap();
|
||||
|
||||
let params = build_method_params(endpoint);
|
||||
let params_with_opts = if params.is_empty() {
|
||||
"{ signal, onValue } = {}".to_string()
|
||||
} else {
|
||||
format!("{}, {{ signal, onValue }} = {{}}", params)
|
||||
};
|
||||
writeln!(output, " async {}({}) {{", method_name, params_with_opts).unwrap();
|
||||
|
||||
let path = build_path_template(&endpoint.path, &endpoint.path_params);
|
||||
|
||||
let fetch_call: String = if endpoint.returns_binary() {
|
||||
"this.getBytes(path, { signal, onValue })".to_string()
|
||||
} else if endpoint.returns_json() {
|
||||
"this.getJson(path, { signal, onValue })".to_string()
|
||||
} else if endpoint.response_kind.text_is_numeric() {
|
||||
"Number(await this.getText(path, { signal, onValue }))".to_string()
|
||||
} else {
|
||||
"this.getText(path, { signal, onValue })".to_string()
|
||||
};
|
||||
|
||||
if endpoint.query_params.is_empty() {
|
||||
writeln!(output, " const path = `{}`;", path).unwrap();
|
||||
} else {
|
||||
writeln!(output, " const params = new URLSearchParams();").unwrap();
|
||||
for param in &endpoint.query_params {
|
||||
let ident = sanitize_ident(¶m.name);
|
||||
let is_array = param.param_type.ends_with("[]");
|
||||
if is_array {
|
||||
writeln!(
|
||||
output,
|
||||
" for (const _v of {}) params.append('{}', String(_v));",
|
||||
ident, param.name
|
||||
)
|
||||
.unwrap();
|
||||
} else if param.required {
|
||||
writeln!(
|
||||
output,
|
||||
" params.set('{}', String({}));",
|
||||
param.name, ident
|
||||
)
|
||||
.unwrap();
|
||||
} else {
|
||||
writeln!(
|
||||
output,
|
||||
" if ({} !== undefined) params.set('{}', String({}));",
|
||||
ident, param.name, ident
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
writeln!(output, " const query = params.toString();").unwrap();
|
||||
writeln!(
|
||||
output,
|
||||
" const path = `{}${{query ? '?' + query : ''}}`;",
|
||||
path
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
if endpoint.supports_csv {
|
||||
writeln!(
|
||||
output,
|
||||
" if (format === 'csv') return this.getText(path, {{ signal, onValue }});"
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
writeln!(output, " return {};", fetch_call).unwrap();
|
||||
|
||||
writeln!(output, " }}\n").unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -569,6 +569,67 @@ class BrkClientBase {{
|
||||
return this._getCached(path, async (res) => new Uint8Array(await res.arrayBuffer()), options);
|
||||
}}
|
||||
|
||||
/**
|
||||
* Make a POST request with a string body.
|
||||
*
|
||||
* POST responses are uncached and never invoke `onValue` — every call hits
|
||||
* the network with the same body and returns the upstream response.
|
||||
*
|
||||
* @param {{string}} path
|
||||
* @param {{string}} body
|
||||
* @param {{{{ signal?: AbortSignal }}}} [options]
|
||||
* @returns {{Promise<Response>}}
|
||||
*/
|
||||
async post(path, body, {{ signal }} = {{}}) {{
|
||||
const url = `${{this.baseUrl}}${{path}}`;
|
||||
const signals = [AbortSignal.timeout(this.timeout)];
|
||||
if (signal) signals.push(signal);
|
||||
const res = await fetch(url, {{
|
||||
method: 'POST',
|
||||
body,
|
||||
signal: AbortSignal.any(signals),
|
||||
}});
|
||||
if (!res.ok) throw new BrkError(`HTTP ${{res.status}}: ${{url}}`, res.status);
|
||||
return res;
|
||||
}}
|
||||
|
||||
/**
|
||||
* Make a POST request expecting a JSON response.
|
||||
* @template T
|
||||
* @param {{string}} path
|
||||
* @param {{string}} body
|
||||
* @param {{{{ signal?: AbortSignal }}}} [options]
|
||||
* @returns {{Promise<T>}}
|
||||
*/
|
||||
async postJson(path, body, options) {{
|
||||
const res = await this.post(path, body, options);
|
||||
return _addCamelGetters(await res.json());
|
||||
}}
|
||||
|
||||
/**
|
||||
* Make a POST request expecting a text response.
|
||||
* @param {{string}} path
|
||||
* @param {{string}} body
|
||||
* @param {{{{ signal?: AbortSignal }}}} [options]
|
||||
* @returns {{Promise<string>}}
|
||||
*/
|
||||
async postText(path, body, options) {{
|
||||
const res = await this.post(path, body, options);
|
||||
return res.text();
|
||||
}}
|
||||
|
||||
/**
|
||||
* Make a POST request expecting binary data (application/octet-stream).
|
||||
* @param {{string}} path
|
||||
* @param {{string}} body
|
||||
* @param {{{{ signal?: AbortSignal }}}} [options]
|
||||
* @returns {{Promise<Uint8Array>}}
|
||||
*/
|
||||
async postBytes(path, body, options) {{
|
||||
const res = await this.post(path, body, options);
|
||||
return new Uint8Array(await res.arrayBuffer());
|
||||
}}
|
||||
|
||||
/**
|
||||
* Fetch series data and wrap with helper methods (internal)
|
||||
* @template T
|
||||
|
||||
Reference in New Issue
Block a user