UNPKG

@sanity/mutator

Version:

A set of models to make it easier to utilize the powerful real time collaborative features of Sanity

378 lines (363 loc) • 12.8 kB
import {CreateIfNotExistsMutation} from '@sanity/types' import {CreateMutation} from '@sanity/types' import {CreateOrReplaceMutation} from '@sanity/types' import {DeleteMutation} from '@sanity/types' import {PatchMutation} from '@sanity/types' import {Path} from '@sanity/types' /** * Converts a path in array form to a JSONPath string * * @param pathArray - Array of path segments * @returns String representation of the path * @internal */ export declare function arrayToJSONMatchPath(pathArray: Path): string /** * @internal */ export declare class BufferedDocument { private mutations /** * The Document we are wrapping */ document: Document_2 /** * 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_2['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<void> 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 } /** * @internal */ export declare interface CommitHandlerMessage { mutation: Mutation success: () => void failure: () => void cancel: (error: Error) => void } /** * Sanity document with a guaranteed `_id` and `_type` * * @internal */ export declare interface Doc { _id: string _type: string _rev?: string _updatedAt?: string _createdAt?: string [attribute: string]: unknown } /** * 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_2 { /** * 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 } export {Document_2 as Document} /** * 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 */ export 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 */ export declare function extractWithPath( path: string, value: unknown, ): { path: (string | number)[] value: 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 */ export declare interface Mut { create?: CreateMutation['create'] createIfNotExists?: CreateIfNotExistsMutation['createIfNotExists'] createOrReplace?: CreateOrReplaceMutation['createOrReplace'] delete?: DeleteMutation['delete'] patch?: PatchMutation['patch'] } /** * 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 */ export 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 } /** * Parameters attached to the mutation * * @internal */ export declare interface MutationParams { transactionId?: string transition?: string identity?: string previousRev?: string resultRev?: string mutations: Mut[] timestamp?: string effects?: { apply: unknown revert: unknown } } /** * 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 */ export 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<string, Mut | undefined> /** * `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 */ export declare interface SubmissionResponder { success: () => void failure: () => void } export {}