import type { Model, Patch } from 'json-joy/lib/json-crdt'; import type { Observable } from 'rxjs'; export type BlockId = [...collection: string[], id: string] | string[]; /** * The local repo persists data on the local device. It is the primary building * block for the local-first applications. The local repo also facilitates * synchronization and conflict resolution between multiple tabs or processes * running on the same device. */ export interface LocalRepo { /** * Creates a new block (document) in the local repo. */ create(request: LocalRepoCreateRequest): Promise; /** * Reads a block (document) from the local repo. Simply fetches the current * state of the block stored in the repo. */ get(request: LocalRepoGetRequest): Promise; /** * Reads a block from the local repo, if the model clock or the remote cursor * is behind. */ getIf(request: LocalRepoGetIfRequest): Promise; /** * Synchronizes an in-memory editing session changes to the locally stored * data. The `sync` call is used to create, read, and update data. */ sync(request: LocalRepoSyncRequest): Promise; /** * Retrieves the latest state of the block from the remote. * * @param id Unique ID of the block. */ pull(id: BlockId): Promise; /** * Deletes a block (document) from the local repo. */ del(id: BlockId): Promise; /** * Emits an event every time a block is updated. * * @param id Unique ID of the block. */ change$(id: BlockId): Observable; } export interface LocalRepoCreateRequest { id: BlockId; patches?: Patch[]; } export interface LocalRepoCreateResponse { /** * Promise that resolves when the local changes have been successfully * synchronized with the server or remote peers. */ remote: Promise; /** * Model snapshot that the client should reset its "start" state to. The * `Model` is sent when `rebase` patches are not available, or when the * patch set is too large. */ model: Model; } /** * A single "sync" call servers as three different operations: "create", "read", * and "update". * * - When `cursor` is not set and `batch` is set, the call is equivalent to "create". * - When `cursor` and `batch` ar both not set, the call is equivalent to "read". * - When `cursor` is set and `batch` is set, the call is equivalent to "update". */ export interface LocalRepoSyncRequest { /** * Unique ID of the block. */ id: BlockId; /** * Logical clock time of the local operations which the client has caught up * to. */ time?: number; /** * The last known cursor returned in the `.sync()` call response. The cursor * should be omitted in the first `.sync()` call, and then set to the value * returned in the previous `.sync()` call. */ cursor?: undefined | unknown; /** * List of changes that the client wants to persist. */ patches?: Patch[]; /** * The session ID, which originated this sync call. The session ID is * forwarded in events, so the client can distinguish between its own changes * and changes made by other clients. */ session?: number; } export interface LocalRepoSyncResponse { /** * The last known cursor. */ cursor: undefined | unknown; /** * Model snapshot that the client should reset its "start" state to. The * `Model` is sent when the *sync* call detects that the client is behind the * remote or the local frontier. */ model?: Model; /** * List of patches that the client should apply to the local editing session. */ merge?: Patch[]; /** * Promise that resolves when the local changes have been successfully * synchronized with the server or remote peers. */ remote?: Promise; } export interface LocalRepoPullResponse { /** * Cursor that the client should use in the next `.sync()` call. If the cursor * is not set, the client should use the cursor from the previous `.sync()` * call. */ cursor: undefined | unknown; /** The latest state of the block. */ model: Model; } export interface LocalRepoGetRequest { /** * Unique ID of the block. */ id: BlockId; /** * Whether to load the block from the remote, if it does not exist locally. * Defaults to `false`. */ remote?: boolean; } export interface LocalRepoGetResponse { /** * Cursor that the client should use in the next `.sync()` call. If the cursor * is not set, the client should use the cursor from the previous `.sync()` * call. */ cursor: undefined | unknown; /** The latest state of the block. */ model: Model; } export interface LocalRepoGetIfRequest { /** * Unique ID of the block. */ id: BlockId; /** * The last known cursor returned in the `.sync()` call response. */ cursor?: unknown | undefined; /** * The last model clock time. */ time?: number; } export interface LocalRepoGetIfResponse { model: Model; cursor: unknown; } /** * The change event. It is emitted every time a block is updated, either by * the local client or by a remote client. It contains various types of changes * that can be applied to the local editing session. */ export type LocalRepoEvent = LocalRepoMergeEvent | LocalRepoRebaseEvent | LocalRepoResetEvent | LocalRepoDeleteEvent; export interface LocalRepoMergeEvent { /** * List of patches that the client should apply to the local editing session. * They can be applied "on top" of the current editing session state, without * the need to reset or rebase the editing session. */ merge: Patch[]; cursor: unknown; } export interface LocalRepoRebaseEvent { /** * List of patches that the client should rebase its editing session on top * of. The rebase patches usually result from the changes happening in another * local editing session, for example, another tab. This is because the tabs * reuse the same session ID, hence, for the timestamps to be unique, the * timestamps of the in-progress editing session are "rebased". * * In practice, this should almost never happen, as by the time the user * switches tabs, the changes are already synchronized. */ rebase: Patch[]; session?: number; } export interface LocalRepoResetEvent { /** * The new model snapshot that the client should reset its editing session to. * This happens when the changes are too large to be sent as patches, or when * the changes are too many and the client should reset its editing session * to the new state. When resetting, the client might still need to apply * `merge` and `rebase` patches on top of the new model. */ reset: Model; } export interface LocalRepoDeleteEvent { del: true; }