-
Notifications
You must be signed in to change notification settings - Fork 152
fix(db): batch D2 output callbacks to prevent duplicate key errors in joins #1114
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
KyleAMathews
wants to merge
4
commits into
main
Choose a base branch
from
fix/live-query-batch-outputs
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
… joins When D2's incremental join processes data, it can produce multiple outputs during a single graph.run() iteration - first a partial join result, then the full result plus a delete of the partial. Previously, each output callback had its own begin()/commit() cycle, causing the second insert for the same key to fail with DuplicateKeySyncError. This fix: - Accumulates all changes from output callbacks into a pendingChanges Map - Flushes accumulated changes in a single transaction after each graph.run() - Adds subscribedToAllCollections check to updateLiveQueryStatus() to ensure markReady() is only called after the graph has processed data - Properly types flushPendingChanges on SyncState to avoid type assertions Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: bcf9627 The changes in this PR will be included in the next version bump. This PR includes changesets to release 12 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
More templates
@tanstack/angular-db
@tanstack/db
@tanstack/db-ivm
@tanstack/electric-db-collection
@tanstack/offline-transactions
@tanstack/powersync-db-collection
@tanstack/query-db-collection
@tanstack/react-db
@tanstack/rxdb-db-collection
@tanstack/solid-db
@tanstack/svelte-db
@tanstack/trailbase-db-collection
@tanstack/vue-db
commit: |
Contributor
|
Size Change: +43 B (+0.05%) Total Size: 90.2 kB
ℹ️ View Unchanged
|
Contributor
|
Size Change: 0 B Total Size: 3.47 kB ℹ️ View Unchanged
|
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Summary
Fixes
isReady()returningtruewhiletoArray()returns[]. Also fixes a latent bug that was exposed by the timing fix.The Bug Report
Root Cause
markReady()was being called insubscribeToAllCollections()before the D2 graph had a chance to run and process data:The graph only runs in
maybeRunGraph(), which happens after subscriptions are set up. SoisReadybecametruewhile the data pipeline was still empty.The Fix
Add a guard to
updateLiveQueryStatus()so it only marks ready after subscriptions are set up AND the graph has had a chance to run:And remove the premature call from
subscribeToAllCollections(). The canonical place to mark ready is now aftergraph.run()inmaybeRunGraph().The Journey: Exposing a Latent Bug
When I made the timing fix, a test started failing: "should allow custom getKey with joins (1:1 relationships)"
Why did this test "pass" before?
The old code called
markReady()before the graph ran, which resolvedpreload()early. The duplicate key error was thrown after the promise resolved, so it was swallowed. The test appeared to pass, but was actually broken.What was the actual bug?
D2's incremental join produces multiple outputs during a single
graph.run():[key=1, undefined]with multiplicity +1[key=1, data]with multiplicity +1, AND delete of partial with multiplicity -1Previously, each output committed immediately. The first commit added key "1" to syncedData, and the second output's insert failed because the key already existed.
The fix: Batch all outputs within a single
graph.run()into one transaction. Changes for the same key are accumulated (+1, +1, -1 = +1) before committing.Key Invariants
markReady()only after graph processes data - thesubscribedToAllCollectionscheck ensures we don't mark ready before the graph runsgraph.run()share one transaction - prevents intermediate join states from causing duplicate key errorsVerification
Files Changed
collection-config-builder.tssubscribedToAllCollectionsguard toupdateLiveQueryStatus(), remove premature call fromsubscribeToAllCollections(), batch outputs inpendingChangesMaptypes.tsflushPendingChangestoSyncStatetypesync.tsvaluesEqualvariablelive-query-collection.test.ts🤖 Generated with Claude Code