@fluidframework/tree 2.117.0 → 2.118.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/api-report/tree.alpha.api.md +44 -1
  3. package/api-report/tree.beta.api.md +38 -0
  4. package/api-report/tree.legacy.beta.api.md +38 -0
  5. package/dist/entrypoints/alpha.d.ts +1 -1
  6. package/dist/entrypoints/alpha.d.ts.map +1 -1
  7. package/dist/entrypoints/alpha.js.map +1 -1
  8. package/dist/entrypoints/beta.d.ts +1 -1
  9. package/dist/entrypoints/beta.d.ts.map +1 -1
  10. package/dist/entrypoints/beta.js.map +1 -1
  11. package/dist/entrypoints/legacy.d.ts +1 -1
  12. package/dist/entrypoints/legacy.d.ts.map +1 -1
  13. package/dist/entrypoints/legacy.js.map +1 -1
  14. package/dist/feature-libraries/index.d.ts +1 -1
  15. package/dist/feature-libraries/index.js.map +1 -1
  16. package/dist/feature-libraries/schemaChecker.d.ts +9 -19
  17. package/dist/feature-libraries/schemaChecker.d.ts.map +1 -1
  18. package/dist/feature-libraries/schemaChecker.js +130 -46
  19. package/dist/feature-libraries/schemaChecker.js.map +1 -1
  20. package/dist/index.d.ts +1 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js.map +1 -1
  23. package/dist/packageVersion.d.ts +1 -1
  24. package/dist/packageVersion.js +1 -1
  25. package/dist/packageVersion.js.map +1 -1
  26. package/dist/shared-tree/schematizingTreeView.d.ts +13 -3
  27. package/dist/shared-tree/schematizingTreeView.d.ts.map +1 -1
  28. package/dist/shared-tree/schematizingTreeView.js +48 -11
  29. package/dist/shared-tree/schematizingTreeView.js.map +1 -1
  30. package/dist/simple-tree/api/configuration.d.ts +4 -4
  31. package/dist/simple-tree/api/configuration.d.ts.map +1 -1
  32. package/dist/simple-tree/api/configuration.js.map +1 -1
  33. package/dist/simple-tree/api/discrepancies.d.ts +36 -3
  34. package/dist/simple-tree/api/discrepancies.d.ts.map +1 -1
  35. package/dist/simple-tree/api/discrepancies.js +40 -14
  36. package/dist/simple-tree/api/discrepancies.js.map +1 -1
  37. package/dist/simple-tree/api/index.d.ts +3 -2
  38. package/dist/simple-tree/api/index.d.ts.map +1 -1
  39. package/dist/simple-tree/api/index.js +3 -2
  40. package/dist/simple-tree/api/index.js.map +1 -1
  41. package/dist/simple-tree/api/schemaCompatibilityTester.d.ts +22 -3
  42. package/dist/simple-tree/api/schemaCompatibilityTester.d.ts.map +1 -1
  43. package/dist/simple-tree/api/schemaCompatibilityTester.js +140 -10
  44. package/dist/simple-tree/api/schemaCompatibilityTester.js.map +1 -1
  45. package/dist/simple-tree/api/tree.d.ts +172 -7
  46. package/dist/simple-tree/api/tree.d.ts.map +1 -1
  47. package/dist/simple-tree/api/tree.js.map +1 -1
  48. package/dist/simple-tree/core/toStored.d.ts +7 -0
  49. package/dist/simple-tree/core/toStored.d.ts.map +1 -1
  50. package/dist/simple-tree/core/toStored.js.map +1 -1
  51. package/dist/simple-tree/core/treeNodeValid.d.ts.map +1 -1
  52. package/dist/simple-tree/core/treeNodeValid.js +2 -2
  53. package/dist/simple-tree/core/treeNodeValid.js.map +1 -1
  54. package/dist/simple-tree/index.d.ts +1 -1
  55. package/dist/simple-tree/index.d.ts.map +1 -1
  56. package/dist/simple-tree/index.js +4 -3
  57. package/dist/simple-tree/index.js.map +1 -1
  58. package/docs/main/glossary.md +163 -0
  59. package/lib/entrypoints/alpha.d.ts +1 -1
  60. package/lib/entrypoints/alpha.d.ts.map +1 -1
  61. package/lib/entrypoints/alpha.js.map +1 -1
  62. package/lib/entrypoints/beta.d.ts +1 -1
  63. package/lib/entrypoints/beta.d.ts.map +1 -1
  64. package/lib/entrypoints/beta.js.map +1 -1
  65. package/lib/entrypoints/legacy.d.ts +1 -1
  66. package/lib/entrypoints/legacy.d.ts.map +1 -1
  67. package/lib/entrypoints/legacy.js.map +1 -1
  68. package/lib/feature-libraries/index.d.ts +1 -1
  69. package/lib/feature-libraries/index.js.map +1 -1
  70. package/lib/feature-libraries/schemaChecker.d.ts +9 -19
  71. package/lib/feature-libraries/schemaChecker.d.ts.map +1 -1
  72. package/lib/feature-libraries/schemaChecker.js +133 -49
  73. package/lib/feature-libraries/schemaChecker.js.map +1 -1
  74. package/lib/index.d.ts +1 -1
  75. package/lib/index.d.ts.map +1 -1
  76. package/lib/index.js.map +1 -1
  77. package/lib/packageVersion.d.ts +1 -1
  78. package/lib/packageVersion.js +1 -1
  79. package/lib/packageVersion.js.map +1 -1
  80. package/lib/shared-tree/schematizingTreeView.d.ts +13 -3
  81. package/lib/shared-tree/schematizingTreeView.d.ts.map +1 -1
  82. package/lib/shared-tree/schematizingTreeView.js +49 -12
  83. package/lib/shared-tree/schematizingTreeView.js.map +1 -1
  84. package/lib/simple-tree/api/configuration.d.ts +4 -4
  85. package/lib/simple-tree/api/configuration.d.ts.map +1 -1
  86. package/lib/simple-tree/api/configuration.js.map +1 -1
  87. package/lib/simple-tree/api/discrepancies.d.ts +36 -3
  88. package/lib/simple-tree/api/discrepancies.d.ts.map +1 -1
  89. package/lib/simple-tree/api/discrepancies.js +40 -14
  90. package/lib/simple-tree/api/discrepancies.js.map +1 -1
  91. package/lib/simple-tree/api/index.d.ts +3 -2
  92. package/lib/simple-tree/api/index.d.ts.map +1 -1
  93. package/lib/simple-tree/api/index.js +1 -1
  94. package/lib/simple-tree/api/index.js.map +1 -1
  95. package/lib/simple-tree/api/schemaCompatibilityTester.d.ts +22 -3
  96. package/lib/simple-tree/api/schemaCompatibilityTester.d.ts.map +1 -1
  97. package/lib/simple-tree/api/schemaCompatibilityTester.js +136 -7
  98. package/lib/simple-tree/api/schemaCompatibilityTester.js.map +1 -1
  99. package/lib/simple-tree/api/tree.d.ts +172 -7
  100. package/lib/simple-tree/api/tree.d.ts.map +1 -1
  101. package/lib/simple-tree/api/tree.js.map +1 -1
  102. package/lib/simple-tree/core/toStored.d.ts +7 -0
  103. package/lib/simple-tree/core/toStored.d.ts.map +1 -1
  104. package/lib/simple-tree/core/toStored.js.map +1 -1
  105. package/lib/simple-tree/core/treeNodeValid.d.ts.map +1 -1
  106. package/lib/simple-tree/core/treeNodeValid.js +3 -3
  107. package/lib/simple-tree/core/treeNodeValid.js.map +1 -1
  108. package/lib/simple-tree/index.d.ts +1 -1
  109. package/lib/simple-tree/index.d.ts.map +1 -1
  110. package/lib/simple-tree/index.js +1 -1
  111. package/lib/simple-tree/index.js.map +1 -1
  112. package/package.json +22 -22
  113. package/src/entrypoints/alpha.ts +3 -0
  114. package/src/entrypoints/beta.ts +2 -0
  115. package/src/entrypoints/legacy.ts +2 -0
  116. package/src/feature-libraries/index.ts +1 -1
  117. package/src/feature-libraries/schemaChecker.ts +198 -82
  118. package/src/index.ts +3 -0
  119. package/src/packageVersion.ts +1 -1
  120. package/src/shared-tree/schematizingTreeView.ts +75 -21
  121. package/src/simple-tree/api/configuration.ts +4 -4
  122. package/src/simple-tree/api/discrepancies.ts +100 -5
  123. package/src/simple-tree/api/index.ts +7 -1
  124. package/src/simple-tree/api/schemaCompatibilityTester.ts +186 -11
  125. package/src/simple-tree/api/tree.ts +184 -8
  126. package/src/simple-tree/core/toStored.ts +8 -0
  127. package/src/simple-tree/core/treeNodeValid.ts +3 -1
  128. package/src/simple-tree/index.ts +4 -0
  129. package/docs/main/runtime-schema-upgrade.md +0 -208
