Skip to content

Commit

Permalink
feat(tree): Support field schema metadata (microsoft#20980)
Browse files Browse the repository at this point in the history
Adds runtime-only field schema metadata support. End-user customizable,
but provides some built-in properties that can be used by the system.
For now, this amounts to a "description" field that is mapped to the
"description" JSON Schema property when generating JSON Schema via
`getJsonSchema`.
  • Loading branch information
Josmithr authored Sep 17, 2024
1 parent 8b46119 commit a74dc57
Show file tree
Hide file tree
Showing 24 changed files with 290 additions and 74 deletions.
22 changes: 16 additions & 6 deletions packages/dds/tree/api-report/tree.alpha.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,21 +48,29 @@ export enum FieldKind {
}

// @public
export interface FieldProps {
export interface FieldProps<TCustomMetadata = unknown> {
readonly defaultProvider?: DefaultProvider;
readonly key?: string;
readonly metadata?: FieldSchemaMetadata<TCustomMetadata>;
}

// @public @sealed
export class FieldSchema<out Kind extends FieldKind = FieldKind, out Types extends ImplicitAllowedTypes = ImplicitAllowedTypes> {
export class FieldSchema<out Kind extends FieldKind = FieldKind, out Types extends ImplicitAllowedTypes = ImplicitAllowedTypes, out TCustomMetadata = unknown> {
readonly allowedTypes: Types;
get allowedTypeSet(): ReadonlySet<TreeNodeSchema>;
readonly kind: Kind;
readonly props?: FieldProps | undefined;
get metadata(): FieldSchemaMetadata<TCustomMetadata> | undefined;
readonly props?: FieldProps<TCustomMetadata> | undefined;
readonly requiresValue: boolean;
protected _typeCheck: MakeNominal;
}

// @public @sealed
export interface FieldSchemaMetadata<TCustomMetadata = unknown> {
custom?: TCustomMetadata;
description?: string | undefined;
}

// @public
export interface FieldSchemaUnsafe<out Kind extends FieldKind, out Types extends Unenforced<ImplicitAllowedTypes>> extends FieldSchema<Kind, any> {
readonly allowedTypes: Types;
Expand Down Expand Up @@ -205,8 +213,10 @@ export interface JsonArrayNodeSchema extends JsonNodeSchemaBase<NodeKind.Array,

// @alpha @sealed
export type JsonFieldSchema = {
readonly description?: string | undefined;
} & ({
readonly anyOf: JsonSchemaRef[];
} | JsonSchemaRef;
} | JsonSchemaRef);

// @alpha @sealed
export interface JsonLeafNodeSchema extends JsonNodeSchemaBase<NodeKind.Leaf, JsonLeafSchemaType> {
Expand Down Expand Up @@ -413,9 +423,9 @@ export class SchemaFactory<out TScope extends string | undefined = string | unde
readonly number: TreeNodeSchema<"com.fluidframework.leaf.number", NodeKind.Leaf, number, number>;
object<const Name extends TName, const T extends RestrictiveReadonlyRecord<string, ImplicitFieldSchema>>(name: Name, fields: T): TreeNodeSchemaClass<ScopedSchemaName<TScope, Name>, NodeKind.Object, TreeObjectNode<T, ScopedSchemaName<TScope, Name>>, object & InsertableObjectFromSchemaRecord<T>, true, T>;
objectRecursive<const Name extends TName, const T extends Unenforced<RestrictiveReadonlyRecord<string, ImplicitFieldSchema>>>(name: Name, t: T): TreeNodeSchemaClass<ScopedSchemaName<TScope, Name>, NodeKind.Object, TreeObjectNodeUnsafe<T, ScopedSchemaName<TScope, Name>>, object & { readonly [Property in keyof T as FieldHasDefaultUnsafe<T[Property]> extends false ? Property : never]: InsertableTreeFieldFromImplicitFieldUnsafe<T[Property]>; } & { readonly [Property_1 in keyof T as FieldHasDefaultUnsafe<T[Property_1]> extends true ? Property_1 : never]?: InsertableTreeFieldFromImplicitFieldUnsafe<T[Property_1]> | undefined; }, false, T>;
optional<const T extends ImplicitAllowedTypes>(t: T, props?: Omit<FieldProps, "defaultProvider">): FieldSchema<FieldKind.Optional, T>;
optional<const T extends ImplicitAllowedTypes, const TCustomMetadata = unknown>(t: T, props?: Omit<FieldProps<TCustomMetadata>, "defaultProvider">): FieldSchema<FieldKind.Optional, T, TCustomMetadata>;
optionalRecursive<const T extends Unenforced<ImplicitAllowedTypes>>(t: T, props?: Omit<FieldProps, "defaultProvider">): FieldSchemaUnsafe<FieldKind.Optional, T>;
required<const T extends ImplicitAllowedTypes>(t: T, props?: Omit<FieldProps, "defaultProvider">): FieldSchema<FieldKind.Required, T>;
required<const T extends ImplicitAllowedTypes, const TCustomMetadata = unknown>(t: T, props?: Omit<FieldProps<TCustomMetadata>, "defaultProvider">): FieldSchema<FieldKind.Required, T, TCustomMetadata>;
requiredRecursive<const T extends Unenforced<ImplicitAllowedTypes>>(t: T, props?: Omit<FieldProps, "defaultProvider">): FieldSchemaUnsafe<FieldKind.Required, T>;
// (undocumented)
readonly scope: TScope;
Expand Down
18 changes: 13 additions & 5 deletions packages/dds/tree/api-report/tree.beta.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,21 +48,29 @@ export enum FieldKind {
}

// @public
export interface FieldProps {
export interface FieldProps<TCustomMetadata = unknown> {
readonly defaultProvider?: DefaultProvider;
readonly key?: string;
readonly metadata?: FieldSchemaMetadata<TCustomMetadata>;
}

// @public @sealed
export class FieldSchema<out Kind extends FieldKind = FieldKind, out Types extends ImplicitAllowedTypes = ImplicitAllowedTypes> {
export class FieldSchema<out Kind extends FieldKind = FieldKind, out Types extends ImplicitAllowedTypes = ImplicitAllowedTypes, out TCustomMetadata = unknown> {
readonly allowedTypes: Types;
get allowedTypeSet(): ReadonlySet<TreeNodeSchema>;
readonly kind: Kind;
readonly props?: FieldProps | undefined;
get metadata(): FieldSchemaMetadata<TCustomMetadata> | undefined;
readonly props?: FieldProps<TCustomMetadata> | undefined;
readonly requiresValue: boolean;
protected _typeCheck: MakeNominal;
}

// @public @sealed
export interface FieldSchemaMetadata<TCustomMetadata = unknown> {
custom?: TCustomMetadata;
description?: string | undefined;
}

// @public
export interface FieldSchemaUnsafe<out Kind extends FieldKind, out Types extends Unenforced<ImplicitAllowedTypes>> extends FieldSchema<Kind, any> {
readonly allowedTypes: Types;
Expand Down Expand Up @@ -347,9 +355,9 @@ export class SchemaFactory<out TScope extends string | undefined = string | unde
readonly number: TreeNodeSchema<"com.fluidframework.leaf.number", NodeKind.Leaf, number, number>;
object<const Name extends TName, const T extends RestrictiveReadonlyRecord<string, ImplicitFieldSchema>>(name: Name, fields: T): TreeNodeSchemaClass<ScopedSchemaName<TScope, Name>, NodeKind.Object, TreeObjectNode<T, ScopedSchemaName<TScope, Name>>, object & InsertableObjectFromSchemaRecord<T>, true, T>;
objectRecursive<const Name extends TName, const T extends Unenforced<RestrictiveReadonlyRecord<string, ImplicitFieldSchema>>>(name: Name, t: T): TreeNodeSchemaClass<ScopedSchemaName<TScope, Name>, NodeKind.Object, TreeObjectNodeUnsafe<T, ScopedSchemaName<TScope, Name>>, object & { readonly [Property in keyof T as FieldHasDefaultUnsafe<T[Property]> extends false ? Property : never]: InsertableTreeFieldFromImplicitFieldUnsafe<T[Property]>; } & { readonly [Property_1 in keyof T as FieldHasDefaultUnsafe<T[Property_1]> extends true ? Property_1 : never]?: InsertableTreeFieldFromImplicitFieldUnsafe<T[Property_1]> | undefined; }, false, T>;
optional<const T extends ImplicitAllowedTypes>(t: T, props?: Omit<FieldProps, "defaultProvider">): FieldSchema<FieldKind.Optional, T>;
optional<const T extends ImplicitAllowedTypes, const TCustomMetadata = unknown>(t: T, props?: Omit<FieldProps<TCustomMetadata>, "defaultProvider">): FieldSchema<FieldKind.Optional, T, TCustomMetadata>;
optionalRecursive<const T extends Unenforced<ImplicitAllowedTypes>>(t: T, props?: Omit<FieldProps, "defaultProvider">): FieldSchemaUnsafe<FieldKind.Optional, T>;
required<const T extends ImplicitAllowedTypes>(t: T, props?: Omit<FieldProps, "defaultProvider">): FieldSchema<FieldKind.Required, T>;
required<const T extends ImplicitAllowedTypes, const TCustomMetadata = unknown>(t: T, props?: Omit<FieldProps<TCustomMetadata>, "defaultProvider">): FieldSchema<FieldKind.Required, T, TCustomMetadata>;
requiredRecursive<const T extends Unenforced<ImplicitAllowedTypes>>(t: T, props?: Omit<FieldProps, "defaultProvider">): FieldSchemaUnsafe<FieldKind.Required, T>;
// (undocumented)
readonly scope: TScope;
Expand Down
18 changes: 13 additions & 5 deletions packages/dds/tree/api-report/tree.legacy.alpha.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,21 +48,29 @@ export enum FieldKind {
}

// @public
export interface FieldProps {
export interface FieldProps<TCustomMetadata = unknown> {
readonly defaultProvider?: DefaultProvider;
readonly key?: string;
readonly metadata?: FieldSchemaMetadata<TCustomMetadata>;
}

// @public @sealed
export class FieldSchema<out Kind extends FieldKind = FieldKind, out Types extends ImplicitAllowedTypes = ImplicitAllowedTypes> {
export class FieldSchema<out Kind extends FieldKind = FieldKind, out Types extends ImplicitAllowedTypes = ImplicitAllowedTypes, out TCustomMetadata = unknown> {
readonly allowedTypes: Types;
get allowedTypeSet(): ReadonlySet<TreeNodeSchema>;
readonly kind: Kind;
readonly props?: FieldProps | undefined;
get metadata(): FieldSchemaMetadata<TCustomMetadata> | undefined;
readonly props?: FieldProps<TCustomMetadata> | undefined;
readonly requiresValue: boolean;
protected _typeCheck: MakeNominal;
}

// @public @sealed
export interface FieldSchemaMetadata<TCustomMetadata = unknown> {
custom?: TCustomMetadata;
description?: string | undefined;
}

// @public
export interface FieldSchemaUnsafe<out Kind extends FieldKind, out Types extends Unenforced<ImplicitAllowedTypes>> extends FieldSchema<Kind, any> {
readonly allowedTypes: Types;
Expand Down Expand Up @@ -342,9 +350,9 @@ export class SchemaFactory<out TScope extends string | undefined = string | unde
readonly number: TreeNodeSchema<"com.fluidframework.leaf.number", NodeKind.Leaf, number, number>;
object<const Name extends TName, const T extends RestrictiveReadonlyRecord<string, ImplicitFieldSchema>>(name: Name, fields: T): TreeNodeSchemaClass<ScopedSchemaName<TScope, Name>, NodeKind.Object, TreeObjectNode<T, ScopedSchemaName<TScope, Name>>, object & InsertableObjectFromSchemaRecord<T>, true, T>;
objectRecursive<const Name extends TName, const T extends Unenforced<RestrictiveReadonlyRecord<string, ImplicitFieldSchema>>>(name: Name, t: T): TreeNodeSchemaClass<ScopedSchemaName<TScope, Name>, NodeKind.Object, TreeObjectNodeUnsafe<T, ScopedSchemaName<TScope, Name>>, object & { readonly [Property in keyof T as FieldHasDefaultUnsafe<T[Property]> extends false ? Property : never]: InsertableTreeFieldFromImplicitFieldUnsafe<T[Property]>; } & { readonly [Property_1 in keyof T as FieldHasDefaultUnsafe<T[Property_1]> extends true ? Property_1 : never]?: InsertableTreeFieldFromImplicitFieldUnsafe<T[Property_1]> | undefined; }, false, T>;
optional<const T extends ImplicitAllowedTypes>(t: T, props?: Omit<FieldProps, "defaultProvider">): FieldSchema<FieldKind.Optional, T>;
optional<const T extends ImplicitAllowedTypes, const TCustomMetadata = unknown>(t: T, props?: Omit<FieldProps<TCustomMetadata>, "defaultProvider">): FieldSchema<FieldKind.Optional, T, TCustomMetadata>;
optionalRecursive<const T extends Unenforced<ImplicitAllowedTypes>>(t: T, props?: Omit<FieldProps, "defaultProvider">): FieldSchemaUnsafe<FieldKind.Optional, T>;
required<const T extends ImplicitAllowedTypes>(t: T, props?: Omit<FieldProps, "defaultProvider">): FieldSchema<FieldKind.Required, T>;
required<const T extends ImplicitAllowedTypes, const TCustomMetadata = unknown>(t: T, props?: Omit<FieldProps<TCustomMetadata>, "defaultProvider">): FieldSchema<FieldKind.Required, T, TCustomMetadata>;
requiredRecursive<const T extends Unenforced<ImplicitAllowedTypes>>(t: T, props?: Omit<FieldProps, "defaultProvider">): FieldSchemaUnsafe<FieldKind.Required, T>;
// (undocumented)
readonly scope: TScope;
Expand Down
18 changes: 13 additions & 5 deletions packages/dds/tree/api-report/tree.legacy.public.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,21 +48,29 @@ export enum FieldKind {
}

// @public
export interface FieldProps {
export interface FieldProps<TCustomMetadata = unknown> {
readonly defaultProvider?: DefaultProvider;
readonly key?: string;
readonly metadata?: FieldSchemaMetadata<TCustomMetadata>;
}

// @public @sealed
export class FieldSchema<out Kind extends FieldKind = FieldKind, out Types extends ImplicitAllowedTypes = ImplicitAllowedTypes> {
export class FieldSchema<out Kind extends FieldKind = FieldKind, out Types extends ImplicitAllowedTypes = ImplicitAllowedTypes, out TCustomMetadata = unknown> {
readonly allowedTypes: Types;
get allowedTypeSet(): ReadonlySet<TreeNodeSchema>;
readonly kind: Kind;
readonly props?: FieldProps | undefined;
get metadata(): FieldSchemaMetadata<TCustomMetadata> | undefined;
readonly props?: FieldProps<TCustomMetadata> | undefined;
readonly requiresValue: boolean;
protected _typeCheck: MakeNominal;
}

// @public @sealed
export interface FieldSchemaMetadata<TCustomMetadata = unknown> {
custom?: TCustomMetadata;
description?: string | undefined;
}

// @public
export interface FieldSchemaUnsafe<out Kind extends FieldKind, out Types extends Unenforced<ImplicitAllowedTypes>> extends FieldSchema<Kind, any> {
readonly allowedTypes: Types;
Expand Down Expand Up @@ -342,9 +350,9 @@ export class SchemaFactory<out TScope extends string | undefined = string | unde
readonly number: TreeNodeSchema<"com.fluidframework.leaf.number", NodeKind.Leaf, number, number>;
object<const Name extends TName, const T extends RestrictiveReadonlyRecord<string, ImplicitFieldSchema>>(name: Name, fields: T): TreeNodeSchemaClass<ScopedSchemaName<TScope, Name>, NodeKind.Object, TreeObjectNode<T, ScopedSchemaName<TScope, Name>>, object & InsertableObjectFromSchemaRecord<T>, true, T>;
objectRecursive<const Name extends TName, const T extends Unenforced<RestrictiveReadonlyRecord<string, ImplicitFieldSchema>>>(name: Name, t: T): TreeNodeSchemaClass<ScopedSchemaName<TScope, Name>, NodeKind.Object, TreeObjectNodeUnsafe<T, ScopedSchemaName<TScope, Name>>, object & { readonly [Property in keyof T as FieldHasDefaultUnsafe<T[Property]> extends false ? Property : never]: InsertableTreeFieldFromImplicitFieldUnsafe<T[Property]>; } & { readonly [Property_1 in keyof T as FieldHasDefaultUnsafe<T[Property_1]> extends true ? Property_1 : never]?: InsertableTreeFieldFromImplicitFieldUnsafe<T[Property_1]> | undefined; }, false, T>;
optional<const T extends ImplicitAllowedTypes>(t: T, props?: Omit<FieldProps, "defaultProvider">): FieldSchema<FieldKind.Optional, T>;
optional<const T extends ImplicitAllowedTypes, const TCustomMetadata = unknown>(t: T, props?: Omit<FieldProps<TCustomMetadata>, "defaultProvider">): FieldSchema<FieldKind.Optional, T, TCustomMetadata>;
optionalRecursive<const T extends Unenforced<ImplicitAllowedTypes>>(t: T, props?: Omit<FieldProps, "defaultProvider">): FieldSchemaUnsafe<FieldKind.Optional, T>;
required<const T extends ImplicitAllowedTypes>(t: T, props?: Omit<FieldProps, "defaultProvider">): FieldSchema<FieldKind.Required, T>;
required<const T extends ImplicitAllowedTypes, const TCustomMetadata = unknown>(t: T, props?: Omit<FieldProps<TCustomMetadata>, "defaultProvider">): FieldSchema<FieldKind.Required, T, TCustomMetadata>;
requiredRecursive<const T extends Unenforced<ImplicitAllowedTypes>>(t: T, props?: Omit<FieldProps, "defaultProvider">): FieldSchemaUnsafe<FieldKind.Required, T>;
// (undocumented)
readonly scope: TScope;
Expand Down
18 changes: 13 additions & 5 deletions packages/dds/tree/api-report/tree.public.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,21 +48,29 @@ export enum FieldKind {
}

// @public
export interface FieldProps {
export interface FieldProps<TCustomMetadata = unknown> {
readonly defaultProvider?: DefaultProvider;
readonly key?: string;
readonly metadata?: FieldSchemaMetadata<TCustomMetadata>;
}

// @public @sealed
export class FieldSchema<out Kind extends FieldKind = FieldKind, out Types extends ImplicitAllowedTypes = ImplicitAllowedTypes> {
export class FieldSchema<out Kind extends FieldKind = FieldKind, out Types extends ImplicitAllowedTypes = ImplicitAllowedTypes, out TCustomMetadata = unknown> {
readonly allowedTypes: Types;
get allowedTypeSet(): ReadonlySet<TreeNodeSchema>;
readonly kind: Kind;
readonly props?: FieldProps | undefined;
get metadata(): FieldSchemaMetadata<TCustomMetadata> | undefined;
readonly props?: FieldProps<TCustomMetadata> | undefined;
readonly requiresValue: boolean;
protected _typeCheck: MakeNominal;
}

// @public @sealed
export interface FieldSchemaMetadata<TCustomMetadata = unknown> {
custom?: TCustomMetadata;
description?: string | undefined;
}

// @public
export interface FieldSchemaUnsafe<out Kind extends FieldKind, out Types extends Unenforced<ImplicitAllowedTypes>> extends FieldSchema<Kind, any> {
readonly allowedTypes: Types;
Expand Down Expand Up @@ -342,9 +350,9 @@ export class SchemaFactory<out TScope extends string | undefined = string | unde
readonly number: TreeNodeSchema<"com.fluidframework.leaf.number", NodeKind.Leaf, number, number>;
object<const Name extends TName, const T extends RestrictiveReadonlyRecord<string, ImplicitFieldSchema>>(name: Name, fields: T): TreeNodeSchemaClass<ScopedSchemaName<TScope, Name>, NodeKind.Object, TreeObjectNode<T, ScopedSchemaName<TScope, Name>>, object & InsertableObjectFromSchemaRecord<T>, true, T>;
objectRecursive<const Name extends TName, const T extends Unenforced<RestrictiveReadonlyRecord<string, ImplicitFieldSchema>>>(name: Name, t: T): TreeNodeSchemaClass<ScopedSchemaName<TScope, Name>, NodeKind.Object, TreeObjectNodeUnsafe<T, ScopedSchemaName<TScope, Name>>, object & { readonly [Property in keyof T as FieldHasDefaultUnsafe<T[Property]> extends false ? Property : never]: InsertableTreeFieldFromImplicitFieldUnsafe<T[Property]>; } & { readonly [Property_1 in keyof T as FieldHasDefaultUnsafe<T[Property_1]> extends true ? Property_1 : never]?: InsertableTreeFieldFromImplicitFieldUnsafe<T[Property_1]> | undefined; }, false, T>;
optional<const T extends ImplicitAllowedTypes>(t: T, props?: Omit<FieldProps, "defaultProvider">): FieldSchema<FieldKind.Optional, T>;
optional<const T extends ImplicitAllowedTypes, const TCustomMetadata = unknown>(t: T, props?: Omit<FieldProps<TCustomMetadata>, "defaultProvider">): FieldSchema<FieldKind.Optional, T, TCustomMetadata>;
optionalRecursive<const T extends Unenforced<ImplicitAllowedTypes>>(t: T, props?: Omit<FieldProps, "defaultProvider">): FieldSchemaUnsafe<FieldKind.Optional, T>;
required<const T extends ImplicitAllowedTypes>(t: T, props?: Omit<FieldProps, "defaultProvider">): FieldSchema<FieldKind.Required, T>;
required<const T extends ImplicitAllowedTypes, const TCustomMetadata = unknown>(t: T, props?: Omit<FieldProps<TCustomMetadata>, "defaultProvider">): FieldSchema<FieldKind.Required, T, TCustomMetadata>;
requiredRecursive<const T extends Unenforced<ImplicitAllowedTypes>>(t: T, props?: Omit<FieldProps, "defaultProvider">): FieldSchemaUnsafe<FieldKind.Required, T>;
// (undocumented)
readonly scope: TScope;
Expand Down
12 changes: 11 additions & 1 deletion packages/dds/tree/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,17 @@
}
},
"typeValidation": {
"broken": {},
"broken": {
"Interface_FieldSchemaUnsafe": {
"forwardCompat": false
},
"Interface_ITreeViewConfiguration": {
"forwardCompat": false
},
"TypeAlias_ImplicitFieldSchema": {
"forwardCompat": false
}
},
"entrypoint": "internal"
}
}
1 change: 1 addition & 0 deletions packages/dds/tree/src/core/schema-stored/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ export interface SchemaPolicy {
*/
export interface TreeFieldStoredSchema {
readonly kind: FieldKindIdentifier;

/**
* The set of allowed child types.
* If not specified, types are unconstrained.
Expand Down
1 change: 1 addition & 0 deletions packages/dds/tree/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export {
type TreeLeafValue,
FieldKind,
FieldSchema,
type FieldSchemaMetadata,
type ImplicitAllowedTypes,
type InsertableTreeFieldFromImplicitField,
type InsertableTypedNode,
Expand Down
12 changes: 10 additions & 2 deletions packages/dds/tree/src/simple-tree/api/jsonSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,14 @@ export type JsonNodeSchema =
* @sealed
* @alpha
*/
export type JsonFieldSchema =
export type JsonFieldSchema = {
/**
* Description of the field.
* @remarks Derived from {@link FieldSchemaMetadata.description}.
* @see {@link https://json-schema.org/draft/2020-12/json-schema-validation#name-title-and-description}
*/
readonly description?: string | undefined;
} & (
| {
/**
* The kinds of items allowed under the field, for polymorphic types.
Expand All @@ -192,7 +199,8 @@ export type JsonFieldSchema =
*/
readonly anyOf: JsonSchemaRef[];
}
| JsonSchemaRef;
| JsonSchemaRef
);

/**
* {@link https://json-schema.org/draft/2020-12/json-schema-core | JSON Schema} representation of a tree schema.
Expand Down
Loading

0 comments on commit a74dc57

Please sign in to comment.