From 8c91051e7e9b1f2eda43ea95dae8c7f3cdc261ba Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Mon, 11 Nov 2024 12:30:36 -0800 Subject: [PATCH 01/37] Initial example --- .../dds/tree/src/simple-tree/objectNode.ts | 4 +- .../tree/src/test/openPolymorphism.spec.ts | 200 ++++++++++++++++++ 2 files changed, 202 insertions(+), 2 deletions(-) create mode 100644 packages/dds/tree/src/test/openPolymorphism.spec.ts diff --git a/packages/dds/tree/src/simple-tree/objectNode.ts b/packages/dds/tree/src/simple-tree/objectNode.ts index d40c33d2b146..3cbd52da84cd 100644 --- a/packages/dds/tree/src/simple-tree/objectNode.ts +++ b/packages/dds/tree/src/simple-tree/objectNode.ts @@ -47,8 +47,8 @@ import { TreeNodeValid, type MostDerivedData } from "./treeNodeValid.js"; import { getUnhydratedContext } from "./createContext.js"; /** - * Helper used to produce types for object nodes. - * @system @public + * Generates the properties for an ObjectNode from its field schema object. + * @public */ export type ObjectFromSchemaRecord> = { -readonly [Property in keyof T]: Property extends string diff --git a/packages/dds/tree/src/test/openPolymorphism.spec.ts b/packages/dds/tree/src/test/openPolymorphism.spec.ts new file mode 100644 index 000000000000..1c5573fc2d22 --- /dev/null +++ b/packages/dds/tree/src/test/openPolymorphism.spec.ts @@ -0,0 +1,200 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import { strict as assert } from "node:assert"; + +import { + SchemaFactory, + type InternalTreeNode, + type NodeKind, + type ObjectFromSchemaRecord, + type TreeNode, + type TreeNodeSchema, + type Unhydrated, +} from "../simple-tree/index.js"; +import { Tree } from "../shared-tree/index.js"; +import { validateUsageError } from "./utils.js"; + +const sf = new SchemaFactory("test"); + +/** + * Schema used in example. + */ +class Point extends sf.object("Point", { x: sf.number, y: sf.number }) {} + +// #region Example definition of a polymorphic Component named "Item" +// This code defines what an Item is and how to implement it, but does not depend on any of the implementations. +// Instead implementations depend on this, inverting the normal dependency direction for schema. + +/** + * Fields all Items must have. + */ +const itemFields = { location: Point }; + +/** + * Properties all item types must implement. + */ +interface ItemExtensions { + foo(): void; +} + +/** + * An Item node. + * @remarks + * Open polymorphic collection which libraries can provide additional implementations of, similar to TypeScript interfaces. + * Implementations should declare schema who's nodes extends this interface, and have the schema statically implement ItemSchema. + */ +type Item = TreeNode & ItemExtensions & ObjectFromSchemaRecord; + +/** + * Details about the type all item schema must provide. + * @remarks + * This pattern can be used for for things like generating insert content menus which can describe and create any of the allowed child types. + */ +interface ItemStatic { + readonly description: string; + default(): Unhydrated; +} + +/** + * A schema for an Item. + */ +type ItemSchema = TreeNodeSchema & ItemStatic; + +// #endregion + +/** + * Example implementation of an Item. + */ +class TextItem + extends sf.object("TextItem", { ...itemFields, text: sf.string }) + implements Item +{ + public static readonly description = "Text"; + public static default(): TextItem { + return new TextItem({ text: "", location: { x: 0, y: 0 } }); + } + + public foo(): void { + this.text += "foo"; + } +} + +describe("Open Polymorphism design pattern examples and tests for them", () => { + it("mutable static registry", () => { + // ------------- + // Registry for items. If using this pattern, this would typically be defined alongside the Item interface. + + /** + * Item type registry. + * @remarks + * This doesn't have to be a mutable static. + * For example libraries could export their implementations instead of adding them when imported, + * then the top level code which pulls in all the libraries could aggregate the item types. + * + * TODO: document (and enforce/detect) when how late it is safe to modify array's used as allowed types. + * These docs should ideally align with how late lazy type lambdas are evaluated (when the tree configuration is constructed, or an instance is made, which ever is first? Maybe define schema finalization?) + */ + const ItemTypes: ItemSchema[] = []; + + // ------------- + // Library using an Item + + class Container extends sf.array("Container", ItemTypes) {} + + // ------------- + // Library defining an item + + ItemTypes.push(TextItem); + + // ------------- + // Example use of container with generic code and down casting + + const container = new Container(); + + // If we don't do anything special, the insertable type is never, so a cast is required to insert content. + container.insertAtStart(new TextItem({ text: "", location: { x: 0, y: 0 } }) as never); + + // Items read from the container are typed as Item and have thew expected APIs: + const first = container[0]; + first.foo(); + first.location.x += 1; + + // Down casting works as normal. + if (Tree.is(first, TextItem)) { + assert.equal(first.text, "foo"); + } + }); + + it("mutable static registry, error cases", () => { + const ItemTypes: ItemSchema[] = []; + class Container extends sf.array("Container", ItemTypes) {} + + // Not added to registry + // ItemTypes.push(TextItem); + + const container = new Container(); + + // Should error due to out of schema content + assert.throws( + () => + container.insertAtStart(new TextItem({ text: "", location: { x: 0, y: 0 } }) as never), + validateUsageError(/schema/), + ); + + // Modifying registration too late should error + assert.throws(() => ItemTypes.push(TextItem)); + }); + + it("mutable static registry, recursive case", () => { + const ItemTypes: ItemSchema[] = []; + + // Example recursive item implementation + class Container extends sf.array("Container", 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 {} + } + + ItemTypes.push(ContainerItem); + + const container = new Container(); + + container.insertAtStart( + new ContainerItem({ container: [], location: { x: 0, y: 0 } }) as never, + ); + }); + + it("mutable static registry, safer editing API", () => { + const ItemTypes: ItemSchema[] = []; + class Container extends sf.object("Container", { child: sf.optional(ItemTypes) }) { + // Allow any Item in the constructor. This isn't fully type safe since it permits items which have not been registered as allowed, but it likely the API we want. + public constructor(item: InternalTreeNode | undefined | { child?: undefined | Item }) { + super(item as never); + } + + // public override set child(item: Item | undefined) { + // // This should require a type cast, but TypeScript gets the super type wrong here. + // super.child = item; + // } + } + + ItemTypes.push(TextItem); + + const container = new Container({ child: undefined }); + const container2 = new Container({ child: TextItem.default() }); + + // This should not build. + container.child = TextItem.default(); + container.child = undefined; + }); +}); From aea7d62ba5aecba71efe2fe5205321f544c93262 Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Mon, 11 Nov 2024 14:29:14 -0800 Subject: [PATCH 02/37] Make fields safer --- .changeset/metal-sloths-join.md | 24 +++++ .../dds/tree/api-report/tree.alpha.api.md | 12 ++- packages/dds/tree/api-report/tree.beta.api.md | 12 ++- .../tree/api-report/tree.legacy.alpha.api.md | 12 ++- .../tree/api-report/tree.legacy.public.api.md | 12 ++- .../dds/tree/api-report/tree.public.api.md | 12 ++- packages/dds/tree/src/index.ts | 2 + .../tree/src/simple-tree/api/typesUnsafe.ts | 9 +- packages/dds/tree/src/simple-tree/index.ts | 2 + .../dds/tree/src/simple-tree/objectNode.ts | 51 +++++++++- .../shared-tree-core/sharedTreeCore.spec.ts | 4 +- .../src/test/simple-tree/objectNode.spec.ts | 96 ++++++++++++++++++- .../api-report/fluid-framework.alpha.api.md | 12 ++- .../api-report/fluid-framework.beta.api.md | 12 ++- .../fluid-framework.legacy.alpha.api.md | 12 ++- .../fluid-framework.legacy.public.api.md | 12 ++- .../api-report/fluid-framework.public.api.md | 12 ++- 17 files changed, 289 insertions(+), 19 deletions(-) create mode 100644 .changeset/metal-sloths-join.md diff --git a/.changeset/metal-sloths-join.md b/.changeset/metal-sloths-join.md new file mode 100644 index 000000000000..9df8ed6e43ae --- /dev/null +++ b/.changeset/metal-sloths-join.md @@ -0,0 +1,24 @@ +--- +"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. diff --git a/packages/dds/tree/api-report/tree.alpha.api.md b/packages/dds/tree/api-report/tree.alpha.api.md index 3265e4a9b9aa..cf242a8977ef 100644 --- a/packages/dds/tree/api-report/tree.alpha.api.md +++ b/packages/dds/tree/api-report/tree.alpha.api.md @@ -28,11 +28,19 @@ type ApplyKind = { [FieldKind.Identifier]: T; }[Kind]; +// @public +export type ApplyKindAssignment = [Kind] extends [ +FieldKind.Required +] ? T : [Kind] extends [FieldKind.Optional] ? T | undefined : never; + // @public 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 +export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment, Kind> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; + // @alpha export function asTreeViewAlpha(view: TreeView): TreeViewAlpha; @@ -470,7 +478,9 @@ export const noopValidator: JsonValidator; // @public type ObjectFromSchemaRecord> = { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; + -readonly [Property in keyof T as Property extends string ? [AssignableTreeFieldFromImplicitField] extends [never] ? never : Property : never]: Property extends string ? TreeFieldFromImplicitField : unknown; +} & { + readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; }; // @public diff --git a/packages/dds/tree/api-report/tree.beta.api.md b/packages/dds/tree/api-report/tree.beta.api.md index 7c570d89640b..c34bd2f6aba7 100644 --- a/packages/dds/tree/api-report/tree.beta.api.md +++ b/packages/dds/tree/api-report/tree.beta.api.md @@ -17,11 +17,19 @@ type ApplyKind = { [FieldKind.Identifier]: T; }[Kind]; +// @public +export type ApplyKindAssignment = [Kind] extends [ +FieldKind.Required +] ? T : [Kind] extends [FieldKind.Optional] ? T | undefined : never; + // @public 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 +export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment, Kind> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; + // @public export enum CommitKind { Default = 0, @@ -275,7 +283,9 @@ export enum NodeKind { // @public type ObjectFromSchemaRecord> = { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; + -readonly [Property in keyof T as Property extends string ? [AssignableTreeFieldFromImplicitField] extends [never] ? never : Property : never]: Property extends string ? TreeFieldFromImplicitField : unknown; +} & { + readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; }; // @public diff --git a/packages/dds/tree/api-report/tree.legacy.alpha.api.md b/packages/dds/tree/api-report/tree.legacy.alpha.api.md index 4df875307225..1466ffc40d55 100644 --- a/packages/dds/tree/api-report/tree.legacy.alpha.api.md +++ b/packages/dds/tree/api-report/tree.legacy.alpha.api.md @@ -17,11 +17,19 @@ type ApplyKind = { [FieldKind.Identifier]: T; }[Kind]; +// @public +export type ApplyKindAssignment = [Kind] extends [ +FieldKind.Required +] ? T : [Kind] extends [FieldKind.Optional] ? T | undefined : never; + // @public 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 +export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment, Kind> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; + // @public export enum CommitKind { Default = 0, @@ -270,7 +278,9 @@ export enum NodeKind { // @public type ObjectFromSchemaRecord> = { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; + -readonly [Property in keyof T as Property extends string ? [AssignableTreeFieldFromImplicitField] extends [never] ? never : Property : never]: Property extends string ? TreeFieldFromImplicitField : unknown; +} & { + readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; }; // @public 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 b2d24dd09fc8..f50be590e849 100644 --- a/packages/dds/tree/api-report/tree.legacy.public.api.md +++ b/packages/dds/tree/api-report/tree.legacy.public.api.md @@ -17,11 +17,19 @@ type ApplyKind = { [FieldKind.Identifier]: T; }[Kind]; +// @public +export type ApplyKindAssignment = [Kind] extends [ +FieldKind.Required +] ? T : [Kind] extends [FieldKind.Optional] ? T | undefined : never; + // @public 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 +export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment, Kind> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; + // @public export enum CommitKind { Default = 0, @@ -270,7 +278,9 @@ export enum NodeKind { // @public type ObjectFromSchemaRecord> = { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; + -readonly [Property in keyof T as Property extends string ? [AssignableTreeFieldFromImplicitField] extends [never] ? never : Property : never]: Property extends string ? TreeFieldFromImplicitField : unknown; +} & { + readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; }; // @public diff --git a/packages/dds/tree/api-report/tree.public.api.md b/packages/dds/tree/api-report/tree.public.api.md index b2d24dd09fc8..f50be590e849 100644 --- a/packages/dds/tree/api-report/tree.public.api.md +++ b/packages/dds/tree/api-report/tree.public.api.md @@ -17,11 +17,19 @@ type ApplyKind = { [FieldKind.Identifier]: T; }[Kind]; +// @public +export type ApplyKindAssignment = [Kind] extends [ +FieldKind.Required +] ? T : [Kind] extends [FieldKind.Optional] ? T | undefined : never; + // @public 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 +export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment, Kind> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; + // @public export enum CommitKind { Default = 0, @@ -270,7 +278,9 @@ export enum NodeKind { // @public type ObjectFromSchemaRecord> = { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; + -readonly [Property in keyof T as Property extends string ? [AssignableTreeFieldFromImplicitField] extends [never] ? never : Property : never]: Property extends string ? TreeFieldFromImplicitField : unknown; +} & { + readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; }; // @public diff --git a/packages/dds/tree/src/index.ts b/packages/dds/tree/src/index.ts index 5f123fb5a167..798985141a62 100644 --- a/packages/dds/tree/src/index.ts +++ b/packages/dds/tree/src/index.ts @@ -191,6 +191,8 @@ export { type TreeBranch, type TreeBranchEvents, asTreeViewAlpha, + type AssignableTreeFieldFromImplicitField, + type ApplyKindAssignment, } from "./simple-tree/index.js"; export { SharedTree, diff --git a/packages/dds/tree/src/simple-tree/api/typesUnsafe.ts b/packages/dds/tree/src/simple-tree/api/typesUnsafe.ts index da2f39f46651..124ab1fd7593 100644 --- a/packages/dds/tree/src/simple-tree/api/typesUnsafe.ts +++ b/packages/dds/tree/src/simple-tree/api/typesUnsafe.ts @@ -56,9 +56,12 @@ export type Unenforced<_DesiredExtendsConstraint> = unknown; */ export type ObjectFromSchemaRecordUnsafe< T extends Unenforced>, -> = { - -readonly [Property in keyof T]: TreeFieldFromImplicitFieldUnsafe; -}; +> = + // Due to https://github.com/microsoft/TypeScript/issues/43826 we can not set the desired setter type. + // Partial mitigation for this (used for non-recursive types) breaks compilation if used here, so recursive object end up allowing some unsafe assignments which will error at runtime. + { + -readonly [Property in keyof T]: TreeFieldFromImplicitFieldUnsafe; + }; /** * {@link Unenforced} version of {@link TreeNodeSchema}. diff --git a/packages/dds/tree/src/simple-tree/index.ts b/packages/dds/tree/src/simple-tree/index.ts index a9d0c9bd609a..c550e788cf25 100644 --- a/packages/dds/tree/src/simple-tree/index.ts +++ b/packages/dds/tree/src/simple-tree/index.ts @@ -160,6 +160,8 @@ export { type FieldHasDefault, type InsertableObjectFromSchemaRecord, type ObjectFromSchemaRecord, + type AssignableTreeFieldFromImplicitField, + type ApplyKindAssignment, type TreeObjectNode, setField, } from "./objectNode.js"; diff --git a/packages/dds/tree/src/simple-tree/objectNode.ts b/packages/dds/tree/src/simple-tree/objectNode.ts index d40c33d2b146..46f3abe98c6b 100644 --- a/packages/dds/tree/src/simple-tree/objectNode.ts +++ b/packages/dds/tree/src/simple-tree/objectNode.ts @@ -26,6 +26,7 @@ import { normalizeFieldSchema, type ImplicitAllowedTypes, FieldKind, + type InsertableTreeNodeFromImplicitAllowedTypes, } from "./schemaTypes.js"; import { type TreeNodeSchema, @@ -41,7 +42,12 @@ import { getOrCreateInnerNode, } from "./core/index.js"; import { mapTreeFromNodeData, type InsertableContent } from "./toMapTree.js"; -import { type RestrictiveStringRecord, fail, type FlattenKeys } from "../util/index.js"; +import { + type RestrictiveStringRecord, + fail, + type FlattenKeys, + type UnionToIntersection, +} from "../util/index.js"; import type { ObjectNodeSchema, ObjectNodeSchemaInternalData } from "./objectNodeTypes.js"; import { TreeNodeValid, type MostDerivedData } from "./treeNodeValid.js"; import { getUnhydratedContext } from "./createContext.js"; @@ -51,11 +57,52 @@ import { getUnhydratedContext } from "./createContext.js"; * @system @public */ export type ObjectFromSchemaRecord> = { - -readonly [Property in keyof T]: Property extends string + // 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 Property extends string + ? [AssignableTreeFieldFromImplicitField] extends [never] + ? never + : Property + : never]: Property extends string ? TreeFieldFromImplicitField : unknown; +} & { + readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; }; +/** + * 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 implementation detail. + * @system @public + */ +export type AssignableTreeFieldFromImplicitField< + TSchemaInput extends ImplicitFieldSchema, + TSchema = UnionToIntersection, +> = [TSchema] extends [FieldSchema] + ? ApplyKindAssignment, Kind> + : [TSchema] extends [ImplicitAllowedTypes] + ? InsertableTreeNodeFromImplicitAllowedTypes + : 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; + /** * A {@link TreeNode} which modules a JavaScript object. * @remarks diff --git a/packages/dds/tree/src/test/shared-tree-core/sharedTreeCore.spec.ts b/packages/dds/tree/src/test/shared-tree-core/sharedTreeCore.spec.ts index ebc5185e8229..7e604039077a 100644 --- a/packages/dds/tree/src/test/shared-tree-core/sharedTreeCore.spec.ts +++ b/packages/dds/tree/src/test/shared-tree-core/sharedTreeCore.spec.ts @@ -294,9 +294,9 @@ describe("SharedTreeCore", () => { }); const sf = new SchemaFactory("0x4a6 repro"); - const TestNode = sf.objectRecursive("test node", { + class TestNode extends sf.objectRecursive("test node", { child: sf.optionalRecursive([() => TestNode, sf.number]), - }); + }) {} const tree2 = await factory.load( dataStoreRuntime2, diff --git a/packages/dds/tree/src/test/simple-tree/objectNode.spec.ts b/packages/dds/tree/src/test/simple-tree/objectNode.spec.ts index c818240df935..17e205acadcb 100644 --- a/packages/dds/tree/src/test/simple-tree/objectNode.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/objectNode.spec.ts @@ -26,6 +26,8 @@ import type { import { validateUsageError } from "../utils.js"; import { Tree } from "../../shared-tree/index.js"; import type { + ImplicitFieldSchema, + InsertableField, InsertableTreeFieldFromImplicitField, InsertableTreeNodeFromAllowedTypes, InsertableTypedNode, @@ -234,10 +236,99 @@ describeHydration( }) {} const n = init(HasId, {}); assert.throws(() => { - // TODO: AB:9129: this should not compile + // @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, + }) {} + // @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 schema errors - union", () => { + const child = schemaFactory.number as + | typeof schemaFactory.number + | typeof schemaFactory.null; + class NonExact extends schemaFactory.object("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; + 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(() => { + // Due to recursive type limitations, this compiles but shouldn't, see ObjectFromSchemaRecordUnsafe + 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; + 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. @@ -446,7 +537,8 @@ describeHydration( foo: schemaFactory.optional(schemaFactory.number), }) { // 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/framework/fluid-framework/api-report/fluid-framework.alpha.api.md b/packages/framework/fluid-framework/api-report/fluid-framework.alpha.api.md index 37eac2a1a112..17ad7e4c6834 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 @@ -28,11 +28,19 @@ type ApplyKind = { [FieldKind.Identifier]: T; }[Kind]; +// @public +export type ApplyKindAssignment = [Kind] extends [ +FieldKind.Required +] ? T : [Kind] extends [FieldKind.Optional] ? T | undefined : never; + // @public 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 +export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment, Kind> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; + // @alpha export function asTreeViewAlpha(view: TreeView): TreeViewAlpha; @@ -818,7 +826,9 @@ export const noopValidator: JsonValidator; // @public type ObjectFromSchemaRecord> = { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; + -readonly [Property in keyof T as Property extends string ? [AssignableTreeFieldFromImplicitField] extends [never] ? never : Property : never]: Property extends string ? TreeFieldFromImplicitField : unknown; +} & { + readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; }; // @public 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 267375e03fa0..f6b63ac1629b 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 @@ -17,11 +17,19 @@ type ApplyKind = { [FieldKind.Identifier]: T; }[Kind]; +// @public +export type ApplyKindAssignment = [Kind] extends [ +FieldKind.Required +] ? T : [Kind] extends [FieldKind.Optional] ? T | undefined : never; + // @public 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 +export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment, Kind> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; + // @public export enum AttachState { Attached = "Attached", @@ -620,7 +628,9 @@ export enum NodeKind { // @public type ObjectFromSchemaRecord> = { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; + -readonly [Property in keyof T as Property extends string ? [AssignableTreeFieldFromImplicitField] extends [never] ? never : Property : never]: Property extends string ? TreeFieldFromImplicitField : unknown; +} & { + readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; }; // @public diff --git a/packages/framework/fluid-framework/api-report/fluid-framework.legacy.alpha.api.md b/packages/framework/fluid-framework/api-report/fluid-framework.legacy.alpha.api.md index cb71efc1816b..e0e2ef344920 100644 --- a/packages/framework/fluid-framework/api-report/fluid-framework.legacy.alpha.api.md +++ b/packages/framework/fluid-framework/api-report/fluid-framework.legacy.alpha.api.md @@ -17,11 +17,19 @@ type ApplyKind = { [FieldKind.Identifier]: T; }[Kind]; +// @public +export type ApplyKindAssignment = [Kind] extends [ +FieldKind.Required +] ? T : [Kind] extends [FieldKind.Optional] ? T | undefined : never; + // @public 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 +export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment, Kind> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; + // @public export enum AttachState { Attached = "Attached", @@ -917,7 +925,9 @@ export enum NodeKind { // @public type ObjectFromSchemaRecord> = { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; + -readonly [Property in keyof T as Property extends string ? [AssignableTreeFieldFromImplicitField] extends [never] ? never : Property : never]: Property extends string ? TreeFieldFromImplicitField : unknown; +} & { + readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; }; // @public 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 8b4ca68b0fba..c3bfe4b76edf 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 @@ -17,11 +17,19 @@ type ApplyKind = { [FieldKind.Identifier]: T; }[Kind]; +// @public +export type ApplyKindAssignment = [Kind] extends [ +FieldKind.Required +] ? T : [Kind] extends [FieldKind.Optional] ? T | undefined : never; + // @public 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 +export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment, Kind> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; + // @public export enum AttachState { Attached = "Attached", @@ -651,7 +659,9 @@ export enum NodeKind { // @public type ObjectFromSchemaRecord> = { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; + -readonly [Property in keyof T as Property extends string ? [AssignableTreeFieldFromImplicitField] extends [never] ? never : Property : never]: Property extends string ? TreeFieldFromImplicitField : unknown; +} & { + readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; }; // @public 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 8eedb915642f..f280d8ddc150 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 @@ -17,11 +17,19 @@ type ApplyKind = { [FieldKind.Identifier]: T; }[Kind]; +// @public +export type ApplyKindAssignment = [Kind] extends [ +FieldKind.Required +] ? T : [Kind] extends [FieldKind.Optional] ? T | undefined : never; + // @public 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 +export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment, Kind> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; + // @public export enum AttachState { Attached = "Attached", @@ -615,7 +623,9 @@ export enum NodeKind { // @public type ObjectFromSchemaRecord> = { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; + -readonly [Property in keyof T as Property extends string ? [AssignableTreeFieldFromImplicitField] extends [never] ? never : Property : never]: Property extends string ? TreeFieldFromImplicitField : unknown; +} & { + readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; }; // @public From ec64de433a1db87abc0338e76c1b473e0399d375 Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Mon, 11 Nov 2024 16:41:22 -0800 Subject: [PATCH 03/37] Update packages/dds/tree/src/simple-tree/objectNode.ts Co-authored-by: Joshua Smithrud <54606601+Josmithr@users.noreply.github.com> --- packages/dds/tree/src/simple-tree/objectNode.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dds/tree/src/simple-tree/objectNode.ts b/packages/dds/tree/src/simple-tree/objectNode.ts index 46f3abe98c6b..3abd3854f205 100644 --- a/packages/dds/tree/src/simple-tree/objectNode.ts +++ b/packages/dds/tree/src/simple-tree/objectNode.ts @@ -76,7 +76,7 @@ export type ObjectFromSchemaRecord Date: Tue, 12 Nov 2024 14:40:08 -0800 Subject: [PATCH 04/37] typeNarrow asserts --- .../tree/src/simple-tree/objectNodeTypes.ts | 5 +- .../tree/src/test/openPolymorphism.spec.ts | 74 ++++++++++++++++--- 2 files changed, 65 insertions(+), 14 deletions(-) diff --git a/packages/dds/tree/src/simple-tree/objectNodeTypes.ts b/packages/dds/tree/src/simple-tree/objectNodeTypes.ts index 3555a5a168fb..cfff99e8e2a2 100644 --- a/packages/dds/tree/src/simple-tree/objectNodeTypes.ts +++ b/packages/dds/tree/src/simple-tree/objectNodeTypes.ts @@ -10,20 +10,21 @@ import type { SimpleKeyMap, } from "./objectNode.js"; import type { ImplicitFieldSchema, FieldSchema } from "./schemaTypes.js"; -import { NodeKind, type TreeNodeSchemaClass, type TreeNodeSchema } from "./core/index.js"; +import { NodeKind, type TreeNodeSchema, type TreeNodeSchemaBoth } from "./core/index.js"; import type { FieldKey } from "../core/index.js"; /** * A schema for {@link TreeObjectNode}s. * @privateRemarks * This is a candidate for being promoted to the public package API. + * @public */ export interface ObjectNodeSchema< TName extends string = string, T extends RestrictiveStringRecord = RestrictiveStringRecord, ImplicitlyConstructable extends boolean = boolean, -> extends TreeNodeSchemaClass< +> extends TreeNodeSchemaBoth< TName, NodeKind.Object, TreeObjectNode, diff --git a/packages/dds/tree/src/test/openPolymorphism.spec.ts b/packages/dds/tree/src/test/openPolymorphism.spec.ts index 1c5573fc2d22..36f9fae4bf44 100644 --- a/packages/dds/tree/src/test/openPolymorphism.spec.ts +++ b/packages/dds/tree/src/test/openPolymorphism.spec.ts @@ -7,7 +7,7 @@ import { strict as assert } from "node:assert"; import { SchemaFactory, - type InternalTreeNode, + type AssignableTreeFieldFromImplicitField, type NodeKind, type ObjectFromSchemaRecord, type TreeNode, @@ -16,6 +16,12 @@ import { } from "../simple-tree/index.js"; import { Tree } from "../shared-tree/index.js"; import { validateUsageError } from "./utils.js"; +import { + customizeSchemaTyping, + type FieldKind, + type FieldSchema, + type InsertableField, +} from "../simple-tree/schemaTypes.js"; const sf = new SchemaFactory("test"); @@ -176,24 +182,68 @@ describe("Open Polymorphism design pattern examples and tests for them", () => { it("mutable static registry, safer editing API", () => { const ItemTypes: ItemSchema[] = []; - class Container extends sf.object("Container", { child: sf.optional(ItemTypes) }) { - // Allow any Item in the constructor. This isn't fully type safe since it permits items which have not been registered as allowed, but it likely the API we want. - public constructor(item: InternalTreeNode | undefined | { child?: undefined | Item }) { - super(item as never); + class Container extends sf.object("Container", { + child: customizeSchemaTyping(sf.optional(ItemTypes))<{ + input: Item | undefined; + output: Item | undefined; + readWrite: Item | undefined; + allowDefault: true; + }>(), + }) {} + + ItemTypes.push(TextItem); + + const container = new Container({ child: undefined }); + const container2 = new Container({ child: TextItem.default() }); + + type T3 = AssignableTreeFieldFromImplicitField<(typeof Container.info)["child"]>; + + // Enabled by custom setter + container.child = TextItem.default(); + container.child = undefined; + }); + + it("mutable static registry, safer editing API", () => { + const ItemTypes: ItemSchema[] = []; + class Container extends sf.object("Container", { + child: sf.optional(ItemTypes), + }) { + public allowItem( + schema: T, + ): asserts this is Container & + ObjectFromSchemaRecord<{ child: FieldSchema }> { + assert(Container.fields.get("child")?.allowedTypeSet.has(schema) ?? false); + } + + public static allowItem( + schema: T, + ): asserts this is new (item: { child?: InsertableField }) => Container { + assert(Container.fields.get("child")?.allowedTypeSet.has(schema) ?? false); } - // public override set child(item: Item | undefined) { - // // This should require a type cast, but TypeScript gets the super type wrong here. - // super.child = item; - // } + public static fromItemAndSchema( + schema: T, + item: InsertableField, + ): Container { + const x: typeof Container = Container; + x.allowItem(schema); + return new x({ child: item }); + } + + public static fromItem(item: Item): Container { + const schema = Tree.schema(item); + return Container.fromItemAndSchema(schema as ItemSchema, item); + } } ItemTypes.push(TextItem); - const container = new Container({ child: undefined }); - const container2 = new Container({ child: TextItem.default() }); + Container.allowItem(TextItem); + const container: Container = new Container({ child: undefined }); + const container2 = Container.fromItem(TextItem.default()); - // This should not build. + container.allowItem(TextItem); + const read = container.child; container.child = TextItem.default(); container.child = undefined; }); From 41191741f57e0b85398d5cf85edc7acb05f098e1 Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Tue, 12 Nov 2024 14:42:06 -0800 Subject: [PATCH 05/37] Fixes for optional --- .../dds/tree/src/simple-tree/objectNode.ts | 15 ++++----- .../dds/tree/src/simple-tree/schemaTypes.ts | 4 +-- .../src/test/simple-tree/objectNode.spec.ts | 32 +++++++++++++++++++ 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/packages/dds/tree/src/simple-tree/objectNode.ts b/packages/dds/tree/src/simple-tree/objectNode.ts index 3abd3854f205..6843783d8ada 100644 --- a/packages/dds/tree/src/simple-tree/objectNode.ts +++ b/packages/dds/tree/src/simple-tree/objectNode.ts @@ -59,15 +59,14 @@ import { getUnhydratedContext } from "./createContext.js"; 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 Property extends string - ? [AssignableTreeFieldFromImplicitField] extends [never] - ? never - : Property - : never]: Property extends string ? TreeFieldFromImplicitField : unknown; + -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]: TreeFieldFromImplicitField; } & { - readonly [Property in keyof T]: Property extends string - ? TreeFieldFromImplicitField - : unknown; + readonly [Property in keyof T]: TreeFieldFromImplicitField; }; /** diff --git a/packages/dds/tree/src/simple-tree/schemaTypes.ts b/packages/dds/tree/src/simple-tree/schemaTypes.ts index 83979f815c25..c9136c08056d 100644 --- a/packages/dds/tree/src/simple-tree/schemaTypes.ts +++ b/packages/dds/tree/src/simple-tree/schemaTypes.ts @@ -543,9 +543,9 @@ export type TreeFieldFromImplicitField, -> = [TSchema] extends [FieldSchema] +> = TSchema extends FieldSchema ? ApplyKindInput, Kind, true> - : [TSchema] extends [ImplicitAllowedTypes] + : TSchema extends ImplicitAllowedTypes ? InsertableTreeNodeFromImplicitAllowedTypes : never; diff --git a/packages/dds/tree/src/test/simple-tree/objectNode.spec.ts b/packages/dds/tree/src/test/simple-tree/objectNode.spec.ts index 17e205acadcb..7b41ec1096b0 100644 --- a/packages/dds/tree/src/test/simple-tree/objectNode.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/objectNode.spec.ts @@ -12,6 +12,7 @@ import { typeNameSymbol, typeSchemaSymbol, type NodeBuilderData, + type TreeNode, } from "../../simple-tree/index.js"; import type { InsertableObjectFromSchemaRecord, @@ -31,6 +32,8 @@ import type { InsertableTreeFieldFromImplicitField, InsertableTreeNodeFromAllowedTypes, InsertableTypedNode, + TreeFieldFromImplicitField, + TreeLeafValue, // eslint-disable-next-line import/no-internal-modules } from "../../simple-tree/schemaTypes.js"; @@ -255,6 +258,35 @@ describeHydration( }); }); + it("assigning non-exact optional schema", () => { + const child: ImplicitFieldSchema = schemaFactory.number; + class NonExact extends schemaFactory.object("NonExact", { + child: schemaFactory.optional(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(() => { + // @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 From 90a79f5f67a4183af68ba7de95594cfb198bd6e9 Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Tue, 12 Nov 2024 14:54:39 -0800 Subject: [PATCH 06/37] FIx build --- packages/dds/tree/api-report/tree.alpha.api.md | 8 +++++--- packages/dds/tree/api-report/tree.beta.api.md | 8 +++++--- packages/dds/tree/api-report/tree.legacy.alpha.api.md | 8 +++++--- packages/dds/tree/api-report/tree.legacy.public.api.md | 8 +++++--- packages/dds/tree/api-report/tree.public.api.md | 8 +++++--- packages/dds/tree/package.json | 7 +++++++ .../tree/src/test/types/validateTreePrevious.generated.ts | 3 +++ .../api-report/fluid-framework.alpha.api.md | 8 +++++--- .../api-report/fluid-framework.beta.api.md | 8 +++++--- .../api-report/fluid-framework.legacy.alpha.api.md | 8 +++++--- .../api-report/fluid-framework.legacy.public.api.md | 8 +++++--- .../api-report/fluid-framework.public.api.md | 8 +++++--- 12 files changed, 60 insertions(+), 30 deletions(-) diff --git a/packages/dds/tree/api-report/tree.alpha.api.md b/packages/dds/tree/api-report/tree.alpha.api.md index 96c3754d6d41..1f9bd343be8a 100644 --- a/packages/dds/tree/api-report/tree.alpha.api.md +++ b/packages/dds/tree/api-report/tree.alpha.api.md @@ -246,7 +246,7 @@ export type InsertableObjectFromSchemaRecordUnsafe> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; +export type InsertableTreeFieldFromImplicitField> = TSchema extends FieldSchema ? ApplyKindInput, Kind, true> : TSchema extends ImplicitAllowedTypes ? InsertableTreeNodeFromImplicitAllowedTypes : never; // @public export type InsertableTreeFieldFromImplicitFieldUnsafe, TSchema = UnionToIntersection> = [TSchema] extends [FieldSchemaUnsafe] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypesUnsafe : never; @@ -479,9 +479,11 @@ export const noopValidator: JsonValidator; // @public type ObjectFromSchemaRecord> = { - -readonly [Property in keyof T as Property extends string ? [AssignableTreeFieldFromImplicitField] extends [never] ? never : Property : never]: Property extends string ? TreeFieldFromImplicitField : unknown; + -readonly [Property in keyof T as [ + AssignableTreeFieldFromImplicitField + ] extends [never | undefined] ? never : Property]: TreeFieldFromImplicitField; } & { - readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; + readonly [Property in keyof T]: TreeFieldFromImplicitField; }; // @public diff --git a/packages/dds/tree/api-report/tree.beta.api.md b/packages/dds/tree/api-report/tree.beta.api.md index c102e49d9a8c..d01416239694 100644 --- a/packages/dds/tree/api-report/tree.beta.api.md +++ b/packages/dds/tree/api-report/tree.beta.api.md @@ -132,7 +132,7 @@ export type InsertableObjectFromSchemaRecordUnsafe> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; +export type InsertableTreeFieldFromImplicitField> = TSchema extends FieldSchema ? ApplyKindInput, Kind, true> : TSchema extends ImplicitAllowedTypes ? InsertableTreeNodeFromImplicitAllowedTypes : never; // @public export type InsertableTreeFieldFromImplicitFieldUnsafe, TSchema = UnionToIntersection> = [TSchema] extends [FieldSchemaUnsafe] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypesUnsafe : never; @@ -284,9 +284,11 @@ export enum NodeKind { // @public type ObjectFromSchemaRecord> = { - -readonly [Property in keyof T as Property extends string ? [AssignableTreeFieldFromImplicitField] extends [never] ? never : Property : never]: Property extends string ? TreeFieldFromImplicitField : unknown; + -readonly [Property in keyof T as [ + AssignableTreeFieldFromImplicitField + ] extends [never | undefined] ? never : Property]: TreeFieldFromImplicitField; } & { - readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; + readonly [Property in keyof T]: TreeFieldFromImplicitField; }; // @public diff --git a/packages/dds/tree/api-report/tree.legacy.alpha.api.md b/packages/dds/tree/api-report/tree.legacy.alpha.api.md index 4adf43db3e23..cec77ed4c5b4 100644 --- a/packages/dds/tree/api-report/tree.legacy.alpha.api.md +++ b/packages/dds/tree/api-report/tree.legacy.alpha.api.md @@ -132,7 +132,7 @@ export type InsertableObjectFromSchemaRecordUnsafe> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; +export type InsertableTreeFieldFromImplicitField> = TSchema extends FieldSchema ? ApplyKindInput, Kind, true> : TSchema extends ImplicitAllowedTypes ? InsertableTreeNodeFromImplicitAllowedTypes : never; // @public export type InsertableTreeFieldFromImplicitFieldUnsafe, TSchema = UnionToIntersection> = [TSchema] extends [FieldSchemaUnsafe] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypesUnsafe : never; @@ -279,9 +279,11 @@ export enum NodeKind { // @public type ObjectFromSchemaRecord> = { - -readonly [Property in keyof T as Property extends string ? [AssignableTreeFieldFromImplicitField] extends [never] ? never : Property : never]: Property extends string ? TreeFieldFromImplicitField : unknown; + -readonly [Property in keyof T as [ + AssignableTreeFieldFromImplicitField + ] extends [never | undefined] ? never : Property]: TreeFieldFromImplicitField; } & { - readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; + readonly [Property in keyof T]: TreeFieldFromImplicitField; }; // @public 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 835c08f84e6c..40dceae21b2e 100644 --- a/packages/dds/tree/api-report/tree.legacy.public.api.md +++ b/packages/dds/tree/api-report/tree.legacy.public.api.md @@ -132,7 +132,7 @@ export type InsertableObjectFromSchemaRecordUnsafe> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; +export type InsertableTreeFieldFromImplicitField> = TSchema extends FieldSchema ? ApplyKindInput, Kind, true> : TSchema extends ImplicitAllowedTypes ? InsertableTreeNodeFromImplicitAllowedTypes : never; // @public export type InsertableTreeFieldFromImplicitFieldUnsafe, TSchema = UnionToIntersection> = [TSchema] extends [FieldSchemaUnsafe] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypesUnsafe : never; @@ -279,9 +279,11 @@ export enum NodeKind { // @public type ObjectFromSchemaRecord> = { - -readonly [Property in keyof T as Property extends string ? [AssignableTreeFieldFromImplicitField] extends [never] ? never : Property : never]: Property extends string ? TreeFieldFromImplicitField : unknown; + -readonly [Property in keyof T as [ + AssignableTreeFieldFromImplicitField + ] extends [never | undefined] ? never : Property]: TreeFieldFromImplicitField; } & { - readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; + readonly [Property in keyof T]: TreeFieldFromImplicitField; }; // @public diff --git a/packages/dds/tree/api-report/tree.public.api.md b/packages/dds/tree/api-report/tree.public.api.md index 835c08f84e6c..40dceae21b2e 100644 --- a/packages/dds/tree/api-report/tree.public.api.md +++ b/packages/dds/tree/api-report/tree.public.api.md @@ -132,7 +132,7 @@ export type InsertableObjectFromSchemaRecordUnsafe> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; +export type InsertableTreeFieldFromImplicitField> = TSchema extends FieldSchema ? ApplyKindInput, Kind, true> : TSchema extends ImplicitAllowedTypes ? InsertableTreeNodeFromImplicitAllowedTypes : never; // @public export type InsertableTreeFieldFromImplicitFieldUnsafe, TSchema = UnionToIntersection> = [TSchema] extends [FieldSchemaUnsafe] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypesUnsafe : never; @@ -279,9 +279,11 @@ export enum NodeKind { // @public type ObjectFromSchemaRecord> = { - -readonly [Property in keyof T as Property extends string ? [AssignableTreeFieldFromImplicitField] extends [never] ? never : Property : never]: Property extends string ? TreeFieldFromImplicitField : unknown; + -readonly [Property in keyof T as [ + AssignableTreeFieldFromImplicitField + ] extends [never | undefined] ? never : Property]: TreeFieldFromImplicitField; } & { - readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; + readonly [Property in keyof T]: TreeFieldFromImplicitField; }; // @public diff --git a/packages/dds/tree/package.json b/packages/dds/tree/package.json index 73ef5d1e08d6..40cec6e98368 100644 --- a/packages/dds/tree/package.json +++ b/packages/dds/tree/package.json @@ -234,6 +234,13 @@ }, "Interface_InternalTypes_TreeArrayNodeBase": { "backCompat": false + }, + "TypeAlias_InsertableTreeFieldFromImplicitField": { + "backCompat": false + }, + "TypeAlias_InternalTypes_InsertableObjectFromSchemaRecord": { + "backCompat": false, + "forwardCompat": false } }, "entrypoint": "public" diff --git a/packages/dds/tree/src/test/types/validateTreePrevious.generated.ts b/packages/dds/tree/src/test/types/validateTreePrevious.generated.ts index 989b9ebdb489..7a505d71d1fe 100644 --- a/packages/dds/tree/src/test/types/validateTreePrevious.generated.ts +++ b/packages/dds/tree/src/test/types/validateTreePrevious.generated.ts @@ -673,6 +673,7 @@ declare type old_as_current_for_TypeAlias_InsertableTreeFieldFromImplicitField = * typeValidation.broken: * "TypeAlias_InsertableTreeFieldFromImplicitField": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_TypeAlias_InsertableTreeFieldFromImplicitField = requireAssignableTo>, TypeOnly>> /* @@ -961,6 +962,7 @@ declare type current_as_old_for_TypeAlias_InternalTypes_FlexListToUnion = requir * typeValidation.broken: * "TypeAlias_InternalTypes_InsertableObjectFromSchemaRecord": {"forwardCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type old_as_current_for_TypeAlias_InternalTypes_InsertableObjectFromSchemaRecord = requireAssignableTo>, TypeOnly>> /* @@ -970,6 +972,7 @@ declare type old_as_current_for_TypeAlias_InternalTypes_InsertableObjectFromSche * typeValidation.broken: * "TypeAlias_InternalTypes_InsertableObjectFromSchemaRecord": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_TypeAlias_InternalTypes_InsertableObjectFromSchemaRecord = requireAssignableTo>, TypeOnly>> /* diff --git a/packages/framework/fluid-framework/api-report/fluid-framework.alpha.api.md b/packages/framework/fluid-framework/api-report/fluid-framework.alpha.api.md index cf2d76e3f116..df4b23994db3 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 @@ -556,7 +556,7 @@ export type InsertableObjectFromSchemaRecordUnsafe> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; +export type InsertableTreeFieldFromImplicitField> = TSchema extends FieldSchema ? ApplyKindInput, Kind, true> : TSchema extends ImplicitAllowedTypes ? InsertableTreeNodeFromImplicitAllowedTypes : never; // @public export type InsertableTreeFieldFromImplicitFieldUnsafe, TSchema = UnionToIntersection> = [TSchema] extends [FieldSchemaUnsafe] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypesUnsafe : never; @@ -827,9 +827,11 @@ export const noopValidator: JsonValidator; // @public type ObjectFromSchemaRecord> = { - -readonly [Property in keyof T as Property extends string ? [AssignableTreeFieldFromImplicitField] extends [never] ? never : Property : never]: Property extends string ? TreeFieldFromImplicitField : unknown; + -readonly [Property in keyof T as [ + AssignableTreeFieldFromImplicitField + ] extends [never | undefined] ? never : Property]: TreeFieldFromImplicitField; } & { - readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; + readonly [Property in keyof T]: TreeFieldFromImplicitField; }; // @public 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 9225c4586889..d7666521872e 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 @@ -439,7 +439,7 @@ export type InsertableObjectFromSchemaRecordUnsafe> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; +export type InsertableTreeFieldFromImplicitField> = TSchema extends FieldSchema ? ApplyKindInput, Kind, true> : TSchema extends ImplicitAllowedTypes ? InsertableTreeNodeFromImplicitAllowedTypes : never; // @public export type InsertableTreeFieldFromImplicitFieldUnsafe, TSchema = UnionToIntersection> = [TSchema] extends [FieldSchemaUnsafe] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypesUnsafe : never; @@ -629,9 +629,11 @@ export enum NodeKind { // @public type ObjectFromSchemaRecord> = { - -readonly [Property in keyof T as Property extends string ? [AssignableTreeFieldFromImplicitField] extends [never] ? never : Property : never]: Property extends string ? TreeFieldFromImplicitField : unknown; + -readonly [Property in keyof T as [ + AssignableTreeFieldFromImplicitField + ] extends [never | undefined] ? never : Property]: TreeFieldFromImplicitField; } & { - readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; + readonly [Property in keyof T]: TreeFieldFromImplicitField; }; // @public diff --git a/packages/framework/fluid-framework/api-report/fluid-framework.legacy.alpha.api.md b/packages/framework/fluid-framework/api-report/fluid-framework.legacy.alpha.api.md index 0cdd740268b6..b43ed349d434 100644 --- a/packages/framework/fluid-framework/api-report/fluid-framework.legacy.alpha.api.md +++ b/packages/framework/fluid-framework/api-report/fluid-framework.legacy.alpha.api.md @@ -539,7 +539,7 @@ export type InsertableObjectFromSchemaRecordUnsafe> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; +export type InsertableTreeFieldFromImplicitField> = TSchema extends FieldSchema ? ApplyKindInput, Kind, true> : TSchema extends ImplicitAllowedTypes ? InsertableTreeNodeFromImplicitAllowedTypes : never; // @public export type InsertableTreeFieldFromImplicitFieldUnsafe, TSchema = UnionToIntersection> = [TSchema] extends [FieldSchemaUnsafe] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypesUnsafe : never; @@ -926,9 +926,11 @@ export enum NodeKind { // @public type ObjectFromSchemaRecord> = { - -readonly [Property in keyof T as Property extends string ? [AssignableTreeFieldFromImplicitField] extends [never] ? never : Property : never]: Property extends string ? TreeFieldFromImplicitField : unknown; + -readonly [Property in keyof T as [ + AssignableTreeFieldFromImplicitField + ] extends [never | undefined] ? never : Property]: TreeFieldFromImplicitField; } & { - readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; + readonly [Property in keyof T]: TreeFieldFromImplicitField; }; // @public 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 44ae800eadd0..aaba5575164e 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 @@ -467,7 +467,7 @@ export type InsertableObjectFromSchemaRecordUnsafe> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; +export type InsertableTreeFieldFromImplicitField> = TSchema extends FieldSchema ? ApplyKindInput, Kind, true> : TSchema extends ImplicitAllowedTypes ? InsertableTreeNodeFromImplicitAllowedTypes : never; // @public export type InsertableTreeFieldFromImplicitFieldUnsafe, TSchema = UnionToIntersection> = [TSchema] extends [FieldSchemaUnsafe] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypesUnsafe : never; @@ -660,9 +660,11 @@ export enum NodeKind { // @public type ObjectFromSchemaRecord> = { - -readonly [Property in keyof T as Property extends string ? [AssignableTreeFieldFromImplicitField] extends [never] ? never : Property : never]: Property extends string ? TreeFieldFromImplicitField : unknown; + -readonly [Property in keyof T as [ + AssignableTreeFieldFromImplicitField + ] extends [never | undefined] ? never : Property]: TreeFieldFromImplicitField; } & { - readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; + readonly [Property in keyof T]: TreeFieldFromImplicitField; }; // @public 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 3e17c2124377..6c7d901bddb5 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 @@ -439,7 +439,7 @@ export type InsertableObjectFromSchemaRecordUnsafe> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; +export type InsertableTreeFieldFromImplicitField> = TSchema extends FieldSchema ? ApplyKindInput, Kind, true> : TSchema extends ImplicitAllowedTypes ? InsertableTreeNodeFromImplicitAllowedTypes : never; // @public export type InsertableTreeFieldFromImplicitFieldUnsafe, TSchema = UnionToIntersection> = [TSchema] extends [FieldSchemaUnsafe] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypesUnsafe : never; @@ -624,9 +624,11 @@ export enum NodeKind { // @public type ObjectFromSchemaRecord> = { - -readonly [Property in keyof T as Property extends string ? [AssignableTreeFieldFromImplicitField] extends [never] ? never : Property : never]: Property extends string ? TreeFieldFromImplicitField : unknown; + -readonly [Property in keyof T as [ + AssignableTreeFieldFromImplicitField + ] extends [never | undefined] ? never : Property]: TreeFieldFromImplicitField; } & { - readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; + readonly [Property in keyof T]: TreeFieldFromImplicitField; }; // @public From 6997880044f4441b092809196008b7e24dac967a Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Tue, 12 Nov 2024 15:10:43 -0800 Subject: [PATCH 07/37] Export ObjectNodeSchema --- .../tree/src/simple-tree/api/schemaFactory.ts | 14 +-- .../dds/tree/src/simple-tree/schemaTypes.ts | 104 ++++++++++++++++++ 2 files changed, 107 insertions(+), 11 deletions(-) diff --git a/packages/dds/tree/src/simple-tree/api/schemaFactory.ts b/packages/dds/tree/src/simple-tree/api/schemaFactory.ts index 995ae0809407..986c874e81ee 100644 --- a/packages/dds/tree/src/simple-tree/api/schemaFactory.ts +++ b/packages/dds/tree/src/simple-tree/api/schemaFactory.ts @@ -74,6 +74,7 @@ import type { import { createFieldSchemaUnsafe } from "./schemaFactoryRecursive.js"; import { TreeNodeValid } from "../treeNodeValid.js"; import { isLazy } from "../flexList.js"; +import type { ObjectNodeSchema } from "../objectNodeTypes.js"; /** * Gets the leaf domain schema compatible with a given {@link TreeValue}. */ @@ -322,17 +323,7 @@ export class SchemaFactory< public object< const Name extends TName, const T extends RestrictiveStringRecord, - >( - name: Name, - fields: T, - ): TreeNodeSchemaClass< - ScopedSchemaName, - NodeKind.Object, - TreeObjectNode>, - object & InsertableObjectFromSchemaRecord, - true, - T - > { + >(name: Name, fields: T): ObjectNodeSchema, T, true> { return objectSchema(this.scoped(name), fields, true); } @@ -395,6 +386,7 @@ export class SchemaFactory< >; /** + * The implementation (this doc does nothing but make JS-doc lint happy). * @privateRemarks * This should return `TreeNodeSchemaBoth`, however TypeScript gives an error if one of the overloads implicitly up-casts the return type of the implementation. * This seems like a TypeScript bug getting variance backwards for overload return types since it's erroring when the relation between the overload diff --git a/packages/dds/tree/src/simple-tree/schemaTypes.ts b/packages/dds/tree/src/simple-tree/schemaTypes.ts index c9136c08056d..1ce7972b625b 100644 --- a/packages/dds/tree/src/simple-tree/schemaTypes.ts +++ b/packages/dds/tree/src/simple-tree/schemaTypes.ts @@ -28,6 +28,7 @@ import type { import type { FieldKey } from "../core/index.js"; import type { InsertableContent } from "./toMapTree.js"; import { isLazy, type FlexListToUnion, type LazyItem } from "./flexList.js"; +import type { AssignableTreeFieldFromImplicitField } from "./objectNode.js"; /** * Returns true if the given schema is a {@link TreeNodeSchemaClass}, or otherwise false if it is a {@link TreeNodeSchemaNonClass}. @@ -575,6 +576,109 @@ export const UnsafeUnknownSchema: unique symbol = Symbol("UnsafeUnknownSchema"); */ export type UnsafeUnknownSchema = typeof UnsafeUnknownSchema; +/** + * {@inheritdoc (UnsafeUnknownSchema:type)} + * @alpha + */ +export const CustomizedTyping: unique symbol = Symbol("CustomizedTyping"); + +/** + * A special type which can be provided to some APIs as the schema type parameter when schema cannot easily be provided at compile time and an unsafe (instead of disabled) editing API is desired. + * @remarks + * When used, this means the TypeScript typing should err on the side of completeness (allow all inputs that could be valid). + * This introduces the risk that out-of-schema data could be allowed at compile time, and only error at runtime. + * + * @privateRemarks + * This only applies to APIs which input data which is expected to be in schema, since APIs outputting have easy mechanisms to do so in a type safe way even when the schema is unknown. + * In most cases that amounts to returning `TreeNode | TreeLeafValue`. + * + * This can be contrasted with the default behavior of TypeScript, which is to require the intersection of the possible types for input APIs, + * which for unknown schema defining input trees results in the `never` type. + * + * 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. + * @alpha + */ +export type CustomizedTyping = typeof CustomizedTyping; + +// TODO: use this to implement typing for field kinds? (Replace ApplyKind) +interface CustomTypes< + TReadWrite = unknown, + TInput = TReadWrite, + TOutput extends TReadWrite = TReadWrite, + TAllowDefault extends boolean = boolean, +> { + readonly allowDefault: TAllowDefault; + readonly input: TInput; + // Set to never to disable setter. + readonly readWrite: TReadWrite; + readonly output: TOutput; +} + +export type CustomizedSchemaTyping = TSchema & { + [CustomizedTyping]: TCustom; +}; + +export interface StrictTypes { + input: InsertableTreeFieldFromImplicitField; + readWrite: AssignableTreeFieldFromImplicitField; + output: TreeFieldFromImplicitField; + allowDefault: false; +} + +export interface RelaxedTypes { + input: InsertableTreeFieldFromImplicitField; + readWrite: TreeFieldFromImplicitField; + output: TreeFieldFromImplicitField; + allowDefault: undefined extends InsertableTreeFieldFromImplicitField + ? true + : false; +} + +export interface AnyTypes { + input: InsertableField; + readWrite: TreeNode | TreeLeafValue; + output: TreeNode | TreeLeafValue; + allowDefault: true; +} + +export interface UnknownTypes { + input: never; + readWrite: never; + output: TreeNode | TreeLeafValue; + allowDefault: false; +} + +export function relaxSchemaTyping< + TSchema extends ImplicitFieldSchema, + TCustom extends CustomTypes = { + input: InsertableTreeFieldFromImplicitField; + readWrite: TreeFieldFromImplicitField; + output: TreeFieldFromImplicitField; + allowDefault: undefined extends InsertableTreeFieldFromImplicitField + ? true + : false; + }, +>(schema: TSchema): CustomizedSchemaTyping { + return schema as CustomizedSchemaTyping; +} + +export function customizeSchemaTyping( + schema: TSchema, +): < + TCustom extends CustomTypes = { + input: InsertableTreeFieldFromImplicitField; + readWrite: TreeFieldFromImplicitField; + output: TreeFieldFromImplicitField; + allowDefault: undefined extends InsertableTreeFieldFromImplicitField + ? true + : false; + }, +>() => CustomizedSchemaTyping { + return () => schema as CustomizedSchemaTyping; +} + /** * Content which could be inserted into a tree. * From 735622f33cbb7936dfd7c0edb194cd427351dc67 Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Wed, 13 Nov 2024 15:25:18 -0800 Subject: [PATCH 08/37] customizeSchemaTyping --- packages/dds/tree/package.json | 9 + packages/dds/tree/src/simple-tree/index.ts | 1 + .../dds/tree/src/simple-tree/objectNode.ts | 8 +- .../dds/tree/src/simple-tree/schemaTypes.ts | 213 +++++++++++++----- .../tree/src/test/openPolymorphism.spec.ts | 67 ++++-- .../simple-tree/api/schemaFactory.spec.ts | 50 ++++ .../src/test/simple-tree/schemaTypes.spec.ts | 70 ++++-- .../dds/tree/src/test/simple-tree/utils.ts | 2 +- .../types/validateTreePrevious.generated.ts | 3 + 9 files changed, 324 insertions(+), 99 deletions(-) diff --git a/packages/dds/tree/package.json b/packages/dds/tree/package.json index 40cec6e98368..cabb3271c72b 100644 --- a/packages/dds/tree/package.json +++ b/packages/dds/tree/package.json @@ -241,6 +241,15 @@ "TypeAlias_InternalTypes_InsertableObjectFromSchemaRecord": { "backCompat": false, "forwardCompat": false + }, + "Interface_TreeView": { + "backCompat": false + }, + "TypeAlias_InternalTypes_ObjectFromSchemaRecord": { + "backCompat": false + }, + "TypeAlias_TreeObjectNode": { + "backCompat": false } }, "entrypoint": "public" diff --git a/packages/dds/tree/src/simple-tree/index.ts b/packages/dds/tree/src/simple-tree/index.ts index c550e788cf25..eef81a1957c0 100644 --- a/packages/dds/tree/src/simple-tree/index.ts +++ b/packages/dds/tree/src/simple-tree/index.ts @@ -146,6 +146,7 @@ export { type Input, type ReadableField, type ReadSchema, + customizeSchemaTyping, } from "./schemaTypes.js"; export { getTreeNodeForField, diff --git a/packages/dds/tree/src/simple-tree/objectNode.ts b/packages/dds/tree/src/simple-tree/objectNode.ts index 64bf8cb1272b..6401f6d386b5 100644 --- a/packages/dds/tree/src/simple-tree/objectNode.ts +++ b/packages/dds/tree/src/simple-tree/objectNode.ts @@ -26,7 +26,7 @@ import { normalizeFieldSchema, type ImplicitAllowedTypes, FieldKind, - type InsertableTreeNodeFromImplicitAllowedTypes, + type GetTypes, } from "./schemaTypes.js"; import { type TreeNodeSchema, @@ -64,7 +64,7 @@ export type ObjectFromSchemaRecord; + : Property]: AssignableTreeFieldFromImplicitField; } & { readonly [Property in keyof T]: TreeFieldFromImplicitField; }; @@ -82,9 +82,9 @@ export type AssignableTreeFieldFromImplicitField< TSchemaInput extends ImplicitFieldSchema, TSchema = UnionToIntersection, > = [TSchema] extends [FieldSchema] - ? ApplyKindAssignment, Kind> + ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] - ? InsertableTreeNodeFromImplicitAllowedTypes + ? GetTypes["readWrite"] : never; /** diff --git a/packages/dds/tree/src/simple-tree/schemaTypes.ts b/packages/dds/tree/src/simple-tree/schemaTypes.ts index 1ce7972b625b..8d65ff001e72 100644 --- a/packages/dds/tree/src/simple-tree/schemaTypes.ts +++ b/packages/dds/tree/src/simple-tree/schemaTypes.ts @@ -24,11 +24,11 @@ import type { TreeNodeSchemaClass, TreeNode, TreeNodeSchemaCore, + TreeNodeSchemaNonClass, } from "./core/index.js"; import type { FieldKey } from "../core/index.js"; import type { InsertableContent } from "./toMapTree.js"; import { isLazy, type FlexListToUnion, type LazyItem } from "./flexList.js"; -import type { AssignableTreeFieldFromImplicitField } from "./objectNode.js"; /** * Returns true if the given schema is a {@link TreeNodeSchemaClass}, or otherwise false if it is a {@link TreeNodeSchemaNonClass}. @@ -602,83 +602,147 @@ export const CustomizedTyping: unique symbol = Symbol("CustomizedTyping"); */ export type CustomizedTyping = typeof CustomizedTyping; -// TODO: use this to implement typing for field kinds? (Replace ApplyKind) -interface CustomTypes< - TReadWrite = unknown, - TInput = TReadWrite, - TOutput extends TReadWrite = TReadWrite, - TAllowDefault extends boolean = boolean, -> { - readonly allowDefault: TAllowDefault; - readonly input: TInput; +export interface CustomTypes { + readonly input: unknown; // Set to never to disable setter. - readonly readWrite: TReadWrite; - readonly output: TOutput; + readonly readWrite: TreeLeafValue | TreeNode; + readonly output: TreeLeafValue | TreeNode; } +/** + * @system @public + */ export type CustomizedSchemaTyping = TSchema & { [CustomizedTyping]: TCustom; }; -export interface StrictTypes { - input: InsertableTreeFieldFromImplicitField; - readWrite: AssignableTreeFieldFromImplicitField; - output: TreeFieldFromImplicitField; - allowDefault: false; +/** + * 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. + */ +export interface StrictTypes< + TSchema extends ImplicitAllowedTypes, + TInput = DefaultInsertableTreeNodeFromImplicitAllowedTypes, + TOutput extends TreeNode | TreeLeafValue = DefaultTreeNodeFromImplicitAllowedTypes, +> { + input: TInput; + readWrite: TInput extends never ? never : TOutput; + output: TOutput; } -export interface RelaxedTypes { - input: InsertableTreeFieldFromImplicitField; - readWrite: TreeFieldFromImplicitField; - output: TreeFieldFromImplicitField; - allowDefault: undefined extends InsertableTreeFieldFromImplicitField - ? true - : false; +/** + * 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 re allowed. + */ +export interface RelaxedTypes { + input: TSchema extends TreeNodeSchema + ? InsertableTypedNode + : TSchema extends AllowedTypes + ? TSchema[number] extends LazyItem + ? InsertableTypedNode + : never + : never; + readWrite: TreeNodeFromImplicitAllowedTypes; + output: TreeNodeFromImplicitAllowedTypes; } +/** + * Ignores schema, and allows any edit at compile time. + */ export interface AnyTypes { input: InsertableField; readWrite: TreeNode | TreeLeafValue; output: TreeNode | TreeLeafValue; - allowDefault: true; } +/** + * Ignores schema, forbidding all edits. + */ export interface UnknownTypes { input: never; readWrite: never; output: TreeNode | TreeLeafValue; - allowDefault: false; } -export function relaxSchemaTyping< - TSchema extends ImplicitFieldSchema, - TCustom extends CustomTypes = { - input: InsertableTreeFieldFromImplicitField; - readWrite: TreeFieldFromImplicitField; - output: TreeFieldFromImplicitField; - allowDefault: undefined extends InsertableTreeFieldFromImplicitField - ? true - : false; - }, ->(schema: TSchema): CustomizedSchemaTyping { - return schema as CustomizedSchemaTyping; +/** + * Replaces all typing with a single type. + */ +export interface SimpleReplacedType { + input: T; + readWrite: T; + output: T; } -export function customizeSchemaTyping( +export function customizeSchemaTyping( schema: TSchema, -): < - TCustom extends CustomTypes = { - input: InsertableTreeFieldFromImplicitField; - readWrite: TreeFieldFromImplicitField; - output: TreeFieldFromImplicitField; - allowDefault: undefined extends InsertableTreeFieldFromImplicitField - ? true - : false; - }, ->() => CustomizedSchemaTyping { - return () => schema as CustomizedSchemaTyping; +): 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 }; +} + +export interface Customizer { + /** + * The default {@link StrictTypes}, explicitly applied. + */ + strict(): CustomizedSchemaTyping>; + /** + * {@link RelaxedTypes}. + */ + relaxed(): CustomizedSchemaTyping>; + /** + * 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, + SimpleReplacedType + >; + + /** + * The same as {@link Customizer} except that more T values are allowed, even ones not known to be implemented by `TSchema`. + */ + simplifiedUnrestricted(): CustomizedSchemaTyping< + TSchema, + SimpleReplacedType + >; + + /** + * 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]; + } + >; } +export type GetTypes = [TSchema] extends [ + CustomizedSchemaTyping, +] + ? TCustom + : StrictTypes; + /** * Content which could be inserted into a tree. * @@ -764,10 +828,23 @@ export type ApplyKindInput = GetTypes["output"]; + +/** + * 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 @@ -827,6 +904,18 @@ export type TreeNodeFromImplicitAllowedTypes< */ export type Input = T; +/** + * Type of content that can be inserted into the tree for a node of the given schema. + * + * @typeparam TSchema - Schema to process. + * @remarks + * Defaults to {@link DefaultInsertableTreeNodeFromImplicitAllowedTypes}. + * Use {@link Customizer} to customize. + * @public + */ +export type InsertableTreeNodeFromImplicitAllowedTypes = + GetTypes["input"]; + /** * Type of content that can be inserted into the tree for a node of the given schema. * @@ -836,14 +925,15 @@ export type Input = T; * * @privateRemarks * This is a bit overly conservative, since cases like `A | [A]` give never and could give `A`. - * @public + * @system @public */ -export type InsertableTreeNodeFromImplicitAllowedTypes = - [TSchema] extends [TreeNodeSchema] - ? InsertableTypedNode - : [TSchema] extends [AllowedTypes] - ? InsertableTreeNodeFromAllowedTypes - : never; +export type DefaultInsertableTreeNodeFromImplicitAllowedTypes< + TSchema extends ImplicitAllowedTypes, +> = [TSchema] extends [TreeNodeSchema] + ? InsertableTypedNode + : [TSchema] extends [AllowedTypes] + ? InsertableTreeNodeFromAllowedTypes + : never; /** * Type of content that can be inserted into the tree for a node of the given schema. @@ -863,15 +953,20 @@ export type InsertableTreeNodeFromAllowedTypes = /** * Takes in `TreeNodeSchema[]` and returns a TypedNode union. + * @privateRemarks + * For unknown reasons, + * this has to test TreeNodeSchemaClass and TreeNodeSchemaNonClass independently (previously testing just TreeNodeSchema worked). * @public */ -export type NodeFromSchema = T extends TreeNodeSchema< +export type NodeFromSchema = T extends TreeNodeSchemaClass< string, NodeKind, infer TNode > ? TNode - : never; + : T extends TreeNodeSchemaNonClass + ? TNode + : never; /** * Data which can be used as a node to be inserted. diff --git a/packages/dds/tree/src/test/openPolymorphism.spec.ts b/packages/dds/tree/src/test/openPolymorphism.spec.ts index 36f9fae4bf44..4c5e5a3fa06a 100644 --- a/packages/dds/tree/src/test/openPolymorphism.spec.ts +++ b/packages/dds/tree/src/test/openPolymorphism.spec.ts @@ -7,21 +7,19 @@ import { strict as assert } from "node:assert"; import { SchemaFactory, - type AssignableTreeFieldFromImplicitField, + type InternalTreeNode, type NodeKind, type ObjectFromSchemaRecord, type TreeNode, type TreeNodeSchema, type Unhydrated, + type FieldKind, + type FieldSchema, + type Insertable, } from "../simple-tree/index.js"; import { Tree } from "../shared-tree/index.js"; import { validateUsageError } from "./utils.js"; -import { - customizeSchemaTyping, - type FieldKind, - type FieldSchema, - type InsertableField, -} from "../simple-tree/schemaTypes.js"; +import { customizeSchemaTyping } from "../simple-tree/index.js"; const sf = new SchemaFactory("test"); @@ -183,12 +181,7 @@ describe("Open Polymorphism design pattern examples and tests for them", () => { it("mutable static registry, safer editing API", () => { const ItemTypes: ItemSchema[] = []; class Container extends sf.object("Container", { - child: customizeSchemaTyping(sf.optional(ItemTypes))<{ - input: Item | undefined; - output: Item | undefined; - readWrite: Item | undefined; - allowDefault: true; - }>(), + child: sf.optional(customizeSchemaTyping(ItemTypes).simplified()), }) {} ItemTypes.push(TextItem); @@ -196,15 +189,24 @@ describe("Open Polymorphism design pattern examples and tests for them", () => { const container = new Container({ child: undefined }); const container2 = new Container({ child: TextItem.default() }); - type T3 = AssignableTreeFieldFromImplicitField<(typeof Container.info)["child"]>; - - // Enabled by custom setter + // Enabled by customizeSchemaTyping container.child = TextItem.default(); container.child = undefined; }); it("mutable static registry, safer editing API", () => { const ItemTypes: ItemSchema[] = []; + + type ContainerNodeWith = Container & + ObjectFromSchemaRecord<{ child: FieldSchema }>; + + type ContainerSchemaWith = { + info: { child: [T, ItemSchema] }; + new (item: { + child: InternalTreeNode | undefined | Insertable; + }): ContainerNodeWith; + } & typeof Container; + class Container extends sf.object("Container", { child: sf.optional(ItemTypes), }) { @@ -215,18 +217,32 @@ describe("Open Polymorphism design pattern examples and tests for them", () => { assert(Container.fields.get("child")?.allowedTypeSet.has(schema) ?? false); } + /** + * Narrows Container schema to one that allows T as a child. + * @remarks + * It is annoying to actually use this in practice. + * `withAllowItem` is easier in most cases. + * + */ public static allowItem( schema: T, - ): asserts this is new (item: { child?: InsertableField }) => Container { + ): asserts this is ContainerSchemaWith { assert(Container.fields.get("child")?.allowedTypeSet.has(schema) ?? false); } + /** + * Returns this, but types to allow schema. If schema is not allowed, throws. + */ + public static withAllowItem(schema: T): ContainerSchemaWith { + this.allowItem(schema); + return this; + } + public static fromItemAndSchema( schema: T, - item: InsertableField, - ): Container { - const x: typeof Container = Container; - x.allowItem(schema); + item: Insertable | undefined, + ): ContainerNodeWith { + const x = Container.withAllowItem(schema); return new x({ child: item }); } @@ -238,7 +254,7 @@ describe("Open Polymorphism design pattern examples and tests for them", () => { ItemTypes.push(TextItem); - Container.allowItem(TextItem); + // Container.allowItem(TextItem); const container: Container = new Container({ child: undefined }); const container2 = Container.fromItem(TextItem.default()); @@ -246,5 +262,12 @@ describe("Open Polymorphism design pattern examples and tests for them", () => { const read = container.child; container.child = TextItem.default(); container.child = undefined; + + const C2 = Container.withAllowItem(TextItem); + const xxx = new C2({ child: TextItem.default() }); + xxx.child = TextItem.default(); + + type XXXXX = Insertable<[typeof TextItem, ItemSchema]>; + type XXXXXx = Insertable<[ItemSchema]>; }); }); 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 2ecb2be48754..f9c507c4c5cd 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 @@ -39,6 +39,7 @@ import { // eslint-disable-next-line import/no-internal-modules } from "../../../simple-tree/api/schemaFactory.js"; import type { + DefaultTreeNodeFromImplicitAllowedTypes, NodeFromSchema, TreeFieldFromImplicitField, TreeNodeFromImplicitAllowedTypes, @@ -90,6 +91,46 @@ import { validateUsageError } from "../../utils.js"; type FromArray = TreeNodeFromImplicitAllowedTypes<[typeof Note, typeof Note]>; type _check5 = requireTrue>; } + // TreeNodeFromImplicitAllowedTypes class + { + class NoteCustomized extends schema.object("Note", { text: schema.string }) { + public test: boolean = false; + } + + type _check = requireAssignableTo; + type _checkNodeType = requireAssignableTo< + typeof NoteCustomized, + TreeNodeSchema + >; + + type TestDefault = DefaultTreeNodeFromImplicitAllowedTypes; + + type _checkDefault1 = requireAssignableTo; + type _checkDefault2 = requireTrue>; + type Instance = InstanceType; + type _checkInstance = requireTrue>; + + type Test = TreeNodeFromImplicitAllowedTypes; + type _check2 = requireTrue>; + + type _check3 = requireTrue< + areSafelyAssignable< + TreeNodeFromImplicitAllowedTypes<[typeof NoteCustomized]>, + NoteCustomized + > + >; + type _check4 = requireTrue< + areSafelyAssignable< + TreeNodeFromImplicitAllowedTypes<[() => typeof NoteCustomized]>, + NoteCustomized + > + >; + + type FromArray = TreeNodeFromImplicitAllowedTypes< + [typeof NoteCustomized, typeof NoteCustomized] + >; + type _check5 = requireTrue>; + } // TreeFieldFromImplicitField { @@ -108,6 +149,15 @@ import { validateUsageError } from "../../utils.js"; type FromArray = TreeFieldFromImplicitField<[typeof Note, typeof Note]>; type _check5 = requireTrue>; } + + // Subclassing + { + const n = new NodeMap(); + // These schema based types can have methods provided based on their node kind. In this case maps have `get`. + // The type returned from `get` is `Note | undefined`, and shows that way in the intellisense: + // It does not show some complex type expression equivalent to that which is a big improvement over the previous setup. + const item: Note | undefined = n.get("x"); + } } describe("schemaFactory", () => { diff --git a/packages/dds/tree/src/test/simple-tree/schemaTypes.spec.ts b/packages/dds/tree/src/test/simple-tree/schemaTypes.spec.ts index e9092290dd88..cfe57519d9e5 100644 --- a/packages/dds/tree/src/test/simple-tree/schemaTypes.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/schemaTypes.spec.ts @@ -12,20 +12,23 @@ import { SchemaFactory, type booleanSchema, type InsertableObjectFromSchemaRecord, + type NodeKind, type numberSchema, type stringSchema, type TreeNode, type TreeNodeSchema, + type TreeNodeSchemaClass, } from "../../simple-tree/index.js"; import { type AllowedTypes, + type DefaultInsertableTreeNodeFromImplicitAllowedTypes, + type DefaultTreeNodeFromImplicitAllowedTypes, type FieldSchema, type ImplicitAllowedTypes, type ImplicitFieldSchema, type InsertableField, type InsertableTreeFieldFromImplicitField, type InsertableTreeNodeFromAllowedTypes, - type InsertableTreeNodeFromImplicitAllowedTypes, type InsertableTypedNode, type NodeBuilderData, type NodeFromSchema, @@ -101,22 +104,24 @@ describe("schemaTypes", () => { type _check8 = requireTrue>; } - // InsertableTreeNodeFromImplicitAllowedTypes + // DefaultInsertableTreeNodeFromImplicitAllowedTypes { // Input - type I3 = InsertableTreeNodeFromImplicitAllowedTypes; - type I4 = InsertableTreeNodeFromImplicitAllowedTypes; - type I5 = InsertableTreeNodeFromImplicitAllowedTypes< + type I3 = DefaultInsertableTreeNodeFromImplicitAllowedTypes; + type I4 = DefaultInsertableTreeNodeFromImplicitAllowedTypes; + type I5 = DefaultInsertableTreeNodeFromImplicitAllowedTypes< typeof numberSchema | typeof stringSchema >; - type I8 = InsertableTreeNodeFromImplicitAllowedTypes; + type I8 = DefaultInsertableTreeNodeFromImplicitAllowedTypes; - type I6 = InsertableTreeNodeFromImplicitAllowedTypes< + type I6 = DefaultInsertableTreeNodeFromImplicitAllowedTypes< typeof numberSchema & typeof stringSchema >; - type I7 = InsertableTreeNodeFromImplicitAllowedTypes; + type I7 = DefaultInsertableTreeNodeFromImplicitAllowedTypes< + AllowedTypes & TreeNodeSchema + >; - type I9 = InsertableTreeNodeFromImplicitAllowedTypes; + type I9 = DefaultInsertableTreeNodeFromImplicitAllowedTypes; // These types should behave contravariantly type _check3 = requireTrue>; @@ -125,19 +130,19 @@ describe("schemaTypes", () => { type _check6 = requireTrue>; // Actual schema unions - type I12 = InsertableTreeNodeFromImplicitAllowedTypes; + type I12 = DefaultInsertableTreeNodeFromImplicitAllowedTypes; type _check12 = requireTrue>; - type I10 = InsertableTreeNodeFromImplicitAllowedTypes<[typeof numberSchema]>; + type I10 = DefaultInsertableTreeNodeFromImplicitAllowedTypes<[typeof numberSchema]>; type _check10 = requireTrue>; - type I11 = InsertableTreeNodeFromImplicitAllowedTypes< + type I11 = DefaultInsertableTreeNodeFromImplicitAllowedTypes< [typeof numberSchema, typeof stringSchema] >; type _check11 = requireTrue>; // boolean // boolean is sometimes a union of true and false, so it can break in its owns special ways - type I13 = InsertableTreeNodeFromImplicitAllowedTypes; + type I13 = DefaultInsertableTreeNodeFromImplicitAllowedTypes; type _check13 = requireTrue>; } @@ -215,6 +220,45 @@ describe("schemaTypes", () => { type I13 = InsertableField; type _check13 = requireTrue>; } + + // 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; + } + + // NodeFromSchema + { + class Simple extends schema.object("A", { x: [schema.number] }) {} + class Customized extends schema.object("B", { x: [schema.number] }) { + public customized = true; + } + + type TA = NodeFromSchema; + type _checkA = requireAssignableTo; + + type TB = NodeFromSchema; + type _checkB = requireAssignableTo; + + type TC = typeof Customized extends TreeNodeSchema + ? TNode + : never; + // @ts-expect-error It is unknown why this does not work, but NodeFromSchema works around this issue. If this starts compiling that workaround should be removed! + type _checkC = requireAssignableTo; + + type TD = typeof Customized extends TreeNodeSchemaClass + ? TNode + : never; + type _checkD = requireAssignableTo; + } } describe("insertable", () => { diff --git a/packages/dds/tree/src/test/simple-tree/utils.ts b/packages/dds/tree/src/test/simple-tree/utils.ts index 52e2f45f05fc..5b7b095223b1 100644 --- a/packages/dds/tree/src/test/simple-tree/utils.ts +++ b/packages/dds/tree/src/test/simple-tree/utils.ts @@ -88,8 +88,8 @@ export function describeHydration( title: string, runBoth: ( init: < - TInsertable, TSchema extends TreeNodeSchema, + TInsertable, >( schema: TSchema, tree: TInsertable, diff --git a/packages/dds/tree/src/test/types/validateTreePrevious.generated.ts b/packages/dds/tree/src/test/types/validateTreePrevious.generated.ts index 7a505d71d1fe..b4173a546c6a 100644 --- a/packages/dds/tree/src/test/types/validateTreePrevious.generated.ts +++ b/packages/dds/tree/src/test/types/validateTreePrevious.generated.ts @@ -536,6 +536,7 @@ declare type current_as_old_for_Interface_TreeNodeSchemaCore = requireAssignable * typeValidation.broken: * "Interface_TreeView": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_Interface_TreeView = requireAssignableTo>, TypeOnly>> /* @@ -1117,6 +1118,7 @@ declare type old_as_current_for_TypeAlias_InternalTypes_ObjectFromSchemaRecord = * typeValidation.broken: * "TypeAlias_InternalTypes_ObjectFromSchemaRecord": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_TypeAlias_InternalTypes_ObjectFromSchemaRecord = requireAssignableTo>, TypeOnly>> /* @@ -1495,6 +1497,7 @@ declare type old_as_current_for_TypeAlias_TreeObjectNode = requireAssignableTo>, TypeOnly>> /* From a1c3bd0c07d6820b4bd072be335a052d305bffdd Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Wed, 13 Nov 2024 15:35:22 -0800 Subject: [PATCH 09/37] Add customized narrowing example --- .../api/schemaFactory.examples.spec.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) 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 3e87b658ff70..fd24924ff021 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 @@ -14,6 +14,7 @@ import { treeNodeApi as Tree, TreeViewConfiguration, type TreeView, + customizeSchemaTyping, } from "../../../simple-tree/index.js"; import { TreeFactory } from "../../../treeFactory.js"; @@ -110,7 +111,7 @@ describe("Class based end to end example", () => { }); // Confirm that the alternative syntax for initialTree from the example above actually works. - it("using a mix of insertible content and nodes", () => { + it("using a mix of insertable content and nodes", () => { const factory = new TreeFactory({}); const theTree = factory.create( new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), @@ -127,4 +128,16 @@ describe("Class based end to end example", () => { }), ); }); + + it("customized narrowing", () => { + class Specific extends schema.object("Specific", { + s: customizeSchemaTyping(schema.string).simplified<"foo" | "bar">(), + }) {} + 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" }); + }); }); From beffae54c5cd66080b40cacbf53174e76ec6304e Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Wed, 13 Nov 2024 16:54:39 -0800 Subject: [PATCH 10/37] add another example, and more docs --- .../dds/tree/src/simple-tree/schemaTypes.ts | 29 +++++++++++++++-- .../api/schemaFactory.examples.spec.ts | 32 +++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/packages/dds/tree/src/simple-tree/schemaTypes.ts b/packages/dds/tree/src/simple-tree/schemaTypes.ts index 8d65ff001e72..b569a53bc64e 100644 --- a/packages/dds/tree/src/simple-tree/schemaTypes.ts +++ b/packages/dds/tree/src/simple-tree/schemaTypes.ts @@ -577,12 +577,15 @@ export const UnsafeUnknownSchema: unique symbol = Symbol("UnsafeUnknownSchema"); export type UnsafeUnknownSchema = typeof UnsafeUnknownSchema; /** - * {@inheritdoc (UnsafeUnknownSchema:type)} + * {@inheritdoc (CustomizedTyping:type)} * @alpha */ export const CustomizedTyping: unique symbol = Symbol("CustomizedTyping"); /** + * TODO: rewrite this doc and/or dedup this with UnsafeUnknownSchema. + * This system should be able to replace how UnsafeUnknownSchema is used. + * * A special type which can be provided to some APIs as the schema type parameter when schema cannot easily be provided at compile time and an unsafe (instead of disabled) editing API is desired. * @remarks * When used, this means the TypeScript typing should err on the side of completeness (allow all inputs that could be valid). @@ -602,10 +605,32 @@ export const CustomizedTyping: unique symbol = Symbol("CustomizedTyping"); */ export type CustomizedTyping = typeof CustomizedTyping; +/** + * Collection of schema aware types. + * @remarks + * This type is only uses as a type constraint. + * It's fields are similar to an unordered set of generic type parameters. + * @sealed @public + */ export interface CustomTypes { + /** + * Type used for inserting values. + */ readonly input: unknown; - // Set to never to disable setter. + /** + * 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. + */ 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; } 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 fd24924ff021..f0918af628a1 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 @@ -17,6 +17,7 @@ import { customizeSchemaTyping, } from "../../../simple-tree/index.js"; import { TreeFactory } from "../../../treeFactory.js"; +import type { areSafelyAssignable, 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"); @@ -140,4 +141,35 @@ describe("Class based end to end example", () => { // @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, + }) {} + 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>; + }); }); From 2836d9b9c68e0525e911f5710314f295ef69d22e Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Wed, 13 Nov 2024 18:18:13 -0800 Subject: [PATCH 11/37] add branding example --- .../api/schemaFactory.examples.spec.ts | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) 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 f0918af628a1..8e05a937b4e7 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 @@ -17,7 +17,13 @@ import { customizeSchemaTyping, } from "../../../simple-tree/index.js"; import { TreeFactory } from "../../../treeFactory.js"; -import type { areSafelyAssignable, requireTrue } from "../../../util/index.js"; +import { + brand, + type areSafelyAssignable, + type Brand, + type BrandedType, + 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"); @@ -172,4 +178,17 @@ describe("Class based end to end example", () => { const s2 = a[0]; type _check2 = requireTrue>; }); + + it("customized branding", () => { + type SpecialString = Brand; + + class Specific extends schema.object("Specific", { + s: customizeSchemaTyping(schema.string).simplified(), + }) {} + 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" }); + }); }); From eb49097e6678aa5122321f6a756b92a763edf3e9 Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Thu, 14 Nov 2024 16:05:51 -0800 Subject: [PATCH 12/37] Remove unneeded changes --- .../dds/tree/api-report/tree.alpha.api.md | 6 ++--- packages/dds/tree/api-report/tree.beta.api.md | 6 ++--- .../tree/api-report/tree.legacy.alpha.api.md | 6 ++--- .../tree/api-report/tree.legacy.public.api.md | 6 ++--- .../dds/tree/api-report/tree.public.api.md | 6 ++--- .../tree/src/simple-tree/api/schemaFactory.ts | 14 +++++++--- .../dds/tree/src/simple-tree/schemaTypes.ts | 14 +++------- .../tree/src/test/openPolymorphism.spec.ts | 4 --- .../shared-tree-core/sharedTreeCore.spec.ts | 4 +-- .../api/schemaFactory.examples.spec.ts | 3 +-- .../simple-tree/api/schemaFactory.spec.ts | 9 ------- .../src/test/simple-tree/schemaTypes.spec.ts | 27 ------------------- .../dds/tree/src/test/simple-tree/utils.ts | 2 +- 13 files changed, 34 insertions(+), 73 deletions(-) diff --git a/packages/dds/tree/api-report/tree.alpha.api.md b/packages/dds/tree/api-report/tree.alpha.api.md index aceb033a246c..81f5e79156d0 100644 --- a/packages/dds/tree/api-report/tree.alpha.api.md +++ b/packages/dds/tree/api-report/tree.alpha.api.md @@ -246,7 +246,7 @@ export type InsertableObjectFromSchemaRecordUnsafe> = TSchema extends FieldSchema ? ApplyKindInput, Kind, true> : TSchema extends ImplicitAllowedTypes ? InsertableTreeNodeFromImplicitAllowedTypes : never; +export type InsertableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; // @public export type InsertableTreeFieldFromImplicitFieldUnsafe, TSchema = UnionToIntersection> = [TSchema] extends [FieldSchemaUnsafe] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypesUnsafe : never; @@ -451,7 +451,7 @@ export interface NodeChangedData { } // @public -export type NodeFromSchema = T extends TreeNodeSchemaClass ? TNode : T extends TreeNodeSchemaNonClass ? TNode : never; +export type NodeFromSchema = T extends TreeNodeSchema ? TNode : never; // @public type NodeFromSchemaUnsafe> = T extends TreeNodeSchemaUnsafe ? TNode : never; @@ -610,7 +610,7 @@ export class SchemaFactory; readonly null: TreeNodeSchemaNonClass<"com.fluidframework.leaf.null", NodeKind.Leaf, null, null, true, unknown, never>; readonly number: TreeNodeSchemaNonClass<"com.fluidframework.leaf.number", NodeKind.Leaf, number, number, true, unknown, never>; - object>(name: Name, fields: T): ObjectNodeSchema, T, true>; + object>(name: Name, fields: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T>; objectRecursive>>(name: Name, t: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNodeUnsafe>, object & { readonly [Property in keyof T as FieldHasDefaultUnsafe extends false ? Property : never]: InsertableTreeFieldFromImplicitFieldUnsafe>; } & { readonly [Property_1 in keyof T as FieldHasDefaultUnsafe extends true ? Property_1 : never]?: InsertableTreeFieldFromImplicitFieldUnsafe> | undefined; }, false, T>; optional(t: T, props?: Omit, "defaultProvider">): FieldSchema; optionalRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; diff --git a/packages/dds/tree/api-report/tree.beta.api.md b/packages/dds/tree/api-report/tree.beta.api.md index 49b5a9a624ea..746451a41654 100644 --- a/packages/dds/tree/api-report/tree.beta.api.md +++ b/packages/dds/tree/api-report/tree.beta.api.md @@ -132,7 +132,7 @@ export type InsertableObjectFromSchemaRecordUnsafe> = TSchema extends FieldSchema ? ApplyKindInput, Kind, true> : TSchema extends ImplicitAllowedTypes ? InsertableTreeNodeFromImplicitAllowedTypes : never; +export type InsertableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; // @public export type InsertableTreeFieldFromImplicitFieldUnsafe, TSchema = UnionToIntersection> = [TSchema] extends [FieldSchemaUnsafe] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypesUnsafe : never; @@ -259,7 +259,7 @@ export interface NodeChangedData { } // @public -export type NodeFromSchema = T extends TreeNodeSchemaClass ? TNode : T extends TreeNodeSchemaNonClass ? TNode : never; +export type NodeFromSchema = T extends TreeNodeSchema ? TNode : never; // @public type NodeFromSchemaUnsafe> = T extends TreeNodeSchemaUnsafe ? TNode : never; @@ -398,7 +398,7 @@ export class SchemaFactory; readonly null: TreeNodeSchemaNonClass<"com.fluidframework.leaf.null", NodeKind.Leaf, null, null, true, unknown, never>; readonly number: TreeNodeSchemaNonClass<"com.fluidframework.leaf.number", NodeKind.Leaf, number, number, true, unknown, never>; - object>(name: Name, fields: T): ObjectNodeSchema, T, true>; + object>(name: Name, fields: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T>; objectRecursive>>(name: Name, t: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNodeUnsafe>, object & { readonly [Property in keyof T as FieldHasDefaultUnsafe extends false ? Property : never]: InsertableTreeFieldFromImplicitFieldUnsafe>; } & { readonly [Property_1 in keyof T as FieldHasDefaultUnsafe extends true ? Property_1 : never]?: InsertableTreeFieldFromImplicitFieldUnsafe> | undefined; }, false, T>; optional(t: T, props?: Omit, "defaultProvider">): FieldSchema; optionalRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; diff --git a/packages/dds/tree/api-report/tree.legacy.alpha.api.md b/packages/dds/tree/api-report/tree.legacy.alpha.api.md index c601b4b867b7..d357bd442d9a 100644 --- a/packages/dds/tree/api-report/tree.legacy.alpha.api.md +++ b/packages/dds/tree/api-report/tree.legacy.alpha.api.md @@ -132,7 +132,7 @@ export type InsertableObjectFromSchemaRecordUnsafe> = TSchema extends FieldSchema ? ApplyKindInput, Kind, true> : TSchema extends ImplicitAllowedTypes ? InsertableTreeNodeFromImplicitAllowedTypes : never; +export type InsertableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; // @public export type InsertableTreeFieldFromImplicitFieldUnsafe, TSchema = UnionToIntersection> = [TSchema] extends [FieldSchemaUnsafe] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypesUnsafe : never; @@ -254,7 +254,7 @@ type NodeBuilderData> = type NodeBuilderDataUnsafe> = T extends TreeNodeSchemaUnsafe ? TBuild : never; // @public -export type NodeFromSchema = T extends TreeNodeSchemaClass ? TNode : T extends TreeNodeSchemaNonClass ? TNode : never; +export type NodeFromSchema = T extends TreeNodeSchema ? TNode : never; // @public type NodeFromSchemaUnsafe> = T extends TreeNodeSchemaUnsafe ? TNode : never; @@ -393,7 +393,7 @@ export class SchemaFactory; readonly null: TreeNodeSchemaNonClass<"com.fluidframework.leaf.null", NodeKind.Leaf, null, null, true, unknown, never>; readonly number: TreeNodeSchemaNonClass<"com.fluidframework.leaf.number", NodeKind.Leaf, number, number, true, unknown, never>; - object>(name: Name, fields: T): ObjectNodeSchema, T, true>; + object>(name: Name, fields: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T>; objectRecursive>>(name: Name, t: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNodeUnsafe>, object & { readonly [Property in keyof T as FieldHasDefaultUnsafe extends false ? Property : never]: InsertableTreeFieldFromImplicitFieldUnsafe>; } & { readonly [Property_1 in keyof T as FieldHasDefaultUnsafe extends true ? Property_1 : never]?: InsertableTreeFieldFromImplicitFieldUnsafe> | undefined; }, false, T>; optional(t: T, props?: Omit, "defaultProvider">): FieldSchema; optionalRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; 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 11be9561c8fa..6be0f367cae3 100644 --- a/packages/dds/tree/api-report/tree.legacy.public.api.md +++ b/packages/dds/tree/api-report/tree.legacy.public.api.md @@ -132,7 +132,7 @@ export type InsertableObjectFromSchemaRecordUnsafe> = TSchema extends FieldSchema ? ApplyKindInput, Kind, true> : TSchema extends ImplicitAllowedTypes ? InsertableTreeNodeFromImplicitAllowedTypes : never; +export type InsertableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; // @public export type InsertableTreeFieldFromImplicitFieldUnsafe, TSchema = UnionToIntersection> = [TSchema] extends [FieldSchemaUnsafe] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypesUnsafe : never; @@ -254,7 +254,7 @@ type NodeBuilderData> = type NodeBuilderDataUnsafe> = T extends TreeNodeSchemaUnsafe ? TBuild : never; // @public -export type NodeFromSchema = T extends TreeNodeSchemaClass ? TNode : T extends TreeNodeSchemaNonClass ? TNode : never; +export type NodeFromSchema = T extends TreeNodeSchema ? TNode : never; // @public type NodeFromSchemaUnsafe> = T extends TreeNodeSchemaUnsafe ? TNode : never; @@ -393,7 +393,7 @@ export class SchemaFactory; readonly null: TreeNodeSchemaNonClass<"com.fluidframework.leaf.null", NodeKind.Leaf, null, null, true, unknown, never>; readonly number: TreeNodeSchemaNonClass<"com.fluidframework.leaf.number", NodeKind.Leaf, number, number, true, unknown, never>; - object>(name: Name, fields: T): ObjectNodeSchema, T, true>; + object>(name: Name, fields: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T>; objectRecursive>>(name: Name, t: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNodeUnsafe>, object & { readonly [Property in keyof T as FieldHasDefaultUnsafe extends false ? Property : never]: InsertableTreeFieldFromImplicitFieldUnsafe>; } & { readonly [Property_1 in keyof T as FieldHasDefaultUnsafe extends true ? Property_1 : never]?: InsertableTreeFieldFromImplicitFieldUnsafe> | undefined; }, false, T>; optional(t: T, props?: Omit, "defaultProvider">): FieldSchema; optionalRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; diff --git a/packages/dds/tree/api-report/tree.public.api.md b/packages/dds/tree/api-report/tree.public.api.md index 11be9561c8fa..6be0f367cae3 100644 --- a/packages/dds/tree/api-report/tree.public.api.md +++ b/packages/dds/tree/api-report/tree.public.api.md @@ -132,7 +132,7 @@ export type InsertableObjectFromSchemaRecordUnsafe> = TSchema extends FieldSchema ? ApplyKindInput, Kind, true> : TSchema extends ImplicitAllowedTypes ? InsertableTreeNodeFromImplicitAllowedTypes : never; +export type InsertableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; // @public export type InsertableTreeFieldFromImplicitFieldUnsafe, TSchema = UnionToIntersection> = [TSchema] extends [FieldSchemaUnsafe] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypesUnsafe : never; @@ -254,7 +254,7 @@ type NodeBuilderData> = type NodeBuilderDataUnsafe> = T extends TreeNodeSchemaUnsafe ? TBuild : never; // @public -export type NodeFromSchema = T extends TreeNodeSchemaClass ? TNode : T extends TreeNodeSchemaNonClass ? TNode : never; +export type NodeFromSchema = T extends TreeNodeSchema ? TNode : never; // @public type NodeFromSchemaUnsafe> = T extends TreeNodeSchemaUnsafe ? TNode : never; @@ -393,7 +393,7 @@ export class SchemaFactory; readonly null: TreeNodeSchemaNonClass<"com.fluidframework.leaf.null", NodeKind.Leaf, null, null, true, unknown, never>; readonly number: TreeNodeSchemaNonClass<"com.fluidframework.leaf.number", NodeKind.Leaf, number, number, true, unknown, never>; - object>(name: Name, fields: T): ObjectNodeSchema, T, true>; + object>(name: Name, fields: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T>; objectRecursive>>(name: Name, t: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNodeUnsafe>, object & { readonly [Property in keyof T as FieldHasDefaultUnsafe extends false ? Property : never]: InsertableTreeFieldFromImplicitFieldUnsafe>; } & { readonly [Property_1 in keyof T as FieldHasDefaultUnsafe extends true ? Property_1 : never]?: InsertableTreeFieldFromImplicitFieldUnsafe> | undefined; }, false, T>; optional(t: T, props?: Omit, "defaultProvider">): FieldSchema; optionalRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; diff --git a/packages/dds/tree/src/simple-tree/api/schemaFactory.ts b/packages/dds/tree/src/simple-tree/api/schemaFactory.ts index fbb1b6747d9c..8a8efaf86628 100644 --- a/packages/dds/tree/src/simple-tree/api/schemaFactory.ts +++ b/packages/dds/tree/src/simple-tree/api/schemaFactory.ts @@ -74,7 +74,6 @@ import type { import { createFieldSchemaUnsafe } from "./schemaFactoryRecursive.js"; import { TreeNodeValid } from "../treeNodeValid.js"; import { isLazy } from "../flexList.js"; -import type { ObjectNodeSchema } from "../objectNodeTypes.js"; /** * Gets the leaf domain schema compatible with a given {@link TreeValue}. */ @@ -323,7 +322,17 @@ export class SchemaFactory< public object< const Name extends TName, const T extends RestrictiveStringRecord, - >(name: Name, fields: T): ObjectNodeSchema, T, true> { + >( + name: Name, + fields: T, + ): TreeNodeSchemaClass< + ScopedSchemaName, + NodeKind.Object, + TreeObjectNode>, + object & InsertableObjectFromSchemaRecord, + true, + T + > { return objectSchema(this.scoped(name), fields, true); } @@ -386,7 +395,6 @@ export class SchemaFactory< >; /** - * The implementation (this doc does nothing but make JS-doc lint happy). * @privateRemarks * This should return `TreeNodeSchemaBoth`, however TypeScript gives an error if one of the overloads implicitly up-casts the return type of the implementation. * This seems like a TypeScript bug getting variance backwards for overload return types since it's erroring when the relation between the overload diff --git a/packages/dds/tree/src/simple-tree/schemaTypes.ts b/packages/dds/tree/src/simple-tree/schemaTypes.ts index b569a53bc64e..f23c113dbfa6 100644 --- a/packages/dds/tree/src/simple-tree/schemaTypes.ts +++ b/packages/dds/tree/src/simple-tree/schemaTypes.ts @@ -24,7 +24,6 @@ import type { TreeNodeSchemaClass, TreeNode, TreeNodeSchemaCore, - TreeNodeSchemaNonClass, } from "./core/index.js"; import type { FieldKey } from "../core/index.js"; import type { InsertableContent } from "./toMapTree.js"; @@ -544,9 +543,9 @@ export type TreeFieldFromImplicitField, -> = TSchema extends FieldSchema +> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> - : TSchema extends ImplicitAllowedTypes + : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; @@ -978,20 +977,15 @@ export type InsertableTreeNodeFromAllowedTypes = /** * Takes in `TreeNodeSchema[]` and returns a TypedNode union. - * @privateRemarks - * For unknown reasons, - * this has to test TreeNodeSchemaClass and TreeNodeSchemaNonClass independently (previously testing just TreeNodeSchema worked). * @public */ -export type NodeFromSchema = T extends TreeNodeSchemaClass< +export type NodeFromSchema = T extends TreeNodeSchema< string, NodeKind, infer TNode > ? TNode - : T extends TreeNodeSchemaNonClass - ? TNode - : never; + : never; /** * Data which can be used as a node to be inserted. diff --git a/packages/dds/tree/src/test/openPolymorphism.spec.ts b/packages/dds/tree/src/test/openPolymorphism.spec.ts index b663007fd6f7..f7167494b1cf 100644 --- a/packages/dds/tree/src/test/openPolymorphism.spec.ts +++ b/packages/dds/tree/src/test/openPolymorphism.spec.ts @@ -7,15 +7,11 @@ import { strict as assert } from "node:assert"; import { SchemaFactory, - type InternalTreeNode, type NodeKind, type ObjectFromSchemaRecord, type TreeNode, type TreeNodeSchema, type Unhydrated, - type FieldKind, - type FieldSchema, - type Insertable, } from "../simple-tree/index.js"; import { Tree } from "../shared-tree/index.js"; import { validateUsageError } from "./utils.js"; diff --git a/packages/dds/tree/src/test/shared-tree-core/sharedTreeCore.spec.ts b/packages/dds/tree/src/test/shared-tree-core/sharedTreeCore.spec.ts index 7e604039077a..ebc5185e8229 100644 --- a/packages/dds/tree/src/test/shared-tree-core/sharedTreeCore.spec.ts +++ b/packages/dds/tree/src/test/shared-tree-core/sharedTreeCore.spec.ts @@ -294,9 +294,9 @@ describe("SharedTreeCore", () => { }); const sf = new SchemaFactory("0x4a6 repro"); - class TestNode extends sf.objectRecursive("test node", { + const TestNode = sf.objectRecursive("test node", { child: sf.optionalRecursive([() => TestNode, sf.number]), - }) {} + }); const tree2 = await factory.load( dataStoreRuntime2, 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 8e05a937b4e7..9d17a769389c 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 @@ -21,7 +21,6 @@ import { brand, type areSafelyAssignable, type Brand, - type BrandedType, type requireTrue, } from "../../../util/index.js"; @@ -118,7 +117,7 @@ describe("Class based end to end example", () => { }); // Confirm that the alternative syntax for initialTree from the example above actually works. - it("using a mix of insertable content and nodes", () => { + it("using a mix of insertible content and nodes", () => { const factory = new TreeFactory({}); const theTree = factory.create( new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), 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 f9c507c4c5cd..85aa3f3849e0 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 @@ -149,15 +149,6 @@ import { validateUsageError } from "../../utils.js"; type FromArray = TreeFieldFromImplicitField<[typeof Note, typeof Note]>; type _check5 = requireTrue>; } - - // Subclassing - { - const n = new NodeMap(); - // These schema based types can have methods provided based on their node kind. In this case maps have `get`. - // The type returned from `get` is `Note | undefined`, and shows that way in the intellisense: - // It does not show some complex type expression equivalent to that which is a big improvement over the previous setup. - const item: Note | undefined = n.get("x"); - } } describe("schemaFactory", () => { diff --git a/packages/dds/tree/src/test/simple-tree/schemaTypes.spec.ts b/packages/dds/tree/src/test/simple-tree/schemaTypes.spec.ts index cfe57519d9e5..f823fb0cb7bb 100644 --- a/packages/dds/tree/src/test/simple-tree/schemaTypes.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/schemaTypes.spec.ts @@ -12,12 +12,10 @@ import { SchemaFactory, type booleanSchema, type InsertableObjectFromSchemaRecord, - type NodeKind, type numberSchema, type stringSchema, type TreeNode, type TreeNodeSchema, - type TreeNodeSchemaClass, } from "../../simple-tree/index.js"; import { type AllowedTypes, @@ -234,31 +232,6 @@ describe("schemaTypes", () => { type TB = DefaultTreeNodeFromImplicitAllowedTypes; type _checkB = requireAssignableTo; } - - // NodeFromSchema - { - class Simple extends schema.object("A", { x: [schema.number] }) {} - class Customized extends schema.object("B", { x: [schema.number] }) { - public customized = true; - } - - type TA = NodeFromSchema; - type _checkA = requireAssignableTo; - - type TB = NodeFromSchema; - type _checkB = requireAssignableTo; - - type TC = typeof Customized extends TreeNodeSchema - ? TNode - : never; - // @ts-expect-error It is unknown why this does not work, but NodeFromSchema works around this issue. If this starts compiling that workaround should be removed! - type _checkC = requireAssignableTo; - - type TD = typeof Customized extends TreeNodeSchemaClass - ? TNode - : never; - type _checkD = requireAssignableTo; - } } describe("insertable", () => { diff --git a/packages/dds/tree/src/test/simple-tree/utils.ts b/packages/dds/tree/src/test/simple-tree/utils.ts index 5b7b095223b1..52e2f45f05fc 100644 --- a/packages/dds/tree/src/test/simple-tree/utils.ts +++ b/packages/dds/tree/src/test/simple-tree/utils.ts @@ -88,8 +88,8 @@ export function describeHydration( title: string, runBoth: ( init: < - TSchema extends TreeNodeSchema, TInsertable, + TSchema extends TreeNodeSchema, >( schema: TSchema, tree: TInsertable, From 10c5ef890fc79fc8820d5335f58ed45bdf9a1cc8 Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Thu, 14 Nov 2024 17:30:23 -0800 Subject: [PATCH 13/37] Fix package API --- packages/dds/tree/.vscode/settings.json | 1 + .../dds/tree/api-report/tree.alpha.api.md | 65 +++++++++++++ packages/dds/tree/api-report/tree.beta.api.md | 62 ++++++++++++ .../tree/api-report/tree.legacy.alpha.api.md | 62 ++++++++++++ .../tree/api-report/tree.legacy.public.api.md | 62 ++++++++++++ .../dds/tree/api-report/tree.public.api.md | 62 ++++++++++++ packages/dds/tree/src/index.ts | 9 ++ packages/dds/tree/src/simple-tree/index.ts | 8 ++ .../dds/tree/src/simple-tree/schemaTypes.ts | 96 +++++++++---------- .../src/test/simple-tree/schemaTypes.spec.ts | 21 ++++ .../api-report/fluid-framework.alpha.api.md | 71 +++++++++++++- .../api-report/fluid-framework.beta.api.md | 68 ++++++++++++- .../fluid-framework.legacy.alpha.api.md | 68 ++++++++++++- .../fluid-framework.legacy.public.api.md | 68 ++++++++++++- .../api-report/fluid-framework.public.api.md | 68 ++++++++++++- 15 files changed, 726 insertions(+), 65 deletions(-) diff --git a/packages/dds/tree/.vscode/settings.json b/packages/dds/tree/.vscode/settings.json index 28312760378d..a2785069f4af 100644 --- a/packages/dds/tree/.vscode/settings.json +++ b/packages/dds/tree/.vscode/settings.json @@ -19,6 +19,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 81f5e79156d0..8cda088dbf6d 100644 --- a/packages/dds/tree/api-report/tree.alpha.api.md +++ b/packages/dds/tree/api-report/tree.alpha.api.md @@ -73,10 +73,60 @@ export type ConciseTree = Exclude; }; +// @public (undocumented) +export type CustomizedSchemaTyping = TSchema & { + [CustomizedTyping]: TCustom; +}; + +// @public +export const CustomizedTyping: unique symbol; + +// @public +export type CustomizedTyping = typeof CustomizedTyping; + +// @public @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 @sealed +export function customizeSchemaTyping(schema: TSchema): Customizer; + +// @public @sealed +export interface CustomTypes { + readonly input: unknown; + readonly output: TreeLeafValue | TreeNode; + readonly readWrite: TreeLeafValue | TreeNode; +} + +// @public +export type DefaultInsertableTreeNodeFromImplicitAllowedTypes = [TSchema] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; + // @public @sealed interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> { } +// @public +export type DefaultTreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; + // @alpha export interface EncodeOptions { readonly useStoredKeys?: boolean; @@ -195,6 +245,11 @@ export function getBranch(v // @alpha export function getJsonSchema(schema: ImplicitFieldSchema): JsonTreeSchema; +// @public +export type GetTypes = [TSchema] extends [ +CustomizedSchemaTyping +] ? TCustom : StrictTypes; + // @alpha export interface ICodecOptions { readonly jsonValidator: JsonValidator; @@ -652,6 +707,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; +} + // @public export type TransactionConstraint = NodeInDocumentConstraint; diff --git a/packages/dds/tree/api-report/tree.beta.api.md b/packages/dds/tree/api-report/tree.beta.api.md index 746451a41654..d0825a1632e6 100644 --- a/packages/dds/tree/api-report/tree.beta.api.md +++ b/packages/dds/tree/api-report/tree.beta.api.md @@ -43,10 +43,57 @@ export interface CommitMetadata { readonly kind: CommitKind; } +// @public (undocumented) +export type CustomizedSchemaTyping = TSchema & { + [CustomizedTyping]: TCustom; +}; + +// @public +export const CustomizedTyping: unique symbol; + +// @public +export type CustomizedTyping = typeof CustomizedTyping; + +// @public @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>; +} + +// @public @sealed +export interface CustomTypes { + readonly input: unknown; + readonly output: TreeLeafValue | TreeNode; + readonly readWrite: TreeLeafValue | TreeNode; +} + +// @public +export type DefaultInsertableTreeNodeFromImplicitAllowedTypes = [TSchema] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; + // @public @sealed interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> { } +// @public +export type DefaultTreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; + // @public type ExtractItemType = Item extends () => infer Result ? Result : Item; @@ -105,6 +152,11 @@ type FlexList = readonly LazyItem[]; // @public type FlexListToUnion = ExtractItemType; +// @public +export type GetTypes = [TSchema] extends [ +CustomizedSchemaTyping +] ? TCustom : StrictTypes; + // @public export type ImplicitAllowedTypes = AllowedTypes | TreeNodeSchema; @@ -411,6 +463,16 @@ export class SchemaFactory = TScope extends undefined ? `${TName}` : `${TScope}.${TName}`; +// @public @sealed +export interface StrictTypes, TOutput extends TreeNode | TreeLeafValue = DefaultTreeNodeFromImplicitAllowedTypes> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TInput extends never ? never : TOutput; +} + // @public export type TransactionConstraint = NodeInDocumentConstraint; diff --git a/packages/dds/tree/api-report/tree.legacy.alpha.api.md b/packages/dds/tree/api-report/tree.legacy.alpha.api.md index d357bd442d9a..e3affd0037ca 100644 --- a/packages/dds/tree/api-report/tree.legacy.alpha.api.md +++ b/packages/dds/tree/api-report/tree.legacy.alpha.api.md @@ -43,10 +43,57 @@ export interface CommitMetadata { readonly kind: CommitKind; } +// @public (undocumented) +export type CustomizedSchemaTyping = TSchema & { + [CustomizedTyping]: TCustom; +}; + +// @public +export const CustomizedTyping: unique symbol; + +// @public +export type CustomizedTyping = typeof CustomizedTyping; + +// @public @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>; +} + +// @public @sealed +export interface CustomTypes { + readonly input: unknown; + readonly output: TreeLeafValue | TreeNode; + readonly readWrite: TreeLeafValue | TreeNode; +} + +// @public +export type DefaultInsertableTreeNodeFromImplicitAllowedTypes = [TSchema] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; + // @public @sealed interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> { } +// @public +export type DefaultTreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; + // @public type ExtractItemType = Item extends () => infer Result ? Result : Item; @@ -105,6 +152,11 @@ type FlexList = readonly LazyItem[]; // @public type FlexListToUnion = ExtractItemType; +// @public +export type GetTypes = [TSchema] extends [ +CustomizedSchemaTyping +] ? TCustom : StrictTypes; + // @public export type ImplicitAllowedTypes = AllowedTypes | TreeNodeSchema; @@ -409,6 +461,16 @@ type ScopedSchemaName & SharedObjectKind; +// @public @sealed +export interface StrictTypes, TOutput extends TreeNode | TreeLeafValue = DefaultTreeNodeFromImplicitAllowedTypes> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TInput extends never ? never : TOutput; +} + // @public export type TransactionConstraint = NodeInDocumentConstraint; 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 6be0f367cae3..3ab4a56ffe33 100644 --- a/packages/dds/tree/api-report/tree.legacy.public.api.md +++ b/packages/dds/tree/api-report/tree.legacy.public.api.md @@ -43,10 +43,57 @@ export interface CommitMetadata { readonly kind: CommitKind; } +// @public (undocumented) +export type CustomizedSchemaTyping = TSchema & { + [CustomizedTyping]: TCustom; +}; + +// @public +export const CustomizedTyping: unique symbol; + +// @public +export type CustomizedTyping = typeof CustomizedTyping; + +// @public @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>; +} + +// @public @sealed +export interface CustomTypes { + readonly input: unknown; + readonly output: TreeLeafValue | TreeNode; + readonly readWrite: TreeLeafValue | TreeNode; +} + +// @public +export type DefaultInsertableTreeNodeFromImplicitAllowedTypes = [TSchema] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; + // @public @sealed interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> { } +// @public +export type DefaultTreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; + // @public type ExtractItemType = Item extends () => infer Result ? Result : Item; @@ -105,6 +152,11 @@ type FlexList = readonly LazyItem[]; // @public type FlexListToUnion = ExtractItemType; +// @public +export type GetTypes = [TSchema] extends [ +CustomizedSchemaTyping +] ? TCustom : StrictTypes; + // @public export type ImplicitAllowedTypes = AllowedTypes | TreeNodeSchema; @@ -406,6 +458,16 @@ export class SchemaFactory = TScope extends undefined ? `${TName}` : `${TScope}.${TName}`; +// @public @sealed +export interface StrictTypes, TOutput extends TreeNode | TreeLeafValue = DefaultTreeNodeFromImplicitAllowedTypes> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TInput extends never ? never : TOutput; +} + // @public export type TransactionConstraint = NodeInDocumentConstraint; diff --git a/packages/dds/tree/api-report/tree.public.api.md b/packages/dds/tree/api-report/tree.public.api.md index 6be0f367cae3..3ab4a56ffe33 100644 --- a/packages/dds/tree/api-report/tree.public.api.md +++ b/packages/dds/tree/api-report/tree.public.api.md @@ -43,10 +43,57 @@ export interface CommitMetadata { readonly kind: CommitKind; } +// @public (undocumented) +export type CustomizedSchemaTyping = TSchema & { + [CustomizedTyping]: TCustom; +}; + +// @public +export const CustomizedTyping: unique symbol; + +// @public +export type CustomizedTyping = typeof CustomizedTyping; + +// @public @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>; +} + +// @public @sealed +export interface CustomTypes { + readonly input: unknown; + readonly output: TreeLeafValue | TreeNode; + readonly readWrite: TreeLeafValue | TreeNode; +} + +// @public +export type DefaultInsertableTreeNodeFromImplicitAllowedTypes = [TSchema] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; + // @public @sealed interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> { } +// @public +export type DefaultTreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; + // @public type ExtractItemType = Item extends () => infer Result ? Result : Item; @@ -105,6 +152,11 @@ type FlexList = readonly LazyItem[]; // @public type FlexListToUnion = ExtractItemType; +// @public +export type GetTypes = [TSchema] extends [ +CustomizedSchemaTyping +] ? TCustom : StrictTypes; + // @public export type ImplicitAllowedTypes = AllowedTypes | TreeNodeSchema; @@ -406,6 +458,16 @@ export class SchemaFactory = TScope extends undefined ? `${TName}` : `${TScope}.${TName}`; +// @public @sealed +export interface StrictTypes, TOutput extends TreeNode | TreeLeafValue = DefaultTreeNodeFromImplicitAllowedTypes> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TInput extends never ? never : TOutput; +} + // @public export type TransactionConstraint = NodeInDocumentConstraint; diff --git a/packages/dds/tree/src/index.ts b/packages/dds/tree/src/index.ts index 798985141a62..df3063cab869 100644 --- a/packages/dds/tree/src/index.ts +++ b/packages/dds/tree/src/index.ts @@ -193,6 +193,15 @@ export { asTreeViewAlpha, type AssignableTreeFieldFromImplicitField, type ApplyKindAssignment, + type DefaultTreeNodeFromImplicitAllowedTypes, + type Customizer, + type GetTypes, + type StrictTypes, + type CustomTypes, + type CustomizedSchemaTyping, + CustomizedTyping, + type DefaultInsertableTreeNodeFromImplicitAllowedTypes, + customizeSchemaTyping, } from "./simple-tree/index.js"; export { SharedTree, diff --git a/packages/dds/tree/src/simple-tree/index.ts b/packages/dds/tree/src/simple-tree/index.ts index eef81a1957c0..e392cdd4262f 100644 --- a/packages/dds/tree/src/simple-tree/index.ts +++ b/packages/dds/tree/src/simple-tree/index.ts @@ -147,6 +147,14 @@ export { type ReadableField, type ReadSchema, customizeSchemaTyping, + type DefaultTreeNodeFromImplicitAllowedTypes, + type Customizer, + type GetTypes, + type StrictTypes, + type CustomTypes, + type CustomizedSchemaTyping, + CustomizedTyping, + type DefaultInsertableTreeNodeFromImplicitAllowedTypes, } from "./schemaTypes.js"; export { getTreeNodeForField, diff --git a/packages/dds/tree/src/simple-tree/schemaTypes.ts b/packages/dds/tree/src/simple-tree/schemaTypes.ts index f23c113dbfa6..1da16796ea7d 100644 --- a/packages/dds/tree/src/simple-tree/schemaTypes.ts +++ b/packages/dds/tree/src/simple-tree/schemaTypes.ts @@ -577,7 +577,7 @@ export type UnsafeUnknownSchema = typeof UnsafeUnknownSchema; /** * {@inheritdoc (CustomizedTyping:type)} - * @alpha + * @system @public */ export const CustomizedTyping: unique symbol = Symbol("CustomizedTyping"); @@ -600,7 +600,7 @@ export const CustomizedTyping: unique symbol = Symbol("CustomizedTyping"); * 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. - * @alpha + * @system @public */ export type CustomizedTyping = typeof CustomizedTyping; @@ -648,6 +648,7 @@ export type CustomizedSchemaTyping = TSche * @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, @@ -660,50 +661,15 @@ export interface StrictTypes< } /** - * Relaxed policy: allows possible invalid edits (which will err at runtime) when schema is not exact. + * 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 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 re allowed. - */ -export interface RelaxedTypes { - input: TSchema extends TreeNodeSchema - ? InsertableTypedNode - : TSchema extends AllowedTypes - ? TSchema[number] extends LazyItem - ? InsertableTypedNode - : never - : never; - readWrite: TreeNodeFromImplicitAllowedTypes; - output: TreeNodeFromImplicitAllowedTypes; -} - -/** - * Ignores schema, and allows any edit at compile time. - */ -export interface AnyTypes { - input: InsertableField; - readWrite: TreeNode | TreeLeafValue; - output: TreeNode | TreeLeafValue; -} - -/** - * Ignores schema, forbidding all edits. - */ -export interface UnknownTypes { - input: never; - readWrite: never; - output: TreeNode | TreeLeafValue; -} - -/** - * Replaces all typing with a single type. + * Handles input types contravariantly so any input which might be invalid is rejected. + * @sealed @alpha */ -export interface SimpleReplacedType { - input: T; - readWrite: T; - output: T; -} - export function customizeSchemaTyping( schema: TSchema, ): Customizer { @@ -713,15 +679,35 @@ export function customizeSchemaTyping( return { strict: f, relaxed: f, simplified: f, simplifiedUnrestricted: f, custom: f }; } +/** + * Utility for customizing the types used for data matching a given schema. + * @sealed @public + */ export interface Customizer { /** * The default {@link StrictTypes}, explicitly applied. */ strict(): CustomizedSchemaTyping>; /** - * {@link RelaxedTypes}. + * 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 re allowed. */ - relaxed(): CustomizedSchemaTyping>; + relaxed(): CustomizedSchemaTyping< + TSchema, + { + input: TSchema extends TreeNodeSchema + ? InsertableTypedNode + : 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 @@ -731,7 +717,11 @@ export interface Customizer { */ simplified>(): CustomizedSchemaTyping< TSchema, - SimpleReplacedType + { + input: T; + readWrite: T; + output: T; + } >; /** @@ -739,7 +729,11 @@ export interface Customizer { */ simplifiedUnrestricted(): CustomizedSchemaTyping< TSchema, - SimpleReplacedType + { + input: T; + readWrite: T; + output: T; + } >; /** @@ -761,6 +755,10 @@ export interface Customizer { >; } +/** + * Fetch types associated with a schema, or use the default if not customized. + * @system @public + */ export type GetTypes = [TSchema] extends [ CustomizedSchemaTyping, ] @@ -856,7 +854,6 @@ export type ApplyKindInput = T; * @typeparam TSchema - Schema to process. * @remarks * Defaults to {@link DefaultInsertableTreeNodeFromImplicitAllowedTypes}. - * Use {@link Customizer} to customize. * @public */ export type InsertableTreeNodeFromImplicitAllowedTypes = diff --git a/packages/dds/tree/src/test/simple-tree/schemaTypes.spec.ts b/packages/dds/tree/src/test/simple-tree/schemaTypes.spec.ts index f823fb0cb7bb..2426c5b13d56 100644 --- a/packages/dds/tree/src/test/simple-tree/schemaTypes.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/schemaTypes.spec.ts @@ -33,6 +33,7 @@ import { type TreeFieldFromImplicitField, type TreeLeafValue, type TreeNodeFromImplicitAllowedTypes, + type UnsafeUnknownSchema, areImplicitFieldSchemaEqual, normalizeAllowedTypes, // eslint-disable-next-line import/no-internal-modules @@ -234,6 +235,26 @@ describe("schemaTypes", () => { } } + // 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; + } + describe("insertable", () => { it("Lists", () => { const List = schema.array(schema.number); 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 3310539c5b62..0e2a20b5ed73 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 @@ -111,10 +111,60 @@ export interface ContainerSchema { readonly initialObjects: Record; } +// @public (undocumented) +export type CustomizedSchemaTyping = TSchema & { + [CustomizedTyping]: TCustom; +}; + +// @public +export const CustomizedTyping: unique symbol; + +// @public +export type CustomizedTyping = typeof CustomizedTyping; + +// @public @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 @sealed +export function customizeSchemaTyping(schema: TSchema): Customizer; + +// @public @sealed +export interface CustomTypes { + readonly input: unknown; + readonly output: TreeLeafValue | TreeNode; + readonly readWrite: TreeLeafValue | TreeNode; +} + +// @public +export type DefaultInsertableTreeNodeFromImplicitAllowedTypes = [TSchema] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; + // @public @sealed interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> { } +// @public +export type DefaultTreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; + // @alpha export interface EncodeOptions { readonly useStoredKeys?: boolean; @@ -247,6 +297,11 @@ export function getBranch(v // @alpha export function getJsonSchema(schema: ImplicitFieldSchema): JsonTreeSchema; +// @public +export type GetTypes = [TSchema] extends [ +CustomizedSchemaTyping +] ? TCustom : StrictTypes; + // @alpha export interface ICodecOptions { readonly jsonValidator: JsonValidator; @@ -556,7 +611,7 @@ export type InsertableObjectFromSchemaRecordUnsafe> = TSchema extends FieldSchema ? ApplyKindInput, Kind, true> : TSchema extends ImplicitAllowedTypes ? InsertableTreeNodeFromImplicitAllowedTypes : never; +export type InsertableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; // @public export type InsertableTreeFieldFromImplicitFieldUnsafe, TSchema = UnionToIntersection> = [TSchema] extends [FieldSchemaUnsafe] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypesUnsafe : never; @@ -799,7 +854,7 @@ export interface NodeChangedData { } // @public -export type NodeFromSchema = T extends TreeNodeSchemaClass ? TNode : T extends TreeNodeSchemaNonClass ? TNode : never; +export type NodeFromSchema = T extends TreeNodeSchema ? TNode : never; // @public type NodeFromSchemaUnsafe> = T extends TreeNodeSchemaUnsafe ? TNode : never; @@ -963,7 +1018,7 @@ export class SchemaFactory; readonly null: TreeNodeSchemaNonClass<"com.fluidframework.leaf.null", NodeKind.Leaf, null, null, true, unknown, never>; readonly number: TreeNodeSchemaNonClass<"com.fluidframework.leaf.number", NodeKind.Leaf, number, number, true, unknown, never>; - object>(name: Name, fields: T): ObjectNodeSchema, T, true>; + object>(name: Name, fields: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T>; objectRecursive>>(name: Name, t: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNodeUnsafe>, object & { readonly [Property in keyof T as FieldHasDefaultUnsafe extends false ? Property : never]: InsertableTreeFieldFromImplicitFieldUnsafe>; } & { readonly [Property_1 in keyof T as FieldHasDefaultUnsafe extends true ? Property_1 : never]?: InsertableTreeFieldFromImplicitFieldUnsafe> | undefined; }, false, T>; optional(t: T, props?: Omit, "defaultProvider">): FieldSchema; optionalRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; @@ -1013,6 +1068,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; +} + // @public export interface Tagged { // (undocumented) 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 a8d63f8e9fbb..b74fb6420ad8 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 @@ -78,10 +78,57 @@ export interface ContainerSchema { readonly initialObjects: Record; } +// @public (undocumented) +export type CustomizedSchemaTyping = TSchema & { + [CustomizedTyping]: TCustom; +}; + +// @public +export const CustomizedTyping: unique symbol; + +// @public +export type CustomizedTyping = typeof CustomizedTyping; + +// @public @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>; +} + +// @public @sealed +export interface CustomTypes { + readonly input: unknown; + readonly output: TreeLeafValue | TreeNode; + readonly readWrite: TreeLeafValue | TreeNode; +} + +// @public +export type DefaultInsertableTreeNodeFromImplicitAllowedTypes = [TSchema] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; + // @public @sealed interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> { } +// @public +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; @@ -154,6 +201,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 +export type GetTypes = [TSchema] extends [ +CustomizedSchemaTyping +] ? TCustom : StrictTypes; + // @public export interface IConnection { readonly id: string; @@ -439,7 +491,7 @@ export type InsertableObjectFromSchemaRecordUnsafe> = TSchema extends FieldSchema ? ApplyKindInput, Kind, true> : TSchema extends ImplicitAllowedTypes ? InsertableTreeNodeFromImplicitAllowedTypes : never; +export type InsertableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; // @public export type InsertableTreeFieldFromImplicitFieldUnsafe, TSchema = UnionToIntersection> = [TSchema] extends [FieldSchemaUnsafe] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypesUnsafe : never; @@ -604,7 +656,7 @@ export interface NodeChangedData { } // @public -export type NodeFromSchema = T extends TreeNodeSchemaClass ? TNode : T extends TreeNodeSchemaNonClass ? TNode : never; +export type NodeFromSchema = T extends TreeNodeSchema ? TNode : never; // @public type NodeFromSchemaUnsafe> = T extends TreeNodeSchemaUnsafe ? TNode : never; @@ -748,7 +800,7 @@ export class SchemaFactory; readonly null: TreeNodeSchemaNonClass<"com.fluidframework.leaf.null", NodeKind.Leaf, null, null, true, unknown, never>; readonly number: TreeNodeSchemaNonClass<"com.fluidframework.leaf.number", NodeKind.Leaf, number, number, true, unknown, never>; - object>(name: Name, fields: T): ObjectNodeSchema, T, true>; + object>(name: Name, fields: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T>; objectRecursive>>(name: Name, t: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNodeUnsafe>, object & { readonly [Property in keyof T as FieldHasDefaultUnsafe extends false ? Property : never]: InsertableTreeFieldFromImplicitFieldUnsafe>; } & { readonly [Property_1 in keyof T as FieldHasDefaultUnsafe extends true ? Property_1 : never]?: InsertableTreeFieldFromImplicitFieldUnsafe> | undefined; }, false, T>; optional(t: T, props?: Omit, "defaultProvider">): FieldSchema; optionalRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; @@ -769,6 +821,16 @@ export interface SharedObjectKind extends ErasedTyp // @public export const SharedTree: SharedObjectKind; +// @public @sealed +export interface StrictTypes, TOutput extends TreeNode | TreeLeafValue = DefaultTreeNodeFromImplicitAllowedTypes> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TInput extends never ? never : TOutput; +} + // @public export interface Tagged { // (undocumented) diff --git a/packages/framework/fluid-framework/api-report/fluid-framework.legacy.alpha.api.md b/packages/framework/fluid-framework/api-report/fluid-framework.legacy.alpha.api.md index 64355c8a2d8f..eada391a4f7d 100644 --- a/packages/framework/fluid-framework/api-report/fluid-framework.legacy.alpha.api.md +++ b/packages/framework/fluid-framework/api-report/fluid-framework.legacy.alpha.api.md @@ -78,10 +78,57 @@ export interface ContainerSchema { readonly initialObjects: Record; } +// @public (undocumented) +export type CustomizedSchemaTyping = TSchema & { + [CustomizedTyping]: TCustom; +}; + +// @public +export const CustomizedTyping: unique symbol; + +// @public +export type CustomizedTyping = typeof CustomizedTyping; + +// @public @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>; +} + +// @public @sealed +export interface CustomTypes { + readonly input: unknown; + readonly output: TreeLeafValue | TreeNode; + readonly readWrite: TreeLeafValue | TreeNode; +} + +// @public +export type DefaultInsertableTreeNodeFromImplicitAllowedTypes = [TSchema] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; + // @public @sealed interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> { } +// @public +export type DefaultTreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; + // @alpha (undocumented) export type DeserializeCallback = (properties: PropertySet) => void; @@ -157,6 +204,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 +export type GetTypes = [TSchema] extends [ +CustomizedSchemaTyping +] ? TCustom : StrictTypes; + // @alpha export interface IBranchOrigin { id: string; @@ -539,7 +591,7 @@ export type InsertableObjectFromSchemaRecordUnsafe> = TSchema extends FieldSchema ? ApplyKindInput, Kind, true> : TSchema extends ImplicitAllowedTypes ? InsertableTreeNodeFromImplicitAllowedTypes : never; +export type InsertableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; // @public export type InsertableTreeFieldFromImplicitFieldUnsafe, TSchema = UnionToIntersection> = [TSchema] extends [FieldSchemaUnsafe] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypesUnsafe : never; @@ -901,7 +953,7 @@ type NodeBuilderData> = type NodeBuilderDataUnsafe> = T extends TreeNodeSchemaUnsafe ? TBuild : never; // @public -export type NodeFromSchema = T extends TreeNodeSchemaClass ? TNode : T extends TreeNodeSchemaNonClass ? TNode : never; +export type NodeFromSchema = T extends TreeNodeSchema ? TNode : never; // @public type NodeFromSchemaUnsafe> = T extends TreeNodeSchemaUnsafe ? TNode : never; @@ -1045,7 +1097,7 @@ export class SchemaFactory; readonly null: TreeNodeSchemaNonClass<"com.fluidframework.leaf.null", NodeKind.Leaf, null, null, true, unknown, never>; readonly number: TreeNodeSchemaNonClass<"com.fluidframework.leaf.number", NodeKind.Leaf, number, number, true, unknown, never>; - object>(name: Name, fields: T): ObjectNodeSchema, T, true>; + object>(name: Name, fields: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T>; objectRecursive>>(name: Name, t: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNodeUnsafe>, object & { readonly [Property in keyof T as FieldHasDefaultUnsafe extends false ? Property : never]: InsertableTreeFieldFromImplicitFieldUnsafe>; } & { readonly [Property_1 in keyof T as FieldHasDefaultUnsafe extends true ? Property_1 : never]?: InsertableTreeFieldFromImplicitFieldUnsafe> | undefined; }, false, T>; optional(t: T, props?: Omit, "defaultProvider">): FieldSchema; optionalRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; @@ -1144,6 +1196,16 @@ export const SharedTree: SharedObjectKind; export { Side } +// @public @sealed +export interface StrictTypes, TOutput extends TreeNode | TreeLeafValue = DefaultTreeNodeFromImplicitAllowedTypes> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TInput extends never ? never : TOutput; +} + // @public export interface Tagged { // (undocumented) 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 c67c2968959e..c1092388349b 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 @@ -78,10 +78,57 @@ export interface ContainerSchema { readonly initialObjects: Record; } +// @public (undocumented) +export type CustomizedSchemaTyping = TSchema & { + [CustomizedTyping]: TCustom; +}; + +// @public +export const CustomizedTyping: unique symbol; + +// @public +export type CustomizedTyping = typeof CustomizedTyping; + +// @public @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>; +} + +// @public @sealed +export interface CustomTypes { + readonly input: unknown; + readonly output: TreeLeafValue | TreeNode; + readonly readWrite: TreeLeafValue | TreeNode; +} + +// @public +export type DefaultInsertableTreeNodeFromImplicitAllowedTypes = [TSchema] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; + // @public @sealed interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> { } +// @public +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; @@ -154,6 +201,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 +export type GetTypes = [TSchema] extends [ +CustomizedSchemaTyping +] ? TCustom : StrictTypes; + // @public export interface IConnection { readonly id: string; @@ -467,7 +519,7 @@ export type InsertableObjectFromSchemaRecordUnsafe> = TSchema extends FieldSchema ? ApplyKindInput, Kind, true> : TSchema extends ImplicitAllowedTypes ? InsertableTreeNodeFromImplicitAllowedTypes : never; +export type InsertableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; // @public export type InsertableTreeFieldFromImplicitFieldUnsafe, TSchema = UnionToIntersection> = [TSchema] extends [FieldSchemaUnsafe] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypesUnsafe : never; @@ -635,7 +687,7 @@ type NodeBuilderData> = type NodeBuilderDataUnsafe> = T extends TreeNodeSchemaUnsafe ? TBuild : never; // @public -export type NodeFromSchema = T extends TreeNodeSchemaClass ? TNode : T extends TreeNodeSchemaNonClass ? TNode : never; +export type NodeFromSchema = T extends TreeNodeSchema ? TNode : never; // @public type NodeFromSchemaUnsafe> = T extends TreeNodeSchemaUnsafe ? TNode : never; @@ -779,7 +831,7 @@ export class SchemaFactory; readonly null: TreeNodeSchemaNonClass<"com.fluidframework.leaf.null", NodeKind.Leaf, null, null, true, unknown, never>; readonly number: TreeNodeSchemaNonClass<"com.fluidframework.leaf.number", NodeKind.Leaf, number, number, true, unknown, never>; - object>(name: Name, fields: T): ObjectNodeSchema, T, true>; + object>(name: Name, fields: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T>; objectRecursive>>(name: Name, t: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNodeUnsafe>, object & { readonly [Property in keyof T as FieldHasDefaultUnsafe extends false ? Property : never]: InsertableTreeFieldFromImplicitFieldUnsafe>; } & { readonly [Property_1 in keyof T as FieldHasDefaultUnsafe extends true ? Property_1 : never]?: InsertableTreeFieldFromImplicitFieldUnsafe> | undefined; }, false, T>; optional(t: T, props?: Omit, "defaultProvider">): FieldSchema; optionalRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; @@ -804,6 +856,16 @@ export const SharedTree: SharedObjectKind; export { Side } +// @public @sealed +export interface StrictTypes, TOutput extends TreeNode | TreeLeafValue = DefaultTreeNodeFromImplicitAllowedTypes> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TInput extends never ? never : TOutput; +} + // @public export interface Tagged { // (undocumented) 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 ba8e9e45dcb8..4e7b8144904e 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 @@ -78,10 +78,57 @@ export interface ContainerSchema { readonly initialObjects: Record; } +// @public (undocumented) +export type CustomizedSchemaTyping = TSchema & { + [CustomizedTyping]: TCustom; +}; + +// @public +export const CustomizedTyping: unique symbol; + +// @public +export type CustomizedTyping = typeof CustomizedTyping; + +// @public @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>; +} + +// @public @sealed +export interface CustomTypes { + readonly input: unknown; + readonly output: TreeLeafValue | TreeNode; + readonly readWrite: TreeLeafValue | TreeNode; +} + +// @public +export type DefaultInsertableTreeNodeFromImplicitAllowedTypes = [TSchema] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; + // @public @sealed interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> { } +// @public +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; @@ -154,6 +201,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 +export type GetTypes = [TSchema] extends [ +CustomizedSchemaTyping +] ? TCustom : StrictTypes; + // @public export interface IConnection { readonly id: string; @@ -439,7 +491,7 @@ export type InsertableObjectFromSchemaRecordUnsafe> = TSchema extends FieldSchema ? ApplyKindInput, Kind, true> : TSchema extends ImplicitAllowedTypes ? InsertableTreeNodeFromImplicitAllowedTypes : never; +export type InsertableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; // @public export type InsertableTreeFieldFromImplicitFieldUnsafe, TSchema = UnionToIntersection> = [TSchema] extends [FieldSchemaUnsafe] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypesUnsafe : never; @@ -599,7 +651,7 @@ type NodeBuilderData> = type NodeBuilderDataUnsafe> = T extends TreeNodeSchemaUnsafe ? TBuild : never; // @public -export type NodeFromSchema = T extends TreeNodeSchemaClass ? TNode : T extends TreeNodeSchemaNonClass ? TNode : never; +export type NodeFromSchema = T extends TreeNodeSchema ? TNode : never; // @public type NodeFromSchemaUnsafe> = T extends TreeNodeSchemaUnsafe ? TNode : never; @@ -743,7 +795,7 @@ export class SchemaFactory; readonly null: TreeNodeSchemaNonClass<"com.fluidframework.leaf.null", NodeKind.Leaf, null, null, true, unknown, never>; readonly number: TreeNodeSchemaNonClass<"com.fluidframework.leaf.number", NodeKind.Leaf, number, number, true, unknown, never>; - object>(name: Name, fields: T): ObjectNodeSchema, T, true>; + object>(name: Name, fields: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T>; objectRecursive>>(name: Name, t: T): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNodeUnsafe>, object & { readonly [Property in keyof T as FieldHasDefaultUnsafe extends false ? Property : never]: InsertableTreeFieldFromImplicitFieldUnsafe>; } & { readonly [Property_1 in keyof T as FieldHasDefaultUnsafe extends true ? Property_1 : never]?: InsertableTreeFieldFromImplicitFieldUnsafe> | undefined; }, false, T>; optional(t: T, props?: Omit, "defaultProvider">): FieldSchema; optionalRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; @@ -764,6 +816,16 @@ export interface SharedObjectKind extends ErasedTyp // @public export const SharedTree: SharedObjectKind; +// @public @sealed +export interface StrictTypes, TOutput extends TreeNode | TreeLeafValue = DefaultTreeNodeFromImplicitAllowedTypes> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TInput extends never ? never : TOutput; +} + // @public export interface Tagged { // (undocumented) From 9645aa6f911bc34688dcef20c263e09e96cb9cc4 Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Wed, 4 Dec 2024 13:46:36 -0800 Subject: [PATCH 14/37] Recursive type support --- .../dds/tree/api-report/tree.alpha.api.md | 46 +++- packages/dds/tree/api-report/tree.beta.api.md | 44 +++- .../tree/api-report/tree.legacy.alpha.api.md | 44 +++- .../tree/api-report/tree.legacy.public.api.md | 44 +++- .../dds/tree/api-report/tree.public.api.md | 44 +++- packages/dds/tree/src/index.ts | 5 + .../dds/tree/src/simple-tree/api/index.ts | 5 + .../tree/src/simple-tree/api/typesUnsafe.ts | 228 ++++++++++++++++-- packages/dds/tree/src/simple-tree/index.ts | 5 + .../tree/src/simple-tree/objectNodeTypes.ts | 5 +- .../dds/tree/src/simple-tree/schemaTypes.ts | 2 +- .../api/schemaFactoryRecursive.spec.ts | 64 ++++- .../test/simple-tree/api/typesUnsafe.spec.ts | 60 ++++- .../src/test/simple-tree/objectNode.spec.ts | 6 +- .../api-report/fluid-framework.alpha.api.md | 41 +++- .../api-report/fluid-framework.beta.api.md | 41 +++- .../fluid-framework.legacy.alpha.api.md | 41 +++- .../fluid-framework.legacy.public.api.md | 41 +++- .../api-report/fluid-framework.public.api.md | 41 +++- 19 files changed, 744 insertions(+), 63 deletions(-) diff --git a/packages/dds/tree/api-report/tree.alpha.api.md b/packages/dds/tree/api-report/tree.alpha.api.md index 8cda088dbf6d..4067b8de5678 100644 --- a/packages/dds/tree/api-report/tree.alpha.api.md +++ b/packages/dds/tree/api-report/tree.alpha.api.md @@ -41,6 +41,9 @@ Kind // @public export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; +// @public +export type AssignableTreeFieldFromImplicitFieldUnsafe> = TSchema extends FieldSchemaUnsafe ? ApplyKindAssignment["readWrite"], Kind> : GetTypesUnsafe["readWrite"]; + // @alpha export function asTreeViewAlpha(view: TreeView): TreeViewAlpha; @@ -107,7 +110,7 @@ export interface Customizer { strict(): CustomizedSchemaTyping>; } -// @alpha @sealed +// @alpha export function customizeSchemaTyping(schema: TSchema): Customizer; // @public @sealed @@ -120,6 +123,9 @@ export interface CustomTypes { // @public export type DefaultInsertableTreeNodeFromImplicitAllowedTypes = [TSchema] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; +// @public +export type DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe> = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; + // @public @sealed interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> { } @@ -127,6 +133,9 @@ interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider // @public export type DefaultTreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; +// @public +export type DefaultTreeNodeFromImplicitAllowedTypesUnsafe> = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; + // @alpha export interface EncodeOptions { readonly useStoredKeys?: boolean; @@ -250,6 +259,11 @@ export type GetTypes = [TSchema] extends [ CustomizedSchemaTyping ] ? TCustom : StrictTypes; +// @public +export type GetTypesUnsafe> = [ +TSchema +] extends [CustomizedSchemaTyping] ? TCustom : StrictTypesUnsafe; + // @alpha export interface ICodecOptions { readonly jsonValidator: JsonValidator; @@ -322,7 +336,7 @@ LazyItem, export type InsertableTreeNodeFromImplicitAllowedTypes = GetTypes["input"]; // @public -export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe> = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; +export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe> = GetTypesUnsafe["input"]; // @public export type InsertableTypedNode> = (T extends TreeNodeSchema ? NodeBuilderData : never) | (T extends TreeNodeSchema ? Unhydrated ? never : NodeFromSchema> : never); @@ -541,7 +555,21 @@ type ObjectFromSchemaRecord>> = { - -readonly [Property in keyof T]: TreeFieldFromImplicitFieldUnsafe; + -readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? never : Property]: AssignableTreeFieldFromImplicitFieldUnsafe; +} & { + readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? Property : never]: TreeFieldFromImplicitFieldUnsafe; }; // @public @@ -717,6 +745,16 @@ export interface StrictTypes, TInput = DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe, TOutput = DefaultTreeNodeFromImplicitAllowedTypesUnsafe> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TOutput; +} + // @public export type TransactionConstraint = NodeInDocumentConstraint; @@ -875,7 +913,7 @@ export interface TreeNodeApi { export type TreeNodeFromImplicitAllowedTypes = GetTypes["output"]; // @public -type TreeNodeFromImplicitAllowedTypesUnsafe> = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; +type TreeNodeFromImplicitAllowedTypesUnsafe> = GetTypesUnsafe["output"]; // @public @sealed export type TreeNodeSchema = (TNode extends TreeNode ? TreeNodeSchemaClass : never) | TreeNodeSchemaNonClass; diff --git a/packages/dds/tree/api-report/tree.beta.api.md b/packages/dds/tree/api-report/tree.beta.api.md index d0825a1632e6..36434be8a5de 100644 --- a/packages/dds/tree/api-report/tree.beta.api.md +++ b/packages/dds/tree/api-report/tree.beta.api.md @@ -30,6 +30,9 @@ Kind // @public export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; +// @public +export type AssignableTreeFieldFromImplicitFieldUnsafe> = TSchema extends FieldSchemaUnsafe ? ApplyKindAssignment["readWrite"], Kind> : GetTypesUnsafe["readWrite"]; + // @public export enum CommitKind { Default = 0, @@ -87,6 +90,9 @@ export interface CustomTypes { // @public export type DefaultInsertableTreeNodeFromImplicitAllowedTypes = [TSchema] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; +// @public +export type DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe> = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; + // @public @sealed interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> { } @@ -94,6 +100,9 @@ interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider // @public export type DefaultTreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; +// @public +export type DefaultTreeNodeFromImplicitAllowedTypesUnsafe> = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; + // @public type ExtractItemType = Item extends () => infer Result ? Result : Item; @@ -157,6 +166,11 @@ export type GetTypes = [TSchema] extends [ CustomizedSchemaTyping ] ? TCustom : StrictTypes; +// @public +export type GetTypesUnsafe> = [ +TSchema +] extends [CustomizedSchemaTyping] ? TCustom : StrictTypesUnsafe; + // @public export type ImplicitAllowedTypes = AllowedTypes | TreeNodeSchema; @@ -205,7 +219,7 @@ LazyItem, export type InsertableTreeNodeFromImplicitAllowedTypes = GetTypes["input"]; // @public -export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe> = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; +export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe> = GetTypesUnsafe["input"]; // @public export type InsertableTypedNode> = (T extends TreeNodeSchema ? NodeBuilderData : never) | (T extends TreeNodeSchema ? Unhydrated ? never : NodeFromSchema> : never); @@ -343,7 +357,21 @@ type ObjectFromSchemaRecord>> = { - -readonly [Property in keyof T]: TreeFieldFromImplicitFieldUnsafe; + -readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? never : Property]: AssignableTreeFieldFromImplicitFieldUnsafe; +} & { + readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? Property : never]: TreeFieldFromImplicitFieldUnsafe; }; // @public @@ -473,6 +501,16 @@ export interface StrictTypes, TInput = DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe, TOutput = DefaultTreeNodeFromImplicitAllowedTypesUnsafe> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TOutput; +} + // @public export type TransactionConstraint = NodeInDocumentConstraint; @@ -583,7 +621,7 @@ export interface TreeNodeApi { export type TreeNodeFromImplicitAllowedTypes = GetTypes["output"]; // @public -type TreeNodeFromImplicitAllowedTypesUnsafe> = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; +type TreeNodeFromImplicitAllowedTypesUnsafe> = GetTypesUnsafe["output"]; // @public @sealed export type TreeNodeSchema = (TNode extends TreeNode ? TreeNodeSchemaClass : never) | TreeNodeSchemaNonClass; diff --git a/packages/dds/tree/api-report/tree.legacy.alpha.api.md b/packages/dds/tree/api-report/tree.legacy.alpha.api.md index e3affd0037ca..9e2394c85fda 100644 --- a/packages/dds/tree/api-report/tree.legacy.alpha.api.md +++ b/packages/dds/tree/api-report/tree.legacy.alpha.api.md @@ -30,6 +30,9 @@ Kind // @public export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; +// @public +export type AssignableTreeFieldFromImplicitFieldUnsafe> = TSchema extends FieldSchemaUnsafe ? ApplyKindAssignment["readWrite"], Kind> : GetTypesUnsafe["readWrite"]; + // @public export enum CommitKind { Default = 0, @@ -87,6 +90,9 @@ export interface CustomTypes { // @public export type DefaultInsertableTreeNodeFromImplicitAllowedTypes = [TSchema] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; +// @public +export type DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe> = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; + // @public @sealed interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> { } @@ -94,6 +100,9 @@ interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider // @public export type DefaultTreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; +// @public +export type DefaultTreeNodeFromImplicitAllowedTypesUnsafe> = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; + // @public type ExtractItemType = Item extends () => infer Result ? Result : Item; @@ -157,6 +166,11 @@ export type GetTypes = [TSchema] extends [ CustomizedSchemaTyping ] ? TCustom : StrictTypes; +// @public +export type GetTypesUnsafe> = [ +TSchema +] extends [CustomizedSchemaTyping] ? TCustom : StrictTypesUnsafe; + // @public export type ImplicitAllowedTypes = AllowedTypes | TreeNodeSchema; @@ -205,7 +219,7 @@ LazyItem, export type InsertableTreeNodeFromImplicitAllowedTypes = GetTypes["input"]; // @public -export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe> = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; +export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe> = GetTypesUnsafe["input"]; // @public export type InsertableTypedNode> = (T extends TreeNodeSchema ? NodeBuilderData : never) | (T extends TreeNodeSchema ? Unhydrated ? never : NodeFromSchema> : never); @@ -338,7 +352,21 @@ type ObjectFromSchemaRecord>> = { - -readonly [Property in keyof T]: TreeFieldFromImplicitFieldUnsafe; + -readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? never : Property]: AssignableTreeFieldFromImplicitFieldUnsafe; +} & { + readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? Property : never]: TreeFieldFromImplicitFieldUnsafe; }; // @public @@ -471,6 +499,16 @@ export interface StrictTypes, TInput = DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe, TOutput = DefaultTreeNodeFromImplicitAllowedTypesUnsafe> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TOutput; +} + // @public export type TransactionConstraint = NodeInDocumentConstraint; @@ -570,7 +608,7 @@ export interface TreeNodeApi { export type TreeNodeFromImplicitAllowedTypes = GetTypes["output"]; // @public -type TreeNodeFromImplicitAllowedTypesUnsafe> = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; +type TreeNodeFromImplicitAllowedTypesUnsafe> = GetTypesUnsafe["output"]; // @public @sealed export type TreeNodeSchema = (TNode extends TreeNode ? TreeNodeSchemaClass : never) | TreeNodeSchemaNonClass; 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 3ab4a56ffe33..5a330a04cb7c 100644 --- a/packages/dds/tree/api-report/tree.legacy.public.api.md +++ b/packages/dds/tree/api-report/tree.legacy.public.api.md @@ -30,6 +30,9 @@ Kind // @public export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; +// @public +export type AssignableTreeFieldFromImplicitFieldUnsafe> = TSchema extends FieldSchemaUnsafe ? ApplyKindAssignment["readWrite"], Kind> : GetTypesUnsafe["readWrite"]; + // @public export enum CommitKind { Default = 0, @@ -87,6 +90,9 @@ export interface CustomTypes { // @public export type DefaultInsertableTreeNodeFromImplicitAllowedTypes = [TSchema] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; +// @public +export type DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe> = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; + // @public @sealed interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> { } @@ -94,6 +100,9 @@ interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider // @public export type DefaultTreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; +// @public +export type DefaultTreeNodeFromImplicitAllowedTypesUnsafe> = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; + // @public type ExtractItemType = Item extends () => infer Result ? Result : Item; @@ -157,6 +166,11 @@ export type GetTypes = [TSchema] extends [ CustomizedSchemaTyping ] ? TCustom : StrictTypes; +// @public +export type GetTypesUnsafe> = [ +TSchema +] extends [CustomizedSchemaTyping] ? TCustom : StrictTypesUnsafe; + // @public export type ImplicitAllowedTypes = AllowedTypes | TreeNodeSchema; @@ -205,7 +219,7 @@ LazyItem, export type InsertableTreeNodeFromImplicitAllowedTypes = GetTypes["input"]; // @public -export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe> = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; +export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe> = GetTypesUnsafe["input"]; // @public export type InsertableTypedNode> = (T extends TreeNodeSchema ? NodeBuilderData : never) | (T extends TreeNodeSchema ? Unhydrated ? never : NodeFromSchema> : never); @@ -338,7 +352,21 @@ type ObjectFromSchemaRecord>> = { - -readonly [Property in keyof T]: TreeFieldFromImplicitFieldUnsafe; + -readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? never : Property]: AssignableTreeFieldFromImplicitFieldUnsafe; +} & { + readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? Property : never]: TreeFieldFromImplicitFieldUnsafe; }; // @public @@ -468,6 +496,16 @@ export interface StrictTypes, TInput = DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe, TOutput = DefaultTreeNodeFromImplicitAllowedTypesUnsafe> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TOutput; +} + // @public export type TransactionConstraint = NodeInDocumentConstraint; @@ -567,7 +605,7 @@ export interface TreeNodeApi { export type TreeNodeFromImplicitAllowedTypes = GetTypes["output"]; // @public -type TreeNodeFromImplicitAllowedTypesUnsafe> = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; +type TreeNodeFromImplicitAllowedTypesUnsafe> = GetTypesUnsafe["output"]; // @public @sealed export type TreeNodeSchema = (TNode extends TreeNode ? TreeNodeSchemaClass : never) | TreeNodeSchemaNonClass; diff --git a/packages/dds/tree/api-report/tree.public.api.md b/packages/dds/tree/api-report/tree.public.api.md index 3ab4a56ffe33..5a330a04cb7c 100644 --- a/packages/dds/tree/api-report/tree.public.api.md +++ b/packages/dds/tree/api-report/tree.public.api.md @@ -30,6 +30,9 @@ Kind // @public export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; +// @public +export type AssignableTreeFieldFromImplicitFieldUnsafe> = TSchema extends FieldSchemaUnsafe ? ApplyKindAssignment["readWrite"], Kind> : GetTypesUnsafe["readWrite"]; + // @public export enum CommitKind { Default = 0, @@ -87,6 +90,9 @@ export interface CustomTypes { // @public export type DefaultInsertableTreeNodeFromImplicitAllowedTypes = [TSchema] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; +// @public +export type DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe> = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; + // @public @sealed interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> { } @@ -94,6 +100,9 @@ interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider // @public export type DefaultTreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; +// @public +export type DefaultTreeNodeFromImplicitAllowedTypesUnsafe> = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; + // @public type ExtractItemType = Item extends () => infer Result ? Result : Item; @@ -157,6 +166,11 @@ export type GetTypes = [TSchema] extends [ CustomizedSchemaTyping ] ? TCustom : StrictTypes; +// @public +export type GetTypesUnsafe> = [ +TSchema +] extends [CustomizedSchemaTyping] ? TCustom : StrictTypesUnsafe; + // @public export type ImplicitAllowedTypes = AllowedTypes | TreeNodeSchema; @@ -205,7 +219,7 @@ LazyItem, export type InsertableTreeNodeFromImplicitAllowedTypes = GetTypes["input"]; // @public -export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe> = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; +export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe> = GetTypesUnsafe["input"]; // @public export type InsertableTypedNode> = (T extends TreeNodeSchema ? NodeBuilderData : never) | (T extends TreeNodeSchema ? Unhydrated ? never : NodeFromSchema> : never); @@ -338,7 +352,21 @@ type ObjectFromSchemaRecord>> = { - -readonly [Property in keyof T]: TreeFieldFromImplicitFieldUnsafe; + -readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? never : Property]: AssignableTreeFieldFromImplicitFieldUnsafe; +} & { + readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? Property : never]: TreeFieldFromImplicitFieldUnsafe; }; // @public @@ -468,6 +496,16 @@ export interface StrictTypes, TInput = DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe, TOutput = DefaultTreeNodeFromImplicitAllowedTypesUnsafe> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TOutput; +} + // @public export type TransactionConstraint = NodeInDocumentConstraint; @@ -567,7 +605,7 @@ export interface TreeNodeApi { export type TreeNodeFromImplicitAllowedTypes = GetTypes["output"]; // @public -type TreeNodeFromImplicitAllowedTypesUnsafe> = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; +type TreeNodeFromImplicitAllowedTypesUnsafe> = GetTypesUnsafe["output"]; // @public @sealed export type TreeNodeSchema = (TNode extends TreeNode ? TreeNodeSchemaClass : never) | TreeNodeSchemaNonClass; diff --git a/packages/dds/tree/src/index.ts b/packages/dds/tree/src/index.ts index df3063cab869..2253d6106960 100644 --- a/packages/dds/tree/src/index.ts +++ b/packages/dds/tree/src/index.ts @@ -202,6 +202,11 @@ export { CustomizedTyping, type DefaultInsertableTreeNodeFromImplicitAllowedTypes, customizeSchemaTyping, + type GetTypesUnsafe, + type DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe, + type DefaultTreeNodeFromImplicitAllowedTypesUnsafe, + type StrictTypesUnsafe, + type AssignableTreeFieldFromImplicitFieldUnsafe, } from "./simple-tree/index.js"; export { SharedTree, diff --git a/packages/dds/tree/src/simple-tree/api/index.ts b/packages/dds/tree/src/simple-tree/api/index.ts index f4d5c2737afd..1dbf0d3bd7c1 100644 --- a/packages/dds/tree/src/simple-tree/api/index.ts +++ b/packages/dds/tree/src/simple-tree/api/index.ts @@ -79,6 +79,11 @@ export type { AllowedTypesUnsafe, TreeNodeSchemaNonClassUnsafe, InsertableTreeNodeFromAllowedTypesUnsafe, + GetTypesUnsafe, + DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe, + DefaultTreeNodeFromImplicitAllowedTypesUnsafe, + StrictTypesUnsafe, + AssignableTreeFieldFromImplicitFieldUnsafe, } from "./typesUnsafe.js"; export { diff --git a/packages/dds/tree/src/simple-tree/api/typesUnsafe.ts b/packages/dds/tree/src/simple-tree/api/typesUnsafe.ts index 124ab1fd7593..c7605f060b00 100644 --- a/packages/dds/tree/src/simple-tree/api/typesUnsafe.ts +++ b/packages/dds/tree/src/simple-tree/api/typesUnsafe.ts @@ -8,6 +8,8 @@ import type { RestrictiveStringRecord, UnionToIntersection } from "../../util/in import type { ApplyKind, ApplyKindInput, + CustomizedSchemaTyping, + CustomTypes, FieldKind, FieldSchema, ImplicitAllowedTypes, @@ -25,6 +27,7 @@ import type { } from "../core/index.js"; import type { TreeArrayNode } from "../arrayNode.js"; import type { FlexListToUnion, LazyItem } from "../flexList.js"; +import type { ApplyKindAssignment } from "../objectNode.js"; /* * TODO: @@ -48,21 +51,191 @@ import type { FlexListToUnion, LazyItem } from "../flexList.js"; */ export type Unenforced<_DesiredExtendsConstraint> = unknown; +/** + * {@link Unenforced} version of {@link customizeSchemaTyping} for use with recursive schema. + * @alpha + */ +export function customizeSchemaTypingUnsafe>( + 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: + const f = (): any => schema; + return { simplifiedUnrestricted: f, custom: f }; +} + +/** + * Utility for customizing the types used for data matching a given schema. + * @sealed @public + */ +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 re allowed. + */ + // relaxed(): CustomizedSchemaTyping< + // TSchema, + // { + // input: TSchema extends TreeNodeSchema + // ? InsertableTypedNode + // : 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, + 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 `ObjectFromSchemaRecord`. * @remarks - * Do note use this type directly: its only needed in the implementation of generic logic which define recursive schema, not when using recursive schema. + * 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 ObjectFromSchemaRecordUnsafe< T extends Unenforced>, > = // Due to https://github.com/microsoft/TypeScript/issues/43826 we can not set the desired setter type. - // Partial mitigation for this (used for non-recursive types) breaks compilation if used here, so recursive object end up allowing some unsafe assignments which will error at runtime. { - -readonly [Property in keyof T]: TreeFieldFromImplicitFieldUnsafe; + -readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping< + unknown, + { + readonly readWrite: never; + readonly input: unknown; + readonly output: TreeNode | TreeLeafValue; + } + >, + ] + ? never + : Property]: AssignableTreeFieldFromImplicitFieldUnsafe; // + } & { + readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping< + unknown, + { + readonly readWrite: never; + readonly input: unknown; + readonly output: TreeNode | TreeLeafValue; + } + >, + ] + ? Property + : never]: TreeFieldFromImplicitFieldUnsafe; }; +// { +// -readonly [Property in keyof T as never extends AssignableTreeFieldFromImplicitFieldUnsafe< +// T[Property] +// > +// ? never +// : Property]: TreeFieldFromImplicitFieldUnsafe; +// } & { +// readonly [Property in keyof T as never extends AssignableTreeFieldFromImplicitFieldUnsafe< +// T[Property] +// > +// ? Property +// : never]: TreeFieldFromImplicitFieldUnsafe; +// }; + +/** + * {@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 Unenforced, +> = TSchema extends FieldSchemaUnsafe + ? ApplyKindAssignment["readWrite"], Kind> + : GetTypesUnsafe["readWrite"]; + +/** + * {@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 Unenforced, + 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 {@link TreeNodeSchema}. * @remarks @@ -124,7 +297,7 @@ export interface TreeNodeSchemaNonClassUnsafe< /** * {@link Unenforced} version of {@link TreeObjectNode}. * @remarks - * Do note use this type directly: its only needed in the implementation of generic logic which define recursive schema, not when using recursive schema. + * 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 TreeObjectNodeUnsafe< @@ -135,7 +308,7 @@ export type TreeObjectNodeUnsafe< /** * {@link Unenforced} version of {@link TreeFieldFromImplicitField}. * @remarks - * Do note use this type directly: its only needed in the implementation of generic logic which define recursive schema, not when using recursive schema. + * 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 TreeFieldFromImplicitFieldUnsafe> = @@ -156,11 +329,21 @@ export type AllowedTypesUnsafe = readonly LazyItem[]; /** * {@link Unenforced} version of {@link TreeNodeFromImplicitAllowedTypes}. * @remarks - * Do note use this type directly: its only needed in the implementation of generic logic which define recursive schema, not when using recursive schema. + * 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 TreeNodeFromImplicitAllowedTypesUnsafe< TSchema extends Unenforced, +> = GetTypesUnsafe["output"]; + +/** + * {@link Unenforced} version of {@link DefaultTreeNodeFromImplicitAllowedTypesUnsafe}. + * @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 Unenforced, > = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe @@ -171,11 +354,22 @@ export type TreeNodeFromImplicitAllowedTypesUnsafe< * {@link Unenforced} version of {@link InsertableTreeNodeFromImplicitAllowedTypes}. * @see {@link Input} * @remarks - * Do note use this type directly: its only needed in the implementation of generic logic which define recursive schema, not when using recursive schema. + * 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 InsertableTreeNodeFromImplicitAllowedTypesUnsafe< TSchema extends Unenforced, +> = 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 Unenforced, > = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] @@ -200,7 +394,7 @@ export type InsertableTreeNodeFromAllowedTypesUnsafe< * {@link Unenforced} version of {@link InsertableTypedNode}. * @see {@link Input} * @remarks - * Do note use this type directly: its only needed in the implementation of generic logic which define recursive schema, not when using recursive schema. + * 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 * TODO: * This is less strict than InsertableTypedNode when given non-exact schema to avoid compilation issues. @@ -219,7 +413,7 @@ export type InsertableTypedNodeUnsafe< /** * {@link Unenforced} version of {@link NodeFromSchema}. * @remarks - * Do note use this type directly: its only needed in the implementation of generic logic which define recursive schema, not when using recursive schema. + * 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 NodeFromSchemaUnsafe> = @@ -228,7 +422,7 @@ export type NodeFromSchemaUnsafe> = /** * {@link Unenforced} version of {@link InsertableTreeNodeFromImplicitAllowedTypes}. * @remarks - * Do note use this type directly: its only needed in the implementation of generic logic which define recursive schema, not when using recursive schema. + * 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 NodeBuilderDataUnsafe> = @@ -237,7 +431,7 @@ export type NodeBuilderDataUnsafe> = /** * {@link Unenforced} version of {@link (TreeArrayNode:interface)}. * @remarks - * Do note use this type directly: its only needed in the implementation of generic logic which define recursive schema, not when using recursive schema. + * 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 @sealed @public */ export interface TreeArrayNodeUnsafe> @@ -250,7 +444,7 @@ export interface TreeArrayNodeUnsafe> @@ -274,7 +468,7 @@ export interface TreeMapNodeUnsafe> * Copy of TypeScript's ReadonlyMap, but with `TreeNodeFromImplicitAllowedTypesUnsafe` inlined into it. * Using this instead of ReadonlyMap in TreeMapNodeUnsafe is necessary to make recursive map schema not generate compile errors in the d.ts files when exported. * @remarks - * Do note use this type directly: its only needed in the implementation of generic logic which define recursive schema, not when using recursive schema. + * 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 * This is the same as `ReadonlyMap>` (Checked in test), * except that it avoids the above mentioned compile error. @@ -316,7 +510,7 @@ export interface ReadonlyMapInlined> = @@ -331,7 +525,7 @@ export type FieldHasDefaultUnsafe> = * {@link Unenforced} version of `InsertableObjectFromSchemaRecord`. * @see {@link Input} * @remarks - * Do note use this type directly: its only needed in the implementation of generic logic which define recursive schema, not when using recursive schema. + * 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 InsertableObjectFromSchemaRecordUnsafe< @@ -353,7 +547,7 @@ export type InsertableObjectFromSchemaRecordUnsafe< * {@link Unenforced} version of {@link InsertableTreeFieldFromImplicitField}. * @see {@link Input} * @remarks - * Do note use this type directly: its only needed in the implementation of generic logic which define recursive schema, not when using recursive schema. + * 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 InsertableTreeFieldFromImplicitFieldUnsafe< @@ -368,7 +562,7 @@ export type InsertableTreeFieldFromImplicitFieldUnsafe< /** * {@link Unenforced} version of {@link FieldSchema}. * @remarks - * Do note use this type directly: its only needed in the implementation of generic logic which define recursive schema, not when using recursive schema. + * Do not use this type directly: its only needed in the implementation of generic logic which define recursive schema, not when using recursive schema. * @public */ export interface FieldSchemaUnsafe< diff --git a/packages/dds/tree/src/simple-tree/index.ts b/packages/dds/tree/src/simple-tree/index.ts index e392cdd4262f..c18aeb612c83 100644 --- a/packages/dds/tree/src/simple-tree/index.ts +++ b/packages/dds/tree/src/simple-tree/index.ts @@ -114,6 +114,11 @@ export { conciseFromCursor, createFromCursor, asTreeViewAlpha, + type GetTypesUnsafe, + type DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe, + type DefaultTreeNodeFromImplicitAllowedTypesUnsafe, + type StrictTypesUnsafe, + type AssignableTreeFieldFromImplicitFieldUnsafe, } from "./api/index.js"; export { type NodeFromSchema, diff --git a/packages/dds/tree/src/simple-tree/objectNodeTypes.ts b/packages/dds/tree/src/simple-tree/objectNodeTypes.ts index bf9536d165d0..58743b6f1bf7 100644 --- a/packages/dds/tree/src/simple-tree/objectNodeTypes.ts +++ b/packages/dds/tree/src/simple-tree/objectNodeTypes.ts @@ -10,21 +10,20 @@ import type { SimpleKeyMap, } from "./objectNode.js"; import type { ImplicitFieldSchema, FieldSchema } from "./schemaTypes.js"; -import { NodeKind, type TreeNodeSchema, type TreeNodeSchemaBoth } from "./core/index.js"; +import { NodeKind, type TreeNodeSchemaClass, type TreeNodeSchema } from "./core/index.js"; import type { FieldKey } from "../core/index.js"; /** * A schema for {@link TreeObjectNode}s. * @privateRemarks * This is a candidate for being promoted to the public package API. - * @public */ export interface ObjectNodeSchema< TName extends string = string, T extends RestrictiveStringRecord = RestrictiveStringRecord, ImplicitlyConstructable extends boolean = boolean, -> extends TreeNodeSchemaBoth< +> extends TreeNodeSchemaClass< TName, NodeKind.Object, TreeObjectNode, diff --git a/packages/dds/tree/src/simple-tree/schemaTypes.ts b/packages/dds/tree/src/simple-tree/schemaTypes.ts index 1da16796ea7d..519453d6c3d4 100644 --- a/packages/dds/tree/src/simple-tree/schemaTypes.ts +++ b/packages/dds/tree/src/simple-tree/schemaTypes.ts @@ -668,7 +668,7 @@ export interface StrictTypes< * @typeparam TOutput - Internal: do not specify. * @remarks * Handles input types contravariantly so any input which might be invalid is rejected. - * @sealed @alpha + * @alpha */ export function customizeSchemaTyping( schema: TSchema, 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 2ff896ebc044..08f1cdf2ae89 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 @@ -22,17 +22,19 @@ import { type FlexListToUnion, type ApplyKindInput, type NodeBuilderData, + customizeSchemaTyping, } from "../../../simple-tree/index.js"; import type { ValidateRecursiveSchema, // eslint-disable-next-line import/no-internal-modules } from "../../../simple-tree/api/schemaFactoryRecursive.js"; -import type { - FieldSchemaUnsafe, - InsertableTreeFieldFromImplicitFieldUnsafe, - InsertableTreeNodeFromImplicitAllowedTypesUnsafe, - TreeFieldFromImplicitFieldUnsafe, - TreeNodeFromImplicitAllowedTypesUnsafe, +import { + customizeSchemaTypingUnsafe, + type FieldSchemaUnsafe, + type InsertableTreeFieldFromImplicitFieldUnsafe, + type InsertableTreeNodeFromImplicitAllowedTypesUnsafe, + type TreeFieldFromImplicitFieldUnsafe, + type TreeNodeFromImplicitAllowedTypesUnsafe, // eslint-disable-next-line import/no-internal-modules } from "../../../simple-tree/api/typesUnsafe.js"; import { TreeFactory } from "../../../treeFactory.js"; @@ -638,4 +640,54 @@ describe("SchemaFactory Recursive methods", () => { const r = hydrate(Root, { r: new ArrayRecursive([]) }); assert.deepEqual([...r.r], []); }); + + describe("custom types", () => { + 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; + }>(), + ), + }) {} + { + 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 = obj; + }); + }); }); 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 48e53c75dedd..30de5a4ec694 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 @@ -3,12 +3,64 @@ * Licensed under the MIT License. */ -// eslint-disable-next-line import/no-internal-modules -import type { ReadonlyMapInlined } from "../../../simple-tree/api/typesUnsafe.js"; +import { + customizeSchemaTypingUnsafe, + type InsertableObjectFromSchemaRecordUnsafe, + type InsertableTreeFieldFromImplicitFieldUnsafe, + type InsertableTreeNodeFromImplicitAllowedTypesUnsafe, + type ReadonlyMapInlined, + // 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 type { numberSchema } from "../../../simple-tree/leafNodeSchema.js"; import type { areSafelyAssignable, requireTrue } from "../../../util/index.js"; -type MapInlined = ReadonlyMapInlined; +{ + type MapInlined = ReadonlyMapInlined; + type _check = requireTrue>>; +} + +// customizeSchemaTypingUnsafe and InsertableObjectFromSchemaRecordUnsafe +{ + const sf = new SchemaFactory("recursive"); + class Bad extends sf.objectRecursive("O", { + // customizeSchemaTypingUnsafe needs to be applied to the allowed types, not eh field: this is wrong! + recursive: customizeSchemaTypingUnsafe(sf.optionalRecursive([() => Bad])).custom<{ + input: 5; + }>(), + }) {} + + { + // Ideally this would error, but detecting this is invalid is hard. + type _check = ValidateRecursiveSchema; + } + + class O extends sf.objectRecursive("O", { + recursive: sf.optionalRecursive( + customizeSchemaTypingUnsafe([() => O]).custom<{ + input: 5; + }>(), + ), + }) {} + + // Record + { + type T = InsertableObjectFromSchemaRecordUnsafe["recursive"]; + type _check = requireTrue>; + } + + // Field + { + type T = InsertableTreeFieldFromImplicitFieldUnsafe; + type _check = requireTrue>; + } -type _check = requireTrue>>; + // AllowedTypes + { + type T = InsertableTreeNodeFromImplicitAllowedTypesUnsafe< + typeof O.info.recursive.allowedTypes + >; + type _check = requireTrue>; + } +} diff --git a/packages/dds/tree/src/test/simple-tree/objectNode.spec.ts b/packages/dds/tree/src/test/simple-tree/objectNode.spec.ts index 7b41ec1096b0..c8a496e49302 100644 --- a/packages/dds/tree/src/test/simple-tree/objectNode.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/objectNode.spec.ts @@ -11,6 +11,7 @@ import { SchemaFactory, typeNameSymbol, typeSchemaSymbol, + type GetTypesUnsafe, type NodeBuilderData, type TreeNode, } from "../../simple-tree/index.js"; @@ -21,6 +22,7 @@ import type { import { describeHydration, hydrate, pretty } from "./utils.js"; import type { areSafelyAssignable, + FlattenKeys, requireAssignableTo, requireTrue, } from "../../util/index.js"; @@ -318,7 +320,7 @@ describeHydration( }) {} const n = init(HasId, {}); assert.throws(() => { - // Due to recursive type limitations, this compiles but shouldn't, see ObjectFromSchemaRecordUnsafe + // @ts-expect-error Readonly n.id = "x"; }); }); @@ -348,6 +350,8 @@ describeHydration( 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"; 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 0e2a20b5ed73..1df0ef124c9a 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 @@ -158,6 +158,9 @@ export interface CustomTypes { // @public export type DefaultInsertableTreeNodeFromImplicitAllowedTypes = [TSchema] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; +// @public +export type DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe> = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; + // @public @sealed interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> { } @@ -165,6 +168,9 @@ interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider // @public export type DefaultTreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; +// @public +export type DefaultTreeNodeFromImplicitAllowedTypesUnsafe> = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; + // @alpha export interface EncodeOptions { readonly useStoredKeys?: boolean; @@ -302,6 +308,11 @@ export type GetTypes = [TSchema] extends [ CustomizedSchemaTyping ] ? TCustom : StrictTypes; +// @public +export type GetTypesUnsafe> = [ +TSchema +] extends [CustomizedSchemaTyping] ? TCustom : StrictTypesUnsafe; + // @alpha export interface ICodecOptions { readonly jsonValidator: JsonValidator; @@ -632,7 +643,7 @@ LazyItem, export type InsertableTreeNodeFromImplicitAllowedTypes = GetTypes["input"]; // @public -export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe> = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; +export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe> = GetTypesUnsafe["input"]; // @public export type InsertableTypedNode> = (T extends TreeNodeSchema ? NodeBuilderData : never) | (T extends TreeNodeSchema ? Unhydrated ? never : NodeFromSchema> : never); @@ -889,7 +900,21 @@ type ObjectFromSchemaRecord>> = { - -readonly [Property in keyof T]: TreeFieldFromImplicitFieldUnsafe; + -readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? never : Property]: AssignableTreeFieldFromImplicitFieldUnsafe; +} & { + readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? Property : never]: TreeFieldFromImplicitFieldUnsafe; }; // @public @@ -1078,6 +1103,16 @@ export interface StrictTypes, TInput = DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe, TOutput = DefaultTreeNodeFromImplicitAllowedTypesUnsafe> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TInput extends never ? never : TOutput; +} + // @public export interface Tagged { // (undocumented) @@ -1250,7 +1285,7 @@ export interface TreeNodeApi { export type TreeNodeFromImplicitAllowedTypes = GetTypes["output"]; // @public -type TreeNodeFromImplicitAllowedTypesUnsafe> = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; +type TreeNodeFromImplicitAllowedTypesUnsafe> = GetTypesUnsafe["output"]; // @public @sealed export type TreeNodeSchema = (TNode extends TreeNode ? TreeNodeSchemaClass : never) | TreeNodeSchemaNonClass; 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 b74fb6420ad8..34597376fb8c 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 @@ -122,6 +122,9 @@ export interface CustomTypes { // @public export type DefaultInsertableTreeNodeFromImplicitAllowedTypes = [TSchema] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; +// @public +export type DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe> = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; + // @public @sealed interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> { } @@ -129,6 +132,9 @@ interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider // @public export type DefaultTreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; +// @public +export type DefaultTreeNodeFromImplicitAllowedTypesUnsafe> = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; + // @public @sealed export abstract class ErasedType { static [Symbol.hasInstance](value: never): value is never; @@ -206,6 +212,11 @@ export type GetTypes = [TSchema] extends [ CustomizedSchemaTyping ] ? TCustom : StrictTypes; +// @public +export type GetTypesUnsafe> = [ +TSchema +] extends [CustomizedSchemaTyping] ? TCustom : StrictTypesUnsafe; + // @public export interface IConnection { readonly id: string; @@ -512,7 +523,7 @@ LazyItem, export type InsertableTreeNodeFromImplicitAllowedTypes = GetTypes["input"]; // @public -export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe> = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; +export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe> = GetTypesUnsafe["input"]; // @public export type InsertableTypedNode> = (T extends TreeNodeSchema ? NodeBuilderData : never) | (T extends TreeNodeSchema ? Unhydrated ? never : NodeFromSchema> : never); @@ -688,7 +699,21 @@ type ObjectFromSchemaRecord>> = { - -readonly [Property in keyof T]: TreeFieldFromImplicitFieldUnsafe; + -readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? never : Property]: AssignableTreeFieldFromImplicitFieldUnsafe; +} & { + readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? Property : never]: TreeFieldFromImplicitFieldUnsafe; }; // @public @@ -831,6 +856,16 @@ export interface StrictTypes, TInput = DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe, TOutput = DefaultTreeNodeFromImplicitAllowedTypesUnsafe> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TInput extends never ? never : TOutput; +} + // @public export interface Tagged { // (undocumented) @@ -955,7 +990,7 @@ export interface TreeNodeApi { export type TreeNodeFromImplicitAllowedTypes = GetTypes["output"]; // @public -type TreeNodeFromImplicitAllowedTypesUnsafe> = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; +type TreeNodeFromImplicitAllowedTypesUnsafe> = GetTypesUnsafe["output"]; // @public @sealed export type TreeNodeSchema = (TNode extends TreeNode ? TreeNodeSchemaClass : never) | TreeNodeSchemaNonClass; diff --git a/packages/framework/fluid-framework/api-report/fluid-framework.legacy.alpha.api.md b/packages/framework/fluid-framework/api-report/fluid-framework.legacy.alpha.api.md index eada391a4f7d..4ab3fdd21381 100644 --- a/packages/framework/fluid-framework/api-report/fluid-framework.legacy.alpha.api.md +++ b/packages/framework/fluid-framework/api-report/fluid-framework.legacy.alpha.api.md @@ -122,6 +122,9 @@ export interface CustomTypes { // @public export type DefaultInsertableTreeNodeFromImplicitAllowedTypes = [TSchema] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; +// @public +export type DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe> = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; + // @public @sealed interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> { } @@ -129,6 +132,9 @@ interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider // @public export type DefaultTreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; +// @public +export type DefaultTreeNodeFromImplicitAllowedTypesUnsafe> = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; + // @alpha (undocumented) export type DeserializeCallback = (properties: PropertySet) => void; @@ -209,6 +215,11 @@ export type GetTypes = [TSchema] extends [ CustomizedSchemaTyping ] ? TCustom : StrictTypes; +// @public +export type GetTypesUnsafe> = [ +TSchema +] extends [CustomizedSchemaTyping] ? TCustom : StrictTypesUnsafe; + // @alpha export interface IBranchOrigin { id: string; @@ -612,7 +623,7 @@ LazyItem, export type InsertableTreeNodeFromImplicitAllowedTypes = GetTypes["input"]; // @public -export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe> = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; +export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe> = GetTypesUnsafe["input"]; // @public export type InsertableTypedNode> = (T extends TreeNodeSchema ? NodeBuilderData : never) | (T extends TreeNodeSchema ? Unhydrated ? never : NodeFromSchema> : never); @@ -985,7 +996,21 @@ type ObjectFromSchemaRecord>> = { - -readonly [Property in keyof T]: TreeFieldFromImplicitFieldUnsafe; + -readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? never : Property]: AssignableTreeFieldFromImplicitFieldUnsafe; +} & { + readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? Property : never]: TreeFieldFromImplicitFieldUnsafe; }; // @public @@ -1206,6 +1231,16 @@ export interface StrictTypes, TInput = DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe, TOutput = DefaultTreeNodeFromImplicitAllowedTypesUnsafe> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TInput extends never ? never : TOutput; +} + // @public export interface Tagged { // (undocumented) @@ -1319,7 +1354,7 @@ export interface TreeNodeApi { export type TreeNodeFromImplicitAllowedTypes = GetTypes["output"]; // @public -type TreeNodeFromImplicitAllowedTypesUnsafe> = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; +type TreeNodeFromImplicitAllowedTypesUnsafe> = GetTypesUnsafe["output"]; // @public @sealed export type TreeNodeSchema = (TNode extends TreeNode ? TreeNodeSchemaClass : never) | TreeNodeSchemaNonClass; 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 c1092388349b..5e6ccd3996e8 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 @@ -122,6 +122,9 @@ export interface CustomTypes { // @public export type DefaultInsertableTreeNodeFromImplicitAllowedTypes = [TSchema] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; +// @public +export type DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe> = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; + // @public @sealed interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> { } @@ -129,6 +132,9 @@ interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider // @public export type DefaultTreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; +// @public +export type DefaultTreeNodeFromImplicitAllowedTypesUnsafe> = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; + // @public @sealed export abstract class ErasedType { static [Symbol.hasInstance](value: never): value is never; @@ -206,6 +212,11 @@ export type GetTypes = [TSchema] extends [ CustomizedSchemaTyping ] ? TCustom : StrictTypes; +// @public +export type GetTypesUnsafe> = [ +TSchema +] extends [CustomizedSchemaTyping] ? TCustom : StrictTypesUnsafe; + // @public export interface IConnection { readonly id: string; @@ -540,7 +551,7 @@ LazyItem, export type InsertableTreeNodeFromImplicitAllowedTypes = GetTypes["input"]; // @public -export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe> = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; +export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe> = GetTypesUnsafe["input"]; // @public export type InsertableTypedNode> = (T extends TreeNodeSchema ? NodeBuilderData : never) | (T extends TreeNodeSchema ? Unhydrated ? never : NodeFromSchema> : never); @@ -719,7 +730,21 @@ type ObjectFromSchemaRecord>> = { - -readonly [Property in keyof T]: TreeFieldFromImplicitFieldUnsafe; + -readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? never : Property]: AssignableTreeFieldFromImplicitFieldUnsafe; +} & { + readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? Property : never]: TreeFieldFromImplicitFieldUnsafe; }; // @public @@ -866,6 +891,16 @@ export interface StrictTypes, TInput = DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe, TOutput = DefaultTreeNodeFromImplicitAllowedTypesUnsafe> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TInput extends never ? never : TOutput; +} + // @public export interface Tagged { // (undocumented) @@ -979,7 +1014,7 @@ export interface TreeNodeApi { export type TreeNodeFromImplicitAllowedTypes = GetTypes["output"]; // @public -type TreeNodeFromImplicitAllowedTypesUnsafe> = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; +type TreeNodeFromImplicitAllowedTypesUnsafe> = GetTypesUnsafe["output"]; // @public @sealed export type TreeNodeSchema = (TNode extends TreeNode ? TreeNodeSchemaClass : never) | TreeNodeSchemaNonClass; 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 4e7b8144904e..e0ab9f66ff30 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 @@ -122,6 +122,9 @@ export interface CustomTypes { // @public export type DefaultInsertableTreeNodeFromImplicitAllowedTypes = [TSchema] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; +// @public +export type DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe> = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; + // @public @sealed interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> { } @@ -129,6 +132,9 @@ interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider // @public export type DefaultTreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; +// @public +export type DefaultTreeNodeFromImplicitAllowedTypesUnsafe> = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; + // @public @sealed export abstract class ErasedType { static [Symbol.hasInstance](value: never): value is never; @@ -206,6 +212,11 @@ export type GetTypes = [TSchema] extends [ CustomizedSchemaTyping ] ? TCustom : StrictTypes; +// @public +export type GetTypesUnsafe> = [ +TSchema +] extends [CustomizedSchemaTyping] ? TCustom : StrictTypesUnsafe; + // @public export interface IConnection { readonly id: string; @@ -512,7 +523,7 @@ LazyItem, export type InsertableTreeNodeFromImplicitAllowedTypes = GetTypes["input"]; // @public -export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe> = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] ? InsertableTreeNodeFromAllowedTypesUnsafe : never; +export type InsertableTreeNodeFromImplicitAllowedTypesUnsafe> = GetTypesUnsafe["input"]; // @public export type InsertableTypedNode> = (T extends TreeNodeSchema ? NodeBuilderData : never) | (T extends TreeNodeSchema ? Unhydrated ? never : NodeFromSchema> : never); @@ -683,7 +694,21 @@ type ObjectFromSchemaRecord>> = { - -readonly [Property in keyof T]: TreeFieldFromImplicitFieldUnsafe; + -readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? never : Property]: AssignableTreeFieldFromImplicitFieldUnsafe; +} & { + readonly [Property in keyof T as [T[Property]] extends [ + CustomizedSchemaTyping + ] ? Property : never]: TreeFieldFromImplicitFieldUnsafe; }; // @public @@ -826,6 +851,16 @@ export interface StrictTypes, TInput = DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe, TOutput = DefaultTreeNodeFromImplicitAllowedTypesUnsafe> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TInput extends never ? never : TOutput; +} + // @public export interface Tagged { // (undocumented) @@ -939,7 +974,7 @@ export interface TreeNodeApi { export type TreeNodeFromImplicitAllowedTypes = GetTypes["output"]; // @public -type TreeNodeFromImplicitAllowedTypesUnsafe> = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; +type TreeNodeFromImplicitAllowedTypesUnsafe> = GetTypesUnsafe["output"]; // @public @sealed export type TreeNodeSchema = (TNode extends TreeNode ? TreeNodeSchemaClass : never) | TreeNodeSchemaNonClass; From 773afb9f347e1a607cf5d18b24b6a256504695b2 Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Mon, 13 Jan 2025 13:32:13 -0800 Subject: [PATCH 15/37] Fix merge --- .../dds/tree/api-report/tree.alpha.api.md | 20 +++++++++++++++++++ .../api/schemaFactoryRecursive.spec.ts | 1 + 2 files changed, 21 insertions(+) diff --git a/packages/dds/tree/api-report/tree.alpha.api.md b/packages/dds/tree/api-report/tree.alpha.api.md index 5057a0dfbd1a..8900e2824756 100644 --- a/packages/dds/tree/api-report/tree.alpha.api.md +++ b/packages/dds/tree/api-report/tree.alpha.api.md @@ -806,6 +806,26 @@ 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 +export interface StrictTypesUnsafe, TInput = DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe, TOutput = DefaultTreeNodeFromImplicitAllowedTypesUnsafe> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TOutput; +} + // @alpha export type TransactionCallbackStatus = ({ rollback?: false; 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 68c2f15ee9fc..8a7504e12125 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 @@ -23,6 +23,7 @@ import { type ApplyKindInput, type NodeBuilderData, SchemaFactoryAlpha, + customizeSchemaTyping, } from "../../../simple-tree/index.js"; import type { ValidateRecursiveSchema, From d7cf0e59d6f2bed1dadff07eda100b10f90a44b1 Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Mon, 13 Jan 2025 14:18:53 -0800 Subject: [PATCH 16/37] Cleanup CustomizerUnsafe --- .../tree/src/simple-tree/api/typesUnsafe.ts | 71 ++++++++----------- 1 file changed, 30 insertions(+), 41 deletions(-) diff --git a/packages/dds/tree/src/simple-tree/api/typesUnsafe.ts b/packages/dds/tree/src/simple-tree/api/typesUnsafe.ts index c7605f060b00..ba4c28b96bab 100644 --- a/packages/dds/tree/src/simple-tree/api/typesUnsafe.ts +++ b/packages/dds/tree/src/simple-tree/api/typesUnsafe.ts @@ -52,64 +52,50 @@ import type { ApplyKindAssignment } from "../objectNode.js"; export type Unenforced<_DesiredExtendsConstraint> = unknown; /** - * {@link Unenforced} version of {@link customizeSchemaTyping} for use with recursive schema. + * {@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>( schema: TSchema, -): Customizer { +): 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 { simplifiedUnrestricted: f, custom: f }; + return { simplified: f, simplifiedUnrestricted: f, custom: f }; } /** - * Utility for customizing the types used for data matching a given schema. + * {@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 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 re allowed. - */ - // relaxed(): CustomizedSchemaTyping< - // TSchema, - // { - // input: TSchema extends TreeNodeSchema - // ? InsertableTypedNode - // : TSchema extends AllowedTypes - // ? TSchema[number] extends LazyItem - // ? InsertableTypedNode - // : never - // : never; - // readWrite: TreeNodeFromImplicitAllowedTypes; - // output: TreeNodeFromImplicitAllowedTypes; - // } - // >; +export interface CustomizerUnsafe> { /** - * Replace typing with a single substitute which allowed types must implement. + * 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. + * which can be out of schema and err at runtime in the same way {@link CustomizerUnsafe.relaxed} does. + * Until with {@link CustomizerUnsafe.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; - // } - // >; + simplified< + T extends (TreeNode | TreeLeafValue) & TreeNodeFromImplicitAllowedTypesUnsafe, + >(): 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`. + * The same as {@link CustomizerUnsafe} except that more T values are allowed, even ones not known to be implemented by `TSchema`. */ simplifiedUnrestricted(): CustomizedSchemaTyping< TSchema, @@ -123,6 +109,9 @@ export interface Customizer> { /** * Fully arbitrary customization. * Provided types override existing types. + * @remarks + * This can express any of the customizations possible via other {@link CustomizerUnsafe} methods: + * this API is however more verbose and can more easily be used to unsafe typing. */ custom>(): CustomizedSchemaTyping< TSchema, From cc2684afc4868867a387690950960cf51deef920 Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Mon, 13 Jan 2025 17:30:21 -0800 Subject: [PATCH 17/37] Cleanup and docs --- .../dds/tree/src/simple-tree/schemaTypes.ts | 50 +++++++------------ 1 file changed, 19 insertions(+), 31 deletions(-) diff --git a/packages/dds/tree/src/simple-tree/schemaTypes.ts b/packages/dds/tree/src/simple-tree/schemaTypes.ts index e0b8e7c082c5..ced97aaef682 100644 --- a/packages/dds/tree/src/simple-tree/schemaTypes.ts +++ b/packages/dds/tree/src/simple-tree/schemaTypes.ts @@ -575,6 +575,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; @@ -586,24 +589,7 @@ export type UnsafeUnknownSchema = typeof UnsafeUnknownSchema; export const CustomizedTyping: unique symbol = Symbol("CustomizedTyping"); /** - * TODO: rewrite this doc and/or dedup this with UnsafeUnknownSchema. - * This system should be able to replace how UnsafeUnknownSchema is used. - * - * A special type which can be provided to some APIs as the schema type parameter when schema cannot easily be provided at compile time and an unsafe (instead of disabled) editing API is desired. - * @remarks - * When used, this means the TypeScript typing should err on the side of completeness (allow all inputs that could be valid). - * This introduces the risk that out-of-schema data could be allowed at compile time, and only error at runtime. - * - * @privateRemarks - * This only applies to APIs which input data which is expected to be in schema, since APIs outputting have easy mechanisms to do so in a type safe way even when the schema is unknown. - * In most cases that amounts to returning `TreeNode | TreeLeafValue`. - * - * This can be contrasted with the default behavior of TypeScript, which is to require the intersection of the possible types for input APIs, - * which for unknown schema defining input trees results in the `never` type. - * - * 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. + * A type brand used by {@link customizeSchemaTyping}. * @system @public */ export type CustomizedTyping = typeof CustomizedTyping; @@ -696,18 +682,22 @@ export interface Customizer { * 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 re 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: TSchema extends TreeNodeSchema - ? InsertableTypedNode - : TSchema extends AllowedTypes - ? TSchema[number] extends LazyItem - ? InsertableTypedNode - : never - : never; + input: TreeNodeSchema extends TSchema + ? InsertableContent + : TSchema extends TreeNodeSchema + ? InsertableTypedNode + : TSchema extends AllowedTypes + ? TSchema[number] extends LazyItem + ? InsertableTypedNode + : never + : never; readWrite: TreeNodeFromImplicitAllowedTypes; output: TreeNodeFromImplicitAllowedTypes; } @@ -774,13 +764,11 @@ export type GetTypes = [TSchema] extends [ * * @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. From 6e4faa2abf28a57e439a022cfc8e5a1348da11d1 Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Tue, 21 Jan 2025 17:12:11 -0800 Subject: [PATCH 18/37] Fix export and reports --- .../dds/tree/api-report/tree.alpha.api.md | 6 ++--- packages/dds/tree/api-report/tree.beta.api.md | 23 ------------------- .../tree/api-report/tree.legacy.alpha.api.md | 23 ------------------- .../tree/api-report/tree.legacy.public.api.md | 23 ------------------- .../dds/tree/api-report/tree.public.api.md | 23 ------------------- .../dds/tree/src/simple-tree/schemaTypes.ts | 2 +- .../api-report/fluid-framework.alpha.api.md | 6 ++--- .../api-report/fluid-framework.beta.api.md | 23 ------------------- .../fluid-framework.legacy.alpha.api.md | 23 ------------------- .../fluid-framework.legacy.public.api.md | 23 ------------------- .../api-report/fluid-framework.public.api.md | 23 ------------------- 11 files changed, 7 insertions(+), 191 deletions(-) diff --git a/packages/dds/tree/api-report/tree.alpha.api.md b/packages/dds/tree/api-report/tree.alpha.api.md index 8900e2824756..cf7585ca2e06 100644 --- a/packages/dds/tree/api-report/tree.alpha.api.md +++ b/packages/dds/tree/api-report/tree.alpha.api.md @@ -102,13 +102,13 @@ export const CustomizedTyping: unique symbol; // @public export type CustomizedTyping = typeof CustomizedTyping; -// @public @sealed +// @alpha @sealed export interface Customizer { custom>(): CustomizedSchemaTyping[Property] : GetTypes[Property]; }>; relaxed(): CustomizedSchemaTyping : TSchema extends AllowedTypes ? TSchema[number] extends LazyItem ? InsertableTypedNode : never : never; + input: TreeNodeSchema extends TSchema ? InsertableContent : TSchema extends TreeNodeSchema ? InsertableTypedNode : TSchema extends AllowedTypes ? TSchema[number] extends LazyItem ? InsertableTypedNode : never : never; readWrite: TreeNodeFromImplicitAllowedTypes; output: TreeNodeFromImplicitAllowedTypes; }>; @@ -314,7 +314,7 @@ type _InlineTrick = 0; export type Input = T; // @alpha -export type Insertable = TSchema extends ImplicitAllowedTypes ? InsertableTreeNodeFromImplicitAllowedTypes : InsertableContent; +export type Insertable = InsertableTreeNodeFromImplicitAllowedTypes; // @alpha export type InsertableContent = Unhydrated | FactoryContent; diff --git a/packages/dds/tree/api-report/tree.beta.api.md b/packages/dds/tree/api-report/tree.beta.api.md index b3a337ff5252..48b46ce39d80 100644 --- a/packages/dds/tree/api-report/tree.beta.api.md +++ b/packages/dds/tree/api-report/tree.beta.api.md @@ -57,29 +57,6 @@ export const CustomizedTyping: unique symbol; // @public export type CustomizedTyping = typeof CustomizedTyping; -// @public @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>; -} - // @public @sealed export interface CustomTypes { readonly input: unknown; diff --git a/packages/dds/tree/api-report/tree.legacy.alpha.api.md b/packages/dds/tree/api-report/tree.legacy.alpha.api.md index 68be0844718a..3c9ba0fbae2d 100644 --- a/packages/dds/tree/api-report/tree.legacy.alpha.api.md +++ b/packages/dds/tree/api-report/tree.legacy.alpha.api.md @@ -57,29 +57,6 @@ export const CustomizedTyping: unique symbol; // @public export type CustomizedTyping = typeof CustomizedTyping; -// @public @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>; -} - // @public @sealed export interface CustomTypes { readonly input: unknown; 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 7569a4b293a3..0e11d8f3d0ff 100644 --- a/packages/dds/tree/api-report/tree.legacy.public.api.md +++ b/packages/dds/tree/api-report/tree.legacy.public.api.md @@ -57,29 +57,6 @@ export const CustomizedTyping: unique symbol; // @public export type CustomizedTyping = typeof CustomizedTyping; -// @public @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>; -} - // @public @sealed export interface CustomTypes { readonly input: unknown; diff --git a/packages/dds/tree/api-report/tree.public.api.md b/packages/dds/tree/api-report/tree.public.api.md index 7569a4b293a3..0e11d8f3d0ff 100644 --- a/packages/dds/tree/api-report/tree.public.api.md +++ b/packages/dds/tree/api-report/tree.public.api.md @@ -57,29 +57,6 @@ export const CustomizedTyping: unique symbol; // @public export type CustomizedTyping = typeof CustomizedTyping; -// @public @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>; -} - // @public @sealed export interface CustomTypes { readonly input: unknown; diff --git a/packages/dds/tree/src/simple-tree/schemaTypes.ts b/packages/dds/tree/src/simple-tree/schemaTypes.ts index ced97aaef682..bd06f335036a 100644 --- a/packages/dds/tree/src/simple-tree/schemaTypes.ts +++ b/packages/dds/tree/src/simple-tree/schemaTypes.ts @@ -671,7 +671,7 @@ export function customizeSchemaTyping( /** * Utility for customizing the types used for data matching a given schema. - * @sealed @public + * @sealed @alpha */ export interface Customizer { /** 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 2ed23121e9d0..7e5d42c60430 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 @@ -140,13 +140,13 @@ export const CustomizedTyping: unique symbol; // @public export type CustomizedTyping = typeof CustomizedTyping; -// @public @sealed +// @alpha @sealed export interface Customizer { custom>(): CustomizedSchemaTyping[Property] : GetTypes[Property]; }>; relaxed(): CustomizedSchemaTyping : TSchema extends AllowedTypes ? TSchema[number] extends LazyItem ? InsertableTypedNode : never : never; + input: TreeNodeSchema extends TSchema ? InsertableContent : TSchema extends TreeNodeSchema ? InsertableTypedNode : TSchema extends AllowedTypes ? TSchema[number] extends LazyItem ? InsertableTypedNode : never : never; readWrite: TreeNodeFromImplicitAllowedTypes; output: TreeNodeFromImplicitAllowedTypes; }>; @@ -624,7 +624,7 @@ type _InlineTrick = 0; export type Input = T; // @alpha -export type Insertable = TSchema extends ImplicitAllowedTypes ? InsertableTreeNodeFromImplicitAllowedTypes : InsertableContent; +export type Insertable = InsertableTreeNodeFromImplicitAllowedTypes; // @alpha export type InsertableContent = Unhydrated | FactoryContent; 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 193f18a2cac9..236084828fe2 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 @@ -92,29 +92,6 @@ export const CustomizedTyping: unique symbol; // @public export type CustomizedTyping = typeof CustomizedTyping; -// @public @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>; -} - // @public @sealed export interface CustomTypes { readonly input: unknown; diff --git a/packages/framework/fluid-framework/api-report/fluid-framework.legacy.alpha.api.md b/packages/framework/fluid-framework/api-report/fluid-framework.legacy.alpha.api.md index c9f3bbe03426..c947b755deb6 100644 --- a/packages/framework/fluid-framework/api-report/fluid-framework.legacy.alpha.api.md +++ b/packages/framework/fluid-framework/api-report/fluid-framework.legacy.alpha.api.md @@ -92,29 +92,6 @@ export const CustomizedTyping: unique symbol; // @public export type CustomizedTyping = typeof CustomizedTyping; -// @public @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>; -} - // @public @sealed export interface CustomTypes { readonly input: unknown; 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 e1afc5dbf2b8..efc76327ddb7 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 @@ -92,29 +92,6 @@ export const CustomizedTyping: unique symbol; // @public export type CustomizedTyping = typeof CustomizedTyping; -// @public @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>; -} - // @public @sealed export interface CustomTypes { readonly input: unknown; 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 5d856f584cdb..64c743806802 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 @@ -92,29 +92,6 @@ export const CustomizedTyping: unique symbol; // @public export type CustomizedTyping = typeof CustomizedTyping; -// @public @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>; -} - // @public @sealed export interface CustomTypes { readonly input: unknown; From a6df85c1da158c27ed6ec0c83b1338590c8d9659 Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Wed, 22 Jan 2025 17:14:18 -0800 Subject: [PATCH 19/37] Fixes tests and cleanup --- .../dds/tree/api-report/tree.alpha.api.md | 15 +- packages/dds/tree/api-report/tree.beta.api.md | 11 +- .../tree/api-report/tree.legacy.alpha.api.md | 11 +- .../tree/api-report/tree.legacy.public.api.md | 11 +- .../dds/tree/api-report/tree.public.api.md | 11 +- packages/dds/tree/src/index.ts | 1 + packages/dds/tree/src/simple-tree/index.ts | 1 + .../dds/tree/src/simple-tree/objectNode.ts | 10 +- .../dds/tree/src/simple-tree/schemaTypes.ts | 65 ++++- .../tree/src/test/openPolymorphism.spec.ts | 223 ++++++++++-------- .../shared-tree-core/sharedTreeCore.spec.ts | 4 +- .../api/schemaFactory.examples.spec.ts | 57 +++++ .../src/test/simple-tree/schemaTypes.spec.ts | 20 ++ .../dds/tree/src/test/util/typeUtils.spec.ts | 38 +++ 14 files changed, 345 insertions(+), 133 deletions(-) diff --git a/packages/dds/tree/api-report/tree.alpha.api.md b/packages/dds/tree/api-report/tree.alpha.api.md index cf7585ca2e06..3742f507c833 100644 --- a/packages/dds/tree/api-report/tree.alpha.api.md +++ b/packages/dds/tree/api-report/tree.alpha.api.md @@ -39,7 +39,7 @@ Kind ] extends [FieldKind.Required] ? T : [Kind] extends [FieldKind.Optional] ? T | undefined : [Kind] extends [FieldKind.Identifier] ? DefaultsAreOptional extends true ? T | undefined : T : never; // @public -export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; +export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; // @public export type AssignableTreeFieldFromImplicitFieldUnsafe> = TSchema extends FieldSchemaUnsafe ? ApplyKindAssignment["readWrite"], Kind> : GetTypesUnsafe["readWrite"]; @@ -91,7 +91,7 @@ export function createSimpleTreeIndex(view: TreeView, indexer: Map, getValue: (nodes: TreeIndexNodes>) => TValue, isKeyValid: (key: TreeIndexKey) => key is TKey, indexableSchema: readonly TSchema[]): SimpleTreeIndex; -// @public (undocumented) +// @public export type CustomizedSchemaTyping = TSchema & { [CustomizedTyping]: TCustom; }; @@ -108,7 +108,7 @@ export interface Customizer { [Property in keyof CustomTypes]: Property extends keyof T ? T[Property] extends CustomTypes[Property] ? T[Property] : GetTypes[Property] : GetTypes[Property]; }>; relaxed(): CustomizedSchemaTyping : TSchema extends AllowedTypes ? TSchema[number] extends LazyItem ? InsertableTypedNode : never : never; + input: TreeNodeSchema extends TSchema ? InsertableContent : TSchema extends TreeNodeSchema ? InsertableTypedNode : TSchema extends AllowedTypes ? TSchema[number] extends LazyItem ? InsertableTypedNode : never : never; readWrite: TreeNodeFromImplicitAllowedTypes; output: TreeNodeFromImplicitAllowedTypes; }>; @@ -126,7 +126,7 @@ export interface Customizer { } // @alpha -export function customizeSchemaTyping(schema: TSchema): Customizer; +export function customizeSchemaTyping(schema: TSchema): Customizer; // @public @sealed export interface CustomTypes { @@ -339,7 +339,7 @@ export type InsertableObjectFromSchemaRecordUnsafe> = [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 export type InsertableTreeFieldFromImplicitFieldUnsafe, TSchema = UnionToIntersection> = [TSchema] extends [FieldSchemaUnsafe] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypesUnsafe : never; @@ -771,6 +771,11 @@ export interface SchemaFactoryObjectOptions extends N allowUnknownOptionalFields?: boolean; } +// @public +export type SchemaUnionToIntersection = [T] extends [ +CustomizedSchemaTyping +] ? T : UnionToIntersection; + // @alpha export interface SchemaValidationFunction { check(data: unknown): data is Static; diff --git a/packages/dds/tree/api-report/tree.beta.api.md b/packages/dds/tree/api-report/tree.beta.api.md index 48b46ce39d80..a98551aee5ff 100644 --- a/packages/dds/tree/api-report/tree.beta.api.md +++ b/packages/dds/tree/api-report/tree.beta.api.md @@ -28,7 +28,7 @@ Kind ] extends [FieldKind.Required] ? T : [Kind] extends [FieldKind.Optional] ? T | undefined : [Kind] extends [FieldKind.Identifier] ? DefaultsAreOptional extends true ? T | undefined : T : never; // @public -export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; +export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; // @public export type AssignableTreeFieldFromImplicitFieldUnsafe> = TSchema extends FieldSchemaUnsafe ? ApplyKindAssignment["readWrite"], Kind> : GetTypesUnsafe["readWrite"]; @@ -46,7 +46,7 @@ export interface CommitMetadata { readonly kind: CommitKind; } -// @public (undocumented) +// @public export type CustomizedSchemaTyping = TSchema & { [CustomizedTyping]: TCustom; }; @@ -175,7 +175,7 @@ export type InsertableObjectFromSchemaRecordUnsafe> = [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 export type InsertableTreeFieldFromImplicitFieldUnsafe, TSchema = UnionToIntersection> = [TSchema] extends [FieldSchemaUnsafe] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypesUnsafe : never; @@ -471,6 +471,11 @@ export class SchemaFactory; } +// @public +export type SchemaUnionToIntersection = [T] extends [ +CustomizedSchemaTyping +] ? T : UnionToIntersection; + // @public type ScopedSchemaName = TScope extends undefined ? `${TName}` : `${TScope}.${TName}`; diff --git a/packages/dds/tree/api-report/tree.legacy.alpha.api.md b/packages/dds/tree/api-report/tree.legacy.alpha.api.md index 3c9ba0fbae2d..0a2f1ef6efe4 100644 --- a/packages/dds/tree/api-report/tree.legacy.alpha.api.md +++ b/packages/dds/tree/api-report/tree.legacy.alpha.api.md @@ -28,7 +28,7 @@ Kind ] extends [FieldKind.Required] ? T : [Kind] extends [FieldKind.Optional] ? T | undefined : [Kind] extends [FieldKind.Identifier] ? DefaultsAreOptional extends true ? T | undefined : T : never; // @public -export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; +export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; // @public export type AssignableTreeFieldFromImplicitFieldUnsafe> = TSchema extends FieldSchemaUnsafe ? ApplyKindAssignment["readWrite"], Kind> : GetTypesUnsafe["readWrite"]; @@ -46,7 +46,7 @@ export interface CommitMetadata { readonly kind: CommitKind; } -// @public (undocumented) +// @public export type CustomizedSchemaTyping = TSchema & { [CustomizedTyping]: TCustom; }; @@ -175,7 +175,7 @@ export type InsertableObjectFromSchemaRecordUnsafe> = [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 export type InsertableTreeFieldFromImplicitFieldUnsafe, TSchema = UnionToIntersection> = [TSchema] extends [FieldSchemaUnsafe] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypesUnsafe : never; @@ -466,6 +466,11 @@ export class SchemaFactory; } +// @public +export type SchemaUnionToIntersection = [T] extends [ +CustomizedSchemaTyping +] ? T : UnionToIntersection; + // @public type ScopedSchemaName = TScope extends undefined ? `${TName}` : `${TScope}.${TName}`; 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 0e11d8f3d0ff..4e06945f7a8a 100644 --- a/packages/dds/tree/api-report/tree.legacy.public.api.md +++ b/packages/dds/tree/api-report/tree.legacy.public.api.md @@ -28,7 +28,7 @@ Kind ] extends [FieldKind.Required] ? T : [Kind] extends [FieldKind.Optional] ? T | undefined : [Kind] extends [FieldKind.Identifier] ? DefaultsAreOptional extends true ? T | undefined : T : never; // @public -export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; +export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; // @public export type AssignableTreeFieldFromImplicitFieldUnsafe> = TSchema extends FieldSchemaUnsafe ? ApplyKindAssignment["readWrite"], Kind> : GetTypesUnsafe["readWrite"]; @@ -46,7 +46,7 @@ export interface CommitMetadata { readonly kind: CommitKind; } -// @public (undocumented) +// @public export type CustomizedSchemaTyping = TSchema & { [CustomizedTyping]: TCustom; }; @@ -175,7 +175,7 @@ export type InsertableObjectFromSchemaRecordUnsafe> = [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 export type InsertableTreeFieldFromImplicitFieldUnsafe, TSchema = UnionToIntersection> = [TSchema] extends [FieldSchemaUnsafe] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypesUnsafe : never; @@ -466,6 +466,11 @@ export class SchemaFactory; } +// @public +export type SchemaUnionToIntersection = [T] extends [ +CustomizedSchemaTyping +] ? T : UnionToIntersection; + // @public type ScopedSchemaName = TScope extends undefined ? `${TName}` : `${TScope}.${TName}`; diff --git a/packages/dds/tree/api-report/tree.public.api.md b/packages/dds/tree/api-report/tree.public.api.md index 0e11d8f3d0ff..4e06945f7a8a 100644 --- a/packages/dds/tree/api-report/tree.public.api.md +++ b/packages/dds/tree/api-report/tree.public.api.md @@ -28,7 +28,7 @@ Kind ] extends [FieldKind.Required] ? T : [Kind] extends [FieldKind.Optional] ? T | undefined : [Kind] extends [FieldKind.Identifier] ? DefaultsAreOptional extends true ? T | undefined : T : never; // @public -export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; +export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; // @public export type AssignableTreeFieldFromImplicitFieldUnsafe> = TSchema extends FieldSchemaUnsafe ? ApplyKindAssignment["readWrite"], Kind> : GetTypesUnsafe["readWrite"]; @@ -46,7 +46,7 @@ export interface CommitMetadata { readonly kind: CommitKind; } -// @public (undocumented) +// @public export type CustomizedSchemaTyping = TSchema & { [CustomizedTyping]: TCustom; }; @@ -175,7 +175,7 @@ export type InsertableObjectFromSchemaRecordUnsafe> = [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 export type InsertableTreeFieldFromImplicitFieldUnsafe, TSchema = UnionToIntersection> = [TSchema] extends [FieldSchemaUnsafe] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypesUnsafe : never; @@ -466,6 +466,11 @@ export class SchemaFactory; } +// @public +export type SchemaUnionToIntersection = [T] extends [ +CustomizedSchemaTyping +] ? T : UnionToIntersection; + // @public type ScopedSchemaName = TScope extends undefined ? `${TName}` : `${TScope}.${TName}`; diff --git a/packages/dds/tree/src/index.ts b/packages/dds/tree/src/index.ts index 864f16518c9b..779ed4f21eb7 100644 --- a/packages/dds/tree/src/index.ts +++ b/packages/dds/tree/src/index.ts @@ -230,6 +230,7 @@ export { type DefaultTreeNodeFromImplicitAllowedTypesUnsafe, type StrictTypesUnsafe, type AssignableTreeFieldFromImplicitFieldUnsafe, + type SchemaUnionToIntersection, } from "./simple-tree/index.js"; export { SharedTree, diff --git a/packages/dds/tree/src/simple-tree/index.ts b/packages/dds/tree/src/simple-tree/index.ts index c45247a8b797..81010111f421 100644 --- a/packages/dds/tree/src/simple-tree/index.ts +++ b/packages/dds/tree/src/simple-tree/index.ts @@ -172,6 +172,7 @@ export { type CustomizedSchemaTyping, CustomizedTyping, type DefaultInsertableTreeNodeFromImplicitAllowedTypes, + type SchemaUnionToIntersection, } from "./schemaTypes.js"; export { getTreeNodeForField, diff --git a/packages/dds/tree/src/simple-tree/objectNode.ts b/packages/dds/tree/src/simple-tree/objectNode.ts index efd6b75a89dc..a6cf8a76b086 100644 --- a/packages/dds/tree/src/simple-tree/objectNode.ts +++ b/packages/dds/tree/src/simple-tree/objectNode.ts @@ -28,6 +28,7 @@ import { FieldKind, type NodeSchemaMetadata, type GetTypes, + type SchemaUnionToIntersection, } from "./schemaTypes.js"; import { type TreeNodeSchema, @@ -43,12 +44,7 @@ import { getOrCreateInnerNode, } from "./core/index.js"; import { mapTreeFromNodeData, type InsertableContent } from "./toMapTree.js"; -import { - type RestrictiveStringRecord, - fail, - type FlattenKeys, - type UnionToIntersection, -} from "../util/index.js"; +import { type RestrictiveStringRecord, fail, type FlattenKeys } from "../util/index.js"; import { isObjectNodeSchema, type ObjectNodeSchema, @@ -85,7 +81,7 @@ export type ObjectFromSchemaRecord, + TSchema = SchemaUnionToIntersection, > = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] diff --git a/packages/dds/tree/src/simple-tree/schemaTypes.ts b/packages/dds/tree/src/simple-tree/schemaTypes.ts index bd06f335036a..eb872ea396e9 100644 --- a/packages/dds/tree/src/simple-tree/schemaTypes.ts +++ b/packages/dds/tree/src/simple-tree/schemaTypes.ts @@ -546,13 +546,27 @@ export type TreeFieldFromImplicitField, + TSchema = [TSchemaInput] extends [CustomizedSchemaTyping] + ? TSchemaInput + : SchemaUnionToIntersection, > = [TSchema] extends [FieldSchema] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never; +/** + * {@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 (UnsafeUnknownSchema:type)} * @alpha @@ -597,8 +611,9 @@ export type CustomizedTyping = typeof CustomizedTyping; /** * Collection of schema aware types. * @remarks - * This type is only uses as a type constraint. + * 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 { @@ -624,6 +639,9 @@ export interface CustomTypes { } /** + * 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 & { @@ -651,16 +669,35 @@ export interface StrictTypes< } /** - * Default strict policy. - * - * @typeparam TSchema - The schema to process - * @typeparam TInput - Internal: do not specify. - * @typeparam TOutput - Internal: do not specify. + * Customizes the types associated with `TSchema` * @remarks - * Handles input types contravariantly so any input which might be invalid is rejected. + * 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 [compleat and less sound](https://en.wikipedia.org/wiki/Soundness#Relation_to_completeness) 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( +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: @@ -691,11 +728,13 @@ export interface Customizer { { input: TreeNodeSchema extends TSchema ? InsertableContent - : TSchema extends TreeNodeSchema + : // This intentionally distributes unions over the conditional to get covariant type handling. + TSchema extends TreeNodeSchema ? InsertableTypedNode - : TSchema extends AllowedTypes + : // This intentionally distributes unions over the conditional to get covariant type handling. + TSchema extends AllowedTypes ? TSchema[number] extends LazyItem - ? InsertableTypedNode + ? InsertableTypedNode : never : never; readWrite: TreeNodeFromImplicitAllowedTypes; @@ -995,6 +1034,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/test/openPolymorphism.spec.ts b/packages/dds/tree/src/test/openPolymorphism.spec.ts index f7167494b1cf..6f754bd904de 100644 --- a/packages/dds/tree/src/test/openPolymorphism.spec.ts +++ b/packages/dds/tree/src/test/openPolymorphism.spec.ts @@ -83,110 +83,143 @@ class TextItem } describe("Open Polymorphism design pattern examples and tests for them", () => { - it("mutable static registry", () => { - // ------------- - // Registry for items. If using this pattern, this would typically be defined alongside the Item interface. - - /** - * Item type registry. - * @remarks - * This doesn't have to be a mutable static. - * For example libraries could export their implementations instead of adding them when imported, - * then the top level code which pulls in all the libraries could aggregate the item types. - * - * TODO: document (and enforce/detect) when how late it is safe to modify array's used as allowed types. - * These docs should ideally align with how late lazy type lambdas are evaluated (when the tree configuration is constructed, or an instance is made, which ever is first? Maybe define schema finalization?) - */ - const ItemTypes: ItemSchema[] = []; - - // ------------- - // Library using an Item - - class Container extends sf.array("Container", ItemTypes) {} - - // ------------- - // Library defining an item - - ItemTypes.push(TextItem); - - // ------------- - // Example use of container with generic code and down casting - - const container = new Container(); - - // If we don't do anything special, the insertable type is never, so a cast is required to insert content. - container.insertAtStart(new TextItem({ text: "", location: { x: 0, y: 0 } }) as never); + describe("mutable static registry", () => { + it("without customizeSchemaTyping", () => { + // ------------- + // Registry for items. If using this pattern, this would typically be defined alongside the Item interface. + + /** + * Item type registry. + * @remarks + * This doesn't have to be a mutable static. + * For example libraries could export their implementations instead of adding them when imported, + * then the top level code which pulls in all the libraries could aggregate the item types. + * + * TODO: document (and enforce/detect) when how late it is safe to modify array's used as allowed types. + * These docs should ideally align with how late lazy type lambdas are evaluated (when the tree configuration is constructed, or an instance is made, which ever is first? Maybe define schema finalization?) + */ + const ItemTypes: ItemSchema[] = []; + + // ------------- + // Library using an Item + + class Container extends sf.array("Container", ItemTypes) {} + + // ------------- + // Library defining an item + + ItemTypes.push(TextItem); + + // ------------- + // Example use of container with generic code and down casting + + const container = new Container(); + + // If we don't do anything special, the insertable type is never, so a cast is required to insert content. + // See example using customizeSchemaTyping for how to avoid this. + container.insertAtStart(new TextItem({ text: "", location: { x: 0, y: 0 } }) as never); + + // Items read from the container are typed as Item and have thew expected APIs: + const first = container[0]; + first.foo(); + first.location.x += 1; + + // Down casting works as normal. + if (Tree.is(first, TextItem)) { + assert.equal(first.text, "foo"); + } + }); + + it("error cases", () => { + const ItemTypes: ItemSchema[] = []; + class Container extends sf.array("Container", ItemTypes) {} + + // Not added to registry + // ItemTypes.push(TextItem); + + const container = new Container(); + + // Should error due to out of schema content + assert.throws( + () => + container.insertAtStart( + new TextItem({ text: "", location: { x: 0, y: 0 } }) as never, + ), + validateUsageError(/schema/), + ); + + // Modifying registration too late should error + assert.throws(() => ItemTypes.push(TextItem)); + }); + + it("recursive case", () => { + const ItemTypes: ItemSchema[] = []; + + // Example recursive item implementation + class Container extends sf.array("Container", 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 {} + } - // Items read from the container are typed as Item and have thew expected APIs: - const first = container[0]; - first.foo(); - first.location.x += 1; + ItemTypes.push(ContainerItem); - // Down casting works as normal. - if (Tree.is(first, TextItem)) { - assert.equal(first.text, "foo"); - } - }); + const container = new Container(); - it("mutable static registry, error cases", () => { - const ItemTypes: ItemSchema[] = []; - class Container extends sf.array("Container", ItemTypes) {} + container.insertAtStart( + new ContainerItem({ container: [], location: { x: 0, y: 0 } }) as never, + ); + }); - // Not added to registry - // ItemTypes.push(TextItem); + 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()), + }) {} - const container = new Container(); + ItemTypes.push(TextItem); - // Should error due to out of schema content - assert.throws( - () => - container.insertAtStart(new TextItem({ text: "", location: { x: 0, y: 0 } }) as never), - validateUsageError(/schema/), - ); + const container = new Container({ child: undefined }); + const container2 = new Container({ child: TextItem.default() }); - // Modifying registration too late should error - assert.throws(() => ItemTypes.push(TextItem)); - }); + // Enabled by customizeSchemaTyping + container.child = TextItem.default(); + container.child = undefined; - it("mutable static registry, recursive case", () => { - const ItemTypes: ItemSchema[] = []; - - // Example recursive item implementation - class Container extends sf.array("Container", 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 } }); + // Allowed at compile time, but not allowed by schema: + class DisallowedItem + extends sf.object("DisallowedItem", { ...itemFields }) + implements Item + { + public foo(): void {} } - public foo(): void {} - } - - ItemTypes.push(ContainerItem); - - const container = new Container(); - - container.insertAtStart( - new ContainerItem({ container: [], location: { x: 0, y: 0 } }) as never, - ); - }); - - it("mutable static registry, safer editing API", () => { - const ItemTypes: ItemSchema[] = []; - class Container extends sf.object("Container", { - 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; + // 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/), + ); + }); }); }); diff --git a/packages/dds/tree/src/test/shared-tree-core/sharedTreeCore.spec.ts b/packages/dds/tree/src/test/shared-tree-core/sharedTreeCore.spec.ts index ceca0748a52f..1be4ee0f4a6f 100644 --- a/packages/dds/tree/src/test/shared-tree-core/sharedTreeCore.spec.ts +++ b/packages/dds/tree/src/test/shared-tree-core/sharedTreeCore.spec.ts @@ -273,9 +273,9 @@ describe("SharedTreeCore", () => { }); const sf = new SchemaFactory("0x4a6 repro"); - const TestNode = sf.objectRecursive("test node", { + class TestNode extends sf.objectRecursive("test node", { child: sf.optionalRecursive([() => TestNode, sf.number]), - }); + }) {} const tree2 = await factory.load( dataStoreRuntime2, 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 9d17a769389c..9e4a2a47c0d0 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 @@ -15,6 +15,8 @@ import { TreeViewConfiguration, type TreeView, customizeSchemaTyping, + type GetTypes, + type Customizer, } from "../../../simple-tree/index.js"; import { TreeFactory } from "../../../treeFactory.js"; import { @@ -190,4 +192,59 @@ describe("Class based end to end example", () => { // @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, + }) {} + + class Relaxed extends schema.object("Relaxed", { + s: customizeSchemaTyping(runtimeDeterminedSchema).relaxed(), + }) {} + + class RelaxedArray extends schema.object("Relaxed", { + s: customizeSchemaTyping([runtimeDeterminedSchema]).relaxed(), + }) {} + + 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/schemaTypes.spec.ts b/packages/dds/tree/src/test/simple-tree/schemaTypes.spec.ts index acc784c12502..ea8a7ab41e7e 100644 --- a/packages/dds/tree/src/test/simple-tree/schemaTypes.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/schemaTypes.spec.ts @@ -19,6 +19,7 @@ import { } from "../../simple-tree/index.js"; import { type AllowedTypes, + type CustomizedSchemaTyping, type DefaultInsertableTreeNodeFromImplicitAllowedTypes, type DefaultTreeNodeFromImplicitAllowedTypes, type FieldSchema, @@ -30,6 +31,7 @@ import { type InsertableTypedNode, type NodeBuilderData, type NodeFromSchema, + type SchemaUnionToIntersection, type TreeFieldFromImplicitField, type TreeLeafValue, type TreeNodeFromImplicitAllowedTypes, @@ -77,6 +79,24 @@ describe("schemaTypes", () => { >; } + // 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 diff --git a/packages/dds/tree/src/test/util/typeUtils.spec.ts b/packages/dds/tree/src/test/util/typeUtils.spec.ts index 0d9a99ebd81f..d8b83ba1bc96 100644 --- a/packages/dds/tree/src/test/util/typeUtils.spec.ts +++ b/packages/dds/tree/src/test/util/typeUtils.spec.ts @@ -89,4 +89,42 @@ import type { areSafelyAssignable, { x: 2 }> >; type _check4 = requireTrue, 1>>; + + type _check5 = requireTrue< + areSafelyAssignable, readonly [1 | 2]> + >; + + { + type intersectedUnion = ({ a: 1 } | { b: 2 }) & { foo: 1 }; + type convertedx = UnionToIntersection; + type converted = UnionToIntersection2; + type _check6 = requireTrue>; + + type UnionToIntersection2 = T extends T ? T : never; + + type _check8 = requireTrue< + areSafelyAssignable< + ({ a: 1 } | { b: 2 }) & { foo: 1 }, + ({ a: 1 } & { foo: 1 }) | ({ b: 2 } & { foo: 1 }) + > + >; + + type _check9 = requireTrue< + areSafelyAssignable< + ({ a: 1 } | { a: 2 }) & { foo: 1 }, + ({ a: 1 } & { foo: 1 }) | ({ a: 2 } & { foo: 1 }) + > + >; + + type _check10 = requireTrue< + areSafelyAssignable< + { a: 1 | 2 } & { foo: 1 }, + ({ a: 1 } & { foo: 1 }) | ({ a: 2 } & { foo: 1 }) + > + >; + } + + type _check7 = requireTrue< + areSafelyAssignable, { a: 1 } & { b: 2 }> + >; } From bdc69597cbe34c1b7ed7903ec8a2c6a3472c530f Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Wed, 22 Jan 2025 17:26:14 -0800 Subject: [PATCH 20/37] update reports --- .../api-report/fluid-framework.alpha.api.md | 15 ++++++++++----- .../api-report/fluid-framework.beta.api.md | 11 ++++++++--- .../fluid-framework.legacy.alpha.api.md | 11 ++++++++--- .../fluid-framework.legacy.public.api.md | 11 ++++++++--- .../api-report/fluid-framework.public.api.md | 11 ++++++++--- 5 files changed, 42 insertions(+), 17 deletions(-) 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 7e5d42c60430..27faa5c11727 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 @@ -39,7 +39,7 @@ Kind ] extends [FieldKind.Required] ? T : [Kind] extends [FieldKind.Optional] ? T | undefined : [Kind] extends [FieldKind.Identifier] ? DefaultsAreOptional extends true ? T | undefined : T : never; // @public -export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; +export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; // @public export type AssignableTreeFieldFromImplicitFieldUnsafe> = TSchema extends FieldSchemaUnsafe ? ApplyKindAssignment["readWrite"], Kind> : GetTypesUnsafe["readWrite"]; @@ -129,7 +129,7 @@ export function createSimpleTreeIndex(view: TreeView, indexer: Map, getValue: (nodes: TreeIndexNodes>) => TValue, isKeyValid: (key: TreeIndexKey) => key is TKey, indexableSchema: readonly TSchema[]): SimpleTreeIndex; -// @public (undocumented) +// @public export type CustomizedSchemaTyping = TSchema & { [CustomizedTyping]: TCustom; }; @@ -146,7 +146,7 @@ export interface Customizer { [Property in keyof CustomTypes]: Property extends keyof T ? T[Property] extends CustomTypes[Property] ? T[Property] : GetTypes[Property] : GetTypes[Property]; }>; relaxed(): CustomizedSchemaTyping : TSchema extends AllowedTypes ? TSchema[number] extends LazyItem ? InsertableTypedNode : never : never; + input: TreeNodeSchema extends TSchema ? InsertableContent : TSchema extends TreeNodeSchema ? InsertableTypedNode : TSchema extends AllowedTypes ? TSchema[number] extends LazyItem ? InsertableTypedNode : never : never; readWrite: TreeNodeFromImplicitAllowedTypes; output: TreeNodeFromImplicitAllowedTypes; }>; @@ -164,7 +164,7 @@ export interface Customizer { } // @alpha -export function customizeSchemaTyping(schema: TSchema): Customizer; +export function customizeSchemaTyping(schema: TSchema): Customizer; // @public @sealed export interface CustomTypes { @@ -649,7 +649,7 @@ export type InsertableObjectFromSchemaRecordUnsafe> = [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 export type InsertableTreeFieldFromImplicitFieldUnsafe, TSchema = UnionToIntersection> = [TSchema] extends [FieldSchemaUnsafe] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypesUnsafe : never; @@ -1129,6 +1129,11 @@ export interface SchemaFactoryObjectOptions extends N allowUnknownOptionalFields?: boolean; } +// @public +export type SchemaUnionToIntersection = [T] extends [ +CustomizedSchemaTyping +] ? T : UnionToIntersection; + // @alpha export interface SchemaValidationFunction { check(data: unknown): data is Static; 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 236084828fe2..3fa5362208b3 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 @@ -28,7 +28,7 @@ Kind ] extends [FieldKind.Required] ? T : [Kind] extends [FieldKind.Optional] ? T | undefined : [Kind] extends [FieldKind.Identifier] ? DefaultsAreOptional extends true ? T | undefined : T : never; // @public -export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; +export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; // @public export type AssignableTreeFieldFromImplicitFieldUnsafe> = TSchema extends FieldSchemaUnsafe ? ApplyKindAssignment["readWrite"], Kind> : GetTypesUnsafe["readWrite"]; @@ -81,7 +81,7 @@ export interface ContainerSchema { readonly initialObjects: Record; } -// @public (undocumented) +// @public export type CustomizedSchemaTyping = TSchema & { [CustomizedTyping]: TCustom; }; @@ -482,7 +482,7 @@ export type InsertableObjectFromSchemaRecordUnsafe> = [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 export type InsertableTreeFieldFromImplicitFieldUnsafe, TSchema = UnionToIntersection> = [TSchema] extends [FieldSchemaUnsafe] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypesUnsafe : never; @@ -826,6 +826,11 @@ export class SchemaFactory; } +// @public +export type SchemaUnionToIntersection = [T] extends [ +CustomizedSchemaTyping +] ? T : UnionToIntersection; + // @public type ScopedSchemaName = TScope extends undefined ? `${TName}` : `${TScope}.${TName}`; diff --git a/packages/framework/fluid-framework/api-report/fluid-framework.legacy.alpha.api.md b/packages/framework/fluid-framework/api-report/fluid-framework.legacy.alpha.api.md index c947b755deb6..80dfe1a93618 100644 --- a/packages/framework/fluid-framework/api-report/fluid-framework.legacy.alpha.api.md +++ b/packages/framework/fluid-framework/api-report/fluid-framework.legacy.alpha.api.md @@ -28,7 +28,7 @@ Kind ] extends [FieldKind.Required] ? T : [Kind] extends [FieldKind.Optional] ? T | undefined : [Kind] extends [FieldKind.Identifier] ? DefaultsAreOptional extends true ? T | undefined : T : never; // @public -export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; +export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; // @public export type AssignableTreeFieldFromImplicitFieldUnsafe> = TSchema extends FieldSchemaUnsafe ? ApplyKindAssignment["readWrite"], Kind> : GetTypesUnsafe["readWrite"]; @@ -81,7 +81,7 @@ export interface ContainerSchema { readonly initialObjects: Record; } -// @public (undocumented) +// @public export type CustomizedSchemaTyping = TSchema & { [CustomizedTyping]: TCustom; }; @@ -582,7 +582,7 @@ export type InsertableObjectFromSchemaRecordUnsafe> = [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 export type InsertableTreeFieldFromImplicitFieldUnsafe, TSchema = UnionToIntersection> = [TSchema] extends [FieldSchemaUnsafe] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypesUnsafe : never; @@ -1123,6 +1123,11 @@ export class SchemaFactory; } +// @public +export type SchemaUnionToIntersection = [T] extends [ +CustomizedSchemaTyping +] ? T : UnionToIntersection; + // @public type ScopedSchemaName = TScope extends undefined ? `${TName}` : `${TScope}.${TName}`; 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 efc76327ddb7..8ec81c8aa7ba 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 @@ -28,7 +28,7 @@ Kind ] extends [FieldKind.Required] ? T : [Kind] extends [FieldKind.Optional] ? T | undefined : [Kind] extends [FieldKind.Identifier] ? DefaultsAreOptional extends true ? T | undefined : T : never; // @public -export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; +export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; // @public export type AssignableTreeFieldFromImplicitFieldUnsafe> = TSchema extends FieldSchemaUnsafe ? ApplyKindAssignment["readWrite"], Kind> : GetTypesUnsafe["readWrite"]; @@ -81,7 +81,7 @@ export interface ContainerSchema { readonly initialObjects: Record; } -// @public (undocumented) +// @public export type CustomizedSchemaTyping = TSchema & { [CustomizedTyping]: TCustom; }; @@ -510,7 +510,7 @@ export type InsertableObjectFromSchemaRecordUnsafe> = [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 export type InsertableTreeFieldFromImplicitFieldUnsafe, TSchema = UnionToIntersection> = [TSchema] extends [FieldSchemaUnsafe] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypesUnsafe : never; @@ -857,6 +857,11 @@ export class SchemaFactory; } +// @public +export type SchemaUnionToIntersection = [T] extends [ +CustomizedSchemaTyping +] ? T : UnionToIntersection; + // @public type ScopedSchemaName = TScope extends undefined ? `${TName}` : `${TScope}.${TName}`; 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 64c743806802..aac7170c8e36 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 @@ -28,7 +28,7 @@ Kind ] extends [FieldKind.Required] ? T : [Kind] extends [FieldKind.Optional] ? T | undefined : [Kind] extends [FieldKind.Identifier] ? DefaultsAreOptional extends true ? T | undefined : T : never; // @public -export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; +export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; // @public export type AssignableTreeFieldFromImplicitFieldUnsafe> = TSchema extends FieldSchemaUnsafe ? ApplyKindAssignment["readWrite"], Kind> : GetTypesUnsafe["readWrite"]; @@ -81,7 +81,7 @@ export interface ContainerSchema { readonly initialObjects: Record; } -// @public (undocumented) +// @public export type CustomizedSchemaTyping = TSchema & { [CustomizedTyping]: TCustom; }; @@ -482,7 +482,7 @@ export type InsertableObjectFromSchemaRecordUnsafe> = [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 export type InsertableTreeFieldFromImplicitFieldUnsafe, TSchema = UnionToIntersection> = [TSchema] extends [FieldSchemaUnsafe] ? ApplyKindInput, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypesUnsafe : never; @@ -821,6 +821,11 @@ export class SchemaFactory; } +// @public +export type SchemaUnionToIntersection = [T] extends [ +CustomizedSchemaTyping +] ? T : UnionToIntersection; + // @public type ScopedSchemaName = TScope extends undefined ? `${TName}` : `${TScope}.${TName}`; From 86a4b5d8b5aa7dc686a67d9ee14ae87985ab1945 Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Thu, 23 Jan 2025 10:06:04 -0800 Subject: [PATCH 21/37] Better document and test ObjectFromSchemaRecordUnsafe --- .changeset/metal-sloths-join.md | 3 + .../tree/src/simple-tree/api/typesUnsafe.ts | 23 ++----- .../dds/tree/src/simple-tree/schemaTypes.ts | 3 + .../simple-tree/api/integrationTests.spec.ts | 35 ++++++++++ .../api/schemaFactoryRecursive.spec.ts | 64 ++++++++++++++++++- 5 files changed, 110 insertions(+), 18 deletions(-) create mode 100644 packages/dds/tree/src/test/simple-tree/api/integrationTests.spec.ts diff --git a/.changeset/metal-sloths-join.md b/.changeset/metal-sloths-join.md index 9df8ed6e43ae..47f9a5f42953 100644 --- a/.changeset/metal-sloths-join.md +++ b/.changeset/metal-sloths-join.md @@ -22,3 +22,6 @@ Two cases which were actually possible to disallow and should be disallowed for 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/src/simple-tree/api/typesUnsafe.ts b/packages/dds/tree/src/simple-tree/api/typesUnsafe.ts index ba4c28b96bab..e55e01321146 100644 --- a/packages/dds/tree/src/simple-tree/api/typesUnsafe.ts +++ b/packages/dds/tree/src/simple-tree/api/typesUnsafe.ts @@ -138,6 +138,8 @@ export type ObjectFromSchemaRecordUnsafe< T extends Unenforced>, > = // 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< @@ -149,8 +151,8 @@ export type ObjectFromSchemaRecordUnsafe< } >, ] - ? never - : Property]: AssignableTreeFieldFromImplicitFieldUnsafe; // + ? never // Remove readWrite version for cases using CustomizedSchemaTyping to set readWrite to never. + : Property]: AssignableTreeFieldFromImplicitFieldUnsafe; } & { readonly [Property in keyof T as [T[Property]] extends [ CustomizedSchemaTyping< @@ -162,24 +164,11 @@ export type ObjectFromSchemaRecordUnsafe< } >, ] - ? Property + ? // 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]: TreeFieldFromImplicitFieldUnsafe; }; -// { -// -readonly [Property in keyof T as never extends AssignableTreeFieldFromImplicitFieldUnsafe< -// T[Property] -// > -// ? never -// : Property]: TreeFieldFromImplicitFieldUnsafe; -// } & { -// readonly [Property in keyof T as never extends AssignableTreeFieldFromImplicitFieldUnsafe< -// T[Property] -// > -// ? Property -// : never]: TreeFieldFromImplicitFieldUnsafe; -// }; - /** * {@link Unenforced} version of `AssignableTreeFieldFromImplicitField`. * @remarks diff --git a/packages/dds/tree/src/simple-tree/schemaTypes.ts b/packages/dds/tree/src/simple-tree/schemaTypes.ts index eb872ea396e9..e0ef44a9f407 100644 --- a/packages/dds/tree/src/simple-tree/schemaTypes.ts +++ b/packages/dds/tree/src/simple-tree/schemaTypes.ts @@ -628,6 +628,9 @@ export interface CustomTypes { * @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; /** diff --git a/packages/dds/tree/src/test/simple-tree/api/integrationTests.spec.ts b/packages/dds/tree/src/test/simple-tree/api/integrationTests.spec.ts new file mode 100644 index 000000000000..64fd0a4d2f12 --- /dev/null +++ b/packages/dds/tree/src/test/simple-tree/api/integrationTests.spec.ts @@ -0,0 +1,35 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import { strict as assert } from "node:assert"; + +import { SchemaFactory } from "../../../simple-tree/index.js"; +import type { + ValidateRecursiveSchema, + // eslint-disable-next-line import/no-internal-modules +} from "../../../simple-tree/api/schemaFactoryRecursive.js"; +import { validateUsageError } from "../../utils.js"; + +const sf = new SchemaFactory("integration"); + +describe("simple-tree API integration tests", () => { + // TODO: this case should produce a usage error. + // Depending on where the error is detected, tests for recursive maps, arrays and co-recursive cases may be needed. + it("making a recursive unhydrated object node errors", () => { + class O extends sf.objectRecursive("O", { + recursive: sf.optionalRecursive([() => O]), + }) {} + { + type _check = ValidateRecursiveSchema; + } + const obj = new O({ recursive: undefined }); + assert.throws( + () => { + obj.recursive = obj; + }, + validateUsageError(/recursive/), + ); + }); +}); 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 8a7504e12125..110629541c3e 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 @@ -24,6 +24,8 @@ import { type NodeBuilderData, SchemaFactoryAlpha, customizeSchemaTyping, + type AssignableTreeFieldFromImplicitField, + type ObjectFromSchemaRecord, } from "../../../simple-tree/index.js"; import type { ValidateRecursiveSchema, @@ -760,7 +762,67 @@ describe("SchemaFactory Recursive methods", () => { type _checkRead = requireAssignableTo; // @ts-expect-error Readonly. - obj.recursive = obj; + 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; + // @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; + // @ts-expect-error required. + obj.req = undefined; }); }); }); From addb0229854a585ca89faf7171f930511a4fa101 Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Thu, 23 Jan 2025 12:35:21 -0800 Subject: [PATCH 22/37] Skip failing/broken/hanging test --- .../dds/tree/src/test/simple-tree/api/integrationTests.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dds/tree/src/test/simple-tree/api/integrationTests.spec.ts b/packages/dds/tree/src/test/simple-tree/api/integrationTests.spec.ts index 64fd0a4d2f12..e615fc6e20d3 100644 --- a/packages/dds/tree/src/test/simple-tree/api/integrationTests.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/api/integrationTests.spec.ts @@ -17,7 +17,7 @@ const sf = new SchemaFactory("integration"); describe("simple-tree API integration tests", () => { // TODO: this case should produce a usage error. // Depending on where the error is detected, tests for recursive maps, arrays and co-recursive cases may be needed. - it("making a recursive unhydrated object node errors", () => { + it.skip("making a recursive unhydrated object node errors", () => { class O extends sf.objectRecursive("O", { recursive: sf.optionalRecursive([() => O]), }) {} From 224c7b578d97c5529896afd0f9e7dddaff980fc9 Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Mon, 3 Mar 2025 17:13:41 -0800 Subject: [PATCH 23/37] add components example --- .../tree/src/test/openPolymorphism.spec.ts | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/packages/dds/tree/src/test/openPolymorphism.spec.ts b/packages/dds/tree/src/test/openPolymorphism.spec.ts index 6f754bd904de..893648cb22ae 100644 --- a/packages/dds/tree/src/test/openPolymorphism.spec.ts +++ b/packages/dds/tree/src/test/openPolymorphism.spec.ts @@ -7,6 +7,7 @@ import { strict as assert } from "node:assert"; import { SchemaFactory, + TreeViewConfiguration, type NodeKind, type ObjectFromSchemaRecord, type TreeNode, @@ -221,5 +222,76 @@ describe("Open Polymorphism design pattern examples and tests for them", () => { 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 uncashedItemTypes: LazyItems = allComponents.flatMap( + (component): LazyItems => component.itemTypes(lazyConfig), + ); + + const ItemTypes = uncashedItemTypes.map((uncached) => { + let cache: ItemSchema | undefined; + return () => { + cache ??= uncached(); + return cache; + }; + }); + 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, + }); + }); }); }); From 9e1328b682e144cc3f425c07c25260670fd5fdfd Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Mon, 3 Mar 2025 17:35:44 -0800 Subject: [PATCH 24/37] Add generic components system --- .../tree/src/test/openPolymorphism.spec.ts | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/packages/dds/tree/src/test/openPolymorphism.spec.ts b/packages/dds/tree/src/test/openPolymorphism.spec.ts index 893648cb22ae..a643752e7eee 100644 --- a/packages/dds/tree/src/test/openPolymorphism.spec.ts +++ b/packages/dds/tree/src/test/openPolymorphism.spec.ts @@ -293,5 +293,111 @@ describe("Open Polymorphism design pattern examples and tests for them", () => { preventAmbiguity: true, }); }); + + it("generic components system", () => { + /** + * Function which takes in a lazy configuration and returns a collection of schema types. + * @remarks + * This allows the schema to reference items from the configuration, which could include themselves recursively. + */ + type ComponentSchemaCollection = ( + lazyConfig: () => TConfig, + ) => LazyArray; + + type LazyArray = readonly (() => T)[]; + + function composeComponentSchema( + allComponents: readonly ComponentSchemaCollection[], + lazyConfig: () => TConfig, + ): (() => TItem)[] { + const uncashedItemTypes: LazyArray = allComponents.flatMap( + (component): LazyArray => component(lazyConfig), + ); + + const ItemTypes = uncashedItemTypes.map((uncached) => { + let cache: TItem | undefined; + return () => { + cache ??= uncached(); + return cache; + }; + }); + + return ItemTypes; + } + + // App specific // + + /** + * Example configuration type for an application. + * + * Contains a collection of schema to demonstrate how ComponentSchemaCollection works for schema dependency inversions. + */ + interface MyAppConfig { + readonly ItemTypes: LazyArray; + } + + /** + * 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: ComponentSchemaCollection; + } + + /** + * 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 = composeComponentSchema( + allComponents.map((c) => c.itemTypes), + lazyConfig, + ); + const config: MyAppConfig = { ItemTypes }; + return config; + } + + // An example simple component + const textComponent: MyAppComponent = { + itemTypes: (): LazyArray => [() => TextItem], + }; + + // An example component which references schema from the configuration and can be recursive through it. + const containerComponent: MyAppComponent = { + itemTypes: (lazyConfig: () => MyAppConfig): LazyArray => [ + () => createContainer(lazyConfig()), + ], + }; + 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 appConfig = composeComponents([containerComponent, textComponent]); + + const treeConfig = new TreeViewConfiguration({ + schema: appConfig.ItemTypes, + enableSchemaValidation: true, + preventAmbiguity: true, + }); + }); }); }); From b1b2c413343426eab0357e0d6234a2892902e129 Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Tue, 4 Mar 2025 10:35:33 -0800 Subject: [PATCH 25/37] Apply suggestions from code review Co-authored-by: Tommy Brosman --- packages/dds/tree/src/test/openPolymorphism.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/dds/tree/src/test/openPolymorphism.spec.ts b/packages/dds/tree/src/test/openPolymorphism.spec.ts index a643752e7eee..cd52fbc5edcd 100644 --- a/packages/dds/tree/src/test/openPolymorphism.spec.ts +++ b/packages/dds/tree/src/test/openPolymorphism.spec.ts @@ -236,11 +236,11 @@ describe("Open Polymorphism design pattern examples and tests for them", () => { function composeComponents(allComponents: readonly MyAppComponent[]): MyAppConfig { const lazyConfig = () => config; - const uncashedItemTypes: LazyItems = allComponents.flatMap( + const uncachedItemTypes: LazyItems = allComponents.flatMap( (component): LazyItems => component.itemTypes(lazyConfig), ); - const ItemTypes = uncashedItemTypes.map((uncached) => { + const ItemTypes = uncachedItemTypes.map((uncached) => { let cache: ItemSchema | undefined; return () => { cache ??= uncached(); From 23487bf89b9bb5edfa9ade16724be7e8025d002e Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Tue, 4 Mar 2025 13:00:56 -0800 Subject: [PATCH 26/37] More consistent and robust handling of lazy schema --- .../dds/tree/api-report/tree.alpha.api.md | 26 ++++++-- .../tree/api-report/tree.legacy.alpha.api.md | 20 +++++++ .../tree/src/simple-tree/api/schemaFactory.ts | 29 +-------- packages/dds/tree/src/simple-tree/api/tree.ts | 9 ++- .../tree/src/simple-tree/api/treeNodeApi.ts | 19 +----- packages/dds/tree/src/simple-tree/flexList.ts | 18 ++---- packages/dds/tree/src/simple-tree/index.ts | 7 ++- .../dds/tree/src/simple-tree/objectNode.ts | 3 + .../dds/tree/src/simple-tree/schemaTypes.ts | 59 +++++++++++++++---- .../tree/src/simple-tree/toStoredSchema.ts | 9 +-- .../dds/tree/src/simple-tree/treeNodeValid.ts | 21 +++++-- .../tree/src/test/openPolymorphism.spec.ts | 28 ++------- .../api/schemaFactoryRecursive.spec.ts | 7 ++- .../src/test/simple-tree/flexList.spec.ts | 15 ----- .../api-report/fluid-framework.alpha.api.md | 8 +++ 15 files changed, 151 insertions(+), 127 deletions(-) diff --git a/packages/dds/tree/api-report/tree.alpha.api.md b/packages/dds/tree/api-report/tree.alpha.api.md index 8dc7df49bd9b..425bec10fdd2 100644 --- a/packages/dds/tree/api-report/tree.alpha.api.md +++ b/packages/dds/tree/api-report/tree.alpha.api.md @@ -452,21 +452,21 @@ export namespace JsonAsTree { export class JsonObject extends _APIExtractorWorkaroundObjectBase { } const _APIExtractorWorkaroundObjectBase: TreeNodeSchemaClass_2<"com.fluidframework.json.object", NodeKind_2.Map, TreeMapNodeUnsafe_2 typeof JsonObject, () => typeof Array, TreeNodeSchemaNonClass_2<"com.fluidframework.leaf.null", NodeKind_2.Leaf, null, null, true, unknown, never, unknown>, TreeNodeSchemaNonClass_2<"com.fluidframework.leaf.boolean", NodeKind_2.Leaf, boolean, boolean, true, unknown, never, unknown>, TreeNodeSchemaNonClass_2<"com.fluidframework.leaf.number", NodeKind_2.Leaf, number, number, true, unknown, never, unknown>, TreeNodeSchemaNonClass_2<"com.fluidframework.leaf.string", NodeKind_2.Leaf, string, string, true, unknown, never, unknown>]> & WithType_2<"com.fluidframework.json.object", NodeKind_2.Map, unknown>, { - [Symbol.iterator](): Iterator<[string, string | number | JsonObject | Array | InsertableTypedNodeUnsafe_2, TreeNodeSchemaCore_2<"com.fluidframework.leaf.boolean", NodeKind_2.Leaf, true, unknown, boolean, unknown> & { + [Symbol.iterator](): Iterator<[string, string | number | InsertableTypedNodeUnsafe_2, TreeNodeSchemaCore_2<"com.fluidframework.leaf.boolean", NodeKind_2.Leaf, true, unknown, boolean, unknown> & { create(data: boolean): boolean; - }> | null], any, undefined>; + }> | JsonObject | Array | null], any, undefined>; } | { - readonly [x: string]: string | number | JsonObject | Array | InsertableTypedNodeUnsafe_2, TreeNodeSchemaCore_2<"com.fluidframework.leaf.boolean", NodeKind_2.Leaf, true, unknown, boolean, unknown> & { + readonly [x: string]: string | number | InsertableTypedNodeUnsafe_2, TreeNodeSchemaCore_2<"com.fluidframework.leaf.boolean", NodeKind_2.Leaf, true, unknown, boolean, unknown> & { create(data: boolean): boolean; - }> | null; + }> | JsonObject | Array | null; }, false, readonly [() => typeof JsonObject, () => typeof Array, TreeNodeSchemaNonClass_2<"com.fluidframework.leaf.null", NodeKind_2.Leaf, null, null, true, unknown, never, unknown>, TreeNodeSchemaNonClass_2<"com.fluidframework.leaf.boolean", NodeKind_2.Leaf, boolean, boolean, true, unknown, never, unknown>, TreeNodeSchemaNonClass_2<"com.fluidframework.leaf.number", NodeKind_2.Leaf, number, number, true, unknown, never, unknown>, TreeNodeSchemaNonClass_2<"com.fluidframework.leaf.string", NodeKind_2.Leaf, string, string, true, unknown, never, unknown>], undefined>; // (undocumented) export type Primitive = TreeNodeFromImplicitAllowedTypes; export type _RecursiveArrayWorkaroundJsonArray = FixRecursiveArraySchema; const _APIExtractorWorkaroundArrayBase: TreeNodeSchemaClass_2<"com.fluidframework.json.array", NodeKind_2.Array, TreeArrayNodeUnsafe_2 typeof JsonObject, () => typeof Array, TreeNodeSchemaNonClass_2<"com.fluidframework.leaf.null", NodeKind_2.Leaf, null, null, true, unknown, never, unknown>, TreeNodeSchemaNonClass_2<"com.fluidframework.leaf.boolean", NodeKind_2.Leaf, boolean, boolean, true, unknown, never, unknown>, TreeNodeSchemaNonClass_2<"com.fluidframework.leaf.number", NodeKind_2.Leaf, number, number, true, unknown, never, unknown>, TreeNodeSchemaNonClass_2<"com.fluidframework.leaf.string", NodeKind_2.Leaf, string, string, true, unknown, never, unknown>]> & WithType_2<"com.fluidframework.json.array", NodeKind_2.Array, unknown>, { - [Symbol.iterator](): Iterator, TreeNodeSchemaCore_2<"com.fluidframework.leaf.boolean", NodeKind_2.Leaf, true, unknown, boolean, unknown> & { + [Symbol.iterator](): Iterator, TreeNodeSchemaCore_2<"com.fluidframework.leaf.boolean", NodeKind_2.Leaf, true, unknown, boolean, unknown> & { create(data: boolean): boolean; - }> | null, any, undefined>; + }> | JsonObject | Array | null, any, undefined>; }, false, readonly [() => typeof JsonObject, () => typeof Array, TreeNodeSchemaNonClass_2<"com.fluidframework.leaf.null", NodeKind_2.Leaf, null, null, true, unknown, never, unknown>, TreeNodeSchemaNonClass_2<"com.fluidframework.leaf.boolean", NodeKind_2.Leaf, boolean, boolean, true, unknown, never, unknown>, TreeNodeSchemaNonClass_2<"com.fluidframework.leaf.number", NodeKind_2.Leaf, number, number, true, unknown, never, unknown>, TreeNodeSchemaNonClass_2<"com.fluidframework.leaf.string", NodeKind_2.Leaf, string, string, true, unknown, never, unknown>], undefined>; // (undocumented) export type Tree = TreeNodeFromImplicitAllowedTypes; @@ -813,6 +813,20 @@ export interface SchemaFactoryObjectOptions extends N allowUnknownOptionalFields?: boolean; } +// @public @sealed +export const schemaStatics: { + readonly string: TreeNodeSchemaNonClass<"com.fluidframework.leaf.string", NodeKind.Leaf, string, string, true, unknown, never, unknown>; + readonly number: TreeNodeSchemaNonClass<"com.fluidframework.leaf.number", NodeKind.Leaf, number, number, true, unknown, never, unknown>; + readonly boolean: TreeNodeSchemaNonClass<"com.fluidframework.leaf.boolean", NodeKind.Leaf, boolean, boolean, true, unknown, never, unknown>; + readonly null: TreeNodeSchemaNonClass<"com.fluidframework.leaf.null", NodeKind.Leaf, null, null, true, unknown, never, unknown>; + readonly handle: TreeNodeSchemaNonClass<"com.fluidframework.leaf.handle", NodeKind.Leaf, IFluidHandle, IFluidHandle, true, unknown, never, unknown>; + readonly leaves: readonly [TreeNodeSchemaNonClass<"com.fluidframework.leaf.string", NodeKind.Leaf, string, string, true, unknown, never, unknown>, TreeNodeSchemaNonClass<"com.fluidframework.leaf.number", NodeKind.Leaf, number, number, true, unknown, never, unknown>, TreeNodeSchemaNonClass<"com.fluidframework.leaf.boolean", NodeKind.Leaf, boolean, boolean, true, unknown, never, unknown>, TreeNodeSchemaNonClass<"com.fluidframework.leaf.null", NodeKind.Leaf, null, null, true, unknown, never, unknown>, TreeNodeSchemaNonClass<"com.fluidframework.leaf.handle", NodeKind.Leaf, IFluidHandle, IFluidHandle, true, unknown, never, unknown>]; + readonly optional: (t: T, props?: Omit, "defaultProvider">) => FieldSchema; + readonly required: (t: T_1, props?: Omit, "defaultProvider"> | undefined) => FieldSchema; + readonly optionalRecursive: (t: T_2, props?: Omit) => FieldSchemaUnsafe; + readonly requiredRecursive: (t: T_3, props?: Omit) => FieldSchemaUnsafe; +}; + // @public export type SchemaUnionToIntersection = [T] extends [ CustomizedSchemaTyping diff --git a/packages/dds/tree/api-report/tree.legacy.alpha.api.md b/packages/dds/tree/api-report/tree.legacy.alpha.api.md index 8c641a268f43..356eab39bee4 100644 --- a/packages/dds/tree/api-report/tree.legacy.alpha.api.md +++ b/packages/dds/tree/api-report/tree.legacy.alpha.api.md @@ -508,6 +508,26 @@ export const SharedTreeAttributes: IChannelAttributes; // @alpha export const SharedTreeFactoryType = "https://graph.microsoft.com/types/tree"; +// @public @sealed +export interface StrictTypes, TOutput extends TreeNode | TreeLeafValue = DefaultTreeNodeFromImplicitAllowedTypes> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TInput extends never ? never : TOutput; +} + +// @public +export interface StrictTypesUnsafe, TInput = DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe, TOutput = DefaultTreeNodeFromImplicitAllowedTypesUnsafe> { + // (undocumented) + input: TInput; + // (undocumented) + output: TOutput; + // (undocumented) + readWrite: TOutput; +} + // @public export type TransactionConstraint = NodeInDocumentConstraint; diff --git a/packages/dds/tree/src/simple-tree/api/schemaFactory.ts b/packages/dds/tree/src/simple-tree/api/schemaFactory.ts index 0a7ead94e5f3..6a82577b112d 100644 --- a/packages/dds/tree/src/simple-tree/api/schemaFactory.ts +++ b/packages/dds/tree/src/simple-tree/api/schemaFactory.ts @@ -8,7 +8,6 @@ import { assert, unreachableCase } from "@fluidframework/core-utils/internal"; // which degrades the API-Extractor report quality since API-Extractor can not tell the inline import is the same as the non-inline one. // eslint-disable-next-line unused-imports/no-unused-imports import type { IFluidHandle as _dummyImport } from "@fluidframework/core-interfaces"; -import { UsageError } from "@fluidframework/telemetry-utils/internal"; import { isFluidHandle } from "@fluidframework/runtime-utils/internal"; import type { TreeValue } from "../../core/index.js"; @@ -25,7 +24,6 @@ import type { TreeAlpha } from "../../shared-tree/index.js"; import { booleanSchema, handleSchema, - LeafNodeSchema, nullSchema, numberSchema, stringSchema, @@ -41,8 +39,8 @@ import { type DefaultProvider, getDefaultProvider, type NodeSchemaOptions, + markSchemaMostDerived, } from "../schemaTypes.js"; -import { inPrototypeChain } from "../core/index.js"; import type { NodeKind, WithType, @@ -76,7 +74,6 @@ import type { Unenforced, } from "./typesUnsafe.js"; import { createFieldSchemaUnsafe } from "./schemaFactoryRecursive.js"; -import { TreeNodeValid } from "../treeNodeValid.js"; import { isLazy } from "../flexList.js"; /** @@ -1070,27 +1067,3 @@ export function structuralName( } return `${collectionName}<${inner}>`; } - -/** - * Indicates that a schema is the "most derived" version which is allowed to be used, see {@link MostDerivedData}. - * Calling helps with error messages about invalid schema usage (using more than one type from single schema factor produced type, - * and thus calling this for one than one subclass). - * @remarks - * Helper for invoking {@link TreeNodeValid.markMostDerived} for any {@link TreeNodeSchema} if it needed. - */ -export function markSchemaMostDerived(schema: TreeNodeSchema): void { - if (schema instanceof LeafNodeSchema) { - return; - } - - if (!inPrototypeChain(schema, TreeNodeValid)) { - // Use JSON.stringify to quote and escape identifier string. - throw new UsageError( - `Schema for ${JSON.stringify( - schema.identifier, - )} does not extend a SchemaFactory generated class. This is invalid.`, - ); - } - - (schema as typeof TreeNodeValid & TreeNodeSchema).markMostDerived(); -} diff --git a/packages/dds/tree/src/simple-tree/api/tree.ts b/packages/dds/tree/src/simple-tree/api/tree.ts index 349026f7bee7..9d2e50ae4239 100644 --- a/packages/dds/tree/src/simple-tree/api/tree.ts +++ b/packages/dds/tree/src/simple-tree/api/tree.ts @@ -27,14 +27,14 @@ import { type TreeFieldFromImplicitField, type UnsafeUnknownSchema, FieldKind, + markSchemaMostDerived, } from "../schemaTypes.js"; import { NodeKind, type TreeNodeSchema } from "../core/index.js"; import { toStoredSchema } from "../toStoredSchema.js"; import { LeafNodeSchema } from "../leafNodeSchema.js"; import { assert } from "@fluidframework/core-utils/internal"; import { isObjectNodeSchema, type ObjectNodeSchema } from "../objectNodeTypes.js"; -import { markSchemaMostDerived } from "./schemaFactory.js"; -import { fail, getOrCreate } from "../../util/index.js"; +import { fail, getOrCreate, isReadonlyArray } from "../../util/index.js"; import type { MakeNominal } from "../../util/index.js"; import { walkFieldSchema } from "../walkFieldSchema.js"; import type { VerboseTree } from "./verboseTree.js"; @@ -285,6 +285,11 @@ export class TreeViewConfiguration< if (config.preventAmbiguity) { checkUnion(types, ambiguityErrors); } + if (isReadonlyArray(types)) { + // Ensure late additions to unions error. + // TODO: it would be better to do this as part of oneTimeInitialize so more cases trigger it, but this is better than nothing, and really easy to do: + Object.freeze(types); + } }, }); diff --git a/packages/dds/tree/src/simple-tree/api/treeNodeApi.ts b/packages/dds/tree/src/simple-tree/api/treeNodeApi.ts index 9896e0fbc16c..6d3a8ea1e413 100644 --- a/packages/dds/tree/src/simple-tree/api/treeNodeApi.ts +++ b/packages/dds/tree/src/simple-tree/api/treeNodeApi.ts @@ -7,13 +7,14 @@ import { assert, oob } from "@fluidframework/core-utils/internal"; import { EmptyKey, rootFieldKey } from "../../core/index.js"; import { type TreeStatus, isTreeValue, FieldKinds } from "../../feature-libraries/index.js"; -import { fail, extractFromOpaque, isReadonlyArray } from "../../util/index.js"; +import { fail, extractFromOpaque } from "../../util/index.js"; import { type TreeLeafValue, type ImplicitFieldSchema, FieldSchema, type ImplicitAllowedTypes, type TreeNodeFromImplicitAllowedTypes, + normalizeAllowedTypes, } from "../schemaTypes.js"; import { booleanSchema, @@ -39,8 +40,6 @@ import { getOrCreateInnerNode, } from "../core/index.js"; import { isObjectNodeSchema } from "../objectNodeTypes.js"; -import { isLazy, type LazyItem } from "../flexList.js"; -import { markSchemaMostDerived } from "./schemaFactory.js"; /** * Provides various functions for analyzing {@link TreeNode}s. @@ -212,19 +211,7 @@ export const treeNodeApi: TreeNodeApi = { if (actualSchema === undefined) { return false; } - if (isReadonlyArray>(schema)) { - for (const singleSchema of schema) { - const testSchema = isLazy(singleSchema) ? singleSchema() : singleSchema; - markSchemaMostDerived(testSchema); - if (testSchema === actualSchema) { - return true; - } - } - return false; - } else { - markSchemaMostDerived(schema); - return schema === actualSchema; - } + return normalizeAllowedTypes(schema).has(actualSchema); }, schema(node: TreeNode | TreeLeafValue): TreeNodeSchema { return tryGetSchema(node) ?? fail(0xb37 /* Not a tree node */); diff --git a/packages/dds/tree/src/simple-tree/flexList.ts b/packages/dds/tree/src/simple-tree/flexList.ts index 830596a374f6..bce6fcd34bd6 100644 --- a/packages/dds/tree/src/simple-tree/flexList.ts +++ b/packages/dds/tree/src/simple-tree/flexList.ts @@ -44,28 +44,18 @@ export function markEager(t: T): T { * By default, items that are of type `"function"` will be considered lazy and all other items will be considered eager. * To force a `"function"` item to be treated as an eager item, call `markEager` before putting it in the list. * This is necessary e.g. when the eager list items are function types and the lazy items are functions that _return_ function types. - * `FlexList`s are processed by `normalizeFlexList` and `normalizeFlexListEager`. + * Our one use of FlexList has some special normalization logic, see {@link normalizeAllowedTypes}. * @system @public */ export type FlexList = readonly LazyItem[]; -/** - * Given a `FlexList` of eager and lazy items, return an equivalent list where all items are eager. - */ -export function normalizeFlexListEager(t: FlexList): T[] { - const data: T[] = t.map((value: LazyItem) => { - if (isLazy(value)) { - return value(); - } - return value; - }); - return data; -} - /** * An "eager" or "lazy" Item in a `FlexList`. * Lazy items are wrapped in a function to allow referring to themselves before they are declared. * This makes recursive and co-recursive items possible. + * @privateRemarks + * `schemaTypes.ts`'s `evaluateLazySchema` (via {@link normalizeAllowedTypes}) + * applies caching for the only current use of this type. * @public */ export type LazyItem = Item | (() => Item); diff --git a/packages/dds/tree/src/simple-tree/index.ts b/packages/dds/tree/src/simple-tree/index.ts index ca2732156d6b..90a32c4d06b4 100644 --- a/packages/dds/tree/src/simple-tree/index.ts +++ b/packages/dds/tree/src/simple-tree/index.ts @@ -220,4 +220,9 @@ export { handleSchema, nullSchema, } from "./leafNodeSchema.js"; -export type { LazyItem, FlexList, FlexListToUnion, ExtractItemType } from "./flexList.js"; +export type { + LazyItem, + FlexList, + FlexListToUnion, + ExtractItemType, +} from "./flexList.js"; diff --git a/packages/dds/tree/src/simple-tree/objectNode.ts b/packages/dds/tree/src/simple-tree/objectNode.ts index cf4e21a8e5e6..746c1d2f5a99 100644 --- a/packages/dds/tree/src/simple-tree/objectNode.ts +++ b/packages/dds/tree/src/simple-tree/objectNode.ts @@ -382,6 +382,9 @@ export function objectSchema< metadata?: NodeSchemaMetadata, ): ObjectNodeSchema & ObjectNodeSchemaInternalData { + // Field set can't be modified after since derived data is stored in maps. + Object.freeze(info); + // Ensure no collisions between final set of property keys, and final set of stored keys (including those // implicitly derived from property keys) assertUniqueKeys(identifier, info); diff --git a/packages/dds/tree/src/simple-tree/schemaTypes.ts b/packages/dds/tree/src/simple-tree/schemaTypes.ts index c26df804cefb..c894a28f9a03 100644 --- a/packages/dds/tree/src/simple-tree/schemaTypes.ts +++ b/packages/dds/tree/src/simple-tree/schemaTypes.ts @@ -16,19 +16,23 @@ import { compareSets, type requireTrue, type areOnlyKeys, + getOrCreate, } from "../util/index.js"; -import type { - Unhydrated, - NodeKind, - TreeNodeSchema, - TreeNodeSchemaClass, - TreeNode, - TreeNodeSchemaCore, - TreeNodeSchemaNonClass, +import { + type Unhydrated, + type NodeKind, + type TreeNodeSchema, + type TreeNodeSchemaClass, + type TreeNode, + type TreeNodeSchemaCore, + type TreeNodeSchemaNonClass, + inPrototypeChain, } from "./core/index.js"; import type { FieldKey } from "../core/index.js"; import type { InsertableContent } from "./toMapTree.js"; import { isLazy, type FlexListToUnion, type LazyItem } from "./flexList.js"; +import { LeafNodeSchema } from "./leafNodeSchema.js"; +import { TreeNodeValid } from "./treeNodeValid.js"; /** * Returns true if the given schema is a {@link TreeNodeSchemaClass}, or otherwise false if it is a {@link TreeNodeSchemaNonClass}. @@ -69,6 +73,8 @@ export function isTreeNodeSchemaClass< * way to declare and manipulate unordered sets of types in TypeScript. * * Not intended for direct use outside of package. + * @privateRemarks + * Code reading data from this should use `normalizeAllowedTypes` to ensure consistent handling, caching, nice errors etc. * @system @public */ export type AllowedTypes = readonly LazyItem[]; @@ -379,6 +385,8 @@ export function normalizeAllowedTypes( ): ReadonlySet { const normalized = new Set(); if (isReadonlyArray(types)) { + // Types array must not be modified after it is normalized since that would result if the user of the normalized data having wrong (out of date) content. + Object.freeze(types); for (const lazyType of types) { normalized.add(evaluateLazySchema(lazyType)); } @@ -469,16 +477,45 @@ function areMetadataEqual( return a?.custom === b?.custom && a?.description === b?.description; } -function evaluateLazySchema(value: LazyItem): TreeNodeSchema { - const evaluatedSchema = isLazy(value) ? value() : value; +const cachedLazyItem = new WeakMap<() => unknown, unknown>(); + +function evaluateLazySchema(value: LazyItem): T { + const evaluatedSchema = isLazy(value) + ? (getOrCreate(cachedLazyItem, value, value) as T) + : value; if (evaluatedSchema === undefined) { throw new UsageError( `Encountered an undefined schema. This could indicate that some referenced schema has not yet been instantiated.`, ); } + markSchemaMostDerived(evaluatedSchema); return evaluatedSchema; } +/** + * Indicates that a schema is the "most derived" version which is allowed to be used, see {@link MostDerivedData}. + * Calling helps with error messages about invalid schema usage (using more than one type from single schema factor produced type, + * and thus calling this for one than one subclass). + * @remarks + * Helper for invoking {@link TreeNodeValid.markMostDerived} for any {@link TreeNodeSchema} if it needed. + */ +export function markSchemaMostDerived(schema: TreeNodeSchema): void { + if (schema instanceof LeafNodeSchema) { + return; + } + + if (!inPrototypeChain(schema, TreeNodeValid)) { + // Use JSON.stringify to quote and escape identifier string. + throw new UsageError( + `Schema for ${JSON.stringify( + schema.identifier, + )} does not extend a SchemaFactory generated class. This is invalid.`, + ); + } + + (schema as typeof TreeNodeValid & TreeNodeSchema).oneTimeInitialize(); +} + /** * Types of {@link TreeNode|TreeNodes} or {@link TreeLeafValue|TreeLeafValues} allowed at a location in a tree. * @remarks @@ -508,6 +545,8 @@ function evaluateLazySchema(value: LazyItem): TreeNodeSchema { * class A extends sf.array("example", [() => B]) {} * class B extends sf.array("Inner", sf.number) {} * ``` + * @privateRemarks + * Code reading data from this should use `normalizeAllowedTypes` to ensure consistent handling, caching, nice errors etc. * @public */ export type ImplicitAllowedTypes = AllowedTypes | TreeNodeSchema; diff --git a/packages/dds/tree/src/simple-tree/toStoredSchema.ts b/packages/dds/tree/src/simple-tree/toStoredSchema.ts index c06c89b99ba4..36694cfdbdf0 100644 --- a/packages/dds/tree/src/simple-tree/toStoredSchema.ts +++ b/packages/dds/tree/src/simple-tree/toStoredSchema.ts @@ -20,18 +20,18 @@ import { type TreeTypeSet, } from "../core/index.js"; import { FieldKinds, type FlexFieldKind } from "../feature-libraries/index.js"; -import { brand, fail, getOrCreate, isReadonlyArray } from "../util/index.js"; +import { brand, fail, getOrCreate } from "../util/index.js"; import { NodeKind, type TreeNodeSchema } from "./core/index.js"; import { FieldKind, FieldSchema, + normalizeAllowedTypes, type ImplicitAllowedTypes, type ImplicitFieldSchema, } from "./schemaTypes.js"; import { walkFieldSchema } from "./walkFieldSchema.js"; import { LeafNodeSchema } from "./leafNodeSchema.js"; import { isObjectNodeSchema } from "./objectNodeTypes.js"; -import { normalizeFlexListEager } from "./flexList.js"; const viewToStoredCache = new WeakMap(); @@ -92,10 +92,7 @@ const convertFieldKind = new Map([ * Normalizes an {@link ImplicitAllowedTypes} into an {@link TreeTypeSet}. */ export function convertAllowedTypes(schema: ImplicitAllowedTypes): TreeTypeSet { - if (isReadonlyArray(schema)) { - return new Set(normalizeFlexListEager(schema).map((item) => brand(item.identifier))); - } - return new Set([brand(schema.identifier)]); + return new Set([...normalizeAllowedTypes(schema)].map((item) => brand(item.identifier))); } /** diff --git a/packages/dds/tree/src/simple-tree/treeNodeValid.ts b/packages/dds/tree/src/simple-tree/treeNodeValid.ts index 7627119dfb53..5771a81e94ef 100644 --- a/packages/dds/tree/src/simple-tree/treeNodeValid.ts +++ b/packages/dds/tree/src/simple-tree/treeNodeValid.ts @@ -64,6 +64,8 @@ export abstract class TreeNodeValid extends TreeNode { /** * Schema classes can override to provide a callback that is called once when the first node is constructed. * This is a good place to perform extra validation and cache schema derived data needed for the implementation of the node. + * @remarks + * It is valid to dereference LazyItem schema references in this function (or anything that runs after it). */ protected static oneTimeSetup(this: typeof TreeNodeValid): Context { fail(0xae5 /* Missing oneTimeSetup */); @@ -146,7 +148,7 @@ export abstract class TreeNodeValid extends TreeNode { } /** - * @see {@link TreeNodeSchemaCore.createFromInsertable}. + * See {@link TreeNodeSchemaCore.createFromInsertable}. */ public static createFromInsertable TOut>( this: TThis, @@ -155,13 +157,22 @@ export abstract class TreeNodeValid extends TreeNode { return new this(input); } + /** + * Idempotent initialization function that pre-caches data and can dereference lazy schema references. + */ + public static oneTimeInitialize( + this: typeof TreeNodeValid & TreeNodeSchema, + ): Required { + const cache = this.markMostDerived(); + cache.oneTimeInitialized ??= this.oneTimeSetup(); + // Typescript fails to narrow the type of `oneTimeInitialized` to `Context` here, so use a cast: + return cache as MostDerivedData & { oneTimeInitialized: Context }; + } + public constructor(input: TInput | InternalTreeNode) { super(privateToken); const schema = this.constructor as typeof TreeNodeValid & TreeNodeSchema; - const cache = schema.markMostDerived(); - if (cache.oneTimeInitialized === undefined) { - cache.oneTimeInitialized = schema.oneTimeSetup(); - } + const cache = schema.oneTimeInitialize(); if (isTreeNode(input)) { // TODO: update this once we have better support for deep-copying and move operations. diff --git a/packages/dds/tree/src/test/openPolymorphism.spec.ts b/packages/dds/tree/src/test/openPolymorphism.spec.ts index cd52fbc5edcd..dc242d11dd71 100644 --- a/packages/dds/tree/src/test/openPolymorphism.spec.ts +++ b/packages/dds/tree/src/test/openPolymorphism.spec.ts @@ -236,17 +236,10 @@ describe("Open Polymorphism design pattern examples and tests for them", () => { function composeComponents(allComponents: readonly MyAppComponent[]): MyAppConfig { const lazyConfig = () => config; - const uncachedItemTypes: LazyItems = allComponents.flatMap( + const ItemTypes = allComponents.flatMap( (component): LazyItems => component.itemTypes(lazyConfig), ); - const ItemTypes = uncachedItemTypes.map((uncached) => { - let cache: ItemSchema | undefined; - return () => { - cache ??= uncached(); - return cache; - }; - }); const config: MyAppConfig = { ItemTypes }; return config; @@ -301,28 +294,19 @@ describe("Open Polymorphism design pattern examples and tests for them", () => { * This allows the schema to reference items from the configuration, which could include themselves recursively. */ type ComponentSchemaCollection = ( - lazyConfig: () => TConfig, + lazyConfiguration: () => TConfig, ) => LazyArray; type LazyArray = readonly (() => T)[]; function composeComponentSchema( allComponents: readonly ComponentSchemaCollection[], - lazyConfig: () => TConfig, + lazyConfiguration: () => TConfig, ): (() => TItem)[] { - const uncashedItemTypes: LazyArray = allComponents.flatMap( - (component): LazyArray => component(lazyConfig), + const uncachedItemTypes = allComponents.flatMap( + (component): LazyArray => component(lazyConfiguration), ); - - const ItemTypes = uncashedItemTypes.map((uncached) => { - let cache: TItem | undefined; - return () => { - cache ??= uncached(); - return cache; - }; - }); - - return ItemTypes; + return uncachedItemTypes; // uncachedItemTypes.map(cacheLazyItem); } // App specific // 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 110629541c3e..859ebea630bc 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 @@ -821,8 +821,11 @@ describe("SchemaFactory Recursive methods", () => { // @ts-expect-error Readonly. obj.req = 1; - // @ts-expect-error required. - obj.req = undefined; + + assert.throws(() => { + // @ts-expect-error required. + obj.req = undefined; + }); }); }); }); diff --git a/packages/dds/tree/src/test/simple-tree/flexList.spec.ts b/packages/dds/tree/src/test/simple-tree/flexList.spec.ts index d2f8df62a74e..31f3c70235db 100644 --- a/packages/dds/tree/src/test/simple-tree/flexList.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/flexList.spec.ts @@ -6,11 +6,9 @@ import { strict as assert } from "node:assert"; import { - type FlexList, type FlexListToUnion, isLazy, markEager, - normalizeFlexListEager, // Allow importing from this specific file which is being tested: /* eslint-disable-next-line import/no-internal-modules */ } from "../../simple-tree/flexList.js"; @@ -29,23 +27,10 @@ import type { areSafelyAssignable, requireTrue } from "../../util/index.js"; } describe("FlexList", () => { - it("correctly normalizes lists to be eager", () => { - const list = [2, (): 1 => 1] as const; - const normalized: readonly (1 | 2)[] = normalizeFlexListEager(list); - assert.deepEqual(normalized, [2, 1]); - }); - it("can mark functions as eager", () => { const fn = () => 42; assert.equal(isLazy(fn), true); markEager(fn); assert.equal(isLazy(fn), false); }); - - it("correctly normalizes functions marked as eager", () => { - const eagerGenerator = markEager(() => 42); - const lazyGenerator = () => () => 42; - const list: FlexList<() => number> = [eagerGenerator, lazyGenerator]; - normalizeFlexListEager(list).forEach((g) => assert.equal(g(), 42)); - }); }); 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 5d0156c5f0e3..61d288c8ee64 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 @@ -62,6 +62,9 @@ export interface BranchableTree extends ViewableTree { rebase(branch: TreeBranchFork): void; } +// @alpha +export function cacheLazyItem(f: () => T): () => T; + // @public export enum CommitKind { Default = 0, @@ -1185,6 +1188,11 @@ export const schemaStatics: { readonly requiredRecursive: (t: T_3, props?: Omit) => FieldSchemaUnsafe; }; +// @public +export type SchemaUnionToIntersection = [T] extends [ +CustomizedSchemaTyping +] ? T : UnionToIntersection; + // @alpha export interface SchemaValidationFunction { check(data: unknown): data is Static; From 13beb5e2294e1c174f0902f5bf6babfdbe29e98d Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Tue, 4 Mar 2025 13:29:52 -0800 Subject: [PATCH 27/37] Fix infinite recursion and broken test --- packages/dds/tree/src/simple-tree/api/tree.ts | 9 ++------- packages/dds/tree/src/simple-tree/schemaTypes.ts | 12 ++++++++++-- .../dds/tree/src/test/simple-tree/core/types.spec.ts | 2 +- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/dds/tree/src/simple-tree/api/tree.ts b/packages/dds/tree/src/simple-tree/api/tree.ts index 9d2e50ae4239..2ca74cc3171c 100644 --- a/packages/dds/tree/src/simple-tree/api/tree.ts +++ b/packages/dds/tree/src/simple-tree/api/tree.ts @@ -34,7 +34,7 @@ import { toStoredSchema } from "../toStoredSchema.js"; import { LeafNodeSchema } from "../leafNodeSchema.js"; import { assert } from "@fluidframework/core-utils/internal"; import { isObjectNodeSchema, type ObjectNodeSchema } from "../objectNodeTypes.js"; -import { fail, getOrCreate, isReadonlyArray } from "../../util/index.js"; +import { fail, getOrCreate } from "../../util/index.js"; import type { MakeNominal } from "../../util/index.js"; import { walkFieldSchema } from "../walkFieldSchema.js"; import type { VerboseTree } from "./verboseTree.js"; @@ -280,16 +280,11 @@ export class TreeViewConfiguration< // This ensures if multiple schema extending the same schema factory generated class are present (or have been constructed, or get constructed in the future), // an error is reported. - node: markSchemaMostDerived, + node: (schema) => markSchemaMostDerived(schema, true), allowedTypes(types): void { if (config.preventAmbiguity) { checkUnion(types, ambiguityErrors); } - if (isReadonlyArray(types)) { - // Ensure late additions to unions error. - // TODO: it would be better to do this as part of oneTimeInitialize so more cases trigger it, but this is better than nothing, and really easy to do: - Object.freeze(types); - } }, }); diff --git a/packages/dds/tree/src/simple-tree/schemaTypes.ts b/packages/dds/tree/src/simple-tree/schemaTypes.ts index c894a28f9a03..8f59acb07156 100644 --- a/packages/dds/tree/src/simple-tree/schemaTypes.ts +++ b/packages/dds/tree/src/simple-tree/schemaTypes.ts @@ -499,7 +499,10 @@ function evaluateLazySchema(value: LazyItem): T { * @remarks * Helper for invoking {@link TreeNodeValid.markMostDerived} for any {@link TreeNodeSchema} if it needed. */ -export function markSchemaMostDerived(schema: TreeNodeSchema): void { +export function markSchemaMostDerived( + schema: TreeNodeSchema, + oneTimeInitialize = false, +): void { if (schema instanceof LeafNodeSchema) { return; } @@ -513,7 +516,12 @@ export function markSchemaMostDerived(schema: TreeNodeSchema): void { ); } - (schema as typeof TreeNodeValid & TreeNodeSchema).oneTimeInitialize(); + const schemaValid = schema as typeof TreeNodeValid & TreeNodeSchema; + if (oneTimeInitialize) { + schemaValid.oneTimeInitialize(); + } else { + schemaValid.markMostDerived(); + } } /** diff --git a/packages/dds/tree/src/test/simple-tree/core/types.spec.ts b/packages/dds/tree/src/test/simple-tree/core/types.spec.ts index 0ecfc646aaf3..b002986820c0 100644 --- a/packages/dds/tree/src/test/simple-tree/core/types.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/core/types.spec.ts @@ -313,7 +313,7 @@ describe("simple-tree types", () => { protected static override oneTimeSetup(this: typeof TreeNodeValid): Context { log.push(this.name); - return getUnhydratedContext(A); + return getUnhydratedContext(this as typeof A); } } From 3da123fea203a4e8a4a631df86ce8ae2b5a0a600 Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Tue, 4 Mar 2025 14:22:32 -0800 Subject: [PATCH 28/37] Expose evaluateLazySchema --- .../dds/tree/api-report/tree.alpha.api.md | 15 ++++--- packages/dds/tree/src/index.ts | 1 + packages/dds/tree/src/simple-tree/index.ts | 1 + .../dds/tree/src/simple-tree/schemaTypes.ts | 8 +++- .../tree/src/test/openPolymorphism.spec.ts | 42 +++++++++++++------ .../api-report/fluid-framework.alpha.api.md | 6 +-- 6 files changed, 51 insertions(+), 22 deletions(-) diff --git a/packages/dds/tree/api-report/tree.alpha.api.md b/packages/dds/tree/api-report/tree.alpha.api.md index 425bec10fdd2..93364a730ece 100644 --- a/packages/dds/tree/api-report/tree.alpha.api.md +++ b/packages/dds/tree/api-report/tree.alpha.api.md @@ -168,6 +168,9 @@ export function enumFromStrings, true, Record, undefined>; }[Members[number]] : never>; }; +// @alpha +export function evaluateLazySchema(value: LazyItem): T; + // @public type ExtractItemType = Item extends () => infer Result ? Result : Item; @@ -452,21 +455,21 @@ export namespace JsonAsTree { export class JsonObject extends _APIExtractorWorkaroundObjectBase { } const _APIExtractorWorkaroundObjectBase: TreeNodeSchemaClass_2<"com.fluidframework.json.object", NodeKind_2.Map, TreeMapNodeUnsafe_2 typeof JsonObject, () => typeof Array, TreeNodeSchemaNonClass_2<"com.fluidframework.leaf.null", NodeKind_2.Leaf, null, null, true, unknown, never, unknown>, TreeNodeSchemaNonClass_2<"com.fluidframework.leaf.boolean", NodeKind_2.Leaf, boolean, boolean, true, unknown, never, unknown>, TreeNodeSchemaNonClass_2<"com.fluidframework.leaf.number", NodeKind_2.Leaf, number, number, true, unknown, never, unknown>, TreeNodeSchemaNonClass_2<"com.fluidframework.leaf.string", NodeKind_2.Leaf, string, string, true, unknown, never, unknown>]> & WithType_2<"com.fluidframework.json.object", NodeKind_2.Map, unknown>, { - [Symbol.iterator](): Iterator<[string, string | number | InsertableTypedNodeUnsafe_2, TreeNodeSchemaCore_2<"com.fluidframework.leaf.boolean", NodeKind_2.Leaf, true, unknown, boolean, unknown> & { + [Symbol.iterator](): Iterator<[string, string | number | JsonObject | Array | InsertableTypedNodeUnsafe_2, TreeNodeSchemaCore_2<"com.fluidframework.leaf.boolean", NodeKind_2.Leaf, true, unknown, boolean, unknown> & { create(data: boolean): boolean; - }> | JsonObject | Array | null], any, undefined>; + }> | null], any, undefined>; } | { - readonly [x: string]: string | number | InsertableTypedNodeUnsafe_2, TreeNodeSchemaCore_2<"com.fluidframework.leaf.boolean", NodeKind_2.Leaf, true, unknown, boolean, unknown> & { + readonly [x: string]: string | number | JsonObject | Array | InsertableTypedNodeUnsafe_2, TreeNodeSchemaCore_2<"com.fluidframework.leaf.boolean", NodeKind_2.Leaf, true, unknown, boolean, unknown> & { create(data: boolean): boolean; - }> | JsonObject | Array | null; + }> | null; }, false, readonly [() => typeof JsonObject, () => typeof Array, TreeNodeSchemaNonClass_2<"com.fluidframework.leaf.null", NodeKind_2.Leaf, null, null, true, unknown, never, unknown>, TreeNodeSchemaNonClass_2<"com.fluidframework.leaf.boolean", NodeKind_2.Leaf, boolean, boolean, true, unknown, never, unknown>, TreeNodeSchemaNonClass_2<"com.fluidframework.leaf.number", NodeKind_2.Leaf, number, number, true, unknown, never, unknown>, TreeNodeSchemaNonClass_2<"com.fluidframework.leaf.string", NodeKind_2.Leaf, string, string, true, unknown, never, unknown>], undefined>; // (undocumented) export type Primitive = TreeNodeFromImplicitAllowedTypes; export type _RecursiveArrayWorkaroundJsonArray = FixRecursiveArraySchema; const _APIExtractorWorkaroundArrayBase: TreeNodeSchemaClass_2<"com.fluidframework.json.array", NodeKind_2.Array, TreeArrayNodeUnsafe_2 typeof JsonObject, () => typeof Array, TreeNodeSchemaNonClass_2<"com.fluidframework.leaf.null", NodeKind_2.Leaf, null, null, true, unknown, never, unknown>, TreeNodeSchemaNonClass_2<"com.fluidframework.leaf.boolean", NodeKind_2.Leaf, boolean, boolean, true, unknown, never, unknown>, TreeNodeSchemaNonClass_2<"com.fluidframework.leaf.number", NodeKind_2.Leaf, number, number, true, unknown, never, unknown>, TreeNodeSchemaNonClass_2<"com.fluidframework.leaf.string", NodeKind_2.Leaf, string, string, true, unknown, never, unknown>]> & WithType_2<"com.fluidframework.json.array", NodeKind_2.Array, unknown>, { - [Symbol.iterator](): Iterator, TreeNodeSchemaCore_2<"com.fluidframework.leaf.boolean", NodeKind_2.Leaf, true, unknown, boolean, unknown> & { + [Symbol.iterator](): Iterator, TreeNodeSchemaCore_2<"com.fluidframework.leaf.boolean", NodeKind_2.Leaf, true, unknown, boolean, unknown> & { create(data: boolean): boolean; - }> | JsonObject | Array | null, any, undefined>; + }> | null, any, undefined>; }, false, readonly [() => typeof JsonObject, () => typeof Array, TreeNodeSchemaNonClass_2<"com.fluidframework.leaf.null", NodeKind_2.Leaf, null, null, true, unknown, never, unknown>, TreeNodeSchemaNonClass_2<"com.fluidframework.leaf.boolean", NodeKind_2.Leaf, boolean, boolean, true, unknown, never, unknown>, TreeNodeSchemaNonClass_2<"com.fluidframework.leaf.number", NodeKind_2.Leaf, number, number, true, unknown, never, unknown>, TreeNodeSchemaNonClass_2<"com.fluidframework.leaf.string", NodeKind_2.Leaf, string, string, true, unknown, never, unknown>], undefined>; // (undocumented) export type Tree = TreeNodeFromImplicitAllowedTypes; diff --git a/packages/dds/tree/src/index.ts b/packages/dds/tree/src/index.ts index 75abb3482dd7..09014ad1e6ea 100644 --- a/packages/dds/tree/src/index.ts +++ b/packages/dds/tree/src/index.ts @@ -233,6 +233,7 @@ export { type TransactionResultSuccess, type TransactionResultFailed, rollback, + evaluateLazySchema, } from "./simple-tree/index.js"; export { SharedTree, diff --git a/packages/dds/tree/src/simple-tree/index.ts b/packages/dds/tree/src/simple-tree/index.ts index 90a32c4d06b4..aef659ba450f 100644 --- a/packages/dds/tree/src/simple-tree/index.ts +++ b/packages/dds/tree/src/simple-tree/index.ts @@ -185,6 +185,7 @@ export { CustomizedTyping, type DefaultInsertableTreeNodeFromImplicitAllowedTypes, type SchemaUnionToIntersection, + evaluateLazySchema, } from "./schemaTypes.js"; export { getTreeNodeForField, diff --git a/packages/dds/tree/src/simple-tree/schemaTypes.ts b/packages/dds/tree/src/simple-tree/schemaTypes.ts index 8f59acb07156..fdc719715d47 100644 --- a/packages/dds/tree/src/simple-tree/schemaTypes.ts +++ b/packages/dds/tree/src/simple-tree/schemaTypes.ts @@ -479,7 +479,13 @@ function areMetadataEqual( const cachedLazyItem = new WeakMap<() => unknown, unknown>(); -function evaluateLazySchema(value: LazyItem): T { +/** + * Returns the schema referenced by the {@link LazyItem}. + * @remarks + * Caches results to handle {@link LazyItem} which compute their resulting schema. + * @alpha + */ +export function evaluateLazySchema(value: LazyItem): T { const evaluatedSchema = isLazy(value) ? (getOrCreate(cachedLazyItem, value, value) as T) : value; diff --git a/packages/dds/tree/src/test/openPolymorphism.spec.ts b/packages/dds/tree/src/test/openPolymorphism.spec.ts index dc242d11dd71..8611741f1ef6 100644 --- a/packages/dds/tree/src/test/openPolymorphism.spec.ts +++ b/packages/dds/tree/src/test/openPolymorphism.spec.ts @@ -16,7 +16,7 @@ import { } from "../simple-tree/index.js"; import { Tree } from "../shared-tree/index.js"; import { validateUsageError } from "./utils.js"; -import { customizeSchemaTyping } from "../simple-tree/index.js"; +import { customizeSchemaTyping, evaluateLazySchema } from "../simple-tree/index.js"; const sf = new SchemaFactory("test"); @@ -303,21 +303,36 @@ describe("Open Polymorphism design pattern examples and tests for them", () => { allComponents: readonly ComponentSchemaCollection[], lazyConfiguration: () => TConfig, ): (() => TItem)[] { - const uncachedItemTypes = allComponents.flatMap( + const itemTypes = allComponents.flatMap( (component): LazyArray => component(lazyConfiguration), ); - return uncachedItemTypes; // uncachedItemTypes.map(cacheLazyItem); + return itemTypes; } // App specific // + /** + * Subset of `MyAppConfig` which is available while composing components. + */ + interface MyAppConfigPartial { + /** + * {@link AllowedTypes} containing all ItemSchema contributed by components. + */ + readonly allowedItemTypes: LazyArray; + } + /** * Example configuration type for an application. * * Contains a collection of schema to demonstrate how ComponentSchemaCollection works for schema dependency inversions. */ - interface MyAppConfig { - readonly ItemTypes: LazyArray; + 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; } /** @@ -329,7 +344,7 @@ describe("Open Polymorphism design pattern examples and tests for them", () => { * This makes it possible to implement the "open polymorphism" pattern, including handling recursive cases. */ interface MyAppComponent { - readonly itemTypes: ComponentSchemaCollection; + readonly itemTypes: ComponentSchemaCollection; } /** @@ -343,8 +358,11 @@ describe("Open Polymorphism design pattern examples and tests for them", () => { allComponents.map((c) => c.itemTypes), lazyConfig, ); - const config: MyAppConfig = { ItemTypes }; - return config; + const config: MyAppConfigPartial = { + allowedItemTypes: ItemTypes, + }; + const items = new Set(ItemTypes.map(evaluateLazySchema)); + return { ...config, items }; } // An example simple component @@ -354,12 +372,12 @@ describe("Open Polymorphism design pattern examples and tests for them", () => { // An example component which references schema from the configuration and can be recursive through it. const containerComponent: MyAppComponent = { - itemTypes: (lazyConfig: () => MyAppConfig): LazyArray => [ + itemTypes: (lazyConfig: () => MyAppConfigPartial): LazyArray => [ () => createContainer(lazyConfig()), ], }; - function createContainer(config: MyAppConfig): ItemSchema { - class Container extends sf.array("Container", config.ItemTypes) {} + function createContainer(config: MyAppConfigPartial): ItemSchema { + class Container extends sf.array("Container", config.allowedItemTypes) {} class ContainerItem extends sf.object("ContainerItem", { ...itemFields, container: Container, @@ -378,7 +396,7 @@ describe("Open Polymorphism design pattern examples and tests for them", () => { const appConfig = composeComponents([containerComponent, textComponent]); const treeConfig = new TreeViewConfiguration({ - schema: appConfig.ItemTypes, + schema: appConfig.allowedItemTypes, enableSchemaValidation: true, preventAmbiguity: true, }); 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 61d288c8ee64..6ffe7c6e1c10 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 @@ -62,9 +62,6 @@ export interface BranchableTree extends ViewableTree { rebase(branch: TreeBranchFork): void; } -// @alpha -export function cacheLazyItem(f: () => T): () => T; - // @public export enum CommitKind { Default = 0, @@ -215,6 +212,9 @@ export abstract class ErasedType { protected abstract brand(dummy: never): Name; } +// @alpha +export function evaluateLazySchema(value: LazyItem): T; + // @public type ExtractItemType = Item extends () => infer Result ? Result : Item; From 41d2c5ce2b081e8986c9015da2fc744a7e5718bd Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Tue, 4 Mar 2025 14:37:47 -0800 Subject: [PATCH 29/37] Export Component --- .../dds/tree/api-report/tree.alpha.api.md | 7 ++++ packages/dds/tree/src/index.ts | 1 + .../dds/tree/src/simple-tree/api/component.ts | 42 +++++++++++++++++++ .../dds/tree/src/simple-tree/api/index.ts | 2 + packages/dds/tree/src/simple-tree/index.ts | 1 + .../tree/src/test/openPolymorphism.spec.ts | 35 ++++------------ .../api-report/fluid-framework.alpha.api.md | 7 ++++ 7 files changed, 69 insertions(+), 26 deletions(-) create mode 100644 packages/dds/tree/src/simple-tree/api/component.ts diff --git a/packages/dds/tree/api-report/tree.alpha.api.md b/packages/dds/tree/api-report/tree.alpha.api.md index 93364a730ece..69242a147231 100644 --- a/packages/dds/tree/api-report/tree.alpha.api.md +++ b/packages/dds/tree/api-report/tree.alpha.api.md @@ -71,6 +71,13 @@ export interface CommitMetadata { // @alpha export function comparePersistedSchema(persisted: JsonCompatible, view: ImplicitFieldSchema, options: ICodecOptions, canInitialize: boolean): SchemaCompatibilityStatus; +// @alpha +export namespace Component { + export type ComponentSchemaCollection = (lazyConfiguration: () => TConfig) => LazyArray; + export function composeComponentSchema(allComponents: readonly ComponentSchemaCollection[], lazyConfiguration: () => TConfig): (() => TItem)[]; + export type LazyArray = readonly (() => T)[]; +} + // @alpha export type ConciseTree = Exclude | THandle | ConciseTree[] | { [key: string]: ConciseTree; diff --git a/packages/dds/tree/src/index.ts b/packages/dds/tree/src/index.ts index 09014ad1e6ea..dcf29c15a6e6 100644 --- a/packages/dds/tree/src/index.ts +++ b/packages/dds/tree/src/index.ts @@ -234,6 +234,7 @@ export { type TransactionResultFailed, rollback, evaluateLazySchema, + Component, } from "./simple-tree/index.js"; export { SharedTree, diff --git a/packages/dds/tree/src/simple-tree/api/component.ts b/packages/dds/tree/src/simple-tree/api/component.ts new file mode 100644 index 000000000000..c33413ab5b39 --- /dev/null +++ b/packages/dds/tree/src/simple-tree/api/component.ts @@ -0,0 +1,42 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +/** + * Utilities for helping implement various application component design patterns. + * @alpha + */ +export namespace Component { + /** + * Function which takes in a lazy configuration and returns a collection of schema types. + * @remarks + * This allows the schema to reference items from the configuration, which could include themselves recursively. + * @alpha + */ + export type ComponentSchemaCollection = ( + lazyConfiguration: () => TConfig, + ) => LazyArray; + + /** + * {@link AllowedTypes} where all of the allowed types' schema implement `T` and are lazy. + * @alpha + */ + export type LazyArray = readonly (() => T)[]; + + /** + * Combine multiple {@link Component.ComponentSchemaCollection}s into a single {@link AllowedTypes} array. + * @remarks + * + * @alpha + */ + export function composeComponentSchema( + allComponents: readonly ComponentSchemaCollection[], + lazyConfiguration: () => TConfig, + ): (() => TItem)[] { + const itemTypes = allComponents.flatMap( + (component): LazyArray => component(lazyConfiguration), + ); + return itemTypes; + } +} diff --git a/packages/dds/tree/src/simple-tree/api/index.ts b/packages/dds/tree/src/simple-tree/api/index.ts index a9f296052671..9351da973139 100644 --- a/packages/dds/tree/src/simple-tree/api/index.ts +++ b/packages/dds/tree/src/simple-tree/api/index.ts @@ -138,6 +138,8 @@ export { rollback, } from "./transactionTypes.js"; +export { Component } from "./component.js"; + // Exporting the schema (RecursiveObject) to test that recursive types are working correctly. // These are `@internal` so they can't be included in the `InternalClassTreeTypes` due to https://github.com/microsoft/rushstack/issues/3639 export { diff --git a/packages/dds/tree/src/simple-tree/index.ts b/packages/dds/tree/src/simple-tree/index.ts index aef659ba450f..815de6ab675b 100644 --- a/packages/dds/tree/src/simple-tree/index.ts +++ b/packages/dds/tree/src/simple-tree/index.ts @@ -141,6 +141,7 @@ export { type TransactionResultSuccess, type TransactionResultFailed, rollback, + Component, } from "./api/index.js"; export { type NodeFromSchema, diff --git a/packages/dds/tree/src/test/openPolymorphism.spec.ts b/packages/dds/tree/src/test/openPolymorphism.spec.ts index 8611741f1ef6..f0ab48d87938 100644 --- a/packages/dds/tree/src/test/openPolymorphism.spec.ts +++ b/packages/dds/tree/src/test/openPolymorphism.spec.ts @@ -6,6 +6,7 @@ import { strict as assert } from "node:assert"; import { + Component, SchemaFactory, TreeViewConfiguration, type NodeKind, @@ -288,27 +289,6 @@ describe("Open Polymorphism design pattern examples and tests for them", () => { }); it("generic components system", () => { - /** - * Function which takes in a lazy configuration and returns a collection of schema types. - * @remarks - * This allows the schema to reference items from the configuration, which could include themselves recursively. - */ - type ComponentSchemaCollection = ( - lazyConfiguration: () => TConfig, - ) => LazyArray; - - type LazyArray = readonly (() => T)[]; - - function composeComponentSchema( - allComponents: readonly ComponentSchemaCollection[], - lazyConfiguration: () => TConfig, - ): (() => TItem)[] { - const itemTypes = allComponents.flatMap( - (component): LazyArray => component(lazyConfiguration), - ); - return itemTypes; - } - // App specific // /** @@ -318,7 +298,7 @@ describe("Open Polymorphism design pattern examples and tests for them", () => { /** * {@link AllowedTypes} containing all ItemSchema contributed by components. */ - readonly allowedItemTypes: LazyArray; + readonly allowedItemTypes: Component.LazyArray; } /** @@ -344,7 +324,10 @@ describe("Open Polymorphism design pattern examples and tests for them", () => { * This makes it possible to implement the "open polymorphism" pattern, including handling recursive cases. */ interface MyAppComponent { - readonly itemTypes: ComponentSchemaCollection; + readonly itemTypes: Component.ComponentSchemaCollection< + MyAppConfigPartial, + ItemSchema + >; } /** @@ -354,7 +337,7 @@ describe("Open Polymorphism design pattern examples and tests for them", () => { */ function composeComponents(allComponents: readonly MyAppComponent[]): MyAppConfig { const lazyConfig = () => config; - const ItemTypes = composeComponentSchema( + const ItemTypes = Component.composeComponentSchema( allComponents.map((c) => c.itemTypes), lazyConfig, ); @@ -367,12 +350,12 @@ describe("Open Polymorphism design pattern examples and tests for them", () => { // An example simple component const textComponent: MyAppComponent = { - itemTypes: (): LazyArray => [() => TextItem], + itemTypes: (): Component.LazyArray => [() => TextItem], }; // An example component which references schema from the configuration and can be recursive through it. const containerComponent: MyAppComponent = { - itemTypes: (lazyConfig: () => MyAppConfigPartial): LazyArray => [ + itemTypes: (lazyConfig: () => MyAppConfigPartial): Component.LazyArray => [ () => createContainer(lazyConfig()), ], }; 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 6ffe7c6e1c10..3645afa1edcb 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 @@ -78,6 +78,13 @@ export interface CommitMetadata { // @alpha export function comparePersistedSchema(persisted: JsonCompatible, view: ImplicitFieldSchema, options: ICodecOptions, canInitialize: boolean): SchemaCompatibilityStatus; +// @alpha +export namespace Component { + export type ComponentSchemaCollection = (lazyConfiguration: () => TConfig) => LazyArray; + export function composeComponentSchema(allComponents: readonly ComponentSchemaCollection[], lazyConfiguration: () => TConfig): (() => TItem)[]; + export type LazyArray = readonly (() => T)[]; +} + // @alpha export type ConciseTree = Exclude | THandle | ConciseTree[] | { [key: string]: ConciseTree; From 94b9178fcaa5fd75a0367b4be8366ce7abf55a01 Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Wed, 19 Mar 2025 11:01:14 -0700 Subject: [PATCH 30/37] Fix merge --- packages/dds/tree/src/index.ts | 1 - .../dds/tree/src/simple-tree/schemaTypes.ts | 17 +++-- .../src/test/simple-tree/schemaTypes.spec.ts | 65 ++++++------------- 3 files changed, 27 insertions(+), 56 deletions(-) diff --git a/packages/dds/tree/src/index.ts b/packages/dds/tree/src/index.ts index b11098f915c8..3982c4703d8e 100644 --- a/packages/dds/tree/src/index.ts +++ b/packages/dds/tree/src/index.ts @@ -237,7 +237,6 @@ export { evaluateLazySchema, Component, generateSchemaFromSimpleSchema, - evaluateLazySchema, replaceConciseTreeHandles, replaceHandles, replaceVerboseTreeHandles, diff --git a/packages/dds/tree/src/simple-tree/schemaTypes.ts b/packages/dds/tree/src/simple-tree/schemaTypes.ts index acfa83b852f9..66a2c80a8f0e 100644 --- a/packages/dds/tree/src/simple-tree/schemaTypes.ts +++ b/packages/dds/tree/src/simple-tree/schemaTypes.ts @@ -18,15 +18,14 @@ import { type areOnlyKeys, getOrCreate, } from "../util/index.js"; -import { - type Unhydrated, - type NodeKind, - type TreeNodeSchema, - type TreeNodeSchemaClass, - type TreeNode, - type TreeNodeSchemaCore, - type TreeNodeSchemaNonClass, - inPrototypeChain, +import type { + Unhydrated, + NodeKind, + TreeNodeSchema, + TreeNodeSchemaClass, + TreeNode, + TreeNodeSchemaCore, + TreeNodeSchemaNonClass, } from "./core/index.js"; import { inPrototypeChain } from "./core/index.js"; import type { FieldKey } from "../core/index.js"; diff --git a/packages/dds/tree/src/test/simple-tree/schemaTypes.spec.ts b/packages/dds/tree/src/test/simple-tree/schemaTypes.spec.ts index 5934e816a6a0..c4a2cf7a647f 100644 --- a/packages/dds/tree/src/test/simple-tree/schemaTypes.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/schemaTypes.spec.ts @@ -19,55 +19,28 @@ import { } from "../../simple-tree/index.js"; import { type AllowedTypes, -<<<<<<< HEAD -type CustomizedSchemaTyping -, -type DefaultInsertableTreeNodeFromImplicitAllowedTypes -, -type DefaultTreeNodeFromImplicitAllowedTypes -, -======= -type FieldKind -, ->>>>>>> 0c7363626dca3c2d68a7b3a75538e1e0af30fe9e -type FieldKind -, -======= -type FieldKind -, ->>>>>>> 0c7363626dca3c2d68a7b3a75538e1e0af30fe9e -type FieldSchema -, -type ImplicitAllowedTypes -, -type ImplicitFieldSchema -, -type InsertableField -, -type InsertableTreeFieldFromImplicitField -, -type InsertableTreeNodeFromAllowedTypes -, -type InsertableTypedNode -, -type NodeBuilderData -, -type NodeFromSchema -, -type SchemaUnionToIntersection -, -type TreeFieldFromImplicitField -, -type TreeLeafValue -, -type TreeNodeFromImplicitAllowedTypes -, -type UnsafeUnknownSchema -, + type CustomizedSchemaTyping, + type DefaultInsertableTreeNodeFromImplicitAllowedTypes, + type DefaultTreeNodeFromImplicitAllowedTypes, + type FieldKind, + type FieldSchema, + type ImplicitAllowedTypes, + type ImplicitFieldSchema, + type InsertableField, + type InsertableTreeFieldFromImplicitField, + type InsertableTreeNodeFromAllowedTypes, + type InsertableTypedNode, + type NodeBuilderData, + type NodeFromSchema, + type SchemaUnionToIntersection, + type TreeFieldFromImplicitField, + type TreeLeafValue, + type TreeNodeFromImplicitAllowedTypes, + type UnsafeUnknownSchema, areImplicitFieldSchemaEqual, normalizeAllowedTypes, // eslint-disable-next-line import/no-internal-modules -} from "../../simple-tree/schemaTypes.js" +} from "../../simple-tree/schemaTypes.js"; import type { areSafelyAssignable, requireAssignableTo, From 1828f00a7033d1378b725ce2d0478b907d1a0aad Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Thu, 9 Oct 2025 15:08:08 -0700 Subject: [PATCH 31/37] fix non-table stuff --- packages/dds/tree/src/index.ts | 6 --- .../tree/src/simple-tree/api/typesUnsafe.ts | 42 +++++++++++-------- packages/dds/tree/src/simple-tree/index.ts | 17 +++++--- .../tree/src/simple-tree/node-kinds/index.ts | 2 + .../simple-tree/node-kinds/object/index.ts | 2 + .../node-kinds/object/objectNode.ts | 1 + .../node-kinds/object/objectNodeTypes.ts | 2 +- .../dds/tree/src/simple-tree/schemaTypes.ts | 22 ---------- 8 files changed, 42 insertions(+), 52 deletions(-) diff --git a/packages/dds/tree/src/index.ts b/packages/dds/tree/src/index.ts index ee4a55b46318..1a0e07665469 100644 --- a/packages/dds/tree/src/index.ts +++ b/packages/dds/tree/src/index.ts @@ -241,7 +241,6 @@ export { type NodeSchemaMetadata, type AssignableTreeFieldFromImplicitField, type ApplyKindAssignment, - type DefaultTreeNodeFromImplicitAllowedTypes, type Customizer, type GetTypes, type StrictTypes, @@ -250,11 +249,6 @@ export { CustomizedTyping, type DefaultInsertableTreeNodeFromImplicitAllowedTypes, customizeSchemaTyping, - type GetTypesUnsafe, - type DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe, - type DefaultTreeNodeFromImplicitAllowedTypesUnsafe, - type StrictTypesUnsafe, - type AssignableTreeFieldFromImplicitFieldUnsafe, type SchemaUnionToIntersection, type SchemaStatics, type ITreeAlpha, diff --git a/packages/dds/tree/src/simple-tree/api/typesUnsafe.ts b/packages/dds/tree/src/simple-tree/api/typesUnsafe.ts index a78afd503141..1bf5cb4edfb3 100644 --- a/packages/dds/tree/src/simple-tree/api/typesUnsafe.ts +++ b/packages/dds/tree/src/simple-tree/api/typesUnsafe.ts @@ -15,7 +15,6 @@ import type { FieldKind, FieldSchema, FieldSchemaAlpha, - ImplicitFieldSchema, } from "../fieldSchema.js"; import type { NodeKind, @@ -33,9 +32,8 @@ 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 { ApplyKindAssignment } from "../objectNode.js"; import type { CustomizedSchemaTyping, CustomTypes } from "../schemaTypes.js"; /* @@ -79,9 +77,9 @@ export namespace System_Unsafe { * should use the normal (not unsafe/recursive) APIs. * @alpha */ - export function customizeSchemaTypingUnsafe< - TSchema extends Unenforced, - >(schema: TSchema): CustomizerUnsafe { + export function customizeSchemaTypingUnsafe( + schema: TSchema, + ): 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 }; @@ -93,7 +91,7 @@ export namespace System_Unsafe { * This has fewer options than the safe version, but all options can still be expressed using the "custom" method. * @sealed @public */ - export interface CustomizerUnsafe> { + export interface CustomizerUnsafe { /** * Replace typing with a single substitute type which allowed types must implement. * @remarks @@ -155,10 +153,13 @@ export namespace System_Unsafe { * @system @public */ export type AssignableTreeFieldFromImplicitFieldUnsafe< - TSchema extends Unenforced, + TSchema extends ImplicitFieldSchemaUnsafe, > = TSchema extends FieldSchemaUnsafe ? ApplyKindAssignment["readWrite"], Kind> - : GetTypesUnsafe["readWrite"]; + : // 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`. @@ -166,9 +167,9 @@ export namespace System_Unsafe { * 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] + export type GetTypesUnsafe = [TSchema] extends [ + CustomizedSchemaTyping, + ] ? TCustom : StrictTypesUnsafe; @@ -179,7 +180,7 @@ export namespace System_Unsafe { * @system @public */ export interface StrictTypesUnsafe< - TSchema extends Unenforced, + TSchema extends ImplicitAllowedTypesUnsafe, TInput = DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe, TOutput = DefaultTreeNodeFromImplicitAllowedTypesUnsafe, > { @@ -200,7 +201,7 @@ export namespace System_Unsafe { * @system @public */ export type ObjectFromSchemaRecordUnsafe< - T extends Unenforced>, + T extends RestrictiveStringRecord, > = // 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. @@ -217,7 +218,10 @@ export namespace System_Unsafe { >, ] ? never // Remove readWrite version for cases using CustomizedSchemaTyping to set readWrite to never. - : Property]: AssignableTreeFieldFromImplicitFieldUnsafe; + : // 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< @@ -231,7 +235,9 @@ export namespace System_Unsafe { ] ? // 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]: TreeFieldFromImplicitFieldUnsafe; + : never]: Property extends string + ? TreeFieldFromImplicitFieldUnsafe + : unknown; }; /** @@ -378,7 +384,7 @@ export namespace System_Unsafe { * @system @public */ export type DefaultTreeNodeFromImplicitAllowedTypesUnsafe< - TSchema extends Unenforced, + TSchema extends ImplicitAllowedTypesUnsafe, > = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe @@ -404,7 +410,7 @@ export namespace System_Unsafe { * @system @public */ export type DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe< - TSchema extends Unenforced, + TSchema extends ImplicitAllowedTypesUnsafe, > = [TSchema] extends [TreeNodeSchemaUnsafe] ? InsertableTypedNodeUnsafe : [TSchema] extends [AllowedTypesUnsafe] diff --git a/packages/dds/tree/src/simple-tree/index.ts b/packages/dds/tree/src/simple-tree/index.ts index 1b47b4a68c6e..8845f31fdb28 100644 --- a/packages/dds/tree/src/simple-tree/index.ts +++ b/packages/dds/tree/src/simple-tree/index.ts @@ -150,11 +150,6 @@ export { type CustomTreeNode, type CustomTreeValue, tryStoredSchemaAsArray, - type GetTypesUnsafe, - type DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe, - type DefaultTreeNodeFromImplicitAllowedTypesUnsafe, - type StrictTypesUnsafe, - type AssignableTreeFieldFromImplicitFieldUnsafe, type SchemaStatics, type ITreeAlpha, type TransactionConstraint, @@ -188,6 +183,18 @@ export { type TreeParsingOptions, type SchemaFactory_base, } 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, 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..8cc9ebd1feee 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,8 @@ export { setField, type TreeObjectNode, type SimpleKeyMap, + type AssignableTreeFieldFromImplicitField, + type ApplyKindAssignment, } 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..f8f83014488d 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 @@ -11,6 +11,8 @@ export { setField, type TreeObjectNode, type SimpleKeyMap, + type AssignableTreeFieldFromImplicitField, + type ApplyKindAssignment, } 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 5753ed163376..2bc31bde3d9a 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 @@ -50,6 +50,7 @@ import { createField, type TreeNodeSchemaCorePrivate, type TreeNodeSchemaPrivateData, + getInnerNode, } from "../../core/index.js"; import { getTreeNodeSchemaInitializedData, 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 e28fd4c33a35..2c9b0750b2c4 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 @@ -33,7 +33,7 @@ export interface ObjectNodeSchema< > extends TreeNodeSchemaClass< TName, NodeKind.Object, - TreeObcjectNode, + TreeObjectNode, InsertableObjectFromSchemaRecord, ImplicitlyConstructable, T, diff --git a/packages/dds/tree/src/simple-tree/schemaTypes.ts b/packages/dds/tree/src/simple-tree/schemaTypes.ts index 1c8d85b7c894..e7545e71eb54 100644 --- a/packages/dds/tree/src/simple-tree/schemaTypes.ts +++ b/packages/dds/tree/src/simple-tree/schemaTypes.ts @@ -236,17 +236,6 @@ export type GetTypes = [TSchema] extends [ ? TCustom : StrictTypes; -/** - * Content which could be inserted into a tree. - * - * @see {@link Input} - * @remarks - * Alias of {@link InsertableTreeNodeFromImplicitAllowedTypes} with a shorter name. - * @alpha - */ -export type Insertable = - InsertableTreeNodeFromImplicitAllowedTypes; - /** * Default type of tree node for a field of the given schema. * @system @public @@ -259,17 +248,6 @@ export type DefaultTreeNodeFromImplicitAllowedTypes< ? NodeFromSchema> : unknown; -/** - * Type of content that can be inserted into the tree for a node of the given schema. - * - * @typeparam TSchema - Schema to process. - * @remarks - * Defaults to {@link DefaultInsertableTreeNodeFromImplicitAllowedTypes}. - * @public - */ -export type InsertableTreeNodeFromImplicitAllowedTypes = - GetTypes["input"]; - /** * Type of content that can be inserted into the tree for a node of the given schema. * From bbb0ef62b6b641de77ed36cb2a9f9a39f7abe5f3 Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Thu, 9 Oct 2025 20:55:40 -0700 Subject: [PATCH 32/37] Fix build --- .../dds/tree/api-report/tree.alpha.api.md | 177 +++++++++++++++--- packages/dds/tree/api-report/tree.beta.api.md | 128 +++++++++++-- .../tree/api-report/tree.legacy.beta.api.md | 130 ++++++++++++- .../tree/api-report/tree.legacy.public.api.md | 119 ++++++++++-- .../dds/tree/api-report/tree.public.api.md | 119 ++++++++++-- packages/dds/tree/src/index.ts | 3 + .../dds/tree/src/simple-tree/api/index.ts | 7 +- .../src/simple-tree/api/schemaFactoryAlpha.ts | 32 ++++ .../src/simple-tree/api/schemaFactoryBeta.ts | 28 +++ .../tree/src/simple-tree/api/typesUnsafe.ts | 46 ++--- .../dds/tree/src/simple-tree/fieldSchema.ts | 1 - packages/dds/tree/src/simple-tree/index.ts | 5 + .../tree/src/simple-tree/node-kinds/index.ts | 2 + .../simple-tree/node-kinds/object/index.ts | 2 + .../node-kinds/object/objectNode.ts | 63 +++++-- packages/dds/tree/src/tableSchema.ts | 80 ++++++-- .../simple-tree/api/schemaFactory.spec.ts | 1 + .../api/schemaFactoryRecursive.spec.ts | 9 +- .../test/simple-tree/api/typesUnsafe.spec.ts | 17 +- .../src/test/simple-tree/fieldSchema.spec.ts | 40 ++-- .../src/test/simple-tree/largeSchema.spec.ts | 21 ++- .../node-kinds/object/objectNode.spec.ts | 5 +- .../api-report/fluid-framework.alpha.api.md | 177 +++++++++++++++--- .../api-report/fluid-framework.beta.api.md | 128 +++++++++++-- .../fluid-framework.legacy.beta.api.md | 126 ++++++++++++- .../fluid-framework.legacy.public.api.md | 141 ++++++++++---- .../api-report/fluid-framework.public.api.md | 121 ++++++++++-- 27 files changed, 1453 insertions(+), 275 deletions(-) diff --git a/packages/dds/tree/api-report/tree.alpha.api.md b/packages/dds/tree/api-report/tree.alpha.api.md index ff95b8ce8ce9..7d40caf88b09 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 @@ -117,6 +122,9 @@ export const ArrayNodeSchema: { // @alpha export function asAlpha(view: TreeView): TreeViewAlpha; +// @public @system +export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; + // @alpha @deprecated export function asTreeViewAlpha(view: TreeView): TreeViewAlpha; @@ -155,6 +163,13 @@ export interface CommitMetadata { // @alpha export function comparePersistedSchema(persisted: JsonCompatible, view: ImplicitFieldSchema, options: ICodecOptions): Omit; +// @alpha +export namespace Component { + export type ComponentSchemaCollection = (lazyConfiguration: () => TConfig) => LazyArray; + export function composeComponentSchema(allComponents: readonly ComponentSchemaCollection[], lazyConfiguration: () => TConfig): (() => TItem)[]; + export type LazyArray = readonly (() => T)[]; +} + // @beta export type ConciseTree = Exclude | THandle | ConciseTree[] | { [key: string]: ConciseTree; @@ -178,16 +193,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 +// @public @system export type DefaultTreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; -// @public -export type DefaultTreeNodeFromImplicitAllowedTypesUnsafe> = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; - // @alpha export interface DirtyTreeMap { // (undocumented) @@ -373,16 +432,11 @@ export function getJsonSchema(schema: ImplicitAllowedTypes, options: Required = [TSchema] extends [ CustomizedSchemaTyping ] ? TCustom : StrictTypes; -// @public -export type GetTypesUnsafe> = [ -TSchema -] extends [CustomizedSchemaTyping] ? TCustom : StrictTypesUnsafe; - // @alpha export type HandleConverter = (data: IFluidHandle) => TCustom; @@ -435,7 +489,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 : { @@ -725,7 +779,16 @@ export type NumberKeys> = RestrictiveStringRecord extends T ? {} : { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; + -readonly [Property in keyof T as [ + AssignableTreeFieldFromImplicitField + ] extends [never | undefined] ? never : Property]: AssignableTreeFieldFromImplicitField; +} & { + readonly [Property in keyof T]: TreeFieldFromImplicitField; +}; + +// @beta @system +export type ObjectFromSchemaRecordRelaxed> = RestrictiveStringRecord extends T ? {} : { + [Property in keyof T]: TreeFieldFromImplicitField; }; // @alpha @sealed @@ -942,6 +1005,7 @@ export class SchemaFactoryBeta, const TCustomMetadata = unknown>(name: Name, fields: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T>; // (undocumented) objectRecursive, const TCustomMetadata = unknown>(name: Name, t: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, System_Unsafe.TreeObjectNodeUnsafe>, object & System_Unsafe.InsertableObjectFromSchemaRecordUnsafe, false, T>; + objectRelaxed, const TCustomMetadata = unknown>(name: Name, fields: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNodeRelaxed>, object & InsertableObjectFromSchemaRecord, true, T>; record(allowedTypes: T): TreeNodeSchemaNonClass`>, NodeKind.Record, TreeRecordNode & WithType`>, NodeKind.Record>, RecordNodeInsertableData, true, T, undefined>; record(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Record, TreeRecordNode & WithType, NodeKind.Record>, RecordNodeInsertableData, true, T, undefined>; recordRecursive(name: Name, allowedTypes: T, options?: NodeSchemaOptions): TreeNodeSchemaClass, NodeKind.Record, TreeRecordNodeUnsafe & WithType, NodeKind.Record, unknown>, { @@ -978,6 +1042,11 @@ export interface SchemaStaticsAlpha { readonly typesRecursive: >[]>(t: T, metadata?: AllowedTypesMetadata) => AllowedTypesFullFromMixedUnsafe; } +// @public @system +export type SchemaUnionToIntersection = [T] extends [ +CustomizedSchemaTyping +] ? T : UnionToIntersection; + // @alpha @sealed export class SchemaUpgrade { // (undocumented) @@ -1077,6 +1146,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 @@ -1111,18 +1190,18 @@ export namespace System_TableSchema { }>; // @system export function createTableSchema, const TRowSchema extends RowSchemaBase>(inputSchemaFactory: SchemaFactoryAlpha, _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 @@ -1147,6 +1226,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 @@ -1156,6 +1257,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; @@ -1172,7 +1277,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 @@ -1181,7 +1286,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 { @@ -1198,6 +1317,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> { } @@ -1209,7 +1337,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 @@ -1555,6 +1683,9 @@ export type TreeNodeSchemaNonClass, TypeName extends string = string> = TreeNode & ObjectFromSchemaRecord & WithType; +// @beta +export type TreeObjectNodeRelaxed, TypeName extends string = string> = TreeNode & ObjectFromSchemaRecordRelaxed & WithType; + // @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 62b87b65d93a..edce52e3c678 100644 --- a/packages/dds/tree/api-report/tree.beta.api.md +++ b/packages/dds/tree/api-report/tree.beta.api.md @@ -25,17 +25,19 @@ 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 +// @public @system export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; -// @public -export type AssignableTreeFieldFromImplicitFieldUnsafe> = TSchema extends FieldSchemaUnsafe ? ApplyKindAssignment["readWrite"], Kind> : GetTypesUnsafe["readWrite"]; - // @public export enum CommitKind { Default = 0, @@ -57,10 +59,34 @@ export type ConciseTree = Exclude; +// @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; @@ -144,16 +170,11 @@ export const ForestTypeOptimized: ForestType; // @beta export const ForestTypeReference: ForestType; -// @public +// @public @system export type GetTypes = [TSchema] extends [ CustomizedSchemaTyping ] ? TCustom : StrictTypes; -// @public -export type GetTypesUnsafe> = [ -TSchema -] extends [CustomizedSchemaTyping] ? TCustom : StrictTypesUnsafe; - // @public export type ImplicitAllowedTypes = AllowedTypes | TreeNodeSchema; @@ -176,7 +197,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 : { @@ -313,7 +334,16 @@ export type NumberKeys> = RestrictiveStringRecord extends T ? {} : { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; + -readonly [Property in keyof T as [ + AssignableTreeFieldFromImplicitField + ] extends [never | undefined] ? never : Property]: AssignableTreeFieldFromImplicitField; +} & { + readonly [Property in keyof T]: TreeFieldFromImplicitField; +}; + +// @beta @system +export type ObjectFromSchemaRecordRelaxed> = RestrictiveStringRecord extends T ? {} : { + [Property in keyof T]: TreeFieldFromImplicitField; }; // @beta @input @@ -425,6 +455,7 @@ export class SchemaFactoryBeta, const TCustomMetadata = unknown>(name: Name, fields: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T>; // (undocumented) objectRecursive, const TCustomMetadata = unknown>(name: Name, t: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, System_Unsafe.TreeObjectNodeUnsafe>, object & System_Unsafe.InsertableObjectFromSchemaRecordUnsafe, false, T>; + objectRelaxed, const TCustomMetadata = unknown>(name: Name, fields: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNodeRelaxed>, object & InsertableObjectFromSchemaRecord, true, T>; record(allowedTypes: T): TreeNodeSchemaNonClass`>, NodeKind.Record, TreeRecordNode & WithType`>, NodeKind.Record>, RecordNodeInsertableData, true, T, undefined>; record(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Record, TreeRecordNode & WithType, NodeKind.Record>, RecordNodeInsertableData, true, T, undefined>; recordRecursive(name: Name, allowedTypes: T, options?: NodeSchemaOptions): TreeNodeSchemaClass, NodeKind.Record, TreeRecordNodeUnsafe & WithType, NodeKind.Record, unknown>, { @@ -453,6 +484,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}`; @@ -470,10 +506,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 @@ -483,6 +551,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; @@ -499,7 +571,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 @@ -508,7 +580,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 { @@ -525,6 +611,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> { } @@ -536,7 +631,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 @@ -692,6 +787,9 @@ export type TreeNodeSchemaNonClass, TypeName extends string = string> = TreeNode & ObjectFromSchemaRecord & WithType; +// @beta +export type TreeObjectNodeRelaxed, TypeName extends string = string> = TreeNode & ObjectFromSchemaRecordRelaxed & WithType; + // @beta export interface TreeRecordNode extends TreeNode, Record> { [Symbol.iterator](): IterableIterator<[ 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 c76a3a9cb3cd..ce6e28ccee8d 100644 --- a/packages/dds/tree/api-report/tree.legacy.beta.api.md +++ b/packages/dds/tree/api-report/tree.legacy.beta.api.md @@ -25,11 +25,19 @@ 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 export enum CommitKind { Default = 0, @@ -54,10 +62,34 @@ export function configuredSharedTreeBeta(options: SharedTreeOptionsBeta): Shared // @beta @legacy export function configuredSharedTreeBetaLegacy(options: SharedTreeOptionsBeta): ISharedObjectKind & SharedObjectKind; +// @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; @@ -141,6 +173,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; @@ -163,7 +200,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 : { @@ -173,9 +210,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); @@ -302,7 +337,16 @@ export type NumberKeys> = RestrictiveStringRecord extends T ? {} : { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; + -readonly [Property in keyof T as [ + AssignableTreeFieldFromImplicitField + ] extends [never | undefined] ? never : Property]: AssignableTreeFieldFromImplicitField; +} & { + readonly [Property in keyof T]: TreeFieldFromImplicitField; +}; + +// @beta @system +export type ObjectFromSchemaRecordRelaxed> = RestrictiveStringRecord extends T ? {} : { + [Property in keyof T]: TreeFieldFromImplicitField; }; // @beta @input @@ -414,6 +458,7 @@ export class SchemaFactoryBeta, const TCustomMetadata = unknown>(name: Name, fields: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T>; // (undocumented) objectRecursive, const TCustomMetadata = unknown>(name: Name, t: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, System_Unsafe.TreeObjectNodeUnsafe>, object & System_Unsafe.InsertableObjectFromSchemaRecordUnsafe, false, T>; + objectRelaxed, const TCustomMetadata = unknown>(name: Name, fields: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNodeRelaxed>, object & InsertableObjectFromSchemaRecord, true, T>; record(allowedTypes: T): TreeNodeSchemaNonClass`>, NodeKind.Record, TreeRecordNode & WithType`>, NodeKind.Record>, RecordNodeInsertableData, true, T, undefined>; record(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Record, TreeRecordNode & WithType, NodeKind.Record>, RecordNodeInsertableData, true, T, undefined>; recordRecursive(name: Name, allowedTypes: T, options?: NodeSchemaOptions): TreeNodeSchemaClass, NodeKind.Record, TreeRecordNodeUnsafe & WithType, NodeKind.Record, unknown>, { @@ -442,6 +487,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}`; @@ -468,10 +518,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 @@ -481,6 +563,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; @@ -497,7 +583,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 @@ -506,7 +592,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 { @@ -523,6 +623,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> { } @@ -534,7 +643,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 @@ -657,7 +766,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; @@ -690,6 +799,9 @@ export type TreeNodeSchemaNonClass, TypeName extends string = string> = TreeNode & ObjectFromSchemaRecord & WithType; +// @beta +export type TreeObjectNodeRelaxed, TypeName extends string = string> = TreeNode & ObjectFromSchemaRecordRelaxed & WithType; + // @beta export interface TreeRecordNode extends TreeNode, Record> { [Symbol.iterator](): IterableIterator<[ 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 52198c32ecd9..c782ceaf6b5d 100644 --- a/packages/dds/tree/api-report/tree.legacy.public.api.md +++ b/packages/dds/tree/api-report/tree.legacy.public.api.md @@ -14,17 +14,19 @@ 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 +// @public @system export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; -// @public -export type AssignableTreeFieldFromImplicitFieldUnsafe> = TSchema extends FieldSchemaUnsafe ? ApplyKindAssignment["readWrite"], Kind> : GetTypesUnsafe["readWrite"]; - // @public export enum CommitKind { Default = 0, @@ -38,10 +40,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; @@ -96,16 +122,11 @@ type FlexList = readonly LazyItem[]; // @public @system type FlexListToUnion = ExtractItemType; -// @public +// @public @system export type GetTypes = [TSchema] extends [ CustomizedSchemaTyping ] ? TCustom : StrictTypes; -// @public -export type GetTypesUnsafe> = [ -TSchema -] extends [CustomizedSchemaTyping] ? TCustom : StrictTypesUnsafe; - // @public export type ImplicitAllowedTypes = AllowedTypes | TreeNodeSchema; @@ -128,7 +149,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 : { @@ -253,7 +274,11 @@ export type NumberKeys> = RestrictiveStringRecord extends T ? {} : { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; + -readonly [Property in keyof T as [ + AssignableTreeFieldFromImplicitField + ] extends [never | undefined] ? never : Property]: AssignableTreeFieldFromImplicitField; +} & { + readonly [Property in keyof T]: TreeFieldFromImplicitField; }; // @public @deprecated @@ -369,6 +394,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}`; @@ -378,10 +408,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 @@ -391,6 +453,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; @@ -407,7 +473,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 @@ -416,7 +482,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 { @@ -433,6 +513,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> { } @@ -444,7 +533,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 diff --git a/packages/dds/tree/api-report/tree.public.api.md b/packages/dds/tree/api-report/tree.public.api.md index 52198c32ecd9..c782ceaf6b5d 100644 --- a/packages/dds/tree/api-report/tree.public.api.md +++ b/packages/dds/tree/api-report/tree.public.api.md @@ -14,17 +14,19 @@ 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 +// @public @system export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; -// @public -export type AssignableTreeFieldFromImplicitFieldUnsafe> = TSchema extends FieldSchemaUnsafe ? ApplyKindAssignment["readWrite"], Kind> : GetTypesUnsafe["readWrite"]; - // @public export enum CommitKind { Default = 0, @@ -38,10 +40,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; @@ -96,16 +122,11 @@ type FlexList = readonly LazyItem[]; // @public @system type FlexListToUnion = ExtractItemType; -// @public +// @public @system export type GetTypes = [TSchema] extends [ CustomizedSchemaTyping ] ? TCustom : StrictTypes; -// @public -export type GetTypesUnsafe> = [ -TSchema -] extends [CustomizedSchemaTyping] ? TCustom : StrictTypesUnsafe; - // @public export type ImplicitAllowedTypes = AllowedTypes | TreeNodeSchema; @@ -128,7 +149,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 : { @@ -253,7 +274,11 @@ export type NumberKeys> = RestrictiveStringRecord extends T ? {} : { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; + -readonly [Property in keyof T as [ + AssignableTreeFieldFromImplicitField + ] extends [never | undefined] ? never : Property]: AssignableTreeFieldFromImplicitField; +} & { + readonly [Property in keyof T]: TreeFieldFromImplicitField; }; // @public @deprecated @@ -369,6 +394,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}`; @@ -378,10 +408,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 @@ -391,6 +453,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; @@ -407,7 +473,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 @@ -416,7 +482,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 { @@ -433,6 +513,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> { } @@ -444,7 +533,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 diff --git a/packages/dds/tree/src/index.ts b/packages/dds/tree/src/index.ts index 1a0e07665469..cfb2b28f2304 100644 --- a/packages/dds/tree/src/index.ts +++ b/packages/dds/tree/src/index.ts @@ -289,6 +289,9 @@ export { type TreeParsingOptions, type SchemaFactory_base, type NumberKeys, + type TreeObjectNodeRelaxed, + type ObjectFromSchemaRecordRelaxed, + type DefaultTreeNodeFromImplicitAllowedTypes, } from "./simple-tree/index.js"; export { SharedTree, diff --git a/packages/dds/tree/src/simple-tree/api/index.ts b/packages/dds/tree/src/simple-tree/api/index.ts index 51be17c2d728..f7cdd71cf424 100644 --- a/packages/dds/tree/src/simple-tree/api/index.ts +++ b/packages/dds/tree/src/simple-tree/api/index.ts @@ -35,7 +35,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, @@ -97,6 +101,7 @@ export type { AllowedTypesFullFromMixedUnsafe, UnannotateAllowedTypesListUnsafe, AnnotateAllowedTypesListUnsafe, + customizeSchemaTypingUnsafe, } from "./typesUnsafe.js"; export { diff --git a/packages/dds/tree/src/simple-tree/api/schemaFactoryAlpha.ts b/packages/dds/tree/src/simple-tree/api/schemaFactoryAlpha.ts index 9e23c36fff22..1717aa1b729d 100644 --- a/packages/dds/tree/src/simple-tree/api/schemaFactoryAlpha.ts +++ b/packages/dds/tree/src/simple-tree/api/schemaFactoryAlpha.ts @@ -12,6 +12,7 @@ import { objectSchema, type RecordNodeCustomizableSchema, recordSchema, + type TreeObjectNodeRelaxed, } from "../node-kinds/index.js"; import { defaultSchemaFactoryObjectOptions, @@ -33,6 +34,7 @@ import type { WithType, AllowedTypesMetadata, AllowedTypesFullFromMixed, + TreeNode, } from "../core/index.js"; import { normalizeToAnnotatedAllowedType, @@ -568,3 +570,33 @@ 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 + > + ? TreeNodeSchemaClass< + Name, + NodeKind.Object, + TreeObjectNodeRelaxed, + TInsertable, + ImplicitlyConstructable, + Info + > + : T; +} diff --git a/packages/dds/tree/src/simple-tree/api/schemaFactoryBeta.ts b/packages/dds/tree/src/simple-tree/api/schemaFactoryBeta.ts index 1b914eafb90c..aa9f2cdbb418 100644 --- a/packages/dds/tree/src/simple-tree/api/schemaFactoryBeta.ts +++ b/packages/dds/tree/src/simple-tree/api/schemaFactoryBeta.ts @@ -19,6 +19,7 @@ import { type InsertableObjectFromSchemaRecord, type RecordNodeInsertableData, type TreeObjectNode, + type TreeObjectNodeRelaxed, type TreeRecordNode, } from "../node-kinds/index.js"; import { @@ -44,6 +45,7 @@ import type { import type { LeafSchema } from "../leafNodeSchema.js"; import type { SimpleLeafNodeSchema } from "../simpleSchema.js"; import type { RestrictiveStringRecord } from "../../util/index.js"; +import type { Insertable } from "../unsafeUnknownSchema.js"; /* eslint-enable unused-imports/no-unused-imports, @typescript-eslint/no-unused-vars, import/no-duplicates */ /** @@ -100,6 +102,32 @@ export class SchemaFactoryBeta< ); } + /** + * Define a {@link TreeNodeSchemaClass} for a {@link TreeObjectNode}. + * + * @param name - Unique identifier for this schema within this factory's scope. + * @param fields - Schema for fields of the object node's schema. Defines what children can be placed under each key. + * @param options - Additional options for the schema. + */ + public objectRelaxed< + const Name extends TName, + const T extends RestrictiveStringRecord, + const TCustomMetadata = unknown, + >( + name: Name, + fields: T, + options?: ObjectSchemaOptions, + ): TreeNodeSchemaClass< + ScopedSchemaName, + NodeKind.Object, + TreeObjectNodeRelaxed>, + object & InsertableObjectFromSchemaRecord, + true, + T + > { + return this.object(name, fields, options); + } + public override objectRecursive< const Name extends TName, const T extends RestrictiveStringRecord, diff --git a/packages/dds/tree/src/simple-tree/api/typesUnsafe.ts b/packages/dds/tree/src/simple-tree/api/typesUnsafe.ts index 1bf5cb4edfb3..5defa560b006 100644 --- a/packages/dds/tree/src/simple-tree/api/typesUnsafe.ts +++ b/packages/dds/tree/src/simple-tree/api/typesUnsafe.ts @@ -58,6 +58,24 @@ import type { CustomizedSchemaTyping, CustomTypes } from "../schemaTypes.js"; */ 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. @@ -67,24 +85,6 @@ export type Unenforced<_DesiredExtendsConstraint> = unknown; * @system @public */ export namespace System_Unsafe { - /** - * {@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( - schema: TSchema, - ): 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 }; - } - /** * {@link Unenforced} version of `Customizer`. * @remarks @@ -96,8 +96,8 @@ export namespace System_Unsafe { * 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 CustomizerUnsafe.relaxed} does. - * Until with {@link CustomizerUnsafe.relaxed}, implicit construction is disabled, meaning all nodes must be explicitly constructed (and thus implement `T`) before being inserted. + * 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, @@ -111,7 +111,7 @@ export namespace System_Unsafe { >; /** - * The same as {@link CustomizerUnsafe} except that more T values are allowed, even ones not known to be implemented by `TSchema`. + * 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, @@ -126,7 +126,7 @@ export namespace System_Unsafe { * Fully arbitrary customization. * Provided types override existing types. * @remarks - * This can express any of the customizations possible via other {@link CustomizerUnsafe} methods: + * 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< @@ -378,7 +378,7 @@ export namespace System_Unsafe { > = GetTypesUnsafe["output"]; /** - * {@link Unenforced} version of {@link DefaultTreeNodeFromImplicitAllowedTypesUnsafe}. + * {@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 diff --git a/packages/dds/tree/src/simple-tree/fieldSchema.ts b/packages/dds/tree/src/simple-tree/fieldSchema.ts index 17bffd1f91be..968ec0089874 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, diff --git a/packages/dds/tree/src/simple-tree/index.ts b/packages/dds/tree/src/simple-tree/index.ts index 8845f31fdb28..790c0a23a169 100644 --- a/packages/dds/tree/src/simple-tree/index.ts +++ b/packages/dds/tree/src/simple-tree/index.ts @@ -182,6 +182,7 @@ export { KeyEncodingOptions, type TreeParsingOptions, type SchemaFactory_base, + relaxObject, } from "./api/index.js"; export type { @@ -259,6 +260,8 @@ export { type RecordNodePojoEmulationSchema, RecordNodeSchema, type TreeRecordNode, + type ObjectFromSchemaRecordRelaxed, + type TreeObjectNodeRelaxed, } from "./node-kinds/index.js"; export { unhydratedFlexTreeFromInsertable, @@ -289,3 +292,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 8cc9ebd1feee..2870820635c9 100644 --- a/packages/dds/tree/src/simple-tree/node-kinds/index.ts +++ b/packages/dds/tree/src/simple-tree/node-kinds/index.ts @@ -38,6 +38,8 @@ export { type SimpleKeyMap, type AssignableTreeFieldFromImplicitField, type ApplyKindAssignment, + type ObjectFromSchemaRecordRelaxed, + type TreeObjectNodeRelaxed, } 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 f8f83014488d..99cd5201d3c1 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 @@ -7,12 +7,14 @@ export { type FieldHasDefault, type InsertableObjectFromSchemaRecord, type ObjectFromSchemaRecord, + type ObjectFromSchemaRecordRelaxed, objectSchema, setField, type TreeObjectNode, type SimpleKeyMap, type AssignableTreeFieldFromImplicitField, type ApplyKindAssignment, + type TreeObjectNodeRelaxed, } 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 2bc31bde3d9a..830c9debb4a2 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 @@ -95,20 +95,50 @@ import type { GetTypes, SchemaUnionToIntersection } from "../../schemaTypes.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> = { - // 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> = { +// // 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> = + RestrictiveStringRecord extends T + ? // eslint-disable-next-line @typescript-eslint/ban-types + {} + : { + -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; + }; + +/** + * Same as {@link ObjectFromSchemaRecord}, but does not remove setters for fields which can't be assigned to. + * @system @beta + */ +export type ObjectFromSchemaRecordRelaxed< + T extends RestrictiveStringRecord, +> = RestrictiveStringRecord extends T + ? // eslint-disable-next-line @typescript-eslint/ban-types + {} + : { + [Property in keyof T]: TreeFieldFromImplicitField; + }; /** * Type of content that can be assigned to a field of the given schema. @@ -162,6 +192,15 @@ export type TreeObjectNode< TypeName extends string = string, > = TreeNode & ObjectFromSchemaRecord & WithType; +/** + * {@link TreeObjectNode} except using {@link ObjectFromSchemaRecordRelaxed}. + * @beta + */ +export type TreeObjectNodeRelaxed< + T extends RestrictiveStringRecord, + TypeName extends string = string, +> = TreeNode & ObjectFromSchemaRecordRelaxed & WithType; + /** * Type utility for determining if an implicit field schema is known to have a default value. * diff --git a/packages/dds/tree/src/tableSchema.ts b/packages/dds/tree/src/tableSchema.ts index 1ee9d1538140..04f629b5dd62 100644 --- a/packages/dds/tree/src/tableSchema.ts +++ b/packages/dds/tree/src/tableSchema.ts @@ -29,6 +29,8 @@ import { isArrayNodeSchema, type InsertableField, withBufferedTreeEvents, + type DefaultTreeNodeFromImplicitAllowedTypes, + relaxObject, } from "./simple-tree/index.js"; // Future improvement TODOs: @@ -152,6 +154,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 @@ -201,10 +230,12 @@ export namespace System_TableSchema { * A column in a table. */ class Column - extends schemaFactory.object("Column", columnFields, { - // Will make it easier to evolve this schema in the future. - allowUnknownOptionalFields: true, - }) + extends relaxObject( + schemaFactory.object("Column", columnFields, { + // Will make it easier to evolve this schema in the future. + allowUnknownOptionalFields: true, + }), + ) implements TableSchema.Column { public getCells(): { @@ -226,7 +257,7 @@ export namespace System_TableSchema { ); } - result.push({ rowId: row.id, cell: cell as CellValueType }); + result.push({ rowId: row.id, cell }); } } return result; @@ -381,10 +412,12 @@ export namespace System_TableSchema { * The Row schema - this is a map of Cells where the key is the column id */ class Row - extends schemaFactory.object("Row", rowFields, { - // Will make it easier to evolve this schema in the future. - allowUnknownOptionalFields: true, - }) + extends relaxObject( + schemaFactory.object("Row", rowFields, { + // Will make it easier to evolve this schema in the future. + allowUnknownOptionalFields: true, + }), + ) implements TableSchema.Row { public getCell( @@ -565,6 +598,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. @@ -594,7 +630,7 @@ export namespace System_TableSchema { public getColumn(id: string): ColumnValueType | undefined { // TypeScript is unable to narrow the types correctly here, hence the casts. // See: https://github.com/microsoft/TypeScript/issues/52144 - return this.columns.find((column) => (column as ColumnValueType).id === id) as + return this.columns.find((column) => (column as ColumnValueTypeInternal).id === id) as | ColumnValueType | undefined; } @@ -602,7 +638,7 @@ export namespace System_TableSchema { public getRow(id: string): RowValueType | undefined { // 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 === id) as + return this.rows.find((_row) => (_row as RowValueTypeInternal).id === id) as | RowValueType | undefined; } @@ -621,7 +657,7 @@ export namespace System_TableSchema { return undefined; } - return row.getCell(column); + return (row as RowValueTypeInternal).getCell(column as ColumnValueTypeInternal); } public insertColumns({ @@ -702,7 +738,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( @@ -765,7 +801,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. @@ -829,12 +867,14 @@ export namespace System_TableSchema { const row = this._getRow(rowOrId); const column = this._getColumn(columnOrId); - 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; } @@ -845,7 +885,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); } } @@ -931,7 +971,9 @@ export namespace System_TableSchema { * If a node is provided, its ID is returned. */ private _getColumnId(columnOrId: string | ColumnValueType): string { - return typeof columnOrId === "string" ? columnOrId : columnOrId.id; + return typeof columnOrId === "string" + ? columnOrId + : (columnOrId as ColumnValueTypeInternal).id; } /** @@ -977,7 +1019,7 @@ export namespace System_TableSchema { * If a node is provided, its ID is returned. */ private _getRowId(rowOrId: string | RowValueType): string { - return typeof rowOrId === "string" ? rowOrId : rowOrId.id; + return typeof rowOrId === "string" ? rowOrId : (rowOrId as RowValueTypeInternal).id; } /** 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 795a48cf7711..6ffeb8062aeb 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 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 263b6142adb5..c44a1c56e4d9 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 @@ -28,14 +28,18 @@ import { type AllowedTypesFull, type ImplicitAllowedTypes, type AllowedTypes, + 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"; @@ -1463,6 +1467,7 @@ 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<{ 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 ce92387e5403..e4f1f025bbe4 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 @@ -12,10 +12,6 @@ import type { import { allowUnused } from "../../../simple-tree/index.js"; import { customizeSchemaTypingUnsafe, - type InsertableObjectFromSchemaRecordUnsafe, - type InsertableTreeFieldFromImplicitFieldUnsafe, - type InsertableTreeNodeFromImplicitAllowedTypesUnsafe, - type ReadonlyMapInlined, // eslint-disable-next-line import/no-internal-modules } from "../../../simple-tree/api/typesUnsafe.js"; import { SchemaFactory, type ValidateRecursiveSchema } from "../../../simple-tree/index.js"; @@ -45,15 +41,16 @@ import type { areSafelyAssignable, requireTrue } from "../../../util/index.js"; // customizeSchemaTypingUnsafe and InsertableObjectFromSchemaRecordUnsafe { const sf = new SchemaFactory("recursive"); + // @ts-expect-error compiler differes from intelisense here class Bad extends sf.objectRecursive("O", { - // customizeSchemaTypingUnsafe needs to be applied to the allowed types, not eh field: this is wrong! + // @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; }>(), }) {} { - // Ideally this would error, but detecting this is invalid is hard. + // @ts-expect-error this should error type _check = ValidateRecursiveSchema; } @@ -67,19 +64,21 @@ import type { areSafelyAssignable, requireTrue } from "../../../util/index.js"; // Record { - type T = InsertableObjectFromSchemaRecordUnsafe["recursive"]; + type T = System_Unsafe.InsertableObjectFromSchemaRecordUnsafe["recursive"]; type _check = requireTrue>; } // Field { - type T = InsertableTreeFieldFromImplicitFieldUnsafe; + type T = System_Unsafe.InsertableTreeFieldFromImplicitFieldUnsafe< + typeof O.info.recursive.allowedTypes + >; type _check = requireTrue>; } // AllowedTypes { - type T = InsertableTreeNodeFromImplicitAllowedTypesUnsafe< + 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 0adc34ec73a4..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"; @@ -303,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 }); @@ -321,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 }); } @@ -342,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. @@ -392,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; }); @@ -408,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; }); @@ -422,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 9b2513c0ef67..9a91f6ebb241 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 @@ -30,7 +30,7 @@ import { type NodeFromSchema, unhydratedFlexTreeFromInsertable, type TreeFieldFromImplicitField, - type GetTypesUnsafe, + type System_Unsafe, } from "../../../../simple-tree/index.js"; import type { FieldHasDefault, @@ -484,7 +484,7 @@ describeHydration( const initial: InsertableField = { child: 1 }; const n: NonExact = init(NonExact, initial); const childRead = n.child; - type XXX = FlattenKeys>; + type XXX = FlattenKeys>; type _check = requireTrue>; assert.throws(() => { // @ts-expect-error this should not compile @@ -610,6 +610,7 @@ describeHydration( 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"; }); }); 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 ec9839112087..df882f33cb85 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 @@ -117,6 +122,9 @@ export const ArrayNodeSchema: { // @alpha export function asAlpha(view: TreeView): TreeViewAlpha; +// @public @system +export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; + // @alpha @deprecated export function asTreeViewAlpha(view: TreeView): TreeViewAlpha; @@ -162,6 +170,13 @@ export interface CommitMetadata { // @alpha export function comparePersistedSchema(persisted: JsonCompatible, view: ImplicitFieldSchema, options: ICodecOptions): Omit; +// @alpha +export namespace Component { + export type ComponentSchemaCollection = (lazyConfiguration: () => TConfig) => LazyArray; + export function composeComponentSchema(allComponents: readonly ComponentSchemaCollection[], lazyConfiguration: () => TConfig): (() => TItem)[]; + export type LazyArray = readonly (() => T)[]; +} + // @beta export type ConciseTree = Exclude | THandle | ConciseTree[] | { [key: string]: ConciseTree; @@ -220,16 +235,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 +// @public @system export type DefaultTreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; -// @public -export type DefaultTreeNodeFromImplicitAllowedTypesUnsafe> = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; - // @alpha export interface DirtyTreeMap { // (undocumented) @@ -435,16 +494,11 @@ export function getJsonSchema(schema: ImplicitAllowedTypes, options: Required = [TSchema] extends [ CustomizedSchemaTyping ] ? TCustom : StrictTypes; -// @public -export type GetTypesUnsafe> = [ -TSchema -] extends [CustomizedSchemaTyping] ? TCustom : StrictTypesUnsafe; - // @alpha export type HandleConverter = (data: IFluidHandle) => TCustom; @@ -754,7 +808,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 : { @@ -1087,7 +1141,16 @@ export type NumberKeys> = RestrictiveStringRecord extends T ? {} : { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; + -readonly [Property in keyof T as [ + AssignableTreeFieldFromImplicitField + ] extends [never | undefined] ? never : Property]: AssignableTreeFieldFromImplicitField; +} & { + readonly [Property in keyof T]: TreeFieldFromImplicitField; +}; + +// @beta @system +export type ObjectFromSchemaRecordRelaxed> = RestrictiveStringRecord extends T ? {} : { + [Property in keyof T]: TreeFieldFromImplicitField; }; // @alpha @sealed @@ -1312,6 +1375,7 @@ export class SchemaFactoryBeta, const TCustomMetadata = unknown>(name: Name, fields: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T>; // (undocumented) objectRecursive, const TCustomMetadata = unknown>(name: Name, t: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, System_Unsafe.TreeObjectNodeUnsafe>, object & System_Unsafe.InsertableObjectFromSchemaRecordUnsafe, false, T>; + objectRelaxed, const TCustomMetadata = unknown>(name: Name, fields: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNodeRelaxed>, object & InsertableObjectFromSchemaRecord, true, T>; record(allowedTypes: T): TreeNodeSchemaNonClass`>, NodeKind.Record, TreeRecordNode & WithType`>, NodeKind.Record>, RecordNodeInsertableData, true, T, undefined>; record(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Record, TreeRecordNode & WithType, NodeKind.Record>, RecordNodeInsertableData, true, T, undefined>; recordRecursive(name: Name, allowedTypes: T, options?: NodeSchemaOptions): TreeNodeSchemaClass, NodeKind.Record, TreeRecordNodeUnsafe & WithType, NodeKind.Record, unknown>, { @@ -1348,6 +1412,11 @@ export interface SchemaStaticsAlpha { readonly typesRecursive: >[]>(t: T, metadata?: AllowedTypesMetadata) => AllowedTypesFullFromMixedUnsafe; } +// @public @system +export type SchemaUnionToIntersection = [T] extends [ +CustomizedSchemaTyping +] ? T : UnionToIntersection; + // @alpha @sealed export class SchemaUpgrade { // (undocumented) @@ -1455,6 +1524,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 @@ -1489,18 +1568,18 @@ export namespace System_TableSchema { }>; // @system export function createTableSchema, const TRowSchema extends RowSchemaBase>(inputSchemaFactory: SchemaFactoryAlpha, _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 @@ -1525,6 +1604,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 @@ -1534,6 +1635,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; @@ -1550,7 +1655,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 @@ -1559,7 +1664,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 { @@ -1576,6 +1695,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> { } @@ -1587,7 +1715,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 @@ -1947,6 +2075,9 @@ export type TreeNodeSchemaNonClass, TypeName extends string = string> = TreeNode & ObjectFromSchemaRecord & WithType; +// @beta +export type TreeObjectNodeRelaxed, TypeName extends string = string> = TreeNode & ObjectFromSchemaRecordRelaxed & WithType; + // @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 3490c3906376..90116ecce18d 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 @@ -25,17 +25,19 @@ 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 +// @public @system export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; -// @public -export type AssignableTreeFieldFromImplicitFieldUnsafe> = TSchema extends FieldSchemaUnsafe ? ApplyKindAssignment["readWrite"], Kind> : GetTypesUnsafe["readWrite"]; - // @public export enum AttachState { Attached = "Attached", @@ -96,10 +98,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; + // @beta export function enumFromStrings(factory: SchemaFactory, members: Members): ((value: TValue) => TValue extends unknown ? TreeNode & { readonly value: TValue; @@ -197,16 +223,11 @@ export const ForestTypeOptimized: ForestType; // @beta export const ForestTypeReference: ForestType; -// @public +// @public @system export type GetTypes = [TSchema] extends [ CustomizedSchemaTyping ] ? TCustom : StrictTypes; -// @public -export type GetTypesUnsafe> = [ -TSchema -] extends [CustomizedSchemaTyping] ? TCustom : StrictTypesUnsafe; - // @public export interface IConnection { readonly id: string; @@ -486,7 +507,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 : { @@ -666,7 +687,16 @@ export type NumberKeys> = RestrictiveStringRecord extends T ? {} : { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; + -readonly [Property in keyof T as [ + AssignableTreeFieldFromImplicitField + ] extends [never | undefined] ? never : Property]: AssignableTreeFieldFromImplicitField; +} & { + readonly [Property in keyof T]: TreeFieldFromImplicitField; +}; + +// @beta @system +export type ObjectFromSchemaRecordRelaxed> = RestrictiveStringRecord extends T ? {} : { + [Property in keyof T]: TreeFieldFromImplicitField; }; // @beta @input @@ -783,6 +813,7 @@ export class SchemaFactoryBeta, const TCustomMetadata = unknown>(name: Name, fields: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T>; // (undocumented) objectRecursive, const TCustomMetadata = unknown>(name: Name, t: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, System_Unsafe.TreeObjectNodeUnsafe>, object & System_Unsafe.InsertableObjectFromSchemaRecordUnsafe, false, T>; + objectRelaxed, const TCustomMetadata = unknown>(name: Name, fields: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNodeRelaxed>, object & InsertableObjectFromSchemaRecord, true, T>; record(allowedTypes: T): TreeNodeSchemaNonClass`>, NodeKind.Record, TreeRecordNode & WithType`>, NodeKind.Record>, RecordNodeInsertableData, true, T, undefined>; record(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Record, TreeRecordNode & WithType, NodeKind.Record>, RecordNodeInsertableData, true, T, undefined>; recordRecursive(name: Name, allowedTypes: T, options?: NodeSchemaOptions): TreeNodeSchemaClass, NodeKind.Record, TreeRecordNodeUnsafe & WithType, NodeKind.Record, unknown>, { @@ -811,6 +842,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}`; @@ -836,10 +872,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 @@ -849,6 +917,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; @@ -865,7 +937,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 @@ -874,7 +946,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 { @@ -891,6 +977,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> { } @@ -902,7 +997,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 @@ -1072,6 +1167,9 @@ export type TreeNodeSchemaNonClass, TypeName extends string = string> = TreeNode & ObjectFromSchemaRecord & WithType; +// @beta +export type TreeObjectNodeRelaxed, TypeName extends string = string> = TreeNode & ObjectFromSchemaRecordRelaxed & WithType; + // @beta export interface TreeRecordNode extends TreeNode, Record> { [Symbol.iterator](): IterableIterator<[ 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 5747e0bed82c..bd80106eb6ba 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 @@ -25,17 +25,19 @@ 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 +// @public @system export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; -// @public -export type AssignableTreeFieldFromImplicitFieldUnsafe> = TSchema extends FieldSchemaUnsafe ? ApplyKindAssignment["readWrite"], Kind> : GetTypesUnsafe["readWrite"]; - // @public export enum AttachState { Attached = "Attached", @@ -96,10 +98,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; + // @beta @legacy (undocumented) export type DeserializeCallback = (properties: PropertySet) => void; @@ -200,6 +226,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; @@ -523,7 +554,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 : { @@ -934,7 +965,16 @@ export type NumberKeys> = RestrictiveStringRecord extends T ? {} : { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; + -readonly [Property in keyof T as [ + AssignableTreeFieldFromImplicitField + ] extends [never | undefined] ? never : Property]: AssignableTreeFieldFromImplicitField; +} & { + readonly [Property in keyof T]: TreeFieldFromImplicitField; +}; + +// @beta @system +export type ObjectFromSchemaRecordRelaxed> = RestrictiveStringRecord extends T ? {} : { + [Property in keyof T]: TreeFieldFromImplicitField; }; // @beta @input @@ -1051,6 +1091,7 @@ export class SchemaFactoryBeta, const TCustomMetadata = unknown>(name: Name, fields: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T>; // (undocumented) objectRecursive, const TCustomMetadata = unknown>(name: Name, t: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, System_Unsafe.TreeObjectNodeUnsafe>, object & System_Unsafe.InsertableObjectFromSchemaRecordUnsafe, false, T>; + objectRelaxed, const TCustomMetadata = unknown>(name: Name, fields: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNodeRelaxed>, object & InsertableObjectFromSchemaRecord, true, T>; record(allowedTypes: T): TreeNodeSchemaNonClass`>, NodeKind.Record, TreeRecordNode & WithType`>, NodeKind.Record>, RecordNodeInsertableData, true, T, undefined>; record(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Record, TreeRecordNode & WithType, NodeKind.Record>, RecordNodeInsertableData, true, T, undefined>; recordRecursive(name: Name, allowedTypes: T, options?: NodeSchemaOptions): TreeNodeSchemaClass, NodeKind.Record, TreeRecordNodeUnsafe & WithType, NodeKind.Record, unknown>, { @@ -1079,6 +1120,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}`; @@ -1184,10 +1230,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 @@ -1197,6 +1275,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; @@ -1213,7 +1295,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 @@ -1222,7 +1304,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 { @@ -1239,6 +1335,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> { } @@ -1250,7 +1355,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 @@ -1420,6 +1525,9 @@ export type TreeNodeSchemaNonClass, TypeName extends string = string> = TreeNode & ObjectFromSchemaRecord & WithType; +// @beta +export type TreeObjectNodeRelaxed, TypeName extends string = string> = TreeNode & ObjectFromSchemaRecordRelaxed & WithType; + // @beta export interface TreeRecordNode extends TreeNode, Record> { [Symbol.iterator](): IterableIterator<[ 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 2d66e273b8f0..e2611963ad01 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,17 +14,19 @@ 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 +// @public @system export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; -// @public -export type AssignableTreeFieldFromImplicitFieldUnsafe> = TSchema extends FieldSchemaUnsafe ? ApplyKindAssignment["readWrite"], Kind> : GetTypesUnsafe["readWrite"]; - // @public export enum AttachState { Attached = "Attached", @@ -77,16 +79,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 +// @public @system export type DefaultTreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; -// @public -export type DefaultTreeNodeFromImplicitAllowedTypesUnsafe> = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; - // @public @sealed export abstract class ErasedType { static [Symbol.hasInstance](value: never): value is never; @@ -155,16 +175,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 +// @public @system export type GetTypes = [TSchema] extends [ CustomizedSchemaTyping ] ? TCustom : StrictTypes; -// @public -export type GetTypesUnsafe> = [ -TSchema -] extends [CustomizedSchemaTyping] ? TCustom : StrictTypesUnsafe; - // @public export interface IConnection { readonly id: string; @@ -472,7 +487,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 : { @@ -646,7 +661,11 @@ export type NumberKeys> = RestrictiveStringRecord extends T ? {} : { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; + -readonly [Property in keyof T as [ + AssignableTreeFieldFromImplicitField + ] extends [never | undefined] ? never : Property]: AssignableTreeFieldFromImplicitField; +} & { + readonly [Property in keyof T]: TreeFieldFromImplicitField; }; // @public @@ -767,6 +786,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}`; @@ -784,10 +808,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 @@ -797,6 +853,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; @@ -813,7 +873,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 @@ -822,7 +882,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 { @@ -839,6 +913,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> { } @@ -850,7 +933,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 @@ -867,26 +950,6 @@ export namespace System_Unsafe { export type TreeObjectNodeUnsafe, TypeName extends string = string> = TreeNode & ObjectFromSchemaRecordUnsafe & WithType; } -// @public @sealed -export interface StrictTypes, TOutput extends TreeNode | TreeLeafValue = DefaultTreeNodeFromImplicitAllowedTypes> { - // (undocumented) - input: TInput; - // (undocumented) - output: TOutput; - // (undocumented) - readWrite: TInput extends never ? never : TOutput; -} - -// @public -export interface StrictTypesUnsafe, TInput = DefaultInsertableTreeNodeFromImplicitAllowedTypesUnsafe, TOutput = DefaultTreeNodeFromImplicitAllowedTypesUnsafe> { - // (undocumented) - input: TInput; - // (undocumented) - output: TOutput; - // (undocumented) - readWrite: TOutput; -} - // @public export interface Tagged { // (undocumented) 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 5395a21b475e..f5590bba9437 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,17 +14,19 @@ 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 +// @public @system export type AssignableTreeFieldFromImplicitField> = [TSchema] extends [FieldSchema] ? ApplyKindAssignment["readWrite"], Kind> : [TSchema] extends [ImplicitAllowedTypes] ? GetTypes["readWrite"] : never; -// @public -export type AssignableTreeFieldFromImplicitFieldUnsafe> = TSchema extends FieldSchemaUnsafe ? ApplyKindAssignment["readWrite"], Kind> : GetTypesUnsafe["readWrite"]; - // @public export enum AttachState { Attached = "Attached", @@ -77,16 +79,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 +// @public @system export type DefaultTreeNodeFromImplicitAllowedTypes = TSchema extends TreeNodeSchema ? NodeFromSchema : TSchema extends AllowedTypes ? NodeFromSchema> : unknown; -// @public -export type DefaultTreeNodeFromImplicitAllowedTypesUnsafe> = TSchema extends TreeNodeSchemaUnsafe ? NodeFromSchemaUnsafe : TSchema extends AllowedTypesUnsafe ? NodeFromSchemaUnsafe> : unknown; - // @public @sealed export abstract class ErasedType { static [Symbol.hasInstance](value: never): value is never; @@ -155,16 +175,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 +// @public @system export type GetTypes = [TSchema] extends [ CustomizedSchemaTyping ] ? TCustom : StrictTypes; -// @public -export type GetTypesUnsafe> = [ -TSchema -] extends [CustomizedSchemaTyping] ? TCustom : StrictTypesUnsafe; - // @public export interface IConnection { readonly id: string; @@ -444,7 +459,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 : { @@ -612,7 +627,11 @@ export type NumberKeys> = RestrictiveStringRecord extends T ? {} : { - -readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField : unknown; + -readonly [Property in keyof T as [ + AssignableTreeFieldFromImplicitField + ] extends [never | undefined] ? never : Property]: AssignableTreeFieldFromImplicitField; +} & { + readonly [Property in keyof T]: TreeFieldFromImplicitField; }; // @public @@ -733,6 +752,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}`; @@ -750,10 +774,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 @@ -763,6 +819,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; @@ -779,7 +839,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 @@ -788,7 +848,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 { @@ -805,6 +879,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> { } @@ -816,7 +899,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 From fbe06f67d95e668a3389203649d9c8ea91ec048d Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Tue, 21 Oct 2025 11:21:39 -0700 Subject: [PATCH 33/37] Add options for type customization and readonly field inclusion --- packages/dds/tree/src/index.ts | 7 +- .../tree/src/simple-tree/api/schemaFactory.ts | 21 +-- .../src/simple-tree/api/schemaFactoryAlpha.ts | 37 +++-- .../src/simple-tree/api/schemaFactoryBeta.ts | 51 ++----- packages/dds/tree/src/simple-tree/index.ts | 4 +- .../tree/src/simple-tree/node-kinds/index.ts | 4 +- .../simple-tree/node-kinds/object/index.ts | 22 +-- .../node-kinds/object/objectNode.ts | 141 ++++++++++++++---- .../node-kinds/object/objectNodeTypes.ts | 12 +- packages/dds/tree/src/tableSchema.ts | 25 ++-- .../tree/src/test/openPolymorphism.spec.ts | 9 +- .../api/schemaFactory.examples.spec.ts | 68 ++++++--- .../simple-tree/api/schemaFactory.spec.ts | 27 ++++ .../api/schemaFactoryRecursive.spec.ts | 28 ++-- .../dds/tree/src/test/tableSchema.spec.ts | 13 ++ packages/dds/tree/src/util/index.ts | 1 + packages/dds/tree/src/util/typeUtils.ts | 11 ++ 17 files changed, 311 insertions(+), 170 deletions(-) diff --git a/packages/dds/tree/src/index.ts b/packages/dds/tree/src/index.ts index cfb2b28f2304..911ed00f7905 100644 --- a/packages/dds/tree/src/index.ts +++ b/packages/dds/tree/src/index.ts @@ -289,9 +289,11 @@ export { type TreeParsingOptions, type SchemaFactory_base, type NumberKeys, - type TreeObjectNodeRelaxed, - type ObjectFromSchemaRecordRelaxed, type DefaultTreeNodeFromImplicitAllowedTypes, + type ObjectFromSchemaRecordRelaxed, + type ObjectSchemaTypingOptions, + type AssignableTreeFieldFromImplicitFieldDefault, + type TreeFieldFromImplicitFieldDefault, } from "./simple-tree/index.js"; export { SharedTree, @@ -332,6 +334,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/schemaFactory.ts b/packages/dds/tree/src/simple-tree/api/schemaFactory.ts index 17570abbc143..b2bdd5833f34 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< - Omit -> = { - allowUnknownOptionalFields: false, -}; - /** * The name of a schema produced by {@link SchemaFactory}, including its optional scope prefix. * @@ -397,12 +389,7 @@ export class SchemaFactory< true, T > { - return objectSchema( - scoped(this, name), - fields, - true, - defaultSchemaFactoryObjectOptions.allowUnknownOptionalFields, - ); + 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 1717aa1b729d..7ba571f784e3 100644 --- a/packages/dds/tree/src/simple-tree/api/schemaFactoryAlpha.ts +++ b/packages/dds/tree/src/simple-tree/api/schemaFactoryAlpha.ts @@ -8,14 +8,13 @@ import { arraySchema, type MapNodeCustomizableSchema, mapSchema, + type ObjectFromSchemaRecordRelaxed, type ObjectNodeSchema, objectSchema, type RecordNodeCustomizableSchema, recordSchema, - type TreeObjectNodeRelaxed, } from "../node-kinds/index.js"; import { - defaultSchemaFactoryObjectOptions, scoped, type NodeSchemaOptionsAlpha, type ObjectSchemaOptionsAlpha, @@ -23,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, @@ -202,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 @@ -220,15 +219,7 @@ export class SchemaFactoryAlpha< */ readonly createFromInsertable: unknown; } { - return objectSchema( - scoped(this, name), - fields, - true, - options?.allowUnknownOptionalFields ?? - defaultSchemaFactoryObjectOptions.allowUnknownOptionalFields, - options?.metadata, - options?.persistedMetadata, - ); + return objectSchema(scoped(this, name), fields, true, options); } /** @@ -238,10 +229,12 @@ export class SchemaFactoryAlpha< 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, @@ -283,7 +276,7 @@ export class SchemaFactoryAlpha< ScopedSchemaName, RestrictiveStringRecord, false, - TCustomMetadata + TOptions >; } @@ -588,15 +581,19 @@ export function relaxObject + infer Info extends RestrictiveStringRecord, + infer TConstructorExtra, + infer TCustomMetadata > ? TreeNodeSchemaClass< Name, NodeKind.Object, - TreeObjectNodeRelaxed, + TreeNode & WithType & ObjectFromSchemaRecordRelaxed, TInsertable, ImplicitlyConstructable, - Info + 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 aa9f2cdbb418..378b98eb1bc3 100644 --- a/packages/dds/tree/src/simple-tree/api/schemaFactoryBeta.ts +++ b/packages/dds/tree/src/simple-tree/api/schemaFactoryBeta.ts @@ -19,16 +19,15 @@ import { type InsertableObjectFromSchemaRecord, type RecordNodeInsertableData, type TreeObjectNode, - type TreeObjectNodeRelaxed, 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"; @@ -44,7 +43,7 @@ import type { } from "../fieldSchema.js"; import type { LeafSchema } from "../leafNodeSchema.js"; import type { SimpleLeafNodeSchema } from "../simpleSchema.js"; -import type { RestrictiveStringRecord } from "../../util/index.js"; +import type { PreventExtraProperties, RestrictiveStringRecord } from "../../util/index.js"; import type { Insertable } from "../unsafeUnknownSchema.js"; /* eslint-enable unused-imports/no-unused-imports, @typescript-eslint/no-unused-vars, import/no-duplicates */ @@ -79,53 +78,33 @@ 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 > { + // TODO: make type safe return objectSchema( scoped(this, name), fields, true, - options?.allowUnknownOptionalFields ?? - defaultSchemaFactoryObjectOptions.allowUnknownOptionalFields, - options?.metadata, - ); - } - - /** - * Define a {@link TreeNodeSchemaClass} for a {@link TreeObjectNode}. - * - * @param name - Unique identifier for this schema within this factory's scope. - * @param fields - Schema for fields of the object node's schema. Defines what children can be placed under each key. - * @param options - Additional options for the schema. - */ - public objectRelaxed< - const Name extends TName, - const T extends RestrictiveStringRecord, - const TCustomMetadata = unknown, - >( - name: Name, - fields: T, - options?: ObjectSchemaOptions, - ): TreeNodeSchemaClass< - ScopedSchemaName, - NodeKind.Object, - TreeObjectNodeRelaxed>, - object & InsertableObjectFromSchemaRecord, - true, - T - > { - return this.object(name, fields, options); + options as PreventExtraProperties, + ) as TreeNodeSchemaClass< + ScopedSchemaName, + NodeKind.Object, + TreeObjectNode, TOptions>, + object & InsertableObjectFromSchemaRecord, + true, + T + >; } public override objectRecursive< diff --git a/packages/dds/tree/src/simple-tree/index.ts b/packages/dds/tree/src/simple-tree/index.ts index 790c0a23a169..17d5c114f589 100644 --- a/packages/dds/tree/src/simple-tree/index.ts +++ b/packages/dds/tree/src/simple-tree/index.ts @@ -261,7 +261,9 @@ export { RecordNodeSchema, type TreeRecordNode, type ObjectFromSchemaRecordRelaxed, - type TreeObjectNodeRelaxed, + type ObjectSchemaTypingOptions, + type AssignableTreeFieldFromImplicitFieldDefault, + type TreeFieldFromImplicitFieldDefault, } from "./node-kinds/index.js"; export { unhydratedFlexTreeFromInsertable, 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 2870820635c9..87ef7e368e79 100644 --- a/packages/dds/tree/src/simple-tree/node-kinds/index.ts +++ b/packages/dds/tree/src/simple-tree/node-kinds/index.ts @@ -38,8 +38,10 @@ export { type SimpleKeyMap, type AssignableTreeFieldFromImplicitField, type ApplyKindAssignment, + type ObjectSchemaTypingOptions, + type AssignableTreeFieldFromImplicitFieldDefault, + type TreeFieldFromImplicitFieldDefault, type ObjectFromSchemaRecordRelaxed, - type TreeObjectNodeRelaxed, } 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 99cd5201d3c1..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,17 +4,21 @@ */ export { - type FieldHasDefault, - type InsertableObjectFromSchemaRecord, - type ObjectFromSchemaRecord, - type ObjectFromSchemaRecordRelaxed, objectSchema, setField, - type TreeObjectNode, - type SimpleKeyMap, - type AssignableTreeFieldFromImplicitField, - type ApplyKindAssignment, - type TreeObjectNodeRelaxed, +} 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 830c9debb4a2..d1fd47d85adf 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"; @@ -51,6 +52,7 @@ import { 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,7 +90,13 @@ import { type InsertableContent, } from "../../unhydratedFlexTreeFromInsertable.js"; import { convertField, convertFieldKind } from "../../toStoredSchema.js"; -import type { GetTypes, SchemaUnionToIntersection } from "../../schemaTypes.js"; +import type { + DefaultTreeNodeFromImplicitAllowedTypes, + GetTypes, + SchemaUnionToIntersection, + StrictTypes, +} from "../../schemaTypes.js"; +import type { ObjectSchemaOptionsAlpha } from "../../api/index.js"; /** * Generates the properties for an ObjectNode from its field schema object. @@ -112,23 +121,36 @@ import type { GetTypes, SchemaUnionToIntersection } from "../../schemaTypes.js"; // } & { // readonly [Property in keyof T]: TreeFieldFromImplicitField; // }; -export type ObjectFromSchemaRecord> = - RestrictiveStringRecord extends T - ? // eslint-disable-next-line @typescript-eslint/ban-types - {} - : { + +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]: AssignableTreeFieldFromImplicitField; + : Property]: [Options["supportCustomizedFields"]] extends [true] + ? AssignableTreeFieldFromImplicitField + : AssignableTreeFieldFromImplicitFieldDefault; } & { - readonly [Property in keyof T]: TreeFieldFromImplicitField; + readonly [Property in keyof T]: [Options["supportCustomizedFields"]] extends [true] + ? TreeFieldFromImplicitField + : TreeFieldFromImplicitFieldDefault; + } + : { + -readonly [Property in keyof T]: [Options["supportCustomizedFields"]] extends [true] + ? TreeFieldFromImplicitField + : TreeFieldFromImplicitFieldDefault; }; /** - * Same as {@link ObjectFromSchemaRecord}, but does not remove setters for fields which can't be assigned to. + * Same as {@link ObjectFromSchemaRecord}, but hard codes `supportReadonlyFields` and `supportCustomizedFields` to `false`. * @system @beta */ export type ObjectFromSchemaRecordRelaxed< @@ -137,9 +159,27 @@ export type ObjectFromSchemaRecordRelaxed< ? // eslint-disable-next-line @typescript-eslint/ban-types {} : { - [Property in keyof T]: TreeFieldFromImplicitField; + [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. * @@ -158,6 +198,25 @@ export type AssignableTreeFieldFromImplicitField< ? 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. * @@ -173,6 +232,15 @@ export type ApplyKindAssignment = [Kind] extends [ : // 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 @@ -190,16 +258,8 @@ export type ApplyKindAssignment = [Kind] extends [ export type TreeObjectNode< T extends RestrictiveStringRecord, TypeName extends string = string, -> = TreeNode & ObjectFromSchemaRecord & WithType; - -/** - * {@link TreeObjectNode} except using {@link ObjectFromSchemaRecordRelaxed}. - * @beta - */ -export type TreeObjectNodeRelaxed< - T extends RestrictiveStringRecord, - TypeName extends string = string, -> = TreeNode & ObjectFromSchemaRecordRelaxed & WithType; + Options extends ObjectSchemaTypingOptions = Record, +> = TreeNode & WithType & ObjectFromSchemaRecord; /** * Type utility for determining if an implicit field schema is known to have a default value. @@ -486,15 +546,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, - allowUnknownOptionalFields: boolean, - metadata?: NodeSchemaMetadata, - persistedMetadata?: JsonCompatibleReadOnlyObject | undefined, -): ObjectNodeSchema & + options?: PreventExtraProperties, +): ObjectNodeSchema & ObjectNodeSchemaInternalData & TreeNodeSchemaCorePrivate { // Field set can't be modified after this since derived data is stored in maps. @@ -547,7 +605,9 @@ export function objectSchema< ]), ); public static readonly identifierFieldKeys: readonly FieldKey[] = identifierFieldKeys; - public static readonly allowUnknownOptionalFields: boolean = allowUnknownOptionalFields; + public static readonly allowUnknownOptionalFields: boolean = + options?.allowUnknownOptionalFields ?? + defaultSchemaFactoryObjectOptions.allowUnknownOptionalFields; public static override prepareInstance( this: typeof TreeNodeValid, @@ -637,9 +697,20 @@ export function objectSchema< public static get childTypes(): ReadonlySet { return lazyChildTypes.value; } - public static readonly metadata: NodeSchemaMetadata = 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 = - persistedMetadata; + options?.persistedMetadata; // eslint-disable-next-line import/no-deprecated public get [typeNameSymbol](): TName { @@ -668,7 +739,7 @@ export function objectSchema< convertField(fieldSchema.schema, storedOptions), ); } - return new ObjectNodeStoredSchema(fields, persistedMetadata); + return new ObjectNodeStoredSchema(fields, options?.persistedMetadata); }, )); } @@ -676,12 +747,22 @@ 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, + supportReadonlyFields: undefined, + supportCustomizedFields: undefined, +} as const satisfies Omit; + /** * 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/tableSchema.ts b/packages/dds/tree/src/tableSchema.ts index 04f629b5dd62..60fbc123990b 100644 --- a/packages/dds/tree/src/tableSchema.ts +++ b/packages/dds/tree/src/tableSchema.ts @@ -30,7 +30,6 @@ import { type InsertableField, withBufferedTreeEvents, type DefaultTreeNodeFromImplicitAllowedTypes, - relaxObject, } from "./simple-tree/index.js"; // Future improvement TODOs: @@ -230,12 +229,10 @@ export namespace System_TableSchema { * A column in a table. */ class Column - extends relaxObject( - schemaFactory.object("Column", columnFields, { - // Will make it easier to evolve this schema in the future. - allowUnknownOptionalFields: true, - }), - ) + extends schemaFactory.object("Column", columnFields, { + // Will make it easier to evolve this schema in the future. + allowUnknownOptionalFields: true, + }) implements TableSchema.Column { public getCells(): { @@ -412,12 +409,10 @@ export namespace System_TableSchema { * The Row schema - this is a map of Cells where the key is the column id */ class Row - extends relaxObject( - schemaFactory.object("Row", rowFields, { - // Will make it easier to evolve this schema in the future. - allowUnknownOptionalFields: true, - }), - ) + extends schemaFactory.object("Row", rowFields, { + // Will make it easier to evolve this schema in the future. + allowUnknownOptionalFields: true, + }) implements TableSchema.Row { public getCell( @@ -615,9 +610,11 @@ export namespace System_TableSchema { * The Table schema */ class Table - extends schemaFactory.objectAlpha("Table", tableFields, { + 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 { diff --git a/packages/dds/tree/src/test/openPolymorphism.spec.ts b/packages/dds/tree/src/test/openPolymorphism.spec.ts index f0ab48d87938..0d58f8592c00 100644 --- a/packages/dds/tree/src/test/openPolymorphism.spec.ts +++ b/packages/dds/tree/src/test/openPolymorphism.spec.ts @@ -6,6 +6,7 @@ import { strict as assert } from "node:assert"; import { + allowUnused, Component, SchemaFactory, TreeViewConfiguration, @@ -18,6 +19,7 @@ import { import { Tree } from "../shared-tree/index.js"; import { validateUsageError } from "./utils.js"; import { customizeSchemaTyping, evaluateLazySchema } from "../simple-tree/index.js"; +import type { requireAssignableTo } from "../util/index.js"; const sf = new SchemaFactory("test"); @@ -119,9 +121,12 @@ describe("Open Polymorphism design pattern examples and tests for them", () => { // If we don't do anything special, the insertable type is never, so a cast is required to insert content. // See example using customizeSchemaTyping for how to avoid this. - container.insertAtStart(new TextItem({ text: "", location: { x: 0, y: 0 } }) as never); + container.insertAtStart(new TextItem({ text: "", location: { x: 0, y: 0 } })); // TODO: Why is `as never` not required? - // Items read from the container are typed as Item and have thew expected APIs: + type Input = Parameters[0]; + allowUnused>(); + + // Items read from the container are typed as Item and have the expected APIs: const first = container[0]; first.foo(); first.location.x += 1; 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 f1e2b6e02e76..16ddae323229 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,13 +10,13 @@ import { MockFluidDataStoreRuntime } from "@fluidframework/test-runtime-utils/in import { type ITree, - SchemaFactory, treeNodeApi as Tree, TreeViewConfiguration, type TreeView, customizeSchemaTyping, type GetTypes, type Customizer, + SchemaFactoryAlpha, } from "../../../simple-tree/index.js"; import { DefaultTestSharedTreeKind, getView } from "../../utils.js"; import { @@ -27,7 +27,7 @@ import { } 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. @@ -133,9 +133,13 @@ describe("Class based end to end example", () => { }); it("customized narrowing", () => { - class Specific extends schema.object("Specific", { - s: customizeSchemaTyping(schema.string).simplified<"foo" | "bar">(), - }) {} + 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; @@ -150,9 +154,13 @@ describe("Class based end to end example", () => { // 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, - }) {} + 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; @@ -178,9 +186,13 @@ describe("Class based end to end example", () => { it("customized branding", () => { type SpecialString = Brand; - class Specific extends schema.object("Specific", { - s: customizeSchemaTyping(schema.string).simplified(), - }) {} + 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; @@ -192,17 +204,29 @@ describe("Class based end to end example", () => { const runtimeDeterminedSchema = schema.string as | typeof schema.string | typeof schema.number; - class Strict extends schema.object("Strict", { - s: runtimeDeterminedSchema, - }) {} - - class Relaxed extends schema.object("Relaxed", { - s: customizeSchemaTyping(runtimeDeterminedSchema).relaxed(), - }) {} - - class RelaxedArray extends schema.object("Relaxed", { - s: customizeSchemaTyping([runtimeDeterminedSchema]).relaxed(), - }) {} + 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); { 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 6ffeb8062aeb..016066d51e3c 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 @@ -517,6 +517,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 c44a1c56e4d9..e4328df4ad2a 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 @@ -1490,20 +1490,24 @@ describe("SchemaFactory Recursive methods", () => { }); 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; + 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; } diff --git a/packages/dds/tree/src/test/tableSchema.spec.ts b/packages/dds/tree/src/test/tableSchema.spec.ts index 3f077e296361..dcb3dbdbcccd 100644 --- a/packages/dds/tree/src/test/tableSchema.spec.ts +++ b/packages/dds/tree/src/test/tableSchema.spec.ts @@ -295,6 +295,19 @@ describe("TableFactory unit tests", () => { rows: [{ id: "row-0", props: { label: "Row 0" }, cells: {} }], }); }); + + it("Readonly IDs", () => { + const column = new Column({ props: {} }); + assert.throws(() => { + // @ts-expect-error id is readonly + column.id = "column-1"; + }); + const row = new Row({ cells: {} }); + assert.throws(() => { + // @ts-expect-error id is readonly + row.id = "row-1"; + }); + }); }); describeHydration("Initialization", (initializeTree) => { diff --git a/packages/dds/tree/src/util/index.ts b/packages/dds/tree/src/util/index.ts index fc6b2995fc47..0816874293fd 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; +}; From 77b0e2fd124e7479d1daa89fee4c92e7660eee40 Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Tue, 21 Oct 2025 13:37:41 -0700 Subject: [PATCH 34/37] Fix build --- .../dds/tree/api-report/tree.alpha.api.md | 47 ++++++--- packages/dds/tree/api-report/tree.beta.api.md | 39 +++++--- .../tree/api-report/tree.legacy.beta.api.md | 39 +++++--- .../tree/api-report/tree.legacy.public.api.md | 24 ++++- .../dds/tree/api-report/tree.public.api.md | 24 ++++- .../src/simple-tree/api/schemaFactoryAlpha.ts | 18 ++-- .../api/schemaFactoryRecursive.spec.ts | 19 +++- .../simple-tree/core/treeNodeSchema.spec.ts | 2 +- .../node-kinds/object/objectNode.spec.ts | 98 +++++++++++++++---- .../api-report/fluid-framework.alpha.api.md | 47 ++++++--- .../api-report/fluid-framework.beta.api.md | 39 +++++--- .../fluid-framework.legacy.beta.api.md | 39 +++++--- .../fluid-framework.legacy.public.api.md | 24 ++++- .../api-report/fluid-framework.public.api.md | 24 ++++- 14 files changed, 362 insertions(+), 121 deletions(-) diff --git a/packages/dds/tree/api-report/tree.alpha.api.md b/packages/dds/tree/api-report/tree.alpha.api.md index 7d40caf88b09..13042227f281 100644 --- a/packages/dds/tree/api-report/tree.alpha.api.md +++ b/packages/dds/tree/api-report/tree.alpha.api.md @@ -125,6 +125,9 @@ export function asAlpha(view: TreeView> = [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; @@ -778,31 +781,33 @@ export type NumberKeys = Transformed[`${number}` & keyof Transformed]; // @public @system -export type ObjectFromSchemaRecord> = RestrictiveStringRecord extends T ? {} : { +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]: AssignableTreeFieldFromImplicitField; + ] extends [never | undefined] ? never : Property]: [Options["supportCustomizedFields"]] extends [true] ? AssignableTreeFieldFromImplicitField : AssignableTreeFieldFromImplicitFieldDefault; } & { - readonly [Property in keyof T]: TreeFieldFromImplicitField; + 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]: TreeFieldFromImplicitField; + [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; } @@ -810,6 +815,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; @@ -825,6 +838,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>; @@ -973,10 +991,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; @@ -1002,10 +1020,9 @@ export class SchemaFactoryAlpha extends SchemaFactory { - object, const TCustomMetadata = unknown>(name: Name, fields: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T>; + object, const TOptions extends ObjectSchemaOptions = ObjectSchemaOptions>(name: Name, fields: T, options?: PreventExtraProperties): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode, TOptions>, object & InsertableObjectFromSchemaRecord, true, T>; // (undocumented) objectRecursive, const TCustomMetadata = unknown>(name: Name, t: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, System_Unsafe.TreeObjectNodeUnsafe>, object & System_Unsafe.InsertableObjectFromSchemaRecordUnsafe, false, T>; - objectRelaxed, const TCustomMetadata = unknown>(name: Name, fields: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNodeRelaxed>, object & InsertableObjectFromSchemaRecord, true, T>; record(allowedTypes: T): TreeNodeSchemaNonClass`>, NodeKind.Record, TreeRecordNode & WithType`>, NodeKind.Record>, RecordNodeInsertableData, true, T, undefined>; record(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Record, TreeRecordNode & WithType, NodeKind.Record>, RecordNodeInsertableData, true, T, undefined>; recordRecursive(name: Name, allowedTypes: T, options?: NodeSchemaOptions): TreeNodeSchemaClass, NodeKind.Record, TreeRecordNodeUnsafe & WithType, NodeKind.Record, unknown>, { @@ -1595,6 +1612,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; @@ -1681,10 +1701,7 @@ export type TreeNodeSchemaNonClass, TypeName extends string = string> = TreeNode & ObjectFromSchemaRecord & WithType; - -// @beta -export type TreeObjectNodeRelaxed, TypeName extends string = string> = TreeNode & ObjectFromSchemaRecordRelaxed & 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 edce52e3c678..849fba27af58 100644 --- a/packages/dds/tree/api-report/tree.beta.api.md +++ b/packages/dds/tree/api-report/tree.beta.api.md @@ -38,6 +38,9 @@ Kind // @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, @@ -333,30 +336,45 @@ export type NumberKeys = Transformed[`${number}` & keyof Transformed]; // @public @system -export type ObjectFromSchemaRecord> = RestrictiveStringRecord extends T ? {} : { +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]: AssignableTreeFieldFromImplicitField; + ] extends [never | undefined] ? never : Property]: [Options["supportCustomizedFields"]] extends [true] ? AssignableTreeFieldFromImplicitField : AssignableTreeFieldFromImplicitFieldDefault; } & { - readonly [Property in keyof T]: TreeFieldFromImplicitField; + 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]: TreeFieldFromImplicitField; + [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> { } @@ -452,10 +470,9 @@ 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>; + object, const TOptions extends ObjectSchemaOptions = ObjectSchemaOptions>(name: Name, fields: T, options?: PreventExtraProperties): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode, TOptions>, object & InsertableObjectFromSchemaRecord, true, T>; // (undocumented) objectRecursive, const TCustomMetadata = unknown>(name: Name, t: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, System_Unsafe.TreeObjectNodeUnsafe>, object & System_Unsafe.InsertableObjectFromSchemaRecordUnsafe, false, T>; - objectRelaxed, const TCustomMetadata = unknown>(name: Name, fields: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNodeRelaxed>, object & InsertableObjectFromSchemaRecord, true, T>; record(allowedTypes: T): TreeNodeSchemaNonClass`>, NodeKind.Record, TreeRecordNode & WithType`>, NodeKind.Record>, RecordNodeInsertableData, true, T, undefined>; record(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Record, TreeRecordNode & WithType, NodeKind.Record>, RecordNodeInsertableData, true, T, undefined>; recordRecursive(name: Name, allowedTypes: T, options?: NodeSchemaOptions): TreeNodeSchemaClass, NodeKind.Record, TreeRecordNodeUnsafe & WithType, NodeKind.Record, unknown>, { @@ -719,6 +736,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; @@ -785,10 +805,7 @@ export type TreeNodeSchemaNonClass, TypeName extends string = string> = TreeNode & ObjectFromSchemaRecord & WithType; - -// @beta -export type TreeObjectNodeRelaxed, TypeName extends string = string> = TreeNode & ObjectFromSchemaRecordRelaxed & 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 ce6e28ccee8d..b36691132a1b 100644 --- a/packages/dds/tree/api-report/tree.legacy.beta.api.md +++ b/packages/dds/tree/api-report/tree.legacy.beta.api.md @@ -38,6 +38,9 @@ Kind // @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, @@ -336,30 +339,45 @@ export type NumberKeys = Transformed[`${number}` & keyof Transformed]; // @public @system -export type ObjectFromSchemaRecord> = RestrictiveStringRecord extends T ? {} : { +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]: AssignableTreeFieldFromImplicitField; + ] extends [never | undefined] ? never : Property]: [Options["supportCustomizedFields"]] extends [true] ? AssignableTreeFieldFromImplicitField : AssignableTreeFieldFromImplicitFieldDefault; } & { - readonly [Property in keyof T]: TreeFieldFromImplicitField; + 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]: TreeFieldFromImplicitField; + [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> { } @@ -455,10 +473,9 @@ 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>; + object, const TOptions extends ObjectSchemaOptions = ObjectSchemaOptions>(name: Name, fields: T, options?: PreventExtraProperties): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode, TOptions>, object & InsertableObjectFromSchemaRecord, true, T>; // (undocumented) objectRecursive, const TCustomMetadata = unknown>(name: Name, t: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, System_Unsafe.TreeObjectNodeUnsafe>, object & System_Unsafe.InsertableObjectFromSchemaRecordUnsafe, false, T>; - objectRelaxed, const TCustomMetadata = unknown>(name: Name, fields: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNodeRelaxed>, object & InsertableObjectFromSchemaRecord, true, T>; record(allowedTypes: T): TreeNodeSchemaNonClass`>, NodeKind.Record, TreeRecordNode & WithType`>, NodeKind.Record>, RecordNodeInsertableData, true, T, undefined>; record(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Record, TreeRecordNode & WithType, NodeKind.Record>, RecordNodeInsertableData, true, T, undefined>; recordRecursive(name: Name, allowedTypes: T, options?: NodeSchemaOptions): TreeNodeSchemaClass, NodeKind.Record, TreeRecordNodeUnsafe & WithType, NodeKind.Record, unknown>, { @@ -731,6 +748,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; @@ -797,10 +817,7 @@ export type TreeNodeSchemaNonClass, TypeName extends string = string> = TreeNode & ObjectFromSchemaRecord & WithType; - -// @beta -export type TreeObjectNodeRelaxed, TypeName extends string = string> = TreeNode & ObjectFromSchemaRecordRelaxed & 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 c782ceaf6b5d..2af9ba6007c0 100644 --- a/packages/dds/tree/api-report/tree.legacy.public.api.md +++ b/packages/dds/tree/api-report/tree.legacy.public.api.md @@ -27,6 +27,9 @@ Kind // @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, @@ -273,14 +276,24 @@ export type NumberKeys = Transformed[`${number}` & keyof Transformed]; // @public @system -export type ObjectFromSchemaRecord> = RestrictiveStringRecord extends T ? {} : { +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]: AssignableTreeFieldFromImplicitField; + ] extends [never | undefined] ? never : Property]: [Options["supportCustomizedFields"]] extends [true] ? AssignableTreeFieldFromImplicitField : AssignableTreeFieldFromImplicitFieldDefault; } & { - readonly [Property in keyof T]: TreeFieldFromImplicitField; + 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; @@ -598,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; @@ -664,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 c782ceaf6b5d..2af9ba6007c0 100644 --- a/packages/dds/tree/api-report/tree.public.api.md +++ b/packages/dds/tree/api-report/tree.public.api.md @@ -27,6 +27,9 @@ Kind // @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, @@ -273,14 +276,24 @@ export type NumberKeys = Transformed[`${number}` & keyof Transformed]; // @public @system -export type ObjectFromSchemaRecord> = RestrictiveStringRecord extends T ? {} : { +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]: AssignableTreeFieldFromImplicitField; + ] extends [never | undefined] ? never : Property]: [Options["supportCustomizedFields"]] extends [true] ? AssignableTreeFieldFromImplicitField : AssignableTreeFieldFromImplicitFieldDefault; } & { - readonly [Property in keyof T]: TreeFieldFromImplicitField; + 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; @@ -598,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; @@ -664,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/src/simple-tree/api/schemaFactoryAlpha.ts b/packages/dds/tree/src/simple-tree/api/schemaFactoryAlpha.ts index 7ba571f784e3..d0092ec72094 100644 --- a/packages/dds/tree/src/simple-tree/api/schemaFactoryAlpha.ts +++ b/packages/dds/tree/src/simple-tree/api/schemaFactoryAlpha.ts @@ -228,9 +228,7 @@ export class SchemaFactoryAlpha< public override objectRecursive< const Name extends TName, const T extends RestrictiveStringRecord, - const TCustomMetadata = unknown, - const TOptions extends - ObjectSchemaOptionsAlpha = ObjectSchemaOptionsAlpha, + const TOptions extends ObjectSchemaOptionsAlpha = ObjectSchemaOptionsAlpha, >( name: Name, t: T, @@ -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,7 +274,9 @@ export class SchemaFactoryAlpha< false, T, never, - TCustomMetadata + TOptions extends ObjectSchemaOptionsAlpha + ? TCustomMetadataX + : unknown > & ObjectNodeSchema< ScopedSchemaName, 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 e4328df4ad2a..3b529a4be59f 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 @@ -515,14 +515,14 @@ describe("SchemaFactory Recursive methods", () => { }, ) {} - assert.deepEqual(Foo.metadata, { + // Ensure `Foo.metadata` is typed as we expect, and we can access its fields without casting. + const baz = Foo.metadata.custom?.baz; + type _check1 = requireTrue>; // TODO:fix: should be just true + + assert.deepEqual(Foo.metadata as unknown, { description: "A recursive object called Foo", custom: { baz: true }, }); - - // Ensure `Foo.metadata` is typed as we expect, and we can access its fields without casting. - const baz = Foo.metadata.custom.baz; - type _check1 = requireTrue>; }); }); describe("ValidateRecursiveSchema", () => { @@ -1529,6 +1529,15 @@ describe("SchemaFactory Recursive methods", () => { 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; } diff --git a/packages/dds/tree/src/test/simple-tree/core/treeNodeSchema.spec.ts b/packages/dds/tree/src/test/simple-tree/core/treeNodeSchema.spec.ts index fdfa01768828..3b2fcece91eb 100644 --- a/packages/dds/tree/src/test/simple-tree/core/treeNodeSchema.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/core/treeNodeSchema.spec.ts @@ -87,7 +87,7 @@ const schema = new SchemaFactory("com.example"); } // Class that implements both TreeNodeSchemaNonClass and TreeNodeSchemaNonClass - class CustomizedBoth extends objectSchema("B", { x: [schema.number] }, true, false) { + class CustomizedBoth extends objectSchema("B", { x: [schema.number] }, true) { public customized = true; } 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 9a91f6ebb241..d95c59abf314 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 @@ -31,6 +31,7 @@ import { unhydratedFlexTreeFromInsertable, type TreeFieldFromImplicitField, type System_Unsafe, + SchemaFactoryBeta, } from "../../../../simple-tree/index.js"; import type { FieldHasDefault, @@ -65,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 { @@ -370,9 +371,13 @@ describeHydration( }); it("assigning identifier errors", () => { - class HasId extends schemaFactory.object("hasID", { - id: schemaFactory.identifier, - }) {} + class HasId extends schemaFactory.object( + "hasID", + { + id: schemaFactory.identifier, + }, + { supportReadonlyFields: true }, + ) {} const n = init(HasId, {}); assert.throws(() => { // @ts-expect-error this should not compile @@ -382,9 +387,13 @@ describeHydration( it("assigning non-exact schema errors - ImplicitFieldSchema", () => { const child: ImplicitFieldSchema = schemaFactory.number; - class NonExact extends schemaFactory.object("NonExact", { - child, - }) {} + 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); @@ -396,9 +405,13 @@ describeHydration( it("assigning non-exact optional schema", () => { const child: ImplicitFieldSchema = schemaFactory.number; - class NonExact extends schemaFactory.object("NonExact", { - child: schemaFactory.optional(child), - }) {} + 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); @@ -427,9 +440,13 @@ describeHydration( const child = schemaFactory.number as | typeof schemaFactory.number | typeof schemaFactory.null; - class NonExact extends schemaFactory.object("NonExact", { - child, - }) {} + 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); @@ -603,9 +620,13 @@ 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! @@ -821,9 +842,48 @@ 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 match the runtime behavior. // @ts-expect-error bad shadow 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 df882f33cb85..517e96a9e179 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 @@ -125,6 +125,9 @@ export function asAlpha(view: TreeView> = [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; @@ -1140,31 +1143,33 @@ export type NumberKeys = Transformed[`${number}` & keyof Transformed]; // @public @system -export type ObjectFromSchemaRecord> = RestrictiveStringRecord extends T ? {} : { +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]: AssignableTreeFieldFromImplicitField; + ] extends [never | undefined] ? never : Property]: [Options["supportCustomizedFields"]] extends [true] ? AssignableTreeFieldFromImplicitField : AssignableTreeFieldFromImplicitFieldDefault; } & { - readonly [Property in keyof T]: TreeFieldFromImplicitField; + 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]: TreeFieldFromImplicitField; + [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; } @@ -1172,6 +1177,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; @@ -1190,6 +1203,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>; @@ -1343,10 +1361,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; @@ -1372,10 +1390,9 @@ export class SchemaFactoryAlpha extends SchemaFactory { - object, const TCustomMetadata = unknown>(name: Name, fields: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T>; + object, const TOptions extends ObjectSchemaOptions = ObjectSchemaOptions>(name: Name, fields: T, options?: PreventExtraProperties): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode, TOptions>, object & InsertableObjectFromSchemaRecord, true, T>; // (undocumented) objectRecursive, const TCustomMetadata = unknown>(name: Name, t: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, System_Unsafe.TreeObjectNodeUnsafe>, object & System_Unsafe.InsertableObjectFromSchemaRecordUnsafe, false, T>; - objectRelaxed, const TCustomMetadata = unknown>(name: Name, fields: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNodeRelaxed>, object & InsertableObjectFromSchemaRecord, true, T>; record(allowedTypes: T): TreeNodeSchemaNonClass`>, NodeKind.Record, TreeRecordNode & WithType`>, NodeKind.Record>, RecordNodeInsertableData, true, T, undefined>; record(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Record, TreeRecordNode & WithType, NodeKind.Record>, RecordNodeInsertableData, true, T, undefined>; recordRecursive(name: Name, allowedTypes: T, options?: NodeSchemaOptions): TreeNodeSchemaClass, NodeKind.Record, TreeRecordNodeUnsafe & WithType, NodeKind.Record, unknown>, { @@ -1987,6 +2004,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; @@ -2073,10 +2093,7 @@ export type TreeNodeSchemaNonClass, TypeName extends string = string> = TreeNode & ObjectFromSchemaRecord & WithType; - -// @beta -export type TreeObjectNodeRelaxed, TypeName extends string = string> = TreeNode & ObjectFromSchemaRecordRelaxed & 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 90116ecce18d..2390b9caa55f 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 @@ -38,6 +38,9 @@ Kind // @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", @@ -686,30 +689,45 @@ export type NumberKeys = Transformed[`${number}` & keyof Transformed]; // @public @system -export type ObjectFromSchemaRecord> = RestrictiveStringRecord extends T ? {} : { +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]: AssignableTreeFieldFromImplicitField; + ] extends [never | undefined] ? never : Property]: [Options["supportCustomizedFields"]] extends [true] ? AssignableTreeFieldFromImplicitField : AssignableTreeFieldFromImplicitFieldDefault; } & { - readonly [Property in keyof T]: TreeFieldFromImplicitField; + 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]: TreeFieldFromImplicitField; + [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> { } @@ -810,10 +828,9 @@ 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>; + object, const TOptions extends ObjectSchemaOptions = ObjectSchemaOptions>(name: Name, fields: T, options?: PreventExtraProperties): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode, TOptions>, object & InsertableObjectFromSchemaRecord, true, T>; // (undocumented) objectRecursive, const TCustomMetadata = unknown>(name: Name, t: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, System_Unsafe.TreeObjectNodeUnsafe>, object & System_Unsafe.InsertableObjectFromSchemaRecordUnsafe, false, T>; - objectRelaxed, const TCustomMetadata = unknown>(name: Name, fields: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNodeRelaxed>, object & InsertableObjectFromSchemaRecord, true, T>; record(allowedTypes: T): TreeNodeSchemaNonClass`>, NodeKind.Record, TreeRecordNode & WithType`>, NodeKind.Record>, RecordNodeInsertableData, true, T, undefined>; record(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Record, TreeRecordNode & WithType, NodeKind.Record>, RecordNodeInsertableData, true, T, undefined>; recordRecursive(name: Name, allowedTypes: T, options?: NodeSchemaOptions): TreeNodeSchemaClass, NodeKind.Record, TreeRecordNodeUnsafe & WithType, NodeKind.Record, unknown>, { @@ -1099,6 +1116,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; @@ -1165,10 +1185,7 @@ export type TreeNodeSchemaNonClass, TypeName extends string = string> = TreeNode & ObjectFromSchemaRecord & WithType; - -// @beta -export type TreeObjectNodeRelaxed, TypeName extends string = string> = TreeNode & ObjectFromSchemaRecordRelaxed & 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 bd80106eb6ba..d646441d8b20 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 @@ -38,6 +38,9 @@ Kind // @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", @@ -964,30 +967,45 @@ export type NumberKeys = Transformed[`${number}` & keyof Transformed]; // @public @system -export type ObjectFromSchemaRecord> = RestrictiveStringRecord extends T ? {} : { +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]: AssignableTreeFieldFromImplicitField; + ] extends [never | undefined] ? never : Property]: [Options["supportCustomizedFields"]] extends [true] ? AssignableTreeFieldFromImplicitField : AssignableTreeFieldFromImplicitFieldDefault; } & { - readonly [Property in keyof T]: TreeFieldFromImplicitField; + 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]: TreeFieldFromImplicitField; + [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> { } @@ -1088,10 +1106,9 @@ 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>; + object, const TOptions extends ObjectSchemaOptions = ObjectSchemaOptions>(name: Name, fields: T, options?: PreventExtraProperties): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode, TOptions>, object & InsertableObjectFromSchemaRecord, true, T>; // (undocumented) objectRecursive, const TCustomMetadata = unknown>(name: Name, t: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, System_Unsafe.TreeObjectNodeUnsafe>, object & System_Unsafe.InsertableObjectFromSchemaRecordUnsafe, false, T>; - objectRelaxed, const TCustomMetadata = unknown>(name: Name, fields: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNodeRelaxed>, object & InsertableObjectFromSchemaRecord, true, T>; record(allowedTypes: T): TreeNodeSchemaNonClass`>, NodeKind.Record, TreeRecordNode & WithType`>, NodeKind.Record>, RecordNodeInsertableData, true, T, undefined>; record(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Record, TreeRecordNode & WithType, NodeKind.Record>, RecordNodeInsertableData, true, T, undefined>; recordRecursive(name: Name, allowedTypes: T, options?: NodeSchemaOptions): TreeNodeSchemaClass, NodeKind.Record, TreeRecordNodeUnsafe & WithType, NodeKind.Record, unknown>, { @@ -1457,6 +1474,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; @@ -1523,10 +1543,7 @@ export type TreeNodeSchemaNonClass, TypeName extends string = string> = TreeNode & ObjectFromSchemaRecord & WithType; - -// @beta -export type TreeObjectNodeRelaxed, TypeName extends string = string> = TreeNode & ObjectFromSchemaRecordRelaxed & 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 e2611963ad01..a37ef2b5a3fd 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 @@ -27,6 +27,9 @@ Kind // @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", @@ -660,14 +663,24 @@ export type NumberKeys = Transformed[`${number}` & keyof Transformed]; // @public @system -export type ObjectFromSchemaRecord> = RestrictiveStringRecord extends T ? {} : { +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]: AssignableTreeFieldFromImplicitField; + ] extends [never | undefined] ? never : Property]: [Options["supportCustomizedFields"]] extends [true] ? AssignableTreeFieldFromImplicitField : AssignableTreeFieldFromImplicitFieldDefault; } & { - readonly [Property in keyof T]: TreeFieldFromImplicitField; + 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; @@ -1012,6 +1025,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; @@ -1078,7 +1094,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 f5590bba9437..5da6821d6c9f 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 @@ -27,6 +27,9 @@ Kind // @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", @@ -626,14 +629,24 @@ export type NumberKeys = Transformed[`${number}` & keyof Transformed]; // @public @system -export type ObjectFromSchemaRecord> = RestrictiveStringRecord extends T ? {} : { +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]: AssignableTreeFieldFromImplicitField; + ] extends [never | undefined] ? never : Property]: [Options["supportCustomizedFields"]] extends [true] ? AssignableTreeFieldFromImplicitField : AssignableTreeFieldFromImplicitFieldDefault; } & { - readonly [Property in keyof T]: TreeFieldFromImplicitField; + 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; @@ -978,6 +991,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; @@ -1044,7 +1060,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 { From 951d9ce3646082839a1dfd4dde1697ee51d5ad1d Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Mon, 3 Nov 2025 18:40:14 -0800 Subject: [PATCH 35/37] Fix from merge --- .../dds/tree/api-report/tree.alpha.api.md | 30 ++++++++----------- packages/dds/tree/api-report/tree.beta.api.md | 22 +++++--------- .../tree/api-report/tree.legacy.beta.api.md | 22 +++++--------- .../src/simple-tree/api/schemaFactoryBeta.ts | 9 +++--- .../dds/tree/src/simple-tree/schemaTypes.ts | 2 +- .../api-report/fluid-framework.alpha.api.md | 30 ++++++++----------- .../api-report/fluid-framework.beta.api.md | 22 +++++--------- .../fluid-framework.legacy.beta.api.md | 22 +++++--------- 8 files changed, 58 insertions(+), 101 deletions(-) diff --git a/packages/dds/tree/api-report/tree.alpha.api.md b/packages/dds/tree/api-report/tree.alpha.api.md index dd49ffae3369..3b0ce53373d6 100644 --- a/packages/dds/tree/api-report/tree.alpha.api.md +++ b/packages/dds/tree/api-report/tree.alpha.api.md @@ -122,16 +122,14 @@ export const ArrayNodeSchema: { // @alpha export function asAlpha(view: TreeView): TreeViewAlpha; -<<<<<<< HEAD +// @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; -======= -// @beta -export function asBeta(view: TreeView): TreeViewBeta; ->>>>>>> 86a249c645a27fc11428eda68371cf6709a9419e // @alpha @deprecated export function asTreeViewAlpha(view: TreeView): TreeViewAlpha; @@ -1046,11 +1044,7 @@ export class SchemaFactoryAlpha extends SchemaFactory { -<<<<<<< HEAD - object, const TOptions extends ObjectSchemaOptions = ObjectSchemaOptions>(name: Name, fields: T, options?: PreventExtraProperties): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode, TOptions>, object & InsertableObjectFromSchemaRecord, true, T>; -======= - object, const TCustomMetadata = unknown>(name: Name, fields: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T, never, TCustomMetadata>; ->>>>>>> 86a249c645a27fc11428eda68371cf6709a9419e + 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>; @@ -1243,18 +1237,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 : 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>; + readonly rows: TreeNodeSchemaClass, "Table.rows">, NodeKind.Array, TreeArrayNode : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : 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>)>; + readonly rows: (InsertableTypedNode_2, "Table.rows">, NodeKind.Array, true, TRowSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : 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>)>; + readonly rows: (InsertableTypedNode_2, "Table.rows">, NodeKind.Array, true, TRowSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : 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>)>; + readonly rows: (InsertableTypedNode_2, "Table.rows">, NodeKind.Array, true, TRowSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [TColumnSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.columns">, NodeKind.Array, unknown>)>; }) => TreeNode & TableSchema.Table & WithType, "Table">, NodeKind, unknown>>(this: TThis): InstanceType; }; // @system diff --git a/packages/dds/tree/api-report/tree.beta.api.md b/packages/dds/tree/api-report/tree.beta.api.md index 1d7c482c8ca2..a29363d4656d 100644 --- a/packages/dds/tree/api-report/tree.beta.api.md +++ b/packages/dds/tree/api-report/tree.beta.api.md @@ -75,16 +75,14 @@ type ApplyKindInput(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; -======= -// @beta -export function asBeta(view: TreeView): TreeViewBeta; ->>>>>>> 86a249c645a27fc11428eda68371cf6709a9419e // @public export enum CommitKind { @@ -107,7 +105,9 @@ export type ConciseTree = Exclude; -<<<<<<< HEAD +// @beta +export function createIndependentTreeBeta(options?: ForestOptions): ViewableTree; + // @public @system export type CustomizedSchemaTyping = TSchema & { [CustomizedTyping]: TCustom; @@ -128,10 +128,6 @@ export interface CustomTypes { // @public @system export type DefaultInsertableTreeNodeFromImplicitAllowedTypes = [TSchema] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; -======= -// @beta -export function createIndependentTreeBeta(options?: ForestOptions): ViewableTree; ->>>>>>> 86a249c645a27fc11428eda68371cf6709a9419e // @public @sealed @system interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> { @@ -555,11 +551,7 @@ export const SchemaFactory_base: SchemaStatics & (new () => SchemaStatics); // @beta export class SchemaFactoryBeta extends SchemaFactory { -<<<<<<< HEAD - object, const TOptions extends ObjectSchemaOptions = ObjectSchemaOptions>(name: Name, fields: T, options?: PreventExtraProperties): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode, TOptions>, object & InsertableObjectFromSchemaRecord, true, T>; -======= - object, const TCustomMetadata = unknown>(name: Name, fields: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T, never, TCustomMetadata>; ->>>>>>> 86a249c645a27fc11428eda68371cf6709a9419e + 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>; 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 28210b6bdb8d..ffecf1b6e024 100644 --- a/packages/dds/tree/api-report/tree.legacy.beta.api.md +++ b/packages/dds/tree/api-report/tree.legacy.beta.api.md @@ -75,16 +75,14 @@ type ApplyKindInput(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; -======= -// @beta -export function asBeta(view: TreeView): TreeViewBeta; ->>>>>>> 86a249c645a27fc11428eda68371cf6709a9419e // @public export enum CommitKind { @@ -110,7 +108,9 @@ export function configuredSharedTreeBeta(options: SharedTreeOptionsBeta): Shared // @beta @legacy export function configuredSharedTreeBetaLegacy(options: SharedTreeOptionsBeta): ISharedObjectKind & SharedObjectKind; -<<<<<<< HEAD +// @beta +export function createIndependentTreeBeta(options?: ForestOptions): ViewableTree; + // @public @system export type CustomizedSchemaTyping = TSchema & { [CustomizedTyping]: TCustom; @@ -131,10 +131,6 @@ export interface CustomTypes { // @public @system export type DefaultInsertableTreeNodeFromImplicitAllowedTypes = [TSchema] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; -======= -// @beta -export function createIndependentTreeBeta(options?: ForestOptions): ViewableTree; ->>>>>>> 86a249c645a27fc11428eda68371cf6709a9419e // @public @sealed @system interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> { @@ -558,11 +554,7 @@ export const SchemaFactory_base: SchemaStatics & (new () => SchemaStatics); // @beta export class SchemaFactoryBeta extends SchemaFactory { -<<<<<<< HEAD - object, const TOptions extends ObjectSchemaOptions = ObjectSchemaOptions>(name: Name, fields: T, options?: PreventExtraProperties): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode, TOptions>, object & InsertableObjectFromSchemaRecord, true, T>; -======= - object, const TCustomMetadata = unknown>(name: Name, fields: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T, never, TCustomMetadata>; ->>>>>>> 86a249c645a27fc11428eda68371cf6709a9419e + 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>; diff --git a/packages/dds/tree/src/simple-tree/api/schemaFactoryBeta.ts b/packages/dds/tree/src/simple-tree/api/schemaFactoryBeta.ts index b595fe59d693..31ab0b56a426 100644 --- a/packages/dds/tree/src/simple-tree/api/schemaFactoryBeta.ts +++ b/packages/dds/tree/src/simple-tree/api/schemaFactoryBeta.ts @@ -43,9 +43,8 @@ import type { } from "../fieldSchema.js"; import type { LeafSchema } from "../leafNodeSchema.js"; import type { SimpleLeafNodeSchema } from "../simpleSchema.js"; -import type { PreventExtraProperties, RestrictiveStringRecord } from "../../util/index.js"; -import type { Insertable } from "../unsafeUnknownSchema.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. @@ -91,7 +90,7 @@ export class SchemaFactoryBeta< true, T, never, - TCustomMetadata + TOptions extends ObjectSchemaOptions ? TCustomMetadata : unknown > { // TODO: make type safe return objectSchema( @@ -105,7 +104,9 @@ export class SchemaFactoryBeta< TreeObjectNode, TOptions>, object & InsertableObjectFromSchemaRecord, true, - T + T, + never, + TOptions extends ObjectSchemaOptions ? TCustomMetadata : unknown >; } diff --git a/packages/dds/tree/src/simple-tree/schemaTypes.ts b/packages/dds/tree/src/simple-tree/schemaTypes.ts index e7545e71eb54..dc066242b667 100644 --- a/packages/dds/tree/src/simple-tree/schemaTypes.ts +++ b/packages/dds/tree/src/simple-tree/schemaTypes.ts @@ -124,7 +124,7 @@ export interface StrictTypes< * 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 [compleat and less sound](https://en.wikipedia.org/wiki/Soundness#Relation_to_completeness) typing. + * 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`. 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 24f44ddb99c2..7f5b52e870d3 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 @@ -122,16 +122,14 @@ export const ArrayNodeSchema: { // @alpha export function asAlpha(view: TreeView): TreeViewAlpha; -<<<<<<< HEAD +// @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; -======= -// @beta -export function asBeta(view: TreeView): TreeViewBeta; ->>>>>>> 86a249c645a27fc11428eda68371cf6709a9419e // @alpha @deprecated export function asTreeViewAlpha(view: TreeView): TreeViewAlpha; @@ -1417,11 +1415,7 @@ export class SchemaFactoryAlpha extends SchemaFactory { -<<<<<<< HEAD - object, const TOptions extends ObjectSchemaOptions = ObjectSchemaOptions>(name: Name, fields: T, options?: PreventExtraProperties): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode, TOptions>, object & InsertableObjectFromSchemaRecord, true, T>; -======= - object, const TCustomMetadata = unknown>(name: Name, fields: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T, never, TCustomMetadata>; ->>>>>>> 86a249c645a27fc11428eda68371cf6709a9419e + 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>; @@ -1622,18 +1616,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 : 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>; + readonly rows: TreeNodeSchemaClass, "Table.rows">, NodeKind.Array, TreeArrayNode : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : 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>)>; + readonly rows: (InsertableTypedNode_2, "Table.rows">, NodeKind.Array, true, TRowSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : 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>)>; + readonly rows: (InsertableTypedNode_2, "Table.rows">, NodeKind.Array, true, TRowSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : 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>)>; + readonly rows: (InsertableTypedNode_2, "Table.rows">, NodeKind.Array, true, TRowSchema, Iterable>, unknown> & (new (data?: InternalTreeNode | Iterable> | undefined) => TreeArrayNode : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [TColumnSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : never, ReadonlyArrayNode_2> & WithType, "Table.columns">, NodeKind.Array, unknown>)>; }) => TreeNode & TableSchema.Table & WithType, "Table">, NodeKind, unknown>>(this: TThis): InstanceType; }; // @system 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 5a3e0b219a9d..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 @@ -75,16 +75,14 @@ type ApplyKindInput(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; -======= -// @beta -export function asBeta(view: TreeView): TreeViewBeta; ->>>>>>> 86a249c645a27fc11428eda68371cf6709a9419e // @public export enum AttachState { @@ -146,7 +144,9 @@ export interface ContainerSchema { readonly initialObjects: Record; } -<<<<<<< HEAD +// @beta +export function createIndependentTreeBeta(options?: ForestOptions): ViewableTree; + // @public @system export type CustomizedSchemaTyping = TSchema & { [CustomizedTyping]: TCustom; @@ -167,10 +167,6 @@ export interface CustomTypes { // @public @system export type DefaultInsertableTreeNodeFromImplicitAllowedTypes = [TSchema] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; -======= -// @beta -export function createIndependentTreeBeta(options?: ForestOptions): ViewableTree; ->>>>>>> 86a249c645a27fc11428eda68371cf6709a9419e // @public @sealed @system interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> { @@ -920,11 +916,7 @@ export const SchemaFactory_base: SchemaStatics & (new () => SchemaStatics); // @beta export class SchemaFactoryBeta extends SchemaFactory { -<<<<<<< HEAD - object, const TOptions extends ObjectSchemaOptions = ObjectSchemaOptions>(name: Name, fields: T, options?: PreventExtraProperties): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode, TOptions>, object & InsertableObjectFromSchemaRecord, true, T>; -======= - object, const TCustomMetadata = unknown>(name: Name, fields: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T, never, TCustomMetadata>; ->>>>>>> 86a249c645a27fc11428eda68371cf6709a9419e + 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>; 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 4cae4338b3b1..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 @@ -75,16 +75,14 @@ type ApplyKindInput(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; -======= -// @beta -export function asBeta(view: TreeView): TreeViewBeta; ->>>>>>> 86a249c645a27fc11428eda68371cf6709a9419e // @public export enum AttachState { @@ -146,7 +144,9 @@ export interface ContainerSchema { readonly initialObjects: Record; } -<<<<<<< HEAD +// @beta +export function createIndependentTreeBeta(options?: ForestOptions): ViewableTree; + // @public @system export type CustomizedSchemaTyping = TSchema & { [CustomizedTyping]: TCustom; @@ -167,10 +167,6 @@ export interface CustomTypes { // @public @system export type DefaultInsertableTreeNodeFromImplicitAllowedTypes = [TSchema] extends [TreeNodeSchema] ? InsertableTypedNode : [TSchema] extends [AllowedTypes] ? InsertableTreeNodeFromAllowedTypes : never; -======= -// @beta -export function createIndependentTreeBeta(options?: ForestOptions): ViewableTree; ->>>>>>> 86a249c645a27fc11428eda68371cf6709a9419e // @public @sealed @system interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> { @@ -1198,11 +1194,7 @@ export const SchemaFactory_base: SchemaStatics & (new () => SchemaStatics); // @beta export class SchemaFactoryBeta extends SchemaFactory { -<<<<<<< HEAD - object, const TOptions extends ObjectSchemaOptions = ObjectSchemaOptions>(name: Name, fields: T, options?: PreventExtraProperties): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode, TOptions>, object & InsertableObjectFromSchemaRecord, true, T>; -======= - object, const TCustomMetadata = unknown>(name: Name, fields: T, options?: ObjectSchemaOptions): TreeNodeSchemaClass, NodeKind.Object, TreeObjectNode>, object & InsertableObjectFromSchemaRecord, true, T, never, TCustomMetadata>; ->>>>>>> 86a249c645a27fc11428eda68371cf6709a9419e + 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>; From 24cf057b001a342a666880f936ed12aa23fae355 Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Tue, 4 Nov 2025 16:24:00 -0800 Subject: [PATCH 36/37] Update for changes on main --- .../dds/tree/api-report/tree.alpha.api.md | 16 +- .../src/test/openPolymorphism.integration.ts | 208 +++++++++++++++++- .../api-report/fluid-framework.alpha.api.md | 16 +- 3 files changed, 223 insertions(+), 17 deletions(-) diff --git a/packages/dds/tree/api-report/tree.alpha.api.md b/packages/dds/tree/api-report/tree.alpha.api.md index 3b0ce53373d6..59c5ca62ee07 100644 --- a/packages/dds/tree/api-report/tree.alpha.api.md +++ b/packages/dds/tree/api-report/tree.alpha.api.md @@ -1237,18 +1237,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 : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 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/framework/fluid-framework/api-report/fluid-framework.alpha.api.md b/packages/framework/fluid-framework/api-report/fluid-framework.alpha.api.md index 7f5b52e870d3..ce658405039a 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 @@ -1616,18 +1616,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 : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 : TreeLeafValue_2 | TreeNode, [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 From a956b6184f35b750106465da35e0a00975164ccc Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Tue, 4 Nov 2025 20:07:59 -0800 Subject: [PATCH 37/37] unify components --- .../dds/tree/api-report/tree.alpha.api.md | 7 - packages/dds/tree/src/index.ts | 3 +- .../dds/tree/src/simple-tree/api/component.ts | 42 -- .../dds/tree/src/simple-tree/api/index.ts | 2 - packages/dds/tree/src/simple-tree/index.ts | 1 - .../tree/src/test/openPolymorphism.spec.ts | 393 ------------------ .../api/schemaFactory.examples.spec.ts | 2 +- .../api-report/fluid-framework.alpha.api.md | 7 - 8 files changed, 2 insertions(+), 455 deletions(-) delete mode 100644 packages/dds/tree/src/simple-tree/api/component.ts delete mode 100644 packages/dds/tree/src/test/openPolymorphism.spec.ts diff --git a/packages/dds/tree/api-report/tree.alpha.api.md b/packages/dds/tree/api-report/tree.alpha.api.md index 59c5ca62ee07..9ddd06b4c866 100644 --- a/packages/dds/tree/api-report/tree.alpha.api.md +++ b/packages/dds/tree/api-report/tree.alpha.api.md @@ -169,13 +169,6 @@ export interface CommitMetadata { // @alpha export function comparePersistedSchema(persisted: JsonCompatible, view: ImplicitFieldSchema, options: ICodecOptions): Omit; -// @alpha -export namespace Component { - export type ComponentSchemaCollection = (lazyConfiguration: () => TConfig) => LazyArray; - export function composeComponentSchema(allComponents: readonly ComponentSchemaCollection[], lazyConfiguration: () => TConfig): (() => TItem)[]; - export type LazyArray = readonly (() => T)[]; -} - // @beta export type ConciseTree = Exclude | THandle | ConciseTree[] | { [key: string]: ConciseTree; diff --git a/packages/dds/tree/src/index.ts b/packages/dds/tree/src/index.ts index 8444f9e3675e..e0392d5af6b7 100644 --- a/packages/dds/tree/src/index.ts +++ b/packages/dds/tree/src/index.ts @@ -267,9 +267,8 @@ export { type TransactionResultSuccess, type TransactionResultFailed, rollback, - evaluateLazySchema, - Component, generateSchemaFromSimpleSchema, + evaluateLazySchema, replaceConciseTreeHandles, replaceHandles, replaceVerboseTreeHandles, diff --git a/packages/dds/tree/src/simple-tree/api/component.ts b/packages/dds/tree/src/simple-tree/api/component.ts deleted file mode 100644 index c33413ab5b39..000000000000 --- a/packages/dds/tree/src/simple-tree/api/component.ts +++ /dev/null @@ -1,42 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -/** - * Utilities for helping implement various application component design patterns. - * @alpha - */ -export namespace Component { - /** - * Function which takes in a lazy configuration and returns a collection of schema types. - * @remarks - * This allows the schema to reference items from the configuration, which could include themselves recursively. - * @alpha - */ - export type ComponentSchemaCollection = ( - lazyConfiguration: () => TConfig, - ) => LazyArray; - - /** - * {@link AllowedTypes} where all of the allowed types' schema implement `T` and are lazy. - * @alpha - */ - export type LazyArray = readonly (() => T)[]; - - /** - * Combine multiple {@link Component.ComponentSchemaCollection}s into a single {@link AllowedTypes} array. - * @remarks - * - * @alpha - */ - export function composeComponentSchema( - allComponents: readonly ComponentSchemaCollection[], - lazyConfiguration: () => TConfig, - ): (() => TItem)[] { - const itemTypes = allComponents.flatMap( - (component): LazyArray => component(lazyConfiguration), - ); - return itemTypes; - } -} diff --git a/packages/dds/tree/src/simple-tree/api/index.ts b/packages/dds/tree/src/simple-tree/api/index.ts index 10ea17aa96c3..16a7a47e3471 100644 --- a/packages/dds/tree/src/simple-tree/api/index.ts +++ b/packages/dds/tree/src/simple-tree/api/index.ts @@ -165,8 +165,6 @@ export { rollback, } from "./transactionTypes.js"; -export { Component } from "./component.js"; - export { generateSchemaFromSimpleSchema } from "./schemaFromSimple.js"; export { toSimpleTreeSchema } from "./viewSchemaToSimpleSchema.js"; export type { TreeChangeEvents } from "./treeChangeEvents.js"; diff --git a/packages/dds/tree/src/simple-tree/index.ts b/packages/dds/tree/src/simple-tree/index.ts index 6197c58e524a..a1f25958c996 100644 --- a/packages/dds/tree/src/simple-tree/index.ts +++ b/packages/dds/tree/src/simple-tree/index.ts @@ -165,7 +165,6 @@ export { type TransactionResultSuccess, type TransactionResultFailed, rollback, - Component, generateSchemaFromSimpleSchema, replaceConciseTreeHandles, replaceHandles, diff --git a/packages/dds/tree/src/test/openPolymorphism.spec.ts b/packages/dds/tree/src/test/openPolymorphism.spec.ts deleted file mode 100644 index 0d58f8592c00..000000000000 --- a/packages/dds/tree/src/test/openPolymorphism.spec.ts +++ /dev/null @@ -1,393 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -import { strict as assert } from "node:assert"; - -import { - allowUnused, - Component, - SchemaFactory, - TreeViewConfiguration, - type NodeKind, - type ObjectFromSchemaRecord, - type TreeNode, - type TreeNodeSchema, - type Unhydrated, -} from "../simple-tree/index.js"; -import { Tree } from "../shared-tree/index.js"; -import { validateUsageError } from "./utils.js"; -import { customizeSchemaTyping, evaluateLazySchema } from "../simple-tree/index.js"; -import type { requireAssignableTo } from "../util/index.js"; - -const sf = new SchemaFactory("test"); - -/** - * Schema used in example. - */ -class Point extends sf.object("Point", { x: sf.number, y: sf.number }) {} - -// #region Example definition of a polymorphic Component named "Item" -// This code defines what an Item is and how to implement it, but does not depend on any of the implementations. -// Instead implementations depend on this, inverting the normal dependency direction for schema. - -/** - * Fields all Items must have. - */ -const itemFields = { location: Point }; - -/** - * Properties all item types must implement. - */ -interface ItemExtensions { - foo(): void; -} - -/** - * An Item node. - * @remarks - * Open polymorphic collection which libraries can provide additional implementations of, similar to TypeScript interfaces. - * Implementations should declare schema who's nodes extends this interface, and have the schema statically implement ItemSchema. - */ -type Item = TreeNode & ItemExtensions & ObjectFromSchemaRecord; - -/** - * Details about the type all item schema must provide. - * @remarks - * This pattern can be used for for things like generating insert content menus which can describe and create any of the allowed child types. - */ -interface ItemStatic { - readonly description: string; - default(): Unhydrated; -} - -/** - * A schema for an Item. - */ -type ItemSchema = TreeNodeSchema & ItemStatic; - -// #endregion - -/** - * Example implementation of an Item. - */ -class TextItem - extends sf.object("TextItem", { ...itemFields, text: sf.string }) - implements Item -{ - public static readonly description = "Text"; - public static default(): TextItem { - return new TextItem({ text: "", location: { x: 0, y: 0 } }); - } - - public foo(): void { - this.text += "foo"; - } -} - -describe("Open Polymorphism design pattern examples and tests for them", () => { - describe("mutable static registry", () => { - it("without customizeSchemaTyping", () => { - // ------------- - // Registry for items. If using this pattern, this would typically be defined alongside the Item interface. - - /** - * Item type registry. - * @remarks - * This doesn't have to be a mutable static. - * For example libraries could export their implementations instead of adding them when imported, - * then the top level code which pulls in all the libraries could aggregate the item types. - * - * TODO: document (and enforce/detect) when how late it is safe to modify array's used as allowed types. - * These docs should ideally align with how late lazy type lambdas are evaluated (when the tree configuration is constructed, or an instance is made, which ever is first? Maybe define schema finalization?) - */ - const ItemTypes: ItemSchema[] = []; - - // ------------- - // Library using an Item - - class Container extends sf.array("Container", ItemTypes) {} - - // ------------- - // Library defining an item - - ItemTypes.push(TextItem); - - // ------------- - // Example use of container with generic code and down casting - - const container = new Container(); - - // If we don't do anything special, the insertable type is never, so a cast is required to insert content. - // See example using customizeSchemaTyping for how to avoid this. - container.insertAtStart(new TextItem({ text: "", location: { x: 0, y: 0 } })); // TODO: Why is `as never` not required? - - type Input = Parameters[0]; - allowUnused>(); - - // Items read from the container are typed as Item and have the expected APIs: - const first = container[0]; - first.foo(); - first.location.x += 1; - - // Down casting works as normal. - if (Tree.is(first, TextItem)) { - assert.equal(first.text, "foo"); - } - }); - - it("error cases", () => { - const ItemTypes: ItemSchema[] = []; - class Container extends sf.array("Container", ItemTypes) {} - - // Not added to registry - // ItemTypes.push(TextItem); - - const container = new Container(); - - // Should error due to out of schema content - assert.throws( - () => - container.insertAtStart( - new TextItem({ text: "", location: { x: 0, y: 0 } }) as never, - ), - validateUsageError(/schema/), - ); - - // Modifying registration too late should error - assert.throws(() => ItemTypes.push(TextItem)); - }); - - it("recursive case", () => { - const ItemTypes: ItemSchema[] = []; - - // Example recursive item implementation - class Container extends sf.array("Container", 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 {} - } - - ItemTypes.push(ContainerItem); - - const container = new Container(); - - container.insertAtStart( - new ContainerItem({ container: [], location: { x: 0, y: 0 } }) as never, - ); - }); - - 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: Component.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: Component.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 = Component.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: (): Component.LazyArray => [() => TextItem], - }; - - // An example component which references schema from the configuration and can be recursive through it. - const containerComponent: MyAppComponent = { - itemTypes: (lazyConfig: () => MyAppConfigPartial): Component.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 16ddae323229..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,13 +10,13 @@ import { MockFluidDataStoreRuntime } from "@fluidframework/test-runtime-utils/in import { type ITree, + SchemaFactoryAlpha, treeNodeApi as Tree, TreeViewConfiguration, type TreeView, customizeSchemaTyping, type GetTypes, type Customizer, - SchemaFactoryAlpha, } from "../../../simple-tree/index.js"; import { DefaultTestSharedTreeKind, getView } from "../../utils.js"; import { 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 ce658405039a..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 @@ -176,13 +176,6 @@ export interface CommitMetadata { // @alpha export function comparePersistedSchema(persisted: JsonCompatible, view: ImplicitFieldSchema, options: ICodecOptions): Omit; -// @alpha -export namespace Component { - export type ComponentSchemaCollection = (lazyConfiguration: () => TConfig) => LazyArray; - export function composeComponentSchema(allComponents: readonly ComponentSchemaCollection[], lazyConfiguration: () => TConfig): (() => TItem)[]; - export type LazyArray = readonly (() => T)[]; -} - // @beta export type ConciseTree = Exclude | THandle | ConciseTree[] | { [key: string]: ConciseTree;