Skip to content
Draft
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
8c91051
Initial example
CraigMacomber Nov 11, 2024
aea7d62
Make fields safer
CraigMacomber Nov 11, 2024
73d05f5
Merge branch 'fieldSafe' into inversion
CraigMacomber Nov 11, 2024
ec64de4
Update packages/dds/tree/src/simple-tree/objectNode.ts
CraigMacomber Nov 12, 2024
395c688
typeNarrow asserts
CraigMacomber Nov 12, 2024
4119174
Fixes for optional
CraigMacomber Nov 12, 2024
a90e167
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
CraigMacomber Nov 12, 2024
90a79f5
FIx build
CraigMacomber Nov 12, 2024
494dde2
Merge branch 'fieldSafe' into inversion
CraigMacomber Nov 12, 2024
6997880
Export ObjectNodeSchema
CraigMacomber Nov 12, 2024
735622f
customizeSchemaTyping
CraigMacomber Nov 13, 2024
a1c3bd0
Add customized narrowing example
CraigMacomber Nov 13, 2024
beffae5
add another example, and more docs
CraigMacomber Nov 14, 2024
2836d9b
add branding example
CraigMacomber Nov 14, 2024
7ebff45
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
CraigMacomber Nov 14, 2024
1dcd254
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
CraigMacomber Nov 14, 2024
eb49097
Remove unneeded changes
CraigMacomber Nov 15, 2024
10c5ef8
Fix package API
CraigMacomber Nov 15, 2024
9645aa6
Recursive type support
CraigMacomber Dec 4, 2024
2a91ad2
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
CraigMacomber Dec 4, 2024
a3981a8
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
CraigMacomber Jan 13, 2025
773afb9
Fix merge
CraigMacomber Jan 13, 2025
d7cf0e5
Cleanup CustomizerUnsafe
CraigMacomber Jan 13, 2025
cc2684a
Cleanup and docs
CraigMacomber Jan 14, 2025
ec90211
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
CraigMacomber Jan 14, 2025
b3dffdc
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
CraigMacomber Jan 22, 2025
6e4faa2
Fix export and reports
CraigMacomber Jan 22, 2025
a6df85c
Fixes tests and cleanup
CraigMacomber Jan 23, 2025
14893ad
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
CraigMacomber Jan 23, 2025
bdc6959
update reports
CraigMacomber Jan 23, 2025
86a4b5d
Better document and test ObjectFromSchemaRecordUnsafe
CraigMacomber Jan 23, 2025
8ab3281
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
CraigMacomber Jan 23, 2025
addb022
Skip failing/broken/hanging test
CraigMacomber Jan 23, 2025
91fe629
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
CraigMacomber Jan 23, 2025
3b33fea
Add "components" example
CraigMacomber Mar 4, 2025
224c7b5
add components example
CraigMacomber Mar 4, 2025
9e1328b
Add generic components system
CraigMacomber Mar 4, 2025
b1b2c41
Apply suggestions from code review
CraigMacomber Mar 4, 2025
23487bf
More consistent and robust handling of lazy schema
CraigMacomber Mar 4, 2025
13beb5e
Fix infinite recursion and broken test
CraigMacomber Mar 4, 2025
3da123f
Expose evaluateLazySchema
CraigMacomber Mar 4, 2025
41d2c5c
Export Component
CraigMacomber Mar 4, 2025
d8681cb
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
CraigMacomber Mar 11, 2025
ff69090
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
CraigMacomber Mar 19, 2025
94b9178
Fix merge
CraigMacomber Mar 19, 2025
89a5756
Merge
CraigMacomber Oct 9, 2025
1828f00
fix non-table stuff
CraigMacomber Oct 9, 2025
bbb0ef6
Fix build
CraigMacomber Oct 10, 2025
fbe06f6
Add options for type customization and readonly field inclusion
CraigMacomber Oct 21, 2025
77b0e2f
Fix build
CraigMacomber Oct 21, 2025
9a2c09c
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
CraigMacomber Oct 21, 2025
fa17c25
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
CraigMacomber Oct 22, 2025
73fa9dc
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
CraigMacomber Nov 4, 2025
951d9ce
Fix from merge
CraigMacomber Nov 4, 2025
3096691
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
CraigMacomber Nov 4, 2025
24cf057
Update for changes on main
CraigMacomber Nov 5, 2025
2b51d0d
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
CraigMacomber Nov 5, 2025
a956b61
unify components
CraigMacomber Nov 5, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .changeset/metal-sloths-join.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
"fluid-framework": minor
"@fluidframework/tree": minor
---
---
"section": tree
---

