Skip to content

Commit 1b22e40

Browse files
KyleAMathewsclaude
andauthored
Improve DuplicateKeySyncError (#1119)
* fix: improve DuplicateKeySyncError message for distinct + custom getKey When using `.distinct()` with a custom `getKey`, the error message now explains that `.distinct()` deduplicates by the entire selected object, not by the key field. This helps users understand they should either: 1. Ensure SELECT only includes fields that make up the key 2. Use .groupBy() instead of .distinct() for explicit control 3. Remove the custom getKey to use default key behavior This addresses a production bug where users were getting confusing duplicate key errors when combining these features. * chore: add changeset for improved error message * fix: remove misleading groupBy suggestion from error message * fix: add back groupBy suggestion with min()/max() aggregates --------- Co-authored-by: Claude <[email protected]>
1 parent 98e584d commit 1b22e40

5 files changed

Lines changed: 27 additions & 3 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@tanstack/db': patch
3+
---
4+
5+
Improve DuplicateKeySyncError message when using `.distinct()` with custom `getKey`. The error now explains that `.distinct()` deduplicates by the entire selected object, and provides actionable guidance to fix the issue.

packages/db/src/collection/sync.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ export class CollectionSyncManager<
150150
throw new DuplicateKeySyncError(key, this.id, {
151151
hasCustomGetKey: internal?.hasCustomGetKey ?? false,
152152
hasJoins: internal?.hasJoins ?? false,
153+
hasDistinct: internal?.hasDistinct ?? false,
153154
})
154155
}
155156
}

packages/db/src/errors.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,12 +172,28 @@ export class DuplicateKeySyncError extends CollectionOperationError {
172172
constructor(
173173
key: string | number,
174174
collectionId: string,
175-
options?: { hasCustomGetKey?: boolean; hasJoins?: boolean },
175+
options?: {
176+
hasCustomGetKey?: boolean
177+
hasJoins?: boolean
178+
hasDistinct?: boolean
179+
},
176180
) {
177181
const baseMessage = `Cannot insert document with key "${key}" from sync because it already exists in the collection "${collectionId}"`
178182

179-
// Provide enhanced guidance when custom getKey is used with joins
180-
if (options?.hasCustomGetKey && options.hasJoins) {
183+
// Provide enhanced guidance when custom getKey is used with distinct
184+
if (options?.hasCustomGetKey && options.hasDistinct) {
185+
super(
186+
`${baseMessage}. ` +
187+
`This collection uses a custom getKey with .distinct(). ` +
188+
`The .distinct() operator deduplicates by the ENTIRE selected object (standard SQL behavior), ` +
189+
`but your custom getKey extracts only a subset of fields. This causes multiple distinct rows ` +
190+
`(with different values in non-key fields) to receive the same key. ` +
191+
`To fix this, either: (1) ensure your SELECT only includes fields that uniquely identify each row, ` +
192+
`(2) use .groupBy() with min()/max() aggregates to select one value per group, or ` +
193+
`(3) remove the custom getKey to use the default key behavior.`,
194+
)
195+
} else if (options?.hasCustomGetKey && options.hasJoins) {
196+
// Provide enhanced guidance when custom getKey is used with joins
181197
super(
182198
`${baseMessage}. ` +
183199
`This collection uses a custom getKey with joined queries. ` +

packages/db/src/query/live/collection-config-builder.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ export class CollectionConfigBuilder<
227227
getBuilder: () => this,
228228
hasCustomGetKey: !!this.config.getKey,
229229
hasJoins: this.hasJoins(this.query),
230+
hasDistinct: !!this.query.distinct,
230231
},
231232
},
232233
}

packages/db/src/query/live/internal.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ export type LiveQueryInternalUtils = {
1212
getBuilder: () => CollectionConfigBuilder<any, any>
1313
hasCustomGetKey: boolean
1414
hasJoins: boolean
15+
hasDistinct: boolean
1516
}

0 commit comments

Comments
 (0)