Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
b26386b
Centralize schema traversal and conversion logic
CraigMacomber Dec 2, 2025
a199db7
add SchemaType, fix build
CraigMacomber Dec 2, 2025
c65dbff
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
CraigMacomber Dec 2, 2025
b0de824
Fix tests
CraigMacomber Dec 2, 2025
2bc65fd
Update API reports
CraigMacomber Dec 2, 2025
15139f7
Merge branch 'main' into toStoredRefactor
CraigMacomber Dec 2, 2025
2b27466
Fix policy check
CraigMacomber Dec 2, 2025
57e3769
Fix tests
CraigMacomber Dec 3, 2025
956861d
Preserve order of definitions in map
CraigMacomber Dec 3, 2025
20b7993
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
CraigMacomber Dec 3, 2025
6c29a9d
Add refactor todo
CraigMacomber Dec 3, 2025
ede6abb
Better docs, and cleanup config validation a bit
CraigMacomber Dec 3, 2025
6c696d4
Move a few utils so they don't need exporting, add some docs and a li…
CraigMacomber Dec 3, 2025
ea3bfea
Better docs
CraigMacomber Dec 3, 2025
a82a1e3
Better docs
CraigMacomber Dec 3, 2025
6c104c6
Fix api-reports
CraigMacomber Dec 3, 2025
74b1e65
Merge branch 'main' into toStoredRefactor
CraigMacomber Dec 3, 2025
a233cae
Docs for preservesViewData
CraigMacomber Dec 4, 2025
6c99f7a
More comments in getSimpleSchema
CraigMacomber Dec 4, 2025
5e7abf5
Move variance validation to tests
CraigMacomber Dec 5, 2025
bf0096c
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
CraigMacomber Dec 5, 2025
0cb3b93
fix lint
CraigMacomber Dec 5, 2025
9856dc2
More docs
CraigMacomber Dec 5, 2025
8ca82c6
Fix comment on Unchanged
CraigMacomber Dec 5, 2025
4512f98
Better comment on SimpleFieldSchema.metadata
CraigMacomber Dec 5, 2025
8fc7c20
Link SchemaStaticsBeta.staged
CraigMacomber Dec 5, 2025
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
101 changes: 58 additions & 43 deletions packages/dds/tree/api-report/tree.alpha.api.md

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion packages/dds/tree/api-report/tree.beta.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -850,7 +850,6 @@ export interface TreeViewBeta<in out TSchema extends ImplicitFieldSchema> extend
// @public @sealed
export class TreeViewConfiguration<const TSchema extends ImplicitFieldSchema = ImplicitFieldSchema> implements Required<ITreeViewConfiguration<TSchema>> {
constructor(props: ITreeViewConfiguration<TSchema>);
protected readonly definitionsInternal: ReadonlyMap<string, TreeNodeSchema>;
readonly enableSchemaValidation: boolean;
readonly preventAmbiguity: boolean;
readonly schema: TSchema;
Expand Down
1 change: 0 additions & 1 deletion packages/dds/tree/api-report/tree.legacy.beta.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -862,7 +862,6 @@ export interface TreeViewBeta<in out TSchema extends ImplicitFieldSchema> extend
// @public @sealed
export class TreeViewConfiguration<const TSchema extends ImplicitFieldSchema = ImplicitFieldSchema> implements Required<ITreeViewConfiguration<TSchema>> {
constructor(props: ITreeViewConfiguration<TSchema>);
protected readonly definitionsInternal: ReadonlyMap<string, TreeNodeSchema>;
readonly enableSchemaValidation: boolean;
readonly preventAmbiguity: boolean;
readonly schema: TSchema;
Expand Down
1 change: 0 additions & 1 deletion packages/dds/tree/api-report/tree.legacy.public.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,6 @@ export interface TreeView<in out TSchema extends ImplicitFieldSchema> extends ID
// @public @sealed
export class TreeViewConfiguration<const TSchema extends ImplicitFieldSchema = ImplicitFieldSchema> implements Required<ITreeViewConfiguration<TSchema>> {
constructor(props: ITreeViewConfiguration<TSchema>);
protected readonly definitionsInternal: ReadonlyMap<string, TreeNodeSchema>;
readonly enableSchemaValidation: boolean;
readonly preventAmbiguity: boolean;
readonly schema: TSchema;
Expand Down
1 change: 0 additions & 1 deletion packages/dds/tree/api-report/tree.public.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,6 @@ export interface TreeView<in out TSchema extends ImplicitFieldSchema> extends ID
// @public @sealed
export class TreeViewConfiguration<const TSchema extends ImplicitFieldSchema = ImplicitFieldSchema> implements Required<ITreeViewConfiguration<TSchema>> {
constructor(props: ITreeViewConfiguration<TSchema>);
protected readonly definitionsInternal: ReadonlyMap<string, TreeNodeSchema>;
readonly enableSchemaValidation: boolean;
readonly preventAmbiguity: boolean;
readonly schema: TSchema;
Expand Down
5 changes: 3 additions & 2 deletions packages/dds/tree/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ export {
type AnnotateAllowedTypesList,
type AllowedTypesFull,
type AllowedTypesFullFromMixed,
type SchemaType,
// Beta APIs
TreeBeta,
type TreeChangeEventsBeta,
Expand Down Expand Up @@ -283,8 +284,8 @@ export {
type SchemaFactory_base,
type NumberKeys,
type SimpleAllowedTypeAttributes,
encodeSimpleSchema,
decodeSimpleSchema,
encodeSchemaCompatibilitySnapshot,
decodeSchemaCompatibilitySnapshot,
exportCompatibilitySchemaSnapshot,
importCompatibilitySchemaSnapshot,
checkCompatibility,
Expand Down
19 changes: 13 additions & 6 deletions packages/dds/tree/src/shared-tree/sharedTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ import {
type ITreeAlpha,
type SimpleObjectFieldSchema,
type SimpleAllowedTypeAttributes,
type SchemaType,
} from "../simple-tree/index.js";

import { SchematizingSimpleTreeView } from "./schematizingTreeView.js";
Expand Down Expand Up @@ -503,7 +504,9 @@ export class SharedTreeKernel
public onDisconnect(): void {}
}

export function exportSimpleSchema(storedSchema: TreeStoredSchema): SimpleTreeSchema {
export function exportSimpleSchema(
storedSchema: TreeStoredSchema,
): SimpleTreeSchema<SchemaType.Stored> {
return {
root: exportSimpleFieldSchemaStored(storedSchema.rootFieldSchema),
definitions: new Map(
Expand Down Expand Up @@ -819,16 +822,18 @@ export const defaultSharedTreeOptions: Required<SharedTreeOptionsInternal> = {
*/
function buildSimpleAllowedTypeAttributesForStoredSchema(
types: TreeTypeSet,
): ReadonlyMap<string, SimpleAllowedTypeAttributes> {
const allowedTypesInfo = new Map<string, SimpleAllowedTypeAttributes>();
): ReadonlyMap<string, SimpleAllowedTypeAttributes<SchemaType.Stored>> {
const allowedTypesInfo = new Map<string, SimpleAllowedTypeAttributes<SchemaType.Stored>>();
for (const type of types) {
// Stored schemas do not have staged upgrades
allowedTypesInfo.set(type, { isStaged: undefined });
}
return allowedTypesInfo;
}

function exportSimpleFieldSchemaStored(schema: TreeFieldStoredSchema): SimpleFieldSchema {
function exportSimpleFieldSchemaStored(
schema: TreeFieldStoredSchema,
): SimpleFieldSchema<SchemaType.Stored> {
let kind: FieldKind;
switch (schema.kind) {
case FieldKinds.identifier.identifier:
Expand Down Expand Up @@ -862,7 +867,9 @@ function exportSimpleFieldSchemaStored(schema: TreeFieldStoredSchema): SimpleFie
* Note on SimpleNodeSchema construction: In the persisted format `persistedMetadata` is just called `metadata` whereas the `metadata`
* field on SimpleNodeSchema is not persisted.
*/
function exportSimpleNodeSchemaStored(schema: TreeNodeStoredSchema): SimpleNodeSchema {
function exportSimpleNodeSchemaStored(
schema: TreeNodeStoredSchema,
): SimpleNodeSchema<SchemaType.Stored> {
const arrayTypes = tryStoredSchemaAsArray(schema);
if (arrayTypes !== undefined) {
return {
Expand All @@ -873,7 +880,7 @@ function exportSimpleNodeSchemaStored(schema: TreeNodeStoredSchema): SimpleNodeS
};
}
if (schema instanceof ObjectNodeStoredSchema) {
const fields = new Map<FieldKey, SimpleObjectFieldSchema>();
const fields = new Map<FieldKey, SimpleObjectFieldSchema<SchemaType.Stored>>();
for (const [storedKey, field] of schema.objectNodeFields) {
fields.set(storedKey, { ...exportSimpleFieldSchemaStored(field), storedKey });
}
Expand Down
5 changes: 2 additions & 3 deletions packages/dds/tree/src/shared-tree/treeAlpha.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,6 @@ import {
isObjectNodeSchema,
isTreeNode,
toInitialSchema,
convertField,
toUnhydratedSchema,
type TreeParsingOptions,
type NodeChangedData,
type ConciseTree,
Expand All @@ -60,6 +58,7 @@ import {
borrowCursorFromTreeNodeOrValue,
contentSchemaSymbol,
type TreeNodeSchema,
getUnhydratedContext,
} from "../simple-tree/index.js";
import { brand, extractFromOpaque, type JsonCompatible } from "../util/index.js";
import {
Expand Down Expand Up @@ -824,7 +823,7 @@ export const TreeAlpha: TreeAlpha = {
return createFromCursor(
schema,
cursor,
convertField(normalizeFieldSchema(schema), toUnhydratedSchema),
getUnhydratedContext(schema).flexContext.schema.rootFieldSchema,
);
},

Expand Down
89 changes: 21 additions & 68 deletions packages/dds/tree/src/simple-tree/api/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,19 @@
* Licensed under the MIT License.
*/

import {
assert,
debugAssert,
fail,
oob,
unreachableCase,
} from "@fluidframework/core-utils/internal";
import { assert, fail, oob, unreachableCase } from "@fluidframework/core-utils/internal";
import { UsageError } from "@fluidframework/telemetry-utils/internal";

import {
type FieldSchemaAlpha,
type ImplicitFieldSchema,
FieldKind,
normalizeFieldSchema,
} from "../fieldSchema.js";
import { type FieldSchemaAlpha, type ImplicitFieldSchema, FieldKind } from "../fieldSchema.js";
import {
type AllowedTypesFullEvaluated,
NodeKind,
type TreeNodeSchema,
markSchemaMostDerived,
} from "../core/index.js";
import {
permissiveStoredSchemaGenerationOptions,
restrictiveStoredSchemaGenerationOptions,
toStoredSchema,
toInitialSchema,
toUnhydratedSchema,
transformSimpleSchema,
} from "../toStoredSchema.js";
import {
isArrayNodeSchema,
Expand All @@ -42,7 +30,8 @@ import {
import { getOrCreate } from "../../util/index.js";
import type { MakeNominal } from "../../util/index.js";
import { walkFieldSchema } from "../walkFieldSchema.js";
import type { SimpleNodeSchema, SimpleTreeSchema } from "../simpleSchema.js";
import type { SchemaType, SimpleNodeSchema } from "../simpleSchema.js";
import { createTreeSchema, type TreeSchema } from "../treeSchema.js";

/**
* Options when constructing a tree view.
Expand Down Expand Up @@ -187,11 +176,6 @@ export class TreeViewConfiguration<
*/
public readonly preventAmbiguity!: boolean;

/**
* {@link TreeSchema.definitions} but with public types.
*/
protected readonly definitionsInternal!: ReadonlyMap<string, TreeNodeSchema>;

/**
* Construct a new {@link TreeViewConfiguration}.
*
Expand Down Expand Up @@ -225,23 +209,10 @@ export class TreeViewConfiguration<
// Ambiguity errors are lower priority to report than invalid schema errors, so collect these in an array and report them all at once.
const ambiguityErrors: string[] = [];

// Eagerly perform this conversion to surface errors sooner.
// Includes detection of duplicate schema identifiers.
toStoredSchema(config.schema, restrictiveStoredSchemaGenerationOptions);
toStoredSchema(config.schema, permissiveStoredSchemaGenerationOptions);

const definitions = new Map<string, SimpleNodeSchema & TreeNodeSchema>();

// Validate the schema and collect ambiguity errors.
// This does a lot of validation (throwing usage errors as a side effect) in addition to just collecting ambiguity errors.
// ambiguityErrors are considered a lower priority, so only thrown if no other errors are found.
walkFieldSchema(config.schema, {
node: (schema) => {
// Ensure all reachable schema are marked as most derived.
// This ensures if multiple schema extending the same schema factory generated class are present (or have had instances of them constructed, or get instances of them constructed in the future),
// an error is reported.
markSchemaMostDerived(schema, true);

debugAssert(() => !definitions.has(schema.identifier));
definitions.set(schema.identifier, schema as SimpleNodeSchema & TreeNodeSchema);
},
allowedTypes({ types }: AllowedTypesFullEvaluated): void {
checkUnion(
types.map((t) => t.type),
Expand All @@ -251,8 +222,6 @@ export class TreeViewConfiguration<
},
});

this.definitionsInternal = definitions;

if (ambiguityErrors.length !== 0) {
// Duplicate errors are common since when two types conflict, both orders error:
const deduplicated = new Set(ambiguityErrors);
Expand All @@ -273,38 +242,22 @@ export class TreeViewConfigurationAlpha<
extends TreeViewConfiguration<TSchema>
implements TreeSchema
{
/**
* {@inheritDoc TreeSchema.root}
*/
public readonly root: FieldSchemaAlpha;

/**
* {@inheritDoc TreeSchema.definitions}
*/
public get definitions(): ReadonlyMap<string, SimpleNodeSchema & TreeNodeSchema> {
return this.definitionsInternal as ReadonlyMap<string, SimpleNodeSchema & TreeNodeSchema>;
}
public readonly definitions: ReadonlyMap<
string,
SimpleNodeSchema<SchemaType.View> & TreeNodeSchema
>;

public constructor(props: ITreeViewConfiguration<TSchema>) {
super(props);
this.root = normalizeFieldSchema(props.schema);
}
}
const treeSchema = createTreeSchema(this.schema);
this.root = treeSchema.root;
this.definitions = treeSchema.definitions;

/**
* {@link TreeViewConfigurationAlpha}
* @sealed @alpha
*/
export interface TreeSchema extends SimpleTreeSchema {
/**
* {@inheritDoc SimpleTreeSchema.root}
*/
readonly root: FieldSchemaAlpha;

/**
* {@inheritDoc SimpleTreeSchema.definitions}
*/
readonly definitions: ReadonlyMap<string, SimpleNodeSchema & TreeNodeSchema>;
// Eagerly perform these conversions to surface errors sooner.
toInitialSchema(this.root);
transformSimpleSchema(treeSchema, toUnhydratedSchema);
}
}

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/dds/tree/src/simple-tree/api/discrepancies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ import {
type FieldSchema,
} from "../fieldSchema.js";
import { LeafNodeSchema } from "../leafNodeSchema.js";
import type { TreeSchema } from "./configuration.js";
import type { TreeSchema } from "../treeSchema.js";
import { tryStoredSchemaAsArray } from "./customTree.js";
import { FieldKinds } from "../../feature-libraries/index.js";

Expand Down
19 changes: 13 additions & 6 deletions packages/dds/tree/src/simple-tree/api/getSimpleSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
* Licensed under the MIT License.
*/

import { Unchanged } from "../core/index.js";
import type { ImplicitFieldSchema } from "../fieldSchema.js";
import type { SimpleTreeSchema } from "../simpleSchema.js";

import { toSimpleTreeSchema } from "./viewSchemaToSimpleSchema.js";
import type { SchemaType, SimpleTreeSchema } from "../simpleSchema.js";
import { transformSimpleSchema } from "../toStoredSchema.js";
import { createTreeSchema } from "../treeSchema.js";

/**
* Copies data from {@link ImplicitFieldSchema} to create a {@link SimpleTreeSchema} out of new plain JavaScript objects, Sets and Maps.
* Copies data from {@link ImplicitFieldSchema} to create a {@link SimpleTreeSchema} out of new plain JavaScript objects, Sets and Maps.
*
* @remarks
* See also {@link TreeViewConfigurationAlpha} which implements {@link SimpleTreeSchema} as a way to get a `SimpleTreeSchema` without copying the node and field schema and without losing as much type information.
Expand All @@ -23,6 +24,12 @@ import { toSimpleTreeSchema } from "./viewSchemaToSimpleSchema.js";
*
* @alpha
*/
export function getSimpleSchema(schema: ImplicitFieldSchema): SimpleTreeSchema {
return toSimpleTreeSchema(schema, true);
export function getSimpleSchema(
schema: ImplicitFieldSchema,
): SimpleTreeSchema<SchemaType.View> {
// Convert the input into a TreeSchema: This API should probably be updated to take in a TreeSchema directly
// (and maybe a clean way to make TreeSchema from ImplicitFieldSchema other than TreeViewConfigurationAlpha should be provided).
const treeSchema = createTreeSchema(schema);
// Do the actual copy into clean simple schema objects.
return transformSimpleSchema(treeSchema, Unchanged);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import { getTreeNodeSchemaPrivateData, type AllowedTypesFull } from "../core/index.js";
import { isArrayNodeSchema, isObjectNodeSchema } from "../node-kinds/index.js";
import type { TreeSchema } from "./configuration.js";
import type { TreeSchema } from "../treeSchema.js";
import type { IncrementalEncodingPolicy } from "../../feature-libraries/index.js";
import { oneFromIterable } from "../../util/index.js";
import { assert } from "@fluidframework/core-utils/internal";
Expand Down
6 changes: 2 additions & 4 deletions packages/dds/tree/src/simple-tree/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
*/

export type {
TreeSchema,
ITreeViewConfiguration,
ITreeConfigurationOptions,
} from "./configuration.js";
Expand Down Expand Up @@ -161,15 +160,14 @@ export {
} from "./transactionTypes.js";

export { generateSchemaFromSimpleSchema } from "./schemaFromSimple.js";
export { toSimpleTreeSchema } from "./viewSchemaToSimpleSchema.js";
export type { TreeChangeEvents } from "./treeChangeEvents.js";
export {
incrementalEncodingPolicyForAllowedTypes,
incrementalSummaryHint,
} from "./incrementalAllowedTypes.js";
export {
encodeSimpleSchema,
decodeSimpleSchema,
encodeSchemaCompatibilitySnapshot,
decodeSchemaCompatibilitySnapshot,
} from "./simpleSchemaCodec.js";
export {
exportCompatibilitySchemaSnapshot,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { allowsRepoSuperset, defaultSchemaPolicy } from "../../feature-libraries
import type { SchemaCompatibilityStatus } from "./tree.js";
import { getDiscrepanciesInAllowedContent } from "./discrepancies.js";
import { toUpgradeSchema } from "../toStoredSchema.js";
import type { TreeSchema } from "./configuration.js";
import type { TreeSchema } from "../treeSchema.js";

/**
* A collection of View information for schema
Expand Down
4 changes: 2 additions & 2 deletions packages/dds/tree/src/simple-tree/api/schemaFactoryAlpha.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import type {
System_Unsafe,
TreeRecordNodeUnsafe,
} from "./typesUnsafe.js";
import type { SimpleObjectNodeSchema } from "../simpleSchema.js";
import type { SchemaType, SimpleObjectNodeSchema } from "../simpleSchema.js";
import { SchemaFactoryBeta } from "./schemaFactoryBeta.js";

// These imports prevent a large number of type references in the API reports from showing up as *_2.
Expand Down Expand Up @@ -119,7 +119,7 @@ export class SchemaFactoryAlpha<
never,
TCustomMetadata
> &
SimpleObjectNodeSchema<TCustomMetadata> &
SimpleObjectNodeSchema<SchemaType.View, TCustomMetadata> &
// We can't just use non generic `ObjectNodeSchema` here since "Base constructors must all have the same return type".
// We also can't just use generic `ObjectNodeSchema` here and not `TreeNodeSchemaClass` since that doesn't work with unsafe recursive types.
// ObjectNodeSchema<
Expand Down
Loading
Loading