Disallow some invalid and unsafe ObjectNode field assignments at compile time
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR builds upon #23053 . This changeset will probably need to be rewritten based on the major changes.


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.
16 changes: 14 additions & 2 deletions packages/dds/tree/api-report/tree.alpha.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,19 @@ type ApplyKind<T, Kind extends FieldKind> = {
[FieldKind.Identifier]: T;
}[Kind];

// @public
export type ApplyKindAssignment<T, Kind extends FieldKind> = [Kind] extends [
FieldKind.Required
] ? T : [Kind] extends [FieldKind.Optional] ? T | undefined : never;

// @public
type ApplyKindInput<T, Kind extends FieldKind, DefaultsAreOptional extends boolean> = [
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<TSchemaInput extends ImplicitFieldSchema, TSchema = UnionToIntersection<TSchemaInput>> = [TSchema] extends [FieldSchema<infer Kind, infer Types>] ? ApplyKindAssignment<InsertableTreeNodeFromImplicitAllowedTypes<Types>, Kind> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes<TSchema> : never;

// @alpha
export function asTreeViewAlpha<TSchema extends ImplicitFieldSchema>(view: TreeView<TSchema>): TreeViewAlpha<TSchema>;

Expand Down Expand Up @@ -238,7 +246,7 @@ export type InsertableObjectFromSchemaRecordUnsafe<T extends Unenforced<Restrict
};

// @public
export type InsertableTreeFieldFromImplicitField<TSchemaInput extends ImplicitFieldSchema, TSchema = UnionToIntersection<TSchemaInput>> = [TSchema] extends [FieldSchema<infer Kind, infer Types>] ? ApplyKindInput<InsertableTreeNodeFromImplicitAllowedTypes<Types>, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes<TSchema> : never;
export type InsertableTreeFieldFromImplicitField<TSchemaInput extends ImplicitFieldSchema, TSchema = UnionToIntersection<TSchemaInput>> = TSchema extends FieldSchema<infer Kind, infer Types> ? ApplyKindInput<InsertableTreeNodeFromImplicitAllowedTypes<Types>, Kind, true> : TSchema extends ImplicitAllowedTypes ? InsertableTreeNodeFromImplicitAllowedTypes<TSchema> : never;

// @public
export type InsertableTreeFieldFromImplicitFieldUnsafe<TSchemaInput extends Unenforced<ImplicitFieldSchema>, TSchema = UnionToIntersection<TSchemaInput>> = [TSchema] extends [FieldSchemaUnsafe<infer Kind, infer Types>] ? ApplyKindInput<InsertableTreeNodeFromImplicitAllowedTypesUnsafe<Types>, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypesUnsafe<TSchema> : never;
Expand Down Expand Up @@ -471,7 +479,11 @@ export const noopValidator: JsonValidator;

// @public
type ObjectFromSchemaRecord<T extends RestrictiveStringRecord<ImplicitFieldSchema>> = {
-readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField<T[Property]> : unknown;
-readonly [Property in keyof T as [
AssignableTreeFieldFromImplicitField<T[Property & string]>
] extends [never | undefined] ? never : Property]: TreeFieldFromImplicitField<T[Property & string]>;
} & {
readonly [Property in keyof T]: TreeFieldFromImplicitField<T[Property & string]>;
};

// @public
Expand Down
16 changes: 14 additions & 2 deletions packages/dds/tree/api-report/tree.beta.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,19 @@ type ApplyKind<T, Kind extends FieldKind> = {
[FieldKind.Identifier]: T;
}[Kind];

// @public
export type ApplyKindAssignment<T, Kind extends FieldKind> = [Kind] extends [
FieldKind.Required
] ? T : [Kind] extends [FieldKind.Optional] ? T | undefined : never;

