Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
207 commits
Select commit Hold shift + click to select a range
060acdd
add annotated allowed types
jenn-le Mar 28, 2025
3d4d85e
Merge remote-tracking branch 'upstream/main' into readonly-allowedtypes
jenn-le Mar 28, 2025
fed6358
Merge remote-tracking branch 'upstream/main' into readonly-allowedtypes
jenn-le Apr 4, 2025
a8e69d5
unannotate unit tests
jenn-le Apr 8, 2025
5724351
unannotate stuff
jenn-le Apr 9, 2025
984b829
clean up enablable
jenn-le Apr 9, 2025
4ecd8b7
unannote tests
jenn-le Apr 9, 2025
1be4f30
craig's overload workaround
jenn-le Apr 9, 2025
90737e6
accept annotations in object alpha
jenn-le Apr 14, 2025
bcecd66
add overloads for optional and required
jenn-le Apr 15, 2025
9a2036a
fix input for objectAlpha
jenn-le Apr 18, 2025
41f76db
test fixes
jenn-le Apr 21, 2025
e1acdd0
accept annotations in map alpha
jenn-le Apr 21, 2025
56f25e2
Merge remote-tracking branch 'upstream/main' into readonly-allowedtypes
jenn-le Apr 21, 2025
4d7fbfb
accept annotations in arrayalpha
jenn-le Apr 21, 2025
dc6e729
add comment
jenn-le Apr 24, 2025
8858102
format
jenn-le Apr 24, 2025
9c51607
fix test failure
jenn-le Apr 28, 2025
cf3984e
additional check
jenn-le Apr 28, 2025
7b0eeb7
format
jenn-le Apr 28, 2025
e272d83
Merge remote-tracking branch 'upstream/main' into readonly-allowedtypes
jenn-le May 6, 2025
f42b75e
some enablable tests
jenn-le May 7, 2025
fb1704f
temp
jenn-le May 7, 2025
ce39396
add tests and fix
jenn-le May 14, 2025
cc76230
clean up and more tests
jenn-le May 15, 2025
66021d5
add changeset
jenn-le May 15, 2025
c35b163
change upgrade token type
jenn-le May 15, 2025
5a42cef
add doc
jenn-le May 15, 2025
d792096
api update
jenn-le May 15, 2025
0d979aa
add comment
jenn-le May 15, 2025
866ef86
clean up
jenn-le May 15, 2025
f399aef
pr feedback
jenn-le May 16, 2025
df586bf
Merge remote-tracking branch 'upstream/main' into readonly-allowedtypes
jenn-le May 16, 2025
574fd6c
Update .changeset/curly-bikes-train.md
jenn-le May 19, 2025
56c8a8f
Update .changeset/curly-bikes-train.md
jenn-le May 19, 2025
2ebe5fb
Update .changeset/curly-bikes-train.md
jenn-le May 19, 2025
c2e0792
fix comment
jenn-le May 20, 2025
53b58f5
make schema upgrade a better placeholder
jenn-le May 20, 2025
364511d
add initialize view test
jenn-le May 21, 2025
3065dd5
non working test
jenn-le May 21, 2025
4b28d19
rename and clean up initialization param
jenn-le May 21, 2025
aaf5b11
clean up
jenn-le May 21, 2025
b68c3c8
Merge remote-tracking branch 'upstream/main' into readonly-allowedtypes
jenn-le May 22, 2025
c877a3b
remove changes to toMapTree
jenn-le May 22, 2025
a23714e
always do schema validation
jenn-le May 22, 2025
1780379
validate array moves against the stored schema
jenn-le May 22, 2025
26184b0
error off of validation against stored schema
jenn-le May 23, 2025
4dc0c5a
compat tests
jenn-le May 23, 2025
032204f
e2e test
jenn-le May 24, 2025
5e21db1
update docs
jenn-le May 25, 2025
0f7bd8f
fix optional and required
jenn-le May 27, 2025
f3d1811
e2e test
jenn-le May 28, 2025
4b18ccf
fix test
jenn-le May 28, 2025
d2493f3
handle walking enablable kind of
jenn-le May 29, 2025
72e7b22
start things
jenn-le May 29, 2025
aa6c1f1
stuff
jenn-le Jun 2, 2025
4faed42
exposing tree node schema api issue
jenn-le Jun 5, 2025
9e8728c
some conversions
jenn-le Jun 9, 2025
1a34af1
Merge remote-tracking branch 'upstream/main' into fix-walk-allowed-types
jenn-le Jun 10, 2025
e831ff7
write tests and get them working
jenn-le Jun 10, 2025
8792f09
use non alpha
jenn-le Jun 10, 2025
16d2396
clean up and add tests
jenn-le Jun 11, 2025
9986975
Update packages/dds/tree/src/simple-tree/core/treeNodeSchema.ts
jenn-le Jun 11, 2025
58201d3
Update packages/dds/tree/src/simple-tree/core/treeNodeSchema.ts
jenn-le Jun 11, 2025
426ef3d
cleanup
jenn-le Jun 11, 2025
f732d2f
clean up tests
jenn-le Jun 11, 2025
630b151
get rid of readonly sets of annotated allowed schemas
jenn-le Jun 11, 2025
18e5318
cleanup
jenn-le Jun 11, 2025
4c5a5b4
build
jenn-le Jun 12, 2025
3d12712
visit all nodes
jenn-le Jun 14, 2025
616dba6
rename and changeset
jenn-le Jun 14, 2025
593e70b
downcast helper
jenn-le Jun 14, 2025
4addf0d
dedupe annotated types
jenn-le Jun 16, 2025
59e0919
refactor
jenn-le Jun 16, 2025
1cb7ca2
Merge remote-tracking branch 'upstream/main' into fix-walk-allowed-types
jenn-le Jun 16, 2025
5c4e3cc
Merge branch 'fix-walk-allowed-types' into refactor-schema-compat-tester
jenn-le Jun 17, 2025
0c0d1c2
some changes
jenn-le Jun 18, 2025
9a7102d
remove sealed
jenn-le Jun 18, 2025
79065bb
fix configuration
jenn-le Jun 18, 2025
95945f9
Merge branch 'fix-walk-allowed-types' into refactor-schema-compat-tester
jenn-le Jun 18, 2025
2ac5480
fix
jenn-le Jun 18, 2025
058687e
remove internal
jenn-le Jun 18, 2025
508e6b4
Update packages/dds/tree/src/simple-tree/api/configuration.ts
jenn-le Jun 18, 2025
68f1350
Update .changeset/six-steaks-clean.md
jenn-le Jun 18, 2025
12b28df
Update packages/dds/tree/src/simple-tree/core/treeNodeSchema.ts
jenn-le Jun 18, 2025
3e27e94
Merge remote-tracking branch 'upstream/main' into fix-walk-allowed-types
jenn-le Jun 18, 2025
ed00bd7
Merge branch 'fix-walk-allowed-types' of https://github.com/jenn-le/F…
jenn-le Jun 18, 2025
1e122cb
use annotatedallowedtypes
jenn-le Jun 19, 2025
8107622
Merge branch 'fix-walk-allowed-types' into refactor-schema-compat-tester
jenn-le Jun 19, 2025
5b6b110
Update packages/dds/tree/src/simple-tree/api/configuration.ts
jenn-le Jun 19, 2025
d6cddef
Update packages/dds/tree/src/simple-tree/core/treeNodeSchema.ts
jenn-le Jun 19, 2025
55420e8
Update packages/dds/tree/src/simple-tree/core/treeNodeSchema.ts
jenn-le Jun 19, 2025
29fb81f
fix typo
jenn-le Jun 19, 2025
dd106d7
fix error
jenn-le Jun 19, 2025
6fd8393
Update packages/dds/tree/src/simple-tree/core/walkSchema.ts
jenn-le Jun 19, 2025
b4e7de5
don't pass annotations to walkNodeSchema
jenn-le Jun 19, 2025
2519edf
build
jenn-le Jun 19, 2025
5a2e184
Merge branch 'fix-walk-allowed-types' of https://github.com/jenn-le/F…
jenn-le Jun 19, 2025
2ab61e9
fixes
jenn-le Jun 19, 2025
4d07d9e
cleanup
jenn-le Jun 19, 2025
8100000
fix tests
jenn-le Jun 19, 2025
979c3e6
stuff
jenn-le Jun 21, 2025
1f246f1
stuff
jenn-le Jun 23, 2025
2c5dee9
Merge remote-tracking branch 'upstream/main' into refactor-schema-com…
jenn-le Jun 23, 2025
e39d8de
revert changes to isNeverTree
jenn-le Jun 30, 2025
e705e79
Merge remote-tracking branch 'upstream/main' into refactor-schema-com…
jenn-le Jul 1, 2025
6ea6283
fix build errors
jenn-le Jul 2, 2025
6fe2be2
get discrepancies tests passing
jenn-le Jul 10, 2025
e690541
all tests passing
jenn-le Jul 11, 2025
fc7a72d
Merge remote-tracking branch 'upstream/main' into refactor-schema-com…
jenn-le Jul 11, 2025
191eca5
clean up
jenn-le Jul 11, 2025
cbb386a
Merge branch 'fix-walk-allowed-types' into refactor-schema-compat-tester
jenn-le Jul 11, 2025
69cfd02
clean up
jenn-le Jul 11, 2025
6c6b547
revert
jenn-le Jul 11, 2025
3fd37e9
cleanup
jenn-le Jul 11, 2025
0190b8b
Update packages/dds/tree/src/simple-tree/api/schemaCompatibilityTeste…
jenn-le Jul 11, 2025
a99262c
remove readonly
jenn-le Jul 12, 2025
57d47c6
Update packages/dds/tree/src/simple-tree/discrepancies.ts
jenn-le Jul 12, 2025
cba6464
Merge branch 'refactor-schema-compat-tester' of https://github.com/je…
jenn-le Jul 12, 2025
2619a19
readonly
jenn-le Jul 12, 2025
750c2bf
clean up
jenn-le Jul 15, 2025
aed13b9
Merge remote-tracking branch 'upstream/main' into refactor-schema-com…
jenn-le Jul 15, 2025
4a142f2
pr feedback
jenn-le Jul 15, 2025
2336f8b
feedback
jenn-le Jul 15, 2025
51d728e
docs
jenn-le Jul 15, 2025
6934dd0
move tests temporarily
jenn-le Jul 15, 2025
35bb59c
Revert "move tests temporarily"
jenn-le Jul 15, 2025
0dbb76d
clean up
jenn-le Jul 15, 2025
c724bca
docs
jenn-le Jul 15, 2025
3d67469
temporarily move tests
jenn-le Jul 15, 2025
8d4efda
Update packages/dds/tree/src/simple-tree/api/schemaCompatibilityTeste…
jenn-le Jul 15, 2025
926189d
remove adapter stuff
jenn-le Jul 15, 2025
e599596
Merge branch 'refactor-schema-compat-tester' of https://github.com/je…
jenn-le Jul 15, 2025
4f67663
Revert "temporarily move tests"
jenn-le Jul 15, 2025
2940a9e
feedback
jenn-le Jul 15, 2025
89fc5f7
fix empty test
jenn-le Jul 16, 2025
6df6775
remove todo
jenn-le Jul 16, 2025
fd11892
Revert "remove todo"
jenn-le Jul 16, 2025
bda5104
feedback
jenn-le Jul 16, 2025
fa38d4d
fix readonly
jenn-le Jul 16, 2025
4856e0d
Update packages/dds/tree/src/simple-tree/api/schemaCompatibilityTeste…
jenn-le Jul 17, 2025
caa6661
feedback
jenn-le Jul 17, 2025
519cb84
fix
jenn-le Jul 17, 2025
9dc2628
format
jenn-le Jul 17, 2025
839d11e
Merge remote-tracking branch 'upstream/main' into refactor-schema-com…
jenn-le Jul 17, 2025
7a6d416
feedback
jenn-le Jul 17, 2025
b2c3b42
format
jenn-le Jul 17, 2025
6cb2a1c
Merge remote-tracking branch 'upstream/main' into readonly-allowedtypes
jenn-le Jul 17, 2025
f6952ea
delete notes no longer applicable
jenn-le Jul 17, 2025
781ed52
Merge branch 'refactor-schema-compat-tester' into readonly-allowedtypes
jenn-le Jul 17, 2025
69f097e
cleanup
jenn-le Jul 18, 2025
96f58a1
schema factory statics
jenn-le Jul 18, 2025
0be9310
subclass
jenn-le Jul 18, 2025
7765358
clean up
jenn-le Jul 18, 2025
b67c203
cleanup
jenn-le Jul 18, 2025
0eeea9f
rename
jenn-le Jul 18, 2025
f8b3d08
cleanup
jenn-le Jul 18, 2025
139e2d8
remove test
jenn-le Jul 18, 2025
1e4d3b0
Merge branch 'refactor-schema-compat-tester' into readonly-allowedtypes
jenn-le Jul 18, 2025
2dcce56
cleanup
jenn-le Jul 18, 2025
5daef37
Update packages/dds/tree/src/test/simple-tree/discrepancies.spec.ts
jenn-le Jul 18, 2025
334979c
feedback
jenn-le Jul 18, 2025
9cb4647
Merge branch 'refactor-schema-compat-tester' of https://github.com/je…
jenn-le Jul 18, 2025
3cdfca2
revert
jenn-le Jul 18, 2025
a3e13e8
feedback
jenn-le Jul 18, 2025
211ea73
remove test
jenn-le Jul 18, 2025
13583af
put back test
jenn-le Jul 18, 2025
d465cf4
Merge branch 'refactor-schema-compat-tester' into readonly-allowedtypes
jenn-le Jul 18, 2025
4627fee
fix?
jenn-le Jul 18, 2025
6a4a2dc
Merge remote-tracking branch 'upstream/main' into refactor-schema-com…
jenn-le Jul 18, 2025
20f8710
rename to stageable
jenn-le Jul 18, 2025
0370c15
remove
jenn-le Jul 18, 2025
876ab10
rename to staged
jenn-le Jul 18, 2025
c9ca957
update docs
jenn-le Jul 18, 2025
16209ae
cleanup
jenn-le Jul 18, 2025
cf1c35f
Merge remote-tracking branch 'upstream/main' into readonly-allowedtypes
jenn-le Jul 18, 2025
5af0ba2
Merge branch 'refactor-schema-compat-tester' into readonly-allowedtypes
jenn-le Jul 18, 2025
bf141b8
api update
jenn-le Jul 19, 2025
1c828f8
deal with annotations in compat tester a bit
jenn-le Jul 19, 2025
369fe1b
format
jenn-le Jul 19, 2025
e86e8c2
add record tests
jenn-le Jul 19, 2025
1d512f5
always validate schema
jenn-le Jul 19, 2025
fc30fd9
todo
jenn-le Jul 19, 2025
a9f779a
remove duplicate test file
jenn-le Jul 19, 2025
6a59513
change walk schema
jenn-le Jul 20, 2025
e5d9d3f
test cleanup
jenn-le Jul 20, 2025
dac094e
remove allows superset check
jenn-le Jul 21, 2025
320c180
use allowed types callback for discrepancies
jenn-le Jul 21, 2025
f1cfde5
bypass schema validation on initialization
jenn-le Jul 21, 2025
6102ab4
remove comment
jenn-le Jul 21, 2025
2e401fa
disable schema validation after every batch of edits
jenn-le Jul 21, 2025
e1e7b38
fix test
jenn-le Jul 21, 2025
81144bc
add code example
jenn-le Jul 21, 2025
8e348e3
comment
jenn-le Jul 21, 2025
f4344a1
clean up
jenn-le Jul 21, 2025
03565b6
fix example
jenn-le Jul 21, 2025
8da4483
clean up and add todo item
jenn-le Jul 21, 2025
458f1e1
Update packages/dds/tree/src/simple-tree/api/schemaFactoryAlpha.ts
jenn-le Jul 21, 2025
d7cbd8e
Update packages/dds/tree/src/simple-tree/api/schemaFactoryAlpha.ts
jenn-le Jul 21, 2025
2dc21e3
Update .changeset/curly-bikes-train.md
jenn-le Jul 21, 2025
143cfc6
Update .changeset/curly-bikes-train.md
jenn-le Jul 21, 2025
e5b6742
feedback
jenn-le Jul 21, 2025
1cc1426
Update .changeset/curly-bikes-train.md
jenn-le Jul 21, 2025
85aad37
Update .changeset/curly-bikes-train.md
jenn-le Jul 21, 2025
ff2c612
Update .changeset/curly-bikes-train.md
jenn-le Jul 21, 2025
208d885
clean up and make staged static
jenn-le Jul 21, 2025
79b9ba7
feedback
jenn-le Jul 22, 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
94 changes: 94 additions & 0 deletions .changeset/curly-bikes-train.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
---
"fluid-framework": minor
"@fluidframework/tree": minor
"__section": feature
---
Adds staged allowed types to SchemaFactoryAlpha

