Skip to content
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
45ed7f7
Initial change. Implemented viewSchemaToViewCompatibilitySchema. No t…
TommyBrosman Oct 23, 2025
f76e1eb
Added test coverage using the existing toSimpleSchema tests as a star…
TommyBrosman Oct 23, 2025
491c2ed
Refactored the schema copying code.
TommyBrosman Oct 23, 2025
25eef86
Removed persistedMetadata and metadata from Node schemas copied in Vi…
TommyBrosman Oct 23, 2025
d80bee6
Minor: better typing in copy methods.
TommyBrosman Oct 23, 2025
15fa315
Added snapshot tests.
TommyBrosman Oct 24, 2025
a239d88
Got snapshotting working correctly. It currently involves a lot of ca…
TommyBrosman Oct 24, 2025
b168cd4
Cleanup.
TommyBrosman Oct 24, 2025
95c255a
Added a test around hasStagedSchemaUpgrades.
TommyBrosman Oct 24, 2025
7559289
Added staged allowed type info to SimpleSchema. Started replacing all…
TommyBrosman Oct 28, 2025
2e443d4
Replaced SimpleSchema allowedTypesIdentifiers with simpleAllowedTypes.
TommyBrosman Oct 29, 2025
4e970d2
- Fixed a couple references to allowedTypesIdentifiers.
TommyBrosman Oct 29, 2025
55ca84b
Minor refactors. Addressed some PR feedback.
TommyBrosman Oct 29, 2025
f48aa8a
- PR feedback.
TommyBrosman Oct 29, 2025
ee473df
Removed change that shouldn't be in this PR.
TommyBrosman Oct 29, 2025
8dc387a
Merge branch 'main' into simple-allowed-types
TommyBrosman Oct 29, 2025
9bf1810
- Removed evaluateSimpleAllowedTypes from AnnotatedAllowedTypes (whic…
TommyBrosman Oct 29, 2025
97e1b12
Apply suggestion from @noencke
TommyBrosman Oct 29, 2025
6eddd77
Rename.
TommyBrosman Oct 29, 2025
1464268
Merge branch 'simple-allowed-types' of https://github.com/TommyBrosma…
TommyBrosman Oct 29, 2025
f4e16ea
API Extractor.
TommyBrosman Oct 29, 2025
d00a237
Rename.
TommyBrosman Oct 29, 2025
e17113c
Merge branch 'main' into simple-allowed-types
TommyBrosman Oct 30, 2025
550240b
Fixed exports.
TommyBrosman Oct 30, 2025
7a218b1
Fix for failing test and cases where `toSimpleTreeSchema` should not …
TommyBrosman Oct 30, 2025
69c3674
Minor rename.
TommyBrosman Oct 30, 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
15 changes: 11 additions & 4 deletions packages/dds/tree/api-report/tree.alpha.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,8 @@ export class FieldSchemaAlpha<Kind extends FieldKind = FieldKind, Types extends
get allowedTypesIdentifiers(): ReadonlySet<string>;
// (undocumented)
get persistedMetadata(): JsonCompatibleReadOnlyObject | undefined;
// (undocumented)
get simpleAllowedTypes(): ReadonlyMap<string, SimpleAllowedTypeAttributes>;
}

// @alpha @sealed @system
Expand Down Expand Up @@ -1004,17 +1006,22 @@ export type SharedTreeOptions = Partial<CodecWriteOptions> & Partial<SharedTreeF
// @beta @input
export type SharedTreeOptionsBeta = ForestOptions;

// @alpha @sealed
export interface SimpleAllowedTypeAttributes {
readonly isStaged: boolean | undefined;
}

// @alpha @sealed
export interface SimpleArrayNodeSchema<out TCustomMetadata = unknown> extends SimpleNodeSchemaBaseAlpha<NodeKind.Array, TCustomMetadata> {
readonly allowedTypesIdentifiers: ReadonlySet<string>;
readonly simpleAllowedTypes: ReadonlyMap<string, SimpleAllowedTypeAttributes>;
}

// @alpha @sealed
export interface SimpleFieldSchema {
readonly allowedTypesIdentifiers: ReadonlySet<string>;
readonly kind: FieldKind;
readonly metadata: FieldSchemaMetadata;
readonly persistedMetadata?: JsonCompatibleReadOnlyObject | undefined;
readonly simpleAllowedTypes: ReadonlyMap<string, SimpleAllowedTypeAttributes>;
}

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

// @alpha @sealed
export interface SimpleMapNodeSchema<out TCustomMetadata = unknown> extends SimpleNodeSchemaBaseAlpha<NodeKind.Map, TCustomMetadata> {
readonly allowedTypesIdentifiers: ReadonlySet<string>;
readonly simpleAllowedTypes: ReadonlyMap<string, SimpleAllowedTypeAttributes>;
}

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

