Skip to content

Commit fb18659

Browse files
TommyBrosmannoencke
authored andcommitted
Shared Tree: Add allowed types object with staged schema upgrade information to SimpleSchema (microsoft#25770)
This PR removes the `allowedTypesIdentifiers` Set from Simple Schema types and replaces it with a Map keyed on the identifier. Additionally, `isStaged` has been added to reflect whether the allowed types have associated schema upgrades. ## Breaking Changes This change breaks a few alpha APIs in SimpleSchema: - `SimpleFieldSchema.allowedTypesIdentifiers` - `SimpleRecordNodeSchema.allowedTypesIdentifiers` - `SimpleMapNodeSchema.allowedTypesIdentifiers` - `SimpleArrayNodeSchema.allowedTypesIdentifiers` --------- Co-authored-by: Noah Encke <78610362+noencke@users.noreply.github.com>
1 parent f63d3bf commit fb18659

File tree

22 files changed

+322
-105
lines changed

22 files changed

+322
-105
lines changed

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

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,8 @@ export class FieldSchemaAlpha<Kind extends FieldKind = FieldKind, Types extends
274274
get allowedTypesIdentifiers(): ReadonlySet<string>;
275275
// (undocumented)
276276
get persistedMetadata(): JsonCompatibleReadOnlyObject | undefined;
277+
// (undocumented)
278+
get simpleAllowedTypes(): ReadonlyMap<string, SimpleAllowedTypeAttributes>;
277279
}
278280

279281
// @alpha @sealed @system
@@ -1004,17 +1006,22 @@ export type SharedTreeOptions = Partial<CodecWriteOptions> & Partial<SharedTreeF
10041006
// @beta @input
10051007
export type SharedTreeOptionsBeta = ForestOptions;
10061008

1009+
// @alpha @sealed
1010+
export interface SimpleAllowedTypeAttributes {
1011+
readonly isStaged: boolean | undefined;
1012+
}
1013+
10071014
// @alpha @sealed
10081015
export interface SimpleArrayNodeSchema<out TCustomMetadata = unknown> extends SimpleNodeSchemaBaseAlpha<NodeKind.Array, TCustomMetadata> {
1009-
readonly allowedTypesIdentifiers: ReadonlySet<string>;
1016+
readonly simpleAllowedTypes: ReadonlyMap<string, SimpleAllowedTypeAttributes>;
10101017
}
10111018

10121019
// @alpha @sealed
10131020
export interface SimpleFieldSchema {
1014-
readonly allowedTypesIdentifiers: ReadonlySet<string>;
10151021
readonly kind: FieldKind;
10161022
readonly metadata: FieldSchemaMetadata;
10171023
readonly persistedMetadata?: JsonCompatibleReadOnlyObject | undefined;
1024+
readonly simpleAllowedTypes: ReadonlyMap<string, SimpleAllowedTypeAttributes>;
10181025
}
10191026

10201027
// @alpha @sealed
@@ -1024,7 +1031,7 @@ export interface SimpleLeafNodeSchema extends SimpleNodeSchemaBaseAlpha<NodeKind
10241031

10251032
// @alpha @sealed
10261033
export interface SimpleMapNodeSchema<out TCustomMetadata = unknown> extends SimpleNodeSchemaBaseAlpha<NodeKind.Map, TCustomMetadata> {
1027-
readonly allowedTypesIdentifiers: ReadonlySet<string>;
1034+
readonly simpleAllowedTypes: ReadonlyMap<string, SimpleAllowedTypeAttributes>;
10281035
}
10291036

10301037
// @alpha
@@ -1053,7 +1060,7 @@ export interface SimpleObjectNodeSchema<out TCustomMetadata = unknown> extends S
10531060

10541061
// @alpha @sealed
10551062
export interface SimpleRecordNodeSchema<out TCustomMetadata = unknown> extends SimpleNodeSchemaBaseAlpha<NodeKind.Record, TCustomMetadata> {
1056-
readonly allowedTypesIdentifiers: ReadonlySet<string>;
1063+
readonly simpleAllowedTypes: ReadonlyMap<string, SimpleAllowedTypeAttributes>;
10571064
}
10581065

