Skip to content
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

Allow more cases in Tree.is #20973

Merged
merged 5 commits into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion packages/dds/tree/api-report/tree.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1913,7 +1913,7 @@ export abstract class TreeNode implements WithType {

// @public
export interface TreeNodeApi {
is<TSchema extends TreeNodeSchema>(value: unknown, schema: TSchema): value is NodeFromSchema<TSchema>;
is<TSchema extends ImplicitAllowedTypes>(value: unknown, schema: TSchema): value is TreeNodeFromImplicitAllowedTypes<TSchema>;
key(node: TreeNode): string | number;
on<K extends keyof TreeChangeEvents>(node: TreeNode, eventName: K, listener: TreeChangeEvents[K]): () => void;
parent(node: TreeNode): TreeNode | undefined;
Expand Down
2 changes: 1 addition & 1 deletion packages/dds/tree/src/simple-tree/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export {
} from "./schemaTypes.js";
export { SchemaFactory, type ScopedSchemaName } from "./schemaFactory.js";
export { getFlexNode } from "./proxyBinding.js";
export { treeNodeApi, TreeNodeApi, TreeChangeEvents } from "./treeApi.js";
export { treeNodeApi, TreeNodeApi, TreeChangeEvents } from "./treeNodeApi.js";
export { toFlexConfig } from "./toFlexSchema.js";
export {
ObjectFromSchemaRecordUnsafe,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,34 @@ import { assert, unreachableCase } from "@fluidframework/core-utils/internal";
import { Multiplicity, rootFieldKey } from "../core/index.js";
import {
FieldKinds,
LeafNodeSchema,
LazyItem,
TreeStatus,
isLazy,
isTreeValue,
valueSchemaAllows,
} from "../feature-libraries/index.js";
import { fail, extractFromOpaque } from "../util/index.js";
import { fail, extractFromOpaque, isReadonlyArray } from "../util/index.js";

import { getOrCreateNodeProxy } from "./proxies.js";
import { getFlexNode, tryGetFlexNode } from "./proxyBinding.js";
import { getOrCreateNodeProxy, isTreeNode } from "./proxies.js";
import { getFlexNode } from "./proxyBinding.js";
import { tryGetSimpleNodeSchema } from "./schemaCaching.js";
import { schemaFromValue } from "./schemaFactory.js";
import {
NodeFromSchema,
NodeKind,
type TreeLeafValue,
TreeNodeSchema,
type ImplicitFieldSchema,
FieldSchema,
ImplicitAllowedTypes,
TreeNodeFromImplicitAllowedTypes,
} from "./schemaTypes.js";
import { getFlexSchema } from "./toFlexSchema.js";
import { TreeNode } from "./types.js";
import {
booleanSchema,
handleSchema,
nullSchema,
numberSchema,
stringSchema,
} from "./leafNodeSchema.js";
import { isFluidHandle } from "@fluidframework/runtime-utils/internal";

/**
* Provides various functions for analyzing {@link TreeNode}s.
Expand All @@ -51,15 +58,15 @@ export interface TreeNodeApi {
* Narrow the type of the given value if it satisfies the given schema.
* @example
* ```ts
* if (node.is(myNode, point)) {
* const y = myNode.y; // `myNode` is now known to satisfy the `point` schema and therefore has a `y` coordinate.
* if (node.is(myNode, Point)) {
* const y = myNode.y; // `myNode` is now known to satisfy the `Point` schema and therefore has a `y` coordinate.
* }
* ```
*/
is<TSchema extends TreeNodeSchema>(
is<TSchema extends ImplicitAllowedTypes>(
value: unknown,
schema: TSchema,
): value is NodeFromSchema<TSchema>;
): value is TreeNodeFromImplicitAllowedTypes<TSchema>;

/**
* Return the node under which this node resides in the tree (or undefined if this is a root node of the tree).
Expand Down Expand Up @@ -166,30 +173,30 @@ export const treeNodeApi: TreeNodeApi = {
status: (node: TreeNode) => {
return getFlexNode(node, true).treeStatus();
},
is: <TSchema extends TreeNodeSchema>(
is: <TSchema extends ImplicitAllowedTypes>(
value: unknown,
schema: TSchema,
): value is NodeFromSchema<TSchema> => {
const flexSchema = getFlexSchema(schema);
if (isTreeValue(value)) {
return (
flexSchema instanceof LeafNodeSchema && valueSchemaAllows(flexSchema.info, value)
);
): value is TreeNodeFromImplicitAllowedTypes<TSchema> => {
const actualSchema = tryGetSchema(value);
if (actualSchema === undefined) {
return false;
}
if (isReadonlyArray<LazyItem<TreeNodeSchema>>(schema)) {
for (const singleSchema of schema) {
const testSchema = isLazy(singleSchema) ? singleSchema() : singleSchema;
if (testSchema === actualSchema) {
return true;
}
}
return false;
} else {
return (schema as TreeNodeSchema) === actualSchema;
}
return tryGetFlexNode(value)?.is(flexSchema) ?? false;
},
schema<T extends TreeNode | TreeLeafValue>(
node: T,
): TreeNodeSchema<string, NodeKind, unknown, T> {
if (isTreeValue(node)) {
return schemaFromValue(node) as TreeNodeSchema<string, NodeKind, unknown, T>;
}
return tryGetSimpleNodeSchema(getFlexNode(node).schema) as TreeNodeSchema<
string,
NodeKind,
unknown,
T
>;
return tryGetSchema(node) ?? fail("Not a tree node");
},
shortId(node: TreeNode): number | string | undefined {
const flexNode = getFlexNode(node);
Expand All @@ -208,6 +215,38 @@ export const treeNodeApi: TreeNodeApi = {
},
};

/**
* Returns a schema for a value if the value is a {@link TreeNode} or a {@link TreeLeafValue}.
* Returns undefined for other values.
*/
export function tryGetSchema<T>(
value: T,
): undefined | TreeNodeSchema<string, NodeKind, unknown, T> {
type TOut = TreeNodeSchema<string, NodeKind, unknown, T>;
switch (typeof value) {
case "string":
return stringSchema as TOut;
case "number":
return numberSchema as TOut;
case "boolean":
return booleanSchema as TOut;
case "object": {
if (isTreeNode(value)) {
// This case could be optimized, for example by placing the simple schema in a symbol on tree nodes.
return tryGetSimpleNodeSchema(getFlexNode(value).schema) as TOut;
}
if (value === null) {
return nullSchema as TOut;
}
if (isFluidHandle(value)) {
return handleSchema as TOut;
}
}
default:
return undefined;
}
}

/**
* Gets the stored key with which the provided node is associated in the parent.
*/
Expand Down
Loading
Loading