From 9e51a6ec0591f0a35ba1a7b4009454a8ceade5ab Mon Sep 17 00:00:00 2001 From: Danny Ahn Date: Mon, 2 Mar 2026 17:07:00 -0800 Subject: [PATCH] docs: warn about isPersisted vs isPersisted.promise footgun --- docs/guides/mutations.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/guides/mutations.md b/docs/guides/mutations.md index b3f698084..78ab74c51 100644 --- a/docs/guides/mutations.md +++ b/docs/guides/mutations.md @@ -1452,6 +1452,21 @@ try { } ``` +> [!IMPORTANT] +> **Always use `tx.isPersisted.promise`, not `tx.isPersisted`.** The `isPersisted` property is a `Deferred` object, not a `Promise`. Since `Deferred` does not implement a `.then()` method, `await tx.isPersisted` resolves immediately to the `Deferred` object itself — it does **not** wait for the transaction to persist. +> +> This is a silent bug that TypeScript will not catch, because `await` accepts any value. +> +> ```typescript +> // ❌ BUG: resolves immediately — does not wait for persistence +> await tx.isPersisted +> +> // ✅ CORRECT: waits for the transaction to complete or fail +> await tx.isPersisted.promise +> ``` +> +> If you forget `.promise`, your UI may clear forms, navigate, or show success messages before the server has confirmed the write. If the server then rejects the mutation, the optimistic state rolls back silently with no error handling. + ### State Transitions The normal flow is: `pending` → `persisting` → `completed`