UNPKG

@nozbe/watermelondb

Version:

Build powerful React Native and React web apps that scale from hundreds to tens of thousands of records and remain fast

141 lines (125 loc) 5.49 kB
// @flow import type { Database, RecordId, TableName, Model } from '..' import type { Where } from '../QueryDescription' import { type DirtyRaw } from '../RawRecord' import type { SchemaVersion } from '../Schema' import { type MigrationSyncChanges } from '../Schema/migrations/getSyncChanges' export type Timestamp = number export type SyncTableChangeSet = $Exact<{ created: DirtyRaw[], updated: DirtyRaw[], deleted: RecordId[], }> export type SyncDatabaseChangeSet = { [TableName<any>]: SyncTableChangeSet } export type SyncLocalChanges = $Exact<{ changes: SyncDatabaseChangeSet, affectedRecords: Model[] }> export type SyncPullArgs = $Exact<{ lastPulledAt: ?Timestamp, schemaVersion: SchemaVersion, migration: MigrationSyncChanges, }> export type SyncPullStrategyType = // Standard sync strategy (default) | 'incremental' // Advanced alternative strategy: indicates that `changes` contains a full dataset (same as during // initial sync). Local records not present in the changeset will be deleted. Other records will be // applied as usual (created, updated, local update conflicts resolved). // This is useful to recover from a corrupted local database, or to deal with very large state changes // such that server doesn't know how to efficiently send incremental changes and wants to send a full // changeset instead. // See docs for more details. | 'replacement' export type SyncPullStrategy = | SyncPullStrategyType | $Exact<{ default: SyncPullStrategyType, override: { [TableName<any>]: SyncPullStrategyType }, experimentalQueryRecordsForReplacement?: { [TableName<any>]: () => Where[], }, }> export type SyncPullResult = | $Exact<{ changes: SyncDatabaseChangeSet, timestamp: Timestamp, experimentalStrategy?: SyncPullStrategy, }> | $Exact<{ syncJson: string }> | $Exact<{ syncJsonId: number }> export type SyncRejectedIds = { [TableName<any>]: RecordId[] } export type SyncPushArgs = $Exact<{ changes: SyncDatabaseChangeSet, lastPulledAt: Timestamp }> export type SyncPushResult = $Exact<{ experimentalRejectedIds?: SyncRejectedIds }> type SyncConflict = $Exact<{ local: DirtyRaw, remote: DirtyRaw, resolved: DirtyRaw }> export type SyncLog = { startedAt?: Date, lastPulledAt?: ?number, lastPulledSchemaVersion?: ?SchemaVersion, migration?: ?MigrationSyncChanges, newLastPulledAt?: number, resolvedConflicts?: SyncConflict[], rejectedIds?: SyncRejectedIds, finishedAt?: Date, remoteChangeCount?: number, localChangeCount?: number, phase?: string, // NOTE: an textual information, not a stable API! error?: Error, } export type SyncConflictResolver = ( table: TableName<any>, local: DirtyRaw, remote: DirtyRaw, resolved: DirtyRaw, ) => DirtyRaw // TODO: JSDoc'ify this export type SyncArgs = $Exact<{ database: Database, pullChanges: (SyncPullArgs) => Promise<SyncPullResult>, pushChanges?: (SyncPushArgs) => Promise<?SyncPushResult>, // version at which support for migration syncs was added - the version BEFORE first syncable migration migrationsEnabledAtVersion?: SchemaVersion, sendCreatedAsUpdated?: boolean, log?: SyncLog, // 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. // If you don't want to change default behavior for a given record, return `resolved` as is // Note that it's safe to mutate `resolved` object, so you can skip copying it for performance. conflictResolver?: SyncConflictResolver, // commits changes in multiple batches, and not one - temporary workaround for memory issue _unsafeBatchPerCollection?: boolean, // Advanced optimization - pullChanges must return syncJson or syncJsonId to be processed by native code. // This can only be used on initial (login) sync, not for incremental syncs. // This can only be used with SQLiteAdapter with JSI enabled. // The exact API may change between versions of WatermelonDB. // See documentation for more details. unsafeTurbo?: boolean, // Called after changes are pulled with whatever was returned by pullChanges, minus `changes`. Useful // when using turbo mode onDidPullChanges?: (Object) => Promise<void>, // Called after pullChanges is done, but before these changes are applied. Some stats about the pulled // changes are passed as arguments. An advanced user can use this for example to show some UI to the user // when processing a very large sync (could be useful for replacement syncs). Note that remote change count // is NaN in turbo mode. onWillApplyRemoteChanges?: (info: $Exact<{ remoteChangeCount: number }>) => Promise<void>, }> /** * Synchronizes database with a remote server * * See docs for more details */ export async function synchronize(args: SyncArgs): Promise<void> { try { const synchronizeImpl = require('./impl/synchronize').default await synchronizeImpl(args) } catch (error) { args.log && (args.log.error = error) throw error } } /** * Returns `true` if database has any unsynced changes. * * Use this to check if you can safely log out (delete the database) */ export function hasUnsyncedChanges({ database }: $Exact<{ database: Database }>): Promise<boolean> { return require('./impl').hasUnsyncedChanges(database) }