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
146 changes: 119 additions & 27 deletions ably.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2415,11 +2415,42 @@ export type LiveObject = LiveMap | LiveCounter;
*/
export type Value = LiveObject | Primitive;

/**
* CompactedValue transforms LiveObject types into plain JavaScript equivalents.
* LiveMap becomes an object, LiveCounter becomes a number, binary values become base64-encoded strings, other primitives remain unchanged.
*/
export type CompactedValue<T extends Value> =
// LiveMap types
[T] extends [LiveMap<infer U>]
? { [K in keyof U]: CompactedValue<U[K]> }
: [T] extends [LiveMap<infer U> | undefined]
? { [K in keyof U]: CompactedValue<U[K]> } | undefined
: // LiveCounter types
[T] extends [LiveCounter]
? number
: [T] extends [LiveCounter | undefined]
? number | undefined
: // Binary types (converted to base64 strings)
[T] extends [Buffer]
? string
: [T] extends [Buffer | undefined]
? string | undefined
: [T] extends [ArrayBuffer]
? string
: [T] extends [ArrayBuffer | undefined]
? string | undefined
: // Other primitive types
[T] extends [Primitive]
? T
: [T] extends [Primitive | undefined]
? T
: any;

/**
* PathObjectBase defines the set of common methods on a PathObject
* that are present regardless of the underlying type specified by the type parameter T.
* that are present regardless of the underlying type.
*/
interface PathObjectBase<_T extends Value> {
interface PathObjectBase {
/**
* Get the fully-qualified path string for this PathObject.
*
Expand All @@ -2430,14 +2461,6 @@ interface PathObjectBase<_T extends Value> {
*/
path(): string;

/**
* Get a JavaScript object representation of the object at this path.
* If the path does not resolve to any specific entry, returns `undefined`.
*
* @experimental
*/
compact(): any;

/**
* Registers a listener that is called each time the object or a primitive value at this path is updated.
*
Expand Down Expand Up @@ -2528,7 +2551,7 @@ interface LiveMapPathObjectCollectionMethods<T extends Record<string, Value> = R
* The type parameter T describes the expected structure of the map's entries.
*/
export interface LiveMapPathObject<T extends Record<string, Value> = Record<string, Value>>
extends PathObjectBase<LiveMap<T>>,
extends PathObjectBase,
PathObjectCollectionMethods,
LiveMapPathObjectCollectionMethods<T>,
LiveMapOperations<T> {
Expand All @@ -2550,12 +2573,22 @@ export interface LiveMapPathObject<T extends Record<string, Value> = Record<stri
* @experimental
*/
instance(): LiveMapInstance<T> | undefined;

/**
* Get a JavaScript object representation of the map at this path.
* Binary values are returned as base64-encoded strings.
*
* If the path does not resolve to any specific instance, returns `undefined`.
*
* @experimental
*/
compact(): CompactedValue<LiveMap<T>> | undefined;
}

/**
* A PathObject representing a {@link LiveCounter} instance at a specific path.
*/
export interface LiveCounterPathObject extends PathObjectBase<LiveCounter>, LiveCounterOperations {
export interface LiveCounterPathObject extends PathObjectBase, LiveCounterOperations {
/**
* Get the current value of the counter instance currently at this path.
* If the path does not resolve to any specific instance, returns `undefined`.
Expand All @@ -2572,19 +2605,37 @@ export interface LiveCounterPathObject extends PathObjectBase<LiveCounter>, Live
* @experimental
*/
instance(): LiveCounterInstance | undefined;

/**
* Get a number representation of the counter at this path.
* If the path does not resolve to any specific instance, returns `undefined`.
*
* @experimental
*/
compact(): CompactedValue<LiveCounter> | undefined;
}

/**
* A PathObject representing a primitive value at a specific path.
*/
export interface PrimitivePathObject<T extends Primitive = Primitive> extends PathObjectBase<Primitive> {
export interface PrimitivePathObject<T extends Primitive = Primitive> extends PathObjectBase {
/**
* Get the current value of the primitive currently at this path.
* If the path does not resolve to any specific entry, returns `undefined`.
*
* @experimental
*/
value(): T | undefined;

/**
* Get a JavaScript object representation of the primitive value at this path.
* Binary values are returned as base64-encoded strings.
*
* If the path does not resolve to any specific entry, returns `undefined`.
*
* @experimental
*/
compact(): CompactedValue<T> | undefined;
}

/**
Expand Down Expand Up @@ -2640,8 +2691,8 @@ interface AnyPathObjectCollectionMethods {
* Each method supports type parameters to specify the expected
* underlying type when needed.
*/
export interface AnyPathObject<T extends Value = Value>
extends PathObjectBase<T>,
export interface AnyPathObject
extends PathObjectBase,
PathObjectCollectionMethods,
AnyPathObjectCollectionMethods,
AnyOperations {
Expand Down Expand Up @@ -2671,6 +2722,16 @@ export interface AnyPathObject<T extends Value = Value>
* @experimental
*/
instance<T extends Value = Value>(): Instance<T> | undefined;

/**
* Get a JavaScript object representation of the object at this path.
* Binary values are returned as base64-encoded strings.
*
* If the path does not resolve to any specific entry, returns `undefined`.
*
* @experimental
*/
compact<T extends Value = Value>(): CompactedValue<T> | undefined;
}

/**
Expand All @@ -2680,13 +2741,13 @@ export interface AnyPathObject<T extends Value = Value>
*
* @experimental
*/
export type PathObject<T extends Value = Value> = [T] extends [LiveMap<infer T>]
? LiveMapPathObject<T>
export type PathObject<T extends Value = Value> = [T] extends [LiveMap<infer U>]
? LiveMapPathObject<U>
: [T] extends [LiveCounter]
? LiveCounterPathObject
: [T] extends [Primitive]
? PrimitivePathObject<T>
: AnyPathObject<T>;
: AnyPathObject;

/**
* Defines operations available on a {@link LiveMapPathObject}.
Expand Down Expand Up @@ -2866,13 +2927,6 @@ interface InstanceBase<T extends Value> {
*/
id(): string | undefined;

/**
* Get a JavaScript object representation of this instance.
*
* @experimental
*/
compact(): any;

/**
* Registers a listener that is called each time this instance is updated.
*
Expand Down Expand Up @@ -2962,6 +3016,16 @@ export interface LiveMapInstance<T extends Record<string, Value> = Record<string
* @experimental
*/
get<K extends keyof T & string>(key: K): Instance<T[K]> | undefined;

/**
* Get a JavaScript object representation of the map instance.
* Binary values are returned as base64-encoded strings.
*
* If the underlying instance's value is not of the expected type at runtime, returns `undefined`.
*
* @experimental
*/
compact(): CompactedValue<LiveMap<T>> | undefined;
}

/**
Expand All @@ -2975,6 +3039,14 @@ export interface LiveCounterInstance extends InstanceBase<LiveCounter>, LiveCoun
* @experimental
*/
value(): number | undefined;

/**
* Get a number representation of the counter instance.
* If the underlying instance's value is not of the expected type at runtime, returns `undefined`.
*
* @experimental
*/
compact(): CompactedValue<LiveCounter> | undefined;
}

/**
Expand All @@ -2991,6 +3063,16 @@ export interface PrimitiveInstance<T extends Primitive = Primitive> {
* @experimental
*/
value(): T | undefined;

/**
* Get a JavaScript object representation of the primitive value.
* Binary values are returned as base64-encoded strings.
*
* If the underlying instance's value is not of the expected type at runtime, returns `undefined`.
*
* @experimental
*/
compact(): CompactedValue<T> | undefined;
}

/**
Expand Down Expand Up @@ -3074,6 +3156,16 @@ export interface AnyInstance<T extends Value> extends InstanceBase<T>, AnyInstan
* @experimental
*/
value<T extends number | Primitive = number | Primitive>(): T | undefined;

/**
* Get a JavaScript object representation of the object instance.
* Binary values are returned as base64-encoded strings.
*
* If the underlying instance's value is not of the expected type at runtime, returns `undefined`.
*
* @experimental
*/
compact<T extends Value = Value>(): CompactedValue<T> | undefined;
}

