Skip to content

Commit e7d734e

Browse files
committed
Remove non-path-based API from LiveObjects
This marks full transition to path-based API for LiveObjects. Included in this PR: - resolve TODOs related to path-based API - update all LiveObjects tests to use path-based API - remove publicly exposed LiveObject, LiveMap and LiveCounter types from ably.d.ts - remove lifecycle subscriptions API for liveobjects (API and implementation). OBJECT_DELETE events are handled by regular subscription events.
1 parent 80fd55e commit e7d734e

File tree

13 files changed

+552
-928
lines changed

13 files changed

+552
-928
lines changed

ably.d.ts

Lines changed: 101 additions & 271 deletions
Large diffs are not rendered by default.

src/plugins/objects/batchcontext.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ export class DefaultBatchContext implements AnyBatchContext {
8181
throw new this._client.ErrorInfo('Cannot set a key on a non-LiveMap instance', 92007, 400);
8282
}
8383
this._rootContext.queueMessages(async () =>
84-
LiveMap.createMapSetMessage(this._realtimeObject, this._instance.id()!, key, value as Primitive),
84+
LiveMap.createMapSetMessage(this._realtimeObject, this._instance.id()!, key, value),
8585
);
8686
}
8787

src/plugins/objects/instance.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import type {
99
InstanceSubscriptionEvent,
1010
LiveObject as LiveObjectType,
1111
Primitive,
12-
SubscribeResponse,
12+
Subscription,
1313
Value,
1414
} from '../../../ably';
1515
import { LiveCounter } from './livecounter';
@@ -169,11 +169,11 @@ export class DefaultInstance<T extends Value> implements AnyInstance<T> {
169169
return this._value.decrement(amount ?? 1);
170170
}
171171

