@@ -49,13 +49,14 @@ export interface LiveMapEntry {
4949}
5050
5151export interface LiveMapData extends LiveObjectData {
52- data : Map < string , LiveMapEntry > ;
52+ data : Map < string , LiveMapEntry > ; // RTLM3
5353}
5454
5555export interface LiveMapUpdate < T extends API . LiveMapType > extends LiveObjectUpdate {
5656 update : { [ keyName in keyof T & string ] ?: 'updated' | 'removed' } ;
5757}
5858
59+ /** @spec RTLM1, RTLM2 */
5960export class LiveMap < T extends API . LiveMapType > extends LiveObject < LiveMapData , LiveMapUpdate < T > > {
6061 constructor (
6162 objects : Objects ,
@@ -69,6 +70,7 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
6970 * Returns a {@link LiveMap} instance with an empty map data.
7071 *
7172 * @internal
73+ * @spec RTLM4
7274 */
7375 static zeroValue < T extends API . LiveMapType > ( objects : Objects , objectId : string ) : LiveMap < T > {
7476 return new LiveMap < T > ( objects , ObjectsMapSemantics . LWW , objectId ) ;
@@ -294,21 +296,25 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
294296 * - If the value associated with the provided key is an objectId string of another LiveObject, a reference to that LiveObject
295297 * is returned, provided it exists in the local pool and is not tombstoned. Otherwise, `undefined` is returned.
296298 * - If the value is not an objectId, then that value is returned.
299+ *
300+ * @spec RTLM5, RTLM5a
297301 */
298302 // force the key to be of type string as we only allow strings as key in a map
299303 get < TKey extends keyof T & string > ( key : TKey ) : T [ TKey ] | undefined {
300- this . _objects . throwIfInvalidAccessApiConfiguration ( ) ;
304+ this . _objects . throwIfInvalidAccessApiConfiguration ( ) ; // RTLM5b, RTLM5c
301305
302306 if ( this . isTombstoned ( ) ) {
303307 return undefined as T [ TKey ] ;
304308 }
305309
306310 const element = this . _dataRef . data . get ( key ) ;
307311
312+ // RTLM5d1
308313 if ( element === undefined ) {
309314 return undefined as T [ TKey ] ;
310315 }
311316
317+ // RTLM5d2a
312318 if ( element . tombstone === true ) {
313319 return undefined as T [ TKey ] ;
314320 }
@@ -465,6 +471,7 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
465471
466472 /**
467473 * @internal
474+ * @spec RTLM6
468475 */
469476 overrideWithObjectState ( objectState : ObjectState ) : LiveMapUpdate < T > | LiveObjectUpdateNoop {
470477 if ( objectState . objectId !== this . getObjectId ( ) ) {
@@ -512,7 +519,7 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
512519
513520 // object's site serials are still updated even if it is tombstoned, so always use the site serials received from the op.
514521 // should default to empty map if site serials do not exist on the object state, so that any future operation may be applied to this object.
515- this . _siteTimeserials = objectState . siteTimeserials ?? { } ;
522+ this . _siteTimeserials = objectState . siteTimeserials ?? { } ; // RTLM6a
516523
517524 if ( this . isTombstoned ( ) ) {
518525 // this object is tombstoned. this is a terminal state which can't be overridden. skip the rest of object state message processing
@@ -525,8 +532,9 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
525532 this . tombstone ( ) ;
526533 } else {
527534 // override data for this object with data from the object state
528- this . _createOperationIsMerged = false ;
529- this . _dataRef = this . _liveMapDataFromMapEntries ( objectState . map ?. entries ?? { } ) ;
535+ this . _createOperationIsMerged = false ; // RTLM6b
536+ this . _dataRef = this . _liveMapDataFromMapEntries ( objectState . map ?. entries ?? { } ) ; // RTLM6c
537+ // RTLM6d
530538 if ( ! this . _client . Utils . isNil ( objectState . createOp ) ) {
531539 this . _mergeInitialDataFromCreateOperation ( objectState . createOp ) ;
532540 }
@@ -553,6 +561,7 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
553561 keysToDelete . forEach ( ( x ) => this . _dataRef . data . delete ( x ) ) ;
554562 }
555563
564+ /** @spec RTLM4 */
556565 protected _getZeroValueData ( ) : LiveMapData {
557566 return { data : new Map < string , LiveMapEntry > ( ) } ;
558567 }
@@ -621,17 +630,18 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
621630 }
622631
623632 const aggregatedUpdate : LiveMapUpdate < T > = { update : { } } ;
633+ // RTLM6d1
624634 // in order to apply MAP_CREATE op for an existing map, we should merge their underlying entries keys.
625635 // we can do this by iterating over entries from MAP_CREATE op and apply changes on per-key basis as if we had MAP_SET, MAP_REMOVE operations.
626636 Object . entries ( objectOperation . map . entries ?? { } ) . forEach ( ( [ key , entry ] ) => {
627637 // for a MAP_CREATE operation we must use the serial value available on an entry, instead of a serial on a message
628638 const opSerial = entry . timeserial ;
629639 let update : LiveMapUpdate < T > | LiveObjectUpdateNoop ;
630640 if ( entry . tombstone === true ) {
631- // entry in MAP_CREATE op is removed, try to apply MAP_REMOVE op
641+ // RTLM6d1b - entry in MAP_CREATE op is removed, try to apply MAP_REMOVE op
632642 update = this . _applyMapRemove ( { key } , opSerial ) ;
633643 } else {
634- // entry in MAP_CREATE op is not removed, try to set it via MAP_SET op
644+ // RTLM6d1a - entry in MAP_CREATE op is not removed, try to set it via MAP_SET op
635645 update = this . _applyMapSet ( { key, data : entry . data } , opSerial ) ;
636646 }
637647
@@ -644,7 +654,7 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
644654 Object . assign ( aggregatedUpdate . update , update . update ) ;
645655 } ) ;
646656
647- this . _createOperationIsMerged = true ;
657+ this . _createOperationIsMerged = true ; // RTLM6d2
648658
649659 return aggregatedUpdate ;
650660 }
@@ -682,12 +692,14 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
682692 return this . _mergeInitialDataFromCreateOperation ( op ) ;
683693 }
684694
695+ /** @spec RTLM7 */
685696 private _applyMapSet ( op : ObjectsMapOp , opSerial : string | undefined ) : LiveMapUpdate < T > | LiveObjectUpdateNoop {
686697 const { ErrorInfo, Utils } = this . _client ;
687698
688699 const existingEntry = this . _dataRef . data . get ( op . key ) ;
700+ // RTLM7a
689701 if ( existingEntry && ! this . _canApplyMapOperation ( existingEntry . timeserial , opSerial ) ) {
690- // the operation's serial <= the entry's serial, ignore the operation.
702+ // RTLM7a1 - the operation's serial <= the entry's serial, ignore the operation.
691703 this . _client . Logger . logAction (
692704 this . _client . logger ,
693705 this . _client . Logger . LOG_MICRO ,
@@ -713,13 +725,14 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
713725 }
714726
715727 let liveData : ObjectData ;
728+ // RTLM7c
716729 if ( ! Utils . isNil ( op . data . objectId ) ) {
717730 liveData = { objectId : op . data . objectId } as ObjectIdObjectData ;
718731 // this MAP_SET op is setting a key to point to another object via its object id,
719732 // but it is possible that we don't have the corresponding object in the pool yet (for example, we haven't seen the *_CREATE op for it).
720733 // we don't want to return undefined from this map's .get() method even if we don't have the object,
721734 // so instead we create a zero-value object for that object id if it not exists.
722- this . _objects . getPool ( ) . createZeroValueObjectIfNotExists ( op . data . objectId ) ;
735+ this . _objects . getPool ( ) . createZeroValueObjectIfNotExists ( op . data . objectId ) ; // RTLM7c1
723736 } else {
724737 liveData = {
725738 encoding : op . data . encoding ,
@@ -731,13 +744,15 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
731744 }
732745
733746 if ( existingEntry ) {
734- existingEntry . tombstone = false ;
747+ // RTLM7a2
748+ existingEntry . tombstone = false ; // RTLM7a2c
735749 existingEntry . tombstonedAt = undefined ;
736- existingEntry . timeserial = opSerial ;
737- existingEntry . data = liveData ;
750+ existingEntry . timeserial = opSerial ; // RTLM7a2b
751+ existingEntry . data = liveData ; // RTLM7a2a
738752 } else {
753+ // RTLM7b, RTLM7b1
739754 const newEntry : LiveMapEntry = {
740- tombstone : false ,
755+ tombstone : false , // RTLM7b2
741756 tombstonedAt : undefined ,
742757 timeserial : opSerial ,
743758 data : liveData ,
@@ -752,10 +767,12 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
752767 return update ;
753768 }
754769
770+ /** @spec RTLM8 */
755771 private _applyMapRemove ( op : ObjectsMapOp , opSerial : string | undefined ) : LiveMapUpdate < T > | LiveObjectUpdateNoop {
756772 const existingEntry = this . _dataRef . data . get ( op . key ) ;
773+ // RTLM8a
757774 if ( existingEntry && ! this . _canApplyMapOperation ( existingEntry . timeserial , opSerial ) ) {
758- // the operation's serial <= the entry's serial, ignore the operation.
775+ // RTLM8a1 - the operation's serial <= the entry's serial, ignore the operation.
759776 this . _client . Logger . logAction (
760777 this . _client . logger ,
761778 this . _client . Logger . LOG_MICRO ,
@@ -766,13 +783,15 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
766783 }
767784
768785 if ( existingEntry ) {
769- existingEntry . tombstone = true ;
786+ // RTLM8a2
787+ existingEntry . tombstone = true ; // RTLM8a2c
770788 existingEntry . tombstonedAt = Date . now ( ) ;
771- existingEntry . timeserial = opSerial ;
772- existingEntry . data = undefined ;
789+ existingEntry . timeserial = opSerial ; // RTLM8a2b
790+ existingEntry . data = undefined ; // RTLM8a2a
773791 } else {
792+ // RTLM8b, RTLM8b1
774793 const newEntry : LiveMapEntry = {
775- tombstone : true ,
794+ tombstone : true , // RTLM8b2
776795 tombstonedAt : Date . now ( ) ,
777796 timeserial : opSerial ,
778797 data : undefined ,
@@ -790,28 +809,29 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
790809 /**
791810 * Returns true if the serials of the given operation and entry indicate that
792811 * the operation should be applied to the entry, following the CRDT semantics of this LiveMap.
812+ * @spec RTLM9
793813 */
794814 private _canApplyMapOperation ( mapEntrySerial : string | undefined , opSerial : string | undefined ) : boolean {
795815 // for LWW CRDT semantics (the only supported LiveMap semantic) an operation
796816 // should only be applied if its serial is strictly greater ("after") than an entry's serial.
797817
798818 if ( ! mapEntrySerial && ! opSerial ) {
799- // if both serials are nullish or empty strings, we treat them as the "earliest possible" serials,
819+ // RTLM9b - if both serials are nullish or empty strings, we treat them as the "earliest possible" serials,
800820 // in which case they are "equal", so the operation should not be applied
801821 return false ;
802822 }
803823
804824 if ( ! mapEntrySerial ) {
805- // any operation serial is greater than non-existing entry serial
825+ // RTLM9d - any operation serial is greater than non-existing entry serial
806826 return true ;
807827 }
808828
809829 if ( ! opSerial ) {
810- // non-existing operation serial is lower than any entry serial
830+ // RTLM9c - non-existing operation serial is lower than any entry serial
811831 return false ;
812832 }
813833
814- // if both serials exist, compare them lexicographically
834+ // RTLM9e - if both serials exist, compare them lexicographically
815835 return opSerial > mapEntrySerial ;
816836 }
817837
@@ -859,31 +879,31 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
859879 // if object data stores one of the primitive values, just return it as is.
860880 const asValueObject = data as ValueObjectData ;
861881 if ( asValueObject . boolean !== undefined ) {
862- return asValueObject . boolean ;
882+ return asValueObject . boolean ; // RTLM5d2b
863883 }
864884 if ( asValueObject . bytes !== undefined ) {
865- return asValueObject . bytes ;
885+ return asValueObject . bytes ; // RTLM5d2c
866886 }
867887 if ( asValueObject . number !== undefined ) {
868- return asValueObject . number ;
888+ return asValueObject . number ; // RTLM5d2d
869889 }
870890 if ( asValueObject . string !== undefined ) {
871- return asValueObject . string ;
891+ return asValueObject . string ; // RTLM5d2e
872892 }
873893
874- // otherwise, it has an objectId reference, and we should get the actual object from the pool
894+ // RTLM5d2f - otherwise, it has an objectId reference, and we should get the actual object from the pool
875895 const objectId = ( data as ObjectIdObjectData ) . objectId ;
876896 const refObject : LiveObject | undefined = this . _objects . getPool ( ) . get ( objectId ) ;
877897 if ( ! refObject ) {
878- return undefined ;
898+ return undefined ; // RTLM5d2f1
879899 }
880900
881901 if ( refObject . isTombstoned ( ) ) {
882902 // tombstoned objects must not be surfaced to the end users
883903 return undefined ;
884904 }
885905
886- return refObject ;
906+ return refObject ; // RTLM5d2f2
887907 }
888908
889909 private _isMapEntryTombstoned ( entry : LiveMapEntry ) : boolean {
0 commit comments