Skip to content

Commit 827f27c

Browse files
committed
Add spec annotations applying incoming OBJECT messages for Objects
Spec IDs from [1]. This also fixes a couple of minor spec implementation details. [1] ably/specification#343
1 parent 922433d commit 827f27c

File tree

4 files changed

+83
-56
lines changed

4 files changed

+83
-56
lines changed

src/plugins/objects/livecounter.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ export class LiveCounter extends LiveObject<LiveCounterData, LiveCounterUpdate>
168168

169169
/**
170170
* @internal
171+
* @spec RTLC7, RTLC7a
171172
*/
172173
applyOperation(op: ObjectOperation<ObjectData>, msg: ObjectMessage): void {
173174
if (op.objectId !== this.getObjectId()) {
@@ -181,6 +182,7 @@ export class LiveCounter extends LiveObject<LiveCounterData, LiveCounterUpdate>
181182
const opSerial = msg.serial!;
182183
const opSiteCode = msg.siteCode!;
183184
if (!this._canApplyOperation(opSerial, opSiteCode)) {
185+
// RTLC7b
184186
this._client.Logger.logAction(
185187
this._client.logger,
186188
this._client.Logger.LOG_MICRO,
@@ -191,17 +193,18 @@ export class LiveCounter extends LiveObject<LiveCounterData, LiveCounterUpdate>
191193
}
192194
// should update stored site serial immediately. doesn't matter if we successfully apply the op,
193195
// as it's important to mark that the op was processed by the object
194-
this._siteTimeserials[opSiteCode] = opSerial;
196+
this._siteTimeserials[opSiteCode] = opSerial; // RTLC7c
195197

196198
if (this.isTombstoned()) {
197199
// this object is tombstoned so the operation cannot be applied
198200
return;
199201
}
200202

201203
let update: LiveCounterUpdate | LiveObjectUpdateNoop;
204+
// RTLC7d
202205
switch (op.action) {
203206
case ObjectOperationAction.COUNTER_CREATE:
204-
update = this._applyCounterCreate(op);
207+
update = this._applyCounterCreate(op); // RTLC7d1
205208
break;
206209

207210
case ObjectOperationAction.COUNTER_INC:
@@ -210,7 +213,7 @@ export class LiveCounter extends LiveObject<LiveCounterData, LiveCounterUpdate>
210213
// leave an explicit return here, so that TS knows that update object is always set after the switch statement.
211214
return;
212215
} else {
213-
update = this._applyCounterInc(op.counterOp);
216+
update = this._applyCounterInc(op.counterOp); // RTLC7d2
214217
}
215218
break;
216219

@@ -219,6 +222,7 @@ export class LiveCounter extends LiveObject<LiveCounterData, LiveCounterUpdate>
219222
break;
220223

221224
default:
225+
// RTLC7d3
222226
throw new this._client.ErrorInfo(
223227
`Invalid ${op.action} op for LiveCounter objectId=${this.getObjectId()}`,
224228
92000,
@@ -283,9 +287,8 @@ export class LiveCounter extends LiveObject<LiveCounterData, LiveCounterUpdate>
283287
// override data for this object with data from the object state
284288
this._createOperationIsMerged = false; // RTLC6b
285289
this._dataRef = { data: objectState.counter?.count ?? 0 }; // RTLC6c
286-
// RTLC6d
287290
if (!this._client.Utils.isNil(objectState.createOp)) {
288-
this._mergeInitialDataFromCreateOperation(objectState.createOp);
291+
this._mergeInitialDataFromCreateOperation(objectState.createOp); // RTLC6d
289292
}
290293
}
291294

@@ -312,13 +315,14 @@ export class LiveCounter extends LiveObject<LiveCounterData, LiveCounterUpdate>
312315
return { update: { amount: counterDiff } };
313316
}
314317

318+
/** @spec RTLC10 */
315319
protected _mergeInitialDataFromCreateOperation(objectOperation: ObjectOperation<ObjectData>): LiveCounterUpdate {
316320
// if a counter object is missing for the COUNTER_CREATE op, the initial value is implicitly 0 in this case.
317321
// note that it is intentional to SUM the incoming count from the create op.
318322
// if we got here, it means that current counter instance is missing the initial value in its data reference,
319323
// which we're going to add now.
320-
this._dataRef.data += objectOperation.counter?.count ?? 0; // RTLC6d1
321-
this._createOperationIsMerged = true; // RTLC6d2
324+
this._dataRef.data += objectOperation.counter?.count ?? 0; // RTLC10a
325+
this._createOperationIsMerged = true; // RTLC10b
322326

323327
return { update: { amount: objectOperation.counter?.count ?? 0 } };
324328
}
@@ -331,8 +335,10 @@ export class LiveCounter extends LiveObject<LiveCounterData, LiveCounterUpdate>
331335
);
332336
}
333337

