Skip to content

Commit b0513a6

Browse files
Simple-Schema and to stored refactor (microsoft#25962)
## Description Since simple schema can now contain view schema specific data, but might also be derived from stored schema, and might also be storing only a subset of its view specific data that it can hold (like when snapshotting for compatibility tests) using it is rather error prone. This refactoring attempts to better clarify what the semantics of a given simple schema are by allowing them to be typed as either view or stored, and providing more specific names and docs to the compatibility snapshotting APIs. This also refactors how we generate stored schema: now we transform a simple schema for a view to a simple schema for stored schema, then convert that to the persisted format. This redesign separates the concerns for persisted format conversion and the semantics of things like staged schema which get processes when converting from view to stored. This results in some deduplication of logic, and allows all schema transformation logic to be applied directly to simple-schema. This change could be followed up with some further changes to better remove stored schema from the alpha APIs, and replace those APIs with use of stored-simple-schema and thus shrinking the package API surface area and making its types more interoperable. `getUnhydratedContext` has been improved to give better asserts when uses re-entrantly. ## Breaking Changes Several stored and simple schema alpha APIs have been impacted, but all stable APIs should behave as is.
1 parent 644581e commit b0513a6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+1168
-904
lines changed

packages/dds/tree/api-report/tree.alpha.api.md

Lines changed: 58 additions & 43 deletions
Large diffs are not rendered by default.

packages/dds/tree/api-report/tree.beta.api.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -850,7 +850,6 @@ export interface TreeViewBeta<in out TSchema extends ImplicitFieldSchema> extend
850850
// @public @sealed
851851
export class TreeViewConfiguration<const TSchema extends ImplicitFieldSchema = ImplicitFieldSchema> implements Required<ITreeViewConfiguration<TSchema>> {
852852
constructor(props: ITreeViewConfiguration<TSchema>);
853-
protected readonly definitionsInternal: ReadonlyMap<string, TreeNodeSchema>;
854853
readonly enableSchemaValidation: boolean;
855854
readonly preventAmbiguity: boolean;
856855
readonly schema: TSchema;

packages/dds/tree/api-report/tree.legacy.beta.api.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -862,7 +862,6 @@ export interface TreeViewBeta<in out TSchema extends ImplicitFieldSchema> extend
862862
// @public @sealed
863863
export class TreeViewConfiguration<const TSchema extends ImplicitFieldSchema = ImplicitFieldSchema> implements Required<ITreeViewConfiguration<TSchema>> {
864864
constructor(props: ITreeViewConfiguration<TSchema>);
865-
protected readonly definitionsInternal: ReadonlyMap<string, TreeNodeSchema>;
866865
readonly enableSchemaValidation: boolean;
867866
readonly preventAmbiguity: boolean;
868867
readonly schema: TSchema;

packages/dds/tree/api-report/tree.legacy.public.api.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,6 @@ export interface TreeView<in out TSchema extends ImplicitFieldSchema> extends ID
585585
// @public @sealed
586586
export class TreeViewConfiguration<const TSchema extends ImplicitFieldSchema = ImplicitFieldSchema> implements Required<ITreeViewConfiguration<TSchema>> {
587587
constructor(props: ITreeViewConfiguration<TSchema>);
588-
protected readonly definitionsInternal: ReadonlyMap<string, TreeNodeSchema>;
589588
readonly enableSchemaValidation: boolean;
590589
readonly preventAmbiguity: boolean;
591590
readonly schema: TSchema;

packages/dds/tree/api-report/tree.public.api.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,6 @@ export interface TreeView<in out TSchema extends ImplicitFieldSchema> extends ID
585585
// @public @sealed
586586
export class TreeViewConfiguration<const TSchema extends ImplicitFieldSchema = ImplicitFieldSchema> implements Required<ITreeViewConfiguration<TSchema>> {
587587
constructor(props: ITreeViewConfiguration<TSchema>);
588-
protected readonly definitionsInternal: ReadonlyMap<string, TreeNodeSchema>;
589588
readonly enableSchemaValidation: boolean;
590589
readonly preventAmbiguity: boolean;
591590
readonly schema: TSchema;

packages/dds/tree/src/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ export {
185185
type AnnotateAllowedTypesList,
186186
type AllowedTypesFull,
187187
type AllowedTypesFullFromMixed,
188+
type SchemaType,
188189
// Beta APIs
189190
TreeBeta,
190191
type TreeChangeEventsBeta,
@@ -283,8 +284,8 @@ export {
283284
type SchemaFactory_base,
284285
type NumberKeys,
285286
type SimpleAllowedTypeAttributes,
286-
encodeSimpleSchema,
287-
decodeSimpleSchema,
287+
encodeSchemaCompatibilitySnapshot,
288+
decodeSchemaCompatibilitySnapshot,
288289
exportCompatibilitySchemaSnapshot,
289290
importCompatibilitySchemaSnapshot,
290291
checkCompatibility,

packages/dds/tree/src/shared-tree/sharedTree.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ import {
9797
type ITreeAlpha,
9898
type SimpleObjectFieldSchema,
9999
type SimpleAllowedTypeAttributes,
100+
type SchemaType,
100101
} from "../simple-tree/index.js";
101102

102103
import { SchematizingSimpleTreeView } from "./schematizingTreeView.js";
@@ -507,7 +508,9 @@ export class SharedTreeKernel
507508
public onDisconnect(): void {}
508509
}
509510

510-
export function exportSimpleSchema(storedSchema: TreeStoredSchema): SimpleTreeSchema {
511+
export function exportSimpleSchema(
512+
storedSchema: TreeStoredSchema,
513+
): SimpleTreeSchema<SchemaType.Stored> {
511514
return {
512515
root: exportSimpleFieldSchemaStored(storedSchema.rootFieldSchema),
513516
definitions: new Map(
@@ -823,16 +826,18 @@ export const defaultSharedTreeOptions: Required<SharedTreeOptionsInternal> = {
823826
*/
824827
function buildSimpleAllowedTypeAttributesForStoredSchema(
825828
types: TreeTypeSet,
826-
): ReadonlyMap<string, SimpleAllowedTypeAttributes> {
827-
const allowedTypesInfo = new Map<string, SimpleAllowedTypeAttributes>();
829+
): ReadonlyMap<string, SimpleAllowedTypeAttributes<SchemaType.Stored>> {
830+
const allowedTypesInfo = new Map<string, SimpleAllowedTypeAttributes<SchemaType.Stored>>();
828831
for (const type of types) {
829832
// Stored schemas do not have staged upgrades
830833
allowedTypesInfo.set(type, { isStaged: undefined });
831834
}
832835
return allowedTypesInfo;
833836
}
834837

835-
function exportSimpleFieldSchemaStored(schema: TreeFieldStoredSchema): SimpleFieldSchema {
838+
function exportSimpleFieldSchemaStored(
839+
schema: TreeFieldStoredSchema,
840+
): SimpleFieldSchema<SchemaType.Stored> {
836841
let kind: FieldKind;
837842
switch (schema.kind) {
838843
case FieldKinds.identifier.identifier:
@@ -866,7 +871,9 @@ function exportSimpleFieldSchemaStored(schema: TreeFieldStoredSchema): SimpleFie
866871
* Note on SimpleNodeSchema construction: In the persisted format `persistedMetadata` is just called `metadata` whereas the `metadata`
867872
* field on SimpleNodeSchema is not persisted.
868873
*/
869-
function exportSimpleNodeSchemaStored(schema: TreeNodeStoredSchema): SimpleNodeSchema {
874+
function exportSimpleNodeSchemaStored(
875+
schema: TreeNodeStoredSchema,
876+
): SimpleNodeSchema<SchemaType.Stored> {
870877
const arrayTypes = tryStoredSchemaAsArray(schema);
871878
if (arrayTypes !== undefined) {
872879
return {
@@ -877,7 +884,7 @@ function exportSimpleNodeSchemaStored(schema: TreeNodeStoredSchema): SimpleNodeS
877884
};
878885
}
879886
if (schema instanceof ObjectNodeStoredSchema) {
880-
const fields = new Map<FieldKey, SimpleObjectFieldSchema>();
887+
const fields = new Map<FieldKey, SimpleObjectFieldSchema<SchemaType.Stored>>();
881888
for (const [storedKey, field] of schema.objectNodeFields) {
882889
fields.set(storedKey, { ...exportSimpleFieldSchemaStored(field), storedKey });
883890
}

packages/dds/tree/src/shared-tree/treeAlpha.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,6 @@ import {
5050
isObjectNodeSchema,
5151
isTreeNode,
5252
toInitialSchema,
53-
convertField,
54-
toUnhydratedSchema,
5553
type TreeParsingOptions,
5654
type NodeChangedData,
5755
type ConciseTree,
@@ -60,6 +58,7 @@ import {
6058
borrowCursorFromTreeNodeOrValue,
6159
contentSchemaSymbol,
6260
type TreeNodeSchema,
61+
getUnhydratedContext,
6362
} from "../simple-tree/index.js";
6463
import { brand, extractFromOpaque, type JsonCompatible } from "../util/index.js";
6564
import {
@@ -824,7 +823,7 @@ export const TreeAlpha: TreeAlpha = {
824823
return createFromCursor(
825824
schema,
826825
cursor,
827-
convertField(normalizeFieldSchema(schema), toUnhydratedSchema),
826+
getUnhydratedContext(schema).flexContext.schema.rootFieldSchema,
828827
);
829828
},
830829

packages/dds/tree/src/simple-tree/api/configuration.ts

Lines changed: 21 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,19 @@
33
* Licensed under the MIT License.
44
*/
55

6-
import {
7-
assert,
8-
debugAssert,
9-
fail,
10-
oob,
11-
unreachableCase,
12-
} from "@fluidframework/core-utils/internal";
6+
import { assert, fail, oob, unreachableCase } from "@fluidframework/core-utils/internal";
137
import { UsageError } from "@fluidframework/telemetry-utils/internal";
148

15-
import {
16-
type FieldSchemaAlpha,
17-
type ImplicitFieldSchema,
18-
FieldKind,
19-
normalizeFieldSchema,
20-
} from "../fieldSchema.js";
9+
import { type FieldSchemaAlpha, type ImplicitFieldSchema, FieldKind } from "../fieldSchema.js";
2110
import {
2211
type AllowedTypesFullEvaluated,
2312
NodeKind,
2413
type TreeNodeSchema,
25-
markSchemaMostDerived,
2614
} from "../core/index.js";
2715
import {
28-
permissiveStoredSchemaGenerationOptions,
29-
restrictiveStoredSchemaGenerationOptions,
30-
toStoredSchema,
16+
toInitialSchema,
17+
toUnhydratedSchema,
18+
transformSimpleSchema,
3119
} from "../toStoredSchema.js";
3220
import {
3321
isArrayNodeSchema,
@@ -42,7 +30,8 @@ import {
4230
import { getOrCreate } from "../../util/index.js";
4331
import type { MakeNominal } from "../../util/index.js";
4432
import { walkFieldSchema } from "../walkFieldSchema.js";
45-
import type { SimpleNodeSchema, SimpleTreeSchema } from "../simpleSchema.js";
33+
import type { SchemaType, SimpleNodeSchema } from "../simpleSchema.js";
34+
import { createTreeSchema, type TreeSchema } from "../treeSchema.js";
4635

4736
/**
4837
* Options when constructing a tree view.
@@ -187,11 +176,6 @@ export class TreeViewConfiguration<
187176
*/
188177
public readonly preventAmbiguity!: boolean;
189178

190-
/**
191-
* {@link TreeSchema.definitions} but with public types.
192-
*/
193-
protected readonly definitionsInternal!: ReadonlyMap<string, TreeNodeSchema>;
194-
195179
/**
196180
* Construct a new {@link TreeViewConfiguration}.
197181
*
@@ -225,23 +209,10 @@ export class TreeViewConfiguration<
225209
// Ambiguity errors are lower priority to report than invalid schema errors, so collect these in an array and report them all at once.
226210
const ambiguityErrors: string[] = [];
227211

228-
// Eagerly perform this conversion to surface errors sooner.
229-
// Includes detection of duplicate schema identifiers.
230-
toStoredSchema(config.schema, restrictiveStoredSchemaGenerationOptions);
231-
toStoredSchema(config.schema, permissiveStoredSchemaGenerationOptions);
232-
233-
const definitions = new Map<string, SimpleNodeSchema & TreeNodeSchema>();
234-
212+
// Validate the schema and collect ambiguity errors.
213+
// This does a lot of validation (throwing usage errors as a side effect) in addition to just collecting ambiguity errors.
214+
// ambiguityErrors are considered a lower priority, so only thrown if no other errors are found.
235215
walkFieldSchema(config.schema, {
236-
node: (schema) => {
237-
// Ensure all reachable schema are marked as most derived.
238-
// 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),
239-
// an error is reported.
240-
markSchemaMostDerived(schema, true);
241-
242-
debugAssert(() => !definitions.has(schema.identifier));
243-
definitions.set(schema.identifier, schema as SimpleNodeSchema & TreeNodeSchema);
244-
},
245216
allowedTypes({ types }: AllowedTypesFullEvaluated): void {
246217
checkUnion(
247218
types.map((t) => t.type),
@@ -251,8 +222,6 @@ export class TreeViewConfiguration<
251222
},
252223
});
253224

254-
this.definitionsInternal = definitions;
255-
256225
if (ambiguityErrors.length !== 0) {
257226
// Duplicate errors are common since when two types conflict, both orders error:
258227
const deduplicated = new Set(ambiguityErrors);
@@ -273,38 +242,22 @@ export class TreeViewConfigurationAlpha<
273242
extends TreeViewConfiguration<TSchema>
274243
implements TreeSchema
275244
{
276-
/**
277-
* {@inheritDoc TreeSchema.root}
278-
*/
279245
public readonly root: FieldSchemaAlpha;
280-
281-
/**
282-
* {@inheritDoc TreeSchema.definitions}
283-
*/
284-
public get definitions(): ReadonlyMap<string, SimpleNodeSchema & TreeNodeSchema> {
285-
return this.definitionsInternal as ReadonlyMap<string, SimpleNodeSchema & TreeNodeSchema>;
286-
}
246+
public readonly definitions: ReadonlyMap<
247+
string,
248+
SimpleNodeSchema<SchemaType.View> & TreeNodeSchema
249+
>;
287250

288251
public constructor(props: ITreeViewConfiguration<TSchema>) {
289252
super(props);
290-
this.root = normalizeFieldSchema(props.schema);
291-
}
292-
}
253+
const treeSchema = createTreeSchema(this.schema);
254+
this.root = treeSchema.root;
255+
this.definitions = treeSchema.definitions;
293256

294-
/**
295-
* {@link TreeViewConfigurationAlpha}
296-
* @sealed @alpha
297-
*/
298-
export interface TreeSchema extends SimpleTreeSchema {
299-
/**
300-
* {@inheritDoc SimpleTreeSchema.root}
301-
*/
302-
readonly root: FieldSchemaAlpha;
303-
304-
/**
305-
* {@inheritDoc SimpleTreeSchema.definitions}
306-
*/
307-
readonly definitions: ReadonlyMap<string, SimpleNodeSchema & TreeNodeSchema>;
257+
// Eagerly perform these conversions to surface errors sooner.
258+
toInitialSchema(this.root);
259+
transformSimpleSchema(treeSchema, toUnhydratedSchema);
260+
}
308261
}
309262

310263
/**

packages/dds/tree/src/simple-tree/api/discrepancies.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ import {
4343
type FieldSchema,
4444
} from "../fieldSchema.js";
4545
import { LeafNodeSchema } from "../leafNodeSchema.js";
46-
import type { TreeSchema } from "./configuration.js";
46+
import type { TreeSchema } from "../treeSchema.js";
4747
import { tryStoredSchemaAsArray } from "./customTree.js";
4848
import { FieldKinds } from "../../feature-libraries/index.js";
4949

0 commit comments

Comments
 (0)