@@ -3,14 +3,119 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import type { TreeStoredSchema } from "../../core/index.js";
6
+ import { unreachableCase } from "@fluidframework/core-utils/internal";
7
+
8
+ import {
9
+ MapNodeStoredSchema,
10
+ ObjectNodeStoredSchema,
11
+ type TreeStoredSchema,
12
+ ValueSchema,
13
+ } from "../../core/index.js";
7
14
  import type { SchemaUpgrade, StagedSchemaUpgradePolicy } from "../core/index.js";
15
+ import { NodeKind } from "../core/index.js";
8
16
  import { allowsRepoSuperset, defaultSchemaPolicy } from "../../feature-libraries/index.js";
9
- import { toUpgradeSchema } from "../toStoredSchema.js";
17
+ import { resolveStoredSchemaGenerationOptions, toUpgradeSchema } from "../toStoredSchema.js";
10
18
  import type { TreeSchema } from "../treeSchema.js";
11
19
 
12
- import { getDiscrepanciesInAllowedContent } from "./discrepancies.js";
13
- import type { SchemaCompatibilityStatus } from "./tree.js";
20
+ import {
21
+ getDiscrepanciesInAllowedContent,
22
+ type Discrepancy,
23
+ type UpgradeLocationCollector,
24
+ } from "./discrepancies.js";
25
+ import type { SchemaCompatibilityStatusBeta, SchemaDiscrepancy } from "./tree.js";
26
+
27
+ /**
28
+ * Describes the discrepancies that prevent a view schema from viewing a stored schema as a
29
+ * readonly array.
30
+ */
31
+ export function getSchemaIncompatibilityDetails(
32
+ viewSchema: TreeSchema,
33
+ stored: TreeStoredSchema,
34
+ ): readonly SchemaDiscrepancy[] | undefined {
35
+ const discrepancies = [...getDiscrepanciesInAllowedContent(viewSchema, stored)];
36
+ if (discrepancies.length === 0) {
37
+ return undefined;
38
+ }
39
+
40
+ return discrepancies.map(formatSchemaDiscrepancy);
41
+ }
42
+
43
+ function formatSchemaDiscrepancy(value: Discrepancy): SchemaDiscrepancy {
44
+ switch (value.mismatch) {
45
+ case "allowedTypes": {
46
+ return {
47
+ mismatch: value.mismatch,
48
+ location:
49
+ value.identifier === undefined
50
+ ? "root"
51
+ : {
52
+ nodeType: value.identifier,
53
+ fieldKey: value.fieldKey,
54
+ },
55
+ view: value.view.map(({ type }) => type.identifier).sort(),
56
+ ...(value.stagedView === undefined
57
+ ? {}
58
+ : {
59
+ stagedView: value.stagedView.map(({ type }) => type.identifier).sort(),
60
+ }),
61
+ stored: [...value.stored].sort(),
62
+ ...(value.viewIsStagedOptional === true ? { viewIsStagedOptional: true } : {}),
63
+ };
64
+ }
65
+ case "fieldKind": {
66
+ return {
67
+ mismatch: value.mismatch,
68
+ location:
69
+ value.identifier === undefined
70
+ ? "root"
71
+ : {
72
+ nodeType: value.identifier,
73
+ fieldKey: value.fieldKey,
74
+ },
75
+ view: value.view,
76
+ stored: value.stored,
77
+ ...(value.viewIsStagedOptional === true ? { viewIsStagedOptional: true } : {}),
78
+ };
79
+ }
80
+ case "valueSchema": {
81
+ return {
82
+ mismatch: value.mismatch,
83
+ nodeType: value.identifier,
84
+ view: value.view === undefined ? undefined : ValueSchema[value.view],
85
+ stored: value.stored === undefined ? undefined : ValueSchema[value.stored],
86
+ };
87
+ }
88
+ case "nodeKind": {
89
+ const storedNodeKind =
90
+ value.stored === ObjectNodeStoredSchema
91
+ ? "Object"
92
+ : value.stored === MapNodeStoredSchema
93
+ ? "Map"
94
+ : "Leaf";
95
+ return {
96
+ mismatch: value.mismatch,
97
+ nodeType: value.identifier,
98
+ view: NodeKind[value.view],
99
+ stored: storedNodeKind,
100
+ };
101
+ }
102
+ default: {
103
+ return unreachableCase(value);
104
+ }
105
+ }
106
+ }
107
+
108
+ /**
109
+ * The enablement status of a staged schema upgrade in a document's stored schema.
110
+ *
111
+ * @remarks
112
+ * - `"disabled"` — no locations guarded by the upgrade are enabled in stored schema.
113
+ * - `"partial"` — at least one location is enabled but not all of them.
114
+ * - `"enabled"` — all locations guarded by the upgrade are enabled in stored schema.
115
+ *
116
+ * @alpha
117
+ */
118
+ export type StagedUpgradeStatus = "disabled" | "partial" | "enabled";
14
119
 
