[PowerSync]: $synced reflects local SQLite persistence, not backend upload status
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 3.9k
- Forks
- 266
- Avg merge
- 1d 4h
- Merged PRs (30d)
- 55
Description
Description
When using @tanstack/powersync-db-collection, the $synced virtual property on rows becomes true as soon as the optimistic mutation is resolved against the local SQLite database (via the diff trigger in PowerSyncTransactor.applyTransaction). It does not reflect whether the row has actually been uploaded to the backend via PowerSync's uploadData connector method.
This makes $synced unsuitable for determining whether a row has pending changes that need to be sent to the server -- which is the primary use case when building offline-first UIs (e.g. showing a "pending sync" indicator, or allowing edits only on unsynced rows).
Current behaviour
collection.insert(row)-> row entersoptimisticUpserts->$synced = falseonInsertcallstransactor.applyTransaction()-> writes to local SQLite- SQLite diff trigger fires -> TanStack DB sync handler picks up the change
optimisticUpsertsis cleared ->$synced = true
This entire flow is local. Step 4 happens almost immediately after step 1, regardless of whether PowerSync is connected or whether uploadData has been called.
Expected behaviour
For the PowerSync integration, $synced should remain false until the row has been confirmed by the backend (i.e. the CRUD entry has been removed from ps_crud and the data has been synced back via the PowerSync sync stream).
Alternatively, a separate virtual property (e.g. $uploaded or $serverConfirmed) could indicate whether the row's mutations have been acknowledged by the server, distinct from the local optimistic resolution.
Workaround
The only reliable way to determine pending upload status is to query the ps_crud table directly:
const rows = await powerSyncDb.getAll<{ data: string }>('SELECT data FROM ps_crud');
const pendingIds = new Set<string>();
for (const row of rows) {
const parsed = JSON.parse(row.data);
if (parsed.type === 'my_table' && parsed.id) pendingIds.add(parsed.id);
}
This works but requires polling and is not reactive.
Context
@tanstack/dbv0.6.5@tanstack/powersync-db-collection@powersync/react-native
Related: #900 (also about $synced resolving before server confirmation with PowerSync)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by tracing the $synced flow through PowerSyncTransactor.applyTransaction and the optimisticUpserts handling, then compare it with the uploadData connector and ps_crud lifecycle. Determine how server confirmation returns through the PowerSync sync stream; done means pending upload status is distinguishable from local SQLite persistence without requiring polling.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react-native, sqlite, typescript
- Domain
- database, distributed-systems
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100