UNPKG

rxdb

Version:

A local-first realtime NoSQL Database for JavaScript applications - https://rxdb.info/

289 lines (259 loc) 9.52 kB
import { BehaviorSubject, Observable, Subject } from 'rxjs'; import type { RxConflictHandler, RxConflictHandlerInput } from './conflict-handling.d.ts'; import type { RxError, RxTypeError } from './rx-error.d.ts'; import type { BulkWriteRow, RxDocumentData, WithDeleted, WithDeletedAndAttachments } from './rx-storage.d.ts'; import type { RxStorageInstance } from './rx-storage.interface.d.ts'; import type { HashFunction } from './util.d.ts'; export type RxStorageReplicationMeta<RxDocType, CheckpointType> = { /** * Combined primary key consisting * of: [replicationId, itemId, isCheckpoint] * so that the same RxStorageInstance * can be used for multiple replication states. */ id: string; /** * Either the document primaryKey * or the id of the replication checkpoint. */ itemId: string; /** * True if the doc data is about a checkpoint, * False if it is about a document state from the master. * Stored as a string so it can be used * in the combined primary key 'id' */ isCheckpoint: '0' | '1'; checkpointData?: CheckpointType; /** * the document state of the master * only set if not checkpoint. */ docData?: RxDocType | RxDocumentData<RxDocType> | any; /** * If the current assumed master was written while * resolving a conflict, this field contains * the revision of the conflict-solution that * is stored in the forkInstance. */ isResolvedConflict?: string; }; export type RxReplicationWriteToMasterRow<RxDocType> = { assumedMasterState?: WithDeletedAndAttachments<RxDocType>; newDocumentState: WithDeletedAndAttachments<RxDocType>; }; export type DocumentsWithCheckpoint<RxDocType, CheckpointType> = { documents: WithDeletedAndAttachments<RxDocType>[]; checkpoint: CheckpointType; }; export type RxReplicationPullStreamItem<RxDocType, MasterCheckpointType> = DocumentsWithCheckpoint<RxDocType, MasterCheckpointType> | /** * Emit this when the masterChangeStream$ might have missed out * some events because the fork lost the connection to the master. * Like when the user went offline and reconnects. */ 'RESYNC'; /** * The replication handler contains all logic * that is required by the replication protocol * to interact with the master instance. * This is an abstraction so that we can use different * handlers for GraphQL, REST or any other transportation layer. * Even a RxStorageInstance can be wrapped in a way to represent a replication handler. * * The RxStorage instance of the master branch that is * replicated with the fork branch. * The replication algorithm is made to make * as less writes on the master as possible. * The master instance is always 'the truth' which * does never contain conflicting document states. * All conflicts are handled on the fork branch * before being replicated to the master. */ export type RxReplicationHandler<RxDocType, MasterCheckpointType> = { masterChangeStream$: Observable<RxReplicationPullStreamItem<RxDocType, MasterCheckpointType>>; masterChangesSince( checkpoint: MasterCheckpointType | undefined, batchSize: number ): Promise<DocumentsWithCheckpoint<RxDocType, MasterCheckpointType | undefined>>; /** * Writes the fork changes to the master. * Only returns the conflicts if there are any. * (otherwise returns an empty array.) */ masterWrite( rows: RxReplicationWriteToMasterRow<RxDocType>[] ): Promise<WithDeleted<RxDocType>[]>; }; export type RxStorageInstanceReplicationInput<RxDocType> = { /** * A string that uniquely identifies * the replication. * Ensures that checkpoint are not * mixed with other replications. */ identifier: string; pullBatchSize: number; pushBatchSize: number; replicationHandler: RxReplicationHandler<RxDocType, any>; conflictHandler: RxConflictHandler<RxDocType>; // can be set to also replicate the _meta field of the document. keepMeta?: boolean; /** * The fork is the one that contains the forked chain of document writes. * All conflicts are solved on the fork and only resolved correct document data * is written back to the parent. */ forkInstance: RxStorageInstance<RxDocType, any, any>; /** * The replication needs to store some meta data * for documents to know which state is at the master * and how/if it diverges from the fork. * In the past this was stored in the _meta field of * the forkInstance documents but that was not a good design decision * because it required additional writes on the forkInstance * to know which documents have been upstream replicated * to not cause conflicts. * Using the metaInstance instead leads to better overall performance * because RxDB will not re-emit query results or document state * when replication meta data is written. * * In addition to per-document meta data, * the replication checkpoints are also stored in this instance. * */ metaInstance: RxStorageInstance<RxStorageReplicationMeta<RxDocType, any>, any, any>; /** * When a write happens to the fork, * normally the replication will directly try to persist. * * For many use cases, it is better to await the next event loop tick * or to wait until the RxDatabase is idle or requestIdleCallback() calls * to ensure the CPU is idle. * This can improve performance because the persistence will not affect UI * renders. * * But: The longer you wait here, the higher is the risk of losing fork * writes when the replication is closed unexpected. */ waitBeforePersist?: () => Promise<any>; hashFunction: HashFunction; initialCheckpoint?: { upstream?: any; downstream?: any; }; }; export type RxStorageInstanceReplicationState<RxDocType> = { // store the primaryPath here for better reuse and performance. primaryPath: string; hasAttachments: boolean; input: RxStorageInstanceReplicationInput<RxDocType>; events: { /** * Streams all document writes that have successfully * been written in one direction. */ processed: { up: Subject<RxReplicationWriteToMasterRow<RxDocType>>; down: Subject<BulkWriteRow<RxDocType>>; }; resolvedConflicts: Subject<{ input: RxConflictHandlerInput<RxDocType>; output: WithDeleted<RxDocType>; }>; /** * Contains the cancel state. * Emit true here to cancel the replication. */ canceled: BehaviorSubject<boolean>; /** * Contains the pause state. * Emit true here to pause the replication. */ paused: BehaviorSubject<boolean>; /** * Contains true if the replication is doing something * at this point in time. * If this is false, it means that the replication * is idle AND in sync. */ active: { [direction in RxStorageReplicationDirection]: BehaviorSubject<boolean>; }; /** * All errors that would otherwise be unhandled, * get emitted here. */ error: Subject<RxError | RxTypeError>; }; /** * Contains counters that can be used in tests * or to debug problems. */ stats: { down: { addNewTask: number; downstreamResyncOnce: number; downstreamProcessChanges: number; masterChangeStreamEmit: number; persistFromMaster: number; }; up: { upstreamInitialSync: number; forkChangeStreamEmit: number; processTasks: number; persistToMaster: number; persistToMasterHadConflicts: number; persistToMasterConflictWrites: number; }; }; /** * Used in checkpoints and ._meta fields * to ensure we do not mix up meta data of * different replications. * We have to use the promise because the key is hashed which runs async. */ checkpointKey: Promise<string>; /** * Storage.bulkWrites() that are initialized from the * downstream, get this flag as context-param * so that the emitted event bulk can be identified * to be sourced from the downstream and it will not try * to upstream these documents again. */ downstreamBulkWriteFlag: Promise<string>; /** * Tracks if the streams have been in sync * for at least one time. */ firstSyncDone: { [direction in RxStorageReplicationDirection]: BehaviorSubject<boolean>; }; /** * Can be used to detect if the replication is doing something * or if it is in an idle state. */ streamQueue: { [direction in RxStorageReplicationDirection]: Promise<any>; }; checkpointQueue: Promise<any>; /** * For better performance we store the last known checkpoint * document so that we can likely do checkpoint storing without * conflicts. */ lastCheckpointDoc: { [direction in RxStorageReplicationDirection]?: RxDocumentData<RxStorageReplicationMeta<RxDocType, any>>; }; }; export type RxStorageReplicationDirection = 'up' | 'down';