diff --git a/.changeset/metal-sloths-join.md b/.changeset/metal-sloths-join.md new file mode 100644 index 000000000000..47f9a5f42953 --- /dev/null +++ b/.changeset/metal-sloths-join.md @@ -0,0 +1,27 @@ +--- +"fluid-framework": minor +"@fluidframework/tree": minor +--- +--- +"section": tree +--- + +Disallow some invalid and unsafe ObjectNode field assignments at compile time + +The compile time validation of the type of values assigned to ObjectNode fields is limited by TypeScript's limitations. +Two cases which were actually possible to disallow and should be disallowed for consistency with runtime behavior and similar APIs were being allowed: + +1. [Identifier fields](https://fluidframework.com/docs/api/v2/fluid-framework/schemafactory-class#identifier-property): + Identifier fields are immutable, and setting them produces a runtime error. + This changes fixes them to no longer be typed as assignable. + +2. Fields with non-exact schema: + When non-exact scheme is used for a field (for example the schema is either a schema only allowing numbers or a schema only allowing strings) the field is no longer typed as assignable. + This matches how constructors and implicit node construction work. + For example when a node `Foo` has such an non-exact schema for field `bar`, you can no longer unsafely do `foo.bar = 5` just like how you could already not do `new Foo({bar: 5})`. + +This fix only applies to [`SchemaFactory.object`](https://fluidframework.com/docs/api/v2/fluid-framework/schemafactory-class#object-method). +[`SchemaFactory.objectRecursive`](https://fluidframework.com/docs/api/v2/fluid-framework/schemafactory-class#objectrecursive-method) was unable to be updated to match due to TypeScript limitations on recursive types. + +An `@alpha` API, `customizeSchemaTyping` has been added to allow control over the types generated from schema. +For example code relying on the unsound typing fixed above can restore the behavior using `customizeSchemaTyping`: diff --git a/packages/dds/tree/.vscode/settings.json b/packages/dds/tree/.vscode/settings.json index 23138fe634ac..9dbcbcef59f1 100644 --- a/packages/dds/tree/.vscode/settings.json +++ b/packages/dds/tree/.vscode/settings.json @@ -22,6 +22,7 @@ "contravariance", "contravariantly", "covariantly", + "Customizer", "deprioritized", "endregion", "fluidframework", diff --git a/packages/dds/tree/api-report/tree.alpha.api.md b/packages/dds/tree/api-report/tree.alpha.api.md index 78605b755c00..9ddd06b4c866 100644 --- a/packages/dds/tree/api-report/tree.alpha.api.md +++ b/packages/dds/tree/api-report/tree.alpha.api.md @@ -87,6 +87,11 @@ type ApplyKind = { [FieldKind.Identifier]: T; }[Kind]; +// @public @system +export type ApplyKindAssignment = [Kind] extends [ +FieldKind.Required +] ? T : [Kind] extends [FieldKind.Optional] ? T | undefined : never; + // @public @system type ApplyKindInput = [ Kind @@ -120,6 +125,12 @@ export function asAlpha(view: TreeView(view: TreeView): TreeViewBeta; +// @public @system +export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; + +// @public @system +export type AssignableTreeFieldFromImplicitFieldDefault> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? StrictTypes["readWrite"] : never; + // @alpha @deprecated export function asTreeViewAlpha(view: TreeView): TreeViewAlpha; @@ -198,10 +209,60 @@ export function createSimpleTreeIndex(view: TreeView, indexer: Map, getValue: (nodes: TreeIndexNodes>) => TValue, isKeyValid: (key: TreeIndexKey) => key is TKey, indexableSchema: readonly TSchema[]): SimpleTreeIndex; +// @public @system +export type CustomizedSchemaTyping = TSchema & { + [CustomizedTyping]: TCustom; +}; + +// @public @system +export const CustomizedTyping: unique symbol; + +// @public @system +export type CustomizedTyping = typeof CustomizedTyping; + +// @alpha @sealed +export interface Customizer { + custom>(): CustomizedSchemaTyping[Property] : GetTypes[Property]; + }>; + relaxed(): CustomizedSchemaTyping : TSchema extends AllowedTypes ? TSchema[number] extends LazyItem ? InsertableTypedNode : never : never; + readWrite: TreeNodeFromImplicitAllowedTypes; + output: TreeNodeFromImplicitAllowedTypes; + }>; + simplified>(): CustomizedSchemaTyping; + simplifiedUnrestricted(): CustomizedSchemaTyping; + strict(): CustomizedSchemaTyping>; +} + +// @alpha +export function customizeSchemaTyping(schema: TSchema): Customizer; + +// @public @sealed +export interface CustomTypes { + readonly input: unknown; + readonly output: TreeLeafValue | TreeNode; + readonly readWrite: TreeLeafValue | TreeNode; +} + +// @public @system +export type DefaultInsertableTreeNodeFromImplicitAllowedTypes = [TSchema] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; + // @public @sealed @system interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> { } +// @public @system +export type DefaultTreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; + // @alpha export interface DirtyTreeMap { // (undocumented) @@ -391,6 +452,11 @@ export function getJsonSchema(schema: ImplicitAllowedTypes, options: Required = [TSchema] extends [ +CustomizedSchemaTyping +] ? TCustom : StrictTypes; + // @alpha export type HandleConverter = (data: IFluidHandle) => TCustom; @@ -423,7 +489,7 @@ type _InlineTrick = 0; export type Input = T; // @alpha -export type Insertable = TSchema extends ImplicitAllowedTypes ? InsertableTreeNodeFromImplicitAllowedTypes : InsertableContent; +export type Insertable = InsertableTreeNodeFromImplicitAllowedTypes; // @alpha @system export type InsertableContent = Unhydrated | FactoryContent; @@ -443,7 +509,7 @@ type InsertableObjectFromSchemaRecord; // @public -export type InsertableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; +export type InsertableTreeFieldFromImplicitField] ? TSchemaInput : SchemaUnionToIntersection> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; // @public @system export type InsertableTreeNodeFromAllowedTypes = IsUnion extends true ? never : { @@ -453,9 +519,7 @@ export type InsertableTreeNodeFromAllowedTypes = IsU }[NumberKeys]; // @public -export type InsertableTreeNodeFromImplicitAllowedTypes = [ -TSchema -] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; +export type InsertableTreeNodeFromImplicitAllowedTypes = GetTypes["input"]; // @public export type InsertableTypedNode> = (T extends TreeNodeSchema ? NodeBuilderData : never) | (T extends TreeNodeSchema ? Unhydrated ? never : NodeFromSchema> : never); @@ -734,22 +798,33 @@ export type NumberKeys = Transformed[`${number}` & keyof Transformed]; // @public @system -export type ObjectFromSchemaRecord> = RestrictiveStringRecord extends T ? {} : { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; +export type ObjectFromSchemaRecord, Options extends ObjectSchemaTypingOptions = Record> = RestrictiveStringRecord extends T ? {} : [Options["supportReadonlyFields"]] extends [true] ? { + -readonly [Property in keyof T as [ + AssignableTreeFieldFromImplicitField + ] extends [never | undefined] ? never : Property]: [Options["supportCustomizedFields"]] extends [true] ? AssignableTreeFieldFromImplicitField : AssignableTreeFieldFromImplicitFieldDefault; +} & { + readonly [Property in keyof T]: [Options["supportCustomizedFields"]] extends [true] ? TreeFieldFromImplicitField : TreeFieldFromImplicitFieldDefault; +} : { + -readonly [Property in keyof T]: [Options["supportCustomizedFields"]] extends [true] ? TreeFieldFromImplicitField : TreeFieldFromImplicitFieldDefault; +}; + +// @beta @system +export type ObjectFromSchemaRecordRelaxed> = RestrictiveStringRecord extends T ? {} : { + [Property in keyof T]: TreeFieldFromImplicitFieldDefault; }; // @alpha @sealed -export interface ObjectNodeSchema = RestrictiveStringRecord, ImplicitlyConstructable extends boolean = boolean, out TCustomMetadata = unknown> extends TreeNodeSchemaClass, InsertableObjectFromSchemaRecord, ImplicitlyConstructable, T, never, TCustomMetadata>, SimpleObjectNodeSchema { +export interface ObjectNodeSchema = RestrictiveStringRecord, ImplicitlyConstructable extends boolean = boolean, TOptions extends ObjectSchemaOptionsAlpha = ObjectSchemaOptionsAlpha, TCustomMetadataInferred = TOptions extends ObjectSchemaOptionsAlpha ? TCustomMetadataX : unknown> extends TreeNodeSchemaClass, InsertableObjectFromSchemaRecord, ImplicitlyConstructable, T, never, TCustomMetadataInferred>, SimpleObjectNodeSchema { readonly fields: ReadonlyMap; } // @alpha (undocumented) export const ObjectNodeSchema: { - readonly [Symbol.hasInstance]: (value: TreeNodeSchema) => value is ObjectNodeSchema, boolean, unknown>; + readonly [Symbol.hasInstance]: (value: TreeNodeSchema) => value is ObjectNodeSchema, boolean, ObjectSchemaOptionsAlpha, unknown>; }; // @beta @input -export interface ObjectSchemaOptions extends NodeSchemaOptions { +export interface ObjectSchemaOptions extends NodeSchemaOptions, ObjectSchemaTypingOptions { readonly allowUnknownOptionalFields?: boolean; } @@ -757,6 +832,14 @@ export interface ObjectSchemaOptions extends NodeSche export interface ObjectSchemaOptionsAlpha extends ObjectSchemaOptions, NodeSchemaOptionsAlpha { } +// @public @input +export interface ObjectSchemaTypingOptions { + // (undocumented) + readonly supportCustomizedFields?: true | undefined; + // (undocumented) + readonly supportReadonlyFields?: true | undefined; +} + // @alpha @sealed export interface ObservationResults { readonly result: TResult; @@ -772,6 +855,11 @@ export function persistedToSimpleSchema(persisted: JsonCompatible, options: ICod // @beta @system export type PopUnion void : never>> = AsOverloadedFunction extends (a: infer First) => void ? First : never; +// @beta @system +export type PreventExtraProperties = T & { + [x in keyof T]: x extends keyof Expected ? T[x] : undefined; +}; + // @alpha @system export type ReadableField = TreeFieldFromImplicitField>; @@ -920,10 +1008,10 @@ export class SchemaFactoryAlpha & SimpleLeafNodeSchema, LeafSchema<"number", number> & SimpleLeafNodeSchema, LeafSchema<"boolean", boolean> & SimpleLeafNodeSchema, LeafSchema<"null", null> & SimpleLeafNodeSchema, LeafSchema<"handle", IFluidHandle_2> & SimpleLeafNodeSchema]; mapAlpha(name: Name, allowedTypes: T, options?: NodeSchemaOptionsAlpha): MapNodeCustomizableSchema, T, true, TCustomMetadata>; mapRecursive(name: Name, allowedTypes: T, options?: NodeSchemaOptionsAlpha): MapNodeCustomizableSchemaUnsafe, T, TCustomMetadata>; - objectAlpha, const TCustomMetadata = unknown>(name: Name, fields: T, options?: ObjectSchemaOptionsAlpha): ObjectNodeSchema, T, true, TCustomMetadata> & { + objectAlpha, const TOptions extends ObjectSchemaOptionsAlpha = ObjectSchemaOptionsAlpha>(name: Name, fields: T, options?: PreventExtraProperties): ObjectNodeSchema, T, true, TOptions> & { readonly createFromInsertable: unknown; }; - objectRecursive, const TCustomMetadata = unknown>(name: Name, t: T, options?: ObjectSchemaOptionsAlpha): TreeNodeSchemaClass, NodeKind.Object, System_Unsafe.TreeObjectNodeUnsafe>, object & System_Unsafe.InsertableObjectFromSchemaRecordUnsafe, false, T, never, TCustomMetadata> & SimpleObjectNodeSchema & Pick; + objectRecursive, const TOptions extends ObjectSchemaOptionsAlpha = ObjectSchemaOptionsAlpha>(name: Name, t: T, options?: PreventExtraProperties): TreeNodeSchemaClass, NodeKind.Object, System_Unsafe.TreeObjectNodeUnsafe>, object & System_Unsafe.InsertableObjectFromSchemaRecordUnsafe, false, T, never, TOptions extends ObjectSchemaOptionsAlpha ? TCustomMetadataX : unknown> & SimpleObjectNodeSchema ? TCustomMetadataX : unknown> & Pick; static readonly optional: (t: T, props?: Omit, "defaultProvider"> | undefined) => FieldSchemaAlpha; readonly optional: (t: T, props?: Omit, "defaultProvider"> | undefined) => FieldSchemaAlpha; static readonly optionalRecursive: (t: T, props?: Omit, "defaultProvider"> | undefined) => FieldSchemaAlphaUnsafe; @@ -949,7 +1037,7 @@ export class SchemaFactoryAlpha extends SchemaFactory { - object, const TCustomMetadata = unknown>(name: Name, fields: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T, never, TCustomMetadata>; + object, const TOptions extends ObjectSchemaOptions = ObjectSchemaOptions>(name: Name, fields: T, options?: PreventExtraProperties): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode, TOptions>, object & InsertableObjectFromSchemaRecord, true, T, never, TOptions extends ObjectSchemaOptions ? TCustomMetadata : unknown>; // (undocumented) objectRecursive, const TCustomMetadata = unknown>(name: Name, t: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, System_Unsafe.TreeObjectNodeUnsafe>, object & System_Unsafe.InsertableObjectFromSchemaRecordUnsafe, false, T, never, TCustomMetadata>; record(allowedTypes: T): TreeNodeSchemaNonClass`>, NodeKind.Record, TreeRecordNode & WithType`>, NodeKind.Record>, RecordNodeInsertableData, true, T, undefined>; @@ -988,6 +1076,11 @@ export interface SchemaStaticsAlpha { readonly typesRecursive: >[]>(t: T, metadata?: AllowedTypesMetadata) => AllowedTypesFullFromMixedUnsafe; } +// @public @system +export type SchemaUnionToIntersection = [T] extends [ +CustomizedSchemaTyping +] ? T : UnionToIntersection; + // @beta @sealed export class SchemaUpgrade { // (undocumented) @@ -1093,6 +1186,16 @@ export function singletonSchema, true, Record, undefined>; +// @public @sealed +export interface StrictTypes, TOutput extends TreeNode | TreeLeafValue = DefaultTreeNodeFromImplicitAllowedTypes> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TInput extends never ? never : TOutput; +} + // @alpha @system export namespace System_TableSchema { // @sealed @system @@ -1127,18 +1230,18 @@ export namespace System_TableSchema { }>; // @system export function createTableSchema, const TRowSchema extends RowSchemaBase>(inputSchemaFactory: SchemaFactoryBeta, _cellSchema: TCellSchema, columnSchema: TColumnSchema, rowSchema: TRowSchema): TreeNodeSchemaCore_2, "Table">, NodeKind.Object, true, { - readonly rows: TreeNodeSchemaClass, "Table.rows">, NodeKind.Array, TreeArrayNode : TreeNodeFromImplicitAllowedTypes, [TRowSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.rows">, NodeKind.Array, unknown>, Iterable>, true, TRowSchema, undefined>; - readonly columns: TreeNodeSchemaClass, "Table.columns">, NodeKind.Array, TreeArrayNode : TreeNodeFromImplicitAllowedTypes, [TColumnSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.columns">, NodeKind.Array, unknown>, Iterable>, true, TColumnSchema, undefined>; + readonly rows: TreeNodeSchemaClass, "Table.rows">, NodeKind.Array, TreeArrayNode : TreeNode | TreeLeafValue_2, [TRowSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.rows">, NodeKind.Array, unknown>, Iterable>, true, TRowSchema, undefined>; + readonly columns: TreeNodeSchemaClass, "Table.columns">, NodeKind.Array, TreeArrayNode : TreeNode | TreeLeafValue_2, [TColumnSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.columns">, NodeKind.Array, unknown>, Iterable>, true, TColumnSchema, undefined>; }, object & { - readonly rows: (InsertableTypedNode_2, "Table.rows">, NodeKind.Array, true, TRowSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNodeFromImplicitAllowedTypes, [TRowSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.rows">, NodeKind.Array, unknown>)> | undefined) & InsertableTypedNode_2, "Table.rows">, NodeKind.Array, true, TRowSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNodeFromImplicitAllowedTypes, [TRowSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.rows">, NodeKind.Array, unknown>)>; - readonly columns: (InsertableTypedNode_2, "Table.columns">, NodeKind.Array, true, TColumnSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNodeFromImplicitAllowedTypes, [TColumnSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.columns">, NodeKind.Array, unknown>)> | undefined) & InsertableTypedNode_2, "Table.columns">, NodeKind.Array, true, TColumnSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNodeFromImplicitAllowedTypes, [TColumnSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.columns">, NodeKind.Array, unknown>)>; + readonly rows: (InsertableTypedNode_2, "Table.rows">, NodeKind.Array, true, TRowSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNode | TreeLeafValue_2, [TRowSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.rows">, NodeKind.Array, unknown>)> | undefined) & InsertableTypedNode_2, "Table.rows">, NodeKind.Array, true, TRowSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNode | TreeLeafValue_2, [TRowSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.rows">, NodeKind.Array, unknown>)>; + readonly columns: (InsertableTypedNode_2, "Table.columns">, NodeKind.Array, true, TColumnSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNode | TreeLeafValue_2, [TColumnSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.columns">, NodeKind.Array, unknown>)> | undefined) & InsertableTypedNode_2, "Table.columns">, NodeKind.Array, true, TColumnSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNode | TreeLeafValue_2, [TColumnSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.columns">, NodeKind.Array, unknown>)>; }, unknown> & (new (data: InternalTreeNode | (object & { - readonly rows: (InsertableTypedNode_2, "Table.rows">, NodeKind.Array, true, TRowSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNodeFromImplicitAllowedTypes, [TRowSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.rows">, NodeKind.Array, unknown>)> | undefined) & InsertableTypedNode_2, "Table.rows">, NodeKind.Array, true, TRowSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNodeFromImplicitAllowedTypes, [TRowSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.rows">, NodeKind.Array, unknown>)>; - readonly columns: (InsertableTypedNode_2, "Table.columns">, NodeKind.Array, true, TColumnSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNodeFromImplicitAllowedTypes, [TColumnSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.columns">, NodeKind.Array, unknown>)> | undefined) & InsertableTypedNode_2, "Table.columns">, NodeKind.Array, true, TColumnSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNodeFromImplicitAllowedTypes, [TColumnSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.columns">, NodeKind.Array, unknown>)>; + readonly rows: (InsertableTypedNode_2, "Table.rows">, NodeKind.Array, true, TRowSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNode | TreeLeafValue_2, [TRowSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.rows">, NodeKind.Array, unknown>)> | undefined) & InsertableTypedNode_2, "Table.rows">, NodeKind.Array, true, TRowSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNode | TreeLeafValue_2, [TRowSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.rows">, NodeKind.Array, unknown>)>; + readonly columns: (InsertableTypedNode_2, "Table.columns">, NodeKind.Array, true, TColumnSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNode | TreeLeafValue_2, [TColumnSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.columns">, NodeKind.Array, unknown>)> | undefined) & InsertableTypedNode_2, "Table.columns">, NodeKind.Array, true, TColumnSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNode | TreeLeafValue_2, [TColumnSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.columns">, NodeKind.Array, unknown>)>; })) => TreeNode & TableSchema.Table & WithType, "Table">, NodeKind, unknown>) & { empty, "Table.rows">, NodeKind.Array, true, TRowSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNodeFromImplicitAllowedTypes, [TRowSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.rows">, NodeKind.Array, unknown>)> | undefined) & InsertableTypedNode_2, "Table.rows">, NodeKind.Array, true, TRowSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNodeFromImplicitAllowedTypes, [TRowSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.rows">, NodeKind.Array, unknown>)>; - readonly columns: (InsertableTypedNode_2, "Table.columns">, NodeKind.Array, true, TColumnSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNodeFromImplicitAllowedTypes, [TColumnSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.columns">, NodeKind.Array, unknown>)> | undefined) & InsertableTypedNode_2, "Table.columns">, NodeKind.Array, true, TColumnSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNodeFromImplicitAllowedTypes, [TColumnSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.columns">, NodeKind.Array, unknown>)>; + readonly rows: (InsertableTypedNode_2, "Table.rows">, NodeKind.Array, true, TRowSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNode | TreeLeafValue_2, [TRowSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.rows">, NodeKind.Array, unknown>)> | undefined) & InsertableTypedNode_2, "Table.rows">, NodeKind.Array, true, TRowSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNode | TreeLeafValue_2, [TRowSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.rows">, NodeKind.Array, unknown>)>; + readonly columns: (InsertableTypedNode_2, "Table.columns">, NodeKind.Array, true, TColumnSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNode | TreeLeafValue_2, [TColumnSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.columns">, NodeKind.Array, unknown>)> | undefined) & InsertableTypedNode_2, "Table.columns">, NodeKind.Array, true, TColumnSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNode | TreeLeafValue_2, [TColumnSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.columns">, NodeKind.Array, unknown>)>; }) => TreeNode & TableSchema.Table & WithType, "Table">, NodeKind, unknown>>(this: TThis): InstanceType; }; // @system @@ -1163,6 +1266,28 @@ export namespace System_TableSchema { export namespace System_Unsafe { // @system export type AllowedTypesUnsafe = readonly LazyItem[]; + // @system + export type AssignableTreeFieldFromImplicitFieldUnsafe = TSchema extends FieldSchemaUnsafe ? ApplyKindAssignment["readWrite"], Kind> : TSchema extends ImplicitAllowedTypesUnsafe ? GetTypesUnsafe["readWrite"] : never; + // @sealed + export interface CustomizerUnsafe { + custom>(): CustomizedSchemaTyping & { + [Property in keyof CustomTypes]: Property extends keyof T ? T[Property] extends CustomTypes[Property] ? T[Property] : GetTypesUnsafe[Property] : GetTypesUnsafe[Property]; + }>; + simplified>(): CustomizedSchemaTyping; + simplifiedUnrestricted(): CustomizedSchemaTyping; + } + // @system + export type DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; + // @system + export type DefaultTreeNodeFromImplicitAllowedTypesUnsafe = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; // @sealed @system export type FieldHasDefaultUnsafe = T extends FieldSchemaUnsafe ? true : false; // @sealed @system @@ -1172,6 +1297,10 @@ export namespace System_Unsafe { readonly kind: Kind; } // @system + export type GetTypesUnsafe = [TSchema] extends [ + CustomizedSchemaTyping + ] ? TCustom : StrictTypesUnsafe; + // @system export type ImplicitAllowedTypesUnsafe = TreeNodeSchemaUnsafe | readonly LazyItem>[]; // @system export type ImplicitFieldSchemaUnsafe = FieldSchemaUnsafe | ImplicitAllowedTypesUnsafe; @@ -1188,7 +1317,7 @@ export namespace System_Unsafe { readonly [Property in keyof TList]: TList[Property] extends LazyItem ? InsertableTypedNodeUnsafe : never; }[number]; // @system - export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; + export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe = GetTypesUnsafe["input"]; // @system export type InsertableTypedNodeUnsafe> = (T extends TreeNodeSchemaUnsafe ? NodeBuilderDataUnsafe : never) | (T extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : never); // @system @@ -1197,7 +1326,21 @@ export namespace System_Unsafe { export type NodeFromSchemaUnsafe> = T extends TreeNodeSchemaUnsafe ? TNode : never; // @system export type ObjectFromSchemaRecordUnsafe> = { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitFieldUnsafe : unknown; + -readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? never : Property]: Property extends string ? AssignableTreeFieldFromImplicitFieldUnsafe : unknown; + } & { + readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? Property : never]: Property extends string ? TreeFieldFromImplicitFieldUnsafe : unknown; }; // @sealed @system export interface ReadonlyMapInlined { @@ -1214,6 +1357,15 @@ export namespace System_Unsafe { readonly size: number; values(): IterableIterator>; } + // @system + export interface StrictTypesUnsafe, TOutput = DefaultTreeNodeFromImplicitAllowedTypesUnsafe> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TOutput; + } // @sealed @system export interface TreeArrayNodeUnsafe extends TreeArrayNode, InsertableTreeNodeFromImplicitAllowedTypesUnsafe> { } @@ -1225,7 +1377,7 @@ export namespace System_Unsafe { set(key: string, value: InsertableTreeNodeFromImplicitAllowedTypesUnsafe | undefined): void; } // @system - export type TreeNodeFromImplicitAllowedTypesUnsafe = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; + export type TreeNodeFromImplicitAllowedTypesUnsafe = GetTypesUnsafe["output"]; // @system export interface TreeNodeSchemaClassUnsafe, in TInsertable, out ImplicitlyConstructable extends boolean, out Info, out TCustomMetadata = unknown> extends TreeNodeSchemaCore { // @sealed @@ -1492,6 +1644,9 @@ export interface TreeEncodingOptions { // @public export type TreeFieldFromImplicitField = TSchema extends FieldSchema ? ApplyKind, Kind> : TSchema extends ImplicitAllowedTypes ? TreeNodeFromImplicitAllowedTypes : TreeNode | TreeLeafValue | undefined; +// @public @system +export type TreeFieldFromImplicitFieldDefault = TSchema extends FieldSchema ? ApplyKind, Kind> : TSchema extends ImplicitAllowedTypes ? DefaultTreeNodeFromImplicitAllowedTypes : TreeNode | TreeLeafValue | undefined; + // @alpha @sealed export interface TreeIdentifierUtils { (node: TreeNode): string | undefined; @@ -1547,7 +1702,7 @@ export interface TreeNodeApi { } // @public -export type TreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; +export type TreeNodeFromImplicitAllowedTypes = GetTypes["output"]; // @public @sealed export type TreeNodeSchema = (TNode extends TreeNode ? TreeNodeSchemaClass : never) | TreeNodeSchemaNonClass; @@ -1578,7 +1733,7 @@ export type TreeNodeSchemaNonClass, TypeName extends string = string> = TreeNode & ObjectFromSchemaRecord & WithType; +export type TreeObjectNode, TypeName extends string = string, Options extends ObjectSchemaTypingOptions = Record> = TreeNode & WithType & ObjectFromSchemaRecord; // @alpha @input export type TreeParsingOptions = TreeEncodingOptions; diff --git a/packages/dds/tree/api-report/tree.beta.api.md b/packages/dds/tree/api-report/tree.beta.api.md index 724199559044..a29363d4656d 100644 --- a/packages/dds/tree/api-report/tree.beta.api.md +++ b/packages/dds/tree/api-report/tree.beta.api.md @@ -65,6 +65,11 @@ type ApplyKind = { [FieldKind.Identifier]: T; }[Kind]; +// @public @system +export type ApplyKindAssignment = [Kind] extends [ +FieldKind.Required +] ? T : [Kind] extends [FieldKind.Optional] ? T | undefined : never; + // @public @system type ApplyKindInput = [ Kind @@ -73,6 +78,12 @@ Kind // @beta export function asBeta(view: TreeView): TreeViewBeta; +// @public @system +export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; + +// @public @system +export type AssignableTreeFieldFromImplicitFieldDefault> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? StrictTypes["readWrite"] : never; + // @public export enum CommitKind { Default = 0, @@ -97,10 +108,34 @@ export function configuredSharedTreeBeta(options: SharedTreeOptionsBeta): Shared // @beta export function createIndependentTreeBeta(options?: ForestOptions): ViewableTree; +// @public @system +export type CustomizedSchemaTyping = TSchema & { + [CustomizedTyping]: TCustom; +}; + +// @public @system +export const CustomizedTyping: unique symbol; + +// @public @system +export type CustomizedTyping = typeof CustomizedTyping; + +// @public @sealed +export interface CustomTypes { + readonly input: unknown; + readonly output: TreeLeafValue | TreeNode; + readonly readWrite: TreeLeafValue | TreeNode; +} + +// @public @system +export type DefaultInsertableTreeNodeFromImplicitAllowedTypes = [TSchema] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; + // @public @sealed @system interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> { } +// @public @system +export type DefaultTreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; + // @beta export function enumFromStrings(factory: SchemaFactory, members: Members): ((value: TValue) => TValue extends unknown ? TreeNode & { readonly value: TValue; @@ -211,6 +246,11 @@ export const ForestTypeOptimized: ForestType; // @beta export const ForestTypeReference: ForestType; +// @public @system +export type GetTypes = [TSchema] extends [ +CustomizedSchemaTyping +] ? TCustom : StrictTypes; + // @public export type ImplicitAllowedTypes = AllowedTypes | TreeNodeSchema; @@ -233,7 +273,7 @@ type InsertableObjectFromSchemaRecord; // @public -export type InsertableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; +export type InsertableTreeFieldFromImplicitField] ? TSchemaInput : SchemaUnionToIntersection> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; // @public @system export type InsertableTreeNodeFromAllowedTypes = IsUnion extends true ? never : { @@ -243,9 +283,7 @@ export type InsertableTreeNodeFromAllowedTypes = IsU }[NumberKeys]; // @public -export type InsertableTreeNodeFromImplicitAllowedTypes = [ -TSchema -] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; +export type InsertableTreeNodeFromImplicitAllowedTypes = GetTypes["input"]; // @public export type InsertableTypedNode> = (T extends TreeNodeSchema ? NodeBuilderData : never) | (T extends TreeNodeSchema ? Unhydrated ? never : NodeFromSchema> : never); @@ -379,21 +417,45 @@ export type NumberKeys = Transformed[`${number}` & keyof Transformed]; // @public @system -export type ObjectFromSchemaRecord> = RestrictiveStringRecord extends T ? {} : { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; +export type ObjectFromSchemaRecord, Options extends ObjectSchemaTypingOptions = Record> = RestrictiveStringRecord extends T ? {} : [Options["supportReadonlyFields"]] extends [true] ? { + -readonly [Property in keyof T as [ + AssignableTreeFieldFromImplicitField + ] extends [never | undefined] ? never : Property]: [Options["supportCustomizedFields"]] extends [true] ? AssignableTreeFieldFromImplicitField : AssignableTreeFieldFromImplicitFieldDefault; +} & { + readonly [Property in keyof T]: [Options["supportCustomizedFields"]] extends [true] ? TreeFieldFromImplicitField : TreeFieldFromImplicitFieldDefault; +} : { + -readonly [Property in keyof T]: [Options["supportCustomizedFields"]] extends [true] ? TreeFieldFromImplicitField : TreeFieldFromImplicitFieldDefault; +}; + +// @beta @system +export type ObjectFromSchemaRecordRelaxed> = RestrictiveStringRecord extends T ? {} : { + [Property in keyof T]: TreeFieldFromImplicitFieldDefault; }; // @beta @input -export interface ObjectSchemaOptions extends NodeSchemaOptions { +export interface ObjectSchemaOptions extends NodeSchemaOptions, ObjectSchemaTypingOptions { readonly allowUnknownOptionalFields?: boolean; } +// @public @input +export interface ObjectSchemaTypingOptions { + // (undocumented) + readonly supportCustomizedFields?: true | undefined; + // (undocumented) + readonly supportReadonlyFields?: true | undefined; +} + // @public @deprecated export type Off = Off_2; // @beta @system export type PopUnion void : never>> = AsOverloadedFunction extends (a: infer First) => void ? First : never; +// @beta @system +export type PreventExtraProperties = T & { + [x in keyof T]: x extends keyof Expected ? T[x] : undefined; +}; + // @public @sealed @system export interface ReadonlyArrayNode extends ReadonlyArray, Awaited> { } @@ -489,7 +551,7 @@ export const SchemaFactory_base: SchemaStatics & (new () => SchemaStatics); // @beta export class SchemaFactoryBeta extends SchemaFactory { - object, const TCustomMetadata = unknown>(name: Name, fields: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T, never, TCustomMetadata>; + object, const TOptions extends ObjectSchemaOptions = ObjectSchemaOptions>(name: Name, fields: T, options?: PreventExtraProperties): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode, TOptions>, object & InsertableObjectFromSchemaRecord, true, T, never, TOptions extends ObjectSchemaOptions ? TCustomMetadata : unknown>; // (undocumented) objectRecursive, const TCustomMetadata = unknown>(name: Name, t: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, System_Unsafe.TreeObjectNodeUnsafe>, object & System_Unsafe.InsertableObjectFromSchemaRecordUnsafe, false, T, never, TCustomMetadata>; record(allowedTypes: T): TreeNodeSchemaNonClass`>, NodeKind.Record, TreeRecordNode & WithType`>, NodeKind.Record>, RecordNodeInsertableData, true, T, undefined>; @@ -520,6 +582,11 @@ export interface SchemaStatics { readonly string: LeafSchema<"string", string>; } +// @public @system +export type SchemaUnionToIntersection = [T] extends [ +CustomizedSchemaTyping +] ? T : UnionToIntersection; + // @beta @sealed export class SchemaUpgrade { // (undocumented) @@ -543,10 +610,42 @@ export function singletonSchema, true, Record, undefined>; +// @public @sealed +export interface StrictTypes, TOutput extends TreeNode | TreeLeafValue = DefaultTreeNodeFromImplicitAllowedTypes> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TInput extends never ? never : TOutput; +} + // @public @system export namespace System_Unsafe { // @system export type AllowedTypesUnsafe = readonly LazyItem[]; + // @system + export type AssignableTreeFieldFromImplicitFieldUnsafe = TSchema extends FieldSchemaUnsafe ? ApplyKindAssignment["readWrite"], Kind> : TSchema extends ImplicitAllowedTypesUnsafe ? GetTypesUnsafe["readWrite"] : never; + // @sealed + export interface CustomizerUnsafe { + custom>(): CustomizedSchemaTyping & { + [Property in keyof CustomTypes]: Property extends keyof T ? T[Property] extends CustomTypes[Property] ? T[Property] : GetTypesUnsafe[Property] : GetTypesUnsafe[Property]; + }>; + simplified>(): CustomizedSchemaTyping; + simplifiedUnrestricted(): CustomizedSchemaTyping; + } + // @system + export type DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; + // @system + export type DefaultTreeNodeFromImplicitAllowedTypesUnsafe = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; // @sealed @system export type FieldHasDefaultUnsafe = T extends FieldSchemaUnsafe ? true : false; // @sealed @system @@ -556,6 +655,10 @@ export namespace System_Unsafe { readonly kind: Kind; } // @system + export type GetTypesUnsafe = [TSchema] extends [ + CustomizedSchemaTyping + ] ? TCustom : StrictTypesUnsafe; + // @system export type ImplicitAllowedTypesUnsafe = TreeNodeSchemaUnsafe | readonly LazyItem>[]; // @system export type ImplicitFieldSchemaUnsafe = FieldSchemaUnsafe | ImplicitAllowedTypesUnsafe; @@ -572,7 +675,7 @@ export namespace System_Unsafe { readonly [Property in keyof TList]: TList[Property] extends LazyItem ? InsertableTypedNodeUnsafe : never; }[number]; // @system - export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; + export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe = GetTypesUnsafe["input"]; // @system export type InsertableTypedNodeUnsafe> = (T extends TreeNodeSchemaUnsafe ? NodeBuilderDataUnsafe : never) | (T extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : never); // @system @@ -581,7 +684,21 @@ export namespace System_Unsafe { export type NodeFromSchemaUnsafe> = T extends TreeNodeSchemaUnsafe ? TNode : never; // @system export type ObjectFromSchemaRecordUnsafe> = { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitFieldUnsafe : unknown; + -readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? never : Property]: Property extends string ? AssignableTreeFieldFromImplicitFieldUnsafe : unknown; + } & { + readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? Property : never]: Property extends string ? TreeFieldFromImplicitFieldUnsafe : unknown; }; // @sealed @system export interface ReadonlyMapInlined { @@ -598,6 +715,15 @@ export namespace System_Unsafe { readonly size: number; values(): IterableIterator>; } + // @system + export interface StrictTypesUnsafe, TOutput = DefaultTreeNodeFromImplicitAllowedTypesUnsafe> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TOutput; + } // @sealed @system export interface TreeArrayNodeUnsafe extends TreeArrayNode, InsertableTreeNodeFromImplicitAllowedTypesUnsafe> { } @@ -609,7 +735,7 @@ export namespace System_Unsafe { set(key: string, value: InsertableTreeNodeFromImplicitAllowedTypesUnsafe | undefined): void; } // @system - export type TreeNodeFromImplicitAllowedTypesUnsafe = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; + export type TreeNodeFromImplicitAllowedTypesUnsafe = GetTypesUnsafe["output"]; // @system export interface TreeNodeSchemaClassUnsafe, in TInsertable, out ImplicitlyConstructable extends boolean, out Info, out TCustomMetadata = unknown> extends TreeNodeSchemaCore { // @sealed @@ -705,6 +831,9 @@ export interface TreeEncodingOptions { // @public export type TreeFieldFromImplicitField = TSchema extends FieldSchema ? ApplyKind, Kind> : TSchema extends ImplicitAllowedTypes ? TreeNodeFromImplicitAllowedTypes : TreeNode | TreeLeafValue | undefined; +// @public @system +export type TreeFieldFromImplicitFieldDefault = TSchema extends FieldSchema ? ApplyKind, Kind> : TSchema extends ImplicitAllowedTypes ? DefaultTreeNodeFromImplicitAllowedTypes : TreeNode | TreeLeafValue | undefined; + // @public export type TreeLeafValue = number | string | boolean | IFluidHandle | null; @@ -740,7 +869,7 @@ export interface TreeNodeApi { } // @public -export type TreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; +export type TreeNodeFromImplicitAllowedTypes = GetTypes["output"]; // @public @sealed export type TreeNodeSchema = (TNode extends TreeNode ? TreeNodeSchemaClass : never) | TreeNodeSchemaNonClass; @@ -771,7 +900,7 @@ export type TreeNodeSchemaNonClass, TypeName extends string = string> = TreeNode & ObjectFromSchemaRecord & WithType; +export type TreeObjectNode, TypeName extends string = string, Options extends ObjectSchemaTypingOptions = Record> = TreeNode & WithType & ObjectFromSchemaRecord; // @beta export interface TreeRecordNode extends TreeNode, Record> { diff --git a/packages/dds/tree/api-report/tree.legacy.beta.api.md b/packages/dds/tree/api-report/tree.legacy.beta.api.md index 44cb68b83069..ffecf1b6e024 100644 --- a/packages/dds/tree/api-report/tree.legacy.beta.api.md +++ b/packages/dds/tree/api-report/tree.legacy.beta.api.md @@ -65,6 +65,11 @@ type ApplyKind = { [FieldKind.Identifier]: T; }[Kind]; +// @public @system +export type ApplyKindAssignment = [Kind] extends [ +FieldKind.Required +] ? T : [Kind] extends [FieldKind.Optional] ? T | undefined : never; + // @public @system type ApplyKindInput = [ Kind @@ -73,6 +78,12 @@ Kind // @beta export function asBeta(view: TreeView): TreeViewBeta; +// @public @system +export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; + +// @public @system +export type AssignableTreeFieldFromImplicitFieldDefault> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? StrictTypes["readWrite"] : never; + // @public export enum CommitKind { Default = 0, @@ -100,10 +111,34 @@ export function configuredSharedTreeBetaLegacy(options: SharedTreeOptionsBeta): // @beta export function createIndependentTreeBeta(options?: ForestOptions): ViewableTree; +// @public @system +export type CustomizedSchemaTyping = TSchema & { + [CustomizedTyping]: TCustom; +}; + +// @public @system +export const CustomizedTyping: unique symbol; + +// @public @system +export type CustomizedTyping = typeof CustomizedTyping; + +// @public @sealed +export interface CustomTypes { + readonly input: unknown; + readonly output: TreeLeafValue | TreeNode; + readonly readWrite: TreeLeafValue | TreeNode; +} + +// @public @system +export type DefaultInsertableTreeNodeFromImplicitAllowedTypes = [TSchema] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; + // @public @sealed @system interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> { } +// @public @system +export type DefaultTreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; + // @beta export function enumFromStrings(factory: SchemaFactory, members: Members): ((value: TValue) => TValue extends unknown ? TreeNode & { readonly value: TValue; @@ -214,6 +249,11 @@ export const ForestTypeOptimized: ForestType; // @beta export const ForestTypeReference: ForestType; +// @public @system +export type GetTypes = [TSchema] extends [ +CustomizedSchemaTyping +] ? TCustom : StrictTypes; + // @public export type ImplicitAllowedTypes = AllowedTypes | TreeNodeSchema; @@ -236,7 +276,7 @@ type InsertableObjectFromSchemaRecord; // @public -export type InsertableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; +export type InsertableTreeFieldFromImplicitField] ? TSchemaInput : SchemaUnionToIntersection> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; // @public @system export type InsertableTreeNodeFromAllowedTypes = IsUnion extends true ? never : { @@ -246,9 +286,7 @@ export type InsertableTreeNodeFromAllowedTypes = IsU }[NumberKeys]; // @public -export type InsertableTreeNodeFromImplicitAllowedTypes = [ -TSchema -] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; +export type InsertableTreeNodeFromImplicitAllowedTypes = GetTypes["input"]; // @public export type InsertableTypedNode> = (T extends TreeNodeSchema ? NodeBuilderData : never) | (T extends TreeNodeSchema ? Unhydrated ? never : NodeFromSchema> : never); @@ -382,21 +420,45 @@ export type NumberKeys = Transformed[`${number}` & keyof Transformed]; // @public @system -export type ObjectFromSchemaRecord> = RestrictiveStringRecord extends T ? {} : { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; +export type ObjectFromSchemaRecord, Options extends ObjectSchemaTypingOptions = Record> = RestrictiveStringRecord extends T ? {} : [Options["supportReadonlyFields"]] extends [true] ? { + -readonly [Property in keyof T as [ + AssignableTreeFieldFromImplicitField + ] extends [never | undefined] ? never : Property]: [Options["supportCustomizedFields"]] extends [true] ? AssignableTreeFieldFromImplicitField : AssignableTreeFieldFromImplicitFieldDefault; +} & { + readonly [Property in keyof T]: [Options["supportCustomizedFields"]] extends [true] ? TreeFieldFromImplicitField : TreeFieldFromImplicitFieldDefault; +} : { + -readonly [Property in keyof T]: [Options["supportCustomizedFields"]] extends [true] ? TreeFieldFromImplicitField : TreeFieldFromImplicitFieldDefault; +}; + +// @beta @system +export type ObjectFromSchemaRecordRelaxed> = RestrictiveStringRecord extends T ? {} : { + [Property in keyof T]: TreeFieldFromImplicitFieldDefault; }; // @beta @input -export interface ObjectSchemaOptions extends NodeSchemaOptions { +export interface ObjectSchemaOptions extends NodeSchemaOptions, ObjectSchemaTypingOptions { readonly allowUnknownOptionalFields?: boolean; } +// @public @input +export interface ObjectSchemaTypingOptions { + // (undocumented) + readonly supportCustomizedFields?: true | undefined; + // (undocumented) + readonly supportReadonlyFields?: true | undefined; +} + // @public @deprecated export type Off = Off_2; // @beta @system export type PopUnion void : never>> = AsOverloadedFunction extends (a: infer First) => void ? First : never; +// @beta @system +export type PreventExtraProperties = T & { + [x in keyof T]: x extends keyof Expected ? T[x] : undefined; +}; + // @public @sealed @system export interface ReadonlyArrayNode extends ReadonlyArray, Awaited> { } @@ -492,7 +554,7 @@ export const SchemaFactory_base: SchemaStatics & (new () => SchemaStatics); // @beta export class SchemaFactoryBeta extends SchemaFactory { - object, const TCustomMetadata = unknown>(name: Name, fields: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T, never, TCustomMetadata>; + object, const TOptions extends ObjectSchemaOptions = ObjectSchemaOptions>(name: Name, fields: T, options?: PreventExtraProperties): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode, TOptions>, object & InsertableObjectFromSchemaRecord, true, T, never, TOptions extends ObjectSchemaOptions ? TCustomMetadata : unknown>; // (undocumented) objectRecursive, const TCustomMetadata = unknown>(name: Name, t: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, System_Unsafe.TreeObjectNodeUnsafe>, object & System_Unsafe.InsertableObjectFromSchemaRecordUnsafe, false, T, never, TCustomMetadata>; record(allowedTypes: T): TreeNodeSchemaNonClass`>, NodeKind.Record, TreeRecordNode & WithType`>, NodeKind.Record>, RecordNodeInsertableData, true, T, undefined>; @@ -523,6 +585,11 @@ export interface SchemaStatics { readonly string: LeafSchema<"string", string>; } +// @public @system +export type SchemaUnionToIntersection = [T] extends [ +CustomizedSchemaTyping +] ? T : UnionToIntersection; + // @beta @sealed export class SchemaUpgrade { // (undocumented) @@ -555,10 +622,42 @@ export function singletonSchema, true, Record, undefined>; +// @public @sealed +export interface StrictTypes, TOutput extends TreeNode | TreeLeafValue = DefaultTreeNodeFromImplicitAllowedTypes> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TInput extends never ? never : TOutput; +} + // @public @system export namespace System_Unsafe { // @system export type AllowedTypesUnsafe = readonly LazyItem[]; + // @system + export type AssignableTreeFieldFromImplicitFieldUnsafe = TSchema extends FieldSchemaUnsafe ? ApplyKindAssignment["readWrite"], Kind> : TSchema extends ImplicitAllowedTypesUnsafe ? GetTypesUnsafe["readWrite"] : never; + // @sealed + export interface CustomizerUnsafe { + custom>(): CustomizedSchemaTyping & { + [Property in keyof CustomTypes]: Property extends keyof T ? T[Property] extends CustomTypes[Property] ? T[Property] : GetTypesUnsafe[Property] : GetTypesUnsafe[Property]; + }>; + simplified>(): CustomizedSchemaTyping; + simplifiedUnrestricted(): CustomizedSchemaTyping; + } + // @system + export type DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; + // @system + export type DefaultTreeNodeFromImplicitAllowedTypesUnsafe = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; // @sealed @system export type FieldHasDefaultUnsafe = T extends FieldSchemaUnsafe ? true : false; // @sealed @system @@ -568,6 +667,10 @@ export namespace System_Unsafe { readonly kind: Kind; } // @system + export type GetTypesUnsafe = [TSchema] extends [ + CustomizedSchemaTyping + ] ? TCustom : StrictTypesUnsafe; + // @system export type ImplicitAllowedTypesUnsafe = TreeNodeSchemaUnsafe | readonly LazyItem>[]; // @system export type ImplicitFieldSchemaUnsafe = FieldSchemaUnsafe | ImplicitAllowedTypesUnsafe; @@ -584,7 +687,7 @@ export namespace System_Unsafe { readonly [Property in keyof TList]: TList[Property] extends LazyItem ? InsertableTypedNodeUnsafe : never; }[number]; // @system - export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; + export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe = GetTypesUnsafe["input"]; // @system export type InsertableTypedNodeUnsafe> = (T extends TreeNodeSchemaUnsafe ? NodeBuilderDataUnsafe : never) | (T extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : never); // @system @@ -593,7 +696,21 @@ export namespace System_Unsafe { export type NodeFromSchemaUnsafe> = T extends TreeNodeSchemaUnsafe ? TNode : never; // @system export type ObjectFromSchemaRecordUnsafe> = { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitFieldUnsafe : unknown; + -readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? never : Property]: Property extends string ? AssignableTreeFieldFromImplicitFieldUnsafe : unknown; + } & { + readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? Property : never]: Property extends string ? TreeFieldFromImplicitFieldUnsafe : unknown; }; // @sealed @system export interface ReadonlyMapInlined { @@ -610,6 +727,15 @@ export namespace System_Unsafe { readonly size: number; values(): IterableIterator>; } + // @system + export interface StrictTypesUnsafe, TOutput = DefaultTreeNodeFromImplicitAllowedTypesUnsafe> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TOutput; + } // @sealed @system export interface TreeArrayNodeUnsafe extends TreeArrayNode, InsertableTreeNodeFromImplicitAllowedTypesUnsafe> { } @@ -621,7 +747,7 @@ export namespace System_Unsafe { set(key: string, value: InsertableTreeNodeFromImplicitAllowedTypesUnsafe | undefined): void; } // @system - export type TreeNodeFromImplicitAllowedTypesUnsafe = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; + export type TreeNodeFromImplicitAllowedTypesUnsafe = GetTypesUnsafe["output"]; // @system export interface TreeNodeSchemaClassUnsafe, in TInsertable, out ImplicitlyConstructable extends boolean, out Info, out TCustomMetadata = unknown> extends TreeNodeSchemaCore { // @sealed @@ -717,6 +843,9 @@ export interface TreeEncodingOptions { // @public export type TreeFieldFromImplicitField = TSchema extends FieldSchema ? ApplyKind, Kind> : TSchema extends ImplicitAllowedTypes ? TreeNodeFromImplicitAllowedTypes : TreeNode | TreeLeafValue | undefined; +// @public @system +export type TreeFieldFromImplicitFieldDefault = TSchema extends FieldSchema ? ApplyKind, Kind> : TSchema extends ImplicitAllowedTypes ? DefaultTreeNodeFromImplicitAllowedTypes : TreeNode | TreeLeafValue | undefined; + // @public export type TreeLeafValue = number | string | boolean | IFluidHandle | null; @@ -752,7 +881,7 @@ export interface TreeNodeApi { } // @public -export type TreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; +export type TreeNodeFromImplicitAllowedTypes = GetTypes["output"]; // @public @sealed export type TreeNodeSchema = (TNode extends TreeNode ? TreeNodeSchemaClass : never) | TreeNodeSchemaNonClass; @@ -783,7 +912,7 @@ export type TreeNodeSchemaNonClass, TypeName extends string = string> = TreeNode & ObjectFromSchemaRecord & WithType; +export type TreeObjectNode, TypeName extends string = string, Options extends ObjectSchemaTypingOptions = Record> = TreeNode & WithType & ObjectFromSchemaRecord; // @beta export interface TreeRecordNode extends TreeNode, Record> { 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 fdec01b3f397..2af9ba6007c0 100644 --- a/packages/dds/tree/api-report/tree.legacy.public.api.md +++ b/packages/dds/tree/api-report/tree.legacy.public.api.md @@ -14,11 +14,22 @@ type ApplyKind = { [FieldKind.Identifier]: T; }[Kind]; +// @public @system +export type ApplyKindAssignment = [Kind] extends [ +FieldKind.Required +] ? T : [Kind] extends [FieldKind.Optional] ? T | undefined : never; + // @public @system type ApplyKindInput = [ Kind ] extends [FieldKind.Required] ? T : [Kind] extends [FieldKind.Optional] ? T | undefined : [Kind] extends [FieldKind.Identifier] ? DefaultsAreOptional extends true ? T | undefined : T : never; +// @public @system +export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; + +// @public @system +export type AssignableTreeFieldFromImplicitFieldDefault> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? StrictTypes["readWrite"] : never; + // @public export enum CommitKind { Default = 0, @@ -32,10 +43,34 @@ export interface CommitMetadata { readonly kind: CommitKind; } +// @public @system +export type CustomizedSchemaTyping = TSchema & { + [CustomizedTyping]: TCustom; +}; + +// @public @system +export const CustomizedTyping: unique symbol; + +// @public @system +export type CustomizedTyping = typeof CustomizedTyping; + +// @public @sealed +export interface CustomTypes { + readonly input: unknown; + readonly output: TreeLeafValue | TreeNode; + readonly readWrite: TreeLeafValue | TreeNode; +} + +// @public @system +export type DefaultInsertableTreeNodeFromImplicitAllowedTypes = [TSchema] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; + // @public @sealed @system interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> { } +// @public @system +export type DefaultTreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; + // @public @system type ExtractItemType = Item extends () => infer Result ? Result : Item; @@ -90,6 +125,11 @@ type FlexList = readonly LazyItem[]; // @public @system type FlexListToUnion = ExtractItemType; +// @public @system +export type GetTypes = [TSchema] extends [ +CustomizedSchemaTyping +] ? TCustom : StrictTypes; + // @public export type ImplicitAllowedTypes = AllowedTypes | TreeNodeSchema; @@ -112,7 +152,7 @@ type InsertableObjectFromSchemaRecord; // @public -export type InsertableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; +export type InsertableTreeFieldFromImplicitField] ? TSchemaInput : SchemaUnionToIntersection> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; // @public @system export type InsertableTreeNodeFromAllowedTypes = IsUnion extends true ? never : { @@ -122,9 +162,7 @@ export type InsertableTreeNodeFromAllowedTypes = IsU }[NumberKeys]; // @public -export type InsertableTreeNodeFromImplicitAllowedTypes = [ -TSchema -] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; +export type InsertableTreeNodeFromImplicitAllowedTypes = GetTypes["input"]; // @public export type InsertableTypedNode> = (T extends TreeNodeSchema ? NodeBuilderData : never) | (T extends TreeNodeSchema ? Unhydrated ? never : NodeFromSchema> : never); @@ -238,10 +276,24 @@ export type NumberKeys = Transformed[`${number}` & keyof Transformed]; // @public @system -export type ObjectFromSchemaRecord> = RestrictiveStringRecord extends T ? {} : { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; +export type ObjectFromSchemaRecord, Options extends ObjectSchemaTypingOptions = Record> = RestrictiveStringRecord extends T ? {} : [Options["supportReadonlyFields"]] extends [true] ? { + -readonly [Property in keyof T as [ + AssignableTreeFieldFromImplicitField + ] extends [never | undefined] ? never : Property]: [Options["supportCustomizedFields"]] extends [true] ? AssignableTreeFieldFromImplicitField : AssignableTreeFieldFromImplicitFieldDefault; +} & { + readonly [Property in keyof T]: [Options["supportCustomizedFields"]] extends [true] ? TreeFieldFromImplicitField : TreeFieldFromImplicitFieldDefault; +} : { + -readonly [Property in keyof T]: [Options["supportCustomizedFields"]] extends [true] ? TreeFieldFromImplicitField : TreeFieldFromImplicitFieldDefault; }; +// @public @input +export interface ObjectSchemaTypingOptions { + // (undocumented) + readonly supportCustomizedFields?: true | undefined; + // (undocumented) + readonly supportReadonlyFields?: true | undefined; +} + // @public @deprecated export type Off = Off_2; @@ -355,6 +407,11 @@ export interface SchemaStatics { readonly string: LeafSchema<"string", string>; } +// @public @system +export type SchemaUnionToIntersection = [T] extends [ +CustomizedSchemaTyping +] ? T : UnionToIntersection; + // @public @system type ScopedSchemaName = TScope extends undefined ? `${TName}` : `${TScope}.${TName}`; @@ -364,10 +421,42 @@ export interface SimpleNodeSchemaBase; } +// @public @sealed +export interface StrictTypes, TOutput extends TreeNode | TreeLeafValue = DefaultTreeNodeFromImplicitAllowedTypes> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TInput extends never ? never : TOutput; +} + // @public @system export namespace System_Unsafe { // @system export type AllowedTypesUnsafe = readonly LazyItem[]; + // @system + export type AssignableTreeFieldFromImplicitFieldUnsafe = TSchema extends FieldSchemaUnsafe ? ApplyKindAssignment["readWrite"], Kind> : TSchema extends ImplicitAllowedTypesUnsafe ? GetTypesUnsafe["readWrite"] : never; + // @sealed + export interface CustomizerUnsafe { + custom>(): CustomizedSchemaTyping & { + [Property in keyof CustomTypes]: Property extends keyof T ? T[Property] extends CustomTypes[Property] ? T[Property] : GetTypesUnsafe[Property] : GetTypesUnsafe[Property]; + }>; + simplified>(): CustomizedSchemaTyping; + simplifiedUnrestricted(): CustomizedSchemaTyping; + } + // @system + export type DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; + // @system + export type DefaultTreeNodeFromImplicitAllowedTypesUnsafe = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; // @sealed @system export type FieldHasDefaultUnsafe = T extends FieldSchemaUnsafe ? true : false; // @sealed @system @@ -377,6 +466,10 @@ export namespace System_Unsafe { readonly kind: Kind; } // @system + export type GetTypesUnsafe = [TSchema] extends [ + CustomizedSchemaTyping + ] ? TCustom : StrictTypesUnsafe; + // @system export type ImplicitAllowedTypesUnsafe = TreeNodeSchemaUnsafe | readonly LazyItem>[]; // @system export type ImplicitFieldSchemaUnsafe = FieldSchemaUnsafe | ImplicitAllowedTypesUnsafe; @@ -393,7 +486,7 @@ export namespace System_Unsafe { readonly [Property in keyof TList]: TList[Property] extends LazyItem ? InsertableTypedNodeUnsafe : never; }[number]; // @system - export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; + export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe = GetTypesUnsafe["input"]; // @system export type InsertableTypedNodeUnsafe> = (T extends TreeNodeSchemaUnsafe ? NodeBuilderDataUnsafe : never) | (T extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : never); // @system @@ -402,7 +495,21 @@ export namespace System_Unsafe { export type NodeFromSchemaUnsafe> = T extends TreeNodeSchemaUnsafe ? TNode : never; // @system export type ObjectFromSchemaRecordUnsafe> = { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitFieldUnsafe : unknown; + -readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? never : Property]: Property extends string ? AssignableTreeFieldFromImplicitFieldUnsafe : unknown; + } & { + readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? Property : never]: Property extends string ? TreeFieldFromImplicitFieldUnsafe : unknown; }; // @sealed @system export interface ReadonlyMapInlined { @@ -419,6 +526,15 @@ export namespace System_Unsafe { readonly size: number; values(): IterableIterator>; } + // @system + export interface StrictTypesUnsafe, TOutput = DefaultTreeNodeFromImplicitAllowedTypesUnsafe> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TOutput; + } // @sealed @system export interface TreeArrayNodeUnsafe extends TreeArrayNode, InsertableTreeNodeFromImplicitAllowedTypesUnsafe> { } @@ -430,7 +546,7 @@ export namespace System_Unsafe { set(key: string, value: InsertableTreeNodeFromImplicitAllowedTypesUnsafe | undefined): void; } // @system - export type TreeNodeFromImplicitAllowedTypesUnsafe = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; + export type TreeNodeFromImplicitAllowedTypesUnsafe = GetTypesUnsafe["output"]; // @system export interface TreeNodeSchemaClassUnsafe, in TInsertable, out ImplicitlyConstructable extends boolean, out Info, out TCustomMetadata = unknown> extends TreeNodeSchemaCore { // @sealed @@ -495,6 +611,9 @@ export interface TreeChangeEvents { // @public export type TreeFieldFromImplicitField = TSchema extends FieldSchema ? ApplyKind, Kind> : TSchema extends ImplicitAllowedTypes ? TreeNodeFromImplicitAllowedTypes : TreeNode | TreeLeafValue | undefined; +// @public @system +export type TreeFieldFromImplicitFieldDefault = TSchema extends FieldSchema ? ApplyKind, Kind> : TSchema extends ImplicitAllowedTypes ? DefaultTreeNodeFromImplicitAllowedTypes : TreeNode | TreeLeafValue | undefined; + // @public export type TreeLeafValue = number | string | boolean | IFluidHandle | null; @@ -530,7 +649,7 @@ export interface TreeNodeApi { } // @public -export type TreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; +export type TreeNodeFromImplicitAllowedTypes = GetTypes["output"]; // @public @sealed export type TreeNodeSchema = (TNode extends TreeNode ? TreeNodeSchemaClass : never) | TreeNodeSchemaNonClass; @@ -561,7 +680,7 @@ export type TreeNodeSchemaNonClass, TypeName extends string = string> = TreeNode & ObjectFromSchemaRecord & WithType; +export type TreeObjectNode, TypeName extends string = string, Options extends ObjectSchemaTypingOptions = Record> = TreeNode & WithType & ObjectFromSchemaRecord; // @public export enum TreeStatus { diff --git a/packages/dds/tree/api-report/tree.public.api.md b/packages/dds/tree/api-report/tree.public.api.md index fdec01b3f397..2af9ba6007c0 100644 --- a/packages/dds/tree/api-report/tree.public.api.md +++ b/packages/dds/tree/api-report/tree.public.api.md @@ -14,11 +14,22 @@ type ApplyKind = { [FieldKind.Identifier]: T; }[Kind]; +// @public @system +export type ApplyKindAssignment = [Kind] extends [ +FieldKind.Required +] ? T : [Kind] extends [FieldKind.Optional] ? T | undefined : never; + // @public @system type ApplyKindInput = [ Kind ] extends [FieldKind.Required] ? T : [Kind] extends [FieldKind.Optional] ? T | undefined : [Kind] extends [FieldKind.Identifier] ? DefaultsAreOptional extends true ? T | undefined : T : never; +// @public @system +export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; + +// @public @system +export type AssignableTreeFieldFromImplicitFieldDefault> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? StrictTypes["readWrite"] : never; + // @public export enum CommitKind { Default = 0, @@ -32,10 +43,34 @@ export interface CommitMetadata { readonly kind: CommitKind; } +// @public @system +export type CustomizedSchemaTyping = TSchema & { + [CustomizedTyping]: TCustom; +}; + +// @public @system +export const CustomizedTyping: unique symbol; + +// @public @system +export type CustomizedTyping = typeof CustomizedTyping; + +// @public @sealed +export interface CustomTypes { + readonly input: unknown; + readonly output: TreeLeafValue | TreeNode; + readonly readWrite: TreeLeafValue | TreeNode; +} + +// @public @system +export type DefaultInsertableTreeNodeFromImplicitAllowedTypes = [TSchema] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; + // @public @sealed @system interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> { } +// @public @system +export type DefaultTreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; + // @public @system type ExtractItemType = Item extends () => infer Result ? Result : Item; @@ -90,6 +125,11 @@ type FlexList = readonly LazyItem[]; // @public @system type FlexListToUnion = ExtractItemType; +// @public @system +export type GetTypes = [TSchema] extends [ +CustomizedSchemaTyping +] ? TCustom : StrictTypes; + // @public export type ImplicitAllowedTypes = AllowedTypes | TreeNodeSchema; @@ -112,7 +152,7 @@ type InsertableObjectFromSchemaRecord; // @public -export type InsertableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; +export type InsertableTreeFieldFromImplicitField] ? TSchemaInput : SchemaUnionToIntersection> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; // @public @system export type InsertableTreeNodeFromAllowedTypes = IsUnion extends true ? never : { @@ -122,9 +162,7 @@ export type InsertableTreeNodeFromAllowedTypes = IsU }[NumberKeys]; // @public -export type InsertableTreeNodeFromImplicitAllowedTypes = [ -TSchema -] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; +export type InsertableTreeNodeFromImplicitAllowedTypes = GetTypes["input"]; // @public export type InsertableTypedNode> = (T extends TreeNodeSchema ? NodeBuilderData : never) | (T extends TreeNodeSchema ? Unhydrated ? never : NodeFromSchema> : never); @@ -238,10 +276,24 @@ export type NumberKeys = Transformed[`${number}` & keyof Transformed]; // @public @system -export type ObjectFromSchemaRecord> = RestrictiveStringRecord extends T ? {} : { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; +export type ObjectFromSchemaRecord, Options extends ObjectSchemaTypingOptions = Record> = RestrictiveStringRecord extends T ? {} : [Options["supportReadonlyFields"]] extends [true] ? { + -readonly [Property in keyof T as [ + AssignableTreeFieldFromImplicitField + ] extends [never | undefined] ? never : Property]: [Options["supportCustomizedFields"]] extends [true] ? AssignableTreeFieldFromImplicitField : AssignableTreeFieldFromImplicitFieldDefault; +} & { + readonly [Property in keyof T]: [Options["supportCustomizedFields"]] extends [true] ? TreeFieldFromImplicitField : TreeFieldFromImplicitFieldDefault; +} : { + -readonly [Property in keyof T]: [Options["supportCustomizedFields"]] extends [true] ? TreeFieldFromImplicitField : TreeFieldFromImplicitFieldDefault; }; +// @public @input +export interface ObjectSchemaTypingOptions { + // (undocumented) + readonly supportCustomizedFields?: true | undefined; + // (undocumented) + readonly supportReadonlyFields?: true | undefined; +} + // @public @deprecated export type Off = Off_2; @@ -355,6 +407,11 @@ export interface SchemaStatics { readonly string: LeafSchema<"string", string>; } +// @public @system +export type SchemaUnionToIntersection = [T] extends [ +CustomizedSchemaTyping +] ? T : UnionToIntersection; + // @public @system type ScopedSchemaName = TScope extends undefined ? `${TName}` : `${TScope}.${TName}`; @@ -364,10 +421,42 @@ export interface SimpleNodeSchemaBase; } +// @public @sealed +export interface StrictTypes, TOutput extends TreeNode | TreeLeafValue = DefaultTreeNodeFromImplicitAllowedTypes> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TInput extends never ? never : TOutput; +} + // @public @system export namespace System_Unsafe { // @system export type AllowedTypesUnsafe = readonly LazyItem[]; + // @system + export type AssignableTreeFieldFromImplicitFieldUnsafe = TSchema extends FieldSchemaUnsafe ? ApplyKindAssignment["readWrite"], Kind> : TSchema extends ImplicitAllowedTypesUnsafe ? GetTypesUnsafe["readWrite"] : never; + // @sealed + export interface CustomizerUnsafe { + custom>(): CustomizedSchemaTyping & { + [Property in keyof CustomTypes]: Property extends keyof T ? T[Property] extends CustomTypes[Property] ? T[Property] : GetTypesUnsafe[Property] : GetTypesUnsafe[Property]; + }>; + simplified>(): CustomizedSchemaTyping; + simplifiedUnrestricted(): CustomizedSchemaTyping; + } + // @system + export type DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; + // @system + export type DefaultTreeNodeFromImplicitAllowedTypesUnsafe = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; // @sealed @system export type FieldHasDefaultUnsafe = T extends FieldSchemaUnsafe ? true : false; // @sealed @system @@ -377,6 +466,10 @@ export namespace System_Unsafe { readonly kind: Kind; } // @system + export type GetTypesUnsafe = [TSchema] extends [ + CustomizedSchemaTyping + ] ? TCustom : StrictTypesUnsafe; + // @system export type ImplicitAllowedTypesUnsafe = TreeNodeSchemaUnsafe | readonly LazyItem>[]; // @system export type ImplicitFieldSchemaUnsafe = FieldSchemaUnsafe | ImplicitAllowedTypesUnsafe; @@ -393,7 +486,7 @@ export namespace System_Unsafe { readonly [Property in keyof TList]: TList[Property] extends LazyItem ? InsertableTypedNodeUnsafe : never; }[number]; // @system - export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; + export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe = GetTypesUnsafe["input"]; // @system export type InsertableTypedNodeUnsafe> = (T extends TreeNodeSchemaUnsafe ? NodeBuilderDataUnsafe : never) | (T extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : never); // @system @@ -402,7 +495,21 @@ export namespace System_Unsafe { export type NodeFromSchemaUnsafe> = T extends TreeNodeSchemaUnsafe ? TNode : never; // @system export type ObjectFromSchemaRecordUnsafe> = { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitFieldUnsafe : unknown; + -readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? never : Property]: Property extends string ? AssignableTreeFieldFromImplicitFieldUnsafe : unknown; + } & { + readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? Property : never]: Property extends string ? TreeFieldFromImplicitFieldUnsafe : unknown; }; // @sealed @system export interface ReadonlyMapInlined { @@ -419,6 +526,15 @@ export namespace System_Unsafe { readonly size: number; values(): IterableIterator>; } + // @system + export interface StrictTypesUnsafe, TOutput = DefaultTreeNodeFromImplicitAllowedTypesUnsafe> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TOutput; + } // @sealed @system export interface TreeArrayNodeUnsafe extends TreeArrayNode, InsertableTreeNodeFromImplicitAllowedTypesUnsafe> { } @@ -430,7 +546,7 @@ export namespace System_Unsafe { set(key: string, value: InsertableTreeNodeFromImplicitAllowedTypesUnsafe | undefined): void; } // @system - export type TreeNodeFromImplicitAllowedTypesUnsafe = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; + export type TreeNodeFromImplicitAllowedTypesUnsafe = GetTypesUnsafe["output"]; // @system export interface TreeNodeSchemaClassUnsafe, in TInsertable, out ImplicitlyConstructable extends boolean, out Info, out TCustomMetadata = unknown> extends TreeNodeSchemaCore { // @sealed @@ -495,6 +611,9 @@ export interface TreeChangeEvents { // @public export type TreeFieldFromImplicitField = TSchema extends FieldSchema ? ApplyKind, Kind> : TSchema extends ImplicitAllowedTypes ? TreeNodeFromImplicitAllowedTypes : TreeNode | TreeLeafValue | undefined; +// @public @system +export type TreeFieldFromImplicitFieldDefault = TSchema extends FieldSchema ? ApplyKind, Kind> : TSchema extends ImplicitAllowedTypes ? DefaultTreeNodeFromImplicitAllowedTypes : TreeNode | TreeLeafValue | undefined; + // @public export type TreeLeafValue = number | string | boolean | IFluidHandle | null; @@ -530,7 +649,7 @@ export interface TreeNodeApi { } // @public -export type TreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; +export type TreeNodeFromImplicitAllowedTypes = GetTypes["output"]; // @public @sealed export type TreeNodeSchema = (TNode extends TreeNode ? TreeNodeSchemaClass : never) | TreeNodeSchemaNonClass; @@ -561,7 +680,7 @@ export type TreeNodeSchemaNonClass, TypeName extends string = string> = TreeNode & ObjectFromSchemaRecord & WithType; +export type TreeObjectNode, TypeName extends string = string, Options extends ObjectSchemaTypingOptions = Record> = TreeNode & WithType & ObjectFromSchemaRecord; // @public export enum TreeStatus { diff --git a/packages/dds/tree/package.json b/packages/dds/tree/package.json index 28a23f045444..0314eff615c5 100644 --- a/packages/dds/tree/package.json +++ b/packages/dds/tree/package.json @@ -237,7 +237,11 @@ } }, "typeValidation": { - "broken": {}, + "broken": { + "TypeAlias_InsertableTreeNodeFromImplicitAllowedTypes": { + "backCompat": false + } + }, "entrypoint": "public" } } diff --git a/packages/dds/tree/src/index.ts b/packages/dds/tree/src/index.ts index ab01f8b96d2b..e0392d5af6b7 100644 --- a/packages/dds/tree/src/index.ts +++ b/packages/dds/tree/src/index.ts @@ -244,6 +244,17 @@ export { type NodeSchemaOptions, type NodeSchemaOptionsAlpha, type NodeSchemaMetadata, + type AssignableTreeFieldFromImplicitField, + type ApplyKindAssignment, + type Customizer, + type GetTypes, + type StrictTypes, + type CustomTypes, + type CustomizedSchemaTyping, + CustomizedTyping, + type DefaultInsertableTreeNodeFromImplicitAllowedTypes, + customizeSchemaTyping, + type SchemaUnionToIntersection, type SchemaStatics, type ITreeAlpha, type TransactionConstraint, @@ -283,6 +294,11 @@ export { type SchemaFactory_base, type NumberKeys, type SimpleAllowedTypeAttributes, + type DefaultTreeNodeFromImplicitAllowedTypes, + type ObjectFromSchemaRecordRelaxed, + type ObjectSchemaTypingOptions, + type AssignableTreeFieldFromImplicitFieldDefault, + type TreeFieldFromImplicitFieldDefault, } from "./simple-tree/index.js"; export { SharedTree, @@ -323,6 +339,7 @@ export type { JsonCompatibleObject, JsonCompatibleReadOnly, JsonCompatibleReadOnlyObject, + PreventExtraProperties, } from "./util/index.js"; export { cloneWithReplacements } from "./util/index.js"; diff --git a/packages/dds/tree/src/simple-tree/api/index.ts b/packages/dds/tree/src/simple-tree/api/index.ts index 1c930da6ee7e..16a7a47e3471 100644 --- a/packages/dds/tree/src/simple-tree/api/index.ts +++ b/packages/dds/tree/src/simple-tree/api/index.ts @@ -37,7 +37,11 @@ export { type SchemaFactory_base, } from "./schemaFactory.js"; export { SchemaFactoryBeta } from "./schemaFactoryBeta.js"; -export { SchemaFactoryAlpha, type SchemaStaticsAlpha } from "./schemaFactoryAlpha.js"; +export { + SchemaFactoryAlpha, + type SchemaStaticsAlpha, + relaxObject, +} from "./schemaFactoryAlpha.js"; export type { ValidateRecursiveSchema, FixRecursiveArraySchema, @@ -99,6 +103,7 @@ export type { AllowedTypesFullFromMixedUnsafe, UnannotateAllowedTypesListUnsafe, AnnotateAllowedTypesListUnsafe, + customizeSchemaTypingUnsafe, } from "./typesUnsafe.js"; export { diff --git a/packages/dds/tree/src/simple-tree/api/schemaFactory.ts b/packages/dds/tree/src/simple-tree/api/schemaFactory.ts index f97777709dcb..0d5123b8fc07 100644 --- a/packages/dds/tree/src/simple-tree/api/schemaFactory.ts +++ b/packages/dds/tree/src/simple-tree/api/schemaFactory.ts @@ -50,6 +50,7 @@ import { type InsertableObjectFromSchemaRecord, type TreeMapNode, type TreeObjectNode, + type ObjectSchemaTypingOptions, } from "../node-kinds/index.js"; import { FieldKind, @@ -100,7 +101,8 @@ export function schemaFromValue(value: TreeValue): TreeNodeSchema { * @beta */ export interface ObjectSchemaOptions - extends NodeSchemaOptions { + extends NodeSchemaOptions, + ObjectSchemaTypingOptions { /** * Allow nodes typed with this object node schema to contain optional fields that are not present in the schema declaration. * Such nodes can come into existence either via import APIs (see remarks) or by way of collaboration with another client @@ -159,16 +161,6 @@ export interface ObjectSchemaOptionsAlpha extends ObjectSchemaOptions, NodeSchemaOptionsAlpha {} -/** - * Default options for Object node schema creation. - * @remarks Omits parameters that are not relevant for common use cases. - */ -export const defaultSchemaFactoryObjectOptions: Required< - Pick -> = { - allowUnknownOptionalFields: false, -}; - /** * The name of a schema produced by {@link SchemaFactory}, including its optional scope prefix. * @@ -397,9 +389,7 @@ export class SchemaFactory< true, T > { - return objectSchema(scoped(this, name), fields, true, { - ...defaultSchemaFactoryObjectOptions, - }); + return objectSchema(scoped(this, name), fields, true); } /** diff --git a/packages/dds/tree/src/simple-tree/api/schemaFactoryAlpha.ts b/packages/dds/tree/src/simple-tree/api/schemaFactoryAlpha.ts index 82d58ef276f8..cce2d2d08d6b 100644 --- a/packages/dds/tree/src/simple-tree/api/schemaFactoryAlpha.ts +++ b/packages/dds/tree/src/simple-tree/api/schemaFactoryAlpha.ts @@ -8,13 +8,13 @@ import { arraySchema, type MapNodeCustomizableSchema, mapSchema, + type ObjectFromSchemaRecordRelaxed, type ObjectNodeSchema, objectSchema, type RecordNodeCustomizableSchema, recordSchema, } from "../node-kinds/index.js"; import { - defaultSchemaFactoryObjectOptions, scoped, type NodeSchemaOptionsAlpha, type ObjectSchemaOptionsAlpha, @@ -22,7 +22,7 @@ import { } from "./schemaFactory.js"; import { schemaStatics } from "./schemaStatics.js"; import type { ImplicitFieldSchema } from "../fieldSchema.js"; -import type { RestrictiveStringRecord } from "../../util/index.js"; +import type { PreventExtraProperties, RestrictiveStringRecord } from "../../util/index.js"; import type { NodeKind, TreeNodeSchema, @@ -33,6 +33,7 @@ import type { WithType, AllowedTypesMetadata, AllowedTypesFullFromMixed, + TreeNode, } from "../core/index.js"; import { normalizeToAnnotatedAllowedType, @@ -200,12 +201,12 @@ export class SchemaFactoryAlpha< public objectAlpha< const Name extends TName, const T extends RestrictiveStringRecord, - const TCustomMetadata = unknown, + const TOptions extends ObjectSchemaOptionsAlpha = ObjectSchemaOptionsAlpha, >( name: Name, fields: T, - options?: ObjectSchemaOptionsAlpha, - ): ObjectNodeSchema, T, true, TCustomMetadata> & { + options?: PreventExtraProperties, + ): ObjectNodeSchema, T, true, TOptions> & { /** * Typing checking workaround: not for for actual use. * @remarks @@ -218,10 +219,7 @@ export class SchemaFactoryAlpha< */ readonly createFromInsertable: unknown; } { - return objectSchema(scoped(this, name), fields, true, { - ...defaultSchemaFactoryObjectOptions, - ...(options ?? {}), - }); + return objectSchema(scoped(this, name), fields, true, options); } /** @@ -230,11 +228,11 @@ export class SchemaFactoryAlpha< public override objectRecursive< const Name extends TName, const T extends RestrictiveStringRecord, - const TCustomMetadata = unknown, + const TOptions extends ObjectSchemaOptionsAlpha = ObjectSchemaOptionsAlpha, >( name: Name, t: T, - options?: ObjectSchemaOptionsAlpha, + options?: PreventExtraProperties, ): TreeNodeSchemaClass< ScopedSchemaName, NodeKind.Object, @@ -243,9 +241,15 @@ export class SchemaFactoryAlpha< false, T, never, - TCustomMetadata + TOptions extends ObjectSchemaOptionsAlpha + ? TCustomMetadataX + : unknown > & - SimpleObjectNodeSchema & + SimpleObjectNodeSchema< + TOptions extends ObjectSchemaOptionsAlpha + ? TCustomMetadataX + : unknown + > & // We can't just use non generic `ObjectNodeSchema` here since "Base constructors must all have the same return type". // We also can't just use generic `ObjectNodeSchema` here and not `TreeNodeSchemaClass` since that doesn't work with unsafe recursive types. // ObjectNodeSchema< @@ -270,13 +274,15 @@ export class SchemaFactoryAlpha< false, T, never, - TCustomMetadata + TOptions extends ObjectSchemaOptionsAlpha + ? TCustomMetadataX + : unknown > & ObjectNodeSchema< ScopedSchemaName, RestrictiveStringRecord, false, - TCustomMetadata + TOptions >; } @@ -559,3 +565,37 @@ export class SchemaFactoryAlpha< return new SchemaFactoryAlpha(scoped(this, name)); } } + +/** + * Convert an object node to a version with a relaxed types for its fields. + * @remarks + * This can help get TypeScript to allow sub-classing it in generic contexts. + * This must be to the class from the SchemaFactory then subclassed. + * @alpha + */ +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +export function relaxObject>( + t: T, +) { + return t as T extends TreeNodeSchemaClass< + infer Name, + NodeKind.Object, + TreeNode, + infer TInsertable, + infer ImplicitlyConstructable, + infer Info extends RestrictiveStringRecord, + infer TConstructorExtra, + infer TCustomMetadata + > + ? TreeNodeSchemaClass< + Name, + NodeKind.Object, + TreeNode & WithType & ObjectFromSchemaRecordRelaxed, + TInsertable, + ImplicitlyConstructable, + Info, + TConstructorExtra, + TCustomMetadata + > + : T; +} diff --git a/packages/dds/tree/src/simple-tree/api/schemaFactoryBeta.ts b/packages/dds/tree/src/simple-tree/api/schemaFactoryBeta.ts index c0eb5c786d37..31ab0b56a426 100644 --- a/packages/dds/tree/src/simple-tree/api/schemaFactoryBeta.ts +++ b/packages/dds/tree/src/simple-tree/api/schemaFactoryBeta.ts @@ -22,12 +22,12 @@ import { type TreeRecordNode, } from "../node-kinds/index.js"; import { - defaultSchemaFactoryObjectOptions, SchemaFactory, scoped, structuralName, type NodeSchemaOptions, type ObjectSchemaOptions, + type ObjectSchemaOptionsAlpha, type ScopedSchemaName, } from "./schemaFactory.js"; import type { System_Unsafe, TreeRecordNodeUnsafe } from "./typesUnsafe.js"; @@ -43,8 +43,8 @@ import type { } from "../fieldSchema.js"; import type { LeafSchema } from "../leafNodeSchema.js"; import type { SimpleLeafNodeSchema } from "../simpleSchema.js"; -import type { RestrictiveStringRecord } from "../../util/index.js"; /* eslint-enable unused-imports/no-unused-imports, @typescript-eslint/no-unused-vars, import/no-duplicates */ +import type { PreventExtraProperties, RestrictiveStringRecord } from "../../util/index.js"; /** * {@link SchemaFactory} with additional beta APIs. @@ -77,25 +77,37 @@ export class SchemaFactoryBeta< public override object< const Name extends TName, const T extends RestrictiveStringRecord, - const TCustomMetadata = unknown, + const TOptions extends ObjectSchemaOptions = ObjectSchemaOptions, >( name: Name, fields: T, - options?: ObjectSchemaOptions, + options?: PreventExtraProperties, ): TreeNodeSchemaClass< ScopedSchemaName, NodeKind.Object, - TreeObjectNode>, + TreeObjectNode, TOptions>, object & InsertableObjectFromSchemaRecord, true, T, never, - TCustomMetadata + TOptions extends ObjectSchemaOptions ? TCustomMetadata : unknown > { - return objectSchema(scoped(this, name), fields, true, { - ...defaultSchemaFactoryObjectOptions, - ...(options ?? {}), - }); + // TODO: make type safe + return objectSchema( + scoped(this, name), + fields, + true, + options as PreventExtraProperties, + ) as TreeNodeSchemaClass< + ScopedSchemaName, + NodeKind.Object, + TreeObjectNode, TOptions>, + object & InsertableObjectFromSchemaRecord, + true, + T, + never, + TOptions extends ObjectSchemaOptions ? TCustomMetadata : unknown + >; } public override objectRecursive< diff --git a/packages/dds/tree/src/simple-tree/api/typesUnsafe.ts b/packages/dds/tree/src/simple-tree/api/typesUnsafe.ts index 50f6adbb2c48..5defa560b006 100644 --- a/packages/dds/tree/src/simple-tree/api/typesUnsafe.ts +++ b/packages/dds/tree/src/simple-tree/api/typesUnsafe.ts @@ -32,8 +32,9 @@ import type { AnnotatedAllowedType, AnnotatedAllowedTypes, } from "../core/index.js"; -import type { TreeArrayNode } from "../node-kinds/index.js"; +import type { ApplyKindAssignment, TreeArrayNode } from "../node-kinds/index.js"; import type { SimpleArrayNodeSchema, SimpleMapNodeSchema } from "../simpleSchema.js"; +import type { CustomizedSchemaTyping, CustomTypes } from "../schemaTypes.js"; /* * TODO: @@ -57,6 +58,24 @@ import type { SimpleArrayNodeSchema, SimpleMapNodeSchema } from "../simpleSchema */ export type Unenforced<_DesiredExtendsConstraint> = unknown; +/** + * {@link Unenforced} version of {@link customizeSchemaTyping} for use with recursive schema types. + * + * @remarks + * When using this API to modify a schema derived type such that the type is no longer recursive, + * or uses an externally defined type (which can be recursive), {@link customizeSchemaTyping} should be used instead for an improved developer experience. + * Additionally, in this case, none of the "unsafe" type variants should be needed: the whole schema (with runtime but not schema derived type recursion) + * should use the normal (not unsafe/recursive) APIs. + * @alpha + */ +export function customizeSchemaTypingUnsafe< + TSchema extends System_Unsafe.ImplicitAllowedTypesUnsafe, +>(schema: TSchema): System_Unsafe.CustomizerUnsafe { + // This function just does type branding, and duplicating the typing here to avoid any would just make it harder to maintain not easier: + const f = (): any => schema; + return { simplified: f, simplifiedUnrestricted: f, custom: f }; +} + /** * A collection of {@link Unenforced} types that are used in the implementation of recursive schema. * These are all `@system` types, and thus should not be used directly. @@ -66,6 +85,113 @@ export type Unenforced<_DesiredExtendsConstraint> = unknown; * @system @public */ export namespace System_Unsafe { + /** + * {@link Unenforced} version of `Customizer`. + * @remarks + * This has fewer options than the safe version, but all options can still be expressed using the "custom" method. + * @sealed @public + */ + export interface CustomizerUnsafe { + /** + * Replace typing with a single substitute type which allowed types must implement. + * @remarks + * This is generally type safe for reading the tree, but allows instances of `T` other than those listed in the schema to be assigned, + * which can be out of schema and err at runtime in the same way {@link Customizer.relaxed} does. + * Until with {@link Customizer.relaxed}, implicit construction is disabled, meaning all nodes must be explicitly constructed (and thus implement `T`) before being inserted. + */ + simplified< + T extends (TreeNode | TreeLeafValue) & TreeNodeFromImplicitAllowedTypesUnsafe, + >(): CustomizedSchemaTyping< + TSchema, + { + input: T; + readWrite: T; + output: T; + } + >; + + /** + * The same as {@link System_Unsafe.CustomizerUnsafe.simplified} except that more T values are allowed, even ones not known to be implemented by `TSchema`. + */ + simplifiedUnrestricted(): CustomizedSchemaTyping< + TSchema, + { + input: T; + readWrite: T; + output: T; + } + >; + + /** + * Fully arbitrary customization. + * Provided types override existing types. + * @remarks + * This can express any of the customizations possible via other {@link System_Unsafe.CustomizerUnsafe} methods: + * this API is however more verbose and can more easily be used to unsafe typing. + */ + custom>(): CustomizedSchemaTyping< + TSchema, + Pick & { + // Check if property is provided. This check is needed to early out missing values so if undefined is allowed, + // not providing the field doesn't overwrite the corresponding type with undefined. + // TODO: test this case + [Property in keyof CustomTypes]: Property extends keyof T + ? T[Property] extends CustomTypes[Property] + ? T[Property] + : GetTypesUnsafe[Property] + : GetTypesUnsafe[Property]; + } + >; + } + + /** + * {@link Unenforced} version of `AssignableTreeFieldFromImplicitField`. + * @remarks + * Do not use this type directly: its only needed in the implementation of generic logic which define recursive schema, not when using recursive schema. + * @privateRemarks + * Recursive version doesn't remove setters when this is never, so this uses covariant not contravariant union handling. + * @system @public + */ + export type AssignableTreeFieldFromImplicitFieldUnsafe< + TSchema extends ImplicitFieldSchemaUnsafe, + > = TSchema extends FieldSchemaUnsafe + ? ApplyKindAssignment["readWrite"], Kind> + : // TODO: why is this extends check needed? Should already narrow to ImplicitAllowedTypesUnsafe from above. + TSchema extends ImplicitAllowedTypesUnsafe + ? GetTypesUnsafe["readWrite"] + : never; + + /** + * {@link Unenforced} version of `TypesUnsafe`. + * @remarks + * Do not use this type directly: its only needed in the implementation of generic logic which define recursive schema, not when using recursive schema. + * @system @public + */ + export type GetTypesUnsafe = [TSchema] extends [ + CustomizedSchemaTyping, + ] + ? TCustom + : StrictTypesUnsafe; + + /** + * {@link Unenforced} version of `StrictTypes`. + * @remarks + * Do not use this type directly: its only needed in the implementation of generic logic which define recursive schema, not when using recursive schema. + * @system @public + */ + export interface StrictTypesUnsafe< + TSchema extends ImplicitAllowedTypesUnsafe, + TInput = DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe, + TOutput = DefaultTreeNodeFromImplicitAllowedTypesUnsafe, + > { + input: TInput; + // Partial mitigation setter limitations (removal of setters when TInput is never by setting this to never) breaks compilation if used here, + // so recursive objects end up allowing some unsafe assignments which will error at runtime. + // This unsafety occurs when schema types are not exact, so output types are generalized which results in setters being generalized (wince they get the same type) which is unsafe. + readWrite: TOutput; // TInput extends never ? never : TOutput; + output: TOutput; + } + /** * {@link Unenforced} version of `ObjectFromSchemaRecord`. * @remarks @@ -76,11 +202,43 @@ export namespace System_Unsafe { */ export type ObjectFromSchemaRecordUnsafe< T extends RestrictiveStringRecord, - > = { - -readonly [Property in keyof T]: Property extends string - ? TreeFieldFromImplicitFieldUnsafe - : unknown; - }; + > = + // Due to https://github.com/microsoft/TypeScript/issues/43826 we can not set the desired setter type. + // Attempts to implement this in the cleaner way ObjectFromSchemaRecord uses cause recursive types to fail to compile. + // Supporting explicit field schema wrapping CustomizedSchemaTyping here breaks compilation of recursive cases as well. + { + -readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping< + unknown, + { + readonly readWrite: never; + readonly input: unknown; + readonly output: TreeNode | TreeLeafValue; + } + >, + ] + ? never // Remove readWrite version for cases using CustomizedSchemaTyping to set readWrite to never. + : // TODO : maybe filter out non string in logic above? + Property]: Property extends string + ? AssignableTreeFieldFromImplicitFieldUnsafe + : unknown; + } & { + readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping< + unknown, + { + readonly readWrite: never; + readonly input: unknown; + readonly output: TreeNode | TreeLeafValue; + } + >, + ] + ? // Inverse of the conditional above: only include readonly fields when not including the readWrite one. This is required to make recursive types compile. + Property + : never]: Property extends string + ? TreeFieldFromImplicitFieldUnsafe + : unknown; + }; /** * {@link Unenforced} version of {@link TreeNodeSchema}. @@ -217,6 +375,16 @@ export namespace System_Unsafe { */ export type TreeNodeFromImplicitAllowedTypesUnsafe< TSchema extends ImplicitAllowedTypesUnsafe, + > = GetTypesUnsafe["output"]; + + /** + * {@link Unenforced} version of {@link DefaultTreeNodeFromImplicitAllowedTypes}. + * @remarks + * Do not use this type directly: its only needed in the implementation of generic logic which define recursive schema, not when using recursive schema. + * @system @public + */ + export type DefaultTreeNodeFromImplicitAllowedTypesUnsafe< + TSchema extends ImplicitAllowedTypesUnsafe, > = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe @@ -232,6 +400,17 @@ export namespace System_Unsafe { */ export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe< TSchema extends ImplicitAllowedTypesUnsafe, + > = GetTypesUnsafe["input"]; + + /** + * {@link Unenforced} version of {@link DefaultInsertableTreeNodeFromImplicitAllowedTypes}. + * @see {@link Input} + * @remarks + * Do not use this type directly: its only needed in the implementation of generic logic which define recursive schema, not when using recursive schema. + * @system @public + */ + export type DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe< + TSchema extends ImplicitAllowedTypesUnsafe, > = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] diff --git a/packages/dds/tree/src/simple-tree/core/allowedTypes.ts b/packages/dds/tree/src/simple-tree/core/allowedTypes.ts index 366722da72ba..2561f8a77054 100644 --- a/packages/dds/tree/src/simple-tree/core/allowedTypes.ts +++ b/packages/dds/tree/src/simple-tree/core/allowedTypes.ts @@ -17,15 +17,11 @@ import { type IsUnion, type MakeNominal, } from "../../util/index.js"; -import { isLazy, type FlexListToUnion, type LazyItem } from "./flexList.js"; -import { - NodeKind, - type InsertableTypedNode, - type NodeFromSchema, - type TreeNodeSchema, -} from "./treeNodeSchema.js"; +import { isLazy, type LazyItem } from "./flexList.js"; +import { NodeKind, type InsertableTypedNode, type TreeNodeSchema } from "./treeNodeSchema.js"; import { schemaAsTreeNodeValid } from "./treeNodeValid.js"; import type { SimpleAllowedTypeAttributes } from "../simpleSchema.js"; +import type { GetTypes } from "../schemaTypes.js"; /** * Schema for types allowed in some location in a tree (like a field, map entry or array). @@ -636,15 +632,15 @@ export function markSchemaMostDerived( /** * Type of tree node for a field of the given schema. + * + * @typeparam TSchema - Schema to process. + * @remarks + * Defaults to {@link DefaultTreeNodeFromImplicitAllowedTypes}. * @public */ export type TreeNodeFromImplicitAllowedTypes< TSchema extends ImplicitAllowedTypes = TreeNodeSchema, -> = TSchema extends TreeNodeSchema - ? NodeFromSchema - : TSchema extends AllowedTypes - ? NodeFromSchema> - : unknown; +> = GetTypes["output"]; /** * This type exists only to be linked from documentation to provide a single linkable place to document some details of @@ -703,20 +699,13 @@ export type Input = T; /** * Type of content that can be inserted into the tree for a node of the given schema. * - * @see {@link Input} - * * @typeparam TSchema - Schema to process. - * - * @privateRemarks - * This is a bit overly conservative, since cases like `A | [A]` give never and could give `A`. + * @remarks + * Defaults to {@link DefaultInsertableTreeNodeFromImplicitAllowedTypes}. * @public */ export type InsertableTreeNodeFromImplicitAllowedTypes = - [TSchema] extends [TreeNodeSchema] - ? InsertableTypedNode - : [TSchema] extends [AllowedTypes] - ? InsertableTreeNodeFromAllowedTypes - : never; + GetTypes["input"]; /** * Type of content that can be inserted into the tree for a node of the given schema. diff --git a/packages/dds/tree/src/simple-tree/core/treeNodeSchema.ts b/packages/dds/tree/src/simple-tree/core/treeNodeSchema.ts index 36b0e8c8433b..5549f77c815f 100644 --- a/packages/dds/tree/src/simple-tree/core/treeNodeSchema.ts +++ b/packages/dds/tree/src/simple-tree/core/treeNodeSchema.ts @@ -606,6 +606,8 @@ export type NodeFromSchema = T extends TreeNodeSchemaC * One special case this makes is if the result of NodeFromSchema contains TreeNode, this must be an under constrained schema, so the result is set to never. * Note that applying UnionToIntersection on the result of NodeFromSchema does not work since it breaks booleans. * + * Some internal code may use second parameter to opt out of contravariant behavior, but this is not a stable API. + * * @public */ export type InsertableTypedNode< diff --git a/packages/dds/tree/src/simple-tree/fieldSchema.ts b/packages/dds/tree/src/simple-tree/fieldSchema.ts index 994855450f85..8a961cfc98c9 100644 --- a/packages/dds/tree/src/simple-tree/fieldSchema.ts +++ b/packages/dds/tree/src/simple-tree/fieldSchema.ts @@ -12,7 +12,6 @@ import type { FlexTreeHydratedContextMinimal } from "../feature-libraries/index. import { type MakeNominal, brand, - type UnionToIntersection, compareSets, type requireTrue, type areOnlyKeys, @@ -34,6 +33,11 @@ import { normalizeAllowedTypes } from "./core/index.js"; import type { SimpleAllowedTypeAttributes, SimpleFieldSchema } from "./simpleSchema.js"; import type { UnsafeUnknownSchema } from "./unsafeUnknownSchema.js"; import type { InsertableContent } from "./unhydratedFlexTreeFromInsertable.js"; +import type { + CustomizedSchemaTyping, + CustomTypes, + SchemaUnionToIntersection, +} from "./schemaTypes.js"; /** * Kind of a field on an {@link TreeObjectNode}. @@ -597,7 +601,9 @@ export type TreeFieldFromImplicitField, + TSchema = [TSchemaInput] extends [CustomizedSchemaTyping] + ? TSchemaInput + : SchemaUnionToIntersection, > = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] diff --git a/packages/dds/tree/src/simple-tree/index.ts b/packages/dds/tree/src/simple-tree/index.ts index 5c23b174daa6..a1f25958c996 100644 --- a/packages/dds/tree/src/simple-tree/index.ts +++ b/packages/dds/tree/src/simple-tree/index.ts @@ -186,7 +186,20 @@ export { incrementalSummaryHint, getShouldIncrementallySummarizeAllowedTypes, type SchemaFactory_base, + relaxObject, } from "./api/index.js"; + +export type { + SchemaUnionToIntersection, + CustomTypes, + CustomizedSchemaTyping, + StrictTypes, + Customizer, + GetTypes, + DefaultInsertableTreeNodeFromImplicitAllowedTypes, +} from "./schemaTypes.js"; +export { CustomizedTyping, customizeSchemaTyping } from "./schemaTypes.js"; + export type { SimpleTreeSchema, SimpleNodeSchema, @@ -239,6 +252,8 @@ export { type FieldHasDefault, type InsertableObjectFromSchemaRecord, type ObjectFromSchemaRecord, + type AssignableTreeFieldFromImplicitField, + type ApplyKindAssignment, ObjectNodeSchema, type ObjectNodeSchemaPrivate, isObjectNodeSchema, @@ -250,6 +265,10 @@ export { type RecordNodePojoEmulationSchema, RecordNodeSchema, type TreeRecordNode, + type ObjectFromSchemaRecordRelaxed, + type ObjectSchemaTypingOptions, + type AssignableTreeFieldFromImplicitFieldDefault, + type TreeFieldFromImplicitFieldDefault, } from "./node-kinds/index.js"; export { unhydratedFlexTreeFromInsertable, @@ -280,3 +299,5 @@ export { nullSchema, } from "./leafNodeSchema.js"; export type { LeafSchema } from "./leafNodeSchema.js"; + +export type { DefaultTreeNodeFromImplicitAllowedTypes } from "./schemaTypes.js"; diff --git a/packages/dds/tree/src/simple-tree/node-kinds/index.ts b/packages/dds/tree/src/simple-tree/node-kinds/index.ts index 89853d01c1df..87ef7e368e79 100644 --- a/packages/dds/tree/src/simple-tree/node-kinds/index.ts +++ b/packages/dds/tree/src/simple-tree/node-kinds/index.ts @@ -36,6 +36,12 @@ export { setField, type TreeObjectNode, type SimpleKeyMap, + type AssignableTreeFieldFromImplicitField, + type ApplyKindAssignment, + type ObjectSchemaTypingOptions, + type AssignableTreeFieldFromImplicitFieldDefault, + type TreeFieldFromImplicitFieldDefault, + type ObjectFromSchemaRecordRelaxed, } from "./object/index.js"; export { diff --git a/packages/dds/tree/src/simple-tree/node-kinds/object/index.ts b/packages/dds/tree/src/simple-tree/node-kinds/object/index.ts index 62426ae1603a..053639e972c1 100644 --- a/packages/dds/tree/src/simple-tree/node-kinds/object/index.ts +++ b/packages/dds/tree/src/simple-tree/node-kinds/object/index.ts @@ -4,13 +4,21 @@ */ export { - type FieldHasDefault, - type InsertableObjectFromSchemaRecord, - type ObjectFromSchemaRecord, objectSchema, setField, - type TreeObjectNode, - type SimpleKeyMap, +} from "./objectNode.js"; +export type { + FieldHasDefault, + InsertableObjectFromSchemaRecord, + ObjectFromSchemaRecord, + TreeObjectNode, + SimpleKeyMap, + AssignableTreeFieldFromImplicitField, + ApplyKindAssignment, + ObjectSchemaTypingOptions, + AssignableTreeFieldFromImplicitFieldDefault, + TreeFieldFromImplicitFieldDefault, + ObjectFromSchemaRecordRelaxed, } from "./objectNode.js"; export { isObjectNodeSchema, diff --git a/packages/dds/tree/src/simple-tree/node-kinds/object/objectNode.ts b/packages/dds/tree/src/simple-tree/node-kinds/object/objectNode.ts index d0e97b6ef7bf..1e00bcf04b10 100644 --- a/packages/dds/tree/src/simple-tree/node-kinds/object/objectNode.ts +++ b/packages/dds/tree/src/simple-tree/node-kinds/object/objectNode.ts @@ -24,6 +24,7 @@ import type { RestrictiveStringRecord, FlattenKeys, JsonCompatibleReadOnlyObject, + PreventExtraProperties, } from "../../../util/index.js"; import { brand } from "../../../util/index.js"; @@ -38,7 +39,6 @@ import { type InternalTreeNode, type TreeNode, type UnhydratedFlexTreeNode, - getInnerNode, type NodeSchemaMetadata, type ImplicitAllowedTypes, TreeNodeValid, @@ -51,6 +51,8 @@ import { createField, type TreeNodeSchemaCorePrivate, type TreeNodeSchemaPrivateData, + getInnerNode, + type TreeLeafValue, } from "../../core/index.js"; import { getTreeNodeSchemaInitializedData, @@ -77,6 +79,7 @@ import { type ContextualFieldProvider, extractFieldProvider, isConstant, + type ApplyKind, } from "../../fieldSchema.js"; import type { SimpleObjectFieldSchema } from "../../simpleSchema.js"; import { @@ -87,6 +90,12 @@ import { type InsertableContent, } from "../../unhydratedFlexTreeFromInsertable.js"; import { convertField, convertFieldKind } from "../../toStoredSchema.js"; +import type { + DefaultTreeNodeFromImplicitAllowedTypes, + GetTypes, + SchemaUnionToIntersection, + StrictTypes, +} from "../../schemaTypes.js"; import type { ObjectSchemaOptionsAlpha } from "../../api/index.js"; /** @@ -95,20 +104,145 @@ import type { ObjectSchemaOptionsAlpha } from "../../api/index.js"; * Due to {@link https://github.com/microsoft/TypeScript/issues/43826}, we can't enable implicit construction of {@link TreeNode|TreeNodes} for setters. * Therefore code assigning to these fields must explicitly construct nodes using the schema's constructor or create method, * or using some other method like {@link (TreeAlpha:interface).create}. + * @privateRemarks + * Due to https://github.com/microsoft/TypeScript/issues/43826 we can not set the desired setter type. + * We can however merge two mapped types, one readonly and one not. * @system @public */ -export type ObjectFromSchemaRecord> = - RestrictiveStringRecord extends T - ? // eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/ban-types - {} +// export type ObjectFromSchemaRecord> = { +// // Due to https://github.com/microsoft/TypeScript/issues/43826 we can not set the desired setter type, +// // but we can at least remove the setter (by setting the key to never) when there should be no setter. +// -readonly [Property in keyof T as [ +// AssignableTreeFieldFromImplicitField, +// // If the types we want to allow setting to are just never or undefined, remove the setter +// ] extends [never | undefined] +// ? never +// : Property]: AssignableTreeFieldFromImplicitField; +// } & { +// readonly [Property in keyof T]: TreeFieldFromImplicitField; +// }; + +export type ObjectFromSchemaRecord< + T extends RestrictiveStringRecord, + Options extends ObjectSchemaTypingOptions = Record, +> = RestrictiveStringRecord extends T + ? // eslint-disable-next-line @typescript-eslint/ban-types + {} + : [Options["supportReadonlyFields"]] extends [true] + ? { + -readonly [Property in keyof T as [ + AssignableTreeFieldFromImplicitField, + // If the types we want to allow setting to are just never or undefined, remove the setter + ] extends [never | undefined] + ? never + : Property]: [Options["supportCustomizedFields"]] extends [true] + ? AssignableTreeFieldFromImplicitField + : AssignableTreeFieldFromImplicitFieldDefault; + } & { + readonly [Property in keyof T]: [Options["supportCustomizedFields"]] extends [true] + ? TreeFieldFromImplicitField + : TreeFieldFromImplicitFieldDefault; + } : { - -readonly [Property in keyof T]: Property extends string - ? TreeFieldFromImplicitField - : unknown; + -readonly [Property in keyof T]: [Options["supportCustomizedFields"]] extends [true] + ? TreeFieldFromImplicitField + : TreeFieldFromImplicitFieldDefault; }; /** - * A {@link TreeNode} which models a JavaScript object. + * Same as {@link ObjectFromSchemaRecord}, but hard codes `supportReadonlyFields` and `supportCustomizedFields` to `false`. + * @system @beta + */ +export type ObjectFromSchemaRecordRelaxed< + T extends RestrictiveStringRecord, +> = RestrictiveStringRecord extends T + ? // eslint-disable-next-line @typescript-eslint/ban-types + {} + : { + [Property in keyof T]: TreeFieldFromImplicitFieldDefault; + }; + +/** + * Default version of {@link TreeFieldFromImplicitField}. + * + * Uses `StrictTypes` policy. + * + * @remarks + * This exists instead of just clearing the custom types annotation to work around limitations in TypeScript ability to propagate generic type parameters through conditionals in generic code. + * + * @system @public + */ +export type TreeFieldFromImplicitFieldDefault< + TSchema extends ImplicitFieldSchema = FieldSchema, +> = TSchema extends FieldSchema + ? ApplyKind, Kind> + : TSchema extends ImplicitAllowedTypes + ? DefaultTreeNodeFromImplicitAllowedTypes + : TreeNode | TreeLeafValue | undefined; + +/** + * Type of content that can be assigned to a field of the given schema. + * + * @see {@link Input} + * + * @typeparam TSchemaInput - Schema to process. + * @typeparam TSchema - Do not specify: default value used as an implementation detail. + * @system @public + */ +export type AssignableTreeFieldFromImplicitField< + TSchemaInput extends ImplicitFieldSchema, + TSchema = SchemaUnionToIntersection, +> = [TSchema] extends [FieldSchema] + ? ApplyKindAssignment["readWrite"], Kind> + : [TSchema] extends [ImplicitAllowedTypes] + ? GetTypes["readWrite"] + : never; + +/** + * Default version of {@link AssignableTreeFieldFromImplicitField}. + * + * Uses `StrictTypes` policy. + * + * @remarks + * This exists instead of just clearing the custom types annotation to work around limitations in TypeScript ability to propagate generic type parameters through conditionals in generic code. + * + * @system @public + */ +export type AssignableTreeFieldFromImplicitFieldDefault< + TSchemaInput extends ImplicitFieldSchema, + TSchema = SchemaUnionToIntersection, +> = [TSchema] extends [FieldSchema] + ? ApplyKindAssignment["readWrite"], Kind> + : [TSchema] extends [ImplicitAllowedTypes] + ? StrictTypes["readWrite"] + : never; + +/** + * Suitable for assignment. + * + * @see {@link Input} + * @system @public + */ +export type ApplyKindAssignment = [Kind] extends [ + FieldKind.Required, +] + ? T + : [Kind] extends [FieldKind.Optional] + ? T | undefined + : // Unknown, non-exact and identifier fields are not assignable. + never; + +/** + * Control details of type generation for an object schema. + * @input @public + */ +export interface ObjectSchemaTypingOptions { + readonly supportReadonlyFields?: true | undefined; + readonly supportCustomizedFields?: true | undefined; +} + +/** + * A {@link TreeNode} which modules a JavaScript object. * @remarks * Object nodes consist of a type which specifies which {@link TreeNodeSchema} they use (see {@link TreeNodeApi.schema} and {@link SchemaFactory.object}), * and a collections of fields, each with a distinct `key` and its own {@link FieldSchema} defining what can be placed under that key. @@ -141,7 +275,8 @@ export type ObjectFromSchemaRecord, TypeName extends string = string, -> = TreeNode & ObjectFromSchemaRecord & WithType; + Options extends ObjectSchemaTypingOptions = Record, +> = TreeNode & WithType & ObjectFromSchemaRecord; /** * Type utility for determining if an implicit field schema is known to have a default value. @@ -418,13 +553,13 @@ export function objectSchema< TName extends string, const T extends RestrictiveStringRecord, const ImplicitlyConstructable extends boolean, - const TCustomMetadata = unknown, + TOptions extends ObjectSchemaOptionsAlpha = ObjectSchemaOptionsAlpha, >( identifier: TName, info: T, implicitlyConstructable: ImplicitlyConstructable, - nodeOptions: ObjectSchemaOptionsAlpha, -): ObjectNodeSchema & + options?: PreventExtraProperties, +): ObjectNodeSchema & ObjectNodeSchemaInternalData & TreeNodeSchemaCorePrivate { // Field set can't be modified after this since derived data is stored in maps. @@ -478,7 +613,8 @@ export function objectSchema< ); public static readonly identifierFieldKeys: readonly FieldKey[] = identifierFieldKeys; public static readonly allowUnknownOptionalFields: boolean = - nodeOptions.allowUnknownOptionalFields ?? false; + options?.allowUnknownOptionalFields ?? + defaultSchemaFactoryObjectOptions.allowUnknownOptionalFields; public static override prepareInstance( this: typeof TreeNodeValid, @@ -568,10 +704,20 @@ export function objectSchema< public static get childTypes(): ReadonlySet { return lazyChildTypes.value; } - public static readonly metadata: NodeSchemaMetadata = - nodeOptions.metadata ?? {}; + public static readonly metadata: NodeSchemaMetadata< + TOptions extends ObjectSchemaOptionsAlpha + ? TCustomMetadataX + : unknown + > = { + custom: options?.metadata?.custom as TOptions extends ObjectSchemaOptionsAlpha< + infer TCustomMetadataX + > + ? TCustomMetadataX + : unknown, + description: options?.metadata?.description, + }; public static readonly persistedMetadata: JsonCompatibleReadOnlyObject | undefined = - nodeOptions.persistedMetadata; + options?.persistedMetadata; // eslint-disable-next-line import/no-deprecated public get [typeNameSymbol](): TName { @@ -597,7 +743,7 @@ export function objectSchema< convertField(fieldSchema.schema, storedOptions), ); } - return new ObjectNodeStoredSchema(fields, nodeOptions.persistedMetadata); + return new ObjectNodeStoredSchema(fields, options?.persistedMetadata); }, )); } @@ -605,12 +751,20 @@ export function objectSchema< type Output = typeof CustomObjectNode & (new ( input: InsertableObjectFromSchemaRecord | InternalTreeNode, - ) => TreeObjectNode); + ) => TreeObjectNode); return CustomObjectNode as Output; } const targetToProxy: WeakMap = new WeakMap(); +/** + * Default options for Object node schema creation. + * @remarks Omits parameters that are not relevant for common use cases. + */ +export const defaultSchemaFactoryObjectOptions = { + allowUnknownOptionalFields: false, +} as const satisfies Pick; + /** * Ensures that the set of property keys in the schema is unique. * Also ensure that the final set of stored keys (including those implicitly derived from property keys) is unique. diff --git a/packages/dds/tree/src/simple-tree/node-kinds/object/objectNodeTypes.ts b/packages/dds/tree/src/simple-tree/node-kinds/object/objectNodeTypes.ts index 2c9b0750b2c4..3af701a1cfb3 100644 --- a/packages/dds/tree/src/simple-tree/node-kinds/object/objectNodeTypes.ts +++ b/packages/dds/tree/src/simple-tree/node-kinds/object/objectNodeTypes.ts @@ -18,6 +18,7 @@ import { } from "../../core/index.js"; import type { FieldKey } from "../../../core/index.js"; import type { SimpleObjectFieldSchema, SimpleObjectNodeSchema } from "../../simpleSchema.js"; +import type { ObjectSchemaOptionsAlpha } from "../../api/index.js"; /** * A schema for {@link TreeObjectNode}s. @@ -29,18 +30,21 @@ export interface ObjectNodeSchema< in out T extends RestrictiveStringRecord = RestrictiveStringRecord, ImplicitlyConstructable extends boolean = boolean, - out TCustomMetadata = unknown, + TOptions extends ObjectSchemaOptionsAlpha = ObjectSchemaOptionsAlpha, + TCustomMetadataInferred = TOptions extends ObjectSchemaOptionsAlpha + ? TCustomMetadataX + : unknown, > extends TreeNodeSchemaClass< TName, NodeKind.Object, - TreeObjectNode, + TreeObjectNode, InsertableObjectFromSchemaRecord, ImplicitlyConstructable, T, never, - TCustomMetadata + TCustomMetadataInferred >, - SimpleObjectNodeSchema { + SimpleObjectNodeSchema { /** * From property keys to the associated schema. */ diff --git a/packages/dds/tree/src/simple-tree/schemaTypes.ts b/packages/dds/tree/src/simple-tree/schemaTypes.ts new file mode 100644 index 000000000000..dc066242b667 --- /dev/null +++ b/packages/dds/tree/src/simple-tree/schemaTypes.ts @@ -0,0 +1,268 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import type { UnionToIntersection } from "../util/index.js"; +import type { + TreeNodeSchema, + TreeNode, + AllowedTypes, + FlexListToUnion, + ImplicitAllowedTypes, + InsertableTreeNodeFromAllowedTypes, + LazyItem, + NodeFromSchema, + TreeLeafValue, + InsertableTypedNode, + TreeNodeFromImplicitAllowedTypes, +} from "./core/index.js"; +import type { InsertableContent } from "./unhydratedFlexTreeFromInsertable.js"; + +/** + * {@link UnionToIntersection} except it does not distribute over {@link CustomizedSchemaTyping}s when the original type is a union. + * @privateRemarks + * This is a workaround for TypeScript distributing over intersections over unions when distributing extends over unions. + * @system @public + */ +export type SchemaUnionToIntersection = [T] extends [ + CustomizedSchemaTyping, +] + ? T + : UnionToIntersection; + +/** + * {@inheritdoc (CustomizedTyping:type)} + * @system @public + */ +export const CustomizedTyping: unique symbol = Symbol("CustomizedTyping"); + +/** + * A type brand used by {@link customizeSchemaTyping}. + * @system @public + */ +export type CustomizedTyping = typeof CustomizedTyping; + +/** + * Collection of schema aware types. + * @remarks + * This type is only used as a type constraint. + * It's fields are similar to an unordered set of generic type parameters. + * {@link customizeSchemaTyping} applies this to {@link ImplicitAllowedTypes} via {@link CustomizedSchemaTyping}. + * @sealed @public + */ +export interface CustomTypes { + /** + * Type used for inserting values. + */ + readonly input: unknown; + /** + * Type used for the read+write property on object nodes. + * + * Set to never to disable setter. + * @remarks + * Due to https://github.com/microsoft/TypeScript/issues/43826 we cannot set the desired setter type. + * Instead we can only control the types of the read+write property and the type of a readonly property. + * + * For recursive types using {@link SchemaFactory.objectRecursive}, support for using `never` to remove setters is limited: + * When the customized schema is wrapped in an {@link FieldSchema}, the setter will not be fully removed. + */ + readonly readWrite: TreeLeafValue | TreeNode; + /** + * Type for reading data. + * @remarks + * See limitation for read+write properties on ObjectNodes in {@link CustomTypes.readWrite}. + */ + readonly output: TreeLeafValue | TreeNode; +} + +/** + * Type annotation which overrides the default schema derived types with customized ones. + * @remarks + * See {@link customizeSchemaTyping} for more information. + * @system @public + */ +export type CustomizedSchemaTyping = TSchema & { + [CustomizedTyping]: TCustom; +}; + +/** + * Default strict policy. + * + * @typeparam TSchema - The schema to process + * @typeparam TInput - Internal: do not specify. + * @typeparam TOutput - Internal: do not specify. + * @remarks + * Handles input types contravariantly so any input which might be invalid is rejected. + * @sealed @public + */ +export interface StrictTypes< + TSchema extends ImplicitAllowedTypes, + TInput = DefaultInsertableTreeNodeFromImplicitAllowedTypes, + TOutput extends TreeNode | TreeLeafValue = DefaultTreeNodeFromImplicitAllowedTypes, +> { + input: TInput; + readWrite: TInput extends never ? never : TOutput; + output: TOutput; +} + +/** + * Customizes the types associated with `TSchema` + * @remarks + * By default, the types used when constructing, reading and writing tree nodes are derived from the schema. + * In some cases, it may be desirable to override these types with carefully selected alternatives. + * This utility allows for that customization. + * Note that this customization is only used for typing, and does not affect the runtime behavior at all. + * + * This can be used for a wide variety of purposes, including (but not limited to): + * + * 1. Implementing better typing for a runtime extensible set of types (e.g. a polymorphic collection). + * This is commonly needed when implementing containers which don't directly reference their child types, and can be done using {@link Customizer.simplified}. + * 2. Adding type brands to specific values to increase type safety. + * This can be done using {@link Customizer.simplified}. + * 3. Adding some (compile time only) constraints to values, like enum style unions. + * This can be done using {@link Customizer.simplified}. + * 4. Making fields readonly (for the current client). + * This can be done using {@link Customizer.custom} with `{ readWrite: never; }`. + * 5. Opting into more {@link https://en.wikipedia.org/wiki/Soundness#Relation_to_completeness|compleat and less sound} typing. + * {@link Customizer.relaxed} is an example of this. + * + * For this customization to be used, the resulting schema must be used as `ImplicitAllowedTypes`. + * For example applying this to a single type, then using that type in an array of allowed types will have no effect: + * in such a case the customization must instead be applied to the array of allowed types. + * @privateRemarks + * Once this API is more stable/final, the examples in tests such as openPolymorphism.spec.ts and schemaFactory.examples.spec.ts + * should be copied into examples here, or somehow linked. + * @alpha + */ +export function customizeSchemaTyping( + schema: TSchema, +): Customizer { + // This function just does type branding, and duplicating the typing here to avoid any would just make it harder to maintain not easier: + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const f = (): any => schema; + return { strict: f, relaxed: f, simplified: f, simplifiedUnrestricted: f, custom: f }; +} + +/** + * Utility for customizing the types used for data matching a given schema. + * @sealed @alpha + */ +export interface Customizer { + /** + * The default {@link StrictTypes}, explicitly applied. + */ + strict(): CustomizedSchemaTyping>; + /** + * Relaxed policy: allows possible invalid edits (which will err at runtime) when schema is not exact. + * @remarks + * Handles input types covariantly so any input which might be valid with the schema is allowed + * instead of the default strict policy of only inputs with all possible schema are allowed. + * + * This only modifies the typing shallowly: the typing of children are not effected. + */ + relaxed(): CustomizedSchemaTyping< + TSchema, + { + input: TreeNodeSchema extends TSchema + ? InsertableContent + : // This intentionally distributes unions over the conditional to get covariant type handling. + TSchema extends TreeNodeSchema + ? InsertableTypedNode + : // This intentionally distributes unions over the conditional to get covariant type handling. + TSchema extends AllowedTypes + ? TSchema[number] extends LazyItem + ? InsertableTypedNode + : never + : never; + readWrite: TreeNodeFromImplicitAllowedTypes; + output: TreeNodeFromImplicitAllowedTypes; + } + >; + /** + * Replace typing with a single substitute which allowed types must implement. + * @remarks + * This is generally type safe for reading the tree, but allows instances of `T` other than those listed in the schema to be assigned, + * which can be out of schema and err at runtime in the same way {@link Customizer.relaxed} does. + * Until with {@link Customizer.relaxed}, implicit construction is disabled, meaning all nodes must be explicitly constructed (and thus implement `T`) before being inserted. + */ + simplified>(): CustomizedSchemaTyping< + TSchema, + { + input: T; + readWrite: T; + output: T; + } + >; + + /** + * The same as {@link Customizer} except that more T values are allowed, even ones not known to be implemented by `TSchema`. + */ + simplifiedUnrestricted(): CustomizedSchemaTyping< + TSchema, + { + input: T; + readWrite: T; + output: T; + } + >; + + /** + * Fully arbitrary customization. + * Provided types override existing types. + */ + custom>(): CustomizedSchemaTyping< + TSchema, + { + // Check if property is provided. This check is needed to early out missing values so if undefined is allowed, + // not providing the field doesn't overwrite the corresponding type with undefined. + // TODO: test this case + [Property in keyof CustomTypes]: Property extends keyof T + ? T[Property] extends CustomTypes[Property] + ? T[Property] + : GetTypes[Property] + : GetTypes[Property]; + } + >; +} + +/** + * Fetch types associated with a schema, or use the default if not customized. + * @system @public + */ +export type GetTypes = [TSchema] extends [ + CustomizedSchemaTyping, +] + ? TCustom + : StrictTypes; + +/** + * Default type of tree node for a field of the given schema. + * @system @public + */ +export type DefaultTreeNodeFromImplicitAllowedTypes< + TSchema extends ImplicitAllowedTypes = TreeNodeSchema, +> = TSchema extends TreeNodeSchema + ? NodeFromSchema + : TSchema extends AllowedTypes + ? NodeFromSchema> + : unknown; + +/** + * Type of content that can be inserted into the tree for a node of the given schema. + * + * @see {@link Input} + * + * @typeparam TSchema - Schema to process. + * + * @privateRemarks + * This is a bit overly conservative, since cases like `A | [A]` give never and could give `A`. + * @system @public + */ +export type DefaultInsertableTreeNodeFromImplicitAllowedTypes< + TSchema extends ImplicitAllowedTypes, +> = [TSchema] extends [TreeNodeSchema] + ? InsertableTypedNode + : [TSchema] extends [AllowedTypes] + ? InsertableTreeNodeFromAllowedTypes + : never; diff --git a/packages/dds/tree/src/simple-tree/unsafeUnknownSchema.ts b/packages/dds/tree/src/simple-tree/unsafeUnknownSchema.ts index 2b14b8d344ab..0726e246fe9c 100644 --- a/packages/dds/tree/src/simple-tree/unsafeUnknownSchema.ts +++ b/packages/dds/tree/src/simple-tree/unsafeUnknownSchema.ts @@ -35,6 +35,9 @@ export const UnsafeUnknownSchema: unique symbol = Symbol("UnsafeUnknownSchema"); * Any APIs which use this must produce UsageErrors when out of schema data is encountered, and never produce unrecoverable errors, * or silently accept invalid data. * This is currently only type exported from the package: the symbol is just used as a way to get a named type. + * + * TODO: This takes a very different approach than `customizeSchemaTyping` which applies to allowed types. + * Maybe generalize that to apply to field schema as well and replace this with it? * @alpha */ export type UnsafeUnknownSchema = typeof UnsafeUnknownSchema; @@ -44,13 +47,11 @@ export type UnsafeUnknownSchema = typeof UnsafeUnknownSchema; * * @see {@link Input} * @remarks - * Extended version of {@link InsertableTreeNodeFromImplicitAllowedTypes} that also allows {@link (UnsafeUnknownSchema:type)}. + * Alias of {@link InsertableTreeNodeFromImplicitAllowedTypes} with a shorter name. * @alpha */ -export type Insertable = - TSchema extends ImplicitAllowedTypes - ? InsertableTreeNodeFromImplicitAllowedTypes - : InsertableContent; +export type Insertable = + InsertableTreeNodeFromImplicitAllowedTypes; /** * Content which could be inserted into a field within a tree. diff --git a/packages/dds/tree/src/tableSchema.ts b/packages/dds/tree/src/tableSchema.ts index 8443d92ef58d..b758953e000a 100644 --- a/packages/dds/tree/src/tableSchema.ts +++ b/packages/dds/tree/src/tableSchema.ts @@ -29,6 +29,7 @@ import { isArrayNodeSchema, type InsertableField, withBufferedTreeEvents, + type DefaultTreeNodeFromImplicitAllowedTypes, } from "./simple-tree/index.js"; // Future improvement TODOs: @@ -152,6 +153,33 @@ export namespace System_TableSchema { TCell extends ImplicitAllowedTypes = ImplicitAllowedTypes, > = OptionsWithSchemaFactory & OptionsWithCellSchema; + // export function createColumnSchemaX( + // propsSchema: TPropsSchema, + // ): void { + // const columnFields = { + // props: propsSchema, + // } as const; + + // const factory = new SchemaFactory("test"); + + // class _Column extends relaxObject(factory.object("Column", columnFields)) {} + // } + + // export function createColumnSchemaX2< + // const TPropsSchema extends TreeNodeSchema & { + // [CustomizedTyping]: StrictTypes; + // }, + // >(propsSchema: TPropsSchema): void { + // const columnFields = { + // props: propsSchema, + // } as const; + + // const factory = new SchemaFactory("test"); + // const base = factory.object("Column", columnFields); + + // class _Column extends base {} + // } + /** * Factory for creating column schema. * @system @alpha @@ -568,6 +596,9 @@ export namespace System_TableSchema { type ColumnValueType = TreeNodeFromImplicitAllowedTypes; type RowValueType = TreeNodeFromImplicitAllowedTypes; + type ColumnValueTypeInternal = DefaultTreeNodeFromImplicitAllowedTypes; + type RowValueTypeInternal = DefaultTreeNodeFromImplicitAllowedTypes; + /** * {@link Table} fields. * @remarks Extracted for re-use in returned type signature defined later in this function. @@ -585,6 +616,8 @@ export namespace System_TableSchema { extends schemaFactory.object("Table", tableFields, { // Will make it easier to evolve this schema in the future. allowUnknownOptionalFields: true, + supportReadonlyFields: true, + supportCustomizedFields: true, }) implements TableSchema.Table { @@ -616,7 +649,7 @@ export namespace System_TableSchema { return undefined; } - return row.getCell(column); + return (row as RowValueTypeInternal).getCell(column as ColumnValueTypeInternal); } public insertColumns({ @@ -697,7 +730,7 @@ export namespace System_TableSchema { const row = this._getRow(rowOrId); const column = this._getColumn(columnOrId); - row.setCell(column, cell); + (row as RowValueTypeInternal).setCell(column as ColumnValueTypeInternal, cell); } public removeColumns( @@ -760,7 +793,9 @@ export namespace System_TableSchema { for (const row of this.rows) { // TypeScript is unable to narrow the row type correctly here, hence the cast. // See: https://github.com/microsoft/TypeScript/issues/52144 - (row as RowValueType).removeCell(columnToRemove); + (row as RowValueTypeInternal).removeCell( + columnToRemove as ColumnValueTypeInternal, + ); } // We have already validated that all of the columns exist above, so this is safe. @@ -825,12 +860,14 @@ export namespace System_TableSchema { const row = this._getRow(rowOrIdOrIndex); const column = this._getColumn(columnOrIdOrIndex); - const cell: CellValueType | undefined = row.getCell(column.id); + const cell: CellValueType | undefined = (row as RowValueTypeInternal).getCell( + (column as ColumnValueTypeInternal).id, + ); if (cell === undefined) { return undefined; } - row.removeCell(column.id); + (row as RowValueTypeInternal).removeCell((column as ColumnValueTypeInternal).id); return cell; } @@ -841,7 +878,7 @@ export namespace System_TableSchema { for (const row of this.rows) { // TypeScript is unable to narrow the row type correctly here, hence the cast. // See: https://github.com/microsoft/TypeScript/issues/52144 - (row as RowValueType).removeCell(column); + (row as RowValueTypeInternal).removeCell(column as ColumnValueTypeInternal); } } @@ -917,9 +954,9 @@ export namespace System_TableSchema { const columnId = columnOrIdOrIndex; // TypeScript is unable to narrow the types correctly here, hence the casts. // See: https://github.com/microsoft/TypeScript/issues/52144 - return this.columns.find((col) => (col as ColumnValueType).id === columnId) as - | ColumnValueType - | undefined; + return this.columns.find( + (col) => (col as ColumnValueTypeInternal).id === columnId, + ) as ColumnValueType | undefined; } // If the user provided a node, ensure it actually exists in this table. @@ -969,7 +1006,7 @@ export namespace System_TableSchema { } throw new UsageError( - `The specified column node with ID "${columnOrIdOrIndex.id}" does not exist in the table.`, + `The specified column node with ID "${(columnOrIdOrIndex as ColumnValueTypeInternal).id}" does not exist in the table.`, ); } @@ -994,7 +1031,7 @@ export namespace System_TableSchema { const rowId = rowOrIdOrIndex; // TypeScript is unable to narrow the types correctly here, hence the casts. // See: https://github.com/microsoft/TypeScript/issues/52144 - return this.rows.find((row) => (row as RowValueType).id === rowId) as + return this.rows.find((row) => (row as RowValueTypeInternal).id === rowId) as | RowValueType | undefined; } @@ -1035,7 +1072,7 @@ export namespace System_TableSchema { } throw new UsageError( - `The specified row node with ID "${rowOrIdOrIndex.id}" does not exist in the table.`, + `The specified row node with ID "${(rowOrIdOrIndex as RowValueTypeInternal).id}" does not exist in the table.`, ); } diff --git a/packages/dds/tree/src/test/openPolymorphism.integration.ts b/packages/dds/tree/src/test/openPolymorphism.integration.ts index 107ea6c9eed2..a8ca4abf15c3 100644 --- a/packages/dds/tree/src/test/openPolymorphism.integration.ts +++ b/packages/dds/tree/src/test/openPolymorphism.integration.ts @@ -4,6 +4,7 @@ */ import { strict as assert } from "node:assert"; +import { UsageError } from "@fluidframework/telemetry-utils/internal"; import { allowUnused, @@ -11,6 +12,7 @@ import { SchemaFactory, TreeBeta, TreeViewConfiguration, + customizeSchemaTyping, type NodeKind, type ObjectFromSchemaRecord, type TreeNode, @@ -20,7 +22,6 @@ import { import { Tree } from "../shared-tree/index.js"; import { validateUsageError } from "./utils.js"; import { getOrAddInMap, type requireAssignableTo } from "../util/index.js"; -import { UsageError } from "@fluidframework/telemetry-utils/internal"; /** * Examples and tests for open polymorphism design patterns for schema. @@ -539,6 +540,211 @@ describe("Open Polymorphism design pattern examples and tests for them", () => { { location: { x: 0, y: 0 } }, ); }); + + it("safer editing API with customizeSchemaTyping", () => { + const ItemTypes: ItemSchema[] = []; + class Container extends sf.object("Container", { + // Here we force the insertable type to be `Item`, allowing for a potentially unsafe (runtime checked against the schema registrations) insertion of any Item type. + // This avoids the issue from the first example where the insertable type is `never`. + child: sf.optional(customizeSchemaTyping(ItemTypes).simplified()), + }) {} + + ItemTypes.push(TextItem); + + const container = new Container({ child: undefined }); + const container2 = new Container({ child: TextItem.default() }); + + // Enabled by customizeSchemaTyping + container.child = TextItem.default(); + container.child = undefined; + + // Allowed at compile time, but not allowed by schema: + class DisallowedItem + extends sf.object("DisallowedItem", { ...itemFields }) + implements Item + { + public foo(): void {} + } + + // Invalid TreeNodes are rejected at runtime even if allowed at compile time: + assert.throws( + () => { + container.child = new DisallowedItem({ location: { x: 0, y: 0 } }); + }, + validateUsageError(/Invalid schema/), + ); + + // Invalid insertable content is rejected. + // Different use of customizeSchemaTyping could have allowed this at compile time by not including TreeNode in Item. + assert.throws( + () => { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + container.child = {} as Item; + }, + validateUsageError(/incompatible with all of the types allowed by the schema/), + ); + }); + + // Example component design pattern which avoids the mutable static registry and instead composes declarative components. + it("components", () => { + /** + * Example application component interface. + */ + interface MyAppComponent { + itemTypes(lazyConfig: () => MyAppConfig): LazyItems; + } + + type LazyItems = readonly (() => ItemSchema)[]; + + function composeComponents(allComponents: readonly MyAppComponent[]): MyAppConfig { + const lazyConfig = () => config; + const ItemTypes = allComponents.flatMap( + (component): LazyItems => component.itemTypes(lazyConfig), + ); + + const config: MyAppConfig = { ItemTypes }; + + return config; + } + + interface MyAppConfig { + readonly ItemTypes: LazyItems; + } + + function createContainer(config: MyAppConfig): ItemSchema { + class Container extends sf.array("Container", config.ItemTypes) {} + class ContainerItem extends sf.object("ContainerItem", { + ...itemFields, + container: Container, + }) { + public static readonly description = "Text"; + public static default(): TextItem { + return new TextItem({ text: "", location: { x: 0, y: 0 } }); + } + + public foo(): void {} + } + + return ContainerItem; + } + + const containerComponent: MyAppComponent = { + itemTypes(lazyConfig: () => MyAppConfig): LazyItems { + return [() => createContainer(lazyConfig())]; + }, + }; + + const textComponent: MyAppComponent = { + itemTypes(): LazyItems { + return [() => TextItem]; + }, + }; + + const appConfig = composeComponents([containerComponent, textComponent]); + + const treeConfig = new TreeViewConfiguration({ + schema: appConfig.ItemTypes, + enableSchemaValidation: true, + preventAmbiguity: true, + }); + }); + + it("generic components system", () => { + // App specific // + + /** + * Subset of `MyAppConfig` which is available while composing components. + */ + interface MyAppConfigPartial { + /** + * {@link AllowedTypes} containing all ItemSchema contributed by components. + */ + readonly allowedItemTypes: ComponentMinimal.LazyArray; + } + + /** + * Example configuration type for an application. + * + * Contains a collection of schema to demonstrate how ComponentSchemaCollection works for schema dependency inversions. + */ + interface MyAppConfig extends MyAppConfigPartial { + /** + * Set of all ItemSchema contributed by components. + * @remarks + * Same content as {@link MyAppConfig.allowedItemTypes}, but normalized into a Set. + */ + readonly items: ReadonlySet; + } + + /** + * Example component type for an application. + * + * Represents functionality provided by a code library to power a component withing the application. + * + * This example uses ComponentSchemaCollection to allow the component to define schema which reference collections of schema from the application configuration. + * This makes it possible to implement the "open polymorphism" pattern, including handling recursive cases. + */ + interface MyAppComponent { + readonly itemTypes: ComponentMinimal.ComponentSchemaCollection< + MyAppConfigPartial, + ItemSchema + >; + } + + /** + * The application specific compose logic. + * + * Information from the components can be aggregated into the configuration. + */ + function composeComponents(allComponents: readonly MyAppComponent[]): MyAppConfig { + const lazyConfig = () => config; + const ItemTypes = ComponentMinimal.composeComponentSchema( + allComponents.map((c) => c.itemTypes), + lazyConfig, + ); + const config: MyAppConfigPartial = { + allowedItemTypes: ItemTypes, + }; + const items = new Set(ItemTypes.map(evaluateLazySchema)); + return { ...config, items }; + } + + // An example simple component + const textComponent: MyAppComponent = { + itemTypes: (): ComponentMinimal.LazyArray => [() => TextItem], + }; + + // An example component which references schema from the configuration and can be recursive through it. + const containerComponent: MyAppComponent = { + itemTypes: ( + lazyConfig: () => MyAppConfigPartial, + ): ComponentMinimal.LazyArray => [() => createContainer(lazyConfig())], + }; + function createContainer(config: MyAppConfigPartial): ItemSchema { + class Container extends sf.array("Container", config.allowedItemTypes) {} + class ContainerItem extends sf.object("ContainerItem", { + ...itemFields, + container: Container, + }) { + public static readonly description = "Text"; + public static default(): TextItem { + return new TextItem({ text: "", location: { x: 0, y: 0 } }); + } + + public foo(): void {} + } + + return ContainerItem; + } + + const appConfig = composeComponents([containerComponent, textComponent]); + + const treeConfig = new TreeViewConfiguration({ + schema: appConfig.allowedItemTypes, + enableSchemaValidation: true, + preventAmbiguity: true, + }); + }); }); /** diff --git a/packages/dds/tree/src/test/simple-tree/api/schemaFactory.examples.spec.ts b/packages/dds/tree/src/test/simple-tree/api/schemaFactory.examples.spec.ts index 80f2b8c16d26..01f5fb6f6596 100644 --- a/packages/dds/tree/src/test/simple-tree/api/schemaFactory.examples.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/api/schemaFactory.examples.spec.ts @@ -10,15 +10,24 @@ import { MockFluidDataStoreRuntime } from "@fluidframework/test-runtime-utils/in import { type ITree, - SchemaFactory, + SchemaFactoryAlpha, treeNodeApi as Tree, TreeViewConfiguration, type TreeView, + customizeSchemaTyping, + type GetTypes, + type Customizer, } from "../../../simple-tree/index.js"; import { DefaultTestSharedTreeKind, getView } from "../../utils.js"; +import { + brand, + type areSafelyAssignable, + type Brand, + type requireTrue, +} from "../../../util/index.js"; // Since this no longer follows the builder pattern, it is a SchemaFactory instead of a SchemaBuilder. -const schema = new SchemaFactory("com.example"); +const schema = new SchemaFactoryAlpha("com.example"); /** * An example schema based type. @@ -122,4 +131,139 @@ describe("Class based end to end example", () => { }), ); }); + + it("customized narrowing", () => { + class Specific extends schema.object( + "Specific", + { + s: customizeSchemaTyping(schema.string).simplified<"foo" | "bar">(), + }, + { supportCustomizedFields: true }, + ) {} + const parent = new Specific({ s: "bar" }); + // Reading field gives narrowed type + const s: "foo" | "bar" = parent.s; + + // @ts-expect-error custom typing violation does not build, but runs without error + const invalid = new Specific({ s: "x" }); + }); + + it("customized narrowing - safer", () => { + const specialString = customizeSchemaTyping(schema.string).custom<{ + input: "foo" | "bar"; + // Assignment can't be made be more restrictive than the read type, but we can choose to disable it. + readWrite: never; + }>(); + class Specific extends schema.object( + "Specific", + { + s: specialString, + }, + { supportCustomizedFields: true, supportReadonlyFields: true }, + ) {} + const parent = new Specific({ s: "bar" }); + // Reading gives string + const s = parent.s; + type _check = requireTrue>; + + // @ts-expect-error Assigning is disabled; + parent.s = "x"; + + // @ts-expect-error custom typing violation does not build, but runs without error + const invalid = new Specific({ s: "x" }); + + class Array extends schema.array("Specific", specialString) {} + + // Array constructor is also narrowed correctly. + const a = new Array(["bar"]); + // Array insertion is narrowed as well. + a.insertAtEnd("bar"); + // and reading just gives string, since this example choose to do so since other clients could set unexpected strings as its not enforced by schema: + const s2 = a[0]; + type _check2 = requireTrue>; + }); + + it("customized branding", () => { + type SpecialString = Brand; + + class Specific extends schema.object( + "Specific", + { + s: customizeSchemaTyping(schema.string).simplified(), + }, + { supportCustomizedFields: true, supportReadonlyFields: true }, + ) {} + const parent = new Specific({ s: brand("bar") }); + const s: SpecialString = parent.s; + + // @ts-expect-error custom typing violation does not build, but runs without error + const invalid = new Specific({ s: "x" }); + }); + + it("relaxed union", () => { + const runtimeDeterminedSchema = schema.string as + | typeof schema.string + | typeof schema.number; + class Strict extends schema.object( + "Strict", + { + s: runtimeDeterminedSchema, + }, + { supportCustomizedFields: true, supportReadonlyFields: true }, + ) {} + + class Relaxed extends schema.object( + "Relaxed", + { + s: customizeSchemaTyping(runtimeDeterminedSchema).relaxed(), + }, + { supportCustomizedFields: true, supportReadonlyFields: true }, + ) {} + + class RelaxedArray extends schema.object( + "Relaxed", + { + s: customizeSchemaTyping([runtimeDeterminedSchema]).relaxed(), + }, + { supportCustomizedFields: true, supportReadonlyFields: true }, + ) {} + + const customizer = customizeSchemaTyping(runtimeDeterminedSchema); + { + const field = customizer.relaxed(); + type Field = typeof field; + type X = GetTypes; + } + + { + const field = customizeSchemaTyping(runtimeDeterminedSchema).relaxed(); + type Field = typeof field; + type X = GetTypes; + } + + const customizerArray = customizeSchemaTyping([runtimeDeterminedSchema]); + { + const field = customizerArray.relaxed(); + type Field = typeof field; + type X = GetTypes["input"]; + } + + type XXX = GetTypes; + + type F2 = GetTypes>; + type X2 = GetTypes["relaxed"]>>; + + // @ts-expect-error custom typing violation does not build, but runs without error + const s = new Strict({ s: "x" }); + // @ts-expect-error custom typing violation does not build, but runs without error + s.s = "Y"; + + const r = new Relaxed({ s: "x" }); + r.s = "Y"; + const ra = new RelaxedArray({ s: "x" }); + ra.s = "Y"; + + // @ts-expect-error custom typing violation does not build, but runs without error + const invalid = new Strict({ s: "x" }); + }); }); 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 a15bd0850a55..2e93238cb89a 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 @@ -29,6 +29,7 @@ import { SchemaFactoryBeta, allowUnused, type InsertableTreeFieldFromImplicitField, + type DefaultTreeNodeFromImplicitAllowedTypes, } from "../../../simple-tree/index.js"; import { // Import directly to get the non-type import to allow testing of the package only instanceof @@ -105,7 +106,7 @@ import { EmptyKey } from "../../../core/index.js"; type FromArray = TreeNodeFromImplicitAllowedTypes<[typeof Note, typeof Note]>; type _check5 = requireTrue>; } - // TreeNodeFromImplicitAllowedTypes with a class + // TreeNodeFromImplicitAllowedTypes class { class NoteCustomized extends schema.object("Note", { text: schema.string }) { public test: boolean = false; @@ -117,6 +118,10 @@ import { EmptyKey } from "../../../core/index.js"; TreeNodeSchema >; + type TestDefault = DefaultTreeNodeFromImplicitAllowedTypes; + + type _checkDefault1 = requireAssignableTo; + type _checkDefault2 = requireTrue>; type Instance = InstanceType; type _checkInstance = requireTrue>; @@ -561,6 +566,33 @@ describe("schemaFactory", () => { assert.deepEqual(schema.fields.get("qux")!.persistedMetadata, fooMetadata); }); + it("typed options", () => { + const schema = new SchemaFactoryBeta("com.example"); + const fields = { id: schema.identifier, x: schema.number }; + class _NoOptions extends schema.object("X", fields) {} + class EmptyOptions extends schema.object("X", fields, {}) {} + + class TypoOption extends schema.object("X", fields, { + // @ts-expect-error Typo in option name + wrong: true, + }) {} + + class ValidOptions extends schema.object("X", fields, { + allowUnknownOptionalFields: true, + supportReadonlyFields: true, + supportCustomizedFields: true, + }) {} + + const empty = new EmptyOptions({ x: 1 }); + empty.id = "hello"; + + const valid = new ValidOptions({ x: 1 }); + assert.throws(() => { + // @ts-expect-error id is readonly + valid.id = "hello"; + }); + }); + describe("deep equality", () => { const schema = new SchemaFactory("com.example"); diff --git a/packages/dds/tree/src/test/simple-tree/api/schemaFactoryRecursive.spec.ts b/packages/dds/tree/src/test/simple-tree/api/schemaFactoryRecursive.spec.ts index 2c09f7ce4b9f..88d21440cf72 100644 --- a/packages/dds/tree/src/test/simple-tree/api/schemaFactoryRecursive.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/api/schemaFactoryRecursive.spec.ts @@ -29,14 +29,18 @@ import { type ImplicitAllowedTypes, type AllowedTypes, SchemaFactoryBeta, + customizeSchemaTyping, + type ObjectFromSchemaRecord, + type AssignableTreeFieldFromImplicitField, } from "../../../simple-tree/index.js"; import { allowUnused, type ValidateRecursiveSchema, // eslint-disable-next-line import/no-internal-modules } from "../../../simple-tree/api/schemaFactoryRecursive.js"; -import type { - System_Unsafe, +import { + customizeSchemaTypingUnsafe, + type System_Unsafe, // eslint-disable-next-line import/no-internal-modules } from "../../../simple-tree/api/typesUnsafe.js"; import { SharedTree } from "../../../treeFactory.js"; @@ -1507,4 +1511,131 @@ describe("SchemaFactory Recursive methods", () => { } }); }); + + describe("custom types", () => { + const sf = new SchemaFactoryAlpha(""); + it("custom non-recursive children", () => { + class O extends sf.objectRecursive("O", { + a: customizeSchemaTyping(sf.number).custom<{ + input: 1; + readWrite: never; + output: 2; + }>(), + recursive: sf.optionalRecursive([() => O]), + }) {} + + { + type _check = ValidateRecursiveSchema; + } + const obj = new O({ a: 1 }); + const read = obj.a; + type _checkRead = requireAssignableTo; + + // @ts-expect-error Readonly. + obj.a = 2 as never; + }); + + it("custom recursive children", () => { + class O extends sf.objectRecursive( + "O", + { + // Test that customizeSchemaTyping works for non recursive members of recursive types + a: customizeSchemaTyping(sf.number).custom<{ + input: 1; + readWrite: never; + output: 2; + }>(), + recursive: sf.optionalRecursive( + customizeSchemaTypingUnsafe([() => O]).custom<{ + input: unknown; + readWrite: never; + }>(), + ), + }, + { supportCustomizedFields: true, supportReadonlyFields: true }, + ) {} + { + type _check = ValidateRecursiveSchema; + } + // Check custom typing applies to "a" and "recursive" + const obj = new O({ a: 1, recursive: undefined as unknown }); + const read = obj.recursive; + type _checkRead = requireAssignableTo; + + // @ts-expect-error Readonly. + obj.recursive = new O({ a: 1 }); + + // Readonly fails to apply apply when using FieldSchema on recursive objects. + obj.recursive = undefined; + // @ts-expect-error Readonly. + obj.a = 1; + + { + type Obj = ObjectFromSchemaRecord; + type A = AssignableTreeFieldFromImplicitField; + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const x: Obj = {} as O; + // No error: Readonly not applied. + x.recursive = undefined; + } + + { + type Obj = ObjectFromSchemaRecord; + type A = AssignableTreeFieldFromImplicitField; + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const x: Obj = {} as O; + // @ts-expect-error Readonly. + x.recursive = undefined; + } + + { + type A = AssignableTreeFieldFromImplicitField; + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const x: O = {} as O; + // Readonly fails to apply apply when using FieldSchema on recursive objects. + x.recursive = undefined; + } + }); + + it("readonly fields", () => { + class O extends sf.objectRecursive("O", { + // Test that customizeSchemaTyping works for non recursive members of recursive types + opt: sf.optional( + customizeSchemaTyping(sf.number).custom<{ + readWrite: never; + }>(), + ), + req: sf.required( + customizeSchemaTyping(sf.number).custom<{ + readWrite: never; + }>(), + ), + recursive: sf.optionalRecursive( + customizeSchemaTypingUnsafe([() => O]).custom<{ + readWrite: never; + }>(), + ), + }) {} + { + type _check = ValidateRecursiveSchema; + } + // Check custom typing applies to "a" and "recursive" + const obj = new O({ req: 1 }); + const read = obj.recursive; + type _checkRead = requireAssignableTo; + + // @ts-expect-error Readonly. + obj.opt = 1; + // Ideally this would be an error as well, butt adding logic to do so breaks recursive type compilation when using it. + obj.opt = undefined; + + // @ts-expect-error Readonly. + obj.req = 1; + + assert.throws(() => { + // @ts-expect-error required. + obj.req = undefined; + }); + }); + }); }); diff --git a/packages/dds/tree/src/test/simple-tree/api/typesUnsafe.spec.ts b/packages/dds/tree/src/test/simple-tree/api/typesUnsafe.spec.ts index d01d8bea5551..3aa77ccdeece 100644 --- a/packages/dds/tree/src/test/simple-tree/api/typesUnsafe.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/api/typesUnsafe.spec.ts @@ -10,26 +10,77 @@ import type { // eslint-disable-next-line import/no-internal-modules } from "../../../simple-tree/api/typesUnsafe.js"; import { allowUnused } from "../../../simple-tree/index.js"; +import { + customizeSchemaTypingUnsafe, + // eslint-disable-next-line import/no-internal-modules +} from "../../../simple-tree/api/typesUnsafe.js"; +import { SchemaFactory, type ValidateRecursiveSchema } from "../../../simple-tree/index.js"; // eslint-disable-next-line import/no-internal-modules import { numberSchema } from "../../../simple-tree/leafNodeSchema.js"; import type { areSafelyAssignable, requireTrue } from "../../../util/index.js"; -type MapInlined = System_Unsafe.ReadonlyMapInlined; +{ + type MapInlined = System_Unsafe.ReadonlyMapInlined; + type _check = requireTrue>>; + + // UnannotateAllowedTypeUnsafe + { + type num = UnannotateAllowedTypeUnsafe; + allowUnused>>(); + + const annotatedAllowedType = { + type: numberSchema, + metadata: {}, + } satisfies AnnotatedAllowedTypeUnsafe; -type _check = requireTrue>>; + type annotated = UnannotateAllowedTypeUnsafe; + allowUnused>>(); + } +} -// UnannotateAllowedTypeUnsafe +// customizeSchemaTypingUnsafe and InsertableObjectFromSchemaRecordUnsafe { - type num = UnannotateAllowedTypeUnsafe; - // eslint-disable-next-line @typescript-eslint/no-unused-expressions - allowUnused>>; - - const annotatedAllowedType = { - type: numberSchema, - metadata: {}, - } satisfies AnnotatedAllowedTypeUnsafe; - - type annotated = UnannotateAllowedTypeUnsafe; - // eslint-disable-next-line @typescript-eslint/no-unused-expressions - allowUnused>>; + const sf = new SchemaFactory("recursive"); + // @ts-expect-error compiler differes from intelisense here + class Bad extends sf.objectRecursive("O", { + // @ts-expect-error customizeSchemaTypingUnsafe needs to be applied to the allowed types, not the field: this is wrong! + recursive: customizeSchemaTypingUnsafe(sf.optionalRecursive([() => Bad])).custom<{ + input: 5; + }>(), + }) {} + + { + // @ts-expect-error this should error + type _check = ValidateRecursiveSchema; + } + + class O extends sf.objectRecursive("O", { + recursive: sf.optionalRecursive( + customizeSchemaTypingUnsafe([() => O]).custom<{ + input: 5; + }>(), + ), + }) {} + + // Record + { + type T = System_Unsafe.InsertableObjectFromSchemaRecordUnsafe["recursive"]; + type _check = requireTrue>; + } + + // Field + { + type T = System_Unsafe.InsertableTreeFieldFromImplicitFieldUnsafe< + typeof O.info.recursive.allowedTypes + >; + type _check = requireTrue>; + } + + // AllowedTypes + { + type T = System_Unsafe.InsertableTreeNodeFromImplicitAllowedTypesUnsafe< + typeof O.info.recursive.allowedTypes + >; + type _check = requireTrue>; + } } diff --git a/packages/dds/tree/src/test/simple-tree/fieldSchema.spec.ts b/packages/dds/tree/src/test/simple-tree/fieldSchema.spec.ts index 162e2adbf7fc..3a3e9aa68a6e 100644 --- a/packages/dds/tree/src/test/simple-tree/fieldSchema.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/fieldSchema.spec.ts @@ -6,16 +6,21 @@ import { strict as assert } from "node:assert"; import { + relaxObject, SchemaFactory, SchemaFactoryAlpha, type AllowedTypes, type booleanSchema, + type CustomizedSchemaTyping, + type DefaultTreeNodeFromImplicitAllowedTypes, type ImplicitAllowedTypes, type numberSchema, + type SchemaUnionToIntersection, type stringSchema, type TreeLeafValue, type TreeNode, type TreeNodeSchema, + type UnsafeUnknownSchema, } from "../../simple-tree/index.js"; import { @@ -33,6 +38,7 @@ import type { areSafelyAssignable, requireAssignableTo, requireTrue, + UnionToIntersection, } from "../../util/index.js"; import { TreeAlpha } from "../../shared-tree/index.js"; @@ -55,6 +61,24 @@ describe("fieldSchema", () => { >; } + // CustomSchemaIntersection + { + type Original = A | B; + type Custom = CustomizedSchemaTyping; + type OriginalIntersection = UnionToIntersection; + type CustomIntersection = UnionToIntersection; + + type OriginalSchemaIntersection = SchemaUnionToIntersection; + type CustomSchemaIntersection = SchemaUnionToIntersection; + + type _check1 = requireTrue>; + type _check2 = requireTrue>; + type _check3 = requireTrue>; + type _check4 = requireTrue< + areSafelyAssignable + >; + } + // InsertableTreeFieldFromImplicitField { // Input @@ -93,6 +117,74 @@ describe("fieldSchema", () => { } } + // DefaultTreeNodeFromImplicitAllowedTypes + { + class Simple extends schema.object("A", { x: [schema.number] }) {} + class Customized extends schema.object("B", { x: [schema.number] }) { + public customized = true; + } + + type TA = DefaultTreeNodeFromImplicitAllowedTypes; + type _checkA = requireAssignableTo; + + type TB = DefaultTreeNodeFromImplicitAllowedTypes; + type _checkB = requireAssignableTo; + } + + // Example CustomTypes + + /** + * Ignores schema, and allows any edit at compile time. + */ + interface AnyTypes { + input: InsertableField; + readWrite: TreeNode | TreeLeafValue; + output: TreeNode | TreeLeafValue; + } + + /** + * Ignores schema, forbidding all edits. + */ + interface UnknownTypes { + input: never; + readWrite: never; + output: TreeNode | TreeLeafValue; + } + + // DefaultTreeNodeFromImplicitAllowedTypes + { + class Simple extends schema.object("A", { x: [schema.number] }) {} + class Customized extends schema.object("B", { x: [schema.number] }) { + public customized = true; + } + + type TA = DefaultTreeNodeFromImplicitAllowedTypes; + type _checkA = requireAssignableTo; + + type TB = DefaultTreeNodeFromImplicitAllowedTypes; + type _checkB = requireAssignableTo; + } + + // Example CustomTypes + + /** + * Ignores schema, and allows any edit at compile time. + */ + interface AnyTypes { + input: InsertableField; + readWrite: TreeNode | TreeLeafValue; + output: TreeNode | TreeLeafValue; + } + + /** + * Ignores schema, forbidding all edits. + */ + interface UnknownTypes { + input: never; + readWrite: never; + output: TreeNode | TreeLeafValue; + } + // InsertableField { { @@ -217,9 +309,11 @@ describe("fieldSchema", () => { schemaTypes: T, content: InsertableTreeFieldFromImplicitField, ) { - class GenericContainer extends sf.object("GenericContainer", { - content: schemaTypes, - }) {} + class GenericContainer extends relaxObject( + sf.object("GenericContainer", { + content: schemaTypes, + }), + ) {} // Both create and the constructor type check as desired. const _created = TreeAlpha.create(GenericContainer, { content }); @@ -235,17 +329,19 @@ describe("fieldSchema", () => { schemaTypes: T, content: InsertableTreeFieldFromImplicitField, ) { - class GenericContainer extends sf.object("GenericContainer", { - content: sf.required(schemaTypes), - }) {} + class GenericContainer extends relaxObject( + sf.object("GenericContainer", { + content: sf.required(schemaTypes), + }), + ) {} // Users of the class (if it were returned from this test function with a concrete type instead of a generic one) would be fine, // but using it in this generic context has issues. // Specifically the construction APIs don't type check as desired. - // @ts-expect-error Compiler limitation, see comment above. + // todo // @ts-expect-error Compiler limitation, see comment above. const _created = TreeAlpha.create(GenericContainer, { content }); - // @ts-expect-error Compiler limitation, see comment above. + // todo // @ts-expect-error Compiler limitation, see comment above. return new GenericContainer({ content }); } @@ -256,9 +352,11 @@ describe("fieldSchema", () => { schemaTypes: T, content: InsertableTreeFieldFromImplicitField | undefined, ) { - class GenericContainer extends sf.object("GenericContainer", { - content: sf.optional(schemaTypes), - }) {} + class GenericContainer extends relaxObject( + sf.object("GenericContainer", { + content: sf.optional(schemaTypes), + }), + ) {} // Like with the above case, TypeScript fails to simplify the input types, and these do not build. @@ -306,7 +404,7 @@ describe("fieldSchema", () => { // @ts-expect-error Compiler limitation, see comment above. type _check5 = requireAssignableTo; - // @ts-expect-error Compiler limitation, see comment above. + // TODO: // @ts-expect-error Compiler limitation, see comment above. type _check6 = requireAssignableTo; }); @@ -322,7 +420,7 @@ describe("fieldSchema", () => { // @ts-expect-error Compiler limitation, see comment above. type _check5 = requireAssignableTo; - // @ts-expect-error Compiler limitation, see comment above. + // TODO: // @ts-expect-error Compiler limitation, see comment above. type _check6 = requireAssignableTo; }); @@ -336,7 +434,7 @@ describe("fieldSchema", () => { // @ts-expect-error Compiler limitation, see comment above. type _check5 = requireAssignableTo; - // @ts-expect-error Compiler limitation, see comment above. + // TODO: // @ts-expect-error Compiler limitation, see comment above. type _check6 = requireAssignableTo; // At least this case allows undefined, like recursive object fields, but unlike non recursive object fields. diff --git a/packages/dds/tree/src/test/simple-tree/largeSchema.spec.ts b/packages/dds/tree/src/test/simple-tree/largeSchema.spec.ts index 8cd3b0d82d66..e1d84567cf21 100644 --- a/packages/dds/tree/src/test/simple-tree/largeSchema.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/largeSchema.spec.ts @@ -4,6 +4,7 @@ */ import { + relaxObject, SchemaFactory, TreeViewConfiguration, type ImplicitFieldSchema, @@ -544,16 +545,16 @@ describe("largeSchema", () => { prefix: N, inner: T, ) { - class Depth009 extends schema.object(`Deep${prefix}9`, { x: inner }) {} - class Depth008 extends schema.object(`Deep${prefix}8`, { x: Depth009 }) {} - class Depth007 extends schema.object(`Deep${prefix}7`, { x: Depth008 }) {} - class Depth006 extends schema.object(`Deep${prefix}6`, { x: Depth007 }) {} - class Depth005 extends schema.object(`Deep${prefix}5`, { x: Depth006 }) {} - class Depth004 extends schema.object(`Deep${prefix}4`, { x: Depth005 }) {} - class Depth003 extends schema.object(`Deep${prefix}3`, { x: Depth004 }) {} - class Depth002 extends schema.object(`Deep${prefix}2`, { x: Depth003 }) {} - class Depth001 extends schema.object(`Deep${prefix}1`, { x: Depth002 }) {} - class Depth000 extends schema.object(`Deep${prefix}0`, { x: Depth001 }) {} + class Depth009 extends relaxObject(schema.object(`Deep${prefix}9`, { x: inner })) {} + class Depth008 extends relaxObject(schema.object(`Deep${prefix}8`, { x: Depth009 })) {} + class Depth007 extends relaxObject(schema.object(`Deep${prefix}7`, { x: Depth008 })) {} + class Depth006 extends relaxObject(schema.object(`Deep${prefix}6`, { x: Depth007 })) {} + class Depth005 extends relaxObject(schema.object(`Deep${prefix}5`, { x: Depth006 })) {} + class Depth004 extends relaxObject(schema.object(`Deep${prefix}4`, { x: Depth005 })) {} + class Depth003 extends relaxObject(schema.object(`Deep${prefix}3`, { x: Depth004 })) {} + class Depth002 extends relaxObject(schema.object(`Deep${prefix}2`, { x: Depth003 })) {} + class Depth001 extends relaxObject(schema.object(`Deep${prefix}1`, { x: Depth002 })) {} + class Depth000 extends relaxObject(schema.object(`Deep${prefix}0`, { x: Depth001 })) {} return Depth000; } diff --git a/packages/dds/tree/src/test/simple-tree/node-kinds/object/objectNode.spec.ts b/packages/dds/tree/src/test/simple-tree/node-kinds/object/objectNode.spec.ts index ae67f07e7808..a8d55b4bbb8d 100644 --- a/packages/dds/tree/src/test/simple-tree/node-kinds/object/objectNode.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/node-kinds/object/objectNode.spec.ts @@ -29,6 +29,9 @@ import { type InsertableTypedNode, type NodeFromSchema, unhydratedFlexTreeFromInsertable, + type TreeFieldFromImplicitField, + type System_Unsafe, + SchemaFactoryBeta, } from "../../../../simple-tree/index.js"; import type { FieldHasDefault, @@ -40,6 +43,7 @@ import { describeHydration, hydrate, pretty } from "../../utils.js"; import { brand } from "../../../../util/index.js"; import type { areSafelyAssignable, + FlattenKeys, isAssignableTo, requireAssignableTo, requireFalse, @@ -53,6 +57,8 @@ import { FieldKinds } from "../../../../feature-libraries/index.js"; import { createField, UnhydratedFlexTreeNode, + type TreeLeafValue, + type TreeNode, // eslint-disable-next-line import/no-internal-modules } from "../../../../simple-tree/core/index.js"; // eslint-disable-next-line import/no-internal-modules @@ -60,7 +66,7 @@ import { getUnhydratedContext } from "../../../../simple-tree/createContext.js"; // eslint-disable-next-line import/no-internal-modules import { createTreeNodeFromInner } from "../../../../simple-tree/core/treeNodeKernel.js"; -const schemaFactory = new SchemaFactory("Test"); +const schemaFactory = new SchemaFactoryBeta("Test"); // InsertableObjectFromSchemaRecord { @@ -360,15 +366,151 @@ describeHydration( }); it("assigning identifier errors", () => { - class HasId extends schemaFactory.object("hasID", { + class HasId extends schemaFactory.object( + "hasID", + { + id: schemaFactory.identifier, + }, + { supportReadonlyFields: true }, + ) {} + const n = init(HasId, {}); + assert.throws(() => { + // @ts-expect-error this should not compile + n.id = "x"; + }); + }); + + it("assigning non-exact schema errors - ImplicitFieldSchema", () => { + const child: ImplicitFieldSchema = schemaFactory.number; + class NonExact extends schemaFactory.object( + "NonExact", + { + child, + }, + { supportReadonlyFields: true }, + ) {} + // @ts-expect-error Should not compile, and does not due to non-exact typing. + const initial: InsertableField = { child: 1 }; + const n: NonExact = init(NonExact, initial); + assert.throws(() => { + // @ts-expect-error this should not compile + n.child = "x"; + }); + }); + + it("assigning non-exact optional schema", () => { + const child: ImplicitFieldSchema = schemaFactory.number; + class NonExact extends schemaFactory.object( + "NonExact", + { + child: schemaFactory.optional(child), + }, + { supportReadonlyFields: true }, + ) {} + // @ts-expect-error Should not compile, and does not due to non-exact typing. + const initial: InsertableField = { child: 1 }; + const n: NonExact = init(NonExact, initial); + + assert.throws(() => { + // @ts-expect-error this should not compile + n.child = "x"; + }); + + type Read = TreeFieldFromImplicitField<(typeof NonExact.info)["child"]>; + type _check1 = requireTrue< + areSafelyAssignable + >; + + const read = n.child; + type _check2 = requireTrue< + areSafelyAssignable + >; + + // This would be ok, but allowing it forces allowing assigning any of the values that can be read, which is very unsafe here. + // @ts-expect-error this should not compile + n.child = undefined; + }); + + it("assigning non-exact schema errors - union", () => { + const child = schemaFactory.number as + | typeof schemaFactory.number + | typeof schemaFactory.null; + class NonExact extends schemaFactory.object( + "NonExact", + { + child, + }, + { supportReadonlyFields: true }, + ) {} + // @ts-expect-error Should not compile, and does not due to non-exact typing. + const initial: InsertableField = { child: 1 }; + const n: NonExact = init(NonExact, initial); + const childRead = n.child; + assert.throws(() => { + // @ts-expect-error this should not compile + n.child = "x"; + }); + + assert.throws(() => { + // @ts-expect-error this should not compile + n.child = null; + }); + + // @ts-expect-error this should not compile + n.child = 5; + }); + + it("assigning identifier errors - ImplicitFieldSchema - recursive", () => { + class HasId extends schemaFactory.objectRecursive("hasID", { id: schemaFactory.identifier, }) {} const n = init(HasId, {}); assert.throws(() => { - // TODO: AB:9129: this should not compile + // @ts-expect-error Readonly n.id = "x"; }); }); + + it("assigning non-exact schema errors - union - recursive", () => { + const child: ImplicitFieldSchema = schemaFactory.number; + class NonExact extends schemaFactory.objectRecursive("NonExact", { + child, + }) {} + // @ts-expect-error Should not compile, and does not due to non-exact typing. + const initial: InsertableField = { child: 1 }; + const n: NonExact = init(NonExact, initial); + assert.throws(() => { + // Due to recursive type limitations, this compiles but shouldn't, see ObjectFromSchemaRecordUnsafe + n.child = "x"; + }); + }); + + it("assigning non-exact schema errors - recursive", () => { + const child = schemaFactory.number as + | typeof schemaFactory.number + | typeof schemaFactory.null; + class NonExact extends schemaFactory.objectRecursive("NonExact", { + child, + }) {} + // @ts-expect-error Should not compile, and does not due to non-exact typing. + const initial: InsertableField = { child: 1 }; + const n: NonExact = init(NonExact, initial); + const childRead = n.child; + type XXX = FlattenKeys>; + type _check = requireTrue>; + assert.throws(() => { + // @ts-expect-error this should not compile + n.child = "x"; + }); + + assert.throws(() => { + // Due to recursive type limitations, this compiles but shouldn't, see ObjectFromSchemaRecordUnsafe + n.child = null; + }); + + // Due to recursive type limitations, this compiles but shouldn't, see ObjectFromSchemaRecordUnsafe + n.child = 5; + }); }); // Regression test for accidental use of ?? preventing null values from being read correctly. @@ -473,13 +615,18 @@ describeHydration( }); it("identifier", () => { - class Schema extends schemaFactory.object("parent", { - id: schemaFactory.identifier, - }) {} + class Schema extends schemaFactory.object( + "parent", + { + id: schemaFactory.identifier, + }, + { supportReadonlyFields: true }, + ) {} const root = init(Schema, { id: "a" }); assert.throws(() => { // TODO: AB#35799 this should not compile! // If it does compile, it must be a UsageError. + // @ts-expect-error writing to an identifier is not allowed root.id = "b"; }); }); @@ -692,11 +839,51 @@ describeHydration( }); it("optional custom shadowing", () => { - class Schema extends schemaFactory.object("x", { - foo: schemaFactory.optional(schemaFactory.number), - }) { + class Schema extends schemaFactory.object( + "x", + { + foo: schemaFactory.optional(schemaFactory.number), + }, + { supportReadonlyFields: undefined }, + ) { + // Since fields are own properties, we expect inherited properties (like this) to be shadowed by fields. + // However in TypeScript they work like inherited properties, so the types don't match the runtime behavior. + // eslint-disable-next-line @typescript-eslint/class-literal-property-style + public override get foo(): 5 { + return 5; + } + } + function typeTest() { + const n = hydrate(Schema, { foo: 1 }); + assert.equal(n.foo, 1); + // @ts-expect-error TypeScript typing does not understand that fields are own properties and thus shadow the getter here. + n.foo = undefined; + } + + function typeTest2() { + const n = hydrate(Schema, { foo: undefined }); + const x = n.foo; + // TypeScript is typing the "foo" field based on the getter not the field, which does not match runtime behavior. + type check_ = requireAssignableTo; + } + + assert.throws( + () => new Schema({ foo: undefined }), + (e: Error) => validateAssertionError(e, /this shadowing will not work/), + ); + }); + + it("optional custom shadowing readonly", () => { + class Schema extends schemaFactory.object( + "x", + { + foo: schemaFactory.optional(schemaFactory.number), + }, + { supportReadonlyFields: true }, + ) { // Since fields are own properties, we expect inherited properties (like this) to be shadowed by fields. - // However in TypeScript they work like inherited properties, so the types don't make the runtime behavior. + // However in TypeScript they work like inherited properties, so the types don't match the runtime behavior. + // @ts-expect-error bad shadow // eslint-disable-next-line @typescript-eslint/class-literal-property-style public override get foo(): 5 { return 5; diff --git a/packages/dds/tree/src/test/types/validateTreePrevious.generated.ts b/packages/dds/tree/src/test/types/validateTreePrevious.generated.ts index e9a0fb19cd50..e6c39dfa19ca 100644 --- a/packages/dds/tree/src/test/types/validateTreePrevious.generated.ts +++ b/packages/dds/tree/src/test/types/validateTreePrevious.generated.ts @@ -499,6 +499,7 @@ declare type old_as_current_for_TypeAlias_InsertableTreeNodeFromImplicitAllowedT * typeValidation.broken: * "TypeAlias_InsertableTreeNodeFromImplicitAllowedTypes": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_TypeAlias_InsertableTreeNodeFromImplicitAllowedTypes = requireAssignableTo>, TypeOnly>> /* diff --git a/packages/dds/tree/src/util/index.ts b/packages/dds/tree/src/util/index.ts index 49517f0e13bd..1ff249ae63c8 100644 --- a/packages/dds/tree/src/util/index.ts +++ b/packages/dds/tree/src/util/index.ts @@ -109,6 +109,7 @@ export type { UnionToIntersection, UnionToTuple, PopUnion, + PreventExtraProperties, } from "./typeUtils.js"; export { unsafeArrayToTuple } from "./typeUtils.js"; diff --git a/packages/dds/tree/src/util/typeUtils.ts b/packages/dds/tree/src/util/typeUtils.ts index a189deb6e1c8..1c282642f8dd 100644 --- a/packages/dds/tree/src/util/typeUtils.ts +++ b/packages/dds/tree/src/util/typeUtils.ts @@ -224,3 +224,14 @@ export type UnionToTuple< export function unsafeArrayToTuple(items: T[]): UnionToTuple { return items as UnionToTuple; } + +/** + * When inferring a type from a parameter to capture the typing of the members, this can be used provide errors when the user provides an input with a invalid property. + * @remarks + * This ensures typos in property names are caught. + * This does not constrain the types of the values of the properties: it is assumed that the user has already done that via an extends clause if needed. + * @system @beta + */ +export type PreventExtraProperties = T & { + [x in keyof T]: x extends keyof Expected ? T[x] : undefined; +}; 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 6541e559f004..9388bca83be7 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 @@ -87,6 +87,11 @@ type ApplyKind = { [FieldKind.Identifier]: T; }[Kind]; +// @public @system +export type ApplyKindAssignment = [Kind] extends [ +FieldKind.Required +] ? T : [Kind] extends [FieldKind.Optional] ? T | undefined : never; + // @public @system type ApplyKindInput = [ Kind @@ -120,6 +125,12 @@ export function asAlpha(view: TreeView(view: TreeView): TreeViewBeta; +// @public @system +export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; + +// @public @system +export type AssignableTreeFieldFromImplicitFieldDefault> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? StrictTypes["readWrite"] : never; + // @alpha @deprecated export function asTreeViewAlpha(view: TreeView): TreeViewAlpha; @@ -240,10 +251,60 @@ export function createSimpleTreeIndex(view: TreeView, indexer: Map, getValue: (nodes: TreeIndexNodes>) => TValue, isKeyValid: (key: TreeIndexKey) => key is TKey, indexableSchema: readonly TSchema[]): SimpleTreeIndex; +// @public @system +export type CustomizedSchemaTyping = TSchema & { + [CustomizedTyping]: TCustom; +}; + +// @public @system +export const CustomizedTyping: unique symbol; + +// @public @system +export type CustomizedTyping = typeof CustomizedTyping; + +// @alpha @sealed +export interface Customizer { + custom>(): CustomizedSchemaTyping[Property] : GetTypes[Property]; + }>; + relaxed(): CustomizedSchemaTyping : TSchema extends AllowedTypes ? TSchema[number] extends LazyItem ? InsertableTypedNode : never : never; + readWrite: TreeNodeFromImplicitAllowedTypes; + output: TreeNodeFromImplicitAllowedTypes; + }>; + simplified>(): CustomizedSchemaTyping; + simplifiedUnrestricted(): CustomizedSchemaTyping; + strict(): CustomizedSchemaTyping>; +} + +// @alpha +export function customizeSchemaTyping(schema: TSchema): Customizer; + +// @public @sealed +export interface CustomTypes { + readonly input: unknown; + readonly output: TreeLeafValue | TreeNode; + readonly readWrite: TreeLeafValue | TreeNode; +} + +// @public @system +export type DefaultInsertableTreeNodeFromImplicitAllowedTypes = [TSchema] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; + // @public @sealed @system interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> { } +// @public @system +export type DefaultTreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; + // @alpha export interface DirtyTreeMap { // (undocumented) @@ -453,6 +514,11 @@ export function getJsonSchema(schema: ImplicitAllowedTypes, options: Required = [TSchema] extends [ +CustomizedSchemaTyping +] ? TCustom : StrictTypes; + // @alpha export type HandleConverter = (data: IFluidHandle) => TCustom; @@ -742,7 +808,7 @@ type _InlineTrick = 0; export type Input = T; // @alpha -export type Insertable = TSchema extends ImplicitAllowedTypes ? InsertableTreeNodeFromImplicitAllowedTypes : InsertableContent; +export type Insertable = InsertableTreeNodeFromImplicitAllowedTypes; // @alpha @system export type InsertableContent = Unhydrated | FactoryContent; @@ -762,7 +828,7 @@ type InsertableObjectFromSchemaRecord; // @public -export type InsertableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; +export type InsertableTreeFieldFromImplicitField] ? TSchemaInput : SchemaUnionToIntersection> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; // @public @system export type InsertableTreeNodeFromAllowedTypes = IsUnion extends true ? never : { @@ -772,9 +838,7 @@ export type InsertableTreeNodeFromAllowedTypes = IsU }[NumberKeys]; // @public -export type InsertableTreeNodeFromImplicitAllowedTypes = [ -TSchema -] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; +export type InsertableTreeNodeFromImplicitAllowedTypes = GetTypes["input"]; // @public export type InsertableTypedNode> = (T extends TreeNodeSchema ? NodeBuilderData : never) | (T extends TreeNodeSchema ? Unhydrated ? never : NodeFromSchema> : never); @@ -1097,22 +1161,33 @@ export type NumberKeys = Transformed[`${number}` & keyof Transformed]; // @public @system -export type ObjectFromSchemaRecord> = RestrictiveStringRecord extends T ? {} : { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; +export type ObjectFromSchemaRecord, Options extends ObjectSchemaTypingOptions = Record> = RestrictiveStringRecord extends T ? {} : [Options["supportReadonlyFields"]] extends [true] ? { + -readonly [Property in keyof T as [ + AssignableTreeFieldFromImplicitField + ] extends [never | undefined] ? never : Property]: [Options["supportCustomizedFields"]] extends [true] ? AssignableTreeFieldFromImplicitField : AssignableTreeFieldFromImplicitFieldDefault; +} & { + readonly [Property in keyof T]: [Options["supportCustomizedFields"]] extends [true] ? TreeFieldFromImplicitField : TreeFieldFromImplicitFieldDefault; +} : { + -readonly [Property in keyof T]: [Options["supportCustomizedFields"]] extends [true] ? TreeFieldFromImplicitField : TreeFieldFromImplicitFieldDefault; +}; + +// @beta @system +export type ObjectFromSchemaRecordRelaxed> = RestrictiveStringRecord extends T ? {} : { + [Property in keyof T]: TreeFieldFromImplicitFieldDefault; }; // @alpha @sealed -export interface ObjectNodeSchema = RestrictiveStringRecord, ImplicitlyConstructable extends boolean = boolean, out TCustomMetadata = unknown> extends TreeNodeSchemaClass, InsertableObjectFromSchemaRecord, ImplicitlyConstructable, T, never, TCustomMetadata>, SimpleObjectNodeSchema { +export interface ObjectNodeSchema = RestrictiveStringRecord, ImplicitlyConstructable extends boolean = boolean, TOptions extends ObjectSchemaOptionsAlpha = ObjectSchemaOptionsAlpha, TCustomMetadataInferred = TOptions extends ObjectSchemaOptionsAlpha ? TCustomMetadataX : unknown> extends TreeNodeSchemaClass, InsertableObjectFromSchemaRecord, ImplicitlyConstructable, T, never, TCustomMetadataInferred>, SimpleObjectNodeSchema { readonly fields: ReadonlyMap; } // @alpha (undocumented) export const ObjectNodeSchema: { - readonly [Symbol.hasInstance]: (value: TreeNodeSchema) => value is ObjectNodeSchema, boolean, unknown>; + readonly [Symbol.hasInstance]: (value: TreeNodeSchema) => value is ObjectNodeSchema, boolean, ObjectSchemaOptionsAlpha, unknown>; }; // @beta @input -export interface ObjectSchemaOptions extends NodeSchemaOptions { +export interface ObjectSchemaOptions extends NodeSchemaOptions, ObjectSchemaTypingOptions { readonly allowUnknownOptionalFields?: boolean; } @@ -1120,6 +1195,14 @@ export interface ObjectSchemaOptions extends NodeSche export interface ObjectSchemaOptionsAlpha extends ObjectSchemaOptions, NodeSchemaOptionsAlpha { } +// @public @input +export interface ObjectSchemaTypingOptions { + // (undocumented) + readonly supportCustomizedFields?: true | undefined; + // (undocumented) + readonly supportReadonlyFields?: true | undefined; +} + // @alpha @sealed export interface ObservationResults { readonly result: TResult; @@ -1138,6 +1221,11 @@ export function persistedToSimpleSchema(persisted: JsonCompatible, options: ICod // @beta @system export type PopUnion void : never>> = AsOverloadedFunction extends (a: infer First) => void ? First : never; +// @beta @system +export type PreventExtraProperties = T & { + [x in keyof T]: x extends keyof Expected ? T[x] : undefined; +}; + // @alpha @system export type ReadableField = TreeFieldFromImplicitField>; @@ -1291,10 +1379,10 @@ export class SchemaFactoryAlpha & SimpleLeafNodeSchema, LeafSchema<"number", number> & SimpleLeafNodeSchema, LeafSchema<"boolean", boolean> & SimpleLeafNodeSchema, LeafSchema<"null", null> & SimpleLeafNodeSchema, LeafSchema<"handle", IFluidHandle_2> & SimpleLeafNodeSchema]; mapAlpha(name: Name, allowedTypes: T, options?: NodeSchemaOptionsAlpha): MapNodeCustomizableSchema, T, true, TCustomMetadata>; mapRecursive(name: Name, allowedTypes: T, options?: NodeSchemaOptionsAlpha): MapNodeCustomizableSchemaUnsafe, T, TCustomMetadata>; - objectAlpha, const TCustomMetadata = unknown>(name: Name, fields: T, options?: ObjectSchemaOptionsAlpha): ObjectNodeSchema, T, true, TCustomMetadata> & { + objectAlpha, const TOptions extends ObjectSchemaOptionsAlpha = ObjectSchemaOptionsAlpha>(name: Name, fields: T, options?: PreventExtraProperties): ObjectNodeSchema, T, true, TOptions> & { readonly createFromInsertable: unknown; }; - objectRecursive, const TCustomMetadata = unknown>(name: Name, t: T, options?: ObjectSchemaOptionsAlpha): TreeNodeSchemaClass, NodeKind.Object, System_Unsafe.TreeObjectNodeUnsafe>, object & System_Unsafe.InsertableObjectFromSchemaRecordUnsafe, false, T, never, TCustomMetadata> & SimpleObjectNodeSchema & Pick; + objectRecursive, const TOptions extends ObjectSchemaOptionsAlpha = ObjectSchemaOptionsAlpha>(name: Name, t: T, options?: PreventExtraProperties): TreeNodeSchemaClass, NodeKind.Object, System_Unsafe.TreeObjectNodeUnsafe>, object & System_Unsafe.InsertableObjectFromSchemaRecordUnsafe, false, T, never, TOptions extends ObjectSchemaOptionsAlpha ? TCustomMetadataX : unknown> & SimpleObjectNodeSchema ? TCustomMetadataX : unknown> & Pick; static readonly optional: (t: T, props?: Omit, "defaultProvider"> | undefined) => FieldSchemaAlpha; readonly optional: (t: T, props?: Omit, "defaultProvider"> | undefined) => FieldSchemaAlpha; static readonly optionalRecursive: (t: T, props?: Omit, "defaultProvider"> | undefined) => FieldSchemaAlphaUnsafe; @@ -1320,7 +1408,7 @@ export class SchemaFactoryAlpha extends SchemaFactory { - object, const TCustomMetadata = unknown>(name: Name, fields: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T, never, TCustomMetadata>; + object, const TOptions extends ObjectSchemaOptions = ObjectSchemaOptions>(name: Name, fields: T, options?: PreventExtraProperties): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode, TOptions>, object & InsertableObjectFromSchemaRecord, true, T, never, TOptions extends ObjectSchemaOptions ? TCustomMetadata : unknown>; // (undocumented) objectRecursive, const TCustomMetadata = unknown>(name: Name, t: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, System_Unsafe.TreeObjectNodeUnsafe>, object & System_Unsafe.InsertableObjectFromSchemaRecordUnsafe, false, T, never, TCustomMetadata>; record(allowedTypes: T): TreeNodeSchemaNonClass`>, NodeKind.Record, TreeRecordNode & WithType`>, NodeKind.Record>, RecordNodeInsertableData, true, T, undefined>; @@ -1359,6 +1447,11 @@ export interface SchemaStaticsAlpha { readonly typesRecursive: >[]>(t: T, metadata?: AllowedTypesMetadata) => AllowedTypesFullFromMixedUnsafe; } +// @public @system +export type SchemaUnionToIntersection = [T] extends [ +CustomizedSchemaTyping +] ? T : UnionToIntersection; + // @beta @sealed export class SchemaUpgrade { // (undocumented) @@ -1472,6 +1565,16 @@ export function singletonSchema, true, Record, undefined>; +// @public @sealed +export interface StrictTypes, TOutput extends TreeNode | TreeLeafValue = DefaultTreeNodeFromImplicitAllowedTypes> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TInput extends never ? never : TOutput; +} + // @alpha @system export namespace System_TableSchema { // @sealed @system @@ -1506,18 +1609,18 @@ export namespace System_TableSchema { }>; // @system export function createTableSchema, const TRowSchema extends RowSchemaBase>(inputSchemaFactory: SchemaFactoryBeta, _cellSchema: TCellSchema, columnSchema: TColumnSchema, rowSchema: TRowSchema): TreeNodeSchemaCore_2, "Table">, NodeKind.Object, true, { - readonly rows: TreeNodeSchemaClass, "Table.rows">, NodeKind.Array, TreeArrayNode : TreeNodeFromImplicitAllowedTypes, [TRowSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.rows">, NodeKind.Array, unknown>, Iterable>, true, TRowSchema, undefined>; - readonly columns: TreeNodeSchemaClass, "Table.columns">, NodeKind.Array, TreeArrayNode : TreeNodeFromImplicitAllowedTypes, [TColumnSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.columns">, NodeKind.Array, unknown>, Iterable>, true, TColumnSchema, undefined>; + readonly rows: TreeNodeSchemaClass, "Table.rows">, NodeKind.Array, TreeArrayNode : TreeNode | TreeLeafValue_2, [TRowSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.rows">, NodeKind.Array, unknown>, Iterable>, true, TRowSchema, undefined>; + readonly columns: TreeNodeSchemaClass, "Table.columns">, NodeKind.Array, TreeArrayNode : TreeNode | TreeLeafValue_2, [TColumnSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.columns">, NodeKind.Array, unknown>, Iterable>, true, TColumnSchema, undefined>; }, object & { - readonly rows: (InsertableTypedNode_2, "Table.rows">, NodeKind.Array, true, TRowSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNodeFromImplicitAllowedTypes, [TRowSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.rows">, NodeKind.Array, unknown>)> | undefined) & InsertableTypedNode_2, "Table.rows">, NodeKind.Array, true, TRowSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNodeFromImplicitAllowedTypes, [TRowSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.rows">, NodeKind.Array, unknown>)>; - readonly columns: (InsertableTypedNode_2, "Table.columns">, NodeKind.Array, true, TColumnSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNodeFromImplicitAllowedTypes, [TColumnSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.columns">, NodeKind.Array, unknown>)> | undefined) & InsertableTypedNode_2, "Table.columns">, NodeKind.Array, true, TColumnSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNodeFromImplicitAllowedTypes, [TColumnSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.columns">, NodeKind.Array, unknown>)>; + readonly rows: (InsertableTypedNode_2, "Table.rows">, NodeKind.Array, true, TRowSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNode | TreeLeafValue_2, [TRowSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.rows">, NodeKind.Array, unknown>)> | undefined) & InsertableTypedNode_2, "Table.rows">, NodeKind.Array, true, TRowSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNode | TreeLeafValue_2, [TRowSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.rows">, NodeKind.Array, unknown>)>; + readonly columns: (InsertableTypedNode_2, "Table.columns">, NodeKind.Array, true, TColumnSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNode | TreeLeafValue_2, [TColumnSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.columns">, NodeKind.Array, unknown>)> | undefined) & InsertableTypedNode_2, "Table.columns">, NodeKind.Array, true, TColumnSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNode | TreeLeafValue_2, [TColumnSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.columns">, NodeKind.Array, unknown>)>; }, unknown> & (new (data: InternalTreeNode | (object & { - readonly rows: (InsertableTypedNode_2, "Table.rows">, NodeKind.Array, true, TRowSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNodeFromImplicitAllowedTypes, [TRowSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.rows">, NodeKind.Array, unknown>)> | undefined) & InsertableTypedNode_2, "Table.rows">, NodeKind.Array, true, TRowSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNodeFromImplicitAllowedTypes, [TRowSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.rows">, NodeKind.Array, unknown>)>; - readonly columns: (InsertableTypedNode_2, "Table.columns">, NodeKind.Array, true, TColumnSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNodeFromImplicitAllowedTypes, [TColumnSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.columns">, NodeKind.Array, unknown>)> | undefined) & InsertableTypedNode_2, "Table.columns">, NodeKind.Array, true, TColumnSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNodeFromImplicitAllowedTypes, [TColumnSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.columns">, NodeKind.Array, unknown>)>; + readonly rows: (InsertableTypedNode_2, "Table.rows">, NodeKind.Array, true, TRowSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNode | TreeLeafValue_2, [TRowSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.rows">, NodeKind.Array, unknown>)> | undefined) & InsertableTypedNode_2, "Table.rows">, NodeKind.Array, true, TRowSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNode | TreeLeafValue_2, [TRowSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.rows">, NodeKind.Array, unknown>)>; + readonly columns: (InsertableTypedNode_2, "Table.columns">, NodeKind.Array, true, TColumnSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNode | TreeLeafValue_2, [TColumnSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.columns">, NodeKind.Array, unknown>)> | undefined) & InsertableTypedNode_2, "Table.columns">, NodeKind.Array, true, TColumnSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNode | TreeLeafValue_2, [TColumnSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.columns">, NodeKind.Array, unknown>)>; })) => TreeNode & TableSchema.Table & WithType, "Table">, NodeKind, unknown>) & { empty, "Table.rows">, NodeKind.Array, true, TRowSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNodeFromImplicitAllowedTypes, [TRowSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.rows">, NodeKind.Array, unknown>)> | undefined) & InsertableTypedNode_2, "Table.rows">, NodeKind.Array, true, TRowSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNodeFromImplicitAllowedTypes, [TRowSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.rows">, NodeKind.Array, unknown>)>; - readonly columns: (InsertableTypedNode_2, "Table.columns">, NodeKind.Array, true, TColumnSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNodeFromImplicitAllowedTypes, [TColumnSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.columns">, NodeKind.Array, unknown>)> | undefined) & InsertableTypedNode_2, "Table.columns">, NodeKind.Array, true, TColumnSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNodeFromImplicitAllowedTypes, [TColumnSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.columns">, NodeKind.Array, unknown>)>; + readonly rows: (InsertableTypedNode_2, "Table.rows">, NodeKind.Array, true, TRowSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNode | TreeLeafValue_2, [TRowSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.rows">, NodeKind.Array, unknown>)> | undefined) & InsertableTypedNode_2, "Table.rows">, NodeKind.Array, true, TRowSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNode | TreeLeafValue_2, [TRowSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.rows">, NodeKind.Array, unknown>)>; + readonly columns: (InsertableTypedNode_2, "Table.columns">, NodeKind.Array, true, TColumnSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNode | TreeLeafValue_2, [TColumnSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.columns">, NodeKind.Array, unknown>)> | undefined) & InsertableTypedNode_2, "Table.columns">, NodeKind.Array, true, TColumnSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeNode | TreeLeafValue_2, [TColumnSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.columns">, NodeKind.Array, unknown>)>; }) => TreeNode & TableSchema.Table & WithType, "Table">, NodeKind, unknown>>(this: TThis): InstanceType; }; // @system @@ -1542,6 +1645,28 @@ export namespace System_TableSchema { export namespace System_Unsafe { // @system export type AllowedTypesUnsafe = readonly LazyItem[]; + // @system + export type AssignableTreeFieldFromImplicitFieldUnsafe = TSchema extends FieldSchemaUnsafe ? ApplyKindAssignment["readWrite"], Kind> : TSchema extends ImplicitAllowedTypesUnsafe ? GetTypesUnsafe["readWrite"] : never; + // @sealed + export interface CustomizerUnsafe { + custom>(): CustomizedSchemaTyping & { + [Property in keyof CustomTypes]: Property extends keyof T ? T[Property] extends CustomTypes[Property] ? T[Property] : GetTypesUnsafe[Property] : GetTypesUnsafe[Property]; + }>; + simplified>(): CustomizedSchemaTyping; + simplifiedUnrestricted(): CustomizedSchemaTyping; + } + // @system + export type DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; + // @system + export type DefaultTreeNodeFromImplicitAllowedTypesUnsafe = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; // @sealed @system export type FieldHasDefaultUnsafe = T extends FieldSchemaUnsafe ? true : false; // @sealed @system @@ -1551,6 +1676,10 @@ export namespace System_Unsafe { readonly kind: Kind; } // @system + export type GetTypesUnsafe = [TSchema] extends [ + CustomizedSchemaTyping + ] ? TCustom : StrictTypesUnsafe; + // @system export type ImplicitAllowedTypesUnsafe = TreeNodeSchemaUnsafe | readonly LazyItem>[]; // @system export type ImplicitFieldSchemaUnsafe = FieldSchemaUnsafe | ImplicitAllowedTypesUnsafe; @@ -1567,7 +1696,7 @@ export namespace System_Unsafe { readonly [Property in keyof TList]: TList[Property] extends LazyItem ? InsertableTypedNodeUnsafe : never; }[number]; // @system - export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; + export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe = GetTypesUnsafe["input"]; // @system export type InsertableTypedNodeUnsafe> = (T extends TreeNodeSchemaUnsafe ? NodeBuilderDataUnsafe : never) | (T extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : never); // @system @@ -1576,7 +1705,21 @@ export namespace System_Unsafe { export type NodeFromSchemaUnsafe> = T extends TreeNodeSchemaUnsafe ? TNode : never; // @system export type ObjectFromSchemaRecordUnsafe> = { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitFieldUnsafe : unknown; + -readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? never : Property]: Property extends string ? AssignableTreeFieldFromImplicitFieldUnsafe : unknown; + } & { + readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? Property : never]: Property extends string ? TreeFieldFromImplicitFieldUnsafe : unknown; }; // @sealed @system export interface ReadonlyMapInlined { @@ -1593,6 +1736,15 @@ export namespace System_Unsafe { readonly size: number; values(): IterableIterator>; } + // @system + export interface StrictTypesUnsafe, TOutput = DefaultTreeNodeFromImplicitAllowedTypesUnsafe> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TOutput; + } // @sealed @system export interface TreeArrayNodeUnsafe extends TreeArrayNode, InsertableTreeNodeFromImplicitAllowedTypesUnsafe> { } @@ -1604,7 +1756,7 @@ export namespace System_Unsafe { set(key: string, value: InsertableTreeNodeFromImplicitAllowedTypesUnsafe | undefined): void; } // @system - export type TreeNodeFromImplicitAllowedTypesUnsafe = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; + export type TreeNodeFromImplicitAllowedTypesUnsafe = GetTypesUnsafe["output"]; // @system export interface TreeNodeSchemaClassUnsafe, in TInsertable, out ImplicitlyConstructable extends boolean, out Info, out TCustomMetadata = unknown> extends TreeNodeSchemaCore { // @sealed @@ -1885,6 +2037,9 @@ export interface TreeEncodingOptions { // @public export type TreeFieldFromImplicitField = TSchema extends FieldSchema ? ApplyKind, Kind> : TSchema extends ImplicitAllowedTypes ? TreeNodeFromImplicitAllowedTypes : TreeNode | TreeLeafValue | undefined; +// @public @system +export type TreeFieldFromImplicitFieldDefault = TSchema extends FieldSchema ? ApplyKind, Kind> : TSchema extends ImplicitAllowedTypes ? DefaultTreeNodeFromImplicitAllowedTypes : TreeNode | TreeLeafValue | undefined; + // @alpha @sealed export interface TreeIdentifierUtils { (node: TreeNode): string | undefined; @@ -1940,7 +2095,7 @@ export interface TreeNodeApi { } // @public -export type TreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; +export type TreeNodeFromImplicitAllowedTypes = GetTypes["output"]; // @public @sealed export type TreeNodeSchema = (TNode extends TreeNode ? TreeNodeSchemaClass : never) | TreeNodeSchemaNonClass; @@ -1971,7 +2126,7 @@ export type TreeNodeSchemaNonClass, TypeName extends string = string> = TreeNode & ObjectFromSchemaRecord & WithType; +export type TreeObjectNode, TypeName extends string = string, Options extends ObjectSchemaTypingOptions = Record> = TreeNode & WithType & ObjectFromSchemaRecord; // @alpha @input export type TreeParsingOptions = TreeEncodingOptions; 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 154b01852356..1ab3864ed4a6 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 @@ -65,6 +65,11 @@ type ApplyKind = { [FieldKind.Identifier]: T; }[Kind]; +// @public @system +export type ApplyKindAssignment = [Kind] extends [ +FieldKind.Required +] ? T : [Kind] extends [FieldKind.Optional] ? T | undefined : never; + // @public @system type ApplyKindInput = [ Kind @@ -73,6 +78,12 @@ Kind // @beta export function asBeta(view: TreeView): TreeViewBeta; +// @public @system +export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; + +// @public @system +export type AssignableTreeFieldFromImplicitFieldDefault> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? StrictTypes["readWrite"] : never; + // @public export enum AttachState { Attached = "Attached", @@ -136,10 +147,34 @@ export interface ContainerSchema { // @beta export function createIndependentTreeBeta(options?: ForestOptions): ViewableTree; +// @public @system +export type CustomizedSchemaTyping = TSchema & { + [CustomizedTyping]: TCustom; +}; + +// @public @system +export const CustomizedTyping: unique symbol; + +// @public @system +export type CustomizedTyping = typeof CustomizedTyping; + +// @public @sealed +export interface CustomTypes { + readonly input: unknown; + readonly output: TreeLeafValue | TreeNode; + readonly readWrite: TreeLeafValue | TreeNode; +} + +// @public @system +export type DefaultInsertableTreeNodeFromImplicitAllowedTypes = [TSchema] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; + // @public @sealed @system interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> { } +// @public @system +export type DefaultTreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; + // @beta export function enumFromStrings(factory: SchemaFactory, members: Members): ((value: TValue) => TValue extends unknown ? TreeNode & { readonly value: TValue; @@ -270,6 +305,11 @@ export const ForestTypeOptimized: ForestType; // @beta export const ForestTypeReference: ForestType; +// @public @system +export type GetTypes = [TSchema] extends [ +CustomizedSchemaTyping +] ? TCustom : StrictTypes; + // @public export interface IConnection { readonly id: string; @@ -549,7 +589,7 @@ type InsertableObjectFromSchemaRecord; // @public -export type InsertableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; +export type InsertableTreeFieldFromImplicitField] ? TSchemaInput : SchemaUnionToIntersection> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; // @public @system export type InsertableTreeNodeFromAllowedTypes = IsUnion extends true ? never : { @@ -559,9 +599,7 @@ export type InsertableTreeNodeFromAllowedTypes = IsU }[NumberKeys]; // @public -export type InsertableTreeNodeFromImplicitAllowedTypes = [ -TSchema -] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; +export type InsertableTreeNodeFromImplicitAllowedTypes = GetTypes["input"]; // @public export type InsertableTypedNode> = (T extends TreeNodeSchema ? NodeBuilderData : never) | (T extends TreeNodeSchema ? Unhydrated ? never : NodeFromSchema> : never); @@ -739,21 +777,45 @@ export type NumberKeys = Transformed[`${number}` & keyof Transformed]; // @public @system -export type ObjectFromSchemaRecord> = RestrictiveStringRecord extends T ? {} : { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; +export type ObjectFromSchemaRecord, Options extends ObjectSchemaTypingOptions = Record> = RestrictiveStringRecord extends T ? {} : [Options["supportReadonlyFields"]] extends [true] ? { + -readonly [Property in keyof T as [ + AssignableTreeFieldFromImplicitField + ] extends [never | undefined] ? never : Property]: [Options["supportCustomizedFields"]] extends [true] ? AssignableTreeFieldFromImplicitField : AssignableTreeFieldFromImplicitFieldDefault; +} & { + readonly [Property in keyof T]: [Options["supportCustomizedFields"]] extends [true] ? TreeFieldFromImplicitField : TreeFieldFromImplicitFieldDefault; +} : { + -readonly [Property in keyof T]: [Options["supportCustomizedFields"]] extends [true] ? TreeFieldFromImplicitField : TreeFieldFromImplicitFieldDefault; +}; + +// @beta @system +export type ObjectFromSchemaRecordRelaxed> = RestrictiveStringRecord extends T ? {} : { + [Property in keyof T]: TreeFieldFromImplicitFieldDefault; }; // @beta @input -export interface ObjectSchemaOptions extends NodeSchemaOptions { +export interface ObjectSchemaOptions extends NodeSchemaOptions, ObjectSchemaTypingOptions { readonly allowUnknownOptionalFields?: boolean; } +// @public @input +export interface ObjectSchemaTypingOptions { + // (undocumented) + readonly supportCustomizedFields?: true | undefined; + // (undocumented) + readonly supportReadonlyFields?: true | undefined; +} + // @public export type Off = () => void; // @beta @system export type PopUnion void : never>> = AsOverloadedFunction extends (a: infer First) => void ? First : never; +// @beta @system +export type PreventExtraProperties = T & { + [x in keyof T]: x extends keyof Expected ? T[x] : undefined; +}; + // @public @sealed @system export interface ReadonlyArrayNode extends ReadonlyArray, Awaited> { } @@ -854,7 +916,7 @@ export const SchemaFactory_base: SchemaStatics & (new () => SchemaStatics); // @beta export class SchemaFactoryBeta extends SchemaFactory { - object, const TCustomMetadata = unknown>(name: Name, fields: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T, never, TCustomMetadata>; + object, const TOptions extends ObjectSchemaOptions = ObjectSchemaOptions>(name: Name, fields: T, options?: PreventExtraProperties): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode, TOptions>, object & InsertableObjectFromSchemaRecord, true, T, never, TOptions extends ObjectSchemaOptions ? TCustomMetadata : unknown>; // (undocumented) objectRecursive, const TCustomMetadata = unknown>(name: Name, t: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, System_Unsafe.TreeObjectNodeUnsafe>, object & System_Unsafe.InsertableObjectFromSchemaRecordUnsafe, false, T, never, TCustomMetadata>; record(allowedTypes: T): TreeNodeSchemaNonClass`>, NodeKind.Record, TreeRecordNode & WithType`>, NodeKind.Record>, RecordNodeInsertableData, true, T, undefined>; @@ -885,6 +947,11 @@ export interface SchemaStatics { readonly string: LeafSchema<"string", string>; } +// @public @system +export type SchemaUnionToIntersection = [T] extends [ +CustomizedSchemaTyping +] ? T : UnionToIntersection; + // @beta @sealed export class SchemaUpgrade { // (undocumented) @@ -916,10 +983,42 @@ export function singletonSchema, true, Record, undefined>; +// @public @sealed +export interface StrictTypes, TOutput extends TreeNode | TreeLeafValue = DefaultTreeNodeFromImplicitAllowedTypes> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TInput extends never ? never : TOutput; +} + // @public @system export namespace System_Unsafe { // @system export type AllowedTypesUnsafe = readonly LazyItem[]; + // @system + export type AssignableTreeFieldFromImplicitFieldUnsafe = TSchema extends FieldSchemaUnsafe ? ApplyKindAssignment["readWrite"], Kind> : TSchema extends ImplicitAllowedTypesUnsafe ? GetTypesUnsafe["readWrite"] : never; + // @sealed + export interface CustomizerUnsafe { + custom>(): CustomizedSchemaTyping & { + [Property in keyof CustomTypes]: Property extends keyof T ? T[Property] extends CustomTypes[Property] ? T[Property] : GetTypesUnsafe[Property] : GetTypesUnsafe[Property]; + }>; + simplified>(): CustomizedSchemaTyping; + simplifiedUnrestricted(): CustomizedSchemaTyping; + } + // @system + export type DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; + // @system + export type DefaultTreeNodeFromImplicitAllowedTypesUnsafe = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; // @sealed @system export type FieldHasDefaultUnsafe = T extends FieldSchemaUnsafe ? true : false; // @sealed @system @@ -929,6 +1028,10 @@ export namespace System_Unsafe { readonly kind: Kind; } // @system + export type GetTypesUnsafe = [TSchema] extends [ + CustomizedSchemaTyping + ] ? TCustom : StrictTypesUnsafe; + // @system export type ImplicitAllowedTypesUnsafe = TreeNodeSchemaUnsafe | readonly LazyItem>[]; // @system export type ImplicitFieldSchemaUnsafe = FieldSchemaUnsafe | ImplicitAllowedTypesUnsafe; @@ -945,7 +1048,7 @@ export namespace System_Unsafe { readonly [Property in keyof TList]: TList[Property] extends LazyItem ? InsertableTypedNodeUnsafe : never; }[number]; // @system - export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; + export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe = GetTypesUnsafe["input"]; // @system export type InsertableTypedNodeUnsafe> = (T extends TreeNodeSchemaUnsafe ? NodeBuilderDataUnsafe : never) | (T extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : never); // @system @@ -954,7 +1057,21 @@ export namespace System_Unsafe { export type NodeFromSchemaUnsafe> = T extends TreeNodeSchemaUnsafe ? TNode : never; // @system export type ObjectFromSchemaRecordUnsafe> = { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitFieldUnsafe : unknown; + -readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? never : Property]: Property extends string ? AssignableTreeFieldFromImplicitFieldUnsafe : unknown; + } & { + readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? Property : never]: Property extends string ? TreeFieldFromImplicitFieldUnsafe : unknown; }; // @sealed @system export interface ReadonlyMapInlined { @@ -971,6 +1088,15 @@ export namespace System_Unsafe { readonly size: number; values(): IterableIterator>; } + // @system + export interface StrictTypesUnsafe, TOutput = DefaultTreeNodeFromImplicitAllowedTypesUnsafe> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TOutput; + } // @sealed @system export interface TreeArrayNodeUnsafe extends TreeArrayNode, InsertableTreeNodeFromImplicitAllowedTypesUnsafe> { } @@ -982,7 +1108,7 @@ export namespace System_Unsafe { set(key: string, value: InsertableTreeNodeFromImplicitAllowedTypesUnsafe | undefined): void; } // @system - export type TreeNodeFromImplicitAllowedTypesUnsafe = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; + export type TreeNodeFromImplicitAllowedTypesUnsafe = GetTypesUnsafe["output"]; // @system export interface TreeNodeSchemaClassUnsafe, in TInsertable, out ImplicitlyConstructable extends boolean, out Info, out TCustomMetadata = unknown> extends TreeNodeSchemaCore { // @sealed @@ -1092,6 +1218,9 @@ export interface TreeEncodingOptions { // @public export type TreeFieldFromImplicitField = TSchema extends FieldSchema ? ApplyKind, Kind> : TSchema extends ImplicitAllowedTypes ? TreeNodeFromImplicitAllowedTypes : TreeNode | TreeLeafValue | undefined; +// @public @system +export type TreeFieldFromImplicitFieldDefault = TSchema extends FieldSchema ? ApplyKind, Kind> : TSchema extends ImplicitAllowedTypes ? DefaultTreeNodeFromImplicitAllowedTypes : TreeNode | TreeLeafValue | undefined; + // @public export type TreeLeafValue = number | string | boolean | IFluidHandle | null; @@ -1127,7 +1256,7 @@ export interface TreeNodeApi { } // @public -export type TreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; +export type TreeNodeFromImplicitAllowedTypes = GetTypes["output"]; // @public @sealed export type TreeNodeSchema = (TNode extends TreeNode ? TreeNodeSchemaClass : never) | TreeNodeSchemaNonClass; @@ -1158,7 +1287,7 @@ export type TreeNodeSchemaNonClass, TypeName extends string = string> = TreeNode & ObjectFromSchemaRecord & WithType; +export type TreeObjectNode, TypeName extends string = string, Options extends ObjectSchemaTypingOptions = Record> = TreeNode & WithType & ObjectFromSchemaRecord; // @beta export interface TreeRecordNode extends TreeNode, Record> { diff --git a/packages/framework/fluid-framework/api-report/fluid-framework.legacy.beta.api.md b/packages/framework/fluid-framework/api-report/fluid-framework.legacy.beta.api.md index 228a1ce80870..4b8b4413acc3 100644 --- a/packages/framework/fluid-framework/api-report/fluid-framework.legacy.beta.api.md +++ b/packages/framework/fluid-framework/api-report/fluid-framework.legacy.beta.api.md @@ -65,6 +65,11 @@ type ApplyKind = { [FieldKind.Identifier]: T; }[Kind]; +// @public @system +export type ApplyKindAssignment = [Kind] extends [ +FieldKind.Required +] ? T : [Kind] extends [FieldKind.Optional] ? T | undefined : never; + // @public @system type ApplyKindInput = [ Kind @@ -73,6 +78,12 @@ Kind // @beta export function asBeta(view: TreeView): TreeViewBeta; +// @public @system +export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; + +// @public @system +export type AssignableTreeFieldFromImplicitFieldDefault> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? StrictTypes["readWrite"] : never; + // @public export enum AttachState { Attached = "Attached", @@ -136,10 +147,34 @@ export interface ContainerSchema { // @beta export function createIndependentTreeBeta(options?: ForestOptions): ViewableTree; +// @public @system +export type CustomizedSchemaTyping = TSchema & { + [CustomizedTyping]: TCustom; +}; + +// @public @system +export const CustomizedTyping: unique symbol; + +// @public @system +export type CustomizedTyping = typeof CustomizedTyping; + +// @public @sealed +export interface CustomTypes { + readonly input: unknown; + readonly output: TreeLeafValue | TreeNode; + readonly readWrite: TreeLeafValue | TreeNode; +} + +// @public @system +export type DefaultInsertableTreeNodeFromImplicitAllowedTypes = [TSchema] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; + // @public @sealed @system interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> { } +// @public @system +export type DefaultTreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; + // @beta @legacy (undocumented) export type DeserializeCallback = (properties: PropertySet) => void; @@ -273,6 +308,11 @@ export const ForestTypeOptimized: ForestType; // @beta export const ForestTypeReference: ForestType; +// @public @system +export type GetTypes = [TSchema] extends [ +CustomizedSchemaTyping +] ? TCustom : StrictTypes; + // @beta @legacy export interface IBranchOrigin { id: string; @@ -596,7 +636,7 @@ type InsertableObjectFromSchemaRecord; // @public -export type InsertableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; +export type InsertableTreeFieldFromImplicitField] ? TSchemaInput : SchemaUnionToIntersection> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; // @public @system export type InsertableTreeNodeFromAllowedTypes = IsUnion extends true ? never : { @@ -606,9 +646,7 @@ export type InsertableTreeNodeFromAllowedTypes = IsU }[NumberKeys]; // @public -export type InsertableTreeNodeFromImplicitAllowedTypes = [ -TSchema -] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; +export type InsertableTreeNodeFromImplicitAllowedTypes = GetTypes["input"]; // @public export type InsertableTypedNode> = (T extends TreeNodeSchema ? NodeBuilderData : never) | (T extends TreeNodeSchema ? Unhydrated ? never : NodeFromSchema> : never); @@ -1017,21 +1055,45 @@ export type NumberKeys = Transformed[`${number}` & keyof Transformed]; // @public @system -export type ObjectFromSchemaRecord> = RestrictiveStringRecord extends T ? {} : { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; +export type ObjectFromSchemaRecord, Options extends ObjectSchemaTypingOptions = Record> = RestrictiveStringRecord extends T ? {} : [Options["supportReadonlyFields"]] extends [true] ? { + -readonly [Property in keyof T as [ + AssignableTreeFieldFromImplicitField + ] extends [never | undefined] ? never : Property]: [Options["supportCustomizedFields"]] extends [true] ? AssignableTreeFieldFromImplicitField : AssignableTreeFieldFromImplicitFieldDefault; +} & { + readonly [Property in keyof T]: [Options["supportCustomizedFields"]] extends [true] ? TreeFieldFromImplicitField : TreeFieldFromImplicitFieldDefault; +} : { + -readonly [Property in keyof T]: [Options["supportCustomizedFields"]] extends [true] ? TreeFieldFromImplicitField : TreeFieldFromImplicitFieldDefault; +}; + +// @beta @system +export type ObjectFromSchemaRecordRelaxed> = RestrictiveStringRecord extends T ? {} : { + [Property in keyof T]: TreeFieldFromImplicitFieldDefault; }; // @beta @input -export interface ObjectSchemaOptions extends NodeSchemaOptions { +export interface ObjectSchemaOptions extends NodeSchemaOptions, ObjectSchemaTypingOptions { readonly allowUnknownOptionalFields?: boolean; } +// @public @input +export interface ObjectSchemaTypingOptions { + // (undocumented) + readonly supportCustomizedFields?: true | undefined; + // (undocumented) + readonly supportReadonlyFields?: true | undefined; +} + // @public export type Off = () => void; // @beta @system export type PopUnion void : never>> = AsOverloadedFunction extends (a: infer First) => void ? First : never; +// @beta @system +export type PreventExtraProperties = T & { + [x in keyof T]: x extends keyof Expected ? T[x] : undefined; +}; + // @public @sealed @system export interface ReadonlyArrayNode extends ReadonlyArray, Awaited> { } @@ -1132,7 +1194,7 @@ export const SchemaFactory_base: SchemaStatics & (new () => SchemaStatics); // @beta export class SchemaFactoryBeta extends SchemaFactory { - object, const TCustomMetadata = unknown>(name: Name, fields: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T, never, TCustomMetadata>; + object, const TOptions extends ObjectSchemaOptions = ObjectSchemaOptions>(name: Name, fields: T, options?: PreventExtraProperties): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode, TOptions>, object & InsertableObjectFromSchemaRecord, true, T, never, TOptions extends ObjectSchemaOptions ? TCustomMetadata : unknown>; // (undocumented) objectRecursive, const TCustomMetadata = unknown>(name: Name, t: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, System_Unsafe.TreeObjectNodeUnsafe>, object & System_Unsafe.InsertableObjectFromSchemaRecordUnsafe, false, T, never, TCustomMetadata>; record(allowedTypes: T): TreeNodeSchemaNonClass`>, NodeKind.Record, TreeRecordNode & WithType`>, NodeKind.Record>, RecordNodeInsertableData, true, T, undefined>; @@ -1163,6 +1225,11 @@ export interface SchemaStatics { readonly string: LeafSchema<"string", string>; } +// @public @system +export type SchemaUnionToIntersection = [T] extends [ +CustomizedSchemaTyping +] ? T : UnionToIntersection; + // @beta @sealed export class SchemaUpgrade { // (undocumented) @@ -1274,10 +1341,42 @@ export function singletonSchema, true, Record, undefined>; +// @public @sealed +export interface StrictTypes, TOutput extends TreeNode | TreeLeafValue = DefaultTreeNodeFromImplicitAllowedTypes> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TInput extends never ? never : TOutput; +} + // @public @system export namespace System_Unsafe { // @system export type AllowedTypesUnsafe = readonly LazyItem[]; + // @system + export type AssignableTreeFieldFromImplicitFieldUnsafe = TSchema extends FieldSchemaUnsafe ? ApplyKindAssignment["readWrite"], Kind> : TSchema extends ImplicitAllowedTypesUnsafe ? GetTypesUnsafe["readWrite"] : never; + // @sealed + export interface CustomizerUnsafe { + custom>(): CustomizedSchemaTyping & { + [Property in keyof CustomTypes]: Property extends keyof T ? T[Property] extends CustomTypes[Property] ? T[Property] : GetTypesUnsafe[Property] : GetTypesUnsafe[Property]; + }>; + simplified>(): CustomizedSchemaTyping; + simplifiedUnrestricted(): CustomizedSchemaTyping; + } + // @system + export type DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; + // @system + export type DefaultTreeNodeFromImplicitAllowedTypesUnsafe = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; // @sealed @system export type FieldHasDefaultUnsafe = T extends FieldSchemaUnsafe ? true : false; // @sealed @system @@ -1287,6 +1386,10 @@ export namespace System_Unsafe { readonly kind: Kind; } // @system + export type GetTypesUnsafe = [TSchema] extends [ + CustomizedSchemaTyping + ] ? TCustom : StrictTypesUnsafe; + // @system export type ImplicitAllowedTypesUnsafe = TreeNodeSchemaUnsafe | readonly LazyItem>[]; // @system export type ImplicitFieldSchemaUnsafe = FieldSchemaUnsafe | ImplicitAllowedTypesUnsafe; @@ -1303,7 +1406,7 @@ export namespace System_Unsafe { readonly [Property in keyof TList]: TList[Property] extends LazyItem ? InsertableTypedNodeUnsafe : never; }[number]; // @system - export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; + export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe = GetTypesUnsafe["input"]; // @system export type InsertableTypedNodeUnsafe> = (T extends TreeNodeSchemaUnsafe ? NodeBuilderDataUnsafe : never) | (T extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : never); // @system @@ -1312,7 +1415,21 @@ export namespace System_Unsafe { export type NodeFromSchemaUnsafe> = T extends TreeNodeSchemaUnsafe ? TNode : never; // @system export type ObjectFromSchemaRecordUnsafe> = { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitFieldUnsafe : unknown; + -readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? never : Property]: Property extends string ? AssignableTreeFieldFromImplicitFieldUnsafe : unknown; + } & { + readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? Property : never]: Property extends string ? TreeFieldFromImplicitFieldUnsafe : unknown; }; // @sealed @system export interface ReadonlyMapInlined { @@ -1329,6 +1446,15 @@ export namespace System_Unsafe { readonly size: number; values(): IterableIterator>; } + // @system + export interface StrictTypesUnsafe, TOutput = DefaultTreeNodeFromImplicitAllowedTypesUnsafe> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TOutput; + } // @sealed @system export interface TreeArrayNodeUnsafe extends TreeArrayNode, InsertableTreeNodeFromImplicitAllowedTypesUnsafe> { } @@ -1340,7 +1466,7 @@ export namespace System_Unsafe { set(key: string, value: InsertableTreeNodeFromImplicitAllowedTypesUnsafe | undefined): void; } // @system - export type TreeNodeFromImplicitAllowedTypesUnsafe = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; + export type TreeNodeFromImplicitAllowedTypesUnsafe = GetTypesUnsafe["output"]; // @system export interface TreeNodeSchemaClassUnsafe, in TInsertable, out ImplicitlyConstructable extends boolean, out Info, out TCustomMetadata = unknown> extends TreeNodeSchemaCore { // @sealed @@ -1450,6 +1576,9 @@ export interface TreeEncodingOptions { // @public export type TreeFieldFromImplicitField = TSchema extends FieldSchema ? ApplyKind, Kind> : TSchema extends ImplicitAllowedTypes ? TreeNodeFromImplicitAllowedTypes : TreeNode | TreeLeafValue | undefined; +// @public @system +export type TreeFieldFromImplicitFieldDefault = TSchema extends FieldSchema ? ApplyKind, Kind> : TSchema extends ImplicitAllowedTypes ? DefaultTreeNodeFromImplicitAllowedTypes : TreeNode | TreeLeafValue | undefined; + // @public export type TreeLeafValue = number | string | boolean | IFluidHandle | null; @@ -1485,7 +1614,7 @@ export interface TreeNodeApi { } // @public -export type TreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; +export type TreeNodeFromImplicitAllowedTypes = GetTypes["output"]; // @public @sealed export type TreeNodeSchema = (TNode extends TreeNode ? TreeNodeSchemaClass : never) | TreeNodeSchemaNonClass; @@ -1516,7 +1645,7 @@ export type TreeNodeSchemaNonClass, TypeName extends string = string> = TreeNode & ObjectFromSchemaRecord & WithType; +export type TreeObjectNode, TypeName extends string = string, Options extends ObjectSchemaTypingOptions = Record> = TreeNode & WithType & ObjectFromSchemaRecord; // @beta export interface TreeRecordNode extends TreeNode, Record> { 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 0b5f3074b09b..aa90b6b9416a 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 @@ -14,11 +14,22 @@ type ApplyKind = { [FieldKind.Identifier]: T; }[Kind]; +// @public @system +export type ApplyKindAssignment = [Kind] extends [ +FieldKind.Required +] ? T : [Kind] extends [FieldKind.Optional] ? T | undefined : never; + // @public @system type ApplyKindInput = [ Kind ] extends [FieldKind.Required] ? T : [Kind] extends [FieldKind.Optional] ? T | undefined : [Kind] extends [FieldKind.Identifier] ? DefaultsAreOptional extends true ? T | undefined : T : never; +// @public @system +export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; + +// @public @system +export type AssignableTreeFieldFromImplicitFieldDefault> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? StrictTypes["readWrite"] : never; + // @public export enum AttachState { Attached = "Attached", @@ -71,10 +82,34 @@ export interface ContainerSchema { readonly initialObjects: Record; } +// @public @system +export type CustomizedSchemaTyping = TSchema & { + [CustomizedTyping]: TCustom; +}; + +// @public @system +export const CustomizedTyping: unique symbol; + +// @public @system +export type CustomizedTyping = typeof CustomizedTyping; + +// @public @sealed +export interface CustomTypes { + readonly input: unknown; + readonly output: TreeLeafValue | TreeNode; + readonly readWrite: TreeLeafValue | TreeNode; +} + +// @public @system +export type DefaultInsertableTreeNodeFromImplicitAllowedTypes = [TSchema] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; + // @public @sealed @system interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> { } +// @public @system +export type DefaultTreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; + // @public @sealed export abstract class ErasedType { static [Symbol.hasInstance](value: never): value is never; @@ -143,6 +178,11 @@ export type FluidObject = { // @public export type FluidObjectProviderKeys = string extends TProp ? never : number extends TProp ? never : TProp extends keyof Required[TProp] ? Required[TProp] extends Required[TProp]>[TProp] ? TProp : never : never; +// @public @system +export type GetTypes = [TSchema] extends [ +CustomizedSchemaTyping +] ? TCustom : StrictTypes; + // @public export interface IConnection { readonly id: string; @@ -450,7 +490,7 @@ type InsertableObjectFromSchemaRecord; // @public -export type InsertableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; +export type InsertableTreeFieldFromImplicitField] ? TSchemaInput : SchemaUnionToIntersection> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; // @public @system export type InsertableTreeNodeFromAllowedTypes = IsUnion extends true ? never : { @@ -460,9 +500,7 @@ export type InsertableTreeNodeFromAllowedTypes = IsU }[NumberKeys]; // @public -export type InsertableTreeNodeFromImplicitAllowedTypes = [ -TSchema -] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; +export type InsertableTreeNodeFromImplicitAllowedTypes = GetTypes["input"]; // @public export type InsertableTypedNode> = (T extends TreeNodeSchema ? NodeBuilderData : never) | (T extends TreeNodeSchema ? Unhydrated ? never : NodeFromSchema> : never); @@ -626,10 +664,24 @@ export type NumberKeys = Transformed[`${number}` & keyof Transformed]; // @public @system -export type ObjectFromSchemaRecord> = RestrictiveStringRecord extends T ? {} : { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; +export type ObjectFromSchemaRecord, Options extends ObjectSchemaTypingOptions = Record> = RestrictiveStringRecord extends T ? {} : [Options["supportReadonlyFields"]] extends [true] ? { + -readonly [Property in keyof T as [ + AssignableTreeFieldFromImplicitField + ] extends [never | undefined] ? never : Property]: [Options["supportCustomizedFields"]] extends [true] ? AssignableTreeFieldFromImplicitField : AssignableTreeFieldFromImplicitFieldDefault; +} & { + readonly [Property in keyof T]: [Options["supportCustomizedFields"]] extends [true] ? TreeFieldFromImplicitField : TreeFieldFromImplicitFieldDefault; +} : { + -readonly [Property in keyof T]: [Options["supportCustomizedFields"]] extends [true] ? TreeFieldFromImplicitField : TreeFieldFromImplicitFieldDefault; }; +// @public @input +export interface ObjectSchemaTypingOptions { + // (undocumented) + readonly supportCustomizedFields?: true | undefined; + // (undocumented) + readonly supportReadonlyFields?: true | undefined; +} + // @public export type Off = () => void; @@ -748,6 +800,11 @@ export interface SchemaStatics { readonly string: LeafSchema<"string", string>; } +// @public @system +export type SchemaUnionToIntersection = [T] extends [ +CustomizedSchemaTyping +] ? T : UnionToIntersection; + // @public @system type ScopedSchemaName = TScope extends undefined ? `${TName}` : `${TScope}.${TName}`; @@ -765,10 +822,42 @@ export interface SimpleNodeSchemaBase; } +// @public @sealed +export interface StrictTypes, TOutput extends TreeNode | TreeLeafValue = DefaultTreeNodeFromImplicitAllowedTypes> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TInput extends never ? never : TOutput; +} + // @public @system export namespace System_Unsafe { // @system export type AllowedTypesUnsafe = readonly LazyItem[]; + // @system + export type AssignableTreeFieldFromImplicitFieldUnsafe = TSchema extends FieldSchemaUnsafe ? ApplyKindAssignment["readWrite"], Kind> : TSchema extends ImplicitAllowedTypesUnsafe ? GetTypesUnsafe["readWrite"] : never; + // @sealed + export interface CustomizerUnsafe { + custom>(): CustomizedSchemaTyping & { + [Property in keyof CustomTypes]: Property extends keyof T ? T[Property] extends CustomTypes[Property] ? T[Property] : GetTypesUnsafe[Property] : GetTypesUnsafe[Property]; + }>; + simplified>(): CustomizedSchemaTyping; + simplifiedUnrestricted(): CustomizedSchemaTyping; + } + // @system + export type DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; + // @system + export type DefaultTreeNodeFromImplicitAllowedTypesUnsafe = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; // @sealed @system export type FieldHasDefaultUnsafe = T extends FieldSchemaUnsafe ? true : false; // @sealed @system @@ -778,6 +867,10 @@ export namespace System_Unsafe { readonly kind: Kind; } // @system + export type GetTypesUnsafe = [TSchema] extends [ + CustomizedSchemaTyping + ] ? TCustom : StrictTypesUnsafe; + // @system export type ImplicitAllowedTypesUnsafe = TreeNodeSchemaUnsafe | readonly LazyItem>[]; // @system export type ImplicitFieldSchemaUnsafe = FieldSchemaUnsafe | ImplicitAllowedTypesUnsafe; @@ -794,7 +887,7 @@ export namespace System_Unsafe { readonly [Property in keyof TList]: TList[Property] extends LazyItem ? InsertableTypedNodeUnsafe : never; }[number]; // @system - export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; + export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe = GetTypesUnsafe["input"]; // @system export type InsertableTypedNodeUnsafe> = (T extends TreeNodeSchemaUnsafe ? NodeBuilderDataUnsafe : never) | (T extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : never); // @system @@ -803,7 +896,21 @@ export namespace System_Unsafe { export type NodeFromSchemaUnsafe> = T extends TreeNodeSchemaUnsafe ? TNode : never; // @system export type ObjectFromSchemaRecordUnsafe> = { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitFieldUnsafe : unknown; + -readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? never : Property]: Property extends string ? AssignableTreeFieldFromImplicitFieldUnsafe : unknown; + } & { + readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? Property : never]: Property extends string ? TreeFieldFromImplicitFieldUnsafe : unknown; }; // @sealed @system export interface ReadonlyMapInlined { @@ -820,6 +927,15 @@ export namespace System_Unsafe { readonly size: number; values(): IterableIterator>; } + // @system + export interface StrictTypesUnsafe, TOutput = DefaultTreeNodeFromImplicitAllowedTypesUnsafe> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TOutput; + } // @sealed @system export interface TreeArrayNodeUnsafe extends TreeArrayNode, InsertableTreeNodeFromImplicitAllowedTypesUnsafe> { } @@ -831,7 +947,7 @@ export namespace System_Unsafe { set(key: string, value: InsertableTreeNodeFromImplicitAllowedTypesUnsafe | undefined): void; } // @system - export type TreeNodeFromImplicitAllowedTypesUnsafe = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; + export type TreeNodeFromImplicitAllowedTypesUnsafe = GetTypesUnsafe["output"]; // @system export interface TreeNodeSchemaClassUnsafe, in TInsertable, out ImplicitlyConstructable extends boolean, out Info, out TCustomMetadata = unknown> extends TreeNodeSchemaCore { // @sealed @@ -910,6 +1026,9 @@ export interface TreeChangeEvents { // @public export type TreeFieldFromImplicitField = TSchema extends FieldSchema ? ApplyKind, Kind> : TSchema extends ImplicitAllowedTypes ? TreeNodeFromImplicitAllowedTypes : TreeNode | TreeLeafValue | undefined; +// @public @system +export type TreeFieldFromImplicitFieldDefault = TSchema extends FieldSchema ? ApplyKind, Kind> : TSchema extends ImplicitAllowedTypes ? DefaultTreeNodeFromImplicitAllowedTypes : TreeNode | TreeLeafValue | undefined; + // @public export type TreeLeafValue = number | string | boolean | IFluidHandle | null; @@ -945,7 +1064,7 @@ export interface TreeNodeApi { } // @public -export type TreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; +export type TreeNodeFromImplicitAllowedTypes = GetTypes["output"]; // @public @sealed export type TreeNodeSchema = (TNode extends TreeNode ? TreeNodeSchemaClass : never) | TreeNodeSchemaNonClass; @@ -976,7 +1095,7 @@ export type TreeNodeSchemaNonClass, TypeName extends string = string> = TreeNode & ObjectFromSchemaRecord & WithType; +export type TreeObjectNode, TypeName extends string = string, Options extends ObjectSchemaTypingOptions = Record> = TreeNode & WithType & ObjectFromSchemaRecord; // @public export enum TreeStatus { 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 1f10bcbd7a25..afbe8d7192c5 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 @@ -14,11 +14,22 @@ type ApplyKind = { [FieldKind.Identifier]: T; }[Kind]; +// @public @system +export type ApplyKindAssignment = [Kind] extends [ +FieldKind.Required +] ? T : [Kind] extends [FieldKind.Optional] ? T | undefined : never; + // @public @system type ApplyKindInput = [ Kind ] extends [FieldKind.Required] ? T : [Kind] extends [FieldKind.Optional] ? T | undefined : [Kind] extends [FieldKind.Identifier] ? DefaultsAreOptional extends true ? T | undefined : T : never; +// @public @system +export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; + +// @public @system +export type AssignableTreeFieldFromImplicitFieldDefault> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? StrictTypes["readWrite"] : never; + // @public export enum AttachState { Attached = "Attached", @@ -71,10 +82,34 @@ export interface ContainerSchema { readonly initialObjects: Record; } +// @public @system +export type CustomizedSchemaTyping = TSchema & { + [CustomizedTyping]: TCustom; +}; + +// @public @system +export const CustomizedTyping: unique symbol; + +// @public @system +export type CustomizedTyping = typeof CustomizedTyping; + +// @public @sealed +export interface CustomTypes { + readonly input: unknown; + readonly output: TreeLeafValue | TreeNode; + readonly readWrite: TreeLeafValue | TreeNode; +} + +// @public @system +export type DefaultInsertableTreeNodeFromImplicitAllowedTypes = [TSchema] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; + // @public @sealed @system interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> { } +// @public @system +export type DefaultTreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; + // @public @sealed export abstract class ErasedType { static [Symbol.hasInstance](value: never): value is never; @@ -143,6 +178,11 @@ export type FluidObject = { // @public export type FluidObjectProviderKeys = string extends TProp ? never : number extends TProp ? never : TProp extends keyof Required[TProp] ? Required[TProp] extends Required[TProp]>[TProp] ? TProp : never : never; +// @public @system +export type GetTypes = [TSchema] extends [ +CustomizedSchemaTyping +] ? TCustom : StrictTypes; + // @public export interface IConnection { readonly id: string; @@ -422,7 +462,7 @@ type InsertableObjectFromSchemaRecord; // @public -export type InsertableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; +export type InsertableTreeFieldFromImplicitField] ? TSchemaInput : SchemaUnionToIntersection> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; // @public @system export type InsertableTreeNodeFromAllowedTypes = IsUnion extends true ? never : { @@ -432,9 +472,7 @@ export type InsertableTreeNodeFromAllowedTypes = IsU }[NumberKeys]; // @public -export type InsertableTreeNodeFromImplicitAllowedTypes = [ -TSchema -] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; +export type InsertableTreeNodeFromImplicitAllowedTypes = GetTypes["input"]; // @public export type InsertableTypedNode> = (T extends TreeNodeSchema ? NodeBuilderData : never) | (T extends TreeNodeSchema ? Unhydrated ? never : NodeFromSchema> : never); @@ -592,10 +630,24 @@ export type NumberKeys = Transformed[`${number}` & keyof Transformed]; // @public @system -export type ObjectFromSchemaRecord> = RestrictiveStringRecord extends T ? {} : { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; +export type ObjectFromSchemaRecord, Options extends ObjectSchemaTypingOptions = Record> = RestrictiveStringRecord extends T ? {} : [Options["supportReadonlyFields"]] extends [true] ? { + -readonly [Property in keyof T as [ + AssignableTreeFieldFromImplicitField + ] extends [never | undefined] ? never : Property]: [Options["supportCustomizedFields"]] extends [true] ? AssignableTreeFieldFromImplicitField : AssignableTreeFieldFromImplicitFieldDefault; +} & { + readonly [Property in keyof T]: [Options["supportCustomizedFields"]] extends [true] ? TreeFieldFromImplicitField : TreeFieldFromImplicitFieldDefault; +} : { + -readonly [Property in keyof T]: [Options["supportCustomizedFields"]] extends [true] ? TreeFieldFromImplicitField : TreeFieldFromImplicitFieldDefault; }; +// @public @input +export interface ObjectSchemaTypingOptions { + // (undocumented) + readonly supportCustomizedFields?: true | undefined; + // (undocumented) + readonly supportReadonlyFields?: true | undefined; +} + // @public export type Off = () => void; @@ -714,6 +766,11 @@ export interface SchemaStatics { readonly string: LeafSchema<"string", string>; } +// @public @system +export type SchemaUnionToIntersection = [T] extends [ +CustomizedSchemaTyping +] ? T : UnionToIntersection; + // @public @system type ScopedSchemaName = TScope extends undefined ? `${TName}` : `${TScope}.${TName}`; @@ -731,10 +788,42 @@ export interface SimpleNodeSchemaBase; } +// @public @sealed +export interface StrictTypes, TOutput extends TreeNode | TreeLeafValue = DefaultTreeNodeFromImplicitAllowedTypes> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TInput extends never ? never : TOutput; +} + // @public @system export namespace System_Unsafe { // @system export type AllowedTypesUnsafe = readonly LazyItem[]; + // @system + export type AssignableTreeFieldFromImplicitFieldUnsafe = TSchema extends FieldSchemaUnsafe ? ApplyKindAssignment["readWrite"], Kind> : TSchema extends ImplicitAllowedTypesUnsafe ? GetTypesUnsafe["readWrite"] : never; + // @sealed + export interface CustomizerUnsafe { + custom>(): CustomizedSchemaTyping & { + [Property in keyof CustomTypes]: Property extends keyof T ? T[Property] extends CustomTypes[Property] ? T[Property] : GetTypesUnsafe[Property] : GetTypesUnsafe[Property]; + }>; + simplified>(): CustomizedSchemaTyping; + simplifiedUnrestricted(): CustomizedSchemaTyping; + } + // @system + export type DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; + // @system + export type DefaultTreeNodeFromImplicitAllowedTypesUnsafe = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; // @sealed @system export type FieldHasDefaultUnsafe = T extends FieldSchemaUnsafe ? true : false; // @sealed @system @@ -744,6 +833,10 @@ export namespace System_Unsafe { readonly kind: Kind; } // @system + export type GetTypesUnsafe = [TSchema] extends [ + CustomizedSchemaTyping + ] ? TCustom : StrictTypesUnsafe; + // @system export type ImplicitAllowedTypesUnsafe = TreeNodeSchemaUnsafe | readonly LazyItem>[]; // @system export type ImplicitFieldSchemaUnsafe = FieldSchemaUnsafe | ImplicitAllowedTypesUnsafe; @@ -760,7 +853,7 @@ export namespace System_Unsafe { readonly [Property in keyof TList]: TList[Property] extends LazyItem ? InsertableTypedNodeUnsafe : never; }[number]; // @system - export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; + export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe = GetTypesUnsafe["input"]; // @system export type InsertableTypedNodeUnsafe> = (T extends TreeNodeSchemaUnsafe ? NodeBuilderDataUnsafe : never) | (T extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : never); // @system @@ -769,7 +862,21 @@ export namespace System_Unsafe { export type NodeFromSchemaUnsafe> = T extends TreeNodeSchemaUnsafe ? TNode : never; // @system export type ObjectFromSchemaRecordUnsafe> = { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitFieldUnsafe : unknown; + -readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? never : Property]: Property extends string ? AssignableTreeFieldFromImplicitFieldUnsafe : unknown; + } & { + readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? Property : never]: Property extends string ? TreeFieldFromImplicitFieldUnsafe : unknown; }; // @sealed @system export interface ReadonlyMapInlined { @@ -786,6 +893,15 @@ export namespace System_Unsafe { readonly size: number; values(): IterableIterator>; } + // @system + export interface StrictTypesUnsafe, TOutput = DefaultTreeNodeFromImplicitAllowedTypesUnsafe> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TOutput; + } // @sealed @system export interface TreeArrayNodeUnsafe extends TreeArrayNode, InsertableTreeNodeFromImplicitAllowedTypesUnsafe> { } @@ -797,7 +913,7 @@ export namespace System_Unsafe { set(key: string, value: InsertableTreeNodeFromImplicitAllowedTypesUnsafe | undefined): void; } // @system - export type TreeNodeFromImplicitAllowedTypesUnsafe = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; + export type TreeNodeFromImplicitAllowedTypesUnsafe = GetTypesUnsafe["output"]; // @system export interface TreeNodeSchemaClassUnsafe, in TInsertable, out ImplicitlyConstructable extends boolean, out Info, out TCustomMetadata = unknown> extends TreeNodeSchemaCore { // @sealed @@ -876,6 +992,9 @@ export interface TreeChangeEvents { // @public export type TreeFieldFromImplicitField = TSchema extends FieldSchema ? ApplyKind, Kind> : TSchema extends ImplicitAllowedTypes ? TreeNodeFromImplicitAllowedTypes : TreeNode | TreeLeafValue | undefined; +// @public @system +export type TreeFieldFromImplicitFieldDefault = TSchema extends FieldSchema ? ApplyKind, Kind> : TSchema extends ImplicitAllowedTypes ? DefaultTreeNodeFromImplicitAllowedTypes : TreeNode | TreeLeafValue | undefined; + // @public export type TreeLeafValue = number | string | boolean | IFluidHandle | null; @@ -911,7 +1030,7 @@ export interface TreeNodeApi { } // @public -export type TreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; +export type TreeNodeFromImplicitAllowedTypes = GetTypes["output"]; // @public @sealed export type TreeNodeSchema = (TNode extends TreeNode ? TreeNodeSchemaClass : never) | TreeNodeSchemaNonClass; @@ -942,7 +1061,7 @@ export type TreeNodeSchemaNonClass, TypeName extends string = string> = TreeNode & ObjectFromSchemaRecord & WithType; +export type TreeObjectNode, TypeName extends string = string, Options extends ObjectSchemaTypingOptions = Record> = TreeNode & WithType & ObjectFromSchemaRecord; // @public export enum TreeStatus {