import { Ident , IdentSet , IdentGenerator , LSEQIdentGenerator , Segment } from './idents' ; import { AtomList , ArrayAtomList } from './storage' ; import { Op , OpKind , InsertOp , RemoveOp } from './Op' ; /**
 * A CmRDT sequence that supports concurrent simultaneous editing
 * while preserving the intention of each edit.
 */ export class KSeq < T > { /**
     * The unique name of this replica.
     */ name : string ; /**
     * The current logical time.
     */ private time : number ; /**
     * The ordered list of atoms in the sequence.
     */ private atoms : AtomList < T > ; /**
     * The set of idents of atoms that have been removed.
     */ private removed : IdentSet ; /**
     * The generator used to create unique idents for new atoms.
     */ private identGenerator : IdentGenerator ; param name param atoms param identGenerator returns constructor ( name , atoms ? , identGenerator ? ) { this . name = name ; this . time = 0 ; this . atoms = atoms || new ArrayAtomList < T > ( ) ; this . removed = new IdentSet ( ) ; this . identGenerator = identGenerator || new LSEQIdentGenerator ( ) ; } returns size ( ) { return this . atoms . size ( ) ; } returns depth ( ) { let max = 0 ; this . forEach ( ( atom ) => { let depth = atom . id . depth ( ) ; if ( max < depth ) max = depth ; } ) ; return max ; } param value param pos returns insert ( value , pos ) { if ( pos < 0 ) throw new RangeError ( `The position ${ pos } must be greater than or equal to zero.` ) ; let before = this . atoms . get ( pos - 1 ) ; let after = this . atoms . get ( pos ) ; let id = this . identGenerator . getIdent ( this . name , ++ this . time , ( before && before . id ) , ( after && after . id ) ) ; let op = new InsertOp ( this . name , this . getWallTime ( ) , id , value ) ; this . apply ( op ) ; return op ; } param value returns append ( value ) { return this . insert ( value , this . size ( ) ) ; } param pos returns remove ( pos ) { if ( pos < 0 ) throw new RangeError ( `The position ${ pos } must be greater than or equal to zero.` ) ; let atom = this . atoms . get ( pos ) ; if ( atom ) { let op = new RemoveOp ( this . name , this . getWallTime ( ) , atom . id ) ; this . apply ( op ) ; return op ; } return null ; } param pos returns get ( pos ) { const atom = this . atoms . get ( pos ) ; return atom ? atom . value : undefined ; } param func forEach ( func ) { this . atoms . forEach ( ( atom ) => func ( atom . value ) ) ; } param func returns map < R > ( func ) { return this . atoms . map ( ( atom ) => func ( atom . value ) ) ; } returns toArray ( ) { return this . atoms . map ( ( atom ) => atom . value ) ; } returns toJSON ( ) { return { n : this . name , t : this . time , s : this . atoms . map ( ( atom ) => [ atom . id . toString ( ) , atom . value ] ) , r : this . removed . toJSON ( ) } ; } param op apply ( op ) { switch ( op . kind ) { case OpKind . Insert : let insertOp = < InsertOp > op ; if ( ! this . removed . has ( insertOp . id ) ) { this . atoms . add ( insertOp . id , insertOp . value ) ; } break ; case OpKind . Remove : let removeOp = < RemoveOp > op ; if ( ! this . removed . has ( removeOp . id ) ) { this . atoms . remove ( removeOp . id ) ; this . removed . add ( removeOp . id ) ; } break ; default : throw new Error ( `Unknown op kind ${ op . kind }` ) ; } } returns private getWallTime ( ) { return Math . floor ( new Date ( ) . valueOf ( ) / 1000 ) ; } }