Skip to content

feat(tree): enablable allowed types #24631

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 40 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
060acdd
add annotated allowed types
jenn-le Mar 28, 2025
3d4d85e
Merge remote-tracking branch 'upstream/main' into readonly-allowedtypes
jenn-le Mar 28, 2025
fed6358
Merge remote-tracking branch 'upstream/main' into readonly-allowedtypes
jenn-le Apr 4, 2025
a8e69d5
unannotate unit tests
jenn-le Apr 8, 2025
5724351
unannotate stuff
jenn-le Apr 9, 2025
984b829
clean up enablable
jenn-le Apr 9, 2025
4ecd8b7
unannote tests
jenn-le Apr 9, 2025
1be4f30
craig's overload workaround
jenn-le Apr 9, 2025
90737e6
accept annotations in object alpha
jenn-le Apr 14, 2025
bcecd66
add overloads for optional and required
jenn-le Apr 15, 2025
9a2036a
fix input for objectAlpha
jenn-le Apr 18, 2025
41f76db
test fixes
jenn-le Apr 21, 2025
e1acdd0
accept annotations in map alpha
jenn-le Apr 21, 2025
56f25e2
Merge remote-tracking branch 'upstream/main' into readonly-allowedtypes
jenn-le Apr 21, 2025
4d7fbfb
accept annotations in arrayalpha
jenn-le Apr 21, 2025
dc6e729
add comment
jenn-le Apr 24, 2025
8858102
format
jenn-le Apr 24, 2025
9c51607
fix test failure
jenn-le Apr 28, 2025
cf3984e
additional check
jenn-le Apr 28, 2025
7b0eeb7
format
jenn-le Apr 28, 2025
e272d83
Merge remote-tracking branch 'upstream/main' into readonly-allowedtypes
jenn-le May 6, 2025
f42b75e
some enablable tests
jenn-le May 7, 2025
fb1704f
temp
jenn-le May 7, 2025
ce39396
add tests and fix
jenn-le May 14, 2025
cc76230
clean up and more tests
jenn-le May 15, 2025
66021d5
add changeset
jenn-le May 15, 2025
c35b163
change upgrade token type
jenn-le May 15, 2025
5a42cef
add doc
jenn-le May 15, 2025
d792096
api update
jenn-le May 15, 2025
0d979aa
add comment
jenn-le May 15, 2025
866ef86
clean up
jenn-le May 15, 2025
f399aef
pr feedback
jenn-le May 16, 2025
df586bf
Merge remote-tracking branch 'upstream/main' into readonly-allowedtypes
jenn-le May 16, 2025
574fd6c
Update .changeset/curly-bikes-train.md
jenn-le May 19, 2025
56c8a8f
Update .changeset/curly-bikes-train.md
jenn-le May 19, 2025
2ebe5fb
Update .changeset/curly-bikes-train.md
jenn-le May 19, 2025
c2e0792
fix comment
jenn-le May 20, 2025
53b58f5
make schema upgrade a better placeholder
jenn-le May 20, 2025
364511d
add initialize view test
jenn-le May 21, 2025
3065dd5
non working test
jenn-le May 21, 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
26 changes: 26 additions & 0 deletions .changeset/curly-bikes-train.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
"fluid-framework": minor
"@fluidframework/tree": minor
"__section": feature
---
Adds enablable allowed types to SchemaFactoryAlpha

