-
Notifications
You must be signed in to change notification settings - Fork 132
Automatically start sync when there's any mutations #918
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
192dd2c
c3ecea9
bcaa138
b3057d6
0c76796
13c7314
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| --- | ||
| "@tanstack/db": patch | ||
| "@tanstack/query-db-collection": patch | ||
| --- | ||
|
|
||
| Fixed `SyncNotInitializedError` being thrown when calling write operations (`writeUpsert`, `writeInsert`, etc.) or mutations (`insert`, `update`, `delete`) on collections before sync is started. Previously, these operations required `startSync: true` to be explicitly set or `preload()` to be called first. Now, sync is automatically started when any write operation or mutation is called on an idle collection, enabling these operations to work immediately without explicit initialization. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1622,6 +1622,74 @@ describe(`QueryCollection`, () => { | |
| expect(collection.has(`1`)).toBe(false) | ||
| }) | ||
|
|
||
| it(`should auto-start sync when write operations are called on idle collections`, async () => { | ||
| // This test verifies that write operations automatically start sync | ||
| // even when startSync is not explicitly set to true. | ||
| // This fixes SyncNotInitializedError when trying to writeUpsert on | ||
| // collections that haven't been preloaded yet. | ||
|
|
||
| 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 - sync should auto-start on write | ||
| } | ||
|
|
||
| const options = queryCollectionOptions(config) | ||
| const collection = createCollection(options) | ||
|
|
||
| // Collection starts in idle state (sync not started yet) | ||
| expect(collection.status).toBe(`idle`) | ||
| expect(queryFn).not.toHaveBeenCalled() | ||
|
|
||
| // Write operation should auto-start sync and work without error | ||
| const newItem: TestItem = { id: `1`, name: `Item 1`, value: 10 } | ||
| collection.utils.writeInsert(newItem) | ||
|
|
||
| // After write, collection should be ready (sync was auto-started) | ||
| expect(collection.status).toBe(`ready`) | ||
| 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 }) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought the issue was with any mutation, not just the direct writes?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah we handle regular mutations here https://github.com/TanStack/db/pull/918/files#diff-03176e533aea8affa9d789787bfdf48e6a7ce75817f44712dd9c1fe8cf9bd2eb |
||
|
|
||
| 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`) | ||
|
|
||
| // queryFn should still not be called since no data was loaded via loadSubset | ||
| expect(queryFn).not.toHaveBeenCalled() | ||
| }) | ||
|
|
||
| it(`should handle sync method errors appropriately`, async () => { | ||
| const queryKey = [`sync-error-test`] | ||
| const initialItems: Array<TestItem> = [{ id: `1`, name: `Item 1` }] | ||
|
|
@@ -2473,7 +2541,7 @@ describe(`QueryCollection`, () => { | |
| await collection.cleanup() | ||
| }) | ||
|
|
||
| it(`should be no-op when sync has not started (no observer created)`, async () => { | ||
| it(`should auto-start sync when utils are accessed`, async () => { | ||
| const queryKey = [`refetch-test-no-sync`] | ||
| const queryFn = vi.fn().mockResolvedValue([{ id: `1`, name: `A` }]) | ||
|
|
||
|
|
@@ -2488,9 +2556,15 @@ describe(`QueryCollection`, () => { | |
| }) | ||
| ) | ||
|
|
||
| // Refetch should be no-op because observer doesn't exist yet | ||
| // Collection starts idle | ||
| expect(collection.status).toBe(`idle`) | ||
|
|
||
| // Accessing utils auto-starts sync, so refetch will work | ||
| await collection.utils.refetch() | ||
| expect(queryFn).not.toHaveBeenCalled() | ||
|
|
||
| // Sync was started and queryFn was called | ||
| expect(collection.status).toBe(`ready`) | ||
| expect(queryFn).toHaveBeenCalled() | ||
|
|
||
| await collection.cleanup() | ||
| }) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a very strange way to implement this. It seems to be a hack that means that when you access the collection
utilsat all it will call_lifecycle.validateCollectionUsableand trigger sync to start?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah — it seems like a reasonable approach to me — the other way was to have query collection manually call startSync but the idea here is that if a collection is active in any way to start sync. Which covers a lot of stuff.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But this doesn't fix, and will trigger sync to start for any collection implementation that places anything it want on the utils, some of which could very much be intended not to start sync.collection.insertor using a custom mutation (are they also broken?)I really don't think this is the right approach.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
say a sync engine added a
utils.status, accessing that would trigger sync to start.