import { AbstractCrdt , Doc , ApplyResult } from "./AbstractCrdt" ; import { deserialize , isCrdt , selfOrRegister , selfOrRegisterValue , } from "./utils" ; import { Op , CreateMapOp , OpType , SerializedCrdtWithId , CrdtType , } from "./live" ; /**
 * The LiveMap class is similar to a JavaScript Map that is synchronized on all clients.
 * Keys should be a string, and values should be serializable to JSON.
 * If multiple clients update the same property simultaneously, the last modification received by the Liveblocks servers is the winner.
 */ export class LiveMap < TKey extends string , TValue > extends AbstractCrdt { #map : Map < TKey , AbstractCrdt > ; constructor ( entries ? ) { super ( ) ; if ( entries ) { const mappedEntries = [ ] ; for ( const entry of entries ) { const value = selfOrRegister ( entry [ 1 ] ) ; value . _setParentLink ( this , entry [ 0 ] ) ; mappedEntries . push ( [ entry [ 0 ] , value ] ) ; } this . #map = new Map ( mappedEntries ) ; } else { this . #map = new Map ( ) ; } } /**
     * INTERNAL
     */ _serialize ( parentId ? , parentKey ? ) { if ( this . _id == null ) { throw new Error ( "Cannot serialize item is not attached" ) ; } if ( parentId == null || parentKey == null ) { throw new Error ( "Cannot serialize map if parentId or parentKey is undefined" ) ; } const ops = [ ] ; const op = { id : this . _id , type : OpType . CreateMap , parentId , parentKey , } ; ops . push ( op ) ; for ( const [ key , value ] of this . #map ) { ops . push ( ... value . _serialize ( this . _id , key ) ) ; } return ops ; } /**
     * INTERNAL
     */ static _deserialize ( [ id , item ] , parentToChildren , doc ) { if ( item . type !== CrdtType . Map ) { throw new Error ( `Tried to deserialize a map but item type is "${ item . type }"` ) ; } const map = new LiveMap ( ) ; map . _attach ( id , doc ) ; const children = parentToChildren . get ( id ) ; if ( children == null ) { return map ; } for ( const entry of children ) { const crdt = entry [ 1 ] ; if ( crdt . parentKey == null ) { throw new Error ( "Tried to deserialize a crdt but it does not have a parentKey and is not the root" ) ; } const child = deserialize ( entry , parentToChildren , doc ) ; child . _setParentLink ( map , crdt . parentKey ) ; map . #map . set ( crdt . parentKey , child ) ; } return map ; } /**
     * INTERNAL
     */ _attach ( id , doc ) { super . _attach ( id , doc ) ; for ( const [ key , value ] of this . #map ) { if ( isCrdt ( value ) ) { value . _attach ( doc . generateId ( ) , doc ) ; } } } /**
     * INTERNAL
     */ _attachChild ( id , key , child ) { if ( this . _doc == null ) { throw new Error ( "Can't attach child if doc is not present" ) ; } const previousValue = this . #map . get ( key ) ; let reverse ; if ( previousValue ) { reverse = previousValue . _serialize ( this . _id ! , key ) ; previousValue . _detach ( ) ; } else { reverse = [ { type : OpType . DeleteCrdt , id } ] ; } child . _setParentLink ( this , key ) ; child . _attach ( id , this . _doc ) ; this . #map . set ( key , child ) ; return { modified : this , reverse } ; } /**
     * INTERNAL
     */ _detach ( ) { super . _detach ( ) ; for ( const item of this . #map . values ( ) ) { item . _detach ( ) ; } } /**
     * INTERNAL
     */ _detachChild ( child ) { for ( const [ key , value ] of this . #map ) { if ( value === ( child as any ) ) { this . #map . delete ( key ) ; } } child . _detach ( ) ; } param key returns get ( key ) { const value = this . #map . get ( key ) ; if ( value == undefined ) { return undefined ; } return selfOrRegisterValue ( value ) ; } param key param value set ( key , value ) { const oldValue = this . #map . get ( key ) ; if ( oldValue ) { oldValue . _detach ( ) ; } const item = selfOrRegister ( value ) ; item . _setParentLink ( this , key ) ; this . #map . set ( key , item ) ; if ( this . _doc && this . _id ) { const id = this . _doc . generateId ( ) ; item . _attach ( id , this . _doc ) ; this . _doc . dispatch ( item . _serialize ( this . _id , key ) , oldValue ? oldValue . _serialize ( this . _id , key ) : [ { type : OpType . DeleteCrdt , id } ] , [ this ] ) ; } } /**
     * Returns the number of elements in the LiveMap.
     */ get size ( ) { return this . #map . size ; } param key has ( key ) { return this . #map . has ( key ) ; } param key returns delete ( key ) { const item = this . #map . get ( key ) ; if ( item == null ) { return false ; } item . _detach ( ) ; if ( this . _doc && item . _id ) { this . _doc . dispatch ( [ { type : OpType . DeleteCrdt , id : item . _id } ] , item . _serialize ( this . _id ! , key ) , [ this ] ) ; } this . #map . delete ( key ) ; return true ; } /**
     * Returns a new Iterator object that contains the [key, value] pairs for each element.
     */ entries ( ) { const innerIterator = this . #map . entries ( ) ; return { [ Symbol . iterator ] : function ( ) { return this ; } , next ( ) { const iteratorValue = innerIterator . next ( ) ; if ( iteratorValue . done ) { return { done : true , value : undefined , } ; } const entry = iteratorValue . value ; return { value : [ entry [ 0 ] , selfOrRegisterValue ( iteratorValue . value [ 1 ] ) ] , } ; } , } ; } /**
     * Same function object as the initial value of the entries method.
     */ [ Symbol . iterator ] ( ) { return this . entries ( ) ; } /**
     * Returns a new Iterator object that contains the keys for each element.
     */ keys ( ) { return this . #map . keys ( ) ; } /**
     * Returns a new Iterator object that contains the values for each element.
     */ values ( ) { const innerIterator = this . #map . values ( ) ; return { [ Symbol . iterator ] : function ( ) { return this ; } , next ( ) { const iteratorValue = innerIterator . next ( ) ; if ( iteratorValue . done ) { return { done : true , value : undefined , } ; } return { value : selfOrRegisterValue ( iteratorValue . value ) , } ; } , } ; } param callback forEach ( callback ) { for ( const entry of this ) { callback ( entry [ 1 ] , entry [ 0 ] as TKey , this ) ; } } }