refactor(monorepo): reorganización lógica + renames + SDDs + split CHANGELOG
Reorganización física de crates/: - core/ (mezclaba 6 propósitos) se divide en protocol/, init/, runtime/, compat/ - shared/ (3 crates) se redistribuye en protocol/ e init/ - lapaloma (sub-módulo de ui_engine) se promueve a modules/pineal/ Renames de proyectos: - shipote → shuma (runtime de sandboxes) - nouser → akasha (explorador de Mónadas) - yahweh → nahual (motor GPUI, antes ui_engine/) - lapaloma → pineal (data-viz agnóstica) Fraccionamiento UI → core agnóstico: - vista-core (DeckState + snap, 175 LOC, 5 tests verdes) - barra-core (Task + render_html + sanitize, 90 LOC, 5 tests verdes) - vista-web y barra-web ahora son thin DOM bindings Documentación nueva: - 16 SDDs por subdirectorio (≤80 LOC c/u): protocol/init/runtime/compat + 10 módulos + apps/ - docs/STATUS.md con cifras reales por proyecto - docs/ROADMAP.md con plan a finalización (6 hitos, ~6-8 semanas) - CHANGELOG.md particionado en docs/changelog/<proyecto>.md (7 buckets) Automatización: - scripts/reorg.py — script idempotente que: git mv directorios, renombra package names, recomputa path = refs, reescribe imports rust, actualiza workspace Cargo.toml. Soporta --dry-run. - scripts/split-changelog.py — particiona CHANGELOG por componente. Validación: - cargo check --workspace pasa (124 crates + 2 nuevos cores). - 10 tests adicionales (5 en vista-core + 5 en barra-core) verdes. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
//! Provider de SQLite. Crate puro: cero dependencia de UI.
|
||||
//! Tabla `items(id, parent_id, name, display_type, content)` con jerarquía
|
||||
//! por `parent_id NULL` = raíz.
|
||||
|
||||
use async_trait::async_trait;
|
||||
use rusqlite::Connection;
|
||||
use std::io::Cursor;
|
||||
use std::pin::Pin;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use nahual_core::{DataProvider, DisplayType, EntityNode};
|
||||
|
||||
pub const PROVIDER_ID: &str = "sqlite_db";
|
||||
|
||||
pub struct SqliteDataProvider {
|
||||
db: Arc<Mutex<Connection>>,
|
||||
}
|
||||
|
||||
impl SqliteDataProvider {
|
||||
pub fn new(path: &str) -> Result<Self, String> {
|
||||
let conn = Connection::open(path).map_err(|e| e.to_string())?;
|
||||
|
||||
conn.execute(
|
||||
"CREATE TABLE IF NOT EXISTS items (
|
||||
id TEXT PRIMARY KEY,
|
||||
parent_id TEXT,
|
||||
name TEXT NOT NULL,
|
||||
display_type TEXT NOT NULL,
|
||||
content BLOB
|
||||
)",
|
||||
[],
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
// Seed mínimo si la tabla está vacía — para que el DatabaseExplorer
|
||||
// tenga algo que mostrar en una primera ejecución sin pre-config.
|
||||
let count: i64 = conn
|
||||
.query_row("SELECT COUNT(*) FROM items", [], |row| row.get(0))
|
||||
.unwrap_or(0);
|
||||
if count == 0 {
|
||||
let _ = conn.execute(
|
||||
"INSERT INTO items (id, parent_id, name, display_type, content) VALUES \
|
||||
('readme', NULL, 'README.md', 'File', ?), \
|
||||
('notes', NULL, 'notes', 'Folder', NULL), \
|
||||
('todo', 'notes', 'TODO.md', 'File', ?)",
|
||||
rusqlite::params![
|
||||
b"# Yahweh\n\nDemo readme stored in SQLite.\n",
|
||||
b"- TreeView gen\xC3\xA9rico\n- containers swappables\n- layout JSON\n",
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
db: Arc::new(Mutex::new(conn)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl DataProvider for SqliteDataProvider {
|
||||
fn provider_id(&self) -> String {
|
||||
PROVIDER_ID.to_string()
|
||||
}
|
||||
|
||||
async fn list_children(&self, parent_id: Option<&str>) -> Result<Vec<EntityNode>, String> {
|
||||
let db = self.db.lock().unwrap();
|
||||
let mut stmt = db
|
||||
.prepare("SELECT id, name, display_type FROM items WHERE parent_id IS ?")
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let rows = stmt
|
||||
.query_map([parent_id], |row| {
|
||||
let display_type_str: String = row.get(2)?;
|
||||
let display_type = match display_type_str.as_str() {
|
||||
"Folder" => DisplayType::Folder,
|
||||
"Stream" => DisplayType::Stream,
|
||||
_ => DisplayType::File,
|
||||
};
|
||||
|
||||
Ok(EntityNode {
|
||||
id: row.get(0)?,
|
||||
name: row.get(1)?,
|
||||
display_type,
|
||||
mime_type: None,
|
||||
})
|
||||
})
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let mut children = Vec::new();
|
||||
for row in rows {
|
||||
children.push(row.map_err(|e| e.to_string())?);
|
||||
}
|
||||
Ok(children)
|
||||
}
|
||||
|
||||
async fn get_read_stream(
|
||||
&self,
|
||||
entity_id: &str,
|
||||
) -> Result<Pin<Box<dyn AsyncRead + Send>>, String> {
|
||||
let db = self.db.lock().unwrap();
|
||||
let content: Vec<u8> = db
|
||||
.query_row(
|
||||
"SELECT content FROM items WHERE id = ?",
|
||||
[entity_id],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
Ok(Box::pin(Cursor::new(content)))
|
||||
}
|
||||
|
||||
async fn get_write_stream(
|
||||
&self,
|
||||
_entity_id: &str,
|
||||
) -> Result<Pin<Box<dyn AsyncWrite + Send>>, String> {
|
||||
Err("Escritura en streaming no implementada para SQLite (todavía)".to_string())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user