Skip to content

Commit b5d4981

Browse files
KyleAMathewsclaudesamwillis
authored
docs: add debugging section for awaitTxId stalling issues (#651)
* docs: add debugging section for awaitTxId stalling issues Add comprehensive debugging guide to help developers diagnose and fix the common issue where awaitTxId stalls or times out. This happens when pg_current_xact_id() is queried outside the mutation transaction, causing txid mismatches. The guide includes: - Explanation of the root cause (txid mismatch) - How to enable debug logging with localStorage.debug = '*' - Example logs showing both mismatched and matched txids - Correct pattern for querying txid inside sql.begin() - References to working examples in the codebase 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * Apply suggestion from @samwillis Co-authored-by: Sam Willis <[email protected]> * Update electric-collection.md --------- Co-authored-by: Claude <[email protected]> Co-authored-by: Sam Willis <[email protected]>
1 parent dd6cdf7 commit b5d4981

File tree

1 file changed

+91
-0
lines changed

1 file changed

+91
-0
lines changed

docs/collections/electric-collection.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,3 +200,94 @@ todosCollection.utils.awaitTxId(12345)
200200
```
201201

202202
This is useful when you need to ensure a mutation has been synchronized before proceeding with other operations.
203+
204+
## Debugging
205+
206+
### Common Issue: awaitTxId Stalls or Times Out
207+
208+
A frequent issue developers encounter is that `awaitTxId` (or the transaction's `isPersisted.promise`) stalls indefinitely, eventually timing out with no error messages. The data persists correctly to the database, but the optimistic mutation never resolves.
209+
210+
**Root Cause:** This happens when the transaction ID (txid) returned from your API doesn't match the actual transaction ID of the mutation in Postgres. This mismatch occurs when you query `pg_current_xact_id()` **outside** the same transaction that performs the mutation.
211+
212+
### Enable Debug Logging
213+
214+
To diagnose txid issues, enable debug logging in your browser console:
215+
216+
```javascript
217+
localStorage.debug = 'ts/db:electric'
218+
```
219+
220+
This will show you when mutations start waiting for txids and when txids arrive from Electric's sync stream.
221+
222+
This is powered by the [debug](https://www.npmjs.com/package/debug) package.
223+
224+
**When txids DON'T match (common bug):**
225+
```
226+
ts/db:electric awaitTxId called with txid 124
227+
ts/db:electric new txids synced from pg [123]
228+
// Stalls forever - 124 never arrives!
229+
```
230+
231+
In this example, the mutation happened in transaction 123, but you queried `pg_current_xact_id()` in a separate transaction (124) that ran after the mutation. The client waits for 124 which will never arrive.
232+
233+
**When txids DO match (correct):**
234+
```
235+
ts/db:electric awaitTxId called with txid 123
236+
ts/db:electric new txids synced from pg [123]
237+
ts/db:electric awaitTxId found match for txid 123
238+
// Resolves immediately!
239+
```
240+
241+
### The Solution: Query txid Inside the Transaction
242+
243+
You **must** call `pg_current_xact_id()` inside the same transaction as your mutation:
244+
245+
**❌ Wrong - txid queried outside transaction:**
246+
```typescript
247+
// DON'T DO THIS
248+
async function createTodo(data) {
249+
const txid = await generateTxId(sql) // Wrong: separate transaction
250+
251+
await sql.begin(async (tx) => {
252+
await tx`INSERT INTO todos ${tx(data)}`
253+
})
254+
255+
return { txid } // This txid won't match!
256+
}
257+
```
258+
259+
**✅ Correct - txid queried inside transaction:**
260+
```typescript
261+
// DO THIS
262+
async function createTodo(data) {
263+
let txid!: Txid
264+
265+
const result = await sql.begin(async (tx) => {
266+
// Call generateTxId INSIDE the transaction
267+
txid = await generateTxId(tx)
268+
269+
const [todo] = await tx`
270+
INSERT INTO todos ${tx(data)}
271+
RETURNING *
272+
`
273+
return todo
274+
})
275+
276+
return { todo: result, txid } // txid matches the mutation
277+
}
278+
279+
async function generateTxId(tx: any): Promise<Txid> {
280+
const result = await tx`SELECT pg_current_xact_id()::xid::text as txid`
281+
const txid = result[0]?.txid
282+
283+
if (txid === undefined) {
284+
throw new Error(`Failed to get transaction ID`)
285+
}
286+
287+
return parseInt(txid, 10)
288+
}
289+
```
290+
291+
See working examples in:
292+
- `examples/react/todo/src/routes/api/todos.ts`
293+
- `examples/react/todo/src/api/server.ts`

0 commit comments

Comments
 (0)