172-
subscribe(listener: EventCallback<InstanceSubscriptionEvent<T>>): SubscribeResponse {
172+
subscribe(listener: EventCallback<InstanceSubscriptionEvent<T>>): Subscription {
173173
if (!(this._value instanceof LiveObject)) {
174174
throw new this._client.ErrorInfo('Cannot subscribe to a non-LiveObject instance', 92007, 400);
175175
}
176-
return this._value.instanceSubscribe((event: InstanceEvent) => {
176+
return this._value.subscribe((event: InstanceEvent) => {
177177
listener({
178178
object: this as unknown as Instance<T>,
179179
message: event.message?.toUserFacingMessage(this._realtimeObject.getChannel()),

src/plugins/objects/livemap.ts

Lines changed: 24 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import {
1313
ObjectsMapEntry,
1414
ObjectsMapOp,
1515
ObjectsMapSemantics,
16-
PrimitiveObjectValue,
1716
} from './objectmessage';
1817
import { RealtimeObject } from './realtimeobject';
1918

@@ -24,7 +23,7 @@ export interface ObjectIdObjectData {
2423

2524
export interface ValueObjectData {
2625
/** A decoded leaf value from {@link WireObjectData}. */
27-
value: string | number | boolean | Buffer | ArrayBuffer | API.JsonArray | API.JsonObject;
26+
value: API.Primitive;
2827
}
2928

3029
export type LiveMapObjectData = ObjectIdObjectData | ValueObjectData;
@@ -40,13 +39,18 @@ export interface LiveMapData extends LiveObjectData {
4039
data: Map<string, LiveMapEntry>; // RTLM3
4140
}
4241

43-
export interface LiveMapUpdate<T extends API.LiveMapType> extends LiveObjectUpdate {
42+
export interface LiveMapUpdate<T extends Record<string, API.Value>> extends LiveObjectUpdate {
4443
update: { [keyName in keyof T & string]?: 'updated' | 'removed' };
4544
_type: 'LiveMapUpdate';
4645
}
4746

4847
/** @spec RTLM1, RTLM2 */
49-
export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData, LiveMapUpdate<T>> {
48+
export class LiveMap<T extends Record<string, API.Value> = Record<string, API.Value>>
49+
extends LiveObject<LiveMapData, LiveMapUpdate<T>>
50+
implements API.LiveMap<T>
51+
{
52+
declare readonly [API.__livetype]: 'LiveMap'; // type-only, unique symbol to satisfy branded interfaces, no JS emitted
53+
5054
constructor(
5155
realtimeObject: RealtimeObject,
5256
private _semantics: ObjectsMapSemantics,
@@ -61,8 +65,8 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
6165
* @internal
6266
* @spec RTLM4
6367
*/
64-
static zeroValue<T extends API.LiveMapType>(realtimeObject: RealtimeObject, objectId: string): LiveMap<T> {
65-
return new LiveMap<T>(realtimeObject, ObjectsMapSemantics.LWW, objectId);
68+
static zeroValue(realtimeObject: RealtimeObject, objectId: string): LiveMap {
69+
return new LiveMap(realtimeObject, ObjectsMapSemantics.LWW, objectId);
6670
}
6771

6872
/**
@@ -71,23 +75,20 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
7175
*
7276
* @internal
7377
*/
74-
static fromObjectState<T extends API.LiveMapType>(
75-
realtimeObject: RealtimeObject,
76-
objectMessage: ObjectMessage,
77-
): LiveMap<T> {
78-
const obj = new LiveMap<T>(realtimeObject, objectMessage.object!.map!.semantics!, objectMessage.object!.objectId);
78+
static fromObjectState(realtimeObject: RealtimeObject, objectMessage: ObjectMessage): LiveMap {
79+
const obj = new LiveMap(realtimeObject, objectMessage.object!.map!.semantics!, objectMessage.object!.objectId);
7980
obj.overrideWithObjectState(objectMessage);
8081
return obj;
8182
}
8283

8384
/**
8485
* @internal
8586
*/
86-
static async createMapSetMessage<TKey extends keyof API.LiveMapType & string>(
87+
static async createMapSetMessage(
8788
realtimeObject: RealtimeObject,
8889
objectId: string,
89-
key: TKey,
90-
value: API.LiveMapType[TKey] | LiveCounterValueType | LiveMapValueType,
90+
key: string,
91+
value: API.Value,
9192
): Promise<ObjectMessage[]> {
9293
const client = realtimeObject.getClient();
9394

@@ -111,12 +112,8 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
111112

112113
const typedObjectData: ObjectIdObjectData = { objectId: mapCreateMsg.operation?.objectId! };
113114
objectData = typedObjectData;
114-
} else if (value instanceof LiveObject) {
115-
// TODO: remove this branch when LiveObject is no longer directly supported as a map value
116-
const typedObjectData: ObjectIdObjectData = { objectId: value.getObjectId() };
117-
objectData = typedObjectData;
118115
} else {
119-
const typedObjectData: ValueObjectData = { value: value as PrimitiveObjectValue };
116+
const typedObjectData: ValueObjectData = { value: value as API.Primitive };
120117
objectData = typedObjectData;
121118
}
122119

@@ -141,11 +138,7 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
141138
/**
142139
* @internal
143140
*/
144-
static createMapRemoveMessage<TKey extends keyof API.LiveMapType & string>(
145-
realtimeObject: RealtimeObject,
146-
objectId: string,
147-
key: TKey,
148-
): ObjectMessage {
141+
static createMapRemoveMessage(realtimeObject: RealtimeObject, objectId: string, key: string): ObjectMessage {
149142
const client = realtimeObject.getClient();
150143

151144
if (typeof key !== 'string') {
@@ -170,11 +163,7 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
170163
/**
171164
* @internal
172165
*/
173-
static validateKeyValue<TKey extends keyof API.LiveMapType & string>(
174-
realtimeObject: RealtimeObject,
175-
key: TKey,
176-
value: API.LiveMapType[TKey] | LiveCounterValueType | LiveMapValueType,
177-
): void {
166+
static validateKeyValue(realtimeObject: RealtimeObject, key: string, value: API.Value): void {
178167
const client = realtimeObject.getClient();
179168

180169
if (typeof key !== 'string') {
@@ -209,19 +198,19 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
209198
this._realtimeObject.throwIfInvalidAccessApiConfiguration(); // RTLM5b, RTLM5c
210199

211200
if (this.isTombstoned()) {
212-
return undefined as T[TKey];
201+
return undefined;
213202
}
214203

215204
const element = this._dataRef.data.get(key);
216205

217206
// RTLM5d1
218207
if (element === undefined) {
219-
return undefined as T[TKey];
208+
return undefined;
220209
}
221210

222211
// RTLM5d2a
223212
if (element.tombstone === true) {
224-
return undefined as T[TKey];
213+
return undefined;
225214
}
226215

227216
// data always exists for non-tombstoned elements
@@ -507,7 +496,7 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
507496
*
508497
* @internal
509498
*/
510-
compact(): { [K in keyof T]: any } {
499+
compact(): API.CompactedValue<API.LiveMap<T>> {
511500
const result: Record<keyof T, any> = {} as Record<keyof T, any>;
512501

513502
// Use public entries() method to ensure we only include publicly exposed properties
@@ -911,7 +900,7 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
911900
/**
912901
* Returns value as is if object data stores a primitive type, or a reference to another LiveObject from the pool if it stores an objectId.
913902
*/
914-
private _getResolvedValueFromObjectData(data: LiveMapObjectData): PrimitiveObjectValue | LiveObject | undefined {
903+
private _getResolvedValueFromObjectData(data: LiveMapObjectData): API.Value | undefined {
915904
// if object data stores primitive value, just return it as is.
916905
const primitiveValue = (data as ValueObjectData).value;
917906
if (primitiveValue != null) {
@@ -930,7 +919,7 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
930919
return undefined;
931920
}
932921

933-
return refObject; // RTLM5d2f2
922+
return refObject as unknown as API.LiveObject; // RTLM5d2f2
934923
}
935924

936925
private _isMapEntryTombstoned(entry: LiveMapEntry): boolean {

src/plugins/objects/livemapvaluetype.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
ObjectOperationAction,
1111
ObjectsMapEntry,
1212
ObjectsMapSemantics,
13-
PrimitiveObjectValue,
1413
} from './objectmessage';
1514
import { RealtimeObject } from './realtimeobject';
1615

@@ -75,10 +74,7 @@ export class LiveMapValueType<T extends Record<string, API.Value> = Record<strin
7574
throw new client.ErrorInfo('Map entries should be a key-value object', 40003, 400);
7675
}
7776

78-
// TODO: fix as any type assertion when LiveMap type is updated to support new path based types
79-
Object.entries(entries ?? {}).forEach(([key, value]) =>
80-
LiveMap.validateKeyValue(realtimeObject, key, value as any),
81-
);
77+
Object.entries(entries ?? {}).forEach(([key, value]) => LiveMap.validateKeyValue(realtimeObject, key, value));
8278

8379
const { initialValueOperation, nestedObjectsCreateMsgs } = await LiveMapValueType._createInitialValueOperation(
8480
realtimeObject,
@@ -142,7 +138,7 @@ export class LiveMapValueType<T extends Record<string, API.Value> = Record<strin
142138
objectData = typedObjectData;
143139
} else {
144140
// Handle primitive values
145-
const typedObjectData: ValueObjectData = { value: value as PrimitiveObjectValue };
141+
const typedObjectData: ValueObjectData = { value: value as API.Primitive };
146142
objectData = typedObjectData;
147143
}
148144

src/plugins/objects/liveobject.ts

Lines changed: 3 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import type BaseClient from 'common/lib/client/baseclient';
22
import type EventEmitter from 'common/lib/util/eventemitter';
3-
import type { EventCallback, LiveMapType, SubscribeResponse } from '../../../ably';
3+
import type { EventCallback, Subscription } from '../../../ably';
44
import { ROOT_OBJECT_ID } from './constants';
55
import { InstanceEvent } from './instance';
6-
import { LiveMapUpdate } from './livemap';
76
import { ObjectData, ObjectMessage, ObjectOperation } from './objectmessage';
87
import { PathEvent } from './pathobjectsubscriptionregister';
98
import { RealtimeObject } from './realtimeobject';
@@ -32,23 +31,12 @@ export interface LiveObjectUpdateNoop {
3231
noop: true;
3332
}
3433

35-
export enum LiveObjectLifecycleEvent {
36-
deleted = 'deleted',
37-
}
38-
39-
export type LiveObjectLifecycleEventCallback = () => void;
40-
41-
export interface OnLiveObjectLifecycleEventResponse {
42-
off(): void;
43-
}
44-
4534
export abstract class LiveObject<
4635
TData extends LiveObjectData = LiveObjectData,
4736
TUpdate extends LiveObjectUpdate = LiveObjectUpdate,
4837
> {
4938
protected _client: BaseClient;
5039
protected _subscriptions: EventEmitter;
51-
protected _lifecycleEvents: EventEmitter;
5240
protected _objectId: string;
5341
/**
5442
* Represents an aggregated value for an object, which combines the initial value for an object from the create operation,
@@ -71,7 +59,6 @@ export abstract class LiveObject<
7159
) {
7260
this._client = this._realtimeObject.getClient();
7361
this._subscriptions = new this._client.EventEmitter(this._client.logger);
74-
this._lifecycleEvents = new this._client.EventEmitter(this._client.logger);
7562
this._objectId = objectId;
7663
this._dataRef = this._getZeroValueData();
7764
// use empty map of serials by default, so any future operation can be applied to this object
@@ -81,7 +68,7 @@ export abstract class LiveObject<
8168
this._parentReferences = new Map<LiveObject, Set<string>>();
8269
}
8370

84-
instanceSubscribe(listener: EventCallback<InstanceEvent>): SubscribeResponse {
71+
subscribe(listener: EventCallback<InstanceEvent>): Subscription {
8572
this._realtimeObject.throwIfInvalidAccessApiConfiguration();
8673

8774
this._subscriptions.on(LiveObjectSubscriptionEvent.updated, listener);
@@ -93,33 +80,6 @@ export abstract class LiveObject<
9380
return { unsubscribe };
9481
}
9582

96-
on(event: LiveObjectLifecycleEvent, callback: LiveObjectLifecycleEventCallback): OnLiveObjectLifecycleEventResponse {
97-
// this public API method can be called without specific configuration, so checking for invalid settings is unnecessary.
98-
this._lifecycleEvents.on(event, callback);
99-
100-
const off = () => {
101-
this._lifecycleEvents.off(event, callback);
102-
};
103-
104-
return { off };
105-
}
106-
107-
off(event: LiveObjectLifecycleEvent, callback: LiveObjectLifecycleEventCallback): void {
108-
// this public API method can be called without specific configuration, so checking for invalid settings is unnecessary.
109-
110-
// prevent accidentally calling .off without any arguments on an EventEmitter and removing all callbacks
111-
if (this._client.Utils.isNil(event) && this._client.Utils.isNil(callback)) {
112-
return;
113-
}
114-
115-
this._lifecycleEvents.off(event, callback);
116-
}
117-
118-
offAll(): void {
119-
// this public API method can be called without specific configuration, so checking for invalid settings is unnecessary.
120-
this._lifecycleEvents.off();
121-
}
122-
12383
/**
12484
* @internal
12585
*/
@@ -170,8 +130,6 @@ export abstract class LiveObject<
170130
update.objectMessage = objectMessage;
171131
update.tombstone = true;
172132

173-
this._lifecycleEvents.emit(LiveObjectLifecycleEvent.deleted);
174-
175133
return update;
176134
}
177135

@@ -347,7 +305,7 @@ export abstract class LiveObject<
347305

348306
// For LiveMapUpdate, also create non-bubbling events for each updated key
349307
if (update._type === 'LiveMapUpdate') {
350-
const updatedKeys = Object.keys((update as LiveMapUpdate<LiveMapType>).update);
308+
const updatedKeys = Object.keys(update.update);
351309

352310
for (const key of updatedKeys) {
353311
for (const basePath of paths) {

src/plugins/objects/objectmessage.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,6 @@ export enum ObjectsMapSemantics {
3333
LWW = 0,
3434
}
3535

36-
export type PrimitiveObjectValue = string | number | boolean | Buffer | ArrayBuffer | JsonArray | JsonObject;
37-
3836
/**
3937
* An ObjectData represents a value in an object on a channel decoded from {@link WireObjectData}.
4038
* @spec OD1
@@ -43,7 +41,7 @@ export interface ObjectData {
4341
/** A reference to another object, used to support composable object structures. */
4442
objectId?: string; // OD2a
4543
/** A decoded leaf value from {@link WireObjectData}. */
46-
value?: PrimitiveObjectValue;
44+
value?: API.Primitive;
4745
}
4846

4947
/**

src/plugins/objects/objectspool.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import type BaseClient from 'common/lib/client/baseclient';
2-
import type * as API from '../../../ably';
32
import { ROOT_OBJECT_ID } from './constants';
43
import { DEFAULTS } from './defaults';
54
import { LiveCounter } from './livecounter';
@@ -31,8 +30,8 @@ export class ObjectsPool {
3130
return this._pool.get(objectId);
3231
}
3332

34-
getRoot<T extends API.LiveMapType = API.AblyDefaultObject>(): LiveMap<T> {
35-
return this._pool.get(ROOT_OBJECT_ID) as LiveMap<T>;
33+
getRoot(): LiveMap {
34+
return this._pool.get(ROOT_OBJECT_ID) as LiveMap;
3635
}
3736

3837
/**

0 commit comments

Comments
 (0)