Skip to content
Draft
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
3 changes: 2 additions & 1 deletion docs/sync/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
4 changes: 4 additions & 0 deletions packages/base/sync/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
31 changes: 31 additions & 0 deletions packages/base/sync/__tests__/SyncManager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>>()
.mockResolvedValue()
const onError = vi.fn()

const syncManager = new SyncManager({
onError,
persistenceAdapter: () => memoryPersistenceAdapter([]),
push: mockPush,
})

const mockCollection = new Collection<TestItem, string, any>({
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')
})
25 changes: 20 additions & 5 deletions packages/base/sync/src/SyncManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ interface Options<
ItemType extends BaseItem<IdType> = BaseItem,
IdType = any,
> {
pull: (
pull?: (
collectionOptions: SyncOptions<CollectionOptions>,
pullParameters: {
lastFinishedSyncStart?: number,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -599,14 +610,18 @@ export default class SyncManager<
reactive: false,
}).fetch()

const pull = this.options.pull

await sync<ItemType, ItemType['id']>({
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
Expand Down
13 changes: 9 additions & 4 deletions packages/base/sync/src/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ interface Options<ItemType extends BaseItem<IdType>, IdType> {
changes: Change<ItemType, IdType>[],
lastSnapshot?: ItemType[],
data: LoadResponse<ItemType>,
pull: () => Promise<LoadResponse<ItemType>>,
pull: (() => Promise<LoadResponse<ItemType>>) | undefined,
push: (changes: Changeset<ItemType>) => Promise<void>,
insert: (item: ItemType) => void,
update: (id: IdType, modifier: Modifier<ItemType>) => void,
Expand Down Expand Up @@ -81,9 +81,14 @@ export default async function sync<ItemType extends BaseItem<IdType>, 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
}
Expand Down