⎇ 53dbdf0f1deea10d6fc567ea2b43e1e75d6d60cd
crates/modules/ui_engine/libs/providers/sqlite/src/lib.rs
Historia
Quién tocó qué
crudo
chore: monorepo inicial con arje + minga + yahweh absorbidos
1//! Provider de SQLite. Crate puro: cero dependencia de UI.
2//! Tabla `items(id, parent_id, name, display_type, content)` con jerarquía
3//! por `parent_id NULL` = raíz.
4
5use async_trait::async_trait;
6use rusqlite::Connection;
7use std::io::Cursor;
8use std::pin::Pin;
9use std::sync::{Arc, Mutex};
10use tokio::io::{AsyncRead, AsyncWrite};
11use yahweh_core::{DataProvider, DisplayType, EntityNode};
12
13pub const PROVIDER_ID: &str = "sqlite_db";
14
15pub struct SqliteDataProvider {
16 db: Arc<Mutex<Connection>>,
17}
18
19impl SqliteDataProvider {
20 pub fn new(path: &str) -> Result<Self, String> {
21 let conn = Connection::open(path).map_err(|e| e.to_string())?;
22
23 conn.execute(
24 "CREATE TABLE IF NOT EXISTS items (
25 id TEXT PRIMARY KEY,
26 parent_id TEXT,
27 name TEXT NOT NULL,
28 display_type TEXT NOT NULL,
29 content BLOB
30 )",
31 [],
32 )
33 .map_err(|e| e.to_string())?;
34
35 // Seed mínimo si la tabla está vacía — para que el DatabaseExplorer
36 // tenga algo que mostrar en una primera ejecución sin pre-config.
37 let count: i64 = conn
38 .query_row("SELECT COUNT(*) FROM items", [], |row| row.get(0))
39 .unwrap_or(0);
40 if count == 0 {
41 let _ = conn.execute(
42 "INSERT INTO items (id, parent_id, name, display_type, content) VALUES \
43 ('readme', NULL, 'README.md', 'File', ?), \
44 ('notes', NULL, 'notes', 'Folder', NULL), \
45 ('todo', 'notes', 'TODO.md', 'File', ?)",
46 rusqlite::params![
47 b"# Yahweh\n\nDemo readme stored in SQLite.\n",
48 b"- TreeView gen\xC3\xA9rico\n- containers swappables\n- layout JSON\n",
49 ],
50 );
51 }
52
53 Ok(Self {
54 db: Arc::new(Mutex::new(conn)),
55 })
56 }
57}
58
59#[async_trait]
60impl DataProvider for SqliteDataProvider {
61 fn provider_id(&self) -> String {
62 PROVIDER_ID.to_string()
63 }
64
65 async fn list_children(&self, parent_id: Option<&str>) -> Result<Vec<EntityNode>, String> {
66 let db = self.db.lock().unwrap();
67 let mut stmt = db
68 .prepare("SELECT id, name, display_type FROM items WHERE parent_id IS ?")
69 .map_err(|e| e.to_string())?;
70
71 let rows = stmt
72 .query_map([parent_id], |row| {
73 let display_type_str: String = row.get(2)?;
74 let display_type = match display_type_str.as_str() {
75 "Folder" => DisplayType::Folder,
76 "Stream" => DisplayType::Stream,
77 _ => DisplayType::File,
78 };
79
80 Ok(EntityNode {
81 id: row.get(0)?,
82 name: row.get(1)?,
83 display_type,
84 mime_type: None,
85 })
86 })
87 .map_err(|e| e.to_string())?;
88
89 let mut children = Vec::new();
90 for row in rows {
91 children.push(row.map_err(|e| e.to_string())?);
92 }
93 Ok(children)
94 }
95
96 async fn get_read_stream(
97 &self,
98 entity_id: &str,
99 ) -> Result<Pin<Box<dyn AsyncRead + Send>>, String> {
100 let db = self.db.lock().unwrap();
101 let content: Vec<u8> = db
102 .query_row(
103 "SELECT content FROM items WHERE id = ?",
104 [entity_id],
105 |row| row.get(0),
106 )
107 .map_err(|e| e.to_string())?;
108
109 Ok(Box::pin(Cursor::new(content)))
110 }
111
112 async fn get_write_stream(
113 &self,
114 _entity_id: &str,
115 ) -> Result<Pin<Box<dyn AsyncWrite + Send>>, String> {
116 Err("Escritura en streaming no implementada para SQLite (todavía)".to_string())
117 }
118}