// @alpha @sealed
export interface SimpleRecordNodeSchema<out TCustomMetadata = unknown> extends SimpleNodeSchemaBaseAlpha<NodeKind.Record, TCustomMetadata> {
readonly allowedTypesIdentifiers: ReadonlySet<string>;
readonly simpleAllowedTypes: ReadonlyMap<string, SimpleAllowedTypeAttributes>;
}

// @alpha
Expand Down
1 change: 1 addition & 0 deletions packages/dds/tree/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ export {
type TreeParsingOptions,
type SchemaFactory_base,
type NumberKeys,
type SimpleAllowedTypeAttributes,
} from "./simple-tree/index.js";
export {
SharedTree,
Expand Down
28 changes: 25 additions & 3 deletions packages/dds/tree/src/shared-tree/sharedTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
type TreeStoredSchema,
TreeStoredSchemaRepository,
type TreeStoredSchemaSubscription,
type TreeTypeSet,
getCodecTreeForDetachedFieldIndexFormat,
makeDetachedFieldIndex,
moveToDetachedField,
Expand Down Expand Up @@ -98,6 +99,7 @@ import {
FieldKind,
type ITreeAlpha,
type SimpleObjectFieldSchema,
type SimpleAllowedTypeAttributes,
} from "../simple-tree/index.js";

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

/**
* Build the allowed types for a Stored Schema.
*
* @remarks Staged upgrades do not apply to stored schemas, so we omit the {@link SimpleAllowedTypeAttributes.isStaged | staging flag } when building {@link SimpleAllowedTypeAttributes}.
* @param types - The types to create allowed types for.
* @returns The allowed types.
*/
function buildSimpleAllowedTypeAttributesForStoredSchema(
types: TreeTypeSet,
): ReadonlyMap<string, SimpleAllowedTypeAttributes> {
const allowedTypesInfo = new Map<string, SimpleAllowedTypeAttributes>();
for (const type of types) {
// Stored schemas do not have staged upgrades
allowedTypesInfo.set(type, { isStaged: undefined });
}
return allowedTypesInfo;
}

function exportSimpleFieldSchemaStored(schema: TreeFieldStoredSchema): SimpleFieldSchema {
let kind: FieldKind;
switch (schema.kind) {
Expand All @@ -973,7 +993,7 @@ function exportSimpleFieldSchemaStored(schema: TreeFieldStoredSchema): SimpleFie
}
return {
kind,
allowedTypesIdentifiers: schema.types,
simpleAllowedTypes: buildSimpleAllowedTypeAttributesForStoredSchema(schema.types),
metadata: {},
persistedMetadata: schema.persistedMetadata,
};
Expand All @@ -989,7 +1009,7 @@ function exportSimpleNodeSchemaStored(schema: TreeNodeStoredSchema): SimpleNodeS
if (arrayTypes !== undefined) {
return {
kind: NodeKind.Array,
allowedTypesIdentifiers: arrayTypes,
simpleAllowedTypes: buildSimpleAllowedTypeAttributesForStoredSchema(arrayTypes),
metadata: {},
persistedMetadata: schema.metadata,
};
Expand All @@ -1008,7 +1028,9 @@ function exportSimpleNodeSchemaStored(schema: TreeNodeStoredSchema): SimpleNodeS
);
return {
kind: NodeKind.Map,
allowedTypesIdentifiers: schema.mapFields.types,
simpleAllowedTypes: buildSimpleAllowedTypeAttributesForStoredSchema(
schema.mapFields.types,
),
metadata: {},
persistedMetadata: schema.metadata,
};
Expand Down
31 changes: 17 additions & 14 deletions packages/dds/tree/src/simple-tree/api/schemaFromSimple.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
type FieldProps,
} from "../fieldSchema.js";
import type {
SimpleAllowedTypeAttributes,
SimpleFieldSchema,
SimpleNodeSchema,
SimpleTreeSchema,
Expand Down Expand Up @@ -65,7 +66,7 @@ function generateFieldSchema(
context: Context,
storedKey: string | undefined,
): FieldSchemaAlpha {
const allowed = generateAllowedTypes(simple.allowedTypesIdentifiers, context);
const allowed = generateAllowedTypes(simple.simpleAllowedTypes, context);
const props: Omit<FieldProps, "defaultProvider"> = {
metadata: simple.metadata,
key: storedKey,
Expand All @@ -84,8 +85,14 @@ function generateFieldSchema(
}
}

function generateAllowedTypes(allowed: ReadonlySet<string>, context: Context): AllowedTypes {
return [...allowed].map((id) => context.get(id) ?? fail(0xb5a /* Missing schema */));
function generateAllowedTypes(
allowed: ReadonlyMap<string, SimpleAllowedTypeAttributes>,
context: Context,
): AllowedTypes {
return Array.from(
allowed.keys(),
(id) => context.get(id) ?? fail(0xb5a /* Missing schema */),
);
}

function generateNode(
Expand All @@ -104,21 +111,17 @@ function generateNode(
return factory.objectAlpha(id, fields, { metadata: schema.metadata });
}
case NodeKind.Array:
return factory.arrayAlpha(
id,
generateAllowedTypes(schema.allowedTypesIdentifiers, context),
{ metadata: schema.metadata },
);
return factory.arrayAlpha(id, generateAllowedTypes(schema.simpleAllowedTypes, context), {
metadata: schema.metadata,
});
case NodeKind.Map:
return factory.mapAlpha(
id,
generateAllowedTypes(schema.allowedTypesIdentifiers, context),
{ metadata: schema.metadata },
);
return factory.mapAlpha(id, generateAllowedTypes(schema.simpleAllowedTypes, context), {
metadata: schema.metadata,
});
case NodeKind.Record:
return factory.recordAlpha(
id,
generateAllowedTypes(schema.allowedTypesIdentifiers, context),
generateAllowedTypes(schema.simpleAllowedTypes, context),
{ metadata: schema.metadata },
);
case NodeKind.Leaf:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,10 @@ function convertNodeSchema(

function convertArrayNodeSchema(schema: SimpleArrayNodeSchema): JsonArrayNodeSchema {
const allowedTypes: JsonSchemaRef[] = [];
schema.allowedTypesIdentifiers.forEach((type) => {
const allowedTypesIdentifiers: ReadonlySet<string> = new Set(
schema.simpleAllowedTypes.keys(),
);
allowedTypesIdentifiers.forEach((type) => {
allowedTypes.push(createSchemaRef(type));
});

Expand Down Expand Up @@ -206,7 +209,10 @@ function convertRecordLikeNodeSchema(
schema: SimpleRecordNodeSchema | SimpleMapNodeSchema,
): JsonMapNodeSchema | JsonRecordNodeSchema {
const allowedTypes: JsonSchemaRef[] = [];
schema.allowedTypesIdentifiers.forEach((type) => {
const allowedTypesIdentifiers: ReadonlySet<string> = new Set(
schema.simpleAllowedTypes.keys(),
);
allowedTypesIdentifiers.forEach((type) => {
allowedTypes.push(createSchemaRef(type));
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import { assert, unreachableCase } from "@fluidframework/core-utils/internal";
import { normalizeFieldSchema, type ImplicitFieldSchema } from "../fieldSchema.js";
import type {
SimpleAllowedTypeAttributes,
SimpleArrayNodeSchema,
SimpleFieldSchema,
SimpleLeafNodeSchema,
Expand All @@ -31,6 +32,7 @@ import { LeafNodeSchema } from "../leafNodeSchema.js";
*
* @param schema - The schema to convert
* @param copySchemaObjects - If true, TreeNodeSchema and FieldSchema are copied into plain JavaScript objects. Either way, custom metadata is referenced and not copied.
* @param preserveViewSchemaProperties - 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.
*
* @remarks
* Given that the Schema types used in {@link ImplicitFieldSchema} already implement the {@link SimpleNodeSchema} interfaces, there are limited use-cases for this function.
Expand All @@ -45,6 +47,7 @@ import { LeafNodeSchema } from "../leafNodeSchema.js";
export function toSimpleTreeSchema(
schema: ImplicitFieldSchema,
copySchemaObjects: boolean,
preserveViewSchemaProperties: boolean = true,
): SimpleTreeSchema {
const normalizedSchema = normalizeFieldSchema(schema);
const definitions = new Map<string, SimpleNodeSchema>();
Expand All @@ -59,15 +62,20 @@ export function toSimpleTreeSchema(
nodeSchema instanceof RecordNodeSchema,
0xb60 /* Invalid schema */,
);
const outSchema = copySchemaObjects ? copySimpleNodeSchema(nodeSchema) : nodeSchema;
const outSchema = copySchemaObjects
? copySimpleNodeSchema(nodeSchema, preserveViewSchemaProperties)
: nodeSchema;
definitions.set(nodeSchema.identifier, outSchema);
},
});

return {
root: copySchemaObjects
? ({
allowedTypesIdentifiers: normalizedSchema.allowedTypesIdentifiers,
simpleAllowedTypes: normalizeSimpleAllowedTypes(
normalizedSchema.simpleAllowedTypes,
preserveViewSchemaProperties,
),
kind: normalizedSchema.kind,
metadata: normalizedSchema.metadata,
persistedMetadata: normalizedSchema.persistedMetadata,
Expand All @@ -77,22 +85,46 @@ export function toSimpleTreeSchema(
};
}

/**
* Normalizes the {@link SimpleAllowedTypeAttributes} by either preserving or omitting view-specific schema properties.
* @param simpleAllowedTypes - The simple allowed types to normalize.
* @param preserveViewSchemaProperties - If true, properties used by view schema but not part of stored schema (for example, `isStaged` on allowed types) are preserved in the output.
* @returns The normalized simple allowed types.
*/
function normalizeSimpleAllowedTypes(
simpleAllowedTypes: ReadonlyMap<string, SimpleAllowedTypeAttributes>,
preserveViewSchemaProperties: boolean,
): ReadonlyMap<string, SimpleAllowedTypeAttributes> {
if (preserveViewSchemaProperties) {
return simpleAllowedTypes;
} else {
const normalized = new Map<string, SimpleAllowedTypeAttributes>();
for (const [identifier, attributes] of simpleAllowedTypes.entries()) {
normalized.set(identifier, { ...attributes, isStaged: undefined });
}
return normalized;
}
}

/**
* Copies a {@link SimpleNodeSchema} into a new plain JavaScript object.
*
* @remarks Caches the result on the input schema for future calls.
*/
function copySimpleNodeSchema(schema: SimpleNodeSchema): SimpleNodeSchema {
function copySimpleNodeSchema(
schema: SimpleNodeSchema,
preserveViewSchemaProperties: boolean,
): SimpleNodeSchema {
const kind = schema.kind;
switch (kind) {
case NodeKind.Leaf:
return copySimpleLeafSchema(schema);
case NodeKind.Array:
case NodeKind.Map:
case NodeKind.Record:
return copySimpleSchemaWithAllowedTypes(schema);
return copySimpleSchemaWithAllowedTypes(schema, preserveViewSchemaProperties);
case NodeKind.Object:
return copySimpleObjectSchema(schema);
return copySimpleObjectSchema(schema, preserveViewSchemaProperties);
default:
unreachableCase(kind);
}
Expand All @@ -109,22 +141,32 @@ function copySimpleLeafSchema(schema: SimpleLeafNodeSchema): SimpleLeafNodeSchem

function copySimpleSchemaWithAllowedTypes(
schema: SimpleMapNodeSchema | SimpleArrayNodeSchema | SimpleRecordNodeSchema,
preserveViewSchemaProperties: boolean,
): SimpleMapNodeSchema | SimpleArrayNodeSchema | SimpleRecordNodeSchema {
return {
kind: schema.kind,
allowedTypesIdentifiers: schema.allowedTypesIdentifiers,
simpleAllowedTypes: normalizeSimpleAllowedTypes(
schema.simpleAllowedTypes,
preserveViewSchemaProperties,
),
metadata: schema.metadata,
persistedMetadata: schema.persistedMetadata,
};
}

function copySimpleObjectSchema(schema: SimpleObjectNodeSchema): SimpleObjectNodeSchema {
function copySimpleObjectSchema(
schema: SimpleObjectNodeSchema,
preserveViewSchemaProperties: boolean,
): SimpleObjectNodeSchema {
const fields: Map<string, SimpleObjectFieldSchema> = new Map();
for (const [propertyKey, field] of schema.fields) {
// field already is a SimpleObjectFieldSchema, but copy the subset of the properties needed by this interface to get a clean simple object.
fields.set(propertyKey, {
kind: field.kind,
allowedTypesIdentifiers: field.allowedTypesIdentifiers,
simpleAllowedTypes: normalizeSimpleAllowedTypes(
field.simpleAllowedTypes,
preserveViewSchemaProperties,
),
metadata: field.metadata,
persistedMetadata: field.persistedMetadata,
storedKey: field.storedKey,
Expand Down
16 changes: 16 additions & 0 deletions packages/dds/tree/src/simple-tree/core/allowedTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
type TreeNodeSchema,
} from "./treeNodeSchema.js";
import { schemaAsTreeNodeValid } from "./treeNodeValid.js";
import type { SimpleAllowedTypeAttributes } from "../simpleSchema.js";

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

/**
* Get the {@link SimpleAllowedTypeAttributes} version of the allowed types set.
*/
public static evaluateSimpleAllowedTypes(
annotatedAllowedTypes: AnnotatedAllowedTypes,
): ReadonlyMap<string, SimpleAllowedTypeAttributes> {
const simpleAllowedTypes = new Map<string, SimpleAllowedTypeAttributes>();
for (const type of annotatedAllowedTypes.evaluate().types) {
simpleAllowedTypes.set(type.type.identifier, {
isStaged: type.metadata.stagedSchemaUpgrade !== undefined,
});
}
return simpleAllowedTypes;
}

public static override [Symbol.hasInstance]<TThis extends { prototype: object }>(
this: TThis,
value: unknown,
Expand Down
Loading
Loading