10591066
// @alpha

packages/dds/tree/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,7 @@ export {
280280
type TreeParsingOptions,
281281
type SchemaFactory_base,
282282
type NumberKeys,
283+
type SimpleAllowedTypeAttributes,
283284
} from "./simple-tree/index.js";
284285
export {
285286
SharedTree,

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

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import {
4242
type TreeStoredSchema,
4343
TreeStoredSchemaRepository,
4444
type TreeStoredSchemaSubscription,
45+
type TreeTypeSet,
4546
getCodecTreeForDetachedFieldIndexFormat,
4647
makeDetachedFieldIndex,
4748
moveToDetachedField,
@@ -98,6 +99,7 @@ import {
9899
FieldKind,
99100
type ITreeAlpha,
100101
type SimpleObjectFieldSchema,
102+
type SimpleAllowedTypeAttributes,
101103
} from "../simple-tree/index.js";
102104

103105
import { SchematizingSimpleTreeView } from "./schematizingTreeView.js";
@@ -952,6 +954,24 @@ export const defaultSharedTreeOptions: Required<SharedTreeOptionsInternal> = {
952954
shouldEncodeIncrementally: defaultIncrementalEncodingPolicy,
953955
};
954956

957+
/**
958+
* Build the allowed types for a Stored Schema.
959+
*
960+
* @remarks Staged upgrades do not apply to stored schemas, so we omit the {@link SimpleAllowedTypeAttributes.isStaged | staging flag } when building {@link SimpleAllowedTypeAttributes}.
961+
* @param types - The types to create allowed types for.
962+
* @returns The allowed types.
963+
*/
964+
function buildSimpleAllowedTypeAttributesForStoredSchema(
965+
types: TreeTypeSet,
966+
): ReadonlyMap<string, SimpleAllowedTypeAttributes> {
967+
const allowedTypesInfo = new Map<string, SimpleAllowedTypeAttributes>();
968+
for (const type of types) {
969+
// Stored schemas do not have staged upgrades
970+
allowedTypesInfo.set(type, { isStaged: undefined });
971+
}
972+
return allowedTypesInfo;
973+
}
974+
955975
function exportSimpleFieldSchemaStored(schema: TreeFieldStoredSchema): SimpleFieldSchema {
956976
let kind: FieldKind;
957977
switch (schema.kind) {
@@ -973,7 +993,7 @@ function exportSimpleFieldSchemaStored(schema: TreeFieldStoredSchema): SimpleFie
973993
}
974994
return {
975995
kind,
976-
allowedTypesIdentifiers: schema.types,
996+
simpleAllowedTypes: buildSimpleAllowedTypeAttributesForStoredSchema(schema.types),
977997
metadata: {},
978998
persistedMetadata: schema.persistedMetadata,
979999
};
@@ -989,7 +1009,7 @@ function exportSimpleNodeSchemaStored(schema: TreeNodeStoredSchema): SimpleNodeS
9891009
if (arrayTypes !== undefined) {
9901010
return {
9911011
kind: NodeKind.Array,
992-
allowedTypesIdentifiers: arrayTypes,
1012+
simpleAllowedTypes: buildSimpleAllowedTypeAttributesForStoredSchema(arrayTypes),
9931013
metadata: {},
9941014
persistedMetadata: schema.metadata,
9951015
};
@@ -1008,7 +1028,9 @@ function exportSimpleNodeSchemaStored(schema: TreeNodeStoredSchema): SimpleNodeS
10081028
);
10091029
return {
10101030
kind: NodeKind.Map,
1011-
allowedTypesIdentifiers: schema.mapFields.types,
1031+
simpleAllowedTypes: buildSimpleAllowedTypeAttributesForStoredSchema(
1032+
schema.mapFields.types,
1033+
),
10121034
metadata: {},
10131035
persistedMetadata: schema.metadata,
10141036
};

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

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
type FieldProps,
1414
} from "../fieldSchema.js";
1515
import type {
16+
SimpleAllowedTypeAttributes,
1617
SimpleFieldSchema,
1718
SimpleNodeSchema,
1819
SimpleTreeSchema,
@@ -65,7 +66,7 @@ function generateFieldSchema(
6566
context: Context,
6667
storedKey: string | undefined,
6768
): FieldSchemaAlpha {
68-
const allowed = generateAllowedTypes(simple.allowedTypesIdentifiers, context);
69+
const allowed = generateAllowedTypes(simple.simpleAllowedTypes, context);
6970
const props: Omit<FieldProps, "defaultProvider"> = {
7071
metadata: simple.metadata,
7172
key: storedKey,
@@ -84,8 +85,14 @@ function generateFieldSchema(
8485
}
8586
}
8687

87-
function generateAllowedTypes(allowed: ReadonlySet<string>, context: Context): AllowedTypes {
88-
return [...allowed].map((id) => context.get(id) ?? fail(0xb5a /* Missing schema */));
88+
function generateAllowedTypes(
89+
allowed: ReadonlyMap<string, SimpleAllowedTypeAttributes>,
90+
context: Context,
91+
): AllowedTypes {
92+
return Array.from(
93+
allowed.keys(),
94+
(id) => context.get(id) ?? fail(0xb5a /* Missing schema */),
95+
);
8996
}
9097

9198
function generateNode(
@@ -104,21 +111,17 @@ function generateNode(
104111
return factory.objectAlpha(id, fields, { metadata: schema.metadata });
105112
}
106113
case NodeKind.Array:
107-
return factory.arrayAlpha(
108-
id,
109-
generateAllowedTypes(schema.allowedTypesIdentifiers, context),
110-
{ metadata: schema.metadata },
111-
);
114+
return factory.arrayAlpha(id, generateAllowedTypes(schema.simpleAllowedTypes, context), {
115+
metadata: schema.metadata,
116+
});
112117
case NodeKind.Map:
113-
return factory.mapAlpha(
114-
id,
115-
generateAllowedTypes(schema.allowedTypesIdentifiers, context),
116-
{ metadata: schema.metadata },
117-
);
118+
return factory.mapAlpha(id, generateAllowedTypes(schema.simpleAllowedTypes, context), {
119+
metadata: schema.metadata,
120+
});
118121
case NodeKind.Record:
119122
return factory.recordAlpha(
120123
id,
121-
generateAllowedTypes(schema.allowedTypesIdentifiers, context),
124+
generateAllowedTypes(schema.simpleAllowedTypes, context),
122125
{ metadata: schema.metadata },
123126
);
124127
case NodeKind.Leaf:

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,10 @@ function convertNodeSchema(
107107

108108
function convertArrayNodeSchema(schema: SimpleArrayNodeSchema): JsonArrayNodeSchema {
109109
const allowedTypes: JsonSchemaRef[] = [];
110-
schema.allowedTypesIdentifiers.forEach((type) => {
110+
const allowedTypesIdentifiers: ReadonlySet<string> = new Set(
111+
schema.simpleAllowedTypes.keys(),
112+
);
113+
allowedTypesIdentifiers.forEach((type) => {
111114
allowedTypes.push(createSchemaRef(type));
112115
});
113116

@@ -206,7 +209,10 @@ function convertRecordLikeNodeSchema(
206209
schema: SimpleRecordNodeSchema | SimpleMapNodeSchema,
207210
): JsonMapNodeSchema | JsonRecordNodeSchema {
208211
const allowedTypes: JsonSchemaRef[] = [];
209-
schema.allowedTypesIdentifiers.forEach((type) => {
212+
const allowedTypesIdentifiers: ReadonlySet<string> = new Set(
213+
schema.simpleAllowedTypes.keys(),
214+
);
215+
allowedTypesIdentifiers.forEach((type) => {
210216
allowedTypes.push(createSchemaRef(type));
211217
});
212218

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

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import { assert, unreachableCase } from "@fluidframework/core-utils/internal";
77
import { normalizeFieldSchema, type ImplicitFieldSchema } from "../fieldSchema.js";
88
import type {
9+
SimpleAllowedTypeAttributes,
910
SimpleArrayNodeSchema,
1011
SimpleFieldSchema,
1112
SimpleLeafNodeSchema,
@@ -31,6 +32,7 @@ import { LeafNodeSchema } from "../leafNodeSchema.js";
3132
*
3233
* @param schema - The schema to convert
3334
* @param copySchemaObjects - If true, TreeNodeSchema and FieldSchema are copied into plain JavaScript objects. Either way, custom metadata is referenced and not copied.
35+
* @param isViewSchema - If true (default), properties used by view schema but not part of stored schema (for example, `isStaged` on allowed types) are preserved in the output.
3436
*
3537
* @remarks
3638
* Given that the Schema types used in {@link ImplicitFieldSchema} already implement the {@link SimpleNodeSchema} interfaces, there are limited use-cases for this function.
@@ -45,6 +47,7 @@ import { LeafNodeSchema } from "../leafNodeSchema.js";
4547
export function toSimpleTreeSchema(
4648
schema: ImplicitFieldSchema,
4749
copySchemaObjects: boolean,
50+
isViewSchema: boolean = true,
4851
): SimpleTreeSchema {
4952
const normalizedSchema = normalizeFieldSchema(schema);
5053
const definitions = new Map<string, SimpleNodeSchema>();
@@ -59,15 +62,20 @@ export function toSimpleTreeSchema(
5962
nodeSchema instanceof RecordNodeSchema,
6063
0xb60 /* Invalid schema */,
6164
);
62-
const outSchema = copySchemaObjects ? copySimpleNodeSchema(nodeSchema) : nodeSchema;
65+
const outSchema = copySchemaObjects
66+
? copySimpleNodeSchema(nodeSchema, isViewSchema)
67+
: nodeSchema;
6368
definitions.set(nodeSchema.identifier, outSchema);
6469
},
6570
});
6671

6772
return {
6873
root: copySchemaObjects
6974
? ({
70-
allowedTypesIdentifiers: normalizedSchema.allowedTypesIdentifiers,
75+
simpleAllowedTypes: normalizeSimpleAllowedTypes(
76+
normalizedSchema.simpleAllowedTypes,
77+
isViewSchema,
78+
),
7179
kind: normalizedSchema.kind,
7280
metadata: normalizedSchema.metadata,
7381
persistedMetadata: normalizedSchema.persistedMetadata,
@@ -77,22 +85,46 @@ export function toSimpleTreeSchema(
7785
};
7886
}
7987

88+
/**
89+
* Normalizes the {@link SimpleAllowedTypeAttributes} by either preserving or omitting view-specific schema properties.
90+
* @param simpleAllowedTypes - The simple allowed types to normalize.
91+
* @param isViewSchema - If true, properties used by view schema but not part of stored schema (for example, `isStaged` on allowed types) are preserved in the output.
92+
* @returns The normalized simple allowed types.
93+
*/
94+
function normalizeSimpleAllowedTypes(
95+
simpleAllowedTypes: ReadonlyMap<string, SimpleAllowedTypeAttributes>,
96+
isViewSchema: boolean,
97+
): ReadonlyMap<string, SimpleAllowedTypeAttributes> {
98+
if (isViewSchema) {
99+
return simpleAllowedTypes;
100+
} else {
101+
const normalized = new Map<string, SimpleAllowedTypeAttributes>();
102+
for (const [identifier, attributes] of simpleAllowedTypes.entries()) {
103+
normalized.set(identifier, { ...attributes, isStaged: undefined });
104+
}
105+
return normalized;
106+
}
107+
}
108+
80109
/**
81110
* Copies a {@link SimpleNodeSchema} into a new plain JavaScript object.
82111
*
83112
* @remarks Caches the result on the input schema for future calls.
84113
*/
85-
function copySimpleNodeSchema(schema: SimpleNodeSchema): SimpleNodeSchema {
114+
function copySimpleNodeSchema(
115+
schema: SimpleNodeSchema,
116+
isViewSchema: boolean,
117+
): SimpleNodeSchema {
86118
const kind = schema.kind;
87119
switch (kind) {
88120
case NodeKind.Leaf:
89121
return copySimpleLeafSchema(schema);
90122
case NodeKind.Array:
91123
case NodeKind.Map:
92124
case NodeKind.Record:
93-
return copySimpleSchemaWithAllowedTypes(schema);
125+
return copySimpleSchemaWithAllowedTypes(schema, isViewSchema);
94126
case NodeKind.Object:
95-
return copySimpleObjectSchema(schema);
127+
return copySimpleObjectSchema(schema, isViewSchema);
96128
default:
97129
unreachableCase(kind);
98130
}
@@ -109,22 +141,26 @@ function copySimpleLeafSchema(schema: SimpleLeafNodeSchema): SimpleLeafNodeSchem
109141

110142
function copySimpleSchemaWithAllowedTypes(
111143
schema: SimpleMapNodeSchema | SimpleArrayNodeSchema | SimpleRecordNodeSchema,
144+
isViewSchema: boolean,
112145
): SimpleMapNodeSchema | SimpleArrayNodeSchema | SimpleRecordNodeSchema {
113146
return {
114147
kind: schema.kind,
115-
allowedTypesIdentifiers: schema.allowedTypesIdentifiers,
148+
simpleAllowedTypes: normalizeSimpleAllowedTypes(schema.simpleAllowedTypes, isViewSchema),
116149
metadata: schema.metadata,
117150
persistedMetadata: schema.persistedMetadata,
118151
};
119152
}
120153

121-
function copySimpleObjectSchema(schema: SimpleObjectNodeSchema): SimpleObjectNodeSchema {
154+
function copySimpleObjectSchema(
155+
schema: SimpleObjectNodeSchema,
156+
isViewSchema: boolean,
157+
): SimpleObjectNodeSchema {
122158
const fields: Map<string, SimpleObjectFieldSchema> = new Map();
123159
for (const [propertyKey, field] of schema.fields) {
124160
// field already is a SimpleObjectFieldSchema, but copy the subset of the properties needed by this interface to get a clean simple object.
125161
fields.set(propertyKey, {
126162
kind: field.kind,
127-
allowedTypesIdentifiers: field.allowedTypesIdentifiers,
163+
simpleAllowedTypes: normalizeSimpleAllowedTypes(field.simpleAllowedTypes, isViewSchema),
128164
metadata: field.metadata,
129165
persistedMetadata: field.persistedMetadata,
130166
storedKey: field.storedKey,

packages/dds/tree/src/simple-tree/core/allowedTypes.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
type TreeNodeSchema,
2626
} from "./treeNodeSchema.js";
2727
import { schemaAsTreeNodeValid } from "./treeNodeValid.js";
28+
import type { SimpleAllowedTypeAttributes } from "../simpleSchema.js";
2829

2930
/**
3031
* Schema for types allowed in some location in a tree (like a field, map entry or array).
@@ -252,6 +253,21 @@ export class AnnotatedAllowedTypesInternal<
252253
return this.lazyEvaluate.value.identifiers;
253254
}
254255

256+
/**
257+
* Get the {@link SimpleAllowedTypeAttributes} version of the allowed types set.
258+
*/
259+
public static evaluateSimpleAllowedTypes(
260+
annotatedAllowedTypes: AnnotatedAllowedTypes,
261+
): ReadonlyMap<string, SimpleAllowedTypeAttributes> {
262+
const simpleAllowedTypes = new Map<string, SimpleAllowedTypeAttributes>();
263+
for (const type of annotatedAllowedTypes.evaluate().types) {
264+
simpleAllowedTypes.set(type.type.identifier, {
265+
isStaged: type.metadata.stagedSchemaUpgrade !== undefined,
266+
});
267+
}
268+
return simpleAllowedTypes;
269+
}
270+
255271
public static override [Symbol.hasInstance]<TThis extends { prototype: object }>(
256272
this: TThis,
257273
value: unknown,

0 commit comments

Comments
 (0)