1use anyhow::Context;
27use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePool, SqlitePoolOptions};
28use std::path::Path;
29
30mod control;
31mod helpers;
32mod jobs;
33mod kv;
34mod metrics;
35mod pricing;
36mod tasks;
37
38#[allow(unused_imports)]
39pub use control::{parse_since_duration, ChatCostSummary, ControlMessage, MemoryEntry};
40#[allow(unused_imports)]
41pub use helpers::{
42 get_cost_estimate, get_cost_estimate_result, get_recent_memory, get_recent_memory_result,
43 get_task_field, get_task_field_direct, get_token_usage, get_token_usage_result,
44 opt_store_get_task, opt_store_get_task_by_id, resolve_store_id, review_session_expected,
45 set_review_session_expected, store_increment, store_increment_by_id, store_log_activity,
46 store_reset_counters, store_reset_failure_counters, store_set, store_set_by_id,
47 store_set_result, store_set_result_by_id, store_touch_updated_at, store_touch_updated_at_by_id,
48 StoreResult,
49};
50#[allow(unused_imports)]
51pub use jobs::JobState;
52#[allow(unused_imports)]
53pub use metrics::{
54 AgentStat, CostByGroup, CostPeriod, CostSummary, ErrorStat, HighReviewCycleTask,
55 InsertTaskMetric, MetricsSummary, SlowTaskInfo,
56};
57#[allow(unused_imports)]
58pub use pricing::{pricing_for_model, CostEstimate, ModelPricing, TokenUsage};
59#[allow(unused_imports)]
60pub use tasks::{
61 CompleteRun, NewTask, RunTokenUsage, StartRun, StoreRoute, Task, TaskActivity, TaskRun,
62 TaskStatus, UpsertExternal,
63};
64
65pub async fn default_db_path() -> anyhow::Result<std::path::PathBuf> {
67 crate::home::db_path().await
68}
69
70#[derive(Clone)]
72pub struct TaskStore {
73 pool: SqlitePool,
74}
75
76impl TaskStore {
77 pub async fn open(db_path: &Path) -> anyhow::Result<Self> {
81 let options = SqliteConnectOptions::new()
82 .filename(db_path)
83 .journal_mode(SqliteJournalMode::Wal)
84 .create_if_missing(true)
85 .busy_timeout(std::time::Duration::from_secs(5))
86 .pragma("foreign_keys", "ON");
87
88 let pool = SqlitePoolOptions::new()
89 .max_connections(5)
90 .connect_with(options)
91 .await
92 .with_context(|| format!("opening task store: {}", db_path.display()))?;
93
94 let store = Self { pool };
95 store.migrate().await?;
96 Ok(store)
97 }
98
99 #[allow(dead_code)]
101 pub async fn open_single(db_path: &Path) -> anyhow::Result<Self> {
106 let options = SqliteConnectOptions::new()
107 .filename(db_path)
108 .journal_mode(SqliteJournalMode::Wal)
109 .create_if_missing(true)
110 .busy_timeout(std::time::Duration::from_secs(5))
111 .pragma("foreign_keys", "ON");
112
113 let pool = SqlitePoolOptions::new()
114 .max_connections(1)
115 .connect_with(options)
116 .await
117 .with_context(|| format!("opening task store (single): {}", db_path.display()))?;
118
119 let store = Self { pool };
120 store.migrate().await?;
121 Ok(store)
122 }
123
124 #[cfg(test)]
126 pub async fn open_memory() -> anyhow::Result<Self> {
127 let options = SqliteConnectOptions::new()
128 .filename(":memory:")
129 .pragma("foreign_keys", "ON");
130 let pool = SqlitePoolOptions::new()
131 .max_connections(1)
132 .connect_with(options)
133 .await?;
134
135 let store = Self { pool };
136 store.migrate().await?;
137 Ok(store)
138 }
139
140 async fn migrate(&self) -> anyhow::Result<()> {
142 sqlx::migrate!("./migrations")
143 .run(&self.pool)
144 .await
145 .context("running task store migrations")?;
146 Ok(())
147 }
148
149 #[allow(dead_code)]
151 pub fn pool(&self) -> &SqlitePool {
152 &self.pool
153 }
154}
155
156#[cfg(test)]
157mod tests;