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

Add generateSchemaFromSimpleSchema #23950

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
1 change: 1 addition & 0 deletions packages/dds/tree/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ export {
type TransactionResultSuccess,
type TransactionResultFailed,
rollback,
generateSchemaFromSimpleSchema,
} from "./simple-tree/index.js";
export {
SharedTree,
Expand Down
2 changes: 2 additions & 0 deletions packages/dds/tree/src/simple-tree/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ export {
rollback,
} from "./transactionTypes.js";

export { generateSchemaFromSimpleSchema } from "./schemaFromSimple.js";

// Exporting the schema (RecursiveObject) to test that recursive types are working correctly.
// These are `@internal` so they can't be included in the `InternalClassTreeTypes` due to https://github.com/microsoft/rushstack/issues/3639
export {
Expand Down
70 changes: 70 additions & 0 deletions packages/dds/tree/src/simple-tree/api/schemaFromSimple.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 { unreachableCase } from "@fluidframework/core-utils/internal";
import { fail } from "../../util/index.js";
import { NodeKind, type TreeNodeSchema } from "../core/index.js";
import { createFieldSchema, type FieldSchema, type AllowedTypes } from "../schemaTypes.js";
import { SchemaFactory } from "./schemaFactory.js";
import type { SimpleFieldSchema, SimpleNodeSchema, SimpleTreeSchema } from "./simpleSchema.js";

/*
* TODO: Tests for this file
*/

const factory = new SchemaFactory(undefined);

/**
* Create {@link FieldSchema} from a SimpleTreeSchema.
* @remarks
* Only use this API if a hand written (produced using {@link SchemaFactory} cannot be provided.
*
* Using this resulting schema with schema aware APIs (designed to work with strongly typed schema) like {@link TreeViewConfiguration}
* will produce a poor TypeScript typing experience which is subject to change.
*
* Editing through a view produced using this schema can easily violate invariants other users of the document might expect and must be done with great care.
* @internal
*/
export function generateSchemaFromSimpleSchema(simple: SimpleTreeSchema): FieldSchema {
const context: Context = new Map(
[...simple.definitions].map(([id, schema]): [string, () => TreeNodeSchema] => [
id,
() => generateNode(id, schema, context),
]),
);
return generateFieldSchema(simple, context);
}

type Context = ReadonlyMap<string, () => TreeNodeSchema>;

function generateFieldSchema(simple: SimpleFieldSchema, context: Context): FieldSchema {
return createFieldSchema(simple.kind, generateAllowedTypes(simple.allowedTypes, context));
}

function generateAllowedTypes(allowed: ReadonlySet<string>, context: Context): AllowedTypes {
return [...allowed].map((id) => context.get(id) ?? fail(`Missing schema`));
}

function generateNode(id: string, schema: SimpleNodeSchema, context: Context): TreeNodeSchema {
switch (schema.kind) {
case NodeKind.Object: {
const fields: Record<string, FieldSchema> = {};
for (const [key, field] of Object.entries(schema.fields)) {
fields[key] = generateFieldSchema(field, context);
}
return factory.object(id, fields);
}
case NodeKind.Array:
return factory.array(id, generateAllowedTypes(schema.allowedTypes, context));
case NodeKind.Map:
return factory.map(id, generateAllowedTypes(schema.allowedTypes, context));
case NodeKind.Leaf:
return (
SchemaFactory.leaves.find((leaf) => leaf.identifier === id) ?? fail(`Missing schema`)
);
default:
return unreachableCase(schema);
}
}
10 changes: 10 additions & 0 deletions packages/dds/tree/src/simple-tree/api/simpleSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ import type { ValueSchema } from "../../core/index.js";
import type { NodeKind } from "../core/index.js";
import type { FieldKind, FieldSchemaMetadata, NodeSchemaMetadata } from "../schemaTypes.js";

/*
* TODO:
* - Make TreeNodeSchema implement these interfaces directly.
* - Customize their JSON serialization to use these formats or provide some other serialization scheme.
* - Promote these to alpha
*/

/**
* Base interface for all {@link SimpleNodeSchema} implementations.
*
Expand Down Expand Up @@ -38,6 +45,9 @@ export interface SimpleObjectNodeSchema extends SimpleNodeSchemaBase<NodeKind.Ob
* Schemas for each of the object's fields, keyed off of schema's keys.
* @remarks
* Depending on how this schema was exported, the string keys may be either the property keys or the stored keys.
* @privateRemarks
* TODO: if these are supposed to be JSON compatible,
* then using a record here makes sense, but if not, this should use a map, and the allowedTypes sets elsewhere should be arrays for JSON compatibility.
*/
readonly fields: Record<string, SimpleFieldSchema>;
}
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 @@ -136,6 +136,7 @@ export {
type TransactionResultSuccess,
type TransactionResultFailed,
rollback,
generateSchemaFromSimpleSchema,
} from "./api/index.js";
export {
type NodeFromSchema,
Expand Down
Loading