DatabaseWrapper is #[derive(Clone)] and all clones share one underlying write
connection, but observation is stored in a per-value observer: Option<ObservableSqliteDatabase> field. enable_observation() therefore turns
observation on for the handle it is called on only — a sibling clone keeps observer: None, so its acquire_writer() returns the regular (bypass) writer, and a committed
write through it fires no hooks and reaches no subscriber. No error, no warning.
This bites any Tauri consumer that observes via a clone. app.connect(key) (and the JS
Database.load(key)) hand out clones of the plugin's cached wrapper, so a Rust consumer
that does app.connect(key) → enable_observation() → subscribe() silently misses
writes made through every other app.connect(key) clone (command handlers, background
tasks). The only handle whose observation later clones inherit is the cached entry itself,
which is mutated solely by the JS observe command — unreachable from Rust callers, since
the instance cache is private to the crate.
Repro
Minimal, runnable repro (cargo run, prints an explicit verdict; includes a control that
proves the probe detects real notifications):
https://github.com/yokuze/mcve/tree/23aa4a2acc7e0956ba499751770d4c54b9451da1/cases/003-sqlx-sqlite-toolkit-observation-not-shared-across-clones
Root cause (rev e1b3836)
Expected
Observation should apply to the database, not to one handle — e.g. store the observer
behind shared interior state so that enabling it on any clone makes all clones (existing
and future) observe the same broker, or at minimum make a bypassed write on a database
observed elsewhere detectable instead of silently dropped.
DatabaseWrapperis#[derive(Clone)]and all clones share one underlying writeconnection, but observation is stored in a per-value
observer: Option<ObservableSqliteDatabase>field.enable_observation()therefore turnsobservation on for the handle it is called on only — a sibling clone keeps
observer: None, so itsacquire_writer()returns the regular (bypass) writer, and a committedwrite through it fires no hooks and reaches no subscriber. No error, no warning.
This bites any Tauri consumer that observes via a clone.
app.connect(key)(and the JSDatabase.load(key)) hand out clones of the plugin's cached wrapper, so a Rust consumerthat does
app.connect(key)→enable_observation()→subscribe()silently misseswrites made through every other
app.connect(key)clone (command handlers, backgroundtasks). The only handle whose observation later clones inherit is the cached entry itself,
which is mutated solely by the JS
observecommand — unreachable from Rust callers, sincethe instance cache is private to the crate.
Repro
Minimal, runnable repro (
cargo run, prints an explicit verdict; includes a control thatproves the probe detects real notifications):
https://github.com/yokuze/mcve/tree/23aa4a2acc7e0956ba499751770d4c54b9451da1/cases/003-sqlx-sqlite-toolkit-observation-not-shared-across-clones
Root cause (rev
e1b3836)observerbeside a sharedArc<SqliteDatabase>:wrapper.rs#L67-L72acquire_writer()routes onself.observeralone:wrapper.rs#L92-L100enable_observation(&mut self)sets the observer on this handle only:wrapper.rs#L396-L402impl Clone for ObservableSqliteDatabaseshares the brokerArc, so a clone taken after enable does observe (the one working path):conn_mgr.rs#L222-L229Expected
Observation should apply to the database, not to one handle — e.g. store the observer
behind shared interior state so that enabling it on any clone makes all clones (existing
and future) observe the same broker, or at minimum make a bypassed write on a database
observed elsewhere detectable instead of silently dropped.