Awaiting txid across multiple collections.
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 3.9k
- Forks
- 266
- Avg merge
- 1d 4h
- Merged PRs (30d)
- 55
Description
I'd just like to query a pattern I documented in https://electric-sql.com/blog/2025/07/29/local-first-sync-with-tanstack-db#transactions-and-optimistic-actions and https://electric-sql.com/blog/2025/07/29/local-first-sync-with-tanstack-db#write-path-sync
Specifically, is there a better way we can monitor the txid syncing back when writes are made to multiple collections?
The context being that transactions are explicitly designed to allow you to write atomically across collections. With the example as documented:
import { createOptimisticAction } from '@tanstack/react-db'
const createUserWithDefaultWorkspace = createOptimisticAction({
(loginName) => {
const userId = crypto.randomUUID()
const workspaceId = crypto.randomUUID()
// These inserts are applied atomically
userCollection.insert({
id: userId,
name: loginName
})
workspaceCollection.insert({
id: workspaceId,
name: 'Default'
})
membershipCollection.insert({
role: 'owner',
userId,
workspaceId
})
},
mutationFn: async (_loginName, { transaction }) => {
// In this case, the `transaction` contains all three mutations.
// ... handle sending to the server ...
}
})
The local optimistic state is applied atomically (I believe?) and the mutationFn is invoked atomically (i.e.: once, with a single transaction). However, when then monitoring the replication stream for the write, the example awaits the transaction arriving in all three collections:
export const mutationFn = async (_variables, { transaction }) => {
const mutations = transaction.mutations
const collections = new Set(mutations.map(mutation => mutation.collection))
const payloadData = mutations.map(mutation => {
const { collection, ...result } = mutation
return result
})
// Post the mutations data to the ingest endpoint.
const txid = await api.post('/ingest', { mutations: payloadData })
// Monitor the collections for the transaction to sync back in.
const promises = [...collections].map(({ utils }) => utils.awaitTxId(txid))
await Promise.all(promises)
}
Now, the happy path is collections all sync the write back roughly at the same time and there's no real gap between updating the synced state in each collection and dropping the optimistic state. However, obviously there can be race conditions and it's easy for one of the collections to hang, potentially for a long time.
I'm not sure whether this ever would result in flickering / temporary anomalies? But generally it feels like this weakens the transactional guarantees and leaves the door open to oddness. As well as not being very ergonomic DX.
We've obviously discussed various approaches to transactional streams across multiple shapes. I'm not sure what's possible or sane here. Just flagging it up because I was eyeballing it when writing the docs.
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 with the linked Transactions and Optimistic Actions and Write Path Sync documentation, then trace how transaction.mutations and utils.awaitTxId(txid) coordinate across collections. Determine whether the current multi-collection waiting behavior can preserve atomicity and what supported API or documentation change would address the race and hanging concerns.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- databases, distributed-systems
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100