diff --git a/.changeset/lazy-lies-enjoy.md b/.changeset/lazy-lies-enjoy.md new file mode 100644 index 000000000000..73d787037c17 --- /dev/null +++ b/.changeset/lazy-lies-enjoy.md @@ -0,0 +1,20 @@ +--- +"@fluidframework/core-interfaces": minor +"@fluidframework/map": minor +"__section": legacy +--- + +Add legacy beta map compatibility interfaces + +New legacy beta map interfaces make it possible to type legacy map-like DDS APIs against Fluid's stable map abstraction while preserving compatibility with JavaScript `Map` consumers. + +```typescript +import type { FluidMapLegacy } from "@fluidframework/core-interfaces/legacy"; +import type { IDirectoryBeta, ISharedMapBeta } from "@fluidframework/map/legacy"; + +declare const directory: IDirectoryBeta; +declare const sharedMap: ISharedMapBeta; + +const directoryMap: FluidMapLegacy = directory; +const sharedMapAsMap: Map = sharedMap; +``` diff --git a/packages/common/core-interfaces/api-report/core-interfaces.legacy.alpha.api.md b/packages/common/core-interfaces/api-report/core-interfaces.legacy.alpha.api.md index ef43fde81f7b..b2d24d73890d 100644 --- a/packages/common/core-interfaces/api-report/core-interfaces.legacy.alpha.api.md +++ b/packages/common/core-interfaces/api-report/core-interfaces.legacy.alpha.api.md @@ -81,6 +81,14 @@ export interface FluidMap extends FluidReadonlyMap { set(key: K, value: V): void; } +// @beta @sealed @legacy +export interface FluidMapLegacy extends FluidMap { + clear(): void; + delete(key: K): boolean; + forEach(callbackfn: (value: V, key: K, map: FluidMapLegacy) => void, thisArg?: any): void; + set(key: K, value: V): this; +} + // @public export type FluidObject = { [P in FluidObjectProviderKeys]?: T[P]; diff --git a/packages/common/core-interfaces/api-report/core-interfaces.legacy.beta.api.md b/packages/common/core-interfaces/api-report/core-interfaces.legacy.beta.api.md index cae6efefbae2..5ed3325f39fd 100644 --- a/packages/common/core-interfaces/api-report/core-interfaces.legacy.beta.api.md +++ b/packages/common/core-interfaces/api-report/core-interfaces.legacy.beta.api.md @@ -68,6 +68,14 @@ export interface FluidMap extends FluidReadonlyMap { set(key: K, value: V): void; } +// @beta @sealed @legacy +export interface FluidMapLegacy extends FluidMap { + clear(): void; + delete(key: K): boolean; + forEach(callbackfn: (value: V, key: K, map: FluidMapLegacy) => void, thisArg?: any): void; + set(key: K, value: V): this; +} + // @public export type FluidObject = { [P in FluidObjectProviderKeys]?: T[P]; diff --git a/packages/common/core-interfaces/src/fluidMap.ts b/packages/common/core-interfaces/src/fluidMap.ts index fd152e10d208..1da5b8443a6c 100644 --- a/packages/common/core-interfaces/src/fluidMap.ts +++ b/packages/common/core-interfaces/src/fluidMap.ts @@ -149,3 +149,44 @@ export interface FluidMap extends FluidReadonlyMap { */ set(key: K, value: V): void; } + +/** + * Like TypeScript's built in `Map` type, while still extending {@link FluidMap}. + * + * @privateRemarks + * This interface exists for legacy APIs which were already exposed as built-in `Map`-like types. + * New APIs should prefer {@link FluidMap} unless they need to preserve compatibility with an existing `Map` contract. + * + * @sealed @legacy @beta + */ +export interface FluidMapLegacy extends FluidMap { + /** + * Removes all entries from the map. + */ + clear(): void; + + /** + * Executes the provided function once per each key/value pair in the map. + */ + forEach( + callbackfn: (value: V, key: K, map: FluidMapLegacy) => void, + // Typing inherited from Map. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + thisArg?: any, + ): void; + + /** + * Removes the specified element from the map by its key. + * + * @returns `true` if an element existed and has been removed, or `false` if the element does not exist. + */ + delete(key: K): boolean; + + /** + * Adds a new element with a specified key and value to the map. + * If an element with the same key already exists, the element will be updated. + * + * @returns The map itself. + */ + set(key: K, value: V): this; +} diff --git a/packages/common/core-interfaces/src/index.ts b/packages/common/core-interfaces/src/index.ts index 0c4a149ed0b5..ee49488aa196 100644 --- a/packages/common/core-interfaces/src/index.ts +++ b/packages/common/core-interfaces/src/index.ts @@ -11,6 +11,7 @@ export type { FluidIterable, FluidIterableIterator, FluidMap, + FluidMapLegacy, FluidReadonlyMap, } from "./fluidMap.js"; diff --git a/packages/common/core-interfaces/src/test/types/fluidMapTypes.ts b/packages/common/core-interfaces/src/test/types/fluidMapTypes.ts index 94d6a2ee98c8..e7dcef60ff7a 100644 --- a/packages/common/core-interfaces/src/test/types/fluidMapTypes.ts +++ b/packages/common/core-interfaces/src/test/types/fluidMapTypes.ts @@ -7,6 +7,7 @@ import type { FluidIterable, FluidIterableIterator, FluidMap, + FluidMapLegacy, FluidReadonlyMap, } from "../../index.js"; @@ -26,6 +27,26 @@ declare type _fluidMap_to_fluidReadonlyMap = requireTrue< isAssignableTo, FluidReadonlyMap> >; +// FluidMapLegacy extends FluidMap while preserving legacy Map-like mutator returns. +declare type _fluidMapLegacy_to_fluidMap = requireTrue< + isAssignableTo, FluidMap> +>; +declare type _fluidMapLegacy_to_map = requireTrue< + isAssignableTo, Map> +>; +declare type _map_to_fluidMapLegacy = requireTrue< + isAssignableTo, FluidMapLegacy> +>; + +interface LegacyStringMapLike extends Map { + get(key: string): T | undefined; + set(key: string, value: T): this; +} + +declare type _legacyStringMapLike_to_fluidMapLegacy = requireTrue< + isAssignableTo> +>; + // FluidReadonlyMap is assignable to ReadonlyMap (the extra Symbol.toStringTag is compatible). declare type _fluidReadonlyMap_to_readonlyMap = requireTrue< isAssignableTo, ReadonlyMap> diff --git a/packages/dds/map/api-report/map.legacy.beta.api.md b/packages/dds/map/api-report/map.legacy.beta.api.md index 1ed784c556a9..f4b28da8e258 100644 --- a/packages/dds/map/api-report/map.legacy.beta.api.md +++ b/packages/dds/map/api-report/map.legacy.beta.api.md @@ -34,6 +34,12 @@ export interface IDirectory extends Map, IEventProvider; } +// @beta @sealed @legacy +export interface IDirectoryBeta extends Omit>, FluidMapLegacy { + get(key: string): T | undefined; + set(key: string, value: T): this; +} + // @beta @deprecated @legacy export interface IDirectoryDataObject { ci?: ICreateInfo; @@ -91,6 +97,12 @@ export interface ISharedMap extends ISharedObject, Map(key: string, value: T): this; } +// @beta @sealed @legacy +export interface ISharedMapBeta extends Omit>, FluidMapLegacy { + get(key: string): T | undefined; + set(key: string, value: T): this; +} + // @beta @sealed @legacy export interface ISharedMapEvents extends ISharedObjectEvents { (event: "valueChanged", listener: (changed: IValueChanged, local: boolean, target: IEventThisPlaceHolder) => void): any; diff --git a/packages/dds/map/src/index.ts b/packages/dds/map/src/index.ts index e7a39b31df7b..4c01ebb88761 100644 --- a/packages/dds/map/src/index.ts +++ b/packages/dds/map/src/index.ts @@ -17,11 +17,13 @@ export type { IDirectory, + IDirectoryBeta, IDirectoryEvents, IDirectoryValueChanged, ISharedDirectory, ISharedDirectoryEvents, ISharedMap, + ISharedMapBeta, ISharedMapEvents, IValueChanged, } from "./interfaces.js"; diff --git a/packages/dds/map/src/interfaces.ts b/packages/dds/map/src/interfaces.ts index ba4936e316aa..1082bc6fb275 100644 --- a/packages/dds/map/src/interfaces.ts +++ b/packages/dds/map/src/interfaces.ts @@ -9,6 +9,7 @@ import type { IEventProvider, IEventThisPlaceHolder, } from "@fluidframework/core-interfaces"; +import type { FluidMapLegacy } from "@fluidframework/core-interfaces/internal"; import type { ISharedObject, ISharedObjectEvents, @@ -119,6 +120,17 @@ export interface IDirectory getWorkingDirectory(relativePath: string): IDirectory | undefined; } +/** + * Beta version of {@link IDirectory} which uses {@link @fluidframework/core-interfaces/internal#FluidMapLegacy} for its map-like API. + * + * @sealed + * @legacy @beta + */ +export interface IDirectoryBeta + extends Omit, "get" | "set">>, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + Omit, "get" | "set"> {} + /** * Events emitted in response to changes to the directory data. * @@ -378,9 +390,11 @@ export interface ISharedMapEvents extends ISharedObjectEvents { * @sealed * @legacy @beta */ -// TODO: Use `unknown` instead (breaking change). -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export interface ISharedMap extends ISharedObject, Map { +export interface ISharedMap + // TODO: Use `unknown` instead (breaking change). + extends ISharedObject, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + Map { /** * Retrieves the given key from the map if it exists. * @param key - Key to retrieve from @@ -398,3 +412,14 @@ export interface ISharedMap extends ISharedObject, Map(key: string, value: T): this; } + +/** + * Beta version of {@link ISharedMap} which uses {@link @fluidframework/core-interfaces#FluidMapLegacy} for its map-like API. + * + * @sealed + * @legacy @beta + */ +export interface ISharedMapBeta + extends Omit, "get" | "set">>, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + Omit, "get" | "set"> {} diff --git a/packages/dds/map/src/test/types/fluidMapLegacyTypes.ts b/packages/dds/map/src/test/types/fluidMapLegacyTypes.ts new file mode 100644 index 000000000000..d6e00d34047c --- /dev/null +++ b/packages/dds/map/src/test/types/fluidMapLegacyTypes.ts @@ -0,0 +1,38 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import type { requireAssignableTo } from "@fluidframework/build-tools"; +import type { FluidMapLegacy } from "@fluidframework/core-interfaces/legacy"; + +import type { IDirectory, IDirectoryBeta, ISharedMap, ISharedMapBeta } from "../../index.js"; + +declare type _iDirectoryBeta_to_fluidMapLegacy = requireAssignableTo< + IDirectoryBeta, + FluidMapLegacy +>; +declare type _iSharedMapBeta_to_fluidMapLegacy = requireAssignableTo< + ISharedMapBeta, + FluidMapLegacy +>; +declare type _iDirectory_to_map = requireAssignableTo< + IDirectory, + // TODO: Use `unknown` instead (breaking change). + // eslint-disable-next-line @typescript-eslint/no-explicit-any + Map +>; +declare type _iSharedMap_to_map = requireAssignableTo< + ISharedMap, + // TODO: Use `unknown` instead (breaking change). + // eslint-disable-next-line @typescript-eslint/no-explicit-any + Map +>; +declare type _iDirectoryBeta_to_map = requireAssignableTo< + IDirectoryBeta, + Map +>; +declare type _iSharedMapBeta_to_map = requireAssignableTo< + ISharedMapBeta, + Map +>;