Skip to content

Commit

Permalink
refactor(tree): Extract leaf schemas into their own module (#20289)
Browse files Browse the repository at this point in the history
Extracted from #20271
  • Loading branch information
Josmithr authored Mar 23, 2024
1 parent c2a04ca commit d9aa400
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 59 deletions.
70 changes: 70 additions & 0 deletions packages/dds/tree/src/simple-tree/leafNodeSchema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*!
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
* Licensed under the MIT License.
*/

import { assert } from "@fluidframework/core-utils";
import { TreeNodeSchemaIdentifier, TreeValue } from "../core/index.js";
import { leaf } from "../domains/index.js";
import {
LeafNodeSchema as FlexLeafNodeSchema,
FlexTreeNode,
isFlexTreeNode,
valueSchemaAllows,
} from "../feature-libraries/index.js";
import { NodeKind, TreeNodeSchema, TreeNodeSchemaNonClass } from "./schemaTypes.js";
import { setFlexSchemaFromClassSchema } from "./toFlexSchema.js";

type UnbrandedName<T extends FlexLeafNodeSchema> = T["name"] extends TreeNodeSchemaIdentifier<
infer Name extends string
>
? Name
: T["name"];

/**
* Instances of this class are schema for leaf nodes.
* @remarks
* Unlike other schema, leaf schema are class instances instead of classes themselves.
* This is because the instance type (the tree node type) for leaves are not objects,
* so those instances can't be instances of a schema based class.
* @privateRemarks
* This class refers to the underlying flex tree schema in its constructor, so this class can't be included in the package API.
*/
class LeafNodeSchema<T extends FlexLeafNodeSchema>
implements TreeNodeSchemaNonClass<UnbrandedName<T>, NodeKind.Leaf, TreeValue<T["info"]>>
{
public readonly identifier: UnbrandedName<T>;
public readonly kind = NodeKind.Leaf;
public readonly info: T["info"];
public readonly implicitlyConstructable = true as const;
public create(data: TreeValue<T["info"]> | FlexTreeNode): TreeValue<T["info"]> {
if (isFlexTreeNode(data)) {
const value = data.value;
assert(valueSchemaAllows(this.info, value), "invalid value");
return value;
}
return data;
}

public constructor(schema: T) {
setFlexSchemaFromClassSchema(this, schema);
this.identifier = schema.name as UnbrandedName<T>;
this.info = schema.info;
}
}

/**
* Wrapper around LeafNodeSchema's constructor that provides the return type that is desired in the package public API.
*/
function makeLeaf<T extends FlexLeafNodeSchema>(
schema: T,
): TreeNodeSchema<UnbrandedName<T>, NodeKind.Leaf, TreeValue<T["info"]>, TreeValue<T["info"]>> {
return new LeafNodeSchema(schema);
}

// Leaf schema shared between all SchemaFactory instances.
export const stringSchema = makeLeaf(leaf.string);
export const numberSchema = makeLeaf(leaf.number);
export const booleanSchema = makeLeaf(leaf.boolean);
export const nullSchema = makeLeaf(leaf.null);
export const handleSchema = makeLeaf(leaf.handle);
67 changes: 8 additions & 59 deletions packages/dds/tree/src/simple-tree/schemaFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@
import { assert, unreachableCase } from "@fluidframework/core-utils";
import { UsageError } from "@fluidframework/telemetry-utils";
import { TreeNodeSchemaIdentifier, TreeValue } from "../core/index.js";
import { leaf } from "../domains/index.js";
import {
FlexFieldNodeSchema,
LeafNodeSchema as FlexLeafNodeSchema,
FlexMapNodeSchema,
FlexObjectNodeSchema,
FlexTreeNode,
Expand All @@ -18,9 +16,15 @@ import {
isLazy,
markEager,
typeNameSymbol,
valueSchemaAllows,
} from "../feature-libraries/index.js";
import { RestrictiveReadonlyRecord, getOrCreate, isReadonlyArray } from "../util/index.js";
import {
booleanSchema,
handleSchema,
nullSchema,
numberSchema,
stringSchema,
} from "./leafNodeSchema.js";
import {
arrayNodePrototypeProperties,
createArrayNodeProxy,
Expand All @@ -45,62 +49,13 @@ import {
TreeMapNode,
TreeNodeSchema,
TreeNodeSchemaClass,
TreeNodeSchemaNonClass,
WithType,
type,
} from "./schemaTypes.js";
import { getFlexSchema, setFlexSchemaFromClassSchema } from "./toFlexSchema.js";
import { getFlexSchema } from "./toFlexSchema.js";
import { TreeArrayNode } from "./treeArrayNode.js";
import { TreeNode } from "./types.js";

/**
* Instances of this class are schema for leaf nodes.
* @remarks
* Unlike other schema, leaf schema are class instances instead of classes themselves.
* This is because the instance type (the tree node type) for leaves are not objects,
* so those instances can't be instances of a schema based class.
* @privateRemarks
* This class refers to the underlying flex tree schema in its constructor, so this class can't be included in the package API.
*/
class LeafNodeSchema<T extends FlexLeafNodeSchema>
implements TreeNodeSchemaNonClass<UnbrandedName<T>, NodeKind.Leaf, TreeValue<T["info"]>>
{
public readonly identifier: UnbrandedName<T>;
public readonly kind = NodeKind.Leaf;
public readonly info: T["info"];
public readonly implicitlyConstructable = true as const;
public create(data: TreeValue<T["info"]> | FlexTreeNode): TreeValue<T["info"]> {
if (isFlexTreeNode(data)) {
const value = data.value;
assert(valueSchemaAllows(this.info, value), "invalid value");
return value;
}
return data;
}

public constructor(schema: T) {
setFlexSchemaFromClassSchema(this, schema);
this.identifier = schema.name as UnbrandedName<T>;
this.info = schema.info;
}
}

/**
* Wrapper around LeafNodeSchema's constructor that provides the return type that is desired in the package public API.
*/
function makeLeaf<T extends FlexLeafNodeSchema>(
schema: T,
): TreeNodeSchema<UnbrandedName<T>, NodeKind.Leaf, TreeValue<T["info"]>, TreeValue<T["info"]>> {
return new LeafNodeSchema(schema);
}

// Leaf schema shared between all SchemaFactory instances.
const stringSchema = makeLeaf(leaf.string);
const numberSchema = makeLeaf(leaf.number);
const booleanSchema = makeLeaf(leaf.boolean);
const nullSchema = makeLeaf(leaf.null);
const handleSchema = makeLeaf(leaf.handle);

/**
* Gets the leaf domain schema compatible with a given {@link TreeValue}.
*/
Expand All @@ -124,12 +79,6 @@ export function schemaFromValue(value: TreeValue): TreeNodeSchema {
}
}

type UnbrandedName<T extends FlexLeafNodeSchema> = T["name"] extends TreeNodeSchemaIdentifier<
infer Name extends string
>
? Name
: T["name"];

/**
* The name of a schema produced by {@link SchemaFactory}, including its optional scope prefix.
*
Expand Down

0 comments on commit d9aa400

Please sign in to comment.