@nozbe/watermelondb
Version:
Build powerful React Native and React web apps that scale from hundreds to tens of thousands of records and remain fast
149 lines (125 loc) • 4.73 kB
JavaScript
// @flow
import { invariant } from '../../utils/common'
import {
applyRemoteChanges,
fetchLocalChanges,
markLocalChangesAsSynced,
getLastPulledAt,
setLastPulledAt,
setLastPulledSchemaVersion,
getMigrationInfo,
} from './index'
import { ensureSameDatabase, isChangeSetEmpty, changeSetCount } from './helpers'
import type { SyncArgs, Timestamp, SyncPullStrategy } from '../index'
export default async function synchronize({
database,
pullChanges,
onWillApplyRemoteChanges,
onDidPullChanges,
pushChanges,
sendCreatedAsUpdated = false,
migrationsEnabledAtVersion,
log,
conflictResolver,
_unsafeBatchPerCollection,
unsafeTurbo,
}: SyncArgs): Promise<void> {
const resetCount = database._resetCount
log && (log.startedAt = new Date())
log && (log.phase = 'starting')
// TODO: Wrap the three computionally intensive phases in `requestIdleCallback`
// pull phase
const lastPulledAt = await getLastPulledAt(database)
log && (log.lastPulledAt = lastPulledAt)
const { schemaVersion, migration, shouldSaveSchemaVersion } = await getMigrationInfo(
database,
log,
lastPulledAt,
migrationsEnabledAtVersion,
)
log && (log.phase = 'ready to pull')
// $FlowFixMe
const pullResult = await pullChanges({
lastPulledAt,
schemaVersion,
migration,
})
log && (log.phase = 'pulled')
let newLastPulledAt: Timestamp = (pullResult: any).timestamp
const remoteChangeCount = pullResult.changes ? changeSetCount(pullResult.changes) : NaN
if (onWillApplyRemoteChanges) {
await onWillApplyRemoteChanges({ remoteChangeCount })
}
await database.write(async () => {
ensureSameDatabase(database, resetCount)
invariant(
lastPulledAt === (await getLastPulledAt(database)),
'[Sync] Concurrent synchronization is not allowed. More than one synchronize() call was running at the same time, and the later one was aborted before committing results to local database.',
)
if (unsafeTurbo) {
invariant(
!_unsafeBatchPerCollection,
'unsafeTurbo must not be used with _unsafeBatchPerCollection',
)
invariant(
'syncJson' in pullResult || 'syncJsonId' in pullResult,
'missing syncJson/syncJsonId',
)
invariant(lastPulledAt === null, 'unsafeTurbo can only be used as the first sync')
const syncJsonId = pullResult.syncJsonId || Math.floor(Math.random() * 1000000000)
if (pullResult.syncJson) {
await database.adapter.provideSyncJson(syncJsonId, pullResult.syncJson)
}
const resultRest = await database.adapter.unsafeLoadFromSync(syncJsonId)
newLastPulledAt = resultRest.timestamp
onDidPullChanges && onDidPullChanges(resultRest)
}
log && (log.newLastPulledAt = newLastPulledAt)
invariant(
typeof newLastPulledAt === 'number' && newLastPulledAt > 0,
`pullChanges() returned invalid timestamp ${newLastPulledAt}. timestamp must be a non-zero number`,
)
if (!unsafeTurbo) {
// $FlowFixMe
const { changes: remoteChanges, ...resultRest } = pullResult
log && (log.remoteChangeCount = remoteChangeCount)
// $FlowFixMe
await applyRemoteChanges(remoteChanges, {
db: database,
strategy: ((pullResult: any).experimentalStrategy: ?SyncPullStrategy),
sendCreatedAsUpdated,
log,
conflictResolver,
_unsafeBatchPerCollection,
})
onDidPullChanges && onDidPullChanges(resultRest)
}
log && (log.phase = 'applied remote changes')
await setLastPulledAt(database, newLastPulledAt)
if (shouldSaveSchemaVersion) {
await setLastPulledSchemaVersion(database, schemaVersion)
}
}, 'sync-synchronize-apply')
// push phase
if (pushChanges) {
log && (log.phase = 'ready to fetch local changes')
const localChanges = await fetchLocalChanges(database)
log && (log.localChangeCount = changeSetCount(localChanges.changes))
log && (log.phase = 'fetched local changes')
ensureSameDatabase(database, resetCount)
if (!isChangeSetEmpty(localChanges.changes)) {
log && (log.phase = 'ready to push')
const pushResult =
(await pushChanges({ changes: localChanges.changes, lastPulledAt: newLastPulledAt })) || {}
log && (log.phase = 'pushed')
log && (log.rejectedIds = pushResult.experimentalRejectedIds)
ensureSameDatabase(database, resetCount)
await markLocalChangesAsSynced(database, localChanges, pushResult.experimentalRejectedIds)
log && (log.phase = 'marked local changes as synced')
}
} else {
log && (log.phase = 'pushChanges not defined')
}
log && (log.finishedAt = new Date())
log && (log.phase = 'done')
}