338+
/** @spec RTLC8 */
334339
private _applyCounterCreate(op: ObjectOperation<ObjectData>): LiveCounterUpdate | LiveObjectUpdateNoop {
335340
if (this._createOperationIsMerged) {
341+
// RTLC8b
336342
// There can't be two different create operation for the same object id, because the object id
337343
// fully encodes that operation. This means we can safely ignore any new incoming create operations
338344
// if we already merged it once.
@@ -345,11 +351,12 @@ export class LiveCounter extends LiveObject<LiveCounterData, LiveCounterUpdate>
345351
return { noop: true };
346352
}
347353

348-
return this._mergeInitialDataFromCreateOperation(op);
354+
return this._mergeInitialDataFromCreateOperation(op); // RTLC8c
349355
}
350356

357+
/** @spec RTLC9 */
351358
private _applyCounterInc(op: ObjectsCounterOp): LiveCounterUpdate {
352-
this._dataRef.data += op.amount;
359+
this._dataRef.data += op.amount; // RTLC9b
353360
return { update: { amount: op.amount } };
354361
}
355362
}

src/plugins/objects/livemap.ts

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,7 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
402402

403403
/**
404404
* @internal
405+
* @spec RTLM15, RTLM15a
405406
*/
406407
applyOperation(op: ObjectOperation<ObjectData>, msg: ObjectMessage): void {
407408
if (op.objectId !== this.getObjectId()) {
@@ -415,6 +416,7 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
415416
const opSerial = msg.serial!;
416417
const opSiteCode = msg.siteCode!;
417418
if (!this._canApplyOperation(opSerial, opSiteCode)) {
419+
// RTLM15b
418420
this._client.Logger.logAction(
419421
this._client.logger,
420422
this._client.Logger.LOG_MICRO,
@@ -425,17 +427,18 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
425427
}
426428
// should update stored site serial immediately. doesn't matter if we successfully apply the op,
427429
// as it's important to mark that the op was processed by the object
428-
this._siteTimeserials[opSiteCode] = opSerial;
430+
this._siteTimeserials[opSiteCode] = opSerial; // RTLM15c
429431

430432
if (this.isTombstoned()) {
431433
// this object is tombstoned so the operation cannot be applied
432434
return;
433435
}
434436

435437
let update: LiveMapUpdate<T> | LiveObjectUpdateNoop;
438+
// RTLM15d
436439
switch (op.action) {
437440
case ObjectOperationAction.MAP_CREATE:
438-
update = this._applyMapCreate(op);
441+
update = this._applyMapCreate(op); // RTLM15d1
439442
break;
440443

441444
case ObjectOperationAction.MAP_SET:
@@ -444,7 +447,7 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
444447
// leave an explicit return here, so that TS knows that update object is always set after the switch statement.
445448
return;
446449
} else {
447-
update = this._applyMapSet(op.mapOp, opSerial);
450+
update = this._applyMapSet(op.mapOp, opSerial); // RTLM15d2
448451
}
449452
break;
450453

@@ -454,7 +457,7 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
454457
// leave an explicit return here, so that TS knows that update object is always set after the switch statement.
455458
return;
456459
} else {
457-
update = this._applyMapRemove(op.mapOp, opSerial, msg.serialTimestamp);
460+
update = this._applyMapRemove(op.mapOp, opSerial, msg.serialTimestamp); // RTLM15d3
458461
}
459462
break;
460463

@@ -463,6 +466,7 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
463466
break;
464467

465468
default:
469+
// RTLM15d4
466470
throw new this._client.ErrorInfo(
467471
`Invalid ${op.action} op for LiveMap objectId=${this.getObjectId()}`,
468472
92000,
@@ -543,9 +547,8 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
543547
// override data for this object with data from the object state
544548
this._createOperationIsMerged = false; // RTLM6b
545549
this._dataRef = this._liveMapDataFromMapEntries(objectState.map?.entries ?? {}); // RTLM6c
546-
// RTLM6d
547550
if (!this._client.Utils.isNil(objectState.createOp)) {
548-
this._mergeInitialDataFromCreateOperation(objectState.createOp);
551+
this._mergeInitialDataFromCreateOperation(objectState.createOp); // RTLM6d
549552
}
550553
}
551554

@@ -631,6 +634,7 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
631634
return update;
632635
}
633636

637+
/** @spec RTLM17 */
634638
protected _mergeInitialDataFromCreateOperation(objectOperation: ObjectOperation<ObjectData>): LiveMapUpdate<T> {
635639
if (this._client.Utils.isNil(objectOperation.map)) {
636640
// if a map object is missing for the MAP_CREATE op, the initial value is implicitly an empty map.
@@ -639,18 +643,18 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
639643
}
640644

641645
const aggregatedUpdate: LiveMapUpdate<T> = { update: {} };
642-
// RTLM6d1
646+
// RTLM17a
643647
// in order to apply MAP_CREATE op for an existing map, we should merge their underlying entries keys.
644648
// 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.
645649
Object.entries(objectOperation.map.entries ?? {}).forEach(([key, entry]) => {
646650
// for a MAP_CREATE operation we must use the serial value available on an entry, instead of a serial on a message
647651
const opSerial = entry.timeserial;
648652
let update: LiveMapUpdate<T> | LiveObjectUpdateNoop;
649653
if (entry.tombstone === true) {
650-
// RTLM6d1b - entry in MAP_CREATE op is removed, try to apply MAP_REMOVE op
654+
// RTLM17a2 - entry in MAP_CREATE op is removed, try to apply MAP_REMOVE op
651655
update = this._applyMapRemove({ key }, opSerial, entry.serialTimestamp);
652656
} else {
653-
// RTLM6d1a - entry in MAP_CREATE op is not removed, try to set it via MAP_SET op
657+
// RTLM17a1 - entry in MAP_CREATE op is not removed, try to set it via MAP_SET op
654658
update = this._applyMapSet({ key, data: entry.data }, opSerial);
655659
}
656660

@@ -663,7 +667,7 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
663667
Object.assign(aggregatedUpdate.update, update.update);
664668
});
665669

666-
this._createOperationIsMerged = true; // RTLM6d2
670+
this._createOperationIsMerged = true; // RTLM17b
667671

668672
return aggregatedUpdate;
669673
}
@@ -676,8 +680,10 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
676680
);
677681
}
678682

