Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 125 additions & 4 deletions liveobjects.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1489,6 +1489,10 @@ declare namespace ObjectOperationActions {
* Object operation action for deleting an object.
*/
type OBJECT_DELETE = 'object.delete';
/**
* Object operation action for clearing a map object.
*/
type MAP_CLEAR = 'map.clear';
}

/**
Expand All @@ -1500,7 +1504,8 @@ export type ObjectOperationAction =
| ObjectOperationActions.MAP_REMOVE
| ObjectOperationActions.COUNTER_CREATE
| ObjectOperationActions.COUNTER_INC
| ObjectOperationActions.OBJECT_DELETE;
| ObjectOperationActions.OBJECT_DELETE
| ObjectOperationActions.MAP_CLEAR;

/**
* The namespace containing the different types of map object semantics.
Expand Down Expand Up @@ -1577,18 +1582,65 @@ export interface ObjectOperation {
action: ObjectOperationAction;
/** The ID of the object the operation was applied to. */
objectId: string;
/** The payload for the operation if it is a mutation operation on a map object. */

/**
* The payload for the operation if the action is {@link ObjectOperationActions.MAP_CREATE}.
* Defines the initial value of the map object.
*/
mapCreate?: MapCreate;
/**
* The payload for the operation if the action is {@link ObjectOperationActions.MAP_SET}.
* Describes the key and value to set on the map object.
*/
mapSet?: MapSet;
/**
* The payload for the operation if the action is {@link ObjectOperationActions.MAP_REMOVE}.
* Describes the key to remove from the map object.
*/
mapRemove?: MapRemove;
/**
* The payload for the operation if the action is {@link ObjectOperationActions.COUNTER_CREATE}.
* Defines the initial value of the counter object.
*/
counterCreate?: CounterCreate;
/**
* The payload for the operation if the action is {@link ObjectOperationActions.COUNTER_INC}.
* Describes the value to add to the counter.
*/
counterInc?: CounterInc;
/**
* The payload for the operation if the action is {@link ObjectOperationActions.OBJECT_DELETE}.
*/
objectDelete?: ObjectDelete;
/**
* The payload for the operation if the action is {@link ObjectOperationActions.MAP_CLEAR}.
*/
mapClear?: MapClear;

/**
* The payload for the operation if it is a mutation operation on a map object.
*
* @deprecated This property is deprecated and will be removed in a future major version. Use {@link mapSet} and {@link mapRemove} instead.
*/
mapOp?: ObjectsMapOp;
/** The payload for the operation if it is a mutation operation on a counter object. */
/**
* The payload for the operation if it is a mutation operation on a counter object.
*
* @deprecated This property is deprecated and will be removed in a future major version. Use {@link counterInc} instead.
*/
counterOp?: ObjectsCounterOp;
/**
* The payload for the operation if the action is {@link ObjectOperationActions.MAP_CREATE}.
* Defines the initial value of the map object.
*
* @deprecated This property is deprecated and will be removed in a future major version. Use {@link mapCreate} instead.
*/
map?: ObjectsMap;
/**
* The payload for the operation if the action is {@link ObjectOperationActions.COUNTER_CREATE}.
* Defines the initial value of the counter object.
*
* @deprecated This property is deprecated and will be removed in a future major version. Use {@link counterCreate} instead.
*/
counter?: ObjectsCounter;
}
Expand Down Expand Up @@ -1643,13 +1695,82 @@ export interface ObjectsCounter {
count?: number;
}

/**
* Describes the payload for a MAP_CREATE operation.
*/
export interface MapCreate {
/** The conflict-resolution semantics used by the map object, one of the {@link ObjectsMapSemantics} enum values. */
semantics: ObjectsMapSemantics;
/** The map entries, indexed by key. */
entries: Record<string, ObjectsMapEntry>;
}

/**
* Describes the payload for a MAP_SET operation on a map object.
*/
export interface MapSet {
/** The key to set. */
key: string;
/** The value to set. */
value: ObjectData;
}

/**
* Describes the payload for a MAP_REMOVE operation on a map object.
*/
export interface MapRemove {
/** The key to remove. */
key: string;
}

/**
* Describes the payload for a COUNTER_CREATE operation.
*/
export interface CounterCreate {
/** The initial counter value. */
count: number;
}

/**
* Describes the payload for a COUNTER_INC operation on a counter object.
*/
export interface CounterInc {
/** The value to be added to the counter. */
number: number;
}

