From 8005fd23ac05d853044528e7da1f2857b0454253 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Thu, 5 Dec 2024 06:20:38 +0000 Subject: [PATCH] Update `LiveMap.get` to always return `undefined` value as an option for keys that reference other LiveObjects Referenced live objects can be not valid (haven't seen a create op) and we should not surface such objects to the end user and return `undefined` instead. --- ably.d.ts | 6 +++--- src/plugins/liveobjects/livemap.ts | 2 +- test/package/browser/template/src/ably.config.d.ts | 4 ++-- test/package/browser/template/src/index-liveobjects.ts | 4 +++- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/ably.d.ts b/ably.d.ts index 5d1aa1f82..cd60057e7 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -2103,12 +2103,12 @@ export type DefaultRoot = */ export declare interface LiveMap extends LiveObject { /** - * Returns the value associated with a given key. Returns `undefined` if the key doesn't exist in a map. + * Returns the value associated with a given key. Returns `undefined` if the key doesn't exist in a map or if the associated {@link LiveObject} has been deleted. * * @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. + * @returns A {@link LiveObject}, a primitive type (string, number, boolean, or binary data) or `undefined` if the key doesn't exist in a map or the associated {@link LiveObject} has been deleted. */ - get(key: TKey): T[TKey]; + get(key: TKey): T[TKey] extends StateValue ? T[TKey] : T[TKey] | undefined; /** * Returns the number of key/value pairs in the map. diff --git a/src/plugins/liveobjects/livemap.ts b/src/plugins/liveobjects/livemap.ts index e4b1200ef..43eef94f6 100644 --- a/src/plugins/liveobjects/livemap.ts +++ b/src/plugins/liveobjects/livemap.ts @@ -85,7 +85,7 @@ export class LiveMap extends LiveObject(key: TKey): T[TKey] { + get(key: TKey): T[TKey] extends StateValue ? T[TKey] : T[TKey] | undefined { const element = this._dataRef.data.get(key); if (element === undefined) { diff --git a/test/package/browser/template/src/ably.config.d.ts b/test/package/browser/template/src/ably.config.d.ts index e5bca7718..3b3c69ddb 100644 --- a/test/package/browser/template/src/ably.config.d.ts +++ b/test/package/browser/template/src/ably.config.d.ts @@ -5,13 +5,13 @@ type CustomRoot = { stringKey: string; booleanKey: boolean; couldBeUndefined?: string; - mapKey?: LiveMap<{ + mapKey: LiveMap<{ foo: 'bar'; nestedMap?: LiveMap<{ baz: 'qux'; }>; }>; - counterKey?: LiveCounter; + counterKey: LiveCounter; }; declare global { diff --git a/test/package/browser/template/src/index-liveobjects.ts b/test/package/browser/template/src/index-liveobjects.ts index 1cd27b021..67c3e5ba0 100644 --- a/test/package/browser/template/src/index-liveobjects.ts +++ b/test/package/browser/template/src/index-liveobjects.ts @@ -30,8 +30,10 @@ globalThis.testAblyPackage = async function () { const aBoolean: boolean = root.get('booleanKey'); const couldBeUndefined: string | undefined = root.get('couldBeUndefined'); // live objects on a root: + // LiveMap.get can still return undefined for LiveObject typed properties even if custom typings have them as non-optional. + // objects can be tombstoned/non-valid and result in the undefined value const counter: Ably.LiveCounter | undefined = root.get('counterKey'); - const map: LiveObjectsTypes['root']['mapKey'] = root.get('mapKey'); + const map: LiveObjectsTypes['root']['mapKey'] | undefined = root.get('mapKey'); // check string literal types works // need to use nullish coalescing as we didn't actually create any data on the root, // so the next calls would fail. we only need to check that TypeScript types work