diff --git a/packages/framework/presence/api-report/presence.alpha.api.md b/packages/framework/presence/api-report/presence.alpha.api.md index f206e4cb5c03..73bc1226dc98 100644 --- a/packages/framework/presence/api-report/presence.alpha.api.md +++ b/packages/framework/presence/api-report/presence.alpha.api.md @@ -43,10 +43,10 @@ export interface ISessionClient(initialValue: JsonSerializable & JsonDeserialized & object, controls?: LatestValueControls): InternalTypes.ManagerFactory, LatestValueManager>; +export function Latest(initialValue: JsonSerializable & JsonDeserialized & object, controls?: LatestValueControls): InternalTypes.ManagerFactory, LatestValueManager>; // @alpha -export function LatestMap(initialValues?: { +export function LatestMap(initialValues?: { [K in Keys]: JsonSerializable & JsonDeserialized; }, controls?: LatestValueControls): InternalTypes.ManagerFactory, LatestMapValueManager>; @@ -76,7 +76,7 @@ export interface LatestMapValueClientData { clients(): ISessionClient[]; - clientValue(client: ISessionClient): LatestMapValueClientData; + clientValue(client: ISessionClient): ReadonlyMap>; clientValues(): IterableIterator>; readonly controls: LatestValueControls; readonly events: ISubscribable>; @@ -143,7 +143,7 @@ export interface NotificationEmitter, Key extends string>(initialSubscriptions: NotificationSubscriptions): InternalTypes.ManagerFactory, NotificationsManager>; +export function Notifications, Key extends string = string>(initialSubscriptions: NotificationSubscriptions): InternalTypes.ManagerFactory, NotificationsManager>; // @alpha @sealed export interface NotificationsManager> { diff --git a/packages/framework/presence/src/internalTypes.ts b/packages/framework/presence/src/internalTypes.ts index 73bb981a9aca..f94e262658f8 100644 --- a/packages/framework/presence/src/internalTypes.ts +++ b/packages/framework/presence/src/internalTypes.ts @@ -18,6 +18,15 @@ export interface ClientRecord< [ClientSessionId: ClientSessionId]: Exclude; } +/** + * Object.entries retyped to support branded string-based keys. + * + * @internal + */ +export const brandedObjectEntries = Object.entries as ( + o: Record, +) => [K, T][]; + /** * @internal */ diff --git a/packages/framework/presence/src/latestMapValueManager.ts b/packages/framework/presence/src/latestMapValueManager.ts index b345f76e7d8e..0dfbaec23bd2 100644 --- a/packages/framework/presence/src/latestMapValueManager.ts +++ b/packages/framework/presence/src/latestMapValueManager.ts @@ -306,9 +306,7 @@ export interface LatestMapValueManager( - client: ISessionClient, - ): LatestMapValueClientData; + clientValue(client: ISessionClient): ReadonlyMap>; } class LatestMapValueManagerImpl< @@ -343,8 +341,15 @@ class LatestMapValueManagerImpl< public readonly local: ValueMap; - public clientValues(): IterableIterator> { - throw new Error("Method not implemented."); + public *clientValues(): IterableIterator> { + const allKnownStates = this.datastore.knownValues(this.key); + for (const clientSessionId of Object.keys(allKnownStates.states)) { + if (clientSessionId !== allKnownStates.self) { + const client = this.datastore.lookupClient(clientSessionId); + const items = this.clientValue(client); + yield { client, items }; + } + } } public clients(): ISessionClient[] { @@ -354,11 +359,9 @@ class LatestMapValueManagerImpl< .map((clientSessionId) => this.datastore.lookupClient(clientSessionId)); } - public clientValue( - client: SpecificSessionClient, - ): LatestMapValueClientData { + public clientValue(client: ISessionClient): ReadonlyMap> { const allKnownStates = this.datastore.knownValues(this.key); - const clientSessionId: SpecificSessionClientId = client.sessionId; + const clientSessionId = client.sessionId; if (!(clientSessionId in allKnownStates.states)) { throw new Error("No entry for client"); } @@ -373,7 +376,7 @@ class LatestMapValueManagerImpl< }); } } - return { client, items }; + return items; } public update( @@ -441,8 +444,8 @@ class LatestMapValueManagerImpl< */ export function LatestMap< T extends object, - RegistrationKey extends string, Keys extends string | number = string | number, + RegistrationKey extends string = string, >( initialValues?: { [K in Keys]: JsonSerializable & JsonDeserialized; diff --git a/packages/framework/presence/src/latestValueManager.ts b/packages/framework/presence/src/latestValueManager.ts index 3ef4d9978f3d..a2ff2a0bb779 100644 --- a/packages/framework/presence/src/latestValueManager.ts +++ b/packages/framework/presence/src/latestValueManager.ts @@ -4,6 +4,7 @@ */ import type { ValueManager } from "./internalTypes.js"; +import { brandedObjectEntries } from "./internalTypes.js"; import type { LatestValueControls } from "./latestValueControls.js"; import { LatestValueControl } from "./latestValueControls.js"; import type { LatestValueClientData, LatestValueData } from "./latestValueTypes.js"; @@ -105,8 +106,17 @@ class LatestValueManagerImpl this.datastore.localUpdate(this.key, this.value, /* forceUpdate */ false); } - public clientValues(): IterableIterator> { - throw new Error("Method not implemented."); + public *clientValues(): IterableIterator> { + const allKnownStates = this.datastore.knownValues(this.key); + for (const [clientSessionId, value] of brandedObjectEntries(allKnownStates.states)) { + if (clientSessionId !== allKnownStates.self) { + yield { + client: this.datastore.lookupClient(clientSessionId), + value: value.value, + metadata: { revision: value.rev, timestamp: value.timestamp }, + }; + } + } } public clients(): ISessionClient[] { @@ -153,7 +163,7 @@ class LatestValueManagerImpl * * @alpha */ -export function Latest( +export function Latest( initialValue: JsonSerializable & JsonDeserialized & object, controls?: LatestValueControls, ): InternalTypes.ManagerFactory< diff --git a/packages/framework/presence/src/notificationsManager.ts b/packages/framework/presence/src/notificationsManager.ts index a43579486004..13f7a7c05eeb 100644 --- a/packages/framework/presence/src/notificationsManager.ts +++ b/packages/framework/presence/src/notificationsManager.ts @@ -167,7 +167,7 @@ class NotificationsManagerImpl< Key, InternalTypes.ValueRequiredState >, - _initialSubscriptions: NotificationSubscriptions, + _initialSubscriptions: Partial>, ) {} public update( @@ -182,11 +182,15 @@ class NotificationsManagerImpl< /** * Factory for creating a {@link NotificationsManager}. * + * @remarks + * Typescript inference for `Notifications` is not working correctly yet. + * Explicitly specify generics to make result types usable. + * * @alpha */ export function Notifications< T extends InternalUtilityTypes.NotificationEvents, - Key extends string, + Key extends string = string, >( initialSubscriptions: NotificationSubscriptions, ): InternalTypes.ManagerFactory< diff --git a/packages/framework/presence/src/presenceStates.ts b/packages/framework/presence/src/presenceStates.ts index a10cd0ad8e3e..d1012b8f83b2 100644 --- a/packages/framework/presence/src/presenceStates.ts +++ b/packages/framework/presence/src/presenceStates.ts @@ -8,6 +8,7 @@ import { assert } from "@fluidframework/core-utils/internal"; import type { ClientConnectionId } from "./baseTypes.js"; import type { InternalTypes } from "./exposedInternalTypes.js"; import type { ClientRecord } from "./internalTypes.js"; +import { brandedObjectEntries } from "./internalTypes.js"; import type { ClientSessionId, ISessionClient } from "./presence.js"; import { handleFromDatastore, type StateDatastore } from "./stateDatastore.js"; import type { PresenceStates, PresenceStatesMethods, PresenceStatesSchema } from "./types.js"; @@ -192,13 +193,6 @@ export function mergeUntrackedDatastore( } } -/** - * Object.entries retyped to support branded string-based keys. - */ -const brandedObjectEntries = Object.entries as ( - o: Record, -) => [K, T][]; - class PresenceStatesImpl implements PresenceStatesInternal, diff --git a/packages/framework/presence/src/stateDatastore.ts b/packages/framework/presence/src/stateDatastore.ts index 63072611e7a4..fa8e45057a20 100644 --- a/packages/framework/presence/src/stateDatastore.ts +++ b/packages/framework/presence/src/stateDatastore.ts @@ -12,16 +12,16 @@ import type { ClientSessionId, ISessionClient } from "./presence.js"; // TValue extends InternalTypes.ValueDirectoryOrState = InternalTypes.ValueDirectoryOrState, // > = TValue extends InternalTypes.ValueDirectoryOrState ? InternalTypes.ValueDirectoryOrState : never; -/** - * @internal - */ -export interface StateDatastoreSchema { - // This type is not precise. It may - // need to be replaced with PresenceStates schema pattern - // similar to what is commented out. - [key: string]: InternalTypes.ValueDirectoryOrState; - // [key: string]: StateDatastoreSchemaNode; -} +// /** +// * @internal +// */ +// export interface StateDatastoreSchema { +// // This type is not precise. It may +// // need to be replaced with PresenceStates schema pattern +// // similar to what is commented out. +// [key: string]: InternalTypes.ValueDirectoryOrState; +// // [key: string]: StateDatastoreSchemaNode; +// } /** * @internal diff --git a/packages/framework/presence/src/test/latestMapValueManager.spec.ts b/packages/framework/presence/src/test/latestMapValueManager.spec.ts index a286b7403b96..6536a480061b 100644 --- a/packages/framework/presence/src/test/latestMapValueManager.spec.ts +++ b/packages/framework/presence/src/test/latestMapValueManager.spec.ts @@ -53,7 +53,7 @@ export function checkCompiles(): void { tilt?: number; } - map.add("pointers", LatestMap({})); + map.add("pointers", LatestMap({})); const pointers = map.pointers; const localPointers = pointers.local; @@ -75,8 +75,8 @@ export function checkCompiles(): void { pointerItemUpdatedOff(); for (const client of pointers.clients()) { - const clientData = pointers.clientValue(client); - for (const [key, { value }] of clientData.items.entries()) { + const items = pointers.clientValue(client); + for (const [key, { value }] of items.entries()) { logClientValue({ client, key, value }); } } diff --git a/packages/framework/presence/src/test/notificationsManager.spec.ts b/packages/framework/presence/src/test/notificationsManager.spec.ts index 27f89bd0c9a4..21ea140f8746 100644 --- a/packages/framework/presence/src/test/notificationsManager.spec.ts +++ b/packages/framework/presence/src/test/notificationsManager.spec.ts @@ -23,12 +23,9 @@ export function checkCompiles(): void { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const presence = {} as IPresence; const notificationsWorkspace = presence.getNotifications("name:testNotificationWorkspace", { - chat: Notifications< - { - msg: (message: string) => void; - }, - string - >({ + chat: Notifications<{ + msg: (message: string) => void; + }>({ msg: (client: ISessionClient, message: string) => { console.log(`${client.sessionId} says, "${message}"`); },