TanStack / TanStack/db

[PowerSync]: $synced reflects local SQLite persistence, not backend upload status

Open
#1,526 1 comment 2 reactions 0 assignees View on GitHub

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

  1. collection.insert(row) -> row enters optimisticUpserts -> $synced = false
  2. onInsert calls transactor.applyTransaction() -> writes to local SQLite
  3. SQLite diff trigger fires -> TanStack DB sync handler picks up the change
  4. optimisticUpserts is 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/db v0.6.5
  • @tanstack/powersync-db-collection
  • @powersync/react-native

Related: #900 (also about $synced resolving before server confirmation with PowerSync)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.