From fc100e1030daf1e641f36829da989256af302c04 Mon Sep 17 00:00:00 2001 From: Marc MacLeod Date: Sat, 25 Oct 2025 16:18:54 -0500 Subject: [PATCH] fix: local collection manual transactions Signed-off-by: Marc MacLeod --- .changeset/metal-results-float.md | 5 ++++ packages/db/src/local-only.ts | 10 ++++---- packages/db/tests/local-only.test.ts | 35 +++++++++++++++++++++++++++- 3 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 .changeset/metal-results-float.md diff --git a/.changeset/metal-results-float.md b/.changeset/metal-results-float.md new file mode 100644 index 000000000..f3ef8d346 --- /dev/null +++ b/.changeset/metal-results-float.md @@ -0,0 +1,5 @@ +--- +"@tanstack/db": patch +--- + +Fixed local collection manual transactions diff --git a/packages/db/src/local-only.ts b/packages/db/src/local-only.ts index 11f19a43c..b0108c380 100644 --- a/packages/db/src/local-only.ts +++ b/packages/db/src/local-only.ts @@ -179,7 +179,10 @@ export function localOnlyCollectionOptions< ): LocalOnlyCollectionOptionsResult & { schema?: StandardSchemaV1 } { - const { initialData, onInsert, onUpdate, onDelete, ...restConfig } = config + const { initialData, onInsert, onUpdate, onDelete, id, ...restConfig } = + config + + const collectionId = id ?? crypto.randomUUID() // Create the sync configuration with transaction confirmation capability const syncResult = createLocalOnlySync(initialData) @@ -247,9 +250,7 @@ export function localOnlyCollectionOptions< }) => { // Filter mutations that belong to this collection const collectionMutations = transaction.mutations.filter( - (m) => - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - m.collection === syncResult.collection + (m) => m.collection.id === collectionId ) if (collectionMutations.length === 0) { @@ -264,6 +265,7 @@ export function localOnlyCollectionOptions< return { ...restConfig, + id: collectionId, sync: syncResult.sync, onInsert: wrappedOnInsert, onUpdate: wrappedOnUpdate, diff --git a/packages/db/tests/local-only.test.ts b/packages/db/tests/local-only.test.ts index 94c29a8fa..235de44a9 100644 --- a/packages/db/tests/local-only.test.ts +++ b/packages/db/tests/local-only.test.ts @@ -481,7 +481,7 @@ describe(`LocalOnly Collection`, () => { }) describe(`Manual transactions with acceptMutations`, () => { - it(`should accept and persist mutations from manual transactions`, () => { + it(`should accept and persist mutations from manual transactions`, async () => { const tx = createTransaction({ mutationFn: async ({ transaction }: any) => { // Simulate API call success @@ -510,6 +510,39 @@ describe(`LocalOnly Collection`, () => { id: 101, name: `Manual Tx Insert 2`, }) + + // Verify that the item is still present after async operations complete + await new Promise((resolve) => setTimeout(resolve, 1)) + expect(collection.get(100)).toEqual({ id: 100, name: `Manual Tx Insert` }) + }) + + it(`should work without explicit collection ID`, async () => { + // Create a collection without an explicit ID + const noIdCollection = createCollection< + TestItem, + number, + LocalOnlyCollectionUtils + >( + localOnlyCollectionOptions({ + getKey: (item) => item.id, + }) + ) + + const tx = createTransaction({ + mutationFn: async ({ transaction }: any) => { + noIdCollection.utils.acceptMutations(transaction) + }, + autoCommit: false, + }) + + tx.mutate(() => { + noIdCollection.insert({ id: 999, name: `No ID Test` }) + }) + + await tx.commit() + + // Data should persist even without explicit ID + expect(noIdCollection.get(999)).toEqual({ id: 999, name: `No ID Test` }) }) it(`should only accept mutations for the specific collection`, () => {