This adds the `enablable` API to [`SchemaFactoryAlpha`](https://fluidframework.com/docs/api/fluid-framework/schemafactoryalpha-class).
Enablables can be used for schema evolution to add members to an [`AllowedTypes`](https://fluidframework.com/docs/api/fluid-framework/allowedtypes-typealias) while supporting cross version collaboration.

Enablables are allowed types that can be enabled by schema upgrades.
Before being enabled, any attempt to insert or move a node to a location which requires the enablement for its type to be valid will throw an error.

To add a new member to an `AllowedTypes`, add the type wrapped by `enablable`.
For example, migrating an array which previously supported only numbers to support both numbers and strings would start by deploying a version of the app using `enablable`:
```typescript
schemaFactoryAlpha.arrayAlpha("TestArray", [schemaFactoryAlpha.number, schemaFactoryAlpha.enablable(schemaFactoryAlpha.string)]);
```

Once enough clients have this code update, it is safe to allow writing strings to the array.
To enable writing strings to the array, a code change must be made to remove the enablable annotation:
```typescript
schemaFactoryAlpha.arrayAlpha("TestArray", [schemaFactoryAlpha.number, schemaFactoryAlpha.string]);
Copy link
Contributor

Choose a reason for hiding this comment

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

I like the inclusion of an example. I think we should also have an integration test showing this feature end to end in actual code (something like what's in src/test/feature-libraries/modular-schema/schemaEvolutionExamples.spec.ts except targeting the public API, and less horrible).

Making an executable example as a test, then including the same example in the changeset or doc comments is a great pattern to help ensure everything works as promised.

I'd like to see a test with some end to end proof involving a schema upgrade showing the whole process working. If thats too big for the changeset, it can have a simplified version, but link to the full exampe code via a github link.

```

In the future, SharedTree may add an API that allows enablables to be enabled via a runtime schema upgrade so that the type can be more easily deployed using a configuration flag change rather than a code change.
1 change: 1 addition & 0 deletions packages/dds/tree/.vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"covariantly",
"deprioritized",
"enablable",
"enablables",
"endregion",
"fluidframework",
"insertable",
Expand Down
12 changes: 9 additions & 3 deletions packages/dds/tree/api-report/tree.alpha.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export function adaptEnum<TScope extends string, const TEnum extends Record<stri
// @alpha
export interface AllowedTypeMetadata {
readonly custom?: unknown;
readonly enablableSchemaUpgrade?: SchemaUpgrade;
}

// @public @system
Expand Down Expand Up @@ -421,17 +422,17 @@ export namespace JsonAsTree {
}
const // @system
_APIExtractorWorkaroundObjectBase: TreeNodeSchemaClass<"com.fluidframework.json.object", NodeKind.Map, System_Unsafe.TreeMapNodeUnsafe<readonly [LeafSchema<"null", null>, LeafSchema<"number", number>, LeafSchema<"string", string>, LeafSchema<"boolean", boolean>, () => typeof JsonObject, () => typeof Array]> & WithType<"com.fluidframework.json.object", NodeKind.Map, unknown>, {
[Symbol.iterator](): Iterator<[string, string | number | JsonObject | Array | System_Unsafe.InsertableTypedNodeUnsafe<LeafSchema<"boolean", boolean>, LeafSchema<"boolean", boolean>> | null], any, undefined>;
[Symbol.iterator](): Iterator<[string, string | number | System_Unsafe.InsertableTypedNodeUnsafe<LeafSchema<"boolean", boolean>, LeafSchema<"boolean", boolean>> | JsonObject | Array | null], any, undefined>;
} | {
readonly [x: string]: string | number | JsonObject | Array | System_Unsafe.InsertableTypedNodeUnsafe<LeafSchema<"boolean", boolean>, LeafSchema<"boolean", boolean>> | null;
readonly [x: string]: string | number | System_Unsafe.InsertableTypedNodeUnsafe<LeafSchema<"boolean", boolean>, LeafSchema<"boolean", boolean>> | JsonObject | Array | null;
}, false, readonly [LeafSchema<"null", null>, LeafSchema<"number", number>, LeafSchema<"string", string>, LeafSchema<"boolean", boolean>, () => typeof JsonObject, () => typeof Array], undefined>;
// (undocumented)
export type Primitive = TreeNodeFromImplicitAllowedTypes<typeof Primitive>;
// @system
export type _RecursiveArrayWorkaroundJsonArray = FixRecursiveArraySchema<typeof Array>;
const // @system
_APIExtractorWorkaroundArrayBase: TreeNodeSchemaClass<"com.fluidframework.json.array", NodeKind.Array, System_Unsafe.TreeArrayNodeUnsafe<readonly [LeafSchema<"null", null>, LeafSchema<"number", number>, LeafSchema<"string", string>, LeafSchema<"boolean", boolean>, () => typeof JsonObject, () => typeof Array]> & WithType<"com.fluidframework.json.array", NodeKind.Array, unknown>, {
[Symbol.iterator](): Iterator<string | number | JsonObject | Array | System_Unsafe.InsertableTypedNodeUnsafe<LeafSchema<"boolean", boolean>, LeafSchema<"boolean", boolean>> | null, any, undefined>;
[Symbol.iterator](): Iterator<string | number | System_Unsafe.InsertableTypedNodeUnsafe<LeafSchema<"boolean", boolean>, LeafSchema<"boolean", boolean>> | JsonObject | Array | null, any, undefined>;
}, false, readonly [LeafSchema<"null", null>, LeafSchema<"number", number>, LeafSchema<"string", string>, LeafSchema<"boolean", boolean>, () => typeof JsonObject, () => typeof Array], undefined>;
// (undocumented)
export type Tree = TreeNodeFromImplicitAllowedTypes<typeof Tree>;
Expand Down Expand Up @@ -760,6 +761,7 @@ export class SchemaFactory<out TScope extends string | undefined = string | unde
export class SchemaFactoryAlpha<out TScope extends string | undefined = string | undefined, TName extends number | string = string> extends SchemaFactory<TScope, TName> {
arrayAlpha<const Name extends TName, const T extends ImplicitAnnotatedAllowedTypes, const TCustomMetadata = unknown>(name: Name, allowedTypes: T, options?: NodeSchemaOptions<TCustomMetadata>): ArrayNodeCustomizableSchema<ScopedSchemaName<TScope, Name>, T, true, TCustomMetadata>;
arrayRecursive<const Name extends TName, const T extends System_Unsafe.ImplicitAllowedTypesUnsafe, const TCustomMetadata = unknown>(name: Name, allowedTypes: T, options?: NodeSchemaOptions<TCustomMetadata>): ArrayNodeCustomizableSchemaUnsafe<ScopedSchemaName<TScope, Name>, T, TCustomMetadata>;
enablable<const T extends TreeNodeSchema>(t: T | AnnotatedAllowedType<T>): AnnotatedAllowedType<T>;
static readonly identifier: <const TCustomMetadata = unknown>(props?: Omit<FieldProps_2<TCustomMetadata>, "defaultProvider"> | undefined) => FieldSchemaAlpha_2<FieldKind_2.Identifier, LeafSchema_2<"string", string> & SimpleLeafNodeSchema_2, TCustomMetadata>;
static readonly leaves: readonly [LeafSchema_2<"string", string> & SimpleLeafNodeSchema_2, LeafSchema_2<"number", number> & SimpleLeafNodeSchema_2, LeafSchema_2<"boolean", boolean> & SimpleLeafNodeSchema_2, LeafSchema_2<"null", null> & SimpleLeafNodeSchema_2, LeafSchema_2<"handle", IFluidHandle<unknown>> & SimpleLeafNodeSchema_2];
mapAlpha<Name extends TName, const T extends ImplicitAnnotatedAllowedTypes, const TCustomMetadata = unknown>(name: Name, allowedTypes: T, options?: NodeSchemaOptions<TCustomMetadata>): MapNodeCustomizableSchema<ScopedSchemaName<TScope, Name>, T, true, TCustomMetadata>;
Expand Down Expand Up @@ -805,6 +807,10 @@ export interface SchemaStatics {
readonly string: LeafSchema<"string", string>;
}

// @alpha @sealed
export class SchemaUpgrade {
}

// @alpha
export interface SchemaValidationFunction<Schema extends TSchema> {
check(data: unknown): data is Static<Schema>;
Expand Down
1 change: 1 addition & 0 deletions packages/dds/tree/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ export {
type InternalTreeNode,
type WithType,
type NodeChangedData,
type SchemaUpgrade,
// Types not really intended for public use, but used in links.
// Can not be moved to internalTypes since doing so causes app code to throw errors like:
// Error: src/simple-tree/objectNode.ts:72:1 - (ae-unresolved-link) The @link reference could not be resolved: The package "@fluidframework/tree" does not have an export "TreeNodeApi"
Expand Down
4 changes: 3 additions & 1 deletion packages/dds/tree/src/shared-tree/schematizingTreeView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,8 @@ export class SchematizingSimpleTreeView<
policy: this.schemaPolicy,
},
this,
// this allows enablable allowed types to be loaded into a tree
true,
);

initialize(this.checkout, {
Expand Down Expand Up @@ -462,7 +464,7 @@ export class SchematizingSimpleTreeView<
const view = this.getView();
setField(
view.context.root,
this.rootFieldSchema,
normalizeFieldSchema(this.rootFieldSchema),
newRoot as InsertableContent | undefined,
);
}
Expand Down
6 changes: 2 additions & 4 deletions packages/dds/tree/src/simple-tree/api/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,9 @@ export function cursorFromInsertable<
):
| ITreeCursorSynchronous
| (TSchema extends FieldSchema<FieldKind.Optional> ? undefined : never) {
const mapTree = mapTreeFromNodeData(
data as InsertableField<UnsafeUnknownSchema>,
schema,
const mapTree = mapTreeFromNodeData(data as InsertableField<UnsafeUnknownSchema>, schema, {
context,
);
});
if (mapTree === undefined) {
return undefined as TSchema extends FieldSchema<FieldKind.Optional> ? undefined : never;
}
Expand Down
45 changes: 38 additions & 7 deletions packages/dds/tree/src/simple-tree/api/schemaFactoryAlpha.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,19 @@ import {
type SchemaFactoryObjectOptions,
type ScopedSchemaName,
} from "./schemaFactory.js";
import type {
ImplicitAllowedTypes,
ImplicitAnnotatedAllowedTypes,
ImplicitAnnotatedFieldSchema,
ImplicitFieldSchema,
NodeSchemaOptions,
import {
createSchemaUpgrade,
normalizeToAnnotatedAllowedType,
type AnnotatedAllowedType,
type ImplicitAllowedTypes,
type ImplicitAnnotatedAllowedTypes,
type ImplicitAnnotatedFieldSchema,
type ImplicitFieldSchema,
type NodeSchemaOptions,
} from "../schemaTypes.js";
import { objectSchema } from "../objectNode.js";
import type { RestrictiveStringRecord } from "../../util/index.js";
import type { NodeKind, TreeNodeSchemaClass } from "../core/index.js";
import type { NodeKind, TreeNodeSchema, TreeNodeSchemaClass } from "../core/index.js";
import type {
ArrayNodeCustomizableSchemaUnsafe,
MapNodeCustomizableSchemaUnsafe,
Expand Down Expand Up @@ -51,6 +54,34 @@ export class SchemaFactoryAlpha<
) as ScopedSchemaName<TScope, Name>;
}

/**
* Declares a type enablable in a set of {@link AllowedTypes}.
*
* @remarks
* An allowed type that is enablable can be read from the document but cannot be written.
* Enablables add support for reading a type which can be used for schema evolution to add members to
* an {@link AllowedTypes} while supporting cross version collaboration.
*
* Once enough clients supporting reading the type, support for writing can be added by removing the use of
* `enablable` from the schema definition.
*
* A future change will allow writing the type using a runtime schema upgrade so that the type can be enabled
* using a configuration flag change rather than a code change.
*
*/
public enablable<const T extends TreeNodeSchema>(
t: T | AnnotatedAllowedType<T>,
): AnnotatedAllowedType<T> {
const annotatedType = normalizeToAnnotatedAllowedType(t);
return {
type: annotatedType.type,
metadata: {
...annotatedType.metadata,
enablableSchemaUpgrade: createSchemaUpgrade(),
},
};
}

/**
* Define a {@link TreeNodeSchemaClass} for a {@link TreeObjectNode}.
*
Expand Down
51 changes: 32 additions & 19 deletions packages/dds/tree/src/simple-tree/arrayNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -826,15 +826,15 @@ type Insertable<T extends ImplicitAllowedTypes> = readonly (
| IterableTreeArrayContent<InsertableTreeNodeFromImplicitAllowedTypes<T>>
)[];

abstract class CustomArrayNodeBase<const T extends ImplicitAllowedTypes>
abstract class CustomArrayNodeBase<const T extends ImplicitAnnotatedAllowedTypes>
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd like to see a unit test added to src/test/simple-tree/arrayNode.spec.ts testing the case of moving a node to a location where it requires enablement.

Such moves need to error if the type has not been enabled for its destination yet. This might work as is, but should have a test to ensure it works and stays working.

We should also test such a move after the enablement has happened (another client did a schema upgrade). I'm not actually sure if the intention is to allow such a move or not (clarity on what is allowed in this case is missing from the docs) but either way a test sould be good.

Copy link
Contributor

Choose a reason for hiding this comment

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

We should also test such a move after the enablement has happened (another client did a schema upgrade). I'm not actually sure if the intention is to allow such a move or not.

This out of my lane, but my two cents is that it would be preferrable to NOT allow such a move since that would complicate the guarantees associated with this feature. No matter what we pick, I would bet that the vast majority of app authors would looks at a client whose view schema has enablable(Foo) to think "this client couldn't have attached any Foo instances in this field", so we may as well pick the implementation choice that matches that. I may be missing some strong incentive to go the other way though.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thats fine with me. As long as we have some policy, which is documented and tested, and it ensures we don't corrupt documents with out of schema items getting moved in, I'm happy. Yann's suggestion sounds like a fine choice as long as we test and enforce it.

extends TreeNodeWithArrayFeatures<
Iterable<InsertableTreeNodeFromImplicitAllowedTypes<T>>,
TreeNodeFromImplicitAllowedTypes<T>
Iterable<InsertableTreeNodeFromImplicitAllowedTypes<UnannotateImplicitAllowedTypes<T>>>,
TreeNodeFromImplicitAllowedTypes<UnannotateImplicitAllowedTypes<T>>
>
implements TreeArrayNode<T>
implements TreeArrayNode<UnannotateImplicitAllowedTypes<T>>
{
// Indexing must be provided by subclass.
[k: number]: TreeNodeFromImplicitAllowedTypes<T>;
[k: number]: TreeNodeFromImplicitAllowedTypes<UnannotateImplicitAllowedTypes<T>>;

public static readonly kind = NodeKind.Array;

Expand All @@ -847,12 +847,16 @@ abstract class CustomArrayNodeBase<const T extends ImplicitAllowedTypes>
>;

public constructor(
input?: Iterable<InsertableTreeNodeFromImplicitAllowedTypes<T>> | InternalTreeNode,
input?:
| Iterable<InsertableTreeNodeFromImplicitAllowedTypes<UnannotateImplicitAllowedTypes<T>>>
| InternalTreeNode,
) {
super(input ?? []);
}

#mapTreesFromFieldData(value: Insertable<T>): ExclusiveMapTree[] {
#mapTreesFromFieldData(
value: Insertable<UnannotateImplicitAllowedTypes<T>>,
): ExclusiveMapTree[] {
const sequenceField = getSequenceField(this);
const content = value as readonly (
| InsertableContent
Expand Down Expand Up @@ -884,7 +888,9 @@ abstract class CustomArrayNodeBase<const T extends ImplicitAllowedTypes>
return fail(0xadb /* Proxy should intercept length */);
}

public [Symbol.iterator](): IterableIterator<TreeNodeFromImplicitAllowedTypes<T>> {
public [Symbol.iterator](): IterableIterator<
TreeNodeFromImplicitAllowedTypes<UnannotateImplicitAllowedTypes<T>>
> {
return this.values();
}

Expand All @@ -896,28 +902,33 @@ abstract class CustomArrayNodeBase<const T extends ImplicitAllowedTypes>
}

public at(
this: TreeArrayNode<T>,
this: TreeArrayNode<UnannotateImplicitAllowedTypes<T>>,
index: number,
): TreeNodeFromImplicitAllowedTypes<T> | undefined {
): TreeNodeFromImplicitAllowedTypes<UnannotateImplicitAllowedTypes<T>> | undefined {
const field = getSequenceField(this);
const val = field.boxedAt(index);

if (val === undefined) {
return val;
}

return getOrCreateNodeFromInnerNode(val) as TreeNodeFromImplicitAllowedTypes<T>;
return getOrCreateNodeFromInnerNode(val) as TreeNodeFromImplicitAllowedTypes<
UnannotateImplicitAllowedTypes<T>
>;
}
public insertAt(index: number, ...value: Insertable<T>): void {
public insertAt(
index: number,
...value: Insertable<UnannotateImplicitAllowedTypes<T>>
): void {
const field = getSequenceField(this);
validateIndex(index, field, "insertAt", true);
const content = this.#mapTreesFromFieldData(value);
field.editor.insert(index, content);
}
public insertAtStart(...value: Insertable<T>): void {
public insertAtStart(...value: Insertable<UnannotateImplicitAllowedTypes<T>>): void {
this.insertAt(0, ...value);
}
public insertAtEnd(...value: Insertable<T>): void {
public insertAtEnd(...value: Insertable<UnannotateImplicitAllowedTypes<T>>): void {
this.insertAt(this.length, ...value);
}
public removeAt(index: number): void {
Expand Down Expand Up @@ -1057,12 +1068,14 @@ abstract class CustomArrayNodeBase<const T extends ImplicitAllowedTypes>
}
}

public values(): IterableIterator<TreeNodeFromImplicitAllowedTypes<T>> {
public values(): IterableIterator<
TreeNodeFromImplicitAllowedTypes<UnannotateImplicitAllowedTypes<T>>
> {
return this.generateValues(getKernel(this).generationNumber);
}
private *generateValues(
initialLastUpdatedStamp: number,
): Generator<TreeNodeFromImplicitAllowedTypes<T>> {
): Generator<TreeNodeFromImplicitAllowedTypes<UnannotateImplicitAllowedTypes<T>>> {
const kernel = getKernel(this);
if (initialLastUpdatedStamp !== kernel.generationNumber) {
throw new UsageError(`Concurrent editing and iteration is not allowed.`);
Expand Down Expand Up @@ -1113,7 +1126,7 @@ export function arraySchema<

// This class returns a proxy from its constructor to handle numeric indexing.
// Alternatively it could extend a normal class which gets tons of numeric properties added.
class Schema extends CustomArrayNodeBase<UnannotateImplicitAllowedTypes<T>> {
class Schema extends CustomArrayNodeBase<T> {
public static override prepareInstance<T2>(
this: typeof TreeNodeValid<T2>,
instance: TreeNodeValid<T2>,
Expand Down Expand Up @@ -1200,8 +1213,8 @@ export function arraySchema<
return Schema.constructorCached?.constructor as unknown as Output;
}

protected get simpleSchema(): UnannotateImplicitAllowedTypes<T> {
return unannotatedTypes;
protected get simpleSchema(): T {
return info;
}
protected get allowedTypes(): ReadonlySet<TreeNodeSchema> {
return lazyChildTypes.value;
Expand Down
1 change: 1 addition & 0 deletions packages/dds/tree/src/simple-tree/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ export {
type AllowedTypes,
type AllowedTypeMetadata,
type AllowedTypesMetadata,
type SchemaUpgrade,
FieldKind,
FieldSchema,
type FieldSchemaAlpha,
Expand Down
5 changes: 4 additions & 1 deletion packages/dds/tree/src/simple-tree/mapNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,10 @@ export function mapSchema<
): UnhydratedFlexTreeNode {
return UnhydratedFlexTreeNode.getOrCreate(
unhydratedContext,
mapTreeFromNodeData(input as FactoryContent, this as unknown as ImplicitAllowedTypes),
mapTreeFromNodeData(
input as FactoryContent,
this as unknown as ImplicitAnnotatedAllowedTypes,
),
);
}

Expand Down
Loading