import * as Y from 'yjs' ; import * as automerge from 'automerge' ; import assert from 'assert/strict' ; import seed from 'seed-random' ; import consoleLib from 'console' ; import * as crdts from './crdts' ; import * as sync9 from './sync9' ; type DocType = { arr : number [ ] ; } ; const amInit = automerge . from < DocType > ( { arr : [ ] } ) ; export enum Mode { Automerge , Yjs , Sync9 } let log = '' ; export class DocPair { id : number ; idStr : string ; algorithm : crdts . Algorithm ; sephdoc : crdts . Doc < number > ; am ? : automerge . FreezeObject < DocType > ; ydoc ? : Y . Doc ; sync9 ? : any ; constructor ( id , mode ) { this . id = id ; this . idStr = 'abc' [ id ] ; this . algorithm = mode === Mode . Automerge ? crdts . automerge : mode === Mode . Yjs ? crdts . yjs : crdts . sync9 ; this . sephdoc = crdts . newDoc ( ) ; switch ( mode ) { case Mode . Automerge : { const amId = Buffer . from ( [ 255 - id ] ) . toString ( 'hex' ) ; this . am = automerge . merge ( automerge . init ( amId ) , amInit ) ; break ; } case Mode . Yjs : { this . ydoc = new Y . Doc ( ) ; this . ydoc . clientID = id ; break ; } case Mode . Sync9 : { this . sync9 = sync9 . make ( this . idStr ) ; break ; } } } insert ( pos , content ) { this . algorithm . localInsert ( this . sephdoc , this . idStr , pos , content ) ; this . ydoc ?. getArray ( ) . insert ( pos , [ content ] ) ; if ( this . am != null ) { this . am = automerge . change ( this . am , d => { d . arr . splice ( pos , 0 , content ) ; } ) ; } if ( this . sync9 != null ) { sync9 . insert ( this . sync9 , pos , content ) ; } } del ( pos ) { throw Error ( 'NYI' ) ; crdts . localDelete ( this . sephdoc , this . idStr , pos ) ; this . ydoc ?. getArray ( ) . delete ( pos , 1 ) ; if ( this . am != null ) { this . am = automerge . change ( this . am , d => { d . arr . splice ( pos , 1 ) ; } ) ; } if ( this . sync9 ) throw Error ( 'nyi' ) ; } mergeFrom ( other ) { crdts . mergeInto ( this . algorithm , this . sephdoc , other . sephdoc ) ; if ( this . am != null ) { this . am = automerge . merge ( this . am , other . am ! ) ; } if ( this . ydoc != null ) { const sv = Y . encodeStateVector ( this . ydoc ) ; const update = Y . encodeStateAsUpdateV2 ( other . ydoc ! , sv ) ; Y . applyUpdateV2 ( this . ydoc , update ) ; } if ( this . sync9 != null ) { this . sync9 = sync9 . merge ( this . sync9 , other . sync9 ) ; } this . check ( ) ; } merge ( other ) { this . mergeFrom ( other ) ; other . mergeFrom ( this ) ; this . checkEq ( other ) ; } check ( ) { const myContent = crdts . getArray ( this . sephdoc ) ; if ( this . am != null ) { assert . deepStrictEqual ( myContent , this . am . arr ) ; } if ( this . ydoc != null ) { assert . deepStrictEqual ( myContent , this . ydoc . getArray ( ) . toArray ( ) ) ; } if ( this . sync9 != null ) { try { assert . deepStrictEqual ( myContent , sync9 . get_content ( this . sync9 ) ) ; } catch ( e ) { console . log ( 'am' , this . sephdoc . content ) ; console . log ( log ) ; this . algorithm . printDoc ( this . sephdoc ) ; throw e ; } } } checkEq ( other ) { this . check ( ) ; other . check ( ) ; assert . deepEqual ( this . content , other . content ) ; } get content ( ) : number [ ] { return this . am != null ? this . am ! . arr : crdts . getArray ( this . sephdoc ) ; } get length ( ) : number { return this . sephdoc . content . reduce ( ( sum , item ) => item . isDeleted || item . content == null ? sum : sum + 1 , 0 ) ; } } const randomizer = ( mode ) => { globalThis . console = new consoleLib . Console ( { stdout : process . stdout , stderr : process . stderr , inspectOptions : { depth : null } } ) ; for ( let iter = 0 ; ; iter ++ ) { if ( iter % 20 === 0 ) console . log ( 'iter' , iter ) ; const random = seed ( `zz ${ iter }` ) ; const randInt = ( n ) => Math . floor ( random ( ) * n ) ; const randBool = ( weight = 0.5 ) => random ( ) < weight ; const docs = new Array ( 3 ) . fill ( null ) . map ( ( _ , i ) => new DocPair ( i , mode ) ) ; log = '' ; const randDoc = ( ) => docs [ randInt ( docs . length ) ] ; let nextItem = 0 ; for ( let i = 0 ; i < 100 ; i ++ ) { for ( let j = 0 ; j < 3 ; j ++ ) { const doc = randDoc ( ) ; const len = doc . length ; const insWeight = len < 100 ? 0.65 : 0.35 ; if ( true ) { const content = ++ nextItem ; const pos = randInt ( len + 1 ) ; doc . insert ( pos , content ) ; log += `${ doc . idStr }.insert(${ pos }, ${ content })\n` ; } else { const pos = randInt ( len ) ; doc . del ( pos ) ; } doc . check ( ) ; } const a = randDoc ( ) ; const b = randDoc ( ) ; if ( a !== b ) { log += `merge(${ a . idStr }, ${ b . idStr })\n` ; a . merge ( b ) ; } } } } ; if ( require . main === module ) { randomizer ( Mode . Sync9 ) ; }