Skip to content

Commit

Permalink
Move types required for user-provided LiveObjects typings to ably.d.ts
Browse files Browse the repository at this point in the history
Add globally defined `LiveObjectsTypes` to `intentionallyNotExported` [1]
list for `typedoc`, as typedocs does not include documentation for the
globally defined interfaces and complains that "__global.LiveObjectsTypes,
defined in ./ably.d.ts, is referenced by ably.DefaultRoot but not
included in the documentation."

[1] https://typedoc.org/documents/Options.Validation.html#intentionallynotexported
  • Loading branch information
VeskeR committed Nov 29, 2024
1 parent f4113d8 commit 73c8abc
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 32 deletions.
57 changes: 54 additions & 3 deletions ably.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2039,25 +2039,76 @@ export declare interface LiveObjects {
/**
* Retrieves the root {@link LiveMap} object for state on a channel.
*
* A type parameter can be provided to describe the structure of the LiveObjects state on the channel. By default, it uses types from the globally defined `LiveObjectsTypes` interface.
*
* You can specify custom types for LiveObjects by defining a global `LiveObjectsTypes` interface with a `root` property that conforms to {@link LiveMapType}.
*
* Example:
*
* ```typescript
* import { LiveCounter } from 'ably';
*
* type MyRoot = {
* myTypedKey: LiveCounter;
* };
*
* declare global {
* export interface LiveObjectsTypes {
* root: MyRoot;
* }
* }
* ```
*
* @returns A promise which, upon success, will be fulfilled with a {@link LiveMap} object. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error.
*/
getRoot(): Promise<LiveMap>;
getRoot<T extends LiveMapType = DefaultRoot>(): Promise<LiveMap<T>>;
}

declare global {
/**
* A globally defined interface that allows users to define custom types for LiveObjects.
*/
export interface LiveObjectsTypes {
[key: string]: unknown;
}
}

/**
* Represents the type of data stored in a {@link LiveMap}.
* It maps string keys to scalar values ({@link StateValue}), or other LiveObjects.
*/
export type LiveMapType = { [key: string]: StateValue | LiveMap<LiveMapType> | LiveCounter | undefined };

/**
* The default type for the `root` object in the LiveObjects, based on the globally defined {@link LiveObjectsTypes} interface.
*
* - If no custom types are provided in `LiveObjectsTypes`, defaults to an untyped root map representation using the {@link LiveMapType} interface.
* - If a `root` type exists in `LiveObjectsTypes` and conforms to the {@link LiveMapType} interface, it is used as the type for the `root` object.
* - If the provided `root` type does not match {@link LiveMapType}, a type error message is returned.
*/
export type DefaultRoot =
// we need a way to know when no types were provided by the user.
// we expect a "root" property to be set on LiveObjectsTypes interface, e.g. it won't be "unknown" anymore
unknown extends LiveObjectsTypes['root']
? LiveMapType // no custom types provided; use the default untyped map representation for the root
: LiveObjectsTypes['root'] extends LiveMapType
? LiveObjectsTypes['root'] // "root" property exists, and it is of an expected type, we can use this interface for the root object in LiveObjects.
: `Provided type definition for the "root" object in LiveObjectsTypes is not of an expected LiveMapType`;