683+
/** @spec RTLM16 */
679684
private _applyMapCreate(op: ObjectOperation<ObjectData>): LiveMapUpdate<T> | LiveObjectUpdateNoop {
680685
if (this._createOperationIsMerged) {
686+
// RTLM16b
681687
// There can't be two different create operation for the same object id, because the object id
682688
// fully encodes that operation. This means we can safely ignore any new incoming create operations
683689
// if we already merged it once.
@@ -691,20 +697,21 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
691697
}
692698

693699
if (this._semantics !== op.map?.semantics) {
700+
// RTLM16c
694701
throw new this._client.ErrorInfo(
695702
`Cannot apply MAP_CREATE op on LiveMap objectId=${this.getObjectId()}; map's semantics=${this._semantics}, but op expected ${op.map?.semantics}`,
696703
92000,
697704
500,
698705
);
699706
}
700707

701-
return this._mergeInitialDataFromCreateOperation(op);
708+
return this._mergeInitialDataFromCreateOperation(op); // RTLM16d
702709
}
703710

704711
/** @spec RTLM7 */
705712
private _applyMapSet(
706-
op: ObjectsMapOp<ObjectData>,
707-
opSerial: string | undefined,
713+
op: ObjectsMapOp<ObjectData>, // RTLM7d1
714+
opSerial: string | undefined, // RTLM7d2
708715
): LiveMapUpdate<T> | LiveObjectUpdateNoop {
709716
const { ErrorInfo, Utils } = this._client;
710717

@@ -781,8 +788,8 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
781788

782789
/** @spec RTLM8 */
783790
private _applyMapRemove(
784-
op: ObjectsMapOp<ObjectData>,
785-
opSerial: string | undefined,
791+
op: ObjectsMapOp<ObjectData>, // RTLM8c1
792+
opSerial: string | undefined, // RTLM8c2
786793
opTimestamp: number | undefined,
787794
): LiveMapUpdate<T> | LiveObjectUpdateNoop {
788795
const existingEntry = this._dataRef.data.get(op.key);
@@ -937,17 +944,21 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
937944

938945
// RTLM5d2f - otherwise, it has an objectId reference, and we should get the actual object from the pool
939946
const objectId = (data as ObjectIdObjectData).objectId;
940-
const refObject: LiveObject | undefined = this._objects.getPool().get(objectId);
941-
if (!refObject) {
942-
return undefined; // RTLM5d2f1
943-
}
947+
if (objectId != null) {
948+
const refObject: LiveObject | undefined = this._objects.getPool().get(objectId);
949+
if (!refObject) {
950+
return undefined; // RTLM5d2f1
951+
}
952+
953+
if (refObject.isTombstoned()) {
954+
// tombstoned objects must not be surfaced to the end users
955+
return undefined;
956+
}
944957

945-
if (refObject.isTombstoned()) {
946-
// tombstoned objects must not be surfaced to the end users
947-
return undefined;
958+
return refObject; // RTLM5d2f2
948959
}
949960

950-
return refObject; // RTLM5d2f2
961+
return undefined; // RTLM5d2g
951962
}
952963

953964
/** @spec RTLM14 */

src/plugins/objects/liveobject.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,22 @@ export interface OnLiveObjectLifecycleEventResponse {
3535
off(): void;
3636
}
3737

38+
/** @spec RTLO1, RTLO2 */
3839
export abstract class LiveObject<
3940
TData extends LiveObjectData = LiveObjectData,
4041
TUpdate extends LiveObjectUpdate = LiveObjectUpdate,
4142
> {
4243
protected _client: BaseClient;
4344
protected _subscriptions: EventEmitter;
4445
protected _lifecycleEvents: EventEmitter;
45-
protected _objectId: string;
46+
protected _objectId: string; // RTLO3a
4647
/**
4748
* Represents an aggregated value for an object, which combines the initial value for an object from the create operation,
4849
* and all object operations applied to the object.
4950
*/
5051
protected _dataRef: TData;
51-
protected _siteTimeserials: Record<string, string>;
52-
protected _createOperationIsMerged: boolean;
52+
protected _siteTimeserials: Record<string, string>; // RTLO3b
53+
protected _createOperationIsMerged: boolean; // RTLO3c
5354
private _tombstone: boolean;
5455
private _tombstonedAt: number | undefined;
5556

@@ -60,11 +61,11 @@ export abstract class LiveObject<
6061
this._client = this._objects.getClient();
6162
this._subscriptions = new this._client.EventEmitter(this._client.logger);
6263
this._lifecycleEvents = new this._client.EventEmitter(this._client.logger);
63-
this._objectId = objectId;
64+
this._objectId = objectId; // RTLO3a1
6465
this._dataRef = this._getZeroValueData();
6566
// use empty map of serials by default, so any future operation can be applied to this object
66-
this._siteTimeserials = {};
67-
this._createOperationIsMerged = false;
67+
this._siteTimeserials = {}; // RTLO3b1
68+
this._createOperationIsMerged = false; // RTLO3c1
6869
this._tombstone = false;
6970
}
7071

@@ -198,18 +199,20 @@ export abstract class LiveObject<
198199
*
199200
* An operation should be applied if its serial is strictly greater than the serial in the `siteTimeserials` map for the same site.
200201
* If `siteTimeserials` map does not contain a serial for the same site, the operation should be applied.
202+
*
203+
* @spec RTLO4a
201204
*/
202205
protected _canApplyOperation(opSerial: string | undefined, opSiteCode: string | undefined): boolean {
203206
if (!opSerial) {
204-
throw new this._client.ErrorInfo(`Invalid serial: ${opSerial}`, 92000, 500);
207+
throw new this._client.ErrorInfo(`Invalid serial: ${opSerial}`, 92000, 500); // RTLO4a3
205208
}
206209

207210
if (!opSiteCode) {
208-
throw new this._client.ErrorInfo(`Invalid site code: ${opSiteCode}`, 92000, 500);
211+
throw new this._client.ErrorInfo(`Invalid site code: ${opSiteCode}`, 92000, 500); // RTLO4a3
209212
}
210213

211-
const siteSerial = this._siteTimeserials[opSiteCode];
212-
return !siteSerial || opSerial > siteSerial;
214+
const siteSerial = this._siteTimeserials[opSiteCode]; // RTLO4a4
215+
return !siteSerial || opSerial > siteSerial; // RTLO4a5, RTLO4a6
213216
}
214217

215218
protected _applyObjectDelete(objectMessage: ObjectMessage): TUpdate {

0 commit comments

Comments
 (0)