export type ErrorCode = | 'invalid-sync-id' | 'invalid-commits' | 'internal' | 'disconnected' | 'network' | 'bad-request' | 'unauthorized'; export type BaseCommit = { ref: string; // The ref of the parent commit that the delta was based on // or undefined if it's an initial document commit baseRef?: string; // a structure defining the differences between baseRef and this commit delta?: Delta; // application-specific metadata about the commit metadata: CommitMetadata; }; export type EditCommit = BaseCommit< CommitMetadata, Delta >; export type MergeCommit = BaseCommit< CommitMetadata, Delta > & { // primary parent of the merge commit baseRef: string; // secondary parent of the merge commit mergeRef: string; }; export function isMergeCommit( commit: Commit, ): commit is MergeCommit { return (commit as MergeCommit).mergeRef !== undefined; } export type CommitInfo = { ref: string; baseRef?: string; mergeRef?: string; }; export type Commit = | MergeCommit | EditCommit; export type LocalReadStatus = | 'loading' /** reading state from disk */ | 'error' | 'ready'; /** have latest state from disk, receiving local changes */ export type LocalSaveStatus = | 'ready' /** no changes in local memory */ | 'error' | 'pending' /** changes in local memory, not sent to store yet */ | 'saving'; /** sent changes to local store, no `ack` yet */ export type RemoteConnectStatus = 'offline' | 'connecting' | 'online'; export type RemoteReadStatus = | 'offline' | 'loading' | 'ready' | 'error' /** the remote is in a persistent bad state */; export type RemoteSaveStatus = | 'ready' /** all local state has been synced to remote (though maybe local changes in memory) */ | 'pending' /** we have local state that hasn't been sent to remote yet (maybe offline) */ | 'saving' /** we got an error back from remote when saving commits */ | 'error'; /** we sent local state to remote, but haven't got `ack` yet */ export type SyncStatus = { localRead: LocalReadStatus; localSave: LocalSaveStatus; remoteConnect: RemoteConnectStatus; remoteRead: RemoteReadStatus; remoteSave: RemoteSaveStatus; }; export type ClientPresenceRef = { ref: string | undefined; presence: Presence | undefined; }; export type ClientInfo = ClientPresenceRef & { userId: string; clientId: string; }; export type LocalClientInfo = ClientInfo & { self?: true; }; export type ClientList = readonly LocalClientInfo[]; export type InitEvent = | { type: 'init'; version?: undefined; lastSyncId: string | undefined; auth: unknown; } | { type: 'init'; version: 1; localStoreId: string; lastSyncCursor: string | undefined; auth: unknown; docId?: string; }; export type CommitAck = { ref: string; metadata?: CommitMetadata; }; export type CommitsEvent = { type: 'commits'; commits: readonly Commit[]; clientInfo?: ClientInfo; syncId?: string; }; export type ReadyEvent = { type: 'ready'; }; export type AckCommitErrorCode = | 'invalid' | 'unknown-ref' | 'storage-failure' | 'internal'; export type AckCommitError = { code: AckCommitErrorCode; message?: string; }; export type AckRefErrors = Record; export type AckCommitsEvent = { type: 'ack'; acks: readonly CommitAck[]; refErrors?: AckRefErrors; syncId: string; }; export type ClientJoinEvent = { type: 'client-join'; info: ClientInfo; }; export type ClientPresenceEvent = { type: 'client-presence'; info: ClientInfo; }; export type ClientLeaveEvent = { type: 'client-leave'; userId: string; clientId: string; }; export type ErrorEvent = { type: 'error'; code: ErrorCode; message?: string; fatal?: boolean; reconnect?: boolean; }; export type RemoteStateEvent = { type: 'remote-state'; connect?: RemoteConnectStatus; read?: RemoteReadStatus; save?: RemoteSaveStatus; }; export type LeaderEvent = { type: 'leader'; action: 'request' | 'current' | 'accept' | 'withdraw'; clientId: string; }; export type SyncEvent = Readonly< | InitEvent | CommitsEvent | ReadyEvent | LeaderEvent | AckCommitsEvent | ClientJoinEvent | ClientPresenceEvent | ClientLeaveEvent | RemoteStateEvent | ErrorEvent >; export type OnStoreEventFn = ( event: SyncEvent, remoteOrigin: boolean, ) => void; export type OnRemoteEventFn = ( event: SyncEvent, ) => void; export type GetLocalStoreFn = ( userId: string, clientId: string, onEvent: OnStoreEventFn, ) => LocalStore; export type RemoteSyncInfo = { localStoreId: string; lastSyncCursor: string | undefined; }; export type GetRemoteFn = ( userId: string, remoteSyncInfo: RemoteSyncInfo, onRemoteEvent: OnRemoteEventFn, ) => | Remote | Promise>; export interface LocalStore { update( commits: readonly Commit[], presence: ClientPresenceRef | undefined, ): Promise; isRemoteLeader: boolean; shutdown(): void | Promise; } export interface Remote { send(event: SyncEvent): void; shutdown(): void | Promise; } export interface CommitRepository { getLocalCommits(): AsyncIterableIterator< CommitsEvent >; getCommitsForRemote(): AsyncIterableIterator< CommitsEvent >; addCommits( commits: readonly Commit[], remoteSyncId: string | undefined, ): Promise>; acknowledgeRemoteCommits( refs: readonly CommitAck[], remoteSyncId: string, ): Promise; getRemoteSyncInfo(): Promise; shutdown(): void | Promise; }