/**
* The `LiveMap` class represents a key/value map data structure, similar to a JavaScript Map, where all changes are synchronized across clients in realtime.
* Conflict-free resolution for updates follows Last Write Wins (LWW) semantics, meaning that if two clients update the same key in the map, the update with the most recent timestamp wins.
*
* Keys must be strings. Values can be another Live Object, or a primitive type, such as a string, number, boolean, or binary data (see {@link StateValue}).
*/
export declare interface LiveMap extends LiveObject<LiveMapUpdate> {
export declare interface LiveMap<T extends LiveMapType> extends LiveObject<LiveMapUpdate> {
/**
* Returns the value associated with a given key. Returns `undefined` if the key doesn't exist in a map.
*
* @param key - The key to retrieve the value for.
* @returns A {@link LiveObject}, a primitive type (string, number, boolean, or binary data) or `undefined` if the key doesn't exist in a map.
*/
get(key: string): LiveObject | StateValue | undefined;
get<TKey extends keyof T & string>(key: TKey): T[TKey];

/**
* Returns the number of key/value pairs in the map.
Expand Down
8 changes: 4 additions & 4 deletions src/plugins/liveobjects/livemap.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import deepEqual from 'deep-equal';

import type * as API from '../../../ably';
import { LiveObject, LiveObjectData, LiveObjectUpdate, LiveObjectUpdateNoop } from './liveobject';
import { LiveObjects } from './liveobjects';
import {
Expand All @@ -13,7 +14,6 @@ import {
StateValue,
} from './statemessage';
import { DefaultTimeserial, Timeserial } from './timeserial';
import { LiveMapType } from './typings';

export interface ObjectIdStateData {
/** A reference to another state object, used to support composable state objects. */
Expand Down Expand Up @@ -46,7 +46,7 @@ export interface LiveMapUpdate extends LiveObjectUpdate {
update: { [keyName: string]: 'updated' | 'removed' };
}

export class LiveMap<T extends LiveMapType> extends LiveObject<LiveMapData, LiveMapUpdate> {
export class LiveMap<T extends API.LiveMapType> extends LiveObject<LiveMapData, LiveMapUpdate> {
constructor(
liveObjects: LiveObjects,
private _semantics: MapSemantics,
Expand All @@ -60,7 +60,7 @@ export class LiveMap<T extends LiveMapType> extends LiveObject<LiveMapData, Live
*
* @internal
*/
static zeroValue<T extends LiveMapType>(liveobjects: LiveObjects, objectId: string): LiveMap<T> {
static zeroValue<T extends API.LiveMapType>(liveobjects: LiveObjects, objectId: string): LiveMap<T> {
return new LiveMap<T>(liveobjects, MapSemantics.LWW, objectId);
}

Expand All @@ -70,7 +70,7 @@ export class LiveMap<T extends LiveMapType> extends LiveObject<LiveMapData, Live
*
* @internal
*/
static fromStateObject<T extends LiveMapType>(liveobjects: LiveObjects, stateObject: StateObject): LiveMap<T> {
static fromStateObject<T extends API.LiveMapType>(liveobjects: LiveObjects, stateObject: StateObject): LiveMap<T> {
const obj = new LiveMap<T>(liveobjects, stateObject.map?.semantics!, stateObject.objectId);
obj.overrideWithStateObject(stateObject);
return obj;
Expand Down
3 changes: 1 addition & 2 deletions src/plugins/liveobjects/liveobjects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { LiveObject, LiveObjectUpdate } from './liveobject';
import { LiveObjectsPool, ROOT_OBJECT_ID } from './liveobjectspool';
import { StateMessage } from './statemessage';
import { SyncLiveObjectsDataPool } from './syncliveobjectsdatapool';
import { DefaultRoot, LiveMapType } from './typings';

enum LiveObjectsEvents {
SyncCompleted = 'SyncCompleted',
Expand Down Expand Up @@ -42,7 +41,7 @@ export class LiveObjects {
* A user can provide an explicit type for the getRoot method to explicitly set the LiveObjects type structure on this particular channel.
* This is useful when working with LiveObjects on multiple channels with different underlying data.
*/
async getRoot<T extends LiveMapType = DefaultRoot>(): Promise<LiveMap<T>> {
async getRoot<T extends API.LiveMapType = API.DefaultRoot>(): Promise<LiveMap<T>> {
// SYNC is currently in progress, wait for SYNC sequence to finish
if (this._syncInProgress) {
await this._eventEmitter.once(LiveObjectsEvents.SyncCompleted);
Expand Down
22 changes: 0 additions & 22 deletions src/plugins/liveobjects/typings.ts

This file was deleted.

3 changes: 2 additions & 1 deletion typedoc.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@
"TypeAlias",
"Variable",
"Namespace"
]
],
"intentionallyNotExported": ["__global.LiveObjectsTypes"]
}

0 comments on commit 73c8abc

Please sign in to comment.