diff --git a/docs/sync/index.md b/docs/sync/index.md index 324b4fa9d..f2fd610c8 100644 --- a/docs/sync/index.md +++ b/docs/sync/index.md @@ -103,7 +103,8 @@ SignalDB's synchronization mechanism is inherently backend-agnostic. This means Central to this flexibility are the `pull` and `push` functions within the [`SyncManager`](/reference/sync/#syncmanager-default). These functions act as intermediaries between your application and the backend, abstracting the details of data retrieval and submission. This design ensures that: -- **Pull Function**: Retrieves data from the server. You can define how data is fetched, whether it’s through a REST API call, a GraphQL query, or another method. This flexibility allows you to adapt to various server architectures with minimal effort. +- **Pull Function**: Retrieves data from the server. You can define how data is fetched, whether it’s through a REST API call, a GraphQL query, or another method. This flexibility allows you to adapt to various server architectures with minimal effort. The `pull` function is optional, if you don't provide one, the `sync` function will only apply the changes that are already present in the collection. This is useful if you are applying changes with [`registerRemoteChange`](#handle-remote-changes). + - **Push Function**: Sends local changes to the server. Similar to the pull function, you can specify how changes are transmitted, ensuring compatibility with your backend's requirements. This includes sending data through HTTP methods, websockets, or custom protocols. diff --git a/packages/base/sync/CHANGELOG.md b/packages/base/sync/CHANGELOG.md index ef35ae5a4..1489c5de7 100644 --- a/packages/base/sync/CHANGELOG.md +++ b/packages/base/sync/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +* Allow `pull` to be optional in `SyncManager` options + ## [1.2.2] - 2025-03-20 ### Fixed diff --git a/packages/base/sync/__tests__/SyncManager.spec.ts b/packages/base/sync/__tests__/SyncManager.spec.ts index 479455465..0261ab09b 100644 --- a/packages/base/sync/__tests__/SyncManager.spec.ts +++ b/packages/base/sync/__tests__/SyncManager.spec.ts @@ -1401,3 +1401,34 @@ it('should pause and resume sync all collections', async () => { await syncManager.pauseAll() expect(registerRemoteChange).toBeCalledTimes(2) }) + +it('should not pull if pull is not defined', async () => { + const mockPush = vi.fn<(options: any, pushParameters: any) => Promise>() + .mockResolvedValue() + const onError = vi.fn() + + const syncManager = new SyncManager({ + onError, + persistenceAdapter: () => memoryPersistenceAdapter([]), + push: mockPush, + }) + + const mockCollection = new Collection({ + memory: [ + { id: '1', name: 'Test Item' }, + ], + }) + + syncManager.addCollection(mockCollection, { name: 'test' }) + + mockCollection.updateOne({ id: '1' }, { $set: { name: 'Updated' } }) + await syncManager.sync('test') + + await new Promise((resolve) => { + setTimeout(resolve, 110) + }) + + expect(onError).not.toHaveBeenCalled() + expect(mockPush).toHaveBeenCalled() + expect(mockCollection.findOne({ id: '1' })?.name).toBe('Updated') +}) diff --git a/packages/base/sync/src/SyncManager.ts b/packages/base/sync/src/SyncManager.ts index 62fc7f37d..6110032fa 100644 --- a/packages/base/sync/src/SyncManager.ts +++ b/packages/base/sync/src/SyncManager.ts @@ -23,7 +23,7 @@ interface Options< ItemType extends BaseItem = BaseItem, IdType = any, > { - pull: ( + pull?: ( collectionOptions: SyncOptions, pullParameters: { lastFinishedSyncStart?: number, @@ -523,6 +523,17 @@ export default class SyncManager< status: 'active', }) } + + if (!this.options.pull) { + return await this.syncWithData(name, { + changes: { + added: [], + modified: [], + removed: [], + }, + }) + } + const data = await this.options.pull(collectionOptions, { lastFinishedSyncStart: lastFinishedSync?.start, lastFinishedSyncEnd: lastFinishedSync?.end, @@ -599,14 +610,18 @@ export default class SyncManager< reactive: false, }).fetch() + const pull = this.options.pull + await sync({ changes: currentChanges, lastSnapshot: lastSnapshot?.items, data, - pull: () => this.options.pull(collectionOptions, { - lastFinishedSyncStart: lastFinishedSync?.start, - lastFinishedSyncEnd: lastFinishedSync?.end, - }), + pull: pull + ? () => pull(collectionOptions, { + lastFinishedSyncStart: lastFinishedSync?.start, + lastFinishedSyncEnd: lastFinishedSync?.end, + }) + : undefined, push: changes => this.options.push(collectionOptions, { changes }), insert: (item) => { // add multiple remote changes as we don't know if the item will be updated or inserted during replace diff --git a/packages/base/sync/src/sync.ts b/packages/base/sync/src/sync.ts index 89cbc9b53..21a74a6de 100644 --- a/packages/base/sync/src/sync.ts +++ b/packages/base/sync/src/sync.ts @@ -33,7 +33,7 @@ interface Options, IdType> { changes: Change[], lastSnapshot?: ItemType[], data: LoadResponse, - pull: () => Promise>, + pull: (() => Promise>) | undefined, push: (changes: Changeset) => Promise, insert: (item: ItemType) => void, update: (id: IdType, modifier: Modifier) => void, @@ -81,9 +81,14 @@ export default async function sync, IdType>({ // if yes, push the changes to the server await push(changesToPush) - // pull new data afterwards to ensure that all server changes are applied - newData = await pull() - newSnapshot = getSnapshot(newSnapshot, newData) + if (pull) { + // pull new data afterwards to ensure that all server changes are applied + newData = await pull() + newSnapshot = getSnapshot(newSnapshot, newData) + } else { + newData = { changes: changesToPush } + newSnapshot = getSnapshot(newSnapshot, newData) + } } previousSnapshot = lastSnapshotWithChanges }