core: extract the store and sync engine into a shared crate (M12 step 1)
Android becomes a native Kotlin client over this same code (Scribe note 2730), so
the local store and sync engine stop being modules of the desktop app and become
`thoughtsync-core`, a crate with no UI framework in it at all.
This is a move, not a rewrite, and the measurement is why: every file in local/
and sync/ already carried ZERO Tauri references — 4,980 of 6,372 lines. The
coupling was 473 lines of command shim, which stays behind in the desktop crate
as src/commands/. Kept as git renames so history follows the files.
The desktop imports them under their old names (`use thoughtsync_core::{local,
sync}`) so every call site reads exactly as before. What moved is where they
live, not what they are.
Two things a workspace changes that are easy to miss, both caught before pushing:
[profile.release] now lives at the workspace ROOT. Cargo silently ignores
profiles declared by a non-root member — leaving it in the desktop crate would
have dropped lto/strip/opt-level from every release build with only a warning.
And a workspace shares ONE target dir, so the bundles moved from
desktop/src-tauri/target to target/. Thirteen references across publish-release,
debundle-graphics, verify.sh, package-prebuilt and the workflow now point there.
Pinning target-dir back would have been the smaller diff, but the Android lane
also produces Rust artifacts and they do not belong under desktop/.
Also retires the Tauri Android lane in the same push rather than leaving a path
that is being replaced: gen/android, android.yml and docs/android-dev.md are
gone, the mobile_entry_point attribute with them, and the lib drops to rlib —
staticlib/cdylib existed for Tauri mobile, and the .so Android loads will be
built from the core crate instead. Rule 22, no parallel path.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
//! The local-first store: on-device SQLite, and the source of truth for every
|
||||
//! client. A client built on this is fully usable with no server and no account.
|
||||
//!
|
||||
//! Framework-free on purpose. The desktop reaches it through Tauri commands and
|
||||
//! Android through uniffi, but neither of those concerns appears in here.
|
||||
|
||||
pub mod derive;
|
||||
pub mod models;
|
||||
pub mod retention;
|
||||
pub mod schema;
|
||||
pub mod store;
|
||||
|
||||
use std::path::Path;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use rusqlite::Connection;
|
||||
|
||||
/// The shared database handle. rusqlite connections aren't `Sync`, so a `Mutex`
|
||||
/// serializes access — fine, since operations are quick and a client is single-user.
|
||||
/// How it is held is the caller's business: Tauri manages it as state, Android holds
|
||||
/// it in the uniffi object.
|
||||
pub struct Db(pub Mutex<Connection>);
|
||||
|
||||
/// Open (creating if needed) the database at `path` and bring it to the latest schema.
|
||||
pub fn open(path: &Path) -> rusqlite::Result<Db> {
|
||||
let conn = Connection::open(path)?;
|
||||
schema::migrate(&conn)?;
|
||||
Ok(Db(Mutex::new(conn)))
|
||||
}
|
||||
|
||||
/// A one-line count summary of the store, for the startup log.
|
||||
pub fn summary(db: &Db) -> String {
|
||||
let conn = match db.0.lock() {
|
||||
Ok(c) => c,
|
||||
Err(_) => return "counts unavailable (lock poisoned)".to_string(),
|
||||
};
|
||||
let count = |sql: &str| {
|
||||
conn.query_row(sql, [], |r| r.get::<_, i64>(0))
|
||||
.unwrap_or(-1)
|
||||
};
|
||||
format!(
|
||||
"{} notes, {} labels",
|
||||
count("SELECT COUNT(*) FROM notes"),
|
||||
count("SELECT COUNT(*) FROM labels"),
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user