/**
* Describes the payload for an OBJECT_DELETE operation.
*/
export interface ObjectDelete {}

/**
* Describes the payload for a MAP_CLEAR operation.
*/
export interface MapClear {}

/**
* Represents a value in an object on a channel.
*/
export interface ObjectData {
/** A reference to another object. */
objectId?: string;
/** A decoded primitive value. */
/** A boolean leaf value in the object. */
boolean?: boolean;
/** A decoded binary leaf value in the object. */
bytes?: Buffer | ArrayBuffer;
/** A number leaf value in the object. */
number?: number;
/** A string leaf value in the object. */
string?: string;
/** A decoded JSON leaf value in the object. */
json?: JsonObject | JsonArray;

/**
* A decoded primitive value.
*
* @deprecated This property is deprecated and will be removed in a future major version. Use one of the typed {@link boolean}, {@link bytes}, {@link number}, {@link string} or {@link json} fields instead.
*/
value?: Primitive;
}

Expand Down
2 changes: 1 addition & 1 deletion scripts/moduleReport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ async function checkLiveObjectsPluginFiles() {
'src/plugins/liveobjects/pathobjectsubscriptionregister.ts',
'src/plugins/liveobjects/realtimeobject.ts',
'src/plugins/liveobjects/rootbatchcontext.ts',
'src/plugins/liveobjects/syncobjectsdatapool.ts',
'src/plugins/liveobjects/syncobjectspool.ts',
]);

