Skip to content

Commit a620489

Browse files
tree2: Class based schema (#18350)
## Description To support recursive schema using classes for the schema is useful to work around microsoft/TypeScript#55832 . If we are going to use classes to enable that workaround, and also create a separate simple-tree specific schema system, there are some other interesting approaches which can be taken. If we don't support "Any" as a type allowed in fields, then all schema are reachable from the root. This means we can skip the schema library concept, and just pass in the root schema in the configuration. This has the side-effect of making the actual schema objects available programmatically be the actual class objects declared in the schema file (instead of the classes which those extend, which is what a library would have captured). This same reachability from the root also opens the door to supporting contextual view schema where the same type in different locations in the tree could have different schema subclasses, and its parent would unambiguously determine which to use for it. Thus all together this approach makes it possible to make the tree nodes instances of classes which the user declared. This has several benefits: - Users don't have to use "typeof" or invoke any type meta-functions to get the node types they want to pass around: just use the class/schema name as the type. - Intelisense is much cleaner when referring to types defined in schema: It just uses the class name. or "typeof ClassName". (These simplifications are what resolve the d.ts issue noted above with recursive types) - normal JS/TS type narrowing with `instanceof` can be used with schema defined types. - Its possible to add view/session local state to instances as properties, as well as adding methods by just putting them in the class like any other class. - Since the schema/class defines the API for the node, the implementation of the tree API implicitly gets defined in a way that is trivially extensible for adding new node kinds. TODO: - [X] support recursive times. - [x] implement runtime behavior for object fields. - [X] implement runtime behavior for list indexing. - [x] structurally typed maps and lists. - [x] runtime constructing of hydrated nodes (ensure flex tree is type erased). - runtime constructing and later hydrating of unhydrated nodes. - [x] objects - [ ] map - [ ] list - [x] Generation of view schema from simple schema. - [x] Implement full simple-tree API surface - [ ] Evaluate impact on simple-tree API (for example exposing of prototypes impacting deep equals). - [x] Basic end to end testing - [ ] Full unit testing. ## Breaking Changes The `schematize` API for simple-trees not using the new class based APIs has been renamed to `schematizeOld`: either rename the call site or migrate to the new class based schema. Some types which would have collided have their old simple-tree or flex-tree non class based version's renamed: ```typeScript TreeNodeSchema as FlexTreeNodeSchema, TreeListNode as TreeListNodeOld, Tree as TreeOld, TreeApi as TreeApiOld, TreeView as TreeViewOld, ``` The previous version of all these names are now used for class based APIs.
1 parent cc65b92 commit a620489

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+3075
-309
lines changed

examples/apps/tree-comparison/src/model/newTreeInventoryList.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ export class NewTreeInventoryList extends DataObject implements IInventoryList {
152152
// 3. On all loads, gets an (untyped) view of the data (the contents can't be accessed directly from the sharedTree).
153153
// Then the root2() call applies a typing to the untyped view based on our schema. After that we can actually
154154
// reach in and grab the inventoryItems list.
155-
this._inventoryItemList = this.sharedTree.schematize({
155+
this._inventoryItemList = this.sharedTree.schematizeOld({
156156
initialTree: {
157157
inventoryItemList: {
158158
// TODO: The list type unfortunately needs this "" key for now, but it's supposed to go away soon.

examples/data-objects/inventory-app/src/inventoryList.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,24 @@
66
import { DataObject, DataObjectFactory } from "@fluidframework/aqueduct";
77
import {
88
ForestType,
9-
ISharedTree,
10-
SharedTreeFactory,
11-
TreeView,
9+
TreeFactory,
10+
TreeViewOld,
1211
typeboxValidator,
12+
ITree,
1313
} from "@fluid-experimental/tree2";
1414
import { IFluidHandle } from "@fluidframework/core-interfaces";
1515
import { Inventory, treeConfiguration } from "./schema";
1616

1717
const treeKey = "tree";
1818

19-
const factory = new SharedTreeFactory({
19+
const factory = new TreeFactory({
2020
jsonValidator: typeboxValidator,
2121
forest: ForestType.Reference,
2222
});
2323

2424
export class InventoryList extends DataObject {
25-
#tree?: ISharedTree;
26-
#view?: TreeView<Inventory>;
25+
#tree?: ITree;
26+
#view?: TreeViewOld<Inventory>;
2727

2828
public get inventory(): Inventory {
2929
if (this.#view === undefined)
@@ -32,12 +32,12 @@ export class InventoryList extends DataObject {
3232
}
3333

3434
protected async initializingFirstTime() {
35-
this.#tree = this.runtime.createChannel(undefined, factory.type) as ISharedTree;
35+
this.#tree = this.runtime.createChannel(undefined, factory.type) as ITree;
3636
this.root.set(treeKey, this.#tree.handle);
3737
}
3838

3939
protected async initializingFromExisting() {
40-
const handle = this.root.get<IFluidHandle<ISharedTree>>(treeKey);
40+
const handle = this.root.get<IFluidHandle<ITree>>(treeKey);
4141
if (handle === undefined)
4242
throw new Error("map should be populated on creation by 'initializingFirstTime'");
4343
this.#tree = await handle.get();

examples/data-objects/inventory-app/src/schema.ts

Lines changed: 13 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,33 +3,23 @@
33
* Licensed under the MIT License.
44
*/
55

6-
import {
7-
AllowedUpdateType,
8-
buildTreeConfiguration,
9-
TypedNode,
10-
SchemaBuilder,
11-
} from "@fluid-experimental/tree2";
6+
import { TreeConfiguration, SchemaFactory } from "@fluid-experimental/tree2";
127

13-
const builder = new SchemaBuilder({ scope: "com.contoso.app.inventory" });
8+
const builder = new SchemaFactory("com.contoso.app.inventory");
149

15-
export type Part = TypedNode<typeof Part>;
16-
export const Part = builder.object("Part", {
10+
export class Part extends builder.object("Part", {
1711
name: builder.string,
1812
quantity: builder.number,
19-
});
20-
21-
export type Inventory = TypedNode<typeof Inventory>;
22-
export const Inventory = builder.object("Inventory", {
13+
}) {}
14+
export class Inventory extends builder.object("Inventory", {
2315
parts: builder.list(Part),
24-
});
16+
}) {}
2517

26-
export const treeConfiguration = buildTreeConfiguration({
27-
schema: builder.intoSchema(Inventory),
28-
allowedSchemaModifications: AllowedUpdateType.None,
29-
initialTree: {
30-
parts: {
31-
// TODO: FieldNodes should not require wrapper object
32-
"": [
18+
export const treeConfiguration = new TreeConfiguration(
19+
Inventory,
20+
() =>
21+
new Inventory({
22+
parts: [
3323
{
3424
name: "nut",
3525
quantity: 0,
@@ -39,6 +29,5 @@ export const treeConfiguration = buildTreeConfiguration({
3929
quantity: 0,
4030
},
4131
],
42-
},
43-
},
44-
});
32+
}),
33+
);

examples/version-migration/tree-shim/src/model/newTreeInventoryListController.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ export class NewTreeInventoryListController extends EventEmitter implements IInv
8787
// TODO: See note in inventoryList.ts for why this duplicative schematizeView call is here.
8888
// TODO: initial tree type - and revisit if we get separate schematize/initialize calls
8989
public static initializeTree(tree: ISharedTree, initialTree?: any): void {
90-
const view = tree.schematize({
90+
const view = tree.schematizeOld({
9191
initialTree: initialTree ?? {
9292
inventoryItemList: {
9393
// TODO: The list type unfortunately needs this "" key for now, but it's supposed to go away soon.
@@ -128,7 +128,7 @@ export class NewTreeInventoryListController extends EventEmitter implements IInv
128128
// 3. On all loads, gets an (untyped) view of the data (the contents can't be accessed directly from the sharedTree).
129129
// Then the root2() call applies a typing to the untyped view based on our schema. After that we can actually
130130
// reach in and grab the inventoryItems list.
131-
this._inventoryItemList = this._tree.schematize({
131+
this._inventoryItemList = this._tree.schematizeOld({
132132
initialTree: {
133133
inventoryItemList: {
134134
// TODO: The list type unfortunately needs this "" key for now, but it's supposed to go away soon.

experimental/PropertyDDS/packages/property-shared-tree-interop/src/schemaConverter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
SchemaBuilder,
1212
FieldKind,
1313
Any,
14-
TreeNodeSchema,
14+
FlexTreeNodeSchema as TreeNodeSchema,
1515
LazyTreeNodeSchema,
1616
brand,
1717
Brand,

0 commit comments

Comments
 (0)