This adds the `staged` API to [`SchemaFactoryAlpha`](https://fluidframework.com/docs/api/fluid-framework/schemafactoryalpha-class).
Staged allowed types can be used for schema evolution to add members to an [`AllowedTypes`](https://fluidframework.com/docs/api/fluid-framework/allowedtypes-typealias) while supporting cross version collaboration.

Staged allowed types are [allowed types](https://fluidframework.com/docs/api/fluid-framework/allowedtypes-typealias) that can be upgraded by [schema upgrades}(https://fluidframework.com/docs/api/fluid-framework/treeview-interface#upgradeschema-methodsignature).
Before being upgraded, any attempt to insert or move a node to a location which requires its type to be upgraded to be valid will throw an error.

To enable this feature, [schema validation](https://fluidframework.com/docs/api/fluid-framework/treeviewconfiguration-class#enableschemavalidation-property) is now performed by default when editing the tree.

To add a new member to an `AllowedTypes`, add the type wrapped by `staged`.
For example, migrating an array which previously supported only numbers to support both numbers and strings would start by deploying a version of the app using `staged`:
```typescript
class TestArray extends schemaFactoryAlpha.arrayAlpha("TestArray", [SchemaFactoryAlpha.number, SchemaFactoryAlpha.staged(SchemaFactoryAlpha.string)]) {}
```

Once enough clients have this code update, it is safe to allow writing strings to the array.
To allow writing strings to the array, a code change must be made to remove the staged annotation:
```typescript
class TestArray extends schemaFactoryAlpha.arrayAlpha("TestArray", [schemaFactoryAlpha.number, schemaFactoryAlpha.string]) {}
```

Then when opening old documents [upgradeSchema](https://fluidframework.com/docs/api/fluid-framework/treeview-interface#upgradeschema-methodsignature) is used to upgrade the stored schema:
```typescript
view.upgradeSchema()
```

In the future, SharedTree may add an API that allows staged allowed types to be upgraded via a runtime schema upgrade so that the type can be more easily deployed using a configuration flag change rather than a code change.

Below is a full example of how the schema migration process works. This can also be found in our [tests](https://github.com/jenn-le/FluidFramework/blob/main/packages/dds/tree/src/test/simple-tree/api/stagedSchemaUpgrade.spec.ts).

```typescript
// schema A: only number allowed
const schemaA = factory.optional([SchemaFactoryAlpha.number]);

// schema B: number or string (string is staged)
const schemaB = factory.optional([
SchemaFactoryAlpha.number,
factory.staged(SchemaFactoryAlpha.string),
]);

// schema C: number or string, both fully allowed
const schemaC = factory.optional([SchemaFactoryAlpha.number, SchemaFactoryAlpha.string]);

const provider = new TestTreeProviderLite(3);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Advertising to customers that how you write simple examples for how to use our public API surface is to use a non exported private test utility from the tree package is not great.

I think we should try and do this using the actual public API surface. If that means this version of the code has to be in the end-to-end tests instead of the tree package, or that it has to use independentView, then it can be updated accordingly.


// initialize with schema A
const configA = new TreeViewConfiguration({
schema: schemaA,
});
const viewA = provider.trees[0].viewWith(configA);
viewA.initialize(5);
provider.synchronizeMessages();

assert.deepEqual(viewA.root, 5);

// view second tree with schema B
const configB = new TreeViewConfiguration({
schema: schemaB,
});
const viewB = provider.trees[1].viewWith(configB);
// check that we can read the tree
assert.deepEqual(viewB.root, 5);
// upgrade to schema B
viewB.upgradeSchema();
provider.synchronizeMessages();

// check view A can read the document
assert.deepEqual(viewA.root, 5);
// check view B cannot write strings to the root
assert.throws(() => {
viewB.root = "test";
});

// view third tree with schema C
const configC = new TreeViewConfiguration({
schema: schemaC,
});
const viewC = provider.trees[2].viewWith(configC);
// upgrade to schema C and change the root to a string
viewC.upgradeSchema();
viewC.root = "test";
provider.synchronizeMessages();

// view A is now incompatible with the stored schema
assert.throws(viewA.canView, false);
assert.deepEqual(viewB.root, "test");
assert.deepEqual(viewC.root, "test");
```
1 change: 0 additions & 1 deletion packages/dds/tree/.vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
"contravariantly",
"covariantly",
"deprioritized",
"enablable",
"endregion",
"fluidframework",
"insertable",
Expand Down
6 changes: 6 additions & 0 deletions packages/dds/tree/api-report/tree.alpha.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export function adaptEnum<TScope extends string, const TEnum extends Record<stri
// @alpha
export interface AllowedTypeMetadata {
readonly custom?: unknown;
readonly stagedSchemaUpgrade?: SchemaUpgrade;
}

// @public @system
Expand Down Expand Up @@ -849,6 +850,7 @@ export class SchemaFactoryAlpha<out TScope extends string | undefined = string |
static readonly requiredRecursive: <const T extends System_Unsafe.ImplicitAllowedTypesUnsafe, const TCustomMetadata = unknown>(t: T, props?: Omit<FieldPropsAlpha_2<TCustomMetadata>, "defaultProvider"> | undefined) => FieldSchemaAlphaUnsafe_2<FieldKind_2.Required, T, TCustomMetadata>;
readonly requiredRecursive: <const T extends System_Unsafe.ImplicitAllowedTypesUnsafe, const TCustomMetadata = unknown>(t: T, props?: Omit<FieldPropsAlpha_2<TCustomMetadata>, "defaultProvider"> | undefined) => FieldSchemaAlphaUnsafe_2<FieldKind_2.Required, T, TCustomMetadata>;
scopedFactory<const T extends TName, TNameInner extends number | string = string>(name: T): SchemaFactoryAlpha<ScopedSchemaName<TScope, T>, TNameInner>;
static staged<const T extends TreeNodeSchema>(t: T | AnnotatedAllowedType<T>): AnnotatedAllowedType<T>;
}

// @alpha
Expand Down Expand Up @@ -876,6 +878,10 @@ export interface SchemaStatics {
readonly string: LeafSchema<"string", string>;
}

// @alpha @sealed
export class SchemaUpgrade {
}

// @alpha @input
export interface SchemaValidationFunction<Schema extends TSchema> {
check(data: unknown): data is Static<Schema>;
Expand Down
3 changes: 2 additions & 1 deletion packages/dds/tree/src/core/schema-stored/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,10 @@ export interface SchemaPolicy {
readonly fieldKinds: ReadonlyMap<FieldKindIdentifier, FieldKindData>;

/**
* If true, new content inserted into the tree should be validated against the stored schema.
* This is not used for anything. New content that is inserted into the tree will always be validated against the stored schema except during initialization.
* @remarks
* TODO: AB#43546: This is not information used to interpret the stored schema: this configuration should be moved elsewhere.
* TODO: Evaluate if this should be removed or renamed.
*/
readonly validateSchema: boolean;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ export class ObjectForest implements IEditableForest, WithBreakable {
if (this.forest.additionalAsserts) {
// Schema validation:
// When doing "additionalAsserts", validate the content against the schema after every batch of edits.
this.forest.checkSchema();
// this.forest.checkSchema();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed this was introduced in this change: #24658

Since this feature requires schema validation to always be done, does it make sense to revert this change or is there a different way we want to disable it? I commented it out here because it interferes with initialization.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we want to keep this enabled. It is only opt in as part of https://fluidframework.com/docs/api/fluid-framework/#foresttypeexpensivedebug-variable and is there to mainly detect document corruption. No reason to disable it. Its perfectly possible that document corruption can still happen, from older version, via initialize, or from edits other than insert, bugs in insert etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making a note here that we'll want to change schema validation in the initialization case to take view schema into account for staged allowed types and allowUnknownOptionalFields.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

View schema is not relevant here. What is being checked is that the content stored in the document complies with the stored schema. This is required to be true regardless of what the view schema is doing: any violation of it is document corruption.

}
}
public destroy(detachedField: FieldKey, count: number): void {
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 @@ -117,6 +117,7 @@ export {
type InternalTreeNode,
type WithType,
type NodeChangedData,
type SchemaUpgrade,
// Types not really intended for public use, but used in links.
// Can not be moved to internalTypes since doing so causes app code to throw errors like:
// Error: src/simple-tree/objectNode.ts:72:1 - (ae-unresolved-link) The @link reference could not be resolved: The package "@fluidframework/tree" does not have an export "TreeNodeApi"
Expand Down
8 changes: 7 additions & 1 deletion packages/dds/tree/src/shared-tree/schematizingTreeView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import {
type FieldSchema,
toStoredSchema,
tryDisposeTreeNode,
FieldSchemaAlpha,
} from "../simple-tree/index.js";
import {
type Breakable,
Expand Down Expand Up @@ -184,6 +185,7 @@ export class SchematizingSimpleTreeView<
policy: this.schemaPolicy,
},
this,
true,
);

initialize(this.checkout, {
Expand Down Expand Up @@ -321,10 +323,14 @@ export class SchematizingSimpleTreeView<
this.nodeKeyManager,
);
assert(!slots.has(SimpleContextSlot), 0xa47 /* extra simple tree context */);
assert(
this.rootFieldSchema instanceof FieldSchemaAlpha,
"all field schema should be FieldSchemaAlpha",
);
slots.set(
SimpleContextSlot,
new HydratedContext(
normalizeFieldSchema(this.rootFieldSchema).annotatedAllowedTypesNormalized,
this.rootFieldSchema.annotatedAllowedTypesNormalized,
this.flexTreeContext,
),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@
* Licensed under the MIT License.
*/

import { assert, unreachableCase } from "@fluidframework/core-utils/internal";
import { unreachableCase } from "@fluidframework/core-utils/internal";

import type { TreeStoredSchema } from "../../core/index.js";
import {
allowsRepoSuperset,
FieldKinds,
type FullSchemaPolicy,
isNeverTree,
Expand All @@ -22,7 +21,6 @@ import {
PosetComparisonResult,
type FieldDiscrepancy,
} from "../discrepancies.js";
import { toStoredSchema } from "../toStoredSchema.js";

/**
* A collection of View information for schema, including policy.
Expand Down Expand Up @@ -142,7 +140,7 @@ export class SchemaCompatibilityTester {
// View schema has added a node type that the stored schema doesn't know about.
// Note that all cases which trigger this should also trigger an AllowedTypeDiscrepancy (where the type is used).
// This means this case should be redundant and could be removed in the future if there is a reason to do so
// (like simplifying enablable type support).
// (like simplifying staged type support).
// See the TODO in getAllowedContentDiscrepancies.
canView = false;
} else if (discrepancy.view === undefined) {
Expand Down Expand Up @@ -181,12 +179,15 @@ export class SchemaCompatibilityTester {
}
}

if (canUpgrade) {
assert(
allowsRepoSuperset(this.policy, stored, toStoredSchema(this.viewSchemaRoot)),
"View schema must be a superset of the stored schema to allow upgrade",
);
}
// TODO: It is no longer guaranteed that the result of toStoredSchema (which removes any staged allowed types in its conversion)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have an idea for an alternative?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also applies to optional fields via allowUnknownOptionalFields, no? Why don't we already fail this check in that case? Do we have that scenario tested?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SchemaPolicy still has kludges for allowUnknownOptionalFields in it. I'm not sure if that's impacting this, but we should really clean that up now that the schema compatibility tester has been fixed to use view schema.

I'd like to land that cleanup, and some working alternative for this validation (or a decision we are confident without it) before we further complicate things with staged.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to try and remove createUnknownOptionalFieldPolicy and FullSchemaPolicy.allowUnknownOptionalFields and see what happens.

// is a superset of the stored schema, which may have upgraded staged allowed types from another client.
// if (canUpgrade) {
// // This includes staged allowed types in the conversion before their upgrade to ensure compatibility.
// assert(
// allowsRepoSuperset(this.policy, stored, toStoredSchema(this.viewSchemaRoot)),
// "View schema must be a superset of the stored schema to allow upgrade",
// );
// }

return {
canView,
Expand Down
53 changes: 44 additions & 9 deletions packages/dds/tree/src/simple-tree/api/schemaFactoryAlpha.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,18 @@ import {
} from "./schemaFactory.js";
import type { ImplicitAnnotatedFieldSchema, ImplicitFieldSchema } from "../fieldSchema.js";
import type { RestrictiveStringRecord } from "../../util/index.js";
import type {
NodeKind,
TreeNodeSchema,
TreeNodeSchemaBoth,
TreeNodeSchemaClass,
TreeNodeSchemaNonClass,
WithType,
ImplicitAllowedTypes,
ImplicitAnnotatedAllowedTypes,
import {
type NodeKind,
type TreeNodeSchema,
type TreeNodeSchemaBoth,
type TreeNodeSchemaClass,
type TreeNodeSchemaNonClass,
type WithType,
type ImplicitAllowedTypes,
type ImplicitAnnotatedAllowedTypes,
type AnnotatedAllowedType,
normalizeToAnnotatedAllowedType,
createSchemaUpgrade,
} from "../core/index.js";
import type {
ArrayNodeCustomizableSchemaUnsafe,
Expand Down Expand Up @@ -63,6 +66,38 @@ export class SchemaFactoryAlpha<
) as ScopedSchemaName<TScope, Name>;
}

/**
* Declares a staged type in a set of {@link AllowedTypes}.
*
* @remarks
*
* Staged allowed types add support for reading a type which can be used for schema evolution to add members to
* an {@link AllowedTypes} while supporting cross version collaboration.
*
* Once enough clients support reading the type, support for writing can be added by removing the use of
* `staged` from the schema definition and upgrading the schema.
*
* A future change will allow writing the type using a runtime schema upgrade so that the type can be upgraded
* using a configuration flag change rather than a code change.
*
* @privateRemarks
* TODO:#44317 staged allowed types rely on schema validation of stored schema to output errors, these errors are not very
* user friendly and should be improved, particularly in the case of staged allowed types
*
*/
public static staged<const T extends TreeNodeSchema>(
t: T | AnnotatedAllowedType<T>,
): AnnotatedAllowedType<T> {
const annotatedType = normalizeToAnnotatedAllowedType(t);
return {
type: annotatedType.type,
metadata: {
...annotatedType.metadata,
stagedSchemaUpgrade: createSchemaUpgrade(),
},
};
}

/**
* Define a {@link TreeNodeSchemaClass} for a {@link TreeObjectNode}.
*
Expand Down
53 changes: 44 additions & 9 deletions packages/dds/tree/src/simple-tree/core/allowedTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,30 @@ export interface AllowedTypeMetadata {
*/
readonly custom?: unknown;

// TODO metadata for enablable types will be added here
/**
* If defined, indicates that an allowed type is {@link SchemaFactoryAlpha.staged | staged}.
*/
readonly stagedSchemaUpgrade?: SchemaUpgrade;
}

/**
* Package internal construction API.
*/
export let createSchemaUpgrade: () => SchemaUpgrade;

/**
* Unique token used to upgrade schemas and determine if a particular upgrade has been completed.
*
* TODO:#38722 implement runtime schema upgrades until then, the class purely behaves as a placeholder and we disable no-extraneous-class
* @sealed @alpha
*/
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
export class SchemaUpgrade {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this a class? Why not a symbol? And later on, simply a bool/function, etc?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using classes like this is the cleanest way in typescript to be able to make strongly typed objects which users cannot construct.

Each schema upgrade gets its own unique identity.

I'm not sure how you would use symbols Booleans or functions to define a type which can hold values with distinct identities that are not user constructable.

static {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: public static

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@noencke thats not a member, it's a static initialization block. This package has automation that would have detected that issue if it was what you though it was: no need to be a human linter.

createSchemaUpgrade = () => new SchemaUpgrade();
}

private constructor() {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The class is marked as @sealed - do we need to actually enforce that it can't be subclassed? It's confusing because we have many sealed classes elsewhere where we don't use this pattern. It's not standard in our package.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@noencke

For classes who's constructors we do not expose and explicitly have a different way to get an instance of them where no following that will break things and is tempting, this is a good pattern. FieldSchema and IterableTreeArrayContent follow it for example.

I prefer when we can make doing things wrong a compile error rather than rely on someone noticing a type is sealed: good luck catching that in a code review: its hard to check the tags on every constructure called when reviewing code.

Do actually have an example of a sealed class we export as a non-type export that requires a special non-constructor code-path to build that does not follow this pattern? I'm not aware of any, and I listed two classes that use this pattern above.

}

/**
Expand Down Expand Up @@ -237,29 +260,41 @@ export type UnannotateAllowedType<T extends AnnotatedAllowedType> =
* @internal
*/
export function normalizeAllowedTypes(
types: ImplicitAllowedTypes,
types: ImplicitAnnotatedAllowedTypes,
): ReadonlySet<TreeNodeSchema> {
// remove annotations before normalizing
const unannotated = unannotateImplicitAllowedTypes(types);
const normalized = new Set<TreeNodeSchema>();
if (isReadonlyArray(types)) {
if (isReadonlyArray(unannotated)) {
// Types array must not be modified after it is normalized since that would result in the user of the normalized data having wrong (out of date) content.
Object.freeze(types);
for (const lazyType of types) {
Object.freeze(unannotated);
for (const lazyType of unannotated) {
normalized.add(evaluateLazySchema(lazyType));
}
} else {
normalized.add(evaluateLazySchema(types));
normalized.add(evaluateLazySchema(unannotated));
}
return normalized;
}

/**
* Normalizes an allowed type to an {@link AnnotatedAllowedType}, by adding empty annotations if they don't already exist.
* Normalizes an allowed type to an {@link AnnotatedAllowedType}, by adding empty annotations if they don't already exist
* and eagerly evaluating any lazy schema declarations.
*
* @remarks
* Note: this must only be called after all required schemas have been declared, otherwise evaluation of
* recursive schemas may fail.
* type is frozen and should not be modified after being passed in.
*/
export function normalizeToAnnotatedAllowedType<T extends TreeNodeSchema>(
type: T | AnnotatedAllowedType<T> | AnnotatedAllowedType<LazyItem<T>>,
): AnnotatedAllowedType<T> | AnnotatedAllowedType<LazyItem<T>> {
): AnnotatedAllowedType<T> {
Object.freeze(type);
return isAnnotatedAllowedType(type)
? type
? {
metadata: type.metadata,
type: evaluateLazySchema(type.type),
}
: {
metadata: {},
type,
Expand Down
Loading