// @public
type ApplyKindInput<T, Kind extends FieldKind, DefaultsAreOptional extends boolean> = [
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<TSchemaInput extends ImplicitFieldSchema, TSchema = UnionToIntersection<TSchemaInput>> = [TSchema] extends [FieldSchema<infer Kind, infer Types>] ? ApplyKindAssignment<InsertableTreeNodeFromImplicitAllowedTypes<Types>, Kind> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes<TSchema> : never;

// @public
export enum CommitKind {
Default = 0,
Expand Down Expand Up @@ -124,7 +132,7 @@ export type InsertableObjectFromSchemaRecordUnsafe<T extends Unenforced<Restrict
};

// @public
export type InsertableTreeFieldFromImplicitField<TSchemaInput extends ImplicitFieldSchema, TSchema = UnionToIntersection<TSchemaInput>> = [TSchema] extends [FieldSchema<infer Kind, infer Types>] ? ApplyKindInput<InsertableTreeNodeFromImplicitAllowedTypes<Types>, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes<TSchema> : never;
export type InsertableTreeFieldFromImplicitField<TSchemaInput extends ImplicitFieldSchema, TSchema = UnionToIntersection<TSchemaInput>> = TSchema extends FieldSchema<infer Kind, infer Types> ? ApplyKindInput<InsertableTreeNodeFromImplicitAllowedTypes<Types>, Kind, true> : TSchema extends ImplicitAllowedTypes ? InsertableTreeNodeFromImplicitAllowedTypes<TSchema> : never;

// @public
export type InsertableTreeFieldFromImplicitFieldUnsafe<TSchemaInput extends Unenforced<ImplicitFieldSchema>, TSchema = UnionToIntersection<TSchemaInput>> = [TSchema] extends [FieldSchemaUnsafe<infer Kind, infer Types>] ? ApplyKindInput<InsertableTreeNodeFromImplicitAllowedTypesUnsafe<Types>, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypesUnsafe<TSchema> : never;
Expand Down Expand Up @@ -276,7 +284,11 @@ export enum NodeKind {

// @public
type ObjectFromSchemaRecord<T extends RestrictiveStringRecord<ImplicitFieldSchema>> = {
-readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField<T[Property]> : unknown;
-readonly [Property in keyof T as [
AssignableTreeFieldFromImplicitField<T[Property & string]>
] extends [never | undefined] ? never : Property]: TreeFieldFromImplicitField<T[Property & string]>;
} & {
readonly [Property in keyof T]: TreeFieldFromImplicitField<T[Property & string]>;
};

// @public
Expand Down
16 changes: 14 additions & 2 deletions packages/dds/tree/api-report/tree.legacy.alpha.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,19 @@ type ApplyKind<T, Kind extends FieldKind> = {
[FieldKind.Identifier]: T;
}[Kind];

// @public
export type ApplyKindAssignment<T, Kind extends FieldKind> = [Kind] extends [
FieldKind.Required
] ? T : [Kind] extends [FieldKind.Optional] ? T | undefined : never;

// @public
type ApplyKindInput<T, Kind extends FieldKind, DefaultsAreOptional extends boolean> = [
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<TSchemaInput extends ImplicitFieldSchema, TSchema = UnionToIntersection<TSchemaInput>> = [TSchema] extends [FieldSchema<infer Kind, infer Types>] ? ApplyKindAssignment<InsertableTreeNodeFromImplicitAllowedTypes<Types>, Kind> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes<TSchema> : never;

// @public
export enum CommitKind {
Default = 0,
Expand Down Expand Up @@ -124,7 +132,7 @@ export type InsertableObjectFromSchemaRecordUnsafe<T extends Unenforced<Restrict
};

// @public
export type InsertableTreeFieldFromImplicitField<TSchemaInput extends ImplicitFieldSchema, TSchema = UnionToIntersection<TSchemaInput>> = [TSchema] extends [FieldSchema<infer Kind, infer Types>] ? ApplyKindInput<InsertableTreeNodeFromImplicitAllowedTypes<Types>, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes<TSchema> : never;
export type InsertableTreeFieldFromImplicitField<TSchemaInput extends ImplicitFieldSchema, TSchema = UnionToIntersection<TSchemaInput>> = TSchema extends FieldSchema<infer Kind, infer Types> ? ApplyKindInput<InsertableTreeNodeFromImplicitAllowedTypes<Types>, Kind, true> : TSchema extends ImplicitAllowedTypes ? InsertableTreeNodeFromImplicitAllowedTypes<TSchema> : never;

// @public
export type InsertableTreeFieldFromImplicitFieldUnsafe<TSchemaInput extends Unenforced<ImplicitFieldSchema>, TSchema = UnionToIntersection<TSchemaInput>> = [TSchema] extends [FieldSchemaUnsafe<infer Kind, infer Types>] ? ApplyKindInput<InsertableTreeNodeFromImplicitAllowedTypesUnsafe<Types>, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypesUnsafe<TSchema> : never;
Expand Down Expand Up @@ -271,7 +279,11 @@ export enum NodeKind {

// @public
type ObjectFromSchemaRecord<T extends RestrictiveStringRecord<ImplicitFieldSchema>> = {
-readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField<T[Property]> : unknown;
-readonly [Property in keyof T as [
AssignableTreeFieldFromImplicitField<T[Property & string]>
] extends [never | undefined] ? never : Property]: TreeFieldFromImplicitField<T[Property & string]>;
} & {
readonly [Property in keyof T]: TreeFieldFromImplicitField<T[Property & string]>;
};

// @public
Expand Down
16 changes: 14 additions & 2 deletions packages/dds/tree/api-report/tree.legacy.public.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,19 @@ type ApplyKind<T, Kind extends FieldKind> = {
[FieldKind.Identifier]: T;
}[Kind];

// @public
export type ApplyKindAssignment<T, Kind extends FieldKind> = [Kind] extends [
FieldKind.Required
] ? T : [Kind] extends [FieldKind.Optional] ? T | undefined : never;

// @public
type ApplyKindInput<T, Kind extends FieldKind, DefaultsAreOptional extends boolean> = [
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<TSchemaInput extends ImplicitFieldSchema, TSchema = UnionToIntersection<TSchemaInput>> = [TSchema] extends [FieldSchema<infer Kind, infer Types>] ? ApplyKindAssignment<InsertableTreeNodeFromImplicitAllowedTypes<Types>, Kind> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes<TSchema> : never;

// @public
export enum CommitKind {
Default = 0,
Expand Down Expand Up @@ -124,7 +132,7 @@ export type InsertableObjectFromSchemaRecordUnsafe<T extends Unenforced<Restrict
};

// @public
export type InsertableTreeFieldFromImplicitField<TSchemaInput extends ImplicitFieldSchema, TSchema = UnionToIntersection<TSchemaInput>> = [TSchema] extends [FieldSchema<infer Kind, infer Types>] ? ApplyKindInput<InsertableTreeNodeFromImplicitAllowedTypes<Types>, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes<TSchema> : never;
export type InsertableTreeFieldFromImplicitField<TSchemaInput extends ImplicitFieldSchema, TSchema = UnionToIntersection<TSchemaInput>> = TSchema extends FieldSchema<infer Kind, infer Types> ? ApplyKindInput<InsertableTreeNodeFromImplicitAllowedTypes<Types>, Kind, true> : TSchema extends ImplicitAllowedTypes ? InsertableTreeNodeFromImplicitAllowedTypes<TSchema> : never;

// @public
export type InsertableTreeFieldFromImplicitFieldUnsafe<TSchemaInput extends Unenforced<ImplicitFieldSchema>, TSchema = UnionToIntersection<TSchemaInput>> = [TSchema] extends [FieldSchemaUnsafe<infer Kind, infer Types>] ? ApplyKindInput<InsertableTreeNodeFromImplicitAllowedTypesUnsafe<Types>, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypesUnsafe<TSchema> : never;
Expand Down Expand Up @@ -271,7 +279,11 @@ export enum NodeKind {

// @public
type ObjectFromSchemaRecord<T extends RestrictiveStringRecord<ImplicitFieldSchema>> = {
-readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField<T[Property]> : unknown;
-readonly [Property in keyof T as [
AssignableTreeFieldFromImplicitField<T[Property & string]>
] extends [never | undefined] ? never : Property]: TreeFieldFromImplicitField<T[Property & string]>;
} & {
readonly [Property in keyof T]: TreeFieldFromImplicitField<T[Property & string]>;
};

// @public
Expand Down
16 changes: 14 additions & 2 deletions packages/dds/tree/api-report/tree.public.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,19 @@ type ApplyKind<T, Kind extends FieldKind> = {
[FieldKind.Identifier]: T;
}[Kind];

// @public
export type ApplyKindAssignment<T, Kind extends FieldKind> = [Kind] extends [
FieldKind.Required
] ? T : [Kind] extends [FieldKind.Optional] ? T | undefined : never;

// @public
type ApplyKindInput<T, Kind extends FieldKind, DefaultsAreOptional extends boolean> = [
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<TSchemaInput extends ImplicitFieldSchema, TSchema = UnionToIntersection<TSchemaInput>> = [TSchema] extends [FieldSchema<infer Kind, infer Types>] ? ApplyKindAssignment<InsertableTreeNodeFromImplicitAllowedTypes<Types>, Kind> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes<TSchema> : never;

// @public
export enum CommitKind {
Default = 0,
Expand Down Expand Up @@ -124,7 +132,7 @@ export type InsertableObjectFromSchemaRecordUnsafe<T extends Unenforced<Restrict
};

// @public
export type InsertableTreeFieldFromImplicitField<TSchemaInput extends ImplicitFieldSchema, TSchema = UnionToIntersection<TSchemaInput>> = [TSchema] extends [FieldSchema<infer Kind, infer Types>] ? ApplyKindInput<InsertableTreeNodeFromImplicitAllowedTypes<Types>, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes<TSchema> : never;
export type InsertableTreeFieldFromImplicitField<TSchemaInput extends ImplicitFieldSchema, TSchema = UnionToIntersection<TSchemaInput>> = TSchema extends FieldSchema<infer Kind, infer Types> ? ApplyKindInput<InsertableTreeNodeFromImplicitAllowedTypes<Types>, Kind, true> : TSchema extends ImplicitAllowedTypes ? InsertableTreeNodeFromImplicitAllowedTypes<TSchema> : never;

// @public
export type InsertableTreeFieldFromImplicitFieldUnsafe<TSchemaInput extends Unenforced<ImplicitFieldSchema>, TSchema = UnionToIntersection<TSchemaInput>> = [TSchema] extends [FieldSchemaUnsafe<infer Kind, infer Types>] ? ApplyKindInput<InsertableTreeNodeFromImplicitAllowedTypesUnsafe<Types>, Kind, true> : [TSchema] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypesUnsafe<TSchema> : never;
Expand Down Expand Up @@ -271,7 +279,11 @@ export enum NodeKind {

// @public
type ObjectFromSchemaRecord<T extends RestrictiveStringRecord<ImplicitFieldSchema>> = {
-readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField<T[Property]> : unknown;
-readonly [Property in keyof T as [
AssignableTreeFieldFromImplicitField<T[Property & string]>
] extends [never | undefined] ? never : Property]: TreeFieldFromImplicitField<T[Property & string]>;
} & {
readonly [Property in keyof T]: TreeFieldFromImplicitField<T[Property & string]>;
};

// @public
Expand Down
16 changes: 16 additions & 0 deletions packages/dds/tree/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,22 @@
},
"Interface_InternalTypes_TreeArrayNodeBase": {
"backCompat": false
},
"TypeAlias_InsertableTreeFieldFromImplicitField": {
"backCompat": false
},
"TypeAlias_InternalTypes_InsertableObjectFromSchemaRecord": {
"backCompat": false,
"forwardCompat": false
},
"Interface_TreeView": {
"backCompat": false
},
"TypeAlias_InternalTypes_ObjectFromSchemaRecord": {
"backCompat": false
},
"TypeAlias_TreeObjectNode": {
"backCompat": false
}
},
"entrypoint": "public"
Expand Down
2 changes: 2 additions & 0 deletions packages/dds/tree/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,8 @@ export {
type TreeBranch,
type TreeBranchEvents,
asTreeViewAlpha,
type AssignableTreeFieldFromImplicitField,
type ApplyKindAssignment,
} from "./simple-tree/index.js";
export {
SharedTree,
Expand Down
14 changes: 3 additions & 11 deletions packages/dds/tree/src/simple-tree/api/schemaFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}.
*/
Expand Down Expand Up @@ -322,17 +323,7 @@ export class SchemaFactory<
public object<
const Name extends TName,
const T extends RestrictiveStringRecord<ImplicitFieldSchema>,
>(
name: Name,
fields: T,
): TreeNodeSchemaClass<
ScopedSchemaName<TScope, Name>,
NodeKind.Object,
TreeObjectNode<T, ScopedSchemaName<TScope, Name>>,
object & InsertableObjectFromSchemaRecord<T>,
true,
T
> {
>(name: Name, fields: T): ObjectNodeSchema<ScopedSchemaName<TScope, Name>, T, true> {
return objectSchema(this.scoped(name), fields, true);
}

Expand Down Expand Up @@ -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
Expand Down
9 changes: 6 additions & 3 deletions packages/dds/tree/src/simple-tree/api/typesUnsafe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,12 @@ export type Unenforced<_DesiredExtendsConstraint> = unknown;
*/
export type ObjectFromSchemaRecordUnsafe<
T extends Unenforced<RestrictiveStringRecord<ImplicitFieldSchema>>,
> = {
-readonly [Property in keyof T]: TreeFieldFromImplicitFieldUnsafe<T[Property]>;
};
> =
// 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<T[Property]>;
};

/**
* {@link Unenforced} version of {@link TreeNodeSchema}.
Expand Down
3 changes: 3 additions & 0 deletions packages/dds/tree/src/simple-tree/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ export {
type Input,
type ReadableField,
type ReadSchema,
customizeSchemaTyping,
} from "./schemaTypes.js";
export {
getTreeNodeForField,
Expand All @@ -160,6 +161,8 @@ export {
type FieldHasDefault,
type InsertableObjectFromSchemaRecord,
type ObjectFromSchemaRecord,
type AssignableTreeFieldFromImplicitField,
type ApplyKindAssignment,
type TreeObjectNode,
setField,
} from "./objectNode.js";
Expand Down
Loading
Loading