@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
TypeScript
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 {}