import { $reactive , $reactiveproxy } from "@reactivedata/reactive" ; import * as Y from "yjs" ; import { areSame , getYjsValue , INTERNAL_SYMBOL } from "." ; import { Box } from "./boxed" ; import { parseYjsReturnValue , crdtValue , ObjectSchemaType } from "./internal" ; import { CRDTObject } from "./object" ; export type CRDTArray < T > = { [ INTERNAL_SYMBOL ] ? : Y . Array < T > ; [ n ] : T extends Box < infer A > ? A : T extends Array < infer A > ? CRDTArray < A > : T extends ObjectSchemaType ? CRDTObject < T > : T ; } & T [ ] ; function arrayImplementation < T > ( arr ) { const slice = function slice ( ) { let ic = this [ $reactiveproxy ] ?. implicitObserver ; ( arr as any ) . _implicitObserver = ic ; const items = arr . slice . bind ( arr ) . apply ( arr , arguments ) ; return items . map ( ( item ) => { const ret = parseYjsReturnValue ( item , ic ) ; return ret ; } ) ; } as T [ ] [ "slice" ] ; const wrapItems = function wrapItems ( items ) { return items . map ( ( item ) => { const wrapped = crdtValue ( item as any ) ; let valueToSet = getYjsValue ( wrapped ) || wrapped ; if ( valueToSet instanceof Box ) { valueToSet = valueToSet . value ; } if ( valueToSet instanceof Y . AbstractType && valueToSet . parent ) { throw new Error ( "Not supported: reassigning object that already occurs in the tree." ) ; } return valueToSet ; } ) ; } ; const findIndex = function findIndex ( ) { return [ ] . findIndex . apply ( slice . apply ( this ) , arguments ) ; } as T [ ] [ "find" ] ; const ret = { slice , unshift : arr . unshift . bind ( arr ) as Y . Array < T > [ "unshift" ] , push : ( ... items ) => { arr . push ( wrapItems ( items ) ) ; return arr . length ; } , insert : arr . insert . bind ( arr ) as Y . Array < T > [ "insert" ] , toJSON : arr . toJSON . bind ( arr ) as Y . Array < T > [ "toJSON" ] , forEach : function ( ) { return [ ] . forEach . apply ( slice . apply ( this ) , arguments ) ; } as T [ ] [ "forEach" ] , filter : function ( ) { return [ ] . filter . apply ( slice . apply ( this ) , arguments ) ; } as T [ ] [ "filter" ] , find : function ( ) { return [ ] . find . apply ( slice . apply ( this ) , arguments ) ; } as T [ ] [ "find" ] , findIndex , map : function ( ) { return [ ] . map . apply ( slice . apply ( this ) , arguments ) ; } as T [ ] [ "map" ] , indexOf : function ( ) { const arg = arguments [ 0 ] ; return findIndex . call ( this , ( el ) => areSame ( el , arg ) ) ; } as T [ ] [ "indexOf" ] , splice : function ( ) { let start = arguments [ 0 ] < 0 ? arr . length - Math . abs ( arguments [ 0 ] ) : arguments [ 0 ] ; let deleteCount = arguments [ 1 ] ; let items = Array . from ( Array . from ( arguments ) . slice ( 2 ) ) ; let deleted = slice . apply ( this , [ start , Number . isInteger ( deleteCount ) ? start + deleteCount : undefined ] ) ; if ( arr . doc ) { arr . doc . transact ( ( ) => { arr . delete ( start , deleteCount ) ; arr . insert ( start , wrapItems ( items ) ) ; } ) ; } else { arr . delete ( start , deleteCount ) ; arr . insert ( start , wrapItems ( items ) ) ; } return deleted ; } as T [ ] [ "splice" ] , } ; Object . defineProperty ( ret , "length" , { enumerable : false , configurable : false , writable : true , value : arr . length , } ) ; return ret ; } function propertyToNumber ( p ) { if ( typeof p === "string" && p . trim ( ) . length ) { const asNum = Number ( p ) ; if ( Number . isInteger ( asNum ) ) { return asNum ; } } return p ; } export function crdtArray < T > ( initializer , arr = new Y . Array < T > ( ) ) { if ( arr [ $reactive ] ) { throw new Error ( "unexpected" ) ; } const implementation = arrayImplementation ( arr ) ; const proxy = new Proxy ( implementation as any as CRDTArray < T > , { set : ( target , pArg , value ) => { const p = propertyToNumber ( pArg ) ; if ( typeof p !== "number" ) { throw new Error ( ) ; } throw new Error ( "array assignment is not implemented / supported" ) ; return true ; } , get : ( target , pArg , receiver ) => { const p = propertyToNumber ( pArg ) ; if ( p === INTERNAL_SYMBOL ) { return arr ; } if ( typeof p === "number" ) { let ic ; if ( receiver && receiver [ $reactiveproxy ] ) { ic = receiver [ $reactiveproxy ] ?. implicitObserver ; ( arr as any ) . _implicitObserver = ic ; } else { } let ret = arr . get ( p ) as any ; ret = parseYjsReturnValue ( ret , ic ) ; return ret ; } if ( p === Symbol . toStringTag ) { return "Array" ; } if ( p === Symbol . iterator ) { const values = arr . slice ( ) ; return Reflect . get ( values , p ) ; } if ( p === "length" ) { return arr . length ; } const ret = Reflect . get ( target , p , receiver ) ; return ret ; } , deleteProperty : ( target , pArg ) => { const p = propertyToNumber ( pArg ) ; if ( typeof p !== "number" ) { throw new Error ( ) ; } if ( p < arr . length && p >= 0 ) { arr . delete ( p ) ; return true ; } else { return false ; } } , has : ( target , pArg ) => { const p = propertyToNumber ( pArg ) ; if ( typeof p !== "number" ) { return Reflect . has ( target , p ) ; } if ( p < arr . length && p >= 0 ) { return true ; } else { return false ; } } , getOwnPropertyDescriptor ( target , pArg ) { const p = propertyToNumber ( pArg ) ; if ( p === "length" ) { return { enumerable : false , configurable : false , writable : true , } ; } if ( typeof p === "number" && p >= 0 && p < arr . length ) { return { enumerable : true , configurable : true , writable : true , } ; } return undefined ; } , ownKeys : ( target ) => { const keys = [ ] ; for ( let i = 0 ; i < arr . length ; i ++ ) { keys . push ( i + "" ) ; } keys . push ( "length" ) ; return keys ; } , } ) ; implementation . push . apply ( proxy , initializer ) ; return proxy ; }