Skip to content

Tree's enum schema utility are now beta #24749

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

Merged
merged 2 commits into from
Jun 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 8 additions & 0 deletions .changeset/every-cases-enjoy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"fluid-framework": minor
"@fluidframework/tree": minor
"__section": tree
---
Tree's enum schema utility are now beta

Check failure on line 6 in .changeset/every-cases-enjoy.md

View workflow job for this annotation

GitHub Actions / vale

[vale] reported by reviewdog 🐶 [Vale.Spelling] Did you really mean 'enum'? Raw Output: {"message": "[Vale.Spelling] Did you really mean 'enum'?", "location": {"path": ".changeset/every-cases-enjoy.md", "range": {"start": {"line": 6, "column": 8}}}, "severity": "ERROR"}

The functions [singletonSchema](https://fluidframework.com/docs/api/tree/#singletonschema-function), [adaptEnum](https://fluidframework.com/docs/api/tree/#adaptenum-function) and [enumFromStrings](https://fluidframework.com/docs/api/tree/#enumfromstrings-function) are now `@beta` instead of `@alpha`.

Check failure on line 8 in .changeset/every-cases-enjoy.md

View workflow job for this annotation

GitHub Actions / vale

[vale] reported by reviewdog 🐶 [Vale.Spelling] Did you really mean 'singletonSchema'? Raw Output: {"message": "[Vale.Spelling] Did you really mean 'singletonSchema'?", "location": {"path": ".changeset/every-cases-enjoy.md", "range": {"start": {"line": 8, "column": 16}}}, "severity": "ERROR"}

Check failure on line 8 in .changeset/every-cases-enjoy.md

View workflow job for this annotation

GitHub Actions / vale

[vale] reported by reviewdog 🐶 [Vale.Spelling] Did you really mean 'adaptEnum'? Raw Output: {"message": "[Vale.Spelling] Did you really mean 'adaptEnum'?", "location": {"path": ".changeset/every-cases-enjoy.md", "range": {"start": {"line": 8, "column": 103}}}, "severity": "ERROR"}
10 changes: 5 additions & 5 deletions packages/dds/tree/api-report/tree.alpha.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

```ts

// @alpha
// @beta
export function adaptEnum<TScope extends string, const TEnum extends Record<string, string | number>>(factory: SchemaFactory<TScope>, members: TEnum): (<TValue extends TEnum[keyof TEnum]>(value: TValue) => TValue extends unknown ? TreeNode & {
readonly value: TValue;
} : never) & { readonly [Property in keyof TEnum]: TreeNodeSchemaClass<ScopedSchemaName<TScope, TEnum[Property]>, NodeKind.Object, TreeNode & {
Expand Down Expand Up @@ -139,7 +139,7 @@ export function createSimpleTreeIndex<TFieldSchema extends ImplicitFieldSchema,
interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> {
}

// @alpha
// @beta
export function enumFromStrings<TScope extends string, const Members extends readonly string[]>(factory: SchemaFactory<TScope>, members: Members): (<TValue extends Members[number]>(value: TValue) => TValue extends unknown ? TreeNode & {
readonly value: TValue;
} : never) & { [Index in Extract<keyof Members, `${number}`> extends `${infer N extends number}` ? N : never as Members[Index]]: TreeNodeSchemaClass<ScopedSchemaName<TScope, Members[Index]>, NodeKind.Object, TreeNode & {
Expand Down Expand Up @@ -620,7 +620,7 @@ export type Off = Off_2;
// @alpha
export function persistedToSimpleSchema(persisted: JsonCompatible, options: ICodecOptions): SimpleTreeSchema;

// @alpha @system
// @beta @system
export type PopUnion<Union, AsOverloadedFunction = UnionToIntersection<Union extends unknown ? (f: Union) => void : never>> = AsOverloadedFunction extends (a: infer First) => void ? First : never;

// @alpha @system
Expand Down Expand Up @@ -897,7 +897,7 @@ export interface SimpleTreeSchema {
readonly root: SimpleFieldSchema;
}

// @alpha
// @beta
export function singletonSchema<TScope extends string, TName extends string | number>(factory: SchemaFactory<TScope, TName>, name: TName): TreeNodeSchemaClass<ScopedSchemaName<TScope, TName>, NodeKind.Object, TreeNode & {
readonly value: TName;
}, Record<string, never>, true, Record<string, never>, undefined>;
Expand Down Expand Up @@ -1491,7 +1491,7 @@ export type Unhydrated<T> = T;
// @public @system
export type UnionToIntersection<T> = (T extends T ? (k: T) => unknown : never) extends (k: infer U) => unknown ? U : never;

// @alpha
// @beta @system
export type UnionToTuple<Union, A extends unknown[] = [], First = PopUnion<Union>> = IsUnion<Union> extends true ? UnionToTuple<Exclude<Union, First>, [First, ...A]> : [Union, ...A];

// @alpha
Expand Down
33 changes: 33 additions & 0 deletions packages/dds/tree/api-report/tree.beta.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@

```ts

// @beta
export function adaptEnum<TScope extends string, const TEnum extends Record<string, string | number>>(factory: SchemaFactory<TScope>, members: TEnum): (<TValue extends TEnum[keyof TEnum]>(value: TValue) => TValue extends unknown ? TreeNode & {
readonly value: TValue;
} : never) & { readonly [Property in keyof TEnum]: TreeNodeSchemaClass<ScopedSchemaName<TScope, TEnum[Property]>, NodeKind.Object, TreeNode & {
readonly value: TEnum[Property];
}, Record<string, never>, true, Record<string, never>, undefined>; } & {
readonly schema: UnionToTuple<{ readonly [Property in keyof TEnum]: TreeNodeSchemaClass<ScopedSchemaName<TScope, TEnum[Property]>, NodeKind.Object, TreeNode & {
readonly value: TEnum[Property];
}, Record<string, never>, true, Record<string, never>, undefined>; }[keyof TEnum]>;
};

// @public @system
export type AllowedTypes = readonly LazyItem<TreeNodeSchema>[];

Expand Down Expand Up @@ -36,6 +47,17 @@ export interface CommitMetadata {
interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> {
}

// @beta
export function enumFromStrings<TScope extends string, const Members extends readonly string[]>(factory: SchemaFactory<TScope>, members: Members): (<TValue extends Members[number]>(value: TValue) => TValue extends unknown ? TreeNode & {
readonly value: TValue;
} : never) & { [Index in Extract<keyof Members, `${number}`> extends `${infer N extends number}` ? N : never as Members[Index]]: TreeNodeSchemaClass<ScopedSchemaName<TScope, Members[Index]>, NodeKind.Object, TreeNode & {
readonly value: Members[Index];
}, Record<string, never>, true, Record<string, never>, undefined>; } & {
readonly schema: UnionToTuple<Members[number] extends unknown ? { [Index in Extract<keyof Members, `${number}`> extends `${infer N extends number}` ? N : never as Members[Index]]: TreeNodeSchemaClass<ScopedSchemaName<TScope, Members[Index]>, NodeKind.Object, TreeNode & {
readonly value: Members[Index];
}, Record<string, never>, true, Record<string, never>, undefined>; }[Members[number]] : never>;
};

// @public @system
type ExtractItemType<Item extends LazyItem> = Item extends () => infer Result ? Result : Item;

Expand Down Expand Up @@ -242,6 +264,9 @@ export type ObjectFromSchemaRecord<T extends RestrictiveStringRecord<ImplicitFie
// @public @deprecated
export type Off = Off_2;

// @beta @system
export type PopUnion<Union, AsOverloadedFunction = UnionToIntersection<Union extends unknown ? (f: Union) => void : never>> = AsOverloadedFunction extends (a: infer First) => void ? First : never;

// @public @sealed @system
export interface ReadonlyArrayNode<out T = TreeNode | TreeLeafValue> extends ReadonlyArray<T>, Awaited<TreeNode & WithType<string, NodeKind.Array>> {
}
Expand Down Expand Up @@ -377,6 +402,11 @@ export interface SimpleNodeSchemaBase<out TNodeKind extends NodeKind, out TCusto
readonly metadata: NodeSchemaMetadata<TCustomMetadata>;
}

// @beta
export function singletonSchema<TScope extends string, TName extends string | number>(factory: SchemaFactory<TScope, TName>, name: TName): TreeNodeSchemaClass<ScopedSchemaName<TScope, TName>, NodeKind.Object, TreeNode & {
readonly value: TName;
}, Record<string, never>, true, Record<string, never>, undefined>;

// @public @system
export namespace System_Unsafe {
// @system
Expand Down Expand Up @@ -641,6 +671,9 @@ export type Unhydrated<T> = T;
// @public @system
export type UnionToIntersection<T> = (T extends T ? (k: T) => unknown : never) extends (k: infer U) => unknown ? U : never;

// @beta @system
export type UnionToTuple<Union, A extends unknown[] = [], First = PopUnion<Union>> = IsUnion<Union> extends true ? UnionToTuple<Exclude<Union, First>, [First, ...A]> : [Union, ...A];

// @public
export type ValidateRecursiveSchema<T extends ValidateRecursiveSchemaTemplate<T>> = true;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import type { UnionToTuple } from "../../util/index.js";
* This is commonly used in unions when the only information needed is which kind of node the value is.
* Enums are a common example of this pattern.
* @see {@link adaptEnum}
* @alpha
* @beta
*/
// Return type is intentionally derived.
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
Expand Down Expand Up @@ -107,7 +107,7 @@ export function singletonSchema<TScope extends string, TName extends string | nu
* @privateRemarks
* Maybe provide `SchemaFactory.nested` to ease creating nested scopes?
* @see {@link enumFromStrings} for a similar function that works on arrays of strings instead of an enum.
* @alpha
* @beta
*/
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export function adaptEnum<
Expand Down Expand Up @@ -186,7 +186,7 @@ export function adaptEnum<
* class Parent extends schemaFactory.object("Parent", { mode: Mode.schema }) {}
* ```
* @see {@link adaptEnum} for a similar function that works on enums instead of arrays of strings.
* @alpha
* @beta
*/
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export function enumFromStrings<
Expand Down
4 changes: 2 additions & 2 deletions packages/dds/tree/src/util/typeUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ export type UnionToIntersection<T> = (T extends T ? (k: T) => unknown : never) e
*
* @typeparam Union - The union to convert.
* @typeparam AsOverloadedFunction - Implementation detail: do not specify.
* @system @alpha
* @system @beta
*/
export type PopUnion<
Union,
Expand All @@ -189,7 +189,7 @@ export type PopUnion<
*
* https://www.hacklewayne.com/typescript-convert-union-to-tuple-array-yes-but-how and https://catchts.com/union-array both explain the general approach this uses pretty well.
* This implementation is inspired to those, but slightly different in implementation.
* @alpha
* @system @beta
*/
export type UnionToTuple<
Union,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

```ts

// @alpha
// @beta
export function adaptEnum<TScope extends string, const TEnum extends Record<string, string | number>>(factory: SchemaFactory<TScope>, members: TEnum): (<TValue extends TEnum[keyof TEnum]>(value: TValue) => TValue extends unknown ? TreeNode & {
readonly value: TValue;
} : never) & { readonly [Property in keyof TEnum]: TreeNodeSchemaClass<ScopedSchemaName<TScope, TEnum[Property]>, NodeKind.Object, TreeNode & {
Expand Down Expand Up @@ -177,7 +177,7 @@ export function createSimpleTreeIndex<TFieldSchema extends ImplicitFieldSchema,
interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> {
}

// @alpha
// @beta
export function enumFromStrings<TScope extends string, const Members extends readonly string[]>(factory: SchemaFactory<TScope>, members: Members): (<TValue extends Members[number]>(value: TValue) => TValue extends unknown ? TreeNode & {
readonly value: TValue;
} : never) & { [Index in Extract<keyof Members, `${number}`> extends `${infer N extends number}` ? N : never as Members[Index]]: TreeNodeSchemaClass<ScopedSchemaName<TScope, Members[Index]>, NodeKind.Object, TreeNode & {
Expand Down Expand Up @@ -975,7 +975,7 @@ export function onAssertionFailure(handler: (error: Error) => void): () => void;
// @alpha
export function persistedToSimpleSchema(persisted: JsonCompatible, options: ICodecOptions): SimpleTreeSchema;

// @alpha @system
// @beta @system
export type PopUnion<Union, AsOverloadedFunction = UnionToIntersection<Union extends unknown ? (f: Union) => void : never>> = AsOverloadedFunction extends (a: infer First) => void ? First : never;

// @alpha @system
Expand Down Expand Up @@ -1265,7 +1265,7 @@ export interface SimpleTreeSchema {
readonly root: SimpleFieldSchema;
}

// @alpha
// @beta
export function singletonSchema<TScope extends string, TName extends string | number>(factory: SchemaFactory<TScope, TName>, name: TName): TreeNodeSchemaClass<ScopedSchemaName<TScope, TName>, NodeKind.Object, TreeNode & {
readonly value: TName;
}, Record<string, never>, true, Record<string, never>, undefined>;
Expand Down Expand Up @@ -1873,7 +1873,7 @@ export type Unhydrated<T> = T;
// @public @system
export type UnionToIntersection<T> = (T extends T ? (k: T) => unknown : never) extends (k: infer U) => unknown ? U : never;

// @alpha
// @beta @system
export type UnionToTuple<Union, A extends unknown[] = [], First = PopUnion<Union>> = IsUnion<Union> extends true ? UnionToTuple<Exclude<Union, First>, [First, ...A]> : [Union, ...A];

// @alpha
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@

```ts

// @beta
export function adaptEnum<TScope extends string, const TEnum extends Record<string, string | number>>(factory: SchemaFactory<TScope>, members: TEnum): (<TValue extends TEnum[keyof TEnum]>(value: TValue) => TValue extends unknown ? TreeNode & {
readonly value: TValue;
} : never) & { readonly [Property in keyof TEnum]: TreeNodeSchemaClass<ScopedSchemaName<TScope, TEnum[Property]>, NodeKind.Object, TreeNode & {
readonly value: TEnum[Property];
}, Record<string, never>, true, Record<string, never>, undefined>; } & {
readonly schema: UnionToTuple<{ readonly [Property in keyof TEnum]: TreeNodeSchemaClass<ScopedSchemaName<TScope, TEnum[Property]>, NodeKind.Object, TreeNode & {
readonly value: TEnum[Property];
}, Record<string, never>, true, Record<string, never>, undefined>; }[keyof TEnum]>;
};

// @public @system
export type AllowedTypes = readonly LazyItem<TreeNodeSchema>[];

Expand Down Expand Up @@ -71,6 +82,17 @@ export interface ContainerSchema {
interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> {
}

// @beta
export function enumFromStrings<TScope extends string, const Members extends readonly string[]>(factory: SchemaFactory<TScope>, members: Members): (<TValue extends Members[number]>(value: TValue) => TValue extends unknown ? TreeNode & {
readonly value: TValue;
} : never) & { [Index in Extract<keyof Members, `${number}`> extends `${infer N extends number}` ? N : never as Members[Index]]: TreeNodeSchemaClass<ScopedSchemaName<TScope, Members[Index]>, NodeKind.Object, TreeNode & {
readonly value: Members[Index];
}, Record<string, never>, true, Record<string, never>, undefined>; } & {
readonly schema: UnionToTuple<Members[number] extends unknown ? { [Index in Extract<keyof Members, `${number}`> extends `${infer N extends number}` ? N : never as Members[Index]]: TreeNodeSchemaClass<ScopedSchemaName<TScope, Members[Index]>, NodeKind.Object, TreeNode & {
readonly value: Members[Index];
}, Record<string, never>, true, Record<string, never>, undefined>; }[Members[number]] : never>;
};

// @public @sealed
export abstract class ErasedType<out Name = unknown> {
static [Symbol.hasInstance](value: never): value is never;
Expand Down Expand Up @@ -591,6 +613,9 @@ export type ObjectFromSchemaRecord<T extends RestrictiveStringRecord<ImplicitFie
// @public
export type Off = () => void;

// @beta @system
export type PopUnion<Union, AsOverloadedFunction = UnionToIntersection<Union extends unknown ? (f: Union) => void : never>> = AsOverloadedFunction extends (a: infer First) => void ? First : never;

// @public @sealed @system
export interface ReadonlyArrayNode<out T = TreeNode | TreeLeafValue> extends ReadonlyArray<T>, Awaited<TreeNode & WithType<string, NodeKind.Array>> {
}
Expand Down Expand Up @@ -739,6 +764,11 @@ export interface SimpleNodeSchemaBase<out TNodeKind extends NodeKind, out TCusto
readonly metadata: NodeSchemaMetadata<TCustomMetadata>;
}

// @beta
export function singletonSchema<TScope extends string, TName extends string | number>(factory: SchemaFactory<TScope, TName>, name: TName): TreeNodeSchemaClass<ScopedSchemaName<TScope, TName>, NodeKind.Object, TreeNode & {
readonly value: TName;
}, Record<string, never>, true, Record<string, never>, undefined>;

// @public @system
export namespace System_Unsafe {
// @system
Expand Down Expand Up @@ -1017,6 +1047,9 @@ export type Unhydrated<T> = T;
// @public @system
export type UnionToIntersection<T> = (T extends T ? (k: T) => unknown : never) extends (k: infer U) => unknown ? U : never;

// @beta @system
export type UnionToTuple<Union, A extends unknown[] = [], First = PopUnion<Union>> = IsUnion<Union> extends true ? UnionToTuple<Exclude<Union, First>, [First, ...A]> : [Union, ...A];

// @public
export type ValidateRecursiveSchema<T extends ValidateRecursiveSchemaTemplate<T>> = true;

Expand Down
Loading