import { CreateIfNotExistsMutation, CreateMutation, CreateOrReplaceMutation, DeleteMutation, PatchMutation, Path } from "@sanity/types"; /** * Sanity document with a guaranteed `_id` and `_type` * * @internal */ interface Doc { _id: string; _type: string; _rev?: string; _updatedAt?: string; _createdAt?: string; [attribute: string]: unknown; } /** * Internal mutation body representation - note that theoretically a * mutation can only hold one of these operations each, but for sake * of simpler code it is bundled together as one here * * @internal */ interface Mut { create?: CreateMutation['create']; createIfNotExists?: CreateIfNotExistsMutation['createIfNotExists']; createOrReplace?: CreateOrReplaceMutation['createOrReplace']; delete?: DeleteMutation['delete']; patch?: PatchMutation['patch']; } /** * Parameters attached to the mutation * * @internal */ interface MutationParams { transactionId?: string; transition?: string; identity?: string; previousRev?: string; resultRev?: string; mutations: Mut[]; timestamp?: string; effects?: { apply: unknown; revert: unknown; }; } /** * A mutation describing a number of operations on a single document. * This should be considered an immutable structure. Mutations are compiled * on first application, and any changes in properties will not effectively * change its behavior after that. * * @internal */ declare class Mutation { params: MutationParams; compiled?: (doc: Doc | null) => Doc | null; _appliesToMissingDocument: boolean | undefined; constructor(options: MutationParams); get transactionId(): string | undefined; get transition(): string | undefined; get identity(): string | undefined; get previousRev(): string | undefined; get resultRev(): string | undefined; get mutations(): Mut[]; get timestamp(): Date | undefined; get effects(): { apply: unknown; revert: unknown; } | undefined; assignRandomTransactionId(): void; appliesToMissingDocument(): boolean; compile(): void; apply(document: Doc | null): Doc | null; static applyAll(document: Doc | null, mutations: Mutation[]): Doc | null; static squash(document: Doc | null, mutations: Mutation[]): Mutation; } /** * @internal */ interface SubmissionResponder { success: () => void; failure: () => void; } /** * Models a document as it is changed by our own local patches and remote patches coming in from * the server. Consolidates incoming patches with our own submitted patches and maintains two * versions of the document. EDGE is the optimistic document that the user sees that will always * immediately reflect whatever she is doing to it, and HEAD which is the confirmed version of the * document consistent with the mutations we have received from the server. As long as nothing out of * the ordinary happens, we can track all changes by hooking into the onMutation callback, but we * must also respect onRebase events that fire when we have to backtrack because one of our optimistically * applied patches were rejected, or some bastard was able to slip a mutation in between ours own. * * @internal */ declare class Document { /** * Incoming patches from the server waiting to be applied to HEAD */ incoming: Mutation[]; /** * Patches we know has been subitted to the server, but has not been seen yet in the return channel * so we can't be sure about the ordering yet (someone else might have slipped something between them) */ submitted: Mutation[]; /** * Pending mutations */ pending: Mutation[]; /** * Our model of the document according to the incoming patches from the server */ HEAD: Doc | null; /** * Our optimistic model of what the document will probably look like as soon as all our patches * have been processed. Updated every time we stage a new mutation, but also might revert back * to previous states if our mutations fail, or could change if unexpected mutations arrive * between our own. The `onRebase` callback will be called when EDGE changes in this manner. */ EDGE: Doc | null; /** * Called with the EDGE document when that document changes for a reason other than us staging * a new patch or receiving a mutation from the server while our EDGE is in sync with HEAD: * I.e. when EDGE changes because the order of mutations has changed in relation to our * optimistic predictions. */ onRebase?: (edge: Doc | null, incomingMutations: Mutation[], pendingMutations: Mutation[]) => void; /** * Called when we receive a patch in the normal order of things, but the mutation is not ours */ onMutation?: (msg: { mutation: Mutation; document: Doc | null; remote: boolean; }) => void; /** * Called when consistency state changes with the boolean value of the current consistency state */ onConsistencyChanged?: (isConsistent: boolean) => void; /** * Called whenever a new incoming mutation comes in. These are always ordered correctly. */ onRemoteMutation?: (mut: Mutation) => void; /** * We are consistent when there are no unresolved mutations of our own, and no un-applicable * incoming mutations. When this has been going on for too long, and there has been a while * since we staged a new mutation, it is time to reset your state. */ inconsistentAt: Date | null; /** * The last time we staged a patch of our own. If we have been inconsistent for a while, but it * hasn't been long since we staged a new mutation, the reason is probably just because the user * is typing or something. * * Should be used as a guard against resetting state for inconsistency reasons. */ lastStagedAt: Date | null; constructor(doc: Doc | null); reset(doc: Doc | null): void; arrive(mutation: Mutation): void; stage(mutation: Mutation, silent?: boolean): SubmissionResponder; isConsistent(): boolean; considerIncoming(): void; updateConsistencyFlag(): void; applyIncoming(mut: Mutation | undefined): boolean; /** * Returns true if there are unresolved mutations between HEAD and EDGE, meaning we have * mutations that are still waiting to be either submitted, or to be confirmed by the server. * * @returns true if there are unresolved mutations between HEAD and EDGE, false otherwise */ hasUnresolvedMutations(): boolean; /** * When an incoming mutation is applied to HEAD, this is called to remove the mutation from * the unresolved state. If the newly applied patch is the next upcoming unresolved mutation, * no rebase is needed, but we might have the wrong idea about the ordering of mutations, so in * that case we are given the flag `needRebase` to tell us that this mutation arrived out of * order in terms of our optimistic version, so a rebase is needed. * * @param txnId - Transaction ID of the remote mutation * @returns true if rebase is needed, false otherwise */ consumeUnresolved(txnId: string): boolean; pendingSuccessfullySubmitted(pendingTxnId: string): void; pendingFailed(pendingTxnId: string): void; rebase(incomingMutations: Mutation[]): void; } /** * Implements a buffer for mutations that incrementally optimises the mutations by * eliminating set-operations that overwrite earlier set-operations, and rewrite * set-operations that change strings into other strings into diffMatchPatch operations. * * @internal */ declare class SquashingBuffer { /** * The document forming the basis of this squash */ BASIS: Doc | null; /** * The document after the out-Mutation has been applied, but before the staged * operations are committed. */ PRESTAGE: Doc | null; /** * setOperations contain the latest set operation by path. If the set-operations are * updating strings to new strings, they are rewritten as diffMatchPatch operations, * any new set operations on the same paths overwrites any older set operations. * Only set-operations assigning plain values to plain values gets optimized like this. */ setOperations: Record; /** * `documentPresent` is true whenever we know that the document must be present due * to preceeding mutations. `false` implies that it may or may not already exist. */ documentPresent: boolean; /** * The operations in the out-Mutation are not able to be optimized any further */ out: Mut[]; /** * Staged mutation operations */ staged: Mut[]; constructor(doc: Doc | null); add(mut: Mutation): void; hasChanges(): boolean; /** * Extracts the mutations in this buffer. * After this is done, the buffer lifecycle is over and the client should * create an new one with the new, updated BASIS. * * @param txnId - Transaction ID * @returns A `Mutation` instance if we had outgoing mutations pending, null otherwise */ purge(txnId?: string): Mutation | null; addOperation(op: Mut): void; /** * Attempt to perform one single set operation in an optimised manner, return value * reflects whether or not the operation could be performed. * @param path - The JSONPath to the set operation in question * @param nextValue - The value to be set * @returns True of optimized, false otherwise */ optimiseSetOperation(path: string, nextValue: unknown): boolean; stashStagedOperations(): void; /** * Rebases given the new base-document * * @param newBasis - New base document to rebase on * @returns New "edge" document with buffered changes integrated */ rebase(newBasis: Doc | null): Doc | null; } /** * @internal */ interface CommitHandlerMessage { mutation: Mutation; success: () => void; failure: () => void; cancel: (error: Error) => void; } /** * @internal */ declare class BufferedDocument { private mutations; /** * The Document we are wrapping */ document: Document; /** * The Document with local changes applied */ LOCAL: Doc | null; /** * Commits that are waiting to be delivered to the server */ private commits; /** * Local mutations that are not scheduled to be committed yet */ buffer: SquashingBuffer; /** * Assignable event handler for when the buffered document applies a mutation */ onMutation?: (message: { mutation: Mutation; document: Doc | null; remote: boolean; }) => void; /** * Assignable event handler for when a remote mutation happened */ onRemoteMutation?: Document['onRemoteMutation']; /** * Assignable event handler for when the buffered document rebased */ onRebase?: (localDoc: Doc | null, remoteMutations: Mut[], localMutations: Mut[]) => void; /** * Assignable event handler for when the document is deleted */ onDelete?: (doc: Doc | null) => void; /** * Assignable event handler for when the state of consistency changed */ onConsistencyChanged?: (isConsistent: boolean) => void; /** * Assignable event handler for when the buffered document should commit changes */ commitHandler?: (msg: CommitHandlerMessage) => void; /** * Whether or not we are currently commiting */ committerRunning: boolean; constructor(doc: Doc | null); reset(doc: Doc | null): void; add(mutation: Mutation): void; arrive(mutation: Mutation): void; commit(): Promise; performCommits(): void; _cycleCommitter(): void; handleDocRebase(edge: Doc | null, remoteMutations: Mutation[], localMutations: Mutation[]): void; handleDocumentDeleted(): void; handleDocMutation(msg: { mutation: Mutation; document: Doc | null; remote: boolean; }): void; rebase(remoteMutations: Mutation[], localMutations: Mutation[]): void; handleDocConsistencyChanged(isConsistent: boolean): void; } /** * Converts a path in array form to a JSONPath string * * @param pathArray - Array of path segments * @returns String representation of the path * @internal */ declare function arrayToJSONMatchPath(pathArray: Path): string; /** * Extracts values matching the given JsonPath * * @param path - Path to extract * @param value - Value to extract from * @returns An array of values matching the given path * @public */ declare function extract(path: string, value: unknown): unknown[]; /** * Extracts a value for the given JsonPath, and includes the specific path of where it was found * * @param path - Path to extract * @param value - Value to extract from * @returns An array of objects with `path` and `value` keys * @internal */ declare function extractWithPath(path: string, value: unknown): { path: (string | number)[]; value: unknown; }[]; export { BufferedDocument, type CommitHandlerMessage, type Doc, type Document, type Mut, Mutation, type MutationParams, type SquashingBuffer, type SubmissionResponder, arrayToJSONMatchPath, extract, extractWithPath };