Add multi-database data layer

Introduce aether-data-schema and driver-specific schema generation for Postgres, MySQL, and SQLite.

Split data backends, lifecycle, repositories, and gateway runtime integration across database drivers.

Verified with cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings, and cargo test --workspace.
This commit is contained in:
fawney19
2026-05-05 18:27:36 +08:00
parent 099653f732
commit fce7e959e5
372 changed files with 86217 additions and 21160 deletions

View File

@@ -9,6 +9,7 @@ description = "Shared data contracts and repository traits for Aether Rust servi
[dependencies]
aether-ai-formats.workspace = true
async-trait.workspace = true
chrono.workspace = true
serde.workspace = true
serde_json.workspace = true
thiserror.workspace = true

View File

@@ -12,6 +12,9 @@ pub enum DataLayerError {
#[error("redis error: {0}")]
Redis(String),
#[error("sql error: {0}")]
Sql(String),
#[error("operation timed out: {0}")]
TimedOut(String),
@@ -27,4 +30,8 @@ impl DataLayerError {
pub fn redis(error: impl std::fmt::Display) -> Self {
Self::Redis(error.to_string())
}
pub fn sql(error: impl std::fmt::Display) -> Self {
Self::Sql(error.to_string())
}
}

View File

@@ -1,7 +1,7 @@
mod types;
pub use types::{
AdminBillingCollectorRecord, AdminBillingCollectorWriteInput, AdminBillingPresetApplyResult,
AdminBillingRuleRecord, AdminBillingRuleWriteInput, BillingReadRepository,
StoredBillingModelContext,
AdminBillingCollectorRecord, AdminBillingCollectorWriteInput, AdminBillingMutationOutcome,
AdminBillingPresetApplyResult, AdminBillingRuleRecord, AdminBillingRuleWriteInput,
BillingReadRepository, StoredBillingModelContext,
};

View File

@@ -142,6 +142,14 @@ pub struct AdminBillingPresetApplyResult {
pub errors: Vec<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum AdminBillingMutationOutcome<T> {
Applied(T),
NotFound,
Invalid(String),
Unavailable,
}
#[async_trait]
pub trait BillingReadRepository: Send + Sync {
async fn find_model_context(
@@ -160,4 +168,109 @@ pub trait BillingReadRepository: Send + Sync {
let _ = (provider_id, provider_api_key_id, model_id);
Ok(None)
}
async fn admin_billing_enabled_default_value_exists(
&self,
api_format: &str,
task_type: &str,
dimension_name: &str,
existing_id: Option<&str>,
) -> Result<Option<bool>, crate::DataLayerError> {
let _ = (api_format, task_type, dimension_name, existing_id);
Ok(None)
}
async fn create_admin_billing_rule(
&self,
input: &AdminBillingRuleWriteInput,
) -> Result<AdminBillingMutationOutcome<AdminBillingRuleRecord>, crate::DataLayerError> {
let _ = input;
Ok(AdminBillingMutationOutcome::Unavailable)
}
async fn list_admin_billing_rules(
&self,
task_type: Option<&str>,
is_enabled: Option<bool>,
page: u32,
page_size: u32,
) -> Result<Option<(Vec<AdminBillingRuleRecord>, u64)>, crate::DataLayerError> {
let _ = (task_type, is_enabled, page, page_size);
Ok(None)
}
async fn find_admin_billing_rule(
&self,
rule_id: &str,
) -> Result<Option<AdminBillingRuleRecord>, crate::DataLayerError> {
let _ = rule_id;
Ok(None)
}
async fn update_admin_billing_rule(
&self,
rule_id: &str,
input: &AdminBillingRuleWriteInput,
) -> Result<AdminBillingMutationOutcome<AdminBillingRuleRecord>, crate::DataLayerError> {
let _ = (rule_id, input);
Ok(AdminBillingMutationOutcome::Unavailable)
}
async fn create_admin_billing_collector(
&self,
input: &AdminBillingCollectorWriteInput,
) -> Result<AdminBillingMutationOutcome<AdminBillingCollectorRecord>, crate::DataLayerError>
{
let _ = input;
Ok(AdminBillingMutationOutcome::Unavailable)
}
async fn list_admin_billing_collectors(
&self,
api_format: Option<&str>,
task_type: Option<&str>,
dimension_name: Option<&str>,
is_enabled: Option<bool>,
page: u32,
page_size: u32,
) -> Result<Option<(Vec<AdminBillingCollectorRecord>, u64)>, crate::DataLayerError> {
let _ = (
api_format,
task_type,
dimension_name,
is_enabled,
page,
page_size,
);
Ok(None)
}
async fn find_admin_billing_collector(
&self,
collector_id: &str,
) -> Result<Option<AdminBillingCollectorRecord>, crate::DataLayerError> {
let _ = collector_id;
Ok(None)
}
async fn update_admin_billing_collector(
&self,
collector_id: &str,
input: &AdminBillingCollectorWriteInput,
) -> Result<AdminBillingMutationOutcome<AdminBillingCollectorRecord>, crate::DataLayerError>
{
let _ = (collector_id, input);
Ok(AdminBillingMutationOutcome::Unavailable)
}
async fn apply_admin_billing_preset(
&self,
preset: &str,
mode: &str,
collectors: &[AdminBillingCollectorWriteInput],
) -> Result<AdminBillingMutationOutcome<AdminBillingPresetApplyResult>, crate::DataLayerError>
{
let _ = (preset, mode, collectors);
Ok(AdminBillingMutationOutcome::Unavailable)
}
}

View File

@@ -1,21 +1,23 @@
mod types;
pub use types::{
parse_usage_body_ref, usage_body_ref, StoredProviderApiKeyUsageSummary,
StoredProviderUsageSummary, StoredProviderUsageWindow, StoredRequestUsageAudit,
StoredUsageAuditAggregation, StoredUsageAuditSummary, StoredUsageBreakdownSummaryRow,
StoredUsageCacheAffinityHitSummary, StoredUsageCacheAffinityIntervalRow,
StoredUsageCacheHitSummary, StoredUsageCostSavingsSummary, StoredUsageDailySummary,
StoredUsageDashboardDailyBreakdownRow, StoredUsageDashboardProviderCount,
StoredUsageDashboardSummary, StoredUsageErrorDistributionRow, StoredUsageLeaderboardSummary,
parse_usage_body_ref, usage_body_ref, PendingUsageCleanupSummary,
StoredProviderApiKeyUsageSummary, StoredProviderUsageSummary, StoredProviderUsageWindow,
StoredRequestUsageAudit, StoredUsageAuditAggregation, StoredUsageAuditSummary,
StoredUsageBreakdownSummaryRow, StoredUsageCacheAffinityHitSummary,
StoredUsageCacheAffinityIntervalRow, StoredUsageCacheHitSummary, StoredUsageCostSavingsSummary,
StoredUsageDailySummary, StoredUsageDashboardDailyBreakdownRow,
StoredUsageDashboardProviderCount, StoredUsageDashboardSummary,
StoredUsageErrorDistributionRow, StoredUsageLeaderboardSummary,
StoredUsagePerformancePercentilesRow, StoredUsageSettledCostSummary,
StoredUsageTimeSeriesBucket, StoredUsageUserTotals, UpsertUsageRecord,
UsageAuditAggregationGroupBy, UsageAuditAggregationQuery, UsageAuditKeywordSearchQuery,
UsageAuditListQuery, UsageAuditSummaryQuery, UsageBodyCaptureResult, UsageBodyCaptureState,
UsageBodyCaptureStorage, UsageBodyField, UsageBreakdownGroupBy, UsageBreakdownSummaryQuery,
UsageCacheAffinityHitSummaryQuery, UsageCacheAffinityIntervalGroupBy,
UsageCacheAffinityIntervalQuery, UsageCacheHitSummaryQuery, UsageCostSavingsSummaryQuery,
UsageDailyHeatmapQuery, UsageDashboardDailyBreakdownQuery, UsageDashboardProviderCountsQuery,
UsageCacheAffinityIntervalQuery, UsageCacheHitSummaryQuery, UsageCleanupSummary,
UsageCleanupWindow, UsageCostSavingsSummaryQuery, UsageDailyHeatmapQuery,
UsageDashboardDailyBreakdownQuery, UsageDashboardProviderCountsQuery,
UsageDashboardSummaryQuery, UsageErrorDistributionQuery, UsageLeaderboardGroupBy,
UsageLeaderboardQuery, UsageMonitoringErrorCountQuery, UsageMonitoringErrorListQuery,
UsagePerformancePercentilesQuery, UsageReadRepository, UsageRepository,

View File

@@ -1,4 +1,5 @@
use async_trait::async_trait;
use chrono::{DateTime, Utc};
use serde_json::Value;
/// Joined usage read model assembled from the accounting row plus the newer audit/snapshot
@@ -1568,12 +1569,57 @@ pub trait UsageWriteRepository: Send + Sync {
async fn rebuild_api_key_usage_stats(&self) -> Result<u64, crate::DataLayerError>;
async fn rebuild_provider_api_key_usage_stats(&self) -> Result<u64, crate::DataLayerError>;
async fn cleanup_stale_pending_requests(
&self,
cutoff_unix_secs: u64,
now_unix_secs: u64,
timeout_minutes: u64,
batch_size: usize,
) -> Result<PendingUsageCleanupSummary, crate::DataLayerError> {
let _ = (cutoff_unix_secs, now_unix_secs, timeout_minutes, batch_size);
Ok(PendingUsageCleanupSummary::default())
}
async fn cleanup_usage(
&self,
window: &UsageCleanupWindow,
batch_size: usize,
auto_delete_expired_keys: bool,
) -> Result<UsageCleanupSummary, crate::DataLayerError> {
let _ = (window, batch_size, auto_delete_expired_keys);
Ok(UsageCleanupSummary::default())
}
}
pub trait UsageRepository: UsageReadRepository + UsageWriteRepository + Send + Sync {}
impl<T> UsageRepository for T where T: UsageReadRepository + UsageWriteRepository + Send + Sync {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct PendingUsageCleanupSummary {
pub failed: usize,
pub recovered: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
pub struct UsageCleanupSummary {
pub body_externalized: usize,
pub legacy_body_refs_migrated: usize,
pub body_cleaned: usize,
pub header_cleaned: usize,
pub keys_cleaned: usize,
pub records_deleted: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct UsageCleanupWindow {
pub detail_cutoff: DateTime<Utc>,
pub compressed_cutoff: DateTime<Utc>,
pub header_cutoff: DateTime<Utc>,
pub log_cutoff: DateTime<Utc>,
}
fn parse_u64(value: i32, field_name: &str) -> Result<u64, crate::DataLayerError> {
u64::try_from(value).map_err(|_| {
crate::DataLayerError::UnexpectedValue(format!("invalid {field_name}: {value}"))