diff --git a/.changeset/fix-bulk-insert-duplicate-keys.md b/.changeset/fix-bulk-insert-duplicate-keys.md new file mode 100644 index 000000000..d02a6cf23 --- /dev/null +++ b/.changeset/fix-bulk-insert-duplicate-keys.md @@ -0,0 +1,5 @@ +--- +"@tanstack/db": patch +--- + +Fix bulk insert not detecting duplicate keys within the same batch. Previously, when inserting multiple items with the same key in a single bulk insert operation, later items would silently overwrite earlier ones. Now, a `DuplicateKeyError` is thrown when duplicate keys are detected within the same batch. diff --git a/packages/db/src/collection/mutations.ts b/packages/db/src/collection/mutations.ts index 278234f47..fe132df45 100644 --- a/packages/db/src/collection/mutations.ts +++ b/packages/db/src/collection/mutations.ts @@ -163,17 +163,19 @@ export class CollectionMutationsManager< const items = Array.isArray(data) ? data : [data] const mutations: Array> = [] + const keysInCurrentBatch = new Set() // Create mutations for each item items.forEach((item) => { // Validate the data against the schema if one exists const validatedData = this.validateData(item, `insert`) - // Check if an item with this ID already exists in the collection + // Check if an item with this ID already exists in the collection or in the current batch const key = this.config.getKey(validatedData) - if (this.state.has(key)) { + if (this.state.has(key) || keysInCurrentBatch.has(key)) { throw new DuplicateKeyError(key) } + keysInCurrentBatch.add(key) const globalKey = this.generateGlobalKey(key, item) const mutation: PendingMutation = { diff --git a/packages/db/tests/collection.test.ts b/packages/db/tests/collection.test.ts index a5a94fb06..237446805 100644 --- a/packages/db/tests/collection.test.ts +++ b/packages/db/tests/collection.test.ts @@ -567,6 +567,51 @@ describe(`Collection`, () => { }).not.toThrow() }) + it(`should not allow bulk inserting documents with duplicate IDs in the same batch`, async () => { + const collection = createCollection<{ id: number; value: string }>({ + id: `bulk-duplicate-id-test`, + getKey: (item) => item.id, + startSync: true, + sync: { + sync: ({ begin, commit, markReady }) => { + begin() + commit() + markReady() + }, + }, + }) + + await collection.stateWhenReady() + + const mutationFn = async () => {} + const tx = createTransaction({ mutationFn }) + + // Try to bulk insert documents with duplicate IDs within the same batch + expect(() => { + tx.mutate(() => + collection.insert([ + { id: 1, value: `first` }, + { id: 1, value: `second` }, // Same ID - should throw + ]) + ) + }).toThrow(DuplicateKeyError) + + // Should be able to bulk insert documents with different IDs + const tx2 = createTransaction({ mutationFn }) + expect(() => { + tx2.mutate(() => + collection.insert([ + { id: 2, value: `first` }, + { id: 3, value: `second` }, + ]) + ) + }).not.toThrow() + + // Verify both items were inserted + expect(collection.state.get(2)).toEqual({ id: 2, value: `first` }) + expect(collection.state.get(3)).toEqual({ id: 3, value: `second` }) + }) + it(`should support operation handler functions`, async () => { // Create mock handler functions const onInsertMock = vi.fn()