From 6a20be46f779d7927dff69bf6bbd97881f71b1f4 Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Fri, 26 Jul 2024 16:13:13 -0700 Subject: [PATCH 1/5] Allow MapNodes from Records --- .changeset/green-spies-arrive.md | 24 +++++++++++++++++++ .../dds/tree/api-report/tree.alpha.api.md | 7 ++++-- packages/dds/tree/api-report/tree.beta.api.md | 7 ++++-- .../dds/tree/api-report/tree.public.api.md | 7 ++++-- packages/dds/tree/src/index.ts | 5 ++++ packages/dds/tree/src/simple-tree/index.ts | 2 +- packages/dds/tree/src/simple-tree/mapNode.ts | 19 ++++++++++----- .../dds/tree/src/simple-tree/schemaFactory.ts | 21 +++++++--------- .../dds/tree/src/simple-tree/toMapTree.ts | 19 ++++++++++++--- .../tree/src/test/simple-tree/mapNode.spec.ts | 6 +++++ .../api-report/fluid-framework.beta.api.md | 7 ++++-- .../fluid-framework.legacy.alpha.api.md | 7 ++++-- .../fluid-framework.legacy.public.api.md | 7 ++++-- .../api-report/fluid-framework.public.api.md | 7 ++++-- 14 files changed, 108 insertions(+), 37 deletions(-) create mode 100644 .changeset/green-spies-arrive.md diff --git a/.changeset/green-spies-arrive.md b/.changeset/green-spies-arrive.md new file mode 100644 index 000000000000..700d88a6f4a1 --- /dev/null +++ b/.changeset/green-spies-arrive.md @@ -0,0 +1,24 @@ +--- +"fluid-framework": minor +"@fluidframework/tree": minor +--- + +Allow `Record` typed object to be used to construct MapNodes. + +It is now allowed to construct MapNodes from `Record` typed objects, similar to how maps are expressed in JSON. + +Before this change, an `Iterable` was required, but now an object like `{key1: Child1, key2: Child2}` is allowed. + +Full example using this new API: +```typescript +class Schema extends schemaFactory.map("ExampleMap", schemaFactory.number) {} +const fromRecord = new Schema({ x: 5 }); +``` + +This new feature makes it possible for schema, +which do not require unhydrated nodes to differentiate ambiguous unions, +to construct trees entirely from JSON compatible objects using their constructors. + +Due to limitations of TypeScript and recursive types, +recursive maps to not currently advertize support for this feature in their typing, +but it does work at runtime. diff --git a/packages/dds/tree/api-report/tree.alpha.api.md b/packages/dds/tree/api-report/tree.alpha.api.md index b3e3e0f2de0a..e3a2762ee383 100644 --- a/packages/dds/tree/api-report/tree.alpha.api.md +++ b/packages/dds/tree/api-report/tree.alpha.api.md @@ -208,6 +208,9 @@ export type Listeners = { export interface MakeNominal { } +// @public +export type MapNodeInsertableData = Iterable]> | RestrictiveReadonlyRecord>; + // @public type NodeBuilderData = T extends TreeNodeSchema ? TBuild : never; @@ -314,8 +317,8 @@ export class SchemaFactory; readonly handle: TreeNodeSchema<"com.fluidframework.leaf.handle", NodeKind.Leaf, IFluidHandle, IFluidHandle>; get identifier(): FieldSchema; - map(allowedTypes: T): TreeNodeSchema`>, NodeKind.Map, TreeMapNode & WithType`>>, Iterable]>, true, T>; - map(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Map, TreeMapNode & WithType>, Iterable]>, true, T>; + map(allowedTypes: T): TreeNodeSchema`>, NodeKind.Map, TreeMapNode & WithType`>>, MapNodeInsertableData, true, T>; + map(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Map, TreeMapNode & WithType>, MapNodeInsertableData, true, T>; mapRecursive>(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Map, TreeMapNodeUnsafe & WithType>, { [Symbol.iterator](): Iterator<[ string, diff --git a/packages/dds/tree/api-report/tree.beta.api.md b/packages/dds/tree/api-report/tree.beta.api.md index 4630be386dc9..ed5b9c4d38dc 100644 --- a/packages/dds/tree/api-report/tree.beta.api.md +++ b/packages/dds/tree/api-report/tree.beta.api.md @@ -208,6 +208,9 @@ export type Listeners = { export interface MakeNominal { } +// @public +export type MapNodeInsertableData = Iterable]> | RestrictiveReadonlyRecord>; + // @public type NodeBuilderData = T extends TreeNodeSchema ? TBuild : never; @@ -314,8 +317,8 @@ export class SchemaFactory; readonly handle: TreeNodeSchema<"com.fluidframework.leaf.handle", NodeKind.Leaf, IFluidHandle, IFluidHandle>; get identifier(): FieldSchema; - map(allowedTypes: T): TreeNodeSchema`>, NodeKind.Map, TreeMapNode & WithType`>>, Iterable]>, true, T>; - map(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Map, TreeMapNode & WithType>, Iterable]>, true, T>; + map(allowedTypes: T): TreeNodeSchema`>, NodeKind.Map, TreeMapNode & WithType`>>, MapNodeInsertableData, true, T>; + map(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Map, TreeMapNode & WithType>, MapNodeInsertableData, true, T>; mapRecursive>(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Map, TreeMapNodeUnsafe & WithType>, { [Symbol.iterator](): Iterator<[ string, diff --git a/packages/dds/tree/api-report/tree.public.api.md b/packages/dds/tree/api-report/tree.public.api.md index eda15863f6d1..b2856b9c9078 100644 --- a/packages/dds/tree/api-report/tree.public.api.md +++ b/packages/dds/tree/api-report/tree.public.api.md @@ -208,6 +208,9 @@ export type Listeners = { export interface MakeNominal { } +// @public +export type MapNodeInsertableData = Iterable]> | RestrictiveReadonlyRecord>; + // @public type NodeBuilderData = T extends TreeNodeSchema ? TBuild : never; @@ -314,8 +317,8 @@ export class SchemaFactory; readonly handle: TreeNodeSchema<"com.fluidframework.leaf.handle", NodeKind.Leaf, IFluidHandle, IFluidHandle>; get identifier(): FieldSchema; - map(allowedTypes: T): TreeNodeSchema`>, NodeKind.Map, TreeMapNode & WithType`>>, Iterable]>, true, T>; - map(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Map, TreeMapNode & WithType>, Iterable]>, true, T>; + map(allowedTypes: T): TreeNodeSchema`>, NodeKind.Map, TreeMapNode & WithType`>>, MapNodeInsertableData, true, T>; + map(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Map, TreeMapNode & WithType>, MapNodeInsertableData, true, T>; mapRecursive>(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Map, TreeMapNodeUnsafe & WithType>, { [Symbol.iterator](): Iterator<[ string, diff --git a/packages/dds/tree/src/index.ts b/packages/dds/tree/src/index.ts index ff544137df8e..3e5508e2a50f 100644 --- a/packages/dds/tree/src/index.ts +++ b/packages/dds/tree/src/index.ts @@ -384,3 +384,8 @@ export { */ InternalTypes, }; + +// Internal/System types: +// These would be put in `internalTypes` except doing so tents to cause errors like: +// The inferred type of 'NodeMap' cannot be named without a reference to '../../node_modules/@fluidframework/tree/lib/internalTypes.js'. This is likely not portable. A type annotation is necessary. +export type { MapNodeInsertableData } from "./simple-tree/index.js"; diff --git a/packages/dds/tree/src/simple-tree/index.ts b/packages/dds/tree/src/simple-tree/index.ts index 2a1805676c8c..39bfb9aab61d 100644 --- a/packages/dds/tree/src/simple-tree/index.ts +++ b/packages/dds/tree/src/simple-tree/index.ts @@ -95,5 +95,5 @@ export { type TreeObjectNode, setField, } from "./objectNode.js"; -export type { TreeMapNode } from "./mapNode.js"; +export type { TreeMapNode, MapNodeInsertableData } from "./mapNode.js"; export { mapTreeFromNodeData } from "./toMapTree.js"; diff --git a/packages/dds/tree/src/simple-tree/mapNode.ts b/packages/dds/tree/src/simple-tree/mapNode.ts index 5aadc22e678c..eefeee10ef5f 100644 --- a/packages/dds/tree/src/simple-tree/mapNode.ts +++ b/packages/dds/tree/src/simple-tree/mapNode.ts @@ -13,6 +13,7 @@ import { isMapTreeNode, } from "../feature-libraries/index.js"; import { + type FactoryContent, type InsertableContent, getProxyForField, prepareContentForHydration, @@ -33,6 +34,7 @@ import { mapTreeFromNodeData } from "./toMapTree.js"; import { type MostDerivedData, type TreeNode, TreeNodeValid } from "./types.js"; import { getFlexSchema } from "./toFlexSchema.js"; import { UsageError } from "@fluidframework/telemetry-utils/internal"; +import type { RestrictiveReadonlyRecord } from "../util/index.js"; /** * A map of string keys to tree objects. @@ -125,7 +127,7 @@ const handler: ProxyHandler = { }; abstract class CustomMapNodeBase extends TreeNodeValid< - Iterable]> + MapNodeInsertableData > { public static readonly kind = NodeKind.Map; @@ -237,10 +239,7 @@ export function mapSchema< ): MapTreeNode { return getOrCreateMapTreeNode( flexSchema, - mapTreeFromNodeData( - input as Iterable, - this as unknown as ImplicitAllowedTypes, - ), + mapTreeFromNodeData(input as FactoryContent, this as unknown as ImplicitAllowedTypes), ); } @@ -263,9 +262,17 @@ export function mapSchema< TName, NodeKind.Map, TreeMapNode & WithType, - Iterable]>, + MapNodeInsertableData, ImplicitlyConstructable, T > = schema; return schemaErased; } + +/** + * Content which can be used to construct a Map node, explicitly or implicitly. + * @system @public + */ +export type MapNodeInsertableData = + | Iterable]> + | RestrictiveReadonlyRecord>; diff --git a/packages/dds/tree/src/simple-tree/schemaFactory.ts b/packages/dds/tree/src/simple-tree/schemaFactory.ts index 2b7b2f1e2c85..108aa0f0e1f9 100644 --- a/packages/dds/tree/src/simple-tree/schemaFactory.ts +++ b/packages/dds/tree/src/simple-tree/schemaFactory.ts @@ -48,7 +48,7 @@ import { type TreeObjectNode, objectSchema, } from "./objectNode.js"; -import { type TreeMapNode, mapSchema } from "./mapNode.js"; +import { type MapNodeInsertableData, type TreeMapNode, mapSchema } from "./mapNode.js"; import type { FieldSchemaUnsafe, // Adding these unused imports makes the generated d.ts file produced by TypeScript stop breaking API-Extractor's rollup generation. @@ -297,7 +297,7 @@ export class SchemaFactory< ScopedSchemaName`>, NodeKind.Map, TreeMapNode & WithType`>>, - Iterable]>, + MapNodeInsertableData, true, T >; @@ -319,7 +319,7 @@ export class SchemaFactory< ScopedSchemaName, NodeKind.Map, TreeMapNode & WithType>, - Iterable]>, + MapNodeInsertableData, true, T >; @@ -327,14 +327,7 @@ export class SchemaFactory< public map( nameOrAllowedTypes: TName | ((T & TreeNodeSchema) | readonly TreeNodeSchema[]), allowedTypes?: T, - ): TreeNodeSchema< - string, - NodeKind.Map, - TreeMapNode, - Iterable]>, - true, - T - > { + ): TreeNodeSchema, MapNodeInsertableData, true, T> { if (allowedTypes === undefined) { const types = nameOrAllowedTypes as (T & TreeNodeSchema) | readonly TreeNodeSchema[]; const fullName = structuralName("Map", types); @@ -352,7 +345,7 @@ export class SchemaFactory< string, NodeKind.Map, TreeMapNode, - Iterable]>, + MapNodeInsertableData, true, T >; @@ -378,7 +371,7 @@ export class SchemaFactory< ScopedSchemaName, NodeKind.Map, TreeMapNode & WithType>, - Iterable]>, + MapNodeInsertableData, ImplicitlyConstructable, T > { @@ -722,6 +715,8 @@ export class SchemaFactory< [string, InsertableTreeNodeFromImplicitAllowedTypesUnsafe] >; }, + // Ideally this would be included, but doing so breaks recursive types. + // | RestrictiveReadonlyRecord>, false, T >; diff --git a/packages/dds/tree/src/simple-tree/toMapTree.ts b/packages/dds/tree/src/simple-tree/toMapTree.ts index baaf3799e61d..b1b851ccdf78 100644 --- a/packages/dds/tree/src/simple-tree/toMapTree.ts +++ b/packages/dds/tree/src/simple-tree/toMapTree.ts @@ -375,14 +375,22 @@ function arrayToMapTree(data: InsertableContent, schema: TreeNodeSchema): Exclus */ function mapToMapTree(data: InsertableContent, schema: TreeNodeSchema): ExclusiveMapTree { assert(schema.kind === NodeKind.Map, 0x923 /* Expected a Map schema. */); - if (!(typeof data === "object" && data !== null && Symbol.iterator in data)) { + if (!(typeof data === "object" && data !== null)) { throw new UsageError(`Input data is incompatible with Map schema: ${data}`); } const allowedChildTypes = normalizeAllowedTypes(schema.info as ImplicitAllowedTypes); + const fieldsIterator = ( + Symbol.iterator in data + ? // Support iterables of key value pairs (including Map objects) + data + : // Support record objects for JSON style Map data + Object.entries(data) + ) as Iterable; + const transformedFields = new Map(); - for (const item of data as Iterable) { + for (const item of fieldsIterator) { if (!isReadonlyArray(item) || item.length !== 2 || typeof item[0] !== "string") { throw new UsageError(`Input data is incompatible with map entry: ${item}`); } @@ -595,10 +603,15 @@ function shallowCompatibilityTest( return mapOrArray ? CompatibilityLevel.Normal : CompatibilityLevel.None; } - if (mapOrArray) { + if (schema.kind === NodeKind.Array) { return CompatibilityLevel.None; } + if (schema.kind === NodeKind.Map) { + // When not unioned with an ObjectNode, allow objects to be used to crete maps. + return CompatibilityLevel.Low; + } + // Assume record-like object assert(isObjectNodeSchema(schema), "unexpected schema kind"); diff --git a/packages/dds/tree/src/test/simple-tree/mapNode.spec.ts b/packages/dds/tree/src/test/simple-tree/mapNode.spec.ts index a36955e70270..eda36622b0f2 100644 --- a/packages/dds/tree/src/test/simple-tree/mapNode.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/mapNode.spec.ts @@ -73,6 +73,8 @@ describe("MapNode", () => { assert.deepEqual([...fromMap], data); const fromIterable = new Schema(new Map(data).entries()); assert.deepEqual([...fromIterable], data); + const fromRecord = new Schema({ x: 5 }); + assert.deepEqual([...fromRecord], data); }); describe("implicit construction", () => { @@ -91,5 +93,9 @@ describe("MapNode", () => { const fromIterable = new Root({ data: new Map(data).entries() }); assert.deepEqual([...fromIterable.data], data); }); + it("fromRecord", () => { + const fromRecord = new Root({ data: { x: 5 } }); + assert.deepEqual([...fromRecord.data], data); + }); }); }); 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 301472c30e4b..04718ff7a987 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 @@ -545,6 +545,9 @@ export type Listeners = { export interface MakeNominal { } +// @public +export type MapNodeInsertableData = Iterable]> | RestrictiveReadonlyRecord>; + // @public export type MemberChangedListener = (clientId: string, member: M) => void; @@ -660,8 +663,8 @@ export class SchemaFactory; readonly handle: TreeNodeSchema<"com.fluidframework.leaf.handle", NodeKind.Leaf, IFluidHandle, IFluidHandle>; get identifier(): FieldSchema; - map(allowedTypes: T): TreeNodeSchema`>, NodeKind.Map, TreeMapNode & WithType`>>, Iterable]>, true, T>; - map(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Map, TreeMapNode & WithType>, Iterable]>, true, T>; + map(allowedTypes: T): TreeNodeSchema`>, NodeKind.Map, TreeMapNode & WithType`>>, MapNodeInsertableData, true, T>; + map(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Map, TreeMapNode & WithType>, MapNodeInsertableData, true, T>; mapRecursive>(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Map, TreeMapNodeUnsafe & WithType>, { [Symbol.iterator](): Iterator<[ string, 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 b43a4e567c09..0ce303379266 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 @@ -851,6 +851,9 @@ export type Listeners = { export interface MakeNominal { } +// @public +export type MapNodeInsertableData = Iterable]> | RestrictiveReadonlyRecord>; + // @public export type MemberChangedListener = (clientId: string, member: M) => void; @@ -966,8 +969,8 @@ export class SchemaFactory; readonly handle: TreeNodeSchema<"com.fluidframework.leaf.handle", NodeKind.Leaf, IFluidHandle, IFluidHandle>; get identifier(): FieldSchema; - map(allowedTypes: T): TreeNodeSchema`>, NodeKind.Map, TreeMapNode & WithType`>>, Iterable]>, true, T>; - map(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Map, TreeMapNode & WithType>, Iterable]>, true, T>; + map(allowedTypes: T): TreeNodeSchema`>, NodeKind.Map, TreeMapNode & WithType`>>, MapNodeInsertableData, true, T>; + map(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Map, TreeMapNode & WithType>, MapNodeInsertableData, true, T>; mapRecursive>(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Map, TreeMapNodeUnsafe & WithType>, { [Symbol.iterator](): Iterator<[ string, 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 f83779383ecf..ff769a0a92b7 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 @@ -581,6 +581,9 @@ export type Listeners = { export interface MakeNominal { } +// @public +export type MapNodeInsertableData = Iterable]> | RestrictiveReadonlyRecord>; + // @public export type MemberChangedListener = (clientId: string, member: M) => void; @@ -696,8 +699,8 @@ export class SchemaFactory; readonly handle: TreeNodeSchema<"com.fluidframework.leaf.handle", NodeKind.Leaf, IFluidHandle, IFluidHandle>; get identifier(): FieldSchema; - map(allowedTypes: T): TreeNodeSchema`>, NodeKind.Map, TreeMapNode & WithType`>>, Iterable]>, true, T>; - map(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Map, TreeMapNode & WithType>, Iterable]>, true, T>; + map(allowedTypes: T): TreeNodeSchema`>, NodeKind.Map, TreeMapNode & WithType`>>, MapNodeInsertableData, true, T>; + map(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Map, TreeMapNode & WithType>, MapNodeInsertableData, true, T>; mapRecursive>(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Map, TreeMapNodeUnsafe & WithType>, { [Symbol.iterator](): Iterator<[ string, 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 fa2773402887..967360c6ce75 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 @@ -545,6 +545,9 @@ export type Listeners = { export interface MakeNominal { } +// @public +export type MapNodeInsertableData = Iterable]> | RestrictiveReadonlyRecord>; + // @public export type MemberChangedListener = (clientId: string, member: M) => void; @@ -660,8 +663,8 @@ export class SchemaFactory; readonly handle: TreeNodeSchema<"com.fluidframework.leaf.handle", NodeKind.Leaf, IFluidHandle, IFluidHandle>; get identifier(): FieldSchema; - map(allowedTypes: T): TreeNodeSchema`>, NodeKind.Map, TreeMapNode & WithType`>>, Iterable]>, true, T>; - map(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Map, TreeMapNode & WithType>, Iterable]>, true, T>; + map(allowedTypes: T): TreeNodeSchema`>, NodeKind.Map, TreeMapNode & WithType`>>, MapNodeInsertableData, true, T>; + map(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Map, TreeMapNode & WithType>, MapNodeInsertableData, true, T>; mapRecursive>(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Map, TreeMapNodeUnsafe & WithType>, { [Symbol.iterator](): Iterator<[ string, From 31f8ede46e925da3641aa6e2bfe85d8e74499d9b Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Mon, 29 Jul 2024 10:09:43 -0700 Subject: [PATCH 2/5] Apply suggestions from code review Co-authored-by: Noah Encke <78610362+noencke@users.noreply.github.com> --- packages/dds/tree/src/simple-tree/toMapTree.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dds/tree/src/simple-tree/toMapTree.ts b/packages/dds/tree/src/simple-tree/toMapTree.ts index b1b851ccdf78..0fb9edd1ba64 100644 --- a/packages/dds/tree/src/simple-tree/toMapTree.ts +++ b/packages/dds/tree/src/simple-tree/toMapTree.ts @@ -608,7 +608,7 @@ function shallowCompatibilityTest( } if (schema.kind === NodeKind.Map) { - // When not unioned with an ObjectNode, allow objects to be used to crete maps. + // When not unioned with an ObjectNode, allow objects to be used to create maps. return CompatibilityLevel.Low; } From 6f7667f89497f79ff8cd649c99872ba0fa510400 Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Mon, 29 Jul 2024 10:12:00 -0700 Subject: [PATCH 3/5] Clarify comment --- packages/dds/tree/src/simple-tree/toMapTree.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/dds/tree/src/simple-tree/toMapTree.ts b/packages/dds/tree/src/simple-tree/toMapTree.ts index 0fb9edd1ba64..7349a995cd83 100644 --- a/packages/dds/tree/src/simple-tree/toMapTree.ts +++ b/packages/dds/tree/src/simple-tree/toMapTree.ts @@ -603,6 +603,8 @@ function shallowCompatibilityTest( return mapOrArray ? CompatibilityLevel.Normal : CompatibilityLevel.None; } + // At this point, it is assumed data is a record-like object since all the other cases have been eliminated. + if (schema.kind === NodeKind.Array) { return CompatibilityLevel.None; } @@ -612,7 +614,6 @@ function shallowCompatibilityTest( return CompatibilityLevel.Low; } - // Assume record-like object assert(isObjectNodeSchema(schema), "unexpected schema kind"); // TODO: Improve type inference by making this logic more thorough. Handle at least: From 6e0fcb4d35ac86ac05d49aab9f893dfaf8a64bbf Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Mon, 29 Jul 2024 10:35:02 -0700 Subject: [PATCH 4/5] Update .changeset/green-spies-arrive.md Co-authored-by: Tyler Butler --- .changeset/green-spies-arrive.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/green-spies-arrive.md b/.changeset/green-spies-arrive.md index 700d88a6f4a1..f3c19dded4a8 100644 --- a/.changeset/green-spies-arrive.md +++ b/.changeset/green-spies-arrive.md @@ -20,5 +20,5 @@ which do not require unhydrated nodes to differentiate ambiguous unions, to construct trees entirely from JSON compatible objects using their constructors. Due to limitations of TypeScript and recursive types, -recursive maps to not currently advertize support for this feature in their typing, -but it does work at runtime. +recursive maps do not advertise support for this feature in their typing, +but it works at runtime. From b751931b10dd1be6aef8065a79693bd415decdb8 Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Mon, 29 Jul 2024 10:39:10 -0700 Subject: [PATCH 5/5] Update .changeset/green-spies-arrive.md --- .changeset/green-spies-arrive.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.changeset/green-spies-arrive.md b/.changeset/green-spies-arrive.md index f3c19dded4a8..ec87300497f5 100644 --- a/.changeset/green-spies-arrive.md +++ b/.changeset/green-spies-arrive.md @@ -15,9 +15,9 @@ class Schema extends schemaFactory.map("ExampleMap", schemaFactory.number) {} const fromRecord = new Schema({ x: 5 }); ``` -This new feature makes it possible for schema, -which do not require unhydrated nodes to differentiate ambiguous unions, -to construct trees entirely from JSON compatible objects using their constructors. +This new feature makes it possible for schemas to construct a tree entirely from JSON compatible objects using their constructors, +as long as they do not require unhydrated nodes to differentiate ambiguous unions, +or IFluidHandles (which themselevs are not JSON compatible). Due to limitations of TypeScript and recursive types, recursive maps do not advertise support for this feature in their typing,