Skip to content

refactor(presence): Use branded JsonDeserialized type internally #24641

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 81 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
164b448
typebrand the JsonDeserialized type
tylerbutler May 15, 2025
0b1bd66
updates
tylerbutler May 15, 2025
b6eca1f
compiles
tylerbutler May 15, 2025
2ba11eb
Remove T &
tylerbutler May 15, 2025
d6b85e7
feedback
tylerbutler May 16, 2025
e716480
rename types to behopefully clearer
tylerbutler May 16, 2025
d727ad0
feedback
tylerbutler May 16, 2025
653320a
cleanup
tylerbutler May 16, 2025
1c54887
Merge branch 'main' into presence-type-branding
tylerbutler May 28, 2025
90063a9
cleanup
tylerbutler May 28, 2025
6d571db
fix
tylerbutler May 28, 2025
af6b5a8
Merge branch 'main' into presence-type-branding
tylerbutler May 28, 2025
6cd5054
rm invalid docs changes
tylerbutler May 28, 2025
019df60
formatting
tylerbutler May 28, 2025
212c77f
tsignore some errors
tylerbutler May 28, 2025
0e6c144
wip
tylerbutler May 28, 2025
4f12474
wip
tylerbutler May 28, 2025
c9cdf28
Merge branch 'main' into presence-type-branding
tylerbutler May 28, 2025
3d1435f
wip
tylerbutler May 28, 2025
40483c0
formatting
tylerbutler May 29, 2025
a00e294
feat(client-core-interfaces): opaque JSON placeholder types
jason-ha May 27, 2025
e82eb79
wip
tylerbutler May 29, 2025
c1d1f3f
Merge branch 'core/opaque-json-types' into presence-type-branding
tylerbutler May 29, 2025
fb60bcf
revert container-runtime-defs changes
tylerbutler May 29, 2025
34011f4
8 errors in 3 files
tylerbutler May 30, 2025
4cc3372
rename functions
tylerbutler May 30, 2025
0a52598
8 errors, 3 files (still)
tylerbutler May 30, 2025
9043b6c
rename and reorg
tylerbutler May 30, 2025
f2aa271
Merge branch 'main' into presence-type-branding
tylerbutler Jun 2, 2025
31a3478
improvement(client-core-interfaces): list OpaqueJsonSerializable as d…
jason-ha Jun 2, 2025
abe20ee
workarounds
tylerbutler Jun 2, 2025
dbf0696
improvement(client-presence): use opaque json internally
jason-ha May 30, 2025
502e103
Merge commit 'dbf0696243' into presence-type-branding
tylerbutler Jun 2, 2025
bc2ba69
source compiles but tests don't
tylerbutler Jun 2, 2025
1cf182c
source compiles
tylerbutler Jun 2, 2025
a5a0947
improvement(client-presence): reduce data in requirement to JsonSeria…
jason-ha Jun 2, 2025
c04d35a
revert
tylerbutler Jun 2, 2025
823262c
Merge branch 'core/promote-OpaqueJsonSerializable-for-degenerate-case…
tylerbutler Jun 2, 2025
37eb489
test(client-presence): updates for Opaque Json use
jason-ha Jun 2, 2025
90bf2a2
docs(client-presence): update API reports
jason-ha Jun 3, 2025
3d3c0c8
test(client-presence): fix for JsonSerializable change
jason-ha Jun 3, 2025
674cd09
Merge branch 'presence/use-opaque-json' into presence-type-branding
tylerbutler Jun 3, 2025
d38d500
use ts-expect-error instead of as any
tylerbutler Jun 3, 2025
99aa26a
Merge branch 'core/promote-OpaqueJsonSerializable-for-degenerate-case…
tylerbutler Jun 3, 2025
6f966b4
remove ts-expect-error that is no longer triggered
tylerbutler Jun 3, 2025
08ff94d
merge: 'main' into presence/use-opaque-json
jason-ha Jun 3, 2025
24537dd
test(client-presence): correction after merge
jason-ha Jun 3, 2025
fa24bfd
Merge branch 'main' into presence-type-branding
tylerbutler Jun 3, 2025
2aab49e
fix(client-presence): workaround "system:presence" incompatilities
jason-ha Jun 3, 2025
742981e
Merge branch 'presence/use-opaque-json' into presence-type-branding
tylerbutler Jun 3, 2025
d65c189
test-case ts-expect-error
tylerbutler Jun 3, 2025
28ff0cb
core-interfaces beta/system
tylerbutler Jun 3, 2025
5c68d8b
presence beta/system
tylerbutler Jun 3, 2025
6d8059f
Revert "fix(client-presence): workaround "system:presence" incompatil…
jason-ha Jun 3, 2025
db2039f
rename and cleanup
tylerbutler Jun 3, 2025
f34296d
use new to/from opaque function names
tylerbutler Jun 3, 2025
8d51f71
remove beta flags in types where possible
tylerbutler Jun 3, 2025
9fa9a7c
add back beta
tylerbutler Jun 4, 2025
0650353
imports
tylerbutler Jun 4, 2025
667aebc
fix(client-presence): remove opacity from system datastore
jason-ha Jun 4, 2025
3a86482
fix(client-core-interfaces): support Opaque unknown thru JsonSerializ…
jason-ha Jun 4, 2025
eee8245
fix(client-presence): separate system:presence index from others
jason-ha Jun 4, 2025
6ba5d8c
fix(client-core-interfaces): support Opaque unknown thru JsonSerializ…
jason-ha Jun 4, 2025
e101659
improvement(client-presence): separate system:presence index from others
jason-ha Jun 4, 2025
dd026c0
Merge branch 'core/fix-Opaque-unknown-serializability' into presence-…
tylerbutler Jun 4, 2025
cdd1e04
Merge branch 'presence/improvement-separate-system_presence-from-gene…
tylerbutler Jun 4, 2025
a0d5fd8
wip
tylerbutler Jun 4, 2025
c320220
Merge branch 'main' into presence-type-branding
tylerbutler Jun 4, 2025
90cafd3
fix
tylerbutler Jun 4, 2025
a2dfa90
Merge branch 'presence/use-opaque-json' into presence-type-branding
tylerbutler Jun 4, 2025
6ffdcb4
merge: 'main' into presence/use-opaque-json
jason-ha Jun 4, 2025
c140b04
wip
tylerbutler Jun 4, 2025
8f22012
Merge branch 'presence/use-opaque-json' into presence-type-branding
tylerbutler Jun 4, 2025
77ce46f
cleanup
tylerbutler Jun 4, 2025
a1aa8c8
possible fix
tylerbutler Jun 4, 2025
a7a59a4
updates
tylerbutler Jun 4, 2025
c619299
docs(client-presence): comment additions
jason-ha Jun 4, 2025
cad419d
refactor(client-presence): rename helpers
jason-ha Jun 4, 2025
ab2d5a0
Merge branch 'presence/use-opaque-json' into presence-type-branding
tylerbutler Jun 5, 2025
b6da44a
revert(client-presence): unneeded code changes
jason-ha Jun 6, 2025
9764eaa
Merge branch 'presence/use-opaque-json' into presence-type-branding
tylerbutler Jun 7, 2025
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
18 changes: 12 additions & 6 deletions packages/common/core-interfaces/src/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,14 @@ import type {
// Also not useful, at least in VS Code, as substitution is not made in place.

/**
* @internal
* @beta
* @system
*/
export type DeepReadonly<T> = ExposedDeepReadonly<T>;

/**
* @internal
* @beta
* @system
*/
export type JsonDeserialized<
T,
Expand All @@ -57,7 +59,8 @@ export type JsonDeserialized<
> = ExposedJsonDeserialized<T, Options>;

/**
* @internal
* @beta
* @system
*/
export type JsonSerializable<
T,
Expand All @@ -78,7 +81,8 @@ export type JsonTypeWith<T> = ExposedJsonTypeWith<T>;
export type ReadonlyNonNullJsonObjectWith<T> = ExposedReadonlyNonNullJsonObjectWith<T>;

/**
* @internal
* @beta
* @system
*/
export type OpaqueJsonDeserialized<
T,
Expand All @@ -87,7 +91,8 @@ export type OpaqueJsonDeserialized<
> = ExposedOpaqueJsonDeserialized<T, Option_AllowExactly, Option_AllowExtensionOf>;

/**
* @internal
* @beta
* @system
*/
export type OpaqueJsonSerializable<
T,
Expand All @@ -96,7 +101,8 @@ export type OpaqueJsonSerializable<
> = ExposedOpaqueJsonSerializable<T, Option_AllowExactly, Option_AllowExtensionOf>;

/**
* @internal
* @beta
* @system
*/
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace InternalUtilityTypes {
Expand Down
38 changes: 19 additions & 19 deletions packages/framework/presence/api-report/presence.alpha.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export namespace InternalTypes {
// @system (undocumented)
export interface NotificationType {
// (undocumented)
args: (JsonSerializable<unknown> & JsonDeserialized<unknown>)[];
args: unknown[];
// (undocumented)
name: string;
}
Expand All @@ -109,15 +109,15 @@ export namespace InternalTypes {
}
// @system (undocumented)
export type ValueDirectoryOrState<T> = ValueRequiredState<T> | ValueDirectory<T>;
// @system (undocumented)
// @system
export interface ValueOptionalState<TValue> extends ValueStateMetadata {
// (undocumented)
value?: JsonDeserialized<TValue>;
value?: OpaqueJsonDeserialized<TValue>;
}
// @system (undocumented)
// @system
export interface ValueRequiredState<TValue> extends ValueStateMetadata {
// (undocumented)
value: JsonDeserialized<TValue>;
value: OpaqueJsonDeserialized<TValue>;
}
// @system (undocumented)
export interface ValueStateMetadata {
Expand All @@ -131,23 +131,23 @@ export namespace InternalTypes {
// @alpha @system
export namespace InternalUtilityTypes {
// @system
export type IsNotificationListener<Event> = Event extends (...args: infer P) => void ? InternalUtilityTypes_2.IfSameType<P, JsonSerializable<P> & JsonDeserialized<P>, true, false> : false;
export type IfNotificationListener<Event, IfListener, Else> = Event extends (...args: infer P) => void ? InternalUtilityTypes_2.IfSameType<P, JsonSerializable<P>, IfListener, Else> : Else;
// @system
export type JsonDeserializedParameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? JsonDeserialized<P> : never;
export type JsonDeserializedParameters<T extends (...args: any[]) => any> = T extends (...args: infer P) => any ? JsonDeserialized<P> : never;
// @system
export type JsonSerializableParameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? JsonSerializable<P> : never;
export type JsonSerializableParameters<T extends (...args: any[]) => any> = T extends (...args: infer P) => any ? JsonSerializable<P> : never;
// @system
export type NotificationListeners<E> = {
[P in string & keyof E as IsNotificationListener<E[P]> extends true ? P : never]: E[P];
[P in keyof E as IfNotificationListener<E[P], P, never>]: E[P];
};
}

// @beta
export function latest<T extends object | null, Key extends string = string>(args: LatestArguments<T>): InternalTypes.ManagerFactory<Key, InternalTypes.ValueRequiredState<T>, LatestRaw<T>>;

// @beta
// @beta @input
export interface LatestArguments<T extends object | null> {
local: JsonSerializable<T> & JsonDeserialized<T> & (object | null);
local: JsonSerializable<T>;
settings?: BroadcastControlSettings | undefined;
}

Expand All @@ -165,10 +165,10 @@ export interface LatestData<T> {
// @beta
export function latestMap<T, Keys extends string | number = string | number, RegistrationKey extends string = string>(args?: LatestMapArguments<T, Keys>): InternalTypes.ManagerFactory<RegistrationKey, InternalTypes.MapValueState<T, Keys>, LatestMapRaw<T, Keys>>;

// @beta
// @beta @input
export interface LatestMapArguments<T, Keys extends string | number = string | number> {
local?: {
[K in Keys]: JsonSerializable<T> & JsonDeserialized<T>;
[K in Keys]: JsonSerializable<T>;
};
settings?: BroadcastControlSettings | undefined;
}
Expand Down Expand Up @@ -210,7 +210,7 @@ export interface LatestMapRawEvents<T, K extends string | number> {
}) => void;
// @eventProperty
localItemUpdated: (updatedItem: {
value: DeepReadonly<JsonSerializable<T> & JsonDeserialized<T>>;
value: DeepReadonly<JsonSerializable<T>>;
key: K;
}) => void;
// @eventProperty
Expand All @@ -235,24 +235,24 @@ export interface LatestRaw<T> {
getRemotes(): IterableIterator<LatestClientData<T>>;
getStateAttendees(): Attendee[];
get local(): DeepReadonly<JsonDeserialized<T>>;
set local(value: JsonSerializable<T> & JsonDeserialized<T>);
set local(value: JsonSerializable<T>);
readonly presence: Presence;
}

// @beta @sealed
export interface LatestRawEvents<T> {
// @eventProperty
localUpdated: (update: {
value: DeepReadonly<JsonSerializable<T> & JsonDeserialized<T>>;
value: DeepReadonly<JsonSerializable<T>>;
}) => void;
// @eventProperty
remoteUpdated: (update: LatestClientData<T>) => void;
}

// @alpha @sealed
export interface NotificationEmitter<E extends InternalUtilityTypes.NotificationListeners<E>> {
broadcast<K extends string & keyof InternalUtilityTypes.NotificationListeners<E>>(notificationName: K, ...args: Parameters<E[K]>): void;
unicast<K extends string & keyof InternalUtilityTypes.NotificationListeners<E>>(notificationName: K, targetAttendee: Attendee, ...args: Parameters<E[K]>): void;
broadcast<K extends keyof InternalUtilityTypes.NotificationListeners<E>>(notificationName: K, ...args: Parameters<E[K]>): void;
unicast<K extends keyof InternalUtilityTypes.NotificationListeners<E>>(notificationName: K, targetAttendee: Attendee, ...args: Parameters<E[K]>): void;
}

// @alpha @sealed
Expand Down Expand Up @@ -339,7 +339,7 @@ export interface StateMap<K extends string | number, V> {
get(key: K): DeepReadonly<JsonDeserialized<V>> | undefined;
has(key: K): boolean;
keys(): IterableIterator<K>;
set(key: K, value: JsonSerializable<V> & JsonDeserialized<V>): this;
set(key: K, value: JsonSerializable<V>): this;
readonly size: number;
}

Expand Down
26 changes: 13 additions & 13 deletions packages/framework/presence/api-report/presence.beta.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export namespace InternalTypes {
// @system (undocumented)
export interface NotificationType {
// (undocumented)
args: (JsonSerializable<unknown> & JsonDeserialized<unknown>)[];
args: unknown[];
// (undocumented)
name: string;
}
Expand All @@ -96,15 +96,15 @@ export namespace InternalTypes {
}
// @system (undocumented)
export type ValueDirectoryOrState<T> = ValueRequiredState<T> | ValueDirectory<T>;
// @system (undocumented)
// @system
export interface ValueOptionalState<TValue> extends ValueStateMetadata {
// (undocumented)
value?: JsonDeserialized<TValue>;
value?: OpaqueJsonDeserialized<TValue>;
}
// @system (undocumented)
// @system
export interface ValueRequiredState<TValue> extends ValueStateMetadata {
// (undocumented)
value: JsonDeserialized<TValue>;
value: OpaqueJsonDeserialized<TValue>;
}
// @system (undocumented)
export interface ValueStateMetadata {
Expand All @@ -118,9 +118,9 @@ export namespace InternalTypes {
// @beta
export function latest<T extends object | null, Key extends string = string>(args: LatestArguments<T>): InternalTypes.ManagerFactory<Key, InternalTypes.ValueRequiredState<T>, LatestRaw<T>>;

// @beta
// @beta @input
export interface LatestArguments<T extends object | null> {
local: JsonSerializable<T> & JsonDeserialized<T> & (object | null);
local: JsonSerializable<T>;
settings?: BroadcastControlSettings | undefined;
}

Expand All @@ -138,10 +138,10 @@ export interface LatestData<T> {
// @beta
export function latestMap<T, Keys extends string | number = string | number, RegistrationKey extends string = string>(args?: LatestMapArguments<T, Keys>): InternalTypes.ManagerFactory<RegistrationKey, InternalTypes.MapValueState<T, Keys>, LatestMapRaw<T, Keys>>;

// @beta
// @beta @input
export interface LatestMapArguments<T, Keys extends string | number = string | number> {
local?: {
[K in Keys]: JsonSerializable<T> & JsonDeserialized<T>;
[K in Keys]: JsonSerializable<T>;
};
settings?: BroadcastControlSettings | undefined;
}
Expand Down Expand Up @@ -183,7 +183,7 @@ export interface LatestMapRawEvents<T, K extends string | number> {
}) => void;
// @eventProperty
localItemUpdated: (updatedItem: {
value: DeepReadonly<JsonSerializable<T> & JsonDeserialized<T>>;
value: DeepReadonly<JsonSerializable<T>>;
key: K;
}) => void;
// @eventProperty
Expand All @@ -208,15 +208,15 @@ export interface LatestRaw<T> {
getRemotes(): IterableIterator<LatestClientData<T>>;
getStateAttendees(): Attendee[];
get local(): DeepReadonly<JsonDeserialized<T>>;
set local(value: JsonSerializable<T> & JsonDeserialized<T>);
set local(value: JsonSerializable<T>);
readonly presence: Presence;
}

// @beta @sealed
export interface LatestRawEvents<T> {
// @eventProperty
localUpdated: (update: {
value: DeepReadonly<JsonSerializable<T> & JsonDeserialized<T>>;
value: DeepReadonly<JsonSerializable<T>>;
}) => void;
// @eventProperty
remoteUpdated: (update: LatestClientData<T>) => void;
Expand Down Expand Up @@ -257,7 +257,7 @@ export interface StateMap<K extends string | number, V> {
get(key: K): DeepReadonly<JsonDeserialized<V>> | undefined;
has(key: K): boolean;
keys(): IterableIterator<K>;
set(key: K, value: JsonSerializable<V> & JsonDeserialized<V>): this;
set(key: K, value: JsonSerializable<V>): this;
readonly size: number;
}

Expand Down
29 changes: 22 additions & 7 deletions packages/framework/presence/src/exposedInternalTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@
* Licensed under the MIT License.
*/

import type {
JsonDeserialized,
JsonSerializable,
} from "@fluidframework/core-interfaces/internal/exposedUtilityTypes";
import type { OpaqueJsonDeserialized } from "@fluidframework/core-interfaces/internal";

/**
* Collection of value types that are not intended to be used/imported
Expand All @@ -26,17 +23,35 @@ export namespace InternalTypes {
}

/**
* `ValueRequiredState` is used to represent a state that may have a value.
* And it includes standard metadata.
*
* @remarks
* See {@link InternalTypes.ValueRequiredState}.
*
* @system
*/
export interface ValueOptionalState<TValue> extends ValueStateMetadata {
value?: JsonDeserialized<TValue>;
value?: OpaqueJsonDeserialized<TValue>;
}

/**
* `ValueRequiredState` is used to represent a state that must have a value.
* And it includes standard metadata.
*
* @remarks
* The value is wrapped in `OpaqueJsonDeserialized` as uses are expected
* to involve generic or unknown types that will be filtered. It is here
* mostly as a convenience to the many such uses that would otherwise
* need to specify some wrapper themselves.
*
* For known cases, construct a custom interface that extends
* {@link InternalTypes.ValueStateMetadata}.
*
* @system
*/
export interface ValueRequiredState<TValue> extends ValueStateMetadata {
value: JsonDeserialized<TValue>;
value: OpaqueJsonDeserialized<TValue>;
}

/**
Expand Down Expand Up @@ -121,6 +136,6 @@ export namespace InternalTypes {
*/
export interface NotificationType {
name: string;
args: (JsonSerializable<unknown> & JsonDeserialized<unknown>)[];
args: unknown[];
}
}
24 changes: 11 additions & 13 deletions packages/framework/presence/src/exposedUtilityTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type {
InternalUtilityTypes as CoreInternalUtilityTypes,
JsonDeserialized,
JsonSerializable,
} from "@fluidframework/core-interfaces/internal/exposedUtilityTypes";
} from "@fluidframework/core-interfaces/internal";

/**
* Collection of utility types that are not intended to be used/imported
Expand All @@ -19,18 +19,16 @@ import type {
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace InternalUtilityTypes {
/**
* `true` iff the given type is an acceptable shape for a notification.
* `IfListener` iff the given type is an acceptable shape for a notification.
* `Else` otherwise.
*
* @system
*/
export type IsNotificationListener<Event> = Event extends (...args: infer P) => void
? CoreInternalUtilityTypes.IfSameType<
P,
JsonSerializable<P> & JsonDeserialized<P>,
true,
false
>
: false;
export type IfNotificationListener<Event, IfListener, Else> = Event extends (
...args: infer P
) => void
? CoreInternalUtilityTypes.IfSameType<P, JsonSerializable<P>, IfListener, Else>
: Else;

/**
* Used to specify the kinds of notifications emitted by a {@link NotificationListenable}.
Expand All @@ -52,15 +50,15 @@ export namespace InternalUtilityTypes {
* @system
*/
export type NotificationListeners<E> = {
[P in string & keyof E as IsNotificationListener<E[P]> extends true ? P : never]: E[P];
[P in keyof E as IfNotificationListener<E[P], P, never>]: E[P];
};

/**
* {@link @fluidframework/core-interfaces#JsonDeserialized} version of the parameters of a function.
*
* @system
*/
export type JsonDeserializedParameters<T extends (...args: any) => any> = T extends (
export type JsonDeserializedParameters<T extends (...args: any[]) => any> = T extends (
...args: infer P
) => any
? JsonDeserialized<P>
Expand All @@ -71,7 +69,7 @@ export namespace InternalUtilityTypes {
*
* @system
*/
export type JsonSerializableParameters<T extends (...args: any) => any> = T extends (
export type JsonSerializableParameters<T extends (...args: any[]) => any> = T extends (
...args: infer P
) => any
? JsonSerializable<P>
Expand Down
Loading
Loading