import { DetachedSequenceId , NodeId , TraitLabel , UuidString } from '../Identifiers' ; import { assertNotUndefined , assert } from '../Common' ; import { Side } from '../Snapshot' ; import { EditBase , BuildNode , NodeData , Payload , TraitLocation , TreeNodeSequence } from '../generic' ; /**
 * Types for Edits in Fluid Ops and Fluid summaries.
 *
 * Types describing locations in the tree are stable in the presence of other concurrent edits.
 *
 * All types are compatible with Fluid Serializable.
 *
 * These types can only be modified in ways that are both backwards and forwards compatible since they
 * are used in edits, and thus are persisted (using Fluid serialization).
 *
 * This means these types cannot be changed in any way that impacts their Fluid serialization
 * except through a very careful process:
 *
 * 1. The planned change must support all old data, and maintain the exact semantics of it.
 * This means that the change is pretty much limited to adding optional fields,
 * or making required fields optional.
 * 2. Support for the new format must be deployed to all users (This means all applications using SharedTree must do this),
 * and this deployment must be confirmed to be stable and will not be rolled back.
 * 3. Usage of the new format may start.
 *
 * Support for the old format can NEVER be removed: it must be maintained indefinably or old documents will break.
 * Because this process puts requirements on applications using shared tree,
 * step 3 should only ever be done in a Major version update,
 * and must be explicitly called out in the release notes
 * stating which versions of SharedTree are supported for documents modified by the new version.
 */ public export type DefaultEditBase = EditBase < Change > ; public export enum ChangeType { Insert , Detach , Build , SetValue , Constraint } example public export type Change = Insert | Detach | Build | SetValue | Constraint ; public export interface Build { readonly destination : DetachedSequenceId ; readonly source : TreeNodeSequence < BuildNode > ; readonly type : typeof ChangeType . Build ; } public export interface Insert { readonly destination : StablePlace ; readonly source : DetachedSequenceId ; readonly type : typeof ChangeType . Insert ; } public export interface Detach { readonly destination ? : DetachedSequenceId ; readonly source : StableRange ; readonly type : typeof ChangeType . Detach ; } public export interface SetValue { readonly nodeToModify : NodeId ; /**
     * Sets or clears the payload.
     * To improve ease of forwards compatibility, an explicit `null` value is used to represent the clearing of a payload.
     * SetValue may use `undefined` in future API versions to mean "don't change the payload" (which is useful if e.g. other
     * fields are added to SetValue that can be changed without altering the payload)
     */ readonly payload : Payload | null ; readonly type : typeof ChangeType . SetValue ; } public export interface Constraint { /**
     * Selects a sequence of nodes which will be checked against the constraints specified by the optional fields.
     * If `toConstrain` is invalid, it will be treated like a constraint being unmet.
     * Depending on `effect` this may or may not make the Edit invalid.
     *
     * When a constraint is not met, the effects is specified by `effect`.
     */ readonly toConstrain : StableRange ; /**
     * Require that the identities of all the nodes in toConstrain hash to this value.
     * Hash is order dependent.
     * TODO: implement and specify exact hash function.
     *
     * This is an efficient (O(1) space) way to constrain a sequence of nodes to have specific identities.
     */ readonly identityHash ? : UuidString ; /**
     * Require that the number of nodes in toConstrain is this value.
     */ readonly length ? : number ; /**
     * Require that the contents of all of the nodes in toConstrain hash to this value.
     * Hash is an order dependant deep hash, which includes all subtree content recursively.
     * TODO: implement and specify exact hash function.
     *
     * This is an efficient (O(1) space) way to constrain a sequence of nodes have exact values (transitively).
     */ readonly contentHash ? : UuidString ; /**
     * Require that parent under which toConstrain is located has this identifier.
     */ readonly parentNode ? : NodeId ; /**
     * Require that the trait under which toConstrain is located has this label.
     */ readonly label ? : TraitLabel ; /**
     * What to do if a constraint is not met.
     */ readonly effect : ConstraintEffect ; /**
     * Marker for which kind of Change this is.
     */ readonly type : typeof ChangeType . Constraint ; } public export enum ConstraintEffect { /**
     * Discard Edit.
     */ InvalidAndDiscard , /**
     * Discard Edit, but record metadata that application may want to try and recover this change by recreating it.
     * Should this be the default policy for when another (non Constraint) change is invalid?
     */ InvalidRetry , /**
     * Apply the change, but flag it for possible reconsideration by the app
     * (applying it is better than not, but perhaps the high level logic could produce something better).
     */ ValidRetry } public export const Change = { build : ( source , destination ) : Build => ( { destination , source , type : ChangeType . Build } ) , insert : ( source , destination ) : Insert => ( { destination , source , type : ChangeType . Insert } ) , detach : ( source , destination ? ) : Detach => ( { destination , source , type : ChangeType . Detach } ) , setPayload : ( nodeToModify , payload ) : SetValue => ( { nodeToModify , payload , type : ChangeType . SetValue } ) , clearPayload : ( nodeToModify ) : SetValue => ( { nodeToModify , payload : null , type : ChangeType . SetValue } ) , constraint : ( toConstrain , effect , identityHash ? , length ? , contentHash ? , parentNode ? , label ? ) : Constraint => ( { toConstrain , effect , identityHash , length , contentHash , parentNode , label , type : ChangeType . Constraint } ) } ; public export const Delete = { returns create : ( stableRange ) : Change => Change . detach ( stableRange ) } ; public export const Insert = { returns create : ( nodes , destination ) : Change [ ] => { const build = Change . build ( nodes , 0 as DetachedSequenceId ) ; return [ build , Change . insert ( build . destination , destination ) ] ; } } ; public export const Move = { returns create : ( source , destination ) : Change [ ] => { const detach = Change . detach ( source , 0 as DetachedSequenceId ) ; return [ detach , Change . insert ( assertNotUndefined ( detach . destination ) , destination ) ] ; } } ; example public export interface StablePlace { /**
     * Where this StablePlace is relative to the sibling (if specified), or an end of the trait (if no sibling specified).
     * If 'After' and there is no sibling, this StablePlace is after the front of the trait.
     * If 'Before' and there is no sibling, this StablePlace is before the back of the trait.
     */ readonly side : Side ; /**
     * The sibling to which this 'StablePlace' is anchored (by 'side').
     * If specified, referenceTrait must be unspecified.
     */ readonly referenceSibling ? : NodeId ; /**
     * The trait to which this 'StablePlace' is anchored (by 'side').
     * If specified, referenceSibling must be unspecified.
     */ readonly referenceTrait ? : TraitLocation ; } example public export interface StableRange { readonly start : StablePlace ; readonly end : StablePlace ; } /**
 * The remainder of this file consists of ergonomic factory methods for persisted types, or common combinations thereof (e.g. "Move" as a
 * combination of a "Detach" change and an "Insert" change).
 *
 * None of these helpers are persisted in documents, and therefore changes to their semantics need only follow standard semantic versioning
 * practices.
 */ public export const StablePlace = { returns before : ( node ) : StablePlace => ( { side : Side . Before , referenceSibling : getNodeId ( node ) } ) , returns after : ( node ) : StablePlace => ( { side : Side . After , referenceSibling : getNodeId ( node ) } ) , returns atStartOf : ( trait ) : StablePlace => ( { side : Side . After , referenceTrait : trait } ) , returns atEndOf : ( trait ) : StablePlace => ( { side : Side . Before , referenceTrait : trait } ) } ; public export const StableRange = { example from : ( start ) : { to : ( end : StablePlace ) => StableRange ; } => ( { to : ( end ) : StableRange => { if ( start . referenceTrait && end . referenceTrait ) { const message = 'StableRange must be constructed with endpoints from the same trait' ; assert ( start . referenceTrait . parent === end . referenceTrait . parent , message ) ; assert ( start . referenceTrait . label === end . referenceTrait . label , message ) ; } return { start , end } ; } } ) , returns only : ( node ) : StableRange => ( { start : StablePlace . before ( node ) , end : StablePlace . after ( node ) } ) , returns all : ( trait ) : StableRange => ( { start : StablePlace . atStartOf ( trait ) , end : StablePlace . atEndOf ( trait ) } ) } ; returns internal export function isNodeData ( node ) { return ( node as NodeData ) . definition !== undefined && ( node as NodeData ) . identifier !== undefined ; } returns internal export function getNodeId ( node ) { if ( isNodeData ( node ) ) { return node . identifier ; } else { return node ; } }