diff --git a/packages/dds/tree/api-report/tree.alpha.api.md b/packages/dds/tree/api-report/tree.alpha.api.md index 9180b4bf9796..e83ecf87014f 100644 --- a/packages/dds/tree/api-report/tree.alpha.api.md +++ b/packages/dds/tree/api-report/tree.alpha.api.md @@ -48,21 +48,29 @@ export enum FieldKind { } // @public -export interface FieldProps { +export interface FieldProps { readonly defaultProvider?: DefaultProvider; readonly key?: string; + readonly metadata?: FieldSchemaMetadata; } // @public @sealed -export class FieldSchema { +export class FieldSchema { readonly allowedTypes: Types; get allowedTypeSet(): ReadonlySet; readonly kind: Kind; - readonly props?: FieldProps | undefined; + get metadata(): FieldSchemaMetadata | undefined; + readonly props?: FieldProps | undefined; readonly requiresValue: boolean; protected _typeCheck: MakeNominal; } +// @public @sealed +export interface FieldSchemaMetadata { + custom?: TCustomMetadata; + description?: string | undefined; +} + // @public export interface FieldSchemaUnsafe> extends FieldSchema { readonly allowedTypes: Types; @@ -205,8 +213,10 @@ export interface JsonArrayNodeSchema extends JsonNodeSchemaBase { @@ -413,9 +423,9 @@ export class SchemaFactory; object>(name: Name, fields: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T>; objectRecursive>>(name: Name, t: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNodeUnsafe>, object & { readonly [Property in keyof T as FieldHasDefaultUnsafe extends false ? Property : never]: InsertableTreeFieldFromImplicitFieldUnsafe; } & { readonly [Property_1 in keyof T as FieldHasDefaultUnsafe extends true ? Property_1 : never]?: InsertableTreeFieldFromImplicitFieldUnsafe | undefined; }, false, T>; - optional(t: T, props?: Omit): FieldSchema; + optional(t: T, props?: Omit, "defaultProvider">): FieldSchema; optionalRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; - required(t: T, props?: Omit): FieldSchema; + required(t: T, props?: Omit, "defaultProvider">): FieldSchema; requiredRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; // (undocumented) readonly scope: TScope; diff --git a/packages/dds/tree/api-report/tree.beta.api.md b/packages/dds/tree/api-report/tree.beta.api.md index d7b65e514228..8909eb14b83c 100644 --- a/packages/dds/tree/api-report/tree.beta.api.md +++ b/packages/dds/tree/api-report/tree.beta.api.md @@ -48,21 +48,29 @@ export enum FieldKind { } // @public -export interface FieldProps { +export interface FieldProps { readonly defaultProvider?: DefaultProvider; readonly key?: string; + readonly metadata?: FieldSchemaMetadata; } // @public @sealed -export class FieldSchema { +export class FieldSchema { readonly allowedTypes: Types; get allowedTypeSet(): ReadonlySet; readonly kind: Kind; - readonly props?: FieldProps | undefined; + get metadata(): FieldSchemaMetadata | undefined; + readonly props?: FieldProps | undefined; readonly requiresValue: boolean; protected _typeCheck: MakeNominal; } +// @public @sealed +export interface FieldSchemaMetadata { + custom?: TCustomMetadata; + description?: string | undefined; +} + // @public export interface FieldSchemaUnsafe> extends FieldSchema { readonly allowedTypes: Types; @@ -347,9 +355,9 @@ export class SchemaFactory; object>(name: Name, fields: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T>; objectRecursive>>(name: Name, t: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNodeUnsafe>, object & { readonly [Property in keyof T as FieldHasDefaultUnsafe extends false ? Property : never]: InsertableTreeFieldFromImplicitFieldUnsafe; } & { readonly [Property_1 in keyof T as FieldHasDefaultUnsafe extends true ? Property_1 : never]?: InsertableTreeFieldFromImplicitFieldUnsafe | undefined; }, false, T>; - optional(t: T, props?: Omit): FieldSchema; + optional(t: T, props?: Omit, "defaultProvider">): FieldSchema; optionalRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; - required(t: T, props?: Omit): FieldSchema; + required(t: T, props?: Omit, "defaultProvider">): FieldSchema; requiredRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; // (undocumented) readonly scope: TScope; diff --git a/packages/dds/tree/api-report/tree.legacy.alpha.api.md b/packages/dds/tree/api-report/tree.legacy.alpha.api.md index 4b21d8d2a4f8..9f92c0532de9 100644 --- a/packages/dds/tree/api-report/tree.legacy.alpha.api.md +++ b/packages/dds/tree/api-report/tree.legacy.alpha.api.md @@ -48,21 +48,29 @@ export enum FieldKind { } // @public -export interface FieldProps { +export interface FieldProps { readonly defaultProvider?: DefaultProvider; readonly key?: string; + readonly metadata?: FieldSchemaMetadata; } // @public @sealed -export class FieldSchema { +export class FieldSchema { readonly allowedTypes: Types; get allowedTypeSet(): ReadonlySet; readonly kind: Kind; - readonly props?: FieldProps | undefined; + get metadata(): FieldSchemaMetadata | undefined; + readonly props?: FieldProps | undefined; readonly requiresValue: boolean; protected _typeCheck: MakeNominal; } +// @public @sealed +export interface FieldSchemaMetadata { + custom?: TCustomMetadata; + description?: string | undefined; +} + // @public export interface FieldSchemaUnsafe> extends FieldSchema { readonly allowedTypes: Types; @@ -342,9 +350,9 @@ export class SchemaFactory; object>(name: Name, fields: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T>; objectRecursive>>(name: Name, t: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNodeUnsafe>, object & { readonly [Property in keyof T as FieldHasDefaultUnsafe extends false ? Property : never]: InsertableTreeFieldFromImplicitFieldUnsafe; } & { readonly [Property_1 in keyof T as FieldHasDefaultUnsafe extends true ? Property_1 : never]?: InsertableTreeFieldFromImplicitFieldUnsafe | undefined; }, false, T>; - optional(t: T, props?: Omit): FieldSchema; + optional(t: T, props?: Omit, "defaultProvider">): FieldSchema; optionalRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; - required(t: T, props?: Omit): FieldSchema; + required(t: T, props?: Omit, "defaultProvider">): FieldSchema; requiredRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; // (undocumented) readonly scope: TScope; diff --git a/packages/dds/tree/api-report/tree.legacy.public.api.md b/packages/dds/tree/api-report/tree.legacy.public.api.md index 2deaf1af2f1e..02df2789fc21 100644 --- a/packages/dds/tree/api-report/tree.legacy.public.api.md +++ b/packages/dds/tree/api-report/tree.legacy.public.api.md @@ -48,21 +48,29 @@ export enum FieldKind { } // @public -export interface FieldProps { +export interface FieldProps { readonly defaultProvider?: DefaultProvider; readonly key?: string; + readonly metadata?: FieldSchemaMetadata; } // @public @sealed -export class FieldSchema { +export class FieldSchema { readonly allowedTypes: Types; get allowedTypeSet(): ReadonlySet; readonly kind: Kind; - readonly props?: FieldProps | undefined; + get metadata(): FieldSchemaMetadata | undefined; + readonly props?: FieldProps | undefined; readonly requiresValue: boolean; protected _typeCheck: MakeNominal; } +// @public @sealed +export interface FieldSchemaMetadata { + custom?: TCustomMetadata; + description?: string | undefined; +} + // @public export interface FieldSchemaUnsafe> extends FieldSchema { readonly allowedTypes: Types; @@ -342,9 +350,9 @@ export class SchemaFactory; object>(name: Name, fields: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T>; objectRecursive>>(name: Name, t: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNodeUnsafe>, object & { readonly [Property in keyof T as FieldHasDefaultUnsafe extends false ? Property : never]: InsertableTreeFieldFromImplicitFieldUnsafe; } & { readonly [Property_1 in keyof T as FieldHasDefaultUnsafe extends true ? Property_1 : never]?: InsertableTreeFieldFromImplicitFieldUnsafe | undefined; }, false, T>; - optional(t: T, props?: Omit): FieldSchema; + optional(t: T, props?: Omit, "defaultProvider">): FieldSchema; optionalRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; - required(t: T, props?: Omit): FieldSchema; + required(t: T, props?: Omit, "defaultProvider">): FieldSchema; requiredRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; // (undocumented) readonly scope: TScope; diff --git a/packages/dds/tree/api-report/tree.public.api.md b/packages/dds/tree/api-report/tree.public.api.md index 2deaf1af2f1e..02df2789fc21 100644 --- a/packages/dds/tree/api-report/tree.public.api.md +++ b/packages/dds/tree/api-report/tree.public.api.md @@ -48,21 +48,29 @@ export enum FieldKind { } // @public -export interface FieldProps { +export interface FieldProps { readonly defaultProvider?: DefaultProvider; readonly key?: string; + readonly metadata?: FieldSchemaMetadata; } // @public @sealed -export class FieldSchema { +export class FieldSchema { readonly allowedTypes: Types; get allowedTypeSet(): ReadonlySet; readonly kind: Kind; - readonly props?: FieldProps | undefined; + get metadata(): FieldSchemaMetadata | undefined; + readonly props?: FieldProps | undefined; readonly requiresValue: boolean; protected _typeCheck: MakeNominal; } +// @public @sealed +export interface FieldSchemaMetadata { + custom?: TCustomMetadata; + description?: string | undefined; +} + // @public export interface FieldSchemaUnsafe> extends FieldSchema { readonly allowedTypes: Types; @@ -342,9 +350,9 @@ export class SchemaFactory; object>(name: Name, fields: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T>; objectRecursive>>(name: Name, t: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNodeUnsafe>, object & { readonly [Property in keyof T as FieldHasDefaultUnsafe extends false ? Property : never]: InsertableTreeFieldFromImplicitFieldUnsafe; } & { readonly [Property_1 in keyof T as FieldHasDefaultUnsafe extends true ? Property_1 : never]?: InsertableTreeFieldFromImplicitFieldUnsafe | undefined; }, false, T>; - optional(t: T, props?: Omit): FieldSchema; + optional(t: T, props?: Omit, "defaultProvider">): FieldSchema; optionalRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; - required(t: T, props?: Omit): FieldSchema; + required(t: T, props?: Omit, "defaultProvider">): FieldSchema; requiredRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; // (undocumented) readonly scope: TScope; diff --git a/packages/dds/tree/package.json b/packages/dds/tree/package.json index a24ca08dd63d..d6fc8594aadb 100644 --- a/packages/dds/tree/package.json +++ b/packages/dds/tree/package.json @@ -226,7 +226,17 @@ } }, "typeValidation": { - "broken": {}, + "broken": { + "Interface_FieldSchemaUnsafe": { + "forwardCompat": false + }, + "Interface_ITreeViewConfiguration": { + "forwardCompat": false + }, + "TypeAlias_ImplicitFieldSchema": { + "forwardCompat": false + } + }, "entrypoint": "internal" } } diff --git a/packages/dds/tree/src/core/schema-stored/schema.ts b/packages/dds/tree/src/core/schema-stored/schema.ts index fdc260e4abf1..76c9bd3bda6b 100644 --- a/packages/dds/tree/src/core/schema-stored/schema.ts +++ b/packages/dds/tree/src/core/schema-stored/schema.ts @@ -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. diff --git a/packages/dds/tree/src/index.ts b/packages/dds/tree/src/index.ts index 33728f0a05c9..8898e705e3ee 100644 --- a/packages/dds/tree/src/index.ts +++ b/packages/dds/tree/src/index.ts @@ -82,6 +82,7 @@ export { type TreeLeafValue, FieldKind, FieldSchema, + type FieldSchemaMetadata, type ImplicitAllowedTypes, type InsertableTreeFieldFromImplicitField, type InsertableTypedNode, diff --git a/packages/dds/tree/src/simple-tree/api/jsonSchema.ts b/packages/dds/tree/src/simple-tree/api/jsonSchema.ts index 00a45032e08a..78815e4cbd8f 100644 --- a/packages/dds/tree/src/simple-tree/api/jsonSchema.ts +++ b/packages/dds/tree/src/simple-tree/api/jsonSchema.ts @@ -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. @@ -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. diff --git a/packages/dds/tree/src/simple-tree/api/schemaFactory.ts b/packages/dds/tree/src/simple-tree/api/schemaFactory.ts index 0e1a9541919d..0eb22c946471 100644 --- a/packages/dds/tree/src/simple-tree/api/schemaFactory.ts +++ b/packages/dds/tree/src/simple-tree/api/schemaFactory.ts @@ -552,11 +552,14 @@ export class SchemaFactory< * * @param t - The types allowed under the field. * @param props - Optional properties to associate with the field. + * + * @typeParam TCustomMetadata - Custom metadata properties to associate with the field. + * See {@link FieldSchemaMetadata.custom}. */ - public optional( + public optional( t: T, - props?: Omit, - ): FieldSchema { + props?: Omit, "defaultProvider">, + ): FieldSchema { const defaultOptionalProvider: DefaultProvider = getDefaultProvider(() => { return undefined; }); @@ -575,11 +578,14 @@ export class SchemaFactory< * @remarks * Fields are required by default, but this API can be used to make the required nature explicit in the schema, * and allows associating custom {@link FieldProps | properties} with the field. + * + * @typeParam TCustomMetadata - Custom metadata properties to associate with the field. + * See {@link FieldSchemaMetadata.custom}. */ - public required( + public required( t: T, - props?: Omit, - ): FieldSchema { + props?: Omit, "defaultProvider">, + ): FieldSchema { return createFieldSchema(FieldKind.Required, t, props); } diff --git a/packages/dds/tree/src/simple-tree/api/simpleSchema.ts b/packages/dds/tree/src/simple-tree/api/simpleSchema.ts index 4de68f4a401a..b127e0c37cf5 100644 --- a/packages/dds/tree/src/simple-tree/api/simpleSchema.ts +++ b/packages/dds/tree/src/simple-tree/api/simpleSchema.ts @@ -110,6 +110,11 @@ export interface SimpleFieldSchema { * A {@link SimpleTreeSchema} is needed to resolve these identifiers to their schema {@link SimpleTreeSchema.definitions}. */ readonly allowedTypes: ReadonlySet; + + /** + * {@inheritDoc FieldSchemaMetadata.description} + */ + readonly description?: string | undefined; } /** diff --git a/packages/dds/tree/src/simple-tree/api/simpleSchemaToJsonSchema.ts b/packages/dds/tree/src/simple-tree/api/simpleSchemaToJsonSchema.ts index 827b48461906..23005725d924 100644 --- a/packages/dds/tree/src/simple-tree/api/simpleSchemaToJsonSchema.ts +++ b/packages/dds/tree/src/simple-tree/api/simpleSchemaToJsonSchema.ts @@ -6,7 +6,7 @@ import { oob, unreachableCase } from "@fluidframework/core-utils/internal"; import { UsageError } from "@fluidframework/telemetry-utils/internal"; import { ValueSchema } from "../../core/index.js"; -import { getOrCreate } from "../../util/index.js"; +import { getOrCreate, type Mutable } from "../../util/index.js"; import type { JsonArrayNodeSchema, JsonFieldSchema, @@ -142,12 +142,20 @@ function convertObjectNodeSchema(schema: SimpleObjectNodeSchema): JsonObjectNode allowedTypes.push(createSchemaRef(allowedType)); } - properties[key] = + const output: Mutable = allowedTypes.length === 1 ? allowedTypes[0] ?? oob() : { anyOf: allowedTypes, }; + + // Don't include "description" property at all if it's not present in the input. + if (value.description !== undefined) { + output.description = value.description; + } + + properties[key] = output; + if (value.kind === FieldKind.Required) { required.push(key); } diff --git a/packages/dds/tree/src/simple-tree/api/viewSchemaToSimpleSchema.ts b/packages/dds/tree/src/simple-tree/api/viewSchemaToSimpleSchema.ts index c4b7b1c72208..e20fdc432ec7 100644 --- a/packages/dds/tree/src/simple-tree/api/viewSchemaToSimpleSchema.ts +++ b/packages/dds/tree/src/simple-tree/api/viewSchemaToSimpleSchema.ts @@ -19,7 +19,7 @@ import type { SimpleTreeSchema, } from "./simpleSchema.js"; import type { ValueSchema } from "../../core/index.js"; -import { getOrCreate } from "../../util/index.js"; +import { getOrCreate, type Mutable } from "../../util/index.js"; import { isObjectNodeSchema, type ObjectNodeSchema } from "../objectNodeTypes.js"; import { NodeKind, type TreeNodeSchema } from "../core/index.js"; @@ -126,11 +126,16 @@ function fieldSchemaToSimpleSchema(schema: FieldSchema): SimpleFieldSchema { } const allowedTypes = allowedTypesFromFieldSchema(schema); - const result = { + const result: Mutable = { kind: schema.kind, allowedTypes, }; + // Don't include "description" property at all if it's not present. + if (schema.metadata?.description !== undefined) { + result.description = schema.metadata.description; + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any (schema as any)[simpleFieldSchemaCacheSymbol] = result; diff --git a/packages/dds/tree/src/simple-tree/index.ts b/packages/dds/tree/src/simple-tree/index.ts index 04fa5b8dceaf..0cf1b6bfcdc7 100644 --- a/packages/dds/tree/src/simple-tree/index.ts +++ b/packages/dds/tree/src/simple-tree/index.ts @@ -84,6 +84,7 @@ export { type FieldProps, normalizeFieldSchema, type ApplyKind, + type FieldSchemaMetadata, } from "./schemaTypes.js"; export { getOrCreateInnerNode } from "./proxyBinding.js"; export { toFlexSchema } from "./toFlexSchema.js"; diff --git a/packages/dds/tree/src/simple-tree/schemaTypes.ts b/packages/dds/tree/src/simple-tree/schemaTypes.ts index 7a4af11d3ce2..7c554bdc7b64 100644 --- a/packages/dds/tree/src/simple-tree/schemaTypes.ts +++ b/packages/dds/tree/src/simple-tree/schemaTypes.ts @@ -98,9 +98,12 @@ export function getExplicitStoredKey(fieldSchema: ImplicitFieldSchema): string | /** * Additional information to provide to a {@link FieldSchema}. * + * @typeParam TCustomMetadata - Custom metadata properties to associate with the field. + * See {@link FieldSchemaMetadata.custom}. + * * @public */ -export interface FieldProps { +export interface FieldProps { /** * The unique identifier of a field, used in the persisted form of the tree. * @@ -152,12 +155,19 @@ export interface FieldProps { * @defaultValue If not specified, the key that is persisted is the property key that was specified in the schema. */ readonly key?: string; + /** * A default provider used for fields which were not provided any values. * @privateRemarks * We are using an erased type here, as we want to expose this API but `InsertableContent` and `NodeKeyManager` are not public. */ readonly defaultProvider?: DefaultProvider; + + /** + * Optional metadata to associate with the field. + * @remarks Note: this metadata is not persisted in the document. + */ + readonly metadata?: FieldSchemaMetadata; } /** @@ -199,17 +209,44 @@ export function getDefaultProvider(input: FieldProvider): DefaultProvider { return input as unknown as DefaultProvider; } +/** + * Metadata associated with a {@link FieldSchema}. + * + * @remarks Specified via {@link FieldProps.metadata}. + * + * @sealed + * @public + */ +export interface FieldSchemaMetadata { + /** + * User-defined metadata. + */ + custom?: TCustomMetadata; + + /** + * The description of the field. + * + * @remarks + * + * If provided, will be used by the system in scenarios where a description of the field is useful. + * E.g., when converting a field schema to {@link https://json-schema.org/ | JSON Schema}, this description will be + * used as the `description` field. + */ + description?: string | undefined; +} + /** * Package internal construction API. */ export let createFieldSchema: < Kind extends FieldKind = FieldKind, Types extends ImplicitAllowedTypes = ImplicitAllowedTypes, + TCustomMetadata = unknown, >( kind: Kind, allowedTypes: Types, - props?: FieldProps, -) => FieldSchema; + props?: FieldProps, +) => FieldSchema; /** * All policy for a specific field, @@ -220,20 +257,26 @@ export let createFieldSchema: < * @privateRemarks * Public access to the constructor is removed to prevent creating expressible but unsupported (or not stable) configurations. * {@link createFieldSchema} can be used internally to create instances. + * + * @typeParam TCustomMetadata - Custom metadata properties to associate with the field. + * See {@link FieldSchemaMetadata.custom}. + * * @sealed @public */ export class FieldSchema< out Kind extends FieldKind = FieldKind, out Types extends ImplicitAllowedTypes = ImplicitAllowedTypes, + out TCustomMetadata = unknown, > { static { createFieldSchema = < Kind2 extends FieldKind = FieldKind, Types2 extends ImplicitAllowedTypes = ImplicitAllowedTypes, + TCustomMetadata2 = unknown, >( kind: Kind2, allowedTypes: Types2, - props?: FieldProps, + props?: FieldProps, ) => new FieldSchema(kind, allowedTypes, props); } /** @@ -257,6 +300,13 @@ export class FieldSchema< */ public readonly requiresValue: boolean; + /** + * {@inheritDoc FieldProps.metadata} + */ + public get metadata(): FieldSchemaMetadata | undefined { + return this.props?.metadata; + } + private constructor( /** * The {@link https://en.wikipedia.org/wiki/Kind_(type_theory) | kind } of this field. @@ -270,7 +320,7 @@ export class FieldSchema< /** * Optional properties associated with the field. */ - public readonly props?: FieldProps, + public readonly props?: FieldProps, ) { this.lazyTypes = new Lazy(() => normalizeAllowedTypes(this.allowedTypes)); // TODO: optional fields should (by default) get a default provider that returns undefined, removing the need to special case them here: diff --git a/packages/dds/tree/src/test/simple-tree/api/schemaFactory.spec.ts b/packages/dds/tree/src/test/simple-tree/api/schemaFactory.spec.ts index f0055352f45b..2ecb2be48754 100644 --- a/packages/dds/tree/src/test/simple-tree/api/schemaFactory.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/api/schemaFactory.spec.ts @@ -31,6 +31,8 @@ import { typeSchemaSymbol, // eslint-disable-next-line import/no-internal-modules } from "../../../simple-tree/core/index.js"; +// eslint-disable-next-line import/no-internal-modules +import type { ObjectNodeSchema } from "../../../simple-tree/objectNodeTypes.js"; import { SchemaFactory, schemaFromValue, @@ -361,6 +363,24 @@ describe("schemaFactory", () => { ); }); + it("Field Metadata", () => { + const schemaFactory = new SchemaFactory("com.example"); + const barMetadata = { + description: "Bar", + custom: { prop1: "Custom metadata property." }, + }; + + class Foo extends schemaFactory.object("Foo", { + bar: schemaFactory.required(schemaFactory.number, { metadata: barMetadata }), + }) {} + + const foo = hydrate(Foo, { bar: 37 }); + + const schema = Tree.schema(foo) as ObjectNodeSchema; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + assert.deepEqual(schema.fields.get("bar")!.metadata, barMetadata); + }); + describe("deep equality", () => { const schema = new SchemaFactory("com.example"); diff --git a/packages/dds/tree/src/test/simple-tree/api/simpleSchemaToJsonSchema.spec.ts b/packages/dds/tree/src/test/simple-tree/api/simpleSchemaToJsonSchema.spec.ts index 9f9d3cc1cd82..e5d7b61ecc31 100644 --- a/packages/dds/tree/src/test/simple-tree/api/simpleSchemaToJsonSchema.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/api/simpleSchemaToJsonSchema.spec.ts @@ -167,10 +167,12 @@ describe("simpleSchemaToJsonSchema", () => { "foo": { kind: FieldKind.Optional, allowedTypes: new Set(["test.number"]), + description: "A number representing the concept of Foo.", }, "bar": { kind: FieldKind.Required, allowedTypes: new Set(["test.string"]), + description: "A string representing the concept of Bar.", }, }, }, @@ -189,8 +191,14 @@ describe("simpleSchemaToJsonSchema", () => { type: "object", _treeNodeSchemaKind: NodeKind.Object, properties: { - foo: { $ref: "#/$defs/test.number" }, - bar: { $ref: "#/$defs/test.string" }, + foo: { + $ref: "#/$defs/test.number", + description: "A number representing the concept of Foo.", + }, + bar: { + $ref: "#/$defs/test.string", + description: "A string representing the concept of Bar.", + }, }, required: ["bar"], additionalProperties: false, diff --git a/packages/dds/tree/src/test/simple-tree/getJsonSchema.spec.ts b/packages/dds/tree/src/test/simple-tree/getJsonSchema.spec.ts index b841dc4ea6f8..d68a0ce8f31e 100644 --- a/packages/dds/tree/src/test/simple-tree/getJsonSchema.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/getJsonSchema.spec.ts @@ -190,8 +190,12 @@ describe("getJsonSchema", () => { it("Object schema", () => { const schemaFactory = new SchemaFactory("test"); const Schema = schemaFactory.object("object", { - foo: schemaFactory.optional(schemaFactory.number), - bar: schemaFactory.required(schemaFactory.string), + foo: schemaFactory.optional(schemaFactory.number, { + metadata: { description: "A number representing the concept of Foo." }, + }), + bar: schemaFactory.required(schemaFactory.string, { + metadata: { description: "A string representing the concept of Bar." }, + }), }); const actual = getJsonSchema(Schema); @@ -204,9 +208,11 @@ describe("getJsonSchema", () => { properties: { foo: { $ref: "#/$defs/com.fluidframework.leaf.number", + description: "A number representing the concept of Foo.", }, bar: { $ref: "#/$defs/com.fluidframework.leaf.string", + description: "A string representing the concept of Bar.", }, }, required: ["bar"], diff --git a/packages/dds/tree/src/test/types/validateTreePrevious.generated.ts b/packages/dds/tree/src/test/types/validateTreePrevious.generated.ts index 1058f43834b7..71f54c8bb785 100644 --- a/packages/dds/tree/src/test/types/validateTreePrevious.generated.ts +++ b/packages/dds/tree/src/test/types/validateTreePrevious.generated.ts @@ -229,6 +229,7 @@ declare type current_as_old_for_Interface_FieldProps = requireAssignableTo>, TypeOnly>> /* @@ -346,6 +347,7 @@ declare type current_as_old_for_Interface_ITreeConfigurationOptions = requireAss * typeValidation.broken: * "Interface_ITreeViewConfiguration": {"forwardCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type old_as_current_for_Interface_ITreeViewConfiguration = requireAssignableTo, TypeOnly> /* @@ -562,6 +564,7 @@ declare type current_as_old_for_TypeAlias_ImplicitAllowedTypes = requireAssignab * typeValidation.broken: * "TypeAlias_ImplicitFieldSchema": {"forwardCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type old_as_current_for_TypeAlias_ImplicitFieldSchema = requireAssignableTo, TypeOnly> /* diff --git a/packages/framework/fluid-framework/api-report/fluid-framework.alpha.api.md b/packages/framework/fluid-framework/api-report/fluid-framework.alpha.api.md index bbc9f666629f..dcdbdb4ab467 100644 --- a/packages/framework/fluid-framework/api-report/fluid-framework.alpha.api.md +++ b/packages/framework/fluid-framework/api-report/fluid-framework.alpha.api.md @@ -89,21 +89,29 @@ export enum FieldKind { } // @public -export interface FieldProps { +export interface FieldProps { readonly defaultProvider?: DefaultProvider; readonly key?: string; + readonly metadata?: FieldSchemaMetadata; } // @public @sealed -export class FieldSchema { +export class FieldSchema { readonly allowedTypes: Types; get allowedTypeSet(): ReadonlySet; readonly kind: Kind; - readonly props?: FieldProps | undefined; + get metadata(): FieldSchemaMetadata | undefined; + readonly props?: FieldProps | undefined; readonly requiresValue: boolean; protected _typeCheck: MakeNominal; } +// @public @sealed +export interface FieldSchemaMetadata { + custom?: TCustomMetadata; + description?: string | undefined; +} + // @public export interface FieldSchemaUnsafe> extends FieldSchema { readonly allowedTypes: Types; @@ -542,8 +550,10 @@ export interface JsonArrayNodeSchema extends JsonNodeSchemaBase { @@ -763,9 +773,9 @@ export class SchemaFactory; object>(name: Name, fields: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T>; objectRecursive>>(name: Name, t: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNodeUnsafe>, object & { readonly [Property in keyof T as FieldHasDefaultUnsafe extends false ? Property : never]: InsertableTreeFieldFromImplicitFieldUnsafe; } & { readonly [Property_1 in keyof T as FieldHasDefaultUnsafe extends true ? Property_1 : never]?: InsertableTreeFieldFromImplicitFieldUnsafe | undefined; }, false, T>; - optional(t: T, props?: Omit): FieldSchema; + optional(t: T, props?: Omit, "defaultProvider">): FieldSchema; optionalRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; - required(t: T, props?: Omit): FieldSchema; + required(t: T, props?: Omit, "defaultProvider">): FieldSchema; requiredRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; // (undocumented) readonly scope: TScope; diff --git a/packages/framework/fluid-framework/api-report/fluid-framework.beta.api.md b/packages/framework/fluid-framework/api-report/fluid-framework.beta.api.md index d9c83e251b78..cce1df261615 100644 --- a/packages/framework/fluid-framework/api-report/fluid-framework.beta.api.md +++ b/packages/framework/fluid-framework/api-report/fluid-framework.beta.api.md @@ -89,21 +89,29 @@ export enum FieldKind { } // @public -export interface FieldProps { +export interface FieldProps { readonly defaultProvider?: DefaultProvider; readonly key?: string; + readonly metadata?: FieldSchemaMetadata; } // @public @sealed -export class FieldSchema { +export class FieldSchema { readonly allowedTypes: Types; get allowedTypeSet(): ReadonlySet; readonly kind: Kind; - readonly props?: FieldProps | undefined; + get metadata(): FieldSchemaMetadata | undefined; + readonly props?: FieldProps | undefined; readonly requiresValue: boolean; protected _typeCheck: MakeNominal; } +// @public @sealed +export interface FieldSchemaMetadata { + custom?: TCustomMetadata; + description?: string | undefined; +} + // @public export interface FieldSchemaUnsafe> extends FieldSchema { readonly allowedTypes: Types; @@ -697,9 +705,9 @@ export class SchemaFactory; object>(name: Name, fields: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T>; objectRecursive>>(name: Name, t: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNodeUnsafe>, object & { readonly [Property in keyof T as FieldHasDefaultUnsafe extends false ? Property : never]: InsertableTreeFieldFromImplicitFieldUnsafe; } & { readonly [Property_1 in keyof T as FieldHasDefaultUnsafe extends true ? Property_1 : never]?: InsertableTreeFieldFromImplicitFieldUnsafe | undefined; }, false, T>; - optional(t: T, props?: Omit): FieldSchema; + optional(t: T, props?: Omit, "defaultProvider">): FieldSchema; optionalRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; - required(t: T, props?: Omit): FieldSchema; + required(t: T, props?: Omit, "defaultProvider">): FieldSchema; requiredRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; // (undocumented) readonly scope: TScope; diff --git a/packages/framework/fluid-framework/api-report/fluid-framework.legacy.alpha.api.md b/packages/framework/fluid-framework/api-report/fluid-framework.legacy.alpha.api.md index 545570e7103c..800d72180a62 100644 --- a/packages/framework/fluid-framework/api-report/fluid-framework.legacy.alpha.api.md +++ b/packages/framework/fluid-framework/api-report/fluid-framework.legacy.alpha.api.md @@ -92,21 +92,29 @@ export enum FieldKind { } // @public -export interface FieldProps { +export interface FieldProps { readonly defaultProvider?: DefaultProvider; readonly key?: string; + readonly metadata?: FieldSchemaMetadata; } // @public @sealed -export class FieldSchema { +export class FieldSchema { readonly allowedTypes: Types; get allowedTypeSet(): ReadonlySet; readonly kind: Kind; - readonly props?: FieldProps | undefined; + get metadata(): FieldSchemaMetadata | undefined; + readonly props?: FieldProps | undefined; readonly requiresValue: boolean; protected _typeCheck: MakeNominal; } +// @public @sealed +export interface FieldSchemaMetadata { + custom?: TCustomMetadata; + description?: string | undefined; +} + // @public export interface FieldSchemaUnsafe> extends FieldSchema { readonly allowedTypes: Types; @@ -998,9 +1006,9 @@ export class SchemaFactory; object>(name: Name, fields: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T>; objectRecursive>>(name: Name, t: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNodeUnsafe>, object & { readonly [Property in keyof T as FieldHasDefaultUnsafe extends false ? Property : never]: InsertableTreeFieldFromImplicitFieldUnsafe; } & { readonly [Property_1 in keyof T as FieldHasDefaultUnsafe extends true ? Property_1 : never]?: InsertableTreeFieldFromImplicitFieldUnsafe | undefined; }, false, T>; - optional(t: T, props?: Omit): FieldSchema; + optional(t: T, props?: Omit, "defaultProvider">): FieldSchema; optionalRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; - required(t: T, props?: Omit): FieldSchema; + required(t: T, props?: Omit, "defaultProvider">): FieldSchema; requiredRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; // (undocumented) readonly scope: TScope; diff --git a/packages/framework/fluid-framework/api-report/fluid-framework.legacy.public.api.md b/packages/framework/fluid-framework/api-report/fluid-framework.legacy.public.api.md index 10557a506cb1..ab96c50c7b65 100644 --- a/packages/framework/fluid-framework/api-report/fluid-framework.legacy.public.api.md +++ b/packages/framework/fluid-framework/api-report/fluid-framework.legacy.public.api.md @@ -89,21 +89,29 @@ export enum FieldKind { } // @public -export interface FieldProps { +export interface FieldProps { readonly defaultProvider?: DefaultProvider; readonly key?: string; + readonly metadata?: FieldSchemaMetadata; } // @public @sealed -export class FieldSchema { +export class FieldSchema { readonly allowedTypes: Types; get allowedTypeSet(): ReadonlySet; readonly kind: Kind; - readonly props?: FieldProps | undefined; + get metadata(): FieldSchemaMetadata | undefined; + readonly props?: FieldProps | undefined; readonly requiresValue: boolean; protected _typeCheck: MakeNominal; } +// @public @sealed +export interface FieldSchemaMetadata { + custom?: TCustomMetadata; + description?: string | undefined; +} + // @public export interface FieldSchemaUnsafe> extends FieldSchema { readonly allowedTypes: Types; @@ -728,9 +736,9 @@ export class SchemaFactory; object>(name: Name, fields: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T>; objectRecursive>>(name: Name, t: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNodeUnsafe>, object & { readonly [Property in keyof T as FieldHasDefaultUnsafe extends false ? Property : never]: InsertableTreeFieldFromImplicitFieldUnsafe; } & { readonly [Property_1 in keyof T as FieldHasDefaultUnsafe extends true ? Property_1 : never]?: InsertableTreeFieldFromImplicitFieldUnsafe | undefined; }, false, T>; - optional(t: T, props?: Omit): FieldSchema; + optional(t: T, props?: Omit, "defaultProvider">): FieldSchema; optionalRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; - required(t: T, props?: Omit): FieldSchema; + required(t: T, props?: Omit, "defaultProvider">): FieldSchema; requiredRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; // (undocumented) readonly scope: TScope; diff --git a/packages/framework/fluid-framework/api-report/fluid-framework.public.api.md b/packages/framework/fluid-framework/api-report/fluid-framework.public.api.md index b4f27f3d1dc5..6898c50482c9 100644 --- a/packages/framework/fluid-framework/api-report/fluid-framework.public.api.md +++ b/packages/framework/fluid-framework/api-report/fluid-framework.public.api.md @@ -89,21 +89,29 @@ export enum FieldKind { } // @public -export interface FieldProps { +export interface FieldProps { readonly defaultProvider?: DefaultProvider; readonly key?: string; + readonly metadata?: FieldSchemaMetadata; } // @public @sealed -export class FieldSchema { +export class FieldSchema { readonly allowedTypes: Types; get allowedTypeSet(): ReadonlySet; readonly kind: Kind; - readonly props?: FieldProps | undefined; + get metadata(): FieldSchemaMetadata | undefined; + readonly props?: FieldProps | undefined; readonly requiresValue: boolean; protected _typeCheck: MakeNominal; } +// @public @sealed +export interface FieldSchemaMetadata { + custom?: TCustomMetadata; + description?: string | undefined; +} + // @public export interface FieldSchemaUnsafe> extends FieldSchema { readonly allowedTypes: Types; @@ -692,9 +700,9 @@ export class SchemaFactory; object>(name: Name, fields: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T>; objectRecursive>>(name: Name, t: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNodeUnsafe>, object & { readonly [Property in keyof T as FieldHasDefaultUnsafe extends false ? Property : never]: InsertableTreeFieldFromImplicitFieldUnsafe; } & { readonly [Property_1 in keyof T as FieldHasDefaultUnsafe extends true ? Property_1 : never]?: InsertableTreeFieldFromImplicitFieldUnsafe | undefined; }, false, T>; - optional(t: T, props?: Omit): FieldSchema; + optional(t: T, props?: Omit, "defaultProvider">): FieldSchema; optionalRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; - required(t: T, props?: Omit): FieldSchema; + required(t: T, props?: Omit, "defaultProvider">): FieldSchema; requiredRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; // (undocumented) readonly scope: TScope;