/**
Expand All @@ -3083,8 +3175,8 @@ export interface AnyInstance<T extends Value> extends InstanceBase<T>, AnyInstan
*
* @experimental
*/
export type Instance<T extends Value> = [T] extends [LiveMap<infer T>]
? LiveMapInstance<T>
export type Instance<T extends Value> = [T] extends [LiveMap<infer U>]
? LiveMapInstance<U>
: [T] extends [LiveCounter]
? LiveCounterInstance
: [T] extends [Primitive]
Expand Down
17 changes: 14 additions & 3 deletions src/plugins/objects/instance.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type BaseClient from 'common/lib/client/baseclient';
import type {
AnyInstance,
CompactedValue,
EventCallback,
Instance,
InstanceSubscriptionEvent,
Expand Down Expand Up @@ -37,8 +38,18 @@ export class DefaultInstance<T extends Value> implements AnyInstance<T> {
return this._value.getObjectId();
}

compact(): any {
throw new Error('Not implemented');
compact<U extends Value = Value>(): CompactedValue<U> | undefined {
if (this._value instanceof LiveMap) {
return this._value.compact() as CompactedValue<U>;
}

const value = this.value();

if (this._client.Platform.BufferUtils.isBuffer(value)) {
return this._client.Platform.BufferUtils.base64Encode(value) as CompactedValue<U>;
}

return value as CompactedValue<U>;
}

get<U extends Value = Value>(key: string): Instance<U> | undefined {
Expand Down Expand Up @@ -81,7 +92,7 @@ export class DefaultInstance<T extends Value> implements AnyInstance<T> {
this._client.logger,
this._client.Logger.LOG_MAJOR,
'DefaultInstance.value()',
`unexpected value type for instance, resolving to undefined; value=${this._value}`,
`unexpected value type for instance, resolving to undefined; value=${this._value}; type=${typeof this._value}`,
);
// unknown type - return undefined
return undefined;
Expand Down
36 changes: 36 additions & 0 deletions src/plugins/objects/livemap.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { dequal } from 'dequal';

import type * as API from '../../../ably';
import { LiveCounter } from './livecounter';
import { LiveCounterValueType } from './livecountervaluetype';
import { LiveMapValueType } from './livemapvaluetype';
import { LiveObject, LiveObjectData, LiveObjectUpdate, LiveObjectUpdateNoop } from './liveobject';
Expand Down Expand Up @@ -543,6 +544,41 @@ export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData,
return super.clearData();
}

/**
* Returns a plain JavaScript object representation of this LiveMap.
* LiveMap values are recursively compacted using their own compact methods.
* Buffers are converted to base64 strings.
*
* @internal
*/
compact(): { [K in keyof T]: any } {
const result: Record<keyof T, any> = {} as Record<keyof T, any>;

// Use public entries() method to ensure we only include publicly exposed properties
for (const [key, value] of this.entries()) {
if (value instanceof LiveMap) {
result[key] = value.compact();
continue;
}

if (value instanceof LiveCounter) {
result[key] = value.value();
continue;
}

// Convert buffers to base64 strings
if (this._client.Platform.BufferUtils.isBuffer(value)) {
result[key] = this._client.Platform.BufferUtils.base64Encode(value);
continue;
}

// Other values return as is
result[key] = value;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think buffer values should be base64 encoded strings; I think the object returned from compact() should be a trivially JSON-encodable object

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

made binary values return base64 encoded strings in 94be648

}

return result;
}

/** @spec RTLM4 */
protected _getZeroValueData(): LiveMapData {
return { data: new Map<string, LiveMapEntry>() };
Expand Down
Loading
Loading