From f4d30e569563db190568888894c3ec9b82dbeeaf Mon Sep 17 00:00:00 2001 From: primus11 <1196764+primus11@users.noreply.github.com> Date: Sat, 25 Feb 2023 18:21:01 +0100 Subject: [PATCH 1/4] Synchronization option now supports syncUpdateCondition to skip remote Atm synchronization only allows to customize conflict resolution which doesn't provide full control when trying for example to skip remote record altogether. It is possible to use conflict resolution that would return local but this is happening too late and has side effect of modifying updated_at. --- src/sync/impl/applyRemote.d.ts | 8 +++++++- src/sync/impl/applyRemote.js | 22 +++++++++++++++++++--- src/sync/impl/helpers.d.ts | 8 +++++++- src/sync/impl/helpers.js | 15 +++++++++++++-- src/sync/impl/synchronize.js | 2 ++ src/sync/index.d.ts | 9 +++++++++ src/sync/index.js | 9 +++++++++ 7 files changed, 66 insertions(+), 7 deletions(-) diff --git a/src/sync/impl/applyRemote.d.ts b/src/sync/impl/applyRemote.d.ts index 32b5a8ad0..ab79ac562 100644 --- a/src/sync/impl/applyRemote.d.ts +++ b/src/sync/impl/applyRemote.d.ts @@ -1,6 +1,11 @@ import type { Database } from '../..' -import type { SyncDatabaseChangeSet, SyncLog, SyncConflictResolver } from '../index' +import type { + SyncDatabaseChangeSet, + SyncLog, + SyncConflictResolver, + SyncUpdateCondition, +} from '../index' export default function applyRemoteChanges( remoteChanges: SyncDatabaseChangeSet, @@ -10,5 +15,6 @@ export default function applyRemoteChanges( log?: SyncLog, conflictResolver?: SyncConflictResolver, _unsafeBatchPerCollection?: boolean, + syncUpdateCondition?: SyncUpdateCondition, } ): Promise diff --git a/src/sync/impl/applyRemote.js b/src/sync/impl/applyRemote.js index c8a988172..c22e9c5b8 100644 --- a/src/sync/impl/applyRemote.js +++ b/src/sync/impl/applyRemote.js @@ -23,6 +23,7 @@ import type { SyncDatabaseChangeSet, SyncLog, SyncConflictResolver, + SyncUpdateCondition, SyncPullStrategy, } from '../index' import { prepareCreateFromRaw, prepareUpdateFromRaw, recordFromRaw } from './helpers' @@ -34,6 +35,7 @@ type ApplyRemoteChangesContext = $Exact<{ log?: SyncLog, conflictResolver?: SyncConflictResolver, _unsafeBatchPerCollection?: boolean, + syncUpdateCondition?: SyncUpdateCondition, }> // NOTE: Creating JS models is expensive/memory-intensive, so we want to avoid it if possible @@ -263,7 +265,7 @@ function prepareApplyRemoteChangesToCollection( collection: Collection, context: ApplyRemoteChangesContext, ): Array { - const { db, sendCreatedAsUpdated, log, conflictResolver } = context + const { db, sendCreatedAsUpdated, log, conflictResolver, syncUpdateCondition } = context const { table } = collection const { created, @@ -292,7 +294,14 @@ function prepareApplyRemoteChangesToCollection( `[Sync] Server wants client to create record ${table}#${raw.id}, but it already exists locally. This may suggest last sync partially executed, and then failed; or it could be a serious bug. Will update existing record instead.`, ) recordsToBatch.push( - prepareUpdateFromRaw(currentRecord, raw, collection, log, conflictResolver), + prepareUpdateFromRaw( + currentRecord, + raw, + collection, + log, + conflictResolver, + syncUpdateCondition, + ), ) } else if (locallyDeletedIds.includes(raw.id)) { logError( @@ -312,7 +321,14 @@ function prepareApplyRemoteChangesToCollection( if (currentRecord) { recordsToBatch.push( - prepareUpdateFromRaw(currentRecord, raw, collection, log, conflictResolver), + prepareUpdateFromRaw( + currentRecord, + raw, + collection, + log, + conflictResolver, + syncUpdateCondition, + ), ) } else if (locallyDeletedIds.includes(raw.id)) { // Nothing to do, record was locally deleted, deletion will be pushed later diff --git a/src/sync/impl/helpers.d.ts b/src/sync/impl/helpers.d.ts index 746efbc87..6fd6281aa 100644 --- a/src/sync/impl/helpers.d.ts +++ b/src/sync/impl/helpers.d.ts @@ -1,6 +1,11 @@ import type { Model, Collection, Database } from '../..' import type { RawRecord, DirtyRaw } from '../../RawRecord' -import type { SyncLog, SyncDatabaseChangeSet, SyncConflictResolver } from '../index' +import type { + SyncLog, + SyncDatabaseChangeSet, + SyncConflictResolver, + SyncUpdateCondition, +} from '../index' // Returns raw record with naive solution to a conflict based on local `_changed` field // This is a per-column resolution algorithm. All columns that were changed locally win @@ -17,6 +22,7 @@ export function prepareUpdateFromRaw( updatedDirtyRaw: DirtyRaw, log?: SyncLog, conflictResolver?: SyncConflictResolver, + syncUpdateCondition?: SyncUpdateCondition, ): T export function prepareMarkAsSynced(record: T): T diff --git a/src/sync/impl/helpers.js b/src/sync/impl/helpers.js index df9d32d19..bd523f6a4 100644 --- a/src/sync/impl/helpers.js +++ b/src/sync/impl/helpers.js @@ -6,7 +6,12 @@ import { invariant } from '../../utils/common' import type { Model, Collection, Database } from '../..' import { type RawRecord, type DirtyRaw, sanitizedRaw } from '../../RawRecord' -import type { SyncLog, SyncDatabaseChangeSet, SyncConflictResolver } from '../index' +import type { + SyncLog, + SyncDatabaseChangeSet, + SyncConflictResolver, + SyncUpdateCondition, +} from '../index' // Returns raw record with naive solution to a conflict based on local `_changed` field // This is a per-column resolution algorithm. All columns that were changed locally win @@ -59,7 +64,12 @@ export function requiresUpdate( collection: Collection, local: RawRecord, dirtyRemote: DirtyRaw, + syncUpdateCondition?: SyncUpdateCondition, ): boolean { + if (syncUpdateCondition) { + return syncUpdateCondition(collection.table, local, dirtyRemote) + } + if (local._status !== 'synced') { return true } @@ -80,8 +90,9 @@ export function prepareUpdateFromRaw( collection: Collection, log: ?SyncLog, conflictResolver?: SyncConflictResolver, + syncUpdateCondition?: SyncUpdateCondition, ): ?T { - if (!requiresUpdate(collection, localRaw, remoteDirtyRaw)) { + if (!requiresUpdate(collection, localRaw, remoteDirtyRaw, syncUpdateCondition)) { return null } diff --git a/src/sync/impl/synchronize.js b/src/sync/impl/synchronize.js index eef1f6e81..e7e5518e5 100644 --- a/src/sync/impl/synchronize.js +++ b/src/sync/impl/synchronize.js @@ -26,6 +26,7 @@ export default async function synchronize({ conflictResolver, _unsafeBatchPerCollection, unsafeTurbo, + syncUpdateCondition, }: SyncArgs): Promise { const resetCount = database._resetCount log && (log.startedAt = new Date()) @@ -107,6 +108,7 @@ export default async function synchronize({ log, conflictResolver, _unsafeBatchPerCollection, + syncUpdateCondition, }) onDidPullChanges && onDidPullChanges(resultRest) } diff --git a/src/sync/index.d.ts b/src/sync/index.d.ts index b4449d31f..ea9eda10d 100644 --- a/src/sync/index.d.ts +++ b/src/sync/index.d.ts @@ -49,6 +49,12 @@ export type SyncLog = { error?: Error; } +export type SyncUpdateCondition = ( + table: TableName, + local: DirtyRaw, + remote: DirtyRaw, +) => boolean + export type SyncConflictResolver = ( table: TableName, local: DirtyRaw, @@ -64,6 +70,9 @@ export type SyncArgs = $Exact<{ migrationsEnabledAtVersion?: SchemaVersion; sendCreatedAsUpdated?: boolean; log?: SyncLog; + // Advanced (unsafe) customization point. Useful when doing per record conflict resolution and can + // determine directly from remote and local if we can keep local. + syncUpdateCondition?: SyncUpdateCondition; // Advanced (unsafe) customization point. Useful when you have subtle invariants between multiple // columns and want to have them updated consistently, or to implement partial sync // It's called for every record being updated locally, so be sure that this function is FAST. diff --git a/src/sync/index.js b/src/sync/index.js index fcce9b80d..8d33c3222 100644 --- a/src/sync/index.js +++ b/src/sync/index.js @@ -75,6 +75,12 @@ export type SyncLog = { error?: Error, } +export type SyncUpdateCondition = ( + table: TableName, + local: DirtyRaw, + remote: DirtyRaw, +) => boolean + export type SyncConflictResolver = ( table: TableName, local: DirtyRaw, @@ -91,6 +97,9 @@ export type SyncArgs = $Exact<{ migrationsEnabledAtVersion?: SchemaVersion, sendCreatedAsUpdated?: boolean, log?: SyncLog, + // Advanced (unsafe) customization point. Useful when doing per record conflict resolution and can + // determine directly from remote and local if we can keep local. + syncUpdateCondition?: SyncUpdateCondition, // Advanced (unsafe) customization point. Useful when you have subtle invariants between multiple // columns and want to have them updated consistently, or to implement partial sync // It's called for every record being updated locally, so be sure that this function is FAST. From add35a0f5bfea0da2b496860026fbe8825bef63c Mon Sep 17 00:00:00 2001 From: primus11 <1196764+primus11@users.noreply.github.com> Date: Tue, 28 Feb 2023 09:21:46 +0100 Subject: [PATCH 2/4] renamed syncUpdateCondition to shouldUpdateRecord and moved it before conflictResolver where appropriate, added tests --- src/sync/impl/__tests__/applyRemote.test.js | 82 +++++++++++++++++++++ src/sync/impl/applyRemote.d.ts | 2 +- src/sync/impl/applyRemote.js | 10 +-- src/sync/impl/helpers.d.ts | 4 +- src/sync/impl/helpers.js | 12 +-- src/sync/impl/synchronize.d.ts | 1 + src/sync/impl/synchronize.js | 4 +- src/sync/index.d.ts | 4 +- src/sync/index.js | 4 +- 9 files changed, 103 insertions(+), 20 deletions(-) diff --git a/src/sync/impl/__tests__/applyRemote.test.js b/src/sync/impl/__tests__/applyRemote.test.js index 45f06653b..011b1e0e7 100644 --- a/src/sync/impl/__tests__/applyRemote.test.js +++ b/src/sync/impl/__tests__/applyRemote.test.js @@ -506,4 +506,86 @@ describe('applyRemoteChanges', () => { // Record ID mock_tasks#tSynced was sent over the bridge, but it's not cached await database.get('mock_tasks').find('tSynced') }) + describe('shouldUpdateRecord', () => { + it('can ignore record', async () => { + const { database, tasks } = makeDatabase() + + await makeLocalChanges(database) + await testApplyRemoteChanges(database, { + mock_tasks: { + updated: [ + // update / updated - will be ignored when any local change + { id: 'tUpdated', name: 'remote', description: 'remote' }, + ], + }, + }, { + shouldUpdateRecord: (_table, local, _remote) => { + return local._status !== 'updated' + }, + }) + + await expectSyncedAndMatches(tasks, 'tUpdated', { + _status: 'updated', + _changed: 'name,position', + name: 'local', // local change preserved + position: 100, + description: 'orig', // orig should be preserved + project_id: 'orig', // unchanged + }) + }) + it('can still update', async () => { + const { database, tasks } = makeDatabase() + + await makeLocalChanges(database) + await testApplyRemoteChanges(database, { + mock_tasks: { + updated: [ + // update / updated - should not be ignored when local wasn't changed + { id: 'tSynced', name: 'remote', description: 'remote' }, + ], + }, + }, { + shouldUpdateRecord: (_table, local, _remote) => { + return local._status !== 'updated' + }, + }) + + await expectSyncedAndMatches(tasks, 'tSynced', { + _status: 'synced', + name: 'remote', // remote change + description: 'remote', // remote change + }) + }) + }) + describe('conflictResolver', () => { + it('can account for conflictResolver', async () => { + const { database, tasks } = makeDatabase() + + await makeLocalChanges(database) + await testApplyRemoteChanges(database, { + mock_tasks: { + updated: [ + // update / updated - resolve and update (description is concat of local/remote on change) + { id: 'tUpdated', name: 'remote', description: 'remote' }, + ], + }, + }, { + conflictResolver: (_table, local, remote, resolved) => { + if (local.name !== remote.name) { + resolved.name = `${remote.name} ${local.name}` + } + return resolved + }, + }) + + await expectSyncedAndMatches(tasks, 'tUpdated', { + _status: 'updated', + _changed: 'name,position', + name: 'remote local', // concat of remote and local change + position: 100, + description: 'remote', // remote change + project_id: 'orig', // unchanged + }) + }) + }) }) diff --git a/src/sync/impl/applyRemote.d.ts b/src/sync/impl/applyRemote.d.ts index ab79ac562..06eb18327 100644 --- a/src/sync/impl/applyRemote.d.ts +++ b/src/sync/impl/applyRemote.d.ts @@ -4,7 +4,7 @@ import type { SyncDatabaseChangeSet, SyncLog, SyncConflictResolver, - SyncUpdateCondition, + SyncShouldUpdateRecord, } from '../index' export default function applyRemoteChanges( diff --git a/src/sync/impl/applyRemote.js b/src/sync/impl/applyRemote.js index c22e9c5b8..4f6c1af92 100644 --- a/src/sync/impl/applyRemote.js +++ b/src/sync/impl/applyRemote.js @@ -23,7 +23,7 @@ import type { SyncDatabaseChangeSet, SyncLog, SyncConflictResolver, - SyncUpdateCondition, + SyncShouldUpdateRecord, SyncPullStrategy, } from '../index' import { prepareCreateFromRaw, prepareUpdateFromRaw, recordFromRaw } from './helpers' @@ -33,9 +33,9 @@ type ApplyRemoteChangesContext = $Exact<{ strategy?: ?SyncPullStrategy, sendCreatedAsUpdated?: boolean, log?: SyncLog, + shouldUpdateRecord?: SyncShouldUpdateRecord, conflictResolver?: SyncConflictResolver, _unsafeBatchPerCollection?: boolean, - syncUpdateCondition?: SyncUpdateCondition, }> // NOTE: Creating JS models is expensive/memory-intensive, so we want to avoid it if possible @@ -265,7 +265,7 @@ function prepareApplyRemoteChangesToCollection( collection: Collection, context: ApplyRemoteChangesContext, ): Array { - const { db, sendCreatedAsUpdated, log, conflictResolver, syncUpdateCondition } = context + const { db, sendCreatedAsUpdated, log, conflictResolver, shouldUpdateRecord } = context const { table } = collection const { created, @@ -299,8 +299,8 @@ function prepareApplyRemoteChangesToCollection( raw, collection, log, + shouldUpdateRecord, conflictResolver, - syncUpdateCondition, ), ) } else if (locallyDeletedIds.includes(raw.id)) { @@ -326,8 +326,8 @@ function prepareApplyRemoteChangesToCollection( raw, collection, log, + shouldUpdateRecord, conflictResolver, - syncUpdateCondition, ), ) } else if (locallyDeletedIds.includes(raw.id)) { diff --git a/src/sync/impl/helpers.d.ts b/src/sync/impl/helpers.d.ts index 6fd6281aa..ea1fc1780 100644 --- a/src/sync/impl/helpers.d.ts +++ b/src/sync/impl/helpers.d.ts @@ -4,7 +4,7 @@ import type { SyncLog, SyncDatabaseChangeSet, SyncConflictResolver, - SyncUpdateCondition, + SyncShouldUpdateRecord, } from '../index' // Returns raw record with naive solution to a conflict based on local `_changed` field @@ -21,8 +21,8 @@ export function prepareUpdateFromRaw( record: T, updatedDirtyRaw: DirtyRaw, log?: SyncLog, + shouldUpdateRecord?: SyncShouldUpdateRecord, conflictResolver?: SyncConflictResolver, - syncUpdateCondition?: SyncUpdateCondition, ): T export function prepareMarkAsSynced(record: T): T diff --git a/src/sync/impl/helpers.js b/src/sync/impl/helpers.js index bd523f6a4..2967219a9 100644 --- a/src/sync/impl/helpers.js +++ b/src/sync/impl/helpers.js @@ -9,8 +9,8 @@ import { type RawRecord, type DirtyRaw, sanitizedRaw } from '../../RawRecord' import type { SyncLog, SyncDatabaseChangeSet, + SyncShouldUpdateRecord, SyncConflictResolver, - SyncUpdateCondition, } from '../index' // Returns raw record with naive solution to a conflict based on local `_changed` field @@ -64,10 +64,10 @@ export function requiresUpdate( collection: Collection, local: RawRecord, dirtyRemote: DirtyRaw, - syncUpdateCondition?: SyncUpdateCondition, + shouldUpdateRecord?: SyncShouldUpdateRecord, ): boolean { - if (syncUpdateCondition) { - return syncUpdateCondition(collection.table, local, dirtyRemote) + if (shouldUpdateRecord) { + return shouldUpdateRecord(collection.table, local, dirtyRemote) } if (local._status !== 'synced') { @@ -89,10 +89,10 @@ export function prepareUpdateFromRaw( remoteDirtyRaw: DirtyRaw, collection: Collection, log: ?SyncLog, + shouldUpdateRecord?: SyncShouldUpdateRecord, conflictResolver?: SyncConflictResolver, - syncUpdateCondition?: SyncUpdateCondition, ): ?T { - if (!requiresUpdate(collection, localRaw, remoteDirtyRaw, syncUpdateCondition)) { + if (!requiresUpdate(collection, localRaw, remoteDirtyRaw, shouldUpdateRecord)) { return null } diff --git a/src/sync/impl/synchronize.d.ts b/src/sync/impl/synchronize.d.ts index f19c96957..ccf0751c1 100644 --- a/src/sync/impl/synchronize.d.ts +++ b/src/sync/impl/synchronize.d.ts @@ -8,6 +8,7 @@ export default function synchronize({ sendCreatedAsUpdated, migrationsEnabledAtVersion, log, + shouldUpdateRecord, conflictResolver, _unsafeBatchPerCollection, unsafeTurbo, diff --git a/src/sync/impl/synchronize.js b/src/sync/impl/synchronize.js index e7e5518e5..4755c879d 100644 --- a/src/sync/impl/synchronize.js +++ b/src/sync/impl/synchronize.js @@ -23,10 +23,10 @@ export default async function synchronize({ sendCreatedAsUpdated = false, migrationsEnabledAtVersion, log, + shouldUpdateRecord, conflictResolver, _unsafeBatchPerCollection, unsafeTurbo, - syncUpdateCondition, }: SyncArgs): Promise { const resetCount = database._resetCount log && (log.startedAt = new Date()) @@ -106,9 +106,9 @@ export default async function synchronize({ strategy: ((pullResult: any).experimentalStrategy: ?SyncPullStrategy), sendCreatedAsUpdated, log, + shouldUpdateRecord, conflictResolver, _unsafeBatchPerCollection, - syncUpdateCondition, }) onDidPullChanges && onDidPullChanges(resultRest) } diff --git a/src/sync/index.d.ts b/src/sync/index.d.ts index ea9eda10d..acdc274e2 100644 --- a/src/sync/index.d.ts +++ b/src/sync/index.d.ts @@ -49,7 +49,7 @@ export type SyncLog = { error?: Error; } -export type SyncUpdateCondition = ( +export type SyncShouldUpdateRecord = ( table: TableName, local: DirtyRaw, remote: DirtyRaw, @@ -72,7 +72,7 @@ export type SyncArgs = $Exact<{ log?: SyncLog; // Advanced (unsafe) customization point. Useful when doing per record conflict resolution and can // determine directly from remote and local if we can keep local. - syncUpdateCondition?: SyncUpdateCondition; + shouldUpdateRecord?: SyncShouldUpdateRecord; // Advanced (unsafe) customization point. Useful when you have subtle invariants between multiple // columns and want to have them updated consistently, or to implement partial sync // It's called for every record being updated locally, so be sure that this function is FAST. diff --git a/src/sync/index.js b/src/sync/index.js index 8d33c3222..6bb8142e6 100644 --- a/src/sync/index.js +++ b/src/sync/index.js @@ -75,7 +75,7 @@ export type SyncLog = { error?: Error, } -export type SyncUpdateCondition = ( +export type SyncShouldUpdateRecord = ( table: TableName, local: DirtyRaw, remote: DirtyRaw, @@ -99,7 +99,7 @@ export type SyncArgs = $Exact<{ log?: SyncLog, // Advanced (unsafe) customization point. Useful when doing per record conflict resolution and can // determine directly from remote and local if we can keep local. - syncUpdateCondition?: SyncUpdateCondition, + shouldUpdateRecord?: SyncShouldUpdateRecord, // Advanced (unsafe) customization point. Useful when you have subtle invariants between multiple // columns and want to have them updated consistently, or to implement partial sync // It's called for every record being updated locally, so be sure that this function is FAST. From b8a9ca5dde2aa8c404d8c89e605be06a9a7b390a Mon Sep 17 00:00:00 2001 From: primus11 <1196764+primus11@users.noreply.github.com> Date: Thu, 2 Mar 2023 07:57:31 +0100 Subject: [PATCH 3/4] changed shouldUpdateRecord condition since it makes more sense --- src/sync/impl/helpers.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/sync/impl/helpers.js b/src/sync/impl/helpers.js index 2967219a9..9f901f623 100644 --- a/src/sync/impl/helpers.js +++ b/src/sync/impl/helpers.js @@ -67,7 +67,9 @@ export function requiresUpdate( shouldUpdateRecord?: SyncShouldUpdateRecord, ): boolean { if (shouldUpdateRecord) { - return shouldUpdateRecord(collection.table, local, dirtyRemote) + if (!shouldUpdateRecord(collection.table, local, dirtyRemote)) { + return false; + } } if (local._status !== 'synced') { From 9ca644146e4bc9cb51ebef016e5136743c298f45 Mon Sep 17 00:00:00 2001 From: primus11 <1196764+primus11@users.noreply.github.com> Date: Tue, 16 Jan 2024 11:21:29 +0100 Subject: [PATCH 4/4] reordered some args after rebasing --- src/sync/impl/applyRemote.d.ts | 4 ++-- src/sync/impl/applyRemote.js | 2 +- src/sync/impl/helpers.d.ts | 2 +- src/sync/impl/helpers.js | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sync/impl/applyRemote.d.ts b/src/sync/impl/applyRemote.d.ts index 06eb18327..bcc4ae31b 100644 --- a/src/sync/impl/applyRemote.d.ts +++ b/src/sync/impl/applyRemote.d.ts @@ -3,8 +3,8 @@ import type { Database } from '../..' import type { SyncDatabaseChangeSet, SyncLog, - SyncConflictResolver, SyncShouldUpdateRecord, + SyncConflictResolver, } from '../index' export default function applyRemoteChanges( @@ -13,8 +13,8 @@ export default function applyRemoteChanges( db: Database, sendCreatedAsUpdated: boolean, log?: SyncLog, + shouldUpdateRecord?: SyncShouldUpdateRecord, conflictResolver?: SyncConflictResolver, _unsafeBatchPerCollection?: boolean, - syncUpdateCondition?: SyncUpdateCondition, } ): Promise diff --git a/src/sync/impl/applyRemote.js b/src/sync/impl/applyRemote.js index 4f6c1af92..5773ca93f 100644 --- a/src/sync/impl/applyRemote.js +++ b/src/sync/impl/applyRemote.js @@ -265,7 +265,7 @@ function prepareApplyRemoteChangesToCollection( collection: Collection, context: ApplyRemoteChangesContext, ): Array { - const { db, sendCreatedAsUpdated, log, conflictResolver, shouldUpdateRecord } = context + const { db, sendCreatedAsUpdated, log, shouldUpdateRecord, conflictResolver } = context const { table } = collection const { created, diff --git a/src/sync/impl/helpers.d.ts b/src/sync/impl/helpers.d.ts index ea1fc1780..373d37fa1 100644 --- a/src/sync/impl/helpers.d.ts +++ b/src/sync/impl/helpers.d.ts @@ -3,8 +3,8 @@ import type { RawRecord, DirtyRaw } from '../../RawRecord' import type { SyncLog, SyncDatabaseChangeSet, - SyncConflictResolver, SyncShouldUpdateRecord, + SyncConflictResolver, } from '../index' // Returns raw record with naive solution to a conflict based on local `_changed` field diff --git a/src/sync/impl/helpers.js b/src/sync/impl/helpers.js index 9f901f623..cbb3f94d5 100644 --- a/src/sync/impl/helpers.js +++ b/src/sync/impl/helpers.js @@ -68,7 +68,7 @@ export function requiresUpdate( ): boolean { if (shouldUpdateRecord) { if (!shouldUpdateRecord(collection.table, local, dirtyRemote)) { - return false; + return false } }