Skip to content

Commit 53ecef0

Browse files
CraigMacomberanthony-murphy-agent
authored andcommitted
Support recursive annotated and staged types (microsoft#25199)
## Description Add support for recursive annotated and staged types.
1 parent 8e4ecd1 commit 53ecef0

File tree

13 files changed

+704
-53
lines changed

13 files changed

+704
-53
lines changed

.changeset/quiet-hairs-lay.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"fluid-framework": minor
3+
"@fluidframework/tree": minor
4+
"__section": tree
5+
---
6+
Add SchemaFactoryAlpha.typesRecursive and SchemaFactoryAlpha.stagedRecursive
7+
8+
With these new APIs, it is now possible to [`stage`](https://fluidframework.com/docs/api/fluid-framework/schemafactoryalpha-class#staged-property) changes to recursive types.

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ export type AllowedTypesFullEvaluated = AllowedTypesFull<readonly AnnotatedAllow
3333
// @alpha @sealed
3434
export type AllowedTypesFullFromMixed<T extends readonly (AnnotatedAllowedType | LazyItem<TreeNodeSchema>)[]> = UnannotateAllowedTypesList<T> & AnnotatedAllowedTypes<AnnotateAllowedTypesList<T>>;
3535

36+
// @alpha @sealed @system
37+
export type AllowedTypesFullFromMixedUnsafe<T extends readonly Unenforced<AnnotatedAllowedType | LazyItem<TreeNodeSchema>>[]> = UnannotateAllowedTypesListUnsafe<T> & AnnotatedAllowedTypes<AnnotateAllowedTypesListUnsafe<T>>;
38+
39+
// @alpha @sealed @system
40+
export type AllowedTypesFullUnsafe<T extends readonly AnnotatedAllowedTypeUnsafe[] = readonly AnnotatedAllowedTypeUnsafe[]> = AnnotatedAllowedTypes<T> & UnannotateAllowedTypesListUnsafe<T>;
41+
3642
// @alpha @input
3743
export interface AllowedTypesMetadata {
3844
readonly custom?: unknown;
@@ -46,6 +52,11 @@ export type AnnotateAllowedTypesList<T extends readonly (AnnotatedAllowedType |
4652
[I in keyof T]: T[I] extends AnnotatedAllowedType<unknown> ? T[I] : AnnotatedAllowedType<T[I]>;
4753
};
4854

55+
// @alpha @sealed @system
56+
export type AnnotateAllowedTypesListUnsafe<T extends readonly Unenforced<AnnotatedAllowedType | LazyItem<TreeNodeSchema>>[]> = {
57+
[I in keyof T]: T[I] extends AnnotatedAllowedTypeUnsafe ? T[I] : AnnotatedAllowedTypeUnsafe<T[I]>;
58+
};
59+
4960
// @alpha @sealed
5061
export interface AnnotatedAllowedType<T = LazyItem<TreeNodeSchema>> {
5162
readonly metadata: AllowedTypeMetadata;
@@ -61,6 +72,14 @@ export interface AnnotatedAllowedTypes<T = readonly AnnotatedAllowedType[]> exte
6172
readonly types: T;
6273
}
6374

75+
// @alpha @sealed @system
76+
export interface AnnotatedAllowedTypesUnsafe extends AnnotatedAllowedTypes<LazyItem<System_Unsafe.TreeNodeSchemaUnsafe>> {
77+
}
78+
79+
// @alpha @sealed @system
80+
export interface AnnotatedAllowedTypeUnsafe<T = Unenforced<LazyItem<TreeNodeSchema>>> extends AnnotatedAllowedType<T> {
81+
}
82+
6483
// @public @system
6584
type ApplyKind<T, Kind extends FieldKind> = {
6685
[FieldKind.Required]: T;
@@ -896,8 +915,12 @@ export class SchemaFactoryAlpha<out TScope extends string | undefined = string |
896915
scopedFactoryAlpha<const T extends TName, TNameInner extends number | string = string>(name: T): SchemaFactoryAlpha<ScopedSchemaName<TScope, T>, TNameInner>;
897916
static staged: <const T extends LazyItem<TreeNodeSchema>>(t: T | AnnotatedAllowedType<T>) => AnnotatedAllowedType<T>;
898917
staged: <const T extends LazyItem<TreeNodeSchema>>(t: T | AnnotatedAllowedType<T>) => AnnotatedAllowedType<T>;
918+
static stagedRecursive: <const T extends unknown>(t: T) => AnnotatedAllowedTypeUnsafe<UnannotateAllowedTypeUnsafe<T>>;
919+
stagedRecursive: <const T extends unknown>(t: T) => AnnotatedAllowedTypeUnsafe<UnannotateAllowedTypeUnsafe<T>>;
899920
static types: <const T extends readonly (LazyItem<TreeNodeSchema> | AnnotatedAllowedType<LazyItem<TreeNodeSchema>>)[]>(t: T, metadata?: AllowedTypesMetadata | undefined) => AllowedTypesFullFromMixed<T>;
900921
types: <const T extends readonly (LazyItem<TreeNodeSchema> | AnnotatedAllowedType<LazyItem<TreeNodeSchema>>)[]>(t: T, metadata?: AllowedTypesMetadata | undefined) => AllowedTypesFullFromMixed<T>;
922+
static typesRecursive: <const T extends readonly unknown[]>(t: T, metadata?: AllowedTypesMetadata | undefined) => AllowedTypesFullFromMixedUnsafe<T>;
923+
typesRecursive: <const T extends readonly unknown[]>(t: T, metadata?: AllowedTypesMetadata | undefined) => AllowedTypesFullFromMixedUnsafe<T>;
901924
}
902925

903926
// @beta
@@ -936,7 +959,9 @@ export interface SchemaStatics {
936959
// @alpha @sealed @system
937960
export interface SchemaStaticsAlpha {
938961
readonly staged: <const T extends LazyItem<TreeNodeSchema>>(t: T | AnnotatedAllowedType<T>) => AnnotatedAllowedType<T>;
962+
stagedRecursive: <const T extends Unenforced<AnnotatedAllowedType | LazyItem<TreeNodeSchema>>>(t: T) => AnnotatedAllowedTypeUnsafe<UnannotateAllowedTypeUnsafe<T>>;
939963
readonly types: <const T extends readonly (AnnotatedAllowedType | LazyItem<TreeNodeSchema>)[]>(t: T, metadata?: AllowedTypesMetadata) => AllowedTypesFullFromMixed<T>;
964+
readonly typesRecursive: <const T extends readonly Unenforced<AnnotatedAllowedType | LazyItem<TreeNodeSchema>>[]>(t: T, metadata?: AllowedTypesMetadata) => AllowedTypesFullFromMixedUnsafe<T>;
940965
}
941966

942967
// @alpha @sealed
@@ -1615,6 +1640,16 @@ export type UnannotateAllowedTypesList<T extends readonly (AnnotatedAllowedType
16151640
[I in keyof T]: T[I] extends AnnotatedAllowedType<infer X> ? X : T[I];
16161641
};
16171642

1643+
// @alpha @sealed @system
1644+
export type UnannotateAllowedTypesListUnsafe<T extends readonly Unenforced<AnnotatedAllowedType | LazyItem<TreeNodeSchema>>[]> = {
1645+
readonly [I in keyof T]: T[I] extends {
1646+
type: infer X;
1647+
} ? X : T[I];
1648+
};
1649+
1650+
// @alpha @sealed @system
1651+
export type UnannotateAllowedTypeUnsafe<T extends Unenforced<AnnotatedAllowedTypeUnsafe | LazyItem<System_Unsafe.TreeNodeSchemaUnsafe>>> = T extends AnnotatedAllowedTypeUnsafe<infer X> ? X : T;
1652+
16181653
// @public
16191654
export type Unenforced<_DesiredExtendsConstraint> = unknown;
16201655

packages/dds/tree/src/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,13 @@ export {
137137
type ArrayNodeCustomizableSchemaUnsafe,
138138
type MapNodeCustomizableSchemaUnsafe,
139139
type TreeRecordNodeUnsafe,
140+
type UnannotateAllowedTypeUnsafe,
141+
type AnnotatedAllowedTypeUnsafe,
142+
type AnnotatedAllowedTypesUnsafe,
143+
type AllowedTypesFullUnsafe,
144+
type AllowedTypesFullFromMixedUnsafe,
145+
type UnannotateAllowedTypesListUnsafe,
146+
type AnnotateAllowedTypesListUnsafe,
140147
// System types (not in Internal types for various reasons, like doc links or cannot be named errors).
141148
type typeSchemaSymbol,
142149
type TreeNodeSchemaNonClass,

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,13 @@ export type {
9090
MapNodeCustomizableSchemaUnsafe,
9191
System_Unsafe,
9292
TreeRecordNodeUnsafe,
93+
UnannotateAllowedTypeUnsafe,
94+
AnnotatedAllowedTypeUnsafe,
95+
AnnotatedAllowedTypesUnsafe,
96+
AllowedTypesFullUnsafe,
97+
AllowedTypesFullFromMixedUnsafe,
98+
UnannotateAllowedTypesListUnsafe,
99+
AnnotateAllowedTypesListUnsafe,
93100
} from "./typesUnsafe.js";
94101

95102
export {

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

Lines changed: 79 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,14 @@ import {
4040
AnnotatedAllowedTypesInternal,
4141
} from "../core/index.js";
4242
import type {
43+
AllowedTypesFullFromMixedUnsafe,
44+
AnnotatedAllowedTypeUnsafe,
4345
ArrayNodeCustomizableSchemaUnsafe,
4446
MapNodeCustomizableSchemaUnsafe,
4547
System_Unsafe,
4648
TreeRecordNodeUnsafe,
49+
UnannotateAllowedTypeUnsafe,
50+
Unenforced,
4751
} from "./typesUnsafe.js";
4852
import type { SimpleObjectNodeSchema } from "../simpleSchema.js";
4953
import { SchemaFactoryBeta } from "./schemaFactoryBeta.js";
@@ -113,28 +117,64 @@ export interface SchemaStaticsAlpha {
113117
t: T,
114118
metadata?: AllowedTypesMetadata,
115119
) => AllowedTypesFullFromMixed<T>;
120+
121+
/**
122+
* {@link SchemaStaticsAlpha.staged} except tweaked to work better for recursive types.
123+
* Use with {@link ValidateRecursiveSchema} for improved type safety.
124+
* @remarks
125+
* This version of {@link SchemaStaticsAlpha.staged} has fewer type constraints to work around TypeScript limitations, see {@link Unenforced}.
126+
* See {@link ValidateRecursiveSchema} for additional information about using recursive schema.
127+
*/
128+
stagedRecursive: <
129+
const T extends Unenforced<AnnotatedAllowedType | LazyItem<TreeNodeSchema>>,
130+
>(
131+
t: T,
132+
) => AnnotatedAllowedTypeUnsafe<UnannotateAllowedTypeUnsafe<T>>;
133+
134+
/**
135+
* {@link SchemaStaticsAlpha.types} except tweaked to work better for recursive types.
136+
* Use with {@link ValidateRecursiveSchema} for improved type safety.
137+
* @remarks
138+
* This version of {@link SchemaStaticsAlpha.types} has fewer type constraints to work around TypeScript limitations, see {@link Unenforced}.
139+
* See {@link ValidateRecursiveSchema} for additional information about using recursive schema.
140+
* @privateRemarks
141+
* If all inputs (at least recursive ones) were required to be annotated, this could be typed more strongly.
142+
* In that case it could use `T extends readonly (AnnotatedAllowedTypeUnsafe | LazyItem<System_Unsafe.TreeNodeSchemaUnsafe>)[]`.
143+
*/
144+
readonly typesRecursive: <
145+
const T extends readonly Unenforced<AnnotatedAllowedType | LazyItem<TreeNodeSchema>>[],
146+
>(
147+
t: T,
148+
metadata?: AllowedTypesMetadata,
149+
) => AllowedTypesFullFromMixedUnsafe<T>;
116150
}
117151

152+
const staged = <const T extends LazyItem<TreeNodeSchema>>(
153+
t: T | AnnotatedAllowedType<T>,
154+
): AnnotatedAllowedType<T> => {
155+
const annotatedType = normalizeToAnnotatedAllowedType(t);
156+
return {
157+
type: annotatedType.type,
158+
metadata: {
159+
...annotatedType.metadata,
160+
stagedSchemaUpgrade: createSchemaUpgrade(),
161+
},
162+
};
163+
};
164+
165+
const types = <const T extends readonly (AnnotatedAllowedType | LazyItem<TreeNodeSchema>)[]>(
166+
t: T,
167+
metadata: AllowedTypesMetadata = {},
168+
): AllowedTypesFullFromMixed<T> => {
169+
return AnnotatedAllowedTypesInternal.createMixed<T>(t, metadata);
170+
};
171+
118172
const schemaStaticsAlpha: SchemaStaticsAlpha = {
119-
staged: <const T extends LazyItem<TreeNodeSchema>>(
120-
t: T | AnnotatedAllowedType<T>,
121-
): AnnotatedAllowedType<T> => {
122-
const annotatedType = normalizeToAnnotatedAllowedType(t);
123-
return {
124-
type: annotatedType.type,
125-
metadata: {
126-
...annotatedType.metadata,
127-
stagedSchemaUpgrade: createSchemaUpgrade(),
128-
},
129-
};
130-
},
173+
staged,
174+
types,
131175

132-
types: <const T extends readonly (AnnotatedAllowedType | LazyItem<TreeNodeSchema>)[]>(
133-
t: T,
134-
metadata: AllowedTypesMetadata = {},
135-
): AllowedTypesFullFromMixed<T> => {
136-
return AnnotatedAllowedTypesInternal.createMixed<T>(t, metadata);
137-
},
176+
stagedRecursive: staged as SchemaStaticsAlpha["stagedRecursive"],
177+
typesRecursive: types as unknown as SchemaStaticsAlpha["typesRecursive"],
138178
};
139179

140180
/**
@@ -310,6 +350,16 @@ export class SchemaFactoryAlpha<
310350
*/
311351
public staged = schemaStaticsAlpha.staged;
312352

353+
/**
354+
* {@inheritDoc SchemaStaticsAlpha.stagedRecursive}
355+
*/
356+
public static stagedRecursive = schemaStaticsAlpha.stagedRecursive;
357+
358+
/**
359+
* {@inheritDoc SchemaStaticsAlpha.stagedRecursive}
360+
*/
361+
public stagedRecursive = schemaStaticsAlpha.stagedRecursive;
362+
313363
/**
314364
* {@inheritDoc SchemaStaticsAlpha.types}
315365
*/
@@ -320,6 +370,16 @@ export class SchemaFactoryAlpha<
320370
*/
321371
public types = schemaStaticsAlpha.types;
322372

373+
/**
374+
* {@inheritDoc SchemaStaticsAlpha.typesRecursive}
375+
*/
376+
public static typesRecursive = schemaStaticsAlpha.typesRecursive;
377+
378+
/**
379+
* {@inheritDoc SchemaStaticsAlpha.typesRecursive}
380+
*/
381+
public typesRecursive = schemaStaticsAlpha.typesRecursive;
382+
323383
/**
324384
* Define a {@link TreeNodeSchema} for a {@link TreeMapNode}.
325385
*
@@ -405,7 +465,7 @@ export class SchemaFactoryAlpha<
405465
}
406466

407467
/**
408-
* {@inheritDoc SchemaFactory.objectRecursive}
468+
* {@link SchemaFactory.arrayRecursive} but with support for some alpha features.
409469
*/
410470
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
411471
public override arrayRecursive<

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,9 @@ export const schemaStatics = {
228228
},
229229
} as const;
230230

231+
/**
232+
* {@link Unenforced} version of {@link createFieldSchema}.
233+
*/
231234
function createFieldSchemaUnsafe<
232235
Kind extends FieldKind,
233236
Types extends System_Unsafe.ImplicitAllowedTypesUnsafe,

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

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ import type {
2929
TreeLeafValue,
3030
FlexListToUnion,
3131
LazyItem,
32+
AnnotatedAllowedType,
33+
AnnotatedAllowedTypes,
3234
} from "../core/index.js";
3335
import type { TreeArrayNode } from "../node-kinds/index.js";
3436
import type { SimpleArrayNodeSchema, SimpleMapNodeSchema } from "../simpleSchema.js";
@@ -543,3 +545,82 @@ export interface TreeRecordNodeUnsafe<
543545
[string, System_Unsafe.TreeNodeFromImplicitAllowedTypesUnsafe<TAllowedTypes>]
544546
>;
545547
}
548+
549+
/**
550+
* {@link Unenforced} utility to remove {@link AnnotatedAllowedTypeUnsafe} wrappers.
551+
* @remarks
552+
* Do not use this type directly: it's only needed in the implementation of generic logic which define recursive schema, not when using recursive schema.
553+
* @sealed
554+
* @alpha
555+
* @system
556+
*/
557+
export type UnannotateAllowedTypeUnsafe<
558+
T extends Unenforced<
559+
AnnotatedAllowedTypeUnsafe | LazyItem<System_Unsafe.TreeNodeSchemaUnsafe>
560+
>,
561+
> = T extends AnnotatedAllowedTypeUnsafe<infer X> ? X : T;
562+
563+
/**
564+
* {@link Unenforced} version of {@link AnnotatedAllowedType}.
565+
* @remarks
566+
* Do not use this type directly: it's only needed in the implementation of generic logic which define recursive schema, not when using recursive schema.
567+
* @system @sealed @alpha
568+
*/
569+
export interface AnnotatedAllowedTypeUnsafe<T = Unenforced<LazyItem<TreeNodeSchema>>>
570+
extends AnnotatedAllowedType<T> {}
571+
572+
/**
573+
* {@link Unenforced} version of {@link AnnotatedAllowedTypes}.
574+
* @remarks
575+
* Do not use this type directly: it's only needed in the implementation of generic logic which define recursive schema, not when using recursive schema.
576+
* @system @sealed @alpha
577+
*/
578+
export interface AnnotatedAllowedTypesUnsafe
579+
extends AnnotatedAllowedTypes<LazyItem<System_Unsafe.TreeNodeSchemaUnsafe>> {}
580+
581+
/**
582+
* {@link Unenforced} version of {@link AllowedTypesFull}.
583+
* @remarks
584+
* Do not use this type directly: it's only needed in the implementation of generic logic which define recursive schema, not when using recursive schema.
585+
* @system @sealed @alpha
586+
*/
587+
export type AllowedTypesFullUnsafe<
588+
T extends readonly AnnotatedAllowedTypeUnsafe[] = readonly AnnotatedAllowedTypeUnsafe[],
589+
> = AnnotatedAllowedTypes<T> & UnannotateAllowedTypesListUnsafe<T>;
590+
591+
/**
592+
* {@link Unenforced} version of {@link AllowedTypesFullFromMixed}.
593+
* @remarks
594+
* Do not use this type directly: it's only needed in the implementation of generic logic which define recursive schema, not when using recursive schema.
595+
* @system @sealed @alpha
596+
*/
597+
export type AllowedTypesFullFromMixedUnsafe<
598+
T extends readonly Unenforced<AnnotatedAllowedType | LazyItem<TreeNodeSchema>>[],
599+
> = UnannotateAllowedTypesListUnsafe<T> &
600+
AnnotatedAllowedTypes<AnnotateAllowedTypesListUnsafe<T>>;
601+
602+
/**
603+
* {@link Unenforced} version of {@link UnannotateAllowedTypesList}.
604+
* @remarks
605+
* Do not use this type directly: it's only needed in the implementation of generic logic which define recursive schema, not when using recursive schema.
606+
* @system @sealed @alpha
607+
*/
608+
export type UnannotateAllowedTypesListUnsafe<
609+
T extends readonly Unenforced<AnnotatedAllowedType | LazyItem<TreeNodeSchema>>[],
610+
> = {
611+
readonly [I in keyof T]: T[I] extends { type: infer X } ? X : T[I];
612+
};
613+
614+
/**
615+
* {@link Unenforced} version of {@link AnnotateAllowedTypesList}.
616+
* @remarks
617+
* Do not use this type directly: it's only needed in the implementation of generic logic which define recursive schema, not when using recursive schema.
618+
* @system @sealed @alpha
619+
*/
620+
export type AnnotateAllowedTypesListUnsafe<
621+
T extends readonly Unenforced<AnnotatedAllowedType | LazyItem<TreeNodeSchema>>[],
622+
> = {
623+
[I in keyof T]: T[I] extends AnnotatedAllowedTypeUnsafe
624+
? T[I]
625+
: AnnotatedAllowedTypeUnsafe<T[I]>;
626+
};

packages/dds/tree/src/simple-tree/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,13 @@ export {
127127
type ArrayNodeCustomizableSchemaUnsafe,
128128
type MapNodeCustomizableSchemaUnsafe,
129129
type TreeRecordNodeUnsafe,
130+
type UnannotateAllowedTypeUnsafe,
131+
type AnnotatedAllowedTypeUnsafe,
132+
type AnnotatedAllowedTypesUnsafe,
133+
type AllowedTypesFullUnsafe,
134+
type AllowedTypesFullFromMixedUnsafe,
135+
type UnannotateAllowedTypesListUnsafe,
136+
type AnnotateAllowedTypesListUnsafe,
130137
type TreeViewAlpha,
131138
type TreeBranch,
132139
type TreeBranchEvents,

0 commit comments

Comments
 (0)