Skip to main content

codewhale_core/engine/thread/
store.rs

1//! `RuntimeThreadStore` — persisted JSON state (issue #5261 / #3313).
2//!
3//! The store is the `state.json` + `<root>/{threads,turns,items,events}`
4//! layout that `crates/state` already owns. This module is the `core`
5//! owner for that layout so the TUI's `RuntimeThreadManager` can be split
6//! without changing the file shape. The current `ThreadManager` in
7//! `crates/core/src/lib.rs` already uses `StateStore`; this file is the
8//! next home for that impl once the `git mv` lands. Until then it
9//! documents the contract and exposes the typed store handle.
10
11use codewhale_protocol::ids::ThreadId;
12use codewhale_state::StateStore;
13
14/// Typed handle over `StateStore` that the executor and events modules share.
15/// The methods are thin wrappers so the store boundary is greppable and the
16/// persisted shape can be asserted in one place (back-compat tests hold).
17#[derive(Debug, Clone)]
18pub struct ThreadStore {
19    inner: StateStore,
20    root: std::path::PathBuf,
21}
22
23impl ThreadStore {
24    #[must_use]
25    pub fn new(inner: StateStore, root: std::path::PathBuf) -> Self {
26        Self { inner, root }
27    }
28
29    #[must_use]
30    pub fn state(&self) -> &StateStore {
31        &self.inner
32    }
33
34    #[must_use]
35    pub fn root(&self) -> &std::path::Path {
36        &self.root
37    }
38
39    pub fn thread_exists(&self, id: &ThreadId) -> anyhow::Result<bool> {
40        Ok(self.inner.get_thread(id.as_str())?.is_some())
41    }
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47    use tempfile::tempdir;
48
49    #[test]
50    fn store_wraps_state() {
51        let dir = tempdir().unwrap();
52        let state = StateStore::open(Some(dir.path().join("state.db"))).unwrap();
53        let store = ThreadStore::new(state, dir.path().to_path_buf());
54        assert!(!store.thread_exists(&ThreadId::new()).unwrap());
55    }
56}