Conversation
|
Cursor Agent can help with this pull request. Just |
|
More templates
@tanstack/angular-db
@tanstack/db
@tanstack/db-browser-wa-sqlite-persisted-collection
@tanstack/db-cloudflare-do-sqlite-persisted-collection
@tanstack/db-electron-sqlite-persisted-collection
@tanstack/db-ivm
@tanstack/db-node-sqlite-persisted-collection
@tanstack/db-react-native-sqlite-persisted-collection
@tanstack/db-sqlite-persisted-collection-core
@tanstack/electric-db-collection
@tanstack/offline-transactions
@tanstack/powersync-db-collection
@tanstack/query-db-collection
@tanstack/react-db
@tanstack/rxdb-db-collection
@tanstack/solid-db
@tanstack/svelte-db
@tanstack/trailbase-db-collection
@tanstack/vue-db
commit: |
|
Size Change: +1.92 kB (+2.06%) Total Size: 95.1 kB
ℹ️ View Unchanged
|
|
Size Change: 0 B Total Size: 3.85 kB ℹ️ View Unchanged
|
238391b to
283fc63
Compare
|
, not sure if this is the right place to share this, but I was able to get it working on my end. |
|
@solarsoft0 thats awesome! |
, I created one for Tauri, and I think my fix confirms it is not a storage layer issue. |
Hi @solarsoft0, i can confirm this issue in local-only mode as i'm hitting the same problem. Working on a fix for it. |
…cate tx skipping The PersistedCollectionRuntime never restored its stream position from the database on startup, always beginning at localTerm=1, localSeq=0. After a page reload, the first new mutation would collide with a previously applied transaction (term=1, seq=1), causing the SQLite adapter's applyCommittedTx to silently skip it as a duplicate. Add getStreamPosition to PersistenceAdapter (optional) and implement it in SQLiteCorePersistenceAdapter. Call it from startInternal() so that observeStreamPosition seeds the local counters before any mutations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Include full row values in the tx:committed message so receiving tabs can apply changes directly without a SQLite round-trip via loadRowsByKeys. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ti-tab support Web Locks for per-collection leadership election, BroadcastChannel for cross-tab RPC transport, DB writer lock for SQLite write serialization, envelope dedup for exactly-once mutations, and leader heartbeats. Includes 15 unit tests with Web Locks/BroadcastChannel mocks. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Multi-tab sync bug fixesThree bugs were identified and fixed that caused multi-tab sync to silently break. Together they caused follower tab mutations to disappear from the leader tab's in-memory collection, and eventually caused sync to stop working in both directions. Bug 1: Leadership state stuck after setup error — 5163087What: Fix: Wrap the entire lock callback body in a single Bug 2: Seq collision between leader direct path and coordinator — 533be70What: The leader tab had two mutation paths: a "direct" path (write to SQLite and broadcast) and a coordinator RPC path. Only follower tabs used the RPC path — the leader bypassed the coordinator entirely. This caused a seq collision: the leader's direct writes incremented the runtime's Fix: Always route through Bug 3: Leader ignoring coordinator-delivered tx:committed for follower mutations — 49720f6What: The runtime's Fix: Allow |
Previously, `state.isLeader = true` was set before the setup code that calls `getStreamPosition()`. If `getStreamPosition` threw (e.g. due to a UNIQUE constraint violation from React StrictMode double-mounting), `isLeader` remained permanently stuck at `true` because the `finally` block that resets it was inside an inner try/finally that was never entered. Fix: Wrap the entire lock callback body in a single try/finally. Set `state.isLeader = true` only after successful setup (stream position restore and term increment). The finally block always runs and resets `isLeader = false` + cleans up the heartbeat timer. Also refactors the coordinator to support lazy adapter wiring via `setAdapter()`, allowing `createBrowserWASQLitePersistence` to inject the adapter after construction. This enables the demo to construct the coordinator without requiring the adapter upfront. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nt seq collisions The leader tab had two mutation paths: a "direct" path (write to SQLite and broadcast) and an RPC path (through the coordinator). Previously, only follower tabs used the RPC path — the leader bypassed the coordinator and wrote directly. This caused a seq collision: the leader's direct writes incremented the runtime's `localSeq` but left the coordinator's `state.latestSeq` at 0. When a follower later sent an RPC, the coordinator assigned seq starting from 1 again, producing duplicate seq numbers. The leader then skipped these "already-seen" tx:committed messages, causing follower mutations to silently disappear. Fix: Always route through `requestApplyLocalMutations` when available, regardless of leader/follower status. This keeps the coordinator's seq counter in sync with all writes. Also removes `requestApplyLocalMutations` from `SingleProcessCoordinator` — it was a stub that returned success without persisting, which would break now that the leader uses this path. Single-process mode correctly falls back to the direct path since it has no multi-tab coordination. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…mmitted messages The leader tab's `onCoordinatorMessage` handler skipped ALL messages where `senderId` matched the coordinator's own node ID. But when the coordinator processes a follower's RPC in `handleApplyLocalMutations`, it delivers the resulting `tx:committed` to local subscribers using the coordinator's own `senderId`. This caused the leader's runtime to silently ignore follower mutations — they were written to SQLite but never applied to the leader's in-memory collection. Fix: Allow `tx:committed` messages from self to pass through the filter. The seq dedup logic in `processCommittedTxUnsafe` already prevents double-processing: when the leader's own mutations go through the coordinator, `observeStreamPosition` is called with the response's term/seq before the local `tx:committed` delivery runs under the mutex, so the duplicate is detected via `txCommitted.seq <= this.latestSeq`. Other message types (heartbeats, resets) from self are still skipped. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…riteMessage
When queryCollectionOptions detects a server-side deletion, it sends
{ type: 'delete', value: oldItem } through the sync. The persistence
layer only checked for 'key' in message to detect deletes, causing
value-based deletes to be misclassified as updates. Also use optional
chaining for process.versions in React Native where process exists but
versions may not.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ss-window sync Add ElectronCollectionCoordinator using BroadcastChannel + Web Locks for leader election and cross-window coordination in Electron renderer windows. Wire coordinator into renderer persistence via setAdapter(), add getStreamPosition to the IPC protocol, and export from package index. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Superseded by #1358 |
🎯 Changes
Implements #865 (comment) from #865
✅ Checklist
pnpm test:pr.🚀 Release Impact