Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-on-demand-write-operations.md
Original file line number Diff line number Diff line change
@@ -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.
7 changes: 5 additions & 2 deletions packages/db/src/collection/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
Expand Down
75 changes: 69 additions & 6 deletions packages/query-db-collection/tests/query.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<TestItem> = {
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<TestItem> = [{ id: `1`, name: `Item 1` }]
Expand Down Expand Up @@ -2942,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)
Expand Down
Loading