Skip to content

Commit ee40cc2

Browse files
authored
Warn about using DO SQL cursor after yielding event loop (#31066)
1 parent 7445b8b commit ee40cc2

1 file changed

Lines changed: 23 additions & 0 deletions

File tree

src/content/docs/durable-objects/api/sqlite-storage-api.mdx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,29 @@ class MyDurableObject(DurableObject):
153153

154154
A cursor (`SqlStorageCursor`) to iterate over query row results as objects. `SqlStorageCursor` is a JavaScript [Iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterable_protocol), which supports iteration using `for (let row of cursor)`. `SqlStorageCursor` is also a JavaScript [Iterator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterator_protocol), which supports iteration using `cursor.next()`.
155155

156+
:::caution[Consume cursors synchronously]
157+
It is not safe to hold a reference to a `SqlStorageCursor` and continue reading from it after an `await`. When you `await` an asynchronous operation, other requests can interleave and modify the underlying database, which can silently invalidate the cursor. This does not produce an error — the cursor may appear to work but return unpredictable results.
158+
159+
Always consume the cursor fully before yielding the event loop. Call `.toArray()`, `.one()`, or iterate with `for...of` synchronously after calling `sql.exec()`:
160+
161+
```ts
162+
// ✅ Safe: cursor is fully consumed before any await
163+
const rows = this.ctx.storage.sql.exec("SELECT * FROM users").toArray();
164+
const result = await fetch("https://example.com", { method: "POST", body: JSON.stringify(rows) });
165+
```
166+
167+
```ts
168+
// 🔴 Unsafe: cursor is read after an await
169+
const cursor = this.ctx.storage.sql.exec("SELECT * FROM users");
170+
await fetch("https://example.com/notify");
171+
// Another request may have modified the database during the await above.
172+
// The cursor is now potentially invalid — it may appear to work but return wrong data.
173+
const rows = cursor.toArray();
174+
```
175+
176+
If you need to prevent other requests from interleaving while you hold a cursor, use [`blockConcurrencyWhile()`](/durable-objects/api/state/#blockconcurrencywhile).
177+
:::
178+
156179
`SqlStorageCursor` supports the following methods:
157180

158181
- `next()`

0 commit comments

Comments
 (0)