UNPKG

json-crdt-repo

Version:

JSON CRDT server and syncing local-first browser client

148 lines (147 loc) 6.61 kB
import { BehaviorSubject, type Observable } from 'rxjs'; import { Model, Patch } from 'json-joy/lib/json-crdt'; import type { ServerBatch, ServerHistory } from '../../remote/types'; import type { BlockId, LocalRepo, LocalRepoEvent, LocalRepoSyncRequest, LocalRepoSyncResponse, LocalRepoGetResponse, LocalRepoGetRequest, LocalRepoCreateResponse, LocalRepoCreateRequest, LocalRepoGetIfResponse, LocalRepoGetIfRequest, LocalRepoPullResponse } from '../types'; import type { BinStrLevel, BinStrLevelOperation, BlockMeta, SyncResult, LevelLocalRepoPubSub, LevelLocalRepoCursor, BlockSyncRecord } from './types'; import type { CrudLocalRepoCipher } from './types'; import type { Locks } from 'thingies/lib/Locks'; import type { JsonValueCodec } from '@jsonjoy.com/json-pack/lib/codecs/types'; export interface LevelLocalRepoOpts { /** * Session ID of the user on this device. The same session ID is reused across * all tabs. */ readonly sid: number; /** * Local persistance LevelDB API. */ readonly kv: BinStrLevel; /** * Optional content encryption/decryption API. */ readonly cipher?: CrudLocalRepoCipher; /** * Cross-tab locking API. */ readonly locks: Locks; /** * Optional observable that emits `true` when the device is connected to the * server and `false` when it's not. */ readonly connected$?: BehaviorSubject<boolean>; /** * RPC API for communication with the server. */ readonly rpc: ServerHistory; /** * Event bus. */ readonly pubsub?: LevelLocalRepoPubSub; /** * Number of milliseconds after which remote calls are considered timed out. */ readonly remoteTimeout?: number; /** * Minimum backoff time in milliseconds for the sync loop. */ readonly syncLoopMinBackoff?: number; /** * Maximum backoff time in milliseconds for the sync loop. */ readonly syncLoopMaxBackoff?: number; /** * If specified, background sync errors will be emitted here. */ readonly onSyncError?: (error: Error | unknown) => void; } export declare class LevelLocalRepo implements LocalRepo { protected readonly opts: LevelLocalRepoOpts; readonly kv: BinStrLevel; readonly locks: Locks; readonly sid: number; readonly connected$: BehaviorSubject<boolean>; protected readonly pubsub: LevelLocalRepoPubSub; protected readonly cipher?: CrudLocalRepoCipher; protected readonly codec: JsonValueCodec; constructor(opts: LevelLocalRepoOpts); private _conSub; private _stopped; stop(): void; protected encrypt(blob: Uint8Array, zip: boolean): Promise<Uint8Array>; protected decrypt(blob: Uint8Array, zip: boolean): Promise<Uint8Array>; protected encode(value: unknown, zip: boolean): Promise<Uint8Array>; protected decode(blob: Uint8Array, zip: boolean): Promise<unknown>; /** @todo Encrypt collection and key. */ blockKeyBase(id: BlockId): Promise<string>; frontierKeyBase(blockKeyBase: string): string; frontierKey(blockKeyBase: string, time: number): string; batchKeyBase(blockKeyBase: string): string; batchKey(blockKeyBase: string, seq: number): string; snapshotKeyBase(blockKeyBase: string): string; snapshotKey(blockKeyBase: string, seq: number): string; protected syncKey(keyBase: string): string; protected _exists(keyBase: string): Promise<boolean>; protected _modelWrOp(keyBase: string, model: Uint8Array): Promise<BinStrLevelOperation>; protected _metaWrOp(keyBase: string, meta?: BlockMeta): Promise<BinStrLevelOperation>; protected _modelWrOps(keyBase: string, model: Uint8Array, meta?: BlockMeta): Promise<BinStrLevelOperation[]>; protected _wrModel(keyBase: string, model: Uint8Array, meta?: BlockMeta): Promise<void>; protected load(id: BlockId): Promise<{ model: Model; cursor: number; }>; protected readMeta(keyBase: string): Promise<BlockMeta>; readModel0(keyBase: string): Promise<Uint8Array>; readModel(keyBase: string): Promise<Model>; readFrontierBlobs0(keyBase: string): AsyncGenerator<readonly [string, Uint8Array<ArrayBufferLike>], void, unknown>; readFrontier0(keyBase: string): Promise<Patch[]>; readFrontierTip(keyBase: string): Promise<Patch | undefined>; protected lockBlock<T>(keyBase: string, fn: () => Promise<T>): Promise<T>; protected lockBlockForSync<T>(keyBase: string, fn: () => Promise<T>): Promise<T>; protected isBlockLockedForSync(keyBase: string): boolean; protected markDirty(keyBase: string, id: BlockId): Promise<void>; protected markDirtyAndSync(keyBase: string, id: BlockId): Promise<boolean>; protected remoteTimeout(): number; /** * Pushes to remote. * @param id Block ID. * @param pull Whether to pull if there are no patches to push. */ protected push(keyBase: string, id: BlockId, doPull?: boolean): Promise<boolean>; /** * Iterates over all blocks marked as dirty. */ protected listDirty(): AsyncIterableIterator<BlockSyncRecord>; protected listDirtyBatch(batchSize?: number): AsyncIterableIterator<BlockSyncRecord[]>; syncItem(keyBase: string, id: BlockId): Promise<SyncResult>; remoteSyncAll(): Promise<SyncResult[]>; protected _remoteSyncLoopActive: boolean; protected _remoteSyncDelayTimer: unknown; protected runRemoteSyncLoop(): void; /** ----------------------------------------------------- {@link LocalRepo} */ create({ id, patches }: LocalRepoCreateRequest): Promise<LocalRepoCreateResponse>; get({ id, remote }: LocalRepoGetRequest): Promise<LocalRepoGetResponse>; getIf(request: LocalRepoGetIfRequest): Promise<null | LocalRepoGetIfResponse>; sync(req: LocalRepoSyncRequest): Promise<LocalRepoSyncResponse>; private _syncCreate; private _syncMerge; protected readLocal0(keyBase: string): Promise<[model: Model, cursor: LevelLocalRepoCursor]>; private _syncRead0; private _syncRead; del(id: BlockId): Promise<void>; /** * Pull from remote. */ pull(id: BlockId): Promise<LocalRepoPullResponse>; protected pullNew(id: BlockId, keyBase: string): Promise<{ model: Model; meta: BlockMeta; }>; protected pullExisting(id: BlockId, keyBase: string): Promise<{ model: Model; meta: BlockMeta; }>; change$(id: BlockId): Observable<LocalRepoEvent>; private _subs; protected _subRemote(id: BlockId): Observable<void>; protected _onUpd(id: BlockId, batch: ServerBatch): Promise<void>; }