// 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, } /// 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, /// Optional inclusive minimum slot. #[ts(type = "number | null")] pub(crate) min_slot: std::option::Option, /// Optional inclusive maximum slot. #[ts(type = "number | null")] pub(crate) max_slot: std::option::Option, /// Optional raw processing state. pub(crate) raw_processing_state: std::option::Option, /// Optional latest ledger status. pub(crate) ledger_status: std::option::Option, /// Optional exact program id. pub(crate) program_id: std::option::Option, /// 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, /// Optional exact entity value. pub(crate) entity_value: std::option::Option, /// 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, /// Latest core extraction ledger status. pub(crate) ledger_status: std::string::String, /// Latest processor version when available. pub(crate) processor_version: std::option::Option, /// 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, /// 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, /// 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, /// 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 { 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 { 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::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 { 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")); } }