Skip to content

Commit

Permalink
tree: Move TreeNodeSchema and TreeNode to core (microsoft#22161)
Browse files Browse the repository at this point in the history
## Description

Move TreeNodeSchema, TreeNode and some more related code to core.

Originally I planned to leave NodeKind and TreeNodeSchema out of core,

but this ended up preventing generic TreeNode code (like TreeNode
itself) from moving to core, so this decision was reversed.

Stuff specific to individual node kinds other than the definition in the
enum, will still be kept out of core.

Note that FieldKinds and all field related stuff is part of ObjectNode,
and thus not core.
  • Loading branch information
CraigMacomber authored Aug 8, 2024
1 parent e1c546c commit ba440c1
Show file tree
Hide file tree
Showing 36 changed files with 651 additions and 568 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import { UsageError } from "@fluidframework/telemetry-utils/internal";
import { fail } from "../../util/index.js";

import type { SchemaFactory, ScopedSchemaName } from "./schemaFactory.js";
import type { NodeFromSchema, NodeKind, TreeNodeSchemaClass } from "../schemaTypes.js";
import type { TreeNode } from "../types.js";
import type { NodeFromSchema } from "../schemaTypes.js";
import type { NodeKind, TreeNodeSchemaClass, TreeNode } from "../core/index.js";
import type {
InsertableObjectFromSchemaRecord,
ObjectFromSchemaRecord,
Expand Down
13 changes: 8 additions & 5 deletions packages/dds/tree/src/simple-tree/api/schemaFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,18 @@ import {
type ImplicitAllowedTypes,
type ImplicitFieldSchema,
type InsertableTreeNodeFromImplicitAllowedTypes,
type NodeKind,
type TreeNodeSchema,
type TreeNodeSchemaClass,
type FieldProps,
createFieldSchema,
type DefaultProvider,
getDefaultProvider,
} from "../schemaTypes.js";
import type { WithType } from "../core/index.js";
import { inPrototypeChain } from "../core/index.js";
import type {
NodeKind,
WithType,
TreeNodeSchema,
TreeNodeSchemaClass,
} from "../core/index.js";
import { type TreeArrayNode, arraySchema } from "../arrayNode.js";
import {
type InsertableObjectFromSchemaRecord,
Expand All @@ -70,7 +73,7 @@ import type {
TreeObjectNodeUnsafe,
} from "../typesUnsafe.js";
import { createFieldSchemaUnsafe } from "./schemaFactoryRecursive.js";
import { inPrototypeChain, TreeNodeValid } from "../types.js";
import { TreeNodeValid } from "../treeNodeValid.js";
/**
* Gets the leaf domain schema compatible with a given {@link TreeValue}.
*/
Expand Down
12 changes: 7 additions & 5 deletions packages/dds/tree/src/simple-tree/api/schemaFactoryRecursive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ import {
type ImplicitAllowedTypes,
type ImplicitFieldSchema,
type InsertableTreeNodeFromImplicitAllowedTypes,
type NodeKind,
type TreeNodeSchemaClass,
type TreeNodeSchema,
} from "../schemaTypes.js";
import type { WithType } from "../core/index.js";
import type { TreeNode } from "../types.js";
import type {
NodeKind,
TreeNodeSchemaClass,
TreeNodeSchema,
WithType,
TreeNode,
} from "../core/index.js";
import type { FieldSchemaUnsafe } from "../typesUnsafe.js";

export function createFieldSchemaUnsafe<
Expand Down
4 changes: 3 additions & 1 deletion packages/dds/tree/src/simple-tree/api/testRecursiveDomain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ import { SchemaFactory } from "./schemaFactory.js";
// If we let TypeScript generate these includes, they use relative paths which break API extractor's rollup.
// API-Extractor issue: https://github.com/microsoft/rushstack/issues/4507
// eslint-disable-next-line @typescript-eslint/no-unused-vars, unused-imports/no-unused-imports
import type { FieldKind, NodeKind } from "../schemaTypes.js";
import type { FieldKind } from "../schemaTypes.js";
// eslint-disable-next-line @typescript-eslint/no-unused-vars, unused-imports/no-unused-imports
import type { NodeKind } from "../core/index.js";

const builder = new SchemaFactory("Test Recursive Domain");

Expand Down
3 changes: 1 addition & 2 deletions packages/dds/tree/src/simple-tree/api/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,14 @@ import type { RevertibleFactory } from "../../shared-tree/index.js";

import {
type ImplicitAllowedTypes,
NodeKind,
normalizeFieldSchema,
type ImplicitFieldSchema,
type InsertableTreeFieldFromImplicitField,
type TreeFieldFromImplicitField,
type TreeNodeSchema,
FieldKind,
normalizeAllowedTypes,
} from "../schemaTypes.js";
import { NodeKind, type TreeNodeSchema } from "../core/index.js";
import { toFlexSchema } from "../toFlexSchema.js";
import { LeafNodeSchema } from "../leafNodeSchema.js";
import { assert } from "@fluidframework/core-utils/internal";
Expand Down
15 changes: 11 additions & 4 deletions packages/dds/tree/src/simple-tree/api/treeNodeApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,12 @@ import { fail, extractFromOpaque, isReadonlyArray } from "../../util/index.js";
import { getOrCreateNodeFromFlexTreeNode } from "../proxies.js";
import { getOrCreateInnerNode } from "../proxyBinding.js";
import {
NodeKind,
type TreeLeafValue,
type TreeNodeSchema,
type ImplicitFieldSchema,
FieldSchema,
type ImplicitAllowedTypes,
type TreeNodeFromImplicitAllowedTypes,
} from "../schemaTypes.js";
import { type TreeNode, type TreeChangeEvents, tryGetTreeNodeSchema } from "../types.js";
import {
booleanSchema,
handleSchema,
Expand All @@ -38,7 +35,15 @@ import {
import { isFluidHandle } from "@fluidframework/runtime-utils/internal";
import { UsageError } from "@fluidframework/telemetry-utils/internal";
import type { Off } from "../../events/index.js";
import { getKernel, isTreeNode } from "../core/index.js";
import {
getKernel,
isTreeNode,
type TreeNodeSchema,
NodeKind,
type TreeNode,
type TreeChangeEvents,
tryGetTreeNodeSchema,
} from "../core/index.js";

/**
* Provides various functions for analyzing {@link TreeNode}s.
Expand Down Expand Up @@ -186,6 +191,8 @@ export const treeNodeApi: TreeNodeApi = {
}
return false;
} else {
// Linter is incorrect about this bering unnecessary: it does not compile without the type assertion.
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
return (schema as TreeNodeSchema) === actualSchema;
}
},
Expand Down
16 changes: 8 additions & 8 deletions packages/dds/tree/src/simple-tree/arrayNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,27 +23,27 @@ import {
} from "./proxies.js";
import { getOrCreateInnerNode } from "./proxyBinding.js";
import {
NodeKind,
type ImplicitAllowedTypes,
type InsertableTreeNodeFromImplicitAllowedTypes,
type TreeNodeFromImplicitAllowedTypes,
type TreeNodeSchemaClass,
type TreeNodeSchema,
normalizeFieldSchema,
} from "./schemaTypes.js";
import { type WithType, typeNameSymbol } from "./core/index.js";
import { mapTreeFromNodeData } from "./toMapTree.js";
import {
type WithType,
typeNameSymbol,
NodeKind,
type TreeNode,
TreeNodeValid,
type InternalTreeNode,
type MostDerivedData,
} from "./types.js";
type TreeNodeSchemaClass,
type TreeNodeSchema,
} from "./core/index.js";
import { mapTreeFromNodeData } from "./toMapTree.js";
import { fail } from "../util/index.js";
import { getFlexSchema } from "./toFlexSchema.js";
import { UsageError } from "@fluidframework/telemetry-utils/internal";
import { assert } from "@fluidframework/core-utils/internal";
import { getKernel } from "./core/index.js";
import { TreeNodeValid, type MostDerivedData } from "./treeNodeValid.js";

/**
* A generic array type, used to defined types like {@link (TreeArrayNode:interface)}.
Expand Down
6 changes: 3 additions & 3 deletions packages/dds/tree/src/simple-tree/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
Core logic for simple tree.
This is what the rest of simple-tree is build on, and thus to avoid cyclic deps, this should not depend on other parts of simple-tree.

This specifically does not contain any logic specific fo node kinds or field kinds: everything here should generically apply all of simple-tree's cases.
This specifically does not contain any logic specific to field kinds or node kinds (other than the NodeKinds enum definition which is required as part of TreeNodeSchema since the set of NodeKinds is not extensible): everything here should generically apply all of simple-tree's cases.

## Status

Currently this has some type dependencies on the rest of simple-tree, but not runtime dependencies.
This is not ideal, and reducing these is a work in progress.
More content should be moved into this directory as its disentangled from node kind specific logic.
`proxyBinding.ts` is a good candidate to work toward moving here to.
28 changes: 27 additions & 1 deletion packages/dds/tree/src/simple-tree/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,31 @@
* Licensed under the MIT License.
*/

export { isTreeNode, TreeNodeKernel, getKernel } from "./treeNodeKernel.js";
export {
isTreeNode,
TreeNodeKernel,
getKernel,
tryGetTreeNodeSchema,
} from "./treeNodeKernel.js";
export { type WithType, typeNameSymbol } from "./withType.js";
export {
type TreeChangeEvents,
TreeNode,
type Unhydrated,
inPrototypeChain,
type InternalTreeNode,
privateToken,
} from "./types.js";
export {
type TreeNodeSchema,
NodeKind,
type TreeNodeSchemaClass,
type TreeNodeSchemaNonClass,
type TreeNodeSchemaCore,
} from "./treeNodeSchema.js";
export {
getSimpleNodeSchema,
setFlexSchemaFromClassSchema,
tryGetSimpleNodeSchema,
cachedFlexSchemaFromClassSchema,
} from "./schemaCaching.js";
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,10 @@

import { assert } from "@fluidframework/core-utils/internal";

import type {
FlexFieldSchema,
FlexTreeNodeSchema,
TreeNodeSchemaBase,
} from "../feature-libraries/index.js";
import { fail } from "../util/index.js";
import type { FlexTreeNodeSchema, TreeNodeSchemaBase } from "../../feature-libraries/index.js";
import { fail } from "../../util/index.js";

import {
type FieldSchema,
type ImplicitFieldSchema,
type TreeNodeSchema,
normalizeFieldSchema,
} from "./schemaTypes.js";
import type { TreeNodeSchema } from "./treeNodeSchema.js";

/**
* A symbol for storing FlexTreeSchema on TreeNodeSchema.
Expand All @@ -30,11 +21,6 @@ const flexSchemaSymbol: unique symbol = Symbol(`flexSchema`);
*/
const simpleNodeSchemaSymbol: unique symbol = Symbol(`simpleNodeSchema`);

/**
* A symbol for storing {@link FieldSchema}s on a {@link FlexFieldSchema}.
*/
const simpleFieldSchemaSymbol: unique symbol = Symbol(`simpleFieldSchema`);

export function cachedFlexSchemaFromClassSchema(
schema: TreeNodeSchema,
): TreeNodeSchemaBase | undefined {
Expand Down Expand Up @@ -74,23 +60,3 @@ export function tryGetSimpleNodeSchema(
export function getSimpleNodeSchema(flexSchema: FlexTreeNodeSchema): TreeNodeSchema {
return tryGetSimpleNodeSchema(flexSchema) ?? fail("missing simple schema");
}

/**
* Gets the {@link FieldSchema} which corresponds with the provided {@link FlexFieldSchema | flexSchema}.
* Caches the result on the provided `flexSchema` for future access.
* @param flexSchema - The flex schema on which the result will be cached.
* @param implicitSimpleSchema - The allowed types from which the `FieldSchema` will be derived.
*/
export function getSimpleFieldSchema(
flexSchema: FlexFieldSchema,
implicitSimpleSchema: ImplicitFieldSchema,
): FieldSchema {
if (simpleFieldSchemaSymbol in flexSchema) {
return flexSchema[simpleFieldSchemaSymbol] as FieldSchema;
}

const fieldSchema = normalizeFieldSchema(implicitSimpleSchema);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(flexSchema as any)[simpleFieldSchemaSymbol] = fieldSchema;
return fieldSchema;
}
22 changes: 20 additions & 2 deletions packages/dds/tree/src/simple-tree/core/treeNodeKernel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import { assert } from "@fluidframework/core-utils/internal";
import { createEmitter, type Listenable, type Off } from "../../events/index.js";
import type { TreeChangeEvents, TreeNode, Unhydrated } from "../types.js";
import type { TreeChangeEvents, TreeNode, Unhydrated } from "./types.js";
import type { AnchorNode } from "../../core/index.js";
import {
flexTreeSlot,
Expand All @@ -14,6 +14,7 @@ import {
TreeStatus,
treeStatusFromAnchorCache,
} from "../../feature-libraries/index.js";
import type { NodeKind, TreeNodeSchema } from "./treeNodeSchema.js";

const treeNodeToKernel = new WeakMap<TreeNode, TreeNodeKernel>();

Expand Down Expand Up @@ -41,6 +42,20 @@ export function isTreeNode(candidate: unknown): candidate is TreeNode | Unhydrat
return treeNodeToKernel.has(candidate as TreeNode);
}

/**
* Returns a schema for a value if the value is a {@link TreeNode}.
*
* Returns undefined for other values.
* @remarks
* Does not give schema for a {@link TreeLeafValue}.
*/
export function tryGetTreeNodeSchema<T>(
value: T,
): undefined | TreeNodeSchema<string, NodeKind, unknown, T> {
const kernel = treeNodeToKernel.get(value as TreeNode);
return kernel?.schema as undefined | TreeNodeSchema<string, NodeKind, unknown, T>;
}

/**
* Contains state and an internal API for managing {@link TreeNode}s.
* @remarks All {@link TreeNode}s have an associated kernel object.
Expand All @@ -59,7 +74,10 @@ export class TreeNodeKernel implements Listenable<TreeChangeEvents> {
* @remarks
* Exactly one kernel per TreeNode should be created.
*/
public constructor(public readonly node: TreeNode) {
public constructor(
public readonly node: TreeNode,
public readonly schema: TreeNodeSchema,
) {
assert(!treeNodeToKernel.has(node), "only one kernel per node can be made");
treeNodeToKernel.set(node, this);
}
Expand Down
Loading

0 comments on commit ba440c1

Please sign in to comment.