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,283 @@
|
||||
//! Tauri command surface for the local store — the offline mirror of the REST API
|
||||
//! the frontend's repository seam calls. Each command locks the shared connection and
|
||||
//! delegates to `store`; errors are stringified for the JS boundary. adapters/local.ts
|
||||
//! (M10.5) invokes these.
|
||||
|
||||
use serde_json::Value;
|
||||
use tauri::State;
|
||||
|
||||
use thoughtsync_core::local::models::*;
|
||||
use thoughtsync_core::local::retention;
|
||||
use thoughtsync_core::local::store;
|
||||
use thoughtsync_core::local::Db;
|
||||
use thoughtsync_core::sync::state;
|
||||
|
||||
// A macro would hide the (very regular) locking; kept explicit so each command reads
|
||||
// as an obvious lock -> delegate -> stringify.
|
||||
|
||||
#[tauri::command]
|
||||
pub fn config_get(db: State<'_, Db>) -> PublicConfig {
|
||||
// What the Trash view counts down against: the linked server's window if we know
|
||||
// it, else this device's own. Reading it here rather than hard-coding the offline
|
||||
// default is what keeps the deadline on screen equal to the one that will actually
|
||||
// be enforced. A store error falls back to the default rather than failing the
|
||||
// call — the app must still boot.
|
||||
let fallback = retention::LOCAL_RETENTION_DAYS;
|
||||
let retention_days = match db.0.lock() {
|
||||
Ok(conn) => state::effective_retention_days(&conn, fallback).unwrap_or(fallback),
|
||||
Err(_) => fallback,
|
||||
};
|
||||
// Offline defaults: no signups, no server-side URL unfurling (needs network).
|
||||
PublicConfig {
|
||||
site_name: "ThoughtSync".to_string(),
|
||||
allow_registration: false,
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
enable_url_unfurl: false,
|
||||
trash_retention_days: retention_days.max(0) as u32,
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn auth_me() -> User {
|
||||
// The single synthetic local user, so the auth-gated router resolves with no login.
|
||||
User {
|
||||
id: "local".to_string(),
|
||||
email: "local@thoughtsync.app".to_string(),
|
||||
display_name: "You".to_string(),
|
||||
email_verified: true,
|
||||
is_admin: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn notes_list(query: ListQuery, db: State<'_, Db>) -> Result<Vec<Note>, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
store::list_notes(&conn, &query).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn notes_get(id: String, db: State<'_, Db>) -> Result<Note, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
store::get_note(&conn, &id).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn notes_create(input: NoteCreateInput, db: State<'_, Db>) -> Result<Note, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
store::create_note(&conn, &input).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn notes_create_titled(title: String, db: State<'_, Db>) -> Result<Note, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
store::create_titled(&conn, &title).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn notes_update(id: String, changes: Value, db: State<'_, Db>) -> Result<Note, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
store::update_note(&conn, &id, &changes).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn notes_complete_reminder(id: String, db: State<'_, Db>) -> Result<Note, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
store::complete_reminder(&conn, &id).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn notes_snooze_reminder(id: String, minutes: i64, db: State<'_, Db>) -> Result<Note, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
store::snooze_reminder(&conn, &id, minutes).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn notes_set_labels(
|
||||
id: String,
|
||||
label_ids: Vec<String>,
|
||||
db: State<'_, Db>,
|
||||
) -> Result<Note, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
store::set_labels(&conn, &id, &label_ids).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn notes_add_item(id: String, text: String, db: State<'_, Db>) -> Result<Note, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
store::add_item(&conn, &id, &text).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn notes_update_item(
|
||||
id: String,
|
||||
item_id: String,
|
||||
changes: Value,
|
||||
db: State<'_, Db>,
|
||||
) -> Result<Note, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
store::update_item(&conn, &id, &item_id, &changes).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn notes_delete_item(id: String, item_id: String, db: State<'_, Db>) -> Result<Note, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
store::delete_item(&conn, &id, &item_id).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn notes_delete_attachment(
|
||||
id: String,
|
||||
att_id: String,
|
||||
db: State<'_, Db>,
|
||||
) -> Result<Note, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
store::delete_attachment(&conn, &id, &att_id).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn notes_delete_preview(
|
||||
id: String,
|
||||
preview_id: String,
|
||||
db: State<'_, Db>,
|
||||
) -> Result<Note, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
store::delete_preview(&conn, &id, &preview_id).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn notes_reorder(ordered_ids: Vec<String>, db: State<'_, Db>) -> Result<(), String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
store::reorder(&conn, &ordered_ids).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn notes_trash(id: String, db: State<'_, Db>) -> Result<Note, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
store::trash(&conn, &id).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn notes_restore(id: String, db: State<'_, Db>) -> Result<Note, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
store::restore(&conn, &id).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn notes_delete_forever(id: String, db: State<'_, Db>) -> Result<(), String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
store::delete_forever(&conn, &id).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn notes_revisions(id: String, db: State<'_, Db>) -> Result<Vec<NoteRevision>, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
store::revisions(&conn, &id).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn notes_restore_revision(
|
||||
id: String,
|
||||
rev_id: String,
|
||||
db: State<'_, Db>,
|
||||
) -> Result<Note, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
store::restore_revision(&conn, &id, &rev_id).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn notes_reminders(db: State<'_, Db>) -> Result<Vec<Note>, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
store::reminders(&conn).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn notes_titles(db: State<'_, Db>) -> Result<Vec<TitleEntry>, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
store::titles(&conn).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn notes_search(q: String, db: State<'_, Db>) -> Result<Vec<Note>, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
store::search(&conn, &q).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn notes_backlinks(id: String, db: State<'_, Db>) -> Result<Vec<Backlink>, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
store::backlinks(&conn, &id).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn notes_link_search(q: String, db: State<'_, Db>) -> Result<Vec<TitleEntry>, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
store::link_search(&conn, &q).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn labels_list(db: State<'_, Db>) -> Result<Vec<Label>, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
store::list_labels(&conn).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn labels_create(name: String, db: State<'_, Db>) -> Result<Label, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
store::create_label(&conn, &name).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn labels_rename(id: String, name: String, db: State<'_, Db>) -> Result<Label, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
store::rename_label(&conn, &id, &name).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn labels_set_color(id: String, color: String, db: State<'_, Db>) -> Result<Label, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
store::set_label_color(&conn, &id, &color).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn labels_remove(id: String, db: State<'_, Db>) -> Result<(), String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
store::remove_label(&conn, &id).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn labels_merge(source_id: String, into: String, db: State<'_, Db>) -> Result<Label, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
store::merge_labels(&conn, &source_id, &into).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn saved_filters_list(db: State<'_, Db>) -> Result<Vec<SavedFilter>, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
store::list_saved_filters(&conn).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn saved_filters_create(
|
||||
name: String,
|
||||
params: Value,
|
||||
db: State<'_, Db>,
|
||||
) -> Result<SavedFilter, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
store::create_saved_filter(&conn, &name, ¶ms).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn saved_filters_remove(id: String, db: State<'_, Db>) -> Result<(), String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
store::remove_saved_filter(&conn, &id).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn saved_filters_rename(
|
||||
id: String,
|
||||
name: String,
|
||||
db: State<'_, Db>,
|
||||
) -> Result<SavedFilter, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
store::rename_saved_filter(&conn, &id, &name).map_err(|e| e.to_string())
|
||||
}
|
||||
Reference in New Issue
Block a user