15
120
  /**
16
121
  * Determines the compatibility of a stored document (based on its stored schema) with a viewer (based on its view schema).
@@ -24,27 +129,60 @@ import type { SchemaCompatibilityStatus } from "./tree.js";
24
129
  * @param viewSchema - Schema for the view
25
130
  * @param stored - The stored schema to check compatibility against
26
131
  * @param stagedSchemaUpgrades - Staged schema upgrades enabled for this view, or explicit stored-schema generation options
132
+ *
27
133
  */
28
134
  export function checkSchemaCompatibility(
29
135
  viewSchema: TreeSchema,
30
136
  stored: TreeStoredSchema,
31
137
  stagedSchemaUpgrades?: Iterable<SchemaUpgrade> | StagedSchemaUpgradePolicy,
32
- ): Omit<SchemaCompatibilityStatus, "canInitialize"> {
138
+ ): Omit<SchemaCompatibilityStatusBeta, "canInitialize"> & {
139
+ enabledUpgrades: ReadonlyMap<SchemaUpgrade, StagedUpgradeStatus>;
140
+ } {
33
141
  // The public API surface assumes defaultSchemaPolicy
34
142
  const policy = defaultSchemaPolicy;
143
+ const configuredPolicy = resolveStoredSchemaGenerationOptions(stagedSchemaUpgrades);
144
+ const includeAlreadyEnabledUpgrades =
145
+ configuredPolicy.includeAlreadyEnabledUpgrades ?? false;
146
+
147
+ // Collect upgrade locations during the discrepancy walk (single pass).
148
+ const totalLocations = new Map<SchemaUpgrade, number>();
149
+ const enabledLocations = new Map<SchemaUpgrade, number>();
150
+ const upgradeCollector: UpgradeLocationCollector = {
151
+ allowedType(upgrade, isEnabled) {
152
+ totalLocations.set(upgrade, (totalLocations.get(upgrade) ?? 0) + 1);
153
+ if (isEnabled) {
154
+ enabledLocations.set(upgrade, (enabledLocations.get(upgrade) ?? 0) + 1);
155
+ }
156
+ },
157
+ optionalField(upgrade, isEnabled) {
158
+ totalLocations.set(upgrade, (totalLocations.get(upgrade) ?? 0) + 1);
159
+ if (isEnabled) {
160
+ enabledLocations.set(upgrade, (enabledLocations.get(upgrade) ?? 0) + 1);
161
+ }
162
+ },
163
+ };
35
164
 
36
165
  // View schema allows a subset of documents that stored schema does, and the discrepancies are allowed by policy
37
166
  // determined by the view schema (i.e. objects with extra optional fields in the stored schema have opted into allowing this.
38
167
  // In the future, this would also include things like:
39
168
  // - fields with more allowed types in the stored schema than in the view schema have out-of-schema "unknown content" adapters
40
- let canView = true;
41
-
42
- for (const _discrepancy of getDiscrepanciesInAllowedContent(viewSchema, stored)) {
43
- canView = false;
44
- break;
169
+ // Complete the full walk even after finding an incompatibility so discrepancy details and
170
+ // staged-upgrade status contain all available information for debugging.
171
+ const discrepancies: SchemaDiscrepancy[] = [];
172
+ for (const discrepancy of getDiscrepanciesInAllowedContent(
173
+ viewSchema,
174
+ stored,
175
+ upgradeCollector,
176
+ )) {
177
+ discrepancies.push(formatSchemaDiscrepancy(discrepancy));
45
178
  }
179
+ const canView = discrepancies.length === 0;
46
180
 
47
- const wouldUpgradeTo = toUpgradeSchema(viewSchema.root, stagedSchemaUpgrades);
181
+ const enabledUpgrades = computeUpgradeStatuses(totalLocations, enabledLocations);
182
+ const upgradePolicy = includeAlreadyEnabledUpgrades
183
+ ? includeEnabledUpgrades(configuredPolicy, enabledUpgrades)
184
+ : configuredPolicy;
185
+ const wouldUpgradeTo = toUpgradeSchema(viewSchema.root, upgradePolicy);
48
186
 
49
187
  const canUpgrade = allowsRepoSuperset(policy, stored, wouldUpgradeTo);
50
188
 
@@ -57,5 +195,42 @@ export function checkSchemaCompatibility(
57
195
  canView,
58
196
  canUpgrade,
59
197
  isEquivalent,
198
+ discrepancies: canView ? undefined : discrepancies,
199
+ enabledUpgrades,
60
200
  };
61
201
  }
202
+
203
+ function includeEnabledUpgrades(
204
+ configuredPolicy: StagedSchemaUpgradePolicy,
205
+ enabledUpgrades: ReadonlyMap<SchemaUpgrade, StagedUpgradeStatus>,
206
+ ): StagedSchemaUpgradePolicy {
207
+ if (enabledUpgrades.size === 0) {
208
+ return configuredPolicy;
209
+ }
210
+ return {
211
+ includeStaged: (upgrade) =>
212
+ configuredPolicy.includeStaged(upgrade) || enabledUpgrades.has(upgrade),
213
+ includeStagedOptional: (upgrade) =>
214
+ configuredPolicy.includeStagedOptional(upgrade) || enabledUpgrades.has(upgrade),
215
+ };
216
+ }
217
+
218
+ /**
219
+ * Computes the {@link StagedUpgradeStatus} for each upgrade token from total and enabled location counts.
220
+ * Only tokens with at least one enabled location are included in the returned map.
221
+ */
222
+ function computeUpgradeStatuses(
223
+ totalLocations: ReadonlyMap<SchemaUpgrade, number>,
224
+ enabledLocations: ReadonlyMap<SchemaUpgrade, number>,
225
+ ): ReadonlyMap<SchemaUpgrade, StagedUpgradeStatus> {
226
+ const result = new Map<SchemaUpgrade, StagedUpgradeStatus>();
227
+ for (const [upgrade, total] of totalLocations) {
228
+ const enabled = enabledLocations.get(upgrade) ?? 0;
229
+ if (enabled > 0 && enabled < total) {
230
+ result.set(upgrade, "partial");
231
+ } else if (enabled >= total && enabled > 0) {
232
+ result.set(upgrade, "enabled");
233
+ }
234
+ }
235
+ return result;
236
+ }
@@ -26,7 +26,7 @@ import type {
26
26
  JsonCompatibleReadOnlyObject,
27
27
  } from "../../util/index.js";
28
28
  // eslint-disable-next-line @typescript-eslint/no-unused-vars -- This is referenced by doc comments.
29
- import type { Unhydrated } from "../core/index.js";
29
+ import type { SchemaUpgrade, Unhydrated } from "../core/index.js";
30
30
  import type {
31
31
  ImplicitFieldSchema,
32
32
  InsertableField,
@@ -39,6 +39,7 @@ import type { SimpleTreeSchema } from "../simpleSchema.js";
39
39
  import type { UnsafeUnknownSchema } from "../unsafeUnknownSchema.js";
40
40
 
41
41
  import type { TreeViewConfiguration } from "./configuration.js";
42
+ import type { StagedUpgradeStatus } from "./schemaCompatibilityTester.js";
42
43
  import type {
43
44
  RunTransactionParamsAlpha,
44
45
  RunTransactionParamsBeta,
@@ -601,9 +602,8 @@ export interface TreeView<in out TSchema extends ImplicitFieldSchema> extends ID
601
602
  *
602
603
  * When using {@link TreeViewConfigurationAlpha} with a {@link ITreeViewConfigurationAlpha.stagedUpgradePolicy},
603
604
  * staged schema upgrades matching the configured policy are included in the target stored schema.
604
- * Once a staged schema upgrade has been enabled in a document's stored schema, loading that document
605
- * with a view that does not include equivalent staged members in its construction-time policy will cause
606
- * `upgradeSchema` to throw a `UsageError` because the requested target would narrow the stored schema.
605
+ * Set {@link (StagedSchemaUpgradePolicy:interface).includeAlreadyEnabledUpgrades} to `true` to
606
+ * also include staged upgrades that are already enabled in the document.
607
607
  *
608
608
  * @example Enabling a staged allowed type for documents, selected by a feature flag
609
609
  *
@@ -675,6 +675,164 @@ export interface TreeView<in out TSchema extends ImplicitFieldSchema> extends ID
675
675
  readonly schema: TSchema;
676
676
  }
677
677
 
678
+ /**
679
+ * A discrepancy between a view schema and a document's stored schema.
680
+ *
681
+ * @remarks
682
+ * The `mismatch` property discriminates the different discrepancy shapes.
683
+ *
684
+ * @sealed @beta
685
+ */
686
+ export type SchemaDiscrepancy =
687
+ | {
688
+ /**
689
+ * Indicates that a field allows different node types in the view and stored schemas.
690
+ */
691
+ readonly mismatch: "allowedTypes";
692
+ /**
693
+ * The field with the discrepancy.
694
+ *
695
+ * `"root"` identifies the root field. Otherwise, `nodeType` identifies the containing
696
+ * node schema and `fieldKey` identifies its field. `fieldKey` is undefined for a map
697
+ * node's implicit field.
698
+ */
699
+ readonly location:
700
+ | "root"
701
+ | {
702
+ readonly nodeType: string;
703
+ readonly fieldKey: string | undefined;
704
+ };
705
+ /**
706
+ * Non-staged node type identifiers allowed by the view schema but not the stored schema.
707
+ */
708
+ readonly view: readonly string[];
709
+ /**
710
+ * Staged node type identifiers allowed by the view schema but not the stored schema.
711
+ *
712
+ * @remarks These types provide rollout context but do not cause the discrepancy.
713
+ */
714
+ readonly stagedView?: readonly string[];
715
+ /**
716
+ * Node type identifiers allowed by the stored schema but not the view schema.
717
+ */
718
+ readonly stored: readonly string[];
719
+ /**
720
+ * Whether the view field is a staged optional field.
721
+ *
722
+ * @remarks Omitted when false.
723
+ */
724
+ readonly viewIsStagedOptional?: true;
725
+ }
726
+ | {
727
+ /**
728
+ * Indicates that a field has different field kinds in the view and stored schemas.
729
+ */
730
+ readonly mismatch: "fieldKind";
731
+ /**
732
+ * The field with the discrepancy.
733
+ *
734
+ * `"root"` identifies the root field. Otherwise, `nodeType` identifies the containing
735
+ * node schema and `fieldKey` identifies its field. `fieldKey` is undefined for a map
736
+ * node's implicit field.
737
+ */
738
+ readonly location:
739
+ | "root"
740
+ | {
741
+ readonly nodeType: string;
742
+ readonly fieldKey: string | undefined;
743
+ };
744
+ /**
745
+ * The field kind required by the view schema.
746
+ */
747
+ readonly view: string;
748
+ /**
749
+ * The field kind recorded in the stored schema.
750
+ */
751
+ readonly stored: string;
752
+ /**
753
+ * Whether the view field is a staged optional field.
754
+ *
755
+ * @remarks Omitted when false.
756
+ */
757
+ readonly viewIsStagedOptional?: true;
758
+ }
759
+ | {
760
+ /**
761
+ * Indicates that a leaf node accepts different value types in the view and stored schemas.
762
+ */
763
+ readonly mismatch: "valueSchema";
764
+ /**
765
+ * The identifier of the leaf node schema with the discrepancy.
766
+ */
767
+ readonly nodeType: string;
768
+ /**
769
+ * The value schema required by the view, or undefined when it does not constrain values.
770
+ */
771
+ readonly view: string | undefined;
772
+ /**
773
+ * The value schema recorded in the stored schema, or undefined when it does not constrain values.
774
+ */
775
+ readonly stored: string | undefined;
776
+ }
777
+ | {
778
+ /**
779
+ * Indicates that a node is represented by different node kinds in the view and stored schemas.
780
+ */
781
+ readonly mismatch: "nodeKind";
782
+ /**
783
+ * The identifier of the node schema with the discrepancy.
784
+ */
785
+ readonly nodeType: string;
786
+ /**
787
+ * The node kind required by the view schema.
788
+ */
789
+ readonly view: string;
790
+ /**
791
+ * The node kind recorded in the stored schema.
792
+ */
793
+ readonly stored: string;
794
+ };
795
+
796
+ /**
797
+ * {@link SchemaCompatibilityStatus} with additional beta APIs.
798
+ *
799
+ * @sealed @beta
800
+ */
801
+ export interface SchemaCompatibilityStatusBeta extends SchemaCompatibilityStatus {
802
+ /**
803
+ * Details about the schema discrepancies that prevent this view from accessing the tree.
804
+ *
805
+ * @remarks
806
+ * This property is undefined when {@link SchemaCompatibilityStatus.canView} is true and present
807
+ * when `canView` is false.
808
+ * It can include application-defined schema identifiers and field keys.
809
+ *
810
+ * @example Interpreting an allowed-types discrepancy
811
+ *
812
+ * If a document's stored schema allows `string` for `Todo.title`, but the view schema expects
813
+ * `number`, the discrepancy identifies the field and the type permitted by each schema:
814
+ *
815
+ * ```typescript
816
+ * const sf = new SchemaFactory("com.example");
817
+ * class Todo extends sf.object("Todo", {
818
+ * title: sf.number,
819
+ * }) {}
820
+ *
821
+ * const view = asBeta(tree.viewWith(new TreeViewConfiguration({ schema: Todo })));
822
+ * if (!view.compatibility.canView) {
823
+ * // [{
824
+ * // mismatch: "allowedTypes",
825
+ * // location: { nodeType: "com.example.Todo", fieldKey: "title" },
826
+ * // view: ["com.fluidframework.leaf.number"],
827
+ * // stored: ["com.fluidframework.leaf.string"],
828
+ * // }]
829
+ * console.error(view.compatibility.discrepancies);
830
+ * }
831
+ * ```
832
+ */
833
+ readonly discrepancies: readonly SchemaDiscrepancy[] | undefined;
834
+ }
835
+
678
836
  /**
679
837
  * {@link TreeView} with additional beta APIs.
680
838
  * @sealed @beta
@@ -682,6 +840,11 @@ export interface TreeView<in out TSchema extends ImplicitFieldSchema> extends ID
682
840
  export interface TreeViewBeta<in out TSchema extends ImplicitFieldSchema>
683
841
  extends TreeView<TSchema>,
684
842
  UntypedTreeView {
843
+ /**
844
+ * {@inheritDoc TreeView.compatibility}
845
+ */
846
+ readonly compatibility: SchemaCompatibilityStatusBeta;
847
+
685
848
  // Override the base branch method to return a typed view rather than merely a branch.
686
849
  fork(): ReturnType<UntypedTreeView["fork"]> & TreeViewBeta<TSchema>;
687
850
 
@@ -773,16 +936,29 @@ export interface TreeViewAlpha<
773
936
  * Only valid to call when this view's {@link SchemaCompatibilityStatus.canInitialize} is true.
774
937
  *
775
938
  * Enables staged schema upgrades declared by {@link ITreeViewConfigurationAlpha.stagedUpgradePolicy} when generating the initial stored schema.
776
- * Once a staged schema upgrade has been enabled in a document's stored schema, loading that document
777
- * with a view that does not include equivalent staged members in its construction-time policy will cause
778
- * a subsequent `upgradeSchema` call to throw a `UsageError` because the stored schema already contains
779
- * the upgraded members and the new target would narrow it.
780
939
  *
781
940
  * Applications should typically call this function before attaching a `SharedTree`.
782
941
  * @param content - The content to initialize the tree with.
783
942
  */
784
943
  initialize(content: InsertableField<TSchema>): void;
785
944
 
945
+ /**
946
+ * Checks whether a staged schema upgrade has been applied to the document's stored schema.
947
+ *
948
+ * @param upgrade - The upgrade token to check.
949
+ *
950
+ * @returns The {@link StagedUpgradeStatus} of the upgrade.
951
+ *
952
+ * @remarks
953
+ * Use this to determine whether a document has already been upgraded, for example when deciding
954
+ * whether to include an upgrade token in the view configuration after a feature flag rollback.
955
+ *
956
+ * Results are derived from this view's schema and the current stored schema.
957
+ * The full schema is checked even when the view is incompatible with the stored schema, so the
958
+ * result includes all locations declared by the view schema.
959
+ */
960
+ isStagedUpgradeEnabled(upgrade: SchemaUpgrade): StagedUpgradeStatus;
961
+
786
962
  readonly events: Listenable<TreeViewEvents & TreeBranchEvents>;
787
963
 
788
964
  // Override the base fork method to return a TreeViewAlpha.
@@ -11,6 +11,14 @@ import type { SchemaUpgrade } from "./allowedTypes.js";
11
11
  * @alpha
12
12
  */
13
13
  export interface StagedSchemaUpgradePolicy {
14
+ /**
15
+ * Whether schema upgrades should include staged upgrades that are already enabled in the
16
+ * document's stored schema.
17
+ *
18
+ * @defaultValue `false`
19
+ */
20
+ readonly includeAlreadyEnabledUpgrades?: boolean;
21
+
14
22
  /**
15
23
  * Determines whether to include staged allowed types in the resulting stored schema.
16
24
  * @remarks
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { assert, fail } from "@fluidframework/core-utils/internal";
7
- import { UsageError } from "@fluidframework/telemetry-utils/internal";
7
+ import { tagSchemaArtifacts, UsageError } from "@fluidframework/telemetry-utils/internal";
8
8
 
9
9
  import { type FlexTreeNode, isFlexTreeNode } from "../../feature-libraries/index.js";
10
10
 
@@ -135,6 +135,7 @@ export abstract class TreeNodeValid<TInput> extends TreeNode {
135
135
  }) which derived from the same SchemaFactory generated class (${JSON.stringify(
136
136
  this.identifier,
137
137
  )}). This is invalid.`,
138
+ tagSchemaArtifacts({ schemaIdentifier: this.identifier }),
138
139
  );
139
140
  }
140
141
 
@@ -237,6 +238,7 @@ export function schemaAsTreeNodeValid(
237
238
  `Schema for ${JSON.stringify(
238
239
  schema.identifier,
239
240
  )} does not extend a SchemaFactory generated class. This is invalid.`,
241
+ tagSchemaArtifacts({ schemaIdentifier: schema.identifier }),
240
242
  );
241
243
  }
242
244
 
@@ -77,6 +77,8 @@ export {
77
77
  type ITreeViewConfiguration,
78
78
  type ITreeViewConfigurationAlpha,
79
79
  type SchemaCompatibilityStatus,
80
+ type SchemaCompatibilityStatusBeta,
81
+ type SchemaDiscrepancy,
80
82
  type ITreeConfigurationOptions,
81
83
  SchemaFactory,
82
84
  scoped,
@@ -137,6 +139,7 @@ export {
137
139
  type TreeSchemaEncodingOptions,
138
140
  getJsonSchema,
139
141
  getSimpleSchema,
142
+ type StagedUpgradeStatus,
140
143
  type VerboseTreeNode,
141
144
  type TreeEncodingOptions,
142
145
  type VerboseTree,
@@ -144,6 +147,7 @@ export {
144
147
  comparePersistedSchema,
145
148
  type ConciseTree,
146
149
  checkSchemaCompatibility,
150
+ getSchemaIncompatibilityDetails,
147
151
  type Unenforced,
148
152
  type System_Unsafe,
149
153
  type ArrayNodeCustomizableSchemaUnsafe,