return checkBundleFiles(pluginBundleInfo, allowedFiles, 100);
Expand Down
2 changes: 1 addition & 1 deletion src/common/lib/util/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ const Defaults = {
maxMessageSize: 65536,

version,
protocolVersion: 5,
protocolVersion: 6,
agent,
getPort,
getHttpScheme,
Expand Down
36 changes: 22 additions & 14 deletions src/plugins/liveobjects/livecounter.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { __livetype } from '../../../ably';
import { LiveCounter as PublicLiveCounter } from '../../../liveobjects';
import { LiveObject, LiveObjectData, LiveObjectUpdate, LiveObjectUpdateNoop } from './liveobject';
import { ObjectData, ObjectMessage, ObjectOperation, ObjectOperationAction, ObjectsCounterOp } from './objectmessage';
import { CounterInc, ObjectData, ObjectMessage, ObjectOperation, ObjectOperationAction } from './objectmessage';
import { ObjectsOperationSource, RealtimeObject } from './realtimeobject';

export interface LiveCounterData extends LiveObjectData {
Expand Down Expand Up @@ -52,9 +52,9 @@ export class LiveCounter extends LiveObject<LiveCounterData, LiveCounterUpdate>
const msg = ObjectMessage.fromValues(
{
operation: {
action: ObjectOperationAction.COUNTER_INC,
objectId,
counterOp: { amount },
action: ObjectOperationAction.COUNTER_INC, // RTLC12e2
objectId, // RTLC12e3
counterInc: { number: amount }, // RTLC12e5
} as ObjectOperation<ObjectData>,
},
client.Utils,
Expand Down Expand Up @@ -136,18 +136,21 @@ export class LiveCounter extends LiveObject<LiveCounterData, LiveCounterUpdate>
let update: LiveCounterUpdate | LiveObjectUpdateNoop;
switch (op.action) {
case ObjectOperationAction.COUNTER_CREATE:
// RTLC7d1
update = this._applyCounterCreate(op, msg);
break;

case ObjectOperationAction.COUNTER_INC:
if (this._client.Utils.isNil(op.counterOp)) {
if (this._client.Utils.isNil(op.counterInc)) {
this._throwNoPayloadError(op);
} else {
update = this._applyCounterInc(op.counterOp, msg);
// RTLC7d5
update = this._applyCounterInc(op.counterInc, msg);
}
break;

case ObjectOperationAction.OBJECT_DELETE:
// RTLC7d4
update = this._applyObjectDelete(msg);
break;

Expand All @@ -159,8 +162,8 @@ export class LiveCounter extends LiveObject<LiveCounterData, LiveCounterUpdate>
);
}

this.notifyUpdated(update);
return true; // RTLC7d1b, RTLC7d2b, RTLC7d4b
this.notifyUpdated(update); // RTLC7d1a, RTLC7d5a, RTLC7d4a
return true; // RTLC7d1b, RTLC7d5b, RTLC7d4b
}

/**
Expand Down Expand Up @@ -253,15 +256,19 @@ export class LiveCounter extends LiveObject<LiveCounterData, LiveCounterUpdate>
objectOperation: ObjectOperation<ObjectData>,
msg: ObjectMessage,
): LiveCounterUpdate {
// RTLC16 - resolve counterCreate from either the direct property or the one from which counterCreateWithObjectId was derived
const counterCreate = objectOperation.counterCreate ?? objectOperation.counterCreateWithObjectId?._derivedFrom;

// if a counter object is missing for the COUNTER_CREATE op, the initial value is implicitly 0 in this case.
// note that it is intentional to SUM the incoming count from the create op.
// if we got here, it means that current counter instance is missing the initial value in its data reference,
// which we're going to add now.
this._dataRef.data += objectOperation.counter?.count ?? 0; // RTLC6d1
this._createOperationIsMerged = true; // RTLC6d2
this._dataRef.data += counterCreate?.count ?? 0; // RTLC16a
this._createOperationIsMerged = true; // RTLC16b

// RTLC16c
return {
update: { amount: objectOperation.counter?.count ?? 0 },
update: { amount: counterCreate?.count ?? 0 },
objectMessage: msg,
_type: 'LiveCounterUpdate',
};
Expand Down Expand Up @@ -295,10 +302,11 @@ export class LiveCounter extends LiveObject<LiveCounterData, LiveCounterUpdate>
return this._mergeInitialDataFromCreateOperation(op, msg);
}

private _applyCounterInc(op: ObjectsCounterOp, msg: ObjectMessage): LiveCounterUpdate {
this._dataRef.data += op.amount;
/** @spec RTLC9, RTLC9a2 */
private _applyCounterInc(op: CounterInc, msg: ObjectMessage): LiveCounterUpdate {
this._dataRef.data += op.number; // RTLC9f
return {
update: { amount: op.amount },
update: { amount: op.number }, // RTLC9g
objectMessage: msg,
_type: 'LiveCounterUpdate',
};
Expand Down
32 changes: 18 additions & 14 deletions src/plugins/liveobjects/livecountervaluetype.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { __livetype } from '../../../ably';
import { LiveCounter } from '../../../liveobjects';
import { ObjectId } from './objectid';
import {
createInitialValueJSONString,
CounterCreate,
encodePartialObjectOperationForWire,
ObjectData,
ObjectMessage,
ObjectOperation,
Expand Down Expand Up @@ -58,11 +59,13 @@ export class LiveCounterValueType implements LiveCounter {
throw new client.ErrorInfo('Counter value should be a valid number', 40003, 400);
}

const initialValueOperation = LiveCounterValueType.createInitialValueOperation(count);
const initialValueJSONString = createInitialValueJSONString(initialValueOperation, client);
const nonce = client.Utils.cheapRandStr();
const msTimestamp = await client.getTimestamp(true);
const counterCreate = LiveCounterValueType._getCounterCreate(count); // RTO12f12
const { counterCreate: encodedCounterCreate } = encodePartialObjectOperationForWire({ counterCreate }, client);
const initialValueJSONString = JSON.stringify(encodedCounterCreate); // RTO12f13
const nonce = client.Utils.cheapRandStr(); // RTO12f4
const msTimestamp = await client.getTimestamp(true); // RTO12f5

// RTO12f6
const objectId = ObjectId.fromInitialValue(
client.Platform,
'counter',
Expand All @@ -74,11 +77,14 @@ export class LiveCounterValueType implements LiveCounter {
const msg = ObjectMessage.fromValues(
{
operation: {
...initialValueOperation,
action: ObjectOperationAction.COUNTER_CREATE,
objectId,
nonce,
initialValue: initialValueJSONString,
action: ObjectOperationAction.COUNTER_CREATE, // RTO12f7
objectId, // RTO12f8
counterCreateWithObjectId: {
nonce, // RTO12f14
initialValue: initialValueJSONString, // RTO12f15
// RTO12f16 - retain the source CounterCreate for local use (size calculation and apply-on-ACK)
_derivedFrom: counterCreate,
},
} as ObjectOperation<ObjectData>,
},
client.Utils,
Expand All @@ -88,11 +94,9 @@ export class LiveCounterValueType implements LiveCounter {
return msg;
}

private static createInitialValueOperation(count?: number): Pick<ObjectOperation<ObjectData>, 'counter'> {
private static _getCounterCreate(count?: number): CounterCreate {
return {
counter: {
count: count ?? 0,
},
count: count ?? 0, // RTO12f12a, RTO12f12b
};
}
}
Loading
Loading