Files
khadhroony-bot3/kb-app-demo-desktop/src/demo_sql_replay_candidates.rs
2026-07-25 21:57:01 +02:00

408 lines
16 KiB
Rust

// file: kb-app-demo-desktop/src/demo_sql_replay_candidates.rs
// version: 7
//! Read-only SQL replay candidate browser commands.
use ts_rs::TS; // rust-rules: derive-import
/// One program identifier exposed by the runtime `kb_program_ids` registry.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_sql_replay_candidates/DemoSqlReplayKnownProgramOption.ts"
)]
pub(crate) struct DemoSqlReplayKnownProgramOption {
/// Stable lower snake case program code.
pub(crate) code: std::string::String,
/// Base58 Solana program identifier.
pub(crate) program_id: std::string::String,
}
/// Static options for the replay candidate browser.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_sql_replay_candidates/DemoSqlReplayOptionsPayload.ts"
)]
pub(crate) struct DemoSqlReplayOptionsPayload {
/// Active profile name.
pub(crate) active_profile_name: std::string::String,
/// Masked PostgreSQL DSN.
pub(crate) masked_dsn: std::string::String,
/// Maximum rows accepted by one query.
pub(crate) maximum_limit: u32,
/// Program identifiers enumerable from `kb_program_ids`.
pub(crate) known_programs: std::vec::Vec<crate::DemoSqlReplayKnownProgramOption>,
}
/// UI request for bounded transaction replay candidates.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_sql_replay_candidates/DemoSqlReplayTransactionRequest.ts"
)]
pub(crate) struct DemoSqlReplayTransactionRequest {
/// Optional partial signature search.
pub(crate) signature_contains: std::option::Option<std::string::String>,
/// Optional inclusive minimum slot.
#[ts(type = "number | null")]
pub(crate) min_slot: std::option::Option<u64>,
/// Optional inclusive maximum slot.
#[ts(type = "number | null")]
pub(crate) max_slot: std::option::Option<u64>,
/// Optional raw processing state.
pub(crate) raw_processing_state: std::option::Option<std::string::String>,
/// Optional latest ledger status.
pub(crate) ledger_status: std::option::Option<std::string::String>,
/// Optional exact program id.
pub(crate) program_id: std::option::Option<std::string::String>,
/// Program scope code: any, outer, inner or logs.
pub(crate) program_scope: std::string::String,
/// Optional entity kind: mint, owner or account_key.
pub(crate) entity_kind: std::option::Option<std::string::String>,
/// Optional exact entity value.
pub(crate) entity_value: std::option::Option<std::string::String>,
/// Maximum returned rows.
pub(crate) limit: u32,
/// Orders newest slots first when true.
pub(crate) newest_first: bool,
}
/// One transaction row shown by the replay candidate browser.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_sql_replay_candidates/DemoSqlReplayTransactionRow.ts"
)]
pub(crate) struct DemoSqlReplayTransactionRow {
/// Canonical transaction signature.
pub(crate) signature: std::string::String,
/// Transaction slot.
#[ts(type = "number")]
pub(crate) slot: i64,
/// Current raw processing state.
pub(crate) raw_processing_state: std::string::String,
/// Current raw retention state.
pub(crate) retention_state: std::string::String,
/// Whether a core transaction exists.
pub(crate) has_core_transaction: bool,
/// Core transaction failure flag when available.
pub(crate) transaction_failed: std::option::Option<bool>,
/// Latest core extraction ledger status.
pub(crate) ledger_status: std::string::String,
/// Latest processor version when available.
pub(crate) processor_version: std::option::Option<std::string::String>,
/// Latest attempt count.
pub(crate) attempt_count: i32,
/// Number of top-level instructions.
#[ts(type = "number")]
pub(crate) outer_instruction_count: i64,
/// Number of inner instructions.
#[ts(type = "number")]
pub(crate) inner_instruction_count: i64,
/// Number of distinct top-level programs.
#[ts(type = "number")]
pub(crate) outer_program_count: i64,
/// Number of distinct inner programs.
#[ts(type = "number")]
pub(crate) inner_program_count: i64,
/// Raw row update timestamp.
pub(crate) updated_at: std::string::String,
}
/// UI request for bounded program summaries.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_sql_replay_candidates/DemoSqlReplayProgramRequest.ts"
)]
pub(crate) struct DemoSqlReplayProgramRequest {
/// Optional partial program id search.
pub(crate) program_id_contains: std::option::Option<std::string::String>,
/// Maximum returned rows.
pub(crate) limit: u32,
}
/// Aggregated program row shown by the replay candidate browser.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_sql_replay_candidates/DemoSqlReplayProgramRow.ts"
)]
pub(crate) struct DemoSqlReplayProgramRow {
/// Optional stable code from `kb_program_ids`.
pub(crate) program_code: std::option::Option<std::string::String>,
/// Program id.
pub(crate) program_id: std::string::String,
/// Number of distinct transactions.
#[ts(type = "number")]
pub(crate) transaction_count: i64,
/// Number of top-level instruction occurrences.
#[ts(type = "number")]
pub(crate) outer_instruction_count: i64,
/// Number of inner instruction occurrences.
#[ts(type = "number")]
pub(crate) inner_instruction_count: i64,
/// Number of reliably linked log occurrences.
#[ts(type = "number")]
pub(crate) log_count: i64,
/// Lowest observed slot.
#[ts(type = "number")]
pub(crate) min_slot: i64,
/// Highest observed slot.
#[ts(type = "number")]
pub(crate) max_slot: i64,
}
/// UI request for bounded core entity summaries.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_sql_replay_candidates/DemoSqlReplayEntityRequest.ts"
)]
pub(crate) struct DemoSqlReplayEntityRequest {
/// Entity kind: mint, owner or account_key.
pub(crate) entity_kind: std::string::String,
/// Optional partial entity value search.
pub(crate) entity_value_contains: std::option::Option<std::string::String>,
/// Maximum returned rows.
pub(crate) limit: u32,
}
/// Aggregated core entity row shown by the replay candidate browser.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_sql_replay_candidates/DemoSqlReplayEntityRow.ts"
)]
pub(crate) struct DemoSqlReplayEntityRow {
/// Stable entity kind code.
pub(crate) entity_kind: std::string::String,
/// Mint, owner or account-key address.
pub(crate) entity_value: std::string::String,
/// Number of distinct transactions.
#[ts(type = "number")]
pub(crate) transaction_count: i64,
/// Total number of core-table occurrences.
#[ts(type = "number")]
pub(crate) occurrence_count: i64,
/// Lowest observed slot.
#[ts(type = "number")]
pub(crate) min_slot: i64,
/// Highest observed slot.
#[ts(type = "number")]
pub(crate) max_slot: i64,
}
pub(crate) fn transaction_row_from_pg(
row: kb_store::PostgresReplayTransactionCandidate,
) -> crate::DemoSqlReplayTransactionRow {
return crate::DemoSqlReplayTransactionRow {
signature: row.signature,
slot: row.slot,
raw_processing_state: row.raw_processing_state,
retention_state: row.retention_state,
has_core_transaction: row.has_core_transaction,
transaction_failed: row.transaction_failed,
ledger_status: row.ledger_status,
processor_version: row.processor_version,
attempt_count: row.attempt_count,
outer_instruction_count: row.outer_instruction_count,
inner_instruction_count: row.inner_instruction_count,
outer_program_count: row.outer_program_count,
inner_program_count: row.inner_program_count,
updated_at: row.updated_at,
};
}
pub(crate) fn program_row_from_pg(
row: kb_store::PostgresReplayProgramSummary,
) -> crate::DemoSqlReplayProgramRow {
let program_code = kb_program_ids::find_registered_program_id(&row.program_id)
.map(|entry| return entry.code().to_owned());
return crate::DemoSqlReplayProgramRow {
program_code,
program_id: row.program_id,
transaction_count: row.transaction_count,
outer_instruction_count: row.outer_instruction_count,
inner_instruction_count: row.inner_instruction_count,
log_count: row.log_count,
min_slot: row.min_slot,
max_slot: row.max_slot,
};
}
pub(crate) fn entity_row_from_pg(
row: kb_store::PostgresReplayEntitySummary,
) -> crate::DemoSqlReplayEntityRow {
return crate::DemoSqlReplayEntityRow {
entity_kind: row.entity_kind,
entity_value: row.entity_value,
transaction_count: row.transaction_count,
occurrence_count: row.occurrence_count,
min_slot: row.min_slot,
max_slot: row.max_slot,
};
}
pub(crate) fn validated_csv_file_name(
file_name: &str,
) -> std::result::Result<&'static str, std::string::String> {
return match file_name {
"replay_transactions.csv" => std::result::Result::Ok("replay_transactions.csv"),
"replay_programs.csv" => std::result::Result::Ok("replay_programs.csv"),
"replay_mints.csv" => std::result::Result::Ok("replay_mints.csv"),
"replay_owners.csv" => std::result::Result::Ok("replay_owners.csv"),
"replay_account_keys.csv" => std::result::Result::Ok("replay_account_keys.csv"),
_ => std::result::Result::Err(format!("unsupported replay CSV file name: {file_name}")),
};
}
pub(crate) fn csv_export_directory_from_current_dir(
current_dir: &std::path::Path,
) -> std::path::PathBuf {
let current_name = current_dir.file_name().and_then(std::ffi::OsStr::to_str);
if let (std::option::Option::Some("kb_app_demo_desktop"), std::option::Option::Some(parent)) =
(current_name, current_dir.parent())
{
return parent.join("data").join("exports_csv");
}
return current_dir.join("data").join("exports_csv");
}
pub(crate) async fn available_csv_export_path(
export_dir: &std::path::Path,
file_name: &str,
) -> std::result::Result<std::path::PathBuf, std::string::String> {
let direct_path = export_dir.join(file_name);
let direct_exists_result = tokio::fs::try_exists(&direct_path).await;
let direct_exists = match direct_exists_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(format!("cannot inspect CSV export path: {error}"));
},
};
if !direct_exists {
return std::result::Result::Ok(direct_path);
}
let stem = file_name.trim_end_matches(".csv");
for suffix in 1_u16..=999_u16 {
let candidate = export_dir.join(format!("{stem}_{suffix}.csv"));
let exists_result = tokio::fs::try_exists(&candidate).await;
let exists = match exists_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(format!(
"cannot inspect CSV export path: {error}"
));
},
};
if !exists {
return std::result::Result::Ok(candidate);
}
}
return std::result::Result::Err("cannot allocate a unique CSV export path".to_owned());
}
pub(crate) fn program_scope_from_code(
code: &str,
) -> std::result::Result<kb_store::PostgresReplayProgramScope, std::string::String> {
return match code {
"any" => std::result::Result::Ok(kb_store::PostgresReplayProgramScope::Any),
"outer" => std::result::Result::Ok(kb_store::PostgresReplayProgramScope::Outer),
"inner" => std::result::Result::Ok(kb_store::PostgresReplayProgramScope::Inner),
"logs" => std::result::Result::Ok(kb_store::PostgresReplayProgramScope::Logs),
_ => std::result::Result::Err(format!("unsupported replay program scope: {code}")),
};
}
pub(crate) fn optional_entity_kind_from_code(
code: std::option::Option<&str>,
) -> std::result::Result<std::option::Option<kb_store::PostgresReplayEntityKind>, std::string::String>
{
return match code {
std::option::Option::Some(value) => {
let result = entity_kind_from_code(value);
match result {
std::result::Result::Ok(kind) => {
std::result::Result::Ok(std::option::Option::Some(kind))
},
std::result::Result::Err(error) => std::result::Result::Err(error),
}
},
std::option::Option::None => std::result::Result::Ok(std::option::Option::None),
};
}
pub(crate) fn entity_kind_from_code(
code: &str,
) -> std::result::Result<kb_store::PostgresReplayEntityKind, std::string::String> {
return match code {
"mint" => std::result::Result::Ok(kb_store::PostgresReplayEntityKind::Mint),
"owner" => std::result::Result::Ok(kb_store::PostgresReplayEntityKind::Owner),
"account_key" => std::result::Result::Ok(kb_store::PostgresReplayEntityKind::AccountKey),
_ => std::result::Result::Err(format!("unsupported replay entity kind: {code}")),
};
}
#[cfg(test)]
mod tests {
#[test]
fn program_scope_parser_accepts_logs() {
let result = crate::program_scope_from_code("logs");
assert_eq!(result, std::result::Result::Ok(kb_store::PostgresReplayProgramScope::Logs));
}
#[test]
fn entity_kind_parser_rejects_unknown_code() {
let result = crate::entity_kind_from_code("unknown");
assert!(result.is_err());
}
#[test]
fn csv_file_name_rejects_unknown_name() {
let result = crate::validated_csv_file_name("arbitrary.csv");
assert!(result.is_err());
}
#[test]
fn csv_export_directory_uses_workspace_root_from_demo_crate() {
let current_dir = std::path::Path::new("/tmp/khadhroony-bot2/kb_app_demo_desktop");
let result = crate::csv_export_directory_from_current_dir(current_dir);
assert_eq!(result, std::path::Path::new("/tmp/khadhroony-bot2/data/exports_csv"));
}
#[test]
fn csv_file_name_accepts_split_entity_exports() {
let mint_result = crate::validated_csv_file_name("replay_mints.csv");
let owner_result = crate::validated_csv_file_name("replay_owners.csv");
let account_result = crate::validated_csv_file_name("replay_account_keys.csv");
assert!(mint_result.is_ok());
assert!(owner_result.is_ok());
assert!(account_result.is_ok());
}
#[test]
fn program_row_exposes_registered_code() {
let row = kb_store::PostgresReplayProgramSummary {
program_id: kb_program_ids::SYSTEM_PROGRAM_ID.to_owned(),
transaction_count: 1,
outer_instruction_count: 1,
inner_instruction_count: 0,
log_count: 1,
min_slot: 1,
max_slot: 1,
};
let converted = crate::program_row_from_pg(row);
assert_eq!(converted.program_code.as_deref(), std::option::Option::Some("system"));
}
}