From 192dd2c4dd0e18cd58e53cede07b4659fe935ace Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 26 Nov 2025 19:28:08 +0000 Subject: [PATCH 1/3] fix: auto-start sync for on-demand collections to enable write operations On-demand collections were throwing SyncNotInitializedError when users tried to call writeUpsert or other write operations. This happened because the sync function was only invoked when startSync: true was explicitly set, but write operations require the sync infrastructure to be initialized. For on-demand collections, starting sync is cheap (no data is fetched until loadSubset is called), so we now automatically start sync when syncMode is 'on-demand'. This ensures write operations work immediately while maintaining the on-demand loading behavior. Fixes the issue where users with on-demand collections could not manually insert related records using writeUpsert. --- packages/db/src/collection/index.ts | 7 +- .../query-db-collection/tests/query.test.ts | 64 +++++++++++++++++++ 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/packages/db/src/collection/index.ts b/packages/db/src/collection/index.ts index 5616a8ceb..ff0bdc3d9 100644 --- a/packages/db/src/collection/index.ts +++ b/packages/db/src/collection/index.ts @@ -310,8 +310,11 @@ export class CollectionImpl< events: this._events, }) - // Only start sync immediately if explicitly enabled - if (config.startSync === true) { + // Start sync immediately if: + // 1. Explicitly enabled via startSync: true + // 2. Collection uses on-demand sync mode - sync must be started for write + // operations to work, but no data will be fetched until loadSubset is called + if (config.startSync === true || config.syncMode === `on-demand`) { this._sync.startSync() } } diff --git a/packages/query-db-collection/tests/query.test.ts b/packages/query-db-collection/tests/query.test.ts index 0c4408646..b552fefb9 100644 --- a/packages/query-db-collection/tests/query.test.ts +++ b/packages/query-db-collection/tests/query.test.ts @@ -1622,6 +1622,70 @@ describe(`QueryCollection`, () => { expect(collection.has(`1`)).toBe(false) }) + it(`should allow write operations on on-demand collections without startSync`, async () => { + // This test verifies that on-demand collections can perform write operations + // even when startSync is not explicitly set to true. + // See: https://github.com/TanStack/db/issues/xyz - SyncNotInitializedError + // was thrown when trying to writeUpsert on on-demand collections + + const queryKey = [`on-demand-write-test`] + const queryFn = vi.fn().mockResolvedValue([]) + + const config: QueryCollectionConfig = { + id: `on-demand-write-collection`, + queryClient, + queryKey, + queryFn, + getKey, + syncMode: `on-demand`, + // Note: startSync is NOT set to true - this was the bug scenario + } + + const options = queryCollectionOptions(config) + const collection = createCollection(options) + + // On-demand collections should be ready immediately + await vi.waitFor(() => { + expect(collection.status).toBe(`ready`) + }) + + // Write operations should work without throwing SyncNotInitializedError + const newItem: TestItem = { id: `1`, name: `Item 1`, value: 10 } + collection.utils.writeInsert(newItem) + + expect(collection.size).toBe(1) + expect(collection.get(`1`)).toEqual(newItem) + + // Test writeUpsert (the specific operation from the bug report) + collection.utils.writeUpsert({ id: `2`, name: `Item 2`, value: 20 }) + + expect(collection.size).toBe(2) + expect(collection.get(`2`)).toEqual({ + id: `2`, + name: `Item 2`, + value: 20, + }) + + // Test writeUpdate + collection.utils.writeUpdate({ id: `1`, name: `Updated Item 1` }) + expect(collection.get(`1`)?.name).toBe(`Updated Item 1`) + + // Test writeDelete + collection.utils.writeDelete(`1`) + expect(collection.size).toBe(1) + expect(collection.has(`1`)).toBe(false) + + // Test writeBatch + collection.utils.writeBatch(() => { + collection.utils.writeInsert({ id: `3`, name: `Item 3`, value: 30 }) + collection.utils.writeUpsert({ id: `4`, name: `Item 4`, value: 40 }) + }) + + expect(collection.size).toBe(3) + expect(collection.get(`3`)?.name).toBe(`Item 3`) + expect(collection.get(`4`)?.name).toBe(`Item 4`) + }) + it(`should handle sync method errors appropriately`, async () => { const queryKey = [`sync-error-test`] const initialItems: Array = [{ id: `1`, name: `Item 1` }] From c3ecea979d4c3b8d91cf169853b615e92208baa2 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 26 Nov 2025 19:37:45 +0000 Subject: [PATCH 2/3] chore: add changeset for on-demand write operations fix --- .changeset/fix-on-demand-write-operations.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fix-on-demand-write-operations.md diff --git a/.changeset/fix-on-demand-write-operations.md b/.changeset/fix-on-demand-write-operations.md new file mode 100644 index 000000000..94cfe18c6 --- /dev/null +++ b/.changeset/fix-on-demand-write-operations.md @@ -0,0 +1,5 @@ +--- +"@tanstack/db": patch +--- + +Fixed `SyncNotInitializedError` being thrown when calling write operations (`writeUpsert`, `writeInsert`, etc.) on collections with `syncMode: 'on-demand'`. Previously, write operations required `startSync: true` to be explicitly set, even though on-demand collections don't fetch data automatically. Now, sync is automatically started for on-demand collections, enabling write operations to work immediately while maintaining the on-demand loading behavior. From bcaa1382d7d7c123ff8aed546781a8428f24cf5d Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 26 Nov 2025 19:55:27 +0000 Subject: [PATCH 3/3] test: update on-demand preload test to reflect auto-start sync behavior On-demand collections now start in 'ready' status immediately since sync is auto-started to enable write operations. Updated the test to expect 'ready' instead of 'idle' initial status while still verifying no data is loaded until loadSubset is called. --- packages/query-db-collection/tests/query.test.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/query-db-collection/tests/query.test.ts b/packages/query-db-collection/tests/query.test.ts index b552fefb9..d1c9248f4 100644 --- a/packages/query-db-collection/tests/query.test.ts +++ b/packages/query-db-collection/tests/query.test.ts @@ -3006,17 +3006,16 @@ describe(`QueryCollection`, () => { const options = queryCollectionOptions(config) const collection = createCollection(options) - // Collection should be idle initially - expect(collection.status).toBe(`idle`) + // On-demand collections are ready immediately (sync auto-starts for write support) + // but no data is loaded until loadSubset is called + expect(collection.status).toBe(`ready`) expect(queryFn).not.toHaveBeenCalled() expect(collection.size).toBe(0) - // Preload should resolve immediately without calling queryFn - // since there's no initial query in on-demand mode + // Preload is a no-op for on-demand collections (logs a warning) await collection.preload() - // After preload, collection should be ready - // but queryFn should NOT have been called and collection should still be empty + // Collection should still be ready with no data loaded expect(collection.status).toBe(`ready`) expect(queryFn).not.toHaveBeenCalled() expect(collection.size).toBe(0)