@fluidframework/tree 2.117.0 → 2.118.0

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 +173 -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 +173 -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 +185 -11
  125. package/src/simple-tree/api/tree.ts +185 -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
@@ -10,7 +10,7 @@ import type {
10
10
  Listenable,
11
11
  } from "@fluidframework/core-interfaces/internal";
12
12
  import { assert } from "@fluidframework/core-utils/internal";
13
- import { UsageError } from "@fluidframework/telemetry-utils/internal";
13
+ import { tagSchemaArtifacts, UsageError } from "@fluidframework/telemetry-utils/internal";
14
14
 
15
15
  import { anchorSlot, rootFieldKey, type RevertToOptionsAlpha } from "../core/index.js";
16
16
  import {
@@ -27,7 +27,7 @@ import {
27
27
  } from "../feature-libraries/index.js";
28
28
  import {
29
29
  type ImplicitFieldSchema,
30
- type SchemaCompatibilityStatus,
30
+ type SchemaCompatibilityStatusBeta,
31
31
  type TreeView,
32
32
  type TreeViewEvents,
33
33
  tryGetTreeNodeForField,
@@ -61,6 +61,8 @@ import {
61
61
  type TreeBranchHistory,
62
62
  type UntypedTreeViewAlpha,
63
63
  type TreeSchema,
64
+ type SchemaUpgrade,
65
+ type StagedUpgradeStatus,
64
66
  } from "../simple-tree/index.js";
65
67
  import {
66
68
  type Breakable,
@@ -79,6 +81,27 @@ import type { TreeCheckout } from "./treeCheckout.js";
79
81
  */
80
82
  export const ViewSlot = anchorSlot<TreeView<ImplicitFieldSchema>>();
81
83
 
84
+ function throwIfSchemaIsIncompatible(compatibility: SchemaCompatibilityStatusBeta): void {
85
+ if (compatibility.canView) {
86
+ return;
87
+ }
88
+
89
+ const resolution = compatibility.canInitialize
90
+ ? "The document is uninitialized; call TreeView.initialize() before reading or writing TreeView.root."
91
+ : compatibility.canUpgrade
92
+ ? "The stored schema can be upgraded; call TreeView.upgradeSchema() before reading or writing TreeView.root."
93
+ : "The schemas cannot be upgraded automatically. Use a compatible view schema or explicitly migrate the document schema and data.";
94
+ throw new UsageError(
95
+ `TreeView.root is unavailable because the view schema is incompatible with the stored schema. ${resolution}`,
96
+ tagSchemaArtifacts({
97
+ schemaIncompatibilityDetails:
98
+ compatibility.discrepancies === undefined
99
+ ? undefined
100
+ : JSON.stringify(compatibility.discrepancies),
101
+ }),
102
+ );
103
+ }
104
+
82
105
  /**
83
106
  * Implementation of TreeView wrapping a FlexTreeView.
84
107
  */
@@ -95,9 +118,14 @@ export class SchematizingSimpleTreeView<
95
118
  private flexTreeContext: Context | undefined;
96
119
 
97
120
  /**
98
- * Undefined iff uninitialized or disposed.
121
+ * Undefined if and only if uninitialized or disposed.
99
122
  */
100
- private currentCompatibility: SchemaCompatibilityStatus | undefined;
123
+ private currentCompatibility: SchemaCompatibilityStatusBeta | undefined;
124
+ /**
125
+ * Cached map of upgrade statuses, computed alongside compatibility.
126
+ * @remarks Undefined if and only if uninitialized or disposed.
127
+ */
128
+ private currentEnabledUpgrades: ReadonlyMap<SchemaUpgrade, StagedUpgradeStatus> | undefined;
101
129
  public readonly events: Listenable<TreeViewEvents & TreeBranchEvents> &
102
130
  IEmitter<TreeViewEvents & TreeBranchEvents> &
103
131
  HasListeners<TreeViewEvents & TreeBranchEvents> = createEmitter();
@@ -155,9 +183,7 @@ export class SchematizingSimpleTreeView<
155
183
  const stagedUpgradePolicy =
156
184
  config instanceof TreeViewConfigurationAlpha ? config.stagedUpgradePolicy : undefined;
157
185
  const configAlpha = new TreeViewConfigurationAlpha({
158
- schema: config.schema,
159
- enableSchemaValidation: config.enableSchemaValidation,
160
- preventAmbiguity: config.preventAmbiguity,
186
+ ...config,
161
187
  stagedUpgradePolicy,
162
188
  });
163
189
  this.stagedUpgradePolicy = configAlpha.stagedUpgradePolicy;
@@ -170,7 +196,9 @@ export class SchematizingSimpleTreeView<
170
196
  canUpgrade: true,
171
197
  isEquivalent: false,
172
198
  canInitialize: true,
199
+ discrepancies: undefined,
173
200
  };
201
+ this.currentEnabledUpgrades = new Map();
174
202
  this.update();
175
203
 
176
204
  this.unregisterCallbacks.add(
@@ -270,7 +298,7 @@ export class SchematizingSimpleTreeView<
270
298
  public upgradeSchema(): void {
271
299
  this.ensureUndisposed();
272
300
 
273
- const newSchema = toUpgradeSchema(this.viewSchema.root, this.stagedUpgradePolicy);
301
+ const newSchema = toUpgradeSchema(this.viewSchema.root, this.effectiveUpgradePolicy);
274
302
  const storedSchema = this.checkout.storedSchema.clone();
275
303
  if (!allowsRepoSuperset(defaultSchemaPolicy, storedSchema, newSchema)) {
276
304
  throw new UsageError(
@@ -285,6 +313,35 @@ export class SchematizingSimpleTreeView<
285
313
  this.runSchemaEdit(() => this.checkout.updateSchema(newSchema));
286
314
  }
287
315
 
316
+ public isStagedUpgradeEnabled(upgrade: SchemaUpgrade): StagedUpgradeStatus {
317
+ if (!this.currentEnabledUpgrades) {
318
+ this.failDisposed();
319
+ }
320
+ return this.currentEnabledUpgrades.get(upgrade) ?? "disabled";
321
+ }
322
+
323
+ private get effectiveUpgradePolicy(): StagedSchemaUpgradePolicy {
324
+ const configuredPolicy = this.stagedUpgradePolicy;
325
+ if (configuredPolicy.includeAlreadyEnabledUpgrades === false) {
326
+ return configuredPolicy;
327
+ }
328
+ const enabledUpgrades = this.currentEnabledUpgrades;
329
+ assert(
330
+ enabledUpgrades !== undefined,
331
+ 0xd3e /* Enabled upgrades must be available for an active view */,
332
+ );
333
+ if (enabledUpgrades.size === 0) {
334
+ return configuredPolicy;
335
+ }
336
+
337
+ return {
338
+ includeStaged: (upgrade) =>
339
+ configuredPolicy.includeStaged(upgrade) || enabledUpgrades.has(upgrade),
340
+ includeStagedOptional: (upgrade) =>
341
+ configuredPolicy.includeStagedOptional(upgrade) || enabledUpgrades.has(upgrade),
342
+ };
343
+ }
344
+
288
345
  /**
289
346
  * Gets the flex-tree context. Throws when disposed or out of schema.
290
347
  */
@@ -442,12 +499,16 @@ export class SchematizingSimpleTreeView<
442
499
  }
443
500
  }
444
501
 
445
- private computeCompatibility(): SchemaCompatibilityStatus {
446
- const compatibility = checkSchemaCompatibility(
502
+ /**
503
+ * Computes the current schema compatibility status and updates the cached enabled upgrades.
504
+ */
505
+ private computeCompatibility(): SchemaCompatibilityStatusBeta {
506
+ const { enabledUpgrades, ...compatibility } = checkSchemaCompatibility(
447
507
  this.viewSchema,
448
508
  this.checkout.storedSchema,
449
509
  this.stagedUpgradePolicy,
450
510
  );
511
+ this.currentEnabledUpgrades = enabledUpgrades;
451
512
  return {
452
513
  ...compatibility,
453
514
  canInitialize: canInitialize(this.checkout),
@@ -486,7 +547,7 @@ export class SchematizingSimpleTreeView<
486
547
  anchors.slots.delete(SimpleContextSlot);
487
548
  }
488
549
 
489
- public get compatibility(): SchemaCompatibilityStatus {
550
+ public get compatibility(): SchemaCompatibilityStatusBeta {
490
551
  if (!this.currentCompatibility) {
491
552
  this.failDisposed();
492
553
  }
@@ -501,6 +562,7 @@ export class SchematizingSimpleTreeView<
501
562
  }
502
563
  this.checkout.forest.anchors.slots.delete(ViewSlot);
503
564
  this.currentCompatibility = undefined;
565
+ this.currentEnabledUpgrades = undefined;
504
566
  this.onDispose?.();
505
567
  if (!this.checkout.isSharedBranch && !this.checkout.disposed) {
506
568
  // All non-shared branches are 1:1 with views, so if a user manually disposes a view, we should also dispose the checkout/branch.
@@ -510,11 +572,7 @@ export class SchematizingSimpleTreeView<
510
572
 
511
573
  private get flexRoot(): FlexTreeOptionalField | FlexTreeRequiredField {
512
574
  this.breaker.use();
513
- if (!this.compatibility.canView) {
514
- throw new UsageError(
515
- "Document is out of schema. Check TreeView.compatibility before accessing TreeView.root.",
516
- );
517
- }
575
+ throwIfSchemaIsIncompatible(this.compatibility);
518
576
  const view = this.getFlexTreeContext();
519
577
  assert(
520
578
  view.root.is(FieldKinds.optional) ||
@@ -531,11 +589,7 @@ export class SchematizingSimpleTreeView<
531
589
 
532
590
  public set root(newRoot: InsertableField<TRootSchema>) {
533
591
  this.breaker.use();
534
- if (!this.compatibility.canView) {
535
- throw new UsageError(
536
- "Document is out of schema. Check TreeView.compatibility before accessing TreeView.root.",
537
- );
538
- }
592
+ throwIfSchemaIsIncompatible(this.compatibility);
539
593
  const view = this.getFlexTreeContext();
540
594
  setField(
541
595
  view.root,
@@ -158,22 +158,22 @@ export interface ITreeViewConfiguration<
158
158
 
159
159
  /**
160
160
  * Property-bag configuration for {@link TreeViewConfigurationAlpha} construction.
161
+ * @input
161
162
  * @alpha
162
163
  */
163
164
  export interface ITreeViewConfigurationAlpha<
164
165
  TSchema extends ImplicitFieldSchema = ImplicitFieldSchema,
165
166
  > extends ITreeViewConfiguration<TSchema> {
166
167
  /**
167
- * Policy for generating stored schema from the view schema.
168
+ * Policy for generating stored schema from the view schema during staged schema upgrades.
168
169
  *
169
170
  * @remarks
170
- * If omitted or `undefined`, defaults to {@link StagedSchemaUpgradePolicyFactory.restrictive}
171
- * which does not enable any staged schema upgrades.
172
- *
173
171
  * If provided, this policy is used when generating stored schema to include in documents via
174
172
  * `initialize` / `upgradeSchema` as well as in {@link snapshotSchemaCompatibility} to validate
175
173
  * the compatibility of such documents.
176
174
  *
175
+ * @defaultValue {@link StagedSchemaUpgradePolicyFactory.restrictive}
176
+ *
177
177
  * @example Enabling specific staged upgrades
178
178
  * ```typescript
179
179
  * const config = new TreeViewConfigurationAlpha({
@@ -27,6 +27,7 @@ import {
27
27
  NodeKind,
28
28
  normalizeAndEvaluateAnnotatedAllowedTypes,
29
29
  type AnnotatedAllowedType,
30
+ type SchemaUpgrade,
30
31
  type TreeNodeSchema,
31
32
  } from "../core/index.js";
32
33
  import {
@@ -48,6 +49,25 @@ import type { TreeSchema } from "../treeSchema.js";
48
49
 
49
50
  import { tryStoredSchemaAsArray } from "./customTree.js";
50
51
 
52
+ /**
53
+ * Collects upgrade location data during a schema walk.
54
+ * @remarks Called for each staged type or staged optional field encountered.
55
+ */
56
+ export interface UpgradeLocationCollector {
57
+ /**
58
+ * Record a staged allowed type location.
59
+ * @param upgrade - The upgrade token guarding this type.
60
+ * @param isEnabled - Whether this type is present in stored schema.
61
+ */
62
+ allowedType(upgrade: SchemaUpgrade, isEnabled: boolean): void;
63
+ /**
64
+ * Record a staged optional field location.
65
+ * @param upgrade - The upgrade token guarding this field.
66
+ * @param isEnabled - Whether the stored field is already optional.
67
+ */
68
+ optionalField(upgrade: SchemaUpgrade, isEnabled: boolean): void;
69
+ }
70
+
51
71
  /**
52
72
  * Discriminated union (keyed on `mismatch`) of discrepancies between a view and stored schema which
53
73
  * make it possible for content matching the stored schema to be incompatible with the view schema.
@@ -79,6 +99,11 @@ export interface FieldDiscrepancyLocation {
79
99
  * - the discrepancy is for 'all fields' of a map node
80
100
  */
81
101
  readonly fieldKey: FieldKey | undefined;
102
+ /**
103
+ * Whether the view field is a staged optional field.
104
+ * Omitted when false.
105
+ */
106
+ readonly viewIsStagedOptional?: true;
82
107
  }
83
108
 
84
109
  /**
@@ -94,6 +119,13 @@ export interface AllowedTypeDiscrepancy extends FieldDiscrepancyLocation {
94
119
  * (excluding {@link SchemaStaticsBeta.staged | staged} schema) which are not allowed in stored schema.
95
120
  */
96
121
  readonly view: readonly AnnotatedAllowedType<TreeNodeSchema>[];
122
+ /**
123
+ * Staged allowed types in the view schema which are not allowed in stored schema.
124
+ *
125
+ * @remarks
126
+ * These types do not cause the discrepancy and are omitted when there are none.
127
+ */
128
+ readonly stagedView?: readonly AnnotatedAllowedType<TreeNodeSchema>[];
97
129
  /**
98
130
  * Allowed type identifiers in stored schema which are not allowed in view schema
99
131
  * (including the view schema's {@link SchemaStaticsBeta.staged | staged} schema).
@@ -174,15 +206,25 @@ function doesNodeKindMatchStoredNodeKind(
174
206
 
175
207
  /**
176
208
  * Finds and reports discrepancies between a view schema and a stored schema which make "canView" false.
209
+ *
210
+ * Optionally collects upgrade location information for staged schema upgrades during the same walk.
211
+ *
177
212
  * @remarks
178
213
  * See documentation on {@link Discrepancy} and its subtypes for details of possible discrepancies.
179
214
  */
180
215
  export function* getDiscrepanciesInAllowedContent(
181
216
  view: TreeSchema,
182
217
  stored: TreeStoredSchema,
218
+ upgradeCollector?: UpgradeLocationCollector,
183
219
  ): Iterable<Discrepancy> {
184
220
  // check root field discrepancies
185
- yield* getFieldDiscrepancies(view.root, stored.rootFieldSchema, undefined, undefined);
221
+ yield* getFieldDiscrepancies(
222
+ view.root,
223
+ stored.rootFieldSchema,
224
+ undefined,
225
+ undefined,
226
+ upgradeCollector,
227
+ );
186
228
 
187
229
  // Check all of the stored nodes, including their fields for discrepancies.
188
230
  for (const [identifier, storedSchema] of stored.nodeSchema) {
@@ -190,7 +232,7 @@ export function* getDiscrepanciesInAllowedContent(
190
232
 
191
233
  // if the view schema has a node that's also in the stored schema, check it.
192
234
  if (viewSchema !== undefined) {
193
- yield* getNodeDiscrepancies(identifier, viewSchema, storedSchema);
235
+ yield* getNodeDiscrepancies(identifier, viewSchema, storedSchema, upgradeCollector);
194
236
  }
195
237
  // Note that nodes that are missing in the view schema are only a problem if other stored schema nodes actually reference them which will produce its own discrepancy, so we can rely on that to produce any needed discrepancies.
196
238
  }
@@ -200,6 +242,7 @@ function* getNodeDiscrepancies(
200
242
  identifier: TreeNodeSchemaIdentifier,
201
243
  view: TreeNodeSchema,
202
244
  stored: TreeNodeStoredSchema,
245
+ upgradeCollector?: UpgradeLocationCollector,
203
246
  ): Iterable<Discrepancy> {
204
247
  if (!doesNodeKindMatchStoredNodeKind(view.kind, getStoredNodeSchemaType(stored))) {
205
248
  yield {
@@ -221,6 +264,7 @@ function* getNodeDiscrepancies(
221
264
  identifier,
222
265
  view,
223
266
  stored as ObjectNodeStoredSchema,
267
+ upgradeCollector,
224
268
  );
225
269
  break;
226
270
  }
@@ -246,6 +290,8 @@ function* getNodeDiscrepancies(
246
290
  arrayStoredSchema,
247
291
  brand(view.identifier),
248
292
  EmptyKey,
293
+ false,
294
+ upgradeCollector,
249
295
  );
250
296
 
251
297
  break;
@@ -261,6 +307,7 @@ function* getNodeDiscrepancies(
261
307
  (stored as MapNodeStoredSchema).mapFields,
262
308
  identifier,
263
309
  undefined,
310
+ upgradeCollector,
264
311
  );
265
312
  break;
266
313
  }
@@ -275,6 +322,7 @@ function* getNodeDiscrepancies(
275
322
  (stored as MapNodeStoredSchema).mapFields,
276
323
  identifier,
277
324
  undefined,
325
+ upgradeCollector,
278
326
  );
279
327
  break;
280
328
  }
@@ -308,7 +356,7 @@ function* getNodeDiscrepancies(
308
356
  export function findExtraAllowedTypes(
309
357
  viewAllowedTypes: readonly AnnotatedAllowedType<TreeNodeSchema>[],
310
358
  storedAllowedTypes: TreeTypeSet,
311
- ): Pick<AllowedTypeDiscrepancy, "view" | "stored"> {
359
+ ): Pick<AllowedTypeDiscrepancy, "view" | "stagedView" | "stored"> {
312
360
  const viewNodeSchemaIdentifiers = new Set(
313
361
  viewAllowedTypes.map((value) => value.type.identifier),
314
362
  );
@@ -322,10 +370,19 @@ export function findExtraAllowedTypes(
322
370
  !storedAllowedTypes.has(brand(value.type.identifier)) &&
323
371
  value.metadata.stagedSchemaUpgrade === undefined,
324
372
  );
373
+ const stagedView = viewAllowedTypes.filter(
374
+ (value) =>
375
+ !storedAllowedTypes.has(brand(value.type.identifier)) &&
376
+ value.metadata.stagedSchemaUpgrade !== undefined,
377
+ );
325
378
  const stored = [...storedAllowedTypes].filter(
326
379
  (value) => !viewNodeSchemaIdentifiers.has(value),
327
380
  );
328
- return { view, stored };
381
+ return {
382
+ view,
383
+ ...(stagedView.length === 0 ? {} : { stagedView }),
384
+ stored,
385
+ };
329
386
  }
330
387
 
331
388
  /**
@@ -346,16 +403,28 @@ function* getFieldDiscrepancies(
346
403
  stored: TreeFieldStoredSchema,
347
404
  identifier: TreeNodeSchemaIdentifier | undefined,
348
405
  fieldKey: FieldKey | undefined,
406
+ upgradeCollector?: UpgradeLocationCollector,
349
407
  ): Iterable<FieldDiscrepancy> {
350
408
  assert(
351
409
  view instanceof FieldSchemaAlpha,
352
410
  0xbee /* all field schema should be FieldSchemaAlpha */,
353
411
  );
412
+
413
+ // Collect staged-optional upgrade location if present.
414
+ if (upgradeCollector !== undefined && view.isStagedOptional !== false) {
415
+ upgradeCollector.optionalField(
416
+ view.isStagedOptional,
417
+ stored.kind === FieldKinds.optional.identifier,
418
+ );
419
+ }
420
+
354
421
  yield* getAllowedTypeDiscrepancies(
355
422
  view.allowedTypesFull.evaluate().types,
356
423
  stored.types,
357
424
  identifier,
358
425
  fieldKey,
426
+ view.isStagedOptional !== false,
427
+ upgradeCollector,
359
428
  );
360
429
 
361
430
  const viewKind =
@@ -375,6 +444,7 @@ function* getFieldDiscrepancies(
375
444
  mismatch: "fieldKind",
376
445
  view: viewKind.identifier,
377
446
  stored: stored.kind,
447
+ ...(view.isStagedOptional === false ? {} : { viewIsStagedOptional: true }),
378
448
  } satisfies FieldKindDiscrepancy;
379
449
  }
380
450
  }
@@ -396,7 +466,21 @@ function* getAllowedTypeDiscrepancies(
396
466
  stored: TreeTypeSet,
397
467
  identifier: TreeNodeSchemaIdentifier | undefined,
398
468
  fieldKey: FieldKey | undefined,
469
+ viewIsStagedOptional: boolean,
470
+ upgradeCollector?: UpgradeLocationCollector,
399
471
  ): Iterable<FieldDiscrepancy> {
472
+ // Collect staged allowed type upgrade locations if present.
473
+ if (upgradeCollector !== undefined) {
474
+ for (const allowedType of view) {
475
+ if (allowedType.metadata.stagedSchemaUpgrade !== undefined) {
476
+ upgradeCollector.allowedType(
477
+ allowedType.metadata.stagedSchemaUpgrade,
478
+ stored.has(brand(allowedType.type.identifier)),
479
+ );
480
+ }
481
+ }
482
+ }
483
+
400
484
  const discrepancies = findExtraAllowedTypes(view, stored);
401
485
  if (discrepancies.view.length > 0 || discrepancies.stored.length > 0) {
402
486
  yield {
@@ -404,6 +488,7 @@ function* getAllowedTypeDiscrepancies(
404
488
  identifier,
405
489
  fieldKey,
406
490
  mismatch: "allowedTypes",
491
+ ...(viewIsStagedOptional ? { viewIsStagedOptional: true } : {}),
407
492
  } satisfies AllowedTypeDiscrepancy;
408
493
  }
409
494
  }
@@ -424,6 +509,7 @@ function* computeObjectNodeDiscrepancies(
424
509
  identifier: TreeNodeSchemaIdentifier,
425
510
  view: ObjectNodeSchemaPrivate,
426
511
  stored: ObjectNodeStoredSchema,
512
+ upgradeCollector?: UpgradeLocationCollector,
427
513
  ): Iterable<FieldDiscrepancy> {
428
514
  /**
429
515
  * Similar to the logic used for tracking discrepancies between two node schemas, we will identify
@@ -453,9 +539,18 @@ function* computeObjectNodeDiscrepancies(
453
539
  mismatch: "fieldKind",
454
540
  view: viewKind.identifier,
455
541
  stored: storedEmptyFieldSchema.kind,
542
+ ...(fieldSchema instanceof FieldSchemaAlpha && fieldSchema.isStagedOptional !== false
543
+ ? { viewIsStagedOptional: true }
544
+ : {}),
456
545
  } satisfies FieldKindDiscrepancy;
457
546
  } else {
458
- yield* getFieldDiscrepancies(fieldSchema, storedSchema, identifier, fieldKey);
547
+ yield* getFieldDiscrepancies(
548
+ fieldSchema,
549
+ storedSchema,
550
+ identifier,
551
+ fieldKey,
552
+ upgradeCollector,
553
+ );
459
554
  }
460
555
  }
461
556
 
@@ -19,6 +19,8 @@ export type {
19
19
  ViewableTree,
20
20
  TreeViewEvents,
21
21
  SchemaCompatibilityStatus,
22
+ SchemaCompatibilityStatusBeta,
23
+ SchemaDiscrepancy,
22
24
  TreeViewAlpha,
23
25
  TreeViewBeta,
24
26
  UntypedTreeView,
@@ -105,7 +107,11 @@ export {
105
107
  export type { TreeSchemaEncodingOptions } from "./getJsonSchema.js";
106
108
  export { getJsonSchema } from "./getJsonSchema.js";
107
109
  export { getSimpleSchema } from "./getSimpleSchema.js";
108
- export { checkSchemaCompatibility } from "./schemaCompatibilityTester.js";
110
+ export {
111
+ checkSchemaCompatibility,
112
+ getSchemaIncompatibilityDetails,
113
+ } from "./schemaCompatibilityTester.js";
114
+ export { type StagedUpgradeStatus } from "./schemaCompatibilityTester.js";
109
115
  export type {
110
116
  Unenforced,
111
117
  FieldSchemaAlphaUnsafe,