Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/huge-forks-tan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@tanstack/electric-db-collection": patch
"@tanstack/db": patch
---

Fixed a bug where a live query result could become inconsistent after an electric "must-refetch".
6 changes: 6 additions & 0 deletions .changeset/wet-camels-brush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@tanstack/electric-db-collection": patch
"@tanstack/db": patch
---

Fixed a bug where a live query could get stuck in "loading" state when an electric "must-refetch" message arrived before the first "up-to-date".
18 changes: 13 additions & 5 deletions packages/db/src/collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -395,13 +395,14 @@ export class CollectionImpl<
const callbacks = [...this.onFirstReadyCallbacks]
this.onFirstReadyCallbacks = []
callbacks.forEach((callback) => callback())

// to notify subscribers (like LiveQueryCollection) that the collection is ready
if (this.changeListeners.size > 0) {
this.emitEmptyReadyEvent()
}
}
}

// Always notify dependents when markReady is called, after status is set
// This ensures live queries get notified when their dependencies become ready
if (this.changeListeners.size > 0) {
this.emitEmptyReadyEvent()
}
Comment on lines +401 to +405
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the fix for the stuck loading state

}

public id = ``
Expand Down Expand Up @@ -1270,6 +1271,13 @@ export class CollectionImpl<
this.syncedData.clear()
this.syncedMetadata.clear()
this.syncedKeys.clear()

// 3) Clear currentVisibleState for truncated keys to ensure subsequent operations
// are compared against the post-truncate state (undefined) rather than pre-truncate state
// This ensures that re-inserted keys are emitted as INSERT events, not UPDATE events
for (const key of changedKeys) {
currentVisibleState.delete(key)
}
Comment on lines +1275 to +1280
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the fix for the inconsistent state

}

for (const operation of transaction.operations) {
Expand Down
4 changes: 2 additions & 2 deletions packages/db/tests/collection-subscribe-changes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1169,8 +1169,8 @@ describe(`Collection.subscribeChanges`, () => {
f.write({ type: `insert`, value: { id: 1, value: `server-after` } })
f.commit()

// Expect delete then insert with optimistic value
expect(changeEvents.length).toBe(2)
// Expect delete, insert with optimistic value, and an empty event from markReady
expect(changeEvents.length).toBe(3)
expect(changeEvents[0]).toEqual({
type: `delete`,
key: 1,
Expand Down
Loading
Loading